summaryrefslogtreecommitdiff
path: root/fw/src/ether.c
diff options
context:
space:
mode:
Diffstat (limited to 'fw/src/ether.c')
-rw-r--r--fw/src/ether.c374
1 files changed, 374 insertions, 0 deletions
diff --git a/fw/src/ether.c b/fw/src/ether.c
new file mode 100644
index 0000000..54f5068
--- /dev/null
+++ b/fw/src/ether.c
@@ -0,0 +1,374 @@
+/*
+ * fw, the ethernet side.
+ *
+ * To filter a real machine, fw has to be the wire: the card cannot
+ * stay attached to the IP stack, or packets reach it whatever we
+ * decide. So fw opens /net/etherN itself and the protected stack gets
+ * a pkt interface instead - a fake card with fw on the other end.
+ *
+ * ether0 ---- fw ---- pkt ---- the stack ---- programs
+ *
+ * The stack no longer has ethernet, so nobody is doing ARP for it any
+ * more, and fw has to: answer requests for the address we are
+ * protecting, and resolve the next hop for anything we send. That is
+ * all the ethernet fw knows about; everything else it treats as an IP
+ * packet and hands to the rules.
+ *
+ * An ether "bypass" connection looks like it should serve instead, but
+ * it only intercepts the stack's transmissions - etheriq drops what
+ * arrives from the wire while bypass is set - so it cannot filter
+ * inbound at all.
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+#include "rules.h"
+
+extern int etherdebug;
+
+enum
+{
+ Eaddrlen = 6,
+ Ehdrlen = 14,
+ Etip4 = 0x0800,
+ Etarp = 0x0806,
+ Etip6 = 0x86DD,
+
+ Arplen = 28,
+ Eminlen = 60, /* ethernet minimum frame, devether enforces it */
+ Arpreq = 1,
+ Arpreply = 2,
+
+ Narp = 64,
+ Arplife = 300,
+};
+
+typedef struct Arpent Arpent;
+struct Arpent
+{
+ uchar ip[IPaddrlen];
+ uchar mac[Eaddrlen];
+ long when;
+};
+
+static Arpent arptab[Narp];
+static Lock arplock;
+
+static uchar ourmac[Eaddrlen];
+static uchar ouraddr[IPaddrlen];
+static uchar gateway[IPaddrlen];
+static int haveg;
+static int efd = -1;
+
+static uchar bcast[Eaddrlen] = { 0xff,0xff,0xff,0xff,0xff,0xff };
+
+/*
+ * Open a card for raw frames of every type. Promiscuous because we
+ * are answering for an address the card does not believe is its own
+ * once the stack has let go of it.
+ */
+int
+etheropen(char *dev, uchar *mac)
+{
+ char path[128], buf[64];
+ int cfd, dfd, n, conn;
+
+ snprint(path, sizeof path, "%s/clone", dev);
+ if((cfd = open(path, ORDWR)) < 0)
+ sysfatal("open %s: %r", path);
+ if((n = read(cfd, buf, sizeof buf - 1)) <= 0)
+ sysfatal("read %s: %r", path);
+ buf[n] = '\0';
+ conn = atoi(buf);
+
+ if(fprint(cfd, "connect -1") < 0)
+ sysfatal("%s: connect -1: %r", dev);
+ if(fprint(cfd, "promiscuous") < 0)
+ sysfatal("%s: promiscuous: %r", dev);
+
+ snprint(path, sizeof path, "%s/%d/data", dev, conn);
+ if((dfd = open(path, ORDWR)) < 0)
+ sysfatal("open %s: %r", path);
+
+ /* the card's own address, which we answer with */
+ snprint(path, sizeof path, "%s/addr", dev);
+ if((n = open(path, OREAD)) < 0)
+ sysfatal("open %s: %r", path);
+ if(read(n, buf, 12) != 12)
+ sysfatal("read %s: %r", path);
+ close(n);
+ buf[12] = '\0';
+ if(parseether(mac, buf) < 0)
+ sysfatal("%s: unparseable address %s", dev, buf);
+
+ efd = dfd;
+ memmove(ourmac, mac, Eaddrlen);
+ return dfd;
+}
+
+void
+ethersetaddr(uchar *ip, uchar *gw, int haveit)
+{
+ ipmove(ouraddr, ip);
+ if(haveit)
+ ipmove(gateway, gw);
+ haveg = haveit;
+}
+
+static void
+arpput(uchar *ip, uchar *mac)
+{
+ Arpent *a, *old;
+ long now;
+ int i;
+
+ now = time(0);
+ lock(&arplock);
+ old = &arptab[0];
+ for(i = 0; i < Narp; i++){
+ a = &arptab[i];
+ if(a->when != 0 && ipcmp(a->ip, ip) == 0){
+ memmove(a->mac, mac, Eaddrlen);
+ a->when = now;
+ unlock(&arplock);
+ return;
+ }
+ if(a->when < old->when)
+ old = a;
+ }
+ ipmove(old->ip, ip);
+ memmove(old->mac, mac, Eaddrlen);
+ old->when = now;
+ unlock(&arplock);
+}
+
+static int
+arpget(uchar *ip, uchar *mac)
+{
+ Arpent *a;
+ long now;
+ int i, r;
+
+ r = 0;
+ now = time(0);
+ lock(&arplock);
+ for(i = 0; i < Narp; i++){
+ a = &arptab[i];
+ if(a->when != 0 && ipcmp(a->ip, ip) == 0){
+ if(now - a->when <= Arplife){
+ memmove(mac, a->mac, Eaddrlen);
+ r = 1;
+ }
+ break;
+ }
+ }
+ unlock(&arplock);
+ return r;
+}
+
+/*
+ * Broadcast and multicast are not resolved, they are addressed by rule.
+ * Without this a DHCP renewal, which goes to 255.255.255.255, would be
+ * sent to whatever the gateway's ethernet address happened to be, and
+ * the lease would quietly never renew.
+ */
+static int
+groupmac(uchar *dst, uchar *mask, uchar *mac)
+{
+ uchar net[IPaddrlen], all[IPaddrlen];
+ int i;
+
+ if(isv4(dst)){
+ /* 255.255.255.255 */
+ for(i = IPv4off; i < IPaddrlen; i++)
+ if(dst[i] != 0xff)
+ break;
+ if(i == IPaddrlen){
+ memmove(mac, bcast, Eaddrlen);
+ return 1;
+ }
+ /* the broadcast address of our own network */
+ maskip(ouraddr, mask, net);
+ for(i = 0; i < IPaddrlen; i++)
+ all[i] = net[i] | ~mask[i];
+ if(ipcmp(dst, all) == 0){
+ memmove(mac, bcast, Eaddrlen);
+ return 1;
+ }
+ /* 224.0.0.0/4 */
+ if(dst[IPv4off] >= 224 && dst[IPv4off] < 240){
+ mac[0] = 0x01;
+ mac[1] = 0x00;
+ mac[2] = 0x5e;
+ mac[3] = dst[IPv4off+1] & 0x7f;
+ mac[4] = dst[IPv4off+2];
+ mac[5] = dst[IPv4off+3];
+ return 1;
+ }
+ return 0;
+ }
+ /* ff00::/8 */
+ if(dst[0] == 0xff){
+ mac[0] = 0x33;
+ mac[1] = 0x33;
+ memmove(mac+2, dst+12, 4);
+ return 1;
+ }
+ return 0;
+}
+
+static void
+puthdr(uchar *f, uchar *dst, int type)
+{
+ memmove(f, dst, Eaddrlen);
+ memmove(f + Eaddrlen, ourmac, Eaddrlen);
+ f[12] = type >> 8;
+ f[13] = type;
+}
+
+/* ask who has ip; the answer arrives later and goes in the cache */
+static void
+arpask(uchar *ip)
+{
+ uchar f[Eminlen];
+
+ if(!isv4(ip))
+ return;
+ memset(f, 0, sizeof f);
+ puthdr(f, bcast, Etarp);
+ hnputs(f + 14, 1); /* ethernet */
+ hnputs(f + 16, Etip4);
+ f[18] = Eaddrlen;
+ f[19] = 4;
+ hnputs(f + 20, Arpreq);
+ memmove(f + 22, ourmac, Eaddrlen);
+ memmove(f + 28, ouraddr + IPv4off, 4);
+ memmove(f + 38, ip + IPv4off, 4);
+ if(etherdebug)
+ fprint(2, "arp: who has %I? (asking)\n", ip);
+ if(write(efd, f, sizeof f) != sizeof f)
+ fprint(2, "arp: write failed: %r\n");
+}
+
+/*
+ * An arp frame from the wire. Learn from it either way, and answer a
+ * request for the address we are standing in for.
+ */
+static void
+arpin(uchar *f, int n)
+{
+ uchar sip[IPaddrlen], tip[IPaddrlen], r[Eminlen];
+ int op;
+
+ if(n < Ehdrlen + Arplen)
+ return;
+ if(nhgets(f + 16) != Etip4 || f[18] != Eaddrlen || f[19] != 4)
+ return;
+ op = nhgets(f + 20);
+ v4tov6(sip, f + 28);
+ v4tov6(tip, f + 38);
+
+ if(ipcmp(sip, IPnoaddr) != 0){
+ arpput(sip, f + 22);
+ if(etherdebug)
+ fprint(2, "arp: learned %I is %E (op %d)\n", sip, f+22, op);
+ }
+ if(op != Arpreq || ipcmp(tip, ouraddr) != 0)
+ return;
+
+ memset(r, 0, sizeof r);
+ puthdr(r, f + 22, Etarp);
+ hnputs(r + 14, 1);
+ hnputs(r + 16, Etip4);
+ r[18] = Eaddrlen;
+ r[19] = 4;
+ hnputs(r + 20, Arpreply);
+ memmove(r + 22, ourmac, Eaddrlen);
+ memmove(r + 28, ouraddr + IPv4off, 4);
+ memmove(r + 32, f + 22, Eaddrlen);
+ memmove(r + 38, f + 28, 4);
+ write(efd, r, sizeof r);
+}
+
+int
+etherisarp(uchar *f, int n)
+{
+ if(n < Ehdrlen)
+ return 0;
+ if(nhgets(f + 12) != Etarp)
+ return 0;
+ arpin(f, n);
+ return 1;
+}
+
+int
+etherisip(uchar *f, int n)
+{
+ int t;
+
+ if(n < Ehdrlen)
+ return 0;
+ t = nhgets(f + 12);
+ return t == Etip4 || t == Etip6;
+}
+
+/*
+ * Send an IP packet out the card. The next hop is the destination if
+ * it is on our own network, otherwise the gateway. If we do not know
+ * its ethernet address yet we ask and drop this one; the sender will
+ * try again, which is what every other stack does too.
+ */
+int
+etherwriteip(uchar *p, int n, uchar *mask)
+{
+ uchar f[Ehdrlen + 64*1024], dst[IPaddrlen], hop[IPaddrlen];
+ uchar net[IPaddrlen], ournet[IPaddrlen], mac[Eaddrlen];
+ int type, len;
+
+ if(n < 20 || n > 64*1024 - Ehdrlen)
+ return -1;
+ switch(p[0] >> 4){
+ case 4:
+ type = Etip4;
+ v4tov6(dst, p + 16);
+ break;
+ case 6:
+ type = Etip6;
+ ipmove(dst, p + 24);
+ break;
+ default:
+ return -1;
+ }
+
+ if(groupmac(dst, mask, mac))
+ goto Send;
+
+ maskip(dst, mask, net);
+ maskip(ouraddr, mask, ournet);
+ if(ipcmp(net, ournet) == 0)
+ ipmove(hop, dst);
+ else if(haveg)
+ ipmove(hop, gateway);
+ else
+ ipmove(hop, dst);
+
+ if(!arpget(hop, mac)){
+ if(etherdebug)
+ fprint(2, "arp: no entry for %I, dropping and asking\n", hop);
+ arpask(hop);
+ return 0;
+ }
+Send:
+ puthdr(f, mac, type);
+ memmove(f + Ehdrlen, p, n);
+ len = Ehdrlen + n;
+ if(len < Eminlen){
+ memset(f + len, 0, Eminlen - len);
+ len = Eminlen;
+ }
+ if((len = write(efd, f, len)) < 0)
+ fprint(2, "ether: write failed: %r\n");
+ return len;
+}