diff options
Diffstat (limited to 'drivers/net/ipvlan/ipvlan_main.c')
| -rw-r--r-- | drivers/net/ipvlan/ipvlan_main.c | 296 |
1 files changed, 221 insertions, 75 deletions
diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c index 8b0f99300cbc..f37e3c1fd4e7 100644 --- a/drivers/net/ipvlan/ipvlan_main.c +++ b/drivers/net/ipvlan/ipvlan_main.c @@ -9,7 +9,11 @@ #include "ipvlan.h" -static u32 ipvl_nf_hook_refcnt = 0; +static unsigned int ipvlan_netid __read_mostly; + +struct ipvlan_netns { + unsigned int ipvl_nf_hook_refcnt; +}; static struct nf_hook_ops ipvl_nfops[] __read_mostly = { { @@ -35,28 +39,34 @@ static void ipvlan_adjust_mtu(struct ipvl_dev *ipvlan, struct net_device *dev) ipvlan->dev->mtu = dev->mtu; } -static int ipvlan_register_nf_hook(void) +static int ipvlan_register_nf_hook(struct net *net) { + struct ipvlan_netns *vnet = net_generic(net, ipvlan_netid); int err = 0; - if (!ipvl_nf_hook_refcnt) { - err = _nf_register_hooks(ipvl_nfops, ARRAY_SIZE(ipvl_nfops)); + if (!vnet->ipvl_nf_hook_refcnt) { + err = nf_register_net_hooks(net, ipvl_nfops, + ARRAY_SIZE(ipvl_nfops)); if (!err) - ipvl_nf_hook_refcnt = 1; + vnet->ipvl_nf_hook_refcnt = 1; } else { - ipvl_nf_hook_refcnt++; + vnet->ipvl_nf_hook_refcnt++; } return err; } -static void ipvlan_unregister_nf_hook(void) +static void ipvlan_unregister_nf_hook(struct net *net) { - WARN_ON(!ipvl_nf_hook_refcnt); + struct ipvlan_netns *vnet = net_generic(net, ipvlan_netid); + + if (WARN_ON(!vnet->ipvl_nf_hook_refcnt)) + return; - ipvl_nf_hook_refcnt--; - if (!ipvl_nf_hook_refcnt) - _nf_unregister_hooks(ipvl_nfops, ARRAY_SIZE(ipvl_nfops)); + 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) @@ -69,7 +79,7 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval) if (port->mode != nval) { if (nval == IPVLAN_MODE_L3S) { /* New mode is L3S */ - err = ipvlan_register_nf_hook(); + err = ipvlan_register_nf_hook(read_pnet(&port->pnet)); if (!err) { mdev->l3mdev_ops = &ipvl_l3mdev_ops; mdev->priv_flags |= IFF_L3MDEV_MASTER; @@ -78,7 +88,7 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval) } else if (port->mode == IPVLAN_MODE_L3S) { /* Old mode was L3S */ mdev->priv_flags &= ~IFF_L3MDEV_MASTER; - ipvlan_unregister_nf_hook(); + ipvlan_unregister_nf_hook(read_pnet(&port->pnet)); mdev->l3mdev_ops = NULL; } list_for_each_entry(ipvlan, &port->ipvlans, pnode) { @@ -102,8 +112,8 @@ static int ipvlan_port_create(struct net_device *dev) return -EINVAL; } - if (netif_is_macvlan_port(dev)) { - netdev_err(dev, "Master is a macvlan port.\n"); + if (netdev_is_rx_handler_busy(dev)) { + netdev_err(dev, "Device is already in use.\n"); return -EBUSY; } @@ -111,6 +121,7 @@ static int ipvlan_port_create(struct net_device *dev) if (!port) return -ENOMEM; + write_pnet(&port->pnet, dev_net(dev)); port->dev = dev; port->mode = IPVLAN_MODE_L3; INIT_LIST_HEAD(&port->ipvlans); @@ -119,6 +130,8 @@ static int ipvlan_port_create(struct net_device *dev) skb_queue_head_init(&port->backlog); INIT_WORK(&port->wq, ipvlan_process_multicast); + ida_init(&port->ida); + port->dev_id_start = 1; err = netdev_rx_handler_register(dev, ipvlan_handle_frame, port); if (err) @@ -140,7 +153,7 @@ static void ipvlan_port_destroy(struct net_device *dev) dev->priv_flags &= ~IFF_IPVLAN_MASTER; if (port->mode == IPVLAN_MODE_L3S) { dev->priv_flags &= ~IFF_L3MDEV_MASTER; - ipvlan_unregister_nf_hook(); + ipvlan_unregister_nf_hook(dev_net(dev)); dev->l3mdev_ops = NULL; } netdev_rx_handler_unregister(dev); @@ -150,6 +163,7 @@ static void ipvlan_port_destroy(struct net_device *dev) dev_put(skb->dev); kfree_skb(skb); } + ida_destroy(&port->ida); kfree(port); } @@ -301,8 +315,8 @@ static void ipvlan_set_multicast_mac_filter(struct net_device *dev) dev_mc_sync(ipvlan->phy_dev, dev); } -static struct rtnl_link_stats64 *ipvlan_get_stats64(struct net_device *dev, - struct rtnl_link_stats64 *s) +static void ipvlan_get_stats64(struct net_device *dev, + struct rtnl_link_stats64 *s) { struct ipvl_dev *ipvlan = netdev_priv(dev); @@ -339,7 +353,6 @@ static struct rtnl_link_stats64 *ipvlan_get_stats64(struct net_device *dev, s->rx_dropped = rx_errs; s->tx_dropped = tx_drps; } - return s; } static int ipvlan_vlan_rx_add_vid(struct net_device *dev, __be16 proto, u16 vid) @@ -442,7 +455,8 @@ static const struct ethtool_ops ipvlan_ethtool_ops = { }; static int ipvlan_nl_changelink(struct net_device *dev, - struct nlattr *tb[], struct nlattr *data[]) + struct nlattr *tb[], struct nlattr *data[], + struct netlink_ext_ack *extack) { struct ipvl_dev *ipvlan = netdev_priv(dev); struct ipvl_port *port = ipvlan_port_get_rtnl(ipvlan->phy_dev); @@ -463,7 +477,8 @@ static size_t ipvlan_nl_getsize(const struct net_device *dev) ); } -static int ipvlan_nl_validate(struct nlattr *tb[], struct nlattr *data[]) +static int ipvlan_nl_validate(struct nlattr *tb[], struct nlattr *data[], + struct netlink_ext_ack *extack) { if (data && data[IFLA_IPVLAN_MODE]) { u16 mode = nla_get_u16(data[IFLA_IPVLAN_MODE]); @@ -494,8 +509,9 @@ err: return ret; } -static int ipvlan_link_new(struct net *src_net, struct net_device *dev, - struct nlattr *tb[], struct nlattr *data[]) +int ipvlan_link_new(struct net *src_net, struct net_device *dev, + struct nlattr *tb[], struct nlattr *data[], + struct netlink_ext_ack *extack) { struct ipvl_dev *ipvlan = netdev_priv(dev); struct ipvl_port *port; @@ -533,6 +549,29 @@ static int ipvlan_link_new(struct net *src_net, struct net_device *dev, ipvlan_adjust_mtu(ipvlan, phy_dev); INIT_LIST_HEAD(&ipvlan->addrs); + /* If the port-id base is at the MAX value, then wrap it around and + * begin from 0x1 again. This may be due to a busy system where lots + * of slaves are getting created and deleted. + */ + if (port->dev_id_start == 0xFFFE) + port->dev_id_start = 0x1; + + /* Since L2 address is shared among all IPvlan slaves including + * master, use unique 16 bit dev-ids to diffentiate among them. + * Assign IDs between 0x1 and 0xFFFE (used by the master) to each + * slave link [see addrconf_ifid_eui48()]. + */ + err = ida_simple_get(&port->ida, port->dev_id_start, 0xFFFE, + GFP_KERNEL); + if (err < 0) + err = ida_simple_get(&port->ida, 0x1, port->dev_id_start, + GFP_KERNEL); + if (err < 0) + goto destroy_ipvlan_port; + dev->dev_id = err; + /* Increment id-base to the next slot for the future assignment */ + port->dev_id_start = err + 1; + /* TODO Probably put random address here to be presented to the * world but keep using the physical-dev address for the outgoing * packets. @@ -543,7 +582,7 @@ static int ipvlan_link_new(struct net *src_net, struct net_device *dev, err = register_netdevice(dev); if (err < 0) - goto destroy_ipvlan_port; + goto remove_ida; err = netdev_upper_dev_link(phy_dev, dev); if (err) { @@ -562,13 +601,16 @@ unlink_netdev: netdev_upper_dev_unlink(phy_dev, dev); unregister_netdev: unregister_netdevice(dev); +remove_ida: + ida_simple_remove(&port->ida, dev->dev_id); destroy_ipvlan_port: if (create) ipvlan_port_destroy(phy_dev); return err; } +EXPORT_SYMBOL_GPL(ipvlan_link_new); -static void ipvlan_link_delete(struct net_device *dev, struct list_head *head) +void ipvlan_link_delete(struct net_device *dev, struct list_head *head) { struct ipvl_dev *ipvlan = netdev_priv(dev); struct ipvl_addr *addr, *next; @@ -579,22 +621,25 @@ static void ipvlan_link_delete(struct net_device *dev, struct list_head *head) kfree_rcu(addr, rcu); } + ida_simple_remove(&ipvlan->port->ida, dev->dev_id); list_del_rcu(&ipvlan->pnode); unregister_netdevice_queue(dev, head); netdev_upper_dev_unlink(ipvlan->phy_dev, dev); } +EXPORT_SYMBOL_GPL(ipvlan_link_delete); -static void ipvlan_link_setup(struct net_device *dev) +void ipvlan_link_setup(struct net_device *dev) { ether_setup(dev); dev->priv_flags &= ~(IFF_XMIT_DST_RELEASE | IFF_TX_SKB_SHARING); dev->priv_flags |= IFF_UNICAST_FLT | IFF_NO_QUEUE; dev->netdev_ops = &ipvlan_netdev_ops; - dev->destructor = free_netdev; + dev->needs_free_netdev = true; dev->header_ops = &ipvlan_header_ops; dev->ethtool_ops = &ipvlan_ethtool_ops; } +EXPORT_SYMBOL_GPL(ipvlan_link_setup); static const struct nla_policy ipvlan_nl_policy[IFLA_IPVLAN_MAX + 1] = { @@ -605,22 +650,22 @@ static struct rtnl_link_ops ipvlan_link_ops = { .kind = "ipvlan", .priv_size = sizeof(struct ipvl_dev), - .get_size = ipvlan_nl_getsize, - .policy = ipvlan_nl_policy, - .validate = ipvlan_nl_validate, - .fill_info = ipvlan_nl_fillinfo, - .changelink = ipvlan_nl_changelink, - .maxtype = IFLA_IPVLAN_MAX, - .setup = ipvlan_link_setup, .newlink = ipvlan_link_new, .dellink = ipvlan_link_delete, }; -static int ipvlan_link_register(struct rtnl_link_ops *ops) +int ipvlan_link_register(struct rtnl_link_ops *ops) { + ops->get_size = ipvlan_nl_getsize; + ops->policy = ipvlan_nl_policy; + ops->validate = ipvlan_nl_validate; + ops->fill_info = ipvlan_nl_fillinfo; + ops->changelink = ipvlan_nl_changelink; + ops->maxtype = IFLA_IPVLAN_MAX; return rtnl_link_register(ops); } +EXPORT_SYMBOL_GPL(ipvlan_link_register); static int ipvlan_device_event(struct notifier_block *unused, unsigned long event, void *ptr) @@ -642,6 +687,24 @@ static int ipvlan_device_event(struct notifier_block *unused, ipvlan->dev); break; + 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)) + break; + + 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); + break; + } case NETDEV_UNREGISTER: if (dev->reg_state != NETREG_UNREGISTERING) break; @@ -674,23 +737,22 @@ static int ipvlan_device_event(struct notifier_block *unused, return NOTIFY_DONE; } -static int ipvlan_add_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr) +static int ipvlan_add_addr(struct ipvl_dev *ipvlan, void *iaddr, bool is_v6) { struct ipvl_addr *addr; - if (ipvlan_addr_busy(ipvlan->port, ip6_addr, true)) { - netif_err(ipvlan, ifup, ipvlan->dev, - "Failed to add IPv6=%pI6c addr for %s intf\n", - ip6_addr, ipvlan->dev->name); - return -EINVAL; - } addr = kzalloc(sizeof(struct ipvl_addr), GFP_ATOMIC); if (!addr) return -ENOMEM; addr->master = ipvlan; - memcpy(&addr->ip6addr, ip6_addr, sizeof(struct in6_addr)); - addr->atype = IPVL_IPV6; + if (is_v6) { + memcpy(&addr->ip6addr, iaddr, sizeof(struct in6_addr)); + addr->atype = IPVL_IPV6; + } else { + memcpy(&addr->ip4addr, iaddr, sizeof(struct in_addr)); + addr->atype = IPVL_IPV4; + } list_add_tail(&addr->anode, &ipvlan->addrs); /* If the interface is not up, the address will be added to the hash @@ -702,11 +764,11 @@ static int ipvlan_add_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr) return 0; } -static void ipvlan_del_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr) +static void ipvlan_del_addr(struct ipvl_dev *ipvlan, void *iaddr, bool is_v6) { struct ipvl_addr *addr; - addr = ipvlan_find_addr(ipvlan, ip6_addr, true); + addr = ipvlan_find_addr(ipvlan, iaddr, is_v6); if (!addr) return; @@ -717,6 +779,23 @@ static void ipvlan_del_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr) return; } +static int ipvlan_add_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr) +{ + if (ipvlan_addr_busy(ipvlan->port, ip6_addr, true)) { + netif_err(ipvlan, ifup, ipvlan->dev, + "Failed to add IPv6=%pI6c addr for %s intf\n", + ip6_addr, ipvlan->dev->name); + return -EINVAL; + } + + return ipvlan_add_addr(ipvlan, ip6_addr, true); +} + +static void ipvlan_del_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr) +{ + return ipvlan_del_addr(ipvlan, ip6_addr, true); +} + static int ipvlan_addr6_event(struct notifier_block *unused, unsigned long event, void *ptr) { @@ -748,47 +827,48 @@ static int ipvlan_addr6_event(struct notifier_block *unused, return NOTIFY_OK; } -static int ipvlan_add_addr4(struct ipvl_dev *ipvlan, struct in_addr *ip4_addr) +static int ipvlan_addr6_validator_event(struct notifier_block *unused, + unsigned long event, void *ptr) { - struct ipvl_addr *addr; + struct in6_validator_info *i6vi = (struct in6_validator_info *)ptr; + struct net_device *dev = (struct net_device *)i6vi->i6vi_dev->dev; + struct ipvl_dev *ipvlan = netdev_priv(dev); + + /* FIXME IPv6 autoconf calls us from bh without RTNL */ + if (in_softirq()) + return NOTIFY_DONE; + + if (!netif_is_ipvlan(dev)) + return NOTIFY_DONE; + + if (!ipvlan || !ipvlan->port) + return NOTIFY_DONE; + + switch (event) { + case NETDEV_UP: + if (ipvlan_addr_busy(ipvlan->port, &i6vi->i6vi_addr, true)) + return notifier_from_errno(-EADDRINUSE); + break; + } + + return NOTIFY_OK; +} +static int ipvlan_add_addr4(struct ipvl_dev *ipvlan, struct in_addr *ip4_addr) +{ if (ipvlan_addr_busy(ipvlan->port, ip4_addr, false)) { netif_err(ipvlan, ifup, ipvlan->dev, "Failed to add IPv4=%pI4 on %s intf.\n", ip4_addr, ipvlan->dev->name); return -EINVAL; } - addr = kzalloc(sizeof(struct ipvl_addr), GFP_KERNEL); - if (!addr) - return -ENOMEM; - - addr->master = ipvlan; - memcpy(&addr->ip4addr, ip4_addr, sizeof(struct in_addr)); - addr->atype = IPVL_IPV4; - list_add_tail(&addr->anode, &ipvlan->addrs); - /* If the interface is not up, the address will be added to the hash - * list by ipvlan_open. - */ - if (netif_running(ipvlan->dev)) - ipvlan_ht_addr_add(ipvlan, addr); - - return 0; + return ipvlan_add_addr(ipvlan, ip4_addr, false); } static void ipvlan_del_addr4(struct ipvl_dev *ipvlan, struct in_addr *ip4_addr) { - struct ipvl_addr *addr; - - addr = ipvlan_find_addr(ipvlan, ip4_addr, false); - if (!addr) - return; - - ipvlan_ht_addr_del(addr); - list_del(&addr->anode); - kfree_rcu(addr, rcu); - - return; + return ipvlan_del_addr(ipvlan, ip4_addr, false); } static int ipvlan_addr4_event(struct notifier_block *unused, @@ -821,10 +901,37 @@ static int ipvlan_addr4_event(struct notifier_block *unused, return NOTIFY_OK; } +static int ipvlan_addr4_validator_event(struct notifier_block *unused, + unsigned long event, void *ptr) +{ + struct in_validator_info *ivi = (struct in_validator_info *)ptr; + struct net_device *dev = (struct net_device *)ivi->ivi_dev->dev; + struct ipvl_dev *ipvlan = netdev_priv(dev); + + if (!netif_is_ipvlan(dev)) + return NOTIFY_DONE; + + if (!ipvlan || !ipvlan->port) + return NOTIFY_DONE; + + switch (event) { + case NETDEV_UP: + if (ipvlan_addr_busy(ipvlan->port, &ivi->ivi_addr, false)) + return notifier_from_errno(-EADDRINUSE); + break; + } + + return NOTIFY_OK; +} + static struct notifier_block ipvlan_addr4_notifier_block __read_mostly = { .notifier_call = ipvlan_addr4_event, }; +static struct notifier_block ipvlan_addr4_vtor_notifier_block __read_mostly = { + .notifier_call = ipvlan_addr4_validator_event, +}; + static struct notifier_block ipvlan_notifier_block __read_mostly = { .notifier_call = ipvlan_device_event, }; @@ -833,6 +940,27 @@ static struct notifier_block ipvlan_addr6_notifier_block __read_mostly = { .notifier_call = ipvlan_addr6_event, }; +static struct notifier_block ipvlan_addr6_vtor_notifier_block __read_mostly = { + .notifier_call = ipvlan_addr6_validator_event, +}; + +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; @@ -840,16 +968,29 @@ static int __init ipvlan_init_module(void) ipvlan_init_secret(); register_netdevice_notifier(&ipvlan_notifier_block); register_inet6addr_notifier(&ipvlan_addr6_notifier_block); + register_inet6addr_validator_notifier( + &ipvlan_addr6_vtor_notifier_block); register_inetaddr_notifier(&ipvlan_addr4_notifier_block); + register_inetaddr_validator_notifier(&ipvlan_addr4_vtor_notifier_block); - err = ipvlan_link_register(&ipvlan_link_ops); + err = register_pernet_subsys(&ipvlan_net_ops); if (err < 0) goto error; + err = ipvlan_link_register(&ipvlan_link_ops); + if (err < 0) { + unregister_pernet_subsys(&ipvlan_net_ops); + goto error; + } + return 0; error: unregister_inetaddr_notifier(&ipvlan_addr4_notifier_block); + unregister_inetaddr_validator_notifier( + &ipvlan_addr4_vtor_notifier_block); unregister_inet6addr_notifier(&ipvlan_addr6_notifier_block); + unregister_inet6addr_validator_notifier( + &ipvlan_addr6_vtor_notifier_block); unregister_netdevice_notifier(&ipvlan_notifier_block); return err; } @@ -857,9 +998,14 @@ error: static void __exit ipvlan_cleanup_module(void) { rtnl_link_unregister(&ipvlan_link_ops); + unregister_pernet_subsys(&ipvlan_net_ops); unregister_netdevice_notifier(&ipvlan_notifier_block); unregister_inetaddr_notifier(&ipvlan_addr4_notifier_block); + unregister_inetaddr_validator_notifier( + &ipvlan_addr4_vtor_notifier_block); unregister_inet6addr_notifier(&ipvlan_addr6_notifier_block); + unregister_inet6addr_validator_notifier( + &ipvlan_addr6_vtor_notifier_block); } module_init(ipvlan_init_module); |