To compile this driver as a module, choose M here: the module
          will be called macvtap.
 
+config IPVLAN_L3S
+       depends on NETFILTER
+       def_bool y
+       select NET_L3_MASTER_DEV
 
 config IPVLAN
     tristate "IP-VLAN support"
     depends on INET
     depends on IPV6 || !IPV6
-    depends on NETFILTER
-    select NET_L3_MASTER_DEV
     ---help---
       This allows one to create virtual devices off of a main interface
       and packets will be delivered based on the dest L3 (IPv6/IPv4 addr)
 
 obj-$(CONFIG_IPVLAN) += ipvlan.o
 obj-$(CONFIG_IPVTAP) += ipvtap.o
 
-ipvlan-objs := ipvlan_core.o ipvlan_main.o
+ipvlan-objs-$(CONFIG_IPVLAN_L3S) += ipvlan_l3s.o
+ipvlan-objs := ipvlan_core.o ipvlan_main.o $(ipvlan-objs-y)
 
                                   const void *iaddr, bool is_v6);
 bool ipvlan_addr_busy(struct ipvl_port *port, void *iaddr, bool is_v6);
 void ipvlan_ht_addr_del(struct ipvl_addr *addr);
-struct sk_buff *ipvlan_l3_rcv(struct net_device *dev, struct sk_buff *skb,
-                             u16 proto);
-unsigned int ipvlan_nf_input(void *priv, struct sk_buff *skb,
-                            const struct nf_hook_state *state);
+struct ipvl_addr *ipvlan_addr_lookup(struct ipvl_port *port, void *lyr3h,
+                                    int addr_type, bool use_dest);
+void *ipvlan_get_L3_hdr(struct ipvl_port *port, struct sk_buff *skb, int *type);
 void ipvlan_count_rx(const struct ipvl_dev *ipvlan,
                     unsigned int len, bool success, bool mcast);
 int ipvlan_link_new(struct net *src_net, struct net_device *dev,
 void ipvlan_link_delete(struct net_device *dev, struct list_head *head);
 void ipvlan_link_setup(struct net_device *dev);
 int ipvlan_link_register(struct rtnl_link_ops *ops);
+#ifdef CONFIG_IPVLAN_L3S
+int ipvlan_l3s_register(struct ipvl_port *port);
+void ipvlan_l3s_unregister(struct ipvl_port *port);
+void ipvlan_migrate_l3s_hook(struct net *oldnet, struct net *newnet);
+int ipvlan_l3s_init(void);
+void ipvlan_l3s_cleanup(void);
+#else
+static inline int ipvlan_l3s_register(struct ipvl_port *port)
+{
+       return -ENOTSUPP;
+}
+
+static inline void ipvlan_l3s_unregister(struct ipvl_port *port)
+{
+}
+
+static inline void ipvlan_migrate_l3s_hook(struct net *oldnet,
+                                          struct net *newnet)
+{
+}
+
+static inline int ipvlan_l3s_init(void)
+{
+       return 0;
+}
+
+static inline void ipvlan_l3s_cleanup(void)
+{
+}
+#endif /* CONFIG_IPVLAN_L3S */
 
 static inline bool netif_is_ipvlan_port(const struct net_device *dev)
 {
 
        return ret;
 }
 
-static void *ipvlan_get_L3_hdr(struct ipvl_port *port, struct sk_buff *skb, int *type)
+void *ipvlan_get_L3_hdr(struct ipvl_port *port, struct sk_buff *skb, int *type)
 {
        void *lyr3h = NULL;
 
        return ret;
 }
 
-static struct ipvl_addr *ipvlan_addr_lookup(struct ipvl_port *port,
-                                           void *lyr3h, int addr_type,
-                                           bool use_dest)
+struct ipvl_addr *ipvlan_addr_lookup(struct ipvl_port *port, void *lyr3h,
+                                    int addr_type, bool use_dest)
 {
        struct ipvl_addr *addr = NULL;
 
        case IPVLAN_MODE_L2:
                return ipvlan_xmit_mode_l2(skb, dev);
        case IPVLAN_MODE_L3:
+#ifdef CONFIG_IPVLAN_L3S
        case IPVLAN_MODE_L3S:
+#endif
                return ipvlan_xmit_mode_l3(skb, dev);
        }
 
                return ipvlan_handle_mode_l2(pskb, port);
        case IPVLAN_MODE_L3:
                return ipvlan_handle_mode_l3(pskb, port);
+#ifdef CONFIG_IPVLAN_L3S
        case IPVLAN_MODE_L3S:
                return RX_HANDLER_PASS;
+#endif
        }
 
        /* Should not reach here */
        kfree_skb(skb);
        return RX_HANDLER_CONSUMED;
 }
-
-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;
-       void *lyr3h;
-       int addr_type;
-
-       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;
-}
-
-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:
-       {
-               int err;
-               struct iphdr *ip4h = ip_hdr(skb);
-
-               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;
-}
-
-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;
-}
 
