On 19/08/16(Fri) 19:19, Alexander Bluhm wrote:
> On Mon, Aug 15, 2016 at 01:52:46PM +0200, Martin Pieuchot wrote:
> > More tests, comments, ok are welcome.
> 
> There is an issue with path mtu discovery that my regression test
> found, but i think that can be fixed with this diff in tree.

I integrated your fix in the updated diff below.

> > +   KASSERT(nhrt->rt_cachecnt > 0);
> 
> Could you put a KASSERT(ISSET(rt->rt_flags, RTF_CACHED)) before
> accessing rt_cachecnt?

Done.

> > @@ -615,7 +615,27 @@ route_output(struct mbuf *m, ...)
> >             }
> >             break;
> >     case RTM_DELETE:
> > -           error = rtrequest(RTM_DELETE, &info, prio, &rt, tableid);
> ...
> > +           error = rtrequest(RTM_DELETE, &info, prio, NULL, tableid);
> >             if (error == 0)
> >                     goto report;
> >             break;
> 
> I think you should keep passing &rt instead of NULL, otherwise rt
> might be NULL when you goto report.

Done.

> > +void
> > +arpinvalidate(struct rtentry *rt)
> > +{
> > +   struct llinfo_arp *la = (struct llinfo_arp *)rt->rt_llinfo;
> > +   struct sockaddr_dl *sdl = satosdl(rt->rt_gateway);
> > +
> > +   la_hold_total -= ml_purge(&la->la_ml);
> > +   sdl->sdl_alen = 0;
> > +   la->la_asked = 0;
> > +}
> 
> Is it safe to modify the la and sdl while another CPU might use it?
> Especially ml_purge() looks dangerous.

No it is not.  This should be addressed afterward.

> > @@ -1495,18 +1512,13 @@ nd6_resolve(struct ifnet *ifp, struct rt
> >     struct sockaddr_dl *sdl;
> >     struct rtentry *rt;
> >     struct llinfo_nd6 *ln = NULL;
> > -   int error;
> >  
> >     if (m->m_flags & M_MCAST) {
> >             ETHER_MAP_IPV6_MULTICAST(&satosin6(dst)->sin6_addr, desten);
> >             return (0);
> >     }
> >  
> > -   error = rt_checkgate(rt0, &rt);
> > -   if (error) {
> > -           m_freem(m);
> > -           return (error);
> > -   }
> > +   rt = rt_getll(rt0);
> 
> Should we check for RTF_REJECT like it was done in rt_checkgate()
> before?

Indeed.

Updated diff below fix the issues you reported and some regression with
the route(8) and arp(8) regress tests.  I'll commit it soon to fix the
remaining issues in tree.

Index: net/route.c
===================================================================
RCS file: /cvs/src/sys/net/route.c,v
retrieving revision 1.315
diff -u -p -r1.315 route.c
--- net/route.c 19 Aug 2016 07:12:54 -0000      1.315
+++ net/route.c 22 Aug 2016 09:00:14 -0000
@@ -153,7 +153,9 @@ struct pool         rtentry_pool;   /* pool for r
 struct pool            rttimer_pool;   /* pool for rttimer structures */
 
 void   rt_timer_init(void);
-void   rt_setgwroute(struct rtentry *, u_int);
+int    rt_setgwroute(struct rtentry *, u_int);
+void   rt_putgwroute(struct rtentry *);
+int    rt_fixgwroute(struct rtentry *, void *, unsigned int);
 int    rtflushclone1(struct rtentry *, void *, u_int);
 void   rtflushclone(unsigned int, struct rtentry *);
 int    rt_if_remove_rtdelete(struct rtentry *, void *, u_int);
@@ -204,21 +206,20 @@ rtisvalid(struct rtentry *rt)
        if (rt == NULL)
                return (0);
 
-#ifdef DIAGNOSTIC
-       if (ISSET(rt->rt_flags, RTF_GATEWAY) && (rt->rt_gwroute != NULL) &&
-           ISSET(rt->rt_gwroute->rt_flags, RTF_GATEWAY))
-               panic("next hop must be directly reachable");
-#endif
-
-       if ((rt->rt_flags & RTF_UP) == 0)
+       if (!ISSET(rt->rt_flags, RTF_UP))
                return (0);
 
        /* Routes attached to stale ifas should be freed. */
        if (rt->rt_ifa == NULL || rt->rt_ifa->ifa_ifp == NULL)
                return (0);
 
-       if (ISSET(rt->rt_flags, RTF_GATEWAY) && !rtisvalid(rt->rt_gwroute))
-               return (0);
+#ifdef DIAGNOSTIC
+       if (ISSET(rt->rt_flags, RTF_GATEWAY)) {
+               KASSERT(rt->rt_gwroute != NULL);
+               KASSERT(ISSET(rt->rt_gwroute->rt_flags, RTF_UP));
+               KASSERT(!ISSET(rt->rt_gwroute->rt_flags, RTF_GATEWAY));
+       }
+#endif /* DIAGNOSTIC */
 
        return (1);
 }
