text: /* Copyright (c) 2014 Mahesh Bandewar <maheshb@google.com>
   *
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of the GNU General Public License as
   * published by the Free Software Foundation; either version 2 of
   * the License, or (at your option) any later version.
   */
  
  #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;
  }
  