aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHaiyang Zhang <haiyangz@microsoft.com>2012-07-10 03:19:22 -0400
committerDavid S. Miller <davem@davemloft.net>2012-07-17 01:54:24 -0400
commit1ce09e899d2864b4c8ed8f777c396bcb953aa3c9 (patch)
tree953c896879802b24e3596a51b403ec03d3677ccd
parent5e96855fc505082389813afcf796d4c46301d4fe (diff)
hyperv: Add support for setting MAC from within guests
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>
-rw-r--r--drivers/net/hyperv/hyperv_net.h1
-rw-r--r--drivers/net/hyperv/netvsc_drv.c30
-rw-r--r--drivers/net/hyperv/rndis_filter.c79
3 files changed, 109 insertions, 1 deletions
diff --git a/drivers/net/hyperv/hyperv_net.h b/drivers/net/hyperv/hyperv_net.h
index 2857ab078aa..95ceb359304 100644
--- a/drivers/net/hyperv/hyperv_net.h
+++ b/drivers/net/hyperv/hyperv_net.h
@@ -131,6 +131,7 @@ int rndis_filter_send(struct hv_device *dev,
131 struct hv_netvsc_packet *pkt); 131 struct hv_netvsc_packet *pkt);
132 132
133int rndis_filter_set_packet_filter(struct rndis_device *dev, u32 new_filter); 133int rndis_filter_set_packet_filter(struct rndis_device *dev, u32 new_filter);
134int rndis_filter_set_device_mac(struct hv_device *hdev, char *mac);
134 135
135 136
136#define NVSP_INVALID_PROTOCOL_VERSION ((u32)0xFFFFFFFF) 137#define NVSP_INVALID_PROTOCOL_VERSION ((u32)0xFFFFFFFF)
diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c
index 8f8ed332042..8e23c084c4a 100644
--- a/drivers/net/hyperv/netvsc_drv.c
+++ b/drivers/net/hyperv/netvsc_drv.c
@@ -341,6 +341,34 @@ static int netvsc_change_mtu(struct net_device *ndev, int mtu)
341 return 0; 341 return 0;
342} 342}
343 343
344
345static int netvsc_set_mac_addr(struct net_device *ndev, void *p)
346{
347 struct net_device_context *ndevctx = netdev_priv(ndev);
348 struct hv_device *hdev = ndevctx->device_ctx;
349 struct sockaddr *addr = p;
350 char save_adr[14];
351 unsigned char save_aatype;
352 int err;
353
354 memcpy(save_adr, ndev->dev_addr, ETH_ALEN);
355 save_aatype = ndev->addr_assign_type;
356
357 err = eth_mac_addr(ndev, p);
358 if (err != 0)
359 return err;
360
361 err = rndis_filter_set_device_mac(hdev, addr->sa_data);
362 if (err != 0) {
363 /* roll back to saved MAC */
364 memcpy(ndev->dev_addr, save_adr, ETH_ALEN);
365 ndev->addr_assign_type = save_aatype;
366 }
367
368 return err;
369}
370
371
344static const struct ethtool_ops ethtool_ops = { 372static const struct ethtool_ops ethtool_ops = {
345 .get_drvinfo = netvsc_get_drvinfo, 373 .get_drvinfo = netvsc_get_drvinfo,
346 .get_link = ethtool_op_get_link, 374 .get_link = ethtool_op_get_link,
@@ -353,7 +381,7 @@ static const struct net_device_ops device_ops = {
353 .ndo_set_rx_mode = netvsc_set_multicast_list, 381 .ndo_set_rx_mode = netvsc_set_multicast_list,
354 .ndo_change_mtu = netvsc_change_mtu, 382 .ndo_change_mtu = netvsc_change_mtu,
355 .ndo_validate_addr = eth_validate_addr, 383 .ndo_validate_addr = eth_validate_addr,
356 .ndo_set_mac_address = eth_mac_addr, 384 .ndo_set_mac_address = netvsc_set_mac_addr,
357}; 385};
358 386
359/* 387/*
diff --git a/drivers/net/hyperv/rndis_filter.c b/drivers/net/hyperv/rndis_filter.c
index 981ebb11563..fbf53946820 100644
--- a/drivers/net/hyperv/rndis_filter.c
+++ b/drivers/net/hyperv/rndis_filter.c
@@ -27,6 +27,7 @@
27#include <linux/if_ether.h> 27#include <linux/if_ether.h>
28#include <linux/netdevice.h> 28#include <linux/netdevice.h>
29#include <linux/if_vlan.h> 29#include <linux/if_vlan.h>
30#include <linux/nls.h>
30 31
31#include "hyperv_net.h" 32#include "hyperv_net.h"
32 33
@@ -47,6 +48,7 @@ struct rndis_request {
47 struct hv_page_buffer buf; 48 struct hv_page_buffer buf;
48 /* FIXME: We assumed a fixed size request here. */ 49 /* FIXME: We assumed a fixed size request here. */
49 struct rndis_message request_msg; 50 struct rndis_message request_msg;
51 u8 ext[100];
50}; 52};
51 53
52static void rndis_filter_send_completion(void *ctx); 54static void rndis_filter_send_completion(void *ctx);
@@ -511,6 +513,83 @@ static int rndis_filter_query_device_mac(struct rndis_device *dev)
511 dev->hw_mac_adr, &size); 513 dev->hw_mac_adr, &size);
512} 514}
513 515
516#define NWADR_STR "NetworkAddress"
517#define NWADR_STRLEN 14
518
519int rndis_filter_set_device_mac(struct hv_device *hdev, char *mac)
520{
521 struct netvsc_device *nvdev = hv_get_drvdata(hdev);
522 struct rndis_device *rdev = nvdev->extension;
523 struct net_device *ndev = nvdev->ndev;
524 struct rndis_request *request;
525 struct rndis_set_request *set;
526 struct rndis_config_parameter_info *cpi;
527 wchar_t *cfg_nwadr, *cfg_mac;
528 struct rndis_set_complete *set_complete;
529 char macstr[2*ETH_ALEN+1];
530 u32 extlen = sizeof(struct rndis_config_parameter_info) +
531 2*NWADR_STRLEN + 4*ETH_ALEN;
532 int ret, t;
533
534 request = get_rndis_request(rdev, RNDIS_MSG_SET,
535 RNDIS_MESSAGE_SIZE(struct rndis_set_request) + extlen);
536 if (!request)
537 return -ENOMEM;
538
539 set = &request->request_msg.msg.set_req;
540 set->oid = RNDIS_OID_GEN_RNDIS_CONFIG_PARAMETER;
541 set->info_buflen = extlen;
542 set->info_buf_offset = sizeof(struct rndis_set_request);
543 set->dev_vc_handle = 0;
544
545 cpi = (struct rndis_config_parameter_info *)((ulong)set +
546 set->info_buf_offset);
547 cpi->parameter_name_offset =
548 sizeof(struct rndis_config_parameter_info);
549 /* Multiply by 2 because host needs 2 bytes (utf16) for each char */
550 cpi->parameter_name_length = 2*NWADR_STRLEN;
551 cpi->parameter_type = RNDIS_CONFIG_PARAM_TYPE_STRING;
552 cpi->parameter_value_offset =
553 cpi->parameter_name_offset + cpi->parameter_name_length;
554 /* Multiply by 4 because each MAC byte displayed as 2 utf16 chars */
555 cpi->parameter_value_length = 4*ETH_ALEN;
556
557 cfg_nwadr = (wchar_t *)((ulong)cpi + cpi->parameter_name_offset);
558 cfg_mac = (wchar_t *)((ulong)cpi + cpi->parameter_value_offset);
559 ret = utf8s_to_utf16s(NWADR_STR, NWADR_STRLEN, UTF16_HOST_ENDIAN,
560 cfg_nwadr, NWADR_STRLEN);
561 if (ret < 0)
562 goto cleanup;
563 snprintf(macstr, 2*ETH_ALEN+1, "%pm", mac);
564 ret = utf8s_to_utf16s(macstr, 2*ETH_ALEN, UTF16_HOST_ENDIAN,
565 cfg_mac, 2*ETH_ALEN);
566 if (ret < 0)
567 goto cleanup;
568
569 ret = rndis_filter_send_request(rdev, request);
570 if (ret != 0)
571 goto cleanup;
572
573 t = wait_for_completion_timeout(&request->wait_event, 5*HZ);
574 if (t == 0) {
575 netdev_err(ndev, "timeout before we got a set response...\n");
576 /*
577 * can't put_rndis_request, since we may still receive a
578 * send-completion.
579 */
580 return -EBUSY;
581 } else {
582 set_complete = &request->response_msg.msg.set_complete;
583 if (set_complete->status != RNDIS_STATUS_SUCCESS)
584 ret = -EINVAL;
585 }
586
587cleanup:
588 put_rndis_request(rdev, request);
589 return ret;
590}
591
592
514static int rndis_filter_query_device_link_status(struct rndis_device *dev) 593static int rndis_filter_query_device_link_status(struct rndis_device *dev)
515{ 594{
516 u32 size = sizeof(u32); 595 u32 size = sizeof(u32);