-/* $OpenBSD: route.c,v 1.315 2016/08/19 07:12:54 mpi Exp $ */
+/* $OpenBSD: route.c,v 1.316 2016/08/22 16:01:52 mpi Exp $ */
/* $NetBSD: route.c,v 1.14 1996/02/13 22:00:46 christos Exp $ */
/*
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);
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);
}
return (rt);
}
-struct rtentry *_rtalloc(struct sockaddr *, uint32_t *, int, unsigned int);
-
#ifndef SMALL_KERNEL
/*
* Originated from bridge_hash() in if_bridge.c
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''.
* 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
*/
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);
}
/*
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
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;
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);
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);
}
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
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
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);
}
* 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)))
-/* $OpenBSD: route.h,v 1.141 2016/07/13 08:40:46 mpi Exp $ */
+/* $OpenBSD: route.h,v 1.142 2016/08/22 16:01:52 mpi Exp $ */
/* $NetBSD: route.h,v 1.9 1996/02/13 22:00:49 christos Exp $ */
/*
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 */
#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 */
#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 */
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 *);
-/* $OpenBSD: rtsock.c,v 1.194 2016/07/11 13:06:31 bluhm Exp $ */
+/* $OpenBSD: rtsock.c,v 1.195 2016/08/22 16:01:52 mpi Exp $ */
/* $NetBSD: rtsock.c,v 1.18 1996/03/29 00:32:10 cgd Exp $ */
/*
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,
/* 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) {
}
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;
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);
#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) {
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);
+}
+
/*
* Check if the user request to insert an ARP entry does not conflict
* with existing ones.
-/* $OpenBSD: if_ether.c,v 1.220 2016/07/14 14:01:40 mpi Exp $ */
+/* $OpenBSD: if_ether.c,v 1.221 2016/08/22 16:01:52 mpi Exp $ */
/* $NetBSD: if_ether.c,v 1.31 1996/05/11 12:59:58 mycroft Exp $ */
/*
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);
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;
}
}
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));
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)) {
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);
}