Remember all ip-addresses on main iface and check in ipvlan_addr_busy() that addr is not used on main. Store IPs in separate list. Remember IP address at port create and listen for addr-change events. Don't allow to configure addresses on children with addresses of main. In learning mode, child may not learn the address if it is used on main. Signed-off-by: Dmitry Skorodumov --- drivers/net/ipvlan/ipvlan.h | 13 ++ drivers/net/ipvlan/ipvlan_core.c | 39 ++++-- drivers/net/ipvlan/ipvlan_main.c | 196 +++++++++++++++++++++++++++++++ 3 files changed, 235 insertions(+), 13 deletions(-) diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h index 0ab1797c6128..faba1308c135 100644 --- a/drivers/net/ipvlan/ipvlan.h +++ b/drivers/net/ipvlan/ipvlan.h @@ -89,10 +89,21 @@ struct ipvl_addr { struct rcu_head rcu; }; +struct ipvl_port_addr { + union { + struct in6_addr ip6; + struct in_addr ip4; + } ipu; + ipvl_hdr_type atype; + struct list_head anode; + struct rcu_head rcu; +}; + struct ipvl_port { struct net_device *dev; possible_net_t pnet; struct hlist_head hlhead[IPVLAN_HASH_SIZE]; + struct list_head port_addrs; /* addresses of main iface.*/ spinlock_t addrs_lock; /* guards hash-table and addrs */ struct list_head ipvlans; struct packet_type ipvl_ptype; @@ -199,6 +210,8 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params, 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); +struct ipvl_port_addr *ipvlan_port_find_addr(struct ipvl_port *port, + const void *iaddr, bool is_v6); #ifdef CONFIG_IPVLAN_L3S int ipvlan_l3s_register(struct ipvl_port *port); void ipvlan_l3s_unregister(struct ipvl_port *port); diff --git a/drivers/net/ipvlan/ipvlan_core.c b/drivers/net/ipvlan/ipvlan_core.c index a952a257a791..cba1378cc920 100644 --- a/drivers/net/ipvlan/ipvlan_core.c +++ b/drivers/net/ipvlan/ipvlan_core.c @@ -133,6 +133,8 @@ bool ipvlan_addr_busy(struct ipvl_port *port, void *iaddr, bool is_v6) break; } } + if (!ret) + ret = !!ipvlan_port_find_addr(port, iaddr, is_v6); rcu_read_unlock(); return ret; } @@ -469,17 +471,21 @@ static bool is_ipv6_usable(const struct in6_addr *addr) !ipv6_addr_any(addr); } -static void __ipvlan_addr_learn(struct ipvl_dev *ipvlan, void *addr, bool is_v6, - const u8 *hwaddr) +static int __ipvlan_addr_learn(struct ipvl_dev *ipvlan, void *addr, bool is_v6, + const u8 *hwaddr) { const ipvl_hdr_type atype = is_v6 ? IPVL_IPV6 : IPVL_IPV4; struct ipvl_addr *ipvladdr, *oldest = NULL; unsigned int naddrs = 0; + int ret = -1; spin_lock_bh(&ipvlan->port->addrs_lock); + if (ipvlan_port_find_addr(ipvlan->port, addr, is_v6)) + goto out_unlock; /* used by main. */ + if (ipvlan_addr_busy(ipvlan->port, addr, is_v6)) - goto out_unlock; + goto out_unlock; /* used by other ipvlan. */ list_for_each_entry_rcu(ipvladdr, &ipvlan->addrs, anode) { if (ipvladdr->atype != atype) @@ -497,15 +503,19 @@ static void __ipvlan_addr_learn(struct ipvl_dev *ipvlan, void *addr, bool is_v6, } ipvlan_add_addr(ipvlan, addr, is_v6, hwaddr); + ret = 0; out_unlock: spin_unlock_bh(&ipvlan->port->addrs_lock); if (oldest) kfree_rcu(oldest, rcu); + + return ret; } -static void ipvlan_addr_learn(struct ipvl_dev *ipvlan, void *lyr3h, - int addr_type, const u8 *hwaddr) +/* return -1 if frame should be dropped. */ +static int ipvlan_addr_learn(struct ipvl_dev *ipvlan, void *lyr3h, + int addr_type, const u8 *hwaddr) { struct ipvl_addr *ipvladdr; void *addr = NULL; @@ -519,7 +529,7 @@ static void ipvlan_addr_learn(struct ipvl_dev *ipvlan, void *lyr3h, ip6h = (struct ipv6hdr *)lyr3h; if (!is_ipv6_usable(&ip6h->saddr)) - return; + return 0; is_v6 = true; addr = &ip6h->saddr; break; @@ -532,7 +542,7 @@ static void ipvlan_addr_learn(struct ipvl_dev *ipvlan, void *lyr3h, ip4h = (struct iphdr *)lyr3h; i4addr = &ip4h->saddr; if (!is_ipv4_usable(*i4addr)) - return; + return 0; is_v6 = false; addr = i4addr; break; @@ -547,17 +557,18 @@ static void ipvlan_addr_learn(struct ipvl_dev *ipvlan, void *lyr3h, arp_ptr += ipvlan->port->dev->addr_len; i4addr = (__be32 *)arp_ptr; if (!is_ipv4_usable(*i4addr)) - return; + return 0; is_v6 = false; addr = i4addr; break; } default: - return; + return 0; } /* handle situation when MAC changed, but IP is the same. */ ipvladdr = ipvlan_ht_addr_lookup(ipvlan->port, addr, is_v6); + if (ipvladdr && !ether_addr_equal(ipvladdr->hwaddr, hwaddr)) { /* del_addr is safe to call, because we are inside xmit. */ ipvlan_del_addr(ipvladdr->master, addr, is_v6); @@ -565,7 +576,9 @@ static void ipvlan_addr_learn(struct ipvl_dev *ipvlan, void *lyr3h, } if (!ipvladdr) - __ipvlan_addr_learn(ipvlan, addr, is_v6, hwaddr); + return __ipvlan_addr_learn(ipvlan, addr, is_v6, hwaddr); + + return 0; } static noinline_for_stack int ipvlan_process_v4_outbound(struct sk_buff *skb) @@ -905,9 +918,9 @@ static int ipvlan_xmit_mode_l2(struct sk_buff *skb, struct net_device *dev) lyr3h = ipvlan_get_L3_hdr(ipvlan->port, skb, &addr_type); if (ipvlan_is_macnat(ipvlan->port)) { - if (lyr3h) - ipvlan_addr_learn(ipvlan, lyr3h, addr_type, - eth->h_source); + if (lyr3h && ipvlan_addr_learn(ipvlan, lyr3h, addr_type, + eth->h_source) < 0) + goto out_drop; /* Mark SKB in advance */ skb = skb_share_check(skb, GFP_ATOMIC); if (!skb) diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c index b888c2ef77ca..18b49f74dc35 100644 --- a/drivers/net/ipvlan/ipvlan_main.c +++ b/drivers/net/ipvlan/ipvlan_main.c @@ -156,6 +156,115 @@ static int ipvlan_port_rcv(struct sk_buff *skb, struct net_device *wdev, return NET_RX_DROP; } +static int ipvlan_port_add_addr(struct ipvl_port *port, const void *iaddr, + bool is_v6) +{ + struct ipvl_port_addr *addr; + + addr = kzalloc(sizeof(*addr), GFP_KERNEL); + if (!addr) + return -ENOMEM; + if (!is_v6) { + memcpy(&addr->ip4addr, iaddr, sizeof(struct in_addr)); + addr->atype = IPVL_IPV4; + } else { + memcpy(&addr->ip6addr, iaddr, sizeof(struct in6_addr)); + addr->atype = IPVL_IPV6; + } + + spin_lock_bh(&port->addrs_lock); + list_add_tail_rcu(&addr->anode, &port->port_addrs); + spin_unlock_bh(&port->addrs_lock); + + return 0; +} + +static bool portaddr_equal(bool is_v6, const struct ipvl_port_addr *addr, + const void *iaddr) +{ + if (!is_v6 && addr->atype == IPVL_IPV4) { + struct in_addr *i4addr = (struct in_addr *)iaddr; + + return addr->ip4addr.s_addr == i4addr->s_addr; +#if IS_ENABLED(CONFIG_IPV6) + } else if (is_v6 && addr->atype == IPVL_IPV6) { + struct in6_addr *i6addr = (struct in6_addr *)iaddr; + + return ipv6_addr_equal(&addr->ip6addr, i6addr); +#endif + } + + return false; +} + +struct ipvl_port_addr *ipvlan_port_find_addr(struct ipvl_port *port, + const void *iaddr, bool is_v6) +{ + struct ipvl_port_addr *addr; + + list_for_each_entry_rcu(addr, &port->port_addrs, anode) + if (portaddr_equal(is_v6, addr, iaddr)) + return addr; + return NULL; +} + +static void ipvlan_port_del_addr(struct ipvl_port *port, const void *iaddr, + bool is_v6) +{ + struct ipvl_port_addr *addr; + + spin_lock_bh(&port->addrs_lock); + addr = ipvlan_port_find_addr(port, iaddr, is_v6); + if (addr) + list_del_rcu(&addr->anode); + spin_unlock_bh(&port->addrs_lock); + + if (addr) + kfree_rcu(addr, rcu); +} + +static int ipvlan_port_enum_addrs(struct ipvl_port *port) +{ + const struct inet6_dev *in6_dev __maybe_unused; + const struct inet6_ifaddr *ifa6 __maybe_unused; + const struct in_device *in_dev; + const struct in_ifaddr *ifa; + int r = 0; + + ASSERT_RTNL(); + + in_dev = __in_dev_get_rcu(port->dev); + if (in_dev) + in_dev_for_each_ifa_rcu(ifa, in_dev) { + r = ipvlan_port_add_addr(port, &ifa->ifa_local, false); + if (r < 0) + return r; + } + +#if IS_ENABLED(CONFIG_IPV6) + in6_dev = __in6_dev_get(port->dev); + if (in6_dev) + list_for_each_entry_rcu(ifa6, &in6_dev->addr_list, if_list) { + r = ipvlan_port_add_addr(port, &ifa6->addr, true); + if (r < 0) + return r; + } +#endif + return r; +} + +static void ipvlan_port_free_port_addrs(struct ipvl_port *port) +{ + struct ipvl_port_addr *addr, *next; + + ASSERT_RTNL(); + + list_for_each_entry_safe(addr, next, &port->port_addrs, anode) { + list_del_rcu(&addr->anode); + kfree_rcu(addr, rcu); + } +} + static int ipvlan_port_create(struct net_device *dev) { struct ipvl_port *port; @@ -172,12 +281,15 @@ static int ipvlan_port_create(struct net_device *dev) for (idx = 0; idx < IPVLAN_HASH_SIZE; idx++) INIT_HLIST_HEAD(&port->hlhead[idx]); + INIT_LIST_HEAD(&port->port_addrs); spin_lock_init(&port->addrs_lock); skb_queue_head_init(&port->backlog); INIT_WORK(&port->wq, ipvlan_process_multicast); ida_init(&port->ida); port->dev_id_start = 1; + ipvlan_port_enum_addrs(port); + err = netdev_rx_handler_register(dev, ipvlan_handle_frame, port); if (err) goto err; @@ -191,6 +303,7 @@ static int ipvlan_port_create(struct net_device *dev) return 0; err: + ipvlan_port_free_port_addrs(port); kfree(port); return err; } @@ -212,6 +325,7 @@ static void ipvlan_port_destroy(struct net_device *dev) kfree_skb(skb); } ida_destroy(&port->ida); + ipvlan_port_free_port_addrs(port); kfree(port); } @@ -1010,6 +1124,50 @@ void ipvlan_del_addr(struct ipvl_dev *ipvlan, void *iaddr, bool is_v6) kfree_rcu(addr, rcu); } +static void ipvlan_port_del_addr_ipvlans(struct ipvl_port *port, + const void *iaddr, bool is_v6) +{ + struct ipvl_addr *addr = NULL; + struct ipvl_dev *ipvlan; + + list_for_each_entry_rcu(ipvlan, &port->ipvlans, pnode) { + spin_lock_bh(&port->addrs_lock); + addr = ipvlan_find_addr(ipvlan, iaddr, is_v6); + if (addr) { + ipvlan_ht_addr_del(addr); + list_del_rcu(&addr->anode); + spin_unlock_bh(&port->addrs_lock); + break; + } + spin_unlock_bh(&port->addrs_lock); + } + + if (addr) + kfree_rcu(addr, rcu); +} + +static int ipvlan_port_add_addr_event(struct ipvl_port *port, + const void *iaddr, bool is_v6) +{ + int r; + + r = ipvlan_port_add_addr(port, iaddr, is_v6); + if (r < 0) + return r; + + ipvlan_port_del_addr_ipvlans(port, iaddr, is_v6); + + return NOTIFY_OK; +} + +static int ipvlan_port_del_addr_event(struct ipvl_port *port, + const void *iaddr, bool is_v6) +{ + ipvlan_port_del_addr(port, iaddr, is_v6); + + return NOTIFY_OK; +} + #if IS_ENABLED(CONFIG_IPV6) static int ipvlan_add_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr) { @@ -1038,6 +1196,24 @@ static int ipvlan_addr6_event(struct notifier_block *unused, struct net_device *dev = (struct net_device *)if6->idev->dev; struct ipvl_dev *ipvlan = netdev_priv(dev); + if (netif_is_ipvlan_port(dev)) { + struct ipvl_port *port = ipvlan_port_get_rcu(dev); + + if (!ipvlan_is_macnat(port)) + return NOTIFY_DONE; + + switch (event) { + case NETDEV_UP: + return ipvlan_port_add_addr_event(port, &if6->addr, + true); + case NETDEV_DOWN: + return ipvlan_port_del_addr_event(port, &if6->addr, + true); + default: + return NOTIFY_OK; + } + } + if (!ipvlan_is_valid_dev(dev)) return NOTIFY_DONE; @@ -1110,6 +1286,26 @@ static int ipvlan_addr4_event(struct notifier_block *unused, struct ipvl_dev *ipvlan = netdev_priv(dev); struct in_addr ip4_addr; + if (netif_is_ipvlan_port(dev)) { + struct ipvl_port *port = ipvlan_port_get_rcu(dev); + + if (!ipvlan_is_macnat(port)) + return NOTIFY_DONE; + + switch (event) { + case NETDEV_UP: + return ipvlan_port_add_addr_event(port, + &if4->ifa_address, + false); + case NETDEV_DOWN: + return ipvlan_port_del_addr_event(port, + &if4->ifa_address, + false); + default: + return NOTIFY_OK; + } + } + if (!ipvlan_is_valid_dev(dev)) return NOTIFY_DONE; -- 2.25.1