net/ipv6: Another followup to the fib6_info change
Subsystems (3)
cover net/ipv6: Another followup to the fib6_info change
Last one - for this week.
Patches 1, 2 and 7 are more cleanup patches - removing dead code, moving code from a header to near its single caller, and updating function name.
Patches 3-5 do some refactoring leading up to patch 6 which fixes a NULL dereference. I have only managed to trigger a panic once, so I can not definitively confirm it addresses the problem but it seems pretty clear that it is a race on removing a 'from' reference on an rt6_info and another path using that 'from' value to do cookie checking.
David Ahern (7):
net/ipv6: Clean up rt expires helpers
net/ipv6: Rename rt6_get_cookie_safe
net/ipv6: Move rcu_read_lock to callers of ip6_rt_cache_alloc net/ipv6: Move rcu locking to callers of fib6_get_cookie_safe net/ipv6: Move release of fib6_info from pcpu routes to helper net/ipv6: Make from in rt6_info rcu protected net/ipv6: Remove unncessary check on f6i in fib6_check
include/net/ip6_fib.h | 41 +++++------------ net/ipv6/ip6_fib.c | 45 ++++++++++-------- net/ipv6/ip6_output.c | 9 +++- net/ipv6/route.c | 124 ++++++++++++++++++++++++++++++++++++-------------- 4 files changed, 136 insertions(+), 83 deletions(-)
Signature · 1 line
2.11.0
PATCH 1/7 net/ipv6: Clean up rt expires helpers
rt6_clean_expires and rt6_set_expires are no longer used. Removed them. rt6_update_expires has 1 caller in route.c, so move it from the header.
--- include/net/ip6_fib.h | 21 --------------------- net/ipv6/route.c | 9 +++++++++ 2 files changed, 9 insertions(+), 21 deletions(-)
include/net/ip6_fib.h +0 -21
@@ -223,27 +223,6 @@ static inline bool fib6_check_expired(const struct fib6_info *f6i)return false; } -static inline void rt6_clean_expires (struct rt6_info *rt) -{ - rt->rt6i_flags &= ~RTF_EXPIRES ; - rt->dst.expires =0 ; -} - -static inline void rt6_set_expires (struct rt6_info *rt,unsigned long expires) -{ - rt->dst.expires = expires; - rt->rt6i_flags |=RTF_EXPIRES ; -} - -static inline void rt6_update_expires (struct rt6_info *rt0,int timeout) -{ -if (!(rt0->rt6i_flags &RTF_EXPIRES ) && rt0->from) - rt0->dst.expires = rt0->from->expires; - -dst_set_expires (&rt0->dst, timeout); - rt0->rt6i_flags |=RTF_EXPIRES ; -} -/* Function to safely get fn->sernum for passed in rt * and store result in passed in cookie. * Return true if we can get cookie safely
net/ipv6/route.c +9 -0
@@ -2221,6 +2221,15 @@ static void ip6_link_failure(struct sk_buff *skb) } } +static void rt6_update_expires (struct rt6_info *rt0,int timeout) +{ +if (!(rt0->rt6i_flags &RTF_EXPIRES ) && rt0->from) + rt0->dst.expires = rt0->from->expires; + +dst_set_expires (&rt0->dst, timeout); + rt0->rt6i_flags |=RTF_EXPIRES ; +} +static void rt6_do_update_pmtu (struct rt6_info *rt,u32 mtu) {struct net *net =dev_net (rt->dst.dev);
Signature · 1 line
2.11.0
PATCH 2/7 net/ipv6: Rename rt6_get_cookie_safe
In reply to David Ahernrt6_get_cookie_safe takes a fib6_info and checks the sernum of the node. Update the name to reflect its purpose.
--- include/net/ip6_fib.h | 6 +++--- net/ipv6/route.c | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-)
include/net/ip6_fib.h +3 -3
@@ -228,8 +228,8 @@ static inline bool fib6_check_expired(const struct fib6_info *f6i)* Return true if we can get cookie safely * Return false if not */ -static inline bool rt6_get_cookie_safe (const struct fib6_info *f6i, -u32 *cookie) +static inline bool fib6_get_cookie_safe (const struct fib6_info *f6i, +u32 *cookie) {struct fib6_node *fn;bool status = false;
@@ -254,7 +254,7 @@ static inline u32 rt6_get_cookie(const struct rt6_info *rt)if (rt->rt6i_flags &RTF_PCPU || (unlikely (!list_empty (&rt->rt6i_uncached)) && rt->from)) -rt6_get_cookie_safe (rt->from, &cookie); +fib6_get_cookie_safe (rt->from, &cookie);return cookie; }
net/ipv6/route.c +2 -2
@@ -2128,7 +2128,7 @@ static bool fib6_check(struct fib6_info *f6i, u32 cookie) {u32 rt_cookie =0 ; -if ((f6i && !rt6_get_cookie_safe (f6i, &rt_cookie)) || +if ((f6i && !fib6_get_cookie_safe (f6i, &rt_cookie)) || rt_cookie != cookie)return false;
@@ -2142,7 +2142,7 @@ static struct dst_entry *rt6_check(struct rt6_info *rt, u32 cookie) {u32 rt_cookie =0 ; -if ((rt->from && !rt6_get_cookie_safe (rt->from, &rt_cookie)) || +if ((rt->from && !fib6_get_cookie_safe (rt->from, &rt_cookie)) || rt_cookie != cookie)return NULL ;
Signature · 1 line
2.11.0
PATCH 3/7 net/ipv6: Move rcu_read_lock to callers of ip6_rt_cache_alloc
In reply to David AhernA later patch protects 'from' in rt6_info and this simplifies the locking needed by it.
With the move, the fib6_info_hold for the uncached_rt is no longer needed since the rcu_lock is still held.
--- net/ipv6/route.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-)
net/ipv6/route.c +6 -7
@@ -1164,10 +1164,8 @@ static struct rt6_info *ip6_rt_cache_alloc(struct fib6_info *ort,* Clone the route. */ -rcu_read_lock (); dev =ip6_rt_get_dev_rcu (ort); rt =ip6_dst_alloc (dev_net (dev), dev,0 ); -rcu_read_unlock ();if (!rt)return NULL ;
@@ -1855,14 +1853,11 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,* the daddr in the skb during the neighbor look-up is different * from the fl6->daddr used to look-up route here. */ -struct rt6_info *uncached_rt; -fib6_info_hold (f6i); -rcu_read_unlock (); - uncached_rt =ip6_rt_cache_alloc (f6i, &fl6->daddr,NULL ); -fib6_info_release (f6i); + +rcu_read_unlock ();if (uncached_rt) {/* Uncached_rt's refcnt is taken during ip6_rt_cache_alloc()
@@ -2280,7 +2275,9 @@ static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk, }else if (daddr) {struct rt6_info *nrt6; +rcu_read_lock (); nrt6 =ip6_rt_cache_alloc (rt6->from, daddr, saddr); +rcu_read_unlock ();if (nrt6) {rt6_do_update_pmtu (nrt6, mtu);if (rt6_insert_exception (nrt6, rt6->from))
@@ -3299,7 +3296,9 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu NEIGH_UPDATE_F_ISROUTER)), NDISC_REDIRECT, &ndopts); +rcu_read_lock (); nrt =ip6_rt_cache_alloc (rt->from, &msg->dest,NULL ); +rcu_read_unlock ();if (!nrt) goto out;
Signature · 1 line
2.11.0
PATCH 4/7 net/ipv6: Move rcu locking to callers of fib6_get_cookie_safe
In reply to David AhernA later patch protects 'from' in rt6_info and this simplifies the locking needed by it.
--- include/net/ip6_fib.h | 6 ++++-- net/ipv6/route.c | 13 ++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-)
include/net/ip6_fib.h +4 -2
@@ -234,7 +234,6 @@ static inline bool fib6_get_cookie_safe(const struct fib6_info *f6i,struct fib6_node *fn;bool status = false; -rcu_read_lock (); fn =rcu_dereference (f6i->fib6_node);if (fn) {
@@ -244,7 +243,6 @@ static inline bool fib6_get_cookie_safe(const struct fib6_info *f6i, status = true; } -rcu_read_unlock ();return status; }
@@ -252,10 +250,14 @@ static inline u32 rt6_get_cookie(const struct rt6_info *rt) {u32 cookie =0 ; +rcu_read_lock (); +if (rt->rt6i_flags &RTF_PCPU || (unlikely (!list_empty (&rt->rt6i_uncached)) && rt->from))fib6_get_cookie_safe (rt->from, &cookie); +rcu_read_unlock (); +return cookie; }
net/ipv6/route.c +10 -3
@@ -2159,9 +2159,12 @@ static struct dst_entry *rt6_dst_from_check(struct rt6_info *rt, u32 cookie)static struct dst_entry *ip6_dst_check (struct dst_entry *dst,u32 cookie) { +struct dst_entry *dst_ret;struct rt6_info *rt; - rt = (struct rt6_info *) dst; + rt = container_of(dst, struct rt6_info, dst); + +rcu_read_lock ();/* All IPV6 dsts are created with ->obsolete set to the value * DST_OBSOLETE_FORCE_CHK which forces validation calls down
@@ -2170,9 +2173,13 @@ static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie)if (rt->rt6i_flags &RTF_PCPU || (unlikely (!list_empty (&rt->rt6i_uncached)) && rt->from)) -return rt6_dst_from_check (rt, cookie); + dst_ret =rt6_dst_from_check (rt, cookie);else -return rt6_check (rt, cookie); + dst_ret =rt6_check (rt, cookie); + +rcu_read_unlock (); + +return dst_ret; }static struct dst_entry *ip6_negative_advice (struct dst_entry *dst)
Signature · 1 line
2.11.0
PATCH 5/7 net/ipv6: Move release of fib6_info from pcpu routes to helper
In reply to David AhernCode move only; no functional change intended.
--- net/ipv6/ip6_fib.c | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-)
net/ipv6/ip6_fib.c +23 -18
@@ -860,6 +860,27 @@ static struct fib6_node *fib6_add_1(struct net *net,return ln; } +static void fib6_drop_pcpu_from (struct fib6_info *f6i, +const struct fib6_table *table) +{ +int cpu; + +/* release the reference to this fib entry from +* all of its cached pcpu routes +*/ +for_each_possible_cpu (cpu) { +struct rt6_info **ppcpu_rt; +struct rt6_info *pcpu_rt; + + ppcpu_rt =per_cpu_ptr (f6i->rt6i_pcpu, cpu); + pcpu_rt = *ppcpu_rt; +if (pcpu_rt) { +fib6_info_release (pcpu_rt->from); + pcpu_rt->from =NULL ; + } + } +} +static void fib6_purge_rt (struct fib6_info *rt,struct fib6_node *fn,struct net *net) {
@@ -887,24 +908,8 @@ static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn, lockdep_is_held(&table->tb6_lock)); } -if (rt->rt6i_pcpu) { -int cpu; - -/* release the reference to this fib entry from -* all of its cached pcpu routes -*/ -for_each_possible_cpu (cpu) { -struct rt6_info **ppcpu_rt; -struct rt6_info *pcpu_rt; - - ppcpu_rt =per_cpu_ptr (rt->rt6i_pcpu, cpu); - pcpu_rt = *ppcpu_rt; -if (pcpu_rt) { -fib6_info_release (pcpu_rt->from); - pcpu_rt->from =NULL ; - } - } - } +if (rt->rt6i_pcpu) +fib6_drop_pcpu_from (rt, table); } }
Signature · 1 line
2.11.0
PATCH 6/7 net/ipv6: Make from in rt6_info rcu protected
In reply to David AhernWhen a dst entry is created from a fib entry, the 'from' in rt6_info is set to the fib entry. The 'from' reference is used most notably for cookie checking - making sure stale dst entries are updated if the fib entry is changed.
When a fib entry is deleted, the pcpu routes on it are walked releasing the fib6_info reference. This is needed for the fib6_info cleanup to happen and to make sure all device references are released in a timely manner.
There is a race window when a FIB entry is deleted and the 'from' on the pcpu route is dropped and the pcpu route hits a cookie check. Handle this race using rcu on from.
--- include/net/ip6_fib.h | 10 +++--- net/ipv6/ip6_fib.c | 8 +++-- net/ipv6/ip6_output.c | 9 +++-- net/ipv6/route.c | 96 ++++++++++++++++++++++++++++++++++++--------------- 4 files changed, 88 insertions(+), 35 deletions(-)
include/net/ip6_fib.h +6 -4
@@ -174,7 +174,7 @@ struct fib6_info {struct rt6_info {struct dst_entry dst; -struct fib6_info *from; +struct fib6_info __rcu *from;struct rt6key rt6i_dst;struct rt6key rt6i_src;
@@ -248,13 +248,15 @@ static inline bool fib6_get_cookie_safe(const struct fib6_info *f6i,static inline u32 rt6_get_cookie (const struct rt6_info *rt) { +struct fib6_info *from;u32 cookie =0 ;rcu_read_lock (); -if (rt->rt6i_flags &RTF_PCPU || - (unlikely (!list_empty (&rt->rt6i_uncached)) && rt->from)) -fib6_get_cookie_safe (rt->from, &cookie); + from =rcu_dereference (rt->from); +if (from && (rt->rt6i_flags &RTF_PCPU || +unlikely (!list_empty (&rt->rt6i_uncached)))) +fib6_get_cookie_safe (from, &cookie);rcu_read_unlock ();
net/ipv6/ip6_fib.c +6 -2
@@ -875,8 +875,12 @@ static void fib6_drop_pcpu_from(struct fib6_info *f6i, ppcpu_rt =per_cpu_ptr (f6i->rt6i_pcpu, cpu); pcpu_rt = *ppcpu_rt;if (pcpu_rt) { -fib6_info_release (pcpu_rt->from); - pcpu_rt->from =NULL ; +struct fib6_info *from; + + from =rcu_dereference_protected (pcpu_rt->from, +lockdep_is_held (&table->tb6_lock)); +rcu_assign_pointer (pcpu_rt->from,NULL ); +fib6_info_release (from); } } }
net/ipv6/ip6_output.c +7 -2
@@ -962,16 +962,21 @@ static int ip6_dst_lookup_tail(struct net *net, const struct sock *sk,* that's why we try it again later. */ if (ipv6_addr_any (&fl6->saddr) && (!*dst || !(*dst)->error)) { +struct fib6_info *from;struct rt6_info *rt;bool had_dst = *dst !=NULL ;if (!had_dst) *dst =ip6_route_output (net, sk, fl6); rt = (*dst)->error ?NULL : (struct rt6_info *)*dst; - err =ip6_route_get_saddr (net, rt ? rt->from :NULL , - &fl6->daddr, + +rcu_read_lock (); + from = rt ?rcu_dereference (rt->from) :NULL ; + err =ip6_route_get_saddr (net, from, &fl6->daddr, sk ?inet6_sk (sk)->srcprefs :0 , &fl6->saddr); +rcu_read_unlock (); +if (err) goto out_err_release;
net/ipv6/route.c +69 -27
221 lines not on this page · raw message
Signature · 1 line
2.11.0
PATCH 7/7 net/ipv6: Remove unncessary check on f6i in fib6_check
In reply to David AhernDan reported an imbalance in fib6_check on use of f6i and checking whether it is null. Since fib6_check is only called if f6i is non-null, remove the unnecessary check.
--- net/ipv6/route.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
net/ipv6/route.c +1 -2
@@ -2130,8 +2130,7 @@ static bool fib6_check(struct fib6_info *f6i, u32 cookie) {u32 rt_cookie =0 ; -if ((f6i && !fib6_get_cookie_safe (f6i, &rt_cookie)) || - rt_cookie != cookie) +if (!fib6_get_cookie_safe (f6i, &rt_cookie) || rt_cookie != cookie)return false;if (fib6_check_expired (f6i))
Signature · 1 line
2.11.0
David Miller
maintainerFrom: David Ahern <[email protected]>
Date: Fri, 20 Apr 2018 15:37:56 -0700
Quoted 6 lines
Last one - for this week.
Patches 1, 2 and 7 are more cleanup patches - removing dead code, moving code from a header to near its single caller, and updating function name.
Quoted 6 lines
Patches 3-5 do some refactoring leading up to patch 6 which fixes a NULL dereference. I have only managed to trigger a panic once, so I can not definitively confirm it addresses the problem but it seems pretty clear that it is a race on removing a 'from' reference on an
rt6_infoand another path using that 'from' value to do cookie checking.
Great work, series applied.