Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 508e14b4 authored by Daniel Borkmann's avatar Daniel Borkmann Committed by David S. Miller
Browse files

netpoll: allow execution of multiple rx_hooks per interface

parent e1d5a010
Loading
Loading
Loading
Loading
+8 −3
Original line number Original line Diff line number Diff line
@@ -21,15 +21,20 @@ struct netpoll {
	__be32 local_ip, remote_ip;
	__be32 local_ip, remote_ip;
	u16 local_port, remote_port;
	u16 local_port, remote_port;
	u8 remote_mac[ETH_ALEN];
	u8 remote_mac[ETH_ALEN];

	struct list_head rx; /* rx_np list element */
};
};


struct netpoll_info {
struct netpoll_info {
	atomic_t refcnt;
	atomic_t refcnt;

	int rx_flags;
	int rx_flags;
	spinlock_t rx_lock;
	spinlock_t rx_lock;
	struct netpoll *rx_np; /* netpoll that registered an rx_hook */
	struct list_head rx_np; /* netpolls that registered an rx_hook */

	struct sk_buff_head arp_tx; /* list of arp requests to reply to */
	struct sk_buff_head arp_tx; /* list of arp requests to reply to */
	struct sk_buff_head txq;
	struct sk_buff_head txq;

	struct delayed_work tx_work;
	struct delayed_work tx_work;
};
};


@@ -51,7 +56,7 @@ static inline int netpoll_rx(struct sk_buff *skb)
	unsigned long flags;
	unsigned long flags;
	int ret = 0;
	int ret = 0;


	if (!npinfo || (!npinfo->rx_np && !npinfo->rx_flags))
	if (!npinfo || (list_empty(&npinfo->rx_np) && !npinfo->rx_flags))
		return 0;
		return 0;


	spin_lock_irqsave(&npinfo->rx_lock, flags);
	spin_lock_irqsave(&npinfo->rx_lock, flags);
@@ -67,7 +72,7 @@ static inline int netpoll_rx_on(struct sk_buff *skb)
{
{
	struct netpoll_info *npinfo = skb->dev->npinfo;
	struct netpoll_info *npinfo = skb->dev->npinfo;


	return npinfo && (npinfo->rx_np || npinfo->rx_flags);
	return npinfo && (!list_empty(&npinfo->rx_np) || npinfo->rx_flags);
}
}


static inline int netpoll_receive_skb(struct sk_buff *skb)
static inline int netpoll_receive_skb(struct sk_buff *skb)
+106 −63
Original line number Original line Diff line number Diff line
@@ -407,11 +407,24 @@ static void arp_reply(struct sk_buff *skb)
	__be32 sip, tip;
	__be32 sip, tip;
	unsigned char *sha;
	unsigned char *sha;
	struct sk_buff *send_skb;
	struct sk_buff *send_skb;
	struct netpoll *np = NULL;
	struct netpoll *np, *tmp;
	unsigned long flags;
	int hits = 0;

	if (list_empty(&npinfo->rx_np))
		return;

	/* Before checking the packet, we do some early
	   inspection whether this is interesting at all */
	spin_lock_irqsave(&npinfo->rx_lock, flags);
	list_for_each_entry_safe(np, tmp, &npinfo->rx_np, rx) {
		if (np->dev == skb->dev)
			hits++;
	}
	spin_unlock_irqrestore(&npinfo->rx_lock, flags);


	if (npinfo->rx_np && npinfo->rx_np->dev == skb->dev)
	/* No netpoll struct is using this dev */
		np = npinfo->rx_np;
	if (!hits)
	if (!np)
		return;
		return;


	/* No arp on this interface */
	/* No arp on this interface */
