hlfw.ca

drawcpu

Download patch

ref: e825af37ac4e6fd8fe73d8df6048b7ee304805ea
parent: fe00d007d93413d37e138202ace03eb6a2b59748
author: halfwit <michaelmisch1985@gmail.com>
date: Sat Mar 2 02:39:38 PST 2024

ip gives us access to dial and friends

--- a/Makefile
+++ b/Makefile
@@ -12,6 +12,7 @@
 LIBS1=\
 	kern/libkern.a\
 	rc/librc.a\
+	libip/libip.a\
 	libmemdraw/libmemdraw.a\
 	libmemlayer/libmemlayer.a\
 	libdraw/libdraw.a\
@@ -34,7 +35,9 @@
 kern/libkern.a:
 	(cd kern; $(MAKE))
 
-
+libip/libip.a:
+	(cd libip; $(MAKE))
+	
 libmemdraw/libmemdraw.a:
 	(cd libmemdraw; $(MAKE))
 
--- a/README
+++ b/README
@@ -1,6 +1,6 @@
 DESCRIPTION
 -----------
-This is a fork of Russ Cox's drawterm and plan9front's drawterm that listens for incoming rcpu requests, instead of making one itself
+This is a fork of Russ Cox's drawterm and plan9front's drawterm that listens for incoming rcpu requests, instead of making one itself. It has rc with enough builtins that run inside the kernel for a typical session
 
 
 INSTALLATION
--- a/TODO
+++ b/TODO
@@ -2,15 +2,20 @@
  - [x]' read'
  - [ ] 'write' ?
  - [x] 'ls'
- - [x] 'mount'
+ - [x] 'cat'
+ - [ ] 'srv' ?
+ - [~] 'mount' - DEBUG: spin up an exportfs on 9, dial() in our mount call to it to use as our fd to get output
  - [x] 'bind'
  - [ ] 'mkdir'
- - [ ] 'rm'
+ - [x] 'rm'
  - [ ] 'unmount'
  - [ ] 'ns'
+ - [~] 'test' - only -d and maybe -f for now
+ - [x] 'echo'
+ - [ ] 'cp'
 
 TODO:
- - [ ] Install rcmain properly on system, refer to it as needed
+ - [x] Install rcmain properly on system, refer to it as needed
  - [ ] have it export a var service=unix
  - [ ] Some people probably want aan?
  - [x] Move rc code to use our libc.h, u.h everywhere so our defs of open + such are correct
@@ -37,6 +42,7 @@
  - devcons  - #c: needed, /dev/cons
  - devenv   - #e: needed, /dev/env
  - devfs    - #U: needed, local files
+ - devlfd   - #L: needed, /fd
  - devip    - #I: !needed, networking
  - devlfd   - #L: !needed
  - devmnt   - #M: needed, client shares
@@ -44,4 +50,5 @@
  - devroot  - #/: needed, base of stack
  - devssl   - #D: !needed, ssl
  - devtls   - #a: !needed, tls
+ - devproc  - #p: needed, /dev/proc, overlay any existing 
  - devtab   - meta, needed - add/remove any devices that we add/remove, from here as well
--- /dev/null
+++ b/include/ip.h
@@ -1,0 +1,38 @@
+enum 
+{
+	IPaddrlen=	16,
+	IPv4addrlen=	4,
+	IPv4off=	12,
+};
+
+uchar*	defmask(uchar*);
+void	maskip(uchar*, uchar*, uchar*);
+int	eipfmt(Fmt*);
+int	isv4(uchar*);
+vlong	parseip(uchar*, char*);
+vlong	parseipmask(uchar*, char*, int);
+vlong	parseipandmask(uchar*, uchar*, char*, char*);
+char*	v4parseip(uchar*, char*);
+
+void	hnputv(void*, uvlong);
+void	hnputl(void*, uint);
+void	hnputs(void*, ushort);
+uvlong	nhgetv(void*);
+uint	nhgetl(void*);
+ushort	nhgets(void*);
+
+int	v6tov4(uchar*, uchar*);
+void	v4tov6(uchar*, uchar*);
+
+#define	ipcmp(x, y) memcmp(x, y, IPaddrlen)
+#define	ipmove(x, y) memmove(x, y, IPaddrlen)
+
+extern uchar IPv4bcast[IPaddrlen];
+extern uchar IPv4bcastobs[IPaddrlen];
+extern uchar IPv4allsys[IPaddrlen];
+extern uchar IPv4allrouter[IPaddrlen];
+extern uchar IPnoaddr[IPaddrlen];
+extern uchar v4prefix[IPaddrlen];
+extern uchar IPallbits[IPaddrlen];
+
+#define CLASS(p) ((*(uchar*)(p))>>6)
--- a/kern/Makefile
+++ b/kern/Makefile
@@ -14,6 +14,8 @@
 	devenv.$O\
 	devfs-$(OS).$O\
 	devlfd-$(OS).$O\
+	devip-$(OS).$O\
+	devip.$O\
 	devmnt.$O\
 	devpipe.$O\
 	devroot.$O\