@@ -269,8 +270,6 @@ rt_match(struct sockaddr *dst, uint32_t 
        return (rt);
 }
 
-struct rtentry *_rtalloc(struct sockaddr *, uint32_t *, int, unsigned int);
-
 #ifndef SMALL_KERNEL
 /*
  * Originated from bridge_hash() in if_bridge.c
@@ -351,16 +350,10 @@ rt_hash(struct rtentry *rt, struct socka
 struct rtentry *
 rtalloc_mpath(struct sockaddr *dst, uint32_t *src, unsigned int rtableid)
 {
-       return (_rtalloc(dst, src, RT_RESOLVE, rtableid));
+       return (rt_match(dst, src, RT_RESOLVE, rtableid));
 }
 #endif /* SMALL_KERNEL */
 
-struct rtentry *
-rtalloc(struct sockaddr *dst, int flags, unsigned int rtableid)
-{
-       return (_rtalloc(dst, NULL, flags, rtableid));
-}
-
 /*
  * Look in the routing table for the best matching entry for
  * ``dst''.
@@ -369,44 +362,35 @@ rtalloc(struct sockaddr *dst, int flags,
  * longer valid, try to cache it.
  */
 struct rtentry *
-_rtalloc(struct sockaddr *dst, uint32_t *src, int flags, unsigned int rtableid)
+rtalloc(struct sockaddr *dst, int flags, unsigned int rtableid)
 {
-       struct rtentry *rt;
-
-       rt = rt_match(dst, src, flags, rtableid);
-
-       /* No match or route to host?  We're done. */
-       if (rt == NULL || !ISSET(rt->rt_flags, RTF_GATEWAY))
-               return (rt);
-
-       /* Nothing to do if the next hop is valid. */
-       if (rtisvalid(rt->rt_gwroute))
-               return (rt);
-
-       rt_setgwroute(rt, rtableid);
-
-       return (rt);
+       return (rt_match(dst, NULL, flags, rtableid));
 }
 