--- /dev/null
+/* 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_MASTER;
+       }
+
+       return ret;
+}
+
+void ipvlan_l3s_unregister(struct ipvl_port *port)
+{
+       struct net_device *dev = port->dev;
+
+       ASSERT_RTNL();
+
+       dev->priv_flags &= ~IFF_L3MDEV_MASTER;
+       ipvlan_unregister_nf_hook(read_pnet(&port->pnet));
+       dev->l3mdev_ops = NULL;
+}
 
 
 #include "ipvlan.h"
 
-static unsigned int ipvlan_netid __read_mostly;
-
-struct ipvlan_netns {
-       unsigned int ipvl_nf_hook_refcnt;
-};
-
-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 const struct l3mdev_ops ipvl_l3mdev_ops = {
-       .l3mdev_l3_rcv = ipvlan_l3_rcv,
-};
-
-static void ipvlan_adjust_mtu(struct ipvl_dev *ipvlan, struct net_device *dev)
-{
-       ipvlan->dev->mtu = dev->mtu;
-}
-
-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));
-}
-
 static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval,
                                struct netlink_ext_ack *extack)
 {
        struct ipvl_dev *ipvlan;
-       struct net_device *mdev = port->dev;
        unsigned int flags;
        int err;
 
                }
                if (nval == IPVLAN_MODE_L3S) {
                        /* New mode is L3S */
-                       err = ipvlan_register_nf_hook(read_pnet(&port->pnet));
-                       if (!err) {
-                               mdev->l3mdev_ops = &ipvl_l3mdev_ops;
-                               mdev->priv_flags |= IFF_L3MDEV_MASTER;
-                       } else
+                       err = ipvlan_l3s_register(port);
+                       if (err)
                                goto fail;
                } else if (port->mode == IPVLAN_MODE_L3S) {
                        /* Old mode was L3S */
-                       mdev->priv_flags &= ~IFF_L3MDEV_MASTER;
-                       ipvlan_unregister_nf_hook(read_pnet(&port->pnet));
-                       mdev->l3mdev_ops = NULL;
+                       ipvlan_l3s_unregister(port);
                }
                port->mode = nval;
        }
        struct ipvl_port *port = ipvlan_port_get_rtnl(dev);
        struct sk_buff *skb;
 
-       if (port->mode == IPVLAN_MODE_L3S) {
-               dev->priv_flags &= ~IFF_L3MDEV_MASTER;
-               ipvlan_unregister_nf_hook(dev_net(dev));
-               dev->l3mdev_ops = NULL;
-       }
+       if (port->mode == IPVLAN_MODE_L3S)
+               ipvlan_l3s_unregister(port);
        netdev_rx_handler_unregister(dev);
        cancel_work_sync(&port->wq);
        while ((skb = __skb_dequeue(&port->backlog)) != NULL) {
        .cache_update   = eth_header_cache_update,
 };
 
+static void ipvlan_adjust_mtu(struct ipvl_dev *ipvlan, struct net_device *dev)
+{
+       ipvlan->dev->mtu = dev->mtu;
+}
+
 static bool netif_is_ipvlan(const struct net_device *dev)
 {
        /* both ipvlan and ipvtap devices use the same netdev_ops */
 
        case NETDEV_REGISTER: {
                struct net *oldnet, *newnet = dev_net(dev);
-               struct ipvlan_netns *old_vnet;
 
                oldnet = read_pnet(&port->pnet);
                if (net_eq(newnet, oldnet))
 
                write_pnet(&port->pnet, newnet);
 
-               old_vnet = net_generic(oldnet, ipvlan_netid);
-               if (!old_vnet->ipvl_nf_hook_refcnt)
-                       break;
-
-               ipvlan_register_nf_hook(newnet);
-               ipvlan_unregister_nf_hook(oldnet);
+               ipvlan_migrate_l3s_hook(oldnet, newnet);
                break;
        }
        case NETDEV_UNREGISTER:
 };
 #endif
 
-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,
-};
-
 static int __init ipvlan_init_module(void)
 {
        int err;
        register_inetaddr_notifier(&ipvlan_addr4_notifier_block);
        register_inetaddr_validator_notifier(&ipvlan_addr4_vtor_notifier_block);
 
-       err = register_pernet_subsys(&ipvlan_net_ops);
+       err = ipvlan_l3s_init();
        if (err < 0)
                goto error;
 
        err = ipvlan_link_register(&ipvlan_link_ops);
        if (err < 0) {
-               unregister_pernet_subsys(&ipvlan_net_ops);
+               ipvlan_l3s_cleanup();
                goto error;
        }
 
 static void __exit ipvlan_cleanup_module(void)
 {
        rtnl_link_unregister(&ipvlan_link_ops);
-       unregister_pernet_subsys(&ipvlan_net_ops);
+       ipvlan_l3s_cleanup();
        unregister_netdevice_notifier(&ipvlan_notifier_block);
        unregister_inetaddr_notifier(&ipvlan_addr4_notifier_block);
        unregister_inetaddr_validator_notifier(