This diff place the user-set source address outside of struct art_root and make the code more readable (to me).
Based on a concept by mpi@ Index: net/art.h =================================================================== RCS file: /cvs/src/sys/net/art.h,v retrieving revision 1.20 diff -u -p -r1.20 art.h --- net/art.h 12 Nov 2020 15:25:28 -0000 1.20 +++ net/art.h 9 Jan 2021 16:04:02 -0000 @@ -42,7 +42,6 @@ struct art_root { uint8_t ar_nlvl; /* [I] Number of levels */ uint8_t ar_alen; /* [I] Address length in bits */ uint8_t ar_off; /* [I] Offset of key in bytes */ - struct sockaddr *source; /* [K] optional src addr to use */ }; #define ISLEAF(e) (((unsigned long)(e) & 1) == 0) Index: net/route.c =================================================================== RCS file: /cvs/src/sys/net/route.c,v retrieving revision 1.397 diff -u -p -r1.397 route.c --- net/route.c 29 Oct 2020 21:15:27 -0000 1.397 +++ net/route.c 9 Jan 2021 16:04:02 -0000 @@ -1192,9 +1192,9 @@ rt_ifa_del(struct ifaddr *ifa, int flags if (flags & RTF_CONNECTED) prio = ifp->if_priority + RTP_CONNECTED; - rtable_clearsource(rdomain, ifa->ifa_addr); error = rtrequest_delete(&info, prio, ifp, &rt, rdomain); if (error == 0) { + rt_sourceclear(rt, rdomain); rtm_send(rt, RTM_DELETE, 0, rdomain); if (flags & RTF_LOCAL) rtm_addr(RTM_DELADDR, ifa); Index: net/route.h =================================================================== RCS file: /cvs/src/sys/net/route.h,v retrieving revision 1.183 diff -u -p -r1.183 route.h --- net/route.h 29 Oct 2020 21:15:27 -0000 1.183 +++ net/route.h 9 Jan 2021 16:04:02 -0000 @@ -478,6 +478,9 @@ int rtrequest_delete(struct rt_addrinfo int rt_if_track(struct ifnet *); int rt_if_linkstate_change(struct rtentry *, void *, u_int); int rtdeletemsg(struct rtentry *, struct ifnet *, u_int); + +struct ifaddr *rt_get_ifa(struct rtentry *, unsigned int); +void rt_sourceclear(struct rtentry *, unsigned int); #endif /* _KERNEL */ #endif /* _NET_ROUTE_H_ */ Index: net/rtable.c =================================================================== RCS file: /cvs/src/sys/net/rtable.c,v retrieving revision 1.72 diff -u -p -r1.72 rtable.c --- net/rtable.c 7 Nov 2020 09:51:40 -0000 1.72 +++ net/rtable.c 9 Jan 2021 16:04:02 -0000 @@ -365,44 +365,6 @@ rtable_alloc(unsigned int rtableid, unsi return (art_alloc(rtableid, alen, off)); } -int -rtable_setsource(unsigned int rtableid, int af, struct sockaddr *src) -{ - struct art_root *ar; - - if ((ar = rtable_get(rtableid, af)) == NULL) - return (EAFNOSUPPORT); - - ar->source = src; - - return (0); -} - -struct sockaddr * -rtable_getsource(unsigned int rtableid, int af) -{ - struct art_root *ar; - - ar = rtable_get(rtableid, af); - if (ar == NULL) - return (NULL); - - return (ar->source); -} - -void -rtable_clearsource(unsigned int rtableid, struct sockaddr *src) -{ - struct sockaddr *addr; - - addr = rtable_getsource(rtableid, src->sa_family); - if (addr && (addr->sa_len == src->sa_len)) { - if (memcmp(src, addr, addr->sa_len) == 0) { - rtable_setsource(rtableid, src->sa_family, NULL); - } - } -} - struct rtentry * rtable_lookup(unsigned int rtableid, struct sockaddr *dst, struct sockaddr *mask, struct sockaddr *gateway, uint8_t prio) Index: net/rtable.h =================================================================== RCS file: /cvs/src/sys/net/rtable.h,v retrieving revision 1.26 diff -u -p -r1.26 rtable.h --- net/rtable.h 7 Nov 2020 09:51:40 -0000 1.26 +++ net/rtable.h 9 Jan 2021 16:04:02 -0000 @@ -39,9 +39,6 @@ unsigned int rtable_l2(unsigned int); unsigned int rtable_loindex(unsigned int); void rtable_l2set(unsigned int, unsigned int, unsigned int); -int rtable_setsource(unsigned int, int, struct sockaddr *); -struct sockaddr *rtable_getsource(unsigned int, int); -void rtable_clearsource(unsigned int, struct sockaddr *); struct rtentry *rtable_lookup(unsigned int, struct sockaddr *, struct sockaddr *, struct sockaddr *, uint8_t); struct rtentry *rtable_match(unsigned int, struct sockaddr *, uint32_t *); Index: net/rtsock.c =================================================================== RCS file: /cvs/src/sys/net/rtsock.c,v retrieving revision 1.304 diff -u -p -r1.304 rtsock.c --- net/rtsock.c 7 Nov 2020 09:51:40 -0000 1.304 +++ net/rtsock.c 9 Jan 2021 16:04:02 -0000 @@ -138,7 +138,8 @@ int sysctl_iflist(int, struct walkarg int sysctl_ifnames(struct walkarg *); int sysctl_rtable_rtstat(void *, size_t *, void *); -int rt_setsource(unsigned int, struct sockaddr *); +int rt_sourceset(struct rtentry *, unsigned int); +struct rtentry *rt_get_rt(int, unsigned int); /* * Locks used to protect struct members @@ -170,6 +171,14 @@ struct rtptable { struct pool rtpcb_pool; struct rtptable rtptable; +struct rt_srcaddr { + LIST_ENTRY(rt_srcaddr) rts_next; + unsigned int rts_rtableid; + struct rtentry *rts_rt; +}; + +LIST_HEAD(, rt_srcaddr) srcaddr_h = LIST_HEAD_INITIALIZER(srcaddr_h); + /* * These flags and timeout are used for indicating to userland (via a * RTM_DESYNC msg) when the route socket has overflowed and messages @@ -664,10 +673,7 @@ rtm_report(struct rtentry *rt, u_char ty ifp = if_get(rt->rt_ifidx); if (ifp != NULL) { info.rti_info[RTAX_IFP] = sdltosa(ifp->if_sadl); - info.rti_info[RTAX_IFA] = - rtable_getsource(tableid, info.rti_info[RTAX_DST]->sa_family); - if (info.rti_info[RTAX_IFA] == NULL) - info.rti_info[RTAX_IFA] = rt->rt_ifa->ifa_addr; + info.rti_info[RTAX_IFA] = rt_get_ifa(rt, tableid)->ifa_addr; if (ifp->if_flags & IFF_POINTOPOINT) info.rti_info[RTAX_BRD] = rt->rt_ifa->ifa_dstaddr; } @@ -860,10 +866,28 @@ route_output(struct mbuf *m, struct sock if (info.rti_info[RTAX_IFA] == NULL) { error = EINVAL; goto fail; + } else if ((info.rti_info[RTAX_IFA]->sa_family == AF_INET6 && + IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *) + info.rti_info[RTAX_IFA])->sin6_addr)) || + (info.rti_info[RTAX_IFA]->sa_family == AF_INET && + ((struct sockaddr_in *) + info.rti_info[RTAX_IFA])->sin_addr.s_addr == 0)) { + rt_sourceclear(rt_get_rt(info.rti_info[RTAX_IFA]->sa_family, + tableid), tableid); + rtfree(rt); + rt = NULL; + } else { + rt = rtalloc(info.rti_info[RTAX_IFA], 0, tableid); + if (rt == NULL || !ISSET(rt->rt_flags, RTF_LOCAL)) { + error = EINVAL; + goto fail; + } + NET_LOCK(); + error = rt_sourceset(rt, tableid); + NET_UNLOCK(); + if (error != 0) + goto fail; } - if ((error = - rt_setsource(tableid, info.rti_info[RTAX_IFA])) != 0) - goto fail; } else { error = rtm_output(rtm, &rt, &info, prio, tableid); if (!error) { @@ -873,9 +897,9 @@ route_output(struct mbuf *m, struct sock rtm = rtm_report(rt, type, seq, tableid); len = rtm->rtm_msglen; } + rtfree(rt); } - rtfree(rt); if (error) { rtm->rtm_errno = error; } else { @@ -1687,10 +1711,7 @@ rtm_send(struct rtentry *rt, int cmd, in ifp = if_get(rt->rt_ifidx); if (ifp != NULL) { info.rti_info[RTAX_IFP] = sdltosa(ifp->if_sadl); - info.rti_info[RTAX_IFA] = - rtable_getsource(rtableid, info.rti_info[RTAX_DST]->sa_family); - if (info.rti_info[RTAX_IFA] == NULL) - info.rti_info[RTAX_IFA] = rt->rt_ifa->ifa_addr; + info.rti_info[RTAX_IFA] = rt_get_ifa(rt, rtableid)->ifa_addr; } rtm_miss(cmd, &info, rt->rt_flags, rt->rt_priority, rt->rt_ifidx, error, @@ -1928,10 +1949,7 @@ sysctl_dumpentry(struct rtentry *rt, voi ifp = if_get(rt->rt_ifidx); if (ifp != NULL) { info.rti_info[RTAX_IFP] = sdltosa(ifp->if_sadl); - info.rti_info[RTAX_IFA] = - rtable_getsource(id, info.rti_info[RTAX_DST]->sa_family); - if (info.rti_info[RTAX_IFA] == NULL) - info.rti_info[RTAX_IFA] = rt->rt_ifa->ifa_addr; + info.rti_info[RTAX_IFA] = rt_get_ifa(rt, id)->ifa_addr; if (ifp->if_flags & IFF_POINTOPOINT) info.rti_info[RTAX_BRD] = rt->rt_ifa->ifa_dstaddr; } @@ -2067,33 +2085,30 @@ sysctl_ifnames(struct walkarg *w) } int -sysctl_source(int af, u_int tableid, struct walkarg *w) +sysctl_source(int af, struct walkarg *w) { - struct sockaddr *sa; - int size, error = 0; + struct rt_srcaddr *rtsa = NULL; + unsigned int tableid = w->w_arg; + int error = 0; - sa = rtable_getsource(tableid, af); - if (sa) { - switch (sa->sa_family) { - case AF_INET: - size = sizeof(struct sockaddr_in); - break; -#ifdef INET6 - case AF_INET6: - size = sizeof(struct sockaddr_in6); - break; -#endif - default: - return (0); - } - w->w_needed += size; + LIST_FOREACH(rtsa, &srcaddr_h, rts_next) { + if (rtsa->rts_rtableid != tableid) + continue; + if (af != 0 && rtsa->rts_rt->rt_dest->sa_family != af) + continue; + + w->w_needed += rtsa->rts_rt->rt_dest->sa_len; if (w->w_where && w->w_needed <= 0) { - if ((error = copyout(sa, w->w_where, size))) - return (error); - w->w_where += size; + error = copyout(rtsa->rts_rt->rt_ifa->ifa_addr, + w->w_where, rtsa->rts_rt->rt_dest->sa_len); + if (error == EAFNOSUPPORT) + error = 0; + if (error) + break; + w->w_where += rtsa->rts_rt->rt_dest->sa_len; } } - return (0); + return (error); } int @@ -2171,16 +2186,7 @@ sysctl_rtable(int *name, u_int namelen, if (!rtable_exists(tableid)) return (ENOENT); NET_LOCK(); - for (i = 1; i <= AF_MAX; i++) { - if (af != 0 && af != i) - continue; - - error = sysctl_source(i, tableid, &w); - if (error == EAFNOSUPPORT) - error = 0; - if (error) - break; - } + error = sysctl_source(af, &w); NET_UNLOCK(); break; } @@ -2314,40 +2320,96 @@ rtm_validate_proposal(struct rt_addrinfo } int -rt_setsource(unsigned int rtableid, struct sockaddr *src) +rt_sourceset(struct rtentry *rt, unsigned int rtableid) { - struct ifaddr *ifa; - /* - * If source address is 0.0.0.0 or :: - * use automatic source selection - */ - switch(src->sa_family) { - case AF_INET: - if(satosin(src)->sin_addr.s_addr == INADDR_ANY) { - rtable_setsource(rtableid, AF_INET, NULL); - return (0); - } - break; -#ifdef INET6 - case AF_INET6: - if (IN6_IS_ADDR_UNSPECIFIED(&satosin6(src)->sin6_addr)) { - rtable_setsource(rtableid, AF_INET6, NULL); - return (0); + struct rt_srcaddr *rtsa = NULL; + + LIST_FOREACH(rtsa, &srcaddr_h, rts_next) { + if (rtsa->rts_rtableid == rtableid && + rtsa->rts_rt->rt_dest->sa_family == rt->rt_dest->sa_family) + break; + } + if (rtsa == NULL) { + if ((rtsa = malloc(sizeof(struct rt_srcaddr), M_IFADDR, + M_NOWAIT|M_ZERO)) == NULL) + return (ENOMEM); + rtsa->rts_rtableid = rtableid; + rtsa->rts_rt = rt; + LIST_INSERT_HEAD(&srcaddr_h, rtsa, rts_next); + } else { + /* Update existing entry */ + rtfree(rtsa->rts_rt); + rtsa->rts_rt = rt; + } + + return (0); +} + +/* + * Return the 'ifa' associated to a given 'rt' unless a preferred + * source address has been specified for the same routing table. + */ +struct rtentry * +rt_get_rt(int af, unsigned int rtableid) +{ + struct rt_srcaddr *rtsa = NULL; + + LIST_FOREACH(rtsa, &srcaddr_h, rts_next) { + if (rtsa->rts_rtableid == rtableid && + rtsa->rts_rt->rt_dest->sa_family == af) + break; + } + if (rtsa) + return (rtsa->rts_rt); + + return (NULL); +} + +struct ifaddr * +rt_get_ifa(struct rtentry *rt, unsigned int rtableid) +{ + struct rt_srcaddr *rtsa = NULL; + struct ifnet *ifp = NULL; + + if (ISSET(rt->rt_flags, RTF_HOST|RTF_LLINFO)) + return (rt->rt_ifa); + + LIST_FOREACH(rtsa, &srcaddr_h, rts_next) { + if (rtsa->rts_rtableid == rtableid && + rtsa->rts_rt->rt_dest->sa_family == rt->rt_dest->sa_family) + break; + } + if (rtsa != NULL && rtisvalid(rtsa->rts_rt)) { + struct rtentry *nrt = rtsa->rts_rt; + + ifp = if_get(nrt->rt_ifidx); + if (ifp != NULL) { + if (ISSET(ifp->if_flags, IFF_UP)) { + if_put(ifp); + return (nrt->rt_ifa); + } } - break; -#endif - default: - return (EAFNOSUPPORT); } + return (rt->rt_ifa); +} - /* - * Check if source address is assigned to an interface in the - * same rdomain - */ - if ((ifa = ifa_ifwithaddr(src, rtableid)) == NULL) - return (EINVAL); +void +rt_sourceclear(struct rtentry *rt, unsigned int rtableid) +{ + struct rt_srcaddr *rtsa = NULL; - return (rtable_setsource(rtableid, src->sa_family, ifa->ifa_addr)); + if (rt == NULL) + return; + + LIST_FOREACH(rtsa, &srcaddr_h, rts_next) { + if (rtsa->rts_rtableid != rtableid) + continue; + if (rtsa->rts_rt == rt) { + LIST_REMOVE(rtsa, rts_next); + free(rtsa, M_IFADDR, sizeof(struct rt_srcaddr)); + break; + } + } } /* Index: netinet/in_pcb.c =================================================================== RCS file: /cvs/src/sys/netinet/in_pcb.c,v retrieving revision 1.252 diff -u -p -r1.252 in_pcb.c --- netinet/in_pcb.c 7 Nov 2020 09:51:40 -0000 1.252 +++ netinet/in_pcb.c 9 Jan 2021 16:04:02 -0000 @@ -887,7 +887,6 @@ in_pcbselsrc(struct in_addr **insrc, str struct route *ro = &inp->inp_route; struct in_addr *laddr = &inp->inp_laddr; u_int rtableid = inp->inp_rtableid; - struct sockaddr *ip4_source = NULL; struct sockaddr_in *sin2; struct in_ifaddr *ia = NULL; @@ -951,30 +950,11 @@ in_pcbselsrc(struct in_addr **insrc, str } /* - * If we found a route, use the address - * corresponding to the outgoing interface. + * If we found a route, use the address corresponding to the + * outgoing interface or the preferred source address if set. */ if (ro->ro_rt != NULL) - ia = ifatoia(ro->ro_rt->rt_ifa); - - /* - * Use preferred source address if : - * - destination is not onlink - * - preferred source addresss is set - * - output interface is UP - */ - if (ro->ro_rt && !(ro->ro_rt->rt_flags & RTF_LLINFO) && - !(ro->ro_rt->rt_flags & RTF_HOST)) { - ip4_source = rtable_getsource(rtableid, AF_INET); - if (ip4_source != NULL) { - struct ifaddr *ifa; - if ((ifa = ifa_ifwithaddr(ip4_source, rtableid)) != - NULL && ISSET(ifa->ifa_ifp->if_flags, IFF_UP)) { - *insrc = &satosin(ip4_source)->sin_addr; - return (0); - } - } - } + ia = ifatoia(rt_get_ifa(ro->ro_rt, rtableid)); if (ia == NULL) return (EADDRNOTAVAIL); Index: netinet/ip_icmp.c =================================================================== RCS file: /cvs/src/sys/netinet/ip_icmp.c,v retrieving revision 1.184 diff -u -p -r1.184 ip_icmp.c --- netinet/ip_icmp.c 20 Dec 2020 21:15:47 -0000 1.184 +++ netinet/ip_icmp.c 9 Jan 2021 16:04:02 -0000 @@ -745,7 +745,7 @@ icmp_reflect(struct mbuf *m, struct mbuf return (EHOSTUNREACH); } - ia = ifatoia(rt->rt_ifa); + ia = ifatoia(rt_get_ifa(rt, rtableid)); } ip->ip_dst = ip->ip_src; Index: netinet6/icmp6.c =================================================================== RCS file: /cvs/src/sys/netinet6/icmp6.c,v retrieving revision 1.233 diff -u -p -r1.233 icmp6.c --- netinet6/icmp6.c 28 Oct 2020 17:27:35 -0000 1.233 +++ netinet6/icmp6.c 9 Jan 2021 16:04:02 -0000 @@ -1163,7 +1163,10 @@ icmp6_reflect(struct mbuf **mp, size_t o rtfree(rt); goto bad; } - ia6 = in6_ifawithscope(rt->rt_ifa->ifa_ifp, &t, rtableid); + ia6 = ifatoia6(rt_get_ifa(rt, rtableid)); + if (ia6 == NULL) + ia6 = in6_ifawithscope(rt->rt_ifa->ifa_ifp, &t, + rtableid); if (ia6 != NULL) src = &ia6->ia_addr.sin6_addr; if (src == NULL) Index: netinet6/in6_src.c =================================================================== RCS file: /cvs/src/sys/netinet6/in6_src.c,v retrieving revision 1.84 diff -u -p -r1.84 in6_src.c --- netinet6/in6_src.c 7 Nov 2020 09:51:40 -0000 1.84 +++ netinet6/in6_src.c 9 Jan 2021 16:04:02 -0000 @@ -100,7 +100,6 @@ in6_pcbselsrc(struct in6_addr **in6src, struct in6_addr *laddr = &inp->inp_laddr6; u_int rtableid = inp->inp_rtableid; struct ifnet *ifp = NULL; - struct sockaddr *ip6_source = NULL; struct in6_addr *dst; struct in6_ifaddr *ia6 = NULL; struct in6_pktinfo *pi = NULL; @@ -208,32 +207,16 @@ in6_pcbselsrc(struct in6_addr **in6src, */ if (ro->ro_rt) { - ifp = if_get(ro->ro_rt->rt_ifidx); - if (ifp != NULL) { - ia6 = in6_ifawithscope(ifp, dst, rtableid); - if_put(ifp); + ia6 = ifatoia6(rt_get_ifa(ro->ro_rt, rtableid)); + if (ia6 == NULL) { + ifp = if_get(ro->ro_rt->rt_ifidx); + if (ifp != NULL) { + ia6 = in6_ifawithscope(ifp, dst, rtableid); + if_put(ifp); + } } if (ia6 == NULL) /* xxx scope error ?*/ ia6 = ifatoia6(ro->ro_rt->rt_ifa); - } - - /* - * Use preferred source address if : - * - destination is not onlink - * - preferred source addresss is set - * - output interface is UP - */ - if (ro->ro_rt && !(ro->ro_rt->rt_flags & RTF_LLINFO) && - !(ro->ro_rt->rt_flags & RTF_HOST)) { - ip6_source = rtable_getsource(rtableid, AF_INET6); - if (ip6_source != NULL) { - struct ifaddr *ifa; - if ((ifa = ifa_ifwithaddr(ip6_source, rtableid)) != - NULL && ISSET(ifa->ifa_ifp->if_flags, IFF_UP)) { - *in6src = &satosin6(ip6_source)->sin6_addr; - return (0); - } - } } if (ia6 == NULL)