Import source code for dummynet innode emulation.
[ipfw.git] / ipfw / ipv6.c
diff --git a/ipfw/ipv6.c b/ipfw/ipv6.c
new file mode 100644 (file)
index 0000000..e1c0dee
--- /dev/null
@@ -0,0 +1,501 @@
+/*
+ * Copyright (c) 2002-2003 Luigi Rizzo
+ * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp
+ * Copyright (c) 1994 Ugen J.S.Antsilevich
+ *
+ * Idea and grammar partially left from:
+ * Copyright (c) 1993 Daniel Boulet
+ *
+ * Redistribution and use in source forms, with and without modification,
+ * are permitted provided that this entire comment appears intact.
+ *
+ * Redistribution in binary form may occur without any restrictions.
+ * Obviously, it would be nice if you gave credit where credit is due
+ * but requiring it would be too onerous.
+ *
+ * This software is provided ``AS IS'' without any warranties of any kind.
+ *
+ * NEW command line interface for IP firewall facility
+ *
+ * $FreeBSD: head/sbin/ipfw/ipv6.c 187770 2009-01-27 12:01:30Z luigi $
+ *
+ * ipv6 support
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "ipfw2.h"
+
+#include <err.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/icmp6.h>
+#include <netinet/ip_fw.h>
+#include <arpa/inet.h>
+
+static struct _s_x icmp6codes[] = {
+      { "no-route",            ICMP6_DST_UNREACH_NOROUTE },
+      { "admin-prohib",                ICMP6_DST_UNREACH_ADMIN },
+      { "address",             ICMP6_DST_UNREACH_ADDR },
+      { "port",                        ICMP6_DST_UNREACH_NOPORT },
+      { NULL, 0 }
+};
+
+void
+fill_unreach6_code(u_short *codep, char *str)
+{
+       int val;
+       char *s;
+
+       val = strtoul(str, &s, 0);
+       if (s == str || *s != '\0' || val >= 0x100)
+               val = match_token(icmp6codes, str);
+       if (val < 0)
+               errx(EX_DATAERR, "unknown ICMPv6 unreachable code ``%s''", str);
+       *codep = val;
+       return;
+}
+
+void
+print_unreach6_code(uint16_t code)
+{
+       char const *s = match_value(icmp6codes, code);
+
+       if (s != NULL)
+               printf("unreach6 %s", s);
+       else
+               printf("unreach6 %u", code);
+}
+
+/* 
+ * Print the ip address contained in a command.
+ */
+void
+print_ip6(ipfw_insn_ip6 *cmd, char const *s)
+{
+       struct hostent *he = NULL;
+       int len = F_LEN((ipfw_insn *) cmd) - 1;
+       struct in6_addr *a = &(cmd->addr6);
+       char trad[255];
+
+       printf("%s%s ", cmd->o.len & F_NOT ? " not": "", s);
+
+       if (cmd->o.opcode == O_IP6_SRC_ME || cmd->o.opcode == O_IP6_DST_ME) {
+               printf("me6");
+               return;
+       }
+       if (cmd->o.opcode == O_IP6) {
+               printf(" ip6");
+               return;
+       }
+
+       /*
+        * len == 4 indicates a single IP, whereas lists of 1 or more
+        * addr/mask pairs have len = (2n+1). We convert len to n so we
+        * use that to count the number of entries.
+        */
+
+       for (len = len / 4; len > 0; len -= 2, a += 2) {
+           int mb =        /* mask length */
+               (cmd->o.opcode == O_IP6_SRC || cmd->o.opcode == O_IP6_DST) ?
+               128 : contigmask((uint8_t *)&(a[1]), 128);
+
+           if (mb == 128 && co.do_resolv)
+               he = gethostbyaddr((char *)a, sizeof(*a), AF_INET6);
+           if (he != NULL)             /* resolved to name */
+               printf("%s", he->h_name);
+           else if (mb == 0)           /* any */
+               printf("any");
+           else {          /* numeric IP followed by some kind of mask */
+               if (inet_ntop(AF_INET6,  a, trad, sizeof( trad ) ) == NULL)
+                   printf("Error ntop in print_ip6\n");
+               printf("%s",  trad );
+               if (mb < 0)     /* XXX not really legal... */
+                   printf(":%s",
+                       inet_ntop(AF_INET6, &a[1], trad, sizeof(trad)));
+               else if (mb < 128)
+                   printf("/%d", mb);
+           }
+           if (len > 2)
+               printf(",");
+       }
+}
+
+void
+fill_icmp6types(ipfw_insn_icmp6 *cmd, char *av)
+{
+       uint8_t type;
+
+       bzero(cmd, sizeof(*cmd));
+       while (*av) {
+           if (*av == ',')
+               av++;
+           type = strtoul(av, &av, 0);
+           if (*av != ',' && *av != '\0')
+               errx(EX_DATAERR, "invalid ICMP6 type");
+          /*
+           * XXX: shouldn't this be 0xFF?  I can't see any reason why
+           * we shouldn't be able to filter all possiable values
+           * regardless of the ability of the rest of the kernel to do
+           * anything useful with them.
+           */
+           if (type > ICMP6_MAXTYPE)
+               errx(EX_DATAERR, "ICMP6 type out of range");
+           cmd->d[type / 32] |= ( 1 << (type % 32));
+       }
+       cmd->o.opcode = O_ICMP6TYPE;
+       cmd->o.len |= F_INSN_SIZE(ipfw_insn_icmp6);
+}
+
+
+void
+print_icmp6types(ipfw_insn_u32 *cmd)
+{
+       int i, j;
+       char sep= ' ';
+
+       printf(" ip6 icmp6types");
+       for (i = 0; i < 7; i++)
+               for (j=0; j < 32; ++j) {
+                       if ( (cmd->d[i] & (1 << (j))) == 0)
+                               continue;
+                       printf("%c%d", sep, (i*32 + j));
+                       sep = ',';
+               }
+}
+
+void
+print_flow6id( ipfw_insn_u32 *cmd)
+{
+       uint16_t i, limit = cmd->o.arg1;
+       char sep = ',';
+
+       printf(" flow-id ");
+       for( i=0; i < limit; ++i) {
+               if (i == limit - 1)
+                       sep = ' ';
+               printf("%d%c", cmd->d[i], sep);
+       }
+}
+
+/* structure and define for the extension header in ipv6 */
+static struct _s_x ext6hdrcodes[] = {
+       { "frag",       EXT_FRAGMENT },
+       { "hopopt",     EXT_HOPOPTS },
+       { "route",      EXT_ROUTING },
+       { "dstopt",     EXT_DSTOPTS },
+       { "ah",         EXT_AH },
+       { "esp",        EXT_ESP },
+       { "rthdr0",     EXT_RTHDR0 },
+       { "rthdr2",     EXT_RTHDR2 },
+       { NULL,         0 }
+};
+
+/* fills command for the extension header filtering */
+int
+fill_ext6hdr( ipfw_insn *cmd, char *av)
+{
+       int tok;
+       char *s = av;
+
+       cmd->arg1 = 0;
+
+       while(s) {
+           av = strsep( &s, ",") ;
+           tok = match_token(ext6hdrcodes, av);
+           switch (tok) {
+           case EXT_FRAGMENT:
+               cmd->arg1 |= EXT_FRAGMENT;
+               break;
+
+           case EXT_HOPOPTS:
+               cmd->arg1 |= EXT_HOPOPTS;
+               break;
+
+           case EXT_ROUTING:
+               cmd->arg1 |= EXT_ROUTING;
+               break;
+
+           case EXT_DSTOPTS:
+               cmd->arg1 |= EXT_DSTOPTS;
+               break;
+
+           case EXT_AH:
+               cmd->arg1 |= EXT_AH;
+               break;
+
+           case EXT_ESP:
+               cmd->arg1 |= EXT_ESP;
+               break;
+
+           case EXT_RTHDR0:
+               cmd->arg1 |= EXT_RTHDR0;
+               break;
+
+           case EXT_RTHDR2:
+               cmd->arg1 |= EXT_RTHDR2;
+               break;
+
+           default:
+               errx( EX_DATAERR, "invalid option for ipv6 exten header" );
+               break;
+           }
+       }
+       if (cmd->arg1 == 0 )
+           return 0;
+       cmd->opcode = O_EXT_HDR;
+       cmd->len |= F_INSN_SIZE( ipfw_insn );
+       return 1;
+}
+
+void
+print_ext6hdr( ipfw_insn *cmd )
+{
+       char sep = ' ';
+
+       printf(" extension header:");
+       if (cmd->arg1 & EXT_FRAGMENT ) {
+           printf("%cfragmentation", sep);
+           sep = ',';
+       }
+       if (cmd->arg1 & EXT_HOPOPTS ) {
+           printf("%chop options", sep);
+           sep = ',';
+       }
+       if (cmd->arg1 & EXT_ROUTING ) {
+           printf("%crouting options", sep);
+           sep = ',';
+       }
+       if (cmd->arg1 & EXT_RTHDR0 ) {
+           printf("%crthdr0", sep);
+           sep = ',';
+       }
+       if (cmd->arg1 & EXT_RTHDR2 ) {
+           printf("%crthdr2", sep);
+           sep = ',';
+       }
+       if (cmd->arg1 & EXT_DSTOPTS ) {
+           printf("%cdestination options", sep);
+           sep = ',';
+       }
+       if (cmd->arg1 & EXT_AH ) {
+           printf("%cauthentication header", sep);
+           sep = ',';
+       }
+       if (cmd->arg1 & EXT_ESP ) {
+           printf("%cencapsulated security payload", sep);
+       }
+}
+
+/* Try to find ipv6 address by hostname */
+static int
+lookup_host6 (char *host, struct in6_addr *ip6addr)
+{
+       struct hostent *he;
+
+       if (!inet_pton(AF_INET6, host, ip6addr)) {
+               if ((he = gethostbyname2(host, AF_INET6)) == NULL)
+                       return(-1);
+               memcpy(ip6addr, he->h_addr_list[0], sizeof( struct in6_addr));
+       }
+       return(0);
+}
+
+
+/*
+ * fill the addr and mask fields in the instruction as appropriate from av.
+ * Update length as appropriate.
+ * The following formats are allowed:
+ *     any     matches any IP6. Actually returns an empty instruction.
+ *     me      returns O_IP6_*_ME
+ *
+ *     03f1::234:123:0342                single IP6 addres
+ *     03f1::234:123:0342/24            address/mask
+ *     03f1::234:123:0342/24,03f1::234:123:0343/               List of address
+ *
+ * Set of address (as in ipv6) not supported because ipv6 address
+ * are typically random past the initial prefix.
+ * Return 1 on success, 0 on failure.
+ */
+static int
+fill_ip6(ipfw_insn_ip6 *cmd, char *av)
+{
+       int len = 0;
+       struct in6_addr *d = &(cmd->addr6);
+       /*
+        * Needed for multiple address.
+        * Note d[1] points to struct in6_add r mask6 of cmd
+        */
+
+       cmd->o.len &= ~F_LEN_MASK;      /* zero len */
+
+       if (strcmp(av, "any") == 0)
+              return (1);
+
+
+       if (strcmp(av, "me") == 0) {    /* Set the data for "me" opt*/
+              cmd->o.len |= F_INSN_SIZE(ipfw_insn);
+              return (1);
+       }
+
+       if (strcmp(av, "me6") == 0) {   /* Set the data for "me" opt*/
+              cmd->o.len |= F_INSN_SIZE(ipfw_insn);
+              return (1);
+       }
+
+       av = strdup(av);
+       while (av) {
+               /*
+                * After the address we can have '/' indicating a mask,
+                * or ',' indicating another address follows.
+                */
+
+               char *p;
+               int masklen;
+               char md = '\0';
+
+               if ((p = strpbrk(av, "/,")) ) {
+                       md = *p;        /* save the separator */
+                       *p = '\0';      /* terminate address string */
+                       p++;            /* and skip past it */
+               }
+               /* now p points to NULL, mask or next entry */
+
+               /* lookup stores address in *d as a side effect */
+               if (lookup_host6(av, d) != 0) {
+                       /* XXX: failed. Free memory and go */
+                       errx(EX_DATAERR, "bad address \"%s\"", av);
+               }
+               /* next, look at the mask, if any */
+               masklen = (md == '/') ? atoi(p) : 128;
+               if (masklen > 128 || masklen < 0)
+                       errx(EX_DATAERR, "bad width \"%s\''", p);
+               else
+                       n2mask(&d[1], masklen);
+
+               APPLY_MASK(d, &d[1])   /* mask base address with mask */
+
+               /* find next separator */
+
+               if (md == '/') {        /* find separator past the mask */
+                       p = strpbrk(p, ",");
+                       if (p != NULL)
+                               p++;
+               }
+               av = p;
+
+               /* Check this entry */
+               if (masklen == 0) {
+                       /*
+                        * 'any' turns the entire list into a NOP.
+                        * 'not any' never matches, so it is removed from the
+                        * list unless it is the only item, in which case we
+                        * report an error.
+                        */
+                       if (cmd->o.len & F_NOT && av == NULL && len == 0)
+                               errx(EX_DATAERR, "not any never matches");
+                       continue;
+               }
+
+               /*
+                * A single IP can be stored alone
+                */
+               if (masklen == 128 && av == NULL && len == 0) {
+                       len = F_INSN_SIZE(struct in6_addr);
+                       break;
+               }
+
+               /* Update length and pointer to arguments */
+               len += F_INSN_SIZE(struct in6_addr)*2;
+               d += 2;
+       } /* end while */
+
+       /*
+        * Total length of the command, remember that 1 is the size of
+        * the base command.
+        */
+       if (len + 1 > F_LEN_MASK)
+               errx(EX_DATAERR, "address list too long");
+       cmd->o.len |= len+1;
+       free(av);
+       return (1);
+}
+
+/*
+ * fills command for ipv6 flow-id filtering
+ * note that the 20 bit flow number is stored in a array of u_int32_t
+ * it's supported lists of flow-id, so in the o.arg1 we store how many
+ * additional flow-id we want to filter, the basic is 1
+ */
+void
+fill_flow6( ipfw_insn_u32 *cmd, char *av )
+{
+       u_int32_t type;  /* Current flow number */
+       u_int16_t nflow = 0;    /* Current flow index */
+       char *s = av;
+       cmd->d[0] = 0;    /* Initializing the base number*/
+
+       while (s) {
+               av = strsep( &s, ",") ;
+               type = strtoul(av, &av, 0);
+               if (*av != ',' && *av != '\0')
+                       errx(EX_DATAERR, "invalid ipv6 flow number %s", av);
+               if (type > 0xfffff)
+                       errx(EX_DATAERR, "flow number out of range %s", av);
+               cmd->d[nflow] |= type;
+               nflow++;
+       }
+       if( nflow > 0 ) {
+               cmd->o.opcode = O_FLOW6ID;
+               cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32) + nflow;
+               cmd->o.arg1 = nflow;
+       }
+       else {
+               errx(EX_DATAERR, "invalid ipv6 flow number %s", av);
+       }
+}
+
+ipfw_insn *
+add_srcip6(ipfw_insn *cmd, char *av)
+{
+
+       fill_ip6((ipfw_insn_ip6 *)cmd, av);
+       if (F_LEN(cmd) == 0) {                          /* any */
+       } else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn)) {      /* "me" */
+               cmd->opcode = O_IP6_SRC_ME;
+       } else if (F_LEN(cmd) ==
+           (F_INSN_SIZE(struct in6_addr) + F_INSN_SIZE(ipfw_insn))) {
+               /* single IP, no mask*/
+               cmd->opcode = O_IP6_SRC;
+       } else {                                        /* addr/mask opt */
+               cmd->opcode = O_IP6_SRC_MASK;
+       }
+       return cmd;
+}
+
+ipfw_insn *
+add_dstip6(ipfw_insn *cmd, char *av)
+{
+
+       fill_ip6((ipfw_insn_ip6 *)cmd, av);
+       if (F_LEN(cmd) == 0) {                          /* any */
+       } else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn)) {      /* "me" */
+               cmd->opcode = O_IP6_DST_ME;
+       } else if (F_LEN(cmd) ==
+           (F_INSN_SIZE(struct in6_addr) + F_INSN_SIZE(ipfw_insn))) {
+               /* single IP, no mask*/
+               cmd->opcode = O_IP6_DST;
+       } else {                                        /* addr/mask opt */
+               cmd->opcode = O_IP6_DST_MASK;
+       }
+       return cmd;
+}