summaryrefslogtreecommitdiff
path: root/os/ip/gre.c
diff options
context:
space:
mode:
Diffstat (limited to 'os/ip/gre.c')
-rw-r--r--os/ip/gre.c282
1 files changed, 282 insertions, 0 deletions
diff --git a/os/ip/gre.c b/os/ip/gre.c
new file mode 100644
index 00000000..96106331
--- /dev/null
+++ b/os/ip/gre.c
@@ -0,0 +1,282 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+#include "ip.h"
+
+#define DPRINT if(0)print
+
+enum
+{
+ GRE_IPONLY = 12, /* size of ip header */
+ GRE_IPPLUSGRE = 12, /* minimum size of GRE header */
+ IP_GREPROTO = 47,
+
+ GRErxms = 200,
+ GREtickms = 100,
+ GREmaxxmit = 10,
+};
+
+typedef struct GREhdr
+{
+ /* ip header */
+ uchar vihl; /* Version and header length */
+ uchar tos; /* Type of service */
+ uchar len[2]; /* packet length (including headers) */
+ uchar id[2]; /* Identification */
+ uchar frag[2]; /* Fragment information */
+ uchar Unused;
+ uchar proto; /* Protocol */
+ uchar cksum[2]; /* checksum */
+ uchar src[4]; /* Ip source */
+ uchar dst[4]; /* Ip destination */
+
+ /* gre header */
+ uchar flags[2];
+ uchar eproto[2]; /* encapsulation protocol */
+} GREhdr;
+
+typedef struct GREpriv GREpriv;
+struct GREpriv
+{
+ int raw; /* Raw GRE mode */
+
+ /* non-MIB stats */
+ ulong csumerr; /* checksum errors */
+ ulong lenerr; /* short packet */
+};
+
+static void grekick(void *x, Block *bp);
+
+static char*
+greconnect(Conv *c, char **argv, int argc)
+{
+ Proto *p;
+ char *err;
+ Conv *tc, **cp, **ecp;
+
+ err = Fsstdconnect(c, argv, argc);
+ if(err != nil)
+ return err;
+
+ /* make sure noone's already connected to this other sys */
+ p = c->p;
+ qlock(p);
+ ecp = &p->conv[p->nc];
+ for(cp = p->conv; cp < ecp; cp++){
+ tc = *cp;
+ if(tc == nil)
+ break;
+ if(tc == c)
+ continue;
+ if(tc->rport == c->rport && ipcmp(tc->raddr, c->raddr) == 0){
+ err = "already connected to that addr/proto";
+ ipmove(c->laddr, IPnoaddr);
+ ipmove(c->raddr, IPnoaddr);
+ break;
+ }
+ }
+ qunlock(p);
+
+ if(err != nil)
+ return err;
+ Fsconnected(c, nil);
+
+ return nil;
+}
+
+static void
+grecreate(Conv *c)
+{
+ c->rq = qopen(64*1024, Qmsg, 0, c);
+ c->wq = qbypass(grekick, c);
+}
+
+static int
+grestate(Conv *c, char *state, int n)
+{
+ USED(c);
+ return snprint(state, n, "%s", "Datagram");
+}
+
+static char*
+greannounce(Conv*, char**, int)
+{
+ return "pktifc does not support announce";
+}
+
+static void
+greclose(Conv *c)
+{
+ qclose(c->rq);
+ qclose(c->wq);
+ qclose(c->eq);
+ ipmove(c->laddr, IPnoaddr);
+ ipmove(c->raddr, IPnoaddr);
+ c->lport = 0;
+ c->rport = 0;
+}
+
+int drop;
+
+static void
+grekick(void *x, Block *bp)
+{
+ Conv *c = x;
+ GREhdr *ghp;
+ uchar laddr[IPaddrlen], raddr[IPaddrlen];
+
+ if(bp == nil)
+ return;
+
+ /* Make space to fit ip header (gre header already there) */
+ bp = padblock(bp, GRE_IPONLY);
+ if(bp == nil)
+ return;
+
+ /* make sure the message has a GRE header */
+ bp = pullupblock(bp, GRE_IPONLY+GRE_IPPLUSGRE);
+ if(bp == nil)
+ return;
+
+ ghp = (GREhdr *)(bp->rp);
+ ghp->vihl = IP_VER4;
+
+ if(!((GREpriv*)c->p->priv)->raw){
+ v4tov6(raddr, ghp->dst);
+ if(ipcmp(raddr, v4prefix) == 0)
+ memmove(ghp->dst, c->raddr + IPv4off, IPv4addrlen);
+ v4tov6(laddr, ghp->src);
+ if(ipcmp(laddr, v4prefix) == 0){
+ if(ipcmp(c->laddr, IPnoaddr) == 0)
+ findlocalip(c->p->f, c->laddr, raddr); /* pick interface closest to dest */
+ memmove(ghp->src, c->laddr + IPv4off, IPv4addrlen);
+ }
+ hnputs(ghp->eproto, c->rport);
+ }
+
+ ghp->proto = IP_GREPROTO;
+ ghp->frag[0] = 0;
+ ghp->frag[1] = 0;
+
+ ipoput4(c->p->f, bp, 0, c->ttl, c->tos, nil);
+}
+
+static void
+greiput(Proto *gre, Ipifc*, Block *bp)
+{
+ int len;
+ GREhdr *ghp;
+ Conv *c, **p;
+ ushort eproto;
+ uchar raddr[IPaddrlen];
+ GREpriv *gpriv;
+
+ gpriv = gre->priv;
+ ghp = (GREhdr*)(bp->rp);
+
+ v4tov6(raddr, ghp->src);
+ eproto = nhgets(ghp->eproto);
+ qlock(gre);
+
+ /* Look for a conversation structure for this port and address */
+ c = nil;
+ for(p = gre->conv; *p; p++) {
+ c = *p;
+ if(c->inuse == 0)
+ continue;
+ if(c->rport == eproto &&
+ (gpriv->raw || ipcmp(c->raddr, raddr) == 0))
+ break;
+ }
+
+ if(*p == nil) {
+ qunlock(gre);
+ freeblist(bp);
+ return;
+ }
+
+ qunlock(gre);
+
+ /*
+ * Trim the packet down to data size
+ */
+ len = nhgets(ghp->len) - GRE_IPONLY;
+ if(len < GRE_IPPLUSGRE){
+ freeblist(bp);
+ return;
+ }
+ bp = trimblock(bp, GRE_IPONLY, len);
+ if(bp == nil){
+ gpriv->lenerr++;
+ return;
+ }
+
+ /*
+ * Can't delimit packet so pull it all into one block.
+ */
+ if(qlen(c->rq) > 64*1024)
+ freeblist(bp);
+ else{
+ bp = concatblock(bp);
+ if(bp == 0)
+ panic("greiput");
+ qpass(c->rq, bp);
+ }
+}
+
+int
+grestats(Proto *gre, char *buf, int len)
+{
+ GREpriv *gpriv;
+
+ gpriv = gre->priv;
+
+ return snprint(buf, len, "gre: len %lud\n", gpriv->lenerr);
+}
+
+char*
+grectl(Conv *c, char **f, int n)
+{
+ GREpriv *gpriv;
+
+ gpriv = c->p->priv;
+ if(n == 1){
+ if(strcmp(f[0], "raw") == 0){
+ gpriv->raw = 1;
+ return nil;
+ }
+ else if(strcmp(f[0], "cooked") == 0){
+ gpriv->raw = 0;
+ return nil;
+ }
+ }
+ return "unknown control request";
+}
+
+void
+greinit(Fs *fs)
+{
+ Proto *gre;
+
+ gre = smalloc(sizeof(Proto));
+ gre->priv = smalloc(sizeof(GREpriv));
+ gre->name = "gre";
+ gre->connect = greconnect;
+ gre->announce = greannounce;
+ gre->state = grestate;
+ gre->create = grecreate;
+ gre->close = greclose;
+ gre->rcv = greiput;
+ gre->ctl = grectl;
+ gre->advise = nil;
+ gre->stats = grestats;
+ gre->ipproto = IP_GREPROTO;
+ gre->nc = 64;
+ gre->ptclsize = 0;
+
+ Fsproto(fs, gre);
+}