hyperv: Add support for setting MAC from within guests
authorHaiyang Zhang <haiyangz@microsoft.com>
Tue, 10 Jul 2012 07:19:22 +0000 (07:19 +0000)
committerDavid S. Miller <davem@davemloft.net>
Tue, 17 Jul 2012 05:54:24 +0000 (22:54 -0700)
This adds support for setting synthetic NIC MAC address from within Linux
guests. Before using this feature, the option "spoofing of MAC address"
should be enabled at the Hyper-V manager / Settings of the synthetic
NIC.

Thanks to Kin Cho <kcho@infoblox.com> for the initial implementation and
tests. And, thanks to Long Li <longli@microsoft.com> for the debugging
works.

Reported-and-tested-by: Kin Cho <kcho@infoblox.com>
Reported-by: Long Li <longli@microsoft.com>
Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com>
Reviewed-by: K. Y. Srinivasan <kys@microsoft.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/hyperv/hyperv_net.h
drivers/net/hyperv/netvsc_drv.c
drivers/net/hyperv/rndis_filter.c

index 2857ab0..95ceb35 100644 (file)
@@ -131,6 +131,7 @@ int rndis_filter_send(struct hv_device *dev,
                        struct hv_netvsc_packet *pkt);
 
 int rndis_filter_set_packet_filter(struct rndis_device *dev, u32 new_filter);
+int rndis_filter_set_device_mac(struct hv_device *hdev, char *mac);
 
 
 #define NVSP_INVALID_PROTOCOL_VERSION  ((u32)0xFFFFFFFF)
index 8f8ed33..8e23c08 100644 (file)
@@ -341,6 +341,34 @@ static int netvsc_change_mtu(struct net_device *ndev, int mtu)
        return 0;
 }
 
+
+static int netvsc_set_mac_addr(struct net_device *ndev, void *p)
+{
+       struct net_device_context *ndevctx = netdev_priv(ndev);
+       struct hv_device *hdev =  ndevctx->device_ctx;
+       struct sockaddr *addr = p;
+       char save_adr[14];
+       unsigned char save_aatype;
+       int err;
+
+       memcpy(save_adr, ndev->dev_addr, ETH_ALEN);
+       save_aatype = ndev->addr_assign_type;
+
+       err = eth_mac_addr(ndev, p);
+       if (err != 0)
+               return err;
+
+       err = rndis_filter_set_device_mac(hdev, addr->sa_data);
+       if (err != 0) {
+               /* roll back to saved MAC */
+               memcpy(ndev->dev_addr, save_adr, ETH_ALEN);
+               ndev->addr_assign_type = save_aatype;
+       }
+
+       return err;
+}
+
+
 static const struct ethtool_ops ethtool_ops = {
        .get_drvinfo    = netvsc_get_drvinfo,
        .get_link       = ethtool_op_get_link,
@@ -353,7 +381,7 @@ static const struct net_device_ops device_ops = {
        .ndo_set_rx_mode =              netvsc_set_multicast_list,
        .ndo_change_mtu =               netvsc_change_mtu,
        .ndo_validate_addr =            eth_validate_addr,
-       .ndo_set_mac_address =          eth_mac_addr,
+       .ndo_set_mac_address =          netvsc_set_mac_addr,
 };
 
 /*
index 981ebb1..fbf5394 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/if_ether.h>
 #include <linux/netdevice.h>
 #include <linux/if_vlan.h>
+#include <linux/nls.h>
 
 #include "hyperv_net.h"
 
@@ -47,6 +48,7 @@ struct rndis_request {
        struct hv_page_buffer buf;
        /* FIXME: We assumed a fixed size request here. */
        struct rndis_message request_msg;
+       u8 ext[100];
 };
 
 static void rndis_filter_send_completion(void *ctx);
@@ -511,6 +513,83 @@ static int rndis_filter_query_device_mac(struct rndis_device *dev)
                                      dev->hw_mac_adr, &size);
 }
 
+#define NWADR_STR "NetworkAddress"
+#define NWADR_STRLEN 14
+
+int rndis_filter_set_device_mac(struct hv_device *hdev, char *mac)
+{
+       struct netvsc_device *nvdev = hv_get_drvdata(hdev);
+       struct rndis_device *rdev = nvdev->extension;
+       struct net_device *ndev = nvdev->ndev;
+       struct rndis_request *request;
+       struct rndis_set_request *set;
+       struct rndis_config_parameter_info *cpi;
+       wchar_t *cfg_nwadr, *cfg_mac;
+       struct rndis_set_complete *set_complete;
+       char macstr[2*ETH_ALEN+1];
+       u32 extlen = sizeof(struct rndis_config_parameter_info) +
+               2*NWADR_STRLEN + 4*ETH_ALEN;
+       int ret, t;
+
+       request = get_rndis_request(rdev, RNDIS_MSG_SET,
+               RNDIS_MESSAGE_SIZE(struct rndis_set_request) + extlen);
+       if (!request)
+               return -ENOMEM;
+
+       set = &request->request_msg.msg.set_req;
+       set->oid = RNDIS_OID_GEN_RNDIS_CONFIG_PARAMETER;
+       set->info_buflen = extlen;
+       set->info_buf_offset = sizeof(struct rndis_set_request);
+       set->dev_vc_handle = 0;
+
+       cpi = (struct rndis_config_parameter_info *)((ulong)set +
+               set->info_buf_offset);
+       cpi->parameter_name_offset =
+               sizeof(struct rndis_config_parameter_info);
+       /* Multiply by 2 because host needs 2 bytes (utf16) for each char */
+       cpi->parameter_name_length = 2*NWADR_STRLEN;
+       cpi->parameter_type = RNDIS_CONFIG_PARAM_TYPE_STRING;
+       cpi->parameter_value_offset =
+               cpi->parameter_name_offset + cpi->parameter_name_length;
+       /* Multiply by 4 because each MAC byte displayed as 2 utf16 chars */
+       cpi->parameter_value_length = 4*ETH_ALEN;
+
+       cfg_nwadr = (wchar_t *)((ulong)cpi + cpi->parameter_name_offset);
+       cfg_mac = (wchar_t *)((ulong)cpi + cpi->parameter_value_offset);
+       ret = utf8s_to_utf16s(NWADR_STR, NWADR_STRLEN, UTF16_HOST_ENDIAN,
+                             cfg_nwadr, NWADR_STRLEN);
+       if (ret < 0)
+               goto cleanup;
+       snprintf(macstr, 2*ETH_ALEN+1, "%pm", mac);
+       ret = utf8s_to_utf16s(macstr, 2*ETH_ALEN, UTF16_HOST_ENDIAN,
+                             cfg_mac, 2*ETH_ALEN);
+       if (ret < 0)
+               goto cleanup;
+
+       ret = rndis_filter_send_request(rdev, request);
+       if (ret != 0)
+               goto cleanup;
+
+       t = wait_for_completion_timeout(&request->wait_event, 5*HZ);
+       if (t == 0) {
+               netdev_err(ndev, "timeout before we got a set response...\n");
+               /*
+                * can't put_rndis_request, since we may still receive a
+                * send-completion.
+                */
+               return -EBUSY;
+       } else {
+               set_complete = &request->response_msg.msg.set_complete;
+               if (set_complete->status != RNDIS_STATUS_SUCCESS)
+                       ret = -EINVAL;
+       }
+
+cleanup:
+       put_rndis_request(rdev, request);
+       return ret;
+}
+
+
 static int rndis_filter_query_device_link_status(struct rndis_device *dev)
 {
        u32 size = sizeof(u32);