123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- // 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;
- 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;
- }
|