mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	The commit59a0b022aa("ipvlan: Make skb->skb_iif track skb->dev for l3s mode") fixed ipvlan bonded dev checking by updating skb skb_iif. This fix works for IPv4, as in raw_v4_input() the dif is from inet_iif(skb), which is skb->skb_iif when there is no route. But for IPv6, the fix is not enough, because in ipv6_raw_deliver() -> raw_v6_match(), the dif is inet6_iif(skb), which is returns IP6CB(skb)->iif instead of skb->skb_iif if it's not a l3_slave. To fix the IPv6 part issue. Let's set IP6CB(skb)->iif to correct ifindex. BTW, ipvlan handles NS/NA specifically. Since it works fine, I will not reset IP6CB(skb)->iif when addr->atype is IPVL_ICMPV6. Fixes:c675e06a98("ipvlan: decouple l3s mode dependencies from other modes") Link: https://bugzilla.redhat.com/show_bug.cgi?id=2196710 Signed-off-by: Hangbin Liu <liuhangbin@gmail.com> Reviewed-by: Larysa Zaremba <larysa.zaremba@intel.com> Signed-off-by: David S. Miller <davem@davemloft.net>
		
			
				
	
	
		
			228 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0-or-later
 | 
						|
/* Copyright (c) 2014 Mahesh Bandewar <maheshb@google.com>
 | 
						|
 */
 | 
						|
 | 
						|
#include "ipvlan.h"
 | 
						|
 | 
						|
static unsigned int ipvlan_netid __read_mostly;
 | 
						|
 | 
						|
struct ipvlan_netns {
 | 
						|
	unsigned int ipvl_nf_hook_refcnt;
 | 
						|
};
 | 
						|
 | 
						|
static struct ipvl_addr *ipvlan_skb_to_addr(struct sk_buff *skb,
 | 
						|
					    struct net_device *dev)
 | 
						|
{
 | 
						|
	struct ipvl_addr *addr = NULL;
 | 
						|
	struct ipvl_port *port;
 | 
						|
	int addr_type;
 | 
						|
	void *lyr3h;
 | 
						|
 | 
						|
	if (!dev || !netif_is_ipvlan_port(dev))
 | 
						|
		goto out;
 | 
						|
 | 
						|
	port = ipvlan_port_get_rcu(dev);
 | 
						|
	if (!port || port->mode != IPVLAN_MODE_L3S)
 | 
						|
		goto out;
 | 
						|
 | 
						|
	lyr3h = ipvlan_get_L3_hdr(port, skb, &addr_type);
 | 
						|
	if (!lyr3h)
 | 
						|
		goto out;
 | 
						|
 | 
						|
	addr = ipvlan_addr_lookup(port, lyr3h, addr_type, true);
 | 
						|
out:
 | 
						|
	return addr;
 | 
						|
}
 | 
						|
 | 
						|
