|
@@ -9,24 +9,87 @@
|
|
|
|
|
|
#include "ipvlan.h"
|
|
|
|
|
|
+static u32 ipvl_nf_hook_refcnt = 0;
|
|
|
+
|
|
|
+static struct nf_hook_ops ipvl_nfops[] __read_mostly = {
|
|
|
+ {
|
|
|
+ .hook = ipvlan_nf_input,
|
|
|
+ .pf = NFPROTO_IPV4,
|
|
|
+ .hooknum = NF_INET_LOCAL_IN,
|
|
|
+ .priority = INT_MAX,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .hook = ipvlan_nf_input,
|
|
|
+ .pf = NFPROTO_IPV6,
|
|
|
+ .hooknum = NF_INET_LOCAL_IN,
|
|
|
+ .priority = INT_MAX,
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+static struct l3mdev_ops ipvl_l3mdev_ops __read_mostly = {
|
|
|
+ .l3mdev_l3_rcv = ipvlan_l3_rcv,
|
|
|
+};
|
|
|
+
|
|
|
static void ipvlan_adjust_mtu(struct ipvl_dev *ipvlan, struct net_device *dev)
|
|
|
{
|
|
|
ipvlan->dev->mtu = dev->mtu - ipvlan->mtu_adj;
|
|
|
}
|
|
|
|
|
|
-static void ipvlan_set_port_mode(struct ipvl_port *port, u16 nval)
|
|
|
+static int ipvlan_register_nf_hook(void)
|
|
|
+{
|
|
|
+ int err = 0;
|
|
|
+
|
|
|
+ if (!ipvl_nf_hook_refcnt) {
|
|
|
+ err = _nf_register_hooks(ipvl_nfops, ARRAY_SIZE(ipvl_nfops));
|
|
|
+ if (!err)
|
|
|
+ ipvl_nf_hook_refcnt = 1;
|
|
|
+ } else {
|
|
|
+ ipvl_nf_hook_refcnt++;
|
|
|
+ }
|
|
|
+
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static void ipvlan_unregister_nf_hook(void)
|
|
|
+{
|
|
|
+ WARN_ON(!ipvl_nf_hook_refcnt);
|
|
|
+
|
|
|
+ ipvl_nf_hook_refcnt--;
|
|
|
+ if (!ipvl_nf_hook_refcnt)
|
|
|
+ _nf_unregister_hooks(ipvl_nfops, ARRAY_SIZE(ipvl_nfops));
|
|
|
+}
|
|
|
+
|
|
|
+static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval)
|
|
|
{
|
|
|
struct ipvl_dev *ipvlan;
|
|
|
+ struct net_device *mdev = port->dev;
|
|
|
+ int err = 0;
|
|
|
|
|
|
+ ASSERT_RTNL();
|
|
|
if (port->mode != nval) {
|
|
|
+ if (nval == IPVLAN_MODE_L3S) {
|
|
|
+ /* New mode is L3S */
|
|
|
+ err = ipvlan_register_nf_hook();
|
|
|
+ if (!err) {
|
|
|
+ mdev->l3mdev_ops = &ipvl_l3mdev_ops;
|
|
|
+ mdev->priv_flags |= IFF_L3MDEV_MASTER;
|
|
|
+ } else
|
|
|
+ return err;
|
|
|
+ } else if (port->mode == IPVLAN_MODE_L3S) {
|
|
|
+ /* Old mode was L3S */
|
|
|
+ mdev->priv_flags &= ~IFF_L3MDEV_MASTER;
|
|
|
+ ipvlan_unregister_nf_hook();
|
|
|
+ mdev->l3mdev_ops = NULL;
|
|
|
+ }
|
|
|
list_for_each_entry(ipvlan, &port->ipvlans, pnode) {
|
|
|
- if (nval == IPVLAN_MODE_L3)
|
|
|
+ if (nval == IPVLAN_MODE_L3 || nval == IPVLAN_MODE_L3S)
|
|
|
ipvlan->dev->flags |= IFF_NOARP;
|
|
|
else
|
|
|
ipvlan->dev->flags &= ~IFF_NOARP;
|
|
|
}
|
|
|
port->mode = nval;
|
|
|
}
|
|
|
+ return err;
|
|
|
}
|
|
|
|
|
|
static int ipvlan_port_create(struct net_device *dev)
|
|
@@ -74,6 +137,11 @@ static void ipvlan_port_destroy(struct net_device *dev)
|
|
|
struct ipvl_port *port = ipvlan_port_get_rtnl(dev);
|
|
|
|
|
|
dev->priv_flags &= ~IFF_IPVLAN_MASTER;
|
|
|
+ if (port->mode == IPVLAN_MODE_L3S) {
|
|
|
+ dev->priv_flags &= ~IFF_L3MDEV_MASTER;
|
|
|
+ ipvlan_unregister_nf_hook();
|
|
|
+ dev->l3mdev_ops = NULL;
|
|
|
+ }
|
|
|
netdev_rx_handler_unregister(dev);
|
|
|
cancel_work_sync(&port->wq);
|
|
|
__skb_queue_purge(&port->backlog);
|
|
@@ -132,7 +200,8 @@ static int ipvlan_open(struct net_device *dev)
|
|
|
struct net_device *phy_dev = ipvlan->phy_dev;
|
|
|
struct ipvl_addr *addr;
|
|
|
|
|
|
- if (ipvlan->port->mode == IPVLAN_MODE_L3)
|
|
|
+ if (ipvlan->port->mode == IPVLAN_MODE_L3 ||
|
|
|
+ ipvlan->port->mode == IPVLAN_MODE_L3S)
|
|
|
dev->flags |= IFF_NOARP;
|
|
|
else
|
|
|
dev->flags &= ~IFF_NOARP;
|
|
@@ -372,13 +441,14 @@ static int ipvlan_nl_changelink(struct net_device *dev,
|
|
|
{
|
|
|
struct ipvl_dev *ipvlan = netdev_priv(dev);
|
|
|
struct ipvl_port *port = ipvlan_port_get_rtnl(ipvlan->phy_dev);
|
|
|
+ int err = 0;
|
|
|
|
|
|
if (data && data[IFLA_IPVLAN_MODE]) {
|
|
|
u16 nmode = nla_get_u16(data[IFLA_IPVLAN_MODE]);
|
|
|
|
|
|
- ipvlan_set_port_mode(port, nmode);
|
|
|
+ err = ipvlan_set_port_mode(port, nmode);
|
|
|
}
|
|
|
- return 0;
|
|
|
+ return err;
|
|
|
}
|
|
|
|
|
|
static size_t ipvlan_nl_getsize(const struct net_device *dev)
|
|
@@ -473,10 +543,13 @@ static int ipvlan_link_new(struct net *src_net, struct net_device *dev,
|
|
|
unregister_netdevice(dev);
|
|
|
return err;
|
|
|
}
|
|
|
+ err = ipvlan_set_port_mode(port, mode);
|
|
|
+ if (err) {
|
|
|
+ unregister_netdevice(dev);
|
|
|
+ return err;
|
|
|
+ }
|
|
|
|
|
|
list_add_tail_rcu(&ipvlan->pnode, &port->ipvlans);
|
|
|
- ipvlan_set_port_mode(port, mode);
|
|
|
-
|
|
|
netif_stacked_transfer_operstate(phy_dev, dev);
|
|
|
return 0;
|
|
|
}
|