diff options
Diffstat (limited to 'fw')
| -rw-r--r-- | fw/src/fw.c | 87 | ||||
| -rw-r--r-- | fw/src/netfs.c | 72 | ||||
| -rw-r--r-- | fw/src/rules.c | 39 | ||||
| -rw-r--r-- | fw/src/rules.h | 34 | ||||
| -rwxr-xr-x | fw/test/fwtest.rc | 39 |
5 files changed, 182 insertions, 89 deletions
diff --git a/fw/src/fw.c b/fw/src/fw.c index 70bcc5c..3c13182 100644 --- a/fw/src/fw.c +++ b/fw/src/fw.c @@ -190,23 +190,27 @@ int revalidate(void) { Flow *f, **pp; - uchar *peer; - int i, port, lport, n; + Match m; + int i, n; n = 0; + memset(&m, 0, sizeof m); + m.count = 0; /* re-checking a flow is not traffic */ lock(&flowlock); for(i = 0; i < Nflow; i++) for(pp = &flowtab[i]; (f = *pp) != nil; ){ + m.verb = f->verb; + m.proto = protonum2name(f->proto); if(f->verb == Vout){ - peer = f->dst; - port = f->dport; - lport = f->sport; + m.ip = f->dst; + m.port = f->dport; + m.lport = f->sport; }else{ - peer = f->src; - port = f->sport; - lport = f->dport; + m.ip = f->src; + m.port = f->sport; + m.lport = f->dport; } - if(matchrule(f->verb, protonum2name(f->proto), peer, 0, port, lport, nil) != nil){ + if(!matchrule(&m)){ *pp = f->next; free(f); n++; @@ -397,11 +401,9 @@ static void relay(Wire *from, Wire *to, int verb) { uchar *buf; - char *e; - uchar *peer; - Rule *rule; + Match m; Pkt p; - int n, port, lport; + int n; /* * On the heap, not the stack: these run as libthread procs with a @@ -424,14 +426,18 @@ relay(Wire *from, Wire *to, int verb) } p.verb = verb; + memset(&m, 0, sizeof m); + m.count = 1; + m.verb = verb; + m.proto = protonum2name(p.proto); if(verb == Vout){ - peer = p.dst; - port = p.dport; - lport = p.sport; + m.ip = p.dst; + m.port = p.dport; + m.lport = p.sport; }else{ - peer = p.src; - port = p.sport; - lport = p.dport; + m.ip = p.src; + m.port = p.sport; + m.lport = p.dport; } if(flowseen(&p)){ @@ -446,24 +452,23 @@ relay(Wire *from, Wire *to, int verb) continue; } - e = matchrule(verb, protonum2name(p.proto), peer, 0, port, lport, &rule); - if(e != nil){ - if(rule != nil && rule->log) + if(!matchrule(&m)){ + if(m.log) syslog(0, "fw", "drop %s %s %I!%d -> %I!%d: %s", verb == Vout ? "out" : "in", protonum2name(p.proto), - p.src, p.sport, p.dst, p.dport, e); + p.src, p.sport, p.dst, p.dport, m.err); ndeny++; if(debug) fprint(2, "drop %s %s %I!%d -> %I!%d: %s\n", verb == Vout ? "out" : "in", protonum2name(p.proto), - p.src, p.sport, p.dst, p.dport, e); + p.src, p.sport, p.dst, p.dport, m.err); continue; } nallow++; - if(rule != nil && rule->log) + if(m.log) syslog(0, "fw", "pass %s %s %I!%d -> %I!%d", verb == Vout ? "out" : "in", protonum2name(p.proto), @@ -925,10 +930,7 @@ relayproc(void *a) static int permitted(uchar *buf, int n, int verb, Pkt *p) { - Rule *rule; - uchar *peer; - char *e; - int port, lport; + Match m; parsepkt(buf, n, p); if(!p->ok){ @@ -939,34 +941,37 @@ permitted(uchar *buf, int n, int verb, Pkt *p) return 0; } p->verb = verb; + memset(&m, 0, sizeof m); + m.count = 1; + m.verb = verb; + m.proto = protonum2name(p->proto); if(verb == Vout){ - peer = p->dst; - port = p->dport; - lport = p->sport; + m.ip = p->dst; + m.port = p->dport; + m.lport = p->sport; }else{ - peer = p->src; - port = p->sport; - lport = p->dport; + m.ip = p->src; + m.port = p->sport; + m.lport = p->dport; } if(flowseen(p)){ nallow++; return 1; } - e = matchrule(verb, protonum2name(p->proto), peer, 0, port, lport, &rule); - if(e != nil){ + if(!matchrule(&m)){ ndeny++; - if(rule != nil && rule->log) + if(m.log) syslog(0, "fw", "drop %s %s %I!%d -> %I!%d: %s", verb == Vout ? "out" : "in", protonum2name(p->proto), - p->src, p->sport, p->dst, p->dport, e); + p->src, p->sport, p->dst, p->dport, m.err); if(debug) fprint(2, "drop %s %s %I!%d -> %I!%d: %s\n", verb == Vout ? "out" : "in", protonum2name(p->proto), - p->src, p->sport, p->dst, p->dport, e); + p->src, p->sport, p->dst, p->dport, m.err); return 0; } nallow++; - if(rule != nil && rule->log) + if(m.log) syslog(0, "fw", "pass %s %s %I!%d -> %I!%d", verb == Vout ? "out" : "in", protonum2name(p->proto), p->src, p->sport, p->dst, p->dport); diff --git a/fw/src/netfs.c b/fw/src/netfs.c index f44b089..1300717 100644 --- a/fw/src/netfs.c +++ b/fw/src/netfs.c @@ -345,14 +345,16 @@ static char *okverbs[] = { * a bare port, or addr!port with addr often "*". */ static char* -checkctl(char *proto, char *msg, long n) +checkctl(Match *m, char *proto, char *msg, long n) { - char buf[512], *f[8], *a[4], *addr, *e; - static char err[128]; + char buf[512], dest[128], *f[8], *a[4], *addr; uchar ip[IPaddrlen], mask[IPaddrlen]; - Rule *rule; - int nf, na, verb, anyip, port, lport, i; + int nf, na, i; + memset(m, 0, sizeof *m); + m->count = 1; + m->proto = proto; + m->ip = ip; if(n <= 0) return nil; if(n >= sizeof buf) @@ -363,9 +365,9 @@ checkctl(char *proto, char *msg, long n) if((nf = tokenize(buf, f, nelem(f))) < 1) return nil; if(strcmp(f[0], "connect") == 0) - verb = Vout; + m->verb = Vout; else if(strcmp(f[0], "announce") == 0) - verb = Vin; + m->verb = Vin; else{ /* * Anything else is refused unless it is known to be @@ -387,22 +389,29 @@ checkctl(char *proto, char *msg, long n) return nil; syslog(0, "fw", "deny %s %s: control message not permitted", proto, f[0]); - snprint(err, sizeof err, + snprint(m->err, sizeof m->err, "fw: %s: not a permitted control message", f[0]); - return err; + return m->err; } if(nf < 2) return nil; /* malformed; let the kernel say so */ + /* + * Keep the address before splitting it: getfields writes over the + * separators, so f[1] afterwards is only what precedes the first + * one, and the log said "connect 127.0.0.2" for a connection to a + * port it never named. + */ + snprint(dest, sizeof dest, "%s", f[1]); na = getfields(f[1], a, nelem(a), 0, "!"); if(na < 1) return nil; if(na == 1){ addr = "*"; /* announce 17019 */ - port = atoi(a[0]); + m->port = atoi(a[0]); }else{ addr = a[0]; - port = strcmp(a[1], "*") == 0 ? -1 : atoi(a[1]); + m->port = strcmp(a[1], "*") == 0 ? -1 : atoi(a[1]); } /* @@ -418,27 +427,30 @@ checkctl(char *proto, char *msg, long n) * peer therefore cannot apply to an announce, which is right: * at this point there is no peer to name. */ - if(verb == Vin){ - lport = port; - port = -1; - anyip = 1; + if(m->verb == Vin){ + m->lport = m->port; + m->port = -1; + m->anyip = 1; }else{ - lport = -1; - anyip = strcmp(addr, "*") == 0; - if(!anyip && parseipandmask(ip, mask, addr, nil) == -1){ + m->lport = -1; + m->anyip = strcmp(addr, "*") == 0; + if(!m->anyip && parseipandmask(ip, mask, addr, nil) == -1){ syslog(0, "fw", "deny %s %s %s: unparseable address", - proto, f[0], f[1]); - return "fw: unparseable address"; + proto, f[0], dest); + snprint(m->err, sizeof m->err, "fw: unparseable address"); + return m->err; } } - if((e = matchrule(verb, proto, ip, anyip, port, lport, &rule)) != nil){ - if(rule != nil && rule->log) - syslog(0, "fw", "deny %s %s %s: %s", proto, f[0], f[1], e); - snprint(err, sizeof err, "fw: %s", e); - return err; + if(!matchrule(m)){ + if(m->log) + syslog(0, "fw", "deny %s %s %s: %s", + proto, f[0], dest, m->err); + snprint(buf, sizeof buf, "fw: %s", m->err); + snprint(m->err, sizeof m->err, "%s", buf); + return m->err; } - if(rule != nil && rule->log) - syslog(0, "fw", "allow %s %s %s", proto, f[0], f[1]); + if(m->log) + syslog(0, "fw", "allow %s %s %s", proto, f[0], dest); return nil; } @@ -646,10 +658,12 @@ fswrite(Req *r) return; } if((proto = ctlproto(f->path)) != nil){ - e = checkctl(proto, r->ifcall.data, r->ifcall.count); + Match m; + + e = checkctl(&m, proto, r->ifcall.data, r->ifcall.count); free(proto); if(e != nil){ - respond(r, e); + respond(r, e); /* m outlives the respond, which packs it */ return; } } diff --git a/fw/src/rules.c b/fw/src/rules.c index 55de9cb..b5992b0 100644 --- a/fw/src/rules.c +++ b/fw/src/rules.c @@ -397,44 +397,47 @@ fmthits(char *buf, long nbuf) return p - buf; } -char* -matchrule(int verb, char *proto, uchar *ip, int anyip, int port, int lport, Rule **rp) +int +matchrule(Match *m) { uchar net[IPaddrlen], rnet[IPaddrlen]; - static char err[128]; Rule *r; - if(rp != nil) - *rp = nil; + m->nr = 0; + m->log = 0; lock(&rulelock); for(r = rules; r != nil; r = r->next){ - if(r->verb != Vany && r->verb != verb) + if(r->verb != Vany && r->verb != m->verb) continue; - if(r->proto != nil && (proto == nil || strcmp(r->proto, proto) != 0)) + if(r->proto != nil + && (m->proto == nil || strcmp(r->proto, m->proto) != 0)) continue; - if(r->port >= 0 && r->port != port) + if(r->port >= 0 && r->port != m->port) continue; - if(r->lport >= 0 && r->lport != lport) + if(r->lport >= 0 && r->lport != m->lport) continue; if(!r->anyip){ - if(anyip) /* a wildcard request cannot match a specific rule */ + if(m->anyip) /* a wildcard request cannot match a specific rule */ continue; - maskip(ip, r->mask, net); + maskip(m->ip, r->mask, net); maskip(r->ip, r->mask, rnet); if(ipcmp(net, rnet) != 0) continue; } - if(rp != nil) - *rp = r; - r->hits++; + m->nr = r->nr; + m->log = r->log; + if(m->count) + r->hits++; if(r->allow){ unlock(&rulelock); - return nil; + m->err[0] = '\0'; + return 1; } - snprint(err, sizeof err, "denied by rule %d", r->nr); unlock(&rulelock); - return err; + snprint(m->err, sizeof m->err, "denied by rule %d", r->nr); + return 0; } unlock(&rulelock); - return "denied, no rule matched"; + snprint(m->err, sizeof m->err, "denied, no rule matched"); + return 0; } diff --git a/fw/src/rules.h b/fw/src/rules.h index 6aad0ae..f2c38d4 100644 --- a/fw/src/rules.h +++ b/fw/src/rules.h @@ -49,7 +49,39 @@ long fmtrules(char*, long); /* current set, back in ndb form */ long fmthits(char*, long); /* the same, with hit counts */ void dumprules(void); void checklogging(void); -char* matchrule(int verb, char *proto, uchar *ip, int anyip, int port, int lport, Rule**); +/* + * One question for the rule list, and its answer, in the caller's + * frame. + * + * matchrule used to hand back a Rule* and a pointer into a static + * buffer, and callers read both after it had let go of the lock. A + * rule set installed in between freed the Rule under them, and two + * procs deciding at once overwrote each other's reason - in a program + * whose entire output is the reason. Nothing here outlives the Match, + * and the Match belongs to whoever asked. + * + * count says to charge the decision to the rule's tally. Re-checking + * live flows after a rule change is not traffic and must not be + * counted, or "how often has this rule decided something" answers a + * different question every time the rules are edited. + */ +typedef struct Match Match; +struct Match +{ + int verb; /* in */ + char *proto; /* nil: any */ + uchar *ip; /* the peer; unread if anyip */ + int anyip; + int port; /* peer port, -1: any */ + int lport; /* local port, -1: any */ + int count; /* charge this to the rule */ + + int nr; /* out: the rule that decided, 0 if none */ + int log; /* it asked to be logged */ + char err[128]; /* why not, if it said no */ +}; + +int matchrule(Match*); /* 1 to permit, 0 to refuse */ int protoname2num(char*); char* protonum2name(int); diff --git a/fw/test/fwtest.rc b/fw/test/fwtest.rc index ffbc6b6..f687295 100755 --- a/fw/test/fwtest.rc +++ b/fw/test/fwtest.rc @@ -261,6 +261,35 @@ echo '== namespace mode' check 'a different port is denied' refused $"r } +echo '== logging and accounting' +# /sys/log/fw is fw's only durable output. syslog(2) does not create +# it, so make it if it is not there and take it away again if we did. +madelog=no +if(! test -f /sys/log/fw){ + >/sys/log/fw + chmod 666 /sys/log/fw + madelog=yes +} +@{ + rfork n + { + echo 'deny=out proto=tcp ip=127.0.0.2 log=yes' + echo 'allow=out proto=tcp ip=127.0.0.1 port='^$port^' log=yes' + } > $tmp/log.ndb + $fw $tmp/log.ndb >[2]/dev/null + + wr /net/tcp/clone 'connect 127.0.0.2!'^$port >/dev/null + wr /net/tcp/clone 'connect 127.0.0.1!'^$port >/dev/null +} +sleep 1 +# the port is this run's, so the whole file can be searched +r=`{grep -c 'deny tcp connect 127.0.0.2!'^$port^': denied by rule 1' /sys/log/fw} +check 'a denied connection is logged, with the rule that said so' 1 $"r +r=`{grep -c 'allow tcp connect 127.0.0.1!'^$port /sys/log/fw} +check 'and a permitted one, when the rule asks' 1 $"r +if(~ $madelog yes) + rm -f /sys/log/fw + echo '== rules: round-trip through ctl' cat > $tmp/ip.ndb <<'!' allow=out proto=tcp ip=10.9.0.0/24 port=80 @@ -324,6 +353,16 @@ echo '== packets, between two stacks' r=`{grep -c . $mtpt/rules} check 'it took one rule to do that' 1 $"r + # A rule edit rebuilds the set, so every count starts at zero; what + # runs next is revalidate, re-checking the live flow against the new + # rules. That is not traffic. Counting it made "how often has this + # rule decided something" answer a different question after every + # edit. + echo -n 'append deny=out proto=udp' > $mtpt/ctl + sleep 1 + r=`{awk '/allow=in/ {print $1}' $mtpt/stats} + check 'rechecking flows after an edit is not a decision' 0 $"r + echo -n 'prepend deny=in proto=tcp lport='^$port > $mtpt/ctl sleep 1 r=`{grep -c $port $mtpt/flows} |
