Commit 1ce09e89 authored by Haiyang Zhang's avatar Haiyang Zhang Committed by David S. Miller

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: default avatarKin Cho <kcho@infoblox.com>
Reported-by: default avatarLong Li <longli@microsoft.com>
Signed-off-by: default avatarHaiyang Zhang <haiyangz@microsoft.com>
Reviewed-by: default avatarK. Y. Srinivasan <kys@microsoft.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 5e96855f
...@@ -131,6 +131,7 @@ int rndis_filter_send(struct hv_device *dev, ...@@ -131,6 +131,7 @@ int rndis_filter_send(struct hv_device *dev,
struct hv_netvsc_packet *pkt); struct hv_netvsc_packet *pkt);
int rndis_filter_set_packet_filter(struct rndis_device *dev, u32 new_filter); 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) #define NVSP_INVALID_PROTOCOL_VERSION ((u32)0xFFFFFFFF)
......
...@@ -341,6 +341,34 @@ static int netvsc_change_mtu(struct net_device *ndev, int mtu) ...@@ -341,6 +341,34 @@ static int netvsc_change_mtu(struct net_device *ndev, int mtu)
return 0; 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 = { static const struct ethtool_ops ethtool_ops = {
.get_drvinfo = netvsc_get_drvinfo, .get_drvinfo = netvsc_get_drvinfo,
.get_link = ethtool_op_get_link, .get_link = ethtool_op_get_link,
...@@ -353,7 +381,7 @@ static const struct net_device_ops device_ops = { ...@@ -353,7 +381,7 @@ static const struct net_device_ops device_ops = {
.ndo_set_rx_mode = netvsc_set_multicast_list, .ndo_set_rx_mode = netvsc_set_multicast_list,
.ndo_change_mtu = netvsc_change_mtu, .ndo_change_mtu = netvsc_change_mtu,
.ndo_validate_addr = eth_validate_addr, .ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = eth_mac_addr, .ndo_set_mac_address = netvsc_set_mac_addr,
}; };
/* /*
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include <linux/if_ether.h> #include <linux/if_ether.h>
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/if_vlan.h> #include <linux/if_vlan.h>
#include <linux/nls.h>
#include "hyperv_net.h" #include "hyperv_net.h"
...@@ -47,6 +48,7 @@ struct rndis_request { ...@@ -47,6 +48,7 @@ struct rndis_request {
struct hv_page_buffer buf; struct hv_page_buffer buf;
/* FIXME: We assumed a fixed size request here. */ /* FIXME: We assumed a fixed size request here. */
struct rndis_message request_msg; struct rndis_message request_msg;
u8 ext[100];
}; };
static void rndis_filter_send_completion(void *ctx); static void rndis_filter_send_completion(void *ctx);
...@@ -511,6 +513,83 @@ static int rndis_filter_query_device_mac(struct rndis_device *dev) ...@@ -511,6 +513,83 @@ static int rndis_filter_query_device_mac(struct rndis_device *dev)
dev->hw_mac_adr, &size); 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) static int rndis_filter_query_device_link_status(struct rndis_device *dev)
{ {
u32 size = sizeof(u32); u32 size = sizeof(u32);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment