add storage and helpers to associate an ipv{4,6} address with the local route to self. This will be used by a later patch to implement early demux for unconnected UDP sockets.
The caches are filled on address creation, with DST_OBSOLETE_NONE. Ipv6 cache are explicitly clearered and refreshed on underlaying device down/up events. The above schema is simpler than refreshing the cache every time the dst expires under the default obsolete schema. Signed-off-by: Paolo Abeni <pab...@redhat.com> --- include/linux/inetdevice.h | 4 ++++ include/net/addrconf.h | 3 +++ include/net/if_inet6.h | 4 ++++ net/ipv4/devinet.c | 29 ++++++++++++++++++++++++++++- net/ipv6/addrconf.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 1 deletion(-) diff --git a/include/linux/inetdevice.h b/include/linux/inetdevice.h index 751d051f0bc7..c29982f178bb 100644 --- a/include/linux/inetdevice.h +++ b/include/linux/inetdevice.h @@ -130,6 +130,8 @@ static inline void ipv4_devconf_setall(struct in_device *in_dev) #define IN_DEV_ARP_IGNORE(in_dev) IN_DEV_MAXCONF((in_dev), ARP_IGNORE) #define IN_DEV_ARP_NOTIFY(in_dev) IN_DEV_MAXCONF((in_dev), ARP_NOTIFY) +struct dst_entry; + struct in_ifaddr { struct hlist_node hash; struct in_ifaddr *ifa_next; @@ -149,6 +151,7 @@ struct in_ifaddr { __u32 ifa_preferred_lft; unsigned long ifa_cstamp; /* created timestamp */ unsigned long ifa_tstamp; /* updated timestamp */ + struct dst_entry *dst; /* local route to self */ }; struct in_validator_info { @@ -180,6 +183,7 @@ __be32 inet_confirm_addr(struct net *net, struct in_device *in_dev, __be32 dst, struct in_ifaddr *inet_ifa_byprefix(struct in_device *in_dev, __be32 prefix, __be32 mask); struct in_ifaddr *inet_lookup_ifaddr_rcu(struct net *net, __be32 addr); +struct dst_entry *inet_get_ifaddr_dst_rcu(struct net *net, __be32 addr); static __inline__ bool inet_ifa_match(__be32 addr, struct in_ifaddr *ifa) { return !((addr^ifa->ifa_address)&ifa->ifa_mask); diff --git a/include/net/addrconf.h b/include/net/addrconf.h index 87981cd63180..bdfa3306a4c5 100644 --- a/include/net/addrconf.h +++ b/include/net/addrconf.h @@ -87,6 +87,9 @@ struct inet6_ifaddr *ipv6_get_ifaddr(struct net *net, const struct in6_addr *addr, struct net_device *dev, int strict); +struct dst_entry *inet6_get_ifaddr_dst_rcu_bh(struct net *net, + const struct in6_addr *addr); + int ipv6_dev_get_saddr(struct net *net, const struct net_device *dev, const struct in6_addr *daddr, unsigned int srcprefs, struct in6_addr *saddr); diff --git a/include/net/if_inet6.h b/include/net/if_inet6.h index d4088d1a688d..1dd42e7c17a4 100644 --- a/include/net/if_inet6.h +++ b/include/net/if_inet6.h @@ -39,6 +39,8 @@ enum { INET6_IFADDR_STATE_DEAD, }; +struct dst_entry; + struct inet6_ifaddr { struct in6_addr addr; __u32 prefix_len; @@ -77,6 +79,8 @@ struct inet6_ifaddr { struct rcu_head rcu; struct in6_addr peer_addr; + + struct dst_entry *dst; /* local route to self */ }; struct ip6_sf_socklist { diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c index 7ce22a2c07ce..a7748f787866 100644 --- a/net/ipv4/devinet.c +++ b/net/ipv4/devinet.c @@ -179,6 +179,17 @@ struct in_ifaddr *inet_lookup_ifaddr_rcu(struct net *net, __be32 addr) return NULL; } +/* called under RCU lock */ +struct dst_entry *inet_get_ifaddr_dst_rcu(struct net *net, __be32 addr) +{ + struct in_ifaddr *ifa = inet_lookup_ifaddr_rcu(net, addr); + + if (!ifa) + return NULL; + + return dst_access(&ifa->dst, 0); +} + static void rtmsg_ifa(int event, struct in_ifaddr *, struct nlmsghdr *, u32); static BLOCKING_NOTIFIER_HEAD(inetaddr_chain); @@ -337,6 +348,7 @@ static void __inet_del_ifa(struct in_device *in_dev, struct in_ifaddr **ifap, struct in_ifaddr *last_prim = in_dev->ifa_list; struct in_ifaddr *prev_prom = NULL; int do_promote = IN_DEV_PROMOTE_SECONDARIES(in_dev); + struct dst_entry *dst; ASSERT_RTNL(); @@ -395,7 +407,12 @@ static void __inet_del_ifa(struct in_device *in_dev, struct in_ifaddr **ifap, *ifap = ifa1->ifa_next; inet_hash_remove(ifa1); - /* 3. Announce address deletion */ + /* 3. Clear dst cache */ + + dst = xchg(&ifa1->dst, NULL); + dst_release(dst); + + /* 4. Announce address deletion */ /* Send message first, then call notifier. At first sight, FIB update triggered by notifier @@ -449,6 +466,7 @@ static int __inet_insert_ifa(struct in_ifaddr *ifa, struct nlmsghdr *nlh, struct in_device *in_dev = ifa->ifa_dev; struct in_ifaddr *ifa1, **ifap, **last_primary; struct in_validator_info ivi; + struct rtable *rt; int ret; ASSERT_RTNL(); @@ -516,6 +534,15 @@ static int __inet_insert_ifa(struct in_ifaddr *ifa, struct nlmsghdr *nlh, rtmsg_ifa(RTM_NEWADDR, ifa, nlh, portid); blocking_notifier_call_chain(&inetaddr_chain, NETDEV_UP, ifa); + /* fill the dst cache and transfer the dst ownership to it */ + rt = ip_local_route_alloc(in_dev->dev, 0, 0, RTN_LOCAL, false); + if (rt) { + /* the local route will be valid for till the address will be + * up + */ + rt->dst.obsolete = DST_OBSOLETE_NONE; + __dst_update(&ifa->dst, &rt->dst); + } return 0; } diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index 5940062cac8d..5fa8d1b764ca 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -904,6 +904,26 @@ static int addrconf_fixup_linkdown(struct ctl_table *table, int *p, int newf) #endif +static void inet6_addr_dst_clear(struct inet6_ifaddr *ifp) +{ + dst_release(xchg(&ifp->dst, NULL)); +} + +static void inet6_addr_dst_update(struct inet6_dev *idev, + struct inet6_ifaddr *ifp) +{ + struct rt6_info *rt = addrconf_dst_alloc(idev, &ifp->addr, false); + + if (IS_ERR(rt)) + return; + + /* we are going to manully clear the cache when the related dev will + * go down + */ + rt->dst.obsolete = DST_OBSOLETE_NONE; + __dst_update(&ifp->dst, &rt->dst); +} + /* Nobody refers to this ifaddr, destroy it */ void inet6_ifa_finish_destroy(struct inet6_ifaddr *ifp) { @@ -914,6 +934,7 @@ void inet6_ifa_finish_destroy(struct inet6_ifaddr *ifp) #endif in6_dev_put(ifp->idev); + inet6_addr_dst_clear(ifp); if (cancel_delayed_work(&ifp->dad_work)) pr_notice("delayed DAD work was pending while freeing ifa=%p\n", @@ -1907,6 +1928,18 @@ int ipv6_chk_prefix(const struct in6_addr *addr, struct net_device *dev) } EXPORT_SYMBOL(ipv6_chk_prefix); +/* called under RCU lock */ +struct dst_entry *inet6_get_ifaddr_dst_rcu_bh(struct net *net, + const struct in6_addr *addr) +{ + struct inet6_ifaddr *ifp = ipv6_lookup_ifaddr_rcu_bh(net, addr); + + if (!ifp) + return NULL; + + return dst_access(&ifp->dst, 0); +} + struct inet6_ifaddr *ipv6_get_ifaddr(struct net *net, const struct in6_addr *addr, struct net_device *dev, int strict) { @@ -3300,6 +3333,15 @@ static int fixup_permanent_addr(struct inet6_dev *idev, ifp->rt = rt; spin_unlock(&ifp->lock); + /* if dad is not going to start, we must cache the new route + * elsewhere we can just clear the old one + */ + if (ifp->state == INET6_IFADDR_STATE_PREDAD || + ifp->flags & IFA_F_TENTATIVE) + inet6_addr_dst_clear(ifp); + else + inet6_addr_dst_update(idev, ifp); + ip6_rt_put(prev); } @@ -3679,6 +3721,7 @@ static int addrconf_ifdown(struct net_device *dev, int how) spin_unlock_bh(&ifa->lock); + inet6_addr_dst_clear(ifa); if (rt) ip6_del_rt(rt); @@ -4009,6 +4052,7 @@ static void addrconf_dad_completed(struct inet6_ifaddr *ifp, bool bump_id) */ ipv6_ifa_notify(RTM_NEWADDR, ifp); + inet6_addr_dst_update(ifp->idev, ifp); /* If added prefix is link local and we are prepared to process router advertisements, start sending router solicitations. -- 2.13.5