Signed-off-by: Daniel Borkmann diff -Nur a/include/linux/netpoll.h b/include/linux/netpoll.h --- a/include/linux/netpoll.h 2010-01-05 23:52:58.000000000 +0100 +++ b/include/linux/netpoll.h 2010-01-06 00:12:54.000000000 +0100 @@ -21,6 +21,7 @@ __be32 local_ip, remote_ip; u16 local_port, remote_port; u8 remote_mac[ETH_ALEN]; + struct netpoll *next; /* rx list for net device */ }; struct netpoll_info { diff -Nur a/net/core/netpoll.c b/net/core/netpoll.c --- a/net/core/netpoll.c 2010-01-05 23:53:07.000000000 +0100 +++ b/net/core/netpoll.c 2010-01-06 20:44:59.000000000 +0100 @@ -493,13 +493,13 @@ int __netpoll_rx(struct sk_buff *skb) { - int proto, len, ulen; + int proto, len, ulen, hits; struct iphdr *iph; struct udphdr *uh; struct netpoll_info *npi = skb->dev->npinfo; - struct netpoll *np = npi->rx_np; + struct netpoll **np = &npi->rx_np; - if (!np) + if (!(*np)) goto out; if (skb->dev->type != ARPHRD_ETHER) goto out; @@ -551,16 +551,23 @@ goto out; if (checksum_udp(skb, uh, ulen, iph->saddr, iph->daddr)) goto out; - if (np->local_ip && np->local_ip != iph->daddr) - goto out; - if (np->remote_ip && np->remote_ip != iph->saddr) - goto out; - if (np->local_port && np->local_port != ntohs(uh->dest)) - goto out; - np->rx_hook(np, ntohs(uh->source), - (char *)(uh+1), - ulen - sizeof(struct udphdr)); + for (hits = 0; (*np) != NULL; np = &((*np)->next)) { + if ((*np)->local_ip && (*np)->local_ip != iph->daddr) + continue; + if ((*np)->remote_ip && (*np)->remote_ip != iph->saddr) + continue; + if ((*np)->local_port && (*np)->local_port != ntohs(uh->dest)) + continue; + + (*np)->rx_hook((*np), ntohs(uh->source), + (char *)(uh+1), + ulen - sizeof(struct udphdr)); + hits++; + } + + if (!hits) + goto out; kfree_skb(skb); return 1; @@ -679,6 +686,45 @@ return -1; } +static int netpoll_rx_list_register(struct netpoll **np_head, + struct netpoll *np) +{ + while ((*np_head) != NULL) { + if ((*np_head) == np) + return -EEXIST; + np_head = &((*np_head)->next); + } + + np->next = *np_head; + rcu_assign_pointer(*np_head, np); + return 0; +} + +static int netpoll_rx_list_unregister(struct netpoll **np_head, + struct netpoll *np) +{ + while ((*np_head) != NULL) { + if ((*np_head) == np) { + rcu_assign_pointer(*np_head, np->next); + return 0; + } + np_head = &((*np_head)->next); + } + return -ENOENT; +} + +static void netpoll_rx_list_nullify(struct netpoll **np_head) +{ + struct netpoll *np = NULL; + + while ((*np_head) != NULL) { + np = (*np_head); + np_head = &((*np_head)->next); + np->dev = NULL; + np->next = NULL; + } +} + int netpoll_setup(struct netpoll *np) { struct net_device *ndev = NULL; @@ -695,6 +741,7 @@ return -ENODEV; } + np->next = NULL; np->dev = ndev; if (!ndev->npinfo) { npinfo = kmalloc(sizeof(*npinfo), GFP_KERNEL); @@ -785,7 +832,7 @@ if (np->rx_hook) { spin_lock_irqsave(&npinfo->rx_lock, flags); npinfo->rx_flags |= NETPOLL_RX_ENABLED; - npinfo->rx_np = np; + netpoll_rx_list_register(&npinfo->rx_np, np); spin_unlock_irqrestore(&npinfo->rx_lock, flags); } @@ -801,9 +848,14 @@ return 0; release: - if (!ndev->npinfo) + if (!ndev->npinfo) { + spin_lock_irqsave(&npinfo->rx_lock, flags); + netpoll_rx_list_nullify(&npinfo->rx_np); + spin_unlock_irqrestore(&npinfo->rx_lock, flags); + kfree(npinfo); - np->dev = NULL; + } + dev_put(ndev); return err; } @@ -823,10 +875,11 @@ if (np->dev) { npinfo = np->dev->npinfo; if (npinfo) { - if (npinfo->rx_np == np) { + if (npinfo->rx_np) { spin_lock_irqsave(&npinfo->rx_lock, flags); - npinfo->rx_np = NULL; - npinfo->rx_flags &= ~NETPOLL_RX_ENABLED; + netpoll_rx_list_unregister(&npinfo->rx_np, np); + if (!npinfo->rx_np) + npinfo->rx_flags &= ~NETPOLL_RX_ENABLED; spin_unlock_irqrestore(&npinfo->rx_lock, flags); } @@ -845,7 +898,10 @@ dev_put(np->dev); } + np->next = NULL; np->dev = NULL; + + synchronize_rcu(); } int netpoll_trap(void)