-void
+/*
+ * Cache the route entry corresponding to a reachable next hop in
+ * the gateway entry ``rt''.
+ */
+int
 rt_setgwroute(struct rtentry *rt, u_int rtableid)
 {
        struct rtentry *nhrt;
 
-       rtfree(rt->rt_gwroute);
-       rt->rt_gwroute = NULL;
+       KERNEL_ASSERT_LOCKED();
 
-       /*
-        * If we cannot find a valid next hop, return the route
-        * with a gateway.
-        *
-        * XXX Some dragons hiding in the tree certainly depends on
-        * this behavior.  But it is safe since rt_checkgate() wont
-        * allow us to us this route later on.
-        */
+       KASSERT(ISSET(rt->rt_flags, RTF_GATEWAY));
+       KASSERT(rt->rt_gwroute == NULL);
+
+       /* If we cannot find a valid next hop bail. */
        nhrt = rt_match(rt->rt_gateway, NULL, RT_RESOLVE, rtable_l2(rtableid));
        if (nhrt == NULL)
-               return;
+               return (ENOENT);
+
+       /* Next hop entry must be on the same interface. */
+       if (nhrt->rt_ifidx != rt->rt_ifidx) {
+               rtfree(nhrt);
+               return (EHOSTUNREACH);
+       }
 
        /*
         * Next hop must be reachable, this also prevents rtentry
@@ -414,13 +398,7 @@ rt_setgwroute(struct rtentry *rt, u_int 
         */
        if (ISSET(nhrt->rt_flags, RTF_CLONING|RTF_GATEWAY)) {
                rtfree(nhrt);
-               return;
-       }
-
-       /* Next hop entry must be UP and on the same interface. */
-       if (!ISSET(nhrt->rt_flags, RTF_UP) || nhrt->rt_ifidx != rt->rt_ifidx) {
-               rtfree(nhrt);
-               return;
+               return (ELOOP);
        }
 
        /*
@@ -431,10 +409,76 @@ rt_setgwroute(struct rtentry *rt, u_int 
                rt->rt_mtu = nhrt->rt_mtu;
 
        /*
-        * Do not return the cached next-hop route, rt_checkgate() will
-        * do the magic for us.
+        * To avoid reference counting problems when writting link-layer
+        * addresses in an outgoing packet, we ensure that the lifetime
+        * of a cached entry is greater that the bigger lifetime of the
+        * gateway entries it is pointed by.
         */
+       nhrt->rt_flags |= RTF_CACHED;
+       nhrt->rt_cachecnt++;
+
        rt->rt_gwroute = nhrt;
+
+       return (0);
+}
+
+/*
+ * Invalidate the cached route entry of the gateway entry ``rt''.
+ */
+void
+rt_putgwroute(struct rtentry *rt)
+{
+       struct rtentry *nhrt = rt->rt_gwroute;
+
+       KERNEL_ASSERT_LOCKED();
+
+       if (!ISSET(rt->rt_flags, RTF_GATEWAY) || nhrt == NULL)
+               return;
+
+       KASSERT(ISSET(nhrt->rt_flags, RTF_CACHED));
+       KASSERT(nhrt->rt_cachecnt > 0);
+
+       --nhrt->rt_cachecnt;
+       if (nhrt->rt_cachecnt == 0)
+               nhrt->rt_flags &= ~RTF_CACHED;
+
+       rtfree(rt->rt_gwroute);
+       rt->rt_gwroute = NULL;
+}
+
+/*
+ * Refresh cached entries of RTF_GATEWAY routes for a given interface.
+ *
+ * This clever logic is necessary to try to fix routes linked to stale
+ * ifas.
+ */
+int
+rt_fixgwroute(struct rtentry *rt, void *arg, unsigned int id)
+{
+       struct ifnet *ifp = arg;
+
+       KERNEL_ASSERT_LOCKED();
+
+       if (rt->rt_ifidx != ifp->if_index || !ISSET(rt->rt_flags, RTF_GATEWAY))
+               return (0);
+
+       /*
+        * If the gateway route is not stale, its associated cached
+        * is also not stale.
+        */
+       if (rt->rt_ifa->ifa_ifp != NULL)
+               return (0);
+
+       /* If we can fix the cached next hop entry, we can fix the ifa. */
+       if (rt_setgate(rt, rt->rt_gateway, ifp->if_rdomain) == 0) {
+               struct ifaddr *ifa = rt->rt_gwroute->rt_ifa;
+
+               ifafree(rt->rt_ifa);
+               ifa->ifa_refcnt++;
+               rt->rt_ifa = ifa;
+       }
+
+       return (0);
 }
 
 void