--- /dev/null
+++ b/kern/devip-posix.c
@@ -1,0 +1,259 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include "ip.h"
+
+#include "devip.h"
+
+#undef listen
+#undef accept
+#undef bind
+
+static int
+family(unsigned char *addr)
+{
+	if(isv4(addr))
+		return AF_INET;
+	return AF_INET6;
+}
+
+static int
+addrlen(struct sockaddr_storage *ss)
+{
+	switch(ss->ss_family){
+	case AF_INET:
+		return sizeof(struct sockaddr_in);
+	case AF_INET6:
+		return sizeof(struct sockaddr_in6);
+	}
+	return 0;
+}
+
+void
+osipinit(void)
+{
+	char buf[1024];
+	gethostname(buf, sizeof(buf));
+	kstrdup(&sysname, buf);
+
+}
+
+int
+so_socket(int type, unsigned char *addr)
+{
+	int fd, one;
+
+	switch(type) {
+	default:
+		error("bad protocol type");
+	case S_TCP:
+		type = SOCK_STREAM;
+		break;
+	case S_UDP:
+		type = SOCK_DGRAM;
+		break;
+	}
+
+	fd = socket(family(addr), type, 0);
+	if(fd < 0)
+		oserror();
+
+	one = 1;
+	if(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one)) > 0){
+		oserrstr();
+		print("setsockopt: %r");
+	}
+
+	return fd;
+}
+
+void
+so_connect(int fd, unsigned char *raddr, unsigned short rport)
+{
+	struct sockaddr_storage ss;
+
+	memset(&ss, 0, sizeof(ss));
+
+	ss.ss_family = family(raddr);
+
+	switch(ss.ss_family){
+	case AF_INET:
+		hnputs(&((struct sockaddr_in*)&ss)->sin_port, rport);
+		v6tov4((unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr, raddr);
+		break;
+	case AF_INET6:
+		hnputs(&((struct sockaddr_in6*)&ss)->sin6_port, rport);
+		memcpy(&((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, raddr, sizeof(struct in6_addr));
+		break;
+	}
+
+	if(connect(fd, (struct sockaddr*)&ss, addrlen(&ss)) < 0)
+		oserror();
+}
+
+void
+so_getsockname(int fd, unsigned char *laddr, unsigned short *lport)
+{
+	socklen_t len;
+	struct sockaddr_storage ss;
+
+	len = sizeof(ss);
+	if(getsockname(fd, (struct sockaddr*)&ss, &len) < 0)
+		oserror();
+
+	switch(ss.ss_family){
+	case AF_INET:
+		v4tov6(laddr, (unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr);
+		*lport = nhgets(&((struct sockaddr_in*)&ss)->sin_port);
+		break;
+	case AF_INET6:
+		memcpy(laddr, &((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, sizeof(struct in6_addr));
+		*lport = nhgets(&((struct sockaddr_in6*)&ss)->sin6_port);
+		break;
+	default:
+		error("not AF_INET or AF_INET6");
+	}
+}
+
+void
+so_listen(int fd)
+{
+	if(listen(fd, 5) < 0)
+		oserror();
+}
+
+int
+so_accept(int fd, unsigned char *raddr, unsigned short *rport)
+{
+	int nfd;
+	socklen_t len;
+	struct sockaddr_storage ss;
+
+	len = sizeof(ss);
+	nfd = accept(fd, (struct sockaddr*)&ss, &len);
+	if(nfd < 0)
+		oserror();
+
+	switch(ss.ss_family){
+	case AF_INET:
+		v4tov6(raddr, (unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr);
+		*rport = nhgets(&((struct sockaddr_in*)&ss)->sin_port);
+		break;
+	case AF_INET6:
+		memcpy(raddr, &((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, sizeof(struct in6_addr));
+		*rport = nhgets(&((struct sockaddr_in6*)&ss)->sin6_port);
+		break;
+	default:
+		error("not AF_INET or AF_INET6");
+	}
+	return nfd;
+}
+
+void
+so_bind(int fd, int su, unsigned short port, unsigned char *addr)
+{
+	int i, one;
+	struct sockaddr_storage ss;
+
+	one = 1;
+	if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) < 0){
+		oserrstr();
+		print("setsockopt: %r");
+	}
+
+	if(su) {
+		for(i = 600; i < 1024; i++) {
+			memset(&ss, 0, sizeof(ss));
+			ss.ss_family = family(addr);
+
+			switch(ss.ss_family){
+			case AF_INET:
+				((struct sockaddr_in*)&ss)->sin_port = i;
+				break;
+			case AF_INET6:
+				((struct sockaddr_in6*)&ss)->sin6_port = i;
+				break;
+			}
+
+			if(bind(fd, (struct sockaddr*)&ss, addrlen(&ss)) >= 0)	
+				return;
+		}
+		oserror();
+	}
+
+	memset(&ss, 0, sizeof(ss));
+	ss.ss_family = family(addr);
+
+	switch(ss.ss_family){
+	case AF_INET:
+		hnputs(&((struct sockaddr_in*)&ss)->sin_port, port);
+		break;
+	case AF_INET6:
+		hnputs(&((struct sockaddr_in6*)&ss)->sin6_port, port);
+		break;
+	}
+
+	if(bind(fd, (struct sockaddr*)&ss, addrlen(&ss)) < 0)
+		oserror();
+}
+
+int
+so_gethostbyname(char *host, char **hostv, int n)
+{
+	char buf[INET6_ADDRSTRLEN];
+	struct addrinfo *r, *p;
+	int i;
+
+	r = NULL;
+	if(getaddrinfo(host, NULL, NULL, &r))
+		return 0;
+	for(i = 0, p = r; i < n && p != NULL; p = p->ai_next){
+		switch (p->ai_family) {
+		default:
+			continue;
+		case AF_INET:
+			inet_ntop(AF_INET, &((struct sockaddr_in*)p->ai_addr)->sin_addr, buf, sizeof(buf));
+			break;
+		case AF_INET6:
+			inet_ntop(AF_INET6, &((struct sockaddr_in6*)p->ai_addr)->sin6_addr, buf, sizeof(buf));
+			break;
+		}
+		hostv[i++] = strdup(buf);
+	}
+	freeaddrinfo(r);
+	return i;
+}
+
+int
+so_getservbyname(char *service, char *net, char *port)
+{
+	struct servent *s;
+
+	s = getservbyname(service, net);
+	if(s == 0)
+		return -1;
+
+	sprint(port, "%d", nhgets(&s->s_port));
+	return 0;
+}
+
+int
+so_send(int fd, void *d, int n, int f)
+{
+	return send(fd, d, n, f);
+}
+
+int
+so_recv(int fd, void *d, int n, int f)
+{
+	return recv(fd, d, n, f);
+}
--- /dev/null
+++ b/kern/devip-win32.c
@@ -1,0 +1,265 @@
+#define  _WIN32_WINNT 0x0501
+#include <winsock2.h>
+#include <windows.h>
+#include <ws2tcpip.h>
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include "ip.h"
+
+#include "devip.h"
+
+#ifdef MSVC
+#pragma comment(lib, "wsock32.lib")
+#endif
+
+#undef listen
+#undef accept
+#undef bind
+
+static int
+family(unsigned char *addr)
+{
+	if(isv4(addr))
+		return AF_INET;
+	return AF_INET6;
+}
+
+static int
+addrlen(struct sockaddr_storage *ss)
+{
+	switch(ss->ss_family){
+	case AF_INET:
+		return sizeof(struct sockaddr_in);
+	case AF_INET6:
+		return sizeof(struct sockaddr_in6);
+	}
+	return 0;
+}
+
+void
+osipinit(void)
+{
+	WSADATA wasdat;
+	char buf[1024];
+
+	if(WSAStartup(MAKEWORD(1, 1), &wasdat) != 0)
+		panic("no winsock.dll");
+
+	gethostname(buf, sizeof(buf));
+	kstrdup(&sysname, buf);
+}
+
+int
+so_socket(int type, unsigned char *addr)
+{
+	int fd, one;
+
+	switch(type) {
+	default:
+		error("bad protocol type");
+	case S_TCP:
+		type = SOCK_STREAM;
+		break;
+	case S_UDP:
+		type = SOCK_DGRAM;
+		break;
+	}
+
+	fd = socket(family(addr), type, 0);
+	if(fd < 0)
+		oserror();
+
+	one = 1;
+	if(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one)) > 0){
+		oserrstr();
+		print("setsockopt: %s\n", up->errstr);
+	}
+
+	return fd;
+}
+
+
+void
+so_connect(int fd, unsigned char *raddr, unsigned short rport)
+{
+	struct sockaddr_storage ss;
+
+	memset(&ss, 0, sizeof(ss));
+
+	ss.ss_family = family(raddr);
+
+	switch(ss.ss_family){
+	case AF_INET:
+		hnputs(&((struct sockaddr_in*)&ss)->sin_port, rport);
+		v6tov4((unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr, raddr);
+		break;
+	case AF_INET6:
+		hnputs(&((struct sockaddr_in6*)&ss)->sin6_port, rport);
+		memcpy(&((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, raddr, sizeof(struct in6_addr));
+		break;
+	}
+
+	if(connect(fd, (struct sockaddr*)&ss, addrlen(&ss)) < 0)
+		oserror();
+}
+
+void
+so_getsockname(int fd, unsigned char *laddr, unsigned short *lport)
+{
+	int len;
+	struct sockaddr_storage ss;
+
+	len = sizeof(ss);
+	if(getsockname(fd, (struct sockaddr*)&ss, &len) < 0)
+		oserror();
+
+	switch(ss.ss_family){
+	case AF_INET:
+		v4tov6(laddr, (unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr);
+		*lport = nhgets(&((struct sockaddr_in*)&ss)->sin_port);
+		break;
+	case AF_INET6:
+		memcpy(laddr, &((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, sizeof(struct in6_addr));
+		*lport = nhgets(&((struct sockaddr_in6*)&ss)->sin6_port);
+		break;
+	default:
+		error("not AF_INET or AF_INET6");
+	}
+}
+
+void
+so_listen(int fd)
+{
+	if(listen(fd, 5) < 0)
+		oserror();
+}
+
+int
+so_accept(int fd, unsigned char *raddr, unsigned short *rport)
+{
+	int nfd;
+	int len;
+	struct sockaddr_storage ss;
+
+	len = sizeof(ss);
+	nfd = accept(fd, (struct sockaddr*)&ss, &len);
+	if(nfd < 0)
+		oserror();
+
+	switch(ss.ss_family){
+	case AF_INET:
+		v4tov6(raddr, (unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr);
+		*rport = nhgets(&((struct sockaddr_in*)&ss)->sin_port);
+		break;
+	case AF_INET6:
+		memcpy(raddr, &((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, sizeof(struct in6_addr));
+		*rport = nhgets(&((struct sockaddr_in6*)&ss)->sin6_port);
+		break;
+	default:
+		error("not AF_INET or AF_INET6");
+	}
+	return nfd;
+}
+
+void
+so_bind(int fd, int su, unsigned short port, unsigned char *addr)
+{
+	int i, one;
+	struct sockaddr_storage ss;
+
+	one = 1;
+	if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) < 0){
+		oserrstr();
+		print("setsockopt: %r");
+	}
+
+	if(su) {
+		for(i = 600; i < 1024; i++) {
+			memset(&ss, 0, sizeof(ss));
+			ss.ss_family = family(addr);
+
+			switch(ss.ss_family){
+			case AF_INET:
+				((struct sockaddr_in*)&ss)->sin_port = i;
+				break;
+			case AF_INET6:
+				((struct sockaddr_in6*)&ss)->sin6_port = i;
+				break;
+			}
+
+			if(bind(fd, (struct sockaddr*)&ss, addrlen(&ss)) >= 0)	
+				return;
+		}
+		oserror();
+	}
+
+	memset(&ss, 0, sizeof(ss));
+	ss.ss_family = family(addr);
+
+	switch(ss.ss_family){
+	case AF_INET:
+		hnputs(&((struct sockaddr_in*)&ss)->sin_port, port);
+		break;
+	case AF_INET6:
+		hnputs(&((struct sockaddr_in6*)&ss)->sin6_port, port);
+		break;
+	}
+
+	if(bind(fd, (struct sockaddr*)&ss, addrlen(&ss)) < 0)
+		oserror();
+}
+
+int
+so_gethostbyname(char *host, char **hostv, int n)
+{
+	char buf[INET6_ADDRSTRLEN];
+	struct addrinfo *r, *p;
+	DWORD l;
+	int i;
+
+	r = NULL;
+	if(getaddrinfo(host, NULL, NULL, &r))
+		return 0;
+	for(i = 0, p = r; i < n && p != NULL; p = p->ai_next){
+		switch (p->ai_family) {
+		default:
+			continue;
+		case AF_INET:
+		case AF_INET6:
+			l = sizeof(buf);
+			WSAAddressToStringA(p->ai_addr, p->ai_addrlen, NULL, buf, &l);
+			break;
+		}
+		hostv[i++] = strdup(buf);
+	}
+	freeaddrinfo(r);
+	return i;
+}
+
+int
+so_getservbyname(char *service, char *net, char *port)
+{
+	struct servent *s;
+
+	s = getservbyname(service, net);
+	if(s == 0)
+		return -1;
+
+	sprint(port, "%d", nhgets(&s->s_port));
+	return 0;
+}
+
+int
+so_send(int fd, void *d, int n, int f)
+{
+	return send(fd, d, n, f);
+}
+
+int
+so_recv(int fd, void *d, int n, int f)
+{
+	return recv(fd, d, n, f);
+}
--- /dev/null
+++ b/kern/devip.c
@@ -1,0 +1,870 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include "ip.h"
+
+#include "devip.h"
+
+void	csclose(Chan*);
+long	csread(Chan*, void*, long, vlong);
+long	cswrite(Chan*, void*, long, vlong);
+
+void osipinit(void);
+
+enum
+{
+	Qtopdir		= 1,	/* top level directory */
+	Qtopbase,
+	Qcs		= Qtopbase,
+
+	Qprotodir,		/* directory for a protocol */
+	Qprotobase,
+	Qclone		= Qprotobase,
+
+	Qconvdir,		/* directory for a conversation */
+	Qconvbase,
+	Qctl		= Qconvbase,
+	Qdata,
+	Qstatus,
+	Qremote,
+	Qlocal,
+	Qlisten,
+
+	MAXPROTO	= 4
+};
+#define TYPE(x) 	((int)((x).path & 0xf))
+#define CONV(x) 	((int)(((x).path >> 4)&0xfff))
+#define PROTO(x) 	((int)(((x).path >> 16)&0xff))
+#define QID(p, c, y) 	(((p)<<16) | ((c)<<4) | (y))
+#define ipzero(x)	memset(x, 0, IPaddrlen)
+
+typedef struct Proto	Proto;
+typedef struct Conv	Conv;
+struct Conv
+{
+	int	x;
+	Ref	r;
+	int	sfd;
+	int	perm;
+	char	owner[KNAMELEN];
+	char*	state;
+	uchar	laddr[IPaddrlen];
+	ushort	lport;
+	uchar	raddr[IPaddrlen];
+	ushort	rport;
+	int	restricted;
+	char	cerr[KNAMELEN];
+	Proto*	p;
+};
+
+struct Proto
+{
+	Lock	l;
+	int	x;
+	int	stype;
+	char	name[KNAMELEN];
+	int	nc;
+	int	maxconv;
+	Conv**	conv;
+	Qid	qid;
+};
+
+static	int	np;
+static	Proto	proto[MAXPROTO];
+
+static	Conv*	protoclone(Proto*, char*, int);
+static	void	setladdr(Conv*);
+
+static char	network[] = "network";
+
+static int
+ip3gen(Chan *c, int i, Dir *dp)
+{
+	Qid q;
+	Conv *cv;
+	char *p;
+
+	cv = proto[PROTO(c->qid)].conv[CONV(c->qid)];
+	mkqid(&q, QID(PROTO(c->qid), CONV(c->qid), i), 0, QTFILE);
+
+	switch(i) {
+	default:
+		return -1;
+	case Qctl:
+		devdir(c, q, "ctl", 0, cv->owner, cv->perm, dp);
+		return 1;
+	case Qdata:
+		devdir(c, q, "data", 0, cv->owner, cv->perm, dp);
+		return 1;
+	case Qlisten:
+		devdir(c, q, "listen", 0, cv->owner, cv->perm, dp);
+		return 1;
+	case Qlocal:
+		p = "local";
+		break;
+	case Qremote:
+		p = "remote";
+		break;
+	case Qstatus:
+		p = "status";
+		break;
+	}
+	devdir(c, q, p, 0, cv->owner, 0444, dp);
+	return 1;
+}
+
+static int
+ip2gen(Chan *c, int i, Dir *dp)
+{
+	Qid q;
+
+	switch(i) {
+	case Qclone:
+		mkqid(&q, QID(PROTO(c->qid), 0, Qclone), 0, QTFILE);
+		devdir(c, q, "clone", 0, network, 0666, dp);
+		return 1;
+	}
+	return -1;
+}
+
+static int
+ip1gen(Chan *c, int i, Dir *dp)
+{
+	Qid q;
+	char *p;
+	int prot;
+	int len = 0;
+
+	prot = 0666;
+	mkqid(&q, QID(0, 0, i), 0, QTFILE);
+	switch(i) {
+	default:
+		return -1;
+	case Qcs:
+		p = "cs";
+		prot = 0664;
+		break;
+	}
+	devdir(c, q, p, len, network, prot, dp);
+	return 1;
+}
+
+static int
+ipgen(Chan *c, char *nname, Dirtab *d, int nd, int s, Dir *dp)
+{
+	Qid q;
+	Conv *cv;
+
+	switch(TYPE(c->qid)) {
+	case Qtopdir:
+		if(s == DEVDOTDOT){
+			mkqid(&q, QID(0, 0, Qtopdir), 0, QTDIR);
+			snprint(up->genbuf, sizeof up->genbuf, "#I%lud", c->dev);
+			devdir(c, q, up->genbuf, 0, network, 0555, dp);
+			return 1;
+		}
+		if(s < np) {
+			mkqid(&q, QID(s, 0, Qprotodir), 0, QTDIR);
+			devdir(c, q, proto[s].name, 0, network, 0555, dp);
+			return 1;
+		}
+		s -= np;
+		return ip1gen(c, s+Qtopbase, dp);
+	case Qcs:
+		return ip1gen(c, TYPE(c->qid), dp);
+	case Qprotodir:
+		if(s == DEVDOTDOT){
+			mkqid(&q, QID(0, 0, Qtopdir), 0, QTDIR);
+			snprint(up->genbuf, sizeof up->genbuf, "#I%lud", c->dev);
+			devdir(c, q, up->genbuf, 0, network, 0555, dp);
+			return 1;
+		}
+		if(s < proto[PROTO(c->qid)].nc) {
+			cv = proto[PROTO(c->qid)].conv[s];
+			snprint(up->genbuf, sizeof up->genbuf, "%d", s);
+			mkqid(&q, QID(PROTO(c->qid), s, Qconvdir), 0, QTDIR);
+			devdir(c, q, up->genbuf, 0, cv->owner, 0555, dp);
+			return 1;
+		}
+		s -= proto[PROTO(c->qid)].nc;
+		return ip2gen(c, s+Qprotobase, dp);
+	case Qclone:
+		return ip2gen(c, TYPE(c->qid), dp);
+	case Qconvdir:
+		if(s == DEVDOTDOT){
+			s = PROTO(c->qid);
+			mkqid(&q, QID(s, 0, Qprotodir), 0, QTDIR);
+			devdir(c, q, proto[s].name, 0, network, 0555, dp);
+			return 1;
+		}
+		return ip3gen(c, s+Qconvbase, dp);
+	case Qctl:
+	case Qdata:
+	case Qlisten:
+	case Qlocal:
+	case Qremote:
+	case Qstatus:
+		return ip3gen(c, TYPE(c->qid), dp);
+	}
+	return -1;
+}
+
+static void
+newproto(char *name, int type, int maxconv)
+{
+	int l;
+	Proto *p;
+
+	if(np >= MAXPROTO) {
+		print("no %s: increase MAXPROTO", name);
+		return;
+	}
+
+	p = &proto[np];
+	strcpy(p->name, name);
+	p->stype = type;
+	p->qid.path = QID(np, 0, Qprotodir);
+	p->qid.type = QTDIR;
+	p->x = np++;
+	p->maxconv = maxconv;
+	l = sizeof(Conv*)*(p->maxconv+1);
+	p->conv = mallocz(l, 1);
+	if(p->conv == 0)
+		panic("no memory");
+}
+
+void
+ipinit(void)
+{
+	osipinit();
+
+	newproto("udp", S_UDP, 10);
+	newproto("tcp", S_TCP, 30);
+
+	fmtinstall('I', eipfmt);
+	fmtinstall('E', eipfmt);
+	
+}
+
+Chan *
+ipattach(char *spec)
+{
+	Chan *c;
+
+	c = devattach('I', spec);
+	c->qid.path = QID(0, 0, Qtopdir);
+	c->qid.type = QTDIR;
+	c->qid.vers = 0;
+	return c;
+}
+
+static Walkqid*
+ipwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name, nname, 0, 0, ipgen);
+}
+
+int
+ipstat(Chan *c, uchar *dp, int n)
+{
+	return devstat(c, dp, n, 0, 0, ipgen);
+}
+
+Chan *
+ipopen(Chan *c, int omode)
+{
+	Proto *p;
+	uchar raddr[IPaddrlen];
+	ushort rport;
+	int perm, sfd;
+	Conv *cv, *lcv;
+
+	omode &= 3;
+	perm = 0;
+	switch(omode) {
+	case OREAD:
+		perm = 4;
+		break;
+	case OWRITE:
+		perm = 2;
+		break;
+	case ORDWR:
+		perm = 6;
+		break;
+	}
+
+	switch(TYPE(c->qid)) {
+	default:
+		break;
+	case Qtopdir:
+	case Qprotodir:
+	case Qconvdir:
+	case Qstatus:
+	case Qremote:
+	case Qlocal:
+		if(omode != OREAD)
+			error(Eperm);
+		break;
+	case Qclone:
+		p = &proto[PROTO(c->qid)];
+		cv = protoclone(p, up->user, -1);
+		if(cv == 0)
+			error(Enodev);
+		c->qid.path = QID(p->x, cv->x, Qctl);
+		c->qid.vers = 0;
+		break;
+	case Qdata:
+	case Qctl:
+		p = &proto[PROTO(c->qid)];
+		lock(&p->l);
+		cv = p->conv[CONV(c->qid)];
+		lock(&cv->r.lk);
+		if((perm & (cv->perm>>6)) != perm) {
+			if(strcmp(up->user, cv->owner) != 0 ||
+		 	  (perm & cv->perm) != perm) {
+				unlock(&cv->r.lk);
+				unlock(&p->l);
+				error(Eperm);
+			}
+		}
+		cv->r.ref++;
+		if(cv->r.ref == 1) {
+			memmove(cv->owner, up->user, KNAMELEN);
+			cv->perm = 0660;
+		}
+		unlock(&cv->r.lk);
+		unlock(&p->l);
+		break;
+	case Qlisten:
+		p = &proto[PROTO(c->qid)];
+		lcv = p->conv[CONV(c->qid)];
+		sfd = so_accept(lcv->sfd, raddr, &rport);
+		cv = protoclone(p, up->user, sfd);
+		if(cv == 0) {
+			close(sfd);
+			error(Enodev);
+		}
+		ipmove(cv->raddr, raddr);
+		cv->rport = rport;
+		setladdr(cv);
+		cv->state = "Established";
+		c->qid.path = QID(p->x, cv->x, Qctl);
+		break;
+	}
+	c->mode = openmode(omode);
+	c->flag |= COPEN;
+	c->offset = 0;
+	return c;
+}
+
+void
+ipclose(Chan *c)
+{
+	Conv *cc;
+
+	switch(TYPE(c->qid)) {
+	case Qcs:
+		csclose(c);
+		break;
+	case Qdata:
+	case Qctl:
+		if((c->flag & COPEN) == 0)
+			break;
+		cc = proto[PROTO(c->qid)].conv[CONV(c->qid)];
+		if(decref(&cc->r) != 0)
+			break;
+		strcpy(cc->owner, network);
+		cc->perm = 0666;
+		cc->state = "Closed";
+		ipzero(cc->laddr);
+		ipzero(cc->raddr);
+		cc->lport = 0;
+		cc->rport = 0;
+		close(cc->sfd);
+		break;
+	}
+}
+
+long
+ipread(Chan *ch, void *a, long n, vlong offset)
+{
+	int r;
+	Conv *c;
+	Proto *x;
+	uchar ip[IPaddrlen];
+	char buf[128], *p;
+
+/*print("ipread %s %lux\n", chanpath(ch), (long)ch->qid.path);*/
+	p = a;
+	switch(TYPE(ch->qid)) {
+	default:
+		error(Eperm);
+	case Qcs:
+		return csread(ch, a, n, offset);
+	case Qprotodir:
+	case Qtopdir:
+	case Qconvdir:
+		return devdirread(ch, a, n, 0, 0, ipgen);
+	case Qctl:
+		sprint(buf, "%d", CONV(ch->qid));
+		return readstr(offset, p, n, buf);
+	case Qremote:
+		c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
+		ipmove(ip, c->raddr);
+		sprint(buf, "%I!%d\n", ip, c->rport);
+		return readstr(offset, p, n, buf);
+	case Qlocal:
+		c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
+		ipmove(ip, c->laddr);
+		sprint(buf, "%I!%d\n", ip, c->lport);
+		return readstr(offset, p, n, buf);
+	case Qstatus:
+		x = &proto[PROTO(ch->qid)];
+		c = x->conv[CONV(ch->qid)];
+		sprint(buf, "%s/%d %d %s \n",
+			c->p->name, c->x, c->r.ref, c->state);
+		return readstr(offset, p, n, buf);
+	case Qdata:
+		c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
+		r = so_recv(c->sfd, a, n, 0);
+		if(r < 0){
+			oserrstr();
+			nexterror();
+		}
+		return r;
+	}
+}
+
+static void
+setladdr(Conv *c)
+{
+	so_getsockname(c->sfd, c->laddr, &c->lport);
+}
+
+static void
+setlport(Conv *c)
+{
+	if(c->restricted == 0 && c->lport == 0)
+		return;
+
+	if(c->sfd == -1)
+		c->sfd = so_socket(c->p->stype, c->laddr);
+
+	so_bind(c->sfd, c->restricted, c->lport, c->laddr);
+}
+
+static void
+setladdrport(Conv *c, char *str)
+{
+	char *p;
+	uchar addr[IPaddrlen];
+
+	p = strchr(str, '!');
+	if(p == 0) {
+		p = str;
+		ipzero(c->laddr);
+	}
+	else {
+		*p++ = 0;
+		parseip(addr, str);
+		ipmove(c->laddr, addr);
+	}
+	if(*p == '*')
+		c->lport = 0;
+	else
+		c->lport = atoi(p);
+
+	setlport(c);
+}
+
+static char*
+setraddrport(Conv *c, char *str)
+{
+	char *p;
+	uchar addr[IPaddrlen];
+
+	p = strchr(str, '!');
+	if(p == 0)
+		return "malformed address";
+	*p++ = 0;
+	parseip(addr, str);
+	ipmove(c->raddr, addr);
+	c->rport = atoi(p);
+	p = strchr(p, '!');
+	if(p) {
+		if(strcmp(p, "!r") == 0)
+			c->restricted = 1;
+	}
+	return 0;
+}
+
+long
+ipwrite(Chan *ch, void *a, long n, vlong offset)
+{
+	Conv *c;
+	Proto *x;
+	int r, nf;
+	char *p, *fields[3], buf[128];
+
+	switch(TYPE(ch->qid)) {
+	default:
+		error(Eperm);
+	case Qcs:
+		return cswrite(ch, a, n, offset);
+	case Qctl:
+		x = &proto[PROTO(ch->qid)];
+		c = x->conv[CONV(ch->qid)];
+		if(n > sizeof(buf)-1)
+			n = sizeof(buf)-1;
+		memmove(buf, a, n);
+		buf[n] = '\0';
+
+		nf = tokenize(buf, fields, 3);
+		if(strcmp(fields[0], "connect") == 0){
+			switch(nf) {
+			default:
+				error("bad args to connect");
+			case 2:
+				p = setraddrport(c, fields[1]);
+				if(p != 0)
+					error(p);
+				break;
+			case 3:
+				p = setraddrport(c, fields[1]);
+				if(p != 0)
+					error(p);
+				c->lport = atoi(fields[2]);
+				setlport(c);
+				break;
+			}
+			if(c->sfd == -1)
+				c->sfd = so_socket(c->p->stype, c->raddr);
+			so_connect(c->sfd, c->raddr, c->rport);
+			setladdr(c);
+			c->state = "Established";
+			return n;
+		}
+		if(strcmp(fields[0], "announce") == 0) {
+			switch(nf){
+			default:
+				error("bad args to announce");
+			case 2:
+				setladdrport(c, fields[1]);
+				break;
+			}
+			so_listen(c->sfd);
+			c->state = "Announced";
+			return n;
+		}
+		if(strcmp(fields[0], "bind") == 0){
+			switch(nf){
+			default:
+				error("bad args to bind");
+			case 2:
+				c->lport = atoi(fields[1]);
+				break;
+			}
+			setlport(c);
+			return n;
+		}
+		error("bad control message");
+	case Qdata:
+		x = &proto[PROTO(ch->qid)];
+		c = x->conv[CONV(ch->qid)];
+		r = so_send(c->sfd, a, n, 0);
+		if(r < 0){
+			oserrstr();
+			nexterror();
+		}
+		return r;
+	}
+	return n;
+}
+
+static Conv*
+protoclone(Proto *p, char *user, int nfd)
+{
+	Conv *c, **pp, **ep;
+
+	c = 0;
+	lock(&p->l);
+	if(waserror()) {
+		unlock(&p->l);
+		nexterror();
+	}
+	ep = &p->conv[p->maxconv];
+	for(pp = p->conv; pp < ep; pp++) {
+		c = *pp;
+		if(c == 0) {
+			c = mallocz(sizeof(Conv), 1);
+			if(c == 0)
+				error(Enomem);
+			lock(&c->r.lk);
+			c->r.ref = 1;
+			c->p = p;
+			c->x = pp - p->conv;
+			p->nc++;
+			*pp = c;
+			break;
+		}
+		lock(&c->r.lk);
+		if(c->r.ref == 0) {
+			c->r.ref++;
+			break;
+		}
+		unlock(&c->r.lk);
+	}
+	if(pp >= ep) {
+		unlock(&p->l);
+		poperror();
+		return 0;
+	}
+
+	strcpy(c->owner, user);
+	c->perm = 0660;
+	c->state = "Closed";
+	c->restricted = 0;
+	ipzero(c->laddr);
+	ipzero(c->raddr);
+	c->lport = 0;
+	c->rport = 0;
+	c->sfd = nfd;
+
+	unlock(&c->r.lk);
+	unlock(&p->l);
+	poperror();
+	return c;
+}
+
+void
+csclose(Chan *c)
+{
+	free(c->aux);
+	c->aux = nil;
+}
+
+long
+csread(Chan *c, void *a, long n, vlong offset)
+{
+	char *s, *e, *p;
+
+	s = c->aux;
+	if(s == nil)
+		return 0;
+	e = strchr(s, 0);
+	s += offset;
+	if(s >= e)
+		return 0;
+	p = strchr(s, '\n');
+	if(p != nil)
+		p++;
+	else
+		p = e;
+	if(p - s < n)
+		n = p - s;
+	memmove(a, s, n);
+	return n;
+}
+
+static struct
+{
+	char *name;
+	uint num;
+} tab[] = {
+	"cs", 1,
+	"echo", 7,
+	"discard", 9,
+	"systat", 11,
+	"daytime", 13,
+	"netstat", 15,
+	"chargen", 19,
+	"ftp-data", 20,
+	"ftp", 21,
+	"ssh", 22,
+	"telnet", 23,
+	"smtp", 25,
+	"time", 37,
+	"whois", 43,
+	"dns", 53,
+	"domain", 53,
+	"uucp", 64,
+	"gopher", 70,
+	"rje", 77,
+	"finger", 79,
+	"http", 80,
+	"link", 87,
+	"supdup", 95,
+	"hostnames", 101,
+	"iso-tsap", 102,
+	"x400", 103,
+	"x400-snd", 104,
+	"csnet-ns", 105,
+	"pop-2", 109,
+	"pop3", 110,
+	"portmap", 111,
+	"uucp-path", 117,
+	"nntp", 119,
+	"netbios", 139,
+	"imap4", 143,
+	"NeWS", 144,
+	"print-srv", 170,
+	"z39.50", 210,
+	"fsb", 400,
+	"sysmon", 401,
+	"proxy", 402,
+	"proxyd", 404,
+	"https", 443,
+	"cifs", 445,
+	"ssmtp", 465,
+	"rexec", 512,
+	"login", 513,
+	"shell", 514,
+	"printer", 515,
+	"courier", 530,
+	"cscan", 531,
+	"uucp", 540,
+	"snntp", 563,
+	"9fs", 564,
+	"whoami", 565,
+	"guard", 566,
+	"ticket", 567,
+	"dlsftp", 666,
+	"fmclient", 729,
+	"imaps", 993,
+	"pop3s", 995,
+	"ingreslock", 1524,
+	"pptp", 1723,
+	"nfs", 2049,
+	"webster", 2627,
+	"weather", 3000,
+	"secstore", 5356,
+	"Xdisplay", 6000,
+	"styx", 6666,
+	"mpeg", 6667,
+	"rstyx", 6668,
+	"infdb", 6669,
+	"infsigner", 6671,
+	"infcsigner", 6672,
+	"inflogin", 6673,
+	"bandt", 7330,
+	"face", 32000,
+	"dhashgate", 11978,
+	"exportfs", 17007,
+	"rexexec", 17009,
+	"ncpu", 17010,
+	"cpu", 17013,
+	"rcpu", 17019,
+	"t9fs", 17020,
+	"glenglenda2", 17021,
+	"glenglenda3", 17022,
+	"glenglenda4", 17023,
+	"glenglenda5", 17024,
+	"glenglenda6", 17025,
+	"glenglenda7", 17026,
+	"glenglenda8", 17027,
+	"glenglenda9", 17028,
+	"glenglenda10", 17029,
+	"flyboy", 17032,
+	"dlsftp", 17033,
+	"venti", 17034,
+	"wiki", 17035,
+	"vica", 17036,
+	0
+};
+
+static int
+lookupport(char *s)
+{
+	int i;
+	char buf[10], *p;
+
+	i = strtol(s, &p, 0);
+	if(*s && *p == 0)
+		return i;
+
+	i = so_getservbyname(s, "tcp", buf);
+	if(i != -1)
+		return atoi(buf);
+	for(i=0; tab[i].name; i++)
+		if(strcmp(s, tab[i].name) == 0)
+			return tab[i].num;
+	return 0;
+}
+
+long
+cswrite(Chan *c, void *a, long n, vlong offset)
+{
+	char *f[4], *ips[8];
+	char *s, *ns;
+	uchar ip[IPaddrlen];
+	int i, nf, port, nips;
+
+	s = malloc(n+1);
+	if(s == nil)
+		error(Enomem);
+	if(waserror()){
+		free(s);
+		nexterror();
+	}
+	memmove(s, a, n);
+	s[n] = 0;
+	nf = getfields(s, f, nelem(f), 0, "!");
+	if(nf != 3)
+		error("can't translate");
+
+	port = lookupport(f[2]);
+	if(port <= 0)
+		error("no translation for port found");
+	if(strcmp(f[0], "net") == 0)
+		f[0] = "tcp";
+	if(strcmp(f[1], "*") == 0){
+		ns = smprint("/net/%s/clone %d", f[0], port);
+	} else {
+		if(parseip(ip, f[1]) != -1){
+			ips[0] = smprint("%I", ip);
+			nips = 1;
+		} else {
+			nips = so_gethostbyname(f[1], ips, nelem(ips));
+			if(nips <= 0)
+				error("no translation for host found");
+		}
+		ns = smprint("/net/%s/clone %s!%d", f[0], ips[0], port);
+		free(ips[0]);
+		for(i=1; i<nips; i++){
+			ips[0] = smprint("%s\n/net/%s/clone %s!%d", ns, f[0], ips[i], port);
+			free(ips[i]);
+			free(ns);
+			ns = ips[0];
+		}
+	}
+	free(c->aux);
+	c->aux = ns;
+	poperror();
+	free(s);
+	return n;
+}
+
+Dev ipdevtab = 
+{
+	'I',
+	"ip",
+
+	devreset,
+	ipinit,
+	devshutdown,
+	ipattach,
+	ipwalk,
+	ipstat,
+	ipopen,
+	devcreate,
+	ipclose,
+	ipread,
+	devbread,
+	ipwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
+
--- a/kern/devtab.c
+++ b/kern/devtab.c
@@ -8,6 +8,8 @@
 extern Dev rootdevtab;
 extern Dev pipedevtab;
 extern Dev fsdevtab;
+extern Dev lfddevtab;
+extern Dev ipdevtab;
 extern Dev mntdevtab;
 extern Dev lfddevtab;
 extern Dev cmddevtab;
@@ -18,6 +20,8 @@
 	&consdevtab,
 	&pipedevtab,
 	&fsdevtab,
+	&lfddevtab,
+	&ipdevtab,
 	&mntdevtab,
 	&lfddevtab,
 	&cmddevtab,
--- /dev/null
+++ b/libip/Makefile
@@ -1,0 +1,19 @@
+ROOT=..
+include ../Make.config
+LIB=libip.a
+
+OFILES=\
+	eipfmt.$O\
+	parseip.$O\
+	classmask.$O\
+	bo.$O\
+	ipaux.$O\
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libip/bo.c
@@ -1,0 +1,77 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+
+void
+hnputv(void *p, uvlong v)
+{
+	uchar *a;
+
+	a = p;
+	a[0] = v>>56;
+	a[1] = v>>48;
+	a[2] = v>>40;
+	a[3] = v>>32;
+	a[4] = v>>24;
+	a[5] = v>>16;
+	a[6] = v>>8;
+	a[7] = v;
+}
+
+void
+hnputl(void *p, uint v)
+{
+	uchar *a;
+
+	a = p;
+	a[0] = v>>24;
+	a[1] = v>>16;
+	a[2] = v>>8;
+	a[3] = v;
+}
+
+void
+hnputs(void *p, ushort v)
+{
+	uchar *a;
+
+	a = p;
+	a[0] = v>>8;
+	a[1] = v;
+}
+
+uvlong
+nhgetv(void *p)
+{
+	uchar *a;
+	uvlong v;
+
+	a = p;
+	v = (uvlong)a[0]<<56;
+	v |= (uvlong)a[1]<<48;
+	v |= (uvlong)a[2]<<40;
+	v |= (uvlong)a[3]<<32;
+	v |= a[4]<<24;
+	v |= a[5]<<16;
+	v |= a[6]<<8;
+	v |= a[7]<<0;
+	return v;
+}
+
+uint
+nhgetl(void *p)
+{
+	uchar *a;
+
+	a = p;
+	return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0);
+}
+
+ushort
+nhgets(void *p)
+{
+	uchar *a;
+
+	a = p;
+	return (a[0]<<8)|(a[1]<<0);
+}
--- /dev/null
+++ b/libip/classmask.c
@@ -1,0 +1,86 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+
+static uchar classmask[4][16] = {
+	0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0xff,  0xff,0x00,0x00,0x00,
+	0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0xff,  0xff,0x00,0x00,0x00,
+	0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0xff,  0xff,0xff,0x00,0x00,
+	0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0x00,
+};
+
+static uchar v6loopback[IPaddrlen] = {
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0x01
+};
+
+static uchar v6linklocal[IPaddrlen] = {
+	0xfe, 0x80, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0
+};
+static uchar v6linklocalmask[IPaddrlen] = {
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff,
+	0, 0, 0, 0,
+	0, 0, 0, 0
+};
+static int v6llpreflen = 8;	/* link-local prefix length in bytes */
+
+static uchar v6multicast[IPaddrlen] = {
+	0xff, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0
+};
+static uchar v6multicastmask[IPaddrlen] = {
+	0xff, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0
+};
+static int v6mcpreflen = 1;	/* multicast prefix length */
+
+static uchar v6solicitednode[IPaddrlen] = {
+	0xff, 0x02, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0x01,
+	0xff, 0, 0, 0
+};
+static uchar v6solicitednodemask[IPaddrlen] = {
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0x0, 0x0, 0x0
+};
+static int v6snpreflen = 13;
+
+uchar*
+defmask(uchar *ip)
+{
+	if(isv4(ip))
+		return classmask[ip[IPv4off]>>6];
+	else {
+		if(ipcmp(ip, v6loopback) == 0)
+			return IPallbits;
+		else if(memcmp(ip, v6linklocal, v6llpreflen) == 0)
+			return v6linklocalmask;
+		else if(memcmp(ip, v6solicitednode, v6snpreflen) == 0)
+			return v6solicitednodemask;
+		else if(memcmp(ip, v6multicast, v6mcpreflen) == 0)
+			return v6multicastmask;
+		return IPallbits;
+	}
+}
+
+void
+maskip(uchar *from, uchar *mask, uchar *to)
+{
+	int i;
+
+	for(i = 0; i < IPaddrlen; i++)
+		to[i] = from[i] & mask[i];
+}
--- /dev/null
+++ b/libip/eipfmt.c
@@ -1,0 +1,109 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+
+enum
+{
+	Isprefix= 16,
+};
+
+uchar prefixvals[256] =
+{
+[0x00] 0 | Isprefix,
+[0x80] 1 | Isprefix,
+[0xC0] 2 | Isprefix,
+[0xE0] 3 | Isprefix,
+[0xF0] 4 | Isprefix,
+[0xF8] 5 | Isprefix,
+[0xFC] 6 | Isprefix,
+[0xFE] 7 | Isprefix,
+[0xFF] 8 | Isprefix,
+};
+
+int
+eipfmt(Fmt *f)
+{
+	char buf[5*8];
+	static char *efmt = "%.2ux%.2ux%.2ux%.2ux%.2ux%.2ux";
+	static char *ifmt = "%d.%d.%d.%d";
+	uchar *p, ip[16];
+	ulong *lp;
+	ushort s;
+	int i, j, n, eln, eli;
+
+	switch(f->r) {
+	case 'E':		/* Ethernet address */
+		p = va_arg(f->args, uchar*);
+		snprint(buf, sizeof buf, efmt, p[0], p[1], p[2], p[3], p[4], p[5]);
+		return fmtstrcpy(f, buf);
+
+	case 'I':		/* Ip address */
+		p = va_arg(f->args, uchar*);
+common:
+		if(memcmp(p, v4prefix, 12) == 0){
+			snprint(buf, sizeof buf, ifmt, p[12], p[13], p[14], p[15]);
+			return fmtstrcpy(f, buf);
+		}
+
+		/* find longest elision */
+		eln = eli = -1;
+		for(i = 0; i < 16; i += 2){
+			for(j = i; j < 16; j += 2)
+				if(p[j] != 0 || p[j+1] != 0)
+					break;
+			if(j > i && j - i > eln){
+				eli = i;
+				eln = j - i;
+			}
+		}
+
+		/* print with possible elision */
+		n = 0;
+		for(i = 0; i < 16; i += 2){
+			if(i == eli){
+				n += sprint(buf+n, "::");
+				i += eln;
+				if(i >= 16)
+					break;
+			} else if(i != 0)
+				n += sprint(buf+n, ":");
+			s = (p[i]<<8) + p[i+1];
+			n += sprint(buf+n, "%ux", s);
+		}
+		return fmtstrcpy(f, buf);
+
+	case 'i':		/* v6 address as 4 longs */
+		lp = va_arg(f->args, ulong*);
+		for(i = 0; i < 4; i++)
+			hnputl(ip+4*i, *lp++);
+		p = ip;
+		goto common;
+
+	case 'V':		/* v4 ip address */
+		p = va_arg(f->args, uchar*);
+		snprint(buf, sizeof buf, ifmt, p[0], p[1], p[2], p[3]);
+		return fmtstrcpy(f, buf);
+
+	case 'M':		/* ip mask */
+		p = va_arg(f->args, uchar*);
+
+		/* look for a prefix mask */
+		for(i = 0; i < 16; i++)
+			if(p[i] != 0xff)
+				break;
+		if(i < 16){
+			if((prefixvals[p[i]] & Isprefix) == 0)
+				goto common;
+			for(j = i+1; j < 16; j++)
+				if(p[j] != 0)
+					goto common;
+			n = 8*i + (prefixvals[p[i]] & ~Isprefix);
+		} else
+			n = 8*16;
+
+		/* got one, use /xx format */
+		snprint(buf, sizeof buf, "/%d", n);
+		return fmtstrcpy(f, buf);
+	}
+	return fmtstrcpy(f, "(eipfmt)");
+}
--- /dev/null
+++ b/libip/ipaux.c
@@ -1,0 +1,102 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+
+/*
+ *  well known IP addresses
+ */
+uchar IPv4bcast[IPaddrlen] = {
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff
+};
+uchar IPv4allsys[IPaddrlen] = {
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0xff, 0xff,
+	0xe0, 0, 0, 0x01
+};
+uchar IPv4allrouter[IPaddrlen] = {
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0xff, 0xff,
+	0xe0, 0, 0, 0x02
+};
+uchar IPallbits[IPaddrlen] = {
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff
+};
+uchar IPnoaddr[IPaddrlen];
+
+/*
+ *  prefix of all v4 addresses
+ */
+uchar v4prefix[IPaddrlen] = {
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0xff, 0xff,
+	0, 0, 0, 0
+};
+
+int
+isv4(uchar *ip)
+{
+	return memcmp(ip, v4prefix, IPv4off) == 0;
+}
+
+/*
+ *  the following routines are unrolled with no memset's to speed
+ *  up the usual case
+ */
+void
+v4tov6(uchar *v6, uchar *v4)
+{
+	v6[0] = 0;
+	v6[1] = 0;
+	v6[2] = 0;
+	v6[3] = 0;
+	v6[4] = 0;
+	v6[5] = 0;
+	v6[6] = 0;
+	v6[7] = 0;
+	v6[8] = 0;
+	v6[9] = 0;
+	v6[10] = 0xff;
+	v6[11] = 0xff;
+	v6[12] = v4[0];
+	v6[13] = v4[1];
+	v6[14] = v4[2];
+	v6[15] = v4[3];
+}
+
+int
+v6tov4(uchar *v4, uchar *v6)
+{
+	if(v6[0] == 0
+	&& v6[1] == 0
+	&& v6[2] == 0
+	&& v6[3] == 0
+	&& v6[4] == 0
+	&& v6[5] == 0
+	&& v6[6] == 0
+	&& v6[7] == 0
+	&& v6[8] == 0
+	&& v6[9] == 0
+	&& v6[10] == 0xff
+	&& v6[11] == 0xff)
+	{
+		v4[0] = v6[12];
+		v4[1] = v6[13];
+		v4[2] = v6[14];
+		v4[3] = v6[15];
+		return 0;
+	} else {
+		memset(v4, 0, 4);
+		if(memcmp(v6, IPnoaddr, IPaddrlen) == 0)
+			return 0;
+		return -1;
+	}
+}
--- /dev/null
+++ b/libip/parseip.c
@@ -1,0 +1,176 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <ip.h>
+
+char*
+v4parseip(uchar *to, char *from)
+{
+	int i;
+	char *p;
+
+	p = from;
+	for(i = 0; i < 4 && *p; i++){
+		to[i] = strtoul(p, &p, 0);
+		if(*p == '.')
+			p++;
+	}
+	switch(CLASS(to)){
+	case 0:	/* class A - 1 uchar net */
+	case 1:
+		if(i == 3){
+			to[3] = to[2];
+			to[2] = to[1];
+			to[1] = 0;
+		} else if (i == 2){
+			to[3] = to[1];
+			to[1] = 0;
+		}
+		break;
+	case 2:	/* class B - 2 uchar net */
+		if(i == 3){
+			to[3] = to[2];
+			to[2] = 0;
+		}
+		break;
+	}
+	return p;
+}
+
+static int
+ipcharok(int c)
+{
+	return c == '.' || c == ':' || (isascii(c) && isxdigit(c));
+}
+
+static int
+delimchar(int c)
+{
+	if(c == '\0')
+		return 1;
+	if(c == '.' || c == ':' || (isascii(c) && isalnum(c)))
+		return 0;
+	return 1;
+}
+
+/*
+ * `from' may contain an address followed by other characters,
+ * at least in /boot, so we permit whitespace (and more) after the address.
+ * we do ensure that "delete" cannot be parsed as "de::".
+ *
+ * some callers don't check the return value for errors, so
+ * set `to' to something distinctive in the case of a parse error.
+ */
+vlong
+parseip(uchar *to, char *from)
+{
+	int i, elipsis = 0, v4 = 1;
+	ulong x;
+	char *p, *op;
+
+	memset(to, 0, IPaddrlen);
+	p = from;
+	for(i = 0; i < IPaddrlen && ipcharok(*p); i+=2){
+		op = p;
+		x = strtoul(p, &p, 16);
+		if(*p == '.' || (*p == 0 && i == 0)){	/* ends with v4? */
+			if(i > IPaddrlen-4){
+				memset(to, 0, IPaddrlen);
+				return -1;		/* parse error */
+			}
+			p = v4parseip(to+i, op);
+			i += 4;
+			break;
+		}
+		/* v6: at most 4 hex digits, followed by colon or delim */
+		if(x != (ushort)x || (*p != ':' && !delimchar(*p))) {
+			memset(to, 0, IPaddrlen);
+			return -1;			/* parse error */
+		}
+		to[i] = x>>8;
+		to[i+1] = x;
+		if(*p == ':'){
+			v4 = 0;
+			if(*++p == ':'){	/* :: is elided zero short(s) */
+				if (elipsis) {
+					memset(to, 0, IPaddrlen);
+					return -1;	/* second :: */
+				}
+				elipsis = i+2;
+				p++;
+			}
+		} else if (p == op)		/* strtoul made no progress? */
+			break;
+	}
+	if (p == from || !delimchar(*p)) {
+		memset(to, 0, IPaddrlen);
+		return -1;				/* parse error */
+	}
+	if(i < IPaddrlen){
+		memmove(&to[elipsis+IPaddrlen-i], &to[elipsis], i-elipsis);
+		memset(&to[elipsis], 0, IPaddrlen-i);
+	}
+	if(v4){
+		to[10] = to[11] = 0xff;
+		return (ulong)nhgetl(to + IPv4off);
+	} else
+		return 6;
+}
+
+/*
+ *  hack to allow ip v4 masks to be entered in the old
+ *  style
+ */
+vlong
+parseipmask(uchar *to, char *from, int v4)
+{
+	vlong x;
+	int i, w;
+	uchar *p;
+
+	if(*from == '/'){
+		/* as a number of prefix bits */
+		i = atoi(from+1);
+		if(i < 0)
+			i = 0;
+		if(i <= 32 && v4)
+			i += 96;
+		if(i > 128)
+			i = 128;
+		w = i;
+		memset(to, 0, IPaddrlen);
+		for(p = to; i >= 8; i -= 8)
+			*p++ = 0xff;
+		if(i > 0)
+			*p = ~((1<<(8-i))-1);
+		/*
+		 * identify as ipv6 if the mask is inexpressible as a v4 mask
+		 * (because it has too few mask bits).  Arguably, we could
+		 * always return 6 here.
+		 */
+		if (w < 96)
+			return v4 ? -1 : 6;
+		x = (ulong)nhgetl(to+IPv4off);
+	} else {
+		/* as a straight v4 bit mask */
+		x = parseip(to, from);
+		if(memcmp(to, v4prefix, IPv4off) == 0)
+			memset(to, 0xff, IPv4off);
+		else if(v4 && memcmp(to, IPallbits, IPv4off) != 0)
+			x = -1;
+	}
+	return x;
+}
+
+vlong
+parseipandmask(uchar *ip, uchar *mask, char *ipstr, char *maskstr)
+{
+	vlong x;
+
+	x = parseip(ip, ipstr);
+	if(maskstr == nil)
+		memset(mask, 0xff, IPaddrlen);
+	else if(parseipmask(mask, maskstr, memcmp(ip, v4prefix, IPv4off) == 0) == -1)
+		x = -1;
+	return x;
+}
--- a/main.c
+++ b/main.c
@@ -60,10 +60,13 @@
 		panic("bind #c: %r");
 	if(bind("#e", "/env", MREPL|MCREATE) < 0)
 		panic("bind #e: %r");
+	if(bind("#I", "/net", MBEFORE) < 0)
+		panic("bind #I: %r");
 	if(bind("#U", "/root", MREPL) < 0)
 		panic("bind #U: %r");
     if(bind("/root", "/", MAFTER) < 0)
 		panic("bind /root: %r");
+
 	char *cmd[] = {
 		"drawcpu",
 		"-c"
--- a/rc/drawcpu.c
+++ b/rc/drawcpu.c
@@ -30,6 +30,11 @@
 	"mount",	execmount,
 	"unmount",	execunmount,
 	"ls", 		execls,
+	"cat",		execcat,
+	"rm",       execrm,
+	"mkdir",    execmkdir,
+	"test",     exectest,
+	"echo",     exececho,
 	0
 };
 
@@ -412,8 +417,14 @@
 int
 Open(char *file, int mode)
 {
+	int fd;
 	static int tab[] = {OREAD,OWRITE,ORDWR,OREAD|ORCLOSE};
-	return open(file, tab[mode&3]);
+	fd = open(file, tab[mode&3]);
+	if(fd < 0 && strcmp(file, "/fd/0") == 0)
+		fd = lfdfd(0);
+	if(fd < 0 && strcmp(file, "/fd/1") == 0)
+		fd = lfdfd(1);
+	return fd;
 }
 
 void
@@ -457,12 +468,6 @@
 Isatty(int fd)
 {
 	return isatty(fd);
-	/*
-	char buf[64];
-	if(fd2path(fd, buf, sizeof buf) != 0)
-		return 0;
-	return strlen(buf) >= 9 && strcmp(buf+strlen(buf)-9, "/dev/cons") == 0;
-	*/
 }
 
 void
--- a/rc/exec.h
+++ b/rc/exec.h
@@ -74,7 +74,8 @@
 };
 extern void (*builtinfunc(char *name))(void);
 
-void execread(void), execns(void), execls(void);
+void execread(void), execns(void), execls(void), execcat(void);
+void execrm(void), execmkdir(void), exectest(void), exececho(void);
 void execbind(void), execmount(void), execunmount(void);
 void execcd(void), execwhatis(void), execeval(void), execexec(void);
 int execforkexec(void);
--- a/rc/simple.c
+++ b/rc/simple.c
@@ -302,6 +302,59 @@
 }
 
 void
+execrm(void)
+{
+	int i, recurse, force;
+	char *fd;
+	Dir *db;
+	word *a;
+
+	force = recurse = 0;
+	popword(); /* rm */
+	while(runq->argv->words && runq->argv->words->word[0]=='-'){
+		char *f = runq->argv->words->word+1;
+		if(*f == '-'){
+			popword();
+			break;
+		}
+		for(; *f; f++){
+			switch(*f){
+			case 'r':
+				recurse = 1;
+				break;
+			case 'f':
+				force = 1;
+				break;
+			default:
+				goto Usage;
+			}
+			popword();
+		}
+	}
+	a = runq->argv->words;
+	for(;a;a = a->next){
+		if(remove(a->word) != -1)
+			continue;
+		db = nil;
+		if(recurse && (db=dirstat(a->word))!=nil && (db->qid.type&QTDIR))
+			rmdir(a->word);
+		else
+			goto Error;
+		free(db);
+	}
+	return;
+Error:
+	pfmt(err, "rm: %r\n", strerror(errno));
+	poplist();
+	return;
+Usage:
+	pfmt(err, "usage: rm [-fr] file ...\n");
+	setstatus("rm usage");
+	poplist();
+	return;
+}
+
+void
 execns(void)
 {
 //print("Execns\n");
@@ -346,7 +399,6 @@
 		goto Error;
 	return;
 Error:
-	setstatus("bind error");
 	poplist();
 	if(qflag)
 		return;
@@ -412,6 +464,7 @@
 		goto Error;
 	if(sysmount(fd, -1, runq->argv->words->next->word, flag, spec) < 0)
 		goto Error;
+	poplist();
 	return;
 Error:
 	setstatus("mount error");
@@ -432,7 +485,106 @@
 	//unmount
 }
 
+int
+cat(int f, char *s)
+{
+	char buf[IOUNIT];
+	long n;
+	while((n=Read(f, buf, sizeof buf))>0)
+		if(Write(1, buf, n)!=n)
+			pfmt(err, "write error copying %s: %s", s, strerror(errno));
+	if(n < 0)
+		pfmt(err, "error reading %s: %s", s, strerror(errno));
+}
+
 void
+execcat(void)
+{
+	int f;
+	popword(); /* cat */
+	word *a;
+
+	a = runq->argv->words;
+	if(count(a) == 0){
+		cat(f, "<stdin>");
+		close(f);
+	} else for(;a;a = a->next){
+		f = Open(a->word, OREAD);
+		if(f < 0){
+			pfmt(err, "can't open %s: %s", a->word, strerror(errno));
+			break;
+		}
+		cat(f, a->word);
+		close(f);
+		write(1, "\n", 1);
+	}
+	poplist();
+}
+
+int
+hasmode(char *f, ulong m)
+{
+	int r;
+	Dir *dir;
+	dir = dirstat(f);
+	if (dir == nil)
+		return 0;
+	r = (dir->mode & m) != 0;
+	free(dir);
+	return r;
+}
+
+void
+exectest(void)
+{
+	/* TODO(halfwit): Only care about -d for my needs, but test has a ton of things */
+	setstatus("no such file");
+	if(strcmp(runq->argv->words->next->word, "-d")==0)
+		if(hasmode(runq->argv->words->next->next->word, DMDIR))
+			setstatus("");
+	poplist();
+}
+
+void
+execmkdir(void)
+{
+
+}
+
+void
+exececho(void)
+{
+	int nflag;
+	int i, len;
+	char *buf, *p;
+	word *a, *c;
+
+	nflag = 0;
+	popword(); /* echo */
+	a = runq->argv->words;
+	if(count(a) > 0 && strcmp(a->word, "-n") == 0){
+		a = a->next; // move up our counter as well
+		nflag = 1;
+	}
+	len = 1;
+	for(c = a; c; c = c->next)
+		len += strlen(c->word)+1;
+	buf = malloc(len);
+	if(buf == 0)
+		panic("no memory");
+	p = buf;
+	for(c = a; c; c = c->next){
+		strcpy(p, c->word);
+		p += strlen(p);
+		if(c->next)
+			*p++ = ' ';
+	}
+	if(!nflag)
+		*p++ = '\n';
+	write(1, buf, p-buf);
+}
+
+void
 execls(void)
 {
 	Dir *db;
@@ -714,9 +866,6 @@
 		fd = Open(file, 0);
 		if(fd >= 0)
 			break;
-		if(strcmp(file, "/fd/0")==0){
-			fd = open("/dev/cons", OREAD);
-		}
 		free(file);
 	}
 	if(fd<0){