From b638c7753d8498eaec90f06a7c140ffe0ed76cdb Mon Sep 17 00:00:00 2001 From: cinap_lenrek Date: Sun, 10 Nov 2019 19:50:46 +0100 Subject: [PATCH] devip: use the routing table for local source ip address selection when making outgoing connections, the source ip was selected by just iterating from the first to the last interface and trying each local address until a route was found. the result was kind of hard to predict as it depends on the interface order. this change replaces the algorithm with the route lookup algorithm that we already have which takes more specific desination and source prefixes into account. so the order of interfaces does not matter anymore. --- sys/src/9/ip/arp.c | 6 +- sys/src/9/ip/ethermedium.c | 2 +- sys/src/9/ip/icmp.c | 2 +- sys/src/9/ip/icmp6.c | 12 +-- sys/src/9/ip/ip.h | 6 +- sys/src/9/ip/ipifc.c | 82 ++++++--------- sys/src/9/ip/iproute.c | 201 +++++++++++++++++++++++++++++++------ sys/src/9/ip/rudp.c | 11 +- sys/src/9/ip/udp.c | 2 +- 9 files changed, 221 insertions(+), 103 deletions(-) diff --git a/sys/src/9/ip/arp.c b/sys/src/9/ip/arp.c index f71b0bad6..9c1a0cfc7 100644 --- a/sys/src/9/ip/arp.c +++ b/sys/src/9/ip/arp.c @@ -401,7 +401,7 @@ arpwrite(Fs *fs, char *s, int len) if((ifc = findipifc(fs, ia, ia, Runi)) == nil) error("no interface"); rlock(ifc); - if(!ipv6local(ifc, ia, ip) || arpenter(fs, V6, ip, mac, n, ia, ifc, 0) < 0){ + if(!ipv6local(ifc, ia, 0, ip) || arpenter(fs, V6, ip, mac, n, ia, ifc, 0) < 0){ runlock(ifc); error("destination unreachable"); } @@ -450,7 +450,7 @@ arpread(Arp *arp, char *s, ulong offset, int len) qlock(arp); state = arpstate[a->state]; ipmove(ip, a->ip); - if(ifc->m == nil || a->ifcid != ifc->ifcid || !ipv6local(ifc, ia, ip)){ + if(ifc->m == nil || a->ifcid != ifc->ifcid || !ipv6local(ifc, ia, 0, ip)){ qunlock(arp); runlock(ifc); continue; @@ -507,7 +507,7 @@ ndpsendsol(Fs *f, Ipifc *ifc, Arpent *a) } else { arprelease(f->arp, a); } - if(!ipv6local(ifc, src, targ)) + if(!ipv6local(ifc, src, 0, targ)) return; send: if(!waserror()){ diff --git a/sys/src/9/ip/ethermedium.c b/sys/src/9/ip/ethermedium.c index 41928e89f..0f0975fc2 100644 --- a/sys/src/9/ip/ethermedium.c +++ b/sys/src/9/ip/ethermedium.c @@ -462,7 +462,7 @@ sendarp(Ipifc *ifc, Arpent *a) memmove(targ, a->ip+IPv4off, IPv4addrlen); arprelease(er->f->arp, a); - if(!ipv4local(ifc, src, targ)) + if(!ipv4local(ifc, src, 0, targ)) return; n = sizeof(Etherarp); diff --git a/sys/src/9/ip/icmp.c b/sys/src/9/ip/icmp.c index 5a2a810e8..93f0cd173 100644 --- a/sys/src/9/ip/icmp.c +++ b/sys/src/9/ip/icmp.c @@ -218,7 +218,7 @@ icmpttlexceeded(Fs *f, Ipifc *ifc, Block *bp) uchar ia[IPv4addrlen]; p = (Icmp *)bp->rp; - if(!ip4reply(f, p->src) || !ipv4local(ifc, ia, p->src)) + if(!ip4reply(f, p->src) || !ipv4local(ifc, ia, 0, p->src)) return; netlog(f, Logicmp, "sending icmpttlexceeded %V -> src %V dst %V\n", diff --git a/sys/src/9/ip/icmp6.c b/sys/src/9/ip/icmp6.c index 4f483a79d..a4b4b50c8 100644 --- a/sys/src/9/ip/icmp6.c +++ b/sys/src/9/ip/icmp6.c @@ -332,7 +332,7 @@ mkechoreply6(Block *bp, Ipifc *ifc) ipmove(addr, p->src); if(!isv6mcast(p->dst)) ipmove(p->src, p->dst); - else if (!ipv6local(ifc, p->src, addr)) + else if (!ipv6local(ifc, p->src, 0, addr)) return nil; ipmove(p->dst, addr); p->type = EchoReplyV6; @@ -434,7 +434,7 @@ icmphostunr6(Fs *f, Ipifc *ifc, Block *bp, int code, int tome) uchar ia[IPaddrlen]; p = (Ip6hdr *)bp->rp; - if(isv6mcast(p->dst) || isv6mcast(p->src) || !ipv6local(ifc, ia, p->src)) + if(isv6mcast(p->dst) || isv6mcast(p->src) || !ipv6local(ifc, ia, 0, p->src)) return; netlog(f, Logicmp, "send icmphostunr %I -> src %I dst %I\n", @@ -471,7 +471,7 @@ icmpttlexceeded6(Fs *f, Ipifc *ifc, Block *bp) uchar ia[IPaddrlen]; p = (Ip6hdr *)bp->rp; - if(isv6mcast(p->dst) || isv6mcast(p->src) || !ipv6local(ifc, ia, p->src)) + if(isv6mcast(p->dst) || isv6mcast(p->src) || !ipv6local(ifc, ia, 0, p->src)) return; netlog(f, Logicmp, "send icmpttlexceeded6 %I -> src %I dst %I\n", @@ -504,7 +504,7 @@ icmppkttoobig6(Fs *f, Ipifc *ifc, Block *bp) uchar ia[IPaddrlen]; p = (Ip6hdr *)bp->rp; - if(isv6mcast(p->dst) || isv6mcast(p->src) || !ipv6local(ifc, ia, p->src)) + if(isv6mcast(p->dst) || isv6mcast(p->src) || !ipv6local(ifc, ia, 0, p->src)) return; netlog(f, Logicmp, "send icmppkttoobig6 %I -> src %I dst %I\n", @@ -769,7 +769,7 @@ icmpiput6(Proto *icmp, Ipifc *ifc, Block *bp) /* fall through */ case Tuniproxy: - if(ipv6local(ifc, ia, np->src)) { + if(ipv6local(ifc, ia, 0, np->src)) { if(arpenter(icmp->f, V6, np->src, np->lnaddr, 8*np->olen-2, ia, ifc, 0) < 0) break; pktflags |= Sflag; @@ -801,7 +801,7 @@ icmpiput6(Proto *icmp, Ipifc *ifc, Block *bp) lifc = iplocalonifc(ifc, np->target); if(lifc != nil && lifc->tentative) arpenter(icmp->f, V6, np->target, np->lnaddr, 8*np->olen-2, np->target, ifc, 0); - else if(ipv6local(ifc, ia, np->target)) + else if(ipv6local(ifc, ia, 0, np->target)) arpenter(icmp->f, V6, np->target, np->lnaddr, 8*np->olen-2, ia, ifc, 1); freeblist(bp); break; diff --git a/sys/src/9/ip/ip.h b/sys/src/9/ip/ip.h index 676c09fc4..9a444466b 100644 --- a/sys/src/9/ip/ip.h +++ b/sys/src/9/ip/ip.h @@ -567,6 +567,8 @@ extern void addroute(Fs *f, uchar *a, uchar *mask, uchar *s, uchar *smask, uchar extern void remroute(Fs *f, uchar *a, uchar *mask, uchar *s, uchar *smask, uchar *gate, int type, Ipifc *ifc, char *tag); extern Route* v4lookup(Fs *f, uchar *a, uchar *s, Routehint *h); extern Route* v6lookup(Fs *f, uchar *a, uchar *s, Routehint *h); +extern Route* v4source(Fs *f, uchar *a, uchar *s); +extern Route* v6source(Fs *f, uchar *a, uchar *s); extern long routeread(Fs *f, char*, ulong, int); extern long routewrite(Fs *f, Chan*, char*, int); extern void routetype(int type, char p[8]); @@ -664,8 +666,8 @@ extern int ipismulticast(uchar *ip); extern Ipifc* findipifc(Fs*, uchar *local, uchar *remote, int type); extern Ipifc* findipifcstr(Fs *f, char *s); extern void findlocalip(Fs*, uchar *local, uchar *remote); -extern int ipv4local(Ipifc *ifc, uchar *local, uchar *remote); -extern int ipv6local(Ipifc *ifc, uchar *local, uchar *remote); +extern int ipv4local(Ipifc *ifc, uchar *local, int prefixlen, uchar *remote); +extern int ipv6local(Ipifc *ifc, uchar *local, int prefixlen, uchar *remote); extern Iplifc* iplocalonifc(Ipifc *ifc, uchar *ip); extern Iplifc* ipremoteonifc(Ipifc *ifc, uchar *ip); extern int ipproxyifc(Fs *f, Ipifc *ifc, uchar *ip); diff --git a/sys/src/9/ip/ipifc.c b/sys/src/9/ip/ipifc.c index 45187583b..3a4380877 100644 --- a/sys/src/9/ip/ipifc.c +++ b/sys/src/9/ip/ipifc.c @@ -1263,10 +1263,17 @@ findprimaryipv4(Fs *f, uchar *local) } /* - * return v4 address associated with an interface close to remote + * ipv4local, ipv6local: + * return a local address associated with an interface close to remote. + * prefixlen is the number of leading bits in the local address that + * have to match an interface address to be considered. this is used + * by source specific routes to filter on the source address. + * return non-zero on success or zero when no address was found. + * + * for ipv4local, all addresses are 4 byte format. */ int -ipv4local(Ipifc *ifc, uchar *local, uchar *remote) +ipv4local(Ipifc *ifc, uchar *local, int prefixlen, uchar *remote) { Iplifc *lifc; int a, b; @@ -1275,6 +1282,10 @@ ipv4local(Ipifc *ifc, uchar *local, uchar *remote) for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){ if((lifc->type & Rv4) == 0 || ipcmp(lifc->local, IPnoaddr) == 0) continue; + + if(prefixlen && comprefixlen(lifc->local+IPv4off, local, IPv4addrlen) < prefixlen) + continue; + a = comprefixlen(lifc->local+IPv4off, remote, IPv4addrlen); if(a > b){ b = a; @@ -1284,11 +1295,8 @@ ipv4local(Ipifc *ifc, uchar *local, uchar *remote) return b >= 0; } -/* - * return v6 address associated with an interface close to remote - */ int -ipv6local(Ipifc *ifc, uchar *local, uchar *remote) +ipv6local(Ipifc *ifc, uchar *local, int prefixlen, uchar *remote) { struct { int atype; @@ -1300,12 +1308,13 @@ ipv6local(Ipifc *ifc, uchar *local, uchar *remote) Iplifc *lifc; if(isv4(remote)){ - ipmove(local, v4prefix); - return ipv4local(ifc, local+IPv4off, remote+IPv4off); + memmove(local, v4prefix, IPv4off); + if((prefixlen -= IPv4off*8) < 0) + prefixlen = 0; + return ipv4local(ifc, local+IPv4off, prefixlen, remote+IPv4off); } atype = v6addrtype(remote); - ipmove(local, v6Unspecified); b.atype = unknownv6; b.deprecated = 1; b.comprefixlen = 0; @@ -1315,6 +1324,9 @@ ipv6local(Ipifc *ifc, uchar *local, uchar *remote) if(lifc->tentative) continue; + if(prefixlen && comprefixlen(lifc->local, local, IPaddrlen) < prefixlen) + continue; + a.atype = v6addrtype(lifc->local); a.deprecated = lifc->preflt != ~0UL && lifc->preflt < now-lifc->origint; a.comprefixlen = comprefixlen(lifc->local, remote, IPaddrlen); @@ -1347,54 +1359,22 @@ ipv6local(Ipifc *ifc, uchar *local, uchar *remote) return b.atype >= atype; } +/* + * find the local address for a remote destination + */ void findlocalip(Fs *f, uchar *local, uchar *remote) { - Route *r; - Iplifc *lifc; - Ipifc *ifc, *nifc; - Conv **cp; - - for(cp = f->ipifc->conv; *cp != nil; cp++){ - ifc = (Ipifc*)(*cp)->ptcl; - rlock(ifc); - for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){ - if(lifc->tentative) - continue; - - r = v6lookup(f, remote, lifc->local, nil); - if(r == nil || (nifc = r->ifc) == nil) - continue; - if(r->type & Runi){ - ipmove(local, remote); - runlock(ifc); - return; - } - if(nifc != ifc) rlock(nifc); - if((r->type & (Rifc|Rbcast|Rmulti|Rv4)) == Rv4){ - ipmove(local, v4prefix); - if(ipv4local(nifc, local+IPv4off, r->v4.gate)){ - if(nifc != ifc) runlock(nifc); - runlock(ifc); - return; - } - } - if(ipv6local(nifc, local, remote)){ - if(nifc != ifc) runlock(nifc); - runlock(ifc); - return; - } - if(nifc != ifc) runlock(nifc); - } - runlock(ifc); + if(isv4(remote)) { + memmove(local, v4prefix, IPv4off); + if(v4source(f, remote+IPv4off, local+IPv4off) == nil) + findprimaryipv4(f, local); + } else { + if(v6source(f, remote, local) == nil) + findprimaryipv6(f, local); } - if(isv4(remote)) - findprimaryipv4(f, local); - else - findprimaryipv6(f, local); } - /* * see if this address is bound to the interface */ diff --git a/sys/src/9/ip/iproute.c b/sys/src/9/ip/iproute.c index 65ea63ff2..e90f61d44 100644 --- a/sys/src/9/ip/iproute.c +++ b/sys/src/9/ip/iproute.c @@ -535,10 +535,64 @@ remroute(Fs *f, uchar *a, uchar *mask, uchar *s, uchar *smask, uchar *gate, int wunlock(&routelock); } +/* get the outgoing interface for route r */ +static Ipifc* +routefindipifc(Route *r, Fs *f) +{ + uchar local[IPaddrlen], gate[IPaddrlen]; + Ipifc *ifc; + int i; + + ifc = r->ifc; + if(ifc != nil && ifc->ifcid == r->ifcid) + return ifc; + + if(r->type & Rsrc) { + if(r->type & Rv4) { + hnputl(local+IPv4off, r->v4.source); + memmove(local, v4prefix, IPv4off); + } else { + for(i = 0; i < IPllen; i++) + hnputl(local+4*i, r->v6.source[i]); + } + } else { + ipmove(local, IPnoaddr); + } + + if(r->type & Rifc) { + if(r->type & Rv4) { + hnputl(gate+IPv4off, r->v4.address); + memmove(gate, v4prefix, IPv4off); + } else { + for(i = 0; i < IPllen; i++) + hnputl(gate+4*i, r->v6.address[i]); + } + } else { + if(r->type & Rv4) + v4tov6(gate, r->v4.gate); + else + ipmove(gate, r->v6.gate); + } + + if((ifc = findipifc(f, local, gate, r->type)) == nil) + return nil; + + r->ifc = ifc; + r->ifcid = ifc->ifcid; + return ifc; +} + +/* + * v4lookup, v6lookup: + * lookup a route to destination address a from source address s + * and return the route. returns nil if no route was found. + * an optional Routehint can be passed in rh to cache the lookup. + * + * for v4lookup, addresses are in 4 byte format. + */ Route* v4lookup(Fs *f, uchar *a, uchar *s, Routehint *rh) { - uchar local[IPaddrlen], gate[IPaddrlen]; ulong la, ls; Route *p, *q; Ipifc *ifc; @@ -577,23 +631,9 @@ v4lookup(Fs *f, uchar *a, uchar *s, Routehint *rh) p = p->mid; } - if(q == nil || q->ref == 0) + if(q == nil || q->ref == 0 || routefindipifc(q, f) == nil) return nil; - if(q->ifc == nil || q->ifcid != q->ifc->ifcid){ - if(q->type & Rifc) { - hnputl(gate+IPv4off, q->v4.address); - memmove(gate, v4prefix, IPv4off); - } else - v4tov6(gate, q->v4.gate); - v4tov6(local, s); - ifc = findipifc(f, local, gate, q->type); - if(ifc == nil) - return nil; - q->ifc = ifc; - q->ifcid = ifc->ifcid; - } - if(rh != nil){ rh->r = q; rh->rgen = v4routegeneration; @@ -605,7 +645,6 @@ v4lookup(Fs *f, uchar *a, uchar *s, Routehint *rh) Route* v6lookup(Fs *f, uchar *a, uchar *s, Routehint *rh) { - uchar gate[IPaddrlen]; ulong la[IPllen], ls[IPllen]; ulong x, y; Route *p, *q; @@ -684,22 +723,9 @@ v6lookup(Fs *f, uchar *a, uchar *s, Routehint *rh) next: ; } - if(q == nil || q->ref == 0) + if(q == nil || q->ref == 0 || routefindipifc(q, f) == nil) return nil; - if(q->ifc == nil || q->ifcid != q->ifc->ifcid){ - if(q->type & Rifc) { - for(h = 0; h < IPllen; h++) - hnputl(gate+4*h, q->v6.address[h]); - ifc = findipifc(f, s, gate, q->type); - } else - ifc = findipifc(f, s, q->v6.gate, q->type); - if(ifc == nil) - return nil; - q->ifc = ifc; - q->ifcid = ifc->ifcid; - } - if(rh != nil){ rh->r = q; rh->rgen = v6routegeneration; @@ -708,6 +734,119 @@ next: ; return q; } +/* + * v4source, v6source: + * lookup a route to destination address a and also find + * a suitable source address s on the outgoing interface. + * return the route on success or nil when no route + * was found. + * + * for v4source, addresses are in 4 byte format. + */ +Route* +v4source(Fs *f, uchar *a, uchar *s) +{ + uchar src[IPv4addrlen]; + int splen; + ulong x, la; + Route *p, *q; + Ipifc *ifc; + + q = nil; + la = nhgetl(a); + rlock(&routelock); + for(p = f->v4root[V4H(la)]; p != nil;){ + if(la < p->v4.address){ + p = p->left; + continue; + } + if(la > p->v4.endaddress){ + p = p->right; + continue; + } + splen = 0; + if(p->type & Rsrc){ + /* calculate local prefix length for source specific routes */ + for(x = ~(p->v4.endsource ^ p->v4.source); x & 0x80000000UL; x <<= 1) + splen++; + hnputl(src, p->v4.source); + } + if((ifc = routefindipifc(p, f)) == nil + || !ipv4local(ifc, src, splen, (p->type & (Rifc|Rbcast|Rmulti|Rv4))==Rv4? p->v4.gate: a)){ + p = p->mid; + continue; + } + memmove(s, src, IPv4addrlen); + q = p; + p = p->mid; + } + runlock(&routelock); + return q; +} + +Route* +v6source(Fs *f, uchar *a, uchar *s) +{ + uchar src[IPaddrlen]; + int splen, h; + ulong x, y, la[IPllen]; + Route *p, *q; + Ipifc *ifc; + + q = nil; + for(h = 0; h < IPllen; h++) + la[h] = nhgetl(a+4*h); + rlock(&routelock); + for(p = f->v6root[V6H(la)]; p != nil;){ + for(h = 0; h < IPllen; h++){ + x = la[h]; + y = p->v6.address[h]; + if(x == y) + continue; + if(x < y){ + p = p->left; + goto next; + } + break; + } + for(h = 0; h < IPllen; h++){ + x = la[h]; + y = p->v6.endaddress[h]; + if(x == y) + continue; + if(x > y){ + p = p->right; + goto next; + } + break; + } + splen = 0; + if(p->type & Rsrc){ + /* calculate local prefix length for source specific routes */ + for(h = 0; h < IPllen; h++){ + hnputl(src+4*h, p->v6.source[h]); + if((x = ~(p->v6.endsource[h] ^ p->v6.source[h])) != ~0UL){ + for(; x & 0x80000000UL; x <<= 1) + splen++; + break; + } + splen += 32; + } + } + if((ifc = routefindipifc(p, f)) == nil + || !ipv6local(ifc, src, splen, a)){ + p = p->mid; + continue; + } + ipmove(s, src); + q = p; + p = p->mid; +next: ; + } + runlock(&routelock); + return q; +} + static int parseroutetype(char *p) { diff --git a/sys/src/9/ip/rudp.c b/sys/src/9/ip/rudp.c index 3631b57d5..9b6c96438 100644 --- a/sys/src/9/ip/rudp.c +++ b/sys/src/9/ip/rudp.c @@ -559,15 +559,12 @@ rudpiput(Proto *rudp, Ipifc *ifc, Block *bp) default: /* connection oriented rudp */ if(ipcmp(c->raddr, IPnoaddr) == 0){ - /* save the src address in the conversation */ - ipmove(c->raddr, raddr); - c->rport = rport; - /* reply with the same ip address (if not broadcast) */ if(ipforme(f, laddr) != Runi) - ipv6local(ifc, c->laddr, c->raddr); - else - ipmove(c->laddr, laddr); + ipv6local(ifc, laddr, 0, raddr); + ipmove(c->laddr, laddr); + ipmove(c->raddr, raddr); + c->rport = rport; } break; } diff --git a/sys/src/9/ip/udp.c b/sys/src/9/ip/udp.c index 6a2e03dcd..86205caec 100644 --- a/sys/src/9/ip/udp.c +++ b/sys/src/9/ip/udp.c @@ -423,7 +423,7 @@ udpiput(Proto *udp, Ipifc *ifc, Block *bp) if(ucb->headers == 0){ /* create a new conversation */ if(ipforme(f, laddr) != Runi) - ipv6local(ifc, laddr, raddr); + ipv6local(ifc, laddr, 0, raddr); c = Fsnewcall(c, raddr, rport, laddr, lport, version); if(c == nil){ qunlock(udp);