| #include <u.h> |
| #include <libc.h> |
| #include <ip.h> |
| #include <bio.h> |
| #include <fcall.h> |
| #include <libsec.h> |
| #include "dat.h" |
| #include "protos.h" |
| #include "y.tab.h" |
| |
| int Cflag; |
| int pflag; |
| int Nflag; |
| int sflag; |
| int tiflag; |
| int toflag; |
| |
| char *prom = "promiscuous"; |
| |
| enum |
| { |
| Pktlen= 64*1024, |
| Blen= 16*1024 |
| }; |
| |
| Filter *filter; |
| Proto *root; |
| Biobuf out; |
| vlong starttime, pkttime; |
| int pcap; |
| |
| int filterpkt(Filter *f, uchar *ps, uchar *pe, Proto *pr, int); |
| void printpkt(char *p, char *e, uchar *ps, uchar *pe); |
| void mkprotograph(void); |
| Proto* findproto(char *name); |
| Filter* compile(Filter *f); |
| void printfilter(Filter *f, char *tag); |
| void printhelp(char*); |
| void tracepkt(uchar*, int); |
| void pcaphdr(int); |
| |
| struct pcap_pkthdr { |
| u64int ts; /* time stamp */ |
| u32int caplen; /* length of portion present */ |
| u32int len; /* length this packet (off wire) */ |
| }; |
| |
| |
| void |
| printusage(void) |
| { |
| fprint(2, "usage: %s [-CDdpst] [-N n] [-f filter] [-h first-header] path\n", argv0); |
| fprint(2, " for protocol help: %s -? [proto]\n", argv0); |
| } |
| |
| void |
| usage(void) |
| { |
| printusage(); |
| exits("usage"); |
| } |
| |
| void |
| main(int argc, char **argv) |
| { |
| uchar *pkt; |
| char *buf, *file, *p, *e; |
| int fd; |
| int n; |
| |
| Binit(&out, 1, OWRITE); |
| |
| fmtinstall('E', eipfmt); |
| fmtinstall('V', eipfmt); |
| fmtinstall('I', eipfmt); |
| fmtinstall('H', encodefmt); |
| fmtinstall('F', fcallfmt); |
| |
| pkt = malloc(Pktlen+16); |
| pkt += 16; |
| buf = malloc(Blen); |
| e = buf+Blen-1; |
| |
| pflag = 1; |
| Nflag = 32; |
| sflag = 0; |
| |
| mkprotograph(); |
| |
| ARGBEGIN{ |
| default: |
| usage(); |
| case '?': |
| printusage(); |
| printhelp(ARGF()); |
| exits(0); |
| break; |
| case 'N': |
| p = EARGF(usage()); |
| Nflag = atoi(p); |
| break; |
| case 'f': |
| p = EARGF(usage()); |
| yyinit(p); |
| yyparse(); |
| break; |
| case 's': |
| sflag = 1; |
| break; |
| case 'h': |
| p = EARGF(usage()); |
| root = findproto(p); |
| if(root == nil) |
| sysfatal("unknown protocol: %s", p); |
| break; |
| case 'd': |
| toflag = 1; |
| break; |
| case 'D': |
| toflag = 1; |
| pcap = 1; |
| break; |
| case 't': |
| tiflag = 1; |
| break; |
| case 'T': |
| tiflag = 1; |
| pcap = 1; |
| break; |
| case 'C': |
| Cflag = 1; |
| break; |
| case 'p': |
| pflag = 0; |
| break; |
| }ARGEND; |
| |
| if(argc > 1) |
| usage(); |
| |
| if(argc == 0) |
| file = nil; |
| else |
| file = argv[0]; |
| |
| if(tiflag){ |
| if(file == nil) |
| sysfatal("must specify file with -t"); |
| fd = open(file, OREAD); |
| if(fd < 0) |
| sysfatal("opening %s: %r", file); |
| }else{ |
| fd = opendevice(file, pflag); |
| if(fd < 0) |
| sysfatal("opening device %s: %r", file); |
| } |
| if(root == nil) |
| root = ðer; |
| |
| if(pcap) |
| pcaphdr(fd); |
| |
| filter = compile(filter); |
| |
| if(tiflag){ |
| /* read a trace file */ |
| for(;;){ |
| if(pcap){ |
| struct pcap_pkthdr *goo; |
| n = read(fd, pkt, 16); |
| if(n != 16) |
| break; |
| goo = (struct pcap_pkthdr*)pkt; |
| pkttime = goo->ts; |
| n = goo->caplen; |
| }else{ |
| n = read(fd, pkt, 10); |
| if(n != 10) |
| break; |
| pkttime = NetL(pkt+2); |
| pkttime = (pkttime<<32) | NetL(pkt+6); |
| if(starttime == 0LL) |
| starttime = pkttime; |
| n = NetS(pkt); |
| } |
| if(readn(fd, pkt, n) != n) |
| break; |
| if(filterpkt(filter, pkt, pkt+n, root, 1)) |
| if(toflag) |
| tracepkt(pkt, n); |
| else |
| printpkt(buf, e, pkt, pkt+n); |
| } |
| } else { |
| /* read a real time stream */ |
| starttime = nsec(); |
| for(;;){ |
| n = root->framer(fd, pkt, Pktlen); |
| if(n <= 0) |
| break; |
| pkttime = nsec(); |
| if(filterpkt(filter, pkt, pkt+n, root, 1)) |
| if(toflag) |
| tracepkt(pkt, n); |
| else |
| printpkt(buf, e, pkt, pkt+n); |
| } |
| } |
| } |
| |
| /* create a new filter node */ |
| Filter* |
| newfilter(void) |
| { |
| Filter *f; |
| |
| f = mallocz(sizeof(*f), 1); |
| if(f == nil) |
| sysfatal("newfilter: %r"); |
| return f; |
| } |
| |
| /* |
| * apply filter to packet |
| */ |
| int |
| _filterpkt(Filter *f, Msg *m) |
| { |
| Msg ma; |
| |
| if(f == nil) |
| return 1; |
| |
| switch(f->op){ |
| case '!': |
| return !_filterpkt(f->l, m); |
| case LAND: |
| ma = *m; |
| return _filterpkt(f->l, &ma) && _filterpkt(f->r, m); |
| case LOR: |
| ma = *m; |
| return _filterpkt(f->l, &ma) || _filterpkt(f->r, m); |
| case WORD: |
| if(m->needroot){ |
| if(m->pr != f->pr) |
| return 0; |
| m->needroot = 0; |
| }else{ |
| if(m->pr && (m->pr->filter==nil || !(m->pr->filter)(f, m))) |
| return 0; |
| } |
| if(f->l == nil) |
| return 1; |
| m->pr = f->pr; |
| return _filterpkt(f->l, m); |
| } |
| sysfatal("internal error: filterpkt op: %d", f->op); |
| return 0; |
| } |
| int |
| filterpkt(Filter *f, uchar *ps, uchar *pe, Proto *pr, int needroot) |
| { |
| Msg m; |
| |
| if(f == nil) |
| return 1; |
| |
| m.needroot = needroot; |
| m.ps = ps; |
| m.pe = pe; |
| m.pr = pr; |
| return _filterpkt(f, &m); |
| } |
| |
| /* |
| * from the Unix world |
| */ |
| #define PCAP_VERSION_MAJOR 2 |
| #define PCAP_VERSION_MINOR 4 |
| #define TCPDUMP_MAGIC 0xa1b2c3d4 |
| |
| struct pcap_file_header { |
| u32int magic; |
| u16int version_major; |
| u16int version_minor; |
| s32int thiszone; /* gmt to local correction */ |
| u32int sigfigs; /* accuracy of timestamps */ |
| u32int snaplen; /* max length saved portion of each pkt */ |
| u32int linktype; /* data link type (DLT_*) */ |
| }; |
| |
| /* |
| * pcap trace header |
| */ |
| void |
| pcaphdr(int fd) |
| { |
| if(tiflag){ |
| struct pcap_file_header hdr; |
| |
| if(readn(fd, &hdr, sizeof hdr) != sizeof hdr) |
| sysfatal("short header"); |
| if(hdr.magic != TCPDUMP_MAGIC) |
| sysfatal("packet header %ux != %ux", hdr.magic, TCPDUMP_MAGIC); |
| if(hdr.version_major != PCAP_VERSION_MAJOR || hdr.version_minor != PCAP_VERSION_MINOR) |
| sysfatal("version %d.%d != %d.%d", hdr.version_major, hdr.version_minor, PCAP_VERSION_MAJOR, PCAP_VERSION_MINOR); |
| if(hdr.linktype != 1) |
| sysfatal("unknown linktype %d != 1 (ethernet)", hdr.linktype); |
| } |
| if(toflag){ |
| struct pcap_file_header hdr; |
| |
| hdr.magic = TCPDUMP_MAGIC; |
| hdr.version_major = PCAP_VERSION_MAJOR; |
| hdr.version_minor = PCAP_VERSION_MINOR; |
| |
| hdr.thiszone = 0; |
| hdr.snaplen = 1500; |
| hdr.sigfigs = 0; |
| hdr.linktype = 1; |
| |
| write(1, &hdr, sizeof(hdr)); |
| } |
| } |
| |
| /* |
| * write out a packet trace |
| */ |
| void |
| tracepkt(uchar *ps, int len) |
| { |
| struct pcap_pkthdr *goo; |
| |
| if(pcap){ |
| goo = (struct pcap_pkthdr*)(ps-16); |
| goo->ts = pkttime; |
| goo->caplen = len; |
| goo->len = len; |
| write(1, goo, len+16); |
| } else { |
| hnputs(ps-10, len); |
| hnputl(ps-8, pkttime>>32); |
| hnputl(ps-4, pkttime); |
| write(1, ps-10, len+10); |
| } |
| } |
| |
| /* |
| * format and print a packet |
| */ |
| void |
| printpkt(char *p, char *e, uchar *ps, uchar *pe) |
| { |
| Msg m; |
| ulong dt; |
| |
| dt = (pkttime-starttime)/1000000LL; |
| m.p = seprint(p, e, "%6.6uld ms ", dt); |
| m.ps = ps; |
| m.pe = pe; |
| m.e = e; |
| m.pr = root; |
| while(m.p < m.e){ |
| if(!sflag) |
| m.p = seprint(m.p, m.e, "\n\t"); |
| m.p = seprint(m.p, m.e, "%s(", m.pr->name); |
| if((*m.pr->seprint)(&m) < 0){ |
| m.p = seprint(m.p, m.e, "TOO SHORT"); |
| m.ps = m.pe; |
| } |
| m.p = seprint(m.p, m.e, ")"); |
| if(m.pr == nil || m.ps >= m.pe) |
| break; |
| } |
| *m.p++ = '\n'; |
| |
| if(write(1, p, m.p - p) < 0) |
| sysfatal("stdout: %r"); |
| } |
| |
| Proto **xprotos; |
| int nprotos; |
| |
| /* look up a protocol by its name */ |
| Proto* |
| findproto(char *name) |
| { |
| int i; |
| |
| for(i = 0; i < nprotos; i++) |
| if(strcmp(xprotos[i]->name, name) == 0) |
| return xprotos[i]; |
| return nil; |
| } |
| |
| /* |
| * add an undefined protocol to protos[] |
| */ |
| Proto* |
| addproto(char *name) |
| { |
| Proto *pr; |
| |
| xprotos = realloc(xprotos, (nprotos+1)*sizeof(Proto*)); |
| pr = malloc(sizeof *pr); |
| *pr = dump; |
| pr->name = name; |
| xprotos[nprotos++] = pr; |
| return pr; |
| } |
| |
| /* |
| * build a graph of protocols, this could easily be circular. This |
| * links together all the multiplexing in the protocol modules. |
| */ |
| void |
| mkprotograph(void) |
| { |
| Proto **l; |
| Proto *pr; |
| Mux *m; |
| |
| /* copy protos into a reallocable area */ |
| for(nprotos = 0; protos[nprotos] != nil; nprotos++) |
| ; |
| xprotos = malloc(nprotos*sizeof(Proto*)); |
| memmove(xprotos, protos, nprotos*sizeof(Proto*)); |
| |
| for(l = protos; *l != nil; l++){ |
| pr = *l; |
| for(m = pr->mux; m != nil && m->name != nil; m++){ |
| m->pr = findproto(m->name); |
| if(m->pr == nil) |
| m->pr = addproto(m->name); |
| } |
| } |
| } |
| |
| /* |
| * add in a protocol node |
| */ |
| static Filter* |
| addnode(Filter *f, Proto *pr) |
| { |
| Filter *nf; |
| nf = newfilter(); |
| nf->pr = pr; |
| nf->s = pr->name; |
| nf->l = f; |
| nf->op = WORD; |
| return nf; |
| } |
| |
| /* |
| * recurse through the protocol graph adding missing nodes |
| * to the filter if we reach the filter's protocol |
| */ |
| static Filter* |
| _fillin(Filter *f, Proto *last, int depth) |
| { |
| Mux *m; |
| Filter *nf; |
| |
| if(depth-- <= 0) |
| return nil; |
| |
| for(m = last->mux; m != nil && m->name != nil; m++){ |
| if(m->pr == nil) |
| continue; |
| if(f->pr == m->pr) |
| return f; |
| nf = _fillin(f, m->pr, depth); |
| if(nf != nil) |
| return addnode(nf, m->pr); |
| } |
| return nil; |
| } |
| |
| static Filter* |
| fillin(Filter *f, Proto *last) |
| { |
| int i; |
| Filter *nf; |
| |
| /* hack to make sure top level node is the root */ |
| if(last == nil){ |
| if(f->pr == root) |
| return f; |
| f = fillin(f, root); |
| if(f == nil) |
| return nil; |
| return addnode(f, root); |
| } |
| |
| /* breadth first search though the protocol graph */ |
| nf = f; |
| for(i = 1; i < 20; i++){ |
| nf = _fillin(f, last, i); |
| if(nf != nil) |
| break; |
| } |
| return nf; |
| } |
| |
| /* |
| * massage tree so that all paths from the root to a leaf |
| * contain a filter node for each header. |
| * |
| * also, set f->pr where possible |
| */ |
| Filter* |
| complete(Filter *f, Proto *last) |
| { |
| Proto *pr; |
| |
| if(f == nil) |
| return f; |
| |
| /* do a depth first traversal of the filter tree */ |
| switch(f->op){ |
| case '!': |
| f->l = complete(f->l, last); |
| break; |
| case LAND: |
| case LOR: |
| f->l = complete(f->l, last); |
| f->r = complete(f->r, last); |
| break; |
| case '=': |
| break; |
| case WORD: |
| pr = findproto(f->s); |
| f->pr = pr; |
| if(pr == nil){ |
| if(f->l != nil){ |
| fprint(2, "%s unknown proto, ignoring params\n", |
| f->s); |
| f->l = nil; |
| } |
| } else { |
| f->l = complete(f->l, pr); |
| f = fillin(f, last); |
| if(f == nil) |
| sysfatal("internal error: can't get to %s", pr->name); |
| } |
| break; |
| } |
| return f; |
| } |
| |
| /* |
| * merge common nodes under | and & moving the merged node |
| * above the | or &. |
| * |
| * do some constant foldong, e.g. `true & x' becomes x and |
| * 'true | x' becomes true. |
| */ |
| static int changed; |
| |
| static Filter* |
| _optimize(Filter *f) |
| { |
| Filter *l; |
| |
| if(f == nil) |
| return f; |
| |
| switch(f->op){ |
| case '!': |
| /* is child also a not */ |
| if(f->l->op == '!'){ |
| changed = 1; |
| return f->l->l; |
| } |
| break; |
| case LOR: |
| /* are two children the same protocol? */ |
| if(f->l->op != f->r->op || f->r->op != WORD |
| || f->l->pr != f->r->pr || f->l->pr == nil) |
| break; /* no optimization */ |
| |
| changed = 1; |
| |
| /* constant folding */ |
| /* if either child is childless, just return that */ |
| if(f->l->l == nil) |
| return f->l; |
| else if(f->r->l == nil) |
| return f->r; |
| |
| /* move the common node up, thow away one node */ |
| l = f->l; |
| f->l = l->l; |
| f->r = f->r->l; |
| l->l = f; |
| return l; |
| case LAND: |
| /* are two children the same protocol? */ |
| if(f->l->op != f->r->op || f->r->op != WORD |
| || f->l->pr != f->r->pr || f->l->pr == nil) |
| break; /* no optimization */ |
| |
| changed = 1; |
| |
| /* constant folding */ |
| /* if either child is childless, ignore it */ |
| if(f->l->l == nil) |
| return f->r; |
| else if(f->r->l == nil) |
| return f->l; |
| |
| /* move the common node up, thow away one node */ |
| l = f->l; |
| f->l = _optimize(l->l); |
| f->r = _optimize(f->r->l); |
| l->l = f; |
| return l; |
| } |
| f->l = _optimize(f->l); |
| f->r = _optimize(f->r); |
| return f; |
| } |
| |
| Filter* |
| optimize(Filter *f) |
| { |
| do{ |
| changed = 0; |
| f = _optimize(f); |
| }while(changed); |
| |
| return f; |
| } |
| |
| /* |
| * find any top level nodes that aren't the root |
| */ |
| int |
| findbogus(Filter *f) |
| { |
| int rv; |
| |
| if(f->op != WORD){ |
| rv = findbogus(f->l); |
| if(f->r) |
| rv |= findbogus(f->r); |
| return rv; |
| } else if(f->pr != root){ |
| fprint(2, "bad top-level protocol: %s\n", f->s); |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* |
| * compile the filter |
| */ |
| static void |
| _compile(Filter *f, Proto *last) |
| { |
| if(f == nil) |
| return; |
| |
| switch(f->op){ |
| case '!': |
| _compile(f->l, last); |
| break; |
| case LOR: |
| case LAND: |
| _compile(f->l, last); |
| _compile(f->r, last); |
| break; |
| case WORD: |
| if(last != nil){ |
| if(last->compile == nil) |
| sysfatal("unknown %s subprotocol: %s", f->pr->name, f->s); |
| (*last->compile)(f); |
| } |
| if(f->l) |
| _compile(f->l, f->pr); |
| break; |
| case '=': |
| if(last == nil) |
| sysfatal("internal error: compilewalk: badly formed tree"); |
| |
| if(last->compile == nil) |
| sysfatal("unknown %s field: %s", f->pr->name, f->s); |
| (*last->compile)(f); |
| break; |
| default: |
| sysfatal("internal error: compilewalk op: %d", f->op); |
| } |
| } |
| |
| Filter* |
| compile(Filter *f) |
| { |
| if(f == nil) |
| return f; |
| |
| /* fill in the missing header filters */ |
| f = complete(f, nil); |
| |
| /* constant folding */ |
| f = optimize(f); |
| if(!toflag) |
| printfilter(f, "after optimize"); |
| |
| /* protocol specific compilations */ |
| _compile(f, nil); |
| |
| /* at this point, the root had better be the root proto */ |
| if(findbogus(f)){ |
| fprint(2, "bogus filter\n"); |
| exits("bad filter"); |
| } |
| |
| return f; |
| } |
| |
| /* |
| * parse a byte array |
| */ |
| int |
| parseba(uchar *to, char *from) |
| { |
| char nip[4]; |
| char *p; |
| int i; |
| |
| p = from; |
| for(i = 0; i < 16; i++){ |
| if(*p == 0) |
| return -1; |
| nip[0] = *p++; |
| if(*p == 0) |
| return -1; |
| nip[1] = *p++; |
| nip[2] = 0; |
| to[i] = strtoul(nip, 0, 16); |
| } |
| return i; |
| } |
| |
| /* |
| * compile WORD = WORD, becomes a single node with a subop |
| */ |
| void |
| compile_cmp(char *proto, Filter *f, Field *fld) |
| { |
| uchar x[IPaddrlen]; |
| |
| if(f->op != '=') |
| sysfatal("internal error: compile_cmp %s: not a cmp", proto); |
| |
| for(; fld->name != nil; fld++){ |
| if(strcmp(f->l->s, fld->name) == 0){ |
| f->op = WORD; |
| f->subop = fld->subop; |
| switch(fld->ftype){ |
| case Fnum: |
| f->ulv = atoi(f->r->s); |
| break; |
| case Fether: |
| parseether(f->a, f->r->s); |
| break; |
| case Fv4ip: |
| f->ulv = parseip(x, f->r->s); |
| break; |
| case Fv6ip: |
| parseip(f->a, f->r->s); |
| break; |
| case Fba: |
| parseba(f->a, f->r->s); |
| break; |
| default: |
| sysfatal("internal error: compile_cmp %s: %d", |
| proto, fld->ftype); |
| } |
| f->l = f->r = nil; |
| return; |
| } |
| } |
| sysfatal("unknown %s field in: %s = %s", proto, f->l->s, f->r->s); |
| } |
| |
| void |
| _pf(Filter *f) |
| { |
| char *s; |
| |
| if(f == nil) |
| return; |
| |
| s = nil; |
| switch(f->op){ |
| case '!': |
| fprint(2, "!"); |
| _pf(f->l); |
| break; |
| case WORD: |
| fprint(2, "%s", f->s); |
| if(f->l != nil){ |
| fprint(2, "("); |
| _pf(f->l); |
| fprint(2, ")"); |
| } |
| break; |
| case LAND: |
| s = "&&"; |
| goto print; |
| case LOR: |
| s = "||"; |
| goto print; |
| case '=': |
| print: |
| _pf(f->l); |
| if(s) |
| fprint(2, " %s ", s); |
| else |
| fprint(2, " %c ", f->op); |
| _pf(f->r); |
| break; |
| default: |
| fprint(2, "???"); |
| break; |
| } |
| } |
| |
| void |
| printfilter(Filter *f, char *tag) |
| { |
| fprint(2, "%s: ", tag); |
| _pf(f); |
| fprint(2, "\n"); |
| } |
| |
| void |
| cat(void) |
| { |
| char buf[1024]; |
| int n; |
| |
| while((n = read(0, buf, sizeof buf)) > 0) |
| write(1, buf, n); |
| } |
| |
| static int fd1 = -1; |
| void |
| startmc(void) |
| { |
| int p[2]; |
| |
| if(fd1 == -1) |
| fd1 = dup(1, -1); |
| |
| if(pipe(p) < 0) |
| return; |
| switch(fork()){ |
| case -1: |
| return; |
| default: |
| close(p[0]); |
| dup(p[1], 1); |
| if(p[1] != 1) |
| close(p[1]); |
| return; |
| case 0: |
| close(p[1]); |
| dup(p[0], 0); |
| if(p[0] != 0) |
| close(p[0]); |
| execl("/bin/mc", "mc", nil); |
| cat(); |
| _exits(0); |
| } |
| } |
| |
| void |
| stopmc(void) |
| { |
| close(1); |
| dup(fd1, 1); |
| waitpid(); |
| } |
| |
| void |
| printhelp(char *name) |
| { |
| int len; |
| Proto *pr, **l; |
| Mux *m; |
| Field *f; |
| char fmt[40]; |
| |
| if(name == nil){ |
| print("protocols:\n"); |
| startmc(); |
| for(l=protos; (pr=*l) != nil; l++) |
| print(" %s\n", pr->name); |
| stopmc(); |
| return; |
| } |
| |
| pr = findproto(name); |
| if(pr == nil){ |
| print("unknown protocol %s\n", name); |
| return; |
| } |
| |
| if(pr->field){ |
| print("%s's filter attributes:\n", pr->name); |
| len = 0; |
| for(f=pr->field; f->name; f++) |
| if(len < strlen(f->name)) |
| len = strlen(f->name); |
| startmc(); |
| for(f=pr->field; f->name; f++) |
| print(" %-*s - %s\n", len, f->name, f->help); |
| stopmc(); |
| } |
| if(pr->mux){ |
| print("%s's subprotos:\n", pr->name); |
| startmc(); |
| snprint(fmt, sizeof fmt, " %s %%s\n", pr->valfmt); |
| for(m=pr->mux; m->name != nil; m++) |
| print(fmt, m->val, m->name); |
| stopmc(); |
| } |
| } |
| |
| /* |
| * demultiplex to next prototol header |
| */ |
| void |
| demux(Mux *mx, ulong val1, ulong val2, Msg *m, Proto *def) |
| { |
| m->pr = def; |
| for(mx = mx; mx->name != nil; mx++){ |
| if(val1 == mx->val || val2 == mx->val){ |
| m->pr = mx->pr; |
| break; |
| } |
| } |
| } |
| |
| /* |
| * default framer just assumes the input packet is |
| * a single read |
| */ |
| int |
| defaultframer(int fd, uchar *pkt, int pktlen) |
| { |
| return read(fd, pkt, pktlen); |
| } |