@@ -437,21 +450,26 @@ static void arp_reply(struct sk_buff *skb)
	arp_ptr += skb->dev->addr_len;
	arp_ptr += skb->dev->addr_len;
	memcpy(&sip, arp_ptr, 4);
	memcpy(&sip, arp_ptr, 4);
	arp_ptr += 4;
	arp_ptr += 4;
	/* if we actually cared about dst hw addr, it would get copied here */
	/* If we actually cared about dst hw addr,
	   it would get copied here */
	arp_ptr += skb->dev->addr_len;
	arp_ptr += skb->dev->addr_len;
	memcpy(&tip, arp_ptr, 4);
	memcpy(&tip, arp_ptr, 4);


	/* Should we ignore arp? */
	/* Should we ignore arp? */
	if (tip != np->local_ip ||
	if (ipv4_is_loopback(tip) || ipv4_is_multicast(tip))
	    ipv4_is_loopback(tip) || ipv4_is_multicast(tip))
		return;
		return;


	size = arp_hdr_len(skb->dev);
	size = arp_hdr_len(skb->dev);

	spin_lock_irqsave(&npinfo->rx_lock, flags);
	list_for_each_entry_safe(np, tmp, &npinfo->rx_np, rx) {
		if (tip != np->local_ip)
			continue;

		send_skb = find_skb(np, size + LL_ALLOCATED_SPACE(np->dev),
		send_skb = find_skb(np, size + LL_ALLOCATED_SPACE(np->dev),
				    LL_RESERVED_SPACE(np->dev));
				    LL_RESERVED_SPACE(np->dev));

		if (!send_skb)
		if (!send_skb)
		return;
			continue;


		skb_reset_network_header(send_skb);
		skb_reset_network_header(send_skb);
		arp = (struct arphdr *) skb_put(send_skb, size);
		arp = (struct arphdr *) skb_put(send_skb, size);
@@ -463,14 +481,15 @@ static void arp_reply(struct sk_buff *skb)
				    sha, np->dev->dev_addr,
				    sha, np->dev->dev_addr,
				    send_skb->len) < 0) {
				    send_skb->len) < 0) {
			kfree_skb(send_skb);
			kfree_skb(send_skb);
		return;
			continue;
		}
		}


		/*
		/*
		 * Fill out the arp protocol part.
		 * Fill out the arp protocol part.
		 *
		 *
		 * we only support ethernet device type,
		 * we only support ethernet device type,
	 * which (according to RFC 1390) should always equal 1 (Ethernet).
		 * which (according to RFC 1390) should
		 * always equal 1 (Ethernet).
		 */
		 */


		arp->ar_hrd = htons(np->dev->type);
		arp->ar_hrd = htons(np->dev->type);
@@ -489,25 +508,33 @@ static void arp_reply(struct sk_buff *skb)
		memcpy(arp_ptr, &sip, 4);
		memcpy(arp_ptr, &sip, 4);


		netpoll_send_skb(np, send_skb);
		netpoll_send_skb(np, send_skb);

		/* If there are several rx_hooks for the same address,
		   we're fine by sending a single reply */
		break;
	}
	spin_unlock_irqrestore(&npinfo->rx_lock, flags);
}
}


