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.
This commit is contained in:
cinap_lenrek 2019-11-10 19:50:46 +01:00
parent d72a404399
commit b638c7753d
9 changed files with 221 additions and 103 deletions

View file

@ -401,7 +401,7 @@ arpwrite(Fs *fs, char *s, int len)
if((ifc = findipifc(fs, ia, ia, Runi)) == nil) if((ifc = findipifc(fs, ia, ia, Runi)) == nil)
error("no interface"); error("no interface");
rlock(ifc); 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); runlock(ifc);
error("destination unreachable"); error("destination unreachable");
} }
@ -450,7 +450,7 @@ arpread(Arp *arp, char *s, ulong offset, int len)
qlock(arp); qlock(arp);
state = arpstate[a->state]; state = arpstate[a->state];
ipmove(ip, a->ip); 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); qunlock(arp);
runlock(ifc); runlock(ifc);
continue; continue;
@ -507,7 +507,7 @@ ndpsendsol(Fs *f, Ipifc *ifc, Arpent *a)
} else { } else {
arprelease(f->arp, a); arprelease(f->arp, a);
} }
if(!ipv6local(ifc, src, targ)) if(!ipv6local(ifc, src, 0, targ))
return; return;
send: send:
if(!waserror()){ if(!waserror()){

View file

@ -462,7 +462,7 @@ sendarp(Ipifc *ifc, Arpent *a)
memmove(targ, a->ip+IPv4off, IPv4addrlen); memmove(targ, a->ip+IPv4off, IPv4addrlen);
arprelease(er->f->arp, a); arprelease(er->f->arp, a);
if(!ipv4local(ifc, src, targ)) if(!ipv4local(ifc, src, 0, targ))
return; return;
n = sizeof(Etherarp); n = sizeof(Etherarp);

View file

@ -218,7 +218,7 @@ icmpttlexceeded(Fs *f, Ipifc *ifc, Block *bp)
uchar ia[IPv4addrlen]; uchar ia[IPv4addrlen];
p = (Icmp *)bp->rp; 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; return;
netlog(f, Logicmp, "sending icmpttlexceeded %V -> src %V dst %V\n", netlog(f, Logicmp, "sending icmpttlexceeded %V -> src %V dst %V\n",

View file

@ -332,7 +332,7 @@ mkechoreply6(Block *bp, Ipifc *ifc)
ipmove(addr, p->src); ipmove(addr, p->src);
if(!isv6mcast(p->dst)) if(!isv6mcast(p->dst))
ipmove(p->src, p->dst); ipmove(p->src, p->dst);
else if (!ipv6local(ifc, p->src, addr)) else if (!ipv6local(ifc, p->src, 0, addr))
return nil; return nil;
ipmove(p->dst, addr); ipmove(p->dst, addr);
p->type = EchoReplyV6; p->type = EchoReplyV6;
@ -434,7 +434,7 @@ icmphostunr6(Fs *f, Ipifc *ifc, Block *bp, int code, int tome)
uchar ia[IPaddrlen]; uchar ia[IPaddrlen];
p = (Ip6hdr *)bp->rp; 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; return;
netlog(f, Logicmp, "send icmphostunr %I -> src %I dst %I\n", 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]; uchar ia[IPaddrlen];
p = (Ip6hdr *)bp->rp; 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; return;
netlog(f, Logicmp, "send icmpttlexceeded6 %I -> src %I dst %I\n", 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]; uchar ia[IPaddrlen];
p = (Ip6hdr *)bp->rp; 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; return;
netlog(f, Logicmp, "send icmppkttoobig6 %I -> src %I dst %I\n", netlog(f, Logicmp, "send icmppkttoobig6 %I -> src %I dst %I\n",
@ -769,7 +769,7 @@ icmpiput6(Proto *icmp, Ipifc *ifc, Block *bp)
/* fall through */ /* fall through */
case Tuniproxy: 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) if(arpenter(icmp->f, V6, np->src, np->lnaddr, 8*np->olen-2, ia, ifc, 0) < 0)
break; break;
pktflags |= Sflag; pktflags |= Sflag;
@ -801,7 +801,7 @@ icmpiput6(Proto *icmp, Ipifc *ifc, Block *bp)
lifc = iplocalonifc(ifc, np->target); lifc = iplocalonifc(ifc, np->target);
if(lifc != nil && lifc->tentative) if(lifc != nil && lifc->tentative)
arpenter(icmp->f, V6, np->target, np->lnaddr, 8*np->olen-2, np->target, ifc, 0); 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); arpenter(icmp->f, V6, np->target, np->lnaddr, 8*np->olen-2, ia, ifc, 1);
freeblist(bp); freeblist(bp);
break; break;

View file

@ -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 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* v4lookup(Fs *f, uchar *a, uchar *s, Routehint *h);
extern Route* v6lookup(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 routeread(Fs *f, char*, ulong, int);
extern long routewrite(Fs *f, Chan*, char*, int); extern long routewrite(Fs *f, Chan*, char*, int);
extern void routetype(int type, char p[8]); 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* findipifc(Fs*, uchar *local, uchar *remote, int type);
extern Ipifc* findipifcstr(Fs *f, char *s); extern Ipifc* findipifcstr(Fs *f, char *s);
extern void findlocalip(Fs*, uchar *local, uchar *remote); extern void findlocalip(Fs*, uchar *local, uchar *remote);
extern int ipv4local(Ipifc *ifc, uchar *local, uchar *remote); extern int ipv4local(Ipifc *ifc, uchar *local, int prefixlen, uchar *remote);
extern int ipv6local(Ipifc *ifc, uchar *local, uchar *remote); extern int ipv6local(Ipifc *ifc, uchar *local, int prefixlen, uchar *remote);
extern Iplifc* iplocalonifc(Ipifc *ifc, uchar *ip); extern Iplifc* iplocalonifc(Ipifc *ifc, uchar *ip);
extern Iplifc* ipremoteonifc(Ipifc *ifc, uchar *ip); extern Iplifc* ipremoteonifc(Ipifc *ifc, uchar *ip);
extern int ipproxyifc(Fs *f, Ipifc *ifc, uchar *ip); extern int ipproxyifc(Fs *f, Ipifc *ifc, uchar *ip);

View file

@ -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 int
ipv4local(Ipifc *ifc, uchar *local, uchar *remote) ipv4local(Ipifc *ifc, uchar *local, int prefixlen, uchar *remote)
{ {
Iplifc *lifc; Iplifc *lifc;
int a, b; int a, b;
@ -1275,6 +1282,10 @@ ipv4local(Ipifc *ifc, uchar *local, uchar *remote)
for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){ for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
if((lifc->type & Rv4) == 0 || ipcmp(lifc->local, IPnoaddr) == 0) if((lifc->type & Rv4) == 0 || ipcmp(lifc->local, IPnoaddr) == 0)
continue; continue;
if(prefixlen && comprefixlen(lifc->local+IPv4off, local, IPv4addrlen) < prefixlen)
continue;
a = comprefixlen(lifc->local+IPv4off, remote, IPv4addrlen); a = comprefixlen(lifc->local+IPv4off, remote, IPv4addrlen);
if(a > b){ if(a > b){
b = a; b = a;
@ -1284,11 +1295,8 @@ ipv4local(Ipifc *ifc, uchar *local, uchar *remote)
return b >= 0; return b >= 0;
} }
/*
* return v6 address associated with an interface close to remote
*/
int int
ipv6local(Ipifc *ifc, uchar *local, uchar *remote) ipv6local(Ipifc *ifc, uchar *local, int prefixlen, uchar *remote)
{ {
struct { struct {
int atype; int atype;
@ -1300,12 +1308,13 @@ ipv6local(Ipifc *ifc, uchar *local, uchar *remote)
Iplifc *lifc; Iplifc *lifc;
if(isv4(remote)){ if(isv4(remote)){
ipmove(local, v4prefix); memmove(local, v4prefix, IPv4off);
return ipv4local(ifc, local+IPv4off, remote+IPv4off); if((prefixlen -= IPv4off*8) < 0)
prefixlen = 0;
return ipv4local(ifc, local+IPv4off, prefixlen, remote+IPv4off);
} }
atype = v6addrtype(remote); atype = v6addrtype(remote);
ipmove(local, v6Unspecified);
b.atype = unknownv6; b.atype = unknownv6;
b.deprecated = 1; b.deprecated = 1;
b.comprefixlen = 0; b.comprefixlen = 0;
@ -1315,6 +1324,9 @@ ipv6local(Ipifc *ifc, uchar *local, uchar *remote)
if(lifc->tentative) if(lifc->tentative)
continue; continue;
if(prefixlen && comprefixlen(lifc->local, local, IPaddrlen) < prefixlen)
continue;
a.atype = v6addrtype(lifc->local); a.atype = v6addrtype(lifc->local);
a.deprecated = lifc->preflt != ~0UL && lifc->preflt < now-lifc->origint; a.deprecated = lifc->preflt != ~0UL && lifc->preflt < now-lifc->origint;
a.comprefixlen = comprefixlen(lifc->local, remote, IPaddrlen); a.comprefixlen = comprefixlen(lifc->local, remote, IPaddrlen);
@ -1347,54 +1359,22 @@ ipv6local(Ipifc *ifc, uchar *local, uchar *remote)
return b.atype >= atype; return b.atype >= atype;
} }
/*
* find the local address for a remote destination
*/
void void
findlocalip(Fs *f, uchar *local, uchar *remote) findlocalip(Fs *f, uchar *local, uchar *remote)
{ {
Route *r; if(isv4(remote)) {
Iplifc *lifc; memmove(local, v4prefix, IPv4off);
Ipifc *ifc, *nifc; if(v4source(f, remote+IPv4off, local+IPv4off) == nil)
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))
findprimaryipv4(f, local); findprimaryipv4(f, local);
else } else {
if(v6source(f, remote, local) == nil)
findprimaryipv6(f, local); findprimaryipv6(f, local);
}
} }
/* /*
* see if this address is bound to the interface * see if this address is bound to the interface
*/ */

View file

@ -535,10 +535,64 @@ remroute(Fs *f, uchar *a, uchar *mask, uchar *s, uchar *smask, uchar *gate, int
wunlock(&routelock); 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* Route*
v4lookup(Fs *f, uchar *a, uchar *s, Routehint *rh) v4lookup(Fs *f, uchar *a, uchar *s, Routehint *rh)
{ {
uchar local[IPaddrlen], gate[IPaddrlen];
ulong la, ls; ulong la, ls;
Route *p, *q; Route *p, *q;
Ipifc *ifc; Ipifc *ifc;
@ -577,23 +631,9 @@ v4lookup(Fs *f, uchar *a, uchar *s, Routehint *rh)
p = p->mid; p = p->mid;
} }
if(q == nil || q->ref == 0) if(q == nil || q->ref == 0 || routefindipifc(q, f) == nil)
return 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){ if(rh != nil){
rh->r = q; rh->r = q;
rh->rgen = v4routegeneration; rh->rgen = v4routegeneration;
@ -605,7 +645,6 @@ v4lookup(Fs *f, uchar *a, uchar *s, Routehint *rh)
Route* Route*
v6lookup(Fs *f, uchar *a, uchar *s, Routehint *rh) v6lookup(Fs *f, uchar *a, uchar *s, Routehint *rh)
{ {
uchar gate[IPaddrlen];
ulong la[IPllen], ls[IPllen]; ulong la[IPllen], ls[IPllen];
ulong x, y; ulong x, y;
Route *p, *q; Route *p, *q;
@ -684,22 +723,9 @@ v6lookup(Fs *f, uchar *a, uchar *s, Routehint *rh)
next: ; next: ;
} }
if(q == nil || q->ref == 0) if(q == nil || q->ref == 0 || routefindipifc(q, f) == nil)
return 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){ if(rh != nil){
rh->r = q; rh->r = q;
rh->rgen = v6routegeneration; rh->rgen = v6routegeneration;
@ -708,6 +734,119 @@ next: ;
return q; 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 static int
parseroutetype(char *p) parseroutetype(char *p)
{ {

View file

@ -559,15 +559,12 @@ rudpiput(Proto *rudp, Ipifc *ifc, Block *bp)
default: default:
/* connection oriented rudp */ /* connection oriented rudp */
if(ipcmp(c->raddr, IPnoaddr) == 0){ 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) */ /* reply with the same ip address (if not broadcast) */
if(ipforme(f, laddr) != Runi) if(ipforme(f, laddr) != Runi)
ipv6local(ifc, c->laddr, c->raddr); ipv6local(ifc, laddr, 0, raddr);
else
ipmove(c->laddr, laddr); ipmove(c->laddr, laddr);
ipmove(c->raddr, raddr);
c->rport = rport;
} }
break; break;
} }

View file

@ -423,7 +423,7 @@ udpiput(Proto *udp, Ipifc *ifc, Block *bp)
if(ucb->headers == 0){ if(ucb->headers == 0){
/* create a new conversation */ /* create a new conversation */
if(ipforme(f, laddr) != Runi) if(ipforme(f, laddr) != Runi)
ipv6local(ifc, laddr, raddr); ipv6local(ifc, laddr, 0, raddr);
c = Fsnewcall(c, raddr, rport, laddr, lport, version); c = Fsnewcall(c, raddr, rport, laddr, lport, version);
if(c == nil){ if(c == nil){
qunlock(udp); qunlock(udp);