rndis_wlan: correct multicast_list handling V3
My previous patch (655ffee284
) added locking in
a bad way. Because rndis_set_oid can sleep, there is need to prepare multicast
addresses into local buffer under netif_addr_lock first, then call
rndis_set_oid outside. This caused reorganizing of the whole function.
Signed-off-by: Jiri Pirko <jpirko@redhat.com>
Reported-by: Jussi Kivilinna <jussi.kivilinna@mbnet.fi>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
committed by
David S. Miller
parent
4c32531324
commit
12c3400a84
@@ -1496,51 +1496,67 @@ static void set_multicast_list(struct usbnet *usbdev)
|
|||||||
{
|
{
|
||||||
struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev);
|
struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev);
|
||||||
struct dev_mc_list *mclist;
|
struct dev_mc_list *mclist;
|
||||||
__le32 filter;
|
__le32 filter, basefilter;
|
||||||
int ret, i, size;
|
int ret;
|
||||||
char *buf;
|
char *mc_addrs = NULL;
|
||||||
|
int mc_count;
|
||||||
|
|
||||||
filter = RNDIS_PACKET_TYPE_DIRECTED | RNDIS_PACKET_TYPE_BROADCAST;
|
basefilter = filter = RNDIS_PACKET_TYPE_DIRECTED |
|
||||||
|
RNDIS_PACKET_TYPE_BROADCAST;
|
||||||
|
|
||||||
netif_addr_lock_bh(usbdev->net);
|
|
||||||
if (usbdev->net->flags & IFF_PROMISC) {
|
if (usbdev->net->flags & IFF_PROMISC) {
|
||||||
filter |= RNDIS_PACKET_TYPE_PROMISCUOUS |
|
filter |= RNDIS_PACKET_TYPE_PROMISCUOUS |
|
||||||
RNDIS_PACKET_TYPE_ALL_LOCAL;
|
RNDIS_PACKET_TYPE_ALL_LOCAL;
|
||||||
} else if (usbdev->net->flags & IFF_ALLMULTI ||
|
} else if (usbdev->net->flags & IFF_ALLMULTI) {
|
||||||
netdev_mc_count(usbdev->net) > priv->multicast_size) {
|
|
||||||
filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST;
|
filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST;
|
||||||
} else if (!netdev_mc_empty(usbdev->net)) {
|
}
|
||||||
size = min(priv->multicast_size, netdev_mc_count(usbdev->net));
|
|
||||||
buf = kmalloc(size * ETH_ALEN, GFP_KERNEL);
|
if (filter != basefilter)
|
||||||
if (!buf) {
|
goto set_filter;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* mc_list should be accessed holding the lock, so copy addresses to
|
||||||
|
* local buffer first.
|
||||||
|
*/
|
||||||
|
netif_addr_lock_bh(usbdev->net);
|
||||||
|
mc_count = netdev_mc_count(usbdev->net);
|
||||||
|
if (mc_count > priv->multicast_size) {
|
||||||
|
filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST;
|
||||||
|
} else if (mc_count) {
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
mc_addrs = kmalloc(mc_count * ETH_ALEN, GFP_ATOMIC);
|
||||||
|
if (!mc_addrs) {
|
||||||
netdev_warn(usbdev->net,
|
netdev_warn(usbdev->net,
|
||||||
"couldn't alloc %d bytes of memory\n",
|
"couldn't alloc %d bytes of memory\n",
|
||||||
size * ETH_ALEN);
|
mc_count * ETH_ALEN);
|
||||||
netif_addr_unlock_bh(usbdev->net);
|
netif_addr_unlock_bh(usbdev->net);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
i = 0;
|
netdev_for_each_mc_addr(mclist, usbdev->net)
|
||||||
netdev_for_each_mc_addr(mclist, usbdev->net) {
|
memcpy(mc_addrs + i++ * ETH_ALEN,
|
||||||
if (i == size)
|
mclist->dmi_addr, ETH_ALEN);
|
||||||
break;
|
}
|
||||||
memcpy(buf + i++ * ETH_ALEN, mclist->dmi_addr, ETH_ALEN);
|
netif_addr_unlock_bh(usbdev->net);
|
||||||
}
|
|
||||||
|
|
||||||
ret = rndis_set_oid(usbdev, OID_802_3_MULTICAST_LIST, buf,
|
if (filter != basefilter)
|
||||||
i * ETH_ALEN);
|
goto set_filter;
|
||||||
if (ret == 0 && i > 0)
|
|
||||||
|
if (mc_count) {
|
||||||
|
ret = rndis_set_oid(usbdev, OID_802_3_MULTICAST_LIST, mc_addrs,
|
||||||
|
mc_count * ETH_ALEN);
|
||||||
|
kfree(mc_addrs);
|
||||||
|
if (ret == 0)
|
||||||
filter |= RNDIS_PACKET_TYPE_MULTICAST;
|
filter |= RNDIS_PACKET_TYPE_MULTICAST;
|
||||||
else
|
else
|
||||||
filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST;
|
filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST;
|
||||||
|
|
||||||
netdev_dbg(usbdev->net, "OID_802_3_MULTICAST_LIST(%d, max: %d) -> %d\n",
|
netdev_dbg(usbdev->net, "OID_802_3_MULTICAST_LIST(%d, max: %d) -> %d\n",
|
||||||
i, priv->multicast_size, ret);
|
mc_count, priv->multicast_size, ret);
|
||||||
|
|
||||||
kfree(buf);
|
|
||||||
}
|
}
|
||||||
netif_addr_unlock_bh(usbdev->net);
|
|
||||||
|
|
||||||
|
set_filter:
|
||||||
ret = rndis_set_oid(usbdev, OID_GEN_CURRENT_PACKET_FILTER, &filter,
|
ret = rndis_set_oid(usbdev, OID_GEN_CURRENT_PACKET_FILTER, &filter,
|
||||||
sizeof(filter));
|
sizeof(filter));
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
Reference in New Issue
Block a user