Plan 9 programs for controlling and checking the network: cs.c, csquery.c, netstat.c
authorRonald G. Minnich <rminnich@google.com>
Tue, 11 Mar 2014 19:14:05 +0000 (12:14 -0700)
committerRonald G. Minnich <rminnich@google.com>
Tue, 11 Mar 2014 19:14:05 +0000 (12:14 -0700)
Signed-off-by: Ronald G. Minnich <rminnich@google.com>
tests/Makefile
tests/cs.c [new file with mode: 0644]
tests/csquery.c [new file with mode: 0644]
tests/netstat.c [new file with mode: 0644]

index 465c2e9..a4b0612 100644 (file)
@@ -8,7 +8,7 @@ TESTS_DIR = tests
 CFLAGS_TESTS += $(CFLAGS_USER) -g
 TESTS_CXXFLAGS += $(CXXFLAGS_USER) -g
 
-TESTS_LDLIBS := -lpthread -lbenchutil -lm
+TESTS_LDLIBS := -lpthread -lbenchutil -lm -lnixip -lndb
 
 TESTS_SRCS_C := $(wildcard $(TESTS_DIR)/*.c)
 TESTS_SRCS_CPP := $(wildcard $(TESTS_DIR)/*.cc)
diff --git a/tests/cs.c b/tests/cs.c
new file mode 100644 (file)
index 0000000..1d4cdb4
--- /dev/null
@@ -0,0 +1,1914 @@
+/* 
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+#include <stdlib.h>
+
+#include <stdio.h>
+#include <parlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <pthread.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <error.h>
+#include <iplib.h>
+#include <dir.h>
+#include <ndb.h>
+#include <fcall.h>
+
+enum
+{
+       Nreply=                 20,
+       Maxreply=               256,
+       Maxrequest=             128,
+       Maxpath=                128,
+       Maxfdata=               8192,
+       Maxhost=                64,             /* maximum host name size */
+       Maxservice=             64,             /* maximum service name size */
+
+       Qdir=                   0,
+       Qcs=                    1,
+};
+
+typedef struct Mfile   Mfile;
+typedef struct Mlist   Mlist;
+typedef struct Network Network;
+typedef struct Flushreq        Flushreq;
+typedef struct Job     Job;
+
+int vers;              /* incremented each clone/attach */
+/* need to resolve the #inluce for all this stuff. */
+#define DMDIR              0x80000000      /* mode bit for directories */
+#define QTDIR 0x80
+#define QTFILE 0
+#define ERRMAX 128
+
+struct Mfile
+{
+       int             busy;
+
+       char            *user;
+       struct qid     qid;
+       int             fid;
+
+       /*
+        *  current request
+        */
+       char            *net;
+       char            *host;
+       char            *serv;
+       char            *rem;
+
+       /*
+        *  result of the last lookup
+        */
+       Network         *nextnet;
+       int             nreply;
+       char            *reply[Nreply];
+       int             replylen[Nreply];
+};
+
+struct Mlist
+{
+       Mlist   *next;
+       Mfile   mf;
+};
+
+
+/*
+ *  active requests
+ */
+struct Job
+{
+       Job     *next;
+       int     flushed;
+       struct fcall    request;
+       struct fcall    reply;
+       pthread_t thread;
+};
+
+//spinlock_t   joblock;
+int joblock;
+Job    *joblist;
+
+Mlist  *mlist;
+int    mfd[2];
+int    debug;
+int    paranoia;
+int    ipv6lookups = 1;
+char   *dbfile = "lib/ndb/local";
+struct ndb     *db, *netdb;
+
+void   rversion(Job*);
+void   rflush(Job*);
+void   rattach(Job*, Mfile*);
+char*  rwalk(Job*, Mfile*);
+void   ropen(Job*, Mfile*);
+void   rcreate(Job*, Mfile*);
+void   rread(Job*, Mfile*);
+void   rwrite(Job*, Mfile*);
+void   rclunk(Job*, Mfile*);
+void   rremove(Job*, Mfile*);
+void   rstat(Job*, Mfile*);
+void   rwstat(Job*, Mfile*);
+void   rauth(Job*);
+void   sendmsg(Job*, char*);
+void   mountinit(char*, char*);
+void   io(void);
+void   ndbinit(void);
+void   netinit(int);
+void   netadd(char*);
+char   *genquery(Mfile*, char*);
+char*  ipinfoquery(Mfile*, char**, int);
+int    needproto(Network*, struct ndbtuple*);
+int    lookup(Mfile*);
+struct ndbtuple*       reorder(struct ndbtuple*, struct ndbtuple*);
+void   ipid(void);
+void   readipinterfaces(void);
+void*  emalloc(int);
+char*  estrdup(char*);
+Job*   newjob(void);
+void   freejob(Job*);
+void   setext(char*, int, char*);
+void   cleanmf(Mfile*);
+
+extern void    paralloc(void);
+
+spinlock_t     dblock;         /* mutex on database operations */
+spinlock_t     netlock;        /* mutex for netinit() */
+
+char   *logfile = "cs";
+char   *paranoiafile = "cs.paranoia";
+
+char   mntpt[Maxpath];
+char   netndb[Maxpath];
+
+/*
+ *  Network specific translators
+ */
+struct ndbtuple*       iplookup(Network*, char*, char*, int);
+char*          iptrans(struct ndbtuple*, Network*, char*, char*, int);
+struct ndbtuple*       telcolookup(Network*, char*, char*, int);
+char*          telcotrans(struct ndbtuple*, Network*, char*, char*, int);
+struct ndbtuple*       dnsiplookup(char*, struct ndbs*);
+
+struct Network
+{
+       char            *net;
+       struct ndbtuple *(*lookup)(Network*, char*, char*, int);
+       char            *(*trans)(struct ndbtuple*, Network*, char*, char*, int);
+       int             considered;             /* flag: ignored for "net!"? */
+       int             fasttimeouthack;        /* flag. was for IL */
+       Network         *next;
+};
+
+enum
+{
+       Ntcp = 0,
+};
+
+/*
+ *  net doesn't apply to (r)udp, icmp(v6), or telco (for speed).
+ */
+Network network[] = {
+[Ntcp] { "tcp",        iplookup,       iptrans,        0 },
+       { "udp",        iplookup,       iptrans,        1 },
+       { "icmp",       iplookup,       iptrans,        1 },
+       { "icmpv6",     iplookup,       iptrans,        1 },
+       { "rudp",       iplookup,       iptrans,        1 },
+       { "ssh",        iplookup,       iptrans,        1 },
+       { "telco",      telcolookup,    telcotrans,     1 },
+       { 0 },
+};
+
+spinlock_t ipifclock;
+struct ipifc *ipifcs;
+
+char   eaddr[16];              /* ascii ethernet address */
+char   ipaddr[64];             /* ascii internet address */
+uint8_t        ipa[IPaddrlen];         /* binary internet address */
+char   *mysysname;
+
+Network *netlist;              /* networks ordered by preference */
+Network *last;
+
+static void
+nstrcpy(char *to, char *from, int len)
+{
+       strncpy(to, from, len);
+       to[len-1] = 0;
+}
+
+char *argv0;
+void
+usage(void)
+{
+       fprintf(stderr, "CS:usage: %s [-dn] [-f ndb-file] [-x netmtpt]\n", argv0);
+       fprintf(stderr, "CS:usage");
+       exit(1);
+}
+
+/*
+ * based on libthread's threadsetname, but drags in less library code.
+ * actually just sets the arguments displayed.
+ */
+void
+procsetname(char *fmt, ...)
+{
+/* someday ... */
+#if 0
+       int fd;
+       char *cmdname;
+       char buf[128];
+       va_list arg;
+
+       va_start(arg, fmt);
+       cmdname = vsmprint(fmt, arg);
+       va_end(arg);
+       if (cmdname == NULL)
+               return;
+       snprintf(buf, sizeof buf, "#p/%d/args", getpid());
+       if((fd = open(buf, OWRITE)) >= 0){
+               write(fd, cmdname, strlen(cmdname)+1);
+               close(fd);
+       }
+       free(cmdname);
+#endif
+}
+
+void
+main(int argc, char *argv[])
+{
+       int justsetname;
+       char ext[Maxpath], servefile[Maxpath];
+       argv0 = argv[0];
+       justsetname = 0;
+       setnetmtpt(mntpt, sizeof(mntpt), NULL);
+       ext[0] = 0;
+       argc--, argv++;
+       while (argc && **argv == '-'){
+               switch(argv[0][1]){
+       case '4':
+               ipv6lookups = 0;
+               break;
+       case 'd':
+               debug = 1;
+               break;
+       case 'f':
+               if (argc < 2)
+                       usage();
+               dbfile = argv[1];
+               argc--,argv++;
+               break;
+       case 'n':
+               justsetname = 1;
+               break;
+       case 'x':
+               if (argc < 2)
+                       usage();
+               setnetmtpt(mntpt, sizeof(mntpt), argv[1]);
+               argc--,argv++;
+               setext(ext, sizeof(ext), mntpt);
+               break;
+               }
+               argc--,argv++;
+       }
+
+       //rfork(RFREND|RFNOTEG);
+
+       snprintf(servefile, sizeof(servefile), "#s/cs%s", ext);
+       snprintf(netndb, sizeof(netndb), "%s/ndb", mntpt);
+       syscall(SYS_nunmount, (unsigned long)servefile, (unsigned long)mntpt);
+       remove(servefile);
+
+       ndbinit();
+       netinit(0);
+
+       if(!justsetname){
+               mountinit(servefile, mntpt);
+               io();
+       }
+       exit(0);
+}
+
+/*
+ *  if a mount point is specified, set the cs extention to be the mount point
+ *  with '_'s replacing '/'s
+ */
+void
+setext(char *ext, int n, char *p)
+{
+       int i, c;
+
+       n--;
+       for(i = 0; i < n; i++){
+               c = p[i];
+               if(c == 0)
+                       break;
+               if(c == '/')
+                       c = '_';
+               ext[i] = c;
+       }
+       ext[i] = 0;
+}
+
+void
+mountinit(char *service, char *mntpt)
+{
+       int f;
+       int p[2];
+       char buf[32];
+       int ret;
+
+       ret = syscall(SYS_pipe, (unsigned long)p);
+       if (ret < 0){
+               error(1, 0, "pipe: %r");
+               exit(1);
+       }
+
+       /*
+        *  make a /srv/cs
+        * ORCLOSE means remove on last close. Handy. Not here yet. 
+        */
+       f = open(service, O_WRONLY|O_CREAT/*|ORCLOSE*/, 0666);
+printf("open %s gets %d\n", service, f);
+       if(f < 0)
+               error(1, 0, "%s: %r",service);
+       snprintf(buf, sizeof(buf), "%d", p[1]);
+       if(write(f, buf, strlen(buf)) != strlen(buf))
+               error(1, 0, "Write %s: %r", service);
+       /* using #s: we create a pipe and drop it into #s. 
+        * we no longer mount. That's up to you.
+        * #s will route requests to us.
+        */
+       close(p[1]);
+
+       mfd[0] = mfd[1] = p[0];
+}
+
+void
+ndbinit(void)
+{
+       db = ndbopen(dbfile);
+       if(db == NULL)
+               error(1, 0, "%s: %r","can't open network database");
+
+       netdb = ndbopen(netndb);
+       if(netdb != NULL){
+               netdb->nohash = 1;
+               db = ndbcat(netdb, db);
+       }
+}
+
+Mfile*
+newfid(int fid)
+{
+       Mlist *f, *ff;
+       Mfile *mf;
+
+       ff = 0;
+       for(f = mlist; f; f = f->next)
+               if(f->mf.busy && f->mf.fid == fid)
+                       return &f->mf;
+               else if(!ff && !f->mf.busy)
+                       ff = f;
+       if(ff == 0){
+               ff = emalloc(sizeof *f);
+               ff->next = mlist;
+               mlist = ff;
+       }
+       mf = &ff->mf;
+       memset(mf, 0, sizeof *mf);
+       mf->fid = fid;
+       return mf;
+}
+
+Job*
+newjob(void)
+{
+       Job *job;
+
+       job = calloc(1, sizeof(Job));
+       if (! job){
+               error(1, 0, "%s: %r","job calloc");
+       }
+#warning "fix lock"
+//     //lock(&joblock);
+       job->next = joblist;
+       joblist = job;
+       job->request.tag = -1;
+//     //unlock(&joblock);
+       return job;
+}
+
+void
+freejob(Job *job)
+{
+       Job **l;
+return;
+       //lock(&joblock);
+       for(l = &joblist; *l; l = &(*l)->next){
+               if((*l) == job){
+                       *l = job->next;
+                       free(job);
+                       break;
+               }
+       }
+       //unlock(&joblock);
+}
+
+void
+flushjob(int tag)
+{
+       Job *job;
+
+       //lock(&joblock);
+       for(job = joblist; job; job = job->next){
+               if(job->request.tag == tag && job->request.type != Tflush){
+                       job->flushed = 1;
+                       break;
+               }
+       }
+       //unlock(&joblock);
+}
+
+void *job_thread(void* arg)
+{
+       Mfile *mf;
+       Job *job = arg;
+       //lock(&dblock);
+printf("JOB!\n");
+       mf = newfid(job->request.fid);
+
+printf("NEWFID is %p\n", mf);
+       if(debug)
+               fprintf(stderr, "CS:%F", &job->request);
+printf("DO %d\n", job->request.type);
+       switch(job->request.type){
+       default:
+               fprintf(stderr, "CS:unknown request type %d", job->request.type);
+               break;
+       case Tversion:
+               rversion(job);
+               break;
+       case Tauth:
+               rauth(job);
+               break;
+       case Tflush:
+               rflush(job);
+               break;
+       case Tattach:
+               rattach(job, mf);
+               break;
+       case Twalk:
+               rwalk(job, mf);
+               break;
+       case Topen:
+               ropen(job, mf);
+               break;
+       case Tcreate:
+               rcreate(job, mf);
+               break;
+       case Tread:
+               rread(job, mf);
+               break;
+       case Twrite:
+               rwrite(job, mf);
+               break;
+       case Tclunk:
+               rclunk(job, mf);
+               break;
+       case Tremove:
+               rremove(job, mf);
+               break;
+       case Tstat:
+               rstat(job, mf);
+               break;
+       case Twstat:
+               rwstat(job, mf);
+               break;
+       }
+       //unlock(&dblock);
+
+       freejob(job);
+
+       fprintf(stderr, "CS:Job done\n");
+       return 0;
+}
+
+
+void
+io(void)
+{
+       long n;
+
+       uint8_t mdata[IOHDRSZ + Maxfdata];
+       Job *job;
+
+       /*
+        * each request is handled via a thread. Somewhat less efficient than the old
+        * cs but way cleaner. 
+        */
+
+       for(;;){
+               n = read9pmsg(mfd[0], mdata, sizeof mdata);
+               if(n<=0)
+                       error(1, 0, "%s: %r","mount read");
+               job = newjob();
+               if(convM2S(mdata, n, &job->request) != n){
+                       error(1, 0, "format error %ux %ux %ux %ux %ux",
+                               mdata[0], mdata[1], mdata[2], mdata[3], mdata[4]);
+                       freejob(job);
+                       continue;
+               }
+               /* stash the thread in the job so we can join them all
+                * later if we want to.
+                */
+#if 0
+printf("RUN THAT JOB!\n");
+               if (pthread_create(&job->thread, NULL, &job_thread, job)) {
+printf("ERROR!\n");
+                       error(1, 0, "%s: %r","Failed to create job");
+                       continue;
+               }
+#endif
+       job_thread(job);
+
+
+       }
+}
+
+void
+rversion(Job *job)
+{
+       if(job->request.msize > IOHDRSZ + Maxfdata)
+               job->reply.msize = IOHDRSZ + Maxfdata;
+       else
+               job->reply.msize = job->request.msize;
+       if(strncmp(job->request.version, "9P2000", 6) != 0)
+               sendmsg(job, "unknown 9P version");
+       else{
+               job->reply.version = "9P2000";
+               sendmsg(job, 0);
+       }
+}
+
+void
+rauth(Job *job)
+{
+       sendmsg(job, "cs: authentication not required");
+}
+
+/*
+ *  don't flush till all the threads  are done
+ */
+void
+rflush(Job *job)
+{
+       flushjob(job->request.oldtag);
+       sendmsg(job, 0);
+}
+
+void
+rattach(Job *job, Mfile *mf)
+{
+       if(mf->busy == 0){
+               mf->busy = 1;
+               mf->user = estrdup(job->request.uname);
+       }
+       mf->qid.vers = vers++;
+       mf->qid.type = QTDIR;
+       mf->qid.path = 0LL;
+       job->reply.qid = mf->qid;
+       sendmsg(job, 0);
+}
+
+
+char*
+rwalk(Job *job, Mfile *mf)
+{
+       char *err;
+       char **elems;
+       int nelems;
+       int i;
+       Mfile *nmf;
+       struct qid qid;
+
+       err = 0;
+       nmf = NULL;
+       elems = job->request.wname;
+       nelems = job->request.nwname;
+       job->reply.nwqid = 0;
+
+       if(job->request.newfid != job->request.fid){
+               /* clone fid */
+               nmf = newfid(job->request.newfid);
+               if(nmf->busy){
+                       nmf = NULL;
+                       err = "clone to used channel";
+                       goto send;
+               }
+               *nmf = *mf;
+               nmf->user = estrdup(mf->user);
+               nmf->fid = job->request.newfid;
+               nmf->qid.vers = vers++;
+               mf = nmf;
+       }
+       /* else nmf will be nil */
+
+       qid = mf->qid;
+       if(nelems > 0){
+               /* walk fid */
+               for(i=0; i<nelems && i<MAXWELEM; i++){
+printf("cs: i %d nelems %d \n", i, nelems);
+                       if((qid.type & QTDIR) == 0){
+                               err = "not a directory";
+                               break;
+                       }
+                       if(strcmp(elems[i], "..") == 0 || strcmp(elems[i], ".") == 0){
+                               qid.type = QTDIR;
+                               qid.path = Qdir;
+    Found:
+                               job->reply.wqid[i] = qid;
+                               job->reply.nwqid++;
+                               continue;
+                       }
+                       if(strcmp(elems[i], "cs") == 0){
+                               qid.type = QTFILE;
+                               qid.path = Qcs;
+printf("found cs, goto Found\n");
+                               goto Found;
+                       }
+                       err = malloc(4096);
+                       snprintf(err, 4096,"%s:file does not exist", elems[i]);
+                       break;
+               }
+       }
+
+    send:
+       if(nmf != NULL && (err!=NULL || job->reply.nwqid<nelems)){
+               cleanmf(nmf);
+               free(nmf->user);
+               nmf->user = 0;
+               nmf->busy = 0;
+               nmf->fid = 0;
+       }
+       if(err == NULL)
+               mf->qid = qid;
+       sendmsg(job, err);
+       free(err);
+       /* shoot me. */
+       /* we can fix this or we can get a real language. Guess how Ron votes? */
+       err = "file does not exist";
+       return err;
+}
+
+void
+ropen(Job *job, Mfile *mf)
+{
+       int mode;
+       char *err;
+
+       err = 0;
+       mode = job->request.mode;
+       if(mf->qid.type & QTDIR){
+               if(mode)
+                       err = "permission denied";
+       }
+       job->reply.qid = mf->qid;
+       job->reply.iounit = 0;
+       sendmsg(job, err);
+}
+
+void
+rcreate(Job *job, Mfile *mf)
+{
+       sendmsg(job, "creation permission denied");
+}
+
+void
+rread(Job *job, Mfile *mf)
+{
+       int i, n, cnt;
+       long off, toff, clock;
+       struct dir dir;
+       uint8_t buf[Maxfdata];
+       char *err;
+
+       n = 0;
+       err = 0;
+       off = job->request.offset;
+       cnt = job->request.count;
+       if(mf->qid.type & QTDIR){
+               //      clock = time(0);
+               if(off == 0){
+                       memset(&dir, 0, sizeof dir);
+                       dir.name = "cs";
+                       dir.qid.type = QTFILE;
+                       dir.qid.vers = vers;
+                       dir.qid.path = Qcs;
+                       dir.mode = 0666;
+                       dir.length = 0;
+                       dir.uid = mf->user;
+                       dir.gid = mf->user;
+                       dir.muid = mf->user;
+                       dir.atime = clock;      /* wrong */
+                       dir.mtime = clock;      /* wrong */
+                       n = convD2M(&dir, buf, sizeof buf);
+               }
+               job->reply.data = (char*)buf;
+       } else {
+               for(;;){
+                       /* look for an answer at the right offset */
+                       toff = 0;
+                       for(i = 0; mf->reply[i] && i < mf->nreply; i++){
+                               n = mf->replylen[i];
+                               if(off < toff + n)
+                                       break;
+                               toff += n;
+                       }
+                       if(i < mf->nreply)
+                               break;          /* got something to return */
+
+                       /* try looking up more answers */
+                       if(lookup(mf) == 0){
+                               /* no more */
+                               n = 0;
+                               goto send;
+                       }
+               }
+
+               /* give back a single reply (or part of one) */
+               job->reply.data = mf->reply[i] + (off - toff);
+               if(cnt > toff - off + n)
+                       n = toff - off + n;
+               else
+                       n = cnt;
+       }
+send:
+       job->reply.count = n;
+       sendmsg(job, err);
+}
+void
+cleanmf(Mfile *mf)
+{
+       int i;
+
+       if(mf->net != NULL){
+               free(mf->net);
+               mf->net = NULL;
+       }
+       if(mf->host != NULL){
+               free(mf->host);
+               mf->host = NULL;
+       }
+       if(mf->serv != NULL){
+               free(mf->serv);
+               mf->serv = NULL;
+       }
+       if(mf->rem != NULL){
+               free(mf->rem);
+               mf->rem = NULL;
+       }
+       for(i = 0; i < mf->nreply; i++){
+               free(mf->reply[i]);
+               mf->reply[i] = NULL;
+               mf->replylen[i] = 0;
+       }
+       mf->nreply = 0;
+       mf->nextnet = netlist;
+}
+
+void
+rwrite(Job *job, Mfile *mf)
+{
+       int cnt, n;
+       char *err;
+       char *field[4];
+       char curerr[64];
+
+       err = 0;
+       cnt = job->request.count;
+       if(mf->qid.type & QTDIR){
+               err = "can't write directory";
+               goto send;
+       }
+       if(cnt >= Maxrequest){
+               err = "request too long";
+               goto send;
+       }
+       job->request.data[cnt] = 0;
+printf("CS: request data is :%s:\n", job->request.data);
+       /*
+        *  toggle debugging
+        */
+       if(strncmp(job->request.data, "debug", 5)==0){
+               debug ^= 1;
+               fprintf(stderr, "CS:debug %d", debug);
+               goto send;
+       }
+
+       /*
+        *  toggle ipv6 lookups
+        */
+       if(strncmp(job->request.data, "ipv6", 4)==0){
+               ipv6lookups ^= 1;
+               fprintf(stderr, "CS:ipv6lookups %d", ipv6lookups);
+               goto send;
+       }
+
+       /*
+        *  toggle debugging
+        */
+       if(strncmp(job->request.data, "paranoia", 8)==0){
+               paranoia ^= 1;
+               fprintf(stderr,  "CS:paranoia %d", paranoia);
+               goto send;
+       }
+
+       /*
+        *  add networks to the default list
+        */
+       if(strncmp(job->request.data, "add ", 4)==0){
+               if(job->request.data[cnt-1] == '\n')
+                       job->request.data[cnt-1] = 0;
+               netadd(job->request.data+4);
+               readipinterfaces();
+               goto send;
+       }
+
+       /*
+        *  refresh all state
+        */
+       if(strncmp(job->request.data, "refresh", 7)==0){
+               netinit(1);
+               goto send;
+       }
+
+       /* start transaction with a clean slate */
+       cleanmf(mf);
+
+       /*
+        *  look for a general query
+        */
+       if(*job->request.data == '!'){
+               err = genquery(mf, job->request.data+1);
+               goto send;
+       }
+
+       if(debug)
+               fprintf(stderr,  "CS:write %s", job->request.data);
+       if(paranoia)
+               fprintf(stderr,  "CS:write %s by %s", job->request.data, mf->user);
+
+       /*
+        *  break up name
+        */
+       n = getfields(job->request.data, field, 4, 1, "!");
+       switch(n){
+       case 1:
+               mf->net = strdup("net");
+               mf->host = strdup(field[0]);
+               break;
+       case 4:
+               mf->rem = strdup(field[3]);
+               /* fall through */
+       case 3:
+               mf->serv = strdup(field[2]);
+               /* fall through */
+       case 2:
+               mf->host = strdup(field[1]);
+               mf->net = strdup(field[0]);
+               break;
+       }
+
+       /*
+        *  do the first net worth of lookup
+        */
+       if(lookup(mf) == 0){
+               snprintf(curerr, sizeof curerr, "%r");
+               err = curerr;
+       }
+send:
+       job->reply.count = cnt;
+       sendmsg(job, err);
+}
+
+void
+rclunk(Job *job, Mfile *mf)
+{
+       cleanmf(mf);
+       free(mf->user);
+       mf->user = 0;
+       mf->busy = 0;
+       mf->fid = 0;
+       sendmsg(job, 0);
+}
+
+void
+rremove(Job *job, Mfile *mf)
+{
+       sendmsg(job, "remove permission denied");
+}
+
+void
+rstat(Job *job, Mfile *mf)
+{
+       struct dir dir;
+       uint8_t buf[IOHDRSZ+Maxfdata];
+
+       memset(&dir, 0, sizeof dir);
+       if(mf->qid.type & QTDIR){
+               dir.name = ".";
+               dir.mode = DMDIR|0555;
+       } else {
+               dir.name = "cs";
+               dir.mode = 0666;
+       }
+       dir.qid = mf->qid;
+       dir.length = 0;
+       dir.uid = mf->user;
+       dir.gid = mf->user;
+       dir.muid = mf->user;
+       //dir.atime = dir.mtime = time(0);
+       job->reply.nstat = convD2M(&dir, buf, sizeof buf);
+       job->reply.stat = buf;
+       sendmsg(job, 0);
+}
+
+void
+rwstat(Job *job, Mfile *mf)
+{
+       sendmsg(job, "wstat permission denied");
+}
+
+void
+sendmsg(Job *job, char *err)
+{
+       int n;
+       uint8_t mdata[IOHDRSZ + Maxfdata];
+       char ename[ERRMAX];
+
+       if(err){
+               job->reply.type = Rerror;
+               snprintf(ename, sizeof(ename), "cs: %s", err);
+               job->reply.ename = ename;
+       }else{
+               job->reply.type = job->request.type+1;
+       }
+       job->reply.tag = job->request.tag;
+       n = convS2M(&job->reply, mdata, sizeof mdata);
+       if(n == 0){
+               fprintf(stderr,  "CS:sendmsg convS2M of %F returns 0", &job->reply);
+               abort();
+       }
+       //lock(&joblock);
+       if(job->flushed == 0)
+               if(write(mfd[1], mdata, n)!=n)
+                       error(1, 0, "%s: %r","mount write");
+       //unlock(&joblock);
+       if(debug)
+               fprintf(stderr,  "CS:%F %d", &job->reply, n);
+}
+
+static int
+isvalidip(uint8_t *ip)
+{
+       return ipcmp(ip, IPnoaddr) != 0 && ipcmp(ip, v4prefix) != 0;
+}
+
+static uint8_t loopbacknet[IPaddrlen] = {
+       0, 0, 0, 0,
+       0, 0, 0, 0,
+       0, 0, 0xff, 0xff,
+       127, 0, 0, 0
+};
+static uint8_t loopbackmask[IPaddrlen] = {
+       0xff, 0xff, 0xff, 0xff,
+       0xff, 0xff, 0xff, 0xff,
+       0xff, 0xff, 0xff, 0xff,
+       0xff, 0, 0, 0
+};
+
+void
+readipinterfaces(void)
+{
+       if(myipaddr(ipa, mntpt) != 0)
+               ipmove(ipa, IPnoaddr);
+       snprintf(ipaddr, sizeof(ipaddr), "%I", ipa);
+       if (debug)
+               fprintf(stderr, "CS:dns", "ipaddr is %s\n", ipaddr);
+}
+
+/*
+ *  get the system name
+ */
+void
+ipid(void)
+{
+       uint8_t addr[6];
+       struct ndbtuple *t, *tt;
+       char *p, *attr;
+       struct ndbs s;
+       int f;
+       char buf[Maxpath];
+
+       /* use environment, ether addr, or ipaddr to get system name */
+       if(mysysname == 0){
+               /*
+                *  environment has priority.
+                *
+                *  on the sgi power the default system name
+                *  is the ip address.  ignore that.
+                *
+                */
+               p = getenv("sysname");
+               if(p && *p){
+                       attr = ipattr(p);
+                       if(strcmp(attr, "ip") != 0)
+                               mysysname = strdup(p);
+               }
+
+               /*
+                *  the /net/ndb contains what the network
+                *  figured out from DHCP.  use that name if
+                *  there is one.
+                */
+               if(mysysname == 0 && netdb != NULL){
+                       ndbreopen(netdb);
+                       for(tt = t = ndbparse(netdb); t != NULL; t = t->entry){
+                               if(strcmp(t->attr, "sys") == 0){
+                                       mysysname = strdup(t->val);
+                                       break;
+                               }
+                       }
+                       ndbfree(tt);
+               }
+
+               /* next network database, ip address, and ether address to find a name */
+               if(mysysname == 0){
+                       t = NULL;
+                       if(isvalidip(ipa))
+                               free(ndbgetvalue(db, &s, "ip", ipaddr, "sys", &t));
+                       if(t == NULL){
+                               for(f = 0; f < 3; f++){
+                                       snprintf(buf, sizeof buf, "%s/ether%d", mntpt, f);
+                                       if(myetheraddr(addr, buf) >= 0){
+                                               snprintf(eaddr, sizeof(eaddr), "%E", addr);
+                                               free(ndbgetvalue(db, &s, "ether", eaddr, "sys", &t));
+                                               if(t != NULL)
+                                                       break;
+                                       }
+                               }
+                       }
+                       for(tt = t; tt != NULL; tt = tt->entry){
+                               if(strcmp(tt->attr, "sys") == 0){
+                                       mysysname = strdup(tt->val);
+                                       break;
+                               }
+                       }
+                       ndbfree(t);
+               }
+
+               /* nothing else worked, use the ip address */
+               if(mysysname == 0 && isvalidip(ipa))
+                       mysysname = strdup(ipaddr);
+
+
+               /* set /dev/sysname if we now know it */
+               if(mysysname){
+                       f = open("/dev/sysname", O_RDWR);
+                       if(f >= 0){
+                               write(f, mysysname, strlen(mysysname));
+                               close(f);
+                       }
+               }
+       }
+}
+
+/*
+ *  Set up a list of default networks by looking for
+ *  /net/^*^/clone.
+ *  For now, never background.
+ */
+void
+netinit(int background)
+{
+       char clone[Maxpath];
+       Network *np;
+       static int working;
+
+       /* add the mounted networks to the default list */
+       for(np = network; np->net; np++){
+               if(np->considered)
+                       continue;
+               snprintf(clone, sizeof(clone), "%s/%s/clone", mntpt, np->net);
+               if(access(clone, R_OK))
+                       continue;
+               if(netlist)
+                       last->next = np;
+               else
+                       netlist = np;
+               last = np;
+               np->next = 0;
+               np->considered = 1;
+       }
+
+       /* find out what our ip address is */
+       readipinterfaces();
+
+       /* set the system name if we need to, these days ip is all we have */
+       ipid();
+
+       if(debug)
+               fprintf(stderr, logfile, "CS:mysysname %s eaddr %s ipaddr %s ipa %I\n",
+                       mysysname?mysysname:"???", eaddr, ipaddr, ipa);
+
+}
+
+/*
+ *  add networks to the standard list
+ */
+void
+netadd(char *p)
+{
+       Network *np;
+       char *field[12];
+       int i, n;
+
+       n = getfields(p, field, 12, 1, " ");
+       for(i = 0; i < n; i++){
+               for(np = network; np->net; np++){
+                       if(strcmp(field[i], np->net) != 0)
+                               continue;
+                       if(np->considered)
+                               break;
+                       if(netlist)
+                               last->next = np;
+                       else
+                               netlist = np;
+                       last = np;
+                       np->next = 0;
+                       np->considered = 1;
+               }
+       }
+}
+
+int
+lookforproto(struct ndbtuple *t, char *proto)
+{
+       for(; t != NULL; t = t->entry)
+               if(strcmp(t->attr, "proto") == 0 && strcmp(t->val, proto) == 0)
+                       return 1;
+       return 0;
+}
+
+/*
+ *  lookup a request.  the network "net" means we should pick the
+ *  best network to get there.
+ */
+int
+lookup(Mfile *mf)
+{
+       Network *np;
+       char *cp;
+       struct ndbtuple *nt, *t;
+       char reply[Maxreply];
+       int i, rv;
+       int hack;
+
+       /* open up the standard db files */
+       if(db == 0)
+               ndbinit();
+       if(db == 0)
+               error(1, 0, "%s: %r","can't open mf->network database\n");
+
+       rv = 0;
+
+       if(mf->net == NULL)
+               return 0;       /* must have been a genquery */
+
+       if(strcmp(mf->net, "net") == 0){
+               /*
+                *  go through set of default nets
+                */
+               for(np = mf->nextnet; np; np = np->next){
+                       nt = (*np->lookup)(np, mf->host, mf->serv, 1);
+                       if(nt == NULL)
+                               continue;
+                       hack = np->fasttimeouthack && !lookforproto(nt, np->net);
+                       for(t = nt; mf->nreply < Nreply && t; t = t->entry){
+                               cp = (*np->trans)(t, np, mf->serv, mf->rem, hack);
+                               if(cp){
+                                       /* avoid duplicates */
+                                       for(i = 0; i < mf->nreply; i++)
+                                               if(strcmp(mf->reply[i], cp) == 0)
+                                                       break;
+                                       if(i == mf->nreply){
+                                               /* save the reply */
+                                               mf->replylen[mf->nreply] = strlen(cp);
+                                               mf->reply[mf->nreply++] = cp;
+                                               rv++;
+                                       }
+                               }
+                       }
+                       ndbfree(nt);
+                       np = np->next;
+                       break;
+               }
+               mf->nextnet = np;
+               return rv;
+       }
+
+       /*
+        *  if not /net, we only get one lookup
+        */
+       if(mf->nreply != 0)
+               return 0;
+       /*
+        *  look for a specific network
+        */
+       for(np = netlist; np && np->net != NULL; np++){
+               if(np->fasttimeouthack)
+                       continue;
+               if(strcmp(np->net, mf->net) == 0)
+                       break;
+       }
+
+       if(np && np->net != NULL){
+               /*
+                *  known network
+                */
+               nt = (*np->lookup)(np, mf->host, mf->serv, 1);
+               for(t = nt; mf->nreply < Nreply && t; t = t->entry){
+                       cp = (*np->trans)(t, np, mf->serv, mf->rem, 0);
+                       if(cp){
+                               mf->replylen[mf->nreply] = strlen(cp);
+                               mf->reply[mf->nreply++] = cp;
+                               rv++;
+                       }
+               }
+               ndbfree(nt);
+               return rv;
+       } else {
+               /*
+                *  not a known network, don't translate host or service
+                */
+               if(mf->serv)
+                       snprintf(reply, sizeof(reply), "%s/%s/clone %s!%s",
+                               mntpt, mf->net, mf->host, mf->serv);
+               else
+                       snprintf(reply, sizeof(reply), "%s/%s/clone %s",
+                               mntpt, mf->net, mf->host);
+               mf->reply[0] = strdup(reply);
+               mf->replylen[0] = strlen(reply);
+               mf->nreply = 1;
+               return 1;
+       }
+}
+
+/*
+ *  translate an ip service name into a port number.  If it's a numeric port
+ *  number, look for restricted access.
+ *
+ *  the service '*' needs no translation.
+ */
+char*
+ipserv(Network *np, char *name, char *buf, int blen)
+{
+       char *p;
+       int alpha = 0;
+       int restr = 0;
+       char port[10];
+       struct ndbtuple *t, *nt;
+       struct ndbs s;
+
+       /* '*' means any service */
+       if(strcmp(name, "*")==0){
+               strcpy(buf, name);
+               return buf;
+       }
+
+       /*  see if it's numeric or symbolic */
+       port[0] = 0;
+       for(p = name; *p; p++){
+               if(isdigit(*p))
+                       {}
+               else if(isalpha(*p) || *p == '-' || *p == '$')
+                       alpha = 1;
+               else
+                       return 0;
+       }
+       t = NULL;
+       p = NULL;
+       if(alpha){
+               p = ndbgetvalue(db, &s, np->net, name, "port", &t);
+               if(p == NULL)
+                       return 0;
+       } else {
+               /* look up only for tcp ports < 1024 to get the restricted
+                * attribute
+                */
+               if(atoi(name) < 1024 && strcmp(np->net, "tcp") == 0)
+                       p = ndbgetvalue(db, &s, "port", name, "port", &t);
+               if(p == NULL)
+                       p = strdup(name);
+       }
+
+       if(t){
+               for(nt = t; nt; nt = nt->entry)
+                       if(strcmp(nt->attr, "restricted") == 0)
+                               restr = 1;
+               ndbfree(t);
+       }
+       snprintf(buf, blen, "%s%s", p, restr ? "!r" : "");
+       free(p);
+
+       return buf;
+}
+
+/*
+ *  lookup an ip attribute
+ */
+int
+ipattrlookup(struct ndb *db, char *ipa, char *attr, char *val, int vlen)
+{
+
+       struct ndbtuple *t, *nt;
+       char *alist[2];
+
+       alist[0] = attr;
+       t = ndbipinfo(db, "ip", ipa, alist, 1);
+       if(t == NULL)
+               return 0;
+       for(nt = t; nt != NULL; nt = nt->entry){
+               if(strcmp(nt->attr, attr) == 0){
+                       nstrcpy(val, nt->val, vlen);
+                       ndbfree(t);
+                       return 1;
+               }
+       }
+
+       /* we shouldn't get here */
+       ndbfree(t);
+       return 0;
+}
+
+/*
+ *  lookup (and translate) an ip destination
+ */
+struct ndbtuple*
+iplookup(Network *np, char *host, char *serv, int nolookup)
+{
+       char *attr, *dnsname;
+       struct ndbtuple *t, *nt;
+       struct ndbs s;
+       char ts[Maxservice];
+       char dollar[Maxhost];
+       uint8_t ip[IPaddrlen];
+       uint8_t net[IPaddrlen];
+       uint8_t tnet[IPaddrlen];
+       struct ipifc *ifc;
+       struct iplifc *lifc;
+
+       /*
+        *  start with the service since it's the most likely to fail
+        *  and costs the least
+        */
+       werrstr("can't translate address");
+       if(serv==0 || ipserv(np, serv, ts, sizeof ts) == 0){
+               werrstr("can't translate service");
+               return 0;
+       }
+
+       /* for dial strings with no host */
+       if(strcmp(host, "*") == 0)
+               return ndbnew("ip", "*");
+
+       /*
+        *  hack till we go v6 :: = 0.0.0.0
+        */
+       if(strcmp("::", host) == 0)
+               return ndbnew("ip", "*");
+
+       /*
+        *  '$' means the rest of the name is an attribute that we
+        *  need to search for
+        */
+       if(*host == '$'){
+               if(ipattrlookup(db, ipaddr, host+1, dollar, sizeof dollar))
+                       host = dollar;
+       }
+
+       /*
+        *  turn '[ip address]' into just 'ip address'
+        */
+       if(*host == '[' && host[strlen(host)-1] == ']'){
+               host++;
+               host[strlen(host)-1] = 0;
+       }
+
+       /*
+        *  just accept addresses
+        */
+       attr = ipattr(host);
+       if(strcmp(attr, "ip") == 0)
+               return ndbnew("ip", host);
+
+       /*
+        *  give the domain name server the first opportunity to
+        *  resolve domain names.  if that fails try the database.
+        */
+       t = 0;
+       werrstr("can't translate address");
+       if(strcmp(attr, "dom") == 0)
+               t = dnsiplookup(host, &s);
+       if(t == 0)
+               free(ndbgetvalue(db, &s, attr, host, "ip", &t));
+       if(t == 0){
+               dnsname = ndbgetvalue(db, &s, attr, host, "dom", NULL);
+               if(dnsname){
+                       t = dnsiplookup(dnsname, &s);
+                       free(dnsname);
+               }
+       }
+       if(t == 0)
+               t = dnsiplookup(host, &s);
+       if(t == 0)
+               return 0;
+
+       /*
+        *  reorder the tuple to have the matched line first and
+        *  save that in the request structure.
+        */
+       t = reorder(t, s.t);
+
+       /*
+        * reorder according to our interfaces
+        */
+       //lock(&ipifclock);
+       for(ifc = ipifcs; ifc != NULL; ifc = ifc->next){
+               for(lifc = ifc->lifc; lifc != NULL; lifc = lifc->next){
+                       maskip(lifc->ip, lifc->mask, net);
+                       for(nt = t; nt; nt = nt->entry){
+                               if(strcmp(nt->attr, "ip") != 0)
+                                       continue;
+                               parseip(ip, nt->val);
+                               maskip(ip, lifc->mask, tnet);
+                               if(memcmp(net, tnet, IPaddrlen) == 0){
+                                       t = reorder(t, nt);
+                                       //unlock(&ipifclock);
+                                       return t;
+                               }
+                       }
+               }
+       }
+       //unlock(&ipifclock);
+
+       return t;
+}
+
+/*
+ *  translate an ip address
+ */
+char*
+iptrans(struct ndbtuple *t, Network *np, char *serv, char *rem, int hack)
+{
+       char ts[Maxservice];
+       char reply[Maxreply];
+       char x[Maxservice];
+
+       if(strcmp(t->attr, "ip") != 0)
+               return 0;
+
+       if(serv == 0 || ipserv(np, serv, ts, sizeof ts) == 0){
+               werrstr("can't translate service");
+               return 0;
+       }
+       if(rem != NULL)
+               snprintf(x, sizeof(x), "!%s", rem);
+       else
+               *x = 0;
+
+       if(*t->val == '*')
+               snprintf(reply, sizeof(reply), "%s/%s/clone %s%s",
+                       mntpt, np->net, ts, x);
+       else
+               snprintf(reply, sizeof(reply), "%s/%s/clone %s!%s%s%s",
+                       mntpt, np->net, t->val, ts, x, hack? "!fasttimeout": "");
+
+       return strdup(reply);
+}
+
+/*
+ *  lookup a telephone number
+ */
+struct ndbtuple*
+telcolookup(Network *np, char *host, char *serv, int nolookup)
+{
+       struct ndbtuple *t;
+       struct ndbs s;
+
+       werrstr("can't translate address");
+       free(ndbgetvalue(db, &s, "sys", host, "telco", &t));
+       if(t == 0)
+               return ndbnew("telco", host);
+
+       return reorder(t, s.t);
+}
+
+/*
+ *  translate a telephone address
+ */
+char*
+telcotrans(struct ndbtuple *t, Network *np, char *serv, char *rem, int unused)
+{
+       char reply[Maxreply];
+       char x[Maxservice];
+
+       if(strcmp(t->attr, "telco") != 0)
+               return 0;
+
+       if(rem != NULL)
+               snprintf(x, sizeof(x), "!%s", rem);
+       else
+               *x = 0;
+       if(serv)
+               snprintf(reply, sizeof(reply), "%s/%s/clone %s!%s%s", mntpt, np->net,
+                       t->val, serv, x);
+       else
+               snprintf(reply, sizeof(reply), "%s/%s/clone %s%s", mntpt, np->net,
+                       t->val, x);
+       return strdup(reply);
+}
+
+/*
+ *  reorder the tuple to put x's line first in the entry
+ */
+struct ndbtuple*
+reorder(struct ndbtuple *t, struct ndbtuple *x)
+{
+       struct ndbtuple *nt;
+       struct ndbtuple *line;
+
+       /* find start of this entry's line */
+       for(line = x; line->entry == line->line; line = line->line)
+               ;
+       line = line->line;
+       if(line == t)
+               return t;       /* already the first line */
+
+       /* remove this line and everything after it from the entry */
+       for(nt = t; nt->entry != line; nt = nt->entry)
+               ;
+       nt->entry = 0;
+
+       /* make that the start of the entry */
+       for(nt = line; nt->entry; nt = nt->entry)
+               ;
+       nt->entry = t;
+       return line;
+}
+
+static struct ndbtuple*
+dnsip6lookup(char *mntpt, char *buf, struct ndbtuple *t)
+{
+       struct ndbtuple *t6, *tt;
+
+       t6 = dnsquery(mntpt, buf, "ipv6");      /* lookup AAAA dns RRs */
+       if (t6 == NULL)
+               return t;
+
+       /* convert ipv6 attr to ip */
+       for (tt = t6; tt != NULL; tt = tt->entry)
+               if (strcmp(tt->attr, "ipv6") == 0)
+                       strncpy(tt->attr, "ip", sizeof tt->attr - 1);
+
+       if (t == NULL)
+               return t6;
+
+       /* append t6 list to t list */
+       for (tt = t; tt->entry != NULL; tt = tt->entry)
+               ;
+       tt->entry = t6;
+       return t;
+}
+
+/*
+ *  call the dns process and have it try to translate a name
+ */
+struct ndbtuple*
+dnsiplookup(char *host, struct ndbs *s)
+{
+       char buf[Maxreply];
+       struct ndbtuple *t;
+
+       //unlock(&dblock);
+
+       /* save the name */
+       snprintf(buf, sizeof(buf), "%s", host);
+
+       if(strcmp(ipattr(buf), "ip") == 0)
+               t = dnsquery(mntpt, buf, "ptr");
+       else {
+               t = dnsquery(mntpt, buf, "ip");
+               /* special case: query ipv6 (AAAA dns RR) too */
+               if (ipv6lookups)
+                       t = dnsip6lookup(mntpt, buf, t);
+       }
+       s->t = t;
+
+       if(t == NULL){
+               snprintf(buf, sizeof buf, "%r");
+               if(strstr(buf, "exist"))
+                       werrstr("can't translate address: %s", buf);
+               else if(strstr(buf, "dns failure"))
+                       werrstr("temporary problem: %s", buf);
+       }
+
+       //lock(&dblock);
+       return t;
+}
+
+int
+qmatch(struct ndbtuple *t, char **attr, char **val, int n)
+{
+       int i, found;
+       struct ndbtuple *nt;
+
+       for(i = 1; i < n; i++){
+               found = 0;
+               for(nt = t; nt; nt = nt->entry)
+                       if(strcmp(attr[i], nt->attr) == 0)
+                               if(strcmp(val[i], "*") == 0
+                               || strcmp(val[i], nt->val) == 0){
+                                       found = 1;
+                                       break;
+                               }
+               if(found == 0)
+                       break;
+       }
+       return i == n;
+}
+
+/* this is awful but I don't want to bring in libstring just for this.
+ * you want real strings don't use C
+ */
+void
+qreply(Mfile *mf, struct ndbtuple *t)
+{
+       struct ndbtuple *nt;
+       char *s, *cur;
+       int len, amt;
+
+       s = malloc(4096);
+       cur = s;
+       len = 4096;
+
+       for(nt = t; mf->nreply < Nreply && nt; nt = nt->entry){
+               amt = snprintf(cur, len, "%s=%s", nt->attr, nt->val);
+
+               if (amt < 0)
+                       len = 0;
+               else {
+                       len -= amt;
+                       cur += len;
+               }
+
+               if(nt->line != nt->entry){
+                       mf->replylen[mf->nreply] = strlen(s);
+                       mf->reply[mf->nreply++] = strdup(s);
+                       cur = s;
+                       len = 4096;
+               } else {
+                       amt = snprintf(cur, len, " ");
+                       if (amt < 0)
+                               len = 0;
+                       else {
+                               len -= amt;
+                               cur += len;
+                       }
+               }
+
+       }
+       free(s);
+}
+
+enum
+{
+       Maxattr=        32,
+};
+
+/*
+ *  generic query lookup.  The query is of one of the following
+ *  forms:
+ *
+ *  attr1=val1 attr2=val2 attr3=val3 ...
+ *
+ *  returns the matching tuple
+ *
+ *  ipinfo attr=val attr1 attr2 attr3 ...
+ *
+ *  is like ipinfo and returns the attr{1-n}
+ *  associated with the ip address.
+ */
+char*
+genquery(Mfile *mf, char *query)
+{
+       int i, n;
+       char *p;
+       char *attr[Maxattr];
+       char *val[Maxattr];
+       struct ndbtuple *t;
+       struct ndbs s;
+
+       n = getfields(query, attr, ARRAY_SIZE(attr), 1, " ");
+       if(n == 0)
+               return "bad query";
+
+       if(strcmp(attr[0], "ipinfo") == 0)
+               return ipinfoquery(mf, attr, n);
+
+       /* parse pairs */
+       for(i = 0; i < n; i++){
+               p = strchr(attr[i], '=');
+               if(p == 0)
+                       return "bad query";
+               *p++ = 0;
+               val[i] = p;
+       }
+
+       /* give dns a chance */
+       if((strcmp(attr[0], "dom") == 0 || strcmp(attr[0], "ip") == 0) && val[0]){
+               t = dnsiplookup(val[0], &s);
+               if(t){
+                       if(qmatch(t, attr, val, n)){
+                               qreply(mf, t);
+                               ndbfree(t);
+                               return 0;
+                       }
+                       ndbfree(t);
+               }
+       }
+
+       /* first pair is always the key.  It can't be a '*' */
+       t = ndbsearch(db, &s, attr[0], val[0]);
+
+       /* search is the and of all the pairs */
+       while(t){
+               if(qmatch(t, attr, val, n)){
+                       qreply(mf, t);
+                       ndbfree(t);
+                       return 0;
+               }
+
+               ndbfree(t);
+               t = ndbsnext(&s, attr[0], val[0]);
+       }
+
+       return "no match";
+}
+
+/*
+ *  resolve an ip address
+ */
+static struct ndbtuple*
+ipresolve(char *attr, char *host)
+{
+       struct ndbtuple *t, *nt, **l;
+
+       t = iplookup(&network[Ntcp], host, "*", 0);
+       for(l = &t; *l != NULL; ){
+               nt = *l;
+               if(strcmp(nt->attr, "ip") != 0){
+                       *l = nt->entry;
+                       nt->entry = NULL;
+                       ndbfree(nt);
+                       continue;
+               }
+               strcpy(nt->attr, attr);
+               l = &nt->entry;
+       }
+       return t;
+}
+
+char*
+ipinfoquery(Mfile *mf, char **list, int n)
+{
+       int i, nresolve;
+       int resolve[Maxattr];
+       struct ndbtuple *t, *nt, **l;
+       char *attr, *val;
+
+       /* skip 'ipinfo' */
+       list++; n--;
+
+       if(n < 1)
+               return "bad query";
+
+       /* get search attribute=value, or assume ip=myipaddr */
+       attr = *list;
+       if((val = strchr(attr, '=')) != NULL){
+               *val++ = 0;
+               list++;
+               n--;
+       }else{
+               attr = "ip";
+               val = ipaddr;
+       }
+
+       if(n < 1)
+               return "bad query";
+
+       /*
+        *  don't let ndbipinfo resolve the addresses, we're
+        *  better at it.
+        */
+       nresolve = 0;
+       for(i = 0; i < n; i++)
+               if(*list[i] == '@'){            /* @attr=val ? */
+                       list[i]++;
+                       resolve[i] = 1;         /* we'll resolve it */
+                       nresolve++;
+               } else
+                       resolve[i] = 0;
+
+       t = ndbipinfo(db, attr, val, list, n);
+       if(t == NULL)
+               return "no match";
+
+       if(nresolve != 0){
+               for(l = &t; *l != NULL;){
+                       nt = *l;
+
+                       /* already an address? */
+                       if(strcmp(ipattr(nt->val), "ip") == 0){
+                               l = &(*l)->entry;
+                               continue;
+                       }
+
+                       /* user wants it resolved? */
+                       for(i = 0; i < n; i++)
+                               if(strcmp(list[i], nt->attr) == 0)
+                                       break;
+                       if(i >= n || resolve[i] == 0){
+                               l = &(*l)->entry;
+                               continue;
+                       }
+
+                       /* resolve address and replace entry */
+                       *l = ipresolve(nt->attr, nt->val);
+                       while(*l != NULL)
+                               l = &(*l)->entry;
+                       *l = nt->entry;
+
+                       nt->entry = NULL;
+                       ndbfree(nt);
+               }
+       }
+
+       /* make it all one line */
+       for(nt = t; nt != NULL; nt = nt->entry){
+               if(nt->entry == NULL)
+                       nt->line = t;
+               else
+                       nt->line = nt->entry;
+       }
+
+       qreply(mf, t);
+
+       return NULL;
+}
+
+void*
+emalloc(int size)
+{
+       void *x;
+
+       x = calloc(size, 1);
+       if(x == NULL)
+               abort();
+       memset(x, 0, size);
+       return x;
+}
+
+char*
+estrdup(char *s)
+{
+       int size;
+       char *p;
+
+       size = strlen(s)+1;
+       p = calloc(size, 1);
+       if(p == NULL)
+               abort();
+       memmove(p, s, size);
+       return p;
+}
diff --git a/tests/csquery.c b/tests/csquery.c
new file mode 100644 (file)
index 0000000..283007a
--- /dev/null
@@ -0,0 +1,114 @@
+/* 
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+#include <stdlib.h>
+
+#include <stdio.h>
+#include <parlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <error.h>
+#include <iplib.h>
+#include <ndb.h>
+#include <fcall.h>
+
+char *server;
+char *status;
+int statusonly;
+
+void usage(void)
+{
+       fprintf(stderr, "CSQUERY:usage: ndb/csquery [/net/cs [addr...]]\n");
+       fprintf(stderr, "CSQUERY:usage");
+       exit(1);
+}
+
+void query(char *addr)
+{
+       char buf[128];
+       int fd, n;
+       int amt;
+
+       printf("CSQUERY:Open %s\n", server);
+       fd = open(server, O_RDWR);
+       if (fd < 0)
+               error(1, 0, "cannot open %s: %r", server);
+printf("CSQUERY:ask %d about :%s:\n", fd, addr);
+       amt = write(fd, addr, strlen(addr));
+       if (amt != strlen(addr)) {
+               printf("CSQUERY:Tried to write %d to fd %d, only wrote %d\n", strlen(addr),fd,amt);
+               if (!statusonly)
+                       fprintf(stderr, "CSQUERY:Writing request: translating %s: %r\n", addr);
+               status = "errors";
+               close(fd);
+               return;
+       }
+       if (!statusonly) {
+printf("CSQUERY:lseek\n");
+               lseek(fd, 0, 0);
+printf("CSQUERY:now read\n");
+               while ((n = read(fd, buf, sizeof(buf) - 1)) > 0) {
+printf("CSQUERY:got %d bytes\n", n);
+                       buf[n] = 0;
+                       printf("CSQUERY:%s\n", buf);
+               }
+printf("CSQUERY:done reading ... %s\n", buf);
+       }
+printf("CSQUERY:close and return\n");
+       close(fd);
+}
+
+void main(int argc, char **argv)
+{
+       char p[512];
+       int i;
+
+       argc--, argv++;
+       while (argc) {
+               if (argv[0][0] != '-')
+                       break;
+               switch (argv[0][1]) {
+                       case 's':
+                               statusonly = 1;
+                               break;
+                       default:
+                               usage();
+               }
+               argc--, argv++;
+       }
+
+       if (argc > 0)
+               server = argv[0];
+       else
+               server = "/net/cs";
+
+       if (argc > 1) {
+               for (i = 1; i < argc; i++)
+                       query(argv[i]);
+               exit(0);
+       }
+
+       for (;;) {
+               printf("CSQUERY:> ");
+               i = 0;
+               while (read(0, &p[i], 1) > 0){
+                       /* Attempt to echo our input back to stdout */
+                       write(1, &p[i], 1);
+                       if (p[i] == '\n')
+                               break;
+                       i++;
+               }
+               if (i < 0)
+                       break;
+               p[i] = 0;
+               printf("CSQUERY:Got %d bytes:%s:\n", i, p);
+               if (i)
+                       query(p);
+       }
+}
diff --git a/tests/netstat.c b/tests/netstat.c
new file mode 100644 (file)
index 0000000..005ea88
--- /dev/null
@@ -0,0 +1,258 @@
+/* 
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+#include <sys/types.h>
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <parlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <iplib.h>
+#include <dirent.h>
+
+enum {
+       maxproto = 20,
+};
+void pip(char *, struct dirent *);
+void nstat(char *, void (*)(char *, struct dirent *));
+void pipifc(void);
+
+FILE *out;
+char *netroot;
+char *proto[maxproto];
+int nproto;
+int notrans;
+
+void usage(char *argv0)
+{
+       fprintf(stderr, "usage: %s [-in] [-p proto] [network-dir]\n", argv0);
+       fprintf(stderr, "usage");
+       exit(1);
+}
+
+void main(int argc, char *argv[])
+{
+       int justinterfaces = 0;
+       int i, tot, fd;
+       DIR *dir;
+       struct dirent *d;
+       char buf[128];
+       out = stdout;
+       argc--, argv++;
+       while (argc > 0 && *argv[0] == '-') {
+               switch (argv[0][1]) {
+                       case 'i':
+                               justinterfaces = 1;
+                               break;
+                       case 'n':
+                               notrans = 1;
+                               break;
+                       case 'p':
+                               if (nproto >= maxproto){
+                                       fprintf(stderr, "too many protos");
+                                       exit(1);
+                               }
+                               if (argc < 2)
+                                       usage("netstat");
+                               argc--, argv++;
+                               proto[nproto++] = argv[0];
+                               break;
+                       default:
+                               usage("netstat");
+               }
+               argc--, argv++;
+       }
+
+       netroot = "/net";
+       switch (argc) {
+               case 0:
+                       break;
+               case 1:
+                       netroot = argv[0];
+                       break;
+               default:
+                       usage("netstat");
+       }
+
+       if (justinterfaces) {
+               pipifc();
+               exit(0);
+       }
+
+       if (nproto) {
+               for (i = 0; i < nproto; i++)
+                       nstat(proto[i], pip);
+       } else {
+               dir = opendir(netroot);
+               if (!dir) {
+                       fprintf(stderr, "open %s: %r", netroot);
+                       exit(1);
+               }
+
+               while ((d = readdir(dir))) {
+                       if (strcmp(d->d_name, "ipifc") == 0)
+                               continue;
+                       snprintf(buf, sizeof buf, "%s/%s/0/local", netroot, d->d_name);
+                       /* access is bogus for now. */
+                       if (1 || access(buf, 0) >= 0)
+                               nstat(d->d_name, pip);
+                       else
+                               fprintf(stderr, "Can't access %s\n", d->d_name);
+               }
+       }
+
+       exit(0);
+}
+
+void nstat(char *net, void (*f) (char *, struct dirent *))
+{
+       int fdir, i, tot;
+       struct dirent *d;
+       DIR *dir;
+       char buf[128];
+
+       snprintf(buf, sizeof buf, "%s/%s", netroot, net);
+       dir = opendir(buf);
+       if (!dir)
+               return;
+
+       while ((d = readdir(dir))) {
+               (*f) (net, d);
+       }
+       /* leak dir */
+}
+
+char *getport(char *net, char *p)
+{
+       static char port[10];
+
+       strncpy(port, p, sizeof(port) - 1);
+       port[sizeof(port) - 1] = 0;
+       if (1)  //if(notrans || (p = csgetvalue(netroot, "port", p, net, nil)) == nil)
+               return port;
+       strncpy(port, p, sizeof(port) - 1);
+       port[sizeof(port) - 1] = 0;
+       free(p);
+       return port;
+}
+
+void pip(char *net, struct dirent *db)
+{
+       int n, fd;
+       char buf[128], *p;
+       char *dname;
+
+       if (strcmp(db->d_name, "clone") == 0)
+               return;
+       if (strcmp(db->d_name, "stats") == 0)
+               return;
+
+       snprintf(buf, sizeof buf, "%s/%s/%s/status", netroot, net, db->d_name);
+       fd = open(buf, O_RDONLY);
+       if (fd < 0)
+               return;
+       n = read(fd, buf, sizeof(buf));
+       close(fd);
+       if (n < 0)
+               return;
+       buf[n] = 0;
+
+       p = strchr(buf, ' ');
+       if (p != 0)
+               *p = 0;
+       p = strrchr(buf, '\n');
+       if (p != 0)
+               *p = 0;
+       fprintf(out, "%-4s %-4s %-12s ", net, db->d_name, /*db->uid, */ buf);
+
+       snprintf(buf, sizeof buf, "%s/%s/%s/local", netroot, net, db->d_name);
+       fd = open(buf, O_RDONLY);
+       if (fd < 0) {
+               fprintf(out, "\n");
+               return;
+       }
+       n = read(fd, buf, sizeof(buf));
+       close(fd);
+       if (n < 0) {
+               fprintf(out, "\n");
+               return;
+       }
+       buf[n - 1] = 0;
+       p = strchr(buf, '!');
+       if (p == 0) {
+               fprintf(out, "\n");
+               return;
+       }
+       *p = '\0';
+       fprintf(out, "%-10s ", getport(net, p + 1));
+
+       snprintf(buf, sizeof buf, "%s/%s/%s/remote", netroot, net, db->d_name);
+       fd = open(buf, O_RDONLY);
+       if (fd < 0) {
+               printf("\n");
+               return;
+       }
+       n = read(fd, buf, sizeof(buf));
+       close(fd);
+       if (n < 0) {
+               printf("\n");
+               return;
+       }
+       buf[n - 1] = 0;
+       p = strchr(buf, '!');
+       if (p != NULL)
+               *p++ = '\0';
+
+       if (notrans) {
+               fprintf(out, "%-10s %s\n", getport(net, p), buf);
+               return;
+       }
+       dname = NULL;   //csgetvalue(netroot, "ip", buf, "dom", nil);
+       if (dname == NULL) {
+               fprintf(out, "%-10s %s\n", getport(net, p), buf);
+               return;
+       }
+       fprintf(out, "%-10s %s\n", getport(net, p), dname);
+       free(dname);
+}
+
+void pipifc(void)
+{
+       struct ipifc *ip, *nip;
+       struct iplifc *lifc;
+       char buf[100];
+       int l, i;
+
+//  fmtinstall('I', eipfmt);
+//  fmtinstall('M', eipfmt);
+
+       ip = readipifc(netroot, NULL, -1);
+
+       l = 7;
+       for (nip = ip; nip; nip = nip->next) {
+               for (lifc = nip->lifc; lifc; lifc = lifc->next) {
+                       i = snprintf(buf, sizeof buf, "%I", lifc->ip);
+                       if (i > l)
+                               l = i;
+                       i = snprintf(buf, sizeof buf, "%I", lifc->net);
+                       if (i > l)
+                               l = i;
+               }
+       }
+
+       for (nip = ip; nip; nip = nip->next) {
+               for (lifc = nip->lifc; lifc; lifc = lifc->next)
+                       fprintf(out, "%-12s %5d %-*I %5M %-*I %8lud %8lud %8lud %8lud\n",
+                                       nip->dev, nip->mtu,
+                                       l, lifc->ip, lifc->mask, l, lifc->net,
+                                       nip->pktin, nip->pktout, nip->errin, nip->errout);
+       }
+}