Plan 9 from Bell Labs’s /usr/web/sources/contrib/djc/nat/nat.diff

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


--- /n/sources/plan9/sys/src/9/ip/icmp.c	Mon Jan 11 21:25:07 2010
+++ /sys/src/9/ip/icmp.c	Sun Jul 31 00:00:00 2011
@@ -465,6 +465,12 @@
 	}
 	return p - buf;
 }
+
+int
+icmpgc(Proto *icmp)
+{
+	return natgc(icmp->ipproto);
+}
 	
 void
 icmpinit(Fs *fs)
@@ -483,9 +489,9 @@
 	icmp->stats = icmpstats;
 	icmp->ctl = nil;
 	icmp->advise = icmpadvise;
-	icmp->gc = nil;
+	icmp->gc = icmpgc;
 	icmp->ipproto = IP_ICMPPROTO;
-	icmp->nc = 128;
+	icmp->nc = Nchans;
 	icmp->ptclsize = 0;
 
 	Fsproto(fs, icmp);
--- /sys/src/9/ip/il.c	Sun Jul 30 00:00:00 2011
+++ /sys/src/9/ip/il.c	Sun Jul 31 00:00:00 2011
@@ -1380,6 +1380,12 @@
 	}
 }
 
+int
+ilgc(Proto *il)
+{
+	return natgc(il->ipproto);
+}
+
 void
 ilinit(Fs *f)
 {
@@ -1400,7 +1406,7 @@
 	il->advise = iladvise;
 	il->stats = ilxstats;
 	il->inuse = ilinuse;
-	il->gc = nil;
+	il->gc = ilgc;
 	il->ipproto = IP_ILPROTO;
 	il->nc = scalednconv();
 	il->ptclsize = sizeof(Ilcb);
--- /n/sources/plan9/sys/src/9/ip/ip.c	Fri May  6 19:31:29 2011
+++ /sys/src/9/ip/ip.c	Sun Jul 31 00:00:00 2011
@@ -192,6 +192,10 @@
 	if(ifc->m == nil)
 		goto raise;
 
+	/* Ouput NAT */
+	if(nato(bp, ifc, f) != 0)
+		goto raise;
+
 	/* If we dont need to fragment just send it */
 	if(c && c->maxfragsize && c->maxfragsize < ifc->maxtu)
 		medialen = c->maxfragsize - ifc->m->hsize;
@@ -344,6 +348,9 @@
 	}
 
 	h = (Ip4hdr*)(bp->rp);
+
+	/* Input NAT */
+	nati(bp, ifc);
 
 	/* dump anything that whose header doesn't checksum */
 	if((bp->flag & Bipck) == 0 && ipcsum(&h->vihl)) {
--- /n/sources/plan9/sys/src/9/ip/ip.h	Wed Nov 25 04:19:33 2009
+++ /sys/src/9/ip/ip.h	Sun Jul 31 00:00:00 2011
@@ -27,6 +27,8 @@
 typedef struct 	v6router	v6router;
 typedef struct	v6params	v6params;
 
+typedef struct	Nat		Nat;
+
 #pragma incomplete Arp
 #pragma incomplete Ipself
 #pragma incomplete Ipselftab
@@ -39,7 +41,7 @@
 	Maxproto=	20,
 	Nhash=		64,
 	Maxincall=	10,
-	Nchans=		1024,
+	Nchans=		16383,
 	MAClen=		16,		/* longest mac address */
 
 	MAXTTL=		255,
@@ -501,6 +503,7 @@
 	Logrudpmsg=	1<<16,
 	Logesp=		1<<17,
 	Logtcpwin=	1<<18,
+	Lognat=		1<<19,
 };
 
 void	netloginit(Fs*);
@@ -611,6 +614,7 @@
 };
 
 extern IPaux*	newipaux(char*, char*);