int __netpoll_rx(struct sk_buff *skb)
int __netpoll_rx(struct sk_buff *skb)
{
{
	int proto, len, ulen;
	int proto, len, ulen;
	int hits = 0;
	struct iphdr *iph;
	struct iphdr *iph;
	struct udphdr *uh;
	struct udphdr *uh;
	struct netpoll_info *npi = skb->dev->npinfo;
	struct netpoll_info *npinfo = skb->dev->npinfo;
	struct netpoll *np = npi->rx_np;
	struct netpoll *np, *tmp;


	if (!np)
	if (list_empty(&npinfo->rx_np))
		goto out;
		goto out;

	if (skb->dev->type != ARPHRD_ETHER)
	if (skb->dev->type != ARPHRD_ETHER)
		goto out;
		goto out;


	/* check if netpoll clients need ARP */
	/* check if netpoll clients need ARP */
	if (skb->protocol == htons(ETH_P_ARP) &&
	if (skb->protocol == htons(ETH_P_ARP) &&
	    atomic_read(&trapped)) {
	    atomic_read(&trapped)) {
		skb_queue_tail(&npi->arp_tx, skb);
		skb_queue_tail(&npinfo->arp_tx, skb);
		return 1;
		return 1;
	}
	}


@@ -551,16 +578,23 @@ int __netpoll_rx(struct sk_buff *skb)
		goto out;
		goto out;
	if (checksum_udp(skb, uh, ulen, iph->saddr, iph->daddr))
	if (checksum_udp(skb, uh, ulen, iph->saddr, iph->daddr))
		goto out;
		goto out;

	list_for_each_entry_safe(np, tmp, &npinfo->rx_np, rx) {
		if (np->local_ip && np->local_ip != iph->daddr)
		if (np->local_ip && np->local_ip != iph->daddr)
		goto out;
			continue;
		if (np->remote_ip && np->remote_ip != iph->saddr)
		if (np->remote_ip && np->remote_ip != iph->saddr)
		goto out;
			continue;
		if (np->local_port && np->local_port != ntohs(uh->dest))
		if (np->local_port && np->local_port != ntohs(uh->dest))
		goto out;
			continue;


		np->rx_hook(np, ntohs(uh->source),
		np->rx_hook(np, ntohs(uh->source),
			       (char *)(uh+1),
			       (char *)(uh+1),
			       ulen - sizeof(struct udphdr));
			       ulen - sizeof(struct udphdr));
		hits++;
	}

	if (!hits)
		goto out;


	kfree_skb(skb);
	kfree_skb(skb);
	return 1;
	return 1;
@@ -684,6 +718,7 @@ int netpoll_setup(struct netpoll *np)
	struct net_device *ndev = NULL;
	struct net_device *ndev = NULL;
	struct in_device *in_dev;
	struct in_device *in_dev;
	struct netpoll_info *npinfo;
	struct netpoll_info *npinfo;
	struct netpoll *npe, *tmp;
	unsigned long flags;
	unsigned long flags;
	int err;
	int err;


@@ -704,7 +739,7 @@ int netpoll_setup(struct netpoll *np)
		}
		}


		npinfo->rx_flags = 0;
		npinfo->rx_flags = 0;
		npinfo->rx_np = NULL;
		INIT_LIST_HEAD(&npinfo->rx_np);


		spin_lock_init(&npinfo->rx_lock);
		spin_lock_init(&npinfo->rx_lock);
		skb_queue_head_init(&npinfo->arp_tx);
		skb_queue_head_init(&npinfo->arp_tx);
@@ -785,7 +820,7 @@ int netpoll_setup(struct netpoll *np)
	if (np->rx_hook) {
	if (np->rx_hook) {
		spin_lock_irqsave(&npinfo->rx_lock, flags);
		spin_lock_irqsave(&npinfo->rx_lock, flags);
		npinfo->rx_flags |= NETPOLL_RX_ENABLED;
		npinfo->rx_flags |= NETPOLL_RX_ENABLED;
		npinfo->rx_np = np;
		list_add_tail(&np->rx, &npinfo->rx_np);
		spin_unlock_irqrestore(&npinfo->rx_lock, flags);
		spin_unlock_irqrestore(&npinfo->rx_lock, flags);
	}
	}


@@ -801,9 +836,16 @@ int netpoll_setup(struct netpoll *np)
	return 0;
	return 0;


 release:
 release:
	if (!ndev->npinfo)
	if (!ndev->npinfo) {
		spin_lock_irqsave(&npinfo->rx_lock, flags);
		list_for_each_entry_safe(npe, tmp, &npinfo->rx_np, rx) {
			npe->dev = NULL;
		}
		spin_unlock_irqrestore(&npinfo->rx_lock, flags);

		kfree(npinfo);
		kfree(npinfo);
	np->dev = NULL;
	}

	dev_put(ndev);
	dev_put(ndev);
	return err;
	return err;
}
}
@@ -823,9 +865,10 @@ void netpoll_cleanup(struct netpoll *np)
	if (np->dev) {
	if (np->dev) {
		npinfo = np->dev->npinfo;
		npinfo = np->dev->npinfo;
		if (npinfo) {
		if (npinfo) {
			if (npinfo->rx_np == np) {
			if (!list_empty(&npinfo->rx_np)) {
				spin_lock_irqsave(&npinfo->rx_lock, flags);
				spin_lock_irqsave(&npinfo->rx_lock, flags);
				npinfo->rx_np = NULL;
				list_del(&np->rx);
				if (list_empty(&npinfo->rx_np))
					npinfo->rx_flags &= ~NETPOLL_RX_ENABLED;
					npinfo->rx_flags &= ~NETPOLL_RX_ENABLED;
				spin_unlock_irqrestore(&npinfo->rx_lock, flags);
				spin_unlock_irqrestore(&npinfo->rx_lock, flags);
			}
			}