@@ -891,8 +935,7 @@ rtrequest_delete(struct rt_addrinfo *inf
        if ((rt->rt_flags & RTF_CLONING) != 0)
                rtflushclone(tableid, rt);
 
-       rtfree(rt->rt_gwroute);
-       rt->rt_gwroute = NULL;
+       rt_putgwroute(rt);
 
        rtfree(rt->rt_parent);
        rt->rt_parent = NULL;
@@ -1101,7 +1144,7 @@ rtrequest(int req, struct rt_addrinfo *i
                    tableid))) {
                        ifafree(ifa);
                        rtfree(rt->rt_parent);
-                       rtfree(rt->rt_gwroute);
+                       rt_putgwroute(rt);
                        free(rt->rt_gateway, M_RTABLE, 0);
                        free(ndst, M_RTABLE, dlen);
                        pool_put(&rtentry_pool, rt);
@@ -1131,7 +1174,7 @@ rtrequest(int req, struct rt_addrinfo *i
                if (error != 0) {
                        ifafree(ifa);
                        rtfree(rt->rt_parent);
-                       rtfree(rt->rt_gwroute);
+                       rt_putgwroute(rt);
                        free(rt->rt_gateway, M_RTABLE, 0);
                        free(ndst, M_RTABLE, dlen);
                        pool_put(&rtentry_pool, rt);
@@ -1172,33 +1215,27 @@ rt_setgate(struct rtentry *rt, struct so
        }
        memmove(rt->rt_gateway, gate, glen);
 
-       if (ISSET(rt->rt_flags, RTF_GATEWAY))
-               rt_setgwroute(rt, rtableid);
+       if (ISSET(rt->rt_flags, RTF_GATEWAY)) {
+               rt_putgwroute(rt);
+               return (rt_setgwroute(rt, rtableid));
+       }
 
        return (0);
 }
 
-int
-rt_checkgate(struct rtentry *rt, struct rtentry **rtp)
+/*
+ * Return the route entry containing the next hop link-layer
+ * address corresponding to ``rt''.
+ */
+struct rtentry *
+rt_getll(struct rtentry *rt)
 {
-       struct rtentry *rt0;
-
-       KASSERT(rt != NULL);
-
-       rt0 = rt;
-
-       if (rt->rt_flags & RTF_GATEWAY) {
-               if (rt->rt_gwroute == NULL)
-                       return (EHOSTUNREACH);
-               rt = rt->rt_gwroute;
+       if (ISSET(rt->rt_flags, RTF_GATEWAY)) {
+               KASSERT(rt->rt_gwroute != NULL);
+               return (rt->rt_gwroute);
        }
 
-       if (rt->rt_flags & RTF_REJECT)
-               if (rt->rt_expire == 0 || time_uptime < rt->rt_expire)
-                       return (rt == rt0 ? EHOSTDOWN : EHOSTUNREACH);
-
-       *rtp = rt;
-       return (0);
+       return (rt);
 }
 
 void
@@ -1262,6 +1299,8 @@ rt_ifa_add(struct ifaddr *ifa, int flags
 
        error = rtrequest(RTM_ADD, &info, prio, &rt, rtableid);
        if (error == 0) {
+               unsigned int i;
+
                /*
                 * A local route is created for every address configured
                 * on an interface, so use this information to notify
@@ -1271,6 +1310,18 @@ rt_ifa_add(struct ifaddr *ifa, int flags
                        rt_sendaddrmsg(rt, RTM_NEWADDR, ifa);
                rt_sendmsg(rt, RTM_ADD, rtableid);
                rtfree(rt);
+
+               /*
+                * Userland inserted routes stay in the table even
+                * if their corresponding ``ifa'' is no longer valid.
+                *
+                * Try to fix the stale RTF_GATEWAY entries in case
+                * their gateway match the newly inserted route.
+                */
+               for (i = 0; i <= RT_TABLEID_MAX; i++) {
+                       rtable_walk(i, ifa->ifa_addr->sa_family,
+                           rt_fixgwroute, ifp);
+               }
        }
        return (error);
 }
@@ -1790,7 +1841,8 @@ rt_if_linkstate_change(struct rtentry *r
                         * from down interfaces so we have a chance to get
                         * new routes from a better source.
                         */
-                       if (ISSET(rt->rt_flags, RTF_CLONED|RTF_DYNAMIC)) {
+                       if (ISSET(rt->rt_flags, RTF_CLONED|RTF_DYNAMIC) &&
+                           !ISSET(rt->rt_flags, RTF_CACHED)) {
                                int error;
 
                                if ((error = rtdeletemsg(rt, ifp, id)))
Index: net/route.h
===================================================================
RCS file: /cvs/src/sys/net/route.h,v
retrieving revision 1.141
diff -u -p -r1.141 route.h
--- net/route.h 13 Jul 2016 08:40:46 -0000      1.141
+++ net/route.h 19 Aug 2016 09:15:32 -0000
@@ -103,7 +103,12 @@ struct rtentry {
        struct ifaddr   *rt_ifa;        /* the answer: interface addr to use */
        caddr_t          rt_llinfo;     /* pointer to link level info cache or
                                           to an MPLS structure */ 
-       struct rtentry  *rt_gwroute;    /* implied entry for gatewayed routes */
+       union {
+               struct rtentry  *_nh;   /* implied entry for gatewayed routes */
+               unsigned int     _ref;  /* # gatewayed caching this route */
+       } RT_gw;
+#define rt_gwroute      RT_gw._nh
+#define rt_cachecnt     RT_gw._ref
        struct rtentry  *rt_parent;     /* If cloned, parent of this route. */
        LIST_HEAD(, rttimer) rt_timer;  /* queue of timeouts for misc funcs */
        struct rt_kmetrics rt_rmx;      /* metrics used by rx'ing protocols */
@@ -139,6 +144,7 @@ struct rtentry {
 #define RTF_ANNOUNCE   RTF_PROTO2      /* announce L2 entry */
 #define RTF_PROTO1     0x8000          /* protocol specific routing flag */
 #define RTF_CLONED     0x10000         /* this is a cloned route */
+#define RTF_CACHED     0x20000         /* cached by a RTF_GATEWAY entry */
 #define RTF_MPATH      0x40000         /* multipath route or operation */
 #define RTF_MPLS       0x100000        /* MPLS additional infos */
 #define RTF_LOCAL      0x200000        /* route to a local address */
@@ -227,6 +233,7 @@ struct rt_msghdr {
 #define RTM_IFINFO     0xe     /* iface going up/down etc. */
 #define RTM_IFANNOUNCE 0xf     /* iface arrival/departure */
 #define RTM_DESYNC     0x10    /* route socket buffer overflow */
+#define RTM_INVALIDATE 0x11    /* Invalidate cache of L2 route */
 
 #define RTV_MTU                0x1     /* init or lock _mtu */
 #define RTV_HOPCOUNT   0x2     /* init or lock _hopcount */
@@ -363,7 +370,7 @@ void         rt_sendmsg(struct rtentry *, int, 
 void    rt_sendaddrmsg(struct rtentry *, int, struct ifaddr *);
 void    rt_missmsg(int, struct rt_addrinfo *, int, uint8_t, u_int, int, u_int);
 int     rt_setgate(struct rtentry *, struct sockaddr *, u_int);
-int     rt_checkgate(struct rtentry *, struct rtentry **);
+struct rtentry *rt_getll(struct rtentry *);
 void    rt_setmetrics(u_long, const struct rt_metrics *, struct rt_kmetrics *);
 void    rt_getmetrics(const struct rt_kmetrics *, struct rt_metrics *);
 
Index: net/rtsock.c
===================================================================
RCS file: /cvs/src/sys/net/rtsock.c,v
retrieving revision 1.194
diff -u -p -r1.194 rtsock.c
--- net/rtsock.c        11 Jul 2016 13:06:31 -0000      1.194
+++ net/rtsock.c        22 Aug 2016 10:04:11 -0000
@@ -99,6 +99,7 @@ struct walkarg {
 int    route_ctloutput(int, struct socket *, int, int, struct mbuf **);
 void   route_input(struct mbuf *m0, ...);
 int    route_arp_conflict(struct rt_addrinfo *, unsigned int);
+int    route_cleargateway(struct rtentry *, void *, unsigned int);
 
 struct mbuf    *rt_msg1(int, struct rt_addrinfo *);
 int             rt_msg2(int, int, struct rt_addrinfo *, caddr_t,
@@ -556,7 +557,7 @@ route_output(struct mbuf *m, ...)
 
        /* make sure that kernel-only bits are not set */
        rtm->rtm_priority &= RTP_MASK;
-       rtm->rtm_flags &= ~(RTF_DONE|RTF_CLONED);
+       rtm->rtm_flags &= ~(RTF_DONE|RTF_CLONED|RTF_CACHED);
        rtm->rtm_fmask &= RTF_FMASK;
 
        if (rtm->rtm_priority != 0) {
@@ -615,6 +616,33 @@ route_output(struct mbuf *m, ...)
                }
                break;
        case RTM_DELETE:
+               if (!rtable_exists(tableid)) {
+                       error = EAFNOSUPPORT;
+                       goto flush;
+               }
+
+               rt = rtable_lookup(tableid, info.rti_info[RTAX_DST],
+                   info.rti_info[RTAX_NETMASK], info.rti_info[RTAX_GATEWAY],
+                   prio);
+
+               /*
+                * Invalidate the cache of automagically created and
+                * referenced L2 entries to make sure that ``rt_gwroute''
+                * pointer stays valid for other CPUs.
+                */
+               if ((rt != NULL) && (ISSET(rt->rt_flags, RTF_CACHED))) {
+                       ifp = if_get(rt->rt_ifidx);
+                       KASSERT(ifp != NULL);
+                       ifp->if_rtrequest(ifp, RTM_INVALIDATE, rt);
+                       if_put(ifp);
+                       /* Reset the MTU of the gateway route. */
+                       rtable_walk(tableid, rt_key(rt)->sa_family,
+                           route_cleargateway, rt);
+                       goto report;
+               }
+               rtfree(rt);
+               rt = NULL;
+
                error = rtrequest(RTM_DELETE, &info, prio, &rt, tableid);
                if (error == 0)
                        goto report;
@@ -746,12 +774,6 @@ report:
                                if ((error = rt_getifa(&info, tableid)) != 0)
                                        goto flush;
                                ifa = info.rti_ifa;
-                       }
-                       if (info.rti_info[RTAX_GATEWAY] != NULL && (error =
-                           rt_setgate(rt, info.rti_info[RTAX_GATEWAY],
-                           tableid)))
-                               goto flush;
-                       if (ifa) {
                                if (rt->rt_ifa != ifa) {
                                        ifp = if_get(rt->rt_ifidx);
                                        KASSERT(ifp != NULL);
@@ -769,6 +791,10 @@ report:
 #endif
                                }
                        }
+                       if (info.rti_info[RTAX_GATEWAY] != NULL && (error =
+                           rt_setgate(rt, info.rti_info[RTAX_GATEWAY],
+                           tableid)))
+                               goto flush;
 #ifdef MPLS
                        if ((rtm->rtm_flags & RTF_MPLS) &&
                            info.rti_info[RTAX_SRC] != NULL) {
@@ -888,6 +914,18 @@ fail:
                rp->rcb_proto.sp_family = PF_ROUTE;
 
        return (error);
+}
+
+int
+route_cleargateway(struct rtentry *rt, void *arg, unsigned int rtableid)
+{
+       struct rtentry *nhrt = arg;
+
+       if (ISSET(rt->rt_flags, RTF_GATEWAY) && rt->rt_gwroute == nhrt &&
+           !ISSET(rt->rt_locks, RTV_MTU))
+                rt->rt_mtu = 0;
+
+       return (0);
 }
 
 /*
Index: netinet/if_ether.c
===================================================================
RCS file: /cvs/src/sys/netinet/if_ether.c,v
retrieving revision 1.220
diff -u -p -r1.220 if_ether.c
--- netinet/if_ether.c  14 Jul 2016 14:01:40 -0000      1.220
+++ netinet/if_ether.c  19 Aug 2016 09:15:32 -0000
@@ -78,6 +78,7 @@ int   arpt_prune = (5 * 60);  /* walk list 
 int    arpt_keep = (20 * 60);  /* once resolved, cache for 20 minutes */
 int    arpt_down = 20;         /* once declared down, don't send for 20 secs */
 
+void arpinvalidate(struct rtentry *);
 void arptfree(struct rtentry *);
 void arptimer(void *);
 struct rtentry *arplookup(struct in_addr *, int, int, unsigned int);
@@ -215,6 +216,12 @@ arp_rtrequest(struct ifnet *ifp, int req
                rt->rt_flags &= ~RTF_LLINFO;
                la_hold_total -= ml_purge(&la->la_ml);
                pool_put(&arp_pool, la);
+               break;
+
+       case RTM_INVALIDATE:
+               if (!ISSET(rt->rt_flags, RTF_LOCAL))
+                       arpinvalidate(rt);
+               break;
        }
 }
 
@@ -306,7 +313,6 @@ arpresolve(struct ifnet *ifp, struct rte
        struct sockaddr_dl *sdl;
        struct rtentry *rt = NULL;
        char addr[INET_ADDRSTRLEN];
-       int error;
 
        if (m->m_flags & M_BCAST) {     /* broadcast */
                memcpy(desten, etherbroadcastaddr, sizeof(etherbroadcastaddr));
@@ -317,10 +323,12 @@ arpresolve(struct ifnet *ifp, struct rte
                return (0);
        }
 
-       error = rt_checkgate(rt0, &rt);
-       if (error) {
+       rt = rt_getll(rt0);
+
+       if (ISSET(rt->rt_flags, RTF_REJECT) &&
+           (rt->rt_expire == 0 || time_uptime < rt->rt_expire)) {
                m_freem(m);
-               return (error);
+               return (rt == rt0 ? EHOSTDOWN : EHOSTUNREACH);
        }
 
        if (!ISSET(rt->rt_flags, RTF_LLINFO)) {
@@ -667,23 +675,31 @@ arpcache(struct ifnet *ifp, struct ether
 
        return (0);
 }
+
+void
+arpinvalidate(struct rtentry *rt)
+{
+       struct llinfo_arp *la = (struct llinfo_arp *)rt->rt_llinfo;
+       struct sockaddr_dl *sdl = satosdl(rt->rt_gateway);
+
+       la_hold_total -= ml_purge(&la->la_ml);
+       sdl->sdl_alen = 0;
+       la->la_asked = 0;
+}
+
 /*
  * Free an arp entry.
  */
 void
 arptfree(struct rtentry *rt)
 {
-       struct llinfo_arp *la = (struct llinfo_arp *)rt->rt_llinfo;
-       struct sockaddr_dl *sdl = satosdl(rt->rt_gateway);
        struct ifnet *ifp;
 
-       ifp = if_get(rt->rt_ifidx);
-       if ((sdl != NULL) && (sdl->sdl_family == AF_LINK)) {
-               sdl->sdl_alen = 0;
-               la->la_asked = 0;
-       }
+       arpinvalidate(rt);
 
-       if (!ISSET(rt->rt_flags, RTF_STATIC))
+       ifp = if_get(rt->rt_ifidx);
+       KASSERT(ifp != NULL);
+       if (!ISSET(rt->rt_flags, RTF_STATIC|RTF_CACHED))
                rtdeletemsg(rt, ifp, ifp->if_rdomain);
        if_put(ifp);
 }
Index: netinet6/nd6.c
===================================================================
RCS file: /cvs/src/sys/netinet6/nd6.c,v
retrieving revision 1.188
diff -u -p -r1.188 nd6.c
--- netinet6/nd6.c      13 Jul 2016 08:40:46 -0000      1.188
+++ netinet6/nd6.c      22 Aug 2016 10:17:20 -0000
@@ -93,6 +93,7 @@ struct nd_prhead nd_prefix = { 0 };
 int nd6_recalc_reachtm_interval = ND6_RECALC_REACHTM_INTERVAL;
 
 void nd6_slowtimo(void *);
+void nd6_invalidate(struct rtentry *);
 struct llinfo_nd6 *nd6_free(struct rtentry *, int);
 void nd6_llinfo_timer(void *);
 
@@ -711,6 +712,17 @@ nd6_is_addr_neighbor(struct sockaddr_in6
        return (0);
 }
 
+void
+nd6_invalidate(struct rtentry *rt)
+{
+       struct llinfo_nd6 *ln = (struct llinfo_nd6 *)rt->rt_llinfo;
+
+       m_freem(ln->ln_hold);
+       ln->ln_hold = NULL;
+       ln->ln_state = ND6_LLINFO_INCOMPLETE;
+       ln->ln_asked = 0;
+}
+
 /*
  * Free an nd6 llinfo entry.
  * Since the function would cause significant changes in the kernel, DO NOT
@@ -814,7 +826,7 @@ nd6_free(struct rtentry *rt, int gc)
         * caches, and disable the route entry not to be used in already
         * cached routes.
         */
-       if (!ISSET(rt->rt_flags, RTF_STATIC))
+       if (!ISSET(rt->rt_flags, RTF_STATIC|RTF_CACHED))
                rtdeletemsg(rt, ifp, ifp->if_rdomain);
        splx(s);
 
@@ -1097,6 +1109,11 @@ nd6_rtrequest(struct ifnet *ifp, int req
                rt->rt_flags &= ~RTF_LLINFO;
                m_freem(ln->ln_hold);
                pool_put(&nd6_pool, ln);
+               break;
+
+       case RTM_INVALIDATE:
+               nd6_invalidate(rt);
+               break;
        }
 }
 
@@ -1495,17 +1512,17 @@ nd6_resolve(struct ifnet *ifp, struct rt
        struct sockaddr_dl *sdl;
        struct rtentry *rt;
        struct llinfo_nd6 *ln = NULL;
-       int error;
 
        if (m->m_flags & M_MCAST) {
                ETHER_MAP_IPV6_MULTICAST(&satosin6(dst)->sin6_addr, desten);
                return (0);
        }
 
-       error = rt_checkgate(rt0, &rt);
-       if (error) {
+       rt = rt_getll(rt0);
+
+       if (ISSET(rt->rt_flags, RTF_REJECT)) {
                m_freem(m);
-               return (error);
+               return (rt == rt0 ? EHOSTDOWN : EHOSTUNREACH);
        }
 
        /*

Reply via email to