+extern char*	setlport(Conv*);
 
 /*
  *  arp.c
@@ -660,6 +664,9 @@
 #define	ipmove(x, y) memmove(x, y, IPaddrlen)
 #define	ipcmp(x, y) ( (x)[IPaddrlen-1] != (y)[IPaddrlen-1] || memcmp(x, y, IPaddrlen) )
 
+#define	ip4move(x, y) memmove(x, y, IPv4addrlen)
+#define	ip4cmp(x, y) ( (x)[IPv4addrlen-1] != (y)[IPv4addrlen-1] || memcmp(x, y, IPv4addrlen) )
+
 extern uchar IPv4bcast[IPaddrlen];
 extern uchar IPv4bcastobs[IPaddrlen];
 extern uchar IPv4allsys[IPaddrlen];
@@ -744,3 +751,15 @@
  *  global to all of the stack
  */
 extern void	(*igmpreportfn)(Ipifc*, uchar*);
+
+/*
+ * nat.c
+ */
+extern int	nato(Block*, Ipifc*, Fs*);
+extern void	nati(Block*, Ipifc*);
+extern int	natgc(uchar);
+
+extern int	addnataddr(uchar*, uchar*, Iplifc*);
+extern int	removenataddr(uchar*, uchar*, Iplifc*);
+extern void	shownataddr(void);
+extern void flushnataddr(void);
--- /n/sources/plan9/sys/src/9/ip/ipifc.c	Wed Aug  4 19:58:07 2010
+++ /sys/src/9/ip/ipifc.c	Sun Jul 31 00:00:00 2011
@@ -747,6 +747,50 @@
 	return nil;
 }
 