static struct sk_buff *ipvlan_l3_rcv(struct net_device *dev,
 | 
						|
				     struct sk_buff *skb, u16 proto)
 | 
						|
{
 | 
						|
	struct ipvl_addr *addr;
 | 
						|
	struct net_device *sdev;
 | 
						|
 | 
						|
	addr = ipvlan_skb_to_addr(skb, dev);
 | 
						|
	if (!addr)
 | 
						|
		goto out;
 | 
						|
 | 
						|
	sdev = addr->master->dev;
 | 
						|
	switch (proto) {
 | 
						|
	case AF_INET:
 | 
						|
	{
 | 
						|
		struct iphdr *ip4h = ip_hdr(skb);
 | 
						|
		int err;
 | 
						|
 | 
						|
		err = ip_route_input_noref(skb, ip4h->daddr, ip4h->saddr,
 | 
						|
					   ip4h->tos, sdev);
 | 
						|
		if (unlikely(err))
 | 
						|
			goto out;
 | 
						|
		break;
 | 
						|
	}
 | 
						|
#if IS_ENABLED(CONFIG_IPV6)
 | 
						|
	case AF_INET6:
 | 
						|
	{
 | 
						|
		struct dst_entry *dst;
 | 
						|
		struct ipv6hdr *ip6h = ipv6_hdr(skb);
 | 
						|
		int flags = RT6_LOOKUP_F_HAS_SADDR;
 | 
						|
		struct flowi6 fl6 = {
 | 
						|
			.flowi6_iif   = sdev->ifindex,
 | 
						|
			.daddr        = ip6h->daddr,
 | 
						|
			.saddr        = ip6h->saddr,
 | 
						|
			.flowlabel    = ip6_flowinfo(ip6h),
 | 
						|
			.flowi6_mark  = skb->mark,
 | 
						|
			.flowi6_proto = ip6h->nexthdr,
 | 
						|
		};
 | 
						|
 | 
						|
		skb_dst_drop(skb);
 | 
						|
		dst = ip6_route_input_lookup(dev_net(sdev), sdev, &fl6,
 | 
						|
					     skb, flags);
 | 
						|
		skb_dst_set(skb, dst);
 | 
						|
		break;
 | 
						|
	}
 | 
						|
#endif
 | 
						|
	default:
 | 
						|
		break;
 | 
						|
	}
 | 
						|
out:
 | 
						|
	return skb;
 | 
						|
}
 | 
						|
 | 
						|
static const struct l3mdev_ops ipvl_l3mdev_ops = {
 | 
						|
	.l3mdev_l3_rcv = ipvlan_l3_rcv,
 | 
						|
};
 | 
						|
 | 
						|
static unsigned int ipvlan_nf_input(void *priv, struct sk_buff *skb,
 | 
						|
				    const struct nf_hook_state *state)
 | 
						|
{
 | 
						|
	struct ipvl_addr *addr;
 | 
						|
	unsigned int len;
 | 
						|
 | 
						|
	addr = ipvlan_skb_to_addr(skb, skb->dev);
 | 
						|
	if (!addr)
 | 
						|
		goto out;
 | 
						|
 | 
						|
	skb->dev = addr->master->dev;
 | 
						|
	skb->skb_iif = skb->dev->ifindex;
 | 
						|
#if IS_ENABLED(CONFIG_IPV6)
 | 
						|
	if (addr->atype == IPVL_IPV6)
 | 
						|
		IP6CB(skb)->iif = skb->dev->ifindex;
 | 
						|
#endif
 | 
						|
	len = skb->len + ETH_HLEN;
 | 
						|
	ipvlan_count_rx(addr->master, len, true, false);
 | 
						|
out:
 | 
						|
	return NF_ACCEPT;
 | 
						|
}
 | 
						|
 | 
						|
static const struct nf_hook_ops ipvl_nfops[] = {
 | 
						|
	{
 | 
						|
		.hook     = ipvlan_nf_input,
 | 
						|
		.pf       = NFPROTO_IPV4,
 | 
						|
		.hooknum  = NF_INET_LOCAL_IN,
 | 
						|
		.priority = INT_MAX,
 | 
						|
	},
 | 
						|
#if IS_ENABLED(CONFIG_IPV6)
 | 
						|
	{
 | 
						|
		.hook     = ipvlan_nf_input,
 | 
						|
		.pf       = NFPROTO_IPV6,
 | 
						|
		.hooknum  = NF_INET_LOCAL_IN,
 | 
						|
		.priority = INT_MAX,
 | 
						|
	},
 | 
						|
#endif
 | 
						|
};
 | 
						|
 | 
						|
