git: 9front

Download patch

ref: 34ac1139df2f671bd299e0ca8f9cf6d93a5b7c46
parent: 2b543568946f65d4e06a8826a04ad8f14473cab2
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sat Dec 13 11:06:38 EST 2025

devip: Add read/writable per-protocol trans file

Expose the per-protocol network translation tables
in a fixed-size record file in the protocol directory.

Everyone can read the currently active translation
entries.

The hostowner can clear and append translation entries.

This can be used to preserve translations entries
across reboots.

--- a/sys/man/3/ip
+++ b/sys/man/3/ip
@@ -33,6 +33,7 @@
 .sp 0.3v
 .B /net/tcp/clone
 .B /net/tcp/stats
+.B /net/tcp/trans
 .BI /net/tcp/ n
 .BI /net/tcp/ n /data
 .BI /net/tcp/ n /ctl
@@ -767,6 +768,21 @@
 See
 .IR dial (2).
 .
+.SS "Network address translation"
+Some protocols such as
+.B tcp
+expose their network address translation tables in a
+.B trans
+file, which is formatted as 6 space-separated fields:
+destination-ip, destination-port, source-ip, source-port,
+local-ip and local-port.
+Reading the
+.B trans
+file returns as many entries as can fit in the buffer.
+The translation tables are mutable: A single entry
+is appended per write.
+Truncating the file or doing a zero-length write
+at offset zero clears the translation table.
 .SS TCP
 TCP connections are reliable point-to-point byte streams; there are no
 message delimiters.
--- a/sys/src/9/ip/devip.c
+++ b/sys/src/9/ip/devip.c
@@ -21,6 +21,7 @@
 	Qprotobase,
 	Qclone=		Qprotobase,
 	Qstats,
+	Qtrans,
 
 	Qconvdir,			/* directory for a conversation */
 	Qconvbase,
@@ -125,6 +126,13 @@
 		mkqid(&q, QID(PROTO(c->qid), 0, Qstats), 0, QTFILE);
 		devdir(c, q, "stats", 0, network, 0444, dp);
 		return 1;
+	case Qtrans:
+		i = transfsize(ipfs[c->dev]->p[PROTO(c->qid)]);
+		if(i < 0)
+			return -1;
+		mkqid(&q, QID(PROTO(c->qid), 0, Qtrans), 0, QTFILE);
+		devdir(c, q, "trans", i, network, 0664, dp);
+		return 1;
 	}
 	return -1;
 }
@@ -234,6 +242,7 @@
 		return ip2gen(c, s+Qprotobase, dp);
 	case Qclone:
 	case Qstats:
+	case Qtrans:
 		return ip2gen(c, TYPE(c->qid), dp);
 	case Qconvdir:
 		if(s == DEVDOTDOT){
@@ -394,6 +403,12 @@
 		if(omode != OREAD && !iseve())
 			error(Eperm);
 		break;
+	case Qtrans:
+		if(omode != OREAD && !iseve())
+			error(Eperm);
+		if(omode & OTRUNC)
+			transwrite(f->p[PROTO(c->qid)], nil, 0, 0);
+		break;
 	case Qtopdir:
 	case Qprotodir:
 	case Qconvdir:
@@ -751,6 +766,8 @@
 		}
 		(*x->stats)(x, buf, Maxstats);
 		goto Readstr;
+	case Qtrans:
+		return transread(f->p[PROTO(ch->qid)], a, offset, n);
 	}
 }
 
@@ -826,7 +843,7 @@
 /*
  * is lport in use by anyone?
  */