+char*
+ipifcnat(Ipifc *ifc, char **argv, int argc)
+{
+	uchar src[IPaddrlen], mask[IPaddrlen], dst[IPaddrlen];
+	Iplifc *lifc;
+
+	if(argc == 2){
+		if((strcmp(argv[1], "show") == 0)){
+			shownataddr();
+			return nil;
+		}else if((strcmp(argv[1], "flush") == 0)){
+			flushnataddr();
+			return nil;
+		}else
+			return Ebadarg;
+	}
+
+	if(argc != 5)
+		return Ebadarg;
+
+	if (parseip(src, argv[2]) == -1)
+		return Ebadip;
+
+	if (parseipmask(mask, argv[3]) == -1)
+		return Ebadip;
+
+	if (parseip(dst, argv[4]) == -1)
+		return Ebadip;
+
+	if((lifc=iplocalonifc(ifc, dst)) == nil)
+		return Ebadip;
+
+	if(strcmp(argv[1], "add") == 0){
+		if(addnataddr(src, mask, lifc) != 0)
+			return Ebadarg;
+	}else if(strcmp(argv[1], "remove") == 0){
+		if(removenataddr(src, mask, lifc) != 0)
+			return Ebadarg;
+	}else
+		return Ebadarg;
+
+	return nil;
+}
+
 /*
  *  non-standard control messages.
  *  called with c->car locked.
@@ -787,6 +831,8 @@
 		return ipifcadd6(ifc, argv, argc);
 	else if(strcmp(argv[0], "ra6") == 0)
 		return ipifcra6(ifc, argv, argc);
+	else if(strcmp(argv[0], "nat") == 0)
+		return ipifcnat(ifc, argv, argc);
 	return "unsupported ctl";
 }
 
--- /sys/src/9/ip/nat.c	Thu Jan  1 00:00:00 1970
+++ /sys/src/9/ip/nat.c	Sun Jul 31 00:00:00 2011
@@ -0,0 +1,550 @@
+#include		"u.h"
+#include		"../port/lib.h"
+#include		"mem.h"
+#include		"dat.h"
+#include		"fns.h"
+#include		"../port/error.h"
+
+#include		"ip.h"
+
+typedef struct NatProto NatProto;
+typedef struct NatAddr NatAddr;
+
+/*
+ * NAT.
+ */
+struct Nat
+{
+	uchar	src[IPv4addrlen];	/* Source address */
+	uchar	sport[2];		/* Source port */
+	uchar	lport[2];		/* Local port */
+	uchar	proto;			/* Protocol */
+	long	time;			/* Time */
+	Conv	*conv;			/* Conversation */
+	Nat	*next;			/* Next node */
+};
+
+/*
+ * Protocol list.
+ */
+struct NatProto
+{
+	uchar	proto;			/* Protocol */
+	int	sport;			/* Source port offset */
+	int	dport;			/* Destination port offset */
+	int	cksum;			/* Checksum offset */
+	int	timeout;		/* Timeout */
+};
+
+/*
+ * Address list.
+ */
+struct NatAddr
+{
+	uchar	src[IPaddrlen];		/* Source address */
+	uchar	mask[IPaddrlen];	/* Source address mask */
+	uchar	net[IPaddrlen];		/* Source network address */
+	Iplifc	*dst;			/* Destination interface */
+	NatAddr	*next;			/* Next node */
+};
+
+static Nat *head = nil;
+static NatAddr *addrhead = nil;
+
+/*
+ * Timeouts for ICMP, TCP and UDP are respectively confirmed
+ * in RFC 5508, RFC 5382 and RFC 4787.
+ */
+static NatProto prototab[] =
+{
+	{ 1, 4, 4, 2, 60*1000 },		/* ICMP */
+	{ 6, 0, 2, 16, (2*60*60+4*60)*1000 },	/* TCP */
+	{ 17, 0, 2, 6, 2*60*1000 },		/* UDP */
+	{ 40, 6, 8, 0, 10*30*1000 },		/* IL */
+	{ 255, 0, 2, 6, 2*60*1000 },		/* RUDP */
+	{ 0 }
+};
+
+NatProto*	parseproto(uchar);
+void		natprepend(Nat*);
+Nat*		natexistout(uchar*, uchar, uchar*);
+Nat*		natexistin(uchar, uchar*);
+int		natdelete(uchar*, uchar, uchar*);
+int		natpurge(uchar);
+Nat*		natlport(Proto*, Ip4hdr*, uchar*);
+int		natgc(uchar);
+void		checksumadjust(uchar*, uchar*, int, uchar*, int);
+Iplifc*		natonifco(Ipifc*, Ip4hdr*);
+Iplifc*		natonifci(Ipifc*);
+void		nataddrprepend(NatAddr*);
+NatAddr*	nataddrexist(uchar*, uchar*, Iplifc*);
+int		addnataddr(uchar*, uchar*, Iplifc*);
+int		removenataddr(uchar*, uchar*, Iplifc*);
+void		shownataddr(void);
+void		flushnataddr(void);
+
+/*
+ * Return protocol attributes if known.
+ */
+NatProto*
+parseproto(uchar proto)
+{
+	NatProto *np;
+
+	for(np = prototab; np->proto; np++)
+		if(proto == np->proto)
+			return np;
+
+	return nil;
+}
+
+/*
+ * Output NAT.
+ * Return -1 if the packet must be NATed but the protocol is unknown.
+ */
+int
+nato(Block *b, Ipifc *ifc, Fs *f)
+{
+	Nat *n;		/* NAT table */
+	NatProto *np;	/* Protocol list */
+	Iplifc *lifc;	/* Logical interface */
+	Ip4hdr *h;	/* Source IPv4 header */
+	Proto *p;	/* New protocol */
+	uchar *laddr;	/* Local address on Iplifc */
+	uchar *sport;	/* Source port */
+	uchar *cksum;	/* Source checksum */
+
+	h = (Ip4hdr*)(b->rp);
+
+	/* Verify on which logical interface NAT is enabled,
+           and if this source address must be translated */
+	if((lifc=natonifco(ifc, h)) == nil)
+		return 0;
+
+	laddr = lifc->local+IPv4off;
+	p = Fsrcvpcolx(f, h->proto);
+
+	if(ip4cmp(h->src, laddr) != 0){
+		if((np=parseproto(h->proto)) != nil){
+			/* Protocol layer */
+			sport = (b->rp)+sizeof(Ip4hdr)+np->sport;
+			cksum = (b->rp)+sizeof(Ip4hdr)+np->cksum;
+			if((n = natlport(p, h, sport)) == nil)
+				return -1;
+			memmove(sport, n->lport, 2);
+			checksumadjust(cksum, n->sport, 2, n->lport, 2);
+			if(np->proto != 1)
+				/* ICMP checksum doesn't include IP header */
+				checksumadjust(cksum, n->src, IPv4addrlen,
+					laddr, IPv4addrlen);
+			/* IP layer */
+			ip4move(h->src, laddr);
+			checksumadjust(h->cksum, n->src, IPv4addrlen,
+				h->src, IPv4addrlen);
+			return 0;
+		}else{
+			netlog(f, Lognat, "nat: unknown protocol %d\n", h->proto);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Input NAT.
+ */
+void
+nati(Block *b, Ipifc *ifc)
+{
+	Nat *n;		/* NAT table */
+	NatProto *np;	/* Protocol list */
+	Ip4hdr *h;	/* Source IPv4 header */
+	uchar *lport;	/* Our local port, and dst port for the packet */
+	uchar *cksum;	/* Source checksum */
+
+	h = (Ip4hdr*)(b->rp);
+
+	/* Verify if NAT is enabled on this interface */
+	if(natonifci(ifc) == nil)
+		return;
+
+	if((np=parseproto(h->proto)) != nil){
+		lport = (b->rp)+sizeof(Ip4hdr)+np->dport;
+		if((n=natexistin(h->proto, lport)) != nil){
+			/* Protocol layer */
+			cksum = (b->rp)+sizeof(Ip4hdr)+np->cksum;
+			checksumadjust(cksum, lport, 2, n->sport, 2);
+			memmove(lport, n->sport, 2);
+			if(np->proto != 1)
+				/* ICMP checksum doesn't include IP header */
+		   		checksumadjust(cksum, h->dst, IPv4addrlen,
+					n->src, IPv4addrlen);
+			/* IP layer */
+			checksumadjust(h->cksum, h->dst, IPv4addrlen,
+				n->src, IPv4addrlen);
+			ip4move(h->dst, n->src);
+		}
+	}
+}
+
+/*
+ * Add Nat to Nat list.
+ */
+void
+natprepend(Nat *n)
+{
+	n->next = head;
+	head = n;
+}
+
+/*
+ * Return Nat if it exists in Nat list.
+ */
+Nat*
+natexistout(uchar *src, uchar proto, uchar *sport)
+{
+	Nat *c;		/* Current node */
+
+	for(c=head; c!=nil; c=c->next)
+		if(ip4cmp(src, c->src) == 0 &&
+			memcmp(sport, c->sport, 2) == 0 &&
+			proto == c->proto){
+			c->time = NOW;
+			return c;
+		}
+
+	return nil;
+}
+
+/*
+ * Return Nat if it exists in Nat list.
+ */
+Nat*
+natexistin(uchar proto, uchar *lport)
+{
+	Nat *c;		/* Current node */
+
+	for(c=head; c!=nil; c=c->next)
+		if(memcmp(lport, c->lport, 2) == 0 &&
+			proto == c->proto){
+			c->time = NOW;
+			return c;
+		}
+
+	return nil;
+}
+
+/*
+ * Delete Nat in Nat list.
+ * Return -1 if it doesn't exist.
+ */
+int
+natdelete(uchar src[IPv4addrlen], uchar proto, uchar sport[2])
+{
+	Nat *p;		/* Precedent node */
+	Nat *c;		/* Current node */
+
+	for(p=nil, c=head; c!=nil; p=c, c=c->next)
+		if(ip4cmp(src, c->src) == 0 &&
+			memcmp(sport, c->sport, 2) == 0 &&
+			proto == c->proto)
+			break;
+
+	if(c == nil)
+		return -1;
+
+	if(p == nil)
+		head = head->next;
+	else
+		p->next = c->next;
+
+	closeconv(c->conv);
+	free(c);
+
+	return 0;
+}
+
+/*
+ * Purge Nat list.
+ */
+int
+natpurge(uchar proto)
+{
+	Nat *c;		/* Current node */
+	int n;		/* Number of purged connections */
+
+	for(n = 0;; n++){
+		do{
+			if((c = head) == nil)
+				return n;
+			head = head->next;
+		}while(c->proto != proto);
+		closeconv(c->conv);
+		free(c);
+	}
+}
+
+/*
+ * Create a new Nat if necessary.
+ */
+Nat*
+natlport(Proto *p, Ip4hdr *h, uchar *sport)
+{
+	Nat *n;		/* New NAT node */
+	Conv *s;	/* New conversation */
+
+	if((n=natexistout(h->src, h->proto, sport)) == nil){
+		qlock(p);
+		s = Fsprotoclone(p, "network");
+		qunlock(p);
+		if(s == nil){
+			error(Enodev);
+			return nil;
+		}
+		if(setlport(s) == nil){
+			n = malloc(sizeof(Nat));
+			ip4move(n->src, h->src);
+			memmove(n->sport, sport, 2);
+			memmove(n->lport, &s->lport, 2);
+			n->proto = h->proto;
+			n->time = NOW;
+			n->conv = s;
+			natprepend(n);
+		}else
+			return nil;
+	}
+
+	return n;
+}
+
+/*
+ * Nat list garbage collector.
+ */
+int
+natgc(uchar proto){
+	Nat *p;		/* Precedent node */
+	Nat *c;		/* Current node */
+	NatProto *np;	/* Protocol list */
+	int n;		/* Number of garbage collected connections */
+
+	n = 0;
+	p = nil;
+	c = head;
+
+	np = parseproto(proto);
+
+	while(c != nil){
+		if(NOW - c->time > np->timeout){
+ 			if(p == nil){
+ 				head = head->next;
+				if(proto == c->proto)
+					n++;
+				closeconv(c->conv);
+				free(c);
+				p = nil;
+				c = head;
+ 			}else{
+ 				p->next = c->next;
+				if(proto == c->proto)
+					n++;
+				closeconv(c->conv);
+				free(c);
+ 				c = p->next;
+ 			}
+		}else{
+			p = c;
+			c = c->next;
+		}
+	}
+
+	if(n == 0)	/* Prevent Conv saturation */
+		n = natpurge(proto);
+
+	return n;
+}
+
+/*
+ * Function checksumadjust from RFC 3022.
+ */
+void
+checksumadjust(uchar *chksum, uchar *optr, int olen, uchar *nptr, int nlen)
+{
+	long x, old, new;
+	x=chksum[0]*256+chksum[1];
+	x=~x & 0xffff;
+	while(olen){
+		old=optr[0]*256+optr[1];
+		optr+=2;
+		x-=old & 0xffff;
+		if(x<=0){
+			x--;
+			x&=0xffff;
+		}
+		olen-=2;
+	}
+	while(nlen){
+		new=nptr[0]*256+nptr[1];
+		nptr+=2;
+		x+=new & 0xffff;
+		if(x & 0x10000){
+			x++;
+			x&=0xffff;
+		}
+		nlen-=2;
+	}
+	x=~x & 0xffff;
+	chksum[0]=x/256;
+	chksum[1]=x & 0xff;
+}
+
+/*
+ * Add NatAddr to NatAddr list.
+ */
+void
+nataddrprepend(NatAddr *na)
+{
+	na->next = addrhead;
+	addrhead = na;
+}
+
+/*
+ * Return NatAddr if it exists in NatAddr list.
+ */
+NatAddr*
+nataddrexist(uchar *src, uchar *mask, Iplifc *dst)
+{
+	NatAddr *c;	/* Current node */
+
+	for(c=addrhead; c!=nil; c=c->next)
+		if(ipcmp(src, c->src) == 0 &&
+			ipcmp(mask, c->mask) == 0 &&
+			dst == c->dst)
+			return c;
+
+	return nil;
+}
+
+/*
+ * Create a new NatAddr.
+ * Return -1 if it already exist.
+ */
+int
+addnataddr(uchar *src, uchar *mask, Iplifc *dst)
+{
+	NatAddr *na;		/* New address node */
+	uchar net[IPaddrlen];	/* Network address */
+
+	maskip(src, mask, net);
+
+	if(nataddrexist(src, mask, dst) != nil)
+		return -1;
+
+	na = malloc(sizeof(NatAddr));
+	ipmove(na->src, src);
+	ipmove(na->mask, mask);
+	ipmove(na->net, net);
+	na->dst = dst;
+
+	nataddrprepend(na);
+
+	return 0;
+}
+
+/*
+ * Remove a NatAddr.
+ * Return -1 if it doesn't exist.
+ */
+int
+removenataddr(uchar *src, uchar *mask, Iplifc *dst)
+{
+	NatAddr *c;	/* Current node */
+	NatAddr *p;	/* Precedent node */
+
+	for(p=nil, c=addrhead; c!=nil; p=c, c=c->next)
+		if(ipcmp(src, c->src) == 0 &&
+			ipcmp(mask, c->mask) == 0 &&
+			dst == c->dst)
+			break;
+
+	if(c == nil)
+		return -1;
+
+	if(p == nil)
+		addrhead = addrhead->next;
+	else
+		p->next = c->next;
+
+	return 0;
+}
+
+/*
+ * Display NatAddr list.
+ */
+void
+shownataddr(void)
+{
+	NatAddr *c;	/* Current node */
+
+	for(c=addrhead; c!=nil; c=c->next)
+		print("%I %V %I\n", c->src, c->mask+IPv4off, c->dst->local);
+}
+
+/*
+ * Flush NatAddr list.
+ */
+void
+flushnataddr(void)
+{
+	NatAddr *c;	/* Current node */
+
+	while((c=addrhead) != nil){
+		addrhead = addrhead->next;
+		free(c);
+	}
+}
+
+/*
+ * Return logical interface if NAT is enabled on this interface,
+ * and the source address must be translated.
+ */
+Iplifc*
+natonifco(Ipifc *ifc, Ip4hdr* h)
+{
+	NatAddr *na;		/* Address list */
+	Iplifc *lifc;		/* Logical interface */
+	uchar src[IPaddrlen];	/* Source address */
+	uchar net[IPaddrlen];	/* Source network address */
+
+	for(lifc=ifc->lifc; lifc!=nil; lifc=lifc->next)
+		for(na=addrhead; na; na=na->next)
+			if(lifc == na->dst){
+				/* NAT enabled on this logical interface */
+				v4tov6(src, h->src);
+				maskip(src, na->mask, net);
+				if(ipcmp(net, na->net) == 0)
+					/* Source address must be translated */
+					return lifc;
+			}
+
+	return nil;
+}
+
+/*
+ * Return logical interface if NAT is enabled on this interface.
+ */
+Iplifc*
+natonifci(Ipifc *ifc)
+{
+	NatAddr *na;		/* Address list */
+	Iplifc *lifc;		/* Logical interface */
+
+	for(lifc=ifc->lifc; lifc!=nil; lifc=lifc->next)
+		for(na=addrhead; na; na=na->next)
+			if(lifc == na->dst){
+				/* NAT enabled on this logical interface */
+				return lifc;
+			}
+
+	return nil;
+}
--- /n/sources/plan9/sys/src/9/ip/rudp.c	Thu Jul  9 03:34:23 2009
+++ /sys/src/9/ip/rudp.c	Sun Jul 31 00:00:00 2011
@@ -671,6 +671,12 @@
 		upriv->orders);
 }
 
+int
+rudpgc(Proto *rudp)
+{
+	return natgc(rudp->ipproto);
+}
+
 void
 rudpinit(Fs *fs)
 {
@@ -689,6 +695,7 @@
 	rudp->rcv = rudpiput;
 	rudp->advise = rudpadvise;
 	rudp->stats = rudpstats;
+	rudp->gc = rudpgc;
 	rudp->ipproto = IP_UDPPROTO;
 	rudp->nc = 32;
 	rudp->ptclsize = sizeof(Rudpcb);
--- /n/sources/plan9/sys/src/9/ip/tcp.c	Fri Mar 18 18:24:20 2011
+++ /sys/src/9/ip/tcp.c	Sun Jul 31 00:00:00 2011
@@ -3146,7 +3146,7 @@
 	Tcpctl *tcb;
 
 
-	n = 0;
+	n = natgc(tcp->ipproto);
 	ep = &tcp->conv[tcp->nc];
 	for(pp = tcp->conv; pp < ep; pp++) {
 		c = *pp;
--- /n/sources/plan9/sys/src/9/ip/udp.c	Wed Nov 25 04:16:56 2009
+++ /sys/src/9/ip/udp.c	Sun Jul 31 00:00:00 2011
@@ -595,6 +595,12 @@
 		upriv->ustats.udpOutDatagrams);
 }
 
+int
+udpgc(Proto *udp)
+{
+	return natgc(udp->ipproto);
+}
+
 void
 udpinit(Fs *fs)
 {
@@ -612,6 +618,7 @@
 	udp->rcv = udpiput;
 	udp->advise = udpadvise;
 	udp->stats = udpstats;
+	udp->gc = udpgc;
 	udp->ipproto = IP_UDPPROTO;
 	udp->nc = Nchans;
 	udp->ptclsize = sizeof(Udpcb);

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.