static int ipvlan_register_nf_hook(struct net *net)
 | 
						|
{
 | 
						|
	struct ipvlan_netns *vnet = net_generic(net, ipvlan_netid);
 | 
						|
	int err = 0;
 | 
						|
 | 
						|
	if (!vnet->ipvl_nf_hook_refcnt) {
 | 
						|
		err = nf_register_net_hooks(net, ipvl_nfops,
 | 
						|
					    ARRAY_SIZE(ipvl_nfops));
 | 
						|
		if (!err)
 | 
						|
			vnet->ipvl_nf_hook_refcnt = 1;
 | 
						|
	} else {
 | 
						|
		vnet->ipvl_nf_hook_refcnt++;
 | 
						|
	}
 | 
						|
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
static void ipvlan_unregister_nf_hook(struct net *net)
 | 
						|
{
 | 
						|
	struct ipvlan_netns *vnet = net_generic(net, ipvlan_netid);
 | 
						|
 | 
						|
	if (WARN_ON(!vnet->ipvl_nf_hook_refcnt))
 | 
						|
		return;
 | 
						|
 | 
						|
	vnet->ipvl_nf_hook_refcnt--;
 | 
						|
	if (!vnet->ipvl_nf_hook_refcnt)
 | 
						|
		nf_unregister_net_hooks(net, ipvl_nfops,
 | 
						|
					ARRAY_SIZE(ipvl_nfops));
 | 
						|
}
 | 
						|
 | 
						|
void ipvlan_migrate_l3s_hook(struct net *oldnet, struct net *newnet)
 | 
						|
{
 | 
						|
	struct ipvlan_netns *old_vnet;
 | 
						|
 | 
						|
	ASSERT_RTNL();
 | 
						|
 | 
						|
	old_vnet = net_generic(oldnet, ipvlan_netid);
 | 
						|
	if (!old_vnet->ipvl_nf_hook_refcnt)
 | 
						|
		return;
 | 
						|
 | 
						|
	ipvlan_register_nf_hook(newnet);
 | 
						|
	ipvlan_unregister_nf_hook(oldnet);
 | 
						|
}
 | 
						|
 | 
						|
static void ipvlan_ns_exit(struct net *net)
 | 
						|
{
 | 
						|
	struct ipvlan_netns *vnet = net_generic(net, ipvlan_netid);
 | 
						|
 | 
						|
	if (WARN_ON_ONCE(vnet->ipvl_nf_hook_refcnt)) {
 | 
						|
		vnet->ipvl_nf_hook_refcnt = 0;
 | 
						|
		nf_unregister_net_hooks(net, ipvl_nfops,
 | 
						|
					ARRAY_SIZE(ipvl_nfops));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static struct pernet_operations ipvlan_net_ops = {
 | 
						|
	.id   = &ipvlan_netid,
 | 
						|
	.size = sizeof(struct ipvlan_netns),
 | 
						|
	.exit = ipvlan_ns_exit,
 | 
						|
};
 | 
						|
 | 
						|
int ipvlan_l3s_init(void)
 | 
						|
{
 | 
						|
	return register_pernet_subsys(&ipvlan_net_ops);
 | 
						|
}
 | 
						|
 | 
						|
void ipvlan_l3s_cleanup(void)
 | 
						|
{
 | 
						|
	unregister_pernet_subsys(&ipvlan_net_ops);
 | 
						|
}
 | 
						|
 | 
						|
int ipvlan_l3s_register(struct ipvl_port *port)
 | 
						|
{
 | 
						|
	struct net_device *dev = port->dev;
 | 
						|
	int ret;
 | 
						|
 | 
						|
	ASSERT_RTNL();
 | 
						|
 | 
						|
	ret = ipvlan_register_nf_hook(read_pnet(&port->pnet));
 | 
						|
	if (!ret) {
 | 
						|
		dev->l3mdev_ops = &ipvl_l3mdev_ops;
 | 
						|
		dev->priv_flags |= IFF_L3MDEV_RX_HANDLER;
 | 
						|
	}
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
void ipvlan_l3s_unregister(struct ipvl_port *port)
 | 
						|
{
 | 
						|
	struct net_device *dev = port->dev;
 | 
						|
 | 
						|
	ASSERT_RTNL();
 | 
						|
 | 
						|
	dev->priv_flags &= ~IFF_L3MDEV_RX_HANDLER;
 | 
						|
	ipvlan_unregister_nf_hook(read_pnet(&port->pnet));
 | 
						|
	dev->l3mdev_ops = NULL;
 | 
						|
}
 |