-static int
+int
 lportinuse(Proto *p, ushort lport)
 {
 	Translation *q;
@@ -1188,6 +1205,8 @@
 		return arpwrite(f, a, n);
 	case Qiproute:
 		return routewrite(f, ch, a, n);
+	case Qtrans:
+		return transwrite(f->p[PROTO(ch->qid)], a, offset, n);
 	case Qlog:
 		netlogctl(f, a, n);
 		return n;
--- a/sys/src/9/ip/icmp.c
+++ b/sys/src/9/ip/icmp.c
@@ -309,7 +309,7 @@
 	recid = nhgets(p->icmpid);
 
 	qlock(icmp);
-	iph = iphtlook(&((Icmppriv*)icmp->priv)->ht, src, recid, dst, recid);
+	iph = iphtlook(icmp->ht, src, recid, dst, recid);
 	if(iph != nil){
 		Routehint *rh;
 		Translation *q;
@@ -526,7 +526,7 @@
 	recid = nhgets(p->icmpid);
 
 	qlock(icmp);
-	iph = iphtlook(&((Icmppriv*)icmp->priv)->ht, dst, recid, src, recid);
+	iph = iphtlook(icmp->ht, dst, recid, src, recid);
 	if(iph != nil){
 		Translation *q;
 
@@ -583,7 +583,7 @@
 	id = nhgets(p->icmpid);
 
 	qlock(icmp);
-	q = transforward(icmp, &((Icmppriv*)icmp->priv)->ht, sa, id, da, id, r);
+	q = transforward(icmp, sa, id, da, id, r);
 	if(q == nil){
 		qunlock(icmp);
 		freeblist(bp);
@@ -624,6 +624,7 @@
 
 	icmp = smalloc(sizeof(Proto));
 	icmp->priv = smalloc(sizeof(Icmppriv));
+	icmp->ht = &((Icmppriv*)icmp->priv)->ht;
 	icmp->name = "icmp";
 	icmp->connect = icmpconnect;
 	icmp->announce = icmpannounce;
--- a/sys/src/9/ip/ip.h
+++ b/sys/src/9/ip/ip.h
@@ -242,9 +242,13 @@
 	Routehint;
 };
 
-Translation *transforward(Proto *p, Ipht *ht, uchar *sa, int sp, uchar *da, int dp, Route *r);
+Translation *transforward(Proto *p, uchar *sa, int sp, uchar *da, int dp, Route *r);
 Translation *transbackward(Proto *p, Iphash *iph);
 
+extern long transread(Proto*, char *, ulong, int);
+extern long transwrite(Proto*, char *, ulong, int);
+extern long transfsize(Proto*);
+
 /*
  *  one per conversation directory
  */
@@ -453,12 +457,15 @@
 
 	/* network address translation */
 	Translation*	translations;
+	Translation*	freetranslations;
 	Block*		(*forward)(Proto*, Block*, Route*);
+	Ipht		*ht;
 
 	void		*priv;
 };
 
-int unusedlport(Proto *p);
+extern int lportinuse(Proto *p, ushort lport);
+extern int unusedlport(Proto *p);
 
 struct Ndb
 {
--- a/sys/src/9/ip/ipaux.c
+++ b/sys/src/9/ip/ipaux.c
@@ -381,6 +381,49 @@
 	return nil;
 }
 
+static Translation*
+deltrans(Proto *p, Translation *q)
+{
+	netlog(p->f, Logtrans, "trans: removing %s!%I!%d -> %I!%d -> %I!%d\n",
+		p->name,
+		q->forward.raddr, q->forward.rport,
+		q->backward.laddr, q->backward.lport,
+		q->forward.laddr, q->forward.lport);
+
+	iphtrem(p->ht, &q->forward);
+	iphtrem(p->ht, &q->backward);
+
+	return q;
+}
+
+static Translation*
+newtrans(Proto *p)
+{
+	Translation *q;
+	ulong now;
+	int num;
+
+	/* Use freelist */
+	if((q = p->freetranslations) != nil)
+		return q;
+
+	/* Reuse expired entries */
+	num = 0;
+	now = NOW;
+	for(q = p->translations; q != nil; q = q->next) {
+		if(++num >= 1000 || (now - q->time) >= 5*60*1000)
+			return deltrans(p, q);
+	}
+
+	/* Allocate, never freed */
+	q = malloc(sizeof(*q));
+	if(q == nil)
+		return nil;
+	q->next = nil;
+	q->link = nil;
+	return q;
+}
+
 /*
  * Move entry to front of Proto.translations
  * and update the timestamp.
@@ -392,11 +435,15 @@
 {
 	q->time = NOW;
 
-	/* unlink */
+	/* already head of translations list? */
+	if(q->link == &p->translations)
+		return q;
+
+	/* unlink (from translations or freelist) */
 	if(q->link != nil && (*q->link = q->next) != nil)
 		q->next->link = q->link;
 
-	/* link to front */
+	/* link to front of translations list */
 	if((q->next = p->translations) != nil)
 		q->next->link = &q->next;
 	p->translations = q;
@@ -405,6 +452,48 @@
 	return q;
 }
 
+static Translation*
+addtrans(Proto *p, uchar *da, int dp, uchar *sa, int sp, uchar *la, int lp, Routehint rh)
+{
+	Translation *q;
+
+	if((q = newtrans(p)) == nil)
+		return nil;
+
+	/* Match what needs to be forwarded */
+	q->forward.trans = 1;
+	q->forward.lport = dp;
+	q->forward.rport = sp;
+	ipmove(q->forward.laddr, da);
+	ipmove(q->forward.raddr, sa);
+
+	/* Match what comes back to us */
+	q->backward.trans = 2;
+	q->backward.lport = lp;
+	ipmove(q->backward.laddr, la);
+	if(p->ipproto == 1 || p->ipproto == 17){
+		/* ICMP and UDP allow reply from anyone (for hole punching) */
+		q->backward.rport = 0;
+		ipmove(q->backward.raddr, IPnoaddr);
+	} else {
+		q->backward.rport = dp;
+		ipmove(q->backward.raddr, da);
+	}
+
+	netlog(p->f, Logtrans, "trans: adding %s!%I!%d -> %I!%d -> %I!%d\n",
+		p->name,
+		q->forward.raddr, q->forward.rport,
+		q->backward.laddr, q->backward.lport,
+		q->forward.laddr, q->forward.lport);
+
+	memmove(&q->Routehint, &rh, sizeof(rh));
+
+	iphtadd(p->ht, &q->forward);
+	iphtadd(p->ht, &q->backward);
+
+	return transupdate(p, q);
+}
+
 /*
  * Called with the 4-tuple (sa,sp,da,dp)
  * that should be source translated,
@@ -413,19 +502,16 @@
  * Proto is locked.
  */
 Translation*
-transforward(Proto *p, Ipht *ht, uchar *sa, int sp, uchar *da, int dp, Route *r)
+transforward(Proto *p, uchar *sa, int sp, uchar *da, int dp, Route *r)
 {
-	uchar ia[IPaddrlen];
+	uchar la[IPaddrlen];
 	Routehint rh;
-	Translation *q;
 	Iphash *iph;
 	Ipifc *ifc;
-	int lport;
-	ulong now;
-	int num;
+	int lp;
 
 	/* Translation already exists? */
-	iph = iphtlook(ht, sa, sp, da, dp);
+	iph = iphtlook(p->ht, sa, sp, da, dp);
 	if(iph != nil){
 		if(iph->trans == 1)
 			return transupdate(p, iphforward(iph));
@@ -456,8 +542,8 @@
 
 	/* Find a source address on the destination interface */
 	rlock(ifc);
-	memmove(ia, v4prefix, IPv4off);
-	if(!ipv4local(ifc, ia+IPv4off, 0, (r->type & (Rifc|Runi|Rbcast|Rmulti))? da+IPv4off: r->v4.gate)){
+	memmove(la, v4prefix, IPv4off);
+	if(!ipv4local(ifc, la+IPv4off, 0, (r->type & (Rifc|Runi|Rbcast|Rmulti))? da+IPv4off: r->v4.gate)){
 		runlock(ifc);
 		netlog(p->f, Logtrans, "trans: no source ip: %s!%I!%d -> %I!%d\n",
 			p->name, sa, sp, da, dp);
@@ -469,77 +555,24 @@
 	rh.a = nil;
 	rh.r = nil;
 	if(ipismulticast(da))
-		r = v4lookup(p->f, sa+IPv4off, ia+IPv4off, nil);
+		r = v4lookup(p->f, sa+IPv4off, la+IPv4off, nil);
 	else
 		r = v4lookup(p->f, sa+IPv4off, da+IPv4off, &rh);
 	if(r == nil || (r->ifc == ifc && !ifc->reflect)){
 		netlog(p->f, Logtrans, "trans: bad backward route: %s!%I!%d <- %I <- %I!%d\n",
-			p->name, sa, sp, ia, da, dp);
+			p->name, sa, sp, la, da, dp);
 		return nil;
 	}
 
 	/* Find local port */
-	lport = unusedlport(p);
-	if(lport <= 0){
+	lp = unusedlport(p);
+	if(lp <= 0){
 		netlog(p->f, Logtrans, "trans: no local port: %s!%I!%d <- %I <- %I!%d\n",
-			p->name, sa, sp, ia, da, dp);
+			p->name, sa, sp, la, da, dp);
 		return nil;
 	}
 
-	/* Reuse expired entries */
-	num = 0;
-	now = NOW;
-	for(q = p->translations; q != nil; q = q->next) {
-		if(++num >= 1000 || (now - q->time) >= 5*60*1000){
-			netlog(p->f, Logtrans, "trans: removing %s!%I!%d -> %I!%d -> %I!%d\n",
-				p->name,
-				q->forward.raddr, q->forward.rport,
-				q->backward.laddr, q->backward.lport,
-				q->forward.laddr, q->forward.lport);
-
-			iphtrem(ht, &q->forward);
-			iphtrem(ht, &q->backward);
-			break;
-		}
-	}
-	if(q == nil){
-		q = malloc(sizeof(*q));
-		if(q == nil)
-			return nil;
-		q->link = nil;
-	}
-
-	/* Match what needs to be forwarded */
-	q->forward.trans = 1;
-	q->forward.lport = dp;
-	q->forward.rport = sp;
-	ipmove(q->forward.laddr, da);
-	ipmove(q->forward.raddr, sa);
-
-	/* Match what comes back to us */
-	q->backward.trans = 2;
-	q->backward.lport = lport;
-	ipmove(q->backward.laddr, ia);
-	if(p->ipproto == 1 || p->ipproto == 17){
-		/* ICMP and UDP allow reply from anyone (for hole punching) */
-		q->backward.rport = 0;
-		ipmove(q->backward.raddr, IPnoaddr);
-	} else {
-		q->backward.rport = dp;
-		ipmove(q->backward.raddr, da);
-	}
-	memmove(&q->Routehint, &rh, sizeof(rh));
-
-	netlog(p->f, Logtrans, "trans: adding %s!%I!%d -> %I!%d -> %I!%d\n",
-		p->name,
-		q->forward.raddr, q->forward.rport,
-		q->backward.laddr, q->backward.lport,
-		q->forward.laddr, q->forward.lport);
-
-	iphtadd(ht, &q->forward);
-	iphtadd(ht, &q->backward);
-
-	return transupdate(p, q);
+	return addtrans(p, da, dp, sa, sp, la, lp, rh);
 }
 
 /*
@@ -553,8 +586,168 @@
 {
 	if(iph == nil || iph->trans != 2)
 		return nil;
-
 	return transupdate(p, iphbackward(iph));
+}
+
+enum {
+	Transfmtsize = 3*16 + 3*6,	/* excluding \0 */
+};
+
+long
+transfsize(Proto *p)
+{
+	Translation *q;
+	int n = 0;
+
+	if(p->ht == nil || p->forward == nil)
+		return -1;
+
+	qlock(p);
+	for(q = p->translations; q != nil; q = q->next)
+		n += Transfmtsize;
+	qunlock(p);
+
+	return n;
+}
+
+long
+transread(Proto *p, char *a, ulong off, int n)
+{
+	Translation *q;
+	char *b, *s;
+	int m;
+
+	if(n > READSTR)
+		n = READSTR;
+
+	if((b = s = malloc(n+1)) == nil)
+		error(Enomem);
+
+	qlock(p);
+	for(q = p->translations; q != nil; q = q->next){
+		if(n < Transfmtsize)
+			break;
+		m = snprint(s, n+1, "%-15I %5d %-15I %5d %-15I %5d\n",
+			q->forward.laddr, q->forward.lport,	/* da, dp */
+			q->forward.raddr, q->forward.rport,	/* sa, sp */
+			q->backward.laddr, q->backward.lport);	/* la, lp */
+		if(off) {
+			if(m < off)
+				off -= m;
+			else
+				off = 0;
+			continue;
+		}
+		s += m;
+		n -= m;
+	}
+	qunlock(p);
+
+	/* copy outside of lock */
+	if(waserror()){
+		free(b);
+		nexterror();
+	}
+	n = s - b;
+	memmove(a, b, n);
+	free(b);
+	poperror();
+
+	return n;
+}
+
+static void
+flushtrans(Proto *p)
+{
+	Translation *q, *l;
+
+	/* Delete all active translations */
+	l = nil;
+	for(q = p->translations; q != nil; q = q->next)
+		l = deltrans(p, q);
+	if(l == nil)
+		return;
+
+	/* Tanslation list becomes freelist */
+	q = p->translations;
+	p->translations = nil;
+	if((l->next = p->freetranslations) != nil)
+		l->next->link = &l->next;
+	q->link = &p->freetranslations;
+	p->freetranslations = q;
+}
+
+long
+transwrite(Proto *p, char *a, ulong off, int n)
+{
+	char buf[Transfmtsize+1], *f[6];
+	uchar da[IPaddrlen], sa[IPaddrlen], la[IPaddrlen];
+	int dp, sp, lp;
+	Routehint rh;
+	Route *r;
+
+	if(p->ht == nil || p->forward == nil)
+		error("protocol does not support translations");
+
+	if(n == 0 && off == 0){
+		qlock(p);
+		flushtrans(p);
+		qunlock(p);
+		return n;
+	}
+	if((uint)n >= sizeof(buf))
+		error(Ebadctl);
+	memmove(buf, a, n);
+	buf[n] = '\0';
+	if(tokenize(buf, f, 6) != 6)
+		error(Ebadctl);
+
+	if(parseip(da, f[0]) == -1 || !isv4(da))
+		error(Ebadctl);
+	if((dp = strtoul(f[1], nil, 0)) & ~0xffff)
+		error(Ebadctl);
+	if(parseip(sa, f[2]) == -1 || !isv4(sa))
+		error(Ebadctl);
+	if((sp = strtoul(f[3], nil, 0)) & ~0xffff)
+		error(Ebadctl);
+	if(parseip(la, f[4]) == -1 || !isv4(la))
+		error(Ebadctl);
+	if((lp = strtoul(f[5], nil, 0)) & ~0xffff)
+		error(Ebadctl);
+
+	if(ipforme(p->f, la) != Runi)
+		error("local ip not found");
+
+	if(ipismulticast(sa) || ipforme(p->f, sa) != 0)
+		error("bad source address");
+
+	/* check forward route */
+	r = v4lookup(p->f, da+IPv4off, sa+IPv4off, nil);
+	if(r == nil || (r->type & Rtrans) == 0)
+		error("no translating route to desination");
+
+	/* check backward route */
+	rh.a = nil;
+	rh.r = nil;
+	if(ipismulticast(da))
+		r = v4lookup(p->f, sa+IPv4off, la+IPv4off, nil);
+	else
+		r = v4lookup(p->f, sa+IPv4off, da+IPv4off, &rh);
+	if(r == nil)
+		error("no route to soruce address");
+
+	qlock(p);
+	if(waserror()){
+		qunlock(p);
+		nexterror();
+	}
+	if(lportinuse(p, lp))
+		error("local port in use");
+	addtrans(p, da, dp, sa, sp, la, lp, rh);
+	qunlock(p);
+	poperror();
+
+	return n;
 }
 
 /*
--- a/sys/src/9/ip/tcp.c
+++ b/sys/src/9/ip/tcp.c
@@ -2923,7 +2923,7 @@
 
 	/* Look for a connection (source/dest reversed; this is the original packet we sent) */
 	qlock(tcp);
-	iph = iphtlook(&((Tcppriv*)tcp->priv)->ht, dest, pdest, source, psource);
+	iph = iphtlook(tcp->ht, dest, pdest, source, psource);
 	if(iph == nil || iph->match != IPmatchexact)
 		goto raise;
 	if(iph->trans){
@@ -2978,7 +2978,7 @@
 		r = nil;
 
 	qlock(tcp);
-	q = transforward(tcp, &((Tcppriv*)tcp->priv)->ht, sa, sp, da, dp, r);
+	q = transforward(tcp, sa, sp, da, dp, r);
 	if(q == nil){
 		qunlock(tcp);
 		freeblist(bp);
@@ -3169,6 +3169,7 @@
 
 	tcp = smalloc(sizeof(Proto));
 	tcp->priv = tpriv = smalloc(sizeof(Tcppriv));
+	tcp->ht = &tpriv->ht;
 	tcp->name = "tcp";
 	tcp->connect = tcpconnect;
 	tcp->announce = tcpannounce;
--- a/sys/src/9/ip/udp.c
+++ b/sys/src/9/ip/udp.c
@@ -406,7 +406,7 @@
 	}
 
 	qlock(udp);
-	iph = iphtlook(&upriv->ht, raddr, rport, laddr, lport);
+	iph = iphtlook(udp->ht, raddr, rport, laddr, lport);
 	if(iph == nil){
 Noconv:
 		/* no conversation found */
@@ -586,7 +586,7 @@
 
 	/* Look for a connection (source/dest reversed; this is the original packet we sent) */
 	qlock(udp);
-	iph = iphtlook(&((Udppriv*)udp->priv)->ht, dest, pdest, source, psource);
+	iph = iphtlook(udp->ht, dest, pdest, source, psource);
 	if(iph == nil || iph->match != IPmatchexact)
 		goto raise;
 	if(iph->trans){
@@ -636,7 +636,7 @@
 	sp = nhgets(uh4->udpsport);
 
 	qlock(udp);
-	q = transforward(udp, &((Udppriv*)udp->priv)->ht, sa, sp, da, dp, r);
+	q = transforward(udp, sa, sp, da, dp, r);
 	if(q == nil){
 		qunlock(udp);
 		freeblist(bp);
@@ -671,6 +671,7 @@
 
 	udp = smalloc(sizeof(Proto));
 	udp->priv = smalloc(sizeof(Udppriv));
+	udp->ht = &((Udppriv*)udp->priv)->ht;
 	udp->name = "udp";
 	udp->connect = udpconnect;
 	udp->announce = udpannounce;
--