Page MenuHomeFreeBSD

D54763.1774746889.diff
No OneTemporary

Size
653 KB
Referenced Files
None
Subscribers
None

D54763.1774746889.diff

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/sbin/Makefile b/sbin/Makefile
--- a/sbin/Makefile
+++ b/sbin/Makefile
@@ -78,6 +78,7 @@
SUBDIR.${MK_INET6}+= rtsol
SUBDIR.${MK_IPFILTER}+= ipf
SUBDIR.${MK_IPFW}+= ipfw
+SUBDIR.${MK_IPFW}+= ipfw15
SUBDIR.${MK_IPFW}+= natd
SUBDIR.${MK_OPENSSL}+= decryptcore
SUBDIR.${MK_PF}+= pfctl
diff --git a/sbin/ipfw/main.c b/sbin/ipfw/main.c
--- a/sbin/ipfw/main.c
+++ b/sbin/ipfw/main.c
@@ -18,6 +18,7 @@
* Command line interface for IP firewall facility
*/
+#include <sys/stat.h>
#include <sys/wait.h>
#include <ctype.h>
#include <err.h>
@@ -30,6 +31,8 @@
#include <unistd.h>
#include <libgen.h>
+#include <osreldate.h>
+
#include "ipfw2.h"
static void
@@ -690,6 +693,35 @@
else
g_co.prog = cmdline_prog_ipfw;
+ /*
+ * ABI-incopatibility detected, check for availability of ipfw/dnctl15
+ * binaries and run them instead
+ */
+ if (getosreldate() >= 1500000) {
+ const char *releng15_progname;
+ struct stat sb;
+
+ if (g_co.prog == cmdline_prog_ipfw)
+ releng15_progname = "/sbin/ipfw15";
+ else
+ releng15_progname = "/sbin/dnctl15";
+
+ printf("WARNING! KBI incompatibility for ipfw is detected,"
+ " trying to run %s.\n", releng15_progname);
+ if (stat(releng15_progname, &sb) < 0) {
+ printf("%s: %s\n", releng15_progname,
+ strerror(errno));
+ return (1);
+ } else if (S_ISREG(sb.st_mode) == 0 ||
+ (sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) {
+ printf("%s is not an executable file\n",
+ releng15_progname);
+ return (1);
+ }
+
+ return execv(releng15_progname, av);
+ }
+
/*
* If the last argument is an absolute pathname, interpret it
* as a file to be preprocessed.
diff --git a/sbin/ipfw15/Makefile b/sbin/ipfw15/Makefile
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/Makefile
@@ -0,0 +1,23 @@
+.include <src.opts.mk>
+
+PACKAGE=ipfw15
+PROG= ipfw15
+
+CFLAGS+= -I ${.CURDIR}/include15
+
+LINKS= ${BINDIR}/ipfw15 ${BINDIR}/dnctl15
+MK_MAN= no
+
+SRCS= ipfw2.c dummynet.c ipv6.c main.c nat.c tables.c
+SRCS+= nat64clat.c nat64lsn.c nat64stl.c nptv6.c
+
+.if ${MK_PF} != "no"
+SRCS+= altq.c
+CFLAGS+=-DPF
+.endif
+
+LIBADD= jail util
+
+.include <bsd.prog.mk>
+
+CWARNFLAGS+= -Wno-cast-align
diff --git a/sbin/ipfw15/Makefile.depend b/sbin/ipfw15/Makefile.depend
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/Makefile.depend
@@ -0,0 +1,19 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libalias/libalias \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libjail \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/sbin/ipfw15/altq.c b/sbin/ipfw15/altq.c
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/altq.c
@@ -0,0 +1,152 @@
+/*-
+ * 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
+ *
+ * altq interface
+ */
+
+#define PFIOC_USE_LATEST
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+
+#include "ipfw2.h"
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <net/if.h> /* IFNAMSIZ */
+#include <net/pfvar.h>
+#include <netinet/in.h> /* in_addr */
+#include <netinet/ip_fw15.h>
+
+/*
+ * Map between current altq queue id numbers and names.
+ */
+static TAILQ_HEAD(, pf_altq) altq_entries =
+ TAILQ_HEAD_INITIALIZER(altq_entries);
+
+void
+altq_set_enabled(int enabled)
+{
+ int pffd;
+
+ pffd = open("/dev/pf", O_RDWR);
+ if (pffd == -1)
+ err(EX_UNAVAILABLE,
+ "altq support opening pf(4) control device");
+ if (enabled) {
+ if (ioctl(pffd, DIOCSTARTALTQ) != 0 && errno != EEXIST)
+ err(EX_UNAVAILABLE, "enabling altq");
+ } else {
+ if (ioctl(pffd, DIOCSTOPALTQ) != 0 && errno != ENOENT)
+ err(EX_UNAVAILABLE, "disabling altq");
+ }
+ close(pffd);
+}
+
+static void
+altq_fetch(void)
+{
+ struct pfioc_altq pfioc;
+ struct pf_altq *altq;
+ int pffd;
+ unsigned int mnr;
+ static int altq_fetched = 0;
+
+ if (altq_fetched)
+ return;
+ altq_fetched = 1;
+ pffd = open("/dev/pf", O_RDONLY);
+ if (pffd == -1) {
+ warn("altq support opening pf(4) control device");
+ return;
+ }
+ bzero(&pfioc, sizeof(pfioc));
+ pfioc.version = PFIOC_ALTQ_VERSION;
+ if (ioctl(pffd, DIOCGETALTQS, &pfioc) != 0) {
+ warn("altq support getting queue list");
+ close(pffd);
+ return;
+ }
+ mnr = pfioc.nr;
+ for (pfioc.nr = 0; pfioc.nr < mnr; pfioc.nr++) {
+ if (ioctl(pffd, DIOCGETALTQ, &pfioc) != 0) {
+ if (errno == EBUSY)
+ break;
+ warn("altq support getting queue list");
+ close(pffd);
+ return;
+ }
+ if (pfioc.altq.qid == 0)
+ continue;
+ altq = safe_calloc(1, sizeof(*altq));
+ *altq = pfioc.altq;
+ TAILQ_INSERT_TAIL(&altq_entries, altq, entries);
+ }
+ close(pffd);
+}
+
+u_int32_t
+altq_name_to_qid(const char *name)
+{
+ struct pf_altq *altq;
+
+ altq_fetch();
+ TAILQ_FOREACH(altq, &altq_entries, entries)
+ if (strcmp(name, altq->qname) == 0)
+ break;
+ if (altq == NULL)
+ errx(EX_DATAERR, "altq has no queue named `%s'", name);
+ return altq->qid;
+}
+
+static const char *
+altq_qid_to_name(u_int32_t qid)
+{
+ struct pf_altq *altq;
+
+ altq_fetch();
+ TAILQ_FOREACH(altq, &altq_entries, entries)
+ if (qid == altq->qid)
+ break;
+ if (altq == NULL)
+ return NULL;
+ return altq->qname;
+}
+
+void
+print_altq_cmd(struct buf_pr *bp, const ipfw_insn_altq *altqptr)
+{
+ if (altqptr) {
+ const char *qname;
+
+ qname = altq_qid_to_name(altqptr->qid);
+ if (qname == NULL)
+ bprintf(bp, " altq ?<%u>", altqptr->qid);
+ else
+ bprintf(bp, " altq %s", qname);
+ }
+}
diff --git a/sbin/ipfw15/dummynet.c b/sbin/ipfw15/dummynet.c
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/dummynet.c
@@ -0,0 +1,2016 @@
+/*-
+ * Codel/FQ_Codel and PIE/FQ_PIE Code:
+ * Copyright (C) 2016 Centre for Advanced Internet Architectures,
+ * Swinburne University of Technology, Melbourne, Australia.
+ * Portions of this code were made possible in part by a gift from
+ * The Comcast Innovation Fund.
+ * Implemented by Rasool Al-Saadi <ralsaadi@swin.edu.au>
+ *
+ * Copyright (c) 2002-2003,2010 Luigi Rizzo
+ *
+ * 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.
+ *
+ * dummynet support
+ */
+
+#define NEW_AQM
+#include <sys/limits.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+/* XXX there are several sysctl leftover here */
+#include <sys/sysctl.h>
+
+#include "ipfw2.h"
+
+#ifdef NEW_AQM
+#include <stdint.h>
+#endif
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <libutil.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/ip_fw15.h>
+#include <netinet/ip_dummynet15.h>
+#include <arpa/inet.h> /* inet_ntoa */
+
+
+static struct _s_x dummynet_params[] = {
+ { "plr", TOK_PLR },
+ { "noerror", TOK_NOERROR },
+ { "buckets", TOK_BUCKETS },
+ { "dst-ip", TOK_DSTIP },
+ { "src-ip", TOK_SRCIP },
+ { "dst-port", TOK_DSTPORT },
+ { "src-port", TOK_SRCPORT },
+ { "proto", TOK_PROTO },
+ { "weight", TOK_WEIGHT },
+ { "lmax", TOK_LMAX },
+ { "maxlen", TOK_LMAX },
+ { "all", TOK_ALL },
+ { "mask", TOK_MASK }, /* alias for both */
+ { "sched_mask", TOK_SCHED_MASK },
+ { "flow_mask", TOK_FLOW_MASK },
+ { "droptail", TOK_DROPTAIL },
+ { "ecn", TOK_ECN },
+ { "red", TOK_RED },
+ { "gred", TOK_GRED },
+#ifdef NEW_AQM
+ { "codel", TOK_CODEL}, /* Codel AQM */
+ { "fq_codel", TOK_FQ_CODEL}, /* FQ-Codel */
+ { "pie", TOK_PIE}, /* PIE AQM */
+ { "fq_pie", TOK_FQ_PIE}, /* FQ-PIE */
+#endif
+ { "bw", TOK_BW },
+ { "bandwidth", TOK_BW },
+ { "delay", TOK_DELAY },
+ { "link", TOK_LINK },
+ { "pipe", TOK_PIPE },
+ { "queue", TOK_QUEUE },
+ { "flowset", TOK_FLOWSET },
+ { "sched", TOK_SCHED },
+ { "pri", TOK_PRI },
+ { "priority", TOK_PRI },
+ { "type", TOK_TYPE },
+ { "flow-id", TOK_FLOWID},
+ { "dst-ipv6", TOK_DSTIP6},
+ { "dst-ip6", TOK_DSTIP6},
+ { "src-ipv6", TOK_SRCIP6},
+ { "src-ip6", TOK_SRCIP6},
+ { "profile", TOK_PROFILE},
+ { "burst", TOK_BURST},
+ { "dummynet-params", TOK_NULL },
+ { NULL, 0 } /* terminator */
+};
+
+#ifdef NEW_AQM
+/* AQM/extra sched parameters tokens*/
+static struct _s_x aqm_params[] = {
+ { "target", TOK_TARGET},
+ { "interval", TOK_INTERVAL},
+ { "limit", TOK_LIMIT},
+ { "flows", TOK_FLOWS},
+ { "quantum", TOK_QUANTUM},
+ { "ecn", TOK_ECN},
+ { "noecn", TOK_NO_ECN},
+ { "tupdate", TOK_TUPDATE},
+ { "max_burst", TOK_MAX_BURST},
+ { "max_ecnth", TOK_MAX_ECNTH},
+ { "alpha", TOK_ALPHA},
+ { "beta", TOK_BETA},
+ { "capdrop", TOK_CAPDROP},
+ { "nocapdrop", TOK_NO_CAPDROP},
+ { "onoff", TOK_ONOFF},
+ { "dre", TOK_DRE},
+ { "ts", TOK_TS},
+ { "derand", TOK_DERAND},
+ { "noderand", TOK_NO_DERAND},
+ { NULL, 0 } /* terminator */
+};
+#endif
+
+#define O_NEXT(p, len) ((void *)((char *)p + len))
+
+static void
+oid_fill(struct dn_id *oid, int len, int type, uintptr_t id)
+{
+ oid->len = len;
+ oid->type = type;
+ oid->subtype = 0;
+ oid->id = id;
+}
+
+/* make room in the buffer and move the pointer forward */
+static void *
+o_next(struct dn_id **o, int len, int type)
+{
+ struct dn_id *ret = *o;
+ oid_fill(ret, len, type, 0);
+ *o = O_NEXT(*o, len);
+ return ret;
+}
+
+#ifdef NEW_AQM
+
+/* Codel flags */
+enum {
+ CODEL_ECN_ENABLED = 1
+};
+
+/* PIE flags, from PIE kernel module */
+enum {
+ PIE_ECN_ENABLED = 1,
+ PIE_CAPDROP_ENABLED = 2,
+ PIE_ON_OFF_MODE_ENABLED = 4,
+ PIE_DEPRATEEST_ENABLED = 8,
+ PIE_DERAND_ENABLED = 16
+};
+
+#define PIE_FIX_POINT_BITS 13
+#define PIE_SCALE (1L<<PIE_FIX_POINT_BITS)
+
+/* integer to time */
+static void
+us_to_time(int t, char *strt)
+{
+ if (t < 0)
+ strt[0]='\0';
+ else if ( t==0 )
+ sprintf(strt,"%d", t);
+ else if (t< 1000)
+ sprintf(strt,"%dus", t);
+ else if (t < 1000000)
+ sprintf(strt,"%gms", (float) t / 1000);
+ else
+ sprintf(strt,"%gfs", (float) t / 1000000);
+}
+
+/*
+ * returns -1 if s is not a valid time, otherwise, return time in us
+ */
+static long
+time_to_us(const char *s)
+{
+ int i, dots = 0;
+ int len = strlen(s);
+ char strt[16]="", stru[16]="";
+
+ if (len>15)
+ return -1;
+ for (i = 0; i<len && (isdigit(s[i]) || s[i]=='.') ; i++)
+ if (s[i]=='.') {
+ if (dots)
+ return -1;
+ else
+ dots++;
+ }
+
+ if (!i)
+ return -1;
+ strncpy(strt, s, i);
+ if (i<len)
+ strcpy(stru, s+i);
+ else
+ strcpy(stru, "ms");
+
+ if (!strcasecmp(stru, "us"))
+ return atol(strt);
+ if (!strcasecmp(stru, "ms"))
+ return (strtod(strt, NULL) * 1000);
+ if (!strcasecmp(stru, "s"))
+ return (strtod(strt, NULL)*1000000);
+
+ return -1;
+}
+
+
+/* Get AQM or scheduler extra parameters */
+static void
+get_extra_parms(uint32_t nr, char *out, int subtype)
+{
+ struct dn_extra_parms *ep;
+ int ret;
+ char strt1[15], strt2[15], strt3[15];
+ u_int l;
+
+ /* prepare the request */
+ l = sizeof(struct dn_extra_parms);
+ ep = safe_calloc(1, l);
+ memset(ep, 0, sizeof(*ep));
+ *out = '\0';
+
+ oid_fill(&ep->oid, l, DN_CMD_GET, DN_API_VERSION);
+ ep->oid.len = l;
+ ep->oid.subtype = subtype;
+ ep->nr = nr;
+
+ ret = do_cmd(-IP_DUMMYNET3, ep, (uintptr_t)&l);
+ if (ret) {
+ free(ep);
+ errx(EX_DATAERR, "Error getting extra parameters\n");
+ }
+
+ switch (subtype) {
+ case DN_AQM_PARAMS:
+ if( !strcasecmp(ep->name, "codel")) {
+ us_to_time(ep->par[0], strt1);
+ us_to_time(ep->par[1], strt2);
+ l = sprintf(out, " AQM CoDel target %s interval %s",
+ strt1, strt2);
+ if (ep->par[2] & CODEL_ECN_ENABLED)
+ l = sprintf(out + l, " ECN");
+ else
+ l += sprintf(out + l, " NoECN");
+ } else if( !strcasecmp(ep->name, "pie")) {
+ us_to_time(ep->par[0], strt1);
+ us_to_time(ep->par[1], strt2);
+ us_to_time(ep->par[2], strt3);
+ l = sprintf(out, " AQM type PIE target %s tupdate %s alpha "
+ "%g beta %g max_burst %s max_ecnth %.3g",
+ strt1,
+ strt2,
+ ep->par[4] / (float) PIE_SCALE,
+ ep->par[5] / (float) PIE_SCALE,
+ strt3,
+ ep->par[3] / (float) PIE_SCALE
+ );
+
+ if (ep->par[6] & PIE_ECN_ENABLED)
+ l += sprintf(out + l, " ECN");
+ else
+ l += sprintf(out + l, " NoECN");
+ if (ep->par[6] & PIE_CAPDROP_ENABLED)
+ l += sprintf(out + l, " CapDrop");
+ else
+ l += sprintf(out + l, " NoCapDrop");
+ if (ep->par[6] & PIE_ON_OFF_MODE_ENABLED)
+ l += sprintf(out + l, " OnOff");
+ if (ep->par[6] & PIE_DEPRATEEST_ENABLED)
+ l += sprintf(out + l, " DRE");
+ else
+ l += sprintf(out + l, " TS");
+ if (ep->par[6] & PIE_DERAND_ENABLED)
+ l += sprintf(out + l, " Derand");
+ else
+ l += sprintf(out + l, " NoDerand");
+ }
+ break;
+
+ case DN_SCH_PARAMS:
+ if (!strcasecmp(ep->name,"FQ_CODEL")) {
+ us_to_time(ep->par[0], strt1);
+ us_to_time(ep->par[1], strt2);
+ l = sprintf(out," FQ_CODEL target %s interval %s"
+ " quantum %jd limit %jd flows %jd",
+ strt1, strt2,
+ (intmax_t) ep->par[3],
+ (intmax_t) ep->par[4],
+ (intmax_t) ep->par[5]
+ );
+ if (ep->par[2] & CODEL_ECN_ENABLED)
+ l += sprintf(out + l, " ECN");
+ else
+ l += sprintf(out + l, " NoECN");
+ l += sprintf(out + l, "\n");
+ } else if (!strcasecmp(ep->name,"FQ_PIE")) {
+ us_to_time(ep->par[0], strt1);
+ us_to_time(ep->par[1], strt2);
+ us_to_time(ep->par[2], strt3);
+ l = sprintf(out, " FQ_PIE target %s tupdate %s alpha "
+ "%g beta %g max_burst %s max_ecnth %.3g"
+ " quantum %jd limit %jd flows %jd",
+ strt1,
+ strt2,
+ ep->par[4] / (float) PIE_SCALE,
+ ep->par[5] / (float) PIE_SCALE,
+ strt3,
+ ep->par[3] / (float) PIE_SCALE,
+ (intmax_t) ep->par[7],
+ (intmax_t) ep->par[8],
+ (intmax_t) ep->par[9]
+ );
+
+ if (ep->par[6] & PIE_ECN_ENABLED)
+ l += sprintf(out + l, " ECN");
+ else
+ l += sprintf(out + l, " NoECN");
+ if (ep->par[6] & PIE_CAPDROP_ENABLED)
+ l += sprintf(out + l, " CapDrop");
+ else
+ l += sprintf(out + l, " NoCapDrop");
+ if (ep->par[6] & PIE_ON_OFF_MODE_ENABLED)
+ l += sprintf(out + l, " OnOff");
+ if (ep->par[6] & PIE_DEPRATEEST_ENABLED)
+ l += sprintf(out + l, " DRE");
+ else
+ l += sprintf(out + l, " TS");
+ if (ep->par[6] & PIE_DERAND_ENABLED)
+ l += sprintf(out + l, " Derand");
+ else
+ l += sprintf(out + l, " NoDerand");
+ l += sprintf(out + l, "\n");
+ }
+ break;
+ }
+
+ free(ep);
+}
+#endif
+
+
+#if 0
+static int
+sort_q(void *arg, const void *pa, const void *pb)
+{
+ int rev = (co.do_sort < 0);
+ int field = rev ? -co.do_sort : co.do_sort;
+ long long res = 0;
+ const struct dn_flow_queue *a = pa;
+ const struct dn_flow_queue *b = pb;
+
+ switch (field) {
+ case 1: /* pkts */
+ res = a->len - b->len;
+ break;
+ case 2: /* bytes */
+ res = a->len_bytes - b->len_bytes;
+ break;
+
+ case 3: /* tot pkts */
+ res = a->tot_pkts - b->tot_pkts;
+ break;
+
+ case 4: /* tot bytes */
+ res = a->tot_bytes - b->tot_bytes;
+ break;
+ }
+ if (res < 0)
+ res = -1;
+ if (res > 0)
+ res = 1;
+ return (int)(rev ? res : -res);
+}
+#endif
+
+/* print a mask and header for the subsequent list of flows */
+static void
+print_mask(struct ipfw_flow_id *id)
+{
+ if (!IS_IP6_FLOW_ID(id)) {
+ printf(" "
+ "mask: %s 0x%02x 0x%08x/0x%04x -> 0x%08x/0x%04x\n",
+ id->extra ? "queue," : "",
+ id->proto,
+ id->src_ip, id->src_port,
+ id->dst_ip, id->dst_port);
+ } else {
+ char buf[255];
+ printf("\n mask: %sproto: 0x%02x, flow_id: 0x%08x, ",
+ id->extra ? "queue," : "",
+ id->proto, id->flow_id6);
+ inet_ntop(AF_INET6, &(id->src_ip6), buf, sizeof(buf));
+ printf("%s/0x%04x -> ", buf, id->src_port);
+ inet_ntop(AF_INET6, &(id->dst_ip6), buf, sizeof(buf));
+ printf("%s/0x%04x\n", buf, id->dst_port);
+ }
+}
+
+static void
+print_header(struct ipfw_flow_id *id)
+{
+ if (!IS_IP6_FLOW_ID(id))
+ printf("BKT Prot ___Source IP/port____ "
+ "____Dest. IP/port____ "
+ "Tot_pkt/bytes Pkt/Byte Drp\n");
+ else
+ printf("BKT ___Prot___ _flow-id_ "
+ "______________Source IPv6/port_______________ "
+ "_______________Dest. IPv6/port_______________ "
+ "Tot_pkt/bytes Pkt/Byte Drp\n");
+}
+
+static void
+list_flow(struct buf_pr *bp, struct dn_flow *ni)
+{
+ char buff[255];
+ struct protoent *pe = NULL;
+ struct in_addr ina;
+ struct ipfw_flow_id *id = &ni->fid;
+
+ pe = getprotobynumber(id->proto);
+ /* XXX: Should check for IPv4 flows */
+ bprintf(bp, "%3u%c", (ni->oid.id) & 0xff,
+ id->extra ? '*' : ' ');
+ if (!IS_IP6_FLOW_ID(id)) {
+ if (pe)
+ bprintf(bp, "%-4s ", pe->p_name);
+ else
+ bprintf(bp, "%4u ", id->proto);
+ ina.s_addr = htonl(id->src_ip);
+ bprintf(bp, "%15s/%-5d ",
+ inet_ntoa(ina), id->src_port);
+ ina.s_addr = htonl(id->dst_ip);
+ bprintf(bp, "%15s/%-5d ",
+ inet_ntoa(ina), id->dst_port);
+ } else {
+ /* Print IPv6 flows */
+ if (pe != NULL)
+ bprintf(bp, "%9s ", pe->p_name);
+ else
+ bprintf(bp, "%9u ", id->proto);
+ bprintf(bp, "%7d %39s/%-5d ", id->flow_id6,
+ inet_ntop(AF_INET6, &(id->src_ip6), buff, sizeof(buff)),
+ id->src_port);
+ bprintf(bp, " %39s/%-5d ",
+ inet_ntop(AF_INET6, &(id->dst_ip6), buff, sizeof(buff)),
+ id->dst_port);
+ }
+ pr_u64(bp, &ni->tot_pkts, 4);
+ pr_u64(bp, &ni->tot_bytes, 8);
+ bprintf(bp, "%2u %4u %3u",
+ ni->length, ni->len_bytes, ni->drops);
+}
+
+static void
+print_flowset_parms(struct dn_fs *fs, char *prefix)
+{
+ int l;
+ char qs[30];
+ char plr[40];
+ char red[200]; /* Display RED parameters */
+
+ l = fs->qsize;
+ if (fs->flags & DN_QSIZE_BYTES) {
+ if (l >= 8192)
+ sprintf(qs, "%d KB", l / 1024);
+ else
+ sprintf(qs, "%d B", l);
+ } else
+ sprintf(qs, "%3d sl.", l);
+ if (fs->plr[0] || fs->plr[1]) {
+ if (fs->plr[1] == 0)
+ sprintf(plr, "plr %f",
+ 1.0 * fs->plr[0] / (double)(0x7fffffff));
+ else
+ sprintf(plr, "plr %f,%f,%f,%f",
+ 1.0 * fs->plr[0] / (double)(0x7fffffff),
+ 1.0 * fs->plr[1] / (double)(0x7fffffff),
+ 1.0 * fs->plr[2] / (double)(0x7fffffff),
+ 1.0 * fs->plr[3] / (double)(0x7fffffff));
+ } else
+ plr[0] = '\0';
+
+ if (fs->flags & DN_IS_RED) { /* RED parameters */
+ sprintf(red,
+ "\n\t %cRED w_q %f min_th %d max_th %d max_p %f",
+ (fs->flags & DN_IS_GENTLE_RED) ? 'G' : ' ',
+ 1.0 * fs->w_q / (double)(1 << SCALE_RED),
+ fs->min_th,
+ fs->max_th,
+ 1.0 * fs->max_p / (double)(1 << SCALE_RED));
+ if (fs->flags & DN_IS_ECN)
+ strlcat(red, " (ecn)", sizeof(red));
+#ifdef NEW_AQM
+ /* get AQM parameters */
+ } else if (fs->flags & DN_IS_AQM) {
+ get_extra_parms(fs->fs_nr, red, DN_AQM_PARAMS);
+#endif
+ } else
+ sprintf(red, "droptail");
+
+ if (prefix[0]) {
+ printf("%s %s%s %d queues (%d buckets) %s\n",
+ prefix, qs, plr, fs->oid.id, fs->buckets, red);
+ prefix[0] = '\0';
+ } else {
+ printf("q%05d %s%s %d flows (%d buckets) sched %d "
+ "weight %d lmax %d pri %d %s\n",
+ fs->fs_nr, qs, plr, fs->oid.id, fs->buckets,
+ fs->sched_nr, fs->par[0], fs->par[1], fs->par[2], red);
+ if (fs->flags & DN_HAVE_MASK)
+ print_mask(&fs->flow_mask);
+ }
+}
+
+static void
+print_extra_delay_parms(struct dn_profile *p)
+{
+ double loss;
+ if (p->samples_no <= 0)
+ return;
+
+ loss = p->loss_level;
+ loss /= p->samples_no;
+ printf("\t profile: name \"%s\" loss %f samples %d\n",
+ p->name, loss, p->samples_no);
+}
+
+static void
+flush_buf(char *buf)
+{
+ if (buf[0])
+ printf("%s\n", buf);
+ buf[0] = '\0';
+}
+
+/*
+ * generic list routine. We expect objects in a specific order, i.e.
+ * PIPES AND SCHEDULERS:
+ * link; scheduler; internal flowset if any; instances
+ * we can tell a pipe from the number.
+ *
+ * FLOWSETS:
+ * flowset; queues;
+ * link i (int queue); scheduler i; si(i) { flowsets() : queues }
+ */
+static void
+list_pipes(struct dn_id *oid, struct dn_id *end)
+{
+ char buf[160]; /* pending buffer */
+ int toPrint = 1; /* print header */
+ struct buf_pr bp;
+
+ buf[0] = '\0';
+ bp_alloc(&bp, 4096);
+ for (; oid != end; oid = O_NEXT(oid, oid->len)) {
+ if (oid->len < sizeof(*oid))
+ errx(1, "invalid oid len %d\n", oid->len);
+
+ switch (oid->type) {
+ default:
+ flush_buf(buf);
+ printf("unrecognized object %d size %d\n", oid->type, oid->len);
+ break;
+ case DN_TEXT: /* list of attached flowsets */
+ {
+ int i, l;
+ struct {
+ struct dn_id id;
+ uint32_t p[0];
+ } *d = (void *)oid;
+ l = (oid->len - sizeof(*oid))/sizeof(d->p[0]);
+ if (l == 0)
+ break;
+ printf(" Children flowsets: ");
+ for (i = 0; i < l; i++)
+ printf("%u ", d->p[i]);
+ printf("\n");
+ break;
+ }
+ case DN_CMD_GET:
+ if (g_co.verbose)
+ printf("answer for cmd %d, len %d\n", oid->type, oid->id);
+ break;
+ case DN_SCH: {
+ struct dn_sch *s = (struct dn_sch *)oid;
+ flush_buf(buf);
+ printf(" sched %d type %s flags 0x%x %d buckets %d active\n",
+ s->sched_nr,
+ s->name, s->flags, s->buckets, s->oid.id);
+#ifdef NEW_AQM
+ char parms[200];
+ get_extra_parms(s->sched_nr, parms, DN_SCH_PARAMS);
+ printf("%s",parms);
+#endif
+ if (s->flags & DN_HAVE_MASK)
+ print_mask(&s->sched_mask);
+ }
+ break;
+
+ case DN_FLOW:
+ if (toPrint != 0) {
+ print_header(&((struct dn_flow *)oid)->fid);
+ toPrint = 0;
+ }
+ list_flow(&bp, (struct dn_flow *)oid);
+ printf("%s\n", bp.buf);
+ bp_flush(&bp);
+ break;
+
+ case DN_LINK: {
+ struct dn_link *p = (struct dn_link *)oid;
+ double b = p->bandwidth;
+ char bwbuf[30];
+ char burst[5 + 7];
+
+ /* This starts a new object so flush buffer */
+ flush_buf(buf);
+ /* data rate */
+ if (b == 0)
+ sprintf(bwbuf, "unlimited ");
+ else if (b >= 1000000000)
+ sprintf(bwbuf, "%7.3f Gbit/s", b/1000000000);
+ else if (b >= 1000000)
+ sprintf(bwbuf, "%7.3f Mbit/s", b/1000000);
+ else if (b >= 1000)
+ sprintf(bwbuf, "%7.3f Kbit/s", b/1000);
+ else
+ sprintf(bwbuf, "%7.3f bit/s ", b);
+
+ if (humanize_number(burst, sizeof(burst), p->burst,
+ "", HN_AUTOSCALE, 0) < 0 || g_co.verbose)
+ sprintf(burst, "%d", (int)p->burst);
+ sprintf(buf, "%05d: %s %4d ms burst %s",
+ p->link_nr % DN_MAX_ID, bwbuf, p->delay, burst);
+ }
+ break;
+
+ case DN_FS:
+ print_flowset_parms((struct dn_fs *)oid, buf);
+ break;
+ case DN_PROFILE:
+ flush_buf(buf);
+ print_extra_delay_parms((struct dn_profile *)oid);
+ }
+ flush_buf(buf); // XXX does it really go here ?
+ }
+
+ bp_free(&bp);
+}
+
+/*
+ * Delete pipe, queue or scheduler i
+ */
+int
+ipfw_delete_pipe(int do_pipe, int i)
+{
+ struct {
+ struct dn_id oid;
+ uintptr_t a[1]; /* add more if we want a list */
+ } cmd;
+ oid_fill((void *)&cmd, sizeof(cmd), DN_CMD_DELETE, DN_API_VERSION);
+ cmd.oid.subtype = (do_pipe == 1) ? DN_LINK :
+ ( (do_pipe == 2) ? DN_FS : DN_SCH);
+ cmd.a[0] = i;
+ i = do_cmd(IP_DUMMYNET3, &cmd, cmd.oid.len);
+ if (i) {
+ i = 1;
+ warn("rule %u: setsockopt(IP_DUMMYNET3)", i);
+ }
+ return i;
+}
+
+/*
+ * Code to parse delay profiles.
+ *
+ * Some link types introduce extra delays in the transmission
+ * of a packet, e.g. because of MAC level framing, contention on
+ * the use of the channel, MAC level retransmissions and so on.
+ * From our point of view, the channel is effectively unavailable
+ * for this extra time, which is constant or variable depending
+ * on the link type. Additionally, packets may be dropped after this
+ * time (e.g. on a wireless link after too many retransmissions).
+ * We can model the additional delay with an empirical curve
+ * that represents its distribution.
+ *
+ * cumulative probability
+ * 1.0 ^
+ * |
+ * L +-- loss-level x
+ * | ******
+ * | *
+ * | *****
+ * | *
+ * | **
+ * | *
+ * +-------*------------------->
+ * delay
+ *
+ * The empirical curve may have both vertical and horizontal lines.
+ * Vertical lines represent constant delay for a range of
+ * probabilities; horizontal lines correspond to a discontinuty
+ * in the delay distribution: the link will use the largest delay
+ * for a given probability.
+ *
+ * To pass the curve to dummynet, we must store the parameters
+ * in a file as described below, and issue the command
+ *
+ * ipfw pipe <n> config ... bw XXX profile <filename> ...
+ *
+ * The file format is the following, with whitespace acting as
+ * a separator and '#' indicating the beginning a comment:
+ *
+ * samples N
+ * the number of samples used in the internal
+ * representation (2..1024; default 100);
+ *
+ * loss-level L
+ * The probability above which packets are lost.
+ * (0.0 <= L <= 1.0, default 1.0 i.e. no loss);
+ *
+ * name identifier
+ * Optional a name (listed by "ipfw pipe show")
+ * to identify the distribution;
+ *
+ * "delay prob" | "prob delay"
+ * One of these two lines is mandatory and defines
+ * the format of the following lines with data points.
+ *
+ * XXX YYY
+ * 2 or more lines representing points in the curve,
+ * with either delay or probability first, according
+ * to the chosen format.
+ * The unit for delay is milliseconds.
+ *
+ * Data points does not need to be ordered or equal to the number
+ * specified in the "samples" line. ipfw will sort and interpolate
+ * the curve as needed.
+ *
+ * Example of a profile file:
+
+ name bla_bla_bla
+ samples 100
+ loss-level 0.86
+ prob delay
+ 0 200 # minimum overhead is 200ms
+ 0.5 200
+ 0.5 300
+ 0.8 1000
+ 0.9 1300
+ 1 1300
+
+ * Internally, we will convert the curve to a fixed number of
+ * samples, and when it is time to transmit a packet we will
+ * model the extra delay as extra bits in the packet.
+ *
+ */
+
+#define ED_MAX_LINE_LEN 256+ED_MAX_NAME_LEN
+#define ED_TOK_SAMPLES "samples"
+#define ED_TOK_LOSS "loss-level"
+#define ED_TOK_NAME "name"
+#define ED_TOK_DELAY "delay"
+#define ED_TOK_PROB "prob"
+#define ED_TOK_BW "bw"
+#define ED_SEPARATORS " \t\n"
+#define ED_MIN_SAMPLES_NO 2
+
+/*
+ * returns 1 if s is a non-negative number, with at least one '.'
+ */
+static int
+is_valid_number(const char *s)
+{
+ int i, dots_found = 0;
+ int len = strlen(s);
+
+ for (i = 0; i<len; ++i)
+ if (!isdigit(s[i]) && (s[i] !='.' || ++dots_found > 1))
+ return 0;
+ return 1;
+}
+
+/*
+ * Take as input a string describing a bandwidth value
+ * and return the numeric bandwidth value.
+ * set clocking interface or bandwidth value
+ */
+static void
+read_bandwidth(char *arg, uint32_t *bandwidth, char *if_name, int namelen)
+{
+ if (*bandwidth != (uint32_t)-1)
+ warnx("duplicate token, override bandwidth value!");
+
+ if (arg[0] >= 'a' && arg[0] <= 'z') {
+ if (!if_name) {
+ errx(1, "no if support");
+ }
+ if (namelen >= IFNAMSIZ)
+ warn("interface name truncated");
+ namelen--;
+ /* interface name */
+ strlcpy(if_name, arg, namelen);
+ *bandwidth = 0;
+ } else { /* read bandwidth value */
+ uint64_t bw;
+ char *end = NULL;
+
+ bw = strtoul(arg, &end, 0);
+ if (*end == 'K' || *end == 'k') {
+ end++;
+ bw *= 1000;
+ } else if (*end == 'M' || *end == 'm') {
+ end++;
+ bw *= 1000000;
+ } else if (*end == 'G' || *end == 'g') {
+ end++;
+ bw *= 1000000000;
+ }
+ if ((*end == 'B' &&
+ _substrcmp2(end, "Bi", "Bit/s") != 0) ||
+ _substrcmp2(end, "by", "bytes") == 0)
+ bw *= 8;
+
+ if (bw > UINT_MAX)
+ errx(EX_DATAERR, "bandwidth too large");
+
+ *bandwidth = (uint32_t)bw;
+ if (if_name)
+ if_name[0] = '\0';
+ }
+}
+
+struct point {
+ double prob;
+ double delay;
+};
+
+static int
+compare_points(const void *vp1, const void *vp2)
+{
+ const struct point *p1 = vp1;
+ const struct point *p2 = vp2;
+ double res = 0;
+
+ res = p1->prob - p2->prob;
+ if (res == 0)
+ res = p1->delay - p2->delay;
+ if (res < 0)
+ return -1;
+ else if (res > 0)
+ return 1;
+ else
+ return 0;
+}
+
+#define ED_EFMT(s) EX_DATAERR,"error in %s at line %d: "#s,filename,lineno
+
+static void
+load_extra_delays(const char *filename, struct dn_profile *p,
+ struct dn_link *link)
+{
+ char line[ED_MAX_LINE_LEN];
+ FILE *f;
+ int lineno = 0;
+ int i;
+
+ int samples = -1;
+ double loss = -1.0;
+ char profile_name[ED_MAX_NAME_LEN];
+ int delay_first = -1;
+ int do_points = 0;
+ struct point points[ED_MAX_SAMPLES_NO];
+ int points_no = 0;
+
+ /* XXX link never NULL? */
+ p->link_nr = link->link_nr;
+
+ profile_name[0] = '\0';
+ f = fopen(filename, "r");
+ if (f == NULL)
+ err(EX_UNAVAILABLE, "fopen: %s", filename);
+
+ while (fgets(line, ED_MAX_LINE_LEN, f)) { /* read commands */
+ char *s, *cur = line, *name = NULL, *arg = NULL;
+
+ ++lineno;
+
+ /* parse the line */
+ while (cur) {
+ s = strsep(&cur, ED_SEPARATORS);
+ if (s == NULL || *s == '#')
+ break;
+ if (*s == '\0')
+ continue;
+ if (arg)
+ errx(ED_EFMT("too many arguments"));
+ if (name == NULL)
+ name = s;
+ else
+ arg = s;
+ }
+ if (name == NULL) /* empty line */
+ continue;
+ if (arg == NULL)
+ errx(ED_EFMT("missing arg for %s"), name);
+
+ if (!strcasecmp(name, ED_TOK_SAMPLES)) {
+ if (samples > 0)
+ errx(ED_EFMT("duplicate ``samples'' line"));
+ if (atoi(arg) <=0)
+ errx(ED_EFMT("invalid number of samples"));
+ samples = atoi(arg);
+ if (samples>ED_MAX_SAMPLES_NO)
+ errx(ED_EFMT("too many samples, maximum is %d"),
+ ED_MAX_SAMPLES_NO);
+ do_points = 0;
+ } else if (!strcasecmp(name, ED_TOK_BW)) {
+ char buf[IFNAMSIZ];
+ read_bandwidth(arg, &link->bandwidth, buf, sizeof(buf));
+ } else if (!strcasecmp(name, ED_TOK_LOSS)) {
+ if (loss != -1.0)
+ errx(ED_EFMT("duplicated token: %s"), name);
+ if (!is_valid_number(arg))
+ errx(ED_EFMT("invalid %s"), arg);
+ loss = atof(arg);
+ if (loss > 1)
+ errx(ED_EFMT("%s greater than 1.0"), name);
+ do_points = 0;
+ } else if (!strcasecmp(name, ED_TOK_NAME)) {
+ if (profile_name[0] != '\0')
+ errx(ED_EFMT("duplicated token: %s"), name);
+ strlcpy(profile_name, arg, sizeof(profile_name));
+ do_points = 0;
+ } else if (!strcasecmp(name, ED_TOK_DELAY)) {
+ if (do_points)
+ errx(ED_EFMT("duplicated token: %s"), name);
+ delay_first = 1;
+ do_points = 1;
+ } else if (!strcasecmp(name, ED_TOK_PROB)) {
+ if (do_points)
+ errx(ED_EFMT("duplicated token: %s"), name);
+ delay_first = 0;
+ do_points = 1;
+ } else if (do_points) {
+ if (!is_valid_number(name) || !is_valid_number(arg))
+ errx(ED_EFMT("invalid point found"));
+ if (delay_first) {
+ points[points_no].delay = atof(name);
+ points[points_no].prob = atof(arg);
+ } else {
+ points[points_no].delay = atof(arg);
+ points[points_no].prob = atof(name);
+ }
+ if (points[points_no].prob > 1.0)
+ errx(ED_EFMT("probability greater than 1.0"));
+ ++points_no;
+ } else {
+ errx(ED_EFMT("unrecognised command '%s'"), name);
+ }
+ }
+
+ fclose (f);
+
+ if (samples == -1) {
+ warnx("'%s' not found, assuming 100", ED_TOK_SAMPLES);
+ samples = 100;
+ }
+
+ if (loss == -1.0) {
+ warnx("'%s' not found, assuming no loss", ED_TOK_LOSS);
+ loss = 1;
+ }
+
+ /* make sure that there are enough points. */
+ if (points_no < ED_MIN_SAMPLES_NO)
+ errx(ED_EFMT("too few samples, need at least %d"),
+ ED_MIN_SAMPLES_NO);
+
+ qsort(points, points_no, sizeof(struct point), compare_points);
+
+ /* interpolation */
+ for (i = 0; i<points_no-1; ++i) {
+ double y1 = points[i].prob * samples;
+ double x1 = points[i].delay;
+ double y2 = points[i+1].prob * samples;
+ double x2 = points[i+1].delay;
+
+ int ix = y1;
+ int stop = y2;
+
+ if (x1 == x2) {
+ for (; ix<stop; ++ix)
+ p->samples[ix] = x1;
+ } else {
+ double m = (y2-y1)/(x2-x1);
+ double c = y1 - m*x1;
+ for (; ix<stop ; ++ix)
+ p->samples[ix] = (ix - c)/m;
+ }
+ }
+ p->samples_no = samples;
+ p->loss_level = loss * samples;
+ strlcpy(p->name, profile_name, sizeof(p->name));
+}
+
+#ifdef NEW_AQM
+
+/* Parse AQM/extra scheduler parameters */
+static int
+process_extra_parms(int *ac, char **av, struct dn_extra_parms *ep,
+ uint16_t type)
+{
+ int i;
+
+ /* use kernel defaults */
+ for (i=0; i<DN_MAX_EXTRA_PARM; i++)
+ ep->par[i] = -1;
+
+ switch(type) {
+ case TOK_CODEL:
+ case TOK_FQ_CODEL:
+ /* Codel
+ * 0- target, 1- interval, 2- flags,
+ * FQ_CODEL
+ * 3- quantum, 4- limit, 5- flows
+ */
+ if (type==TOK_CODEL)
+ ep->par[2] = 0;
+ else
+ ep->par[2] = CODEL_ECN_ENABLED;
+
+ while (*ac > 0) {
+ int tok = match_token(aqm_params, *av);
+ (*ac)--; av++;
+ switch(tok) {
+ case TOK_TARGET:
+ if (*ac <= 0 || time_to_us(av[0]) < 0)
+ errx(EX_DATAERR, "target needs time\n");
+
+ ep->par[0] = time_to_us(av[0]);
+ (*ac)--; av++;
+ break;
+
+ case TOK_INTERVAL:
+ if (*ac <= 0 || time_to_us(av[0]) < 0)
+ errx(EX_DATAERR, "interval needs time\n");
+
+ ep->par[1] = time_to_us(av[0]);
+ (*ac)--; av++;
+ break;
+
+ case TOK_ECN:
+ ep->par[2] = CODEL_ECN_ENABLED;
+ break;
+ case TOK_NO_ECN:
+ ep->par[2] &= ~CODEL_ECN_ENABLED;
+ break;
+ /* Config fq_codel parameters */
+ case TOK_QUANTUM:
+ if (type != TOK_FQ_CODEL)
+ errx(EX_DATAERR, "quantum is not for codel\n");
+ if (*ac <= 0 || !is_valid_number(av[0]))
+ errx(EX_DATAERR, "quantum needs number\n");
+
+ ep->par[3]= atoi(av[0]);
+ (*ac)--; av++;
+ break;
+
+ case TOK_LIMIT:
+ if (type != TOK_FQ_CODEL)
+ errx(EX_DATAERR, "limit is not for codel, use queue instead\n");
+ if (*ac <= 0 || !is_valid_number(av[0]))
+ errx(EX_DATAERR, "limit needs number\n");
+
+ ep->par[4] = atoi(av[0]);
+ (*ac)--; av++;
+ break;
+
+ case TOK_FLOWS:
+ if (type != TOK_FQ_CODEL)
+ errx(EX_DATAERR, "flows is not for codel\n");
+ if (*ac <= 0 || !is_valid_number(av[0]))
+ errx(EX_DATAERR, "flows needs number\n");
+
+ ep->par[5] = atoi(av[0]);
+ (*ac)--; av++;
+ break;
+
+ default:
+ printf("%s is Invalid parameter\n", av[-1]);
+ }
+ }
+ break;
+ case TOK_PIE:
+ case TOK_FQ_PIE:
+ /* PIE
+ * 0- target , 1- tupdate, 2- max_burst,
+ * 3- max_ecnth, 4- alpha,
+ * 5- beta, 6- flags
+ * FQ_CODEL
+ * 7- quantum, 8- limit, 9- flows
+ */
+
+ if ( type == TOK_PIE)
+ ep->par[6] = PIE_CAPDROP_ENABLED | PIE_DEPRATEEST_ENABLED
+ | PIE_DERAND_ENABLED;
+ else
+ /* for FQ-PIE, use TS mode */
+ ep->par[6] = PIE_CAPDROP_ENABLED | PIE_DERAND_ENABLED
+ | PIE_ECN_ENABLED;
+
+ while (*ac > 0) {
+ int tok = match_token(aqm_params, *av);
+ (*ac)--; av++;
+ switch(tok) {
+ case TOK_TARGET:
+ if (*ac <= 0 || time_to_us(av[0]) < 0)
+ errx(EX_DATAERR, "target needs time\n");
+
+ ep->par[0] = time_to_us(av[0]);
+ (*ac)--; av++;
+ break;
+
+ case TOK_TUPDATE:
+ if (*ac <= 0 || time_to_us(av[0]) < 0)
+ errx(EX_DATAERR, "tupdate needs time\n");
+
+ ep->par[1] = time_to_us(av[0]);
+ (*ac)--; av++;
+ break;
+
+ case TOK_MAX_BURST:
+ if (*ac <= 0 || time_to_us(av[0]) < 0)
+ errx(EX_DATAERR, "max_burst needs time\n");
+
+ ep->par[2] = time_to_us(av[0]);
+ (*ac)--; av++;
+ break;
+
+ case TOK_MAX_ECNTH:
+ if (*ac <= 0 || !is_valid_number(av[0]))
+ errx(EX_DATAERR, "max_ecnth needs number\n");
+
+ ep->par[3] = atof(av[0]) * PIE_SCALE;
+ (*ac)--; av++;
+ break;
+
+ case TOK_ALPHA:
+ if (*ac <= 0 || !is_valid_number(av[0]))
+ errx(EX_DATAERR, "alpha needs number\n");
+
+ ep->par[4] = atof(av[0]) * PIE_SCALE;
+ (*ac)--; av++;
+ break;
+
+ case TOK_BETA:
+ if (*ac <= 0 || !is_valid_number(av[0]))
+ errx(EX_DATAERR, "beta needs number\n");
+
+ ep->par[5] = atof(av[0]) * PIE_SCALE;
+ (*ac)--; av++;
+ break;
+
+ case TOK_ECN:
+ ep->par[6] |= PIE_ECN_ENABLED;
+ break;
+ case TOK_NO_ECN:
+ ep->par[6] &= ~PIE_ECN_ENABLED;
+ break;
+
+ case TOK_CAPDROP:
+ ep->par[6] |= PIE_CAPDROP_ENABLED;
+ break;
+ case TOK_NO_CAPDROP:
+ ep->par[6] &= ~PIE_CAPDROP_ENABLED;
+ break;
+
+ case TOK_ONOFF:
+ ep->par[6] |= PIE_ON_OFF_MODE_ENABLED;
+ break;
+
+ case TOK_DRE:
+ ep->par[6] |= PIE_DEPRATEEST_ENABLED;
+ break;
+
+ case TOK_TS:
+ ep->par[6] &= ~PIE_DEPRATEEST_ENABLED;
+ break;
+
+ case TOK_DERAND:
+ ep->par[6] |= PIE_DERAND_ENABLED;
+ break;
+ case TOK_NO_DERAND:
+ ep->par[6] &= ~PIE_DERAND_ENABLED;
+ break;
+
+ /* Config fq_pie parameters */
+ case TOK_QUANTUM:
+ if (type != TOK_FQ_PIE)
+ errx(EX_DATAERR, "quantum is not for pie\n");
+ if (*ac <= 0 || !is_valid_number(av[0]))
+ errx(EX_DATAERR, "quantum needs number\n");
+
+ ep->par[7]= atoi(av[0]);
+ (*ac)--; av++;
+ break;
+
+ case TOK_LIMIT:
+ if (type != TOK_FQ_PIE)
+ errx(EX_DATAERR, "limit is not for pie, use queue instead\n");
+ if (*ac <= 0 || !is_valid_number(av[0]))
+ errx(EX_DATAERR, "limit needs number\n");
+
+ ep->par[8] = atoi(av[0]);
+ (*ac)--; av++;
+ break;
+
+ case TOK_FLOWS:
+ if (type != TOK_FQ_PIE)
+ errx(EX_DATAERR, "flows is not for pie\n");
+ if (*ac <= 0 || !is_valid_number(av[0]))
+ errx(EX_DATAERR, "flows needs number\n");
+
+ ep->par[9] = atoi(av[0]);
+ (*ac)--; av++;
+ break;
+
+
+ default:
+ printf("%s is invalid parameter\n", av[-1]);
+ }
+ }
+ break;
+ }
+
+ return 0;
+}
+
+#endif
+
+
+/*
+ * configuration of pipes, schedulers, flowsets.
+ * When we configure a new scheduler, an empty pipe is created, so:
+ *
+ * do_pipe = 1 -> "pipe N config ..." only for backward compatibility
+ * sched N+Delta type fifo sched_mask ...
+ * pipe N+Delta <parameters>
+ * flowset N+Delta pipe N+Delta (no parameters)
+ * sched N type wf2q+ sched_mask ...
+ * pipe N <parameters>
+ *
+ * do_pipe = 2 -> flowset N config
+ * flowset N parameters
+ *
+ * do_pipe = 3 -> sched N config
+ * sched N parameters (default no pipe)
+ * optional Pipe N config ...
+ * pipe ==>
+ */
+void
+ipfw_config_pipe(int ac, char **av)
+{
+ int i;
+ u_int j;
+ char *end;
+ struct dn_id *buf, *base;
+ struct dn_sch *sch = NULL;
+ struct dn_link *p = NULL;
+ struct dn_fs *fs = NULL;
+ struct dn_profile *pf = NULL;
+ struct ipfw_flow_id *mask = NULL;
+#ifdef NEW_AQM
+ struct dn_extra_parms *aqm_extra = NULL;
+ struct dn_extra_parms *sch_extra = NULL;
+ int lmax_extra;
+#endif
+
+ int lmax;
+ uint32_t _foo = 0, *flags = &_foo , *buckets = &_foo;
+
+ /*
+ * allocate space for 1 header,
+ * 1 scheduler, 1 link, 1 flowset, 1 profile
+ */
+ lmax = sizeof(struct dn_id); /* command header */
+ lmax += sizeof(struct dn_sch) + sizeof(struct dn_link) +
+ sizeof(struct dn_fs) + sizeof(struct dn_profile);
+
+#ifdef NEW_AQM
+ /* Extra Params */
+ lmax_extra = sizeof(struct dn_extra_parms);
+ /* two lmax_extra because one for AQM params and another
+ * sch params
+ */
+ lmax += lmax_extra*2;
+#endif
+
+ av++; ac--;
+ /* Pipe number */
+ if (ac && isdigit(**av)) {
+ i = atoi(*av); av++; ac--;
+ } else
+ i = -1;
+ if (i <= 0)
+ errx(EX_USAGE, "need a pipe/flowset/sched number");
+ base = buf = safe_calloc(1, lmax);
+ /* all commands start with a 'CONFIGURE' and a version */
+ o_next(&buf, sizeof(struct dn_id), DN_CMD_CONFIG);
+ base->id = DN_API_VERSION;
+
+ switch (g_co.do_pipe) {
+ case 1: /* "pipe N config ..." */
+ /* Allocate space for the WF2Q+ scheduler, its link
+ * and the FIFO flowset. Set the number, but leave
+ * the scheduler subtype and other parameters to 0
+ * so the kernel will use appropriate defaults.
+ * XXX todo: add a flag to record if a parameter
+ * is actually configured.
+ * If we do a 'pipe config' mask -> sched_mask.
+ * The FIFO scheduler and link are derived from the
+ * WF2Q+ one in the kernel.
+ */
+#ifdef NEW_AQM
+ sch_extra = o_next(&buf, lmax_extra, DN_TEXT);
+ sch_extra ->oid.subtype = 0; /* don't configure scheduler */
+#endif
+ sch = o_next(&buf, sizeof(*sch), DN_SCH);
+ p = o_next(&buf, sizeof(*p), DN_LINK);
+#ifdef NEW_AQM
+ aqm_extra = o_next(&buf, lmax_extra, DN_TEXT);
+ aqm_extra ->oid.subtype = 0; /* don't configure AQM */
+#endif
+ fs = o_next(&buf, sizeof(*fs), DN_FS);
+
+ sch->sched_nr = i;
+ sch->oid.subtype = 0; /* defaults to WF2Q+ */
+ mask = &sch->sched_mask;
+ flags = &sch->flags;
+ buckets = &sch->buckets;
+ *flags |= DN_PIPE_CMD;
+
+ p->link_nr = i;
+
+ /* This flowset is only for the FIFO scheduler */
+ fs->fs_nr = i + 2*DN_MAX_ID;
+ fs->sched_nr = i + DN_MAX_ID;
+ break;
+
+ case 2: /* "queue N config ... " */
+#ifdef NEW_AQM
+ aqm_extra = o_next(&buf, lmax_extra, DN_TEXT);
+ aqm_extra ->oid.subtype = 0;
+#endif
+ fs = o_next(&buf, sizeof(*fs), DN_FS);
+ fs->fs_nr = i;
+ mask = &fs->flow_mask;
+ flags = &fs->flags;
+ buckets = &fs->buckets;
+ break;
+
+ case 3: /* "sched N config ..." */
+#ifdef NEW_AQM
+ sch_extra = o_next(&buf, lmax_extra, DN_TEXT);
+ sch_extra ->oid.subtype = 0;
+#endif
+ sch = o_next(&buf, sizeof(*sch), DN_SCH);
+#ifdef NEW_AQM
+ aqm_extra = o_next(&buf, lmax_extra, DN_TEXT);
+ aqm_extra ->oid.subtype = 0;
+#endif
+ fs = o_next(&buf, sizeof(*fs), DN_FS);
+ sch->sched_nr = i;
+ mask = &sch->sched_mask;
+ flags = &sch->flags;
+ buckets = &sch->buckets;
+ /* fs is used only with !MULTIQUEUE schedulers */
+ fs->fs_nr = i + DN_MAX_ID;
+ fs->sched_nr = i;
+ break;
+ }
+ /* set to -1 those fields for which we want to reuse existing
+ * values from the kernel.
+ * Also, *_nr and subtype = 0 mean reuse the value from the kernel.
+ * XXX todo: support reuse of the mask.
+ */
+ if (p)
+ p->bandwidth = -1;
+ for (j = 0; j < nitems(fs->par); j++)
+ fs->par[j] = -1;
+ while (ac > 0) {
+ double d;
+ int tok = match_token(dummynet_params, *av);
+ ac--; av++;
+
+ switch(tok) {
+ case TOK_NOERROR:
+ NEED(fs, "noerror is only for pipes");
+ fs->flags |= DN_NOERROR;
+ break;
+
+ case TOK_PLR:
+ NEED(fs, "plr is only for pipes");
+ NEED1("plr needs one or four arguments 0..1\n");
+ if ((end = strsep(&av[0], ","))) {
+ d = strtod(end, NULL);
+ d = (d < 0) ? 0 : (d <= 1) ? d : 1;
+ fs->plr[0] = (int)(d*0x7fffffff);
+ }
+ if ((end = strsep(&av[0], ","))) {
+ d = strtod(end, NULL);
+ d = (d < 0) ? 0 : (d <= 1) ? d : 1;
+ fs->plr[1] = (int)(d*0x7fffffff);
+ }
+ if ((end = strsep(&av[0], ","))) {
+ d = strtod(end, NULL);
+ d = (d < 0) ? 0 : (d <= 1) ? d : 1;
+ fs->plr[2] = (int)(d*0x7fffffff);
+ }
+ if ((end = strsep(&av[0], ","))) {
+ d = strtod(end, NULL);
+ d = (d < 0) ? 0 : (d <= 1) ? d : 1;
+ fs->plr[3] = (int)(d*0x7fffffff);
+ }
+ ac--; av++;
+ break;
+
+ case TOK_QUEUE:
+ NEED(fs, "queue is only for pipes or flowsets");
+ NEED1("queue needs queue size\n");
+ end = NULL;
+ fs->qsize = strtoul(av[0], &end, 0);
+ if (*end == 'K' || *end == 'k') {
+ fs->flags |= DN_QSIZE_BYTES;
+ fs->qsize *= 1024;
+ } else if (*end == 'B' ||
+ _substrcmp2(end, "by", "bytes") == 0) {
+ fs->flags |= DN_QSIZE_BYTES;
+ }
+ ac--; av++;
+ break;
+
+ case TOK_BUCKETS:
+ NEED(fs, "buckets is only for pipes or flowsets");
+ NEED1("buckets needs argument\n");
+ *buckets = strtoul(av[0], NULL, 0);
+ ac--; av++;
+ break;
+
+ case TOK_FLOW_MASK:
+ case TOK_SCHED_MASK:
+ case TOK_MASK:
+ NEED(mask, "tok_mask");
+ NEED1("mask needs mask specifier\n");
+ /*
+ * per-flow queue, mask is dst_ip, dst_port,
+ * src_ip, src_port, proto measured in bits
+ */
+
+ bzero(mask, sizeof(*mask));
+ end = NULL;
+
+ while (ac >= 1) {
+ uint32_t *p32 = NULL;
+ uint16_t *p16 = NULL;
+ uint32_t *p20 = NULL;
+ struct in6_addr *pa6 = NULL;
+ uint32_t a;
+
+ tok = match_token(dummynet_params, *av);
+ ac--; av++;
+ switch(tok) {
+ case TOK_ALL:
+ /*
+ * special case, all bits significant
+ * except 'extra' (the queue number)
+ */
+ mask->dst_ip = ~0;
+ mask->src_ip = ~0;
+ mask->dst_port = ~0;
+ mask->src_port = ~0;
+ mask->proto = ~0;
+ n2mask(&mask->dst_ip6, 128);
+ n2mask(&mask->src_ip6, 128);
+ mask->flow_id6 = ~0;
+ *flags |= DN_HAVE_MASK;
+ goto end_mask;
+
+ case TOK_QUEUE:
+ mask->extra = ~0;
+ *flags |= DN_HAVE_MASK;
+ goto end_mask;
+
+ case TOK_DSTIP:
+ mask->addr_type = 4;
+ p32 = &mask->dst_ip;
+ break;
+
+ case TOK_SRCIP:
+ mask->addr_type = 4;
+ p32 = &mask->src_ip;
+ break;
+
+ case TOK_DSTIP6:
+ mask->addr_type = 6;
+ pa6 = &mask->dst_ip6;
+ break;
+
+ case TOK_SRCIP6:
+ mask->addr_type = 6;
+ pa6 = &mask->src_ip6;
+ break;
+
+ case TOK_FLOWID:
+ mask->addr_type = 6;
+ p20 = &mask->flow_id6;
+ break;
+
+ case TOK_DSTPORT:
+ p16 = &mask->dst_port;
+ break;
+
+ case TOK_SRCPORT:
+ p16 = &mask->src_port;
+ break;
+
+ case TOK_PROTO:
+ break;
+
+ default:
+ ac++; av--; /* backtrack */
+ goto end_mask;
+ }
+ if (ac < 1)
+ errx(EX_USAGE, "mask: value missing");
+ if (*av[0] == '/') {
+ a = strtoul(av[0]+1, &end, 0);
+ if (pa6 == NULL)
+ a = (a == 32) ? ~0 : (1 << a) - 1;
+ } else
+ a = strtoul(av[0], &end, 0);
+ if (p32 != NULL)
+ *p32 = a;
+ else if (p16 != NULL) {
+ if (a > 0xFFFF)
+ errx(EX_DATAERR,
+ "port mask must be 16 bit");
+ *p16 = (uint16_t)a;
+ } else if (p20 != NULL) {
+ if (a > 0xfffff)
+ errx(EX_DATAERR,
+ "flow_id mask must be 20 bit");
+ *p20 = (uint32_t)a;
+ } else if (pa6 != NULL) {
+ if (a > 128)
+ errx(EX_DATAERR,
+ "in6addr invalid mask len");
+ else
+ n2mask(pa6, a);
+ } else {
+ if (a > 0xFF)
+ errx(EX_DATAERR,
+ "proto mask must be 8 bit");
+ mask->proto = (uint8_t)a;
+ }
+ if (a != 0)
+ *flags |= DN_HAVE_MASK;
+ ac--; av++;
+ } /* end while, config masks */
+end_mask:
+ break;
+#ifdef NEW_AQM
+ case TOK_CODEL:
+ case TOK_PIE:
+ NEED(fs, "codel/pie is only for flowsets");
+
+ fs->flags &= ~(DN_IS_RED|DN_IS_GENTLE_RED);
+ fs->flags |= DN_IS_AQM;
+
+ strlcpy(aqm_extra->name, av[-1],
+ sizeof(aqm_extra->name));
+ aqm_extra->oid.subtype = DN_AQM_PARAMS;
+
+ process_extra_parms(&ac, av, aqm_extra, tok);
+ break;
+
+ case TOK_FQ_CODEL:
+ case TOK_FQ_PIE:
+ if (!strcmp(av[-1],"type"))
+ errx(EX_DATAERR, "use type before fq_codel/fq_pie");
+
+ NEED(sch, "fq_codel/fq_pie is only for schd");
+ strlcpy(sch_extra->name, av[-1],
+ sizeof(sch_extra->name));
+ sch_extra->oid.subtype = DN_SCH_PARAMS;
+ process_extra_parms(&ac, av, sch_extra, tok);
+ break;
+#endif
+ case TOK_RED:
+ case TOK_GRED:
+ NEED1("red/gred needs w_q/min_th/max_th/max_p\n");
+ fs->flags |= DN_IS_RED;
+ if (tok == TOK_GRED)
+ fs->flags |= DN_IS_GENTLE_RED;
+ /*
+ * the format for parameters is w_q/min_th/max_th/max_p
+ */
+ if ((end = strsep(&av[0], "/"))) {
+ double w_q = strtod(end, NULL);
+ if (w_q > 1 || w_q <= 0)
+ errx(EX_DATAERR, "0 < w_q <= 1");
+ fs->w_q = (int) (w_q * (1 << SCALE_RED));
+ }
+ if ((end = strsep(&av[0], "/"))) {
+ fs->min_th = strtoul(end, &end, 0);
+ if (*end == 'K' || *end == 'k')
+ fs->min_th *= 1024;
+ }
+ if ((end = strsep(&av[0], "/"))) {
+ fs->max_th = strtoul(end, &end, 0);
+ if (*end == 'K' || *end == 'k')
+ fs->max_th *= 1024;
+ }
+ if ((end = strsep(&av[0], "/"))) {
+ double max_p = strtod(end, NULL);
+ if (max_p > 1 || max_p < 0)
+ errx(EX_DATAERR, "0 <= max_p <= 1");
+ fs->max_p = (int)(max_p * (1 << SCALE_RED));
+ }
+ ac--; av++;
+ break;
+
+ case TOK_ECN:
+ fs->flags |= DN_IS_ECN;
+ break;
+
+ case TOK_DROPTAIL:
+ NEED(fs, "droptail is only for flowsets");
+ fs->flags &= ~(DN_IS_RED|DN_IS_GENTLE_RED);
+ break;
+
+ case TOK_BW:
+ NEED(p, "bw is only for links");
+ NEED1("bw needs bandwidth or interface\n");
+ read_bandwidth(av[0], &p->bandwidth, NULL, 0);
+ ac--; av++;
+ break;
+
+ case TOK_DELAY:
+ NEED(p, "delay is only for links");
+ NEED1("delay needs argument 0..10000ms\n");
+ p->delay = strtoul(av[0], NULL, 0);
+ ac--; av++;
+ break;
+
+ case TOK_TYPE: {
+ int l;
+ NEED(sch, "type is only for schedulers");
+ NEED1("type needs a string");
+ l = strlen(av[0]);
+ if (l == 0 || l > 15)
+ errx(1, "type %s too long\n", av[0]);
+ strlcpy(sch->name, av[0], sizeof(sch->name));
+ sch->oid.subtype = 0; /* use string */
+#ifdef NEW_AQM
+ /* if fq_codel is selected, consider all tokens after it
+ * as parameters
+ */
+ if (!strcasecmp(av[0],"fq_codel") || !strcasecmp(av[0],"fq_pie")){
+ strlcpy(sch_extra->name, av[0],
+ sizeof(sch_extra->name));
+ sch_extra->oid.subtype = DN_SCH_PARAMS;
+ process_extra_parms(&ac, av, sch_extra, tok);
+ } else {
+ ac--;av++;
+ }
+#else
+ ac--;av++;
+#endif
+ break;
+ }
+
+ case TOK_WEIGHT:
+ NEED(fs, "weight is only for flowsets");
+ NEED1("weight needs argument\n");
+ fs->par[0] = strtol(av[0], &end, 0);
+ ac--; av++;
+ break;
+
+ case TOK_LMAX:
+ NEED(fs, "lmax is only for flowsets");
+ NEED1("lmax needs argument\n");
+ fs->par[1] = strtol(av[0], &end, 0);
+ ac--; av++;
+ break;
+
+ case TOK_PRI:
+ NEED(fs, "priority is only for flowsets");
+ NEED1("priority needs argument\n");
+ fs->par[2] = strtol(av[0], &end, 0);
+ ac--; av++;
+ break;
+
+ case TOK_SCHED:
+ case TOK_PIPE:
+ NEED(fs, "pipe/sched");
+ NEED1("pipe/link/sched needs number\n");
+ fs->sched_nr = strtoul(av[0], &end, 0);
+ ac--; av++;
+ break;
+
+ case TOK_PROFILE:
+ NEED((!pf), "profile already set");
+ NEED(p, "profile");
+ {
+ NEED1("extra delay needs the file name\n");
+ pf = o_next(&buf, sizeof(*pf), DN_PROFILE);
+ load_extra_delays(av[0], pf, p); //XXX can't fail?
+ --ac; ++av;
+ }
+ break;
+
+ case TOK_BURST:
+ NEED(p, "burst");
+ NEED1("burst needs argument\n");
+ errno = 0;
+ if (expand_number(av[0], &p->burst) < 0)
+ if (errno != ERANGE)
+ errx(EX_DATAERR,
+ "burst: invalid argument");
+ if (errno || p->burst > (1ULL << 48) - 1)
+ errx(EX_DATAERR,
+ "burst: out of range (0..2^48-1)");
+ ac--; av++;
+ break;
+
+ default:
+ errx(EX_DATAERR, "unrecognised option ``%s''", av[-1]);
+ }
+ }
+
+ /* check validity of parameters */
+ if (p) {
+ if (p->delay > 10000)
+ errx(EX_DATAERR, "delay must be < 10000");
+ if (p->bandwidth == (uint32_t)-1)
+ p->bandwidth = 0;
+ }
+ if (fs) {
+ /* XXX accept a 0 scheduler to keep the default */
+ if (fs->flags & DN_QSIZE_BYTES) {
+ size_t len;
+ long limit;
+
+ len = sizeof(limit);
+ if (sysctlbyname("net.inet.ip.dummynet.pipe_byte_limit",
+ &limit, &len, NULL, 0) == -1)
+ limit = 1024*1024;
+ if (fs->qsize > limit)
+ errx(EX_DATAERR, "queue size must be < %ldB", limit);
+ } else {
+ size_t len;
+ long limit;
+
+ len = sizeof(limit);
+ if (sysctlbyname("net.inet.ip.dummynet.pipe_slot_limit",
+ &limit, &len, NULL, 0) == -1)
+ limit = 100;
+ if (fs->qsize > limit)
+ errx(EX_DATAERR, "2 <= queue size <= %ld", limit);
+ }
+
+#ifdef NEW_AQM
+ if ((fs->flags & DN_IS_ECN) && !((fs->flags & DN_IS_RED)||
+ (fs->flags & DN_IS_AQM)))
+ errx(EX_USAGE, "ECN can be used with red/gred/"
+ "codel/fq_codel only!");
+#else
+ if ((fs->flags & DN_IS_ECN) && !(fs->flags & DN_IS_RED))
+ errx(EX_USAGE, "enable red/gred for ECN");
+
+#endif
+
+ if (fs->flags & DN_IS_RED) {
+ size_t len;
+ int lookup_depth, avg_pkt_size;
+
+ if (!(fs->flags & DN_IS_ECN) && (fs->min_th >= fs->max_th))
+ errx(EX_DATAERR, "min_th %d must be < than max_th %d",
+ fs->min_th, fs->max_th);
+ else if ((fs->flags & DN_IS_ECN) && (fs->min_th > fs->max_th))
+ errx(EX_DATAERR, "min_th %d must be =< than max_th %d",
+ fs->min_th, fs->max_th);
+
+ if (fs->max_th == 0)
+ errx(EX_DATAERR, "max_th must be > 0");
+
+ len = sizeof(int);
+ if (sysctlbyname("net.inet.ip.dummynet.red_lookup_depth",
+ &lookup_depth, &len, NULL, 0) == -1)
+ lookup_depth = 256;
+ if (lookup_depth == 0)
+ errx(EX_DATAERR, "net.inet.ip.dummynet.red_lookup_depth"
+ " must be greater than zero");
+
+ len = sizeof(int);
+ if (sysctlbyname("net.inet.ip.dummynet.red_avg_pkt_size",
+ &avg_pkt_size, &len, NULL, 0) == -1)
+ avg_pkt_size = 512;
+
+ if (avg_pkt_size == 0)
+ errx(EX_DATAERR,
+ "net.inet.ip.dummynet.red_avg_pkt_size must"
+ " be greater than zero");
+
+#if 0 /* the following computation is now done in the kernel */
+ /*
+ * Ticks needed for sending a medium-sized packet.
+ * Unfortunately, when we are configuring a WF2Q+ queue, we
+ * do not have bandwidth information, because that is stored
+ * in the parent pipe, and also we have multiple queues
+ * competing for it. So we set s=0, which is not very
+ * correct. But on the other hand, why do we want RED with
+ * WF2Q+ ?
+ */
+ if (p.bandwidth==0) /* this is a WF2Q+ queue */
+ s = 0;
+ else
+ s = (double)ck.hz * avg_pkt_size * 8 / p.bandwidth;
+ /*
+ * max idle time (in ticks) before avg queue size becomes 0.
+ * NOTA: (3/w_q) is approx the value x so that
+ * (1-w_q)^x < 10^-3.
+ */
+ w_q = ((double)fs->w_q) / (1 << SCALE_RED);
+ idle = s * 3. / w_q;
+ fs->lookup_step = (int)idle / lookup_depth;
+ if (!fs->lookup_step)
+ fs->lookup_step = 1;
+ weight = 1 - w_q;
+ for (t = fs->lookup_step; t > 1; --t)
+ weight *= 1 - w_q;
+ fs->lookup_weight = (int)(weight * (1 << SCALE_RED));
+#endif /* code moved in the kernel */
+ }
+ }
+
+ i = do_cmd(IP_DUMMYNET3, base, (char *)buf - (char *)base);
+
+ if (i)
+ err(1, "setsockopt(%s)", "IP_DUMMYNET3");
+}
+
+void
+dummynet_flush(void)
+{
+ struct dn_id oid;
+ oid_fill(&oid, sizeof(oid), DN_CMD_FLUSH, DN_API_VERSION);
+ do_cmd(IP_DUMMYNET3, &oid, oid.len);
+}
+
+/* Parse input for 'ipfw [pipe|sched|queue] show [range list]'
+ * Returns the number of ranges, and possibly stores them
+ * in the array v of size len.
+ */
+static int
+parse_range(int ac, char *av[], uint32_t *v, int len)
+{
+ int n = 0;
+ char *endptr, *s;
+ uint32_t base[2];
+
+ if (v == NULL || len < 2) {
+ v = base;
+ len = 2;
+ }
+
+ for (s = *av; s != NULL; av++, ac--) {
+ v[0] = strtoul(s, &endptr, 10);
+ v[1] = (*endptr != '-') ? v[0] :
+ strtoul(endptr+1, &endptr, 10);
+ if (*endptr == '\0') { /* prepare for next round */
+ s = (ac > 0) ? *(av+1) : NULL;
+ } else {
+ if (*endptr != ',') {
+ warn("invalid number: %s", s);
+ s = ++endptr;
+ continue;
+ }
+ /* continue processing from here */
+ s = ++endptr;
+ ac++;
+ av--;
+ }
+ if (v[1] < v[0] ||
+ v[0] >= DN_MAX_ID-1 ||
+ v[1] >= DN_MAX_ID-1) {
+ continue; /* invalid entry */
+ }
+ n++;
+ /* translate if 'pipe list' */
+ if (g_co.do_pipe == 1) {
+ v[0] += DN_MAX_ID;
+ v[1] += DN_MAX_ID;
+ }
+ v = (n*2 < len) ? v + 2 : base;
+ }
+ return n;
+}
+
+/* main entry point for dummynet list functions. co.do_pipe indicates
+ * which function we want to support.
+ * av may contain filtering arguments, either individual entries
+ * or ranges, or lists (space or commas are valid separators).
+ * Format for a range can be n1-n2 or n3 n4 n5 ...
+ * In a range n1 must be <= n2, otherwise the range is ignored.
+ * A number 'n4' is translate in a range 'n4-n4'
+ * All number must be > 0 and < DN_MAX_ID-1
+ */
+void
+dummynet_list(int ac, char *av[], int show_counters)
+{
+ struct dn_id *oid, *x = NULL;
+ int ret, i;
+ int n; /* # of ranges */
+ u_int buflen, l;
+ u_int max_size; /* largest obj passed up */
+
+ (void)show_counters; // XXX unused, but we should use it.
+ ac--;
+ av++; /* skip 'list' | 'show' word */
+
+ n = parse_range(ac, av, NULL, 0); /* Count # of ranges. */
+
+ /* Allocate space to store ranges */
+ l = sizeof(*oid) + sizeof(uint32_t) * n * 2;
+ oid = safe_calloc(1, l);
+ oid_fill(oid, l, DN_CMD_GET, DN_API_VERSION);
+
+ if (n > 0) /* store ranges in idx */
+ parse_range(ac, av, (uint32_t *)(oid + 1), n*2);
+ /*
+ * Compute the size of the largest object returned. If the
+ * response leaves at least this much spare space in the
+ * buffer, then surely the response is complete; otherwise
+ * there might be a risk of truncation and we will need to
+ * retry with a larger buffer.
+ * XXX don't bother with smaller structs.
+ */
+ max_size = sizeof(struct dn_fs);
+ if (max_size < sizeof(struct dn_sch))
+ max_size = sizeof(struct dn_sch);
+ if (max_size < sizeof(struct dn_flow))
+ max_size = sizeof(struct dn_flow);
+
+ switch (g_co.do_pipe) {
+ case 1:
+ oid->subtype = DN_LINK; /* list pipe */
+ break;
+ case 2:
+ oid->subtype = DN_FS; /* list queue */
+ break;
+ case 3:
+ oid->subtype = DN_SCH; /* list sched */
+ break;
+ }
+
+ /*
+ * Ask the kernel an estimate of the required space (result
+ * in oid.id), unless we are requesting a subset of objects,
+ * in which case the kernel does not give an exact answer.
+ * In any case, space might grow in the meantime due to the
+ * creation of new queues, so we must be prepared to retry.
+ */
+ if (n > 0) {
+ buflen = 4*1024;
+ } else {
+ ret = do_cmd(-IP_DUMMYNET3, oid, (uintptr_t)&l);
+ if (ret != 0 || oid->id <= sizeof(*oid))
+ goto done;
+ buflen = oid->id + max_size;
+ oid->len = sizeof(*oid); /* restore */
+ }
+ /* Try a few times, until the buffer fits */
+ for (i = 0; i < 20; i++) {
+ l = buflen;
+ x = safe_realloc(x, l);
+ bcopy(oid, x, oid->len);
+ ret = do_cmd(-IP_DUMMYNET3, x, (uintptr_t)&l);
+ if (ret != 0 || x->id <= sizeof(*oid))
+ goto done; /* no response */
+ if (l + max_size <= buflen)
+ break; /* ok */
+ buflen *= 2; /* double for next attempt */
+ }
+ list_pipes(x, O_NEXT(x, l));
+done:
+ if (x)
+ free(x);
+ free(oid);
+}
diff --git a/sbin/ipfw15/include15/alias15.h b/sbin/ipfw15/include15/alias15.h
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/include15/alias15.h
@@ -0,0 +1,259 @@
+/* lint -save -library Flexelint comment for external headers */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2001 Charles Mott <cm@linktel.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Alias.h defines the outside world interfaces for the packet aliasing
+ * software.
+ *
+ * This software is placed into the public domain with no restrictions on its
+ * distribution.
+ */
+
+#ifndef _ALIAS_H_
+#define _ALIAS_H_
+
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+#define LIBALIAS_BUF_SIZE 128
+#ifdef _KERNEL
+/*
+ * The kernel version of libalias does not support these features.
+ */
+#define NO_FW_PUNCH
+#define NO_USE_SOCKETS
+#endif
+
+/*
+ * The external interface to libalias, the packet aliasing engine.
+ *
+ * There are two sets of functions:
+ *
+ * PacketAlias*() the old API which doesn't take an instance pointer
+ * and therefore can only have one packet engine at a time.
+ *
+ * LibAlias*() the new API which takes as first argument a pointer to
+ * the instance of the packet aliasing engine.
+ *
+ * The functions otherwise correspond to each other one for one, except
+ * for the LibAliasUnaliasOut()/PacketUnaliasOut() function which were
+ * were misnamed in the old API.
+ */
+
+/*
+ * The instance structure
+ */
+struct libalias;
+
+/*
+ * An anonymous structure, a pointer to which is returned from
+ * PacketAliasRedirectAddr(), PacketAliasRedirectPort() or
+ * PacketAliasRedirectProto(), passed to PacketAliasAddServer(),
+ * and freed by PacketAliasRedirectDelete().
+ */
+struct alias_link;
+
+/* Initialization and control functions. */
+struct libalias *LibAliasInit(struct libalias *);
+void LibAliasSetAddress(struct libalias *, struct in_addr _addr);
+void LibAliasSetAliasPortRange(struct libalias *la, u_short port_low, u_short port_hi);
+void LibAliasSetFWBase(struct libalias *, unsigned int _base, unsigned int _num);
+void LibAliasSetSkinnyPort(struct libalias *, unsigned int _port);
+unsigned int LibAliasSetMode(struct libalias *, unsigned int _flags, unsigned int _mask);
+void LibAliasUninit(struct libalias *);
+
+/* Packet Handling functions. */
+int LibAliasIn (struct libalias *, void *_ptr, int _maxpacketsize);
+int LibAliasOut(struct libalias *, void *_ptr, int _maxpacketsize);
+int LibAliasOutTry(struct libalias *, void *_ptr, int _maxpacketsize, int _create);
+int LibAliasUnaliasOut(struct libalias *, void *_ptr, int _maxpacketsize);
+
+/* Port and address redirection functions. */
+
+int LibAliasAddServer(struct libalias *, struct alias_link *_lnk,
+ struct in_addr _addr, unsigned short _port);
+struct alias_link * LibAliasRedirectAddr(struct libalias *, struct in_addr _src_addr,
+ struct in_addr _alias_addr);
+int LibAliasRedirectDynamic(struct libalias *, struct alias_link *_lnk);
+void LibAliasRedirectDelete(struct libalias *, struct alias_link *_lnk);
+struct alias_link * LibAliasRedirectPort(struct libalias *, struct in_addr _src_addr,
+ unsigned short _src_port, struct in_addr _dst_addr,
+ unsigned short _dst_port, struct in_addr _alias_addr,
+ unsigned short _alias_port, unsigned char _proto);
+struct alias_link * LibAliasRedirectProto(struct libalias *, struct in_addr _src_addr,
+ struct in_addr _dst_addr, struct in_addr _alias_addr,
+ unsigned char _proto);
+
+/* Fragment Handling functions. */
+void LibAliasFragmentIn(struct libalias *, void *_ptr, void *_ptr_fragment);
+void *LibAliasGetFragment(struct libalias *, void *_ptr);
+int LibAliasSaveFragment(struct libalias *, void *_ptr);
+
+/* Miscellaneous functions. */
+unsigned short LibAliasInternetChecksum(struct libalias *, unsigned short *_ptr, int _nbytes);
+void LibAliasSetTarget(struct libalias *, struct in_addr _target_addr);
+
+/* Transparent proxying routines. */
+int LibAliasProxyRule(struct libalias *, const char *_cmd);
+
+/* Module handling API */
+int LibAliasLoadModule(char *);
+int LibAliasUnLoadAllModule(void);
+int LibAliasRefreshModules(void);
+
+/* Mbuf helper function. */
+struct mbuf *m_megapullup(struct mbuf *, int);
+
+/*
+ * Mode flags and other constants.
+ */
+
+/* Mode flags, set using PacketAliasSetMode() */
+
+/*
+ * If PKT_ALIAS_LOG is set, a message will be printed to /var/log/alias.log
+ * every time a link is created or deleted. This is useful for debugging.
+ */
+#define PKT_ALIAS_LOG 0x01
+
+/*
+ * If PKT_ALIAS_DENY_INCOMING is set, then incoming connections (e.g. to ftp,
+ * telnet or web servers will be prevented by the aliasing mechanism.
+ */
+#define PKT_ALIAS_DENY_INCOMING 0x02
+
+/*
+ * If PKT_ALIAS_SAME_PORTS is set, packets will be attempted sent from the
+ * same port as they originated on. This allows e.g. rsh to work *99% of the
+ * time*, but _not_ 100% (it will be slightly flakey instead of not working
+ * at all). This mode bit is set by PacketAliasInit(), so it is a default
+ * mode of operation.
+ */
+#define PKT_ALIAS_SAME_PORTS 0x04
+
+/*
+ * If PKT_ALIAS_USE_SOCKETS is set, then when partially specified links (e.g.
+ * destination port and/or address is zero), the packet aliasing engine will
+ * attempt to allocate a socket for the aliasing port it chooses. This will
+ * avoid interference with the host machine. Fully specified links do not
+ * require this. This bit is set after a call to PacketAliasInit(), so it is
+ * a default mode of operation.
+ */
+#ifndef NO_USE_SOCKETS
+#define PKT_ALIAS_USE_SOCKETS 0x08
+#endif
+/*-
+ * If PKT_ALIAS_UNREGISTERED_ONLY is set, then only packets with
+ * unregistered source addresses will be aliased. Private
+ * addresses are those in the following ranges:
+ *
+ * 10.0.0.0 -> 10.255.255.255
+ * 172.16.0.0 -> 172.31.255.255
+ * 192.168.0.0 -> 192.168.255.255
+ */
+#define PKT_ALIAS_UNREGISTERED_ONLY 0x10
+
+/*
+ * If PKT_ALIAS_RESET_ON_ADDR_CHANGE is set, then the table of dynamic
+ * aliasing links will be reset whenever PacketAliasSetAddress() changes the
+ * default aliasing address. If the default aliasing address is left
+ * unchanged by this function call, then the table of dynamic aliasing links
+ * will be left intact. This bit is set after a call to PacketAliasInit().
+ */
+#define PKT_ALIAS_RESET_ON_ADDR_CHANGE 0x20
+
+/*
+ * If PKT_ALIAS_PROXY_ONLY is set, then NAT will be disabled and only
+ * transparent proxying is performed.
+ */
+#define PKT_ALIAS_PROXY_ONLY 0x40
+
+/*
+ * If PKT_ALIAS_REVERSE is set, the actions of PacketAliasIn() and
+ * PacketAliasOut() are reversed.
+ */
+#define PKT_ALIAS_REVERSE 0x80
+
+#ifndef NO_FW_PUNCH
+/*
+ * If PKT_ALIAS_PUNCH_FW is set, active FTP and IRC DCC connections will
+ * create a 'hole' in the firewall to allow the transfers to work. The
+ * ipfw rule number that the hole is created with is controlled by
+ * PacketAliasSetFWBase(). The hole will be attached to that
+ * particular alias_link, so when the link goes away the hole is deleted.
+ */
+#define PKT_ALIAS_PUNCH_FW 0x100
+#endif
+
+/*
+ * If PKT_ALIAS_SKIP_GLOBAL is set, nat instance is not checked for matching
+ * states in 'ipfw nat global' rule.
+ */
+#define PKT_ALIAS_SKIP_GLOBAL 0x200
+
+/*
+ * Like PKT_ALIAS_UNREGISTERED_ONLY, but includes the RFC 6598
+ * (Carrier Grade NAT) address range as follows:
+ *
+ * 100.64.0.0 -> 100.127.255.255
+ */
+#define PKT_ALIAS_UNREGISTERED_CGN 0x400
+
+/*
+ * When this bit is set, UDP uses endpoint-independent mapping (EIM), as per
+ * RFC 4787 ("full cone" NAT of RFC 3489). All packets from the same internal
+ * address:port are mapped to the same NAT address:port, regardless of their
+ * destination address:port. If filtering rules allow, and if
+ * PKT_ALIAS_DENY_INCOMING is unset, any other external address:port can also
+ * send to the internal address:port through its mapped NAT address:port. This
+ * is more compatible with applications, and can reduce the need for port
+ * forwarding, but less scalable as each NAT address:port can only be
+ * concurrently used by at most one internal address:port.
+ *
+ * When this bit is unset, UDP packets use endpoint-dependent mapping (EDM)
+ * ("symmetric" NAT). Each connection from a particular internal address:port
+ * to different external addresses:ports is mapped to a random and
+ * unpredictable NAT address:port. Two appplications behind EDM NATs can only
+ * connect to each other by port forwarding on the NAT, or tunnelling through
+ * an in-between server.
+ */
+#define PKT_ALIAS_UDP_EIM 0x800
+
+/* Function return codes. */
+#define PKT_ALIAS_ERROR -1
+#define PKT_ALIAS_OK 1
+#define PKT_ALIAS_IGNORED 2
+#define PKT_ALIAS_UNRESOLVED_FRAGMENT 3
+#define PKT_ALIAS_FOUND_HEADER_FRAGMENT 4
+
+#endif /* !_ALIAS_H_ */
+
+/* lint -restore */
diff --git a/sbin/ipfw15/include15/netinet/ip_dummynet15.h b/sbin/ipfw15/include15/netinet/ip_dummynet15.h
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/include15/netinet/ip_dummynet15.h
@@ -0,0 +1,284 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 1998-2010 Luigi Rizzo, Universita` di Pisa
+ * Portions Copyright (c) 2000 Akamba Corp.
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _IP_DUMMYNET_H
+#define _IP_DUMMYNET_H
+#define NEW_AQM
+/*
+ * Definition of the kernel-userland API for dummynet.
+ *
+ * Setsockopt() and getsockopt() pass a batch of objects, each
+ * of them starting with a "struct dn_id" which should fully identify
+ * the object and its relation with others in the sequence.
+ * The first object in each request should have
+ * type= DN_CMD_*, id = DN_API_VERSION.
+ * For other objects, type and subtype specify the object, len indicates
+ * the total length including the header, and 'id' identifies the specific
+ * object.
+ *
+ * Most objects are numbered with an identifier in the range 1..65535.
+ * DN_MAX_ID indicates the first value outside the range.
+ */
+
+#define DN_API_VERSION 12500000
+#define DN_MAX_ID 0x10000
+
+struct dn_id {
+ uint16_t len; /* total obj len including this header */
+ uint8_t type;
+ uint8_t subtype;
+ uint32_t id; /* generic id */
+};
+
+/*
+ * These values are in the type field of struct dn_id.
+ * To preserve the ABI, never rearrange the list or delete
+ * entries with the exception of DN_LAST
+ */
+enum {
+ DN_NONE = 0,
+ DN_LINK = 1,
+ DN_FS,
+ DN_SCH,
+ DN_SCH_I,
+ DN_QUEUE,
+ DN_DELAY_LINE,
+ DN_PROFILE,
+ DN_FLOW, /* struct dn_flow */
+ DN_TEXT, /* opaque text is the object */
+
+ DN_CMD_CONFIG = 0x80, /* objects follow */
+ DN_CMD_DELETE, /* subtype + list of entries */
+ DN_CMD_GET, /* subtype + list of entries */
+ DN_CMD_FLUSH,
+ /* for compatibility with FreeBSD 7.2/8 */
+ DN_COMPAT_PIPE,
+ DN_COMPAT_QUEUE,
+ DN_GET_COMPAT,
+
+ /* special commands for emulation of sysctl variables */
+ DN_SYSCTL_GET,
+ DN_SYSCTL_SET,
+#ifdef NEW_AQM
+ /* subtypes used for setting/getting extra parameters.
+ * these subtypes used with IP_DUMMYNET3 command (get)
+ * and DN_TEXT (set). */
+ DN_AQM_PARAMS, /* AQM extra params */
+ DN_SCH_PARAMS, /* scheduler extra params */
+#endif
+ DN_LAST,
+};
+
+enum { /* subtype for schedulers, flowset and the like */
+ DN_SCHED_UNKNOWN = 0,
+ DN_SCHED_FIFO = 1,
+ DN_SCHED_WF2QP = 2,
+ /* others are in individual modules */
+};
+
+enum { /* user flags */
+ DN_HAVE_MASK = 0x0001, /* fs or sched has a mask */
+ DN_NOERROR = 0x0002, /* do not report errors */
+ DN_QHT_HASH = 0x0004, /* qht is a hash table */
+ DN_QSIZE_BYTES = 0x0008, /* queue size is in bytes */
+ DN_HAS_PROFILE = 0x0010, /* a link has a profile */
+ DN_IS_RED = 0x0020,
+ DN_IS_GENTLE_RED= 0x0040,
+ DN_IS_ECN = 0x0080,
+ #ifdef NEW_AQM
+ DN_IS_AQM = 0x0100, /* AQMs: e.g Codel & PIE */
+ #endif
+ DN_PIPE_CMD = 0x1000, /* pipe config... */
+};
+
+/*
+ * link template.
+ */
+struct dn_link {
+ struct dn_id oid;
+
+ /*
+ * Userland sets bw and delay in bits/s and milliseconds.
+ * The kernel converts this back and forth to bits/tick and ticks.
+ * XXX what about burst ?
+ */
+ int32_t link_nr;
+ uint32_t bandwidth; /* bit/s or bits/tick. */
+ int delay; /* ms and ticks */
+ uint64_t burst; /* scaled. bits*Hz XXX */
+};
+
+/*
+ * A flowset, which is a template for flows. Contains parameters
+ * from the command line: id, target scheduler, queue sizes, plr,
+ * flow masks, buckets for the flow hash, and possibly scheduler-
+ * specific parameters (weight, quantum and so on).
+ */
+struct dn_fs {
+ struct dn_id oid;
+ uint32_t fs_nr; /* the flowset number */
+ uint32_t flags; /* userland flags */
+ int qsize; /* queue size in slots or bytes */
+ int32_t pl_state; /* packet loss state */
+ uint32_t buckets; /* buckets used for the queue hash table */
+
+ struct ipfw_flow_id flow_mask;
+ uint32_t sched_nr; /* the scheduler we attach to */
+ /* generic scheduler parameters. Leave them at -1 if unset.
+ * Now we use 0: weight, 1: lmax, 2: priority
+ */
+ int par[4];
+
+ /* RED/GRED parameters.
+ * weight and probabilities are in the range 0..1 represented
+ * in fixed point arithmetic with SCALE_RED decimal bits.
+ */
+#define SCALE_RED 16
+#define SCALE(x) ( (x) << SCALE_RED )
+#define SCALE_VAL(x) ( (x) >> SCALE_RED )
+#define SCALE_MUL(x,y) ( ( (x) * (y) ) >> SCALE_RED )
+ int w_q ; /* queue weight (scaled) */
+ int max_th ; /* maximum threshold for queue (scaled) */
+ int min_th ; /* minimum threshold for queue (scaled) */
+ int max_p ; /* maximum value for p_b (scaled) */
+
+ int32_t plr[4]; /* PLR, pkt loss rate (2^31-1 means 100%) */
+};
+
+/*
+ * dn_flow collects flow_id and stats for queues and scheduler
+ * instances, and is used to pass these info to userland.
+ * oid.type/oid.subtype describe the object, oid.id is number
+ * of the parent object.
+ */
+struct dn_flow {
+ struct dn_id oid;
+ struct ipfw_flow_id fid;
+ uint64_t tot_pkts; /* statistics counters */
+ uint64_t tot_bytes;
+ uint32_t length; /* Queue length, in packets */
+ uint32_t len_bytes; /* Queue length, in bytes */
+ uint32_t drops;
+};
+
+/*
+ * Scheduler template, mostly indicating the name, number,
+ * sched_mask and buckets.
+ */
+struct dn_sch {
+ struct dn_id oid;
+ uint32_t sched_nr; /* N, scheduler number */
+ uint32_t buckets; /* number of buckets for the instances */
+ uint32_t flags; /* have_mask, ... */
+
+ char name[16]; /* null terminated */
+ /* mask to select the appropriate scheduler instance */
+ struct ipfw_flow_id sched_mask; /* M */
+};
+
+/* A delay profile is attached to a link.
+ * Note that a profile, as any other object, cannot be longer than 2^16
+ */
+#define ED_MAX_SAMPLES_NO 1024
+struct dn_profile {
+ struct dn_id oid;
+ /* fields to simulate a delay profile */
+#define ED_MAX_NAME_LEN 32
+ char name[ED_MAX_NAME_LEN];
+ int link_nr;
+ int loss_level;
+ uint32_t bandwidth; // XXX use link bandwidth?
+ int samples_no; /* actual len of samples[] */
+ int samples[ED_MAX_SAMPLES_NO]; /* may be shorter */
+};
+
+#ifdef NEW_AQM
+/* Extra parameters for AQM and scheduler.
+ * This struct is used to pass and retrieve parameters (configurations)
+ * to/from AQM and Scheduler.
+ */
+struct dn_extra_parms {
+ struct dn_id oid;
+ char name[16];
+ uint32_t nr;
+#define DN_MAX_EXTRA_PARM 10
+ int64_t par[DN_MAX_EXTRA_PARM];
+};
+#endif
+
+/*
+ * Overall structure of dummynet
+
+In dummynet, packets are selected with the firewall rules, and passed
+to two different objects: PIPE or QUEUE (bad name).
+
+A QUEUE defines a classifier, which groups packets into flows
+according to a 'mask', puts them into independent queues (one
+per flow) with configurable size and queue management policy,
+and passes flows to a scheduler:
+
+ (flow_mask|sched_mask) sched_mask
+ +---------+ weight Wx +-------------+
+ | |->-[flow]-->--| |-+
+ -->--| QUEUE x | ... | | |
+ | |->-[flow]-->--| SCHEDuler N | |
+ +---------+ | | |
+ ... | +--[LINK N]-->--
+ +---------+ weight Wy | | +--[LINK N]-->--
+ | |->-[flow]-->--| | |
+ -->--| QUEUE y | ... | | |
+ | |->-[flow]-->--| | |
+ +---------+ +-------------+ |
+ +-------------+
+
+Many QUEUE objects can connect to the same scheduler, each
+QUEUE object can have its own set of parameters.
+
+In turn, the SCHEDuler 'forks' multiple instances according
+to a 'sched_mask', each instance manages its own set of queues
+and transmits on a private instance of a configurable LINK.
+
+A PIPE is a simplified version of the above, where there
+is no flow_mask, and each scheduler instance handles a single queue.
+
+The following data structures (visible from userland) describe
+the objects used by dummynet:
+
+ + dn_link, contains the main configuration parameters related
+ to delay and bandwidth;
+ + dn_profile describes a delay profile;
+ + dn_flow describes the flow status (flow id, statistics)
+
+ + dn_sch describes a scheduler
+ + dn_fs describes a flowset (msk, weight, queue parameters)
+
+ *
+ */
+
+#endif /* _IP_DUMMYNET_H */
diff --git a/sbin/ipfw15/include15/netinet/ip_fw15.h b/sbin/ipfw15/include15/netinet/ip_fw15.h
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/include15/netinet/ip_fw15.h
@@ -0,0 +1,1172 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2002-2009 Luigi Rizzo, Universita` di Pisa
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _IPFW2_H
+#define _IPFW2_H
+
+/*
+ * The default rule number. By the design of ip_fw, the default rule
+ * is the last one, so its number can also serve as the highest number
+ * allowed for a rule. The ip_fw code relies on both meanings of this
+ * constant.
+ */
+#define IPFW_DEFAULT_RULE 65535
+
+#define RESVD_SET 31 /*set for default and persistent rules*/
+#define IPFW_MAX_SETS 32 /* Number of sets supported by ipfw*/
+
+/*
+ * Compat values for old clients
+ */
+#ifndef _KERNEL
+#define IPFW_TABLES_MAX 65535
+#define IPFW_TABLES_DEFAULT 128
+#endif
+
+/*
+ * Most commands (queue, pipe, tag, untag, limit...) can have a 16-bit
+ * argument between 1 and 65534. The value 0 (IP_FW_TARG) is used
+ * to represent 'tablearg' value, e.g. indicate the use of a 'tablearg'
+ * result of the most recent table() lookup.
+ * Note that 16bit is only a historical limit, resulting from
+ * the use of a 16-bit fields for that value. In reality, we can have
+ * 2^32 pipes, queues, tag values and so on.
+ */
+#define IPFW_ARG_MIN 1
+#define IPFW_ARG_MAX 65534
+#define IP_FW_TABLEARG 65535 /* Compat value for old clients */
+#define IP_FW_TARG 0 /* Current tablearg value */
+#define IP_FW_NAT44_GLOBAL 65535 /* arg1 value for "nat global" */
+
+/*
+ * Number of entries in the call stack of the call/return commands.
+ * Call stack currently is an uint16_t array with rule numbers.
+ */
+#define IPFW_CALLSTACK_SIZE 16
+
+/* IP_FW3 header/opcodes */
+typedef struct _ip_fw3_opheader {
+ uint16_t opcode; /* Operation opcode */
+ uint16_t version; /* Opcode version */
+ uint16_t reserved[2]; /* Align to 64-bit boundary */
+} ip_fw3_opheader;
+
+#define IP_FW3_OPVER_0 0
+#define IP_FW3_OPVER_1 1 /* 32bit rulenum */
+#define IP_FW3_OPVER IP_FW3_OPVER_1
+
+/* IP_FW3 opcodes */
+#define IP_FW_TABLE_XADD 86 /* add entry */
+#define IP_FW_TABLE_XDEL 87 /* delete entry */
+#define IP_FW_TABLE_XGETSIZE 88 /* get table size (deprecated) */
+#define IP_FW_TABLE_XLIST 89 /* list table contents */
+#define IP_FW_TABLE_XDESTROY 90 /* destroy table */
+#define IP_FW_TABLES_XLIST 92 /* list all tables */
+#define IP_FW_TABLE_XINFO 93 /* request info for one table */
+#define IP_FW_TABLE_XFLUSH 94 /* flush table data */
+#define IP_FW_TABLE_XCREATE 95 /* create new table */
+#define IP_FW_TABLE_XMODIFY 96 /* modify existing table */
+#define IP_FW_XGET 97 /* Retrieve configuration */
+#define IP_FW_XADD 98 /* add rule */
+#define IP_FW_XDEL 99 /* del rule */
+#define IP_FW_XMOVE 100 /* move rules to different set */
+#define IP_FW_XZERO 101 /* clear accounting */
+#define IP_FW_XRESETLOG 102 /* zero rules logs */
+#define IP_FW_SET_SWAP 103 /* Swap between 2 sets */
+#define IP_FW_SET_MOVE 104 /* Move one set to another one */
+#define IP_FW_SET_ENABLE 105 /* Enable/disable sets */
+#define IP_FW_TABLE_XFIND 106 /* finds an entry */
+#define IP_FW_XIFLIST 107 /* list tracked interfaces */
+#define IP_FW_TABLES_ALIST 108 /* list table algorithms */
+#define IP_FW_TABLE_XSWAP 109 /* swap two tables */
+#define IP_FW_TABLE_VLIST 110 /* dump table value hash */
+
+#define IP_FW_NAT44_XCONFIG 111 /* Create/modify NAT44 instance */
+#define IP_FW_NAT44_DESTROY 112 /* Destroys NAT44 instance */
+#define IP_FW_NAT44_XGETCONFIG 113 /* Get NAT44 instance config */
+#define IP_FW_NAT44_LIST_NAT 114 /* List all NAT44 instances */
+#define IP_FW_NAT44_XGETLOG 115 /* Get log from NAT44 instance */
+
+#define IP_FW_DUMP_SOPTCODES 116 /* Dump available sopts/versions */
+#define IP_FW_DUMP_SRVOBJECTS 117 /* Dump existing named objects */
+#define IP_FW_SKIPTO_CACHE 118 /* Manage skipto cache */
+
+#define IP_FW_NAT64STL_CREATE 130 /* Create stateless NAT64 instance */
+#define IP_FW_NAT64STL_DESTROY 131 /* Destroy stateless NAT64 instance */
+#define IP_FW_NAT64STL_CONFIG 132 /* Modify stateless NAT64 instance */
+#define IP_FW_NAT64STL_LIST 133 /* List stateless NAT64 instances */
+#define IP_FW_NAT64STL_STATS 134 /* Get NAT64STL instance statistics */
+#define IP_FW_NAT64STL_RESET_STATS 135 /* Reset NAT64STL instance statistics */
+
+#define IP_FW_NAT64LSN_CREATE 140 /* Create stateful NAT64 instance */
+#define IP_FW_NAT64LSN_DESTROY 141 /* Destroy stateful NAT64 instance */
+#define IP_FW_NAT64LSN_CONFIG 142 /* Modify stateful NAT64 instance */
+#define IP_FW_NAT64LSN_LIST 143 /* List stateful NAT64 instances */
+#define IP_FW_NAT64LSN_STATS 144 /* Get NAT64LSN instance statistics */
+#define IP_FW_NAT64LSN_LIST_STATES 145 /* Get stateful NAT64 states */
+#define IP_FW_NAT64LSN_RESET_STATS 146 /* Reset NAT64LSN instance statistics */
+
+#define IP_FW_NPTV6_CREATE 150 /* Create NPTv6 instance */
+#define IP_FW_NPTV6_DESTROY 151 /* Destroy NPTv6 instance */
+#define IP_FW_NPTV6_CONFIG 152 /* Modify NPTv6 instance */
+#define IP_FW_NPTV6_LIST 153 /* List NPTv6 instances */
+#define IP_FW_NPTV6_STATS 154 /* Get NPTv6 instance statistics */
+#define IP_FW_NPTV6_RESET_STATS 155 /* Reset NPTv6 instance statistics */
+
+#define IP_FW_NAT64CLAT_CREATE 160 /* Create clat NAT64 instance */
+#define IP_FW_NAT64CLAT_DESTROY 161 /* Destroy clat NAT64 instance */
+#define IP_FW_NAT64CLAT_CONFIG 162 /* Modify clat NAT64 instance */
+#define IP_FW_NAT64CLAT_LIST 163 /* List clat NAT64 instances */
+#define IP_FW_NAT64CLAT_STATS 164 /* Get NAT64CLAT instance statistics */
+#define IP_FW_NAT64CLAT_RESET_STATS 165 /* Reset NAT64CLAT instance statistics */
+
+/*
+ * The kernel representation of ipfw rules is made of a list of
+ * 'instructions' (for all practical purposes equivalent to BPF
+ * instructions), which specify which fields of the packet
+ * (or its metadata) should be analysed.
+ *
+ * Each instruction is stored in a structure which begins with
+ * "ipfw_insn", and can contain extra fields depending on the
+ * instruction type (listed below).
+ * Note that the code is written so that individual instructions
+ * have a size which is a multiple of 32 bits. This means that, if
+ * such structures contain pointers or other 64-bit entities,
+ * (there is just one instance now) they may end up unaligned on
+ * 64-bit architectures, so the must be handled with care.
+ *
+ * "enum ipfw_opcodes" are the opcodes supported. We can have up
+ * to 256 different opcodes. When adding new opcodes, they should
+ * be appended to the end of the opcode list before O_LAST_OPCODE,
+ * this will prevent the ABI from being broken, otherwise users
+ * will have to recompile ipfw(8) when they update the kernel.
+ */
+
+enum ipfw_opcodes { /* arguments (4 byte each) */
+ O_NOP = 0,
+
+ O_IP_SRC = 1, /* u32 = IP */
+ O_IP_SRC_MASK = 2, /* ip = IP/mask */
+ O_IP_SRC_ME = 3, /* none */
+ O_IP_SRC_SET = 4, /* u32=base, arg1=len, bitmap */
+
+ O_IP_DST = 5, /* u32 = IP */
+ O_IP_DST_MASK = 6, /* ip = IP/mask */
+ O_IP_DST_ME = 7, /* none */
+ O_IP_DST_SET = 8, /* u32=base, arg1=len, bitmap */
+
+ O_IP_SRCPORT = 9, /* (n)port list:mask 4 byte ea */
+ O_IP_DSTPORT = 10, /* (n)port list:mask 4 byte ea */
+ O_PROTO = 11, /* arg1=protocol */
+
+ O_MACADDR2 = 12, /* 2 mac addr:mask */
+ O_MAC_TYPE = 13, /* same as srcport */
+
+ O_LAYER2 = 14, /* none */
+ O_IN = 15, /* none */
+ O_FRAG = 16, /* none */
+
+ O_RECV = 17, /* none */
+ O_XMIT = 18, /* none */
+ O_VIA = 19, /* none */
+
+ O_IPOPT = 20, /* arg1 = 2*u8 bitmap */
+ O_IPLEN = 21, /* arg1 = len */
+ O_IPID = 22, /* arg1 = id */
+
+ O_IPTOS = 23, /* arg1 = id */
+ O_IPPRECEDENCE = 24, /* arg1 = precedence << 5 */
+ O_IPTTL = 25, /* arg1 = TTL */
+
+ O_IPVER = 26, /* arg1 = version */
+ O_UID = 27, /* u32 = id */
+ O_GID = 28, /* u32 = id */
+ O_ESTAB = 29, /* none (tcp established) */
+ O_TCPFLAGS = 30, /* arg1 = 2*u8 bitmap */
+ O_TCPWIN = 31, /* arg1 = desired win */
+ O_TCPSEQ = 32, /* u32 = desired seq. */
+ O_TCPACK = 33, /* u32 = desired seq. */
+ O_ICMPTYPE = 34, /* u32 = icmp bitmap */
+ O_TCPOPTS = 35, /* arg1 = 2*u8 bitmap */
+
+ O_VERREVPATH = 36, /* none */
+ O_VERSRCREACH = 37, /* none */
+
+ O_PROBE_STATE = 38, /* v0:arg1=kidx, v1:kidx=kidx */
+ O_KEEP_STATE = 39, /* v0:arg1=kidx, v1:kidx=kidx */
+ O_LIMIT = 40, /* ipfw_insn_limit */
+ O_LIMIT_PARENT = 41, /* dyn_type, not an opcode. */
+
+ /*
+ * These are really 'actions'.
+ */
+
+ O_LOG = 42, /* ipfw_insn_log */
+ O_PROB = 43, /* u32 = match probability */
+
+ O_CHECK_STATE = 44, /* v0:arg1=kidx, v1:kidx=kidx */
+ O_ACCEPT = 45, /* none */
+ O_DENY = 46, /* none */
+ O_REJECT = 47, /* arg1=icmp arg (same as deny) */
+ O_COUNT = 48, /* none */
+ O_SKIPTO = 49, /* v0:arg1=next rule number */
+ /* v1:kidx= next rule number */
+ O_PIPE = 50, /* arg1=pipe number */
+ O_QUEUE = 51, /* arg1=queue number */
+ O_DIVERT = 52, /* arg1=port number */
+ O_TEE = 53, /* arg1=port number */
+ O_FORWARD_IP = 54, /* fwd sockaddr */
+ O_FORWARD_MAC = 55, /* fwd mac */
+ O_NAT = 56, /* nope */
+ O_REASS = 57, /* none */
+
+ /*
+ * More opcodes.
+ */
+ O_IPSEC = 58, /* has ipsec history */
+ O_IP_SRC_LOOKUP = 59, /* v0:arg1=table number, u32=value */
+ /* v1:kidx=name, u32=value, arg1=key */
+ O_IP_DST_LOOKUP = 60, /* arg1=table number, u32=value */
+ /* v1:kidx=name, u32=value, arg1=key */
+ O_ANTISPOOF = 61, /* none */
+ O_JAIL = 62, /* u32 = id */
+ O_ALTQ = 63, /* u32 = altq classif. qid */
+ O_DIVERTED = 64, /* arg1=bitmap (1:loop, 2:out) */
+ O_TCPDATALEN = 65, /* arg1 = tcp data len */
+ O_IP6_SRC = 66, /* address without mask */
+ O_IP6_SRC_ME = 67, /* my addresses */
+ O_IP6_SRC_MASK = 68, /* address with the mask */
+ O_IP6_DST = 69,
+ O_IP6_DST_ME = 70,
+ O_IP6_DST_MASK = 71,
+ O_FLOW6ID = 72, /* for flow id tag in the ipv6 pkt */
+ O_ICMP6TYPE = 73, /* icmp6 packet type filtering */
+ O_EXT_HDR = 74, /* filtering for ipv6 extension header */
+ O_IP6 = 75,
+
+ /*
+ * actions for ng_ipfw
+ */
+ O_NETGRAPH = 76, /* send to ng_ipfw */
+ O_NGTEE = 77, /* copy to ng_ipfw */
+
+ O_IP4 = 78,
+
+ O_UNREACH6 = 79, /* arg1=icmpv6 code arg (deny) */
+
+ O_TAG = 80, /* arg1=tag number */
+ O_TAGGED = 81, /* arg1=tag number */
+
+ O_SETFIB = 82, /* arg1=FIB number */
+ O_FIB = 83, /* arg1=FIB desired fib number */
+
+ O_SOCKARG = 84, /* socket argument */
+
+ O_CALLRETURN = 85, /* v0:arg1=called rule number */
+ /* v1:kidx=called rule number */
+
+ O_FORWARD_IP6 = 86, /* fwd sockaddr_in6 */
+
+ O_DSCP = 87, /* 2 u32 = DSCP mask */
+ O_SETDSCP = 88, /* arg1=DSCP value */
+ O_IP_FLOW_LOOKUP = 89, /* v0:arg1=table number, u32=value */
+ /* v1:kidx=name, u32=value */
+
+ O_EXTERNAL_ACTION = 90, /* v0:arg1=id of external action handler */
+ /* v1:kidx=id of external action handler */
+ O_EXTERNAL_INSTANCE = 91, /* v0:arg1=id of eaction handler instance */
+ /* v1:kidx=id of eaction handler instance */
+ O_EXTERNAL_DATA = 92, /* variable length data */
+
+ O_SKIP_ACTION = 93, /* none */
+ O_TCPMSS = 94, /* arg1=MSS value */
+
+ O_MAC_SRC_LOOKUP = 95, /* kidx=name, u32=value, arg1=key */
+ O_MAC_DST_LOOKUP = 96, /* kidx=name, u32=value, arg1=key */
+
+ O_SETMARK = 97, /* u32 = value */
+ O_MARK = 98, /* 2 u32 = value, bitmask */
+
+ O_LAST_OPCODE /* not an opcode! */
+};
+
+/*
+ * The extension header are filtered only for presence using a bit
+ * vector with a flag for each header.
+ */
+#define EXT_FRAGMENT 0x1
+#define EXT_HOPOPTS 0x2
+#define EXT_ROUTING 0x4
+#define EXT_AH 0x8
+#define EXT_ESP 0x10
+#define EXT_DSTOPTS 0x20
+#define EXT_RTHDR0 0x40
+#define EXT_RTHDR2 0x80
+
+/*
+ * Template for instructions.
+ *
+ * ipfw_insn is used for all instructions which require no operands,
+ * a single 16-bit value (arg1), or a couple of 8-bit values.
+ *
+ * For other instructions which require different/larger arguments
+ * we have derived structures, ipfw_insn_*.
+ *
+ * The size of the instruction (in 32-bit words) is in the low
+ * 6 bits of "len". The 2 remaining bits are used to implement
+ * NOT and OR on individual instructions. Given a type, you can
+ * compute the length to be put in "len" using F_INSN_SIZE(t)
+ *
+ * F_NOT negates the match result of the instruction.
+ *
+ * F_OR is used to build or blocks. By default, instructions
+ * are evaluated as part of a logical AND. An "or" block
+ * { X or Y or Z } contains F_OR set in all but the last
+ * instruction of the block. A match will cause the code
+ * to skip past the last instruction of the block.
+ *
+ * NOTA BENE: in a couple of places we assume that
+ * sizeof(ipfw_insn) == sizeof(u_int32_t)
+ * this needs to be fixed.
+ *
+ */
+typedef struct _ipfw_insn { /* template for instructions */
+ _Alignas(_Alignof(u_int32_t)) u_int8_t opcode;
+ u_int8_t len; /* number of 32-bit words */
+#define F_NOT 0x80
+#define F_OR 0x40
+#define F_LEN_MASK 0x3f
+#define F_LEN(cmd) ((cmd)->len & F_LEN_MASK)
+
+ u_int16_t arg1;
+} ipfw_insn;
+
+/*
+ * The F_INSN_SIZE(type) computes the size, in 4-byte words, of
+ * a given type.
+ */
+#define F_INSN_SIZE(t) ((sizeof (t))/sizeof(u_int32_t))
+
+/*
+ * This is used to store an array of 16-bit entries (ports etc.)
+ */
+typedef struct _ipfw_insn_u16 {
+ ipfw_insn o;
+ u_int16_t ports[2]; /* there may be more */
+} ipfw_insn_u16;
+
+/*
+ * This is used to store an array of 32-bit entries
+ * (uid, single IPv4 addresses etc.)
+ */
+typedef struct _ipfw_insn_u32 {
+ ipfw_insn o;
+ u_int32_t d[1]; /* one or more */
+} ipfw_insn_u32;
+
+typedef struct _ipfw_insn_kidx {
+ ipfw_insn o;
+ uint32_t kidx;
+} ipfw_insn_kidx;
+
+/*
+ * This is used to store IP addr-mask pairs.
+ */
+typedef struct _ipfw_insn_ip {
+ ipfw_insn o;
+ struct in_addr addr;
+ struct in_addr mask;
+} ipfw_insn_ip;
+
+typedef struct _ipfw_insn_table {
+ ipfw_insn o; /* arg1 is optional lookup key */
+ uint32_t kidx; /* table name index */
+ uint32_t value; /* table value */
+} ipfw_insn_table;
+
+#define IPFW_LOOKUP_TYPE_MASK 0x00FF
+#define IPFW_LOOKUP_TYPE(insn) ((insn)->arg1 & IPFW_LOOKUP_TYPE_MASK)
+#define IPFW_SET_LOOKUP_TYPE(insn, type) do { \
+ (insn)->arg1 &= ~IPFW_LOOKUP_TYPE_MASK; \
+ (insn)->arg1 |= (type) & IPFW_LOOKUP_TYPE_MASK; \
+} while (0)
+
+/*
+ * Defines key types used by lookup instruction
+ */
+enum ipfw_table_lookup_type {
+ LOOKUP_NONE = 0,
+ LOOKUP_DST_IP,
+ LOOKUP_SRC_IP,
+ LOOKUP_DST_PORT,
+ LOOKUP_SRC_PORT,
+ LOOKUP_UID,
+ LOOKUP_JAIL,
+ LOOKUP_DSCP,
+ LOOKUP_DST_MAC,
+ LOOKUP_SRC_MAC,
+ LOOKUP_MARK,
+ LOOKUP_RULENUM,
+};
+
+enum ipfw_return_type {
+ RETURN_NEXT_RULENUM = 0,
+ RETURN_NEXT_RULE,
+};
+
+enum ipfw_skipto_cache_op {
+ SKIPTO_CACHE_DISABLE = 0,
+ SKIPTO_CACHE_ENABLE,
+};
+
+/*
+ * This is used to forward to a given address (ip).
+ */
+typedef struct _ipfw_insn_sa {
+ ipfw_insn o;
+ struct sockaddr_in sa;
+} ipfw_insn_sa;
+
+/*
+ * This is used to forward to a given address (ipv6).
+ */
+typedef struct _ipfw_insn_sa6 {
+ ipfw_insn o;
+ struct sockaddr_in6 sa;
+} ipfw_insn_sa6;
+
+/*
+ * This is used for MAC addr-mask pairs.
+ */
+typedef struct _ipfw_insn_mac {
+ ipfw_insn o;
+ u_char addr[12]; /* dst[6] + src[6] */
+ u_char mask[12]; /* dst[6] + src[6] */
+} ipfw_insn_mac;
+
+/*
+ * This is used for interface match rules (recv xx, xmit xx).
+ */
+typedef struct _ipfw_insn_if {
+ ipfw_insn o;
+ union {
+ struct in_addr ip;
+ int glob;
+ uint16_t kidx_v0;
+ uint32_t kidx;
+ } p;
+ char name[IFNAMSIZ];
+} ipfw_insn_if;
+
+/*
+ * This is used for storing an altq queue id number.
+ */
+typedef struct _ipfw_insn_altq {
+ ipfw_insn o;
+ u_int32_t qid;
+} ipfw_insn_altq;
+
+/*
+ * This is used for limit rules.
+ */
+typedef struct _ipfw_insn_limit {
+ ipfw_insn o;
+ u_int32_t kidx;
+ u_int8_t _pad;
+ u_int8_t limit_mask; /* combination of DYN_* below */
+#define DYN_SRC_ADDR 0x1
+#define DYN_SRC_PORT 0x2
+#define DYN_DST_ADDR 0x4
+#define DYN_DST_PORT 0x8
+
+ u_int16_t conn_limit;
+} ipfw_insn_limit;
+
+/* MAC/InfiniBand/etc address length */
+#define IPFW_MAX_L2_ADDR_LEN 20
+
+/*
+ * This is used for log instructions.
+ */
+typedef struct _ipfw_insn_log {
+ ipfw_insn o;
+ u_int32_t max_log; /* how many do we log -- 0 = all */
+ u_int32_t log_left; /* how many left to log */
+} ipfw_insn_log;
+
+/* ipfw_insn_log->o.arg1 bitmasks */
+#define IPFW_LOG_DEFAULT 0x0000
+#define IPFW_LOG_SYSLOG (1 << 15)
+#define IPFW_LOG_IPFW0 (1 << 14)
+#define IPFW_LOG_RTSOCK (1 << 13)
+
+typedef struct _ipfwlog_rtsock_hdr_v2 {
+ uint32_t rulenum;
+ uint32_t tablearg;
+ ipfw_insn cmd;
+ u_char ether_shost[IPFW_MAX_L2_ADDR_LEN];
+ u_char ether_dhost[IPFW_MAX_L2_ADDR_LEN];
+ uint32_t mark;
+ char comment[0];
+} ipfwlog_rtsock_hdr_v2;
+
+/* Legacy NAT structures, compat only */
+#ifndef _KERNEL
+/*
+ * Data structures required by both ipfw(8) and ipfw(4) but not part of the
+ * management API are protected by IPFW_INTERNAL.
+ */
+#ifdef IPFW_INTERNAL
+/* Server pool support (LSNAT). */
+struct cfg_spool {
+ LIST_ENTRY(cfg_spool) _next; /* chain of spool instances */
+ struct in_addr addr;
+ u_short port;
+};
+#endif
+
+/* Redirect modes id. */
+#define REDIR_ADDR 0x01
+#define REDIR_PORT 0x02
+#define REDIR_PROTO 0x04
+
+#ifdef IPFW_INTERNAL
+/* Nat redirect configuration. */
+struct cfg_redir {
+ LIST_ENTRY(cfg_redir) _next; /* chain of redir instances */
+ u_int16_t mode; /* type of redirect mode */
+ struct in_addr laddr; /* local ip address */
+ struct in_addr paddr; /* public ip address */
+ struct in_addr raddr; /* remote ip address */
+ u_short lport; /* local port */
+ u_short pport; /* public port */
+ u_short rport; /* remote port */
+ u_short pport_cnt; /* number of public ports */
+ u_short rport_cnt; /* number of remote ports */
+ int proto; /* protocol: tcp/udp */
+ struct alias_link **alink;
+ /* num of entry in spool chain */
+ u_int16_t spool_cnt;
+ /* chain of spool instances */
+ LIST_HEAD(spool_chain, cfg_spool) spool_chain;
+};
+#endif
+
+#ifdef IPFW_INTERNAL
+/* Nat configuration data struct. */
+struct cfg_nat {
+ /* chain of nat instances */
+ LIST_ENTRY(cfg_nat) _next;
+ int id; /* nat id */
+ struct in_addr ip; /* nat ip address */
+ char if_name[IF_NAMESIZE]; /* interface name */
+ int mode; /* aliasing mode */
+ struct libalias *lib; /* libalias instance */
+ /* number of entry in spool chain */
+ int redir_cnt;
+ /* chain of redir instances */
+ LIST_HEAD(redir_chain, cfg_redir) redir_chain;
+};
+#endif
+
+#define SOF_NAT sizeof(struct cfg_nat)
+#define SOF_REDIR sizeof(struct cfg_redir)
+#define SOF_SPOOL sizeof(struct cfg_spool)
+
+#endif /* ifndef _KERNEL */
+
+struct nat44_cfg_spool {
+ struct in_addr addr;
+ uint16_t port;
+ uint16_t spare;
+};
+#define NAT44_REDIR_ADDR 0x01
+#define NAT44_REDIR_PORT 0x02
+#define NAT44_REDIR_PROTO 0x04
+
+/* Nat redirect configuration. */
+struct nat44_cfg_redir {
+ struct in_addr laddr; /* local ip address */
+ struct in_addr paddr; /* public ip address */
+ struct in_addr raddr; /* remote ip address */
+ uint16_t lport; /* local port */
+ uint16_t pport; /* public port */
+ uint16_t rport; /* remote port */
+ uint16_t pport_cnt; /* number of public ports */
+ uint16_t rport_cnt; /* number of remote ports */
+ uint16_t mode; /* type of redirect mode */
+ uint16_t spool_cnt; /* num of entry in spool chain */
+ uint16_t spare;
+ uint32_t proto; /* protocol: tcp/udp */
+};
+
+/* Nat configuration data struct. */
+struct nat44_cfg_nat {
+ char name[64]; /* nat name */
+ char if_name[64]; /* interface name */
+ uint32_t size; /* structure size incl. redirs */
+ struct in_addr ip; /* nat IPv4 address */
+ uint32_t mode; /* aliasing mode */
+ uint32_t redir_cnt; /* number of entry in spool chain */
+ u_short alias_port_lo; /* low range for port aliasing */
+ u_short alias_port_hi; /* high range for port aliasing */
+};
+
+/* Nat command. */
+typedef struct _ipfw_insn_nat {
+ ipfw_insn o;
+ struct cfg_nat *nat;
+} ipfw_insn_nat;
+
+/* Apply ipv6 mask on ipv6 addr */
+#define APPLY_MASK(addr,mask) do { \
+ (addr)->__u6_addr.__u6_addr32[0] &= (mask)->__u6_addr.__u6_addr32[0]; \
+ (addr)->__u6_addr.__u6_addr32[1] &= (mask)->__u6_addr.__u6_addr32[1]; \
+ (addr)->__u6_addr.__u6_addr32[2] &= (mask)->__u6_addr.__u6_addr32[2]; \
+ (addr)->__u6_addr.__u6_addr32[3] &= (mask)->__u6_addr.__u6_addr32[3]; \
+} while (0)
+
+/* Structure for ipv6 */
+typedef struct _ipfw_insn_ip6 {
+ ipfw_insn o;
+ struct in6_addr addr6;
+ struct in6_addr mask6;
+} ipfw_insn_ip6;
+
+/* Used to support icmp6 types */
+typedef struct _ipfw_insn_icmp6 {
+ ipfw_insn o;
+ uint32_t d[7]; /* XXX This number si related to the netinet/icmp6.h
+ * define ICMP6_MAXTYPE
+ * as follows: n = ICMP6_MAXTYPE/32 + 1
+ * Actually is 203
+ */
+} ipfw_insn_icmp6;
+
+/* Convert pointer to instruction with specified type */
+#define insntod(p, type) ((ipfw_insn_ ## type *)(p))
+#define insntoc(p, type) ((const ipfw_insn_ ## type *)(p))
+
+/*
+ * Here we have the structure representing an ipfw rule.
+ *
+ * Layout:
+ * struct ip_fw_rule
+ * [ counter block, size = rule->cntr_len ]
+ * [ one or more instructions, size = rule->cmd_len * 4 ]
+ *
+ * It starts with a general area (with link fields).
+ * Counter block may be next (if rule->cntr_len > 0),
+ * followed by an array of one or more instructions, which the code
+ * accesses as an array of 32-bit values. rule->cmd_len represents
+ * the total instructions legth in u32 worrd, while act_ofs represents
+ * rule action offset in u32 words.
+ *
+ * When assembling instruction, remember the following:
+ *
+ * + if a rule has a "keep-state" (or "limit") option, then the
+ * first instruction (at r->cmd) MUST BE an O_PROBE_STATE
+ * + if a rule has a "log" option, then the first action
+ * (at ACTION_PTR(r)) MUST be O_LOG
+ * + if a rule has an "altq" option, it comes after "log"
+ * + if a rule has an O_TAG option, it comes after "log" and "altq"
+ *
+ *
+ * All structures (excluding instructions) are u64-aligned.
+ * Please keep this.
+ */
+
+struct ip_fw_rule {
+ uint16_t act_ofs; /* offset of action in 32-bit units */
+ uint16_t cmd_len; /* # of 32-bit words in cmd */
+ uint16_t spare;
+ uint8_t set; /* rule set (0..31) */
+ uint8_t flags; /* rule flags */
+ uint32_t rulenum; /* rule number */
+ uint32_t id; /* rule id */
+
+ ipfw_insn cmd[1]; /* storage for commands */
+};
+#define IPFW_RULE_NOOPT 0x01 /* Has no options in body */
+#define IPFW_RULE_JUSTOPTS 0x02 /* new format of rule body */
+
+/* Unaligned version */
+
+/* Base ipfw rule counter block. */
+struct ip_fw_bcounter {
+ uint16_t size; /* Size of counter block, bytes */
+ uint8_t flags; /* flags for given block */
+ uint8_t spare;
+ uint32_t timestamp; /* tv_sec of last match */
+ uint64_t pcnt; /* Packet counter */
+ uint64_t bcnt; /* Byte counter */
+};
+
+#ifndef _KERNEL
+/*
+ * Legacy rule format
+ */
+struct ip_fw {
+ struct ip_fw *x_next; /* linked list of rules */
+ struct ip_fw *next_rule; /* ptr to next [skipto] rule */
+ /* 'next_rule' is used to pass up 'set_disable' status */
+
+ uint16_t act_ofs; /* offset of action in 32-bit units */
+ uint16_t cmd_len; /* # of 32-bit words in cmd */
+ uint16_t rulenum; /* rule number */
+ uint8_t set; /* rule set (0..31) */
+ uint8_t _pad; /* padding */
+ uint32_t id; /* rule id */
+
+ /* These fields are present in all rules. */
+ uint64_t pcnt; /* Packet counter */
+ uint64_t bcnt; /* Byte counter */
+ uint32_t timestamp; /* tv_sec of last match */
+
+ ipfw_insn cmd[1]; /* storage for commands */
+};
+#endif
+
+#define ACTION_PTR(rule) \
+ (ipfw_insn *)( (u_int32_t *)((rule)->cmd) + ((rule)->act_ofs) )
+
+#define RULESIZE(rule) (sizeof(*(rule)) + (rule)->cmd_len * 4 - 4)
+
+#if 1 // should be moved to in.h
+/*
+ * This structure is used as a flow mask and a flow id for various
+ * parts of the code.
+ * addr_type is used in userland and kernel to mark the address type.
+ * fib is used in the kernel to record the fib in use.
+ * _flags is used in the kernel to store tcp flags for dynamic rules.
+ */
+struct ipfw_flow_id {
+ uint32_t dst_ip;
+ uint32_t src_ip;
+ uint16_t dst_port;
+ uint16_t src_port;
+ uint8_t fib; /* XXX: must be uint16_t */
+ uint8_t proto;
+ uint8_t _flags; /* protocol-specific flags */
+ uint8_t addr_type; /* 4=ip4, 6=ip6, 1=ether ? */
+ struct in6_addr dst_ip6;
+ struct in6_addr src_ip6;
+ uint32_t flow_id6;
+ uint32_t extra; /* queue/pipe or frag_id */
+};
+#endif
+
+#define IS_IP4_FLOW_ID(id) ((id)->addr_type == 4)
+#define IS_IP6_FLOW_ID(id) ((id)->addr_type == 6)
+
+/*
+ * Dynamic ipfw rule.
+ */
+#define IPFW_DYN_ORPHANED 0x40000 /* state's parent rule was deleted */
+
+typedef struct _ipfw_dyn_rule {
+ struct ipfw_flow_id id; /* (masked) flow id */
+ uint8_t set;
+ uint8_t type; /* rule type */
+ uint16_t pad;
+ uint32_t expire; /* expire time */
+ uint32_t rulenum; /* parent's rule number */
+ uint32_t kidx; /* index of named object */
+ uint64_t pcnt; /* packet match counter */
+ uint64_t bcnt; /* byte match counter */
+ uint32_t hashval; /* hash value */
+ union {
+ uint32_t state; /* state of this rule (typically a
+ * combination of TCP flags)
+ */
+ uint32_t count; /* number of linked states */
+ };
+ uint32_t ack_fwd; /* most recent ACKs in forward */
+ uint32_t ack_rev; /* and reverse directions (used */
+ /* to generate keepalives) */
+} __packed __aligned(8) ipfw_dyn_rule;
+
+/*
+ * Definitions for IP option names.
+ */
+#define IP_FW_IPOPT_LSRR 0x01
+#define IP_FW_IPOPT_SSRR 0x02
+#define IP_FW_IPOPT_RR 0x04
+#define IP_FW_IPOPT_TS 0x08
+
+/*
+ * Definitions for TCP option names.
+ */
+#define IP_FW_TCPOPT_MSS 0x01
+#define IP_FW_TCPOPT_WINDOW 0x02
+#define IP_FW_TCPOPT_SACK 0x04
+#define IP_FW_TCPOPT_TS 0x08
+#define IP_FW_TCPOPT_CC 0x10
+
+#define ICMP_REJECT_RST 0x100 /* fake ICMP code (send a TCP RST) */
+#define ICMP6_UNREACH_RST 0x100 /* fake ICMPv6 code (send a TCP RST) */
+#define ICMP_REJECT_ABORT 0x101 /* fake ICMP code (send an SCTP ABORT) */
+#define ICMP6_UNREACH_ABORT 0x101 /* fake ICMPv6 code (send an SCTP ABORT) */
+
+/*
+ * These are used for lookup tables.
+ */
+
+#define IPFW_TABLE_ADDR 1 /* Table for holding IPv4/IPv6 prefixes */
+#define IPFW_TABLE_INTERFACE 2 /* Table for holding interface names */
+#define IPFW_TABLE_NUMBER 3 /* Table for holding ports/uid/gid/etc */
+#define IPFW_TABLE_FLOW 4 /* Table for holding flow data */
+#define IPFW_TABLE_MAC 5 /* Table for holding mac address prefixes */
+#define IPFW_TABLE_MAXTYPE 5 /* Maximum valid number */
+
+#define IPFW_TABLE_CIDR IPFW_TABLE_ADDR /* compat */
+
+/* Value types */
+#define IPFW_VTYPE_LEGACY 0xFFFFFFFF /* All data is filled in */
+#define IPFW_VTYPE_SKIPTO 0x00000001 /* skipto/call/callreturn */
+#define IPFW_VTYPE_PIPE 0x00000002 /* pipe/queue */
+#define IPFW_VTYPE_FIB 0x00000004 /* setfib */
+#define IPFW_VTYPE_NAT 0x00000008 /* nat */
+#define IPFW_VTYPE_DSCP 0x00000010 /* dscp */
+#define IPFW_VTYPE_TAG 0x00000020 /* tag/untag */
+#define IPFW_VTYPE_DIVERT 0x00000040 /* divert/tee */
+#define IPFW_VTYPE_NETGRAPH 0x00000080 /* netgraph/ngtee */
+#define IPFW_VTYPE_LIMIT 0x00000100 /* limit */
+#define IPFW_VTYPE_NH4 0x00000200 /* IPv4 nexthop */
+#define IPFW_VTYPE_NH6 0x00000400 /* IPv6 nexthop */
+#define IPFW_VTYPE_MARK 0x00000800 /* [fw]mark */
+
+typedef struct _ipfw_table_xentry {
+ uint16_t len; /* Total entry length */
+ uint8_t type; /* entry type */
+ uint8_t masklen; /* mask length */
+ uint16_t tbl; /* table number */
+ uint16_t flags; /* record flags */
+ uint32_t value; /* value */
+ union {
+ /* Longest field needs to be aligned by 4-byte boundary */
+ struct in6_addr addr6; /* IPv6 address */
+ char iface[IF_NAMESIZE]; /* interface name */
+ } k;
+} ipfw_table_xentry;
+#define IPFW_TCF_INET 0x01 /* CIDR flags: IPv4 record */
+
+typedef struct _ipfw_xtable {
+ ip_fw3_opheader opheader; /* IP_FW3 opcode */
+ uint32_t size; /* size of entries in bytes */
+ uint32_t cnt; /* # of entries */
+ uint16_t tbl; /* table number */
+ uint8_t type; /* table type */
+ ipfw_table_xentry xent[0]; /* entries */
+} ipfw_xtable;
+
+typedef struct _ipfw_obj_tlv {
+ uint16_t type; /* TLV type */
+ uint16_t flags; /* TLV-specific flags */
+ uint32_t length; /* Total length, aligned to u64 */
+} ipfw_obj_tlv;
+#define IPFW_TLV_TBL_NAME 1
+#define IPFW_TLV_TBLNAME_LIST 2
+#define IPFW_TLV_RULE_LIST 3
+#define IPFW_TLV_DYNSTATE_LIST 4
+#define IPFW_TLV_TBL_ENT 5
+#define IPFW_TLV_DYN_ENT 6
+#define IPFW_TLV_RULE_ENT 7
+#define IPFW_TLV_TBLENT_LIST 8
+#define IPFW_TLV_RANGE 9
+#define IPFW_TLV_EACTION 10
+#define IPFW_TLV_COUNTERS 11
+#define IPFW_TLV_OBJDATA 12
+#define IPFW_TLV_STATE_NAME 14
+
+#define IPFW_TLV_EACTION_BASE 1000
+#define IPFW_TLV_EACTION_NAME(arg) (IPFW_TLV_EACTION_BASE + (arg))
+
+typedef struct _ipfw_obj_data {
+ ipfw_obj_tlv head;
+ void *data[0];
+} ipfw_obj_data;
+
+/* Object name TLV */
+typedef struct _ipfw_obj_ntlv {
+ ipfw_obj_tlv head; /* TLV header */
+ uint32_t idx; /* Name index */
+ uint8_t set; /* set, if applicable */
+ uint8_t type; /* object type, if applicable */
+ uint16_t spare; /* unused */
+ char name[64]; /* Null-terminated name */
+} ipfw_obj_ntlv;
+
+/* IPv4/IPv6 L4 flow description */
+struct tflow_entry {
+ uint8_t af;
+ uint8_t proto;
+ uint16_t spare;
+ uint16_t sport;
+ uint16_t dport;
+ union {
+ struct {
+ struct in_addr sip;
+ struct in_addr dip;
+ } a4;
+ struct {
+ struct in6_addr sip6;
+ struct in6_addr dip6;
+ } a6;
+ } a;
+};
+
+#define IPFW_TVALUE_TYPE_MASK 0xFF00
+#define IPFW_TVALUE_TYPE(insn) (((insn)->arg1 & IPFW_TVALUE_TYPE_MASK) >> 8)
+#define IPFW_SET_TVALUE_TYPE(insn, type) do { \
+ (insn)->arg1 &= ~IPFW_TVALUE_TYPE_MASK; \
+ (insn)->arg1 |= ((type) << 8) & IPFW_TVALUE_TYPE_MASK; \
+} while (0)
+
+enum ipfw_table_value_type {
+ TVALUE_TAG = 0,
+ TVALUE_PIPE,
+ TVALUE_DIVERT,
+ TVALUE_SKIPTO,
+ TVALUE_NETGRAPH,
+ TVALUE_FIB,
+ TVALUE_NAT,
+ TVALUE_NH4,
+ TVALUE_DSCP,
+ TVALUE_LIMIT,
+ TVALUE_MARK,
+};
+
+/* 64-byte structure representing multi-field table value */
+typedef struct _ipfw_table_value {
+ uint32_t tag; /* O_TAG/O_TAGGED */
+ uint16_t pipe; /* O_PIPE/O_QUEUE */
+ uint16_t divert; /* O_DIVERT/O_TEE */
+ uint32_t skipto; /* skipto, CALLRET */
+ uint32_t netgraph; /* O_NETGRAPH/O_NGTEE */
+ uint32_t nat; /* O_NAT */
+ uint32_t nh4;
+ uint16_t fib; /* O_SETFIB */
+ uint8_t dscp;
+ uint8_t spare0;
+ uint32_t kidx; /* value kernel index */
+ struct in6_addr nh6;
+ uint32_t limit; /* O_LIMIT */
+ uint32_t zoneid; /* scope zone id for nh6 */
+ uint32_t mark; /* O_SETMARK/O_MARK */
+ uint32_t refcnt; /* XXX 64-bit in kernel */
+} ipfw_table_value;
+
+/* Table entry TLV */
+typedef struct _ipfw_obj_tentry {
+ ipfw_obj_tlv head; /* TLV header */
+ uint8_t subtype; /* subtype (IPv4,IPv6) */
+ uint8_t masklen; /* mask length */
+ uint8_t result; /* request result */
+ uint8_t spare0;
+ uint32_t idx; /* Table name index */
+ union {
+ /* Longest field needs to be aligned by 8-byte boundary */
+ struct in_addr addr; /* IPv4 address */
+ uint32_t key; /* uid/gid/port */
+ struct in6_addr addr6; /* IPv6 address */
+ char iface[IF_NAMESIZE]; /* interface name */
+ u_char mac[IPFW_MAX_L2_ADDR_LEN]; /* MAC address */
+ struct tflow_entry flow;
+ } k;
+ union {
+ ipfw_table_value value; /* value data */
+ uint32_t kidx; /* value kernel index */
+ } v;
+} ipfw_obj_tentry;
+#define IPFW_TF_UPDATE 0x01 /* Update record if exists */
+/* Container TLV */
+#define IPFW_CTF_ATOMIC 0x01 /* Perform atomic operation */
+/* Operation results */
+#define IPFW_TR_IGNORED 0 /* Entry was ignored (rollback) */
+#define IPFW_TR_ADDED 1 /* Entry was successfully added */
+#define IPFW_TR_UPDATED 2 /* Entry was successfully updated*/
+#define IPFW_TR_DELETED 3 /* Entry was successfully deleted*/
+#define IPFW_TR_LIMIT 4 /* Entry was ignored (limit) */
+#define IPFW_TR_NOTFOUND 5 /* Entry was not found */
+#define IPFW_TR_EXISTS 6 /* Entry already exists */
+#define IPFW_TR_ERROR 7 /* Request has failed (unknown) */
+
+typedef struct _ipfw_obj_dyntlv {
+ ipfw_obj_tlv head;
+ ipfw_dyn_rule state;
+} ipfw_obj_dyntlv;
+#define IPFW_DF_LAST 0x01 /* Last state in chain */
+
+/* Containter TLVs */
+typedef struct _ipfw_obj_ctlv {
+ ipfw_obj_tlv head; /* TLV header */
+ uint32_t count; /* Number of sub-TLVs */
+ uint16_t objsize; /* Single object size */
+ uint8_t version; /* TLV version */
+ uint8_t flags; /* TLV-specific flags */
+} ipfw_obj_ctlv;
+
+/* Range TLV */
+typedef struct _ipfw_range_tlv {
+ ipfw_obj_tlv head; /* TLV header */
+ uint32_t flags; /* Range flags */
+ uint32_t start_rule; /* Range start */
+ uint32_t end_rule; /* Range end */
+ uint32_t set; /* Range set to match */
+ uint32_t new_set; /* New set to move/swap to */
+} ipfw_range_tlv;
+#define IPFW_RCFLAG_RANGE 0x01 /* rule range is set */
+#define IPFW_RCFLAG_ALL 0x02 /* match ALL rules */
+#define IPFW_RCFLAG_SET 0x04 /* match rules in given set */
+#define IPFW_RCFLAG_DYNAMIC 0x08 /* match only dynamic states */
+/* User-settable flags */
+#define IPFW_RCFLAG_USER (IPFW_RCFLAG_RANGE | IPFW_RCFLAG_ALL | \
+ IPFW_RCFLAG_SET | IPFW_RCFLAG_DYNAMIC)
+/* Internally used flags */
+#define IPFW_RCFLAG_DEFAULT 0x0100 /* Do not skip default rule */
+
+typedef struct _ipfw_ta_tinfo {
+ uint32_t flags; /* Format flags */
+ uint32_t spare;
+ uint8_t taclass4; /* algorithm class */
+ uint8_t spare4;
+ uint16_t itemsize4; /* item size in runtime */
+ uint32_t size4; /* runtime structure size */
+ uint32_t count4; /* number of items in runtime */
+ uint8_t taclass6; /* algorithm class */
+ uint8_t spare6;
+ uint16_t itemsize6; /* item size in runtime */
+ uint32_t size6; /* runtime structure size */
+ uint32_t count6; /* number of items in runtime */
+} ipfw_ta_tinfo;
+#define IPFW_TACLASS_HASH 1 /* algo is based on hash */
+#define IPFW_TACLASS_ARRAY 2 /* algo is based on array */
+#define IPFW_TACLASS_RADIX 3 /* algo is based on radix tree */
+
+#define IPFW_TATFLAGS_DATA 0x0001 /* Has data filled in */
+#define IPFW_TATFLAGS_AFDATA 0x0002 /* Separate data per AF */
+#define IPFW_TATFLAGS_AFITEM 0x0004 /* diff. items per AF */
+
+typedef struct _ipfw_xtable_info {
+ uint8_t type; /* table type (addr,iface,..) */
+ uint8_t tflags; /* type flags */
+ uint16_t mflags; /* modification flags */
+ uint16_t flags; /* generic table flags */
+ uint16_t spare[3];
+ uint32_t vmask; /* bitmask with value types */
+ uint32_t set; /* set table is in */
+ uint32_t kidx; /* kernel index */
+ uint32_t refcnt; /* number of references */
+ uint32_t count; /* Number of records */
+ uint32_t size; /* Total size of records(export)*/
+ uint32_t limit; /* Max number of records */
+ char tablename[64]; /* table name */
+ char algoname[64]; /* algorithm name */
+ ipfw_ta_tinfo ta_info; /* additional algo stats */
+} ipfw_xtable_info;
+/* Generic table flags */
+#define IPFW_TGFLAGS_LOCKED 0x01 /* Tables is locked from changes*/
+/* Table type-specific flags */
+#define IPFW_TFFLAG_SRCIP 0x01
+#define IPFW_TFFLAG_DSTIP 0x02
+#define IPFW_TFFLAG_SRCPORT 0x04
+#define IPFW_TFFLAG_DSTPORT 0x08
+#define IPFW_TFFLAG_PROTO 0x10
+/* Table modification flags */
+#define IPFW_TMFLAGS_LIMIT 0x0002 /* Change limit value */
+#define IPFW_TMFLAGS_LOCK 0x0004 /* Change table lock state */
+
+typedef struct _ipfw_iface_info {
+ char ifname[64]; /* interface name */
+ uint32_t ifindex; /* interface index */
+ uint32_t flags; /* flags */
+ uint32_t refcnt; /* number of references */
+ uint32_t gencnt; /* number of changes */
+ uint64_t spare;
+} ipfw_iface_info;
+#define IPFW_IFFLAG_RESOLVED 0x01 /* Interface exists */
+
+typedef struct _ipfw_ta_info {
+ char algoname[64]; /* algorithm name */
+ uint32_t type; /* lookup type */
+ uint32_t flags;
+ uint32_t refcnt;
+ uint32_t spare0;
+ uint64_t spare1;
+} ipfw_ta_info;
+
+typedef struct _ipfw_cmd_header { /* control command header */
+ ip_fw3_opheader opheader; /* IP_FW3 opcode */
+ uint32_t size; /* Total size (incl. header) */
+ uint32_t cmd; /* command */
+} ipfw_cmd_header;
+
+typedef struct _ipfw_obj_header {
+ ip_fw3_opheader opheader; /* IP_FW3 opcode */
+ uint32_t idx; /* object name index */
+ uint16_t spare;
+ uint8_t objtype; /* object type */
+ uint8_t objsubtype; /* object subtype */
+ ipfw_obj_ntlv ntlv; /* object name tlv */
+} ipfw_obj_header;
+
+typedef struct _ipfw_obj_lheader {
+ ip_fw3_opheader opheader; /* IP_FW3 opcode */
+ uint32_t set_mask; /* disabled set mask */
+ uint32_t count; /* Total objects count */
+ uint32_t size; /* Total size (incl. header) */
+ uint32_t objsize; /* Size of one object */
+} ipfw_obj_lheader;
+
+#define IPFW_CFG_GET_STATIC 0x01
+#define IPFW_CFG_GET_STATES 0x02
+#define IPFW_CFG_GET_COUNTERS 0x04
+typedef struct _ipfw_cfg_lheader {
+ ip_fw3_opheader opheader; /* IP_FW3 opcode */
+ uint32_t set_mask; /* enabled set mask */
+ uint32_t spare;
+ uint32_t flags; /* Request flags */
+ uint32_t size; /* neded buffer size */
+ uint32_t start_rule;
+ uint32_t end_rule;
+} ipfw_cfg_lheader;
+
+typedef struct _ipfw_range_header {
+ ip_fw3_opheader opheader; /* IP_FW3 opcode */
+ ipfw_range_tlv range;
+} ipfw_range_header;
+
+typedef struct _ipfw_sopt_info {
+ uint16_t opcode;
+ uint8_t version;
+ uint8_t dir;
+ uint8_t spare;
+ uint64_t refcnt;
+} ipfw_sopt_info;
+
+#endif /* _IPFW2_H */
diff --git a/sbin/ipfw15/include15/netinet6/ip_fw_nat64_15.h b/sbin/ipfw15/include15/netinet6/ip_fw_nat64_15.h
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/include15/netinet6/ip_fw_nat64_15.h
@@ -0,0 +1,212 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2015-2019 Yandex LLC
+ * Copyright (c) 2015 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ * Copyright (c) 2015-2019 Andrey V. Elsukov <ae@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET6_IP_FW_NAT64_H_
+#define _NETINET6_IP_FW_NAT64_H_
+
+struct ipfw_nat64stl_stats {
+ uint64_t opcnt64; /* 6to4 of packets translated */
+ uint64_t opcnt46; /* 4to6 of packets translated */
+ uint64_t ofrags; /* number of fragments generated */
+ uint64_t ifrags; /* number of fragments received */
+ uint64_t oerrors; /* number of output errors */
+ uint64_t noroute4;
+ uint64_t noroute6;
+ uint64_t noproto; /* Protocol not supported */
+ uint64_t nomem; /* mbuf allocation failed */
+ uint64_t dropped; /* dropped due to some errors */
+};
+
+struct ipfw_nat64clat_stats {
+ uint64_t opcnt64; /* 6to4 of packets translated */
+ uint64_t opcnt46; /* 4to6 of packets translated */
+ uint64_t ofrags; /* number of fragments generated */
+ uint64_t ifrags; /* number of fragments received */
+ uint64_t oerrors; /* number of output errors */
+ uint64_t noroute4;
+ uint64_t noroute6;
+ uint64_t noproto; /* Protocol not supported */
+ uint64_t nomem; /* mbuf allocation failed */
+ uint64_t dropped; /* dropped due to some errors */
+};
+
+struct ipfw_nat64lsn_stats {
+ uint64_t opcnt64; /* 6to4 of packets translated */
+ uint64_t opcnt46; /* 4to6 of packets translated */
+ uint64_t ofrags; /* number of fragments generated */
+ uint64_t ifrags; /* number of fragments received */
+ uint64_t oerrors; /* number of output errors */
+ uint64_t noroute4;
+ uint64_t noroute6;
+ uint64_t noproto; /* Protocol not supported */
+ uint64_t nomem; /* mbuf allocation failed */
+ uint64_t dropped; /* dropped due to some errors */
+
+ uint64_t nomatch4; /* No addr/port match */
+ uint64_t jcalls; /* Number of job handler calls */
+ uint64_t jrequests; /* Number of job requests */
+ uint64_t jhostsreq; /* Number of job host requests */
+ uint64_t jportreq; /* Number of portgroup requests */
+ uint64_t jhostfails; /* Number of failed host allocs */
+ uint64_t jportfails; /* Number of failed portgroup allocs */
+ uint64_t jreinjected; /* Number of packets reinjected to q */
+ uint64_t jmaxlen; /* Max queue length reached */
+ uint64_t jnomem; /* No memory to alloc queue item */
+
+ uint64_t screated; /* Number of states created */
+ uint64_t sdeleted; /* Number of states deleted */
+ uint64_t spgcreated; /* Number of portgroups created */
+ uint64_t spgdeleted; /* Number of portgroups deleted */
+ uint64_t hostcount; /* Number of hosts */
+ uint64_t tcpchunks; /* Number of TCP portgroups */
+ uint64_t udpchunks; /* Number of UDP portgroups */
+ uint64_t icmpchunks; /* Number of ICMP portgroups */
+
+ uint64_t _reserved[4];
+};
+
+#define NAT64_LOG 0x0001 /* Enable logging via BPF */
+#define NAT64_ALLOW_PRIVATE 0x0002 /* Allow private IPv4 address
+ * translation
+ */
+#define NAT64LSN_ALLOW_SWAPCONF 0x0004 /* Allow configuration exchange
+ * between NAT64LSN instances
+ * during the sets swapping.
+ */
+typedef struct _ipfw_nat64stl_cfg {
+ char name[64]; /* NAT name */
+ ipfw_obj_ntlv ntlv6; /* object name tlv */
+ ipfw_obj_ntlv ntlv4; /* object name tlv */
+ struct in6_addr prefix6; /* NAT64 prefix */
+ uint8_t plen6; /* Prefix length */
+ uint8_t set; /* Named instance set [0..31] */
+ uint8_t spare[2];
+ uint32_t flags;
+} ipfw_nat64stl_cfg;
+
+typedef struct _ipfw_nat64clat_cfg {
+ char name[64]; /* NAT name */
+ struct in6_addr plat_prefix; /* NAT64 (PLAT) prefix */
+ struct in6_addr clat_prefix; /* Client (CLAT) prefix */
+ uint8_t plat_plen; /* PLAT Prefix length */
+ uint8_t clat_plen; /* CLAT Prefix length */
+ uint8_t set; /* Named instance set [0..31] */
+ uint8_t spare;
+ uint32_t flags;
+} ipfw_nat64clat_cfg;
+
+/*
+ * NAT64LSN default configuration values
+ */
+#define NAT64LSN_MAX_PORTS 2048 /* Unused */
+#define NAT64LSN_JMAXLEN 2048 /* Max outstanding requests. */
+#define NAT64LSN_TCP_SYN_AGE 10 /* State's TTL after SYN received. */
+#define NAT64LSN_TCP_EST_AGE (2 * 3600) /* TTL for established connection */
+#define NAT64LSN_TCP_FIN_AGE 180 /* State's TTL after FIN/RST received */
+#define NAT64LSN_UDP_AGE 120 /* TTL for UDP states */
+#define NAT64LSN_ICMP_AGE 60 /* TTL for ICMP states */
+#define NAT64LSN_HOST_AGE 3600 /* TTL for stale host entry */
+#define NAT64LSN_PG_AGE 900 /* TTL for stale ports groups */
+
+typedef struct _ipfw_nat64lsn_cfg {
+ char name[64]; /* NAT name */
+ uint32_t flags;
+
+ uint32_t max_ports; /* Unused */
+ uint32_t agg_prefix_len; /* Unused */
+ uint32_t agg_prefix_max; /* Unused */
+
+ struct in_addr prefix4;
+ uint16_t plen4; /* Prefix length */
+ uint16_t plen6; /* Prefix length */
+ struct in6_addr prefix6; /* NAT64 prefix */
+ uint32_t jmaxlen; /* Max jobqueue length */
+
+ uint16_t min_port; /* Unused */
+ uint16_t max_port; /* Unused */
+
+ uint16_t nh_delete_delay;/* Stale host delete delay */
+ uint16_t pg_delete_delay;/* Stale portgroup delete delay */
+ uint16_t st_syn_ttl; /* TCP syn expire */
+ uint16_t st_close_ttl; /* TCP fin expire */
+ uint16_t st_estab_ttl; /* TCP established expire */
+ uint16_t st_udp_ttl; /* UDP expire */
+ uint16_t st_icmp_ttl; /* ICMP expire */
+ uint8_t set; /* Named instance set [0..31] */
+ uint8_t states_chunks; /* Number of states chunks per PG */
+} ipfw_nat64lsn_cfg;
+
+typedef struct _ipfw_nat64lsn_state {
+ struct in_addr daddr; /* Remote IPv4 address */
+ uint16_t dport; /* Remote destination port */
+ uint16_t aport; /* Local alias port */
+ uint16_t sport; /* Source port */
+ uint8_t flags; /* State flags */
+ uint8_t spare[3];
+ uint16_t idle; /* Last used time */
+} ipfw_nat64lsn_state;
+
+typedef struct _ipfw_nat64lsn_stg {
+ uint64_t next_idx; /* next state index */
+ struct in_addr alias4; /* IPv4 alias address */
+ uint8_t proto; /* protocol */
+ uint8_t flags;
+ uint16_t spare;
+ struct in6_addr host6; /* Bound IPv6 host */
+ uint32_t count; /* Number of states */
+ uint32_t spare2;
+} ipfw_nat64lsn_stg;
+
+typedef struct _ipfw_nat64lsn_state_v1 {
+ struct in6_addr host6; /* Bound IPv6 host */
+ struct in_addr daddr; /* Remote IPv4 address */
+ uint16_t dport; /* Remote destination port */
+ uint16_t aport; /* Local alias port */
+ uint16_t sport; /* Source port */
+ uint16_t spare;
+ uint16_t idle; /* Last used time */
+ uint8_t flags; /* State flags */
+ uint8_t proto; /* protocol */
+} ipfw_nat64lsn_state_v1;
+
+typedef struct _ipfw_nat64lsn_stg_v1 {
+ union nat64lsn_pgidx {
+ uint64_t index;
+ struct {
+ uint8_t chunk; /* states chunk */
+ uint8_t proto; /* protocol */
+ uint16_t port; /* base port */
+ in_addr_t addr; /* alias address */
+ };
+ } next; /* next state index */
+ struct in_addr alias4; /* IPv4 alias address */
+ uint32_t count; /* Number of states */
+} ipfw_nat64lsn_stg_v1;
+
+#endif /* _NETINET6_IP_FW_NAT64_H_ */
diff --git a/sbin/ipfw15/include15/netinet6/ip_fw_nptv6_15.h b/sbin/ipfw15/include15/netinet6/ip_fw_nptv6_15.h
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/include15/netinet6/ip_fw_nptv6_15.h
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 2016 Yandex LLC
+ * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NETINET6_IP_FW_NPTV6_H_
+#define _NETINET6_IP_FW_NPTV6_H_
+
+struct ipfw_nptv6_stats {
+ uint64_t in2ex; /* Int->Ext packets translated */
+ uint64_t ex2in; /* Ext->Int packets translated */
+ uint64_t dropped; /* dropped due to some errors */
+ uint64_t reserved[5];
+};
+
+typedef struct _ipfw_nptv6_cfg {
+ char name[64]; /* NPTv6 instance name */
+ struct in6_addr internal; /* NPTv6 internal prefix */
+ union {
+ struct in6_addr external; /* NPTv6 external prefix */
+ char if_name[IF_NAMESIZE];
+ };
+ uint8_t plen; /* Prefix length */
+ uint8_t set; /* Named instance set [0..31] */
+ uint8_t spare[2];
+ uint32_t flags;
+#define NPTV6_DYNAMIC_PREFIX 1 /* Use dynamic external prefix */
+} ipfw_nptv6_cfg;
+
+#endif /* _NETINET6_IP_FW_NPTV6_H_ */
diff --git a/sbin/ipfw15/ip_fw15.h b/sbin/ipfw15/ip_fw15.h
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/ip_fw15.h
@@ -0,0 +1,1172 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2002-2009 Luigi Rizzo, Universita` di Pisa
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _IPFW2_H
+#define _IPFW2_H
+
+/*
+ * The default rule number. By the design of ip_fw, the default rule
+ * is the last one, so its number can also serve as the highest number
+ * allowed for a rule. The ip_fw code relies on both meanings of this
+ * constant.
+ */
+#define IPFW_DEFAULT_RULE 65535
+
+#define RESVD_SET 31 /*set for default and persistent rules*/
+#define IPFW_MAX_SETS 32 /* Number of sets supported by ipfw*/
+
+/*
+ * Compat values for old clients
+ */
+#ifndef _KERNEL
+#define IPFW_TABLES_MAX 65535
+#define IPFW_TABLES_DEFAULT 128
+#endif
+
+/*
+ * Most commands (queue, pipe, tag, untag, limit...) can have a 16-bit
+ * argument between 1 and 65534. The value 0 (IP_FW_TARG) is used
+ * to represent 'tablearg' value, e.g. indicate the use of a 'tablearg'
+ * result of the most recent table() lookup.
+ * Note that 16bit is only a historical limit, resulting from
+ * the use of a 16-bit fields for that value. In reality, we can have
+ * 2^32 pipes, queues, tag values and so on.
+ */
+#define IPFW_ARG_MIN 1
+#define IPFW_ARG_MAX 65534
+#define IP_FW_TABLEARG 65535 /* Compat value for old clients */
+#define IP_FW_TARG 0 /* Current tablearg value */
+#define IP_FW_NAT44_GLOBAL 65535 /* arg1 value for "nat global" */
+
+/*
+ * Number of entries in the call stack of the call/return commands.
+ * Call stack currently is an uint16_t array with rule numbers.
+ */
+#define IPFW_CALLSTACK_SIZE 16
+
+/* IP_FW3 header/opcodes */
+typedef struct _ip_fw3_opheader {
+ uint16_t opcode; /* Operation opcode */
+ uint16_t version; /* Opcode version */
+ uint16_t reserved[2]; /* Align to 64-bit boundary */
+} ip_fw3_opheader;
+
+#define IP_FW3_OPVER_0 0
+#define IP_FW3_OPVER_1 1 /* 32bit rulenum */
+#define IP_FW3_OPVER IP_FW3_OPVER_1
+
+/* IP_FW3 opcodes */
+#define IP_FW_TABLE_XADD 86 /* add entry */
+#define IP_FW_TABLE_XDEL 87 /* delete entry */
+#define IP_FW_TABLE_XGETSIZE 88 /* get table size (deprecated) */
+#define IP_FW_TABLE_XLIST 89 /* list table contents */
+#define IP_FW_TABLE_XDESTROY 90 /* destroy table */
+#define IP_FW_TABLES_XLIST 92 /* list all tables */
+#define IP_FW_TABLE_XINFO 93 /* request info for one table */
+#define IP_FW_TABLE_XFLUSH 94 /* flush table data */
+#define IP_FW_TABLE_XCREATE 95 /* create new table */
+#define IP_FW_TABLE_XMODIFY 96 /* modify existing table */
+#define IP_FW_XGET 97 /* Retrieve configuration */
+#define IP_FW_XADD 98 /* add rule */
+#define IP_FW_XDEL 99 /* del rule */
+#define IP_FW_XMOVE 100 /* move rules to different set */
+#define IP_FW_XZERO 101 /* clear accounting */
+#define IP_FW_XRESETLOG 102 /* zero rules logs */
+#define IP_FW_SET_SWAP 103 /* Swap between 2 sets */
+#define IP_FW_SET_MOVE 104 /* Move one set to another one */
+#define IP_FW_SET_ENABLE 105 /* Enable/disable sets */
+#define IP_FW_TABLE_XFIND 106 /* finds an entry */
+#define IP_FW_XIFLIST 107 /* list tracked interfaces */
+#define IP_FW_TABLES_ALIST 108 /* list table algorithms */
+#define IP_FW_TABLE_XSWAP 109 /* swap two tables */
+#define IP_FW_TABLE_VLIST 110 /* dump table value hash */
+
+#define IP_FW_NAT44_XCONFIG 111 /* Create/modify NAT44 instance */
+#define IP_FW_NAT44_DESTROY 112 /* Destroys NAT44 instance */
+#define IP_FW_NAT44_XGETCONFIG 113 /* Get NAT44 instance config */
+#define IP_FW_NAT44_LIST_NAT 114 /* List all NAT44 instances */
+#define IP_FW_NAT44_XGETLOG 115 /* Get log from NAT44 instance */
+
+#define IP_FW_DUMP_SOPTCODES 116 /* Dump available sopts/versions */
+#define IP_FW_DUMP_SRVOBJECTS 117 /* Dump existing named objects */
+#define IP_FW_SKIPTO_CACHE 118 /* Manage skipto cache */
+
+#define IP_FW_NAT64STL_CREATE 130 /* Create stateless NAT64 instance */
+#define IP_FW_NAT64STL_DESTROY 131 /* Destroy stateless NAT64 instance */
+#define IP_FW_NAT64STL_CONFIG 132 /* Modify stateless NAT64 instance */
+#define IP_FW_NAT64STL_LIST 133 /* List stateless NAT64 instances */
+#define IP_FW_NAT64STL_STATS 134 /* Get NAT64STL instance statistics */
+#define IP_FW_NAT64STL_RESET_STATS 135 /* Reset NAT64STL instance statistics */
+
+#define IP_FW_NAT64LSN_CREATE 140 /* Create stateful NAT64 instance */
+#define IP_FW_NAT64LSN_DESTROY 141 /* Destroy stateful NAT64 instance */
+#define IP_FW_NAT64LSN_CONFIG 142 /* Modify stateful NAT64 instance */
+#define IP_FW_NAT64LSN_LIST 143 /* List stateful NAT64 instances */
+#define IP_FW_NAT64LSN_STATS 144 /* Get NAT64LSN instance statistics */
+#define IP_FW_NAT64LSN_LIST_STATES 145 /* Get stateful NAT64 states */
+#define IP_FW_NAT64LSN_RESET_STATS 146 /* Reset NAT64LSN instance statistics */
+
+#define IP_FW_NPTV6_CREATE 150 /* Create NPTv6 instance */
+#define IP_FW_NPTV6_DESTROY 151 /* Destroy NPTv6 instance */
+#define IP_FW_NPTV6_CONFIG 152 /* Modify NPTv6 instance */
+#define IP_FW_NPTV6_LIST 153 /* List NPTv6 instances */
+#define IP_FW_NPTV6_STATS 154 /* Get NPTv6 instance statistics */
+#define IP_FW_NPTV6_RESET_STATS 155 /* Reset NPTv6 instance statistics */
+
+#define IP_FW_NAT64CLAT_CREATE 160 /* Create clat NAT64 instance */
+#define IP_FW_NAT64CLAT_DESTROY 161 /* Destroy clat NAT64 instance */
+#define IP_FW_NAT64CLAT_CONFIG 162 /* Modify clat NAT64 instance */
+#define IP_FW_NAT64CLAT_LIST 163 /* List clat NAT64 instances */
+#define IP_FW_NAT64CLAT_STATS 164 /* Get NAT64CLAT instance statistics */
+#define IP_FW_NAT64CLAT_RESET_STATS 165 /* Reset NAT64CLAT instance statistics */
+
+/*
+ * The kernel representation of ipfw rules is made of a list of
+ * 'instructions' (for all practical purposes equivalent to BPF
+ * instructions), which specify which fields of the packet
+ * (or its metadata) should be analysed.
+ *
+ * Each instruction is stored in a structure which begins with
+ * "ipfw_insn", and can contain extra fields depending on the
+ * instruction type (listed below).
+ * Note that the code is written so that individual instructions
+ * have a size which is a multiple of 32 bits. This means that, if
+ * such structures contain pointers or other 64-bit entities,
+ * (there is just one instance now) they may end up unaligned on
+ * 64-bit architectures, so the must be handled with care.
+ *
+ * "enum ipfw_opcodes" are the opcodes supported. We can have up
+ * to 256 different opcodes. When adding new opcodes, they should
+ * be appended to the end of the opcode list before O_LAST_OPCODE,
+ * this will prevent the ABI from being broken, otherwise users
+ * will have to recompile ipfw(8) when they update the kernel.
+ */
+
+enum ipfw_opcodes { /* arguments (4 byte each) */
+ O_NOP = 0,
+
+ O_IP_SRC = 1, /* u32 = IP */
+ O_IP_SRC_MASK = 2, /* ip = IP/mask */
+ O_IP_SRC_ME = 3, /* none */
+ O_IP_SRC_SET = 4, /* u32=base, arg1=len, bitmap */
+
+ O_IP_DST = 5, /* u32 = IP */
+ O_IP_DST_MASK = 6, /* ip = IP/mask */
+ O_IP_DST_ME = 7, /* none */
+ O_IP_DST_SET = 8, /* u32=base, arg1=len, bitmap */
+
+ O_IP_SRCPORT = 9, /* (n)port list:mask 4 byte ea */
+ O_IP_DSTPORT = 10, /* (n)port list:mask 4 byte ea */
+ O_PROTO = 11, /* arg1=protocol */
+
+ O_MACADDR2 = 12, /* 2 mac addr:mask */
+ O_MAC_TYPE = 13, /* same as srcport */
+
+ O_LAYER2 = 14, /* none */
+ O_IN = 15, /* none */
+ O_FRAG = 16, /* none */
+
+ O_RECV = 17, /* none */
+ O_XMIT = 18, /* none */
+ O_VIA = 19, /* none */
+
+ O_IPOPT = 20, /* arg1 = 2*u8 bitmap */
+ O_IPLEN = 21, /* arg1 = len */
+ O_IPID = 22, /* arg1 = id */
+
+ O_IPTOS = 23, /* arg1 = id */
+ O_IPPRECEDENCE = 24, /* arg1 = precedence << 5 */
+ O_IPTTL = 25, /* arg1 = TTL */
+
+ O_IPVER = 26, /* arg1 = version */
+ O_UID = 27, /* u32 = id */
+ O_GID = 28, /* u32 = id */
+ O_ESTAB = 29, /* none (tcp established) */
+ O_TCPFLAGS = 30, /* arg1 = 2*u8 bitmap */
+ O_TCPWIN = 31, /* arg1 = desired win */
+ O_TCPSEQ = 32, /* u32 = desired seq. */
+ O_TCPACK = 33, /* u32 = desired seq. */
+ O_ICMPTYPE = 34, /* u32 = icmp bitmap */
+ O_TCPOPTS = 35, /* arg1 = 2*u8 bitmap */
+
+ O_VERREVPATH = 36, /* none */
+ O_VERSRCREACH = 37, /* none */
+
+ O_PROBE_STATE = 38, /* v0:arg1=kidx, v1:kidx=kidx */
+ O_KEEP_STATE = 39, /* v0:arg1=kidx, v1:kidx=kidx */
+ O_LIMIT = 40, /* ipfw_insn_limit */
+ O_LIMIT_PARENT = 41, /* dyn_type, not an opcode. */
+
+ /*
+ * These are really 'actions'.
+ */
+
+ O_LOG = 42, /* ipfw_insn_log */
+ O_PROB = 43, /* u32 = match probability */
+
+ O_CHECK_STATE = 44, /* v0:arg1=kidx, v1:kidx=kidx */
+ O_ACCEPT = 45, /* none */
+ O_DENY = 46, /* none */
+ O_REJECT = 47, /* arg1=icmp arg (same as deny) */
+ O_COUNT = 48, /* none */
+ O_SKIPTO = 49, /* v0:arg1=next rule number */
+ /* v1:kidx= next rule number */
+ O_PIPE = 50, /* arg1=pipe number */
+ O_QUEUE = 51, /* arg1=queue number */
+ O_DIVERT = 52, /* arg1=port number */
+ O_TEE = 53, /* arg1=port number */
+ O_FORWARD_IP = 54, /* fwd sockaddr */
+ O_FORWARD_MAC = 55, /* fwd mac */
+ O_NAT = 56, /* nope */
+ O_REASS = 57, /* none */
+
+ /*
+ * More opcodes.
+ */
+ O_IPSEC = 58, /* has ipsec history */
+ O_IP_SRC_LOOKUP = 59, /* v0:arg1=table number, u32=value */
+ /* v1:kidx=name, u32=value, arg1=key */
+ O_IP_DST_LOOKUP = 60, /* arg1=table number, u32=value */
+ /* v1:kidx=name, u32=value, arg1=key */
+ O_ANTISPOOF = 61, /* none */
+ O_JAIL = 62, /* u32 = id */
+ O_ALTQ = 63, /* u32 = altq classif. qid */
+ O_DIVERTED = 64, /* arg1=bitmap (1:loop, 2:out) */
+ O_TCPDATALEN = 65, /* arg1 = tcp data len */
+ O_IP6_SRC = 66, /* address without mask */
+ O_IP6_SRC_ME = 67, /* my addresses */
+ O_IP6_SRC_MASK = 68, /* address with the mask */
+ O_IP6_DST = 69,
+ O_IP6_DST_ME = 70,
+ O_IP6_DST_MASK = 71,
+ O_FLOW6ID = 72, /* for flow id tag in the ipv6 pkt */
+ O_ICMP6TYPE = 73, /* icmp6 packet type filtering */
+ O_EXT_HDR = 74, /* filtering for ipv6 extension header */
+ O_IP6 = 75,
+
+ /*
+ * actions for ng_ipfw
+ */
+ O_NETGRAPH = 76, /* send to ng_ipfw */
+ O_NGTEE = 77, /* copy to ng_ipfw */
+
+ O_IP4 = 78,
+
+ O_UNREACH6 = 79, /* arg1=icmpv6 code arg (deny) */
+
+ O_TAG = 80, /* arg1=tag number */
+ O_TAGGED = 81, /* arg1=tag number */
+
+ O_SETFIB = 82, /* arg1=FIB number */
+ O_FIB = 83, /* arg1=FIB desired fib number */
+
+ O_SOCKARG = 84, /* socket argument */
+
+ O_CALLRETURN = 85, /* v0:arg1=called rule number */
+ /* v1:kidx=called rule number */
+
+ O_FORWARD_IP6 = 86, /* fwd sockaddr_in6 */
+
+ O_DSCP = 87, /* 2 u32 = DSCP mask */
+ O_SETDSCP = 88, /* arg1=DSCP value */
+ O_IP_FLOW_LOOKUP = 89, /* v0:arg1=table number, u32=value */
+ /* v1:kidx=name, u32=value */
+
+ O_EXTERNAL_ACTION = 90, /* v0:arg1=id of external action handler */
+ /* v1:kidx=id of external action handler */
+ O_EXTERNAL_INSTANCE = 91, /* v0:arg1=id of eaction handler instance */
+ /* v1:kidx=id of eaction handler instance */
+ O_EXTERNAL_DATA = 92, /* variable length data */
+
+ O_SKIP_ACTION = 93, /* none */
+ O_TCPMSS = 94, /* arg1=MSS value */
+
+ O_MAC_SRC_LOOKUP = 95, /* kidx=name, u32=value, arg1=key */
+ O_MAC_DST_LOOKUP = 96, /* kidx=name, u32=value, arg1=key */
+
+ O_SETMARK = 97, /* u32 = value */
+ O_MARK = 98, /* 2 u32 = value, bitmask */
+
+ O_LAST_OPCODE /* not an opcode! */
+};
+
+/*
+ * The extension header are filtered only for presence using a bit
+ * vector with a flag for each header.
+ */
+#define EXT_FRAGMENT 0x1
+#define EXT_HOPOPTS 0x2
+#define EXT_ROUTING 0x4
+#define EXT_AH 0x8
+#define EXT_ESP 0x10
+#define EXT_DSTOPTS 0x20
+#define EXT_RTHDR0 0x40
+#define EXT_RTHDR2 0x80
+
+/*
+ * Template for instructions.
+ *
+ * ipfw_insn is used for all instructions which require no operands,
+ * a single 16-bit value (arg1), or a couple of 8-bit values.
+ *
+ * For other instructions which require different/larger arguments
+ * we have derived structures, ipfw_insn_*.
+ *
+ * The size of the instruction (in 32-bit words) is in the low
+ * 6 bits of "len". The 2 remaining bits are used to implement
+ * NOT and OR on individual instructions. Given a type, you can
+ * compute the length to be put in "len" using F_INSN_SIZE(t)
+ *
+ * F_NOT negates the match result of the instruction.
+ *
+ * F_OR is used to build or blocks. By default, instructions
+ * are evaluated as part of a logical AND. An "or" block
+ * { X or Y or Z } contains F_OR set in all but the last
+ * instruction of the block. A match will cause the code
+ * to skip past the last instruction of the block.
+ *
+ * NOTA BENE: in a couple of places we assume that
+ * sizeof(ipfw_insn) == sizeof(u_int32_t)
+ * this needs to be fixed.
+ *
+ */
+typedef struct _ipfw_insn { /* template for instructions */
+ _Alignas(_Alignof(u_int32_t)) u_int8_t opcode;
+ u_int8_t len; /* number of 32-bit words */
+#define F_NOT 0x80
+#define F_OR 0x40
+#define F_LEN_MASK 0x3f
+#define F_LEN(cmd) ((cmd)->len & F_LEN_MASK)
+
+ u_int16_t arg1;
+} ipfw_insn;
+
+/*
+ * The F_INSN_SIZE(type) computes the size, in 4-byte words, of
+ * a given type.
+ */
+#define F_INSN_SIZE(t) ((sizeof (t))/sizeof(u_int32_t))
+
+/*
+ * This is used to store an array of 16-bit entries (ports etc.)
+ */
+typedef struct _ipfw_insn_u16 {
+ ipfw_insn o;
+ u_int16_t ports[2]; /* there may be more */
+} ipfw_insn_u16;
+
+/*
+ * This is used to store an array of 32-bit entries
+ * (uid, single IPv4 addresses etc.)
+ */
+typedef struct _ipfw_insn_u32 {
+ ipfw_insn o;
+ u_int32_t d[1]; /* one or more */
+} ipfw_insn_u32;
+
+typedef struct _ipfw_insn_kidx {
+ ipfw_insn o;
+ uint32_t kidx;
+} ipfw_insn_kidx;
+
+/*
+ * This is used to store IP addr-mask pairs.
+ */
+typedef struct _ipfw_insn_ip {
+ ipfw_insn o;
+ struct in_addr addr;
+ struct in_addr mask;
+} ipfw_insn_ip;
+
+typedef struct _ipfw_insn_table {
+ ipfw_insn o; /* arg1 is optional lookup key */
+ uint32_t kidx; /* table name index */
+ uint32_t value; /* table value */
+} ipfw_insn_table;
+
+#define IPFW_LOOKUP_TYPE_MASK 0x00FF
+#define IPFW_LOOKUP_TYPE(insn) ((insn)->arg1 & IPFW_LOOKUP_TYPE_MASK)
+#define IPFW_SET_LOOKUP_TYPE(insn, type) do { \
+ (insn)->arg1 &= ~IPFW_LOOKUP_TYPE_MASK; \
+ (insn)->arg1 |= (type) & IPFW_LOOKUP_TYPE_MASK; \
+} while (0)
+
+/*
+ * Defines key types used by lookup instruction
+ */
+enum ipfw_table_lookup_type {
+ LOOKUP_NONE = 0,
+ LOOKUP_DST_IP,
+ LOOKUP_SRC_IP,
+ LOOKUP_DST_PORT,
+ LOOKUP_SRC_PORT,
+ LOOKUP_UID,
+ LOOKUP_JAIL,
+ LOOKUP_DSCP,
+ LOOKUP_DST_MAC,
+ LOOKUP_SRC_MAC,
+ LOOKUP_MARK,
+ LOOKUP_RULENUM,
+};
+
+enum ipfw_return_type {
+ RETURN_NEXT_RULENUM = 0,
+ RETURN_NEXT_RULE,
+};
+
+enum ipfw_skipto_cache_op {
+ SKIPTO_CACHE_DISABLE = 0,
+ SKIPTO_CACHE_ENABLE,
+};
+
+/*
+ * This is used to forward to a given address (ip).
+ */
+typedef struct _ipfw_insn_sa {
+ ipfw_insn o;
+ struct sockaddr_in sa;
+} ipfw_insn_sa;
+
+/*
+ * This is used to forward to a given address (ipv6).
+ */
+typedef struct _ipfw_insn_sa6 {
+ ipfw_insn o;
+ struct sockaddr_in6 sa;
+} ipfw_insn_sa6;
+
+/*
+ * This is used for MAC addr-mask pairs.
+ */
+typedef struct _ipfw_insn_mac {
+ ipfw_insn o;
+ u_char addr[12]; /* dst[6] + src[6] */
+ u_char mask[12]; /* dst[6] + src[6] */
+} ipfw_insn_mac;
+
+/*
+ * This is used for interface match rules (recv xx, xmit xx).
+ */
+typedef struct _ipfw_insn_if {
+ ipfw_insn o;
+ union {
+ struct in_addr ip;
+ int glob;
+ uint16_t kidx_v0;
+ uint32_t kidx;
+ } p;
+ char name[IFNAMSIZ];
+} ipfw_insn_if;
+
+/*
+ * This is used for storing an altq queue id number.
+ */
+typedef struct _ipfw_insn_altq {
+ ipfw_insn o;
+ u_int32_t qid;
+} ipfw_insn_altq;
+
+/*
+ * This is used for limit rules.
+ */
+typedef struct _ipfw_insn_limit {
+ ipfw_insn o;
+ u_int32_t kidx;
+ u_int8_t _pad;
+ u_int8_t limit_mask; /* combination of DYN_* below */
+#define DYN_SRC_ADDR 0x1
+#define DYN_SRC_PORT 0x2
+#define DYN_DST_ADDR 0x4
+#define DYN_DST_PORT 0x8
+
+ u_int16_t conn_limit;
+} ipfw_insn_limit;
+
+/* MAC/InfiniBand/etc address length */
+#define IPFW_MAX_L2_ADDR_LEN 20
+
+/*
+ * This is used for log instructions.
+ */
+typedef struct _ipfw_insn_log {
+ ipfw_insn o;
+ u_int32_t max_log; /* how many do we log -- 0 = all */
+ u_int32_t log_left; /* how many left to log */
+} ipfw_insn_log;
+
+/* ipfw_insn_log->o.arg1 bitmasks */
+#define IPFW_LOG_DEFAULT 0x0000
+#define IPFW_LOG_SYSLOG (1 << 15)
+#define IPFW_LOG_IPFW0 (1 << 14)
+#define IPFW_LOG_RTSOCK (1 << 13)
+
+typedef struct _ipfwlog_rtsock_hdr_v2 {
+ uint32_t rulenum;
+ uint32_t tablearg;
+ ipfw_insn cmd;
+ u_char ether_shost[IPFW_MAX_L2_ADDR_LEN];
+ u_char ether_dhost[IPFW_MAX_L2_ADDR_LEN];
+ uint32_t mark;
+ char comment[0];
+} ipfwlog_rtsock_hdr_v2;
+
+/* Legacy NAT structures, compat only */
+#ifndef _KERNEL
+/*
+ * Data structures required by both ipfw(8) and ipfw(4) but not part of the
+ * management API are protected by IPFW_INTERNAL.
+ */
+#ifdef IPFW_INTERNAL
+/* Server pool support (LSNAT). */
+struct cfg_spool {
+ LIST_ENTRY(cfg_spool) _next; /* chain of spool instances */
+ struct in_addr addr;
+ u_short port;
+};
+#endif
+
+/* Redirect modes id. */
+#define REDIR_ADDR 0x01
+#define REDIR_PORT 0x02
+#define REDIR_PROTO 0x04
+
+#ifdef IPFW_INTERNAL
+/* Nat redirect configuration. */
+struct cfg_redir {
+ LIST_ENTRY(cfg_redir) _next; /* chain of redir instances */
+ u_int16_t mode; /* type of redirect mode */
+ struct in_addr laddr; /* local ip address */
+ struct in_addr paddr; /* public ip address */
+ struct in_addr raddr; /* remote ip address */
+ u_short lport; /* local port */
+ u_short pport; /* public port */
+ u_short rport; /* remote port */
+ u_short pport_cnt; /* number of public ports */
+ u_short rport_cnt; /* number of remote ports */
+ int proto; /* protocol: tcp/udp */
+ struct alias_link **alink;
+ /* num of entry in spool chain */
+ u_int16_t spool_cnt;
+ /* chain of spool instances */
+ LIST_HEAD(spool_chain, cfg_spool) spool_chain;
+};
+#endif
+
+#ifdef IPFW_INTERNAL
+/* Nat configuration data struct. */
+struct cfg_nat {
+ /* chain of nat instances */
+ LIST_ENTRY(cfg_nat) _next;
+ int id; /* nat id */
+ struct in_addr ip; /* nat ip address */
+ char if_name[IF_NAMESIZE]; /* interface name */
+ int mode; /* aliasing mode */
+ struct libalias *lib; /* libalias instance */
+ /* number of entry in spool chain */
+ int redir_cnt;
+ /* chain of redir instances */
+ LIST_HEAD(redir_chain, cfg_redir) redir_chain;
+};
+#endif
+
+#define SOF_NAT sizeof(struct cfg_nat)
+#define SOF_REDIR sizeof(struct cfg_redir)
+#define SOF_SPOOL sizeof(struct cfg_spool)
+
+#endif /* ifndef _KERNEL */
+
+struct nat44_cfg_spool {
+ struct in_addr addr;
+ uint16_t port;
+ uint16_t spare;
+};
+#define NAT44_REDIR_ADDR 0x01
+#define NAT44_REDIR_PORT 0x02
+#define NAT44_REDIR_PROTO 0x04
+
+/* Nat redirect configuration. */
+struct nat44_cfg_redir {
+ struct in_addr laddr; /* local ip address */
+ struct in_addr paddr; /* public ip address */
+ struct in_addr raddr; /* remote ip address */
+ uint16_t lport; /* local port */
+ uint16_t pport; /* public port */
+ uint16_t rport; /* remote port */
+ uint16_t pport_cnt; /* number of public ports */
+ uint16_t rport_cnt; /* number of remote ports */
+ uint16_t mode; /* type of redirect mode */
+ uint16_t spool_cnt; /* num of entry in spool chain */
+ uint16_t spare;
+ uint32_t proto; /* protocol: tcp/udp */
+};
+
+/* Nat configuration data struct. */
+struct nat44_cfg_nat {
+ char name[64]; /* nat name */
+ char if_name[64]; /* interface name */
+ uint32_t size; /* structure size incl. redirs */
+ struct in_addr ip; /* nat IPv4 address */
+ uint32_t mode; /* aliasing mode */
+ uint32_t redir_cnt; /* number of entry in spool chain */
+ u_short alias_port_lo; /* low range for port aliasing */
+ u_short alias_port_hi; /* high range for port aliasing */
+};
+
+/* Nat command. */
+typedef struct _ipfw_insn_nat {
+ ipfw_insn o;
+ struct cfg_nat *nat;
+} ipfw_insn_nat;
+
+/* Apply ipv6 mask on ipv6 addr */
+#define APPLY_MASK(addr,mask) do { \
+ (addr)->__u6_addr.__u6_addr32[0] &= (mask)->__u6_addr.__u6_addr32[0]; \
+ (addr)->__u6_addr.__u6_addr32[1] &= (mask)->__u6_addr.__u6_addr32[1]; \
+ (addr)->__u6_addr.__u6_addr32[2] &= (mask)->__u6_addr.__u6_addr32[2]; \
+ (addr)->__u6_addr.__u6_addr32[3] &= (mask)->__u6_addr.__u6_addr32[3]; \
+} while (0)
+
+/* Structure for ipv6 */
+typedef struct _ipfw_insn_ip6 {
+ ipfw_insn o;
+ struct in6_addr addr6;
+ struct in6_addr mask6;
+} ipfw_insn_ip6;
+
+/* Used to support icmp6 types */
+typedef struct _ipfw_insn_icmp6 {
+ ipfw_insn o;
+ uint32_t d[7]; /* XXX This number si related to the netinet/icmp6.h
+ * define ICMP6_MAXTYPE
+ * as follows: n = ICMP6_MAXTYPE/32 + 1
+ * Actually is 203
+ */
+} ipfw_insn_icmp6;
+
+/* Convert pointer to instruction with specified type */
+#define insntod(p, type) ((ipfw_insn_ ## type *)(p))
+#define insntoc(p, type) ((const ipfw_insn_ ## type *)(p))
+
+/*
+ * Here we have the structure representing an ipfw rule.
+ *
+ * Layout:
+ * struct ip_fw_rule
+ * [ counter block, size = rule->cntr_len ]
+ * [ one or more instructions, size = rule->cmd_len * 4 ]
+ *
+ * It starts with a general area (with link fields).
+ * Counter block may be next (if rule->cntr_len > 0),
+ * followed by an array of one or more instructions, which the code
+ * accesses as an array of 32-bit values. rule->cmd_len represents
+ * the total instructions legth in u32 worrd, while act_ofs represents
+ * rule action offset in u32 words.
+ *
+ * When assembling instruction, remember the following:
+ *
+ * + if a rule has a "keep-state" (or "limit") option, then the
+ * first instruction (at r->cmd) MUST BE an O_PROBE_STATE
+ * + if a rule has a "log" option, then the first action
+ * (at ACTION_PTR(r)) MUST be O_LOG
+ * + if a rule has an "altq" option, it comes after "log"
+ * + if a rule has an O_TAG option, it comes after "log" and "altq"
+ *
+ *
+ * All structures (excluding instructions) are u64-aligned.
+ * Please keep this.
+ */
+
+struct ip_fw_rule {
+ uint16_t act_ofs; /* offset of action in 32-bit units */
+ uint16_t cmd_len; /* # of 32-bit words in cmd */
+ uint16_t spare;
+ uint8_t set; /* rule set (0..31) */
+ uint8_t flags; /* rule flags */
+ uint32_t rulenum; /* rule number */
+ uint32_t id; /* rule id */
+
+ ipfw_insn cmd[1]; /* storage for commands */
+};
+#define IPFW_RULE_NOOPT 0x01 /* Has no options in body */
+#define IPFW_RULE_JUSTOPTS 0x02 /* new format of rule body */
+
+/* Unaligned version */
+
+/* Base ipfw rule counter block. */
+struct ip_fw_bcounter {
+ uint16_t size; /* Size of counter block, bytes */
+ uint8_t flags; /* flags for given block */
+ uint8_t spare;
+ uint32_t timestamp; /* tv_sec of last match */
+ uint64_t pcnt; /* Packet counter */
+ uint64_t bcnt; /* Byte counter */
+};
+
+#ifndef _KERNEL
+/*
+ * Legacy rule format
+ */
+struct ip_fw {
+ struct ip_fw *x_next; /* linked list of rules */
+ struct ip_fw *next_rule; /* ptr to next [skipto] rule */
+ /* 'next_rule' is used to pass up 'set_disable' status */
+
+ uint16_t act_ofs; /* offset of action in 32-bit units */
+ uint16_t cmd_len; /* # of 32-bit words in cmd */
+ uint16_t rulenum; /* rule number */
+ uint8_t set; /* rule set (0..31) */
+ uint8_t _pad; /* padding */
+ uint32_t id; /* rule id */
+
+ /* These fields are present in all rules. */
+ uint64_t pcnt; /* Packet counter */
+ uint64_t bcnt; /* Byte counter */
+ uint32_t timestamp; /* tv_sec of last match */
+
+ ipfw_insn cmd[1]; /* storage for commands */
+};
+#endif
+
+#define ACTION_PTR(rule) \
+ (ipfw_insn *)( (u_int32_t *)((rule)->cmd) + ((rule)->act_ofs) )
+
+#define RULESIZE(rule) (sizeof(*(rule)) + (rule)->cmd_len * 4 - 4)
+
+#if 1 // should be moved to in.h
+/*
+ * This structure is used as a flow mask and a flow id for various
+ * parts of the code.
+ * addr_type is used in userland and kernel to mark the address type.
+ * fib is used in the kernel to record the fib in use.
+ * _flags is used in the kernel to store tcp flags for dynamic rules.
+ */
+struct ipfw_flow_id {
+ uint32_t dst_ip;
+ uint32_t src_ip;
+ uint16_t dst_port;
+ uint16_t src_port;
+ uint8_t fib; /* XXX: must be uint16_t */
+ uint8_t proto;
+ uint8_t _flags; /* protocol-specific flags */
+ uint8_t addr_type; /* 4=ip4, 6=ip6, 1=ether ? */
+ struct in6_addr dst_ip6;
+ struct in6_addr src_ip6;
+ uint32_t flow_id6;
+ uint32_t extra; /* queue/pipe or frag_id */
+};
+#endif
+
+#define IS_IP4_FLOW_ID(id) ((id)->addr_type == 4)
+#define IS_IP6_FLOW_ID(id) ((id)->addr_type == 6)
+
+/*
+ * Dynamic ipfw rule.
+ */
+#define IPFW_DYN_ORPHANED 0x40000 /* state's parent rule was deleted */
+
+typedef struct _ipfw_dyn_rule {
+ struct ipfw_flow_id id; /* (masked) flow id */
+ uint8_t set;
+ uint8_t type; /* rule type */
+ uint16_t pad;
+ uint32_t expire; /* expire time */
+ uint32_t rulenum; /* parent's rule number */
+ uint32_t kidx; /* index of named object */
+ uint64_t pcnt; /* packet match counter */
+ uint64_t bcnt; /* byte match counter */
+ uint32_t hashval; /* hash value */
+ union {
+ uint32_t state; /* state of this rule (typically a
+ * combination of TCP flags)
+ */
+ uint32_t count; /* number of linked states */
+ };
+ uint32_t ack_fwd; /* most recent ACKs in forward */
+ uint32_t ack_rev; /* and reverse directions (used */
+ /* to generate keepalives) */
+} __packed __aligned(8) ipfw_dyn_rule;
+
+/*
+ * Definitions for IP option names.
+ */
+#define IP_FW_IPOPT_LSRR 0x01
+#define IP_FW_IPOPT_SSRR 0x02
+#define IP_FW_IPOPT_RR 0x04
+#define IP_FW_IPOPT_TS 0x08
+
+/*
+ * Definitions for TCP option names.
+ */
+#define IP_FW_TCPOPT_MSS 0x01
+#define IP_FW_TCPOPT_WINDOW 0x02
+#define IP_FW_TCPOPT_SACK 0x04
+#define IP_FW_TCPOPT_TS 0x08
+#define IP_FW_TCPOPT_CC 0x10
+
+#define ICMP_REJECT_RST 0x100 /* fake ICMP code (send a TCP RST) */
+#define ICMP6_UNREACH_RST 0x100 /* fake ICMPv6 code (send a TCP RST) */
+#define ICMP_REJECT_ABORT 0x101 /* fake ICMP code (send an SCTP ABORT) */
+#define ICMP6_UNREACH_ABORT 0x101 /* fake ICMPv6 code (send an SCTP ABORT) */
+
+/*
+ * These are used for lookup tables.
+ */
+
+#define IPFW_TABLE_ADDR 1 /* Table for holding IPv4/IPv6 prefixes */
+#define IPFW_TABLE_INTERFACE 2 /* Table for holding interface names */
+#define IPFW_TABLE_NUMBER 3 /* Table for holding ports/uid/gid/etc */
+#define IPFW_TABLE_FLOW 4 /* Table for holding flow data */
+#define IPFW_TABLE_MAC 5 /* Table for holding mac address prefixes */
+#define IPFW_TABLE_MAXTYPE 5 /* Maximum valid number */
+
+#define IPFW_TABLE_CIDR IPFW_TABLE_ADDR /* compat */
+
+/* Value types */
+#define IPFW_VTYPE_LEGACY 0xFFFFFFFF /* All data is filled in */
+#define IPFW_VTYPE_SKIPTO 0x00000001 /* skipto/call/callreturn */
+#define IPFW_VTYPE_PIPE 0x00000002 /* pipe/queue */
+#define IPFW_VTYPE_FIB 0x00000004 /* setfib */
+#define IPFW_VTYPE_NAT 0x00000008 /* nat */
+#define IPFW_VTYPE_DSCP 0x00000010 /* dscp */
+#define IPFW_VTYPE_TAG 0x00000020 /* tag/untag */
+#define IPFW_VTYPE_DIVERT 0x00000040 /* divert/tee */
+#define IPFW_VTYPE_NETGRAPH 0x00000080 /* netgraph/ngtee */
+#define IPFW_VTYPE_LIMIT 0x00000100 /* limit */
+#define IPFW_VTYPE_NH4 0x00000200 /* IPv4 nexthop */
+#define IPFW_VTYPE_NH6 0x00000400 /* IPv6 nexthop */
+#define IPFW_VTYPE_MARK 0x00000800 /* [fw]mark */
+
+typedef struct _ipfw_table_xentry {
+ uint16_t len; /* Total entry length */
+ uint8_t type; /* entry type */
+ uint8_t masklen; /* mask length */
+ uint16_t tbl; /* table number */
+ uint16_t flags; /* record flags */
+ uint32_t value; /* value */
+ union {
+ /* Longest field needs to be aligned by 4-byte boundary */
+ struct in6_addr addr6; /* IPv6 address */
+ char iface[IF_NAMESIZE]; /* interface name */
+ } k;
+} ipfw_table_xentry;
+#define IPFW_TCF_INET 0x01 /* CIDR flags: IPv4 record */
+
+typedef struct _ipfw_xtable {
+ ip_fw3_opheader opheader; /* IP_FW3 opcode */
+ uint32_t size; /* size of entries in bytes */
+ uint32_t cnt; /* # of entries */
+ uint16_t tbl; /* table number */
+ uint8_t type; /* table type */
+ ipfw_table_xentry xent[0]; /* entries */
+} ipfw_xtable;
+
+typedef struct _ipfw_obj_tlv {
+ uint16_t type; /* TLV type */
+ uint16_t flags; /* TLV-specific flags */
+ uint32_t length; /* Total length, aligned to u64 */
+} ipfw_obj_tlv;
+#define IPFW_TLV_TBL_NAME 1
+#define IPFW_TLV_TBLNAME_LIST 2
+#define IPFW_TLV_RULE_LIST 3
+#define IPFW_TLV_DYNSTATE_LIST 4
+#define IPFW_TLV_TBL_ENT 5
+#define IPFW_TLV_DYN_ENT 6
+#define IPFW_TLV_RULE_ENT 7
+#define IPFW_TLV_TBLENT_LIST 8
+#define IPFW_TLV_RANGE 9
+#define IPFW_TLV_EACTION 10
+#define IPFW_TLV_COUNTERS 11
+#define IPFW_TLV_OBJDATA 12
+#define IPFW_TLV_STATE_NAME 14
+
+#define IPFW_TLV_EACTION_BASE 1000
+#define IPFW_TLV_EACTION_NAME(arg) (IPFW_TLV_EACTION_BASE + (arg))
+
+typedef struct _ipfw_obj_data {
+ ipfw_obj_tlv head;
+ void *data[0];
+} ipfw_obj_data;
+
+/* Object name TLV */
+typedef struct _ipfw_obj_ntlv {
+ ipfw_obj_tlv head; /* TLV header */
+ uint32_t idx; /* Name index */
+ uint8_t set; /* set, if applicable */
+ uint8_t type; /* object type, if applicable */
+ uint16_t spare; /* unused */
+ char name[64]; /* Null-terminated name */
+} ipfw_obj_ntlv;
+
+/* IPv4/IPv6 L4 flow description */
+struct tflow_entry {
+ uint8_t af;
+ uint8_t proto;
+ uint16_t spare;
+ uint16_t sport;
+ uint16_t dport;
+ union {
+ struct {
+ struct in_addr sip;
+ struct in_addr dip;
+ } a4;
+ struct {
+ struct in6_addr sip6;
+ struct in6_addr dip6;
+ } a6;
+ } a;
+};
+
+#define IPFW_TVALUE_TYPE_MASK 0xFF00
+#define IPFW_TVALUE_TYPE(insn) (((insn)->arg1 & IPFW_TVALUE_TYPE_MASK) >> 8)
+#define IPFW_SET_TVALUE_TYPE(insn, type) do { \
+ (insn)->arg1 &= ~IPFW_TVALUE_TYPE_MASK; \
+ (insn)->arg1 |= ((type) << 8) & IPFW_TVALUE_TYPE_MASK; \
+} while (0)
+
+enum ipfw_table_value_type {
+ TVALUE_TAG = 0,
+ TVALUE_PIPE,
+ TVALUE_DIVERT,
+ TVALUE_SKIPTO,
+ TVALUE_NETGRAPH,
+ TVALUE_FIB,
+ TVALUE_NAT,
+ TVALUE_NH4,
+ TVALUE_DSCP,
+ TVALUE_LIMIT,
+ TVALUE_MARK,
+};
+
+/* 64-byte structure representing multi-field table value */
+typedef struct _ipfw_table_value {
+ uint32_t tag; /* O_TAG/O_TAGGED */
+ uint16_t pipe; /* O_PIPE/O_QUEUE */
+ uint16_t divert; /* O_DIVERT/O_TEE */
+ uint32_t skipto; /* skipto, CALLRET */
+ uint32_t netgraph; /* O_NETGRAPH/O_NGTEE */
+ uint32_t nat; /* O_NAT */
+ uint32_t nh4;
+ uint16_t fib; /* O_SETFIB */
+ uint8_t dscp;
+ uint8_t spare0;
+ uint32_t kidx; /* value kernel index */
+ struct in6_addr nh6;
+ uint32_t limit; /* O_LIMIT */
+ uint32_t zoneid; /* scope zone id for nh6 */
+ uint32_t mark; /* O_SETMARK/O_MARK */
+ uint32_t refcnt; /* XXX 64-bit in kernel */
+} ipfw_table_value;
+
+/* Table entry TLV */
+typedef struct _ipfw_obj_tentry {
+ ipfw_obj_tlv head; /* TLV header */
+ uint8_t subtype; /* subtype (IPv4,IPv6) */
+ uint8_t masklen; /* mask length */
+ uint8_t result; /* request result */
+ uint8_t spare0;
+ uint32_t idx; /* Table name index */
+ union {
+ /* Longest field needs to be aligned by 8-byte boundary */
+ struct in_addr addr; /* IPv4 address */
+ uint32_t key; /* uid/gid/port */
+ struct in6_addr addr6; /* IPv6 address */
+ char iface[IF_NAMESIZE]; /* interface name */
+ u_char mac[IPFW_MAX_L2_ADDR_LEN]; /* MAC address */
+ struct tflow_entry flow;
+ } k;
+ union {
+ ipfw_table_value value; /* value data */
+ uint32_t kidx; /* value kernel index */
+ } v;
+} ipfw_obj_tentry;
+#define IPFW_TF_UPDATE 0x01 /* Update record if exists */
+/* Container TLV */
+#define IPFW_CTF_ATOMIC 0x01 /* Perform atomic operation */
+/* Operation results */
+#define IPFW_TR_IGNORED 0 /* Entry was ignored (rollback) */
+#define IPFW_TR_ADDED 1 /* Entry was successfully added */
+#define IPFW_TR_UPDATED 2 /* Entry was successfully updated*/
+#define IPFW_TR_DELETED 3 /* Entry was successfully deleted*/
+#define IPFW_TR_LIMIT 4 /* Entry was ignored (limit) */
+#define IPFW_TR_NOTFOUND 5 /* Entry was not found */
+#define IPFW_TR_EXISTS 6 /* Entry already exists */
+#define IPFW_TR_ERROR 7 /* Request has failed (unknown) */
+
+typedef struct _ipfw_obj_dyntlv {
+ ipfw_obj_tlv head;
+ ipfw_dyn_rule state;
+} ipfw_obj_dyntlv;
+#define IPFW_DF_LAST 0x01 /* Last state in chain */
+
+/* Containter TLVs */
+typedef struct _ipfw_obj_ctlv {
+ ipfw_obj_tlv head; /* TLV header */
+ uint32_t count; /* Number of sub-TLVs */
+ uint16_t objsize; /* Single object size */
+ uint8_t version; /* TLV version */
+ uint8_t flags; /* TLV-specific flags */
+} ipfw_obj_ctlv;
+
+/* Range TLV */
+typedef struct _ipfw_range_tlv {
+ ipfw_obj_tlv head; /* TLV header */
+ uint32_t flags; /* Range flags */
+ uint32_t start_rule; /* Range start */
+ uint32_t end_rule; /* Range end */
+ uint32_t set; /* Range set to match */
+ uint32_t new_set; /* New set to move/swap to */
+} ipfw_range_tlv;
+#define IPFW_RCFLAG_RANGE 0x01 /* rule range is set */
+#define IPFW_RCFLAG_ALL 0x02 /* match ALL rules */
+#define IPFW_RCFLAG_SET 0x04 /* match rules in given set */
+#define IPFW_RCFLAG_DYNAMIC 0x08 /* match only dynamic states */
+/* User-settable flags */
+#define IPFW_RCFLAG_USER (IPFW_RCFLAG_RANGE | IPFW_RCFLAG_ALL | \
+ IPFW_RCFLAG_SET | IPFW_RCFLAG_DYNAMIC)
+/* Internally used flags */
+#define IPFW_RCFLAG_DEFAULT 0x0100 /* Do not skip default rule */
+
+typedef struct _ipfw_ta_tinfo {
+ uint32_t flags; /* Format flags */
+ uint32_t spare;
+ uint8_t taclass4; /* algorithm class */
+ uint8_t spare4;
+ uint16_t itemsize4; /* item size in runtime */
+ uint32_t size4; /* runtime structure size */
+ uint32_t count4; /* number of items in runtime */
+ uint8_t taclass6; /* algorithm class */
+ uint8_t spare6;
+ uint16_t itemsize6; /* item size in runtime */
+ uint32_t size6; /* runtime structure size */
+ uint32_t count6; /* number of items in runtime */
+} ipfw_ta_tinfo;
+#define IPFW_TACLASS_HASH 1 /* algo is based on hash */
+#define IPFW_TACLASS_ARRAY 2 /* algo is based on array */
+#define IPFW_TACLASS_RADIX 3 /* algo is based on radix tree */
+
+#define IPFW_TATFLAGS_DATA 0x0001 /* Has data filled in */
+#define IPFW_TATFLAGS_AFDATA 0x0002 /* Separate data per AF */
+#define IPFW_TATFLAGS_AFITEM 0x0004 /* diff. items per AF */
+
+typedef struct _ipfw_xtable_info {
+ uint8_t type; /* table type (addr,iface,..) */
+ uint8_t tflags; /* type flags */
+ uint16_t mflags; /* modification flags */
+ uint16_t flags; /* generic table flags */
+ uint16_t spare[3];
+ uint32_t vmask; /* bitmask with value types */
+ uint32_t set; /* set table is in */
+ uint32_t kidx; /* kernel index */
+ uint32_t refcnt; /* number of references */
+ uint32_t count; /* Number of records */
+ uint32_t size; /* Total size of records(export)*/
+ uint32_t limit; /* Max number of records */
+ char tablename[64]; /* table name */
+ char algoname[64]; /* algorithm name */
+ ipfw_ta_tinfo ta_info; /* additional algo stats */
+} ipfw_xtable_info;
+/* Generic table flags */
+#define IPFW_TGFLAGS_LOCKED 0x01 /* Tables is locked from changes*/
+/* Table type-specific flags */
+#define IPFW_TFFLAG_SRCIP 0x01
+#define IPFW_TFFLAG_DSTIP 0x02
+#define IPFW_TFFLAG_SRCPORT 0x04
+#define IPFW_TFFLAG_DSTPORT 0x08
+#define IPFW_TFFLAG_PROTO 0x10
+/* Table modification flags */
+#define IPFW_TMFLAGS_LIMIT 0x0002 /* Change limit value */
+#define IPFW_TMFLAGS_LOCK 0x0004 /* Change table lock state */
+
+typedef struct _ipfw_iface_info {
+ char ifname[64]; /* interface name */
+ uint32_t ifindex; /* interface index */
+ uint32_t flags; /* flags */
+ uint32_t refcnt; /* number of references */
+ uint32_t gencnt; /* number of changes */
+ uint64_t spare;
+} ipfw_iface_info;
+#define IPFW_IFFLAG_RESOLVED 0x01 /* Interface exists */
+
+typedef struct _ipfw_ta_info {
+ char algoname[64]; /* algorithm name */
+ uint32_t type; /* lookup type */
+ uint32_t flags;
+ uint32_t refcnt;
+ uint32_t spare0;
+ uint64_t spare1;
+} ipfw_ta_info;
+
+typedef struct _ipfw_cmd_header { /* control command header */
+ ip_fw3_opheader opheader; /* IP_FW3 opcode */
+ uint32_t size; /* Total size (incl. header) */
+ uint32_t cmd; /* command */
+} ipfw_cmd_header;
+
+typedef struct _ipfw_obj_header {
+ ip_fw3_opheader opheader; /* IP_FW3 opcode */
+ uint32_t idx; /* object name index */
+ uint16_t spare;
+ uint8_t objtype; /* object type */
+ uint8_t objsubtype; /* object subtype */
+ ipfw_obj_ntlv ntlv; /* object name tlv */
+} ipfw_obj_header;
+
+typedef struct _ipfw_obj_lheader {
+ ip_fw3_opheader opheader; /* IP_FW3 opcode */
+ uint32_t set_mask; /* disabled set mask */
+ uint32_t count; /* Total objects count */
+ uint32_t size; /* Total size (incl. header) */
+ uint32_t objsize; /* Size of one object */
+} ipfw_obj_lheader;
+
+#define IPFW_CFG_GET_STATIC 0x01
+#define IPFW_CFG_GET_STATES 0x02
+#define IPFW_CFG_GET_COUNTERS 0x04
+typedef struct _ipfw_cfg_lheader {
+ ip_fw3_opheader opheader; /* IP_FW3 opcode */
+ uint32_t set_mask; /* enabled set mask */
+ uint32_t spare;
+ uint32_t flags; /* Request flags */
+ uint32_t size; /* neded buffer size */
+ uint32_t start_rule;
+ uint32_t end_rule;
+} ipfw_cfg_lheader;
+
+typedef struct _ipfw_range_header {
+ ip_fw3_opheader opheader; /* IP_FW3 opcode */
+ ipfw_range_tlv range;
+} ipfw_range_header;
+
+typedef struct _ipfw_sopt_info {
+ uint16_t opcode;
+ uint8_t version;
+ uint8_t dir;
+ uint8_t spare;
+ uint64_t refcnt;
+} ipfw_sopt_info;
+
+#endif /* _IPFW2_H */
diff --git a/sbin/ipfw15/ipfw.8 b/sbin/ipfw15/ipfw.8
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/ipfw.8
@@ -0,0 +1,5094 @@
+.\"
+.Dd December 29, 2025
+.Dt IPFW 8
+.Os
+.Sh NAME
+.Nm ipfw , dnctl
+.Nd User interface for firewall, traffic shaper, packet scheduler,
+in-kernel NAT.\&
+.Sh SYNOPSIS
+.Ss FIREWALL CONFIGURATION
+.Nm
+.Op Fl cq
+.Cm add
+.Ar rule
+.Nm
+.Op Fl acdefnNStT
+.Op Cm set Ar N
+.Brq Cm list | show
+.Op Ar rule | first-last ...
+.Nm
+.Op Fl f | q
+.Op Cm set Ar N
+.Cm flush
+.Nm
+.Op Fl q
+.Op Cm set Ar N
+.Brq Cm delete | zero | resetlog
+.Op Ar number ...
+.Pp
+.Nm
+.Cm set Oo Cm disable Ar number ... Oc Op Cm enable Ar number ...
+.Nm
+.Cm set move
+.Op Cm rule
+.Ar number Cm to Ar number
+.Nm
+.Cm set swap Ar number number
+.Nm
+.Cm set show
+.Ss SYSCTL SHORTCUTS
+.Nm
+.Cm enable
+.Brq Cm firewall | altq | one_pass | debug | verbose | dyn_keepalive | skipto_cache
+.Nm
+.Cm disable
+.Brq Cm firewall | altq | one_pass | debug | verbose | dyn_keepalive | skipto_cache
+.Ss LOOKUP TABLES
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm create Ar create-options
+.Nm
+.Oo Cm set Ar N Oc Cm table
+.Brq Ar name | all
+.Cm destroy
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm modify Ar modify-options
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm swap Ar name
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm add Ar table-key Op Ar value
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm add Op Ar table-key Ar value ...
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm atomic add Op Ar table-key Ar value ...
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm delete Op Ar table-key ...
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm lookup Ar addr
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm lock
+.Nm
+.Oo Cm set Ar N Oc Cm table Ar name Cm unlock
+.Nm
+.Oo Cm set Ar N Oc Cm table
+.Brq Ar name | all
+.Cm list
+.Nm
+.Oo Cm set Ar N Oc Cm table
+.Brq Ar name | all
+.Cm info
+.Nm
+.Oo Cm set Ar N Oc Cm table
+.Brq Ar name | all
+.Cm detail
+.Nm
+.Oo Cm set Ar N Oc Cm table
+.Brq Ar name | all
+.Cm flush
+.Ss DUMMYNET CONFIGURATION (TRAFFIC SHAPER AND PACKET SCHEDULER)
+.Nm dnctl
+.Brq Cm pipe | queue | sched
+.Ar number
+.Cm config
+.Ar config-options
+.Nm dnctl
+.Op Fl s Op Ar field
+.Brq Cm pipe | queue | sched
+.Brq Cm delete | list | show
+.Op Ar number ...
+.Ss IN-KERNEL NAT
+.Nm
+.Op Fl q
+.Cm nat
+.Ar number
+.Cm config
+.Ar config-options
+.Nm
+.Op Fl q
+.Cm nat
+.Ar number
+.Cm delete
+.Nm
+.Cm nat
+.Ar number
+.Cm show
+.Brq Cm config | log
+.Ss STATEFUL IPv6/IPv4 NETWORK ADDRESS AND PROTOCOL TRANSLATION
+.Nm
+.Oo Cm set Ar N Oc Cm nat64lsn Ar name Cm create Ar create-options
+.Nm
+.Oo Cm set Ar N Oc Cm nat64lsn Ar name Cm config Ar config-options
+.Nm
+.Oo Cm set Ar N Oc Cm nat64lsn
+.Brq Ar name | all
+.Brq Cm list | show
+.Op Cm states
+.Nm
+.Oo Cm set Ar N Oc Cm nat64lsn
+.Brq Ar name | all
+.Cm destroy
+.Nm
+.Oo Cm set Ar N Oc Cm nat64lsn Ar name Cm stats Op Cm reset
+.Ss STATELESS IPv6/IPv4 NETWORK ADDRESS AND PROTOCOL TRANSLATION
+.Nm
+.Oo Cm set Ar N Oc Cm nat64stl Ar name Cm create Ar create-options
+.Nm
+.Oo Cm set Ar N Oc Cm nat64stl Ar name Cm config Ar config-options
+.Nm
+.Oo Cm set Ar N Oc Cm nat64stl
+.Brq Ar name | all
+.Brq Cm list | show
+.Nm
+.Oo Cm set Ar N Oc Cm nat64stl
+.Brq Ar name | all
+.Cm destroy
+.Nm
+.Oo Cm set Ar N Oc Cm nat64stl Ar name Cm stats Op Cm reset
+.Ss XLAT464 CLAT IPv6/IPv4 NETWORK ADDRESS AND PROTOCOL TRANSLATION
+.Nm
+.Oo Cm set Ar N Oc Cm nat64clat Ar name Cm create Ar create-options
+.Nm
+.Oo Cm set Ar N Oc Cm nat64clat Ar name Cm config Ar config-options
+.Nm
+.Oo Cm set Ar N Oc Cm nat64clat
+.Brq Ar name | all
+.Brq Cm list | show
+.Nm
+.Oo Cm set Ar N Oc Cm nat64clat
+.Brq Ar name | all
+.Cm destroy
+.Nm
+.Oo Cm set Ar N Oc Cm nat64clat Ar name Cm stats Op Cm reset
+.Ss IPv6-to-IPv6 NETWORK PREFIX TRANSLATION
+.Nm
+.Oo Cm set Ar N Oc Cm nptv6 Ar name Cm create Ar create-options
+.Nm
+.Oo Cm set Ar N Oc Cm nptv6
+.Brq Ar name | all
+.Brq Cm list | show
+.Nm
+.Oo Cm set Ar N Oc Cm nptv6
+.Brq Ar name | all
+.Cm destroy
+.Nm
+.Oo Cm set Ar N Oc Cm nptv6 Ar name Cm stats Op Cm reset
+.Ss INTERNAL DIAGNOSTICS
+.Nm
+.Cm internal iflist
+.Nm
+.Cm internal monitor Op Ar filter-comment
+.Nm
+.Cm internal talist
+.Nm
+.Cm internal vlist
+.Ss LIST OF RULES AND PREPROCESSING
+.Nm
+.Op Fl cfnNqS
+.Oo
+.Fl p Ar preproc
+.Oo
+.Ar preproc-flags
+.Oc
+.Oc
+.Ar pathname
+.Sh DESCRIPTION
+The
+.Nm
+utility is the user interface for controlling the
+.Xr ipfw 4
+firewall, the
+.Xr dummynet 4
+traffic shaper/packet scheduler, and the
+in-kernel NAT services.
+.Pp
+A firewall configuration, or
+.Em ruleset ,
+is made of a list of
+.Em rules
+numbered from 1 to 65535.
+Packets are passed to the firewall
+from a number of different places in the protocol stack
+(depending on the source and destination of the packet,
+it is possible for the firewall to be
+invoked multiple times on the same packet).
+The packet passed to the firewall is compared
+against each of the rules in the
+.Em ruleset ,
+in rule-number order
+(multiple rules with the same number are permitted, in which case
+they are processed in order of insertion).
+When a match is found, the action corresponding to the
+matching rule is performed.
+.Pp
+Depending on the action and certain system settings, packets
+can be reinjected into the firewall at some rule after the
+matching one for further processing.
+.Pp
+A ruleset always includes a
+.Em default
+rule (numbered 65535) which cannot be modified or deleted,
+and matches all packets.
+The action associated with the
+.Em default
+rule can be either
+.Cm deny
+or
+.Cm allow
+depending on how the kernel is configured.
+.Pp
+If the ruleset includes one or more rules with the
+.Cm keep-state ,
+.Cm record-state ,
+.Cm limit
+or
+.Cm set-limit
+option,
+the firewall will have a
+.Em stateful
+behaviour, i.e., upon a match it will create
+.Em dynamic rules ,
+i.e., rules that match packets with the same 5-tuple
+(protocol, source and destination addresses and ports)
+as the packet which caused their creation.
+Dynamic rules, which have a limited lifetime, are checked
+at the first occurrence of a
+.Cm check-state ,
+.Cm keep-state
+or
+.Cm limit
+rule, and are typically used to open the firewall on-demand to
+legitimate traffic only.
+Please note, that
+.Cm keep-state
+and
+.Cm limit
+imply implicit
+.Cm check-state
+for all packets (not only these matched by the rule) but
+.Cm record-state
+and
+.Cm set-limit
+have no implicit
+.Cm check-state .
+See the
+.Sx STATEFUL FIREWALL
+and
+.Sx EXAMPLES
+Sections below for more information on the stateful behaviour of
+.Nm .
+.Pp
+All rules (including dynamic ones) have a few associated counters:
+a packet count, a byte count, a log count and a timestamp
+indicating the time of the last match.
+Counters can be displayed or reset with
+.Nm
+commands.
+.Pp
+Each rule belongs to one of 32 different
+.Em sets
+, and there are
+.Nm
+commands to atomically manipulate sets, such as enable,
+disable, swap sets, move all rules in a set to another
+one, delete all rules in a set.
+These can be useful to
+install temporary configurations, or to test them.
+See Section
+.Sx SETS OF RULES
+for more information on
+.Em sets .
+.Pp
+Rules can be added with the
+.Cm add
+command; deleted individually or in groups with the
+.Cm delete
+command, and globally (except those in set 31) with the
+.Cm flush
+command; displayed, optionally with the content of the
+counters, using the
+.Cm show
+and
+.Cm list
+commands.
+Finally, counters can be reset with the
+.Cm zero
+and
+.Cm resetlog
+commands.
+.Ss COMMAND OPTIONS
+The following general options are available when invoking
+.Nm :
+.Bl -tag -width indent
+.It Fl a
+Show counter values when listing rules.
+The
+.Cm show
+command implies this option.
+.It Fl b
+Only show the action and the comment, not the body of a rule.
+Implies
+.Fl c .
+.It Fl c
+When entering or showing rules, print them in compact form,
+i.e., omitting the "ip from any to any" string
+when this does not carry any additional information.
+.It Fl d
+When listing, show dynamic rules in addition to static ones.
+.It Fl D
+When listing, show only dynamic states.
+When deleting, delete only dynamic states.
+.It Fl f
+Run without prompting for confirmation for commands that can cause problems
+if misused, i.e.,
+.Cm flush .
+If there is no tty associated with the process, this is implied.
+The
+.Cm delete
+command with this flag ignores possible errors,
+i.e., nonexistent rule number.
+And for batched commands execution continues with the next command.
+.It Fl i
+When listing a table (see the
+.Sx LOOKUP TABLES
+section below for more information on lookup tables), format values
+as IP addresses.
+By default, values are shown as integers.
+.It Fl n
+Only check syntax of the command strings, without actually passing
+them to the kernel.
+.It Fl N
+Try to resolve addresses and service names in output.
+.It Fl q
+Be quiet when executing the
+.Cm add ,
+.Cm nat ,
+.Cm zero ,
+.Cm resetlog
+or
+.Cm flush
+commands;
+(implies
+.Fl f ) .
+This is useful when updating rulesets by executing multiple
+.Nm
+commands in a script
+(e.g.,
+.Ql sh\ /etc/rc.firewall ) ,
+or by processing a file with many
+.Nm
+rules across a remote login session.
+It also stops a table add or delete
+from failing if the entry already exists or is not present.
+.Pp
+The reason why this option may be important is that
+for some of these actions,
+.Nm
+may print a message; if the action results in blocking the
+traffic to the remote client,
+the remote login session will be closed
+and the rest of the ruleset will not be processed.
+Access to the console would then be required to recover.
+.It Fl S
+When listing rules, show the
+.Em set
+each rule belongs to.
+If this flag is not specified, disabled rules will not be
+listed.
+.It Fl s Op Ar field
+When listing pipes, sort according to one of the four
+counters (total or current packets or bytes).
+.It Fl t
+When listing, show last match timestamp converted with
+.Fn ctime .
+.It Fl T
+When listing, show last match timestamp as seconds from the epoch.
+This form can be more convenient for postprocessing by scripts.
+.El
+.Ss LIST OF RULES AND PREPROCESSING
+To ease configuration, rules can be put into a file which is
+processed using
+.Nm
+as shown in the last synopsis line.
+An absolute
+.Ar pathname
+must be used.
+The file will be read line by line and applied as arguments to the
+.Nm
+utility.
+.Pp
+Optionally, a preprocessor can be specified using
+.Fl p Ar preproc
+where
+.Ar pathname
+is to be piped through.
+Useful preprocessors include
+.Xr cpp 1
+and
+.Xr m4 1 .
+If
+.Ar preproc
+does not start with a slash
+.Pq Ql /
+as its first character, the usual
+.Ev PATH
+name search is performed.
+Care should be taken with this in environments where not all
+file systems are mounted (yet) by the time
+.Nm
+is being run (e.g.\& when they are mounted over NFS).
+Once
+.Fl p
+has been specified, any additional arguments are passed on to the preprocessor
+for interpretation.
+This allows for flexible configuration files (like conditionalizing
+them on the local hostname) and the use of macros to centralize
+frequently required arguments like IP addresses.
+.Ss TRAFFIC SHAPER CONFIGURATION
+The
+.Nm dnctl
+.Cm pipe , queue
+and
+.Cm sched
+commands are used to configure the traffic shaper and packet scheduler.
+See the
+.Sx TRAFFIC SHAPER (DUMMYNET) CONFIGURATION
+Section below for details.
+.Pp
+If the world and the kernel get out of sync the
+.Nm
+ABI may break, preventing you from being able to add any rules.
+This can adversely affect the booting process.
+You can use
+.Nm
+.Cm disable
+.Cm firewall
+to temporarily disable the firewall to regain access to the network,
+allowing you to fix the problem.
+.Sh PACKET FLOW
+A packet is checked against the active ruleset in multiple places
+in the protocol stack, under control of several sysctl variables.
+These places and variables are shown below, and it is important to
+have this picture in mind in order to design a correct ruleset.
+.Bd -literal -offset indent
+ ^ to upper layers V
+ | |
+ +----------->-----------+
+ ^ V
+ [ip(6)_input] [ip(6)_output] net.inet(6).ip(6).fw.enable=1
+ | |
+ ^ V
+ [ether_demux] [ether_output_frame] net.link.ether.ipfw=1
+ | |
+ +-->--[bdg_forward]-->--+ net.link.bridge.ipfw=1
+ ^ V
+ | to devices |
+.Ed
+.Pp
+The number of
+times the same packet goes through the firewall can
+vary between 0 and 4 depending on packet source and
+destination, and system configuration.
+.Pp
+Note that as packets flow through the stack, headers can be
+stripped or added to it, and so they may or may not be available
+for inspection.
+E.g., incoming packets will include the MAC header when
+.Nm
+is invoked from
+.Cm ether_demux() ,
+but the same packets will have the MAC header stripped off when
+.Nm
+is invoked from
+.Cm ip_input()
+or
+.Cm ip6_input() .
+.Pp
+Also note that each packet is always checked against the complete ruleset,
+irrespective of the place where the check occurs, or the source of the packet.
+If a rule contains some match patterns or actions which are not valid
+for the place of invocation (e.g.\& trying to match a MAC header within
+.Cm ip_input
+or
+.Cm ip6_input ),
+the match pattern will not match, but a
+.Cm not
+operator in front of such patterns
+.Em will
+cause the pattern to
+.Em always
+match on those packets.
+It is thus the responsibility of
+the programmer, if necessary, to write a suitable ruleset to
+differentiate among the possible places.
+.Cm skipto
+rules can be useful here, as an example:
+.Bd -literal -offset indent
+# packets from ether_demux or bdg_forward
+ipfw add 10 skipto 1000 all from any to any layer2 in
+# packets from ip_input
+ipfw add 10 skipto 2000 all from any to any not layer2 in
+# packets from ip_output
+ipfw add 10 skipto 3000 all from any to any not layer2 out
+# packets from ether_output_frame
+ipfw add 10 skipto 4000 all from any to any layer2 out
+.Ed
+.Pp
+(yes, at the moment there is no way to differentiate between
+ether_demux and bdg_forward).
+.Pp
+Also note that only actions
+.Cm allow ,
+.Cm deny ,
+.Cm netgraph ,
+.Cm ngtee
+and related to
+.Cm dummynet
+are processed for
+.Cm layer2
+frames and all other actions act as if they were
+.Cm allow
+for such frames.
+Full set of actions is supported for IP packets without
+.Cm layer2
+headers only.
+For example,
+.Cm divert
+action does not divert
+.Cm layer2
+frames.
+.Sh SYNTAX
+In general, each keyword or argument must be provided as
+a separate command line argument, with no leading or trailing
+spaces.
+Keywords are case-sensitive, whereas arguments may
+or may not be case-sensitive depending on their nature
+(e.g.\& uid's are, hostnames are not).
+.Pp
+Some arguments (e.g., port or address lists) are comma-separated
+lists of values.
+In this case, spaces after commas ',' are allowed to make
+the line more readable.
+You can also put the entire
+command (including flags) into a single argument.
+E.g., the following forms are equivalent:
+.Bd -literal -offset indent
+ipfw -q add deny src-ip 10.0.0.0/24,127.0.0.1/8
+ipfw -q add deny src-ip 10.0.0.0/24, 127.0.0.1/8
+ipfw "-q add deny src-ip 10.0.0.0/24, 127.0.0.1/8"
+.Ed
+.Sh RULE FORMAT
+The format of firewall rules is the following:
+.Bd -ragged -offset indent
+.Bk -words
+.Op Ar rule_number
+.Op Cm set Ar set_number
+.Op Cm prob Ar match_probability
+.Ar action
+.Op Cm log Op log_opts
+.Op Cm altq Ar queue
+.Oo
+.Bro Cm tag | untag
+.Brc Ar number
+.Oc
+.Ar body
+.Ek
+.Ed
+.Pp
+where the body of the rule specifies which information is used
+for filtering packets, among the following:
+.Pp
+.Bl -tag -width "Source and dest. addresses and ports" -offset XXX -compact
+.It Layer2 header fields
+When available
+.It IPv4 and IPv6 Protocol
+SCTP, TCP, UDP, ICMP, etc.
+.It Source and dest. addresses and ports
+.It Direction
+See Section
+.Sx PACKET FLOW
+.It Transmit and receive interface
+By name or address
+.It Misc. IP header fields
+Version, type of service, datagram length, identification,
+fragmentation flags,
+Time To Live
+.It IP options
+.It IPv6 Extension headers
+Fragmentation, Hop-by-Hop options,
+Routing Headers, Source routing rthdr0, Mobile IPv6 rthdr2, IPSec options.
+.It IPv6 Flow-ID
+.It Misc. TCP header fields
+TCP flags (SYN, FIN, ACK, RST, etc.),
+sequence number, acknowledgment number,
+window
+.It TCP options
+.It ICMP types
+for ICMP packets
+.It ICMP6 types
+for ICMP6 packets
+.It User/group ID
+When the packet can be associated with a local socket.
+.It Divert status
+Whether a packet came from a divert socket (e.g.,
+.Xr natd 8 ) .
+.It Fib annotation state
+Whether a packet has been tagged for using a specific FIB (routing table)
+in future forwarding decisions.
+.El
+.Pp
+Note that some of the above information, e.g.\& source MAC or IP addresses and
+TCP/UDP ports, can be easily spoofed, so filtering on those fields
+alone might not guarantee the desired results.
+.Bl -tag -width indent
+.It Ar rule_number
+Each rule is associated with a
+.Ar rule_number
+in the range 1..65535, with the latter reserved for the
+.Em default
+rule.
+Rules are checked sequentially by rule number.
+Multiple rules can have the same number, in which case they are
+checked (and listed) according to the order in which they have
+been added.
+If a rule is entered without specifying a number, the kernel will
+assign one in such a way that the rule becomes the last one
+before the
+.Em default
+rule.
+Automatic rule numbers are assigned by incrementing the last
+non-default rule number by the value of the sysctl variable
+.Ar net.inet.ip.fw.autoinc_step
+which defaults to 100.
+If this is not possible (e.g.\& because we would go beyond the
+maximum allowed rule number), the number of the last
+non-default value is used instead.
+.It Cm set Ar set_number
+Each rule is associated with a
+.Ar set_number
+in the range 0..31.
+Sets can be individually disabled and enabled, so this parameter
+is of fundamental importance for atomic ruleset manipulation.
+It can be also used to simplify deletion of groups of rules.
+If a rule is entered without specifying a set number,
+set 0 will be used.
+.br
+Set 31 is special in that it cannot be disabled,
+and rules in set 31 are not deleted by the
+.Nm ipfw flush
+command (but you can delete them with the
+.Nm ipfw delete set 31
+command).
+Set 31 is also used for the
+.Em default
+rule.
+.It Cm prob Ar match_probability
+A match is only declared with the specified probability
+(floating point number between 0 and 1).
+This can be useful for a number of applications such as
+random packet drop or
+(in conjunction with
+.Nm dummynet )
+to simulate the effect of multiple paths leading to out-of-order
+packet delivery.
+.Pp
+Note: this condition is checked before any other condition, including
+ones such as
+.Cm keep-state
+or
+.Cm check-state
+which might have
+side effects.
+.It Cm log Op Cm logamount Ar number
+Packets matching a rule with the
+.Cm log
+keyword will be made available for logging.
+Unless per-rule log destination is specified by
+.Cm logdst Ar logdst_spec
+option (see below), packets are logged in two ways: if the sysctl variable
+.Va net.inet.ip.fw.verbose
+is set to 0 (default), one can use
+.Xr bpf 4
+attached to the
+.Li ipfw0
+pseudo interface.
+This pseudo interface can be created manually after a system
+boot by using the following command:
+.Bd -literal -offset indent
+# ifconfig ipfw0 create
+.Ed
+.Pp
+Or, automatically at boot time by adding the following
+line to the
+.Xr rc.conf 5
+file:
+.Bd -literal -offset indent
+firewall_logif="YES"
+.Ed
+.Pp
+There is zero overhead when no
+.Xr bpf 4
+is attached to the pseudo interface.
+.Pp
+If
+.Va net.inet.ip.fw.verbose
+is set to 1, packets will be logged to
+.Xr syslogd 8
+with a
+.Dv LOG_SECURITY
+facility up to a maximum of
+.Cm logamount
+packets.
+If no
+.Cm logamount
+is specified, the limit is taken from the sysctl variable
+.Va net.inet.ip.fw.verbose_limit .
+In both cases, a value of 0 means unlimited logging.
+.Pp
+Once the limit is reached, logging can be re-enabled by
+clearing the logging counter or the packet counter for that entry, see the
+.Cm resetlog
+command.
+.Pp
+Note: logging is done after all other packet matching conditions
+have been successfully verified, and before performing the final
+action (accept, deny, etc.) on the packet.
+.It Cm log Oo
+.Cm logamount Ar number
+.Oc Cm logdst Ar logdst_spec
+.Ar logdst_spec
+is a comma-separated list of log destinations for logging
+packets matching the rule.
+Destinations supported are:
+.Bl -tag -width indent
+.It Ar syslog
+Logs a packet to
+.Xr syslogd 8
+with a
+.Dv LOG_SECURITY
+facility.
+.It Ar ipfw0
+Logs a packet to the
+.Li ipfw0
+pseudo interface.
+.It Ar rtsock
+Logs a packet to the
+.Xr route 4
+socket.
+See the comments of
+.Fn ipfw_log_rtsock
+in ipfw source code for more
+information on the message's structure.
+.El
+.Pp
+Note:
+.Cm logamount
+limits a number of logging events rather than packets being logged.
+I.e. A packet matching a rule with
+.Bd -ragged -offset indent
+ ...
+.Cm log logamount
+100
+.Cm logdst
+syslog,ipfw0 ...
+.Ed
+.Pp
+will log upto 50 packets.
+.It Cm tag Ar number
+When a packet matches a rule with the
+.Cm tag
+keyword, the numeric tag for the given
+.Ar number
+in the range 1..65534 will be attached to the packet.
+The tag acts as an internal marker (it is not sent out over
+the wire) that can be used to identify these packets later on.
+This can be used, for example, to provide trust between interfaces
+and to start doing policy-based filtering.
+A packet can have multiple tags at the same time.
+Tags are "sticky", meaning once a tag is applied to a packet by a
+matching rule it exists until explicit removal.
+Tags are kept with the packet everywhere within the kernel, but are
+lost when the packet leaves the kernel, for example, on transmitting
+packet out to the network or sending packet to a
+.Xr divert 4
+socket.
+.Pp
+To check for previously applied tags, use the
+.Cm tagged
+rule option.
+To delete previously applied tag, use the
+.Cm untag
+keyword.
+.Pp
+Note: since tags are kept with the packet everywhere in kernelspace,
+they can be set and unset anywhere in the kernel network subsystem
+(using the
+.Xr mbuf_tags 9
+facility), not only by means of the
+.Xr ipfw 4
+.Cm tag
+and
+.Cm untag
+keywords.
+For example, there can be a specialized
+.Xr netgraph 4
+node doing traffic analyzing and tagging for later inspecting
+in firewall.
+.It Cm untag Ar number
+When a packet matches a rule with the
+.Cm untag
+keyword, the tag with the number
+.Ar number
+is searched among the tags attached to this packet and,
+if found, removed from it.
+Other tags bound to packet, if present, are left untouched.
+.It Cm setmark Ar value | tablearg
+When a packet matches a rule with the
+.Cm setmark
+keyword, a 32-bit numeric mark is assigned to the packet.
+The mark is an extension to the tags.
+The mark is preserved for a packet within a single ipfw ruleset traversal
+and is lost when the packet is checked against the active ruleset
+next time (see
+.Sx PACKET FLOW
+section) or leaves ipfw context (e.g. accepted,
+diverted, bridged or routed).
+Unlike tags, mark can be matched as a lookup table key or compared with bitwise
+mask applied against another value.
+Each packet can have only one mark, so
+.Cm setmark
+always overwrites the previous mark value.
+.Pp
+The initial mark value is 0.
+To check the current mark value, use the
+.Cm mark
+rule option.
+Mark
+.Ar value
+can be entered as decimal or hexadecimal (if prefixed by 0x), and they
+are always printed as hexadecimal.
+.It Cm altq Ar queue
+When a packet matches a rule with the
+.Cm altq
+keyword, the ALTQ identifier for the given
+.Ar queue
+(see
+.Xr altq 4 )
+will be attached.
+Note that this ALTQ tag is only meaningful for packets going "out" of IPFW,
+and not being rejected or going to divert sockets.
+Note that if there is insufficient memory at the time the packet is
+processed, it will not be tagged, so it is wise to make your ALTQ
+"default" queue policy account for this.
+If multiple
+.Cm altq
+rules match a single packet, only the first one adds the ALTQ classification
+tag.
+In doing so, traffic may be shaped by using
+.Cm count Cm altq Ar queue
+rules for classification early in the ruleset, then later applying
+the filtering decision.
+For example,
+.Cm check-state
+and
+.Cm keep-state
+rules may come later and provide the actual filtering decisions in
+addition to the fallback ALTQ tag.
+.Pp
+You must run
+.Xr pfctl 8
+to set up the queues before IPFW will be able to look them up by name,
+and if the ALTQ disciplines are rearranged, the rules in containing the
+queue identifiers in the kernel will likely have gone stale and need
+to be reloaded.
+Stale queue identifiers will probably result in misclassification.
+.Pp
+All system ALTQ processing can be turned on or off via
+.Nm
+.Cm enable Ar altq
+and
+.Nm
+.Cm disable Ar altq .
+The usage of
+.Va net.inet.ip.fw.one_pass
+is irrelevant to ALTQ traffic shaping, as the actual rule action is followed
+always after adding an ALTQ tag.
+.El
+.Ss RULE ACTIONS
+A rule can be associated with one of the following actions, which
+will be executed when the packet matches the body of the rule.
+.Bl -tag -width indent
+.It Cm allow | accept | pass | permit
+Allow packets that match rule.
+The search terminates.
+.It Cm check-state Op Ar :flowname | Cm :any
+Checks the packet against the dynamic ruleset.
+If a match is found, execute the action associated with
+the rule which generated this dynamic rule, otherwise
+move to the next rule.
+.br
+.Cm Check-state
+rules do not have a body.
+If no
+.Cm check-state
+rule is found, the dynamic ruleset is checked at the first
+.Cm keep-state
+or
+.Cm limit
+rule.
+The
+.Ar :flowname
+is symbolic name assigned to dynamic rule by
+.Cm keep-state
+opcode.
+The special flowname
+.Cm :any
+can be used to ignore states flowname when matching.
+The
+.Cm :default
+keyword is special name used for compatibility with old rulesets.
+.It Cm count
+Update counters for all packets that match rule.
+The search continues with the next rule.
+.It Cm deny | drop
+Discard packets that match this rule.
+The search terminates.
+.It Cm divert Ar port
+Divert packets that match this rule to the
+.Xr divert 4
+socket bound to port
+.Ar port .
+The search terminates.
+.It Cm fwd | forward Ar ipaddr | tablearg Ns Op , Ns Ar port
+Change the next-hop on matching packets to
+.Ar ipaddr ,
+which can be an IP address or a host name.
+The next hop can also be supplied by the last table
+looked up for the packet by using the
+.Cm tablearg
+keyword instead of an explicit address.
+The search terminates if this rule matches.
+.Pp
+If
+.Ar ipaddr
+is a local address, then matching packets will be forwarded to
+.Ar port
+(or the port number in the packet if one is not specified in the rule)
+on the local machine.
+.br
+If
+.Ar ipaddr
+is not a local address, then the port number
+(if specified) is ignored, and the packet will be
+forwarded to the remote address, using the route as found in
+the local routing table for that IP.
+.br
+A
+.Ar fwd
+rule will not match layer2 packets (those received
+on ether_input, ether_output, or bridged).
+.br
+The
+.Cm fwd
+action does not change the contents of the packet at all.
+In particular, the destination address remains unmodified, so
+packets forwarded to another system will usually be rejected by that system
+unless there is a matching rule on that system to capture them.
+For packets forwarded locally,
+the local address of the socket will be
+set to the original destination address of the packet.
+This makes the
+.Xr netstat 1
+entry look rather weird but is intended for
+use with transparent proxy servers.
+.It Cm nat Ar nat_nr | global | tablearg
+Pass packet to a
+nat instance
+(for network address translation, address redirect, etc.):
+see the
+.Sx NETWORK ADDRESS TRANSLATION (NAT)\&
+Section for further information.
+.It Cm nat64lsn Ar name
+Pass packet to a stateful NAT64 instance (for IPv6/IPv4 network address and
+protocol translation): see the
+.Sx IPv6/IPv4 NETWORK ADDRESS AND PROTOCOL TRANSLATION
+Section for further information.
+.It Cm nat64stl Ar name
+Pass packet to a stateless NAT64 instance (for IPv6/IPv4 network address and
+protocol translation): see the
+.Sx IPv6/IPv4 NETWORK ADDRESS AND PROTOCOL TRANSLATION
+Section for further information.
+.It Cm nat64clat Ar name
+Pass packet to a CLAT NAT64 instance (for client-side IPv6/IPv4 network address
+and protocol translation): see the
+.Sx IPv6/IPv4 NETWORK ADDRESS AND PROTOCOL TRANSLATION
+Section for further information.
+.It Cm nptv6 Ar name
+Pass packet to a NPTv6 instance (for IPv6-to-IPv6 network prefix translation):
+see the
+.Sx IPv6-to-IPv6 NETWORK PREFIX TRANSLATION (NPTv6)\&
+Section for further information.
+.It Cm pipe Ar pipe_nr
+Pass packet to a
+.Nm dummynet
+.Dq pipe
+(for bandwidth limitation, delay, etc.).
+See the
+.Sx TRAFFIC SHAPER (DUMMYNET) CONFIGURATION
+Section for further information.
+The search terminates; however, on exit from the pipe and if
+the
+.Xr sysctl 8
+variable
+.Va net.inet.ip.fw.one_pass
+is not set, the packet is passed again to the firewall code
+starting from the next rule.
+.It Cm queue Ar queue_nr
+Pass packet to a
+.Nm dummynet
+.Dq queue
+(for bandwidth limitation using WF2Q+).
+.It Cm reject
+(Deprecated).
+Synonym for
+.Cm unreach host .
+.It Cm reset
+Discard packets that match this rule, and if the
+packet is a TCP packet, try to send a TCP reset (RST) notice.
+The search terminates.
+.It Cm reset6
+Discard packets that match this rule, and if the
+packet is a TCP packet, try to send a TCP reset (RST) notice.
+The search terminates.
+.It Cm skipto Ar number | tablearg
+Skip all subsequent rules numbered less than
+.Ar number .
+The search continues with the first rule numbered
+.Ar number
+or higher.
+It is possible to use the
+.Cm tablearg
+keyword with a skipto for a
+.Em computed
+skipto.
+Skipto may work either in O(log(N)) or in O(1) depending
+on amount of memory and/or sysctl variables.
+See the
+.Sx SYSCTL VARIABLES
+section for more details.
+.It Cm call Ar number | tablearg
+The current rule number is saved in the internal stack and
+ruleset processing continues with the first rule numbered
+.Ar number
+or higher.
+If later a rule with the
+.Cm return
+action is encountered, the processing returns to the first rule
+with number of this
+.Cm call
+rule plus one or higher
+(the same behaviour as with packets returning from
+.Xr divert 4
+socket after a
+.Cm divert
+action).
+This could be used to make somewhat like an assembly language
+.Dq subroutine
+calls to rules with common checks for different interfaces, etc.
+.Pp
+Rule with any number could be called, not just forward jumps as with
+.Cm skipto .
+So, to prevent endless loops in case of mistakes, both
+.Cm call
+and
+.Cm return
+actions don't do any jumps and simply go to the next rule if memory
+cannot be allocated or stack overflowed/underflowed.
+.Pp
+Internally stack for rule numbers is implemented using
+.Xr mbuf_tags 9
+facility and currently has size of 16 entries.
+As mbuf tags are lost when packet leaves the kernel,
+.Cm divert
+should not be used in subroutines to avoid endless loops
+and other undesired effects.
+.It Cm return
+Takes rule number saved to internal stack by the last
+.Cm call
+action and returns ruleset processing to the first rule
+with number greater than number of corresponding
+.Cm call
+rule.
+See description of the
+.Cm call
+action for more details.
+.Pp
+Note that
+.Cm return
+rules usually end a
+.Dq subroutine
+and thus are unconditional, but
+.Nm
+command-line utility currently requires every action except
+.Cm check-state
+to have body.
+While it is sometimes useful to return only on some packets,
+usually you want to print just
+.Dq return
+for readability.
+A workaround for this is to use new syntax and
+.Fl c
+switch:
+.Bd -literal -offset indent
+# Add a rule without actual body
+ipfw add 2999 return via any
+
+# List rules without "from any to any" part
+ipfw -c list
+.Ed
+.Pp
+This cosmetic annoyance may be fixed in future releases.
+.It Cm tee Ar port
+Send a copy of packets matching this rule to the
+.Xr divert 4
+socket bound to port
+.Ar port .
+The search continues with the next rule.
+.It Cm unreach Ar code Op mtu
+Discard packets that match this rule, and try to send an ICMP
+unreachable notice with code
+.Ar code ,
+where
+.Ar code
+is a number from 0 to 255, or one of these aliases:
+.Cm net , host , protocol , port ,
+.Cm needfrag , srcfail , net-unknown , host-unknown ,
+.Cm isolated , net-prohib , host-prohib , tosnet ,
+.Cm toshost , filter-prohib , host-precedence
+or
+.Cm precedence-cutoff .
+The
+.Cm needfrag
+code may have an optional
+.Ar mtu
+parameter.
+If specified, the MTU value will be put into generated ICMP packet.
+The search terminates.
+.It Cm unreach6 Ar code
+Discard packets that match this rule, and try to send an ICMPv6
+unreachable notice with code
+.Ar code ,
+where
+.Ar code
+is a number from 0, 1, 3 or 4, or one of these aliases:
+.Cm no-route, admin-prohib, address
+or
+.Cm port .
+The search terminates.
+.It Cm netgraph Ar cookie
+Divert packet into netgraph with given
+.Ar cookie .
+The search terminates.
+If packet is later returned from netgraph it is either
+accepted or continues with the next rule, depending on
+.Va net.inet.ip.fw.one_pass
+sysctl variable.
+.It Cm ngtee Ar cookie
+A copy of packet is diverted into netgraph, original
+packet continues with the next rule.
+See
+.Xr ng_ipfw 4
+for more information on
+.Cm netgraph
+and
+.Cm ngtee
+actions.
+.It Cm setfib Ar fibnum | tablearg
+The packet is tagged so as to use the FIB (routing table)
+.Ar fibnum
+in any subsequent forwarding decisions.
+In the current implementation, this is limited to the values 0 through 15, see
+.Xr setfib 2 .
+Processing continues at the next rule.
+It is possible to use the
+.Cm tablearg
+keyword with setfib.
+If the tablearg value is not within the compiled range of fibs,
+the packet's fib is set to 0.
+.It Cm setdscp Ar DSCP | number | tablearg
+Set specified DiffServ codepoint for an IPv4/IPv6 packet.
+Processing continues at the next rule.
+Supported values are:
+.Pp
+.Cm cs0
+.Pq Dv 000000 ,
+.Cm cs1
+.Pq Dv 001000 ,
+.Cm cs2
+.Pq Dv 010000 ,
+.Cm cs3
+.Pq Dv 011000 ,
+.Cm cs4
+.Pq Dv 100000 ,
+.Cm cs5
+.Pq Dv 101000 ,
+.Cm cs6
+.Pq Dv 110000 ,
+.Cm cs7
+.Pq Dv 111000 ,
+.Cm af11
+.Pq Dv 001010 ,
+.Cm af12
+.Pq Dv 001100 ,
+.Cm af13
+.Pq Dv 001110 ,
+.Cm af21
+.Pq Dv 010010 ,
+.Cm af22
+.Pq Dv 010100 ,
+.Cm af23
+.Pq Dv 010110 ,
+.Cm af31
+.Pq Dv 011010 ,
+.Cm af32
+.Pq Dv 011100 ,
+.Cm af33
+.Pq Dv 011110 ,
+.Cm af41
+.Pq Dv 100010 ,
+.Cm af42
+.Pq Dv 100100 ,
+.Cm af43
+.Pq Dv 100110 ,
+.Cm va
+.Pq Dv 101100 ,
+.Cm ef
+.Pq Dv 101110 ,
+.Cm be
+.Pq Dv 000000 .
+Additionally, DSCP value can be specified by number (0..63).
+It is also possible to use the
+.Cm tablearg
+keyword with setdscp.
+If the tablearg value is not within the 0..63 range, lower 6 bits of supplied
+value are used.
+.It Cm tcp-setmss Ar mss
+Set the Maximum Segment Size (MSS) in the TCP segment to value
+.Ar mss .
+The kernel module
+.Cm ipfw_pmod
+should be loaded or kernel should have
+.Cm options IPFIREWALL_PMOD
+to be able use this action.
+This command does not change a packet if original MSS value is lower than
+specified value.
+Both TCP over IPv4 and over IPv6 are supported.
+Regardless of matched a packet or not by the
+.Cm tcp-setmss
+rule, the search continues with the next rule.
+.It Cm reass
+Queue and reassemble IPv4 fragments.
+If the packet is not fragmented, counters are updated and
+processing continues with the next rule.
+If the packet is the last logical fragment, the packet is reassembled and, if
+.Va net.inet.ip.fw.one_pass
+is set to 0, processing continues with the next rule.
+Otherwise, the packet is allowed to pass and the search terminates.
+If the packet is a fragment in the middle of a logical group of fragments,
+it is consumed and
+processing stops immediately.
+.Pp
+Fragment handling can be tuned via
+.Va net.inet.ip.maxfragpackets
+and
+.Va net.inet.ip.maxfragsperpacket
+which limit, respectively, the maximum number of processable
+fragments (default: 800) and
+the maximum number of fragments per packet (default: 16).
+.Pp
+NOTA BENE: since fragments do not contain port numbers,
+they should be avoided with the
+.Nm reass
+rule.
+Alternatively, direction-based (like
+.Nm in
+/
+.Nm out
+) and source-based (like
+.Nm via
+) match patterns can be used to select fragments.
+.Pp
+Usually a simple rule like:
+.Bd -literal -offset indent
+# reassemble incoming fragments
+ipfw add reass all from any to any in
+.Ed
+.Pp
+is all you need at the beginning of your ruleset.
+.It Cm abort
+Discard packets that match this rule, and if the packet is an SCTP packet,
+try to send an SCTP packet containing an ABORT chunk.
+The search terminates.
+.It Cm abort6
+Discard packets that match this rule, and if the packet is an SCTP packet,
+try to send an SCTP packet containing an ABORT chunk.
+The search terminates.
+.El
+.Ss RULE BODY
+The body of a rule contains zero or more patterns (such as
+specific source and destination addresses or ports,
+protocol options, incoming or outgoing interfaces, etc.)
+that the packet must match in order to be recognised.
+In general, the patterns are connected by (implicit)
+.Cm and
+operators \(em i.e., all must match in order for the
+rule to match.
+Individual patterns can be prefixed by the
+.Cm not
+operator to reverse the result of the match, as in
+.Pp
+.Dl "ipfw add 100 allow ip from not 1.2.3.4 to any"
+.Pp
+Additionally, sets of alternative match patterns
+.Pq Em or-blocks
+can be constructed by putting the patterns in
+lists enclosed between parentheses ( ) or braces { }, and
+using the
+.Cm or
+operator as follows:
+.Pp
+.Dl "ipfw add 100 allow ip from { x or not y or z } to any"
+.Pp
+Only one level of parentheses is allowed.
+Beware that most shells have special meanings for parentheses
+or braces, so it is advisable to put a backslash \\ in front of them
+to prevent such interpretations.
+.Pp
+The body of a rule must in general include a source and destination
+address specifier.
+The keyword
+.Ar any
+can be used in various places to specify that the content of
+a required field is irrelevant.
+.Pp
+The rule body has the following format:
+.Bd -ragged -offset indent
+.Op Ar proto Cm from Ar src Cm to Ar dst
+.Op Ar options
+.Ed
+.Pp
+The first part (proto from src to dst) is for backward
+compatibility with earlier versions of
+.Fx .
+In modern
+.Fx
+any match pattern (including MAC headers, IP protocols,
+addresses and ports) can be specified in the
+.Ar options
+section.
+.Pp
+Rule fields have the following meaning:
+.Bl -tag -width indent
+.It Ar proto : protocol | Cm { Ar protocol Cm or ... }
+.It Ar protocol : Oo Cm not Oc Ar protocol-name | protocol-number
+An IP protocol specified by number or name
+(for a complete list see
+.Pa /etc/protocols ) ,
+or one of the following keywords:
+.Bl -tag -width indent
+.It Cm ip4 | ipv4
+Matches IPv4 packets.
+.It Cm ip6 | ipv6
+Matches IPv6 packets.
+.It Cm ip | all
+Matches any packet.
+.El
+.Pp
+The
+.Cm ipv6
+in
+.Cm proto
+option will be treated as inner protocol.
+And, the
+.Cm ipv4
+is not available in
+.Cm proto
+option.
+.Pp
+The
+.Cm { Ar protocol Cm or ... }
+format (an
+.Em or-block )
+is provided for convenience only but its use is deprecated.
+.It Ar src No and Ar dst : Bro Cm addr | Cm { Ar addr Cm or ... } Brc Op Oo Cm not Oc Ar ports
+An address (or a list, see below)
+optionally followed by
+.Ar ports
+specifiers.
+.Pp
+The second format
+.Em ( or-block
+with multiple addresses) is provided for convenience only and
+its use is discouraged.
+.It Ar addr : Oo Cm not Oc Bro
+.Cm any | me | me6 |
+.Cm table Ns Pq Ar name Ns Op , Ns Ar value
+.Ar | addr-list | addr-set
+.Brc
+.Bl -tag -width indent
+.It Cm any
+Matches any IP address.
+.It Cm me
+Matches any IP address configured on an interface in the system.
+.It Cm me6
+Matches any IPv6 address configured on an interface in the system.
+The address list is evaluated at the time the packet is
+analysed.
+.It Cm table Ns Pq Ar name Ns Op , Ns Ar value
+Matches any IPv4 or IPv6 address for which an entry exists in the lookup table
+.Ar number .
+If an optional 32-bit unsigned
+.Ar value
+is also specified, an entry will match only if it has this value.
+If
+.Ar value
+is specified in form
+.Ar valtype=value ,
+then specified value type field will be checked.
+It can be
+.Ar skipto, pipe, fib, nat, dscp, tag, divert, netgraph, limit, nh4
+and
+.Ar mark.
+
+See the
+.Sx LOOKUP TABLES
+section below for more information on lookup tables.
+.El
+.It Ar addr-list : ip-addr Ns Op , Ns Ar addr-list
+.It Ar ip-addr :
+A host or subnet address specified in one of the following ways:
+.Bl -tag -width indent
+.It Ar numeric-ip | hostname
+Matches a single IPv4 address, specified as dotted-quad or a hostname.
+Hostnames are resolved at the time the rule is added to the firewall list.
+.It Ar addr Ns / Ns Ar masklen
+Matches all addresses with base
+.Ar addr
+(specified as an IP address, a network number, or a hostname)
+and mask width of
+.Cm masklen
+bits.
+As an example, 1.2.3.4/25 or 1.2.3.0/25 will match
+all IP numbers from 1.2.3.0 to 1.2.3.127 .
+.It Ar addr : Ns Ar mask
+Matches all addresses with base
+.Ar addr
+(specified as an IP address, a network number, or a hostname)
+and the mask of
+.Ar mask ,
+specified as a dotted quad.
+As an example, 1.2.3.4:255.0.255.0 or 1.0.3.0:255.0.255.0 will match
+1.*.3.*.
+This form is advised only for non-contiguous
+masks.
+It is better to resort to the
+.Ar addr Ns / Ns Ar masklen
+format for contiguous masks, which is more compact and less
+error-prone.
+.El
+.It Ar addr-set : addr Ns Oo Ns / Ns Ar masklen Oc Ns Cm { Ns Ar list Ns Cm }
+.It Ar list : Bro Ar num | num-num Brc Ns Op , Ns Ar list
+Matches all addresses with base address
+.Ar addr
+(specified as an IP address, a network number, or a hostname)
+and whose last byte is in the list between braces { } .
+Note that there must be no spaces between braces and
+numbers (spaces after commas are allowed).
+Elements of the list can be specified as single entries
+or ranges.
+The
+.Ar masklen
+field is used to limit the size of the set of addresses,
+and can have any value between 24 and 32.
+If not specified,
+it will be assumed as 24.
+.br
+This format is particularly useful to handle sparse address sets
+within a single rule.
+Because the matching occurs using a
+bitmask, it takes constant time and dramatically reduces
+the complexity of rulesets.
+.br
+As an example, an address specified as 1.2.3.4/24{128,35-55,89}
+or 1.2.3.0/24{128,35-55,89}
+will match the following IP addresses:
+.br
+1.2.3.128, 1.2.3.35 to 1.2.3.55, 1.2.3.89 .
+.It Ar addr6-list : ip6-addr Ns Op , Ns Ar addr6-list
+.It Ar ip6-addr :
+A host or subnet specified one of the following ways:
+.Bl -tag -width indent
+.It Ar numeric-ip | hostname
+Matches a single IPv6 address as allowed by
+.Xr inet_pton 3
+or a hostname.
+Hostnames are resolved at the time the rule is added to the firewall
+list.
+.It Ar addr Ns / Ns Ar masklen
+Matches all IPv6 addresses with base
+.Ar addr
+(specified as allowed by
+.Xr inet_pton 3
+or a hostname)
+and mask width of
+.Cm masklen
+bits.
+.It Ar addr Ns / Ns Ar mask
+Matches all IPv6 addresses with base
+.Ar addr
+(specified as allowed by
+.Xr inet_pton 3
+or a hostname)
+and the mask of
+.Ar mask ,
+specified as allowed by
+.Xr inet_pton 3 .
+As an example, fe::640:0:0/ffff::ffff:ffff:0:0 will match
+fe:*:*:*:0:640:*:*.
+This form is advised only for non-contiguous
+masks.
+It is better to resort to the
+.Ar addr Ns / Ns Ar masklen
+format for contiguous masks, which is more compact and less
+error-prone.
+.El
+.Pp
+No support for sets of IPv6 addresses is provided because IPv6 addresses
+are typically random past the initial prefix.
+.It Ar ports : Bro Ar port | port Ns \&- Ns Ar port Ns Brc Ns Op , Ns Ar ports
+For protocols which support port numbers (such as SCTP, TCP and UDP), optional
+.Cm ports
+may be specified as one or more ports or port ranges, separated
+by commas but no spaces, and an optional
+.Cm not
+operator.
+The
+.Ql \&-
+notation specifies a range of ports (including boundaries).
+.Pp
+Service names (from
+.Pa /etc/services )
+may be used instead of numeric port values.
+The length of the port list is limited to 30 ports or ranges,
+though one can specify larger ranges by using an
+.Em or-block
+in the
+.Cm options
+section of the rule.
+.Pp
+A backslash
+.Pq Ql \e
+can be used to escape the dash
+.Pq Ql -
+character in a service name (from a shell, the backslash must be
+typed twice to avoid the shell itself interpreting it as an escape
+character).
+.Pp
+.Dl "ipfw add count tcp from any ftp\e\e-data-ftp to any"
+.Pp
+Fragmented packets which have a non-zero offset (i.e., not the first
+fragment) will never match a rule which has one or more port
+specifications.
+See the
+.Cm frag
+option for details on matching fragmented packets.
+.El
+.Ss RULE OPTIONS (MATCH PATTERNS)
+Additional match patterns can be used within
+rules.
+Zero or more of these so-called
+.Em options
+can be present in a rule, optionally prefixed by the
+.Cm not
+operand, and possibly grouped into
+.Em or-blocks .
+.Pp
+The following match patterns can be used (listed in alphabetical order):
+.Bl -tag -width indent
+.It Cm // this is a comment .
+Inserts the specified text as a comment in the rule.
+Everything following // is considered as a comment and stored in the rule.
+You can have comment-only rules, which are listed as having a
+.Cm count
+action followed by the comment.
+.It Cm bridged
+Alias for
+.Cm layer2 .
+.It Cm defer-immediate-action | defer-action
+A rule with this option will not perform normal action
+upon a match.
+This option is intended to be used with
+.Cm record-state
+or
+.Cm keep-state
+as the dynamic rule, created but ignored on match, will work
+as intended.
+Rules with both
+.Cm record-state
+and
+.Cm defer-immediate-action
+create a dynamic rule and continue with the next rule without actually
+performing the action part of this rule.
+When the rule is later activated via the state table, the action is
+performed as usual.
+.It Cm diverted
+Matches only packets generated by a divert socket.
+.It Cm diverted-loopback
+Matches only packets coming from a divert socket back into the IP stack
+input for delivery.
+.It Cm diverted-output
+Matches only packets going from a divert socket back outward to the IP
+stack output for delivery.
+.It Cm dst-ip Ar ip-address
+Matches IPv4 packets whose destination IP is one of the address(es)
+specified as argument.
+.It Bro Cm dst-ip6 | dst-ipv6 Brc Ar ip6-address
+Matches IPv6 packets whose destination IP is one of the address(es)
+specified as argument.
+.It Cm dst-port Ar ports
+Matches IP packets whose destination port is one of the port(s)
+specified as argument.
+.It Cm established
+Matches TCP packets that have the RST or ACK bits set.
+.It Cm ext6hdr Ar header
+Matches IPv6 packets containing the extended header given by
+.Ar header .
+Supported headers are:
+.Pp
+Fragment,
+.Pq Cm frag ,
+Hop-to-hop options
+.Pq Cm hopopt ,
+any type of Routing Header
+.Pq Cm route ,
+Source routing Routing Header Type 0
+.Pq Cm rthdr0 ,
+Mobile IPv6 Routing Header Type 2
+.Pq Cm rthdr2 ,
+Destination options
+.Pq Cm dstopt ,
+IPSec authentication headers
+.Pq Cm ah ,
+and IPsec encapsulating security payload headers
+.Pq Cm esp .
+.It Cm fib Ar fibnum
+Matches a packet that has been tagged to use
+the given FIB (routing table) number.
+.It Cm flow Ar table Ns Pq Ar name Ns Op , Ns Ar value
+Search for the flow entry in lookup table
+.Ar name .
+If not found, the match fails.
+Otherwise, the match succeeds and
+.Cm tablearg
+is set to the value extracted from the table.
+.Pp
+This option can be useful to quickly dispatch traffic based on
+certain packet fields.
+See the
+.Sx LOOKUP TABLES
+section below for more information on lookup tables.
+.It Cm flow-id Ar labels
+Matches IPv6 packets containing any of the flow labels given in
+.Ar labels .
+.Ar labels
+is a comma separated list of numeric flow labels.
+.It Cm dst-mac Ar table Ns Pq Ar name Ns Op , Ns Ar value
+Search for the destination MAC address entry in lookup table
+.Ar name .
+If not found, the match fails.
+Otherwise, the match succeeds and
+.Cm tablearg
+is set to the value extracted from the table.
+.It Cm src-mac Ar table Ns Pq Ar name Ns Op , Ns Ar value
+Search for the source MAC address entry in lookup table
+.Ar name .
+If not found, the match fails.
+Otherwise, the match succeeds and
+.Cm tablearg
+is set to the value extracted from the table.
+.It Cm frag Ar spec
+Matches IPv4 packets whose
+.Cm ip_off
+field contains the comma separated list of IPv4 fragmentation
+options specified in
+.Ar spec .
+The recognized options are:
+.Cm df
+.Pq Dv don't fragment ,
+.Cm mf
+.Pq Dv more fragments ,
+.Cm rf
+.Pq Dv reserved fragment bit
+.Cm offset
+.Pq Dv non-zero fragment offset .
+The absence of a particular options may be denoted
+with a
+.Ql \&! .
+.Pp
+Empty list of options defaults to matching on non-zero fragment offset.
+Such rule would match all not the first fragment datagrams,
+both IPv4 and IPv6.
+This is a backward compatibility with older rulesets.
+.It Cm gid Ar group
+Matches all TCP or UDP packets sent by or received for a
+.Ar group .
+A
+.Ar group
+may be specified by name or number.
+.It Cm jail Ar jail
+Matches all TCP or UDP packets sent by or received for the
+jail whose ID or name is
+.Ar jail .
+.It Cm icmptypes Ar types
+Matches ICMP packets whose ICMP type is in the list
+.Ar types .
+The list may be specified as any combination of
+individual types (numeric) separated by commas.
+.Em Ranges are not allowed .
+The supported ICMP types are:
+.Pp
+echo reply
+.Pq Cm 0 ,
+destination unreachable
+.Pq Cm 3 ,
+source quench
+.Pq Cm 4 ,
+redirect
+.Pq Cm 5 ,
+echo request
+.Pq Cm 8 ,
+router advertisement
+.Pq Cm 9 ,
+router solicitation
+.Pq Cm 10 ,
+time-to-live exceeded
+.Pq Cm 11 ,
+IP header bad
+.Pq Cm 12 ,
+timestamp request
+.Pq Cm 13 ,
+timestamp reply
+.Pq Cm 14 ,
+information request
+.Pq Cm 15 ,
+information reply
+.Pq Cm 16 ,
+address mask request
+.Pq Cm 17
+and address mask reply
+.Pq Cm 18 .
+.It Cm icmp6types Ar types
+Matches ICMP6 packets whose ICMP6 type is in the list of
+.Ar types .
+The list may be specified as any combination of
+individual types (numeric) separated by commas.
+.Em Ranges are not allowed .
+.It Cm in | out
+Matches incoming or outgoing packets, respectively.
+.Cm in
+and
+.Cm out
+are mutually exclusive (in fact,
+.Cm out
+is implemented as
+.Cm not in Ns No ).
+.It Cm ipid Ar id-list
+Matches IPv4 packets whose
+.Cm ip_id
+field has value included in
+.Ar id-list ,
+which is either a single value or a list of values or ranges
+specified in the same way as
+.Ar ports .
+.It Cm iplen Ar len-list
+Matches IP packets whose total length, including header and data, is
+in the set
+.Ar len-list ,
+which is either a single value or a list of values or ranges
+specified in the same way as
+.Ar ports .
+.It Cm ipoptions Ar spec
+Matches packets whose IPv4 header contains the comma separated list of
+options specified in
+.Ar spec .
+The supported IP options are:
+.Pp
+.Cm ssrr
+(strict source route),
+.Cm lsrr
+(loose source route),
+.Cm rr
+(record packet route) and
+.Cm ts
+(timestamp).
+The absence of a particular option may be denoted
+with a
+.Ql \&! .
+.It Cm ipprecedence Ar precedence
+Matches IPv4 packets whose precedence field is equal to
+.Ar precedence .
+.It Cm ipsec
+Matches packets that have IPSEC history associated with them
+(i.e., the packet comes encapsulated in IPSEC, the kernel
+has IPSEC support, and can correctly decapsulate it).
+.Pp
+Note that specifying
+.Cm ipsec
+is different from specifying
+.Cm proto Ar ipsec
+as the latter will only look at the specific IP protocol field,
+irrespective of IPSEC kernel support and the validity of the IPSEC data.
+.Pp
+Further note that this flag is silently ignored in kernels without
+IPSEC support.
+It does not affect rule processing when given and the
+rules are handled as if with no
+.Cm ipsec
+flag.
+.It Cm iptos Ar spec
+Matches IPv4 packets whose
+.Cm tos
+field contains the comma separated list of
+service types specified in
+.Ar spec .
+The supported IP types of service are:
+.Pp
+.Cm lowdelay
+.Pq Dv IPTOS_LOWDELAY ,
+.Cm throughput
+.Pq Dv IPTOS_THROUGHPUT ,
+.Cm reliability
+.Pq Dv IPTOS_RELIABILITY ,
+.Cm mincost
+.Pq Dv IPTOS_MINCOST ,
+.Cm congestion
+.Pq Dv IPTOS_ECN_CE .
+The absence of a particular type may be denoted
+with a
+.Ql \&! .
+.It Cm dscp spec Ns Op , Ns Ar spec
+Matches IPv4/IPv6 packets whose
+.Cm DS
+field value is contained in
+.Ar spec
+mask.
+Multiple values can be specified via
+the comma separated list.
+Value can be one of keywords used in
+.Cm setdscp
+action or exact number.
+.It Cm ipttl Ar ttl-list
+Matches IPv4 packets whose time to live is included in
+.Ar ttl-list ,
+which is either a single value or a list of values or ranges
+specified in the same way as
+.Ar ports .
+.It Cm ipversion Ar ver
+Matches IP packets whose IP version field is
+.Ar ver .
+.It Cm keep-state Op Ar :flowname
+Upon a match, the firewall will create a dynamic rule, whose
+default behaviour is to match bidirectional traffic between
+source and destination IP/port using the same protocol.
+The rule has a limited lifetime (controlled by a set of
+.Xr sysctl 8
+variables), and the lifetime is refreshed every time a matching
+packet is found.
+The
+.Ar :flowname
+is used to assign additional to addresses, ports and protocol parameter
+to dynamic rule.
+It can be used for more accurate matching by
+.Cm check-state
+rule.
+The
+.Cm :default
+keyword is special name used for compatibility with old rulesets.
+.It Cm layer2
+Matches only layer2 packets, i.e., those passed to
+.Nm
+from
+.Fn ether_demux
+and
+.Fn ether_output_frame .
+.It Cm limit Bro Cm src-addr | src-port | dst-addr | dst-port Brc Ar N Op Ar :flowname
+The firewall will only allow
+.Ar N
+connections with the same
+set of parameters as specified in the rule.
+One or more
+of source and destination addresses and ports can be
+specified.
+.It Cm lookup Bro Cm dst-ip | dst-port | dst-mac | src-ip | src-port | src-mac | uid |
+.Cm jail | dscp | mark | rulenum Brc Ar name
+Search an entry in lookup table
+.Ar name
+that matches the field specified as argument.
+If not found, the match fails.
+Otherwise, the match succeeds and
+.Cm tablearg
+is set to the value extracted from the table.
+.Pp
+This option can be useful to quickly dispatch traffic based on
+certain packet fields.
+See the
+.Sx LOOKUP TABLES
+section below for more information on lookup tables.
+.It Cm { MAC | mac } Ar dst-mac src-mac
+Match packets with a given
+.Ar dst-mac
+and
+.Ar src-mac
+addresses, specified as the
+.Cm any
+keyword (matching any MAC address), or six groups of hex digits
+separated by colons,
+and optionally followed by a mask indicating the significant bits.
+The mask may be specified using either of the following methods:
+.Bl -enum -width indent
+.It
+A slash
+.Pq /
+followed by the number of significant bits.
+For example, an address with 33 significant bits could be specified as:
+.Pp
+.Dl "MAC 10:20:30:40:50:60/33 any"
+.It
+An ampersand
+.Pq &
+followed by a bitmask specified as six groups of hex digits separated
+by colons.
+For example, an address in which the last 16 bits are significant could
+be specified as:
+.Pp
+.Dl "MAC 10:20:30:40:50:60&00:00:00:00:ff:ff any"
+.Pp
+Note that the ampersand character has a special meaning in many shells
+and should generally be escaped.
+.El
+Note that the order of MAC addresses (destination first,
+source second) is
+the same as on the wire, but the opposite of the one used for
+IP addresses.
+.It Cm mac-type Ar mac-type
+Matches packets whose Ethernet Type field
+corresponds to one of those specified as argument.
+.Ar mac-type
+is specified in the same way as
+.Cm port numbers
+(i.e., one or more comma-separated single values or ranges).
+You can use symbolic names for known values such as
+.Em vlan , ipv4, ipv6 .
+Values can be entered as decimal or hexadecimal (if prefixed by 0x),
+and they are always printed as hexadecimal (unless the
+.Cm -N
+option is used, in which case symbolic resolution will be attempted).
+.It Cm proto Ar protocol
+Matches packets with the corresponding IP protocol.
+.It Cm record-state
+Upon a match, the firewall will create a dynamic rule as if
+.Cm keep-state
+was specified.
+However, this option doesn't imply an implicit
+.Cm check-state
+in contrast to
+.Cm keep-state .
+.It Cm recv | xmit | via Brq Ar ifX | Ar ifmask | Ar table Ns Po Ar name Ns Oo , Ns Ar value Oc Pc | Ar ipno | Ar any
+Matches packets received, transmitted or going through,
+respectively, the interface specified by exact name
+.Po Ar ifX Pc ,
+by device mask
+.Po Ar ifmask Pc ,
+by IP address, or through some interface.
+.Pp
+Interface
+name may be matched against
+.Ar ifmask
+with
+.Xr fnmatch 3
+according to the rules used by the shell (f.e. tun*).
+See also the
+.Sx EXAMPLES
+section.
+.Pp
+Table
+.Ar name
+may be used to match interface by its kernel ifindex.
+See the
+.Sx LOOKUP TABLES
+section below for more information on lookup tables.
+.Pp
+The
+.Cm via
+keyword causes the interface to always be checked.
+If
+.Cm recv
+or
+.Cm xmit
+is used instead of
+.Cm via ,
+then only the receive or transmit interface (respectively)
+is checked.
+By specifying both, it is possible to match packets based on
+both receive and transmit interface, e.g.:
+.Pp
+.Dl "ipfw add deny ip from any to any out recv ed0 xmit ed1"
+.Pp
+The
+.Cm recv
+interface can be tested on either incoming or outgoing packets,
+while the
+.Cm xmit
+interface can only be tested on outgoing packets.
+So
+.Cm out
+is required (and
+.Cm in
+is invalid) whenever
+.Cm xmit
+is used.
+.Pp
+A packet might not have a receive or transmit interface: packets
+originating from the local host have no receive interface,
+while packets destined for the local host have no transmit
+interface.
+.It Cm set-limit Bro Cm src-addr | src-port | dst-addr | dst-port Brc Ar N
+Works like
+.Cm limit
+but does not have an implicit
+.Cm check-state
+attached to it.
+.It Cm setup
+Matches TCP packets that have the SYN bit set but no ACK bit.
+This is the short form of
+.Dq Li tcpflags\ syn,!ack .
+.It Cm sockarg
+Matches packets that are associated to a local socket and
+for which the SO_USER_COOKIE socket option has been set
+to a non-zero value.
+As a side effect, the value of the
+option is made available as
+.Cm tablearg
+value, which in turn can be used as
+.Cm skipto
+or
+.Cm pipe
+number.
+.It Cm src-ip Ar ip-address
+Matches IPv4 packets whose source IP is one of the address(es)
+specified as an argument.
+.It Cm src-ip6 Ar ip6-address
+Matches IPv6 packets whose source IP is one of the address(es)
+specified as an argument.
+.It Cm src-port Ar ports
+Matches IP packets whose source port is one of the port(s)
+specified as argument.
+.It Cm tagged Ar tag-list
+Matches packets whose tags are included in
+.Ar tag-list ,
+which is either a single value or a list of values or ranges
+specified in the same way as
+.Ar ports .
+Tags can be applied to the packet using
+.Cm tag
+rule action parameter (see it's description for details on tags).
+.It Cm mark Ar value[:bitmask] | tablearg[:bitmask]
+Matches packets whose mark is equal to
+.Ar value
+with optional
+.Ar bitmask
+applied to it.
+.Cm tablearg
+can also be used instead of an explicit
+.Ar value
+to match a value supplied by the last table lookup.
+.Pp
+Both
+.Ar value
+and
+.Ar bitmask
+can be entered as decimal or hexadecimal (if prefixed by 0x), and they
+are always printed as hexadecimal.
+.It Cm tcpack Ar ack
+TCP packets only.
+Match if the TCP header acknowledgment number field is set to
+.Ar ack .
+.It Cm tcpdatalen Ar tcpdatalen-list
+Matches TCP packets whose length of TCP data is
+.Ar tcpdatalen-list ,
+which is either a single value or a list of values or ranges
+specified in the same way as
+.Ar ports .
+.It Cm tcpflags Ar spec
+TCP packets only.
+Match if the TCP header contains the comma separated list of
+flags specified in
+.Ar spec .
+The supported TCP flags are:
+.Pp
+.Cm fin ,
+.Cm syn ,
+.Cm rst ,
+.Cm psh ,
+.Cm ack
+and
+.Cm urg .
+The absence of a particular flag may be denoted
+with a
+.Ql \&! .
+A rule which contains a
+.Cm tcpflags
+specification can never match a fragmented packet which has
+a non-zero offset.
+See the
+.Cm frag
+option for details on matching fragmented packets.
+.It Cm tcpmss Ar tcpmss-list
+Matches TCP packets whose MSS (maximum segment size) value is set to
+.Ar tcpmss-list ,
+which is either a single value or a list of values or ranges
+specified in the same way as
+.Ar ports .
+.It Cm tcpseq Ar seq
+TCP packets only.
+Match if the TCP header sequence number field is set to
+.Ar seq .
+.It Cm tcpwin Ar tcpwin-list
+Matches TCP packets whose header window field is set to
+.Ar tcpwin-list ,
+which is either a single value or a list of values or ranges
+specified in the same way as
+.Ar ports .
+.It Cm tcpoptions Ar spec
+TCP packets only.
+Match if the TCP header contains the comma separated list of
+options specified in
+.Ar spec .
+The supported TCP options are:
+.Pp
+.Cm mss
+(maximum segment size),
+.Cm window
+(tcp window advertisement),
+.Cm sack
+(selective ack),
+.Cm ts
+(rfc1323 timestamp) and
+.Cm cc
+(rfc1644 t/tcp connection count).
+The absence of a particular option may be denoted
+with a
+.Ql \&! .
+.It Cm uid Ar user
+Match all TCP or UDP packets sent by or received for a
+.Ar user .
+A
+.Ar user
+may be matched by name or identification number.
+.It Cm verrevpath
+For incoming packets,
+a routing table lookup is done on the packet's source address.
+If the interface on which the packet entered the system matches the
+outgoing interface for the route,
+the packet matches.
+If the interfaces do not match up,
+the packet does not match.
+All outgoing packets or packets with no incoming interface match.
+.Pp
+The name and functionality of the option is intentionally similar to
+the Cisco IOS command:
+.Pp
+.Dl ip verify unicast reverse-path
+.Pp
+This option can be used to make anti-spoofing rules to reject all
+packets with source addresses not from this interface.
+See also the option
+.Cm antispoof .
+.It Cm versrcreach
+For incoming packets,
+a routing table lookup is done on the packet's source address.
+If a route to the source address exists, but not the default route
+or a blackhole/reject route, the packet matches.
+Otherwise, the packet does not match.
+All outgoing packets match.
+.Pp
+The name and functionality of the option is intentionally similar to
+the Cisco IOS command:
+.Pp
+.Dl ip verify unicast source reachable-via any
+.Pp
+This option can be used to make anti-spoofing rules to reject all
+packets whose source address is unreachable.
+.It Cm antispoof
+For incoming packets, the packet's source address is checked if it
+belongs to a directly connected network.
+If the network is directly connected, then the interface the packet
+came on in is compared to the interface the network is connected to.
+When incoming interface and directly connected interface are not the
+same, the packet does not match.
+Otherwise, the packet does match.
+All outgoing packets match.
+.Pp
+This option can be used to make anti-spoofing rules to reject all
+packets that pretend to be from a directly connected network but do
+not come in through that interface.
+This option is similar to but more restricted than
+.Cm verrevpath
+because it engages only on packets with source addresses of directly
+connected networks instead of all source addresses.
+.El
+.Sh LOOKUP TABLES
+Lookup tables are useful to handle large sparse sets of
+addresses or other search keys (e.g., ports, jail IDs, interface names).
+In the rest of this section we will use the term ``key''.
+Table name needs to match the following spec:
+.Ar table-name .
+Tables with the same name can be created in different
+.Ar sets .
+However, rule links to the tables in
+.Ar set 0
+by default.
+This behavior can be controlled by
+.Va net.inet.ip.fw.tables_sets
+variable.
+See the
+.Sx SETS OF RULES
+section for more information.
+There may be up to 65535 different lookup tables.
+.Pp
+The following table types are supported:
+.Bl -tag -width indent
+.It Ar table-type : Ar addr | iface | number | flow | mac
+.It Ar table-key : Ar addr Ns Oo / Ns Ar masklen Oc | iface-name | number | flow-spec
+.It Ar flow-spec : Ar flow-field Ns Op , Ns Ar flow-spec
+.It Ar flow-field : src-ip | proto | src-port | dst-ip | dst-port
+.It Cm addr
+Matches IPv4 or IPv6 address.
+Each entry is represented by an
+.Ar addr Ns Op / Ns Ar masklen
+and will match all addresses with base
+.Ar addr
+(specified as an IPv4/IPv6 address, or a hostname) and mask width of
+.Ar masklen
+bits.
+If
+.Ar masklen
+is not specified, it defaults to 32 for IPv4 and 128 for IPv6.
+When looking up an IP address in a table, the most specific
+entry will match.
+.It Cm iface
+Matches interface names.
+Each entry is represented by string treated as interface name.
+Wildcards are not supported.
+.It Cm number
+Matches protocol ports, uids/gids or jail IDs.
+Each entry is represented by 32-bit unsigned integer.
+Ranges are not supported.
+.It Cm flow
+Matches packet fields specified by
+.Ar flow
+type suboptions with table entries.
+.It Cm mac
+Matches MAC address.
+Each entry is represented by an
+.Ar addr Ns Op / Ns Ar masklen
+and will match all addresses with base
+.Ar addr
+and mask width of
+.Ar masklen
+bits.
+If
+.Ar masklen
+is not specified, it defaults to 48.
+When looking up an MAC address in a table, the most specific
+entry will match.
+.El
+.Pp
+Tables require explicit creation via
+.Cm create
+before use.
+.Pp
+The following creation options are supported:
+.Bl -tag -width indent
+.It Ar create-options : Ar create-option | create-options
+.It Ar create-option : Cm type Ar table-type | Cm valtype Ar value-mask | Cm algo Ar algo-desc |
+.Cm limit Ar number | Cm locked | Cm missing | Cm or-flush
+.It Cm type
+Table key type.
+.It Cm valtype
+Table value mask.
+.It Cm algo
+Table algorithm to use (see below).
+.It Cm limit
+Maximum number of items that may be inserted into table.
+.It Cm locked
+Restrict any table modifications.
+.It Cm missing
+Do not fail if table already exists and has exactly same options as new one.
+.It Cm or-flush
+Flush existing table with same name instead of returning error.
+Implies
+.Cm missing
+so existing table must be compatible with new one.
+.El
+.Pp
+Some of these options may be modified later via
+.Cm modify
+keyword.
+The following options can be changed:
+.Bl -tag -width indent
+.It Ar modify-options : Ar modify-option | modify-options
+.It Ar modify-option : Cm limit Ar number
+.It Cm limit
+Alter maximum number of items that may be inserted into table.
+.El
+.Pp
+Additionally, table can be locked or unlocked using
+.Cm lock
+or
+.Cm unlock
+commands.
+.Pp
+Tables of the same
+.Ar type
+can be swapped with each other using
+.Cm swap Ar name
+command.
+Swap may fail if tables limits are set and data exchange
+would result in limits hit.
+Operation is performed atomically.
+.Pp
+One or more entries can be added to a table at once using
+.Cm add
+command.
+Addition of all items are performed atomically.
+By default, error in addition of one entry does not influence
+addition of other entries.
+However, non-zero error code is returned in that case.
+Special
+.Cm atomic
+keyword may be specified before
+.Cm add
+to indicate all-or-none add request.
+.Pp
+One or more entries can be removed from a table at once using
+.Cm delete
+command.
+By default, error in removal of one entry does not influence
+removing of other entries.
+However, non-zero error code is returned in that case.
+.Pp
+It may be possible to check what entry will be found on particular
+.Ar table-key
+using
+.Cm lookup
+.Ar table-key
+command.
+This functionality is optional and may be unsupported in some algorithms.
+.Pp
+The following operations can be performed on
+.Ar one
+or
+.Cm all
+tables:
+.Bl -tag -width indent
+.It Cm list
+List all entries.
+.It Cm flush
+Removes all entries.
+.It Cm info
+Shows generic table information.
+.It Cm detail
+Shows generic table information and algo-specific data.
+.El
+.Pp
+The following lookup algorithms are supported:
+.Bl -tag -width indent
+.It Ar algo-desc : algo-name | "algo-name algo-data"
+.It Ar algo-name : Ar addr: radix | addr: hash | iface: array | number: array | flow: hash | mac: radix
+.It Cm addr: radix
+Separate Radix trees for IPv4 and IPv6, the same way as the routing table (see
+.Xr route 4 ) .
+Default choice for
+.Ar addr
+type.
+.It Cm addr:hash
+Separate auto-growing hashes for IPv4 and IPv6.
+Accepts entries with the same mask length specified initially via
+.Cm "addr:hash masks=/v4,/v6"
+algorithm creation options.
+Assume /32 and /128 masks by default.
+Search removes host bits (according to mask) from supplied address and checks
+resulting key in appropriate hash.
+Mostly optimized for /64 and byte-ranged IPv6 masks.
+.It Cm iface:array
+Array storing sorted indexes for entries which are presented in the system.
+Optimized for very fast lookup.
+.It Cm number:array
+Array storing sorted u32 numbers.
+.It Cm flow:hash
+Auto-growing hash storing flow entries.
+Search calculates hash on required packet fields and searches for matching
+entries in selected bucket.
+.It Cm mac: radix
+Radix tree for MAC address
+.El
+.Pp
+The
+.Cm tablearg
+feature provides the ability to use a value, looked up in the table, as
+the argument for a rule action, action parameter or rule option.
+This can significantly reduce number of rules in some configurations.
+If two tables are used in a rule, the result of the second (destination)
+is used.
+.Pp
+Each record may hold one or more values according to
+.Ar value-mask .
+This mask is set on table creation via
+.Cm valtype
+option.
+The following value types are supported:
+.Bl -tag -width indent
+.It Ar value-mask : Ar value-type Ns Op , Ns Ar value-mask
+.It Ar value-type : Ar skipto | pipe | fib | nat | dscp | tag | divert |
+.Ar netgraph | limit | ipv4 | ipv6 | mark
+.It Cm skipto
+rule number to jump to.
+.It Cm pipe
+Pipe number to use.
+.It Cm fib
+fib number to match/set.
+.It Cm nat
+nat number to jump to.
+.It Cm dscp
+dscp value to match/set.
+.It Cm tag
+tag number to match/set.
+.It Cm divert
+port number to divert traffic to.
+.It Cm netgraph
+hook number to move packet to.
+.It Cm limit
+maximum number of connections.
+.It Cm ipv4
+IPv4 nexthop to fwd packets to.
+.It Cm ipv6
+IPv6 nexthop to fwd packets to.
+.It Cm mark
+mark value to match/set.
+.El
+.Pp
+The
+.Cm tablearg
+argument can be used with the following actions:
+.Cm nat, pipe, queue, divert, tee, netgraph, ngtee, fwd, skipto, setfib ,
+.Cm setmark ,
+action parameters:
+.Cm tag, untag ,
+rule options:
+.Cm limit, tagged, mark .
+.Pp
+When used with the
+.Cm skipto
+action, the user should be aware that the code will walk the ruleset
+up to a rule equal to, or past, the given number.
+.Pp
+See the
+.Sx EXAMPLES
+Section for example usage of tables and the tablearg keyword.
+.Sh SETS OF RULES
+Each rule or table belongs to one of 32 different
+.Em sets
+, numbered 0 to 31.
+Set 31 is reserved for the default rule.
+.Pp
+By default, rules or tables are put in set 0, unless you use the
+.Cm set N
+attribute when adding a new rule or table.
+Sets can be individually and atomically enabled or disabled,
+so this mechanism permits an easy way to store multiple configurations
+of the firewall and quickly (and atomically) switch between them.
+.Pp
+By default, tables from set 0 are referenced when adding rule with
+table opcodes regardless of rule set.
+This behavior can be changed by setting
+.Va net.inet.ip.fw.tables_sets
+variable to 1.
+Rule's set will then be used for table references.
+.Pp
+The command to enable/disable sets is
+.Bd -ragged -offset indent
+.Nm
+.Cm set Oo Cm disable Ar number ... Oc Op Cm enable Ar number ...
+.Ed
+.Pp
+where multiple
+.Cm enable
+or
+.Cm disable
+sections can be specified.
+Command execution is atomic on all the sets specified in the command.
+By default, all sets are enabled.
+.Pp
+When you disable a set, its rules behave as if they do not exist
+in the firewall configuration, with only one exception:
+.Bd -ragged -offset indent
+dynamic rules created from a rule before it had been disabled
+will still be active until they expire.
+In order to delete
+dynamic rules you have to explicitly delete the parent rule
+which generated them.
+.Ed
+.Pp
+The set number of rules can be changed with the command
+.Bd -ragged -offset indent
+.Nm
+.Cm set move
+.Brq Cm rule Ar rule-number | old-set
+.Cm to Ar new-set
+.Ed
+.Pp
+Also, you can atomically swap two rulesets with the command
+.Bd -ragged -offset indent
+.Nm
+.Cm set swap Ar first-set second-set
+.Ed
+.Pp
+See the
+.Sx EXAMPLES
+Section on some possible uses of sets of rules.
+.Sh STATEFUL FIREWALL
+Stateful operation is a way for the firewall to dynamically
+create rules for specific flows when packets that
+match a given pattern are detected.
+Support for stateful
+operation comes through the
+.Cm check-state , keep-state , record-state , limit
+and
+.Cm set-limit
+options of
+.Nm rules .
+.Pp
+Dynamic rules are created when a packet matches a
+.Cm keep-state ,
+.Cm record-state ,
+.Cm limit
+or
+.Cm set-limit
+rule, causing the creation of a
+.Em dynamic
+rule which will match all and only packets with
+a given
+.Em protocol
+between a
+.Em src-ip/src-port dst-ip/dst-port
+pair of addresses
+.Em ( src
+and
+.Em dst
+are used here only to denote the initial match addresses, but they
+are completely equivalent afterwards).
+Rules created by
+.Cm keep-state
+option also have a
+.Ar :flowname
+taken from it.
+This name is used in matching together with addresses, ports and protocol.
+Dynamic rules will be checked at the first
+.Cm check-state, keep-state
+or
+.Cm limit
+occurrence, and the action performed upon a match will be the same
+as in the parent rule.
+.Pp
+Note that no additional attributes other than protocol and IP addresses
+and ports and :flowname are checked on dynamic rules.
+.Pp
+The typical use of dynamic rules is to keep a closed firewall configuration,
+but let the first TCP SYN packet from the inside network install a
+dynamic rule for the flow so that packets belonging to that session
+will be allowed through the firewall:
+.Pp
+.Dl "ipfw add check-state :OUTBOUND"
+.Dl "ipfw add allow tcp from my-subnet to any setup keep-state :OUTBOUND"
+.Dl "ipfw add deny tcp from any to any"
+.Pp
+A similar approach can be used for UDP, where an UDP packet coming
+from the inside will install a dynamic rule to let the response through
+the firewall:
+.Pp
+.Dl "ipfw add check-state :OUTBOUND"
+.Dl "ipfw add allow udp from my-subnet to any keep-state :OUTBOUND"
+.Dl "ipfw add deny udp from any to any"
+.Pp
+Dynamic rules expire after some time, which depends on the status
+of the flow and the setting of some
+.Cm sysctl
+variables.
+See Section
+.Sx SYSCTL VARIABLES
+for more details.
+For TCP sessions, dynamic rules can be instructed to periodically
+send keepalive packets to refresh the state of the rule when it is
+about to expire.
+.Pp
+See Section
+.Sx EXAMPLES
+for more examples on how to use dynamic rules.
+.Sh TRAFFIC SHAPER (DUMMYNET) CONFIGURATION
+.Nm
+is also the user interface for the
+.Nm dummynet
+traffic shaper, packet scheduler and network emulator, a subsystem that
+can artificially queue, delay or drop packets
+emulating the behaviour of certain network links
+or queueing systems.
+.Pp
+.Nm dummynet
+operates by first using the firewall to select packets
+using any match pattern that can be used in
+.Nm
+rules.
+Matching packets are then passed to either of two
+different objects, which implement the traffic regulation:
+.Bl -hang -offset XXXX
+.It Em pipe
+A
+.Em pipe
+emulates a
+.Em link
+with given bandwidth and propagation delay,
+driven by a FIFO scheduler and a single queue with programmable
+queue size and packet loss rate.
+Packets are appended to the queue as they come out from
+.Nm ipfw ,
+and then transferred in FIFO order to the link at the desired rate.
+.It Em queue
+A
+.Em queue
+is an abstraction used to implement packet scheduling
+using one of several packet scheduling algorithms.
+Packets sent to a
+.Em queue
+are first grouped into flows according to a mask on the 5-tuple.
+Flows are then passed to the scheduler associated to the
+.Em queue ,
+and each flow uses scheduling parameters (weight and others)
+as configured in the
+.Em queue
+itself.
+A scheduler in turn is connected to an emulated link,
+and arbitrates the link's bandwidth among backlogged flows according to
+weights and to the features of the scheduling algorithm in use.
+.El
+.Pp
+In practice,
+.Em pipes
+can be used to set hard limits to the bandwidth that a flow can use, whereas
+.Em queues
+can be used to determine how different flows share the available bandwidth.
+.Pp
+A graphical representation of the binding of queues,
+flows, schedulers and links is below.
+.Bd -literal -offset indent
+ (flow_mask|sched_mask) sched_mask
+ +---------+ weight Wx +-------------+
+ | |->-[flow]-->--| |-+
+ -->--| QUEUE x | ... | | |
+ | |->-[flow]-->--| SCHEDuler N | |
+ +---------+ | | |
+ ... | +--[LINK N]-->--
+ +---------+ weight Wy | | +--[LINK N]-->--
+ | |->-[flow]-->--| | |
+ -->--| QUEUE y | ... | | |
+ | |->-[flow]-->--| | |
+ +---------+ +-------------+ |
+ +-------------+
+.Ed
+It is important to understand the role of the SCHED_MASK
+and FLOW_MASK, which are configured through the commands
+.Dl "ipfw sched N config mask SCHED_MASK ..."
+and
+.Dl "ipfw queue X config mask FLOW_MASK ..." .
+.Pp
+The SCHED_MASK is used to assign flows to one or more
+scheduler instances, one for each
+value of the packet's 5-tuple after applying SCHED_MASK.
+As an example, using ``src-ip 0xffffff00'' creates one instance
+for each /24 destination subnet.
+.Pp
+The FLOW_MASK, together with the SCHED_MASK, is used to split
+packets into flows.
+As an example, using
+``src-ip 0x000000ff''
+together with the previous SCHED_MASK makes a flow for
+each individual source address.
+In turn, flows for each /24
+subnet will be sent to the same scheduler instance.
+.Pp
+The above diagram holds even for the
+.Em pipe
+case, with the only restriction that a
+.Em pipe
+only supports a SCHED_MASK, and forces the use of a FIFO
+scheduler (these are for backward compatibility reasons;
+in fact, internally, a
+.Nm dummynet's
+pipe is implemented exactly as above).
+.Pp
+There are two modes of
+.Nm dummynet
+operation:
+.Dq normal
+and
+.Dq fast .
+The
+.Dq normal
+mode tries to emulate a real link: the
+.Nm dummynet
+scheduler ensures that the packet will not leave the pipe faster than it
+would on the real link with a given bandwidth.
+The
+.Dq fast
+mode allows certain packets to bypass the
+.Nm dummynet
+scheduler (if packet flow does not exceed pipe's bandwidth).
+This is the reason why the
+.Dq fast
+mode requires less CPU cycles per packet (on average) and packet latency
+can be significantly lower in comparison to a real link with the same
+bandwidth.
+The default mode is
+.Dq normal .
+The
+.Dq fast
+mode can be enabled by setting the
+.Va net.inet.ip.dummynet.io_fast
+.Xr sysctl 8
+variable to a non-zero value.
+.Ss PIPE, QUEUE AND SCHEDULER CONFIGURATION
+The
+.Em pipe ,
+.Em queue
+and
+.Em scheduler
+configuration commands are the following:
+.Bd -ragged -offset indent
+.Cm pipe Ar number Cm config Ar pipe-configuration
+.Pp
+.Cm queue Ar number Cm config Ar queue-configuration
+.Pp
+.Cm sched Ar number Cm config Ar sched-configuration
+.Ed
+.Pp
+The following parameters can be configured for a pipe:
+.Pp
+.Bl -tag -width indent -compact
+.It Cm bw Ar bandwidth | device
+Bandwidth, measured in
+.Sm off
+.Op Cm K | M | G
+.Brq Cm bit/s | Byte/s .
+.Sm on
+.Pp
+A value of 0 (default) means unlimited bandwidth.
+The unit must immediately follow the number, as in
+.Pp
+.Dl "dnctl pipe 1 config bw 300Kbit/s"
+.Pp
+If a device name is specified instead of a numeric value, as in
+.Pp
+.Dl "dnctl pipe 1 config bw tun0"
+.Pp
+then the transmit clock is supplied by the specified device.
+At the moment only the
+.Xr tun 4
+device supports this
+functionality, for use in conjunction with
+.Xr ppp 8 .
+.Pp
+.It Cm delay Ar ms-delay
+Propagation delay, measured in milliseconds.
+The value is rounded to the next multiple of the clock tick
+(typically 10ms, but it is a good practice to run kernels
+with
+.Dq "options HZ=1000"
+to reduce
+the granularity to 1ms or less).
+The default value is 0, meaning no delay.
+.Pp
+.It Cm burst Ar size
+If the data to be sent exceeds the pipe's bandwidth limit
+(and the pipe was previously idle), up to
+.Ar size
+bytes of data are allowed to bypass the
+.Nm dummynet
+scheduler, and will be sent as fast as the physical link allows.
+Any additional data will be transmitted at the rate specified
+by the
+.Nm pipe
+bandwidth.
+The burst size depends on how long the pipe has been idle;
+the effective burst size is calculated as follows:
+MAX(
+.Ar size
+,
+.Nm bw
+* pipe_idle_time).
+.Pp
+.It Cm profile Ar filename
+A file specifying the additional overhead incurred in the transmission
+of a packet on the link.
+.Pp
+Some link types introduce extra delays in the transmission
+of a packet, e.g., because of MAC level framing, contention on
+the use of the channel, MAC level retransmissions and so on.
+From our point of view, the channel is effectively unavailable
+for this extra time, which is constant or variable depending
+on the link type.
+Additionally, packets may be dropped after this
+time (e.g., on a wireless link after too many retransmissions).
+We can model the additional delay with an empirical curve
+that represents its distribution.
+.Bd -literal -offset indent
+ cumulative probability
+ 1.0 ^
+ |
+ L +-- loss-level x
+ | ******
+ | *
+ | *****
+ | *
+ | **
+ | *
+ +-------*------------------->
+ delay
+.Ed
+The empirical curve may have both vertical and horizontal lines.
+Vertical lines represent constant delay for a range of
+probabilities.
+Horizontal lines correspond to a discontinuity in the delay
+distribution: the pipe will use the largest delay for a
+given probability.
+.Pp
+The file format is the following, with whitespace acting as
+a separator and '#' indicating the beginning a comment:
+.Bl -tag -width indent
+.It Cm name Ar identifier
+optional name (listed by "dnctl pipe show")
+to identify the delay distribution;
+.It Cm bw Ar value
+the bandwidth used for the pipe.
+If not specified here, it must be present
+explicitly as a configuration parameter for the pipe;
+.It Cm loss-level Ar L
+the probability above which packets are lost.
+(0.0 <= L <= 1.0, default 1.0 i.e., no loss);
+.It Cm samples Ar N
+the number of samples used in the internal
+representation of the curve (2..1024; default 100);
+.It Cm "delay prob" | "prob delay"
+One of these two lines is mandatory and defines
+the format of the following lines with data points.
+.It Ar XXX Ar YYY
+2 or more lines representing points in the curve,
+with either delay or probability first, according
+to the chosen format.
+The unit for delay is milliseconds.
+Data points do not need to be sorted.
+Also, the number of actual lines can be different
+from the value of the "samples" parameter:
+.Nm
+utility will sort and interpolate
+the curve as needed.
+.El
+.Pp
+Example of a profile file:
+.Bd -literal -offset indent
+name bla_bla_bla
+samples 100
+loss-level 0.86
+prob delay
+0 200 # minimum overhead is 200ms
+0.5 200
+0.5 300
+0.8 1000
+0.9 1300
+1 1300
+#configuration file end
+.Ed
+.El
+.Pp
+The following parameters can be configured for a queue:
+.Pp
+.Bl -tag -width indent -compact
+.It Cm pipe Ar pipe_nr
+Connects a queue to the specified pipe.
+Multiple queues (with the same or different weights) can be connected to
+the same pipe, which specifies the aggregate rate for the set of queues.
+.Pp
+.It Cm weight Ar weight
+Specifies the weight to be used for flows matching this queue.
+The weight must be in the range 1..100, and defaults to 1.
+.El
+.Pp
+The following case-insensitive parameters can be configured for a
+scheduler:
+.Pp
+.Bl -tag -width indent -compact
+.It Cm type Ar {fifo | wf2q+ | rr | qfq | fq_codel | fq_pie}
+specifies the scheduling algorithm to use.
+.Bl -tag -width indent -compact
+.It Cm fifo
+is just a FIFO scheduler (which means that all packets
+are stored in the same queue as they arrive to the scheduler).
+FIFO has O(1) per-packet time complexity, with very low
+constants (estimate 60-80ns on a 2GHz desktop machine)
+but gives no service guarantees.
+.It Cm wf2q+
+implements the WF2Q+ algorithm, which is a Weighted Fair Queueing
+algorithm which permits flows to share bandwidth according to
+their weights.
+Note that weights are not priorities; even a flow
+with a minuscule weight will never starve.
+WF2Q+ has O(log N) per-packet processing cost, where N is the number
+of flows, and is the default algorithm used by previous versions
+dummynet's queues.
+.It Cm rr
+implements the Deficit Round Robin algorithm, which has O(1) processing
+costs (roughly, 100-150ns per packet)
+and permits bandwidth allocation according to weights, but
+with poor service guarantees.
+.It Cm qfq
+implements the QFQ algorithm, which is a very fast variant of
+WF2Q+, with similar service guarantees and O(1) processing
+costs (roughly, 200-250ns per packet).
+.It Cm fq_codel
+implements the FQ-CoDel (FlowQueue-CoDel) scheduler/AQM algorithm, which
+uses a modified Deficit Round Robin scheduler to manage two lists of sub-queues
+(old sub-queues and new sub-queues) for providing brief periods of priority to
+lightweight or short burst flows.
+By default, the total number of sub-queues is 1024.
+FQ-CoDel's internal, dynamically
+created sub-queues are controlled by separate instances of CoDel AQM.
+.It Cm fq_pie
+implements the FQ-PIE (FlowQueue-PIE) scheduler/AQM algorithm, which similar to
+.Cm fq_codel
+but uses per sub-queue PIE AQM instance to control the queue delay.
+.El
+.Pp
+.Cm fq_codel
+inherits AQM parameters and options from
+.Cm codel
+(see below), and
+.Cm fq_pie
+inherits AQM parameters and options from
+.Cm pie
+(see below).
+Additionally, both of
+.Cm fq_codel
+and
+.Cm fq_pie
+have shared scheduler parameters which are:
+.Bl -tag -width indent
+.It Cm quantum
+.Ar m
+specifies the quantum (credit) of the scheduler.
+.Ar m
+is the number of bytes a queue can serve before being moved to the tail
+of old queues list.
+The default is 1514 bytes, and the maximum acceptable value
+is 9000 bytes.
+.It Cm limit
+.Ar m
+specifies the hard size limit (in unit of packets) of all queues managed by an
+instance of the scheduler.
+The default value of
+.Ar m
+is 10240 packets, and the maximum acceptable value is 20480 packets.
+.It Cm flows
+.Ar m
+specifies the total number of flow queues (sub-queues) that fq_*
+creates and manages.
+By default, 1024 sub-queues are created when an instance
+of the fq_{codel/pie} scheduler is created.
+The maximum acceptable value is
+65536.
+.El
+.Pp
+Note that any token after
+.Cm fq_codel
+or
+.Cm fq_pie
+is considered a parameter for fq_{codel/pie}.
+So, ensure all scheduler
+configuration options not related to fq_{codel/pie} are written before
+.Cm fq_codel/fq_pie
+tokens.
+.El
+.Pp
+In addition to the type, all parameters allowed for a pipe can also
+be specified for a scheduler.
+.Pp
+Finally, the following parameters can be configured for both
+pipes and queues:
+.Pp
+.Bl -tag -width XXXX -compact
+.It Cm buckets Ar hash-table-size
+Specifies the size of the hash table used for storing the
+various queues.
+Default value is 64 controlled by the
+.Xr sysctl 8
+variable
+.Va net.inet.ip.dummynet.hash_size ,
+allowed range is 16 to 65536.
+.Pp
+.It Cm mask Ar mask-specifier
+Packets sent to a given pipe or queue by an
+.Nm
+rule can be further classified into multiple flows, each of which is then
+sent to a different
+.Em dynamic
+pipe or queue.
+A flow identifier is constructed by masking the IP addresses,
+ports and protocol types as specified with the
+.Cm mask
+options in the configuration of the pipe or queue.
+For each different flow identifier, a new pipe or queue is created
+with the same parameters as the original object, and matching packets
+are sent to it.
+.Pp
+Thus, when
+.Em dynamic pipes
+are used, each flow will get the same bandwidth as defined by the pipe,
+whereas when
+.Em dynamic queues
+are used, each flow will share the parent's pipe bandwidth evenly
+with other flows generated by the same queue (note that other queues
+with different weights might be connected to the same pipe).
+.br
+Available mask specifiers are a combination of one or more of the following:
+.Pp
+.Cm dst-ip Ar mask ,
+.Cm dst-ip6 Ar mask ,
+.Cm src-ip Ar mask ,
+.Cm src-ip6 Ar mask ,
+.Cm dst-port Ar mask ,
+.Cm src-port Ar mask ,
+.Cm flow-id Ar mask ,
+.Cm proto Ar mask
+or
+.Cm all ,
+.Pp
+where the latter means all bits in all fields are significant.
+.Pp
+.It Cm noerror
+When a packet is dropped by a
+.Nm dummynet
+queue or pipe, the error
+is normally reported to the caller routine in the kernel, in the
+same way as it happens when a device queue fills up.
+Setting this
+option reports the packet as successfully delivered, which can be
+needed for some experimental setups where you want to simulate
+loss or congestion at a remote router.
+.Pp
+.It Cm plr Ar packet-loss-rate
+.It Cm plr Ar K,p,H,r
+Packet loss rate.
+Argument
+.Ar packet-loss-rate
+is a floating-point number between 0 and 1, with 0 meaning no
+loss, 1 meaning 100% loss.
+.Pp
+When invoked with four arguments, the simple Gilbert-Elliott
+channel model with two states (Good and Bad) is used.
+.Bd -literal -offset indent
+ r
+ .----------------.
+ v |
+ .------------. .------------.
+ | G | | B |
+ | drop (K) | | drop (H) |
+ '------------' '------------'
+ | ^
+ '----------------'
+ p
+
+.Ed
+This has the associated probabilities
+.Po Ar K
+and
+.Ar H Pc
+for the loss probability.
+This is different from the literature, where this model is described with
+probabilities of successful transmission k and h.
+However, converting from literature is easy:
+.Pp
+K = 1 - k ; H = 1 - h
+.Pp
+This is to retain consistency within the interface and allow the
+quick re-use of loss probability when giving only a single argument.
+In addition the state change probabilities
+.Po Ar p
+and
+.Ar r Pc
+are given.
+All of the above probabilities are internally represented on 31 bits.
+.Pp
+.It Cm queue Brq Ar slots | size Ns Cm Kbytes
+Queue size, in
+.Ar slots
+or
+.Cm KBytes .
+Default value is 50 slots, which
+is the typical queue size for Ethernet devices.
+Note that for slow speed links you should keep the queue
+size short or your traffic might be affected by a significant
+queueing delay.
+E.g., 50 max-sized Ethernet packets (1500 bytes) mean 600Kbit
+or 20s of queue on a 30Kbit/s pipe.
+Even worse effects can result if you get packets from an
+interface with a much larger MTU, e.g.\& the loopback interface
+with its 16KB packets.
+The
+.Xr sysctl 8
+variables
+.Em net.inet.ip.dummynet.pipe_byte_limit
+and
+.Em net.inet.ip.dummynet.pipe_slot_limit
+control the maximum lengths that can be specified.
+.Pp
+.It Cm red | gred Ar w_q Ns / Ns Ar min_th Ns / Ns Ar max_th Ns / Ns Ar max_p
+[ecn]
+Make use of the RED (Random Early Detection) queue management algorithm.
+.Ar w_q
+and
+.Ar max_p
+are floating
+point numbers between 0 and 1 (inclusive), while
+.Ar min_th
+and
+.Ar max_th
+are integer numbers specifying thresholds for queue management
+(thresholds are computed in bytes if the queue has been defined
+in bytes, in slots otherwise).
+The two parameters can also be of the same value if needed.
+The
+.Nm dummynet
+also supports the gentle RED variant (gred) and ECN (Explicit Congestion
+Notification) as optional.
+Three
+.Xr sysctl 8
+variables can be used to control the RED behaviour:
+.Bl -tag -width indent
+.It Va net.inet.ip.dummynet.red_lookup_depth
+specifies the accuracy in computing the average queue
+when the link is idle (defaults to 256, must be greater than zero)
+.It Va net.inet.ip.dummynet.red_avg_pkt_size
+specifies the expected average packet size (defaults to 512, must be
+greater than zero)
+.It Va net.inet.ip.dummynet.red_max_pkt_size
+specifies the expected maximum packet size, only used when queue
+thresholds are in bytes (defaults to 1500, must be greater than zero).
+.El
+.Pp
+.It Cm codel Oo Cm target Ar time Oc Oo Cm interval Ar time Oc Oo Cm ecn |
+.Cm noecn Oc
+Make use of the CoDel (Controlled-Delay) queue management algorithm.
+.Ar time
+is interpreted as milliseconds by default but seconds (s), milliseconds (ms) or
+microseconds (us) can be specified instead.
+CoDel drops or marks (ECN) packets
+depending on packet sojourn time in the queue.
+.Cm target
+.Ar time
+(5ms by default) is the minimum acceptable persistent queue delay that CoDel
+allows.
+CoDel does not drop packets directly after packets sojourn time becomes
+higher than
+.Cm target
+.Ar time
+but waits for
+.Cm interval
+.Ar time
+(100ms default) before dropping.
+.Cm interval
+.Ar time
+should be set to maximum RTT for all expected connections.
+.Cm ecn
+enables (disabled by default) packet marking (instead of dropping) for
+ECN-enabled TCP flows when queue delay becomes high.
+.Pp
+Note that any token after
+.Cm codel
+is considered a parameter for CoDel.
+So, ensure all pipe/queue
+configuration options are written before
+.Cm codel
+token.
+.Pp
+The
+.Xr sysctl 8
+variables
+.Va net.inet.ip.dummynet.codel.target
+and
+.Va net.inet.ip.dummynet.codel.interval
+can be used to set CoDel default parameters.
+.Pp
+.It Cm pie Oo Cm target Ar time Oc Oo Cm tupdate Ar time Oc Oo
+.Cm alpha Ar n Oc Oo Cm beta Ar n Oc Oo Cm max_burst Ar time Oc Oo
+.Cm max_ecnth Ar n Oc Oo Cm ecn | Cm noecn Oc Oo Cm capdrop |
+.Cm nocapdrop Oc Oo Cm drand | Cm nodrand Oc Oo Cm onoff
+.Oc Oo Cm dre | Cm ts Oc
+Make use of the PIE (Proportional Integral controller Enhanced) queue management
+algorithm.
+PIE drops or marks packets depending on a calculated drop probability during
+en-queue process, with the aim of achieving high throughput while keeping queue
+delay low.
+At regular time intervals of
+.Cm tupdate
+.Ar time
+(15ms by default) a background process (re)calculates the probability based on
+queue delay deviations from
+.Cm target
+.Ar time
+(15ms by default) and queue delay trends.
+PIE approximates current queue
+delay by using a departure rate estimation method, or (optionally) by using a
+packet timestamp method similar to CoDel.
+.Ar time
+is interpreted as milliseconds by default but seconds (s), milliseconds (ms) or
+microseconds (us) can be specified instead.
+The other PIE parameters and options are as follows:
+.Bl -tag -width indent
+.It Cm alpha Ar n
+.Ar n
+is a floating point number between 0 and 7 which specifies the weight of queue
+delay deviations that is used in drop probability calculation.
+0.125 is the default.
+.It Cm beta Ar n
+.Ar n
+is a floating point number between 0 and 7 which specifies is the weight of
+queue delay trend that is used in drop probability calculation.
+1.25 is the default.
+.It Cm max_burst Ar time
+The maximum period of time that PIE does not drop/mark packets.
+150ms is the
+default and 10s is the maximum value.
+.It Cm max_ecnth Ar n
+Even when ECN is enabled, PIE drops packets instead of marking them when drop
+probability becomes higher than ECN probability threshold
+.Cm max_ecnth Ar n
+, the default is 0.1 (i.e 10%) and 1 is the maximum value.
+.It Cm ecn | noecn
+enable or disable ECN marking for ECN-enabled TCP flows.
+Disabled by default.
+.It Cm capdrop | nocapdrop
+enable or disable cap drop adjustment.
+Cap drop adjustment is enabled by default.
+.It Cm drand | nodrand
+enable or disable drop probability de-randomisation.
+De-randomisation eliminates
+the problem of dropping packets too close or too far.
+De-randomisation is enabled by default.
+.It Cm onoff
+enable turning PIE on and off depending on queue load.
+If this option is enabled,
+PIE turns on when over 1/3 of queue becomes full.
+This option is disabled by
+default.
+.It Cm dre | ts
+Calculate queue delay using departure rate estimation
+.Cm dre
+or timestamps
+.Cm ts .
+.Cm dre
+is used by default.
+.El
+.Pp
+Note that any token after
+.Cm pie
+is considered a parameter for PIE.
+So ensure all pipe/queue
+the configuration options are written before
+.Cm pie
+token.
+.Xr sysctl 8
+variables can be used to control the
+.Cm pie
+default parameters.
+See the
+.Sx SYSCTL VARIABLES
+section for more details.
+.El
+.Pp
+When used with IPv6 data,
+.Nm dummynet
+currently has several limitations.
+Information necessary to route link-local packets to an
+interface is not available after processing by
+.Nm dummynet
+so those packets are dropped in the output path.
+Care should be taken to ensure that link-local packets are not passed to
+.Nm dummynet .
+.Sh CHECKLIST
+Here are some important points to consider when designing your
+rules:
+.Bl -bullet
+.It
+Remember that you filter both packets going
+.Cm in
+and
+.Cm out .
+Most connections need packets going in both directions.
+.It
+Remember to test very carefully.
+It is a good idea to be near the console when doing this.
+If you cannot be near the console,
+use an auto-recovery script such as the one in
+.Pa /usr/share/examples/ipfw/change_rules.sh .
+.It
+Do not forget the loopback interface.
+.El
+.Sh FINE POINTS
+.Bl -bullet
+.It
+There are circumstances where fragmented datagrams are unconditionally
+dropped.
+TCP packets are dropped if they do not contain at least 20 bytes of
+TCP header, UDP packets are dropped if they do not contain a full 8
+byte UDP header, and ICMP packets are dropped if they do not contain
+4 bytes of ICMP header, enough to specify the ICMP type, code, and
+checksum.
+These packets are simply logged as
+.Dq pullup failed
+since there may not be enough good data in the packet to produce a
+meaningful log entry.
+.It
+Another type of packet is unconditionally dropped, a TCP packet with a
+fragment offset of one.
+This is a valid packet, but it only has one use, to try
+to circumvent firewalls.
+When logging is enabled, these packets are
+reported as being dropped by rule -1.
+.It
+If you are logged in over a network, loading the
+.Xr kld 4
+version of
+.Nm
+is probably not as straightforward as you would think.
+The following command line is recommended:
+.Bd -literal -offset indent
+kldload ipfw && \e
+ipfw add 32000 allow ip from any to any
+.Ed
+.Pp
+Along the same lines, doing an
+.Bd -literal -offset indent
+ipfw flush
+.Ed
+.Pp
+in similar surroundings is also a bad idea.
+.It
+The
+.Nm
+filter list may not be modified if the system security level
+is set to 3 or higher
+(see
+.Xr init 8
+for information on system security levels).
+.El
+.Sh PACKET DIVERSION
+A
+.Xr divert 4
+socket bound to the specified port will receive all packets
+diverted to that port.
+If no socket is bound to the destination port, or if the divert module is
+not loaded, or if the kernel was not compiled with divert socket support,
+the packets are dropped.
+.Sh NETWORK ADDRESS TRANSLATION (NAT)
+.Nm
+support in-kernel NAT using the kernel version of
+.Xr libalias 3 .
+The kernel module
+.Cm ipfw_nat
+should be loaded or kernel should have
+.Cm options IPFIREWALL_NAT
+to be able use NAT.
+.Pp
+The nat configuration command is the following:
+.Bd -ragged -offset indent
+.Bk -words
+.Cm nat
+.Ar nat_number
+.Cm config
+.Ar nat-configuration
+.Ek
+.Ed
+.Pp
+The following parameters can be configured:
+.Bl -tag -width indent
+.It Cm ip Ar ip_address
+Define an ip address to use for aliasing.
+.It Cm if Ar nic
+Use ip address of NIC for aliasing, dynamically changing
+it if NIC's ip address changes.
+.It Cm log
+Enable logging on this nat instance.
+.It Cm deny_in
+Deny any incoming connection from outside world.
+.It Cm same_ports
+Try to leave the alias port numbers unchanged from
+the actual local port numbers.
+.It Cm unreg_only
+Traffic on the local network not originating from a RFC 1918
+unregistered address spaces will be ignored.
+.It Cm unreg_cgn
+Like unreg_only, but includes the RFC 6598 (Carrier Grade NAT)
+address range.
+.It Cm reset
+Reset table of the packet aliasing engine on address change.
+.It Cm reverse
+Reverse the way libalias handles aliasing.
+.It Cm proxy_only
+Obey transparent proxy rules only, packet aliasing is not performed.
+.It Cm skip_global
+Skip instance in case of global state lookup (see below).
+.It Cm port_range Ar lower-upper
+Set the aliasing ports between the ranges given.
+Upper port has to be greater than lower.
+.It Cm udp_eim
+When enabled, UDP packets use endpoint-independent mapping (EIM) from RFC 4787
+("full cone" NAT of RFC 3489).
+All packets from the same internal address:port are mapped to the same NAT
+address:port, regardless of their destination address:port.
+If filtering rules allow, and if
+.Em deny_in
+is unset, any other external address:port can
+also send to the internal address:port through its mapped NAT address:port.
+This is more compatible with applications, and can reduce the need for port
+forwarding, but less scalable as each NAT address:port can only be
+concurrently used by at most one internal address:port.
+.Pp
+When disabled, UDP packets use endpoint-dependent mapping (EDM) ("symmetric"
+NAT).
+Each connection from a particular internal address:port to different
+external addresses:ports is mapped to a random and unpredictable NAT
+address:port.
+Two appplications behind EDM NATs can only connect to each other
+by port forwarding on the NAT, or tunnelling through an in-between server.
+.El
+.Pp
+Some special values can be supplied instead of
+.Va nat_number
+in nat rule actions:
+.Bl -tag -width indent
+.It Cm global
+Looks up translation state in all configured nat instances.
+If an entry is found, packet is aliased according to that entry.
+If no entry was found in any of the instances, packet is passed unchanged,
+and no new entry will be created.
+See section
+.Sx MULTIPLE INSTANCES
+in
+.Xr natd 8
+for more information.
+.It Cm tablearg
+Uses argument supplied in lookup table.
+See
+.Sx LOOKUP TABLES
+section below for more information on lookup tables.
+.El
+.Pp
+To let the packet continue after being (de)aliased, set the sysctl variable
+.Va net.inet.ip.fw.one_pass
+to 0.
+For more information about aliasing modes, refer to
+.Xr libalias 3 .
+See Section
+.Sx EXAMPLES
+for some examples of nat usage.
+.Pp
+To delete specific nat configuration instance, use the following command:
+.Bd -ragged -offset indent
+.Bk -words
+.Cm nat
+.Ar nat_number
+.Cm delete
+.Ek
+.Ed
+.Ss REDIRECT AND LSNAT SUPPORT IN IPFW
+Redirect and LSNAT support follow closely the syntax used in
+.Xr natd 8 .
+See Section
+.Sx EXAMPLES
+for some examples on how to do redirect and lsnat.
+.Ss SCTP NAT SUPPORT
+SCTP nat can be configured in a similar manner to TCP through the
+.Nm
+command line tool.
+The main difference is that
+.Nm sctp nat
+does not do port translation.
+Since the local and global side ports will be the same,
+there is no need to specify both.
+Ports are redirected as follows:
+.Bd -ragged -offset indent
+.Bk -words
+.Cm nat
+.Ar nat_number
+.Cm config if
+.Ar nic
+.Cm redirect_port sctp
+.Ar ip_address [,addr_list] {[port | port-port] [,ports]}
+.Ek
+.Ed
+.Pp
+Most
+.Nm sctp nat
+configuration can be done in real-time through the
+.Xr sysctl 8
+interface.
+All may be changed dynamically, though the hash_table size will only
+change for new
+.Nm nat
+instances.
+See
+.Sx SYSCTL VARIABLES
+for more info.
+.Sh IPv6/IPv4 NETWORK ADDRESS AND PROTOCOL TRANSLATION
+.Ss Stateful translation
+.Nm
+supports in-kernel IPv6/IPv4 network address and protocol translation.
+Stateful NAT64 translation allows IPv6-only clients to contact IPv4 servers
+using unicast TCP, UDP or ICMP protocols.
+One or more IPv4 addresses assigned to a stateful NAT64 translator are shared
+among several IPv6-only clients.
+When stateful NAT64 is used in conjunction with DNS64, no changes are usually
+required in the IPv6 client or the IPv4 server.
+The kernel module
+.Cm ipfw_nat64
+should be loaded or kernel should have
+.Cm options IPFIREWALL_NAT64
+to be able use stateful NAT64 translator.
+.Pp
+Stateful NAT64 uses a bunch of memory for several types of objects.
+When IPv6 client initiates connection, NAT64 translator creates a host entry
+in the states table.
+Each host entry uses preallocated IPv4 alias entry.
+Each alias entry has a number of ports group entries allocated on demand.
+Ports group entries contains connection state entries.
+There are several options to control limits and lifetime for these objects.
+.Pp
+NAT64 translator follows RFC7915 when does ICMPv6/ICMP translation,
+unsupported message types will be silently dropped.
+IPv6 needs several ICMPv6 message types to be explicitly allowed for correct
+operation.
+Make sure that ND6 neighbor solicitation (ICMPv6 type 135) and neighbor
+advertisement (ICMPv6 type 136) messages will not be handled by translation
+rules.
+.Pp
+After translation NAT64 translator by default sends packets through
+corresponding netisr queue.
+Thus translator host should be configured as IPv4 and IPv6 router.
+Also this means, that a packet is handled by firewall twice.
+First time an original packet is handled and consumed by translator,
+and then it is handled again as translated packet.
+This behavior can be changed by sysctl variable
+.Va net.inet.ip.fw.nat64_direct_output .
+Also translated packet can be tagged using
+.Cm tag
+rule action, and then matched by
+.Cm tagged
+opcode to avoid loops and extra overhead.
+.Pp
+The stateful NAT64 configuration command is the following:
+.Bd -ragged -offset indent
+.Bk -words
+.Cm nat64lsn
+.Ar name
+.Cm create
+.Ar create-options
+.Ek
+.Ed
+.Pp
+The following parameters can be configured:
+.Bl -tag -width indent
+.It Cm prefix4 Ar ipv4_prefix/plen
+The IPv4 prefix with mask defines the pool of IPv4 addresses used as
+source address after translation.
+Stateful NAT64 module translates IPv6 source address of client to one
+IPv4 address from this pool.
+Note that incoming IPv4 packets that don't have corresponding state entry
+in the states table will be dropped by translator.
+Make sure that translation rules handle packets, destined to configured prefix.
+.It Cm prefix6 Ar ipv6_prefix/length
+The IPv6 prefix defines IPv4-embedded IPv6 addresses used by translator
+to represent IPv4 addresses.
+This IPv6 prefix should be configured in DNS64.
+The translator implementation follows RFC6052, that restricts the length of
+prefixes to one of following: 32, 40, 48, 56, 64, or 96.
+The Well-Known IPv6 Prefix 64:ff9b:: must be 96 bits long.
+The special
+.Ar ::/length
+prefix can be used to handle several IPv6 prefixes with one NAT64 instance.
+The NAT64 instance will determine a destination IPv4 address from prefix
+.Ar length .
+.It Cm states_chunks Ar number
+The number of states chunks in single ports group.
+Each ports group by default can keep 64 state entries in single chunk.
+The above value affects the maximum number of states that can be associated with
+a single IPv4 alias address and port.
+The value must be power of 2, and up to 128.
+.It Cm host_del_age Ar seconds
+The number of seconds until the host entry for a IPv6 client will be deleted
+and all its resources will be released due to inactivity.
+Default value is
+.Ar 3600 .
+.It Cm pg_del_age Ar seconds
+The number of seconds until a ports group with unused state entries will
+be released.
+Default value is
+.Ar 900 .
+.It Cm tcp_syn_age Ar seconds
+The number of seconds while a state entry for TCP connection with only SYN
+sent will be kept.
+If TCP connection establishing will not be finished,
+state entry will be deleted.
+Default value is
+.Ar 10 .
+.It Cm tcp_est_age Ar seconds
+The number of seconds while a state entry for established TCP connection
+will be kept.
+Default value is
+.Ar 7200 .
+.It Cm tcp_close_age Ar seconds
+The number of seconds while a state entry for closed TCP connection
+will be kept.
+Keeping state entries for closed connections is needed, because IPv4 servers
+typically keep closed connections in a TIME_WAIT state for a several minutes.
+Since translator's IPv4 addresses are shared among all IPv6 clients,
+new connections from the same addresses and ports may be rejected by server,
+because these connections are still in a TIME_WAIT state.
+Keeping them in translator's state table protects from such rejects.
+Default value is
+.Ar 180 .
+.It Cm udp_age Ar seconds
+The number of seconds while translator keeps state entry in a waiting for
+reply to the sent UDP datagram.
+Default value is
+.Ar 120 .
+.It Cm icmp_age Ar seconds
+The number of seconds while translator keeps state entry in a waiting for
+reply to the sent ICMP message.
+Default value is
+.Ar 60 .
+.It Cm log
+Turn on logging of all handled packets via BPF through
+.Ar ipfwlog0
+interface.
+.Ar ipfwlog0
+is a pseudo interface and can be created after a boot manually with
+.Cm ifconfig
+command.
+Note that it has different purpose than
+.Ar ipfw0
+interface.
+Translators sends to BPF an additional information with each packet.
+With
+.Cm tcpdump
+you are able to see each handled packet before and after translation.
+.It Cm -log
+Turn off logging of all handled packets via BPF.
+.It Cm allow_private
+Turn on processing private IPv4 addresses.
+By default IPv6 packets with destinations mapped to private address ranges
+defined by RFC1918 are not processed.
+.It Cm -allow_private
+Turn off private address handling in
+.Nm nat64
+instance.
+.El
+.Pp
+To inspect a states table of stateful NAT64 the following command can be used:
+.Bd -ragged -offset indent
+.Bk -words
+.Cm nat64lsn
+.Ar name
+.Cm show Cm states
+.Ek
+.Ed
+.Pp
+Stateless NAT64 translator doesn't use a states table for translation
+and converts IPv4 addresses to IPv6 and vice versa solely based on the
+mappings taken from configured lookup tables.
+Since a states table doesn't used by stateless translator,
+it can be configured to pass IPv4 clients to IPv6-only servers.
+.Pp
+The stateless NAT64 configuration command is the following:
+.Bd -ragged -offset indent
+.Bk -words
+.Cm nat64stl
+.Ar name
+.Cm create
+.Ar create-options
+.Ek
+.Ed
+.Pp
+The following parameters can be configured:
+.Bl -tag -width indent
+.It Cm prefix6 Ar ipv6_prefix/length
+The IPv6 prefix defines IPv4-embedded IPv6 addresses used by translator
+to represent IPv4 addresses.
+This IPv6 prefix should be configured in DNS64.
+.It Cm table4 Ar table46
+The lookup table
+.Ar table46
+contains mapping how IPv4 addresses should be translated to IPv6 addresses.
+.It Cm table6 Ar table64
+The lookup table
+.Ar table64
+contains mapping how IPv6 addresses should be translated to IPv4 addresses.
+.It Cm log
+Turn on logging of all handled packets via BPF through
+.Ar ipfwlog0
+interface.
+.It Cm -log
+Turn off logging of all handled packets via BPF.
+.It Cm allow_private
+Turn on processing private IPv4 addresses.
+By default IPv6 packets with destinations mapped to private address ranges
+defined by RFC1918 are not processed.
+.It Cm -allow_private
+Turn off private address handling in
+.Nm nat64
+instance.
+.El
+.Pp
+Note that the behavior of stateless translator with respect to not matched
+packets differs from stateful translator.
+If corresponding addresses was not found in the lookup tables, the packet
+will not be dropped and the search continues.
+.Ss XLAT464 CLAT translation
+XLAT464 CLAT NAT64 translator implements client-side stateless translation as
+defined in RFC6877 and is very similar to statless NAT64 translator
+explained above.
+Instead of lookup tables it uses one-to-one mapping between IPv4 and IPv6
+addresses using configured prefixes.
+This mode can be used as a replacement of DNS64 service for applications
+that are not using it (e.g. VoIP) allowing them to access IPv4-only Internet
+over IPv6-only networks with help of remote NAT64 translator.
+.Pp
+The CLAT NAT64 configuration command is the following:
+.Bd -ragged -offset indent
+.Bk -words
+.Cm nat64clat
+.Ar name
+.Cm create
+.Ar create-options
+.Ek
+.Ed
+.Pp
+The following parameters can be configured:
+.Bl -tag -width indent
+.It Cm clat_prefix Ar ipv6_prefix/length
+The IPv6 prefix defines IPv4-embedded IPv6 addresses used by translator
+to represent source IPv4 addresses.
+.It Cm plat_prefix Ar ipv6_prefix/length
+The IPv6 prefix defines IPv4-embedded IPv6 addresses used by translator
+to represent destination IPv4 addresses.
+This IPv6 prefix should be configured on a remote NAT64 translator.
+.It Cm log
+Turn on logging of all handled packets via BPF through
+.Ar ipfwlog0
+interface.
+.It Cm -log
+Turn off logging of all handled packets via BPF.
+.It Cm allow_private
+Turn on processing private IPv4 addresses.
+By default
+.Nm nat64clat
+instance will not process IPv4 packets with destination address from private
+ranges as defined in RFC1918.
+.It Cm -allow_private
+Turn off private address handling in
+.Nm nat64clat
+instance.
+.El
+.Pp
+Note that the behavior of CLAT translator with respect to not matched
+packets differs from stateful translator.
+If corresponding addresses were not matched against prefixes configured,
+the packet will not be dropped and the search continues.
+.Sh IPv6-to-IPv6 NETWORK PREFIX TRANSLATION (NPTv6)
+.Nm
+supports in-kernel IPv6-to-IPv6 network prefix translation as described
+in RFC6296.
+The kernel module
+.Cm ipfw_nptv6
+should be loaded or kernel should has
+.Cm options IPFIREWALL_NPTV6
+to be able use NPTv6 translator.
+.Pp
+The NPTv6 configuration command is the following:
+.Bd -ragged -offset indent
+.Bk -words
+.Cm nptv6
+.Ar name
+.Cm create
+.Ar create-options
+.Ek
+.Ed
+.Pp
+The following parameters can be configured:
+.Bl -tag -width indent
+.It Cm int_prefix Ar ipv6_prefix
+IPv6 prefix used in internal network.
+NPTv6 module translates source address when it matches this prefix.
+.It Cm ext_prefix Ar ipv6_prefix
+IPv6 prefix used in external network.
+NPTv6 module translates destination address when it matches this prefix.
+.It Cm ext_if Ar nic
+The NPTv6 module will use first global IPv6 address from interface
+.Ar nic
+as external prefix.
+It can be useful when IPv6 prefix of external network is dynamically obtained.
+.Cm ext_prefix
+and
+.Cm ext_if
+options are mutually exclusive.
+.It Cm prefixlen Ar length
+The length of specified IPv6 prefixes.
+It must be in range from 8 to 64.
+.El
+.Pp
+Note that the prefix translation rules are silently ignored when IPv6 packet
+forwarding is disabled.
+To enable the packet forwarding, set the sysctl variable
+.Va net.inet6.ip6.forwarding
+to 1.
+.Pp
+To let the packet continue after being translated, set the sysctl variable
+.Va net.inet.ip.fw.one_pass
+to 0.
+.Sh LOADER TUNABLES
+Tunables can be set in
+.Xr loader 8
+prompt,
+.Xr loader.conf 5
+or
+.Xr kenv 1
+before ipfw module gets loaded.
+.Bl -tag -width indent
+.It Va net.inet.ip.fw.enable : No 1
+Enables the firewall.
+Setting this variable to 0 lets you run your machine without
+firewall even if compiled in.
+.It Va net.inet6.ip6.fw.enable : No 1
+provides the same functionality as above for the IPv6 case.
+.It Va net.link.ether.ipfw : No 0
+Controls whether layer2 packets are passed to
+.Nm .
+Default is no.
+.It Va net.inet.ip.fw.default_to_accept : No 0
+Defines ipfw last rule behavior.
+This value overrides
+.Cd "options IPFW_DEFAULT_TO_(ACCEPT|DENY)"
+from kernel configuration file.
+.It Va net.inet.ip.fw.tables_max : No 128
+Defines number of tables available in ipfw.
+Number cannot exceed 65534.
+.El
+.Sh SYSCTL VARIABLES
+A set of
+.Xr sysctl 8
+variables controls the behaviour of the firewall and
+associated modules
+.Pq Nm dummynet , bridge , sctp nat .
+These are shown below together with their default value
+(but always check with the
+.Xr sysctl 8
+command what value is actually in use) and meaning:
+.Bl -tag -width indent
+.It Va net.inet.ip.alias.sctp.accept_global_ootb_addip : No 0
+Defines how the
+.Nm nat
+responds to receipt of global OOTB ASCONF-AddIP:
+.Bl -tag -width indent
+.It Cm 0
+No response (unless a partially matching association exists -
+ports and vtags match but global address does not)
+.It Cm 1
+.Nm nat
+will accept and process all OOTB global AddIP messages.
+.El
+.Pp
+Option 1 should never be selected as this forms a security risk.
+An attacker can
+establish multiple fake associations by sending AddIP messages.
+.It Va net.inet.ip.alias.sctp.chunk_proc_limit : No 5
+Defines the maximum number of chunks in an SCTP packet that will be
+parsed for a
+packet that matches an existing association.
+This value is enforced to be greater or equal than
+.Cm net.inet.ip.alias.sctp.initialising_chunk_proc_limit .
+A high value is
+a DoS risk yet setting too low a value may result in
+important control chunks in
+the packet not being located and parsed.
+.It Va net.inet.ip.alias.sctp.error_on_ootb : No 1
+Defines when the
+.Nm nat
+responds to any Out-of-the-Blue (OOTB) packets with ErrorM packets.
+An OOTB packet is a packet that arrives with no existing association
+registered in the
+.Nm nat
+and is not an INIT or ASCONF-AddIP packet:
+.Bl -tag -width indent
+.It Cm 0
+ErrorM is never sent in response to OOTB packets.
+.It Cm 1
+ErrorM is only sent to OOTB packets received on the local side.
+.It Cm 2
+ErrorM is sent to the local side and on the global side ONLY if there is a
+partial match (ports and vtags match but the source global IP does not).
+This value is only useful if the
+.Nm nat
+is tracking global IP addresses.
+.It Cm 3
+ErrorM is sent in response to all OOTB packets on both
+the local and global side
+(DoS risk).
+.El
+.Pp
+At the moment the default is 0, since the ErrorM packet is not yet
+supported by most SCTP stacks.
+When it is supported, and if not tracking
+global addresses, we recommend setting this value to 1 to allow
+multi-homed local hosts to function with the
+.Nm nat .
+To track global addresses, we recommend setting this value to 2 to
+allow global hosts to be informed when they need to (re)send an
+ASCONF-AddIP.
+Value 3 should never be chosen (except for debugging) as the
+.Nm nat
+will respond to all OOTB global packets (a DoS risk).
+.It Va net.inet.ip.alias.sctp.hashtable_size : No 2003
+Size of hash tables used for
+.Nm nat
+lookups (100 < prime_number > 1000001).
+This value sets the
+.Nm hash table
+size for any future created
+.Nm nat
+instance and therefore must be set prior to creating a
+.Nm nat
+instance.
+The table sizes may be changed to suit specific needs.
+If there will be few
+concurrent associations, and memory is scarce, you may make these smaller.
+If there will be many thousands (or millions) of concurrent associations, you
+should make these larger.
+A prime number is best for the table size.
+The sysctl
+update function will adjust your input value to the next highest prime number.
+.It Va net.inet.ip.alias.sctp.holddown_time : No 0
+Hold association in table for this many seconds after receiving a
+SHUTDOWN-COMPLETE.
+This allows endpoints to correct shutdown gracefully if a
+shutdown_complete is lost and retransmissions are required.
+.It Va net.inet.ip.alias.sctp.init_timer : No 15
+Timeout value while waiting for (INIT-ACK|AddIP-ACK).
+This value cannot be 0.
+.It Va net.inet.ip.alias.sctp.initialising_chunk_proc_limit : No 2
+Defines the maximum number of chunks in an SCTP packet that will be parsed when
+no existing association exists that matches that packet.
+Ideally this packet
+will only be an INIT or ASCONF-AddIP packet.
+A higher value may become a DoS
+risk as malformed packets can consume processing resources.
+.It Va net.inet.ip.alias.sctp.param_proc_limit : No 25
+Defines the maximum number of parameters within a chunk that will be
+parsed in a
+packet.
+As for other similar sysctl variables, larger values pose a DoS risk.
+.It Va net.inet.ip.alias.sctp.log_level : No 0
+Level of detail in the system log messages (0 \- minimal, 1 \- event,
+2 \- info, 3 \- detail, 4 \- debug, 5 \- max debug).
+May be a good
+option in high loss environments.
+.It Va net.inet.ip.alias.sctp.shutdown_time : No 15
+Timeout value while waiting for SHUTDOWN-COMPLETE.
+This value cannot be 0.
+.It Va net.inet.ip.alias.sctp.track_global_addresses : No 0
+Enables/disables global IP address tracking within the
+.Nm nat
+and places an
+upper limit on the number of addresses tracked for each association:
+.Bl -tag -width indent
+.It Cm 0
+Global tracking is disabled
+.It Cm >1
+Enables tracking, the maximum number of addresses tracked for each
+association is limited to this value
+.El
+.Pp
+This variable is fully dynamic, the new value will be adopted for all newly
+arriving associations, existing associations are treated
+as they were previously.
+Global tracking will decrease the number of collisions within the
+.Nm nat
+at a cost
+of increased processing load, memory usage, complexity, and possible
+.Nm nat
+state
+problems in complex networks with multiple
+.Nm nats .
+We recommend not tracking
+global IP addresses, this will still result in a fully functional
+.Nm nat .
+.It Va net.inet.ip.alias.sctp.up_timer : No 300
+Timeout value to keep an association up with no traffic.
+This value cannot be 0.
+.It Va net.inet.ip.dummynet.codel.interval : No 100000
+Default
+.Cm codel
+AQM interval in microseconds.
+The value must be in the range 1..5000000.
+.It Va net.inet.ip.dummynet.codel.target : No 5000
+Default
+.Cm codel
+AQM target delay time in microseconds (the minimum acceptable persistent queue
+delay).
+The value must be in the range 1..5000000.
+.It Va net.inet.ip.dummynet.expire : No 1
+Lazily delete dynamic pipes/queue once they have no pending traffic.
+You can disable this by setting the variable to 0, in which case
+the pipes/queues will only be deleted when the threshold is reached.
+.It Va net.inet.ip.dummynet.fqcodel.flows : No 1024
+Defines the default total number of flow queues (sub-queues) that
+.Cm fq_codel
+creates and manages.
+The value must be in the range 1..65536.
+.It Va net.inet.ip.dummynet.fqcodel.interval : No 100000
+Default
+.Cm fq_codel
+scheduler/AQM interval in microseconds.
+The value must be in the range 1..5000000.
+.It Va net.inet.ip.dummynet.fqcodel.limit : No 10240
+The default hard size limit (in unit of packet) of all queues managed by an
+instance of the
+.Cm fq_codel
+scheduler.
+The value must be in the range 1..20480.
+.It Va net.inet.ip.dummynet.fqcodel.quantum : No 1514
+The default quantum (credit) of the
+.Cm fq_codel
+in unit of byte.
+The value must be in the range 1..9000.
+.It Va net.inet.ip.dummynet.fqcodel.target : No 5000
+Default
+.Cm fq_codel
+scheduler/AQM target delay time in microseconds (the minimum acceptable
+persistent queue delay).
+The value must be in the range 1..5000000.
+.It Va net.inet.ip.dummynet.fqpie.alpha : No 125
+The default
+.Ar alpha
+parameter (scaled by 1000) for
+.Cm fq_pie
+scheduler/AQM.
+The value must be in the range 1..7000.
+.It Va net.inet.ip.dummynet.fqpie.beta : No 1250
+The default
+.Ar beta
+parameter (scaled by 1000) for
+.Cm fq_pie
+scheduler/AQM.
+The value must be in the range 1..7000.
+.It Va net.inet.ip.dummynet.fqpie.flows : No 1024
+Defines the default total number of flow queues (sub-queues) that
+.Cm fq_pie
+creates and manages.
+The value must be in the range 1..65536.
+.It Va net.inet.ip.dummynet.fqpie.limit : No 10240
+The default hard size limit (in unit of packet) of all queues managed by an
+instance of the
+.Cm fq_pie
+scheduler.
+The value must be in the range 1..20480.
+.It Va net.inet.ip.dummynet.fqpie.max_burst : No 150000
+The default maximum period of microseconds that
+.Cm fq_pie
+scheduler/AQM does not drop/mark packets.
+The value must be in the range 1..10000000.
+.It Va net.inet.ip.dummynet.fqpie.max_ecnth : No 99
+The default maximum ECN probability threshold (scaled by 1000) for
+.Cm fq_pie
+scheduler/AQM.
+The value must be in the range 1..7000.
+.It Va net.inet.ip.dummynet.fqpie.quantum : No 1514
+The default quantum (credit) of the
+.Cm fq_pie
+in unit of byte.
+The value must be in the range 1..9000.
+.It Va net.inet.ip.dummynet.fqpie.target : No 15000
+The default
+.Cm target
+delay of the
+.Cm fq_pie
+in unit of microsecond.
+The value must be in the range 1..5000000.
+.It Va net.inet.ip.dummynet.fqpie.tupdate : No 15000
+The default
+.Cm tupdate
+of the
+.Cm fq_pie
+in unit of microsecond.
+The value must be in the range 1..5000000.
+.It Va net.inet.ip.dummynet.hash_size : No 64
+Default size of the hash table used for dynamic pipes/queues.
+This value is used when no
+.Cm buckets
+option is specified when configuring a pipe/queue.
+.It Va net.inet.ip.dummynet.io_fast : No 0
+If set to a non-zero value,
+the
+.Dq fast
+mode of
+.Nm dummynet
+operation (see above) is enabled.
+.It Va net.inet.ip.dummynet.io_pkt
+Number of packets passed to
+.Nm dummynet .
+.It Va net.inet.ip.dummynet.io_pkt_drop
+Number of packets dropped by
+.Nm dummynet .
+.It Va net.inet.ip.dummynet.io_pkt_fast
+Number of packets bypassed by the
+.Nm dummynet
+scheduler.
+.It Va net.inet.ip.dummynet.max_chain_len : No 16
+Target value for the maximum number of pipes/queues in a hash bucket.
+The product
+.Cm max_chain_len*hash_size
+is used to determine the threshold over which empty pipes/queues
+will be expired even when
+.Cm net.inet.ip.dummynet.expire=0 .
+.It Va net.inet.ip.dummynet.red_lookup_depth : No 256
+.It Va net.inet.ip.dummynet.red_avg_pkt_size : No 512
+.It Va net.inet.ip.dummynet.red_max_pkt_size : No 1500
+Parameters used in the computations of the drop probability
+for the RED algorithm.
+.It Va net.inet.ip.dummynet.pie.alpha : No 125
+The default
+.Ar alpha
+parameter (scaled by 1000) for
+.Cm pie
+AQM.
+The value must be in the range 1..7000.
+.It Va net.inet.ip.dummynet.pie.beta : No 1250
+The default
+.Ar beta
+parameter (scaled by 1000) for
+.Cm pie
+AQM.
+The value must be in the range 1..7000.
+.It Va net.inet.ip.dummynet.pie.max_burst : No 150000
+The default maximum period of microseconds that
+.Cm pie
+AQM does not drop/mark packets.
+The value must be in the range 1..10000000.
+.It Va net.inet.ip.dummynet.pie.max_ecnth : No 99
+The default maximum ECN probability threshold (scaled by 1000) for
+.Cm pie
+AQM.
+The value must be in the range 1..7000.
+.It Va net.inet.ip.dummynet.pie.target : No 15000
+The default
+.Cm target
+delay of
+.Cm pie
+AQM in unit of microsecond.
+The value must be in the range 1..5000000.
+.It Va net.inet.ip.dummynet.pie.tupdate : No 15000
+The default
+.Cm tupdate
+of
+.Cm pie
+AQM in unit of microsecond.
+The value must be in the range 1..5000000.
+.It Va net.inet.ip.dummynet.pipe_byte_limit : No 1048576
+.It Va net.inet.ip.dummynet.pipe_slot_limit : No 100
+The maximum queue size that can be specified in bytes or packets.
+These limits prevent accidental exhaustion of resources such as mbufs.
+If you raise these limits,
+you should make sure the system is configured so that sufficient resources
+are available.
+.It Va net.inet.ip.fw.autoinc_step : No 100
+Delta between rule numbers when auto-generating them.
+The value must be in the range 1..1000.
+.It Va net.inet.ip.fw.curr_dyn_buckets : Va net.inet.ip.fw.dyn_buckets
+The current number of buckets in the hash table for dynamic rules
+(readonly).
+.It Va net.inet.ip.fw.debug : No 1
+Controls debugging messages produced by
+.Nm .
+.It Va net.inet.ip.fw.default_rule : No 65535
+The default rule number (read-only).
+By the design of
+.Nm , the default rule is the last one, so its number
+can also serve as the highest number allowed for a rule.
+.It Va net.inet.ip.fw.dyn_buckets : No 256
+The number of buckets in the hash table for dynamic rules.
+Must be a power of 2, up to 65536.
+It only takes effect when all dynamic rules have expired, so you
+are advised to use a
+.Cm flush
+command to make sure that the hash table is resized.
+.It Va net.inet.ip.fw.dyn_count : No 3
+Current number of dynamic rules
+(read-only).
+.It Va net.inet.ip.fw.dyn_keepalive : No 1
+Enables generation of keepalive packets for
+.Cm keep-state
+rules on TCP sessions.
+A keepalive is generated to both
+sides of the connection every 5 seconds for the last 20
+seconds of the lifetime of the rule.
+.It Va net.inet.ip.fw.dyn_max : No 8192
+Maximum number of dynamic rules.
+When you hit this limit, no more dynamic rules can be
+installed until old ones expire.
+.It Va net.inet.ip.fw.dyn_ack_lifetime : No 300
+.It Va net.inet.ip.fw.dyn_syn_lifetime : No 20
+.It Va net.inet.ip.fw.dyn_fin_lifetime : No 1
+.It Va net.inet.ip.fw.dyn_rst_lifetime : No 1
+.It Va net.inet.ip.fw.dyn_udp_lifetime : No 5
+.It Va net.inet.ip.fw.dyn_short_lifetime : No 30
+These variables control the lifetime, in seconds, of dynamic
+rules.
+Upon the initial SYN exchange the lifetime is kept short,
+then increased after both SYN have been seen, then decreased
+again during the final FIN exchange or when a RST is received.
+Both
+.Em dyn_fin_lifetime
+and
+.Em dyn_rst_lifetime
+must be strictly lower than 5 seconds, the period of
+repetition of keepalives.
+The firewall enforces that.
+.It Va net.inet.ip.fw.dyn_keep_states : No 0
+Keep dynamic states on rule/set deletion.
+States are relinked to default rule (65535).
+This can be handly for ruleset reload.
+Turned off by default.
+.It Va net.inet.ip.fw.one_pass : No 1
+When set, the packet exiting from the
+.Nm dummynet
+pipe or from
+.Xr ng_ipfw 4
+node is not passed though the firewall again.
+Otherwise, after an action, the packet is
+reinjected into the firewall at the next rule.
+.It Va net.inet.ip.fw.tables_max : No 128
+Maximum number of tables.
+.It Va net.inet.ip.fw.verbose : No 1
+Enables verbose messages.
+.It Va net.inet.ip.fw.verbose_limit : No 0
+Limits the number of messages produced by a verbose firewall.
+.It Va net.inet6.ip6.fw.deny_unknown_exthdrs : No 1
+If enabled packets with unknown IPv6 Extension Headers will be denied.
+.It Va net.link.bridge.ipfw : No 0
+Controls whether bridged packets are passed to
+.Nm .
+Default is no.
+.It Va net.inet.ip.fw.nat64_debug : No 0
+Controls debugging messages produced by
+.Nm ipfw_nat64
+module.
+.It Va net.inet.ip.fw.nat64_direct_output : No 0
+Controls the output method used by
+.Nm ipfw_nat64
+module:
+.Bl -tag -width indent
+.It Cm 0
+A packet is handled by
+.Nm ipfw
+twice.
+First time an original packet is handled by
+.Nm ipfw
+and consumed by
+.Nm ipfw_nat64
+translator.
+Then translated packet is queued via netisr to input processing again.
+.It Cm 1
+A packet is handled by
+.Nm ipfw
+only once, and after translation it will be pushed directly to outgoing
+interface.
+.El
+.El
+.Sh INTERNAL DIAGNOSTICS
+There are some commands that may be useful to understand current state
+of certain subsystems inside kernel module.
+These commands provide debugging output which may change without notice.
+.Pp
+Currently the following commands are available as
+.Cm internal
+sub-options:
+.Bl -tag -width indent
+.It Cm iflist
+Lists all interface which are currently tracked by
+.Nm
+with their in-kernel status.
+.It Cm monitor Op Ar filter-comment
+Capture messages from
+.Xr route 4
+socket, that were logged using rules with
+.Cm log Cm logdst Ar rtsock
+opcode. Optional
+.Ar filter-comment
+can be specified to show only those messages, that were logged
+by rules with specific rule comment.
+.It Cm talist
+List all table lookup algorithms currently available.
+.El
+.Sh EXAMPLES
+There are far too many possible uses of
+.Nm
+so this Section will only give a small set of examples.
+.Ss BASIC PACKET FILTERING
+This command adds an entry which denies all tcp packets from
+.Em cracker.evil.org
+to the telnet port of
+.Em wolf.tambov.su
+from being forwarded by the host:
+.Pp
+.Dl "ipfw add deny tcp from cracker.evil.org to wolf.tambov.su telnet"
+.Pp
+This one disallows any connection from the entire cracker's
+network to my host:
+.Pp
+.Dl "ipfw add deny ip from 123.45.67.0/24 to my.host.org"
+.Pp
+A first and efficient way to limit access (not using dynamic rules)
+is the use of the following rules:
+.Pp
+.Dl "ipfw add allow tcp from any to any established"
+.Dl "ipfw add allow tcp from net1 portlist1 to net2 portlist2 setup"
+.Dl "ipfw add allow tcp from net3 portlist3 to net3 portlist3 setup"
+.Dl "..."
+.Dl "ipfw add deny tcp from any to any"
+.Pp
+The first rule will be a quick match for normal TCP packets,
+but it will not match the initial SYN packet, which will be
+matched by the
+.Cm setup
+rules only for selected source/destination pairs.
+All other SYN packets will be rejected by the final
+.Cm deny
+rule.
+.Pp
+If you administer one or more subnets, you can take advantage
+of the address sets and or-blocks and write extremely
+compact rulesets which selectively enable services to blocks
+of clients, as below:
+.Pp
+.Dl "goodguys=\*q{ 10.1.2.0/24{20,35,66,18} or 10.2.3.0/28{6,3,11} }\*q"
+.Dl "badguys=\*q10.1.2.0/24{8,38,60}\*q"
+.Dl ""
+.Dl "ipfw add allow ip from ${goodguys} to any"
+.Dl "ipfw add deny ip from ${badguys} to any"
+.Dl "... normal policies ..."
+.Pp
+Allow any transit packets coming from single vlan 10 and
+going out to vlans 100-1000:
+.Pp
+.Dl "ipfw add 10 allow out recv vlan10 \e"
+.Dl "{ xmit vlan1000 or xmit \*qvlan[1-9]??\*q }"
+.Pp
+The
+.Cm verrevpath
+option could be used to do automated anti-spoofing by adding the
+following to the top of a ruleset:
+.Pp
+.Dl "ipfw add deny ip from any to any not verrevpath in"
+.Pp
+This rule drops all incoming packets that appear to be coming to the
+system on the wrong interface.
+For example, a packet with a source
+address belonging to a host on a protected internal network would be
+dropped if it tried to enter the system from an external interface.
+.Pp
+The
+.Cm antispoof
+option could be used to do similar but more restricted anti-spoofing
+by adding the following to the top of a ruleset:
+.Pp
+.Dl "ipfw add deny ip from any to any not antispoof in"
+.Pp
+This rule drops all incoming packets that appear to be coming from another
+directly connected system but on the wrong interface.
+For example, a packet with a source address of
+.Li 192.168.0.0/24 ,
+configured on
+.Li fxp0 ,
+but coming in on
+.Li fxp1
+would be dropped.
+.Pp
+The
+.Cm setdscp
+option could be used to (re)mark user traffic,
+by adding the following to the appropriate place in ruleset:
+.Pp
+.Dl "ipfw add setdscp be ip from any to any dscp af11,af21"
+.Ss SELECTIVE MIRRORING
+If your network has network traffic analyzer
+connected to your host directly via dedicated interface
+or remotely via RSPAN vlan, you can selectively mirror
+some Ethernet layer2 frames to the analyzer.
+.Pp
+First, make sure your firewall is already configured and runs.
+Then, enable layer2 processing if not already enabled:
+.Pp
+.Dl "sysctl net.link.ether.ipfw=1"
+.Pp
+Next, load needed additional kernel modules:
+.Pp
+.Dl "kldload ng_ether ng_ipfw"
+.Pp
+Optionally, make system load these modules automatically
+at startup:
+.Pp
+.Dl sysrc kld_list+="ng_ether ng_ipfw"
+.Pp
+Next, configure
+.Xr ng_ipfw 4
+kernel module to transmit mirrored copies of layer2 frames
+out via vlan900 interface:
+.Pp
+.Dl "ngctl connect ipfw: vlan900: 1 lower"
+.Pp
+Think of "1" here as of "mirroring instance index" and vlan900 is its
+destination.
+You can have arbitrary number of instances.
+Refer to
+.Xr ng_ipfw 4
+for details.
+.Pp
+At last, actually start mirroring of selected frames using "instance 1".
+For frames incoming from em0 interface:
+.Pp
+.Dl "ipfw add ngtee 1 ip from any to 192.168.0.1 layer2 in recv em0"
+.Pp
+For frames outgoing to em0 interface:
+.Pp
+.Dl "ipfw add ngtee 1 ip from any to 192.168.0.1 layer2 out xmit em0"
+.Pp
+For both incoming and outgoing frames while flowing through em0:
+.Pp
+.Dl "ipfw add ngtee 1 ip from any to 192.168.0.1 layer2 via em0"
+.Pp
+Make sure you do not perform mirroring for already duplicated frames
+or kernel may hang as there is no safety net.
+.Ss DYNAMIC RULES
+In order to protect a site from flood attacks involving fake
+TCP packets, it is safer to use dynamic rules:
+.Pp
+.Dl "ipfw add check-state"
+.Dl "ipfw add deny tcp from any to any established"
+.Dl "ipfw add allow tcp from my-net to any setup keep-state"
+.Pp
+This will let the firewall install dynamic rules only for
+those connection which start with a regular SYN packet coming
+from the inside of our network.
+Dynamic rules are checked when encountering the first
+occurrence of a
+.Cm check-state ,
+.Cm keep-state
+or
+.Cm limit
+rule.
+A
+.Cm check-state
+rule should usually be placed near the beginning of the
+ruleset to minimize the amount of work scanning the ruleset.
+Your mileage may vary.
+.Pp
+For more complex scenarios with dynamic rules
+.Cm record-state
+and
+.Cm defer-action
+can be used to precisely control creation and checking of dynamic rules.
+Example of usage of these options are provided in
+.Sx NETWORK ADDRESS TRANSLATION (NAT)\&
+Section.
+.Pp
+To limit the number of connections a user can open
+you can use the following type of rules:
+.Pp
+.Dl "ipfw add allow tcp from my-net/24 to any setup limit src-addr 10"
+.Dl "ipfw add allow tcp from any to me setup limit src-addr 4"
+.Pp
+The former (assuming it runs on a gateway) will allow each host
+on a /24 network to open at most 10 TCP connections.
+The latter can be placed on a server to make sure that a single
+client does not use more than 4 simultaneous connections.
+.Pp
+.Em BEWARE :
+stateful rules can be subject to denial-of-service attacks
+by a SYN-flood which opens a huge number of dynamic rules.
+The effects of such attacks can be partially limited by
+acting on a set of
+.Xr sysctl 8
+variables which control the operation of the firewall.
+.Pp
+Here is a good usage of the
+.Cm list
+command to see accounting records and timestamp information:
+.Pp
+.Dl ipfw -at list
+.Pp
+or in short form without timestamps:
+.Pp
+.Dl ipfw -a list
+.Pp
+which is equivalent to:
+.Pp
+.Dl ipfw show
+.Pp
+Next rule diverts all incoming packets from 192.168.2.0/24
+to divert port 5000:
+.Pp
+.Dl ipfw divert 5000 ip from 192.168.2.0/24 to any in
+.Ss TRAFFIC SHAPING
+The following rules show some of the applications of
+.Nm
+and
+.Nm dummynet
+for simulations and the like.
+.Pp
+This rule drops random incoming packets with a probability
+of 5%:
+.Pp
+.Dl "ipfw add prob 0.05 deny ip from any to any in"
+.Pp
+A similar effect can be achieved making use of
+.Nm dummynet
+pipes:
+.Pp
+.Dl "dnctl add pipe 10 ip from any to any"
+.Dl "dnctl pipe 10 config plr 0.05"
+.Pp
+We can use pipes to artificially limit bandwidth, e.g.\& on a
+machine acting as a router, if we want to limit traffic from
+local clients on 192.168.2.0/24 we do:
+.Pp
+.Dl "ipfw add pipe 1 ip from 192.168.2.0/24 to any out"
+.Dl "dnctl pipe 1 config bw 300Kbit/s queue 50KBytes"
+.Pp
+note that we use the
+.Cm out
+modifier so that the rule is not used twice.
+Remember in fact that
+.Nm
+rules are checked both on incoming and outgoing packets.
+.Pp
+Should we want to simulate a bidirectional link with bandwidth
+limitations, the correct way is the following:
+.Pp
+.Dl "ipfw add pipe 1 ip from any to any out"
+.Dl "ipfw add pipe 2 ip from any to any in"
+.Dl "dnctl pipe 1 config bw 64Kbit/s queue 10Kbytes"
+.Dl "dnctl pipe 2 config bw 64Kbit/s queue 10Kbytes"
+.Pp
+The above can be very useful, e.g.\& if you want to see how
+your fancy Web page will look for a residential user who
+is connected only through a slow link.
+You should not use only one pipe for both directions, unless
+you want to simulate a half-duplex medium (e.g.\& AppleTalk,
+Ethernet, IRDA).
+It is not necessary that both pipes have the same configuration,
+so we can also simulate asymmetric links.
+.Pp
+Should we want to verify network performance with the RED queue
+management algorithm:
+.Pp
+.Dl "ipfw add pipe 1 ip from any to any"
+.Dl "dnctl pipe 1 config bw 500Kbit/s queue 100 red 0.002/30/80/0.1"
+.Pp
+Another typical application of the traffic shaper is to
+introduce some delay in the communication.
+This can significantly affect applications which do a lot of Remote
+Procedure Calls, and where the round-trip-time of the
+connection often becomes a limiting factor much more than
+bandwidth:
+.Pp
+.Dl "ipfw add pipe 1 ip from any to any out"
+.Dl "ipfw add pipe 2 ip from any to any in"
+.Dl "dnctl pipe 1 config delay 250ms bw 1Mbit/s"
+.Dl "dnctl pipe 2 config delay 250ms bw 1Mbit/s"
+.Pp
+Per-flow queueing can be useful for a variety of purposes.
+A very simple one is counting traffic:
+.Pp
+.Dl "ipfw add pipe 1 tcp from any to any"
+.Dl "ipfw add pipe 1 udp from any to any"
+.Dl "ipfw add pipe 1 ip from any to any"
+.Dl "dnctl pipe 1 config mask all"
+.Pp
+The above set of rules will create queues (and collect
+statistics) for all traffic.
+Because the pipes have no limitations, the only effect is
+collecting statistics.
+Note that we need 3 rules, not just the last one, because
+when
+.Nm
+tries to match IP packets it will not consider ports, so we
+would not see connections on separate ports as different
+ones.
+.Pp
+A more sophisticated example is limiting the outbound traffic
+on a net with per-host limits, rather than per-network limits:
+.Pp
+.Dl "ipfw add pipe 1 ip from 192.168.2.0/24 to any out"
+.Dl "ipfw add pipe 2 ip from any to 192.168.2.0/24 in"
+.Dl "dnctl pipe 1 config mask src-ip 0x000000ff bw 200Kbit/s queue 20Kbytes"
+.Dl "dnctl pipe 2 config mask dst-ip 0x000000ff bw 200Kbit/s queue 20Kbytes"
+.Ss LOOKUP TABLES
+In the following example, we need to create several traffic bandwidth
+classes and we need different hosts/networks to fall into different classes.
+We create one pipe for each class and configure them accordingly.
+Then we create a single table and fill it with IP subnets and addresses.
+For each subnet/host we set the argument equal to the number of the pipe
+that it should use.
+Then we classify traffic using a single rule:
+.Pp
+.Dl "dnctl pipe 1 config bw 1000Kbyte/s"
+.Dl "dnctl pipe 4 config bw 4000Kbyte/s"
+.Dl "..."
+.Dl "ipfw table T1 create type addr"
+.Dl "ipfw table T1 add 192.168.2.0/24 1"
+.Dl "ipfw table T1 add 192.168.0.0/27 4"
+.Dl "ipfw table T1 add 192.168.0.2 1"
+.Dl "..."
+.Dl "ipfw add pipe tablearg ip from 'table(T1)' to any"
+.Pp
+Using the
+.Cm fwd
+action, the table entries may include hostnames and IP addresses.
+.Pp
+.Dl "ipfw table T2 create type addr valtype ipv4"
+.Dl "ipfw table T2 add 192.168.2.0/24 10.23.2.1"
+.Dl "ipfw table T2 add 192.168.0.0/27 router1.dmz"
+.Dl "..."
+.Dl "ipfw add 100 fwd tablearg ip from any to 'table(T2)'"
+.Pp
+In the following example per-interface firewall is created:
+.Pp
+.Dl "ipfw table IN create type iface valtype skipto,fib"
+.Dl "ipfw table IN add vlan20 12000,12"
+.Dl "ipfw table IN add vlan30 13000,13"
+.Dl "ipfw table OUT create type iface valtype skipto"
+.Dl "ipfw table OUT add vlan20 22000"
+.Dl "ipfw table OUT add vlan30 23000"
+.Dl ".."
+.Dl "ipfw add 100 setfib tablearg ip from any to any recv 'table(IN)' in"
+.Dl "ipfw add 200 skipto tablearg ip from any to any recv 'table(IN)' in"
+.Dl "ipfw add 300 skipto tablearg ip from any to any xmit 'table(OUT)' out"
+.Pp
+The following example illustrate usage of flow tables:
+.Pp
+.Dl "ipfw table fl create type flow:src-ip,proto,dst-ip,dst-port"
+.Dl "ipfw table fl add 2a02:6b8:77::88,tcp,2a02:6b8:77::99,80 11"
+.Dl "ipfw table fl add 10.0.0.1,udp,10.0.0.2,53 12"
+.Dl ".."
+.Dl "ipfw add 100 allow ip from any to any flow 'table(fl,11)' recv ix0"
+.Ss SETS OF RULES
+To add a set of rules atomically, e.g.\& set 18:
+.Pp
+.Dl "ipfw set disable 18"
+.Dl "ipfw add NN set 18 ... # repeat as needed"
+.Dl "ipfw set enable 18"
+.Pp
+To delete a set of rules atomically the command is simply:
+.Pp
+.Dl "ipfw delete set 18"
+.Pp
+To test a ruleset and disable it and regain control if something goes wrong:
+.Pp
+.Dl "ipfw set disable 18"
+.Dl "ipfw add NN set 18 ... # repeat as needed"
+.Dl "ipfw set enable 18; echo done; sleep 30 && ipfw set disable 18"
+.Pp
+Here if everything goes well, you press control-C before the "sleep"
+terminates, and your ruleset will be left active.
+Otherwise, e.g.\& if
+you cannot access your box, the ruleset will be disabled after
+the sleep terminates thus restoring the previous situation.
+.Pp
+To show rules of the specific set:
+.Pp
+.Dl "ipfw set 18 show"
+.Pp
+To show rules of the disabled set:
+.Pp
+.Dl "ipfw -S set 18 show"
+.Pp
+To clear a specific rule counters of the specific set:
+.Pp
+.Dl "ipfw set 18 zero NN"
+.Pp
+To delete a specific rule of the specific set:
+.Pp
+.Dl "ipfw set 18 delete NN"
+.Ss NAT, REDIRECT AND LSNAT
+First redirect all the traffic to nat instance 123:
+.Pp
+.Dl "ipfw add nat 123 all from any to any"
+.Pp
+Then to configure nat instance 123 to alias all the outgoing traffic with ip
+192.168.0.123, blocking all incoming connections, trying to keep
+same ports on both sides, clearing aliasing table on address change
+and keeping a log of traffic/link statistics:
+.Pp
+.Dl "ipfw nat 123 config ip 192.168.0.123 log deny_in reset same_ports"
+.Pp
+Or to change address of instance 123, aliasing table will be cleared (see
+reset option):
+.Pp
+.Dl "ipfw nat 123 config ip 10.0.0.1"
+.Pp
+To see configuration of nat instance 123:
+.Pp
+.Dl "ipfw nat 123 show config"
+.Pp
+To show logs of all instances:
+.Pp
+.Dl "ipfw nat show log"
+.Pp
+To see configurations of all instances:
+.Pp
+.Dl "ipfw nat show config"
+.Pp
+Or a redirect rule with mixed modes could looks like:
+.Bd -literal -offset 2n
+ipfw nat 123 config redirect_addr 10.0.0.1 10.0.0.66
+ redirect_port tcp 192.168.0.1:80 500
+ redirect_proto udp 192.168.1.43 192.168.1.1
+ redirect_addr 192.168.0.10,192.168.0.11
+ 10.0.0.100 # LSNAT
+ redirect_port tcp 192.168.0.1:80,192.168.0.10:22
+ 500 # LSNAT
+.Ed
+.Pp
+or it could be split in:
+.Bd -literal -offset 2n
+ipfw nat 1 config redirect_addr 10.0.0.1 10.0.0.66
+ipfw nat 2 config redirect_port tcp 192.168.0.1:80 500
+ipfw nat 3 config redirect_proto udp 192.168.1.43 192.168.1.1
+ipfw nat 4 config redirect_addr 192.168.0.10,192.168.0.11,192.168.0.12
+ 10.0.0.100
+ipfw nat 5 config redirect_port tcp
+ 192.168.0.1:80,192.168.0.10:22,192.168.0.20:25 500
+.Ed
+.Pp
+Sometimes you may want to mix NAT and dynamic rules.
+It could be achieved with
+.Cm record-state
+and
+.Cm defer-action
+options.
+Problem is, you need to create dynamic rule before NAT and check it
+after NAT actions (or vice versa) to have consistent addresses and ports.
+Rule with
+.Cm keep-state
+option will trigger activation of existing dynamic state, and action of such
+rule will be performed as soon as rule is matched.
+In case of NAT and
+.Cm allow
+rule packet need to be passed to NAT, not allowed as soon is possible.
+.Pp
+There is example of set of rules to achieve this.
+Bear in mind that this is example only and it is not very useful by itself.
+.Pp
+On way out, after all checks place this rules:
+.Pp
+.Dl "ipfw add allow record-state defer-action"
+.Dl "ipfw add nat 1"
+.Pp
+And on way in there should be something like this:
+.Pp
+.Dl "ipfw add nat 1"
+.Dl "ipfw add check-state"
+.Pp
+Please note, that first rule on way out doesn't allow packet and doesn't
+execute existing dynamic rules.
+All it does, create new dynamic rule with
+.Cm allow
+action, if it is not created yet.
+Later, this dynamic rule is used on way in by
+.Cm check-state
+rule.
+.Ss CONFIGURING CODEL, PIE, FQ-CODEL and FQ-PIE AQM
+.Cm codel
+and
+.Cm pie
+AQM can be configured for
+.Nm dummynet
+.Cm pipe
+or
+.Cm queue .
+.Pp
+To configure a
+.Cm pipe
+with
+.Cm codel
+AQM using default configuration for traffic from 192.168.0.0/24 and 1Mbits/s
+rate limit, we do:
+.Pp
+.Dl "dnctl pipe 1 config bw 1mbits/s codel"
+.Dl "ipfw add 100 pipe 1 ip from 192.168.0.0/24 to any"
+.Pp
+To configure a
+.Cm queue
+with
+.Cm codel
+AQM using different configurations parameters for traffic from
+192.168.0.0/24 and 1Mbits/s rate limit, we do:
+.Pp
+.Dl "dnctl pipe 1 config bw 1mbits/s"
+.Dl "dnctl queue 1 config pipe 1 codel target 8ms interval 160ms ecn"
+.Dl "ipfw add 100 queue 1 ip from 192.168.0.0/24 to any"
+.Pp
+To configure a
+.Cm pipe
+with
+.Cm pie
+AQM using default configuration for traffic from 192.168.0.0/24 and 1Mbits/s
+rate limit, we do:
+.Pp
+.Dl "dnctl pipe 1 config bw 1mbits/s pie"
+.Dl "ipfw add 100 pipe 1 ip from 192.168.0.0/24 to any"
+.Pp
+To configure a
+.Cm queue
+with
+.Cm pie
+AQM using different configuration parameters for traffic from
+192.168.0.0/24 and 1Mbits/s rate limit, we do:
+.Pp
+.Dl "dnctl pipe 1 config bw 1mbits/s"
+.Dl "dnctl queue 1 config pipe 1 pie target 20ms tupdate 30ms ecn"
+.Dl "ipfw add 100 queue 1 ip from 192.168.0.0/24 to any"
+.Pp
+.Cm fq_codel
+and
+.Cm fq_pie
+AQM can be configured for
+.Nm dummynet
+schedulers.
+.Pp
+To configure
+.Cm fq_codel
+scheduler using different configurations parameters for traffic from
+192.168.0.0/24 and 1Mbits/s rate limit, we do:
+.Pp
+.Dl "dnctl pipe 1 config bw 1mbits/s"
+.Dl "dnctl sched 1 config pipe 1 type fq_codel"
+.Dl "dnctl queue 1 config sched 1"
+.Dl "ipfw add 100 queue 1 ip from 192.168.0.0/24 to any"
+.Pp
+To change
+.Cm fq_codel
+default configuration for a
+.Cm sched
+such as disable ECN and change the
+.Ar target
+to 10ms, we do:
+.Pp
+.Dl "dnctl sched 1 config pipe 1 type fq_codel target 10ms noecn"
+.Pp
+Similar to
+.Cm fq_codel ,
+to configure
+.Cm fq_pie
+scheduler using different configurations parameters for traffic from
+192.168.0.0/24 and 1Mbits/s rate limit, we do:
+.Pp
+.Dl "dnctl pipe 1 config bw 1mbits/s"
+.Dl "dnctl sched 1 config pipe 1 type fq_pie"
+.Dl "dnctl queue 1 config sched 1"
+.Dl "ipfw add 100 queue 1 ip from 192.168.0.0/24 to any"
+.Pp
+The configurations of
+.Cm fq_pie
+.Cm sched
+can be changed in a similar way as for
+.Cm fq_codel
+.Sh SEE ALSO
+.Xr cpp 1 ,
+.Xr m4 1 ,
+.Xr fnmatch 3 ,
+.Xr altq 4 ,
+.Xr divert 4 ,
+.Xr dummynet 4 ,
+.Xr if_bridge 4 ,
+.Xr ip 4 ,
+.Xr ipfirewall 4 ,
+.Xr ng_ether 4 ,
+.Xr ng_ipfw 4 ,
+.Xr protocols 5 ,
+.Xr services 5 ,
+.Xr init 8 ,
+.Xr kldload 8 ,
+.Xr reboot 8 ,
+.Xr sysctl 8 ,
+.Xr syslogd 8 ,
+.Xr sysrc 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 2.0 .
+.Nm dummynet
+was introduced in
+.Fx 2.2.8 .
+Stateful extensions were introduced in
+.Fx 4.0 .
+.Nm ipfw2
+was introduced in Summer 2002.
+.Sh AUTHORS
+.An Ugen J. S. Antsilevich ,
+.An Poul-Henning Kamp ,
+.An Alex Nash ,
+.An Archie Cobbs ,
+.An Luigi Rizzo ,
+.An Rasool Al-Saadi .
+.Pp
+.An -nosplit
+API based upon code written by
+.An Daniel Boulet
+for BSDI.
+.Pp
+Dummynet has been introduced by Luigi Rizzo in 1997-1998.
+.Pp
+Some early work (1999-2000) on the
+.Nm dummynet
+traffic shaper supported by Akamba Corp.
+.Pp
+The ipfw core (ipfw2) has been completely redesigned and
+reimplemented by Luigi Rizzo in summer 2002.
+Further
+actions and
+options have been added by various developers over the years.
+.Pp
+.An -nosplit
+In-kernel NAT support written by
+.An Paolo Pisati Aq Mt piso@FreeBSD.org
+as part of a Summer of Code 2005 project.
+.Pp
+SCTP
+.Nm nat
+support has been developed by
+.An The Centre for Advanced Internet Architectures (CAIA) Aq http://www.caia.swin.edu.au .
+The primary developers and maintainers are David Hayes and Jason But.
+For further information visit:
+.Aq http://www.caia.swin.edu.au/urp/SONATA
+.Pp
+Delay profiles have been developed by Alessandro Cerri and
+Luigi Rizzo, supported by the
+European Commission within Projects Onelab and Onelab2.
+.Pp
+CoDel, PIE, FQ-CoDel and FQ-PIE AQM for Dummynet have been implemented by
+.An The Centre for Advanced Internet Architectures (CAIA)
+in 2016, supported by The Comcast Innovation Fund.
+The primary developer is
+Rasool Al-Saadi.
+.Sh BUGS
+The syntax has grown over the years and sometimes it might be confusing.
+Unfortunately, backward compatibility prevents cleaning up mistakes
+made in the definition of the syntax.
+.Pp
+.Em !!! WARNING !!!\&
+.Pp
+Misconfiguring the firewall can put your computer in an unusable state,
+possibly shutting down network services and requiring console access to
+regain control of it.
+.Pp
+Incoming packet fragments diverted by
+.Cm divert
+are reassembled before delivery to the socket.
+The action used on those packet is the one from the
+rule which matches the first fragment of the packet.
+.Pp
+Packets diverted to userland, and then reinserted by a userland process
+may lose various packet attributes.
+The packet source interface name
+will be preserved if it is shorter than 8 bytes and the userland process
+saves and reuses the sockaddr_in
+(as does
+.Xr natd 8 ) ;
+otherwise, it may be lost.
+If a packet is reinserted in this manner, later rules may be incorrectly
+applied, making the order of
+.Cm divert
+rules in the rule sequence very important.
+.Pp
+Dummynet drops all packets with IPv6 link-local addresses.
+.Pp
+Rules using
+.Cm uid
+or
+.Cm gid
+may not behave as expected.
+In particular, incoming SYN packets may
+have no uid or gid associated with them since they do not yet belong
+to a TCP connection, and the uid/gid associated with a packet may not
+be as expected if the associated process calls
+.Xr setuid 2
+or similar system calls.
+.Pp
+Rule syntax is subject to the command line environment and some patterns
+may need to be escaped with the backslash character
+or quoted appropriately.
+.Pp
+Due to the architecture of
+.Xr libalias 3 ,
+ipfw nat is not compatible with the TCP segmentation offloading (TSO).
+Thus, to reliably nat your network traffic, please disable TSO
+on your NICs using
+.Xr ifconfig 8 .
+.Pp
+ICMP error messages are not implicitly matched by dynamic rules
+for the respective conversations.
+To avoid failures of network error detection and path MTU discovery,
+ICMP error messages may need to be allowed explicitly through static
+rules.
+.Pp
+Rules using
+.Cm call
+and
+.Cm return
+actions may lead to confusing behaviour if ruleset has mistakes,
+and/or interaction with other subsystems (netgraph, dummynet, etc.) is used.
+One possible case for this is packet leaving
+.Nm
+in subroutine on the input pass, while later on output encountering unpaired
+.Cm return
+first.
+As the call stack is kept intact after input pass, packet will suddenly
+return to the rule number used on input pass, not on output one.
+Order of processing should be checked carefully to avoid such mistakes.
diff --git a/sbin/ipfw15/ipfw2.h b/sbin/ipfw15/ipfw2.h
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/ipfw2.h
@@ -0,0 +1,470 @@
+/*-
+ * 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
+ */
+
+enum cmdline_prog {
+ cmdline_prog_ipfw,
+ cmdline_prog_dnctl
+};
+
+/*
+ * Options that can be set on the command line.
+ * When reading commands from a file, a subset of the options can also
+ * be applied globally by specifying them before the file name.
+ * After that, each line can contain its own option that changes
+ * the global value.
+ * XXX The context is not restored after each line.
+ */
+
+struct cmdline_opts {
+ /* boolean options: */
+ int do_value_as_ip; /* show table value as IP */
+ int do_resolv; /* try to resolve all ip to names */
+ int do_time; /* Show time stamps */
+ int do_quiet; /* Be quiet in add and flush */
+ int do_pipe; /* this cmd refers to a pipe/queue/sched */
+ int do_nat; /* this cmd refers to a nat config */
+ int do_compact; /* show rules in compact mode */
+ int do_force; /* do not ask for confirmation */
+ int show_sets; /* display the set each rule belongs to */
+ int test_only; /* only check syntax */
+ int comment_only; /* only print action and comment */
+ int verbose; /* be verbose on some commands */
+ int debug_only; /* output ioctl i/o on stdout */
+
+ /* The options below can have multiple values. */
+
+ int do_dynamic; /* 1 - display dynamic rules */
+ /* 2 - display/delete only dynamic rules */
+ int do_sort; /* field to sort results (0 = no) */
+ /* valid fields are 1 and above */
+
+ uint32_t use_set; /* work with specified set number */
+ /* 0 means all sets, otherwise apply to set use_set - 1 */
+
+ enum cmdline_prog prog; /* Are we ipfw or dnctl? */
+};
+
+int is_ipfw(void);
+
+enum {
+ TIMESTAMP_NONE = 0,
+ TIMESTAMP_STRING,
+ TIMESTAMP_NUMERIC,
+};
+
+extern struct cmdline_opts g_co;
+
+/*
+ * _s_x is a structure that stores a string <-> token pairs, used in
+ * various places in the parser. Entries are stored in arrays,
+ * with an entry with s=NULL as terminator.
+ * The search routines are match_token() and match_value().
+ * Often, an element with x=0 contains an error string.
+ *
+ */
+struct _s_x {
+ char const *s;
+ int x;
+};
+
+extern struct _s_x f_ipdscp[];
+
+enum tokens {
+ TOK_NULL=0,
+
+ TOK_OR,
+ TOK_NOT,
+ TOK_STARTBRACE,
+ TOK_ENDBRACE,
+
+ TOK_ABORT6,
+ TOK_ABORT,
+ TOK_ACCEPT,
+ TOK_COUNT,
+ TOK_EACTION,
+ TOK_PIPE,
+ TOK_LINK,
+ TOK_QUEUE,
+ TOK_FLOWSET,
+ TOK_SCHED,
+ TOK_DIVERT,
+ TOK_TEE,
+ TOK_NETGRAPH,
+ TOK_NGTEE,
+ TOK_FORWARD,
+ TOK_SKIPTO,
+ TOK_DENY,
+ TOK_REJECT,
+ TOK_RESET,
+ TOK_UNREACH,
+ TOK_CHECKSTATE,
+ TOK_NAT,
+ TOK_REASS,
+ TOK_CALL,
+ TOK_RETURN,
+
+ TOK_ALTQ,
+ TOK_LOG,
+ TOK_TAG,
+ TOK_UNTAG,
+
+ TOK_TAGGED,
+ TOK_UID,
+ TOK_GID,
+ TOK_JAIL,
+ TOK_IN,
+ TOK_LIMIT,
+ TOK_SETLIMIT,
+ TOK_KEEPSTATE,
+ TOK_RECORDSTATE,
+ TOK_LAYER2,
+ TOK_OUT,
+ TOK_DIVERTED,
+ TOK_DIVERTEDLOOPBACK,
+ TOK_DIVERTEDOUTPUT,
+ TOK_XMIT,
+ TOK_RECV,
+ TOK_VIA,
+ TOK_FRAG,
+ TOK_IPOPTS,
+ TOK_IPLEN,
+ TOK_IPID,
+ TOK_IPPRECEDENCE,
+ TOK_DSCP,
+ TOK_IPTOS,
+ TOK_IPTTL,
+ TOK_IPVER,
+ TOK_ESTAB,
+ TOK_SETUP,
+ TOK_TCPDATALEN,
+ TOK_TCPFLAGS,
+ TOK_TCPOPTS,
+ TOK_TCPSEQ,
+ TOK_TCPACK,
+ TOK_TCPMSS,
+ TOK_TCPWIN,
+ TOK_ICMPTYPES,
+ TOK_MAC,
+ TOK_MACTYPE,
+ TOK_VERREVPATH,
+ TOK_VERSRCREACH,
+ TOK_ANTISPOOF,
+ TOK_IPSEC,
+ TOK_COMMENT,
+
+ TOK_PLR,
+ TOK_NOERROR,
+ TOK_BUCKETS,
+ TOK_DSTIP,
+ TOK_SRCIP,
+ TOK_DSTPORT,
+ TOK_SRCPORT,
+ TOK_DSTMAC,
+ TOK_SRCMAC,
+ TOK_ALL,
+ TOK_MASK,
+ TOK_FLOW_MASK,
+ TOK_SCHED_MASK,
+ TOK_BW,
+ TOK_DELAY,
+ TOK_PROFILE,
+ TOK_BURST,
+ TOK_RED,
+ TOK_GRED,
+ TOK_ECN,
+ TOK_DROPTAIL,
+ TOK_PROTO,
+#ifdef NEW_AQM
+ /* AQM tokens*/
+ TOK_NO_ECN,
+ TOK_CODEL,
+ TOK_FQ_CODEL,
+ TOK_TARGET,
+ TOK_INTERVAL,
+ TOK_FLOWS,
+ TOK_QUANTUM,
+
+ TOK_PIE,
+ TOK_FQ_PIE,
+ TOK_TUPDATE,
+ TOK_MAX_BURST,
+ TOK_MAX_ECNTH,
+ TOK_ALPHA,
+ TOK_BETA,
+ TOK_CAPDROP,
+ TOK_NO_CAPDROP,
+ TOK_ONOFF,
+ TOK_DRE,
+ TOK_TS,
+ TOK_DERAND,
+ TOK_NO_DERAND,
+#endif
+ /* dummynet tokens */
+ TOK_WEIGHT,
+ TOK_LMAX,
+ TOK_PRI,
+ TOK_TYPE,
+ TOK_SLOTSIZE,
+
+ TOK_IP,
+ TOK_IF,
+ TOK_ALOG,
+ TOK_DENY_INC,
+ TOK_SAME_PORTS,
+ TOK_UNREG_ONLY,
+ TOK_UNREG_CGN,
+ TOK_SKIP_GLOBAL,
+ TOK_RESET_ADDR,
+ TOK_ALIAS_REV,
+ TOK_PROXY_ONLY,
+ TOK_REDIR_ADDR,
+ TOK_REDIR_PORT,
+ TOK_REDIR_PROTO,
+
+ TOK_IPV6,
+ TOK_FLOWID,
+ TOK_ICMP6TYPES,
+ TOK_EXT6HDR,
+ TOK_DSTIP6,
+ TOK_SRCIP6,
+
+ TOK_IPV4,
+ TOK_UNREACH6,
+ TOK_RESET6,
+
+ TOK_FIB,
+ TOK_SETFIB,
+ TOK_LOOKUP,
+ TOK_SOCKARG,
+ TOK_SETDSCP,
+ TOK_FLOW,
+ TOK_IFLIST,
+ /* Table tokens */
+ TOK_CREATE,
+ TOK_DESTROY,
+ TOK_LIST,
+ TOK_INFO,
+ TOK_DETAIL,
+ TOK_MODIFY,
+ TOK_FLUSH,
+ TOK_SWAP,
+ TOK_ADD,
+ TOK_DEL,
+ TOK_VALTYPE,
+ TOK_ALGO,
+ TOK_TALIST,
+ TOK_ATOMIC,
+ TOK_LOCK,
+ TOK_UNLOCK,
+ TOK_VLIST,
+ TOK_OLIST,
+ TOK_MONITOR,
+ TOK_MISSING,
+ TOK_ORFLUSH,
+
+ /* NAT64 tokens */
+ TOK_NAT64STL,
+ TOK_NAT64LSN,
+ TOK_STATS,
+ TOK_STATES,
+ TOK_CONFIG,
+ TOK_TABLE4,
+ TOK_TABLE6,
+ TOK_PREFIX4,
+ TOK_PREFIX6,
+ TOK_AGG_LEN,
+ TOK_AGG_COUNT,
+ TOK_MAX_PORTS,
+ TOK_STATES_CHUNKS,
+ TOK_JMAXLEN,
+ TOK_PORT_RANGE,
+ TOK_PORT_ALIAS,
+ TOK_HOST_DEL_AGE,
+ TOK_PG_DEL_AGE,
+ TOK_TCP_SYN_AGE,
+ TOK_TCP_CLOSE_AGE,
+ TOK_TCP_EST_AGE,
+ TOK_UDP_AGE,
+ TOK_ICMP_AGE,
+ TOK_LOGOFF,
+ TOK_PRIVATE,
+ TOK_PRIVATEOFF,
+ TOK_SWAPCONF,
+ TOK_SWAPCONFOFF,
+
+ /* NAT64 CLAT tokens */
+ TOK_NAT64CLAT,
+ TOK_PLAT_PREFIX,
+ TOK_CLAT_PREFIX,
+
+ /* NPTv6 tokens */
+ TOK_NPTV6,
+ TOK_INTPREFIX,
+ TOK_EXTPREFIX,
+ TOK_PREFIXLEN,
+ TOK_EXTIF,
+
+ TOK_TCPSETMSS,
+
+ TOK_MARK,
+ TOK_SETMARK,
+
+ TOK_SKIPACTION,
+ TOK_UDP_EIM,
+};
+
+/*
+ * the following macro returns an error message if we run out of
+ * arguments.
+ */
+#define NEED(_p, msg) {if (!_p) errx(EX_USAGE, msg);}
+#define NEED1(msg) {if (!(*av)) errx(EX_USAGE, msg);}
+
+struct buf_pr {
+ char *buf; /* allocated buffer */
+ char *ptr; /* current pointer */
+ size_t size; /* total buffer size */
+ size_t avail; /* available storage */
+ size_t needed; /* length needed */
+};
+
+int pr_u64(struct buf_pr *bp, void *pd, int width);
+int bp_alloc(struct buf_pr *b, size_t size);
+void bp_free(struct buf_pr *b);
+int bprintf(struct buf_pr *b, const char *format, ...) __printflike(2, 3);
+
+
+/* memory allocation support */
+void *safe_calloc(size_t number, size_t size);
+void *safe_realloc(void *ptr, size_t size);
+
+/* string comparison functions used for historical compatibility */
+int _substrcmp(const char *str1, const char* str2);
+int _substrcmp2(const char *str1, const char* str2, const char* str3);
+int stringnum_cmp(const char *a, const char *b);
+
+/* utility functions */
+int match_token(struct _s_x *table, const char *string);
+int match_token_relaxed(struct _s_x *table, const char *string);
+int get_token(struct _s_x *table, const char *string, const char *errbase);
+char const *match_value(struct _s_x *p, int value);
+size_t concat_tokens(char *buf, size_t bufsize, struct _s_x *table,
+ const char *delimiter);
+int fill_flags(struct _s_x *flags, char *p, char **e, uint32_t *set,
+ uint32_t *clear);
+void print_flags_buffer(char *buf, size_t sz, struct _s_x *list, uint32_t set);
+
+struct _ip_fw3_opheader;
+int do_cmd(int optname, void *optval, uintptr_t optlen);
+int do_set3(int optname, struct _ip_fw3_opheader *op3, size_t optlen);
+int do_get3(int optname, struct _ip_fw3_opheader *op3, size_t *optlen);
+
+struct in6_addr;
+void n2mask(struct in6_addr *mask, int n);
+int contigmask(const uint8_t *p, int len);
+
+/*
+ * Forward declarations to avoid include way too many headers.
+ * C does not allow duplicated typedefs, so we use the base struct
+ * that the typedef points to.
+ * Should the typedefs use a different type, the compiler will
+ * still detect the change when compiling the body of the
+ * functions involved, so we do not lose error checking.
+ */
+struct _ipfw_insn;
+struct _ipfw_insn_altq;
+struct _ipfw_insn_u32;
+struct _ipfw_insn_ip6;
+struct _ipfw_insn_icmp6;
+
+/*
+ * The reserved set numer. This is a constant in ip_fw.h
+ * but we store it in a variable so other files do not depend
+ * in that header just for one constant.
+ */
+extern int resvd_set_number;
+
+/* first-level command handlers */
+void ipfw_add(char *av[]);
+void ipfw_show_nat(int ac, char **av);
+int ipfw_delete_nat(int i);
+void ipfw_config_pipe(int ac, char **av);
+void ipfw_config_nat(int ac, char **av);
+void ipfw_sets_handler(char *av[]);
+void ipfw_table_handler(int ac, char *av[]);
+void ipfw_sysctl_handler(char *av[], int which);
+void ipfw_delete(char *av[]);
+void ipfw_flush(int force);
+void ipfw_zero(int ac, char *av[], int optname);
+void ipfw_list(int ac, char *av[], int show_counters);
+void ipfw_internal_handler(int ac, char *av[]);
+void ipfw_nat64clat_handler(int ac, char *av[]);
+void ipfw_nat64lsn_handler(int ac, char *av[]);
+void ipfw_nat64stl_handler(int ac, char *av[]);
+void ipfw_nptv6_handler(int ac, char *av[]);
+int ipfw_check_object_name(const char *name);
+int ipfw_check_nat64prefix(const struct in6_addr *prefix, int length);
+
+#ifdef PF
+/* altq.c */
+void altq_set_enabled(int enabled);
+u_int32_t altq_name_to_qid(const char *name);
+void print_altq_cmd(struct buf_pr *bp, const struct _ipfw_insn_altq *altqptr);
+#else
+#define NO_ALTQ
+#endif
+
+/* dummynet.c */
+void dummynet_list(int ac, char *av[], int show_counters);
+void dummynet_flush(void);
+int ipfw_delete_pipe(int pipe_or_queue, int n);
+
+/* ipv6.c */
+void print_unreach6_code(struct buf_pr *bp, uint16_t code);
+void print_ip6(struct buf_pr *bp, const struct _ipfw_insn_ip6 *cmd);
+void print_flow6id(struct buf_pr *bp, const struct _ipfw_insn_u32 *cmd);
+void print_icmp6types(struct buf_pr *bp, const struct _ipfw_insn_u32 *cmd);
+void print_ext6hdr(struct buf_pr *bp, const struct _ipfw_insn *cmd);
+
+struct tidx;
+struct _ipfw_insn *add_srcip6(struct _ipfw_insn *cmd, char *av, int cblen,
+ struct tidx *tstate);
+struct _ipfw_insn *add_dstip6(struct _ipfw_insn *cmd, char *av, int cblen,
+ struct tidx *tstate);
+
+void fill_flow6(struct _ipfw_insn_u32 *cmd, char *av, int cblen);
+uint16_t get_unreach6_code(const char *str);
+void fill_icmp6types(struct _ipfw_insn_icmp6 *cmd, char *av, int cblen);
+int fill_ext6hdr(struct _ipfw_insn *cmd, char *av);
+
+/* ipfw2.c */
+void bp_flush(struct buf_pr *b);
+void fill_table(struct _ipfw_insn *cmd, char *av, uint8_t opcode,
+ struct tidx *tstate);
+
+/* tables.c */
+struct _ipfw_obj_ctlv;
+struct _ipfw_obj_ntlv;
+int table_check_name(const char *tablename);
+void ipfw_list_ta(int ac, char *av[]);
+void ipfw_list_values(int ac, char *av[]);
+void table_fill_ntlv(struct _ipfw_obj_ntlv *ntlv, const char *name,
+ uint8_t set, uint32_t uidx);
+
diff --git a/sbin/ipfw15/ipfw2.c b/sbin/ipfw15/ipfw2.c
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/ipfw2.c
@@ -0,0 +1,6129 @@
+/*-
+ * 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
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+
+#include "ipfw2.h"
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <grp.h>
+#include <jail.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <time.h> /* ctime */
+#include <timeconv.h> /* _long_to_time */
+#include <unistd.h>
+#include <fcntl.h>
+#include <stddef.h> /* offsetof */
+
+#include <net/ethernet.h>
+#include <net/if.h> /* only IFNAMSIZ */
+#include <net/if_dl.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h> /* only n_short, n_long */
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/ip_fw15.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+struct cmdline_opts g_co; /* global options */
+
+struct format_opts {
+ int bcwidth;
+ int pcwidth;
+ int show_counters;
+ int show_time; /* show timestamp */
+ uint32_t set_mask; /* enabled sets mask */
+ uint32_t flags; /* request flags */
+ uint32_t first; /* first rule to request */
+ uint32_t last; /* last rule to request */
+ uint32_t dcnt; /* number of dynamic states */
+ ipfw_obj_ctlv *tstate; /* table state data */
+};
+
+int resvd_set_number = RESVD_SET;
+
+static int ipfw_socket = -1;
+
+#define CHECK_LENGTH(v, len) do { \
+ if ((v) < (len)) \
+ errx(EX_DATAERR, "Rule too long"); \
+ } while (0)
+/*
+ * Check if we have enough space in cmd buffer. Note that since
+ * first 8? u32 words are reserved by reserved header, full cmd
+ * buffer can't be used, so we need to protect from buffer overrun
+ * only. At the beginning, cblen is less than actual buffer size by
+ * size of ipfw_insn_u32 instruction + 1 u32 work. This eliminates need
+ * for checking small instructions fitting in given range.
+ * We also (ab)use the fact that ipfw_insn is always the first field
+ * for any custom instruction.
+ */
+#define CHECK_CMDLEN CHECK_LENGTH(cblen, F_LEN((ipfw_insn *)cmd))
+
+#define GET_UINT_ARG(arg, min, max, tok, s_x) do { \
+ if (!av[0]) \
+ errx(EX_USAGE, "%s: missing argument", match_value(s_x, tok)); \
+ if (_substrcmp(*av, "tablearg") == 0) { \
+ arg = IP_FW_TARG; \
+ break; \
+ } \
+ \
+ { \
+ long _xval; \
+ char *end; \
+ \
+ _xval = strtol(*av, &end, 10); \
+ \
+ if (!isdigit(**av) || *end != '\0' || (_xval == 0 && errno == EINVAL)) \
+ errx(EX_DATAERR, "%s: invalid argument: %s", \
+ match_value(s_x, tok), *av); \
+ \
+ if (errno == ERANGE || _xval < min || _xval > max) \
+ errx(EX_DATAERR, "%s: argument is out of range (%u..%u): %s", \
+ match_value(s_x, tok), min, max, *av); \
+ \
+ if (_xval == IP_FW_TARG) \
+ errx(EX_DATAERR, "%s: illegal argument value: %s", \
+ match_value(s_x, tok), *av); \
+ arg = _xval; \
+ } \
+} while (0)
+
+static struct _s_x f_tcpflags[] = {
+ { "syn", TH_SYN },
+ { "fin", TH_FIN },
+ { "ack", TH_ACK },
+ { "psh", TH_PUSH },
+ { "rst", TH_RST },
+ { "urg", TH_URG },
+ { "tcp flag", 0 },
+ { NULL, 0 }
+};
+
+static struct _s_x f_tcpopts[] = {
+ { "mss", IP_FW_TCPOPT_MSS },
+ { "maxseg", IP_FW_TCPOPT_MSS },
+ { "window", IP_FW_TCPOPT_WINDOW },
+ { "sack", IP_FW_TCPOPT_SACK },
+ { "ts", IP_FW_TCPOPT_TS },
+ { "timestamp", IP_FW_TCPOPT_TS },
+ { "cc", IP_FW_TCPOPT_CC },
+ { "tcp option", 0 },
+ { NULL, 0 }
+};
+
+/*
+ * IP options span the range 0 to 255 so we need to remap them
+ * (though in fact only the low 5 bits are significant).
+ */
+static struct _s_x f_ipopts[] = {
+ { "ssrr", IP_FW_IPOPT_SSRR},
+ { "lsrr", IP_FW_IPOPT_LSRR},
+ { "rr", IP_FW_IPOPT_RR},
+ { "ts", IP_FW_IPOPT_TS},
+ { "ip option", 0 },
+ { NULL, 0 }
+};
+
+static struct _s_x f_iptos[] = {
+ { "lowdelay", IPTOS_LOWDELAY},
+ { "throughput", IPTOS_THROUGHPUT},
+ { "reliability", IPTOS_RELIABILITY},
+ { "mincost", IPTOS_MINCOST},
+ { "congestion", IPTOS_ECN_CE},
+ { "ecntransport", IPTOS_ECN_ECT0},
+ { "ip tos option", 0},
+ { NULL, 0 }
+};
+
+static struct _s_x f_ipoff[] = {
+ { "rf", IP_RF >> 8 },
+ { "df", IP_DF >> 8 },
+ { "mf", IP_MF >> 8 },
+ { "offset", 0x1 },
+ { NULL, 0}
+};
+
+struct _s_x f_ipdscp[] = {
+ { "af11", IPTOS_DSCP_AF11 >> 2 }, /* 001010 */
+ { "af12", IPTOS_DSCP_AF12 >> 2 }, /* 001100 */
+ { "af13", IPTOS_DSCP_AF13 >> 2 }, /* 001110 */
+ { "af21", IPTOS_DSCP_AF21 >> 2 }, /* 010010 */
+ { "af22", IPTOS_DSCP_AF22 >> 2 }, /* 010100 */
+ { "af23", IPTOS_DSCP_AF23 >> 2 }, /* 010110 */
+ { "af31", IPTOS_DSCP_AF31 >> 2 }, /* 011010 */
+ { "af32", IPTOS_DSCP_AF32 >> 2 }, /* 011100 */
+ { "af33", IPTOS_DSCP_AF33 >> 2 }, /* 011110 */
+ { "af41", IPTOS_DSCP_AF41 >> 2 }, /* 100010 */
+ { "af42", IPTOS_DSCP_AF42 >> 2 }, /* 100100 */
+ { "af43", IPTOS_DSCP_AF43 >> 2 }, /* 100110 */
+ { "be", IPTOS_DSCP_CS0 >> 2 }, /* 000000 */
+ { "va", IPTOS_DSCP_VA >> 2 }, /* 101100 */
+ { "ef", IPTOS_DSCP_EF >> 2 }, /* 101110 */
+ { "cs0", IPTOS_DSCP_CS0 >> 2 }, /* 000000 */
+ { "cs1", IPTOS_DSCP_CS1 >> 2 }, /* 001000 */
+ { "cs2", IPTOS_DSCP_CS2 >> 2 }, /* 010000 */
+ { "cs3", IPTOS_DSCP_CS3 >> 2 }, /* 011000 */
+ { "cs4", IPTOS_DSCP_CS4 >> 2 }, /* 100000 */
+ { "cs5", IPTOS_DSCP_CS5 >> 2 }, /* 101000 */
+ { "cs6", IPTOS_DSCP_CS6 >> 2 }, /* 110000 */
+ { "cs7", IPTOS_DSCP_CS7 >> 2 }, /* 100000 */
+ { NULL, 0 }
+};
+
+static struct _s_x limit_masks[] = {
+ {"all", DYN_SRC_ADDR|DYN_SRC_PORT|DYN_DST_ADDR|DYN_DST_PORT},
+ {"src-addr", DYN_SRC_ADDR},
+ {"src-port", DYN_SRC_PORT},
+ {"dst-addr", DYN_DST_ADDR},
+ {"dst-port", DYN_DST_PORT},
+ {NULL, 0}
+};
+
+/*
+ * we use IPPROTO_ETHERTYPE as a fake protocol id to call the print routines
+ * This is only used in this code.
+ */
+#define IPPROTO_ETHERTYPE 0x1000
+static struct _s_x ether_types[] = {
+ /*
+ * Note, we cannot use "-:&/" in the names because they are field
+ * separators in the type specifications. Also, we use s = NULL as
+ * end-delimiter, because a type of 0 can be legal.
+ */
+ { "ip", 0x0800 },
+ { "ipv4", 0x0800 },
+ { "ipv6", 0x86dd },
+ { "arp", 0x0806 },
+ { "rarp", 0x8035 },
+ { "vlan", 0x8100 },
+ { "loop", 0x9000 },
+ { "trail", 0x1000 },
+ { "at", 0x809b },
+ { "atalk", 0x809b },
+ { "aarp", 0x80f3 },
+ { "pppoe_disc", 0x8863 },
+ { "pppoe_sess", 0x8864 },
+ { "ipx_8022", 0x00E0 },
+ { "ipx_8023", 0x0000 },
+ { "ipx_ii", 0x8137 },
+ { "ipx_snap", 0x8137 },
+ { "ipx", 0x8137 },
+ { "ns", 0x0600 },
+ { NULL, 0 }
+};
+
+static struct _s_x rule_eactions[] = {
+ { "nat64clat", TOK_NAT64CLAT },
+ { "nat64lsn", TOK_NAT64LSN },
+ { "nat64stl", TOK_NAT64STL },
+ { "nptv6", TOK_NPTV6 },
+ { "tcp-setmss", TOK_TCPSETMSS },
+ { NULL, 0 } /* terminator */
+};
+
+static struct _s_x rule_actions[] = {
+ { "abort6", TOK_ABORT6 },
+ { "abort", TOK_ABORT },
+ { "accept", TOK_ACCEPT },
+ { "pass", TOK_ACCEPT },
+ { "allow", TOK_ACCEPT },
+ { "permit", TOK_ACCEPT },
+ { "count", TOK_COUNT },
+ { "pipe", TOK_PIPE },
+ { "queue", TOK_QUEUE },
+ { "divert", TOK_DIVERT },
+ { "tee", TOK_TEE },
+ { "netgraph", TOK_NETGRAPH },
+ { "ngtee", TOK_NGTEE },
+ { "fwd", TOK_FORWARD },
+ { "forward", TOK_FORWARD },
+ { "skipto", TOK_SKIPTO },
+ { "deny", TOK_DENY },
+ { "drop", TOK_DENY },
+ { "reject", TOK_REJECT },
+ { "reset6", TOK_RESET6 },
+ { "reset", TOK_RESET },
+ { "unreach6", TOK_UNREACH6 },
+ { "unreach", TOK_UNREACH },
+ { "check-state", TOK_CHECKSTATE },
+ { "//", TOK_COMMENT },
+ { "nat", TOK_NAT },
+ { "reass", TOK_REASS },
+ { "setfib", TOK_SETFIB },
+ { "setdscp", TOK_SETDSCP },
+ { "call", TOK_CALL },
+ { "return", TOK_RETURN },
+ { "eaction", TOK_EACTION },
+ { "tcp-setmss", TOK_TCPSETMSS },
+ { "setmark", TOK_SETMARK },
+ { NULL, 0 } /* terminator */
+};
+
+static struct _s_x return_types[] = {
+ { "next-rulenum", RETURN_NEXT_RULENUM },
+ { "next-rule", RETURN_NEXT_RULE },
+ { NULL, 0 } /* terminator */
+};
+
+static struct _s_x rule_action_params[] = {
+ { "altq", TOK_ALTQ },
+ { "log", TOK_LOG },
+ { "tag", TOK_TAG },
+ { "untag", TOK_UNTAG },
+ { NULL, 0 } /* terminator */
+};
+
+/*
+ * The 'lookup' instruction accepts one of the following arguments.
+ * Arguments are passed as o.arg1 and o->value in O_DST_LOOKUP option.
+ */
+static struct _s_x lookup_keys[] = {
+ { "dst-ip", LOOKUP_DST_IP },
+ { "src-ip", LOOKUP_SRC_IP },
+ { "dst-port", LOOKUP_DST_PORT },
+ { "src-port", LOOKUP_SRC_PORT },
+ { "dst-mac", LOOKUP_DST_MAC },
+ { "src-mac", LOOKUP_SRC_MAC },
+ { "uid", LOOKUP_UID },
+ { "jail", LOOKUP_JAIL },
+ { "dscp", LOOKUP_DSCP },
+ { "mark", LOOKUP_MARK },
+ { "rulenum", LOOKUP_RULENUM },
+ { NULL, 0 },
+};
+
+/*
+ * table(name,valuename=value) instruction accepts one of the
+ * following arguments as valuename.
+ */
+static struct _s_x tvalue_names[] = {
+ { "tag", TVALUE_TAG },
+ { "pipe", TVALUE_PIPE },
+ { "divert", TVALUE_DIVERT },
+ { "skipto", TVALUE_SKIPTO },
+ { "netgraph", TVALUE_NETGRAPH },
+ { "fib", TVALUE_FIB },
+ { "nat", TVALUE_NAT },
+ { "nh4", TVALUE_NH4 },
+ { "dscp", TVALUE_DSCP },
+ { "limit", TVALUE_LIMIT },
+ { "mark", TVALUE_MARK },
+ { NULL, 0 }
+};
+
+static struct _s_x rule_options[] = {
+ { "tagged", TOK_TAGGED },
+ { "uid", TOK_UID },
+ { "gid", TOK_GID },
+ { "jail", TOK_JAIL },
+ { "in", TOK_IN },
+ { "limit", TOK_LIMIT },
+ { "set-limit", TOK_SETLIMIT },
+ { "keep-state", TOK_KEEPSTATE },
+ { "record-state", TOK_RECORDSTATE },
+ { "bridged", TOK_LAYER2 },
+ { "layer2", TOK_LAYER2 },
+ { "out", TOK_OUT },
+ { "diverted", TOK_DIVERTED },
+ { "diverted-loopback", TOK_DIVERTEDLOOPBACK },
+ { "diverted-output", TOK_DIVERTEDOUTPUT },
+ { "xmit", TOK_XMIT },
+ { "recv", TOK_RECV },
+ { "via", TOK_VIA },
+ { "fragment", TOK_FRAG },
+ { "frag", TOK_FRAG },
+ { "fib", TOK_FIB },
+ { "ipoptions", TOK_IPOPTS },
+ { "ipopts", TOK_IPOPTS },
+ { "iplen", TOK_IPLEN },
+ { "ipid", TOK_IPID },
+ { "ipprecedence", TOK_IPPRECEDENCE },
+ { "dscp", TOK_DSCP },
+ { "iptos", TOK_IPTOS },
+ { "ipttl", TOK_IPTTL },
+ { "ipversion", TOK_IPVER },
+ { "ipver", TOK_IPVER },
+ { "estab", TOK_ESTAB },
+ { "established", TOK_ESTAB },
+ { "setup", TOK_SETUP },
+ { "sockarg", TOK_SOCKARG },
+ { "tcpdatalen", TOK_TCPDATALEN },
+ { "tcpflags", TOK_TCPFLAGS },
+ { "tcpflgs", TOK_TCPFLAGS },
+ { "tcpmss", TOK_TCPMSS },
+ { "tcpoptions", TOK_TCPOPTS },
+ { "tcpopts", TOK_TCPOPTS },
+ { "tcpseq", TOK_TCPSEQ },
+ { "tcpack", TOK_TCPACK },
+ { "tcpwin", TOK_TCPWIN },
+ { "icmptype", TOK_ICMPTYPES },
+ { "icmptypes", TOK_ICMPTYPES },
+ { "dst-ip", TOK_DSTIP },
+ { "src-ip", TOK_SRCIP },
+ { "dst-port", TOK_DSTPORT },
+ { "src-port", TOK_SRCPORT },
+ { "dst-mac", TOK_DSTMAC },
+ { "src-mac", TOK_SRCMAC },
+ { "proto", TOK_PROTO },
+ { "MAC", TOK_MAC },
+ { "mac", TOK_MAC },
+ { "mac-type", TOK_MACTYPE },
+ { "verrevpath", TOK_VERREVPATH },
+ { "versrcreach", TOK_VERSRCREACH },
+ { "antispoof", TOK_ANTISPOOF },
+ { "ipsec", TOK_IPSEC },
+ { "icmp6type", TOK_ICMP6TYPES },
+ { "icmp6types", TOK_ICMP6TYPES },
+ { "ext6hdr", TOK_EXT6HDR },
+ { "flow-id", TOK_FLOWID },
+ { "ipv6", TOK_IPV6 },
+ { "ip6", TOK_IPV6 },
+ { "ipv4", TOK_IPV4 },
+ { "ip4", TOK_IPV4 },
+ { "dst-ipv6", TOK_DSTIP6 },
+ { "dst-ip6", TOK_DSTIP6 },
+ { "src-ipv6", TOK_SRCIP6 },
+ { "src-ip6", TOK_SRCIP6 },
+ { "lookup", TOK_LOOKUP },
+ { "flow", TOK_FLOW },
+ { "mark", TOK_MARK },
+ { "defer-action", TOK_SKIPACTION },
+ { "defer-immediate-action", TOK_SKIPACTION },
+ { "//", TOK_COMMENT },
+
+ { "not", TOK_NOT }, /* pseudo option */
+ { "!", /* escape ? */ TOK_NOT }, /* pseudo option */
+ { "or", TOK_OR }, /* pseudo option */
+ { "|", /* escape */ TOK_OR }, /* pseudo option */
+ { "{", TOK_STARTBRACE }, /* pseudo option */
+ { "(", TOK_STARTBRACE }, /* pseudo option */
+ { "}", TOK_ENDBRACE }, /* pseudo option */
+ { ")", TOK_ENDBRACE }, /* pseudo option */
+ { NULL, 0 } /* terminator */
+};
+
+void bprint_uint_arg(struct buf_pr *bp, const char *str, uint32_t arg);
+static int ipfw_get_config(struct cmdline_opts *co, struct format_opts *fo,
+ ipfw_cfg_lheader **pcfg, size_t *psize);
+static int ipfw_show_config(struct cmdline_opts *co, struct format_opts *fo,
+ ipfw_cfg_lheader *cfg, size_t sz, int ac, char **av);
+static void ipfw_list_tifaces(void);
+
+struct tidx;
+static uint32_t pack_object(struct tidx *tstate, const char *name, int otype);
+static uint32_t pack_table(struct tidx *tstate, const char *name);
+
+static char *table_search_ctlv(ipfw_obj_ctlv *ctlv, uint32_t idx);
+static void object_sort_ctlv(ipfw_obj_ctlv *ctlv);
+static char *object_search_ctlv(ipfw_obj_ctlv *ctlv, uint32_t idx,
+ uint16_t type);
+
+int
+is_ipfw(void)
+{
+ return (g_co.prog == cmdline_prog_ipfw);
+}
+
+/*
+ * Simple string buffer API.
+ * Used to simplify buffer passing between function and for
+ * transparent overrun handling.
+ */
+
+/*
+ * Allocates new buffer of given size @sz.
+ *
+ * Returns 0 on success.
+ */
+int
+bp_alloc(struct buf_pr *b, size_t size)
+{
+ memset(b, 0, sizeof(struct buf_pr));
+
+ if ((b->buf = calloc(1, size)) == NULL)
+ return (ENOMEM);
+
+ b->ptr = b->buf;
+ b->size = size;
+ b->avail = b->size;
+
+ return (0);
+}
+
+void
+bp_free(struct buf_pr *b)
+{
+
+ free(b->buf);
+}
+
+/*
+ * Flushes buffer so new writer start from beginning.
+ */
+void
+bp_flush(struct buf_pr *b)
+{
+
+ b->ptr = b->buf;
+ b->avail = b->size;
+ b->buf[0] = '\0';
+}
+
+/*
+ * Print message specified by @format and args.
+ * Automatically manage buffer space and transparently handle
+ * buffer overruns.
+ *
+ * Returns number of bytes that should have been printed.
+ */
+int
+bprintf(struct buf_pr *b, const char *format, ...)
+{
+ va_list args;
+ int i;
+
+ va_start(args, format);
+
+ i = vsnprintf(b->ptr, b->avail, format, args);
+ va_end(args);
+
+ if (i < 0 || (size_t)i > b->avail) {
+ /* Overflow or print error */
+ b->avail = 0;
+ } else {
+ b->ptr += i;
+ b->avail -= i;
+ }
+
+ b->needed += i;
+
+ return (i);
+}
+
+/*
+ * Special values printer for tablearg-aware opcodes.
+ */
+void
+bprint_uint_arg(struct buf_pr *bp, const char *str, uint32_t arg)
+{
+
+ if (str != NULL)
+ bprintf(bp, "%s", str);
+ if (arg == IP_FW_TARG)
+ bprintf(bp, "tablearg");
+ else
+ bprintf(bp, "%u", arg);
+}
+
+/*
+ * Helper routine to print a possibly unaligned uint64_t on
+ * various platform. If width > 0, print the value with
+ * the desired width, followed by a space;
+ * otherwise, return the required width.
+ */
+int
+pr_u64(struct buf_pr *b, void *pd, int width)
+{
+#ifdef TCC
+#define U64_FMT "I64"
+#else
+#define U64_FMT "llu"
+#endif
+ uint64_t u;
+ unsigned long long d;
+
+ bcopy (pd, &u, sizeof(u));
+ d = u;
+ return (width > 0) ?
+ bprintf(b, "%*" U64_FMT " ", width, d) :
+ snprintf(NULL, 0, "%" U64_FMT, d) ;
+#undef U64_FMT
+}
+
+
+void *
+safe_calloc(size_t number, size_t size)
+{
+ void *ret = calloc(number, size);
+
+ if (ret == NULL)
+ err(EX_OSERR, "calloc");
+ return ret;
+}
+
+void *
+safe_realloc(void *ptr, size_t size)
+{
+ void *ret = realloc(ptr, size);
+
+ if (ret == NULL)
+ err(EX_OSERR, "realloc");
+ return ret;
+}
+
+/*
+ * Compare things like interface or table names.
+ */
+int
+stringnum_cmp(const char *a, const char *b)
+{
+ int la, lb;
+
+ la = strlen(a);
+ lb = strlen(b);
+
+ if (la > lb)
+ return (1);
+ else if (la < lb)
+ return (-01);
+
+ return (strcmp(a, b));
+}
+
+struct debug_header {
+ uint16_t cmd_type;
+ uint16_t spare1;
+ uint32_t opt_name;
+ uint32_t total_len;
+ uint32_t spare2;
+};
+
+/*
+ * conditionally runs the command.
+ * Selected options or negative -> getsockopt
+ */
+int
+do_cmd(int optname, void *optval, uintptr_t optlen)
+{
+ int i;
+
+ if (g_co.debug_only) {
+ struct debug_header dbg = {
+ .cmd_type = 1,
+ .opt_name = optname,
+ .total_len = optlen + sizeof(struct debug_header),
+ };
+ write(1, &dbg, sizeof(dbg));
+ write(1, optval, optlen);
+ }
+
+ if (g_co.test_only)
+ return (0);
+
+ if (ipfw_socket == -1)
+ ipfw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+ if (ipfw_socket < 0)
+ err(EX_UNAVAILABLE, "socket");
+
+ if (optname == IP_FW3 || optname < 0) {
+ if (optname < 0)
+ optname = -optname;
+ i = getsockopt(ipfw_socket, IPPROTO_IP, optname, optval,
+ (socklen_t *)optlen);
+ } else {
+ i = setsockopt(ipfw_socket, IPPROTO_IP, optname, optval, optlen);
+ }
+ return (i);
+}
+
+/*
+ * do_set3 - pass ipfw control cmd to kernel
+ * @optname: option name
+ * @optval: pointer to option data
+ * @optlen: option length
+ *
+ * Assumes op3 header is already embedded.
+ * Calls setsockopt() with IP_FW3 as kernel-visible opcode.
+ * Returns 0 on success or errno otherwise.
+ */
+int
+do_set3(int optname, ip_fw3_opheader *op3, size_t optlen)
+{
+
+ op3->opcode = optname;
+ op3->version = IP_FW3_OPVER; /* use last version */
+
+ if (g_co.debug_only) {
+ struct debug_header dbg = {
+ .cmd_type = 2,
+ .opt_name = optname,
+ .total_len = optlen, sizeof(struct debug_header),
+ };
+ write(1, &dbg, sizeof(dbg));
+ write(1, op3, optlen);
+ }
+
+ if (g_co.test_only)
+ return (0);
+
+ if (ipfw_socket == -1)
+ ipfw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+ if (ipfw_socket < 0)
+ err(EX_UNAVAILABLE, "socket");
+
+
+ return (setsockopt(ipfw_socket, IPPROTO_IP, IP_FW3, op3, optlen));
+}
+
+/*
+ * do_get3 - pass ipfw control cmd to kernel
+ * @optname: option name
+ * @optval: pointer to option data
+ * @optlen: pointer to option length
+ *
+ * Assumes op3 header is already embedded.
+ * Calls getsockopt() with IP_FW3 as kernel-visible opcode.
+ * Returns 0 on success or errno otherwise.
+ */
+int
+do_get3(int optname, ip_fw3_opheader *op3, size_t *optlen)
+{
+ int error;
+ socklen_t len;
+
+ op3->opcode = optname;
+ op3->version = IP_FW3_OPVER; /* use last version */
+
+ if (g_co.debug_only) {
+ struct debug_header dbg = {
+ .cmd_type = 3,
+ .opt_name = optname,
+ .total_len = *optlen + sizeof(struct debug_header),
+ };
+ write(1, &dbg, sizeof(dbg));
+ write(1, op3, *optlen);
+ }
+
+ if (g_co.test_only)
+ return (0);
+
+ if (ipfw_socket == -1)
+ ipfw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+ if (ipfw_socket < 0)
+ err(EX_UNAVAILABLE, "socket");
+
+
+ len = *optlen;
+ error = getsockopt(ipfw_socket, IPPROTO_IP, IP_FW3, op3, &len);
+ *optlen = len;
+
+ return (error);
+}
+
+/**
+ * match_token takes a table and a string, returns the value associated
+ * with the string (-1 in case of failure).
+ */
+int
+match_token(struct _s_x *table, const char *string)
+{
+ struct _s_x *pt;
+ uint i = strlen(string);
+
+ for (pt = table ; i && pt->s != NULL ; pt++)
+ if (strlen(pt->s) == i && !bcmp(string, pt->s, i))
+ return pt->x;
+ return (-1);
+}
+
+/**
+ * match_token_relaxed takes a table and a string, returns the value associated
+ * with the string for the best match.
+ *
+ * Returns:
+ * value from @table for matched records
+ * -1 for non-matched records
+ * -2 if more than one records match @string.
+ */
+int
+match_token_relaxed(struct _s_x *table, const char *string)
+{
+ struct _s_x *pt, *m;
+ int i, c;
+
+ i = strlen(string);
+ c = 0;
+
+ for (pt = table ; i != 0 && pt->s != NULL ; pt++) {
+ if (strncmp(pt->s, string, i) != 0)
+ continue;
+ m = pt;
+ c++;
+ }
+
+ if (c == 1)
+ return (m->x);
+
+ return (c > 0 ? -2: -1);
+}
+
+int
+get_token(struct _s_x *table, const char *string, const char *errbase)
+{
+ int tcmd;
+
+ if ((tcmd = match_token_relaxed(table, string)) < 0)
+ errx(EX_USAGE, "%s %s %s",
+ (tcmd == 0) ? "invalid" : "ambiguous", errbase, string);
+
+ return (tcmd);
+}
+
+/**
+ * match_value takes a table and a value, returns the string associated
+ * with the value (NULL in case of failure).
+ */
+char const *
+match_value(struct _s_x *p, int value)
+{
+ for (; p->s != NULL; p++)
+ if (p->x == value)
+ return p->s;
+ return NULL;
+}
+
+size_t
+concat_tokens(char *buf, size_t bufsize, struct _s_x *table,
+ const char *delimiter)
+{
+ struct _s_x *pt;
+ int l;
+ size_t sz;
+
+ for (sz = 0, pt = table ; pt->s != NULL; pt++) {
+ l = snprintf(buf + sz, bufsize - sz, "%s%s",
+ (sz == 0) ? "" : delimiter, pt->s);
+ sz += l;
+ bufsize += l;
+ if (sz > bufsize)
+ return (bufsize);
+ }
+
+ return (sz);
+}
+
+/*
+ * helper function to process a set of flags and set bits in the
+ * appropriate masks.
+ */
+int
+fill_flags(struct _s_x *flags, char *p, char **e, uint32_t *set,
+ uint32_t *clear)
+{
+ char *q; /* points to the separator */
+ int val;
+ uint32_t *which; /* mask we are working on */
+
+ while (p && *p) {
+ if (*p == '!') {
+ p++;
+ which = clear;
+ } else
+ which = set;
+ q = strchr(p, ',');
+ if (q)
+ *q++ = '\0';
+ val = match_token(flags, p);
+ if (val <= 0) {
+ if (e != NULL)
+ *e = p;
+ return (-1);
+ }
+ *which |= (uint32_t)val;
+ p = q;
+ }
+ return (0);
+}
+
+void
+print_flags_buffer(char *buf, size_t sz, struct _s_x *list, uint32_t set)
+{
+ char const *comma = "";
+ int i, l;
+
+ for (i = 0; list[i].x != 0; i++) {
+ if ((set & list[i].x) == 0)
+ continue;
+
+ set &= ~list[i].x;
+ l = snprintf(buf, sz, "%s%s", comma, list[i].s);
+ if (l < 0 || (size_t)l >= sz)
+ return;
+ comma = ",";
+ buf += l;
+ sz -=l;
+ }
+}
+
+/*
+ * _substrcmp takes two strings and returns 1 if they do not match,
+ * and 0 if they match exactly or the first string is a sub-string
+ * of the second. A warning is printed to stderr in the case that the
+ * first string is a sub-string of the second.
+ *
+ * This function will be removed in the future through the usual
+ * deprecation process.
+ */
+int
+_substrcmp(const char *str1, const char* str2)
+{
+
+ if (strncmp(str1, str2, strlen(str1)) != 0)
+ return 1;
+
+ if (strlen(str1) != strlen(str2))
+ warnx("DEPRECATED: '%s' matched '%s' as a sub-string",
+ str1, str2);
+ return 0;
+}
+
+/*
+ * _substrcmp2 takes three strings and returns 1 if the first two do not match,
+ * and 0 if they match exactly or the second string is a sub-string
+ * of the first. A warning is printed to stderr in the case that the
+ * first string does not match the third.
+ *
+ * This function exists to warn about the bizarre construction
+ * strncmp(str, "by", 2) which is used to allow people to use a shortcut
+ * for "bytes". The problem is that in addition to accepting "by",
+ * "byt", "byte", and "bytes", it also excepts "by_rabid_dogs" and any
+ * other string beginning with "by".
+ *
+ * This function will be removed in the future through the usual
+ * deprecation process.
+ */
+int
+_substrcmp2(const char *str1, const char* str2, const char* str3)
+{
+
+ if (strncmp(str1, str2, strlen(str2)) != 0)
+ return 1;
+
+ if (strcmp(str1, str3) != 0)
+ warnx("DEPRECATED: '%s' matched '%s'",
+ str1, str3);
+ return 0;
+}
+
+/*
+ * prints one port, symbolic or numeric
+ */
+static void
+print_port(struct buf_pr *bp, int proto, uint16_t port)
+{
+
+ if (proto == IPPROTO_ETHERTYPE) {
+ char const *s;
+
+ if (g_co.do_resolv && (s = match_value(ether_types, port)) )
+ bprintf(bp, "%s", s);
+ else
+ bprintf(bp, "0x%04x", port);
+ } else {
+ struct servent *se = NULL;
+ if (g_co.do_resolv) {
+ struct protoent *pe = getprotobynumber(proto);
+
+ se = getservbyport(htons(port), pe ? pe->p_name : NULL);
+ }
+ if (se)
+ bprintf(bp, "%s", se->s_name);
+ else
+ bprintf(bp, "%d", port);
+ }
+}
+
+static struct _s_x _port_name[] = {
+ {"dst-port", O_IP_DSTPORT},
+ {"src-port", O_IP_SRCPORT},
+ {"ipid", O_IPID},
+ {"iplen", O_IPLEN},
+ {"ipttl", O_IPTTL},
+ {"mac-type", O_MAC_TYPE},
+ {"tcpdatalen", O_TCPDATALEN},
+ {"tcpmss", O_TCPMSS},
+ {"tcpwin", O_TCPWIN},
+ {"tagged", O_TAGGED},
+ {NULL, 0}
+};
+
+/*
+ * Print the values in a list 16-bit items of the types above.
+ * XXX todo: add support for mask.
+ */
+static void
+print_newports(struct buf_pr *bp, const ipfw_insn_u16 *cmd, int proto, int opcode)
+{
+ const uint16_t *p = cmd->ports;
+ int i;
+ char const *sep;
+
+ if (opcode != 0) {
+ sep = match_value(_port_name, opcode);
+ if (sep == NULL)
+ sep = "???";
+ bprintf(bp, " %s", sep);
+ }
+ sep = " ";
+ for (i = F_LEN((const ipfw_insn *)cmd) - 1; i > 0; i--, p += 2) {
+ bprintf(bp, "%s", sep);
+ print_port(bp, proto, p[0]);
+ if (p[0] != p[1]) {
+ bprintf(bp, "-");
+ print_port(bp, proto, p[1]);
+ }
+ sep = ",";
+ }
+}
+
+/*
+ * Like strtol, but also translates service names into port numbers
+ * for some protocols.
+ * In particular:
+ * proto == -1 disables the protocol check;
+ * proto == IPPROTO_ETHERTYPE looks up an internal table
+ * proto == <some value in /etc/protocols> matches the values there.
+ * Returns *end == s in case the parameter is not found.
+ */
+static int
+strtoport(char *s, char **end, int base, int proto)
+{
+ char *p, *buf;
+ char *s1;
+ int i;
+
+ *end = s; /* default - not found */
+ if (*s == '\0')
+ return 0; /* not found */
+
+ if (isdigit(*s))
+ return strtol(s, end, base);
+
+ /*
+ * find separator. '\\' escapes the next char.
+ */
+ for (s1 = s; *s1 && (isalnum(*s1) || *s1 == '\\' ||
+ *s1 == '_' || *s1 == '.') ; s1++)
+ if (*s1 == '\\' && s1[1] != '\0')
+ s1++;
+
+ buf = safe_calloc(s1 - s + 1, 1);
+
+ /*
+ * copy into a buffer skipping backslashes
+ */
+ for (p = s, i = 0; p != s1 ; p++)
+ if (*p != '\\')
+ buf[i++] = *p;
+ buf[i++] = '\0';
+
+ if (proto == IPPROTO_ETHERTYPE) {
+ i = match_token(ether_types, buf);
+ free(buf);
+ if (i != -1) { /* found */
+ *end = s1;
+ return i;
+ }
+ } else {
+ struct protoent *pe = NULL;
+ struct servent *se;
+
+ if (proto != 0)
+ pe = getprotobynumber(proto);
+ setservent(1);
+ se = getservbyname(buf, pe ? pe->p_name : NULL);
+ free(buf);
+ if (se != NULL) {
+ *end = s1;
+ return ntohs(se->s_port);
+ }
+ }
+ return 0; /* not found */
+}
+
+/*
+ * Fill the body of the command with the list of port ranges.
+ */
+static int
+fill_newports(ipfw_insn_u16 *cmd, char *av, int proto, int cblen)
+{
+ uint16_t a, b, *p = cmd->ports;
+ int i = 0;
+ char *s = av;
+
+ while (*s) {
+ a = strtoport(av, &s, 0, proto);
+ if (s == av) /* empty or invalid argument */
+ return (0);
+
+ CHECK_LENGTH(cblen, i + 2);
+
+ switch (*s) {
+ case '-': /* a range */
+ av = s + 1;
+ b = strtoport(av, &s, 0, proto);
+ /* Reject expressions like '1-abc' or '1-2-3'. */
+ if (s == av || (*s != ',' && *s != '\0'))
+ return (0);
+ p[0] = a;
+ p[1] = b;
+ break;
+ case ',': /* comma separated list */
+ case '\0':
+ p[0] = p[1] = a;
+ break;
+ default:
+ warnx("port list: invalid separator <%c> in <%s>",
+ *s, av);
+ return (0);
+ }
+
+ i++;
+ p += 2;
+ av = s + 1;
+ }
+ if (i > 0) {
+ if (i + 1 > F_LEN_MASK)
+ errx(EX_DATAERR, "too many ports/ranges\n");
+ cmd->o.len |= i + 1; /* leave F_NOT and F_OR untouched */
+ }
+ return (i);
+}
+
+/*
+ * Fill the body of the command with the list of DiffServ codepoints.
+ */
+static void
+fill_dscp(ipfw_insn *cmd, char *av, int cblen)
+{
+ uint32_t *low, *high;
+ char *s = av, *a;
+ int code;
+
+ cmd->opcode = O_DSCP;
+ cmd->len |= F_INSN_SIZE(ipfw_insn_u32) + 1;
+
+ CHECK_CMDLEN;
+
+ low = (uint32_t *)(cmd + 1);
+ high = low + 1;
+
+ *low = 0;
+ *high = 0;
+
+ while (s != NULL) {
+ a = strchr(s, ',');
+
+ if (a != NULL)
+ *a++ = '\0';
+
+ if (isalpha(*s)) {
+ if ((code = match_token(f_ipdscp, s)) == -1)
+ errx(EX_DATAERR, "Unknown DSCP code");
+ } else {
+ code = strtoul(s, NULL, 10);
+ if (code < 0 || code > 63)
+ errx(EX_DATAERR, "Invalid DSCP value");
+ }
+
+ if (code >= 32)
+ *high |= 1 << (code - 32);
+ else
+ *low |= 1 << code;
+
+ s = a;
+ }
+}
+
+/*
+ * Fill the body of the command with mark value and mask.
+ */
+static void
+fill_mark(ipfw_insn *cmd, char *av, int cblen)
+{
+ uint32_t *value, *mask;
+ char *value_str;
+
+ cmd->opcode = O_MARK;
+ cmd->len |= F_INSN_SIZE(ipfw_insn_u32) + 1;
+
+ CHECK_CMDLEN;
+
+ value = (uint32_t *)(cmd + 1);
+ mask = value + 1;
+
+ value_str = strsep(&av, ":");
+
+ if (strcmp(value_str, "tablearg") == 0) {
+ cmd->arg1 = IP_FW_TARG;
+ *value = 0;
+ } else {
+ /* This is not a tablearg */
+ cmd->arg1 |= 0x8000;
+ *value = strtoul(value_str, NULL, 0);
+ }
+ if (av)
+ *mask = strtoul(av, NULL, 0);
+ else
+ *mask = 0xFFFFFFFF;
+
+ if ((*value & *mask) != *value)
+ errx(EX_DATAERR, "Static mark value: some bits in value are"
+ " set that will be masked out by mask "
+ "(%#x & %#x) = %#x != %#x",
+ *value, *mask, (*value & *mask), *value);
+}
+
+static struct _s_x icmpcodes[] = {
+ { "net", ICMP_UNREACH_NET },
+ { "host", ICMP_UNREACH_HOST },
+ { "protocol", ICMP_UNREACH_PROTOCOL },
+ { "port", ICMP_UNREACH_PORT },
+ { "needfrag", ICMP_UNREACH_NEEDFRAG },
+ { "srcfail", ICMP_UNREACH_SRCFAIL },
+ { "net-unknown", ICMP_UNREACH_NET_UNKNOWN },
+ { "host-unknown", ICMP_UNREACH_HOST_UNKNOWN },
+ { "isolated", ICMP_UNREACH_ISOLATED },
+ { "net-prohib", ICMP_UNREACH_NET_PROHIB },
+ { "host-prohib", ICMP_UNREACH_HOST_PROHIB },
+ { "tosnet", ICMP_UNREACH_TOSNET },
+ { "toshost", ICMP_UNREACH_TOSHOST },
+ { "filter-prohib", ICMP_UNREACH_FILTER_PROHIB },
+ { "host-precedence", ICMP_UNREACH_HOST_PRECEDENCE },
+ { "precedence-cutoff", ICMP_UNREACH_PRECEDENCE_CUTOFF },
+ { NULL, 0 }
+};
+
+static uint16_t
+get_reject_code(const char *str)
+{
+ int val;
+ char *s;
+
+ val = strtoul(str, &s, 0);
+ if (s == str || *s != '\0' || val >= 0x100)
+ val = match_token(icmpcodes, str);
+ if (val < 0)
+ errx(EX_DATAERR, "unknown ICMP unreachable code ``%s''", str);
+ return (val);
+}
+
+static void
+print_reject_code(struct buf_pr *bp, uint16_t code)
+{
+ char const *s;
+
+ if ((s = match_value(icmpcodes, code)) != NULL)
+ bprintf(bp, "unreach %s", s);
+ else
+ bprintf(bp, "unreach %u", code);
+}
+
+/*
+ * Returns the number of bits set (from left) in a contiguous bitmask,
+ * or -1 if the mask is not contiguous.
+ * XXX this needs a proper fix.
+ * This effectively works on masks in big-endian (network) format.
+ * when compiled on little endian architectures.
+ *
+ * First bit is bit 7 of the first byte -- note, for MAC addresses,
+ * the first bit on the wire is bit 0 of the first byte.
+ * len is the max length in bits.
+ */
+int
+contigmask(const uint8_t *p, int len)
+{
+ int i, n;
+
+ for (i=0; i<len ; i++)
+ if ( (p[i/8] & (1 << (7 - (i%8)))) == 0) /* first bit unset */
+ break;
+ for (n=i+1; n < len; n++)
+ if ( (p[n/8] & (1 << (7 - (n%8)))) != 0)
+ return -1; /* mask not contiguous */
+ return i;
+}
+
+/*
+ * print flags set/clear in the two bitmasks passed as parameters.
+ * There is a specialized check for f_tcpflags.
+ */
+static void
+print_flags(struct buf_pr *bp, char const *name, const ipfw_insn *cmd,
+ struct _s_x *list)
+{
+ char const *comma = "";
+ int i;
+ uint8_t set = cmd->arg1 & 0xff;
+ uint8_t clear = (cmd->arg1 >> 8) & 0xff;
+
+ if (list == f_tcpflags && set == TH_SYN && clear == TH_ACK) {
+ bprintf(bp, " setup");
+ return;
+ }
+
+ bprintf(bp, " %s ", name);
+ for (i=0; list[i].x != 0; i++) {
+ if (set & list[i].x) {
+ set &= ~list[i].x;
+ bprintf(bp, "%s%s", comma, list[i].s);
+ comma = ",";
+ }
+ if (clear & list[i].x) {
+ clear &= ~list[i].x;
+ bprintf(bp, "%s!%s", comma, list[i].s);
+ comma = ",";
+ }
+ }
+}
+
+static void
+print_tvalue(struct buf_pr *bp, const ipfw_insn_table *cmd)
+{
+ const char *name;
+
+ name = match_value(tvalue_names, IPFW_TVALUE_TYPE(&cmd->o));
+ bprintf(bp, ",%s=%u", name != NULL ? name: "<invalid>", cmd->value);
+}
+
+
+/*
+ * Print the ip address contained in a command.
+ */
+static void
+print_ip(struct buf_pr *bp, const struct format_opts *fo,
+ const ipfw_insn_ip *cmd)
+{
+ struct hostent *he = NULL;
+ const struct in_addr *ia;
+ const uint32_t *a = ((const ipfw_insn_u32 *)cmd)->d;
+ uint32_t len = F_LEN(&cmd->o);
+ char *t;
+
+ bprintf(bp, " ");
+ switch (cmd->o.opcode) {
+ case O_IP_SRC_ME:
+ case O_IP_DST_ME:
+ bprintf(bp, "me");
+ return;
+
+ case O_IP_DST_LOOKUP:
+ if ((len == F_INSN_SIZE(ipfw_insn_kidx) ||
+ len == F_INSN_SIZE(ipfw_insn_table)) &&
+ IPFW_LOOKUP_TYPE(&cmd->o) != LOOKUP_NONE) {
+ const char *key;
+
+ key = match_value(lookup_keys,
+ IPFW_LOOKUP_TYPE(&cmd->o));
+ t = table_search_ctlv(fo->tstate,
+ insntoc(&cmd->o, kidx)->kidx);
+ if (len == F_INSN_SIZE(ipfw_insn_table)) {
+ bprintf(bp, "lookup %s:%#x %s",
+ (key != NULL ? key : "<invalid>"),
+ insntoc(&cmd->o, table)->value, t);
+ } else
+ bprintf(bp, "lookup %s %s", key != NULL ? key:
+ "<invalid>", t);
+ return;
+ }
+ /* FALLTHROUGH */
+ case O_IP_SRC_LOOKUP:
+ t = table_search_ctlv(fo->tstate,
+ insntoc(&cmd->o, kidx)->kidx);
+ bprintf(bp, "table(%s", t);
+ if (len == F_INSN_SIZE(ipfw_insn_table))
+ print_tvalue(bp, insntoc(&cmd->o, table));
+ bprintf(bp, ")");
+ return;
+ }
+
+ if (cmd->o.opcode == O_IP_SRC_SET || cmd->o.opcode == O_IP_DST_SET) {
+ const uint32_t *map = (const uint32_t *)&cmd->mask;
+ struct in_addr addr;
+ uint32_t x;
+ int i, j;
+ char comma = '{';
+
+ x = cmd->o.arg1 - 1;
+ x = htonl(~x);
+ addr.s_addr = htonl(cmd->addr.s_addr);
+ bprintf(bp, "%s/%d", inet_ntoa(addr),
+ contigmask((uint8_t *)&x, 32));
+ x = cmd->addr.s_addr;
+ x &= 0xff; /* base */
+ /*
+ * Print bits and ranges.
+ * Locate first bit set (i), then locate first bit unset (j).
+ * If we have 3+ consecutive bits set, then print them as a
+ * range, otherwise only print the initial bit and rescan.
+ */
+ for (i=0; i < cmd->o.arg1; i++)
+ if (map[i/32] & (1<<(i & 31))) {
+ for (j=i+1; j < cmd->o.arg1; j++)
+ if (!(map[ j/32] & (1<<(j & 31))))
+ break;
+ bprintf(bp, "%c%d", comma, i+x);
+ if (j>i+2) { /* range has at least 3 elements */
+ bprintf(bp, "-%d", j-1+x);
+ i = j-1;
+ }
+ comma = ',';
+ }
+ bprintf(bp, "}");
+ return;
+ }
+ /*
+ * len == 2 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 / 2; len > 0; len--, a += 2) {
+ int mb = /* mask length */
+ (cmd->o.opcode == O_IP_SRC || cmd->o.opcode == O_IP_DST) ?
+ 32 : contigmask((const uint8_t *)&(a[1]), 32);
+ if (mb == 32 && g_co.do_resolv)
+ he = gethostbyaddr((const char *)&(a[0]), sizeof(in_addr_t),
+ AF_INET);
+ if (he != NULL) /* resolved to name */
+ bprintf(bp, "%s", he->h_name);
+ else if (mb == 0) /* any */
+ bprintf(bp, "any");
+ else { /* numeric IP followed by some kind of mask */
+ ia = (const struct in_addr *)&a[0];
+ bprintf(bp, "%s", inet_ntoa(*ia));
+ if (mb < 0) {
+ ia = (const struct in_addr *)&a[1];
+ bprintf(bp, ":%s", inet_ntoa(*ia));
+ } else if (mb < 32)
+ bprintf(bp, "/%d", mb);
+ }
+ if (len > 1)
+ bprintf(bp, ",");
+ }
+}
+
+/*
+ * prints a MAC address/mask pair
+ */
+static void
+format_mac(struct buf_pr *bp, const uint8_t *addr, const uint8_t *mask)
+{
+ int l = contigmask(mask, 48);
+
+ if (l == 0)
+ bprintf(bp, " any");
+ else {
+ bprintf(bp, " %02x:%02x:%02x:%02x:%02x:%02x",
+ addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
+ if (l == -1)
+ bprintf(bp, "&%02x:%02x:%02x:%02x:%02x:%02x",
+ mask[0], mask[1], mask[2],
+ mask[3], mask[4], mask[5]);
+ else if (l < 48)
+ bprintf(bp, "/%d", l);
+ }
+}
+
+static void
+print_mac(struct buf_pr *bp, const ipfw_insn_mac *mac)
+{
+
+ bprintf(bp, " MAC");
+ format_mac(bp, mac->addr, mac->mask);
+ format_mac(bp, mac->addr + 6, mac->mask + 6);
+}
+
+static void
+print_mac_lookup(struct buf_pr *bp, const struct format_opts *fo,
+ const ipfw_insn *cmd)
+{
+ uint32_t len = F_LEN(cmd);
+ char *t;
+
+ bprintf(bp, " ");
+
+ t = table_search_ctlv(fo->tstate, insntoc(cmd, kidx)->kidx);
+ bprintf(bp, "table(%s", t);
+ if (len == F_INSN_SIZE(ipfw_insn_table))
+ print_tvalue(bp, insntoc(cmd, table));
+ bprintf(bp, ")");
+}
+
+static void
+fill_icmptypes(ipfw_insn_u32 *cmd, char *av)
+{
+ uint8_t type;
+
+ cmd->d[0] = 0;
+ while (*av) {
+ if (*av == ',')
+ av++;
+
+ type = strtoul(av, &av, 0);
+
+ if (*av != ',' && *av != '\0')
+ errx(EX_DATAERR, "invalid ICMP type");
+
+ if (type > 31)
+ errx(EX_DATAERR, "ICMP type out of range");
+
+ cmd->d[0] |= 1 << type;
+ }
+ cmd->o.opcode = O_ICMPTYPE;
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32);
+}
+
+static void
+print_icmptypes(struct buf_pr *bp, const ipfw_insn_u32 *cmd)
+{
+ int i;
+ char sep= ' ';
+
+ bprintf(bp, " icmptypes");
+ for (i = 0; i < 32; i++) {
+ if ( (cmd->d[0] & (1 << (i))) == 0)
+ continue;
+ bprintf(bp, "%c%d", sep, i);
+ sep = ',';
+ }
+}
+
+static void
+print_dscp(struct buf_pr *bp, const ipfw_insn_u32 *cmd)
+{
+ const uint32_t *v;
+ const char *code;
+ int i = 0;
+ char sep= ' ';
+
+ bprintf(bp, " dscp");
+ v = cmd->d;
+ while (i < 64) {
+ if (*v & (1 << i)) {
+ if ((code = match_value(f_ipdscp, i)) != NULL)
+ bprintf(bp, "%c%s", sep, code);
+ else
+ bprintf(bp, "%c%d", sep, i);
+ sep = ',';
+ }
+
+ if ((++i % 32) == 0)
+ v++;
+ }
+}
+
+struct show_state {
+ struct ip_fw_rule *rule;
+ const ipfw_insn_kidx *eaction;
+ uint8_t *printed;
+ int flags;
+#define HAVE_PROTO 0x0001
+#define HAVE_SRCIP 0x0002
+#define HAVE_DSTIP 0x0004
+#define HAVE_PROBE_STATE 0x0008
+ int proto;
+ int or_block;
+};
+
+static int
+init_show_state(struct show_state *state, struct ip_fw_rule *rule)
+{
+
+ state->printed = calloc(rule->cmd_len, sizeof(uint8_t));
+ if (state->printed == NULL)
+ return (ENOMEM);
+ state->rule = rule;
+ state->eaction = NULL;
+ state->flags = 0;
+ state->proto = 0;
+ state->or_block = 0;
+ return (0);
+}
+
+static void
+free_show_state(struct show_state *state)
+{
+
+ free(state->printed);
+}
+
+static uint8_t
+is_printed_opcode(struct show_state *state, const ipfw_insn *cmd)
+{
+
+ return (state->printed[cmd - state->rule->cmd]);
+}
+
+static void
+mark_printed(struct show_state *state, const ipfw_insn *cmd)
+{
+
+ state->printed[cmd - state->rule->cmd] = 1;
+}
+
+static void
+print_limit_mask(struct buf_pr *bp, const ipfw_insn_limit *limit)
+{
+ struct _s_x *p = limit_masks;
+ char const *comma = " ";
+ uint8_t x;
+
+ for (x = limit->limit_mask; p->x != 0; p++) {
+ if ((x & p->x) == p->x) {
+ x &= ~p->x;
+ bprintf(bp, "%s%s", comma, p->s);
+ comma = ",";
+ }
+ }
+ bprint_uint_arg(bp, " ", limit->conn_limit);
+}
+
+static int
+print_instruction(struct buf_pr *bp, const struct format_opts *fo,
+ struct show_state *state, const ipfw_insn *cmd)
+{
+ struct protoent *pe;
+ struct passwd *pwd;
+ struct group *grp;
+ const char *s;
+ double d;
+
+ if (is_printed_opcode(state, cmd))
+ return (0);
+ if ((cmd->len & F_OR) != 0 && state->or_block == 0)
+ bprintf(bp, " {");
+ if (cmd->opcode != O_IN && (cmd->len & F_NOT) != 0)
+ bprintf(bp, " not");
+
+ switch (cmd->opcode) {
+ case O_PROB:
+ d = 1.0 * insntoc(cmd, u32)->d[0] / 0x7fffffff;
+ bprintf(bp, "prob %f ", d);
+ break;
+ case O_PROBE_STATE: /* no need to print anything here */
+ state->flags |= HAVE_PROBE_STATE;
+ break;
+ case O_IP_SRC:
+ case O_IP_SRC_LOOKUP:
+ case O_IP_SRC_MASK:
+ case O_IP_SRC_ME:
+ case O_IP_SRC_SET:
+ if (state->flags & HAVE_SRCIP)
+ bprintf(bp, " src-ip");
+ print_ip(bp, fo, insntoc(cmd, ip));
+ break;
+ case O_IP_DST:
+ case O_IP_DST_MASK:
+ case O_IP_DST_ME:
+ case O_IP_DST_SET:
+ case O_IP_DST_LOOKUP:
+ /*
+ * Special handling for O_IP_DST_LOOKUP when
+ * lookup type is not LOOKUP_NONE.
+ */
+ if ((state->flags & HAVE_DSTIP) != 0 && (
+ cmd->opcode != O_IP_DST_LOOKUP ||
+ IPFW_LOOKUP_TYPE(cmd) == LOOKUP_NONE))
+ bprintf(bp, " dst-ip");
+ print_ip(bp, fo, insntoc(cmd, ip));
+ break;
+ case O_IP6_SRC:
+ case O_IP6_SRC_MASK:
+ case O_IP6_SRC_ME:
+ if (state->flags & HAVE_SRCIP)
+ bprintf(bp, " src-ip6");
+ print_ip6(bp, insntoc(cmd, ip6));
+ break;
+ case O_IP6_DST:
+ case O_IP6_DST_MASK:
+ case O_IP6_DST_ME:
+ if (state->flags & HAVE_DSTIP)
+ bprintf(bp, " dst-ip6");
+ print_ip6(bp, insntoc(cmd, ip6));
+ break;
+ case O_MAC_SRC_LOOKUP:
+ bprintf(bp, " src-mac");
+ print_mac_lookup(bp, fo, cmd);
+ break;
+ case O_MAC_DST_LOOKUP:
+ bprintf(bp, " dst-mac");
+ print_mac_lookup(bp, fo, cmd);
+ break;
+ case O_FLOW6ID:
+ print_flow6id(bp, insntoc(cmd, u32));
+ break;
+ case O_IP_DSTPORT:
+ case O_IP_SRCPORT:
+ print_newports(bp, insntoc(cmd, u16), state->proto,
+ (state->flags & (HAVE_SRCIP | HAVE_DSTIP)) ==
+ (HAVE_SRCIP | HAVE_DSTIP) ? cmd->opcode: 0);
+ break;
+ case O_PROTO:
+ pe = getprotobynumber(cmd->arg1);
+ if (state->flags & HAVE_PROTO)
+ bprintf(bp, " proto");
+ if (pe != NULL)
+ bprintf(bp, " %s", pe->p_name);
+ else
+ bprintf(bp, " %u", cmd->arg1);
+ state->proto = cmd->arg1;
+ break;
+ case O_MACADDR2:
+ print_mac(bp, insntoc(cmd, mac));
+ break;
+ case O_MAC_TYPE:
+ print_newports(bp, insntoc(cmd, u16),
+ IPPROTO_ETHERTYPE, cmd->opcode);
+ break;
+ case O_FRAG:
+ print_flags(bp, "frag", cmd, f_ipoff);
+ break;
+ case O_FIB:
+ bprintf(bp, " fib %u", cmd->arg1);
+ break;
+ case O_SOCKARG:
+ bprintf(bp, " sockarg");
+ break;
+ case O_IN:
+ bprintf(bp, cmd->len & F_NOT ? " out" : " in");
+ break;
+ case O_DIVERTED:
+ switch (cmd->arg1) {
+ case 3:
+ bprintf(bp, " diverted");
+ break;
+ case 2:
+ bprintf(bp, " diverted-output");
+ break;
+ case 1:
+ bprintf(bp, " diverted-loopback");
+ break;
+ default:
+ bprintf(bp, " diverted-?<%u>", cmd->arg1);
+ break;
+ }
+ break;
+ case O_LAYER2:
+ bprintf(bp, " layer2");
+ break;
+ case O_XMIT:
+ case O_RECV:
+ case O_VIA:
+ if (cmd->opcode == O_XMIT)
+ s = "xmit";
+ else if (cmd->opcode == O_RECV)
+ s = "recv";
+ else /* if (cmd->opcode == O_VIA) */
+ s = "via";
+ switch (insntoc(cmd, if)->name[0]) {
+ case '\0':
+ bprintf(bp, " %s %s", s,
+ inet_ntoa(insntoc(cmd, if)->p.ip));
+ break;
+ case '\1':
+ bprintf(bp, " %s table(%s)", s,
+ table_search_ctlv(fo->tstate,
+ insntoc(cmd, if)->p.kidx));
+ break;
+ default:
+ bprintf(bp, " %s %s", s,
+ insntoc(cmd, if)->name);
+ }
+ break;
+ case O_IP_FLOW_LOOKUP:
+ s = table_search_ctlv(fo->tstate,
+ insntoc(cmd, kidx)->kidx);
+ bprintf(bp, " flow table(%s", s);
+ if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn_table))
+ print_tvalue(bp, insntoc(cmd, table));
+ bprintf(bp, ")");
+ break;
+ case O_IPID:
+ case O_IPTTL:
+ case O_IPLEN:
+ case O_TCPDATALEN:
+ case O_TCPMSS:
+ case O_TCPWIN:
+ if (F_LEN(cmd) == 1) {
+ switch (cmd->opcode) {
+ case O_IPID:
+ s = "ipid";
+ break;
+ case O_IPTTL:
+ s = "ipttl";
+ break;
+ case O_IPLEN:
+ s = "iplen";
+ break;
+ case O_TCPDATALEN:
+ s = "tcpdatalen";
+ break;
+ case O_TCPMSS:
+ s = "tcpmss";
+ break;
+ case O_TCPWIN:
+ s = "tcpwin";
+ break;
+ default:
+ s = "<unknown>";
+ break;
+ }
+ bprintf(bp, " %s %u", s, cmd->arg1);
+ } else
+ print_newports(bp, insntoc(cmd, u16), 0,
+ cmd->opcode);
+ break;
+ case O_IPVER:
+ bprintf(bp, " ipver %u", cmd->arg1);
+ break;
+ case O_IPPRECEDENCE:
+ bprintf(bp, " ipprecedence %u", cmd->arg1 >> 5);
+ break;
+ case O_DSCP:
+ print_dscp(bp, insntoc(cmd, u32));
+ break;
+ case O_IPOPT:
+ print_flags(bp, "ipoptions", cmd, f_ipopts);
+ break;
+ case O_IPTOS:
+ print_flags(bp, "iptos", cmd, f_iptos);
+ break;
+ case O_ICMPTYPE:
+ print_icmptypes(bp, insntoc(cmd, u32));
+ break;
+ case O_ESTAB:
+ bprintf(bp, " established");
+ break;
+ case O_TCPFLAGS:
+ print_flags(bp, "tcpflags", cmd, f_tcpflags);
+ break;
+ case O_TCPOPTS:
+ print_flags(bp, "tcpoptions", cmd, f_tcpopts);
+ break;
+ case O_TCPACK:
+ bprintf(bp, " tcpack %d",
+ ntohl(insntoc(cmd, u32)->d[0]));
+ break;
+ case O_TCPSEQ:
+ bprintf(bp, " tcpseq %d",
+ ntohl(insntoc(cmd, u32)->d[0]));
+ break;
+ case O_UID:
+ pwd = getpwuid(insntoc(cmd, u32)->d[0]);
+ if (pwd != NULL)
+ bprintf(bp, " uid %s", pwd->pw_name);
+ else
+ bprintf(bp, " uid %u",
+ insntoc(cmd, u32)->d[0]);
+ break;
+ case O_GID:
+ grp = getgrgid(insntoc(cmd, u32)->d[0]);
+ if (grp != NULL)
+ bprintf(bp, " gid %s", grp->gr_name);
+ else
+ bprintf(bp, " gid %u",
+ insntoc(cmd, u32)->d[0]);
+ break;
+ case O_JAIL:
+ bprintf(bp, " jail %d", insntoc(cmd, u32)->d[0]);
+ break;
+ case O_VERREVPATH:
+ bprintf(bp, " verrevpath");
+ break;
+ case O_VERSRCREACH:
+ bprintf(bp, " versrcreach");
+ break;
+ case O_ANTISPOOF:
+ bprintf(bp, " antispoof");
+ break;
+ case O_IPSEC:
+ bprintf(bp, " ipsec");
+ break;
+ case O_NOP:
+ bprintf(bp, " // %s", (const char *)(cmd + 1));
+ break;
+ case O_KEEP_STATE:
+ if (state->flags & HAVE_PROBE_STATE)
+ bprintf(bp, " keep-state");
+ else
+ bprintf(bp, " record-state");
+ bprintf(bp, " :%s",
+ object_search_ctlv(fo->tstate,
+ insntoc(cmd, kidx)->kidx,
+ IPFW_TLV_STATE_NAME));
+ break;
+ case O_LIMIT:
+ if (state->flags & HAVE_PROBE_STATE)
+ bprintf(bp, " limit");
+ else
+ bprintf(bp, " set-limit");
+ print_limit_mask(bp, insntoc(cmd, limit));
+ bprintf(bp, " :%s",
+ object_search_ctlv(fo->tstate,
+ insntoc(cmd, kidx)->kidx,
+ IPFW_TLV_STATE_NAME));
+ break;
+ case O_IP6:
+ if (state->flags & HAVE_PROTO)
+ bprintf(bp, " proto");
+ bprintf(bp, " ip6");
+ break;
+ case O_IP4:
+ if (state->flags & HAVE_PROTO)
+ bprintf(bp, " proto");
+ bprintf(bp, " ip4");
+ break;
+ case O_ICMP6TYPE:
+ print_icmp6types(bp, insntoc(cmd, u32));
+ break;
+ case O_EXT_HDR:
+ print_ext6hdr(bp, cmd);
+ break;
+ case O_TAGGED:
+ if (F_LEN(cmd) == 1)
+ bprint_uint_arg(bp, " tagged ", cmd->arg1);
+ else
+ print_newports(bp, insntoc(cmd, u16),
+ 0, O_TAGGED);
+ break;
+ case O_SKIP_ACTION:
+ bprintf(bp, " defer-immediate-action");
+ break;
+ case O_MARK:
+ bprintf(bp, " mark");
+ if (cmd->arg1 == IP_FW_TARG)
+ bprintf(bp, " tablearg");
+ else
+ bprintf(bp, " %#x", insntoc(cmd, u32)->d[0]);
+
+ if (insntoc(cmd, u32)->d[1] != 0xFFFFFFFF)
+ bprintf(bp, ":%#x", insntoc(cmd, u32)->d[1]);
+ break;
+
+ default:
+ bprintf(bp, " [opcode %d len %d]", cmd->opcode,
+ cmd->len);
+ }
+ if (cmd->len & F_OR) {
+ bprintf(bp, " or");
+ state->or_block = 1;
+ } else if (state->or_block != 0) {
+ bprintf(bp, " }");
+ state->or_block = 0;
+ }
+ mark_printed(state, cmd);
+
+ return (1);
+}
+
+static ipfw_insn *
+print_opcode(struct buf_pr *bp, struct format_opts *fo,
+ struct show_state *state, int opcode)
+{
+ ipfw_insn *cmd;
+ int l;
+
+ for (l = state->rule->act_ofs, cmd = state->rule->cmd;
+ l > 0; l -= F_LEN(cmd), cmd += F_LEN(cmd)) {
+ /* We use zero opcode to print the rest of options */
+ if (opcode >= 0 && cmd->opcode != opcode)
+ continue;
+ /*
+ * Skip O_NOP, when we printing the rest
+ * of options, it will be handled separately.
+ */
+ if (cmd->opcode == O_NOP && opcode != O_NOP)
+ continue;
+ if (!print_instruction(bp, fo, state, cmd))
+ continue;
+ return (cmd);
+ }
+ return (NULL);
+}
+
+static void
+print_fwd(struct buf_pr *bp, const ipfw_insn *cmd)
+{
+ char buf[INET6_ADDRSTRLEN + IF_NAMESIZE + 2];
+ const ipfw_insn_sa6 *sa6;
+ const ipfw_insn_sa *sa;
+ uint16_t port;
+
+ if (cmd->opcode == O_FORWARD_IP) {
+ sa = insntoc(cmd, sa);
+ port = sa->sa.sin_port;
+ if (sa->sa.sin_addr.s_addr == INADDR_ANY)
+ bprintf(bp, "fwd tablearg");
+ else
+ bprintf(bp, "fwd %s", inet_ntoa(sa->sa.sin_addr));
+ } else {
+ sa6 = insntoc(cmd, sa6);
+ port = sa6->sa.sin6_port;
+ bprintf(bp, "fwd ");
+ if (getnameinfo((const struct sockaddr *)&sa6->sa,
+ sizeof(struct sockaddr_in6), buf, sizeof(buf), NULL, 0,
+ NI_NUMERICHOST) == 0)
+ bprintf(bp, "%s", buf);
+ }
+ if (port != 0)
+ bprintf(bp, ",%u", port);
+}
+
+static void
+print_logdst(struct buf_pr *bp, uint16_t arg1)
+{
+ char const *comma = "";
+
+ bprintf(bp, " logdst ");
+ if (arg1 & IPFW_LOG_SYSLOG) {
+ bprintf(bp, "%ssyslog", comma);
+ comma = ",";
+ }
+ if (arg1 & IPFW_LOG_IPFW0) {
+ bprintf(bp, "%sipfw0", comma);
+ comma = ",";
+ }
+ if (arg1 & IPFW_LOG_RTSOCK) {
+ bprintf(bp, "%srtsock", comma);
+ comma = ",";
+ }
+}
+
+static int
+print_action_instruction(struct buf_pr *bp, const struct format_opts *fo,
+ struct show_state *state, const ipfw_insn *cmd)
+{
+ const char *s;
+
+ if (is_printed_opcode(state, cmd))
+ return (0);
+ switch (cmd->opcode) {
+ case O_CHECK_STATE:
+ bprintf(bp, "check-state");
+ if (insntoc(cmd, kidx)->kidx != 0)
+ s = object_search_ctlv(fo->tstate,
+ insntoc(cmd, kidx)->kidx,
+ IPFW_TLV_STATE_NAME);
+ else
+ s = NULL;
+ bprintf(bp, " :%s", s ? s: "any");
+ break;
+ case O_ACCEPT:
+ bprintf(bp, "allow");
+ break;
+ case O_COUNT:
+ bprintf(bp, "count");
+ break;
+ case O_DENY:
+ bprintf(bp, "deny");
+ break;
+ case O_REJECT:
+ if (cmd->arg1 == ICMP_REJECT_RST)
+ bprintf(bp, "reset");
+ else if (cmd->arg1 == ICMP_REJECT_ABORT)
+ bprintf(bp, "abort");
+ else if (cmd->arg1 == ICMP_UNREACH_HOST)
+ bprintf(bp, "reject");
+ else if (cmd->arg1 == ICMP_UNREACH_NEEDFRAG &&
+ cmd->len == F_INSN_SIZE(ipfw_insn_u16))
+ bprintf(bp, "needfrag %u",
+ insntoc(cmd, u16)->ports[0]);
+ else
+ print_reject_code(bp, cmd->arg1);
+ break;
+ case O_UNREACH6:
+ if (cmd->arg1 == ICMP6_UNREACH_RST)
+ bprintf(bp, "reset6");
+ else if (cmd->arg1 == ICMP6_UNREACH_ABORT)
+ bprintf(bp, "abort6");
+ else
+ print_unreach6_code(bp, cmd->arg1);
+ break;
+ case O_SKIPTO:
+ bprint_uint_arg(bp, "skipto ", insntoc(cmd, u32)->d[0]);
+ break;
+ case O_PIPE:
+ bprint_uint_arg(bp, "pipe ", cmd->arg1);
+ break;
+ case O_QUEUE:
+ bprint_uint_arg(bp, "queue ", cmd->arg1);
+ break;
+ case O_DIVERT:
+ bprint_uint_arg(bp, "divert ", cmd->arg1);
+ break;
+ case O_TEE:
+ bprint_uint_arg(bp, "tee ", cmd->arg1);
+ break;
+ case O_NETGRAPH:
+ bprint_uint_arg(bp, "netgraph ", cmd->arg1);
+ break;
+ case O_NGTEE:
+ bprint_uint_arg(bp, "ngtee ", cmd->arg1);
+ break;
+ case O_FORWARD_IP:
+ case O_FORWARD_IP6:
+ print_fwd(bp, cmd);
+ break;
+ case O_LOG:
+ bprintf(bp, " log");
+ if (insntoc(cmd, log)->max_log > 0)
+ bprintf(bp, " logamount %d",
+ insntoc(cmd, log)->max_log);
+ if (cmd->arg1 != IPFW_LOG_DEFAULT)
+ print_logdst(bp, cmd->arg1);
+ break;
+ case O_ALTQ:
+#ifndef NO_ALTQ
+ print_altq_cmd(bp, insntoc(cmd, altq));
+#endif
+ break;
+ case O_TAG:
+ bprint_uint_arg(bp, cmd->len & F_NOT ? " untag ":
+ " tag ", cmd->arg1);
+ break;
+ case O_NAT:
+ if (cmd->arg1 != IP_FW_NAT44_GLOBAL)
+ bprint_uint_arg(bp, "nat ", cmd->arg1);
+ else
+ bprintf(bp, "nat global");
+ break;
+ case O_SETFIB:
+ if (cmd->arg1 == IP_FW_TARG)
+ bprint_uint_arg(bp, "setfib ", cmd->arg1);
+ else
+ bprintf(bp, "setfib %u", cmd->arg1 & 0x7FFF);
+ break;
+ case O_EXTERNAL_ACTION:
+ /*
+ * The external action can consists of two following
+ * each other opcodes - O_EXTERNAL_ACTION and
+ * O_EXTERNAL_INSTANCE. The first contains the ID of
+ * name of external action. The second contains the ID
+ * of name of external action instance.
+ * NOTE: in case when external action has no named
+ * instances support, the second opcode isn't needed.
+ */
+ state->eaction = insntoc(cmd, kidx);
+ s = object_search_ctlv(fo->tstate,
+ state->eaction->kidx,
+ IPFW_TLV_EACTION);
+ if (match_token(rule_eactions, s) != -1)
+ bprintf(bp, "%s", s);
+ else
+ bprintf(bp, "eaction %s", s);
+ break;
+ case O_EXTERNAL_INSTANCE:
+ if (state->eaction == NULL)
+ break;
+ /*
+ * XXX: we need to teach ipfw(9) to rewrite opcodes
+ * in the user buffer on rule addition. When we add
+ * the rule, we specify zero TLV type for
+ * O_EXTERNAL_INSTANCE object. To show correct
+ * rule after `ipfw add` we need to search instance
+ * name with zero type. But when we do `ipfw show`
+ * we calculate TLV type using IPFW_TLV_EACTION_NAME()
+ * macro.
+ */
+ s = object_search_ctlv(fo->tstate,
+ insntoc(cmd, kidx)->kidx, 0);
+ if (s == NULL)
+ s = object_search_ctlv(fo->tstate,
+ insntoc(cmd, kidx)->kidx, IPFW_TLV_EACTION_NAME(
+ state->eaction->kidx));
+ bprintf(bp, " %s", s);
+ break;
+ case O_EXTERNAL_DATA:
+ if (state->eaction == NULL)
+ break;
+ /*
+ * Currently we support data formatting only for
+ * external data with datalen u16. For unknown data
+ * print its size in bytes.
+ */
+ if (cmd->len == F_INSN_SIZE(ipfw_insn))
+ bprintf(bp, " %u", cmd->arg1);
+ else
+ bprintf(bp, " %ubytes",
+ (unsigned)(cmd->len * sizeof(uint32_t)));
+ break;
+ case O_SETDSCP:
+ if (cmd->arg1 == IP_FW_TARG) {
+ bprintf(bp, "setdscp tablearg");
+ break;
+ }
+ s = match_value(f_ipdscp, cmd->arg1 & 0x3F);
+ if (s != NULL)
+ bprintf(bp, "setdscp %s", s);
+ else
+ bprintf(bp, "setdscp %u", cmd->arg1 & 0x3F);
+ break;
+ case O_REASS:
+ bprintf(bp, "reass");
+ break;
+ case O_CALLRETURN:
+ if (cmd->len & F_NOT) {
+ s = match_value(return_types, cmd->arg1);
+ bprintf(bp, "return %s", s ? s: "<invalid>");
+ } else
+ bprint_uint_arg(bp, "call ", insntoc(cmd, u32)->d[0]);
+ break;
+ case O_SETMARK:
+ if (cmd->arg1 == IP_FW_TARG) {
+ bprintf(bp, "setmark tablearg");
+ break;
+ }
+ bprintf(bp, "setmark %#x", insntoc(cmd, u32)->d[0]);
+ break;
+ default:
+ bprintf(bp, "** unrecognized action %d len %d ",
+ cmd->opcode, cmd->len);
+ }
+ mark_printed(state, cmd);
+
+ return (1);
+}
+
+
+static ipfw_insn *
+print_action(struct buf_pr *bp, struct format_opts *fo,
+ struct show_state *state, uint8_t opcode)
+{
+ ipfw_insn *cmd;
+ int l;
+
+ for (l = state->rule->cmd_len - state->rule->act_ofs,
+ cmd = ACTION_PTR(state->rule); l > 0;
+ l -= F_LEN(cmd), cmd += F_LEN(cmd)) {
+ if (cmd->opcode != opcode)
+ continue;
+ if (!print_action_instruction(bp, fo, state, cmd))
+ continue;
+ return (cmd);
+ }
+ return (NULL);
+}
+
+static void
+print_proto(struct buf_pr *bp, struct format_opts *fo,
+ struct show_state *state)
+{
+ ipfw_insn *cmd;
+ int l, proto, ip4, ip6;
+
+ /* Count all O_PROTO, O_IP4, O_IP6 instructions. */
+ proto = ip4 = ip6 = 0;
+ for (l = state->rule->act_ofs, cmd = state->rule->cmd;
+ l > 0; l -= F_LEN(cmd), cmd += F_LEN(cmd)) {
+ switch (cmd->opcode) {
+ case O_PROTO:
+ proto++;
+ break;
+ case O_IP4:
+ ip4 = 1;
+ if (cmd->len & F_OR)
+ ip4++;
+ break;
+ case O_IP6:
+ ip6 = 1;
+ if (cmd->len & F_OR)
+ ip6++;
+ break;
+ default:
+ continue;
+ }
+ }
+ if (proto == 0 && ip4 == 0 && ip6 == 0) {
+ state->proto = IPPROTO_IP;
+ state->flags |= HAVE_PROTO;
+ bprintf(bp, " ip");
+ return;
+ }
+ /* To handle the case { ip4 or ip6 }, print opcode with F_OR first */
+ cmd = NULL;
+ if (ip4 || ip6)
+ cmd = print_opcode(bp, fo, state, ip4 > ip6 ? O_IP4: O_IP6);
+ if (cmd != NULL && (cmd->len & F_OR))
+ cmd = print_opcode(bp, fo, state, ip4 > ip6 ? O_IP6: O_IP4);
+ if (cmd == NULL || (cmd->len & F_OR))
+ for (l = proto; l > 0; l--) {
+ cmd = print_opcode(bp, fo, state, O_PROTO);
+ if (cmd == NULL || (cmd->len & F_OR) == 0)
+ break;
+ }
+ /* Initialize proto, it is used by print_newports() */
+ state->flags |= HAVE_PROTO;
+ if (state->proto == 0 && ip6 != 0)
+ state->proto = IPPROTO_IPV6;
+}
+
+static int
+match_opcode(int opcode, const int opcodes[], size_t nops)
+{
+ size_t i;
+
+ for (i = 0; i < nops; i++)
+ if (opcode == opcodes[i])
+ return (1);
+ return (0);
+}
+
+static void
+print_address(struct buf_pr *bp, struct format_opts *fo,
+ struct show_state *state, const int opcodes[], size_t nops, int portop,
+ int flag)
+{
+ ipfw_insn *cmd;
+ int count, l, portcnt, pf;
+
+ count = portcnt = 0;
+ for (l = state->rule->act_ofs, cmd = state->rule->cmd;
+ l > 0; l -= F_LEN(cmd), cmd += F_LEN(cmd)) {
+ if (match_opcode(cmd->opcode, opcodes, nops)) {
+ /*
+ * Special handling for O_IP_DST_LOOKUP when
+ * lookup type is not LOOKUP_NONE.
+ */
+ if (cmd->opcode == O_IP_DST_LOOKUP &&
+ IPFW_LOOKUP_TYPE(cmd) != LOOKUP_NONE)
+ continue;
+ count++;
+ } else if (cmd->opcode == portop)
+ portcnt++;
+ }
+ if (count == 0)
+ bprintf(bp, " any");
+ for (l = state->rule->act_ofs, cmd = state->rule->cmd;
+ l > 0 && count > 0; l -= F_LEN(cmd), cmd += F_LEN(cmd)) {
+ if (!match_opcode(cmd->opcode, opcodes, nops))
+ continue;
+ print_instruction(bp, fo, state, cmd);
+ if ((cmd->len & F_OR) == 0)
+ break;
+ count--;
+ }
+ /*
+ * If several O_IP_?PORT opcodes specified, leave them to the
+ * options section.
+ */
+ if (portcnt == 1) {
+ for (l = state->rule->act_ofs, cmd = state->rule->cmd, pf = 0;
+ l > 0; l -= F_LEN(cmd), cmd += F_LEN(cmd)) {
+ if (cmd->opcode != portop) {
+ pf = (cmd->len & F_OR);
+ continue;
+ }
+ /* Print opcode iff it is not in OR block. */
+ if (pf == 0 && (cmd->len & F_OR) == 0)
+ print_instruction(bp, fo, state, cmd);
+ break;
+ }
+ }
+ state->flags |= flag;
+}
+
+static const int action_opcodes[] = {
+ O_CHECK_STATE, O_ACCEPT, O_COUNT, O_DENY, O_REJECT,
+ O_UNREACH6, O_SKIPTO, O_PIPE, O_QUEUE, O_DIVERT, O_TEE,
+ O_NETGRAPH, O_NGTEE, O_FORWARD_IP, O_FORWARD_IP6, O_NAT,
+ O_SETFIB, O_SETDSCP, O_REASS, O_CALLRETURN, O_SETMARK,
+ /* keep the following opcodes at the end of the list */
+ O_EXTERNAL_ACTION, O_EXTERNAL_INSTANCE, O_EXTERNAL_DATA
+};
+
+static const int modifier_opcodes[] = {
+ O_LOG, O_ALTQ, O_TAG
+};
+
+static const int src_opcodes[] = {
+ O_IP_SRC, O_IP_SRC_LOOKUP, O_IP_SRC_MASK, O_IP_SRC_ME,
+ O_IP_SRC_SET, O_IP6_SRC, O_IP6_SRC_MASK, O_IP6_SRC_ME
+};
+
+static const int dst_opcodes[] = {
+ O_IP_DST, O_IP_DST_LOOKUP, O_IP_DST_MASK, O_IP_DST_ME,
+ O_IP_DST_SET, O_IP6_DST, O_IP6_DST_MASK, O_IP6_DST_ME
+};
+
+#if IPFW_DEFAULT_RULE > 65535
+#define RULENUM_FORMAT "%06d"
+#else
+#define RULENUM_FORMAT "%05d"
+#endif
+
+static void
+show_static_rule(struct cmdline_opts *co, struct format_opts *fo,
+ struct buf_pr *bp, struct ip_fw_rule *rule, struct ip_fw_bcounter *cntr)
+{
+ static int twidth = 0;
+ struct show_state state;
+ ipfw_insn *cmd;
+ size_t i;
+
+ /* Print # DISABLED or skip the rule */
+ if ((fo->set_mask & (1 << rule->set)) == 0) {
+ /* disabled mask */
+ if (!co->show_sets)
+ return;
+ else
+ bprintf(bp, "# DISABLED ");
+ }
+ if (init_show_state(&state, rule) != 0) {
+ warn("init_show_state() failed");
+ return;
+ }
+
+ bprintf(bp, RULENUM_FORMAT " ", rule->rulenum);
+
+ /* only if counters are available */
+ if (cntr != NULL) {
+ /* Print counters if enabled */
+ if (fo->pcwidth > 0 || fo->bcwidth > 0) {
+ pr_u64(bp, &cntr->pcnt, fo->pcwidth);
+ pr_u64(bp, &cntr->bcnt, fo->bcwidth);
+ }
+
+ /* Print timestamp */
+ if (co->do_time == TIMESTAMP_NUMERIC)
+ bprintf(bp, "%10u ", cntr->timestamp);
+ else if (co->do_time == TIMESTAMP_STRING) {
+ char timestr[30];
+ time_t t = (time_t)0;
+
+ if (twidth == 0) {
+ strcpy(timestr, ctime(&t));
+ *strchr(timestr, '\n') = '\0';
+ twidth = strlen(timestr);
+ }
+ if (cntr->timestamp > 0) {
+ t = _long_to_time(cntr->timestamp);
+
+ strcpy(timestr, ctime(&t));
+ *strchr(timestr, '\n') = '\0';
+ bprintf(bp, "%s ", timestr);
+ } else {
+ bprintf(bp, "%*s ", twidth, "");
+ }
+ }
+ }
+
+ /* Print set number */
+ if (co->show_sets)
+ bprintf(bp, "set %d ", rule->set);
+
+ /* Print the optional "match probability" */
+ cmd = print_opcode(bp, fo, &state, O_PROB);
+ /* Print rule action */
+ for (i = 0; i < nitems(action_opcodes); i++) {
+ cmd = print_action(bp, fo, &state, action_opcodes[i]);
+ if (cmd == NULL)
+ continue;
+ /* Handle special cases */
+ switch (cmd->opcode) {
+ case O_CHECK_STATE:
+ goto end;
+ case O_EXTERNAL_ACTION:
+ case O_EXTERNAL_INSTANCE:
+ /* External action can have several instructions */
+ continue;
+ }
+ break;
+ }
+ /* Print rule modifiers */
+ for (i = 0; i < nitems(modifier_opcodes); i++)
+ print_action(bp, fo, &state, modifier_opcodes[i]);
+ /*
+ * Print rule body
+ */
+ if (co->comment_only != 0)
+ goto end;
+
+ if (rule->flags & IPFW_RULE_JUSTOPTS) {
+ state.flags |= HAVE_PROTO | HAVE_SRCIP | HAVE_DSTIP;
+ /*
+ * Print `proto ip` if all opcodes has been already printed.
+ */
+ if (memchr(state.printed, 0, rule->act_ofs) == NULL) {
+ bprintf(bp, " proto ip");
+ goto end;
+ }
+ goto justopts;
+ }
+
+ print_proto(bp, fo, &state);
+ if (co->do_compact != 0 && (rule->flags & IPFW_RULE_NOOPT))
+ goto justopts;
+
+ /* Print source */
+ bprintf(bp, " from");
+ print_address(bp, fo, &state, src_opcodes, nitems(src_opcodes),
+ O_IP_SRCPORT, HAVE_SRCIP);
+
+ /* Print destination */
+ bprintf(bp, " to");
+ print_address(bp, fo, &state, dst_opcodes, nitems(dst_opcodes),
+ O_IP_DSTPORT, HAVE_DSTIP);
+
+justopts:
+ /* Print the rest of options */
+ while (print_opcode(bp, fo, &state, -1))
+ ;
+end:
+ /* Print comment at the end */
+ cmd = print_opcode(bp, fo, &state, O_NOP);
+ if (co->comment_only != 0 && cmd == NULL)
+ bprintf(bp, " // ...");
+ bprintf(bp, "\n");
+ free_show_state(&state);
+}
+
+static void
+show_dyn_state(struct cmdline_opts *co, struct format_opts *fo,
+ struct buf_pr *bp, ipfw_dyn_rule *d)
+{
+ struct protoent *pe;
+ struct in_addr a;
+ char buf[INET6_ADDRSTRLEN];
+
+ if (!d->expire && !(d->type == O_LIMIT_PARENT))
+ return;
+
+ bprintf(bp, RULENUM_FORMAT, d->rulenum);
+ if (fo->pcwidth > 0 || fo->bcwidth > 0) {
+ bprintf(bp, " ");
+ pr_u64(bp, &d->pcnt, fo->pcwidth);
+ pr_u64(bp, &d->bcnt, fo->bcwidth);
+ bprintf(bp, "(%ds)", d->expire);
+ }
+ switch (d->type) {
+ case O_LIMIT_PARENT:
+ bprintf(bp, " PARENT %u", d->count);
+ break;
+ case O_LIMIT:
+ bprintf(bp, " LIMIT");
+ break;
+ case O_KEEP_STATE: /* bidir, no mask */
+ bprintf(bp, " STATE");
+ break;
+ }
+
+ if ((pe = getprotobynumber(d->id.proto)) != NULL)
+ bprintf(bp, " %s", pe->p_name);
+ else
+ bprintf(bp, " proto %u", d->id.proto);
+
+ if (d->id.addr_type == 4) {
+ a.s_addr = htonl(d->id.src_ip);
+ bprintf(bp, " %s %d", inet_ntoa(a), d->id.src_port);
+
+ a.s_addr = htonl(d->id.dst_ip);
+ bprintf(bp, " <-> %s %d", inet_ntoa(a), d->id.dst_port);
+ } else if (d->id.addr_type == 6) {
+ bprintf(bp, " %s %d", inet_ntop(AF_INET6, &d->id.src_ip6, buf,
+ sizeof(buf)), d->id.src_port);
+ bprintf(bp, " <-> %s %d", inet_ntop(AF_INET6, &d->id.dst_ip6,
+ buf, sizeof(buf)), d->id.dst_port);
+ } else
+ bprintf(bp, " UNKNOWN <-> UNKNOWN");
+ if (d->kidx != 0)
+ bprintf(bp, " :%s", object_search_ctlv(fo->tstate,
+ d->kidx, IPFW_TLV_STATE_NAME));
+
+#define BOTH_SYN (TH_SYN | (TH_SYN << 8))
+#define BOTH_FIN (TH_FIN | (TH_FIN << 8))
+ if (co->verbose) {
+ bprintf(bp, " state 0x%08x%s", d->state,
+ d->state ? " ": ",");
+ if (d->state & IPFW_DYN_ORPHANED)
+ bprintf(bp, "ORPHANED,");
+ if ((d->state & BOTH_SYN) == BOTH_SYN)
+ bprintf(bp, "BOTH_SYN,");
+ else {
+ if (d->state & TH_SYN)
+ bprintf(bp, "F_SYN,");
+ if (d->state & (TH_SYN << 8))
+ bprintf(bp, "R_SYN,");
+ }
+ if ((d->state & BOTH_FIN) == BOTH_FIN)
+ bprintf(bp, "BOTH_FIN,");
+ else {
+ if (d->state & TH_FIN)
+ bprintf(bp, "F_FIN,");
+ if (d->state & (TH_FIN << 8))
+ bprintf(bp, "R_FIN,");
+ }
+ bprintf(bp, " f_ack 0x%x, r_ack 0x%x", d->ack_fwd,
+ d->ack_rev);
+ }
+}
+
+static int
+do_range_cmd(int cmd, ipfw_range_tlv *rt)
+{
+ ipfw_range_header rh;
+ size_t sz;
+
+ memset(&rh, 0, sizeof(rh));
+ memcpy(&rh.range, rt, sizeof(*rt));
+ rh.range.head.length = sizeof(*rt);
+ rh.range.head.type = IPFW_TLV_RANGE;
+ sz = sizeof(rh);
+
+ if (do_get3(cmd, &rh.opheader, &sz) != 0)
+ return (-1);
+ /* Save number of matched objects */
+ rt->new_set = rh.range.new_set;
+ return (0);
+}
+
+/*
+ * This one handles all set-related commands
+ * ipfw set { show | enable | disable }
+ * ipfw set swap X Y
+ * ipfw set move X to Y
+ * ipfw set move rule X to Y
+ */
+void
+ipfw_sets_handler(char *av[])
+{
+ ipfw_range_tlv rt;
+ const char *msg;
+ size_t size;
+ uint32_t masks[2], rulenum;
+ int i;
+ uint8_t cmd;
+
+ av++;
+ memset(&rt, 0, sizeof(rt));
+
+ if (av[0] == NULL)
+ errx(EX_USAGE, "set needs command");
+ if (_substrcmp(*av, "show") == 0) {
+ struct format_opts fo;
+ ipfw_cfg_lheader *cfg;
+
+ memset(&fo, 0, sizeof(fo));
+ if (ipfw_get_config(&g_co, &fo, &cfg, &size) != 0)
+ err(EX_OSERR, "requesting config failed");
+
+ for (i = 0, msg = "disable"; i < RESVD_SET; i++)
+ if ((cfg->set_mask & (1<<i)) == 0) {
+ printf("%s %d", msg, i);
+ msg = "";
+ }
+ msg = (cfg->set_mask != (uint32_t)-1) ? " enable" : "enable";
+ for (i = 0; i < RESVD_SET; i++)
+ if ((cfg->set_mask & (1<<i)) != 0) {
+ printf("%s %d", msg, i);
+ msg = "";
+ }
+ printf("\n");
+ free(cfg);
+ } else if (_substrcmp(*av, "swap") == 0) {
+ av++;
+ if ( av[0] == NULL || av[1] == NULL )
+ errx(EX_USAGE, "set swap needs 2 set numbers\n");
+ rt.set = atoi(av[0]);
+ rt.new_set = atoi(av[1]);
+ if (!isdigit(*(av[0])) || rt.set > RESVD_SET)
+ errx(EX_DATAERR, "invalid set number %s\n", av[0]);
+ if (!isdigit(*(av[1])) || rt.new_set > RESVD_SET)
+ errx(EX_DATAERR, "invalid set number %s\n", av[1]);
+ i = do_range_cmd(IP_FW_SET_SWAP, &rt);
+ } else if (_substrcmp(*av, "move") == 0) {
+ av++;
+ if (av[0] && _substrcmp(*av, "rule") == 0) {
+ rt.flags = IPFW_RCFLAG_RANGE; /* move rules to new set */
+ cmd = IP_FW_XMOVE;
+ av++;
+ } else
+ cmd = IP_FW_SET_MOVE; /* Move set to new one */
+ if (av[0] == NULL || av[1] == NULL || av[2] == NULL ||
+ av[3] != NULL || _substrcmp(av[1], "to") != 0)
+ errx(EX_USAGE, "syntax: set move [rule] X to Y\n");
+ rulenum = (uint32_t)strtoul(av[0], NULL, 10);
+ rt.new_set = atoi(av[2]);
+ if (cmd == IP_FW_XMOVE) {
+ rt.start_rule = rulenum;
+ rt.end_rule = rulenum;
+ } else
+ rt.set = rulenum;
+ rt.new_set = atoi(av[2]);
+ if (!isdigit(*(av[0])) || (cmd == 3 && rt.set > RESVD_SET) ||
+ (cmd == 2 && rt.start_rule == IPFW_DEFAULT_RULE) )
+ errx(EX_DATAERR, "invalid source number %s\n", av[0]);
+ if (!isdigit(*(av[2])) || rt.new_set > RESVD_SET)
+ errx(EX_DATAERR, "invalid dest. set %s\n", av[1]);
+ i = do_range_cmd(cmd, &rt);
+ if (i < 0)
+ err(EX_OSERR, "failed to move %s",
+ cmd == IP_FW_SET_MOVE ? "set": "rule");
+ } else if (_substrcmp(*av, "disable") == 0 ||
+ _substrcmp(*av, "enable") == 0 ) {
+ int which = _substrcmp(*av, "enable") == 0 ? 1 : 0;
+
+ av++;
+ masks[0] = masks[1] = 0;
+
+ while (av[0]) {
+ if (isdigit(**av)) {
+ i = atoi(*av);
+ if (i < 0 || i > RESVD_SET)
+ errx(EX_DATAERR,
+ "invalid set number %d\n", i);
+ masks[which] |= (1<<i);
+ } else if (_substrcmp(*av, "disable") == 0)
+ which = 0;
+ else if (_substrcmp(*av, "enable") == 0)
+ which = 1;
+ else
+ errx(EX_DATAERR,
+ "invalid set command %s\n", *av);
+ av++;
+ }
+ if ( (masks[0] & masks[1]) != 0 )
+ errx(EX_DATAERR,
+ "cannot enable and disable the same set\n");
+
+ rt.set = masks[0];
+ rt.new_set = masks[1];
+ i = do_range_cmd(IP_FW_SET_ENABLE, &rt);
+ if (i)
+ warn("set enable/disable: setsockopt(IP_FW_SET_ENABLE)");
+ } else
+ errx(EX_USAGE, "invalid set command %s\n", *av);
+}
+
+static void
+manage_skipto_cache(int op)
+{
+ ipfw_cmd_header req;
+
+ memset(&req, 0, sizeof(req));
+ req.size = sizeof(req);
+ req.cmd = op ? SKIPTO_CACHE_ENABLE : SKIPTO_CACHE_DISABLE;
+
+ do_set3(IP_FW_SKIPTO_CACHE, &req.opheader, sizeof(req));
+}
+
+void
+ipfw_sysctl_handler(char *av[], int which)
+{
+ av++;
+
+ if (av[0] == NULL) {
+ warnx("missing keyword to enable/disable\n");
+ } else if (_substrcmp(*av, "firewall") == 0) {
+ sysctlbyname("net.inet.ip.fw.enable", NULL, 0,
+ &which, sizeof(which));
+ sysctlbyname("net.inet6.ip6.fw.enable", NULL, 0,
+ &which, sizeof(which));
+ } else if (_substrcmp(*av, "one_pass") == 0) {
+ sysctlbyname("net.inet.ip.fw.one_pass", NULL, 0,
+ &which, sizeof(which));
+ } else if (_substrcmp(*av, "debug") == 0) {
+ sysctlbyname("net.inet.ip.fw.debug", NULL, 0,
+ &which, sizeof(which));
+ } else if (_substrcmp(*av, "verbose") == 0) {
+ sysctlbyname("net.inet.ip.fw.verbose", NULL, 0,
+ &which, sizeof(which));
+ } else if (_substrcmp(*av, "dyn_keepalive") == 0) {
+ sysctlbyname("net.inet.ip.fw.dyn_keepalive", NULL, 0,
+ &which, sizeof(which));
+ } else if (_substrcmp(*av, "skipto_cache") == 0) {
+ manage_skipto_cache(which);
+#ifndef NO_ALTQ
+ } else if (_substrcmp(*av, "altq") == 0) {
+ altq_set_enabled(which);
+#endif
+ } else {
+ warnx("unrecognize enable/disable keyword: %s\n", *av);
+ }
+}
+
+typedef void state_cb(struct cmdline_opts *co, struct format_opts *fo,
+ void *arg, void *state);
+
+static void
+prepare_format_dyn(struct cmdline_opts *co, struct format_opts *fo,
+ void *arg __unused, void *_state)
+{
+ ipfw_dyn_rule *d;
+ int width;
+
+ d = (ipfw_dyn_rule *)_state;
+ /* Count _ALL_ states */
+ fo->dcnt++;
+
+ if (fo->show_counters == 0)
+ return;
+
+ /* skip states from another set */
+ if (co->use_set != 0 && d->set != co->use_set - 1)
+ return;
+
+ width = pr_u64(NULL, &d->pcnt, 0);
+ if (width > fo->pcwidth)
+ fo->pcwidth = width;
+
+ width = pr_u64(NULL, &d->bcnt, 0);
+ if (width > fo->bcwidth)
+ fo->bcwidth = width;
+}
+
+static int
+foreach_state(struct cmdline_opts *co, struct format_opts *fo,
+ caddr_t base, size_t sz, state_cb dyn_bc, void *dyn_arg)
+{
+ int ttype;
+ state_cb *fptr;
+ void *farg;
+ ipfw_obj_tlv *tlv;
+ ipfw_obj_ctlv *ctlv;
+
+ fptr = NULL;
+ ttype = 0;
+
+ while (sz > 0) {
+ ctlv = (ipfw_obj_ctlv *)base;
+ switch (ctlv->head.type) {
+ case IPFW_TLV_DYNSTATE_LIST:
+ base += sizeof(*ctlv);
+ sz -= sizeof(*ctlv);
+ ttype = IPFW_TLV_DYN_ENT;
+ fptr = dyn_bc;
+ farg = dyn_arg;
+ break;
+ default:
+ return (sz);
+ }
+
+ while (sz > 0) {
+ tlv = (ipfw_obj_tlv *)base;
+ if (tlv->type != ttype)
+ break;
+
+ fptr(co, fo, farg, tlv + 1);
+ sz -= tlv->length;
+ base += tlv->length;
+ }
+ }
+
+ return (sz);
+}
+
+static void
+prepare_format_opts(struct cmdline_opts *co, struct format_opts *fo,
+ ipfw_obj_tlv *rtlv, int rcnt, caddr_t dynbase, size_t dynsz)
+{
+ int bcwidth, pcwidth, width;
+ int n;
+ struct ip_fw_bcounter *cntr;
+ struct ip_fw_rule *r;
+
+ bcwidth = 0;
+ pcwidth = 0;
+ if (fo->show_counters != 0) {
+ for (n = 0; n < rcnt; n++,
+ rtlv = (ipfw_obj_tlv *)((caddr_t)rtlv + rtlv->length)) {
+ cntr = (struct ip_fw_bcounter *)(rtlv + 1);
+ r = (struct ip_fw_rule *)((caddr_t)cntr + cntr->size);
+ /* skip rules from another set */
+ if (co->use_set && r->set != co->use_set - 1)
+ continue;
+
+ /* packet counter */
+ width = pr_u64(NULL, &cntr->pcnt, 0);
+ if (width > pcwidth)
+ pcwidth = width;
+
+ /* byte counter */
+ width = pr_u64(NULL, &cntr->bcnt, 0);
+ if (width > bcwidth)
+ bcwidth = width;
+ }
+ }
+ fo->bcwidth = bcwidth;
+ fo->pcwidth = pcwidth;
+
+ fo->dcnt = 0;
+ if (co->do_dynamic && dynsz > 0)
+ foreach_state(co, fo, dynbase, dynsz, prepare_format_dyn, NULL);
+}
+
+static int
+list_static_range(struct cmdline_opts *co, struct format_opts *fo,
+ struct buf_pr *bp, ipfw_obj_tlv *rtlv, int rcnt)
+{
+ int n, seen;
+ struct ip_fw_rule *r;
+ struct ip_fw_bcounter *cntr;
+
+ for (n = seen = 0; n < rcnt; n++,
+ rtlv = (ipfw_obj_tlv *)((caddr_t)rtlv + rtlv->length)) {
+
+ if ((fo->show_counters | fo->show_time) != 0) {
+ cntr = (struct ip_fw_bcounter *)(rtlv + 1);
+ r = (struct ip_fw_rule *)((caddr_t)cntr + cntr->size);
+ } else {
+ cntr = NULL;
+ r = (struct ip_fw_rule *)(rtlv + 1);
+ }
+ if (r->rulenum > fo->last)
+ break;
+ if (co->use_set && r->set != co->use_set - 1)
+ continue;
+ if (r->rulenum >= fo->first && r->rulenum <= fo->last) {
+ show_static_rule(co, fo, bp, r, cntr);
+ printf("%s", bp->buf);
+ bp_flush(bp);
+ seen++;
+ }
+ }
+
+ return (seen);
+}
+
+static void
+list_dyn_state(struct cmdline_opts *co, struct format_opts *fo,
+ void *_arg, void *_state)
+{
+ ipfw_dyn_rule *d;
+ struct buf_pr *bp;
+
+ d = (ipfw_dyn_rule *)_state;
+ bp = (struct buf_pr *)_arg;
+
+ if (d->rulenum > fo->last)
+ return;
+ if (co->use_set != 0 && d->set != co->use_set - 1)
+ return;
+ if (d->rulenum >= fo->first) {
+ show_dyn_state(co, fo, bp, d);
+ printf("%s\n", bp->buf);
+ bp_flush(bp);
+ }
+}
+
+static int
+list_dyn_range(struct cmdline_opts *co, struct format_opts *fo,
+ struct buf_pr *bp, caddr_t base, size_t sz)
+{
+
+ sz = foreach_state(co, fo, base, sz, list_dyn_state, bp);
+ return (sz);
+}
+
+void
+ipfw_list(int ac, char *av[], int show_counters)
+{
+ ipfw_cfg_lheader *cfg;
+ struct format_opts sfo;
+ size_t sz;
+ int error;
+ int lac;
+ char **lav;
+ uint32_t rnum;
+ char *endptr;
+
+ if (g_co.test_only) {
+ fprintf(stderr, "Testing only, list disabled\n");
+ return;
+ }
+ if (g_co.do_pipe) {
+ dummynet_list(ac, av, show_counters);
+ return;
+ }
+
+ ac--;
+ av++;
+ memset(&sfo, 0, sizeof(sfo));
+
+ /* Determine rule range to request */
+ if (ac > 0) {
+ for (lac = ac, lav = av; lac != 0; lac--) {
+ rnum = strtoul(*lav++, &endptr, 10);
+ if (sfo.first == 0 || rnum < sfo.first)
+ sfo.first = rnum;
+
+ if (*endptr == '-')
+ rnum = strtoul(endptr + 1, &endptr, 10);
+ if (sfo.last == 0 || rnum > sfo.last)
+ sfo.last = rnum;
+ }
+ }
+
+ /* get configuration from kernel */
+ cfg = NULL;
+ sfo.show_counters = show_counters;
+ sfo.show_time = g_co.do_time;
+ if (g_co.do_dynamic != 2)
+ sfo.flags |= IPFW_CFG_GET_STATIC;
+ if (g_co.do_dynamic != 0)
+ sfo.flags |= IPFW_CFG_GET_STATES;
+ if ((sfo.show_counters | sfo.show_time) != 0)
+ sfo.flags |= IPFW_CFG_GET_COUNTERS;
+ if (ipfw_get_config(&g_co, &sfo, &cfg, &sz) != 0)
+ err(EX_OSERR, "retrieving config failed");
+
+ error = ipfw_show_config(&g_co, &sfo, cfg, sz, ac, av);
+
+ free(cfg);
+
+ if (error != EX_OK)
+ exit(error);
+}
+
+static int
+ipfw_show_config(struct cmdline_opts *co, struct format_opts *fo,
+ ipfw_cfg_lheader *cfg, size_t sz, int ac, char *av[])
+{
+ caddr_t dynbase;
+ size_t dynsz;
+ int rcnt;
+ int exitval = EX_OK;
+ int lac;
+ char **lav;
+ char *endptr;
+ size_t readsz;
+ struct buf_pr bp;
+ ipfw_obj_ctlv *ctlv;
+ ipfw_obj_tlv *rbase;
+
+ /*
+ * Handle tablenames TLV first, if any
+ */
+ rbase = NULL;
+ dynbase = NULL;
+ dynsz = 0;
+ readsz = sizeof(*cfg);
+ rcnt = 0;
+
+ fo->set_mask = cfg->set_mask;
+
+ ctlv = (ipfw_obj_ctlv *)(cfg + 1);
+ if (ctlv->head.type == IPFW_TLV_TBLNAME_LIST) {
+ object_sort_ctlv(ctlv);
+ fo->tstate = ctlv;
+ readsz += ctlv->head.length;
+ ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv + ctlv->head.length);
+ }
+
+ if (cfg->flags & IPFW_CFG_GET_STATIC) {
+ /* We've requested static rules */
+ if (ctlv->head.type == IPFW_TLV_RULE_LIST) {
+ rbase = (ipfw_obj_tlv *)(ctlv + 1);
+ rcnt = ctlv->count;
+ readsz += ctlv->head.length;
+ ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv +
+ ctlv->head.length);
+ }
+ }
+
+ if ((cfg->flags & IPFW_CFG_GET_STATES) && (readsz != sz)) {
+ /* We may have some dynamic states */
+ dynsz = sz - readsz;
+ /* Skip empty header */
+ if (dynsz != sizeof(ipfw_obj_ctlv))
+ dynbase = (caddr_t)ctlv;
+ else
+ dynsz = 0;
+ }
+
+ prepare_format_opts(co, fo, rbase, rcnt, dynbase, dynsz);
+ bp_alloc(&bp, 4096);
+
+ /* if no rule numbers were specified, list all rules */
+ if (ac == 0) {
+ fo->first = 0;
+ fo->last = IPFW_DEFAULT_RULE;
+ if (cfg->flags & IPFW_CFG_GET_STATIC)
+ list_static_range(co, fo, &bp, rbase, rcnt);
+
+ if (co->do_dynamic && dynsz > 0) {
+ printf("## Dynamic rules (%d %zu):\n", fo->dcnt,
+ dynsz);
+ list_dyn_range(co, fo, &bp, dynbase, dynsz);
+ }
+
+ bp_free(&bp);
+ return (EX_OK);
+ }
+
+ /* display specific rules requested on command line */
+ for (lac = ac, lav = av; lac != 0; lac--) {
+ /* convert command line rule # */
+ fo->last = fo->first = strtoul(*lav++, &endptr, 10);
+ if (*endptr == '-')
+ fo->last = strtoul(endptr + 1, &endptr, 10);
+ if (*endptr) {
+ exitval = EX_USAGE;
+ warnx("invalid rule number: %s", *(lav - 1));
+ continue;
+ }
+
+ if ((cfg->flags & IPFW_CFG_GET_STATIC) == 0)
+ continue;
+
+ if (list_static_range(co, fo, &bp, rbase, rcnt) == 0) {
+ /* give precedence to other error(s) */
+ if (exitval == EX_OK)
+ exitval = EX_UNAVAILABLE;
+ if (fo->first == fo->last)
+ warnx("rule %u does not exist", fo->first);
+ else
+ warnx("no rules in range %u-%u",
+ fo->first, fo->last);
+ }
+ }
+
+ if (co->do_dynamic && dynsz > 0) {
+ printf("## Dynamic rules:\n");
+ for (lac = ac, lav = av; lac != 0; lac--) {
+ fo->last = fo->first = strtoul(*lav++, &endptr, 10);
+ if (*endptr == '-')
+ fo->last = strtoul(endptr+1, &endptr, 10);
+ if (*endptr)
+ /* already warned */
+ continue;
+ list_dyn_range(co, fo, &bp, dynbase, dynsz);
+ }
+ }
+
+ bp_free(&bp);
+ return (exitval);
+}
+
+
+/*
+ * Retrieves current ipfw configuration of given type
+ * and stores its pointer to @pcfg.
+ *
+ * Caller is responsible for freeing @pcfg.
+ *
+ * Returns 0 on success.
+ */
+
+static int
+ipfw_get_config(struct cmdline_opts *co, struct format_opts *fo,
+ ipfw_cfg_lheader **pcfg, size_t *psize)
+{
+ ipfw_cfg_lheader *cfg;
+ size_t sz;
+ int i;
+
+
+ if (co->test_only != 0) {
+ fprintf(stderr, "Testing only, list disabled\n");
+ return (0);
+ }
+
+ /* Start with some data size */
+ sz = 4096;
+ cfg = NULL;
+
+ for (i = 0; i < 16; i++) {
+ if (cfg != NULL)
+ free(cfg);
+ if ((cfg = calloc(1, sz)) == NULL)
+ return (ENOMEM);
+
+ cfg->flags = fo->flags;
+ cfg->start_rule = fo->first;
+ cfg->end_rule = fo->last;
+
+ if (do_get3(IP_FW_XGET, &cfg->opheader, &sz) != 0) {
+ if (errno != ENOMEM) {
+ free(cfg);
+ return (errno);
+ }
+
+ /* Buffer size is not enough. Try to increase */
+ sz = sz * 2;
+ if (sz < cfg->size)
+ sz = cfg->size;
+ continue;
+ }
+
+ *pcfg = cfg;
+ *psize = sz;
+ return (0);
+ }
+
+ free(cfg);
+ return (ENOMEM);
+}
+
+static int
+lookup_host (char *host, struct in_addr *ipaddr)
+{
+ struct hostent *he;
+
+ if (!inet_aton(host, ipaddr)) {
+ if ((he = gethostbyname(host)) == NULL)
+ return(-1);
+ *ipaddr = *(struct in_addr *)he->h_addr_list[0];
+ }
+ return(0);
+}
+
+struct tidx {
+ ipfw_obj_ntlv *idx;
+ uint32_t count;
+ uint32_t size;
+ uint16_t counter;
+ uint8_t set;
+};
+
+int
+ipfw_check_object_name(const char *name)
+{
+ int c, i, l;
+
+ /*
+ * Check that name is null-terminated and contains
+ * valid symbols only. Valid mask is:
+ * [a-zA-Z0-9\-_\.]{1,63}
+ */
+ l = strlen(name);
+ if (l == 0 || l >= 64)
+ return (EINVAL);
+ for (i = 0; i < l; i++) {
+ c = name[i];
+ if (isalpha(c) || isdigit(c) || c == '_' ||
+ c == '-' || c == '.')
+ continue;
+ return (EINVAL);
+ }
+ return (0);
+}
+
+static const char *default_state_name = "default";
+
+static int
+state_check_name(const char *name)
+{
+
+ if (ipfw_check_object_name(name) != 0)
+ return (EINVAL);
+ if (strcmp(name, "any") == 0)
+ return (EINVAL);
+ return (0);
+}
+
+static int
+eaction_check_name(const char *name)
+{
+
+ if (ipfw_check_object_name(name) != 0)
+ return (EINVAL);
+ /* Restrict some 'special' names */
+ if (match_token(rule_actions, name) != -1 &&
+ match_token(rule_action_params, name) != -1)
+ return (EINVAL);
+ return (0);
+}
+
+static uint32_t
+pack_object(struct tidx *tstate, const char *name, int otype)
+{
+ ipfw_obj_ntlv *ntlv;
+ uint32_t i;
+
+ for (i = 0; i < tstate->count; i++) {
+ if (strcmp(tstate->idx[i].name, name) != 0)
+ continue;
+ if (tstate->idx[i].set != tstate->set)
+ continue;
+ if (tstate->idx[i].head.type != otype)
+ continue;
+
+ return (tstate->idx[i].idx);
+ }
+
+ if (tstate->count + 1 > tstate->size) {
+ tstate->size += 4;
+ tstate->idx = realloc(tstate->idx, tstate->size *
+ sizeof(ipfw_obj_ntlv));
+ if (tstate->idx == NULL)
+ return (0);
+ }
+
+ ntlv = &tstate->idx[i];
+ memset(ntlv, 0, sizeof(ipfw_obj_ntlv));
+ strlcpy(ntlv->name, name, sizeof(ntlv->name));
+ ntlv->head.type = otype;
+ ntlv->head.length = sizeof(ipfw_obj_ntlv);
+ ntlv->set = tstate->set;
+ ntlv->idx = ++tstate->counter;
+ tstate->count++;
+
+ return (ntlv->idx);
+}
+
+static uint32_t
+pack_table(struct tidx *tstate, const char *name)
+{
+
+ if (table_check_name(name) != 0)
+ return (0);
+
+ return (pack_object(tstate, name, IPFW_TLV_TBL_NAME));
+}
+
+static void
+fill_table_value(ipfw_insn *cmd, char *s)
+{
+ char *p;
+ int i;
+
+ p = strchr(s, '=');
+ if (p != NULL) {
+ *p++ = '\0';
+ i = match_token(tvalue_names, s);
+ if (i == -1)
+ errx(EX_USAGE,
+ "format: unknown table value name %s", s);
+ } else {
+ i = TVALUE_TAG;
+ p = s;
+ }
+
+ IPFW_SET_TVALUE_TYPE(cmd, i);
+ insntod(cmd, table)->value = strtoul(p, NULL, 0);
+}
+
+void
+fill_table(ipfw_insn *cmd, char *av, uint8_t opcode, struct tidx *tstate)
+{
+ ipfw_insn_kidx *c = insntod(cmd, kidx);
+ char *p;
+
+ if ((p = strchr(av + 6, ')')) == NULL)
+ errx(EX_DATAERR, "forgotten parenthesis: '%s'", av);
+ *p = '\0';
+ p = strchr(av + 6, ',');
+ if (p)
+ *p++ = '\0';
+
+ if ((c->kidx = pack_table(tstate, av + 6)) == 0)
+ errx(EX_DATAERR, "Invalid table name: %s", av + 6);
+
+ cmd->opcode = opcode;
+ if (p) {
+ cmd->len |= F_INSN_SIZE(ipfw_insn_table);
+ fill_table_value(cmd, p);
+ } else {
+ IPFW_SET_LOOKUP_TYPE(cmd, LOOKUP_NONE);
+ cmd->len |= F_INSN_SIZE(ipfw_insn_kidx);
+ }
+}
+
+/*
+ * fills the addr and mask fields in the instruction as appropriate from av.
+ * Update length as appropriate.
+ * The following formats are allowed:
+ * me returns O_IP_*_ME
+ * 1.2.3.4 single IP address
+ * 1.2.3.4:5.6.7.8 address:mask
+ * 1.2.3.4/24 address/mask
+ * 1.2.3.4/26{1,6,5,4,23} set of addresses in a subnet
+ * We can have multiple comma-separated address/mask entries.
+ */
+static void
+fill_ip(ipfw_insn_ip *cmd, char *av, int cblen, struct tidx *tstate)
+{
+ int len = 0;
+ uint32_t *d = ((ipfw_insn_u32 *)cmd)->d;
+
+ cmd->o.len &= ~F_LEN_MASK; /* zero len */
+
+ if (_substrcmp(av, "any") == 0)
+ return;
+
+ if (_substrcmp(av, "me") == 0) {
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn);
+ return;
+ }
+
+ if (strncmp(av, "table(", 6) == 0) {
+ fill_table(&cmd->o, av, O_IP_DST_LOOKUP, tstate);
+ return;
+ }
+
+ while (av) {
+ /*
+ * After the address we can have '/' or ':' indicating a mask,
+ * ',' indicating another address follows, '{' indicating a
+ * set of addresses of unspecified size.
+ */
+ char *t = NULL, *p = strpbrk(av, "/:,{");
+ int masklen;
+ char md, nd = '\0';
+
+ CHECK_LENGTH(cblen, (int)F_INSN_SIZE(ipfw_insn) + 2 + len);
+
+ if (p) {
+ md = *p;
+ *p++ = '\0';
+ if ((t = strpbrk(p, ",{")) != NULL) {
+ nd = *t;
+ *t = '\0';
+ }
+ } else
+ md = '\0';
+
+ if (lookup_host(av, (struct in_addr *)&d[0]) != 0)
+ errx(EX_NOHOST, "hostname ``%s'' unknown", av);
+ switch (md) {
+ case ':':
+ if (!inet_aton(p, (struct in_addr *)&d[1]))
+ errx(EX_DATAERR, "bad netmask ``%s''", p);
+ break;
+ case '/':
+ masklen = atoi(p);
+ if (masklen == 0)
+ d[1] = htonl(0U); /* mask */
+ else if (masklen > 32)
+ errx(EX_DATAERR, "bad width ``%s''", p);
+ else
+ d[1] = htonl(~0U << (32 - masklen));
+ break;
+ case '{': /* no mask, assume /24 and put back the '{' */
+ d[1] = htonl(~0U << (32 - 24));
+ *(--p) = md;
+ break;
+
+ case ',': /* single address plus continuation */
+ *(--p) = md;
+ /* FALLTHROUGH */
+ case 0: /* initialization value */
+ default:
+ d[1] = htonl(~0U); /* force /32 */
+ break;
+ }
+ d[0] &= d[1]; /* mask base address with mask */
+ if (t)
+ *t = nd;
+ /* find next separator */
+ if (p)
+ p = strpbrk(p, ",{");
+ if (p && *p == '{') {
+ /*
+ * We have a set of addresses. They are stored as follows:
+ * arg1 is the set size (powers of 2, 2..256)
+ * addr is the base address IN HOST FORMAT
+ * mask.. is an array of arg1 bits (rounded up to
+ * the next multiple of 32) with bits set
+ * for each host in the map.
+ */
+ uint32_t *map = (uint32_t *)&cmd->mask;
+ int low, high;
+ int i = contigmask((uint8_t *)&(d[1]), 32);
+
+ if (len > 0)
+ errx(EX_DATAERR, "address set cannot be in a list");
+ if (i < 24 || i > 31)
+ errx(EX_DATAERR, "invalid set with mask %d\n", i);
+ cmd->o.arg1 = 1<<(32-i); /* map length */
+ d[0] = ntohl(d[0]); /* base addr in host format */
+ cmd->o.opcode = O_IP_DST_SET; /* default */
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32) + (cmd->o.arg1+31)/32;
+ for (i = 0; i < (cmd->o.arg1+31)/32 ; i++)
+ map[i] = 0; /* clear map */
+
+ av = p + 1;
+ low = d[0] & 0xff;
+ high = low + cmd->o.arg1 - 1;
+ /*
+ * Here, i stores the previous value when we specify a range
+ * of addresses within a mask, e.g. 45-63. i = -1 means we
+ * have no previous value.
+ */
+ i = -1; /* previous value in a range */
+ while (isdigit(*av)) {
+ char *s;
+ int a = strtol(av, &s, 0);
+
+ if (s == av) { /* no parameter */
+ if (*av != '}')
+ errx(EX_DATAERR, "set not closed\n");
+ if (i != -1)
+ errx(EX_DATAERR, "incomplete range %d-", i);
+ break;
+ }
+ if (a < low || a > high)
+ errx(EX_DATAERR, "addr %d out of range [%d-%d]\n",
+ a, low, high);
+ a -= low;
+ if (i == -1) /* no previous in range */
+ i = a;
+ else { /* check that range is valid */
+ if (i > a)
+ errx(EX_DATAERR, "invalid range %d-%d",
+ i+low, a+low);
+ if (*s == '-')
+ errx(EX_DATAERR, "double '-' in range");
+ }
+ for (; i <= a; i++)
+ map[i/32] |= 1<<(i & 31);
+ i = -1;
+ if (*s == '-')
+ i = a;
+ else if (*s == '}')
+ break;
+ av = s+1;
+ }
+ return;
+ }
+ av = p;
+ if (av) /* then *av must be a ',' */
+ av++;
+
+ /* Check this entry */
+ if (d[1] == 0) { /* "any", specified as x.x.x.x/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) { /* "not any" never matches */
+ if (av == NULL && len == 0) /* only this entry */
+ errx(EX_DATAERR, "not any never matches");
+ }
+ /* else do nothing and skip this entry */
+ return;
+ }
+ /* A single IP can be stored in an optimized format */
+ if (d[1] == (uint32_t)~0 && av == NULL && len == 0) {
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32);
+ return;
+ }
+ len += 2; /* two words... */
+ d += 2;
+ } /* end while */
+ if (len + 1 > F_LEN_MASK)
+ errx(EX_DATAERR, "address list too long");
+ cmd->o.len |= len+1;
+}
+
+
+/* n2mask sets n bits of the mask */
+void
+n2mask(struct in6_addr *mask, int n)
+{
+ static int minimask[9] =
+ { 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
+ u_char *p;
+
+ memset(mask, 0, sizeof(struct in6_addr));
+ p = (u_char *) mask;
+ for (; n > 0; p++, n -= 8) {
+ if (n >= 8)
+ *p = 0xff;
+ else
+ *p = minimask[n];
+ }
+ return;
+}
+
+static void
+fill_flags_cmd(ipfw_insn *cmd, enum ipfw_opcodes opcode,
+ struct _s_x *flags, char *p)
+{
+ char *e;
+ uint32_t set = 0, clear = 0;
+
+ if (fill_flags(flags, p, &e, &set, &clear) != 0)
+ errx(EX_DATAERR, "invalid flag %s", e);
+
+ cmd->opcode = opcode;
+ cmd->len = (cmd->len & (F_NOT | F_OR)) | 1;
+ cmd->arg1 = (set & 0xff) | ( (clear & 0xff) << 8);
+}
+
+
+void
+ipfw_delete(char *av[])
+{
+ ipfw_range_tlv rt;
+ char *sep;
+ uint32_t i, j;
+ int exitval = EX_OK;
+ int do_set = 0;
+
+ av++;
+ NEED1("missing rule specification");
+ if ( *av && _substrcmp(*av, "set") == 0) {
+ /* Do not allow using the following syntax:
+ * ipfw set N delete set M
+ */
+ if (g_co.use_set)
+ errx(EX_DATAERR, "invalid syntax");
+ do_set = 1; /* delete set */
+ av++;
+ }
+
+ /* Rule number */
+ while (*av && isdigit(**av)) {
+ i = strtol(*av, &sep, 10);
+ j = i;
+ if (*sep== '-')
+ j = strtol(sep + 1, NULL, 10);
+ av++;
+ if (g_co.do_nat) {
+ exitval = ipfw_delete_nat(i);
+ } else if (g_co.do_pipe) {
+ exitval = ipfw_delete_pipe(g_co.do_pipe, i);
+ } else {
+ memset(&rt, 0, sizeof(rt));
+ if (do_set != 0) {
+ rt.set = i & 31;
+ rt.flags = IPFW_RCFLAG_SET;
+ } else {
+ rt.start_rule = i;
+ rt.end_rule = j;
+ if (rt.start_rule == 0 && rt.end_rule == 0)
+ rt.flags |= IPFW_RCFLAG_ALL;
+ else
+ rt.flags |= IPFW_RCFLAG_RANGE;
+ if (g_co.use_set != 0) {
+ rt.set = g_co.use_set - 1;
+ rt.flags |= IPFW_RCFLAG_SET;
+ }
+ }
+ if (g_co.do_dynamic == 2)
+ rt.flags |= IPFW_RCFLAG_DYNAMIC;
+ i = do_range_cmd(IP_FW_XDEL, &rt);
+ if (i != 0) {
+ exitval = EX_UNAVAILABLE;
+ if (g_co.do_quiet)
+ continue;
+ warn("rule %u: setsockopt(IP_FW_XDEL)",
+ rt.start_rule);
+ } else if (rt.new_set == 0 && do_set == 0 &&
+ g_co.do_dynamic != 2) {
+ exitval = EX_UNAVAILABLE;
+ if (g_co.do_quiet)
+ continue;
+ if (rt.start_rule != rt.end_rule)
+ warnx("no rules in %u-%u range",
+ rt.start_rule, rt.end_rule);
+ else
+ warnx("rule %u not found",
+ rt.start_rule);
+ }
+ }
+ }
+ if (exitval != EX_OK && g_co.do_force == 0)
+ exit(exitval);
+}
+
+/*
+ * fill the interface structure. We do not check the name as we can
+ * create interfaces dynamically, so checking them at insert time
+ * makes relatively little sense.
+ * Interface names containing '*', '?', or '[' are assumed to be shell
+ * patterns which match interfaces.
+ */
+static void
+fill_iface(ipfw_insn_if *cmd, char *arg, int cblen, struct tidx *tstate)
+{
+ char *p;
+ uint32_t uidx;
+
+ cmd->name[0] = '\0';
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn_if);
+
+ CHECK_CMDLEN;
+
+ /* Parse the interface or address */
+ if (strcmp(arg, "any") == 0)
+ cmd->o.len = 0; /* effectively ignore this command */
+ else if (strncmp(arg, "table(", 6) == 0) {
+ if ((p = strchr(arg + 6, ')')) == NULL)
+ errx(EX_DATAERR, "forgotten parenthesis: '%s'", arg);
+ *p = '\0';
+ p = strchr(arg + 6, ',');
+ if (p)
+ *p++ = '\0';
+ if ((uidx = pack_table(tstate, arg + 6)) == 0)
+ errx(EX_DATAERR, "Invalid table name: %s", arg + 6);
+
+ cmd->name[0] = '\1'; /* Special value indicating table */
+ cmd->p.kidx = uidx;
+ } else if (!isdigit(*arg)) {
+ strlcpy(cmd->name, arg, sizeof(cmd->name));
+ cmd->p.glob = strpbrk(arg, "*?[") != NULL ? 1 : 0;
+ } else if (!inet_aton(arg, &cmd->p.ip))
+ errx(EX_DATAERR, "bad ip address ``%s''", arg);
+}
+
+static void
+get_mac_addr_mask(const char *p, uint8_t *addr, uint8_t *mask)
+{
+ int i;
+ size_t l;
+ char *ap, *ptr, *optr;
+ struct ether_addr *mac;
+ const char *macset = "0123456789abcdefABCDEF:";
+
+ if (strcmp(p, "any") == 0) {
+ for (i = 0; i < ETHER_ADDR_LEN; i++)
+ addr[i] = mask[i] = 0;
+ return;
+ }
+
+ optr = ptr = strdup(p);
+ if ((ap = strsep(&ptr, "&/")) != NULL && *ap != 0) {
+ l = strlen(ap);
+ if (strspn(ap, macset) != l || (mac = ether_aton(ap)) == NULL)
+ errx(EX_DATAERR, "Incorrect MAC address");
+ bcopy(mac, addr, ETHER_ADDR_LEN);
+ } else
+ errx(EX_DATAERR, "Incorrect MAC address");
+
+ if (ptr != NULL) { /* we have mask? */
+ if (p[ptr - optr - 1] == '/') { /* mask len */
+ long ml = strtol(ptr, &ap, 10);
+ if (*ap != 0 || ml > ETHER_ADDR_LEN * 8 || ml < 0)
+ errx(EX_DATAERR, "Incorrect mask length");
+ for (i = 0; ml > 0 && i < ETHER_ADDR_LEN; ml -= 8, i++)
+ mask[i] = (ml >= 8) ? 0xff: (~0) << (8 - ml);
+ } else { /* mask */
+ l = strlen(ptr);
+ if (strspn(ptr, macset) != l ||
+ (mac = ether_aton(ptr)) == NULL)
+ errx(EX_DATAERR, "Incorrect mask");
+ bcopy(mac, mask, ETHER_ADDR_LEN);
+ }
+ } else { /* default mask: ff:ff:ff:ff:ff:ff */
+ for (i = 0; i < ETHER_ADDR_LEN; i++)
+ mask[i] = 0xff;
+ }
+ for (i = 0; i < ETHER_ADDR_LEN; i++)
+ addr[i] &= mask[i];
+
+ free(optr);
+}
+
+/*
+ * helper function, updates the pointer to cmd with the length
+ * of the current command, and also cleans up the first word of
+ * the new command in case it has been clobbered before.
+ */
+static ipfw_insn *
+next_cmd(ipfw_insn *cmd, int *len)
+{
+ *len -= F_LEN(cmd);
+ CHECK_LENGTH(*len, 0);
+ cmd += F_LEN(cmd);
+ bzero(cmd, sizeof(*cmd));
+ return cmd;
+}
+
+/*
+ * Takes arguments and copies them into a comment
+ */
+static void
+fill_comment(ipfw_insn *cmd, char **av, int cblen)
+{
+ int i, l;
+ char *p = (char *)(cmd + 1);
+
+ cmd->opcode = O_NOP;
+ cmd->len = (cmd->len & (F_NOT | F_OR));
+
+ /* Compute length of comment string. */
+ for (i = 0, l = 0; av[i] != NULL; i++)
+ l += strlen(av[i]) + 1;
+ if (l == 0)
+ return;
+ if (l > 84)
+ errx(EX_DATAERR,
+ "comment too long (max 80 chars)");
+ l = 1 + (l+3)/4;
+ cmd->len = (cmd->len & (F_NOT | F_OR)) | l;
+ CHECK_CMDLEN;
+
+ for (i = 0; av[i] != NULL; i++) {
+ strcpy(p, av[i]);
+ p += strlen(av[i]);
+ *p++ = ' ';
+ }
+ *(--p) = '\0';
+}
+
+/*
+ * A function to fill simple commands of size 1.
+ * Existing flags are preserved.
+ */
+static void
+fill_cmd(ipfw_insn *cmd, enum ipfw_opcodes opcode, int flags, uint16_t arg)
+{
+ cmd->opcode = opcode;
+ cmd->len = ((cmd->len | flags) & (F_NOT | F_OR)) | 1;
+ cmd->arg1 = arg;
+}
+
+/*
+ * Fetch and add the MAC address and type, with masks. This generates one or
+ * two microinstructions, and returns the pointer to the last one.
+ */
+static ipfw_insn *
+add_mac(ipfw_insn *cmd, char *av[], int cblen)
+{
+ ipfw_insn_mac *mac;
+
+ if ( ( av[0] == NULL ) || ( av[1] == NULL ) )
+ errx(EX_DATAERR, "MAC dst src");
+
+ cmd->opcode = O_MACADDR2;
+ cmd->len = (cmd->len & (F_NOT | F_OR)) | F_INSN_SIZE(ipfw_insn_mac);
+ CHECK_CMDLEN;
+
+ mac = (ipfw_insn_mac *)cmd;
+ get_mac_addr_mask(av[0], mac->addr, mac->mask); /* dst */
+ get_mac_addr_mask(av[1], &(mac->addr[ETHER_ADDR_LEN]),
+ &(mac->mask[ETHER_ADDR_LEN])); /* src */
+ return cmd;
+}
+
+static ipfw_insn *
+add_mactype(ipfw_insn *cmd, char *av, int cblen)
+{
+ if (!av)
+ errx(EX_DATAERR, "missing MAC type");
+ if (strcmp(av, "any") != 0) { /* we have a non-null type */
+ fill_newports((ipfw_insn_u16 *)cmd, av, IPPROTO_ETHERTYPE,
+ cblen);
+ cmd->opcode = O_MAC_TYPE;
+ return cmd;
+ } else
+ return NULL;
+}
+
+static ipfw_insn *
+add_proto0(ipfw_insn *cmd, char *av, u_char *protop)
+{
+ struct protoent *pe;
+ char *ep;
+ int proto;
+
+ proto = strtol(av, &ep, 10);
+ if (*ep != '\0' || proto <= 0) {
+ if ((pe = getprotobyname(av)) == NULL)
+ return NULL;
+ proto = pe->p_proto;
+ }
+
+ fill_cmd(cmd, O_PROTO, 0, proto);
+ *protop = proto;
+ return cmd;
+}
+
+static ipfw_insn *
+add_proto(ipfw_insn *cmd, char *av, u_char *protop)
+{
+ u_char proto = IPPROTO_IP;
+
+ if (_substrcmp(av, "all") == 0 || strcmp(av, "ip") == 0)
+ ; /* do not set O_IP4 nor O_IP6 */
+ else if (strcmp(av, "ip4") == 0)
+ /* explicit "just IPv4" rule */
+ fill_cmd(cmd, O_IP4, 0, 0);
+ else if (strcmp(av, "ip6") == 0) {
+ /* explicit "just IPv6" rule */
+ proto = IPPROTO_IPV6;
+ fill_cmd(cmd, O_IP6, 0, 0);
+ } else
+ return add_proto0(cmd, av, protop);
+
+ *protop = proto;
+ return cmd;
+}
+
+static ipfw_insn *
+add_proto_compat(ipfw_insn *cmd, char *av, u_char *protop)
+{
+ u_char proto = IPPROTO_IP;
+
+ if (_substrcmp(av, "all") == 0 || strcmp(av, "ip") == 0)
+ ; /* do not set O_IP4 nor O_IP6 */
+ else if (strcmp(av, "ipv4") == 0 || strcmp(av, "ip4") == 0)
+ /* explicit "just IPv4" rule */
+ fill_cmd(cmd, O_IP4, 0, 0);
+ else if (strcmp(av, "ipv6") == 0 || strcmp(av, "ip6") == 0) {
+ /* explicit "just IPv6" rule */
+ proto = IPPROTO_IPV6;
+ fill_cmd(cmd, O_IP6, 0, 0);
+ } else
+ return add_proto0(cmd, av, protop);
+
+ *protop = proto;
+ return cmd;
+}
+
+static ipfw_insn *
+add_srcip(ipfw_insn *cmd, char *av, int cblen, struct tidx *tstate)
+{
+ fill_ip((ipfw_insn_ip *)cmd, av, cblen, tstate);
+ if (cmd->opcode == O_IP_DST_SET) /* set */
+ cmd->opcode = O_IP_SRC_SET;
+ else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */
+ cmd->opcode = O_IP_SRC_LOOKUP;
+ else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn)) /* me */
+ cmd->opcode = O_IP_SRC_ME;
+ else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn_u32)) /* one IP */
+ cmd->opcode = O_IP_SRC;
+ else /* addr/mask */
+ cmd->opcode = O_IP_SRC_MASK;
+ return cmd;
+}
+
+static ipfw_insn *
+add_dstip(ipfw_insn *cmd, char *av, int cblen, struct tidx *tstate)
+{
+ fill_ip((ipfw_insn_ip *)cmd, av, cblen, tstate);
+ if (cmd->opcode == O_IP_DST_SET) /* set */
+ ;
+ else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */
+ ;
+ else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn)) /* me */
+ cmd->opcode = O_IP_DST_ME;
+ else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn_u32)) /* one IP */
+ cmd->opcode = O_IP_DST;
+ else /* addr/mask */
+ cmd->opcode = O_IP_DST_MASK;
+ return cmd;
+}
+
+static ipfw_insn *
+add_srcmac(ipfw_insn *cmd, char *av, struct tidx *tstate)
+{
+
+ if (strncmp(av, "table(", 6) == 0)
+ fill_table(cmd, av, O_MAC_SRC_LOOKUP, tstate);
+ else
+ errx(EX_DATAERR, "only mac table lookup is supported %s", av);
+ return cmd;
+}
+
+static ipfw_insn *
+add_dstmac(ipfw_insn *cmd, char *av, struct tidx *tstate)
+{
+
+ if (strncmp(av, "table(", 6) == 0)
+ fill_table(cmd, av, O_MAC_DST_LOOKUP, tstate);
+ else
+ errx(EX_DATAERR, "only mac table lookup is supported %s", av);
+ return cmd;
+}
+
+
+static struct _s_x f_reserved_keywords[] = {
+ { "altq", TOK_OR },
+ { "//", TOK_OR },
+ { "diverted", TOK_OR },
+ { "dst-port", TOK_OR },
+ { "src-port", TOK_OR },
+ { "established", TOK_OR },
+ { "keep-state", TOK_OR },
+ { "frag", TOK_OR },
+ { "icmptypes", TOK_OR },
+ { "in", TOK_OR },
+ { "out", TOK_OR },
+ { "ip6", TOK_OR },
+ { "any", TOK_OR },
+ { "to", TOK_OR },
+ { "via", TOK_OR },
+ { "{", TOK_OR },
+ { "lookup", TOK_OR },
+ { "tagged", TOK_OR },
+ { NULL, 0 } /* terminator */
+};
+
+static ipfw_insn *
+add_ports(ipfw_insn *cmd, char *av, u_char proto, int opcode, int cblen)
+{
+
+ if (match_token(f_reserved_keywords, av) != -1)
+ return (NULL);
+
+ if (fill_newports((ipfw_insn_u16 *)cmd, av, proto, cblen)) {
+ /* XXX todo: check that we have a protocol with ports */
+ cmd->opcode = opcode;
+ return cmd;
+ }
+ return NULL;
+}
+
+static ipfw_insn *
+add_src(ipfw_insn *cmd, char *av, u_char proto, int cblen, struct tidx *tstate)
+{
+ struct in6_addr a;
+ char *host, *ch, buf[INET6_ADDRSTRLEN];
+ ipfw_insn *ret = NULL;
+ size_t len;
+
+ /* Copy first address in set if needed */
+ if ((ch = strpbrk(av, "/,")) != NULL) {
+ len = ch - av;
+ strlcpy(buf, av, sizeof(buf));
+ if (len < sizeof(buf))
+ buf[len] = '\0';
+ host = buf;
+ } else
+ host = av;
+
+ if (proto == IPPROTO_IPV6 || strcmp(av, "me6") == 0 ||
+ inet_pton(AF_INET6, host, &a) == 1)
+ ret = add_srcip6(cmd, av, cblen, tstate);
+ /* XXX: should check for IPv4, not !IPv6 */
+ if (ret == NULL && (proto == IPPROTO_IP || strcmp(av, "me") == 0 ||
+ inet_pton(AF_INET6, host, &a) != 1))
+ ret = add_srcip(cmd, av, cblen, tstate);
+ if (ret == NULL && strcmp(av, "any") != 0)
+ ret = cmd;
+
+ return ret;
+}
+
+static ipfw_insn *
+add_dst(ipfw_insn *cmd, char *av, u_char proto, int cblen, struct tidx *tstate)
+{
+ struct in6_addr a;
+ char *host, *ch, buf[INET6_ADDRSTRLEN];
+ ipfw_insn *ret = NULL;
+ size_t len;
+
+ /* Copy first address in set if needed */
+ if ((ch = strpbrk(av, "/,")) != NULL) {
+ len = ch - av;
+ strlcpy(buf, av, sizeof(buf));
+ if (len < sizeof(buf))
+ buf[len] = '\0';
+ host = buf;
+ } else
+ host = av;
+
+ if (proto == IPPROTO_IPV6 || strcmp(av, "me6") == 0 ||
+ inet_pton(AF_INET6, host, &a) == 1)
+ ret = add_dstip6(cmd, av, cblen, tstate);
+ /* XXX: should check for IPv4, not !IPv6 */
+ if (ret == NULL && (proto == IPPROTO_IP || strcmp(av, "me") == 0 ||
+ inet_pton(AF_INET6, host, &a) != 1))
+ ret = add_dstip(cmd, av, cblen, tstate);
+ if (ret == NULL && strcmp(av, "any") != 0)
+ ret = cmd;
+
+ return ret;
+}
+
+static uint16_t
+parse_logdst(char *logdst_iter)
+{
+ char *token;
+ uint16_t ret;
+
+ ret = IPFW_LOG_DEFAULT;
+ while ((token = strsep(&logdst_iter, ",")) != NULL) {
+ if (_substrcmp(token, "syslog") == 0) {
+ ret |= IPFW_LOG_SYSLOG;
+ continue;
+ }
+ if (_substrcmp(token, "ipfw0") == 0) {
+ /* XXX add multiple ipfw* */
+ ret |= IPFW_LOG_IPFW0;
+ continue;
+ }
+ if (_substrcmp(token, "rtsock") == 0) {
+ ret |= IPFW_LOG_RTSOCK;
+ continue;
+ }
+ errx(EX_DATAERR,
+ "unsupported logdst token");
+ }
+ return (ret);
+}
+
+static inline uint32_t
+arg_or_targ_relaxed(const char *arg, const char *action, uint32_t maxarg)
+{
+ uint32_t arg1 = (uint32_t)(-1);
+
+ if (arg == NULL)
+ errx(EX_USAGE, "missing argument for %s", action);
+ if (isdigit(arg[0])) {
+ arg1 = strtoul(arg, NULL, 10);
+ if (arg1 < IPFW_ARG_MIN || arg1 > maxarg)
+ errx(EX_DATAERR, "illegal argument %s(%u) for %s",
+ arg, arg1, action);
+ } else if (_substrcmp(arg, "tablearg") == 0)
+ arg1 = IP_FW_TARG;
+ return (arg1);
+}
+
+static inline uint32_t
+arg_or_targ(const char *arg, const char *action)
+{
+ uint32_t arg1 = arg_or_targ_relaxed(arg, action, IPFW_ARG_MAX);
+
+ if (arg1 == (uint32_t)(-1))
+ errx(EX_DATAERR, "illegal argument %s(%u) for %s",
+ arg, arg1, action);
+ return (arg1);
+}
+
+static uint16_t
+get_divert_port(const char *arg, const char *action)
+{
+ uint32_t arg1 = arg_or_targ_relaxed(arg, action, IPFW_ARG_MAX);
+
+ if (arg1 != (uint32_t)(-1))
+ return (arg1);
+
+ struct servent *s;
+ setservent(1);
+ s = getservbyname(arg, "divert");
+ if (s == NULL)
+ errx(EX_DATAERR, "illegal divert/tee port");
+ return (ntohs(s->s_port));
+}
+
+/*
+ * Parse arguments and assemble the microinstructions which make up a rule.
+ * Rules are added into the 'rulebuf' and then copied in the correct order
+ * into the actual rule.
+ *
+ * The syntax for a rule starts with the action, followed by
+ * optional action parameters, and the various match patterns.
+ * In the assembled microcode, the first opcode must be an O_PROBE_STATE
+ * (generated if the rule includes a keep-state option), then the
+ * various match patterns, log/altq actions, and the actual action.
+ *
+ */
+static void
+compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate)
+{
+ /*
+ * rules are added into the 'rulebuf' and then copied in
+ * the correct order into the actual rule.
+ * Some things that need to go out of order (prob, action etc.)
+ * go into actbuf[].
+ */
+ static uint32_t actbuf[255], cmdbuf[255];
+ int rblen, ablen, cblen;
+
+ ipfw_insn *src, *dst, *cmd, *action, *prev=NULL;
+ ipfw_insn *first_cmd; /* first match pattern */
+
+ struct ip_fw_rule *rule;
+
+ /*
+ * various flags used to record that we entered some fields.
+ */
+ ipfw_insn *have_state = NULL; /* any state-related option */
+ int have_rstate = 0;
+ ipfw_insn *have_log = NULL, *have_altq = NULL, *have_tag = NULL;
+ ipfw_insn *have_skipcmd = NULL;
+ size_t len;
+
+ int i;
+
+ int open_par = 0; /* open parenthesis ( */
+
+ /* proto is here because it is used to fetch ports */
+ u_char proto = IPPROTO_IP; /* default protocol */
+
+ double match_prob = 1; /* match probability, default is always match */
+
+ bzero(actbuf, sizeof(actbuf)); /* actions go here */
+ bzero(cmdbuf, sizeof(cmdbuf));
+ bzero(rbuf, *rbufsize);
+
+ rule = (struct ip_fw_rule *)rbuf;
+ cmd = (ipfw_insn *)cmdbuf;
+ action = (ipfw_insn *)actbuf;
+
+ rblen = *rbufsize / sizeof(uint32_t);
+ rblen -= sizeof(struct ip_fw_rule) / sizeof(uint32_t);
+ ablen = nitems(actbuf);
+ cblen = nitems(cmdbuf);
+ cblen -= F_INSN_SIZE(ipfw_insn_u32) + 1;
+
+#define CHECK_RBUFLEN(len) { CHECK_LENGTH(rblen, len); rblen -= len; }
+#define CHECK_ACTLEN CHECK_LENGTH(ablen, action->len)
+
+ av++;
+
+ /* [rule N] -- Rule number optional */
+ if (av[0] && isdigit(**av)) {
+ rule->rulenum = atoi(*av);
+ av++;
+ }
+
+ /* [set N] -- set number (0..RESVD_SET), optional */
+ if (av[0] && av[1] && _substrcmp(*av, "set") == 0) {
+ int set = strtoul(av[1], NULL, 10);
+ if (set < 0 || set > RESVD_SET)
+ errx(EX_DATAERR, "illegal set %s", av[1]);
+ rule->set = set;
+ tstate->set = set;
+ av += 2;
+ }
+
+ /* [prob D] -- match probability, optional */
+ if (av[0] && av[1] && _substrcmp(*av, "prob") == 0) {
+ match_prob = strtod(av[1], NULL);
+
+ if (match_prob <= 0 || match_prob > 1)
+ errx(EX_DATAERR, "illegal match prob. %s", av[1]);
+ av += 2;
+ }
+
+ /* action -- mandatory */
+ NEED1("missing action");
+ i = match_token(rule_actions, *av);
+ av++;
+ action->len = 1; /* default */
+ CHECK_ACTLEN;
+ switch(i) {
+ case TOK_CHECKSTATE:
+ have_state = action;
+ action->opcode = O_CHECK_STATE;
+ action->len = F_INSN_SIZE(ipfw_insn_kidx);
+ CHECK_ACTLEN;
+ if (*av == NULL ||
+ match_token(rule_options, *av) == TOK_COMMENT) {
+ insntod(have_state, kidx)->kidx = pack_object(tstate,
+ default_state_name, IPFW_TLV_STATE_NAME);
+ break;
+ }
+ if (*av[0] == ':') {
+ if (strcmp(*av + 1, "any") == 0)
+ insntod(have_state, kidx)->kidx = 0;
+ else if (state_check_name(*av + 1) == 0)
+ insntod(have_state, kidx)->kidx = pack_object(
+ tstate, *av + 1, IPFW_TLV_STATE_NAME);
+ else
+ errx(EX_DATAERR, "Invalid state name %s",
+ *av);
+ av++;
+ break;
+ }
+ errx(EX_DATAERR, "Invalid state name %s", *av);
+ break;
+
+ case TOK_ABORT:
+ action->opcode = O_REJECT;
+ action->arg1 = ICMP_REJECT_ABORT;
+ break;
+
+ case TOK_ABORT6:
+ action->opcode = O_UNREACH6;
+ action->arg1 = ICMP6_UNREACH_ABORT;
+ break;
+
+ case TOK_ACCEPT:
+ action->opcode = O_ACCEPT;
+ break;
+
+ case TOK_DENY:
+ action->opcode = O_DENY;
+ action->arg1 = 0;
+ break;
+
+ case TOK_REJECT:
+ action->opcode = O_REJECT;
+ action->arg1 = ICMP_UNREACH_HOST;
+ break;
+
+ case TOK_RESET:
+ action->opcode = O_REJECT;
+ action->arg1 = ICMP_REJECT_RST;
+ break;
+
+ case TOK_RESET6:
+ action->opcode = O_UNREACH6;
+ action->arg1 = ICMP6_UNREACH_RST;
+ break;
+
+ case TOK_UNREACH:
+ action->opcode = O_REJECT;
+ NEED1("missing reject code");
+ action->arg1 = get_reject_code(*av);
+ av++;
+ if (action->arg1 == ICMP_UNREACH_NEEDFRAG && isdigit(**av)) {
+ uint16_t mtu;
+
+ mtu = strtoul(*av, NULL, 10);
+ if (mtu < 68 || mtu >= IP_MAXPACKET)
+ errx(EX_DATAERR, "illegal argument for %s",
+ *(av - 1));
+ action->len = F_INSN_SIZE(ipfw_insn_u16);
+ insntod(action, u16)->ports[0] = mtu;
+ av++;
+ }
+ break;
+
+ case TOK_UNREACH6:
+ action->opcode = O_UNREACH6;
+ NEED1("missing unreach code");
+ action->arg1 = get_unreach6_code(*av);
+ av++;
+ break;
+
+ case TOK_COUNT:
+ action->opcode = O_COUNT;
+ break;
+
+ case TOK_NAT:
+ action->opcode = O_NAT;
+ action->len = F_INSN_SIZE(ipfw_insn_nat);
+ CHECK_ACTLEN;
+ if (*av != NULL && _substrcmp(*av, "global") == 0)
+ action->arg1 = IP_FW_NAT44_GLOBAL;
+ else
+ action->arg1 = arg_or_targ(av[0], *(av - 1));
+ av++;
+ break;
+ case TOK_QUEUE:
+ action->opcode = O_QUEUE;
+ action->arg1 = arg_or_targ(av[0], *(av - 1));
+ av++;
+ break;
+ case TOK_PIPE:
+ action->opcode = O_PIPE;
+ action->arg1 = arg_or_targ(av[0], *(av - 1));
+ av++;
+ break;
+ case TOK_SKIPTO:
+ action->opcode = O_SKIPTO;
+ action->len = F_INSN_SIZE(ipfw_insn_u32);
+ CHECK_ACTLEN;
+ insntod(action, u32)->d[0] =
+ arg_or_targ_relaxed(av[0], *(av - 1), IPFW_DEFAULT_RULE);
+ av++;
+ break;
+ case TOK_NETGRAPH:
+ action->opcode = O_NETGRAPH;
+ action->arg1 = arg_or_targ(av[0], *(av - 1));
+ av++;
+ break;
+ case TOK_NGTEE:
+ action->opcode = O_NGTEE;
+ action->arg1 = arg_or_targ(av[0], *(av - 1));
+ av++;
+ break;
+ case TOK_DIVERT:
+ action->opcode = O_DIVERT;
+ action->arg1 = get_divert_port(av[0], *(av - 1));
+ av++;
+ break;
+ case TOK_TEE:
+ action->opcode = O_TEE;
+ action->arg1 = get_divert_port(av[0], *(av - 1));
+ av++;
+ break;
+ case TOK_CALL:
+ action->opcode = O_CALLRETURN;
+ action->len = F_INSN_SIZE(ipfw_insn_u32);
+ CHECK_ACTLEN;
+ insntod(action, u32)->d[0] =
+ arg_or_targ_relaxed(av[0], *(av - 1), IPFW_DEFAULT_RULE);
+ av++;
+ break;
+
+ case TOK_FORWARD: {
+ /*
+ * Locate the address-port separator (':' or ',').
+ * Could be one of the following:
+ * hostname:port
+ * IPv4 a.b.c.d,port
+ * IPv4 a.b.c.d:port
+ * IPv6 w:x:y::z,port
+ * IPv6 [w:x:y::z]:port
+ */
+ struct sockaddr_storage result;
+ struct addrinfo *res;
+ char *s, *end;
+ int family;
+ u_short port_number = 0;
+
+ NEED1("missing forward address[:port]");
+
+ if (strncmp(*av, "tablearg", 8) == 0 &&
+ ((*av)[8] == '\0' || (*av)[8] == ',' || (*av)[8] == ':'))
+ memcpy(++(*av), "0.0.0.0", 7);
+
+ /*
+ * Are we an bracket-enclosed IPv6 address?
+ */
+ if (strchr(*av, '['))
+ (*av)++;
+
+ /*
+ * locate the address-port separator (':' or ',')
+ */
+ s = strchr(*av, ',');
+ if (s == NULL) {
+ s = strchr(*av, ']');
+ /* Prevent erroneous parsing on brackets. */
+ if (s != NULL)
+ *(s++) = '\0';
+ else
+ s = *av;
+
+ /* Distinguish between IPv4:port and IPv6 cases. */
+ s = strchr(s, ':');
+ if (s && strchr(s+1, ':'))
+ s = NULL; /* no port */
+ }
+
+ if (s != NULL) {
+ /* Terminate host portion and set s to start of port. */
+ *(s++) = '\0';
+ i = strtoport(s, &end, 0 /* base */, 0 /* proto */);
+ if (s == end)
+ errx(EX_DATAERR,
+ "illegal forwarding port ``%s''", s);
+ port_number = (u_short)i;
+ }
+
+ /*
+ * Resolve the host name or address to a family and a
+ * network representation of the address.
+ */
+ if (getaddrinfo(*av, NULL, NULL, &res))
+ errx(EX_DATAERR, NULL);
+ /* Just use the first host in the answer. */
+ family = res->ai_family;
+ memcpy(&result, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+
+ if (family == PF_INET) {
+ ipfw_insn_sa *p = (ipfw_insn_sa *)action;
+
+ action->opcode = O_FORWARD_IP;
+ action->len = F_INSN_SIZE(ipfw_insn_sa);
+ CHECK_ACTLEN;
+
+ /*
+ * In the kernel we assume AF_INET and use only
+ * sin_port and sin_addr. Remember to set sin_len as
+ * the routing code seems to use it too.
+ */
+ p->sa.sin_len = sizeof(struct sockaddr_in);
+ p->sa.sin_family = AF_INET;
+ p->sa.sin_port = port_number;
+ p->sa.sin_addr.s_addr =
+ ((struct sockaddr_in *)&result)->sin_addr.s_addr;
+ } else if (family == PF_INET6) {
+ ipfw_insn_sa6 *p = (ipfw_insn_sa6 *)action;
+
+ action->opcode = O_FORWARD_IP6;
+ action->len = F_INSN_SIZE(ipfw_insn_sa6);
+ CHECK_ACTLEN;
+
+ p->sa.sin6_len = sizeof(struct sockaddr_in6);
+ p->sa.sin6_family = AF_INET6;
+ p->sa.sin6_port = port_number;
+ p->sa.sin6_flowinfo = 0;
+ p->sa.sin6_scope_id =
+ ((struct sockaddr_in6 *)&result)->sin6_scope_id;
+ bcopy(&((struct sockaddr_in6*)&result)->sin6_addr,
+ &p->sa.sin6_addr, sizeof(p->sa.sin6_addr));
+ } else {
+ errx(EX_DATAERR, "Invalid address family in forward action");
+ }
+ av++;
+ break;
+ }
+ case TOK_COMMENT:
+ /* pretend it is a 'count' rule followed by the comment */
+ action->opcode = O_COUNT;
+ av--; /* go back... */
+ break;
+
+ case TOK_SETFIB:
+ {
+ int numfibs;
+ size_t intsize = sizeof(int);
+
+ action->opcode = O_SETFIB;
+ NEED1("missing fib number");
+ if (_substrcmp(*av, "tablearg") == 0) {
+ action->arg1 = IP_FW_TARG;
+ } else {
+ action->arg1 = strtoul(*av, NULL, 10);
+ if (sysctlbyname("net.fibs", &numfibs, &intsize,
+ NULL, 0) == -1)
+ errx(EX_DATAERR, "fibs not supported.\n");
+ if (action->arg1 >= numfibs) /* Temporary */
+ errx(EX_DATAERR, "fib too large.\n");
+ /* Add high-order bit to fib to make room for tablearg*/
+ action->arg1 |= 0x8000;
+ }
+ av++;
+ break;
+ }
+
+ case TOK_SETDSCP:
+ {
+ int code;
+
+ action->opcode = O_SETDSCP;
+ NEED1("missing DSCP code");
+ if (_substrcmp(*av, "tablearg") == 0) {
+ action->arg1 = IP_FW_TARG;
+ } else {
+ if (isalpha(*av[0])) {
+ if ((code = match_token(f_ipdscp, *av)) == -1)
+ errx(EX_DATAERR, "Unknown DSCP code");
+ action->arg1 = code;
+ } else
+ action->arg1 = strtoul(*av, NULL, 10);
+ /*
+ * Add high-order bit to DSCP to make room
+ * for tablearg
+ */
+ action->arg1 |= 0x8000;
+ }
+ av++;
+ break;
+ }
+
+ case TOK_REASS:
+ action->opcode = O_REASS;
+ break;
+
+ case TOK_RETURN:
+ action->opcode = O_CALLRETURN;
+ action->len = F_INSN_SIZE(ipfw_insn_u32) | F_NOT;
+ CHECK_ACTLEN;
+ if (*av != NULL) {
+ /*
+ * Return type is optional.
+ * By default we use RETURN_NEXT_RULENUM.
+ */
+ i = match_token(return_types, *av);
+ if (i >= 0) {
+ action->arg1 = i;
+ av++;
+ } else
+ action->arg1 = RETURN_NEXT_RULENUM;
+ }
+ break;
+
+ case TOK_SETMARK: {
+ action->opcode = O_SETMARK;
+ action->len = F_INSN_SIZE(ipfw_insn_u32);
+ NEED1("missing mark");
+ if (strcmp(*av, "tablearg") == 0) {
+ action->arg1 = IP_FW_TARG;
+ } else {
+ insntod(action, u32)->d[0] = strtoul(*av, NULL, 0);
+ /* This is not a tablearg */
+ action->arg1 |= 0x8000;
+ }
+ av++;
+ CHECK_CMDLEN;
+ break;
+ }
+
+ case TOK_TCPSETMSS: {
+ u_long mss;
+ uint32_t idx;
+
+ idx = pack_object(tstate, "tcp-setmss", IPFW_TLV_EACTION);
+ if (idx == 0)
+ errx(EX_DATAERR, "pack_object failed");
+ action->opcode = O_EXTERNAL_ACTION;
+ action->len = F_INSN_SIZE(ipfw_insn_kidx);
+ CHECK_ACTLEN;
+ insntod(action, kidx)->kidx = idx;
+
+ NEED1("Missing MSS value");
+ action = next_cmd(action, &ablen);
+ action->len = 1;
+ CHECK_ACTLEN;
+ mss = strtoul(*av, NULL, 10);
+ if (mss == 0 || mss > UINT16_MAX)
+ errx(EX_USAGE, "invalid MSS value %s", *av);
+ fill_cmd(action, O_EXTERNAL_DATA, 0, (uint16_t)mss);
+ av++;
+ break;
+ }
+
+ default:
+ av--;
+ if (match_token(rule_eactions, *av) == -1)
+ errx(EX_DATAERR, "invalid action %s\n", *av);
+ /*
+ * External actions support.
+ * XXX: we support only syntax with instance name.
+ * For known external actions (from rule_eactions list)
+ * we can handle syntax directly. But with `eaction'
+ * keyword we can use only `eaction <name> <instance>'
+ * syntax.
+ */
+ case TOK_EACTION: {
+ uint32_t idx;
+
+ NEED1("Missing eaction name");
+ if (eaction_check_name(*av) != 0)
+ errx(EX_DATAERR, "Invalid eaction name %s", *av);
+ idx = pack_object(tstate, *av, IPFW_TLV_EACTION);
+ if (idx == 0)
+ errx(EX_DATAERR, "pack_object failed");
+ action->opcode = O_EXTERNAL_ACTION;
+ action->len = F_INSN_SIZE(ipfw_insn_kidx);
+ CHECK_ACTLEN;
+ insntod(action, kidx)->kidx = idx;
+
+ av++;
+ NEED1("Missing eaction instance name");
+ if (eaction_check_name(*av) != 0)
+ errx(EX_DATAERR, "Invalid eaction instance name %s",
+ *av);
+ /*
+ * External action instance object has TLV type depended
+ * from the external action name object index. Since we
+ * currently don't know this index, use zero as TLV type.
+ */
+ idx = pack_object(tstate, *av, 0);
+ if (idx == 0)
+ errx(EX_DATAERR, "pack_object failed");
+ action = next_cmd(action, &ablen);
+ action->opcode = O_EXTERNAL_INSTANCE;
+ action->len = F_INSN_SIZE(ipfw_insn_kidx);
+ CHECK_ACTLEN;
+ insntod(action, kidx)->kidx = idx;
+ av++;
+ }
+ }
+ action = next_cmd(action, &ablen);
+
+ /*
+ * [altq queuename] -- altq tag, optional
+ * [log [logamount N]] -- log, optional
+ *
+ * If they exist, it go first in the cmdbuf, but then it is
+ * skipped in the copy section to the end of the buffer.
+ */
+ while (av[0] != NULL && (i = match_token(rule_action_params, *av)) != -1) {
+ av++;
+ switch (i) {
+ case TOK_LOG:
+ {
+ ipfw_insn_log *c = (ipfw_insn_log *)cmd;
+ int l;
+
+ if (have_log)
+ errx(EX_DATAERR,
+ "log cannot be specified more than once");
+ have_log = (ipfw_insn *)c;
+ cmd->len = F_INSN_SIZE(ipfw_insn_log);
+ CHECK_CMDLEN;
+ cmd->opcode = O_LOG;
+ cmd->arg1 = IPFW_LOG_DEFAULT;
+ /* logdst before logamount */
+ if (av[0] && _substrcmp(*av, "logdst") == 0) {
+ av++;
+ NEED1("logdst requires argument");
+ cmd->arg1 = parse_logdst(*av);
+ av++;
+ }
+
+ if (av[0] && _substrcmp(*av, "logamount") == 0) {
+ av++;
+ NEED1("logamount requires argument");
+ l = atoi(*av);
+ if (l < 0)
+ errx(EX_DATAERR,
+ "logamount must be positive");
+ c->max_log = l;
+ av++;
+ } else {
+ len = sizeof(c->max_log);
+ if (sysctlbyname("net.inet.ip.fw.verbose_limit",
+ &c->max_log, &len, NULL, 0) == -1) {
+ if (g_co.test_only) {
+ c->max_log = 0;
+ break;
+ }
+ errx(1, "sysctlbyname(\"%s\")",
+ "net.inet.ip.fw.verbose_limit");
+ }
+ }
+
+ /* logdst after logamount */
+ if (av[0] && _substrcmp(*av, "logdst") == 0) {
+ av++;
+ NEED1("logdst requires argument");
+ cmd->arg1 = parse_logdst(*av);
+ av++;
+ }
+ }
+ break;
+
+#ifndef NO_ALTQ
+ case TOK_ALTQ:
+ {
+ ipfw_insn_altq *a = (ipfw_insn_altq *)cmd;
+
+ NEED1("missing altq queue name");
+ if (have_altq)
+ errx(EX_DATAERR,
+ "altq cannot be specified more than once");
+ have_altq = (ipfw_insn *)a;
+ cmd->len = F_INSN_SIZE(ipfw_insn_altq);
+ CHECK_CMDLEN;
+ cmd->opcode = O_ALTQ;
+ a->qid = altq_name_to_qid(*av);
+ av++;
+ }
+ break;
+#endif
+
+ case TOK_TAG:
+ case TOK_UNTAG: {
+ uint16_t tag;
+
+ if (have_tag)
+ errx(EX_USAGE, "tag and untag cannot be "
+ "specified more than once");
+ GET_UINT_ARG(tag, IPFW_ARG_MIN, IPFW_ARG_MAX, i,
+ rule_action_params);
+ have_tag = cmd;
+ fill_cmd(cmd, O_TAG, (i == TOK_TAG) ? 0: F_NOT, tag);
+ av++;
+ break;
+ }
+
+ default:
+ abort();
+ }
+ cmd = next_cmd(cmd, &cblen);
+ }
+
+ if (have_state) { /* must be a check-state, we are done */
+ if (*av != NULL &&
+ match_token(rule_options, *av) == TOK_COMMENT) {
+ /* check-state has a comment */
+ av++;
+ fill_comment(cmd, av, cblen);
+ cmd = next_cmd(cmd, &cblen);
+ av[0] = NULL;
+ }
+ goto done;
+ }
+
+#define OR_START(target) \
+ if (av[0] && (*av[0] == '(' || *av[0] == '{')) { \
+ if (open_par) \
+ errx(EX_USAGE, "nested \"(\" not allowed\n"); \
+ prev = NULL; \
+ open_par = 1; \
+ if ( (av[0])[1] == '\0') { \
+ av++; \
+ } else \
+ (*av)++; \
+ } \
+ target: \
+
+
+#define CLOSE_PAR \
+ if (open_par) { \
+ if (av[0] && ( \
+ strcmp(*av, ")") == 0 || \
+ strcmp(*av, "}") == 0)) { \
+ prev = NULL; \
+ open_par = 0; \
+ av++; \
+ } else \
+ errx(EX_USAGE, "missing \")\"\n"); \
+ }
+
+#define NOT_BLOCK \
+ if (av[0] && _substrcmp(*av, "not") == 0) { \
+ if (cmd->len & F_NOT) \
+ errx(EX_USAGE, "double \"not\" not allowed\n"); \
+ cmd->len |= F_NOT; \
+ av++; \
+ }
+
+#define OR_BLOCK(target) \
+ if (av[0] && _substrcmp(*av, "or") == 0) { \
+ if (prev == NULL || open_par == 0) \
+ errx(EX_DATAERR, "invalid OR block"); \
+ prev->len |= F_OR; \
+ av++; \
+ goto target; \
+ } \
+ CLOSE_PAR;
+
+ first_cmd = cmd;
+
+#if 0
+ /*
+ * MAC addresses, optional.
+ * If we have this, we skip the part "proto from src to dst"
+ * and jump straight to the option parsing.
+ */
+ NOT_BLOCK;
+ NEED1("missing protocol");
+ if (_substrcmp(*av, "MAC") == 0 ||
+ _substrcmp(*av, "mac") == 0) {
+ av++; /* the "MAC" keyword */
+ add_mac(cmd, av); /* exits in case of errors */
+ cmd = next_cmd(cmd);
+ av += 2; /* dst-mac and src-mac */
+ NOT_BLOCK;
+ NEED1("missing mac type");
+ if (add_mactype(cmd, av[0]))
+ cmd = next_cmd(cmd);
+ av++; /* any or mac-type */
+ goto read_options;
+ }
+#endif
+
+ /*
+ * protocol, mandatory
+ */
+ OR_START(get_proto);
+ NOT_BLOCK;
+ NEED1("missing protocol");
+ if (add_proto_compat(cmd, *av, &proto)) {
+ av++;
+ if (F_LEN(cmd) != 0) {
+ prev = cmd;
+ cmd = next_cmd(cmd, &cblen);
+ }
+ } else if (first_cmd != cmd) {
+ errx(EX_DATAERR, "invalid protocol ``%s''", *av);
+ } else {
+ rule->flags |= IPFW_RULE_JUSTOPTS;
+ goto read_options;
+ }
+ OR_BLOCK(get_proto);
+
+ first_cmd = cmd; /* update pointer to use in compact form */
+
+ /*
+ * "from", mandatory
+ */
+ if ((av[0] == NULL) || _substrcmp(*av, "from") != 0)
+ errx(EX_USAGE, "missing ``from''");
+ av++;
+
+ /*
+ * source IP, mandatory
+ */
+ OR_START(source_ip);
+ NOT_BLOCK; /* optional "not" */
+ NEED1("missing source address");
+ if (add_src(cmd, *av, proto, cblen, tstate)) {
+ av++;
+ if (F_LEN(cmd) != 0) { /* ! any */
+ prev = cmd;
+ cmd = next_cmd(cmd, &cblen);
+ }
+ } else
+ errx(EX_USAGE, "bad source address %s", *av);
+ OR_BLOCK(source_ip);
+
+ /*
+ * source ports, optional
+ */
+ NOT_BLOCK; /* optional "not" */
+ if ( av[0] != NULL ) {
+ if (_substrcmp(*av, "any") == 0 ||
+ add_ports(cmd, *av, proto, O_IP_SRCPORT, cblen)) {
+ av++;
+ if (F_LEN(cmd) != 0)
+ cmd = next_cmd(cmd, &cblen);
+ }
+ }
+
+ /*
+ * "to", mandatory
+ */
+ if ( (av[0] == NULL) || _substrcmp(*av, "to") != 0 )
+ errx(EX_USAGE, "missing ``to''");
+ av++;
+
+ /*
+ * destination, mandatory
+ */
+ OR_START(dest_ip);
+ NOT_BLOCK; /* optional "not" */
+ NEED1("missing dst address");
+ if (add_dst(cmd, *av, proto, cblen, tstate)) {
+ av++;
+ if (F_LEN(cmd) != 0) { /* ! any */
+ prev = cmd;
+ cmd = next_cmd(cmd, &cblen);
+ }
+ } else
+ errx( EX_USAGE, "bad destination address %s", *av);
+ OR_BLOCK(dest_ip);
+
+ /*
+ * dest. ports, optional
+ */
+ NOT_BLOCK; /* optional "not" */
+ if (av[0]) {
+ if (_substrcmp(*av, "any") == 0 ||
+ add_ports(cmd, *av, proto, O_IP_DSTPORT, cblen)) {
+ av++;
+ if (F_LEN(cmd) != 0)
+ cmd = next_cmd(cmd, &cblen);
+ }
+ }
+ if (first_cmd == cmd)
+ rule->flags |= IPFW_RULE_NOOPT;
+
+read_options:
+ prev = NULL;
+ while ( av[0] != NULL ) {
+ char *s;
+ ipfw_insn_u32 *cmd32; /* alias for cmd */
+
+ s = *av;
+ cmd32 = (ipfw_insn_u32 *)cmd;
+
+ if (*s == '!') { /* alternate syntax for NOT */
+ if (cmd->len & F_NOT)
+ errx(EX_USAGE, "double \"not\" not allowed\n");
+ cmd->len = F_NOT;
+ s++;
+ }
+ i = match_token(rule_options, s);
+ av++;
+ switch(i) {
+ case TOK_NOT:
+ if (cmd->len & F_NOT)
+ errx(EX_USAGE, "double \"not\" not allowed\n");
+ cmd->len = F_NOT;
+ break;
+
+ case TOK_OR:
+ if (open_par == 0 || prev == NULL)
+ errx(EX_USAGE, "invalid \"or\" block\n");
+ prev->len |= F_OR;
+ break;
+
+ case TOK_STARTBRACE:
+ if (open_par)
+ errx(EX_USAGE, "+nested \"(\" not allowed\n");
+ open_par = 1;
+ break;
+
+ case TOK_ENDBRACE:
+ if (!open_par)
+ errx(EX_USAGE, "+missing \")\"\n");
+ open_par = 0;
+ prev = NULL;
+ break;
+
+ case TOK_IN:
+ fill_cmd(cmd, O_IN, 0, 0);
+ break;
+
+ case TOK_OUT:
+ cmd->len ^= F_NOT; /* toggle F_NOT */
+ fill_cmd(cmd, O_IN, 0, 0);
+ break;
+
+ case TOK_DIVERTED:
+ fill_cmd(cmd, O_DIVERTED, 0, 3);
+ break;
+
+ case TOK_DIVERTEDLOOPBACK:
+ fill_cmd(cmd, O_DIVERTED, 0, 1);
+ break;
+
+ case TOK_DIVERTEDOUTPUT:
+ fill_cmd(cmd, O_DIVERTED, 0, 2);
+ break;
+
+ case TOK_FRAG: {
+ uint32_t set = 0, clear = 0;
+
+ if (*av != NULL && fill_flags(f_ipoff, *av, NULL,
+ &set, &clear) == 0)
+ av++;
+ else {
+ /*
+ * Compatibility: no argument after "frag"
+ * keyword equals to "frag offset".
+ */
+ set = 0x01;
+ clear = 0;
+ }
+ fill_cmd(cmd, O_FRAG, 0,
+ (set & 0xff) | ( (clear & 0xff) << 8));
+ break;
+ }
+
+ case TOK_LAYER2:
+ fill_cmd(cmd, O_LAYER2, 0, 0);
+ break;
+
+ case TOK_XMIT:
+ case TOK_RECV:
+ case TOK_VIA:
+ NEED1("recv, xmit, via require interface name"
+ " or address");
+ fill_iface(insntod(cmd, if), av[0], cblen, tstate);
+ av++;
+ if (F_LEN(cmd) == 0) /* not a valid address */
+ break;
+ if (i == TOK_XMIT)
+ cmd->opcode = O_XMIT;
+ else if (i == TOK_RECV)
+ cmd->opcode = O_RECV;
+ else if (i == TOK_VIA)
+ cmd->opcode = O_VIA;
+ break;
+
+ case TOK_ICMPTYPES:
+ NEED1("icmptypes requires list of types");
+ fill_icmptypes((ipfw_insn_u32 *)cmd, *av);
+ av++;
+ break;
+
+ case TOK_ICMP6TYPES:
+ NEED1("icmptypes requires list of types");
+ fill_icmp6types((ipfw_insn_icmp6 *)cmd, *av, cblen);
+ av++;
+ break;
+
+ case TOK_IPTTL:
+ NEED1("ipttl requires TTL");
+ if (strpbrk(*av, "-,")) {
+ if (!add_ports(cmd, *av, 0, O_IPTTL, cblen))
+ errx(EX_DATAERR, "invalid ipttl %s", *av);
+ } else
+ fill_cmd(cmd, O_IPTTL, 0, strtoul(*av, NULL, 0));
+ av++;
+ break;
+
+ case TOK_IPID:
+ NEED1("ipid requires id");
+ if (strpbrk(*av, "-,")) {
+ if (!add_ports(cmd, *av, 0, O_IPID, cblen))
+ errx(EX_DATAERR, "invalid ipid %s", *av);
+ } else
+ fill_cmd(cmd, O_IPID, 0, strtoul(*av, NULL, 0));
+ av++;
+ break;
+
+ case TOK_IPLEN:
+ NEED1("iplen requires length");
+ if (strpbrk(*av, "-,")) {
+ if (!add_ports(cmd, *av, 0, O_IPLEN, cblen))
+ errx(EX_DATAERR, "invalid ip len %s", *av);
+ } else
+ fill_cmd(cmd, O_IPLEN, 0, strtoul(*av, NULL, 0));
+ av++;
+ break;
+
+ case TOK_IPVER:
+ NEED1("ipver requires version");
+ fill_cmd(cmd, O_IPVER, 0, strtoul(*av, NULL, 0));
+ av++;
+ break;
+
+ case TOK_IPPRECEDENCE:
+ NEED1("ipprecedence requires value");
+ fill_cmd(cmd, O_IPPRECEDENCE, 0,
+ (strtoul(*av, NULL, 0) & 7) << 5);
+ av++;
+ break;
+
+ case TOK_DSCP:
+ NEED1("missing DSCP code");
+ fill_dscp(cmd, *av, cblen);
+ av++;
+ break;
+
+ case TOK_IPOPTS:
+ NEED1("missing argument for ipoptions");
+ fill_flags_cmd(cmd, O_IPOPT, f_ipopts, *av);
+ av++;
+ break;
+
+ case TOK_IPTOS:
+ NEED1("missing argument for iptos");
+ fill_flags_cmd(cmd, O_IPTOS, f_iptos, *av);
+ av++;
+ break;
+
+ case TOK_UID:
+ NEED1("uid requires argument");
+ {
+ char *end;
+ uid_t uid;
+ struct passwd *pwd;
+
+ cmd->opcode = O_UID;
+ uid = strtoul(*av, &end, 0);
+ pwd = (*end == '\0') ? getpwuid(uid) : getpwnam(*av);
+ if (pwd == NULL)
+ errx(EX_DATAERR, "uid \"%s\" nonexistent", *av);
+ cmd32->d[0] = pwd->pw_uid;
+ cmd->len |= F_INSN_SIZE(ipfw_insn_u32);
+ av++;
+ }
+ break;
+
+ case TOK_GID:
+ NEED1("gid requires argument");
+ {
+ char *end;
+ gid_t gid;
+ struct group *grp;
+
+ cmd->opcode = O_GID;
+ gid = strtoul(*av, &end, 0);
+ grp = (*end == '\0') ? getgrgid(gid) : getgrnam(*av);
+ if (grp == NULL)
+ errx(EX_DATAERR, "gid \"%s\" nonexistent", *av);
+ cmd32->d[0] = grp->gr_gid;
+ cmd->len |= F_INSN_SIZE(ipfw_insn_u32);
+ av++;
+ }
+ break;
+
+ case TOK_JAIL:
+ NEED1("jail requires argument");
+ {
+ char *end;
+ int jid;
+
+ cmd->opcode = O_JAIL;
+ /*
+ * If av is a number, then we'll just pass it as-is. If
+ * it's a name, try to resolve that to a jid.
+ *
+ * We save the jail_getid(3) call for a fallback because
+ * it entails an unconditional trip to the kernel to
+ * either validate a jid or resolve a name to a jid.
+ * This specific token doesn't currently require a
+ * jid to be an active jail, so we save a transition
+ * by simply using a number that we're given.
+ */
+ jid = strtoul(*av, &end, 10);
+ if (*end != '\0') {
+ jid = jail_getid(*av);
+ if (jid < 0)
+ errx(EX_DATAERR, "%s", jail_errmsg);
+ }
+ cmd32->d[0] = (uint32_t)jid;
+ cmd->len |= F_INSN_SIZE(ipfw_insn_u32);
+ av++;
+ }
+ break;
+
+ case TOK_ESTAB:
+ fill_cmd(cmd, O_ESTAB, 0, 0);
+ break;
+
+ case TOK_SETUP:
+ fill_cmd(cmd, O_TCPFLAGS, 0,
+ (TH_SYN) | ( (TH_ACK) & 0xff) <<8 );
+ break;
+
+ case TOK_TCPDATALEN:
+ NEED1("tcpdatalen requires length");
+ if (strpbrk(*av, "-,")) {
+ if (!add_ports(cmd, *av, 0, O_TCPDATALEN, cblen))
+ errx(EX_DATAERR, "invalid tcpdata len %s", *av);
+ } else
+ fill_cmd(cmd, O_TCPDATALEN, 0,
+ strtoul(*av, NULL, 0));
+ av++;
+ break;
+
+ case TOK_TCPOPTS:
+ NEED1("missing argument for tcpoptions");
+ fill_flags_cmd(cmd, O_TCPOPTS, f_tcpopts, *av);
+ av++;
+ break;
+
+ case TOK_TCPSEQ:
+ case TOK_TCPACK:
+ NEED1("tcpseq/tcpack requires argument");
+ cmd->len = F_INSN_SIZE(ipfw_insn_u32);
+ cmd->opcode = (i == TOK_TCPSEQ) ? O_TCPSEQ : O_TCPACK;
+ cmd32->d[0] = htonl(strtoul(*av, NULL, 0));
+ av++;
+ break;
+
+ case TOK_TCPMSS:
+ case TOK_TCPWIN:
+ NEED1("tcpmss/tcpwin requires size");
+ if (strpbrk(*av, "-,")) {
+ if (add_ports(cmd, *av, 0,
+ i == TOK_TCPWIN ? O_TCPWIN : O_TCPMSS,
+ cblen) == NULL)
+ errx(EX_DATAERR, "invalid %s size %s",
+ s, *av);
+ } else
+ fill_cmd(cmd, i == TOK_TCPWIN ? O_TCPWIN :
+ O_TCPMSS, 0, strtoul(*av, NULL, 0));
+ av++;
+ break;
+
+ case TOK_TCPFLAGS:
+ NEED1("missing argument for tcpflags");
+ cmd->opcode = O_TCPFLAGS;
+ fill_flags_cmd(cmd, O_TCPFLAGS, f_tcpflags, *av);
+ av++;
+ break;
+
+ case TOK_KEEPSTATE:
+ case TOK_RECORDSTATE: {
+ uint32_t uidx;
+
+ if (open_par)
+ errx(EX_USAGE, "keep-state or record-state cannot be part "
+ "of an or block");
+ if (have_state)
+ errx(EX_USAGE, "only one of keep-state, record-state, "
+ " limit and set-limit is allowed");
+ if (*av != NULL && *av[0] == ':') {
+ if (state_check_name(*av + 1) != 0)
+ errx(EX_DATAERR,
+ "Invalid state name %s", *av);
+ uidx = pack_object(tstate, *av + 1,
+ IPFW_TLV_STATE_NAME);
+ av++;
+ } else
+ uidx = pack_object(tstate, default_state_name,
+ IPFW_TLV_STATE_NAME);
+ have_state = cmd;
+ have_rstate = i == TOK_RECORDSTATE;
+ cmd->opcode = O_KEEP_STATE;
+ cmd->len = F_INSN_SIZE(ipfw_insn_kidx);
+ CHECK_CMDLEN;
+ insntod(have_state, kidx)->kidx = uidx;
+ break;
+ }
+
+ case TOK_LIMIT:
+ case TOK_SETLIMIT: {
+ ipfw_insn_limit *c = insntod(cmd, limit);
+ int val;
+
+ if (open_par)
+ errx(EX_USAGE,
+ "limit or set-limit cannot be part of an or block");
+ if (have_state)
+ errx(EX_USAGE, "only one of keep-state, record-state, "
+ " limit and set-limit is allowed");
+ have_state = cmd;
+ have_rstate = i == TOK_SETLIMIT;
+
+ cmd->opcode = O_LIMIT;
+ cmd->len = F_INSN_SIZE(ipfw_insn_limit);
+ CHECK_CMDLEN;
+ c->limit_mask = c->conn_limit = c->kidx = 0;
+
+ while ( av[0] != NULL ) {
+ if ((val = match_token(limit_masks, *av)) <= 0)
+ break;
+ c->limit_mask |= val;
+ av++;
+ }
+
+ if (c->limit_mask == 0)
+ errx(EX_USAGE, "limit: missing limit mask");
+
+ GET_UINT_ARG(c->conn_limit, IPFW_ARG_MIN, IPFW_ARG_MAX,
+ TOK_LIMIT, rule_options);
+ av++;
+
+ if (*av != NULL && *av[0] == ':') {
+ if (state_check_name(*av + 1) != 0)
+ errx(EX_DATAERR,
+ "Invalid state name %s", *av);
+ c->kidx = pack_object(tstate, *av + 1,
+ IPFW_TLV_STATE_NAME);
+ av++;
+ } else
+ c->kidx = pack_object(tstate,
+ default_state_name, IPFW_TLV_STATE_NAME);
+ break;
+ }
+
+ case TOK_PROTO:
+ NEED1("missing protocol");
+ if (add_proto(cmd, *av, &proto)) {
+ av++;
+ } else
+ errx(EX_DATAERR, "invalid protocol ``%s''",
+ *av);
+ break;
+
+ case TOK_SRCIP:
+ NEED1("missing source IP");
+ if (add_srcip(cmd, *av, cblen, tstate)) {
+ av++;
+ }
+ break;
+
+ case TOK_DSTIP:
+ NEED1("missing destination IP");
+ if (add_dstip(cmd, *av, cblen, tstate)) {
+ av++;
+ }
+ break;
+
+ case TOK_SRCIP6:
+ NEED1("missing source IP6");
+ if (add_srcip6(cmd, *av, cblen, tstate)) {
+ av++;
+ }
+ break;
+
+ case TOK_DSTIP6:
+ NEED1("missing destination IP6");
+ if (add_dstip6(cmd, *av, cblen, tstate)) {
+ av++;
+ }
+ break;
+
+
+ case TOK_SRCMAC:
+ NEED1("missing source MAC");
+ if (add_srcmac(cmd, *av, tstate)) {
+ av++;
+ }
+ break;
+
+ case TOK_DSTMAC:
+ NEED1("missing destination MAC");
+ if (add_dstmac(cmd, *av, tstate)) {
+ av++;
+ }
+ break;
+
+ case TOK_SRCPORT:
+ NEED1("missing source port");
+ if (_substrcmp(*av, "any") == 0 ||
+ add_ports(cmd, *av, proto, O_IP_SRCPORT, cblen)) {
+ av++;
+ } else
+ errx(EX_DATAERR, "invalid source port %s", *av);
+ break;
+
+ case TOK_DSTPORT:
+ NEED1("missing destination port");
+ if (_substrcmp(*av, "any") == 0 ||
+ add_ports(cmd, *av, proto, O_IP_DSTPORT, cblen)) {
+ av++;
+ } else
+ errx(EX_DATAERR, "invalid destination port %s",
+ *av);
+ break;
+
+ case TOK_MAC:
+ if (add_mac(cmd, av, cblen))
+ av += 2;
+ break;
+
+ case TOK_MACTYPE:
+ NEED1("missing mac type");
+ if (!add_mactype(cmd, *av, cblen))
+ errx(EX_DATAERR, "invalid mac type %s", *av);
+ av++;
+ break;
+
+ case TOK_VERREVPATH:
+ fill_cmd(cmd, O_VERREVPATH, 0, 0);
+ break;
+
+ case TOK_VERSRCREACH:
+ fill_cmd(cmd, O_VERSRCREACH, 0, 0);
+ break;
+
+ case TOK_ANTISPOOF:
+ fill_cmd(cmd, O_ANTISPOOF, 0, 0);
+ break;
+
+ case TOK_IPSEC:
+ fill_cmd(cmd, O_IPSEC, 0, 0);
+ break;
+
+ case TOK_IPV6:
+ fill_cmd(cmd, O_IP6, 0, 0);
+ break;
+
+ case TOK_IPV4:
+ fill_cmd(cmd, O_IP4, 0, 0);
+ break;
+
+ case TOK_EXT6HDR:
+ NEED1("missing extension header");
+ fill_ext6hdr( cmd, *av );
+ av++;
+ break;
+
+ case TOK_FLOWID:
+ if (proto != IPPROTO_IPV6 )
+ errx( EX_USAGE, "flow-id filter is active "
+ "only for ipv6 protocol\n");
+ fill_flow6( (ipfw_insn_u32 *) cmd, *av, cblen);
+ av++;
+ break;
+
+ case TOK_COMMENT:
+ fill_comment(cmd, av, cblen);
+ av[0]=NULL;
+ break;
+
+ case TOK_TAGGED:
+ if (av[0] && strpbrk(*av, "-,")) {
+ if (!add_ports(cmd, *av, 0, O_TAGGED, cblen))
+ errx(EX_DATAERR, "tagged: invalid tag"
+ " list: %s", *av);
+ }
+ else {
+ uint16_t tag;
+
+ GET_UINT_ARG(tag, IPFW_ARG_MIN, IPFW_ARG_MAX,
+ TOK_TAGGED, rule_options);
+ fill_cmd(cmd, O_TAGGED, 0, tag);
+ }
+ av++;
+ break;
+
+ case TOK_FIB:
+ NEED1("fib requires fib number");
+ fill_cmd(cmd, O_FIB, 0, strtoul(*av, NULL, 0));
+ av++;
+ break;
+ case TOK_SOCKARG:
+ fill_cmd(cmd, O_SOCKARG, 0, 0);
+ break;
+
+ case TOK_LOOKUP: {
+ /* optional mask for some LOOKUP types */
+ ipfw_insn_table *c = insntod(cmd, table);
+ char *lkey;
+
+ if (!av[0] || !av[1])
+ errx(EX_USAGE,
+ "format: lookup argument tablenum");
+ cmd->opcode = O_IP_DST_LOOKUP;
+
+ lkey = strsep(av, ":");
+ i = match_token(lookup_keys, lkey);
+ if (i == -1)
+ errx(EX_USAGE,
+ "format: cannot lookup on %s", lkey);
+ /* masked lookup key */
+ if (*av != NULL) {
+ switch (i) {
+ case LOOKUP_DST_PORT:
+ case LOOKUP_SRC_PORT:
+ case LOOKUP_UID:
+ case LOOKUP_JAIL:
+ case LOOKUP_DSCP:
+ case LOOKUP_MARK:
+ case LOOKUP_RULENUM:
+ break;
+ default:
+ errx(EX_USAGE,
+ "masked lookup is not supported "
+ "for %s", lkey);
+ }
+ cmd->len |= F_INSN_SIZE(ipfw_insn_table);
+ c->value = strtoul(*av, NULL, 0);
+ if (c->value == 0)
+ errx(EX_USAGE,
+ "all-zeroes bitmask for lookup "
+ "is meaningless");
+ } else {
+ cmd->len |= F_INSN_SIZE(ipfw_insn_kidx);
+ }
+ CHECK_CMDLEN;
+
+ IPFW_SET_LOOKUP_TYPE(cmd, i);
+ av++;
+ c->kidx = pack_table(tstate, *av);
+ if (c->kidx == 0)
+ errx(EX_DATAERR,
+ "Invalid table name: %s", *av);
+ av++;
+ }
+ break;
+ case TOK_FLOW:
+ NEED1("missing table name");
+ if (strncmp(*av, "table(", 6) != 0)
+ errx(EX_DATAERR,
+ "enclose table name into \"table()\"");
+ fill_table(cmd, *av, O_IP_FLOW_LOOKUP, tstate);
+ av++;
+ break;
+
+ case TOK_SKIPACTION:
+ if (have_skipcmd)
+ errx(EX_USAGE, "only one defer-action "
+ "is allowed");
+ have_skipcmd = cmd;
+ fill_cmd(cmd, O_SKIP_ACTION, 0, 0);
+ break;
+
+ case TOK_MARK:
+ NEED1("missing mark value:mask");
+ fill_mark(cmd, *av, cblen);
+ av++;
+ break;
+
+ default:
+ errx(EX_USAGE, "unrecognised option [%d] %s\n", i, s);
+ }
+ if (F_LEN(cmd) > 0) { /* prepare to advance */
+ prev = cmd;
+ cmd = next_cmd(cmd, &cblen);
+ }
+ }
+
+done:
+
+ if (!have_state && have_skipcmd)
+ warnx("Rule contains \"defer-immediate-action\" "
+ "and doesn't contain any state-related options.");
+
+ /*
+ * Now copy stuff into the rule.
+ * If we have a keep-state option, the first instruction
+ * must be a PROBE_STATE (which is generated here).
+ * If we have a LOG option, it was stored as the first command,
+ * and now must be moved to the top of the action part.
+ */
+ dst = (ipfw_insn *)rule->cmd;
+
+ /*
+ * First thing to write into the command stream is the match probability.
+ */
+ if (match_prob != 1) { /* 1 means always match */
+ dst->opcode = O_PROB;
+ dst->len = 2;
+ *((int32_t *)(dst+1)) = (int32_t)(match_prob * 0x7fffffff);
+ dst += dst->len;
+ }
+
+ /*
+ * generate O_PROBE_STATE if necessary
+ */
+ if (have_state && have_state->opcode != O_CHECK_STATE && !have_rstate) {
+ dst->opcode = O_PROBE_STATE;
+ dst->len = F_INSN_SIZE(ipfw_insn_kidx);
+ insntod(dst, kidx)->kidx = insntod(have_state, kidx)->kidx;
+ dst = next_cmd(dst, &rblen);
+ }
+
+ /*
+ * copy all commands but O_LOG, O_KEEP_STATE, O_LIMIT, O_ALTQ, O_TAG,
+ * O_SKIP_ACTION
+ */
+ for (src = (ipfw_insn *)cmdbuf; src != cmd; src += i) {
+ i = F_LEN(src);
+ CHECK_RBUFLEN(i);
+
+ switch (src->opcode) {
+ case O_LOG:
+ case O_KEEP_STATE:
+ case O_LIMIT:
+ case O_ALTQ:
+ case O_TAG:
+ case O_SKIP_ACTION:
+ break;
+ default:
+ bcopy(src, dst, i * sizeof(uint32_t));
+ dst += i;
+ }
+ }
+
+ /*
+ * put back the have_state command as last opcode
+ */
+ if (have_state && have_state->opcode != O_CHECK_STATE) {
+ i = F_LEN(have_state);
+ CHECK_RBUFLEN(i);
+ bcopy(have_state, dst, i * sizeof(uint32_t));
+ dst += i;
+ }
+
+ /*
+ * put back the have_skipcmd command as very last opcode
+ */
+ if (have_skipcmd) {
+ i = F_LEN(have_skipcmd);
+ CHECK_RBUFLEN(i);
+ bcopy(have_skipcmd, dst, i * sizeof(uint32_t));
+ dst += i;
+ }
+
+ /*
+ * start action section
+ */
+ rule->act_ofs = dst - rule->cmd;
+
+ /* put back O_LOG, O_ALTQ, O_TAG if necessary */
+ if (have_log) {
+ i = F_LEN(have_log);
+ CHECK_RBUFLEN(i);
+ bcopy(have_log, dst, i * sizeof(uint32_t));
+ dst += i;
+ }
+ if (have_altq) {
+ i = F_LEN(have_altq);
+ CHECK_RBUFLEN(i);
+ bcopy(have_altq, dst, i * sizeof(uint32_t));
+ dst += i;
+ }
+ if (have_tag) {
+ i = F_LEN(have_tag);
+ CHECK_RBUFLEN(i);
+ bcopy(have_tag, dst, i * sizeof(uint32_t));
+ dst += i;
+ }
+
+ /*
+ * copy all other actions
+ */
+ for (src = (ipfw_insn *)actbuf; src != action; src += i) {
+ i = F_LEN(src);
+ CHECK_RBUFLEN(i);
+ bcopy(src, dst, i * sizeof(uint32_t));
+ dst += i;
+ }
+
+ rule->cmd_len = (uint32_t *)dst - (uint32_t *)(rule->cmd);
+ *rbufsize = (char *)dst - (char *)rule;
+}
+
+static int
+compare_ntlv(const void *_a, const void *_b)
+{
+ const ipfw_obj_ntlv *a, *b;
+
+ a = (const ipfw_obj_ntlv *)_a;
+ b = (const ipfw_obj_ntlv *)_b;
+
+ if (a->set < b->set)
+ return (-1);
+ else if (a->set > b->set)
+ return (1);
+
+ if (a->idx < b->idx)
+ return (-1);
+ else if (a->idx > b->idx)
+ return (1);
+
+ if (a->head.type < b->head.type)
+ return (-1);
+ else if (a->head.type > b->head.type)
+ return (1);
+
+ return (0);
+}
+
+/*
+ * Provide kernel with sorted list of referenced objects
+ */
+static void
+object_sort_ctlv(ipfw_obj_ctlv *ctlv)
+{
+
+ qsort(ctlv + 1, ctlv->count, ctlv->objsize, compare_ntlv);
+}
+
+struct object_kt {
+ uint32_t uidx;
+ uint16_t type;
+};
+static int
+compare_object_kntlv(const void *k, const void *v)
+{
+ const ipfw_obj_ntlv *ntlv;
+ struct object_kt key;
+
+ key = *((const struct object_kt *)k);
+ ntlv = (const ipfw_obj_ntlv *)v;
+
+ if (key.uidx < ntlv->idx)
+ return (-1);
+ else if (key.uidx > ntlv->idx)
+ return (1);
+
+ if (key.type < ntlv->head.type)
+ return (-1);
+ else if (key.type > ntlv->head.type)
+ return (1);
+
+ return (0);
+}
+
+/*
+ * Finds object name in @ctlv by @idx and @type.
+ * Uses the following facts:
+ * 1) All TLVs are the same size
+ * 2) Kernel implementation provides already sorted list.
+ *
+ * Returns table name or NULL.
+ */
+static char *
+object_search_ctlv(ipfw_obj_ctlv *ctlv, uint32_t idx, uint16_t type)
+{
+ ipfw_obj_ntlv *ntlv;
+ struct object_kt key;
+
+ key.uidx = idx;
+ key.type = type;
+
+ ntlv = bsearch(&key, (ctlv + 1), ctlv->count, ctlv->objsize,
+ compare_object_kntlv);
+
+ if (ntlv != NULL)
+ return (ntlv->name);
+
+ return (NULL);
+}
+
+static char *
+table_search_ctlv(ipfw_obj_ctlv *ctlv, uint32_t idx)
+{
+
+ return (object_search_ctlv(ctlv, idx, IPFW_TLV_TBL_NAME));
+}
+
+/*
+ * Adds one or more rules to ipfw chain.
+ * Data layout:
+ * Request:
+ * [
+ * ip_fw3_opheader
+ * [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional *1)
+ * [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) [ ip_fw_rule ip_fw_insn ] x N ] (*2) (*3)
+ * ]
+ * Reply:
+ * [
+ * ip_fw3_opheader
+ * [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional)
+ * [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) [ ip_fw_rule ip_fw_insn ] x N ]
+ * ]
+ *
+ * Rules in reply are modified to store their actual ruleset number.
+ *
+ * (*1) TLVs inside IPFW_TLV_TBL_LIST needs to be sorted ascending
+ * according to their idx field and there has to be no duplicates.
+ * (*2) Numbered rules inside IPFW_TLV_RULE_LIST needs to be sorted ascending.
+ * (*3) Each ip_fw structure needs to be aligned to u64 boundary.
+ */
+void
+ipfw_add(char *av[])
+{
+ uint32_t rulebuf[1024];
+ int rbufsize, default_off, tlen, rlen;
+ size_t sz;
+ struct tidx ts;
+ struct ip_fw_rule *rule;
+ caddr_t tbuf;
+ ip_fw3_opheader *op3;
+ ipfw_obj_ctlv *ctlv, *tstate;
+
+ rbufsize = sizeof(rulebuf);
+ memset(rulebuf, 0, rbufsize);
+ memset(&ts, 0, sizeof(ts));
+
+ /* Optimize case with no tables */
+ default_off = sizeof(ipfw_obj_ctlv) + sizeof(ip_fw3_opheader);
+ op3 = (ip_fw3_opheader *)rulebuf;
+ ctlv = (ipfw_obj_ctlv *)(op3 + 1);
+ rule = (struct ip_fw_rule *)(ctlv + 1);
+ rbufsize -= default_off;
+
+ compile_rule(av, (uint32_t *)rule, &rbufsize, &ts);
+ /* Align rule size to u64 boundary */
+ rlen = roundup2(rbufsize, sizeof(uint64_t));
+
+ tbuf = NULL;
+ sz = 0;
+ tstate = NULL;
+ if (ts.count != 0) {
+ /* Some tables. We have to alloc more data */
+ tlen = ts.count * sizeof(ipfw_obj_ntlv);
+ sz = default_off + sizeof(ipfw_obj_ctlv) + tlen + rlen;
+
+ if ((tbuf = calloc(1, sz)) == NULL)
+ err(EX_UNAVAILABLE, "malloc() failed for IP_FW_XADD");
+ op3 = (ip_fw3_opheader *)tbuf;
+ /* Tables first */
+ ctlv = (ipfw_obj_ctlv *)(op3 + 1);
+ ctlv->head.type = IPFW_TLV_TBLNAME_LIST;
+ ctlv->head.length = sizeof(ipfw_obj_ctlv) + tlen;
+ ctlv->count = ts.count;
+ ctlv->objsize = sizeof(ipfw_obj_ntlv);
+ memcpy(ctlv + 1, ts.idx, tlen);
+ object_sort_ctlv(ctlv);
+ tstate = ctlv;
+ /* Rule next */
+ ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv + ctlv->head.length);
+ ctlv->head.type = IPFW_TLV_RULE_LIST;
+ ctlv->head.length = sizeof(ipfw_obj_ctlv) + rlen;
+ ctlv->count = 1;
+ memcpy(ctlv + 1, rule, rbufsize);
+ } else {
+ /* Simply add header */
+ sz = rlen + default_off;
+ memset(ctlv, 0, sizeof(*ctlv));
+ ctlv->head.type = IPFW_TLV_RULE_LIST;
+ ctlv->head.length = sizeof(ipfw_obj_ctlv) + rlen;
+ ctlv->count = 1;
+ }
+
+ if (do_get3(IP_FW_XADD, op3, &sz) != 0)
+ err(EX_UNAVAILABLE, "getsockopt(%s)", "IP_FW_XADD");
+
+ if (!g_co.do_quiet) {
+ struct format_opts sfo;
+ struct buf_pr bp;
+ memset(&sfo, 0, sizeof(sfo));
+ sfo.tstate = tstate;
+ sfo.set_mask = (uint32_t)(-1);
+ bp_alloc(&bp, 4096);
+ show_static_rule(&g_co, &sfo, &bp, rule, NULL);
+ printf("%s", bp.buf);
+ bp_free(&bp);
+ }
+
+ if (tbuf != NULL)
+ free(tbuf);
+
+ if (ts.idx != NULL)
+ free(ts.idx);
+}
+
+/*
+ * clear the counters or the log counters.
+ * optname has the following values:
+ * 0 (zero both counters and logging)
+ * 1 (zero logging only)
+ */
+void
+ipfw_zero(int ac, char *av[], int optname)
+{
+ ipfw_range_tlv rt;
+ char const *errstr;
+ char const *name = optname ? "RESETLOG" : "ZERO";
+ uint32_t arg;
+ int failed = EX_OK;
+
+ optname = optname ? IP_FW_XRESETLOG : IP_FW_XZERO;
+ av++; ac--;
+
+ if (ac == 0) {
+ /* clear all entries */
+ memset(&rt, 0, sizeof(rt));
+ rt.flags = IPFW_RCFLAG_ALL;
+ if (do_range_cmd(optname, &rt) < 0)
+ err(EX_UNAVAILABLE, "setsockopt(IP_FW_X%s)", name);
+ if (!g_co.do_quiet)
+ printf("%s.\n", optname == IP_FW_XZERO ?
+ "Accounting cleared":"Logging counts reset");
+
+ return;
+ }
+
+ while (ac) {
+ /* Rule number */
+ if (isdigit(**av)) {
+ arg = strtonum(*av, 0, 0xffff, &errstr);
+ if (errstr)
+ errx(EX_DATAERR,
+ "invalid rule number %s\n", *av);
+ memset(&rt, 0, sizeof(rt));
+ rt.start_rule = arg;
+ rt.end_rule = arg;
+ rt.flags |= IPFW_RCFLAG_RANGE;
+ if (g_co.use_set != 0) {
+ rt.set = g_co.use_set - 1;
+ rt.flags |= IPFW_RCFLAG_SET;
+ }
+ if (do_range_cmd(optname, &rt) != 0) {
+ warn("rule %u: setsockopt(IP_FW_X%s)",
+ arg, name);
+ failed = EX_UNAVAILABLE;
+ } else if (rt.new_set == 0) {
+ printf("Entry %d not found\n", arg);
+ failed = EX_UNAVAILABLE;
+ } else if (!g_co.do_quiet)
+ printf("Entry %d %s.\n", arg,
+ optname == IP_FW_XZERO ?
+ "cleared" : "logging count reset");
+ } else {
+ errx(EX_USAGE, "invalid rule number ``%s''", *av);
+ }
+ av++; ac--;
+ }
+ if (failed != EX_OK)
+ exit(failed);
+}
+
+void
+ipfw_flush(int force)
+{
+ ipfw_range_tlv rt;
+
+ if (!force && !g_co.do_quiet) { /* need to ask user */
+ int c;
+
+ printf("Are you sure? [yn] ");
+ fflush(stdout);
+ do {
+ c = toupper(getc(stdin));
+ while (c != '\n' && getc(stdin) != '\n')
+ if (feof(stdin))
+ return; /* and do not flush */
+ } while (c != 'Y' && c != 'N');
+ printf("\n");
+ if (c == 'N') /* user said no */
+ return;
+ }
+ if (g_co.do_pipe) {
+ dummynet_flush();
+ return;
+ }
+ /* `ipfw set N flush` - is the same that `ipfw delete set N` */
+ memset(&rt, 0, sizeof(rt));
+ if (g_co.use_set != 0) {
+ rt.set = g_co.use_set - 1;
+ rt.flags = IPFW_RCFLAG_SET;
+ } else
+ rt.flags = IPFW_RCFLAG_ALL;
+ if (do_range_cmd(IP_FW_XDEL, &rt) != 0)
+ err(EX_UNAVAILABLE, "setsockopt(IP_FW_XDEL)");
+ if (!g_co.do_quiet)
+ printf("Flushed all %s.\n", g_co.do_pipe ? "pipes" : "rules");
+}
+
+static struct _s_x intcmds[] = {
+ { "talist", TOK_TALIST },
+ { "iflist", TOK_IFLIST },
+ { "olist", TOK_OLIST },
+ { "vlist", TOK_VLIST },
+ { "monitor", TOK_MONITOR },
+ { NULL, 0 }
+};
+
+static struct _s_x otypes[] = {
+ { "EACTION", IPFW_TLV_EACTION },
+ { "DYNSTATE", IPFW_TLV_STATE_NAME },
+ { NULL, 0 }
+};
+
+static const char*
+lookup_eaction_name(ipfw_obj_ntlv *ntlv, int cnt, uint16_t type)
+{
+ const char *name;
+ int i;
+
+ name = NULL;
+ for (i = 0; i < cnt; i++) {
+ if (ntlv[i].head.type != IPFW_TLV_EACTION)
+ continue;
+ if (IPFW_TLV_EACTION_NAME(ntlv[i].idx) != type)
+ continue;
+ name = ntlv[i].name;
+ break;
+ }
+ return (name);
+}
+
+static void
+ipfw_list_objects(int ac __unused, char *av[] __unused)
+{
+ ipfw_obj_lheader req, *olh;
+ ipfw_obj_ntlv *ntlv;
+ const char *name;
+ size_t sz;
+ uint32_t i;
+
+ memset(&req, 0, sizeof(req));
+ sz = sizeof(req);
+ if (do_get3(IP_FW_DUMP_SRVOBJECTS, &req.opheader, &sz) != 0)
+ if (errno != ENOMEM)
+ return;
+
+ sz = req.size;
+ if ((olh = calloc(1, sz)) == NULL)
+ return;
+
+ olh->size = sz;
+ if (do_get3(IP_FW_DUMP_SRVOBJECTS, &olh->opheader, &sz) != 0) {
+ free(olh);
+ return;
+ }
+
+ if (olh->count > 0)
+ printf("Objects list:\n");
+ else
+ printf("There are no objects\n");
+ ntlv = (ipfw_obj_ntlv *)(olh + 1);
+ for (i = 0; i < olh->count; i++) {
+ name = match_value(otypes, ntlv->head.type);
+ if (name == NULL)
+ name = lookup_eaction_name(
+ (ipfw_obj_ntlv *)(olh + 1), olh->count,
+ ntlv->head.type);
+ if (name == NULL)
+ printf(" kidx: %4d\ttype: %10d\tname: %s\n",
+ ntlv->idx, ntlv->head.type, ntlv->name);
+ else
+ printf(" kidx: %4d\ttype: %10s\tname: %s\n",
+ ntlv->idx, name, ntlv->name);
+ ntlv++;
+ }
+ free(olh);
+}
+
+void
+ipfw_internal_handler(int ac, char *av[])
+{
+ int tcmd;
+
+ ac--; av++;
+ NEED1("internal cmd required");
+
+ if ((tcmd = match_token(intcmds, *av)) == -1)
+ errx(EX_USAGE, "invalid internal sub-cmd: %s", *av);
+
+ switch (tcmd) {
+ case TOK_IFLIST:
+ ipfw_list_tifaces();
+ break;
+ case TOK_TALIST:
+ ipfw_list_ta(ac, av);
+ break;
+ case TOK_OLIST:
+ ipfw_list_objects(ac, av);
+ break;
+ case TOK_VLIST:
+ ipfw_list_values(ac, av);
+ break;
+ }
+}
+
+static int
+ipfw_get_tracked_ifaces(ipfw_obj_lheader **polh)
+{
+ ipfw_obj_lheader req, *olh;
+ size_t sz;
+
+ memset(&req, 0, sizeof(req));
+ sz = sizeof(req);
+
+ if (do_get3(IP_FW_XIFLIST, &req.opheader, &sz) != 0) {
+ if (errno != ENOMEM)
+ return (errno);
+ }
+
+ sz = req.size;
+ if ((olh = calloc(1, sz)) == NULL)
+ return (ENOMEM);
+
+ olh->size = sz;
+ if (do_get3(IP_FW_XIFLIST, &olh->opheader, &sz) != 0) {
+ free(olh);
+ return (errno);
+ }
+
+ *polh = olh;
+ return (0);
+}
+
+static int
+ifinfo_cmp(const void *a, const void *b)
+{
+ const ipfw_iface_info *ia, *ib;
+
+ ia = (const ipfw_iface_info *)a;
+ ib = (const ipfw_iface_info *)b;
+
+ return (stringnum_cmp(ia->ifname, ib->ifname));
+}
+
+/*
+ * Retrieves table list from kernel,
+ * optionally sorts it and calls requested function for each table.
+ * Returns 0 on success.
+ */
+static void
+ipfw_list_tifaces(void)
+{
+ ipfw_obj_lheader *olh = NULL;
+ ipfw_iface_info *info;
+ uint32_t i;
+ int error;
+
+ if ((error = ipfw_get_tracked_ifaces(&olh)) != 0)
+ err(EX_OSERR, "Unable to request ipfw tracked interface list");
+
+ qsort(olh + 1, olh->count, olh->objsize, ifinfo_cmp);
+
+ info = (ipfw_iface_info *)(olh + 1);
+ for (i = 0; i < olh->count; i++) {
+ if (info->flags & IPFW_IFFLAG_RESOLVED)
+ printf("%s ifindex: %d refcount: %u changes: %u\n",
+ info->ifname, info->ifindex, info->refcnt,
+ info->gencnt);
+ else
+ printf("%s ifindex: unresolved refcount: %u changes: %u\n",
+ info->ifname, info->refcnt, info->gencnt);
+ info = (ipfw_iface_info *)((caddr_t)info + olh->objsize);
+ }
+
+ free(olh);
+}
diff --git a/sbin/ipfw15/ipv6.c b/sbin/ipfw15/ipv6.c
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/ipv6.c
@@ -0,0 +1,519 @@
+/*-
+ * 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
+ *
+ * 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_fw15.h>
+#include <arpa/inet.h>
+
+#define CHECK_LENGTH(v, len) do { \
+ if ((v) < (len)) \
+ errx(EX_DATAERR, "Rule too long"); \
+ } while (0)
+
+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 }
+};
+
+uint16_t
+get_unreach6_code(const 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);
+ return (val);
+}
+
+void
+print_unreach6_code(struct buf_pr *bp, uint16_t code)
+{
+ char const *s = match_value(icmp6codes, code);
+
+ if (s != NULL)
+ bprintf(bp, "unreach6 %s", s);
+ else
+ bprintf(bp, "unreach6 %u", code);
+}
+
+/*
+ * Print the ip address contained in a command.
+ */
+void
+print_ip6(struct buf_pr *bp, const ipfw_insn_ip6 *cmd)
+{
+ char trad[255];
+ struct hostent *he = NULL;
+ const struct in6_addr *a = &(cmd->addr6);
+ int len, mb;
+
+ len = F_LEN((const ipfw_insn *)cmd) - 1;
+ if (cmd->o.opcode == O_IP6_SRC_ME || cmd->o.opcode == O_IP6_DST_ME) {
+ bprintf(bp, " me6");
+ return;
+ }
+ if (cmd->o.opcode == O_IP6) {
+ bprintf(bp, " 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.
+ */
+ bprintf(bp, " ");
+ for (len = len / 4; len > 0; len -= 2, a += 2) {
+ /* mask length */
+ mb = (cmd->o.opcode == O_IP6_SRC ||
+ cmd->o.opcode == O_IP6_DST) ? 128:
+ contigmask((const uint8_t *)&(a[1]), 128);
+
+ if (mb == 128 && g_co.do_resolv)
+ he = gethostbyaddr((const char *)a, sizeof(*a),
+ AF_INET6);
+
+ if (he != NULL) /* resolved to name */
+ bprintf(bp, "%s", he->h_name);
+ else if (mb == 0) /* any */
+ bprintf(bp, "any");
+ else { /* numeric IP followed by some kind of mask */
+ if (inet_ntop(AF_INET6, a, trad,
+ sizeof(trad)) == NULL)
+ bprintf(bp, "Error ntop in print_ip6\n");
+ bprintf(bp, "%s", trad );
+ if (mb < 0) /* mask not contiguous */
+ bprintf(bp, "/%s", inet_ntop(AF_INET6, &a[1],
+ trad, sizeof(trad)));
+ else if (mb < 128)
+ bprintf(bp, "/%d", mb);
+ }
+ if (len > 2)
+ bprintf(bp, ",");
+ }
+}
+
+void
+fill_icmp6types(ipfw_insn_icmp6 *cmd, char *av, int cblen)
+{
+ uint8_t type;
+
+ CHECK_LENGTH(cblen, (int)F_INSN_SIZE(ipfw_insn_icmp6));
+ memset(cmd, 0, 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(struct buf_pr *bp, const ipfw_insn_u32 *cmd)
+{
+ int i, j;
+ char sep= ' ';
+
+ bprintf(bp, " icmp6types");
+ for (i = 0; i < 7; i++)
+ for (j=0; j < 32; ++j) {
+ if ( (cmd->d[i] & (1 << (j))) == 0)
+ continue;
+ bprintf(bp, "%c%d", sep, (i*32 + j));
+ sep = ',';
+ }
+}
+
+void
+print_flow6id(struct buf_pr *bp, const ipfw_insn_u32 *cmd)
+{
+ uint16_t i, limit = cmd->o.arg1;
+ char sep = ',';
+
+ bprintf(bp, " flow-id ");
+ for( i=0; i < limit; ++i) {
+ if (i == limit - 1)
+ sep = ' ';
+ bprintf(bp, "%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(struct buf_pr *bp, const ipfw_insn *cmd )
+{
+ char sep = ' ';
+
+ bprintf(bp, " extension header:");
+ if (cmd->arg1 & EXT_FRAGMENT) {
+ bprintf(bp, "%cfragmentation", sep);
+ sep = ',';
+ }
+ if (cmd->arg1 & EXT_HOPOPTS) {
+ bprintf(bp, "%chop options", sep);
+ sep = ',';
+ }
+ if (cmd->arg1 & EXT_ROUTING) {
+ bprintf(bp, "%crouting options", sep);
+ sep = ',';
+ }
+ if (cmd->arg1 & EXT_RTHDR0) {
+ bprintf(bp, "%crthdr0", sep);
+ sep = ',';
+ }
+ if (cmd->arg1 & EXT_RTHDR2) {
+ bprintf(bp, "%crthdr2", sep);
+ sep = ',';
+ }
+ if (cmd->arg1 & EXT_DSTOPTS) {
+ bprintf(bp, "%cdestination options", sep);
+ sep = ',';
+ }
+ if (cmd->arg1 & EXT_AH) {
+ bprintf(bp, "%cauthentication header", sep);
+ sep = ',';
+ }
+ if (cmd->arg1 & EXT_ESP) {
+ bprintf(bp, "%cencapsulating 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 address
+ * 03f1::234:123:0342/24 address/masklen
+ * 03f1::234:123:0342/ffff::ffff:ffff 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 cblen, struct tidx *tstate)
+{
+ int len = 0;
+ struct in6_addr *d = &(cmd->addr6);
+ char *oav;
+ /*
+ * 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);
+
+ /* Set the data for "me" opt */
+ if (strcmp(av, "me") == 0 || strcmp(av, "me6") == 0) {
+ cmd->o.len |= F_INSN_SIZE(ipfw_insn);
+ return (1);
+ }
+
+ if (strncmp(av, "table(", 6) == 0) {
+ fill_table(&cmd->o, av, O_IP_DST_LOOKUP, tstate);
+ return (1);
+ }
+
+ oav = av = strdup(av);
+ while (av) {
+ /*
+ * After the address we can have '/' indicating a mask,
+ * or ',' indicating another address follows.
+ */
+
+ char *p, *q;
+ int masklen;
+ char md = '\0';
+
+ CHECK_LENGTH(cblen,
+ 1 + len + 2 * (int)F_INSN_SIZE(struct in6_addr));
+
+ if ((q = strchr(av, ',')) ) {
+ *q = '\0';
+ q++;
+ }
+
+ if ((p = strchr(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 */
+ if (md == '/' && strchr(p, ':')) {
+ if (!inet_pton(AF_INET6, p, &d[1]))
+ errx(EX_DATAERR, "bad mask \"%s\"", p);
+
+ masklen = contigmask((uint8_t *)&(d[1]), 128);
+ } else {
+ 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 */
+
+ av = q;
+
+ /* 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(oav);
+ 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, int cblen)
+{
+ 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) {
+ CHECK_LENGTH(cblen,
+ (int)F_INSN_SIZE(ipfw_insn_u32) + nflow + 1);
+
+ 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, int cblen, struct tidx *tstate)
+{
+
+ fill_ip6((ipfw_insn_ip6 *)cmd, av, cblen, tstate);
+ if (cmd->opcode == O_IP_DST_SET) /* set */
+ cmd->opcode = O_IP_SRC_SET;
+ else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */
+ cmd->opcode = O_IP_SRC_LOOKUP;
+ else 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, int cblen, struct tidx *tstate)
+{
+
+ fill_ip6((ipfw_insn_ip6 *)cmd, av, cblen, tstate);
+ if (cmd->opcode == O_IP_DST_SET) /* set */
+ ;
+ else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */
+ ;
+ else 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;
+}
diff --git a/sbin/ipfw15/main.c b/sbin/ipfw15/main.c
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/main.c
@@ -0,0 +1,716 @@
+/*-
+ * Copyright (c) 2002-2003,2010 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.
+ *
+ * Command line interface for IP firewall facility
+ */
+
+#include <sys/wait.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <libgen.h>
+
+#include "ipfw2.h"
+
+static void
+help(void)
+{
+ if (is_ipfw()) {
+ fprintf(stderr,
+"IPFW compatibility binary from RELENG_15\n\n"
+"ipfw syntax summary (but please do read the ipfw(8) manpage):\n\n"
+"\tipfw [-abcdefhnNqStTv] <command>\n\n"
+"where <command> is one of the following:\n\n"
+"add [num] [set N] [prob x] RULE-BODY\n"
+"{pipe|queue} N config PIPE-BODY\n"
+"[pipe|queue] {zero|delete|show} [N{,N}]\n"
+"nat N config {ip IPADDR|if IFNAME|log|deny_in|same_ports|unreg_only|unreg_cgn|udp_eim|\n"
+" reset|reverse|proxy_only|redirect_addr linkspec|\n"
+" redirect_port linkspec|redirect_proto linkspec|\n"
+" port_range lower-upper}\n"
+"set [disable N... enable N...] | move [rule] X to Y | swap X Y | show\n"
+"set N {show|list|zero|resetlog|delete} [N{,N}] | flush\n"
+"table N {add ip[/bits] [value] | delete ip[/bits] | flush | list}\n"
+"table all {flush | list}\n"
+"\n"
+"RULE-BODY: check-state [PARAMS] | ACTION [PARAMS] ADDR [OPTION_LIST]\n"
+"ACTION: check-state | allow | count | deny | unreach{,6} CODE |\n"
+" skipto N | {divert|tee} PORT | forward ADDR |\n"
+" pipe N | queue N | nat N | setfib FIB | reass\n"
+"PARAMS: [log [logamount LOGLIMIT]] [altq QUEUE_NAME]\n"
+"ADDR: [ MAC dst src ether_type ] \n"
+" [ ip from IPADDR [ PORT ] to IPADDR [ PORTLIST ] ]\n"
+" [ ipv6|ip6 from IP6ADDR [ PORT ] to IP6ADDR [ PORTLIST ] ]\n"
+"IPADDR: [not] { any | me | ip/bits{x,y,z} | table(t[,v]) | IPLIST }\n"
+"IP6ADDR: [not] { any | me | me6 | ip6/bits | IP6LIST }\n"
+"IP6LIST: { ip6 | ip6/bits }[,IP6LIST]\n"
+"IPLIST: { ip | ip/bits | ip:mask }[,IPLIST]\n"
+"OPTION_LIST: OPTION [OPTION_LIST]\n"
+"OPTION: bridged | diverted | diverted-loopback | diverted-output |\n"
+" {dst-ip|src-ip} IPADDR | {dst-ip6|src-ip6|dst-ipv6|src-ipv6} IP6ADDR |\n"
+" {dst-port|src-port} LIST |\n"
+" estab | frag | {gid|uid} N | icmptypes LIST | in | out | ipid LIST |\n"
+" iplen LIST | ipoptions SPEC | ipprecedence | ipsec | iptos SPEC |\n"
+" ipttl LIST | ipversion VER | keep-state | layer2 | limit ... |\n"
+" icmp6types LIST | ext6hdr LIST | flow-id N[,N] | fib FIB |\n"
+" mac ... | mac-type LIST | proto LIST | {recv|xmit|via} {IF|IPADDR} |\n"
+" setup | {tcpack|tcpseq|tcpwin} NN | tcpflags SPEC | tcpoptions SPEC |\n"
+" tcpdatalen LIST | verrevpath | versrcreach | antispoof\n"
+);
+ } else {
+ fprintf(stderr,
+"IPFW compatibility binary from RELENG_15\n\n"
+"dnctl syntax summary (but please do read the dnctl(8) manpage):\n\n"
+"\tdnctl [-hnsv] <command>\n\n"
+"where <command> is one of the following:\n\n"
+"[pipe|queue|sched] N config PIPE-BODY\n"
+"[pipe|queue|sched] {delete|list|show} [N{,N}]\n"
+"\n"
+);
+ }
+
+ exit(0);
+}
+
+/*
+ * Called with the arguments, including program name because getopt
+ * wants it to be present.
+ * Returns 0 if successful, 1 if empty command, errx() in case of errors.
+ * First thing we do is process parameters creating an argv[] array
+ * which includes the program name and a NULL entry at the end.
+ * If we are called with a single string, we split it on whitespace.
+ * Also, arguments with a trailing ',' are joined to the next one.
+ * The pointers (av[]) and data are in a single chunk of memory.
+ * av[0] points to the original program name, all other entries
+ * point into the allocated chunk.
+ */
+static int
+ipfw_main(int oldac, char **oldav)
+{
+ int ch, ac;
+ const char *errstr;
+ char **av, **save_av;
+ int do_acct = 0; /* Show packet/byte count */
+ int try_next = 0; /* set if pipe cmd not found */
+ int av_size; /* compute the av size */
+ char *av_p; /* used to build the av list */
+
+#define WHITESP " \t\f\v\n\r"
+ if (oldac < 2)
+ return 1; /* need at least one argument */
+
+ if (oldac == 2) {
+ /*
+ * If we are called with one argument, try to split it into
+ * words for subsequent parsing. Spaces after a ',' are
+ * removed by copying the string in-place.
+ */
+ char *arg = oldav[1]; /* The string is the first arg. */
+ int l = strlen(arg);
+ int copy = 0; /* 1 if we need to copy, 0 otherwise */
+ int i, j;
+
+ for (i = j = 0; i < l; i++) {
+ if (arg[i] == '#') /* comment marker */
+ break;
+ if (copy) {
+ arg[j++] = arg[i];
+ copy = !strchr("," WHITESP, arg[i]);
+ } else {
+ copy = !strchr(WHITESP, arg[i]);
+ if (copy)
+ arg[j++] = arg[i];
+ }
+ }
+ if (!copy && j > 0) /* last char was a 'blank', remove it */
+ j--;
+ l = j; /* the new argument length */
+ arg[j++] = '\0';
+ if (l == 0) /* empty string! */
+ return 1;
+
+ /*
+ * First, count number of arguments. Because of the previous
+ * processing, this is just the number of blanks plus 1.
+ */
+ for (i = 0, ac = 1; i < l; i++)
+ if (strchr(WHITESP, arg[i]) != NULL)
+ ac++;
+
+ /*
+ * Allocate the argument list structure as a single block
+ * of memory, containing pointers and the argument
+ * strings. We include one entry for the program name
+ * because getopt expects it, and a NULL at the end
+ * to simplify further parsing.
+ */
+ ac++; /* add 1 for the program name */
+ av_size = (ac+1) * sizeof(char *) + l + 1;
+ av = safe_calloc(av_size, 1);
+
+ /*
+ * Init the argument pointer to the end of the array
+ * and copy arguments from arg[] to av[]. For each one,
+ * j is the initial character, i is the one past the end.
+ */
+ av_p = (char *)&av[ac+1];
+ for (ac = 1, i = j = 0; i < l; i++) {
+ if (strchr(WHITESP, arg[i]) != NULL || i == l-1) {
+ if (i == l-1)
+ i++;
+ bcopy(arg+j, av_p, i-j);
+ av[ac] = av_p;
+ av_p += i-j; /* the length of the string */
+ *av_p++ = '\0';
+ ac++;
+ j = i + 1;
+ }
+ }
+ } else {
+ /*
+ * If an argument ends with ',' join with the next one.
+ */
+ int first, i, l=0;
+
+ /*
+ * Allocate the argument list structure as a single block
+ * of memory, containing both pointers and the argument
+ * strings. We include some space for the program name
+ * because getopt expects it.
+ * We add an extra pointer to the end of the array,
+ * to make simpler further parsing.
+ */
+ for (i=0; i<oldac; i++)
+ l += strlen(oldav[i]);
+
+ av_size = (oldac+1) * sizeof(char *) + l + oldac;
+ av = safe_calloc(av_size, 1);
+
+ /*
+ * Init the argument pointer to the end of the array
+ * and copy arguments from arg[] to av[]
+ */
+ av_p = (char *)&av[oldac+1];
+ for (first = i = ac = 1, l = 0; i < oldac; i++) {
+ char *arg = oldav[i];
+ int k = strlen(arg);
+
+ l += k;
+ if (arg[k-1] != ',' || i == oldac-1) {
+ /* Time to copy. */
+ av[ac] = av_p;
+ for (l=0; first <= i; first++) {
+ strcat(av_p, oldav[first]);
+ av_p += strlen(oldav[first]);
+ }
+ *av_p++ = '\0';
+ ac++;
+ l = 0;
+ first = i+1;
+ }
+ }
+ }
+
+ /*
+ * set the progname pointer to the original string
+ * and terminate the array with null
+ */
+ av[0] = oldav[0];
+ av[ac] = NULL;
+
+ /* Set the force flag for non-interactive processes */
+ if (!g_co.do_force)
+ g_co.do_force = !isatty(STDIN_FILENO);
+
+#ifdef EMULATE_SYSCTL /* sysctl emulation */
+ if (is_ipfw() && ac >= 2 &&
+ !strcmp(av[1], "sysctl")) {
+ char *s;
+ int i;
+
+ if (ac != 3) {
+ printf( "sysctl emulation usage:\n"
+ " ipfw sysctl name[=value]\n"
+ " ipfw sysctl -a\n");
+ return 0;
+ }
+ s = strchr(av[2], '=');
+ if (s == NULL) {
+ s = !strcmp(av[2], "-a") ? NULL : av[2];
+ sysctlbyname(s, NULL, NULL, NULL, 0);
+ } else { /* ipfw sysctl x.y.z=value */
+ /* assume an INT value, will extend later */
+ if (s[1] == '\0') {
+ printf("ipfw sysctl: missing value\n\n");
+ return 0;
+ }
+ *s = '\0';
+ i = strtol(s+1, NULL, 0);
+ sysctlbyname(av[2], NULL, NULL, &i, sizeof(int));
+ }
+ return 0;
+ }
+#endif
+
+ /* Save arguments for final freeing of memory. */
+ save_av = av;
+
+ optind = optreset = 1; /* restart getopt() */
+ if (is_ipfw()) {
+ while ((ch = getopt(ac, av, "abcdDefhinNp:qs:STtvx")) != -1)
+ switch (ch) {
+ case 'a':
+ do_acct = 1;
+ break;
+
+ case 'b':
+ g_co.comment_only = 1;
+ g_co.do_compact = 1;
+ break;
+
+ case 'c':
+ g_co.do_compact = 1;
+ break;
+
+ case 'd':
+ g_co.do_dynamic = 1;
+ break;
+
+ case 'D':
+ g_co.do_dynamic = 2;
+ break;
+
+ case 'e':
+ /* nop for compatibility */
+ break;
+
+ case 'f':
+ g_co.do_force = 1;
+ break;
+
+ case 'h': /* help */
+ free(save_av);
+ help();
+ break; /* NOTREACHED */
+
+ case 'i':
+ g_co.do_value_as_ip = 1;
+ break;
+
+ case 'n':
+ g_co.test_only = 1;
+ break;
+
+ case 'N':
+ g_co.do_resolv = 1;
+ break;
+
+ case 'p':
+ errx(EX_USAGE, "An absolute pathname must be used "
+ "with -p option.");
+ /* NOTREACHED */
+
+ case 'q':
+ g_co.do_quiet = 1;
+ break;
+
+ case 's': /* sort */
+ g_co.do_sort = atoi(optarg);
+ break;
+
+ case 'S':
+ g_co.show_sets = 1;
+ break;
+
+ case 't':
+ g_co.do_time = TIMESTAMP_STRING;
+ break;
+
+ case 'T':
+ g_co.do_time = TIMESTAMP_NUMERIC;
+ break;
+
+ case 'v': /* verbose */
+ g_co.verbose = 1;
+ break;
+
+ case 'x': /* debug output */
+ g_co.debug_only = 1;
+ break;
+
+ default:
+ free(save_av);
+ return 1;
+ }
+ } else {
+ while ((ch = getopt(ac, av, "hns:v")) != -1)
+ switch (ch) {
+
+ case 'h': /* help */
+ free(save_av);
+ help();
+ break; /* NOTREACHED */
+
+ case 'n':
+ g_co.test_only = 1;
+ break;
+
+ case 's': /* sort */
+ g_co.do_sort = atoi(optarg);
+ break;
+
+ case 'v': /* verbose */
+ g_co.verbose = 1;
+ break;
+
+ default:
+ free(save_av);
+ return 1;
+ }
+
+ }
+
+ ac -= optind;
+ av += optind;
+ NEED1("bad arguments, for usage summary ``ipfw''");
+
+ /*
+ * An undocumented behaviour of ipfw1 was to allow rule numbers first,
+ * e.g. "100 add allow ..." instead of "add 100 allow ...".
+ * In case, swap first and second argument to get the normal form.
+ */
+ if (ac > 1 && isdigit(*av[0])) {
+ char *p = av[0];
+
+ av[0] = av[1];
+ av[1] = p;
+ }
+
+ /*
+ * Optional: pipe, queue or nat.
+ */
+ g_co.do_nat = 0;
+ g_co.do_pipe = 0;
+ g_co.use_set = 0;
+ if (is_ipfw() && !strncmp(*av, "nat", strlen(*av)))
+ g_co.do_nat = 1;
+ else if (!strncmp(*av, "pipe", strlen(*av)))
+ g_co.do_pipe = 1;
+ else if (_substrcmp(*av, "queue") == 0)
+ g_co.do_pipe = 2;
+ else if (_substrcmp(*av, "flowset") == 0)
+ g_co.do_pipe = 2;
+ else if (_substrcmp(*av, "sched") == 0)
+ g_co.do_pipe = 3;
+ else if (is_ipfw() && !strncmp(*av, "set", strlen(*av))) {
+ if (ac > 1 && isdigit(av[1][0])) {
+ g_co.use_set = strtonum(av[1], 0, resvd_set_number,
+ &errstr);
+ if (errstr)
+ errx(EX_DATAERR,
+ "invalid set number %s\n", av[1]);
+ ac -= 2; av += 2; g_co.use_set++;
+ }
+ }
+
+ if (g_co.do_pipe || g_co.do_nat) {
+ ac--;
+ av++;
+ }
+ NEED1("missing command");
+
+ /*
+ * For pipes, queues and nats we normally say 'nat|pipe NN config'
+ * but the code is easier to parse as 'nat|pipe config NN'
+ * so we swap the two arguments.
+ */
+ if ((g_co.do_pipe || g_co.do_nat) && ac > 1 && isdigit(*av[0])) {
+ char *p = av[0];
+
+ av[0] = av[1];
+ av[1] = p;
+ }
+
+ if (! is_ipfw() && g_co.do_pipe == 0) {
+ help();
+ }
+
+ if (g_co.use_set == 0) {
+ if (is_ipfw() && _substrcmp(*av, "add") == 0)
+ ipfw_add(av);
+ else if (g_co.do_nat && _substrcmp(*av, "show") == 0)
+ ipfw_show_nat(ac, av);
+ else if (g_co.do_pipe && _substrcmp(*av, "config") == 0)
+ ipfw_config_pipe(ac, av);
+ else if (g_co.do_nat && _substrcmp(*av, "config") == 0)
+ ipfw_config_nat(ac, av);
+ else if (is_ipfw() && _substrcmp(*av, "set") == 0)
+ ipfw_sets_handler(av);
+ else if (is_ipfw() && _substrcmp(*av, "table") == 0)
+ ipfw_table_handler(ac, av);
+ else if (is_ipfw() && _substrcmp(*av, "enable") == 0)
+ ipfw_sysctl_handler(av, 1);
+ else if (is_ipfw() && _substrcmp(*av, "disable") == 0)
+ ipfw_sysctl_handler(av, 0);
+ else
+ try_next = 1;
+ }
+
+ if (g_co.use_set || try_next) {
+ if (_substrcmp(*av, "delete") == 0)
+ ipfw_delete(av);
+ else if (is_ipfw() && !strncmp(*av, "nat64clat", strlen(*av)))
+ ipfw_nat64clat_handler(ac, av);
+ else if (is_ipfw() && !strncmp(*av, "nat64stl", strlen(*av)))
+ ipfw_nat64stl_handler(ac, av);
+ else if (is_ipfw() && !strncmp(*av, "nat64lsn", strlen(*av)))
+ ipfw_nat64lsn_handler(ac, av);
+ else if (is_ipfw() && !strncmp(*av, "nptv6", strlen(*av)))
+ ipfw_nptv6_handler(ac, av);
+ else if (_substrcmp(*av, "flush") == 0)
+ ipfw_flush(g_co.do_force);
+ else if (is_ipfw() && _substrcmp(*av, "zero") == 0)
+ ipfw_zero(ac, av, 0 /* IP_FW_ZERO */);
+ else if (is_ipfw() && _substrcmp(*av, "resetlog") == 0)
+ ipfw_zero(ac, av, 1 /* IP_FW_RESETLOG */);
+ else if (_substrcmp(*av, "print") == 0 ||
+ _substrcmp(*av, "list") == 0)
+ ipfw_list(ac, av, do_acct);
+ else if (_substrcmp(*av, "show") == 0)
+ ipfw_list(ac, av, 1 /* show counters */);
+ else if (is_ipfw() && _substrcmp(*av, "table") == 0)
+ ipfw_table_handler(ac, av);
+ else if (is_ipfw() && _substrcmp(*av, "internal") == 0)
+ ipfw_internal_handler(ac, av);
+ else
+ errx(EX_USAGE, "bad command `%s'", *av);
+ }
+
+ /* Free memory allocated in the argument parsing. */
+ free(save_av);
+ return 0;
+}
+
+
+static void
+ipfw_readfile(int ac, char *av[])
+{
+#define MAX_ARGS 32
+ char buf[4096];
+ char *progname = av[0]; /* original program name */
+ const char *cmd = NULL; /* preprocessor name, if any */
+ const char *filename = av[ac-1]; /* file to read */
+ int c, lineno=0;
+ FILE *f = NULL;
+ pid_t preproc = 0;
+
+ if (is_ipfw()) {
+ while ((c = getopt(ac, av, "cfNnp:qS")) != -1) {
+ switch(c) {
+ case 'c':
+ g_co.do_compact = 1;
+ break;
+
+ case 'f':
+ g_co.do_force = 1;
+ break;
+
+ case 'N':
+ g_co.do_resolv = 1;
+ break;
+
+ case 'n':
+ g_co.test_only = 1;
+ break;
+
+ case 'p':
+ /*
+ * ipfw -p cmd [args] filename
+ *
+ * We are done with getopt(). All arguments
+ * except the filename go to the preprocessor,
+ * so we need to do the following:
+ * - check that a filename is actually present;
+ * - advance av by optind-1 to skip arguments
+ * already processed;
+ * - decrease ac by optind, to remove the args
+ * already processed and the final filename;
+ * - set the last entry in av[] to NULL so
+ * popen() can detect the end of the array;
+ * - set optind=ac to let getopt() terminate.
+ */
+ if (optind == ac)
+ errx(EX_USAGE, "no filename argument");
+ cmd = optarg;
+ av[ac-1] = NULL;
+ av += optind - 1;
+ ac -= optind;
+ optind = ac;
+ break;
+
+ case 'q':
+ g_co.do_quiet = 1;
+ break;
+
+ case 'S':
+ g_co.show_sets = 1;
+ break;
+
+ default:
+ errx(EX_USAGE, "bad arguments, for usage"
+ " summary ``ipfw''");
+ }
+ }
+ } else {
+ while ((c = getopt(ac, av, "nq")) != -1) {
+ switch(c) {
+ case 'n':
+ g_co.test_only = 1;
+ break;
+
+ case 'q':
+ g_co.do_quiet = 1;
+ break;
+
+ default:
+ errx(EX_USAGE, "bad arguments, for usage"
+ " summary ``dnctl''");
+ }
+ }
+ }
+
+ if (cmd == NULL && ac != optind + 1)
+ errx(EX_USAGE, "extraneous filename arguments %s", av[ac-1]);
+
+ if ((f = fopen(filename, "r")) == NULL)
+ err(EX_UNAVAILABLE, "fopen: %s", filename);
+
+ if (cmd != NULL) { /* pipe through preprocessor */
+ int pipedes[2];
+
+ if (pipe(pipedes) == -1)
+ err(EX_OSERR, "cannot create pipe");
+
+ preproc = fork();
+ if (preproc == -1)
+ err(EX_OSERR, "cannot fork");
+
+ if (preproc == 0) {
+ /*
+ * Child, will run the preprocessor with the
+ * file on stdin and the pipe on stdout.
+ */
+ if (dup2(fileno(f), 0) == -1
+ || dup2(pipedes[1], 1) == -1)
+ err(EX_OSERR, "dup2()");
+ fclose(f);
+ close(pipedes[1]);
+ close(pipedes[0]);
+ execvp(cmd, av);
+ err(EX_OSERR, "execvp(%s) failed", cmd);
+ } else { /* parent, will reopen f as the pipe */
+ fclose(f);
+ close(pipedes[1]);
+ if ((f = fdopen(pipedes[0], "r")) == NULL) {
+ int savederrno = errno;
+
+ (void)kill(preproc, SIGTERM);
+ errno = savederrno;
+ err(EX_OSERR, "fdopen()");
+ }
+ }
+ }
+
+ while (fgets(buf, sizeof(buf), f)) { /* read commands */
+ char linename[20];
+ char *args[2];
+
+ lineno++;
+ snprintf(linename, sizeof(linename), "Line %d", lineno);
+ setprogname(linename); /* XXX */
+ args[0] = progname;
+ args[1] = buf;
+ ipfw_main(2, args);
+ }
+ fclose(f);
+ if (cmd != NULL) {
+ int status;
+
+ if (waitpid(preproc, &status, 0) == -1)
+ errx(EX_OSERR, "waitpid()");
+ if (WIFEXITED(status) && WEXITSTATUS(status) != EX_OK)
+ errx(EX_UNAVAILABLE,
+ "preprocessor exited with status %d",
+ WEXITSTATUS(status));
+ else if (WIFSIGNALED(status))
+ errx(EX_UNAVAILABLE,
+ "preprocessor exited with signal %d",
+ WTERMSIG(status));
+ }
+}
+
+int
+main(int ac, char *av[])
+{
+#if defined(_WIN32) && defined(TCC)
+ {
+ WSADATA wsaData;
+ int ret=0;
+ unsigned short wVersionRequested = MAKEWORD(2, 2);
+ ret = WSAStartup(wVersionRequested, &wsaData);
+ if (ret != 0) {
+ /* Tell the user that we could not find a usable */
+ /* Winsock DLL. */
+ printf("WSAStartup failed with error: %d\n", ret);
+ return 1;
+ }
+ }
+#endif
+
+ if (strcmp("dnctl", basename(av[0])) == 0)
+ g_co.prog = cmdline_prog_dnctl;
+ else if (strcmp("dnctl15", basename(av[0])) == 0)
+ g_co.prog = cmdline_prog_dnctl;
+ else
+ g_co.prog = cmdline_prog_ipfw;
+
+ /*
+ * If the last argument is an absolute pathname, interpret it
+ * as a file to be preprocessed.
+ */
+
+ if (ac > 1 && av[ac - 1][0] == '/') {
+ if (access(av[ac - 1], R_OK) == 0)
+ ipfw_readfile(ac, av);
+ else
+ err(EX_USAGE, "pathname: %s", av[ac - 1]);
+ } else {
+ if (ipfw_main(ac, av)) {
+ errx(EX_USAGE,
+ "usage: %s [options]\n"
+ "do \"%s -h\" or \"man %s\" for details", av[0],
+ av[0], av[0]);
+ }
+ }
+ return EX_OK;
+}
diff --git a/sbin/ipfw15/nat.c b/sbin/ipfw15/nat.c
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/nat.c
@@ -0,0 +1,1196 @@
+/*-
+ * 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
+ *
+ * In-kernel nat support
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include "ipfw2.h"
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/route.h> /* def. of struct route */
+#include <netinet/in.h>
+#include <netinet/ip_fw15.h>
+#include <arpa/inet.h>
+#include <alias15.h>
+
+typedef int (nat_cb_t)(struct nat44_cfg_nat *cfg, void *arg);
+static void nat_show_cfg(struct nat44_cfg_nat *n, void *arg);
+static void nat_show_log(struct nat44_cfg_nat *n, void *arg);
+static int nat_show_data(struct nat44_cfg_nat *cfg, void *arg);
+static int natname_cmp(const void *a, const void *b);
+static int nat_foreach(nat_cb_t *f, void *arg, int sort);
+static int nat_get_cmd(char *name, uint16_t cmd, ipfw_obj_header **ooh);
+
+static struct _s_x nat_params[] = {
+ { "ip", TOK_IP },
+ { "if", TOK_IF },
+ { "log", TOK_ALOG },
+ { "deny_in", TOK_DENY_INC },
+ { "same_ports", TOK_SAME_PORTS },
+ { "unreg_only", TOK_UNREG_ONLY },
+ { "unreg_cgn", TOK_UNREG_CGN },
+ { "skip_global", TOK_SKIP_GLOBAL },
+ { "reset", TOK_RESET_ADDR },
+ { "reverse", TOK_ALIAS_REV },
+ { "proxy_only", TOK_PROXY_ONLY },
+ { "port_range", TOK_PORT_ALIAS },
+ { "redirect_addr", TOK_REDIR_ADDR },
+ { "redirect_port", TOK_REDIR_PORT },
+ { "redirect_proto", TOK_REDIR_PROTO },
+ { "udp_eim", TOK_UDP_EIM },
+ { NULL, 0 } /* terminator */
+};
+
+
+/*
+ * Search for interface with name "ifn", and fill n accordingly:
+ *
+ * n->ip ip address of interface "ifn"
+ * n->if_name copy of interface name "ifn"
+ */
+static void
+set_addr_dynamic(const char *ifn, struct nat44_cfg_nat *n)
+{
+ size_t needed;
+ int mib[6];
+ char *buf, *lim, *next;
+ struct if_msghdr *ifm;
+ struct ifa_msghdr *ifam;
+ struct sockaddr_dl *sdl;
+ struct sockaddr_in *sin;
+ int ifIndex;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET;
+ mib[4] = NET_RT_IFLIST;
+ mib[5] = 0;
+/*
+ * Get interface data.
+ */
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1)
+ err(1, "iflist-sysctl-estimate");
+ buf = safe_calloc(1, needed);
+ if (sysctl(mib, 6, buf, &needed, NULL, 0) == -1)
+ err(1, "iflist-sysctl-get");
+ lim = buf + needed;
+/*
+ * Loop through interfaces until one with
+ * given name is found. This is done to
+ * find correct interface index for routing
+ * message processing.
+ */
+ ifIndex = 0;
+ next = buf;
+ while (next < lim) {
+ ifm = (struct if_msghdr *)next;
+ next += ifm->ifm_msglen;
+ if (ifm->ifm_version != RTM_VERSION) {
+ if (g_co.verbose)
+ warnx("routing message version %d "
+ "not understood", ifm->ifm_version);
+ continue;
+ }
+ if (ifm->ifm_type == RTM_IFINFO) {
+ sdl = (struct sockaddr_dl *)(ifm + 1);
+ if (strlen(ifn) == sdl->sdl_nlen &&
+ strncmp(ifn, sdl->sdl_data, sdl->sdl_nlen) == 0) {
+ ifIndex = ifm->ifm_index;
+ break;
+ }
+ }
+ }
+ if (!ifIndex)
+ errx(1, "unknown interface name %s", ifn);
+/*
+ * Get interface address.
+ */
+ sin = NULL;
+ while (next < lim) {
+ ifam = (struct ifa_msghdr *)next;
+ next += ifam->ifam_msglen;
+ if (ifam->ifam_version != RTM_VERSION) {
+ if (g_co.verbose)
+ warnx("routing message version %d "
+ "not understood", ifam->ifam_version);
+ continue;
+ }
+ if (ifam->ifam_type != RTM_NEWADDR)
+ break;
+ if (ifam->ifam_addrs & RTA_IFA) {
+ int i;
+ char *cp = (char *)(ifam + 1);
+
+ for (i = 1; i < RTA_IFA; i <<= 1) {
+ if (ifam->ifam_addrs & i)
+ cp += SA_SIZE((struct sockaddr *)cp);
+ }
+ if (((struct sockaddr *)cp)->sa_family == AF_INET) {
+ sin = (struct sockaddr_in *)cp;
+ break;
+ }
+ }
+ }
+ if (sin == NULL)
+ n->ip.s_addr = htonl(INADDR_ANY);
+ else
+ n->ip = sin->sin_addr;
+ strncpy(n->if_name, ifn, IF_NAMESIZE);
+
+ free(buf);
+}
+
+/*
+ * XXX - The following functions, macros and definitions come from natd.c:
+ * it would be better to move them outside natd.c, in a file
+ * (redirect_support.[ch]?) shared by ipfw and natd, but for now i can live
+ * with it.
+ */
+
+/*
+ * Definition of a port range, and macros to deal with values.
+ * FORMAT: HI 16-bits == first port in range, 0 == all ports.
+ * LO 16-bits == number of ports in range
+ * NOTES: - Port values are not stored in network byte order.
+ */
+
+#define port_range u_long
+
+#define GETLOPORT(x) ((x) >> 0x10)
+#define GETNUMPORTS(x) ((x) & 0x0000ffff)
+#define GETHIPORT(x) (GETLOPORT((x)) + GETNUMPORTS((x)))
+
+/* Set y to be the low-port value in port_range variable x. */
+#define SETLOPORT(x,y) ((x) = ((x) & 0x0000ffff) | ((y) << 0x10))
+
+/* Set y to be the number of ports in port_range variable x. */
+#define SETNUMPORTS(x,y) ((x) = ((x) & 0xffff0000) | (y))
+
+static void
+StrToAddr (const char* str, struct in_addr* addr)
+{
+ struct hostent* hp;
+
+ if (inet_aton (str, addr))
+ return;
+
+ hp = gethostbyname (str);
+ if (!hp)
+ errx (1, "unknown host %s", str);
+
+ memcpy (addr, hp->h_addr, sizeof (struct in_addr));
+}
+
+static int
+StrToPortRange (const char* str, const char* proto, port_range *portRange)
+{
+ char* sep;
+ struct servent* sp;
+ char* end;
+ u_short loPort;
+ u_short hiPort;
+
+ /* First see if this is a service, return corresponding port if so. */
+ sp = getservbyname (str,proto);
+ if (sp) {
+ SETLOPORT(*portRange, ntohs(sp->s_port));
+ SETNUMPORTS(*portRange, 1);
+ return 0;
+ }
+
+ /* Not a service, see if it's a single port or port range. */
+ sep = strchr (str, '-');
+ if (sep == NULL) {
+ SETLOPORT(*portRange, strtol(str, &end, 10));
+ if (end != str) {
+ /* Single port. */
+ SETNUMPORTS(*portRange, 1);
+ return 0;
+ }
+
+ /* Error in port range field. */
+ errx (EX_DATAERR, "%s/%s: unknown service", str, proto);
+ }
+
+ /* Port range, get the values and sanity check. */
+ sscanf (str, "%hu-%hu", &loPort, &hiPort);
+ SETLOPORT(*portRange, loPort);
+ SETNUMPORTS(*portRange, 0); /* Error by default */
+ if (loPort <= hiPort)
+ SETNUMPORTS(*portRange, hiPort - loPort + 1);
+
+ if (GETNUMPORTS(*portRange) == 0)
+ errx (EX_DATAERR, "invalid port range %s", str);
+
+ return 0;
+}
+
+static int
+StrToProto (const char* str)
+{
+ if (!strcmp (str, "tcp"))
+ return IPPROTO_TCP;
+
+ if (!strcmp (str, "udp"))
+ return IPPROTO_UDP;
+
+ if (!strcmp (str, "sctp"))
+ return IPPROTO_SCTP;
+ errx (EX_DATAERR, "unknown protocol %s. Expected sctp, tcp or udp", str);
+}
+
+static int
+StrToAddrAndPortRange (const char* str, struct in_addr* addr, char* proto,
+ port_range *portRange)
+{
+ char* ptr;
+
+ ptr = strchr (str, ':');
+ if (!ptr)
+ errx (EX_DATAERR, "%s is missing port number", str);
+
+ *ptr = '\0';
+ ++ptr;
+
+ StrToAddr (str, addr);
+ return StrToPortRange (ptr, proto, portRange);
+}
+
+/* End of stuff taken from natd.c. */
+
+/*
+ * The next 3 functions add support for the addr, port and proto redirect and
+ * their logic is loosely based on SetupAddressRedirect(), SetupPortRedirect()
+ * and SetupProtoRedirect() from natd.c.
+ *
+ * Every setup_* function fills at least one redirect entry
+ * (struct nat44_cfg_redir) and zero or more server pool entry
+ * (struct nat44_cfg_spool) in buf.
+ *
+ * The format of data in buf is:
+ *
+ * nat44_cfg_nat nat44_cfg_redir nat44_cfg_spool ...... nat44_cfg_spool
+ *
+ * ------------------------------------- ------------
+ * | | .....X ..... | | | | .....
+ * ------------------------------------- ...... ------------
+ * ^
+ * spool_cnt n=0 ...... n=(X-1)
+ *
+ * len points to the amount of available space in buf
+ * space counts the memory consumed by every function
+ *
+ * XXX - Every function get all the argv params so it
+ * has to check, in optional parameters, that the next
+ * args is a valid option for the redir entry and not
+ * another token. Only redir_port and redir_proto are
+ * affected by this.
+ */
+
+static int
+estimate_redir_addr(int *ac, char ***av)
+{
+ size_t space = sizeof(struct nat44_cfg_redir);
+ char *sep = **av;
+ u_int c = 0;
+
+ (void)ac; /* UNUSED */
+ while ((sep = strchr(sep, ',')) != NULL) {
+ c++;
+ sep++;
+ }
+
+ if (c > 0)
+ c++;
+
+ space += c * sizeof(struct nat44_cfg_spool);
+
+ return (space);
+}
+
+static int
+setup_redir_addr(char *buf, int *ac, char ***av)
+{
+ struct nat44_cfg_redir *r;
+ char *sep;
+ size_t space;
+
+ r = (struct nat44_cfg_redir *)buf;
+ r->mode = REDIR_ADDR;
+ /* Skip nat44_cfg_redir at beginning of buf. */
+ buf = &buf[sizeof(struct nat44_cfg_redir)];
+ space = sizeof(struct nat44_cfg_redir);
+
+ /* Extract local address. */
+ if (strchr(**av, ',') != NULL) {
+ struct nat44_cfg_spool *spool;
+
+ /* Setup LSNAT server pool. */
+ r->laddr.s_addr = INADDR_NONE;
+ sep = strtok(**av, ",");
+ while (sep != NULL) {
+ spool = (struct nat44_cfg_spool *)buf;
+ space += sizeof(struct nat44_cfg_spool);
+ StrToAddr(sep, &spool->addr);
+ spool->port = ~0;
+ r->spool_cnt++;
+ /* Point to the next possible nat44_cfg_spool. */
+ buf = &buf[sizeof(struct nat44_cfg_spool)];
+ sep = strtok(NULL, ",");
+ }
+ } else
+ StrToAddr(**av, &r->laddr);
+ (*av)++; (*ac)--;
+
+ /* Extract public address. */
+ StrToAddr(**av, &r->paddr);
+ (*av)++; (*ac)--;
+
+ return (space);
+}
+
+static int
+estimate_redir_port(int *ac, char ***av)
+{
+ size_t space = sizeof(struct nat44_cfg_redir);
+ char *sep = **av;
+ u_int c = 0;
+
+ (void)ac; /* UNUSED */
+ while ((sep = strchr(sep, ',')) != NULL) {
+ c++;
+ sep++;
+ }
+
+ if (c > 0)
+ c++;
+
+ space += c * sizeof(struct nat44_cfg_spool);
+
+ return (space);
+}
+
+static int
+setup_redir_port(char *buf, int *ac, char ***av)
+{
+ struct nat44_cfg_redir *r;
+ char *sep, *protoName, *lsnat = NULL;
+ size_t space;
+ u_short numLocalPorts;
+ port_range portRange;
+
+ numLocalPorts = 0;
+
+ r = (struct nat44_cfg_redir *)buf;
+ r->mode = REDIR_PORT;
+ /* Skip nat44_cfg_redir at beginning of buf. */
+ buf = &buf[sizeof(struct nat44_cfg_redir)];
+ space = sizeof(struct nat44_cfg_redir);
+
+ /*
+ * Extract protocol.
+ */
+ r->proto = StrToProto(**av);
+ protoName = **av;
+ (*av)++; (*ac)--;
+
+ /*
+ * Extract local address.
+ */
+ if (strchr(**av, ',') != NULL) {
+ r->laddr.s_addr = INADDR_NONE;
+ r->lport = ~0;
+ numLocalPorts = 1;
+ lsnat = **av;
+ } else {
+ /*
+ * The sctp nat does not allow the port numbers to be mapped to
+ * new port numbers. Therefore, no ports are to be specified
+ * in the target port field.
+ */
+ if (r->proto == IPPROTO_SCTP) {
+ if (strchr(**av, ':'))
+ errx(EX_DATAERR, "redirect_port:"
+ "port numbers do not change in sctp, so do "
+ "not specify them as part of the target");
+ else
+ StrToAddr(**av, &r->laddr);
+ } else {
+ if (StrToAddrAndPortRange(**av, &r->laddr, protoName,
+ &portRange) != 0)
+ errx(EX_DATAERR, "redirect_port: "
+ "invalid local port range");
+
+ r->lport = GETLOPORT(portRange);
+ numLocalPorts = GETNUMPORTS(portRange);
+ }
+ }
+ (*av)++; (*ac)--;
+
+ /*
+ * Extract public port and optionally address.
+ */
+ if (strchr(**av, ':') != NULL) {
+ if (StrToAddrAndPortRange(**av, &r->paddr, protoName,
+ &portRange) != 0)
+ errx(EX_DATAERR, "redirect_port: "
+ "invalid public port range");
+ } else {
+ r->paddr.s_addr = INADDR_ANY;
+ if (StrToPortRange(**av, protoName, &portRange) != 0)
+ errx(EX_DATAERR, "redirect_port: "
+ "invalid public port range");
+ }
+
+ r->pport = GETLOPORT(portRange);
+ if (r->proto == IPPROTO_SCTP) { /* so the logic below still works */
+ numLocalPorts = GETNUMPORTS(portRange);
+ r->lport = r->pport;
+ }
+ r->pport_cnt = GETNUMPORTS(portRange);
+ (*av)++; (*ac)--;
+
+ /*
+ * Extract remote address and optionally port.
+ */
+ /*
+ * NB: isdigit(**av) => we've to check that next parameter is really an
+ * option for this redirect entry, else stop here processing arg[cv].
+ */
+ if (*ac != 0 && isdigit(***av)) {
+ if (strchr(**av, ':') != NULL) {
+ if (StrToAddrAndPortRange(**av, &r->raddr, protoName,
+ &portRange) != 0)
+ errx(EX_DATAERR, "redirect_port: "
+ "invalid remote port range");
+ } else {
+ SETLOPORT(portRange, 0);
+ SETNUMPORTS(portRange, 1);
+ StrToAddr(**av, &r->raddr);
+ }
+ (*av)++; (*ac)--;
+ } else {
+ SETLOPORT(portRange, 0);
+ SETNUMPORTS(portRange, 1);
+ r->raddr.s_addr = INADDR_ANY;
+ }
+ r->rport = GETLOPORT(portRange);
+ r->rport_cnt = GETNUMPORTS(portRange);
+
+ /*
+ * Make sure port ranges match up, then add the redirect ports.
+ */
+ if (numLocalPorts != r->pport_cnt)
+ errx(EX_DATAERR, "redirect_port: "
+ "port ranges must be equal in size");
+
+ /* Remote port range is allowed to be '0' which means all ports. */
+ if (r->rport_cnt != numLocalPorts &&
+ (r->rport_cnt != 1 || r->rport != 0))
+ errx(EX_DATAERR, "redirect_port: remote port must"
+ "be 0 or equal to local port range in size");
+
+ /* Setup LSNAT server pool. */
+ if (lsnat != NULL) {
+ struct nat44_cfg_spool *spool;
+
+ sep = strtok(lsnat, ",");
+ while (sep != NULL) {
+ spool = (struct nat44_cfg_spool *)buf;
+ space += sizeof(struct nat44_cfg_spool);
+ /*
+ * The sctp nat does not allow the port numbers to
+ * be mapped to new port numbers. Therefore, no ports
+ * are to be specified in the target port field.
+ */
+ if (r->proto == IPPROTO_SCTP) {
+ if (strchr (sep, ':')) {
+ errx(EX_DATAERR, "redirect_port:"
+ "port numbers do not change in "
+ "sctp, so do not specify them as "
+ "part of the target");
+ } else {
+ StrToAddr(sep, &spool->addr);
+ spool->port = r->pport;
+ }
+ } else {
+ if (StrToAddrAndPortRange(sep, &spool->addr,
+ protoName, &portRange) != 0)
+ errx(EX_DATAERR, "redirect_port:"
+ "invalid local port range");
+ if (GETNUMPORTS(portRange) != 1)
+ errx(EX_DATAERR, "redirect_port: "
+ "local port must be single in "
+ "this context");
+ spool->port = GETLOPORT(portRange);
+ }
+ r->spool_cnt++;
+ /* Point to the next possible nat44_cfg_spool. */
+ buf = &buf[sizeof(struct nat44_cfg_spool)];
+ sep = strtok(NULL, ",");
+ }
+ }
+
+ return (space);
+}
+
+static int
+setup_redir_proto(char *buf, int *ac, char ***av)
+{
+ struct nat44_cfg_redir *r;
+ struct protoent *protoent;
+ size_t space;
+
+ r = (struct nat44_cfg_redir *)buf;
+ r->mode = REDIR_PROTO;
+ /* Skip nat44_cfg_redir at beginning of buf. */
+ buf = &buf[sizeof(struct nat44_cfg_redir)];
+ space = sizeof(struct nat44_cfg_redir);
+
+ /*
+ * Extract protocol.
+ */
+ protoent = getprotobyname(**av);
+ if (protoent == NULL)
+ errx(EX_DATAERR, "redirect_proto: unknown protocol %s", **av);
+ else
+ r->proto = protoent->p_proto;
+
+ (*av)++; (*ac)--;
+
+ /*
+ * Extract local address.
+ */
+ StrToAddr(**av, &r->laddr);
+
+ (*av)++; (*ac)--;
+
+ /*
+ * Extract optional public address.
+ */
+ if (*ac == 0) {
+ r->paddr.s_addr = INADDR_ANY;
+ r->raddr.s_addr = INADDR_ANY;
+ } else {
+ /* see above in setup_redir_port() */
+ if (isdigit(***av)) {
+ StrToAddr(**av, &r->paddr);
+ (*av)++; (*ac)--;
+
+ /*
+ * Extract optional remote address.
+ */
+ /* see above in setup_redir_port() */
+ if (*ac != 0 && isdigit(***av)) {
+ StrToAddr(**av, &r->raddr);
+ (*av)++; (*ac)--;
+ }
+ }
+ }
+
+ return (space);
+}
+
+static void
+nat_show_log(struct nat44_cfg_nat *n, void *arg __unused)
+{
+ char *buf;
+
+ buf = (char *)(n + 1);
+ if (buf[0] != '\0')
+ printf("nat %s: %s\n", n->name, buf);
+}
+
+static void
+nat_show_cfg(struct nat44_cfg_nat *n, void *arg __unused)
+{
+ struct nat44_cfg_redir *t;
+ struct nat44_cfg_spool *s;
+ caddr_t buf;
+ struct protoent *p;
+ uint32_t cnt;
+ int i, off;
+
+ buf = (caddr_t)n;
+ off = sizeof(*n);
+ printf("ipfw nat %s config", n->name);
+ if (strlen(n->if_name) != 0)
+ printf(" if %s", n->if_name);
+ else if (n->ip.s_addr != 0)
+ printf(" ip %s", inet_ntoa(n->ip));
+ while (n->mode != 0) {
+ if (n->mode & PKT_ALIAS_LOG) {
+ printf(" log");
+ n->mode &= ~PKT_ALIAS_LOG;
+ } else if (n->mode & PKT_ALIAS_DENY_INCOMING) {
+ printf(" deny_in");
+ n->mode &= ~PKT_ALIAS_DENY_INCOMING;
+ } else if (n->mode & PKT_ALIAS_SAME_PORTS) {
+ printf(" same_ports");
+ n->mode &= ~PKT_ALIAS_SAME_PORTS;
+ } else if (n->mode & PKT_ALIAS_SKIP_GLOBAL) {
+ printf(" skip_global");
+ n->mode &= ~PKT_ALIAS_SKIP_GLOBAL;
+ } else if (n->mode & PKT_ALIAS_UNREGISTERED_ONLY) {
+ printf(" unreg_only");
+ n->mode &= ~PKT_ALIAS_UNREGISTERED_ONLY;
+ } else if (n->mode & PKT_ALIAS_UNREGISTERED_CGN) {
+ printf(" unreg_cgn");
+ n->mode &= ~PKT_ALIAS_UNREGISTERED_CGN;
+ } else if (n->mode & PKT_ALIAS_RESET_ON_ADDR_CHANGE) {
+ printf(" reset");
+ n->mode &= ~PKT_ALIAS_RESET_ON_ADDR_CHANGE;
+ } else if (n->mode & PKT_ALIAS_REVERSE) {
+ printf(" reverse");
+ n->mode &= ~PKT_ALIAS_REVERSE;
+ } else if (n->mode & PKT_ALIAS_PROXY_ONLY) {
+ printf(" proxy_only");
+ n->mode &= ~PKT_ALIAS_PROXY_ONLY;
+ } else if (n->mode & PKT_ALIAS_UDP_EIM) {
+ printf(" udp_eim");
+ n->mode &= ~PKT_ALIAS_UDP_EIM;
+ }
+ }
+ /* Print all the redirect's data configuration. */
+ for (cnt = 0; cnt < n->redir_cnt; cnt++) {
+ t = (struct nat44_cfg_redir *)&buf[off];
+ off += sizeof(struct nat44_cfg_redir);
+ switch (t->mode) {
+ case REDIR_ADDR:
+ printf(" redirect_addr");
+ if (t->spool_cnt == 0)
+ printf(" %s", inet_ntoa(t->laddr));
+ else
+ for (i = 0; i < t->spool_cnt; i++) {
+ s = (struct nat44_cfg_spool *)&buf[off];
+ if (i)
+ printf(",");
+ else
+ printf(" ");
+ printf("%s", inet_ntoa(s->addr));
+ off += sizeof(struct nat44_cfg_spool);
+ }
+ printf(" %s", inet_ntoa(t->paddr));
+ break;
+ case REDIR_PORT:
+ p = getprotobynumber(t->proto);
+ printf(" redirect_port %s ", p->p_name);
+ if (!t->spool_cnt) {
+ printf("%s:%u", inet_ntoa(t->laddr), t->lport);
+ if (t->pport_cnt > 1)
+ printf("-%u", t->lport +
+ t->pport_cnt - 1);
+ } else
+ for (i=0; i < t->spool_cnt; i++) {
+ s = (struct nat44_cfg_spool *)&buf[off];
+ if (i)
+ printf(",");
+ printf("%s:%u", inet_ntoa(s->addr),
+ s->port);
+ off += sizeof(struct nat44_cfg_spool);
+ }
+
+ printf(" ");
+ if (t->paddr.s_addr)
+ printf("%s:", inet_ntoa(t->paddr));
+ printf("%u", t->pport);
+ if (!t->spool_cnt && t->pport_cnt > 1)
+ printf("-%u", t->pport + t->pport_cnt - 1);
+
+ if (t->raddr.s_addr) {
+ printf(" %s", inet_ntoa(t->raddr));
+ if (t->rport) {
+ printf(":%u", t->rport);
+ if (!t->spool_cnt && t->rport_cnt > 1)
+ printf("-%u", t->rport +
+ t->rport_cnt - 1);
+ }
+ }
+ break;
+ case REDIR_PROTO:
+ p = getprotobynumber(t->proto);
+ printf(" redirect_proto %s %s", p->p_name,
+ inet_ntoa(t->laddr));
+ if (t->paddr.s_addr != 0) {
+ printf(" %s", inet_ntoa(t->paddr));
+ if (t->raddr.s_addr)
+ printf(" %s", inet_ntoa(t->raddr));
+ }
+ break;
+ default:
+ errx(EX_DATAERR, "unknown redir mode");
+ break;
+ }
+ }
+ printf("\n");
+}
+
+static int
+nat_port_alias_parse(char *str, u_short *lpout, u_short *hpout) {
+ long lp, hp;
+ char *ptr;
+ /* Lower port parsing */
+ lp = (long) strtol(str, &ptr, 10);
+ if (lp < 1024 || lp > 65535)
+ return 0;
+ if (!ptr || *ptr != '-')
+ return 0;
+ /* Upper port parsing */
+ hp = (long) strtol(ptr, &ptr, 10);
+ if (hp < 1024 || hp > 65535)
+ return 0;
+ if (ptr)
+ return 0;
+
+ *lpout = (u_short) lp;
+ *hpout = (u_short) hp;
+ return 1;
+}
+
+void
+ipfw_config_nat(int ac, char **av)
+{
+ ipfw_obj_header *oh;
+ struct nat44_cfg_nat *n; /* Nat instance configuration. */
+ int i, off, tok, ac1;
+ u_short lp, hp;
+ char *id, *buf, **av1, *end;
+ size_t len;
+
+ av++;
+ ac--;
+ /* Nat id. */
+ if (ac == 0)
+ errx(EX_DATAERR, "missing nat id");
+ id = *av;
+ i = (int)strtol(id, &end, 0);
+ if (i <= 0 || *end != '\0')
+ errx(EX_DATAERR, "illegal nat id: %s", id);
+ av++;
+ ac--;
+ if (ac == 0)
+ errx(EX_DATAERR, "missing option");
+
+ len = sizeof(*oh) + sizeof(*n);
+ ac1 = ac;
+ av1 = av;
+ while (ac1 > 0) {
+ tok = match_token(nat_params, *av1);
+ ac1--;
+ av1++;
+ switch (tok) {
+ case TOK_IP:
+ case TOK_IF:
+ case TOK_PORT_ALIAS:
+ ac1--;
+ av1++;
+ break;
+ case TOK_ALOG:
+ case TOK_DENY_INC:
+ case TOK_SAME_PORTS:
+ case TOK_SKIP_GLOBAL:
+ case TOK_UNREG_ONLY:
+ case TOK_UNREG_CGN:
+ case TOK_RESET_ADDR:
+ case TOK_ALIAS_REV:
+ case TOK_PROXY_ONLY:
+ case TOK_UDP_EIM:
+ break;
+ case TOK_REDIR_ADDR:
+ if (ac1 < 2)
+ errx(EX_DATAERR, "redirect_addr: "
+ "not enough arguments");
+ len += estimate_redir_addr(&ac1, &av1);
+ av1 += 2;
+ ac1 -= 2;
+ break;
+ case TOK_REDIR_PORT:
+ if (ac1 < 3)
+ errx(EX_DATAERR, "redirect_port: "
+ "not enough arguments");
+ av1++;
+ ac1--;
+ len += estimate_redir_port(&ac1, &av1);
+ av1 += 2;
+ ac1 -= 2;
+ /* Skip optional remoteIP/port */
+ if (ac1 != 0 && isdigit(**av1)) {
+ av1++;
+ ac1--;
+ }
+ break;
+ case TOK_REDIR_PROTO:
+ if (ac1 < 2)
+ errx(EX_DATAERR, "redirect_proto: "
+ "not enough arguments");
+ len += sizeof(struct nat44_cfg_redir);
+ av1 += 2;
+ ac1 -= 2;
+ /* Skip optional remoteIP/port */
+ if (ac1 != 0 && isdigit(**av1)) {
+ av1++;
+ ac1--;
+ }
+ if (ac1 != 0 && isdigit(**av1)) {
+ av1++;
+ ac1--;
+ }
+ break;
+ default:
+ errx(EX_DATAERR, "unrecognised option ``%s''", av1[-1]);
+ }
+ }
+
+ if ((buf = malloc(len)) == NULL)
+ errx(EX_OSERR, "malloc failed");
+
+ /* Offset in buf: save space for header at the beginning. */
+ off = sizeof(*oh) + sizeof(*n);
+ memset(buf, 0, len);
+ oh = (ipfw_obj_header *)buf;
+ n = (struct nat44_cfg_nat *)(oh + 1);
+ oh->ntlv.head.length = sizeof(oh->ntlv);
+ snprintf(oh->ntlv.name, sizeof(oh->ntlv.name), "%d", i);
+ snprintf(n->name, sizeof(n->name), "%d", i);
+
+ while (ac > 0) {
+ tok = match_token(nat_params, *av);
+ ac--;
+ av++;
+ switch (tok) {
+ case TOK_IP:
+ if (ac == 0)
+ errx(EX_DATAERR, "missing option");
+ if (!inet_aton(av[0], &(n->ip)))
+ errx(EX_DATAERR, "bad ip address ``%s''",
+ av[0]);
+ ac--;
+ av++;
+ break;
+ case TOK_IF:
+ if (ac == 0)
+ errx(EX_DATAERR, "missing option");
+ set_addr_dynamic(av[0], n);
+ ac--;
+ av++;
+ break;
+ case TOK_ALOG:
+ n->mode |= PKT_ALIAS_LOG;
+ break;
+ case TOK_DENY_INC:
+ n->mode |= PKT_ALIAS_DENY_INCOMING;
+ break;
+ case TOK_SAME_PORTS:
+ n->mode |= PKT_ALIAS_SAME_PORTS;
+ break;
+ case TOK_UNREG_ONLY:
+ n->mode |= PKT_ALIAS_UNREGISTERED_ONLY;
+ break;
+ case TOK_UNREG_CGN:
+ n->mode |= PKT_ALIAS_UNREGISTERED_CGN;
+ break;
+ case TOK_SKIP_GLOBAL:
+ n->mode |= PKT_ALIAS_SKIP_GLOBAL;
+ break;
+ case TOK_RESET_ADDR:
+ n->mode |= PKT_ALIAS_RESET_ON_ADDR_CHANGE;
+ break;
+ case TOK_ALIAS_REV:
+ n->mode |= PKT_ALIAS_REVERSE;
+ break;
+ case TOK_PROXY_ONLY:
+ n->mode |= PKT_ALIAS_PROXY_ONLY;
+ break;
+ case TOK_UDP_EIM:
+ n->mode |= PKT_ALIAS_UDP_EIM;
+ break;
+ /*
+ * All the setup_redir_* functions work directly in
+ * the final buffer, see above for details.
+ */
+ case TOK_REDIR_ADDR:
+ case TOK_REDIR_PORT:
+ case TOK_REDIR_PROTO:
+ switch (tok) {
+ case TOK_REDIR_ADDR:
+ i = setup_redir_addr(&buf[off], &ac, &av);
+ break;
+ case TOK_REDIR_PORT:
+ i = setup_redir_port(&buf[off], &ac, &av);
+ break;
+ case TOK_REDIR_PROTO:
+ i = setup_redir_proto(&buf[off], &ac, &av);
+ break;
+ }
+ n->redir_cnt++;
+ off += i;
+ break;
+ case TOK_PORT_ALIAS:
+ if (ac == 0)
+ errx(EX_DATAERR, "missing option");
+ if (!nat_port_alias_parse(av[0], &lp, &hp))
+ errx(EX_DATAERR,
+ "You need a range of port(s) from 1024 <= x < 65536");
+ if (lp >= hp)
+ errx(EX_DATAERR,
+ "Upper port has to be greater than lower port");
+ n->alias_port_lo = lp;
+ n->alias_port_hi = hp;
+ ac--;
+ av++;
+ break;
+ }
+ }
+ if (n->mode & PKT_ALIAS_SAME_PORTS && n->alias_port_lo)
+ errx(EX_DATAERR, "same_ports and port_range cannot both be selected");
+
+ i = do_set3(IP_FW_NAT44_XCONFIG, &oh->opheader, len);
+ if (i != 0)
+ err(1, "setsockopt(%s)", "IP_FW_NAT44_XCONFIG");
+
+ if (!g_co.do_quiet) {
+ /* After every modification, we show the resultant rule. */
+ int _ac = 3;
+ const char *_av[] = {"show", "config", id};
+ ipfw_show_nat(_ac, (char **)(void *)_av);
+ }
+}
+
+static void
+nat_fill_ntlv(ipfw_obj_ntlv *ntlv, int i)
+{
+
+ ntlv->head.type = IPFW_TLV_EACTION_NAME(1); /* it doesn't matter */
+ ntlv->head.length = sizeof(ipfw_obj_ntlv);
+ ntlv->idx = 1;
+ ntlv->set = 0; /* not yet */
+ snprintf(ntlv->name, sizeof(ntlv->name), "%d", i);
+}
+
+int
+ipfw_delete_nat(int i)
+{
+ ipfw_obj_header oh;
+ int ret;
+
+ memset(&oh, 0, sizeof(oh));
+ nat_fill_ntlv(&oh.ntlv, i);
+ ret = do_set3(IP_FW_NAT44_DESTROY, &oh.opheader, sizeof(oh));
+ if (ret == -1) {
+ if (!g_co.do_quiet)
+ warn("nat %u not available", i);
+ return (EX_UNAVAILABLE);
+ }
+ return (EX_OK);
+}
+
+struct nat_list_arg {
+ uint16_t cmd;
+ int is_all;
+};
+
+static int
+nat_show_data(struct nat44_cfg_nat *cfg, void *arg)
+{
+ struct nat_list_arg *nla;
+ ipfw_obj_header *oh;
+
+ nla = (struct nat_list_arg *)arg;
+
+ switch (nla->cmd) {
+ case IP_FW_NAT44_XGETCONFIG:
+ if (nat_get_cmd(cfg->name, nla->cmd, &oh) != 0) {
+ warnx("Error getting nat instance %s info", cfg->name);
+ break;
+ }
+ nat_show_cfg((struct nat44_cfg_nat *)(oh + 1), NULL);
+ free(oh);
+ break;
+ case IP_FW_NAT44_XGETLOG:
+ if (nat_get_cmd(cfg->name, nla->cmd, &oh) == 0) {
+ nat_show_log((struct nat44_cfg_nat *)(oh + 1), NULL);
+ free(oh);
+ break;
+ }
+ /* Handle error */
+ if (nla->is_all != 0 && errno == ENOENT)
+ break;
+ warn("Error getting nat instance %s info", cfg->name);
+ break;
+ }
+
+ return (0);
+}
+
+/*
+ * Compare nat names.
+ * Honor number comparison.
+ */
+static int
+natname_cmp(const void *a, const void *b)
+{
+ const struct nat44_cfg_nat *ia, *ib;
+
+ ia = (const struct nat44_cfg_nat *)a;
+ ib = (const struct nat44_cfg_nat *)b;
+
+ return (stringnum_cmp(ia->name, ib->name));
+}
+
+/*
+ * Retrieves nat list from kernel,
+ * optionally sorts it and calls requested function for each table.
+ * Returns 0 on success.
+ */
+static int
+nat_foreach(nat_cb_t *f, void *arg, int sort)
+{
+ ipfw_obj_lheader *olh;
+ struct nat44_cfg_nat *cfg;
+ size_t sz;
+ uint32_t i;
+
+ /* Start with reasonable default */
+ sz = sizeof(*olh) + 16 * sizeof(struct nat44_cfg_nat);
+
+ for (;;) {
+ if ((olh = calloc(1, sz)) == NULL)
+ return (ENOMEM);
+
+ olh->size = sz;
+ if (do_get3(IP_FW_NAT44_LIST_NAT, &olh->opheader, &sz) != 0) {
+ sz = olh->size;
+ free(olh);
+ if (errno == ENOMEM)
+ continue;
+ return (errno);
+ }
+
+ if (sort != 0)
+ qsort(olh + 1, olh->count, olh->objsize, natname_cmp);
+
+ cfg = (struct nat44_cfg_nat*)(olh + 1);
+ for (i = 0; i < olh->count; i++) {
+ (void)f(cfg, arg); /* Ignore errors for now */
+ cfg = (struct nat44_cfg_nat *)((caddr_t)cfg +
+ olh->objsize);
+ }
+
+ free(olh);
+ break;
+ }
+
+ return (0);
+}
+
+static int
+nat_get_cmd(char *name, uint16_t cmd, ipfw_obj_header **ooh)
+{
+ ipfw_obj_header *oh;
+ struct nat44_cfg_nat *cfg;
+ size_t sz;
+
+ /* Start with reasonable default */
+ sz = sizeof(*oh) + sizeof(*cfg) + 128;
+
+ for (;;) {
+ if ((oh = calloc(1, sz)) == NULL)
+ return (ENOMEM);
+ cfg = (struct nat44_cfg_nat *)(oh + 1);
+ oh->ntlv.head.length = sizeof(oh->ntlv);
+ strlcpy(oh->ntlv.name, name, sizeof(oh->ntlv.name));
+ strlcpy(cfg->name, name, sizeof(cfg->name));
+
+ if (do_get3(cmd, &oh->opheader, &sz) != 0) {
+ sz = cfg->size;
+ free(oh);
+ if (errno == ENOMEM)
+ continue;
+ return (errno);
+ }
+
+ *ooh = oh;
+ break;
+ }
+
+ return (0);
+}
+
+void
+ipfw_show_nat(int ac, char **av)
+{
+ ipfw_obj_header *oh;
+ char *name;
+ int cmd;
+ struct nat_list_arg nla;
+
+ ac--;
+ av++;
+
+ if (g_co.test_only)
+ return;
+
+ /* Parse parameters. */
+ cmd = 0; /* XXX: Change to IP_FW_NAT44_XGETLOG @ MFC */
+ name = NULL;
+ for ( ; ac != 0; ac--, av++) {
+ if (!strncmp(av[0], "config", strlen(av[0]))) {
+ cmd = IP_FW_NAT44_XGETCONFIG;
+ continue;
+ }
+ if (strcmp(av[0], "log") == 0) {
+ cmd = IP_FW_NAT44_XGETLOG;
+ continue;
+ }
+ if (name != NULL)
+ err(EX_USAGE,"only one instance name may be specified");
+ name = av[0];
+ }
+
+ if (cmd == 0)
+ errx(EX_USAGE, "Please specify action. Available: config,log");
+
+ if (name == NULL) {
+ memset(&nla, 0, sizeof(nla));
+ nla.cmd = cmd;
+ nla.is_all = 1;
+ nat_foreach(nat_show_data, &nla, 1);
+ } else {
+ if (nat_get_cmd(name, cmd, &oh) != 0)
+ err(EX_OSERR, "Error getting nat %s instance info", name);
+ nat_show_cfg((struct nat44_cfg_nat *)(oh + 1), NULL);
+ free(oh);
+ }
+}
+
diff --git a/sbin/ipfw15/nat64clat.c b/sbin/ipfw15/nat64clat.c
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/nat64clat.c
@@ -0,0 +1,536 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Yandex LLC
+ * Copyright (c) 2019 Andrey V. Elsukov <ae@FreeBSD.org>
+ * Copyright (c) 2019 Boris N. Lytochkin <lytboris@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "ipfw2.h"
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.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/ip_fw15.h>
+#include <netinet6/ip_fw_nat64.h>
+#include <arpa/inet.h>
+
+typedef int (nat64clat_cb_t)(ipfw_nat64clat_cfg *i, const char *name,
+ uint8_t set);
+static int nat64clat_foreach(nat64clat_cb_t *f, const char *name, uint8_t set,
+ int sort);
+
+static void nat64clat_create(const char *name, uint8_t set, int ac, char **av);
+static void nat64clat_config(const char *name, uint8_t set, int ac, char **av);
+static void nat64clat_destroy(const char *name, uint8_t set);
+static void nat64clat_stats(const char *name, uint8_t set);
+static void nat64clat_reset_stats(const char *name, uint8_t set);
+static int nat64clat_show_cb(ipfw_nat64clat_cfg *cfg, const char *name,
+ uint8_t set);
+static int nat64clat_destroy_cb(ipfw_nat64clat_cfg *cfg, const char *name,
+ uint8_t set);
+
+static struct _s_x nat64cmds[] = {
+ { "create", TOK_CREATE },
+ { "config", TOK_CONFIG },
+ { "destroy", TOK_DESTROY },
+ { "list", TOK_LIST },
+ { "show", TOK_LIST },
+ { "stats", TOK_STATS },
+ { NULL, 0 }
+};
+
+static struct _s_x nat64statscmds[] = {
+ { "reset", TOK_RESET },
+ { NULL, 0 }
+};
+
+/*
+ * This one handles all nat64clat-related commands
+ * ipfw [set N] nat64clat NAME {create | config} ...
+ * ipfw [set N] nat64clat NAME stats [reset]
+ * ipfw [set N] nat64clat {NAME | all} destroy
+ * ipfw [set N] nat64clat {NAME | all} {list | show}
+ */
+#define nat64clat_check_name table_check_name
+void
+ipfw_nat64clat_handler(int ac, char *av[])
+{
+ const char *name;
+ int tcmd;
+ uint8_t set;
+
+ if (g_co.use_set != 0)
+ set = g_co.use_set - 1;
+ else
+ set = 0;
+ ac--; av++;
+
+ NEED1("nat64clat needs instance name");
+ name = *av;
+ if (nat64clat_check_name(name) != 0) {
+ if (strcmp(name, "all") == 0)
+ name = NULL;
+ else
+ errx(EX_USAGE, "nat64clat instance name %s is invalid",
+ name);
+ }
+ ac--; av++;
+ NEED1("nat64clat needs command");
+
+ tcmd = get_token(nat64cmds, *av, "nat64clat command");
+ if (name == NULL && tcmd != TOK_DESTROY && tcmd != TOK_LIST)
+ errx(EX_USAGE, "nat64clat instance name required");
+ switch (tcmd) {
+ case TOK_CREATE:
+ ac--; av++;
+ nat64clat_create(name, set, ac, av);
+ break;
+ case TOK_CONFIG:
+ ac--; av++;
+ nat64clat_config(name, set, ac, av);
+ break;
+ case TOK_LIST:
+ nat64clat_foreach(nat64clat_show_cb, name, set, 1);
+ break;
+ case TOK_DESTROY:
+ if (name == NULL)
+ nat64clat_foreach(nat64clat_destroy_cb, NULL, set, 0);
+ else
+ nat64clat_destroy(name, set);
+ break;
+ case TOK_STATS:
+ ac--; av++;
+ if (ac == 0) {
+ nat64clat_stats(name, set);
+ break;
+ }
+ tcmd = get_token(nat64statscmds, *av, "stats command");
+ if (tcmd == TOK_RESET)
+ nat64clat_reset_stats(name, set);
+ }
+}
+
+
+static void
+nat64clat_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, uint8_t set)
+{
+
+ ntlv->head.type = IPFW_TLV_EACTION_NAME(1); /* it doesn't matter */
+ ntlv->head.length = sizeof(ipfw_obj_ntlv);
+ ntlv->idx = 1;
+ ntlv->set = set;
+ strlcpy(ntlv->name, name, sizeof(ntlv->name));
+}
+
+static struct _s_x nat64newcmds[] = {
+ { "plat_prefix", TOK_PLAT_PREFIX },
+ { "clat_prefix", TOK_CLAT_PREFIX },
+ { "log", TOK_LOG },
+ { "-log", TOK_LOGOFF },
+ { "allow_private", TOK_PRIVATE },
+ { "-allow_private", TOK_PRIVATEOFF },
+ { NULL, 0 }
+};
+
+/*
+ * Creates new nat64clat instance
+ * ipfw nat64clat <NAME> create clat_prefix <prefix> plat_prefix <prefix>
+ * Request: [ ipfw_obj_lheader ipfw_nat64clat_cfg ]
+ */
+#define NAT64CLAT_HAS_CLAT_PREFIX 0x01
+#define NAT64CLAT_HAS_PLAT_PREFIX 0x02
+static void
+nat64clat_create(const char *name, uint8_t set, int ac, char *av[])
+{
+ char buf[sizeof(ipfw_obj_lheader) + sizeof(ipfw_nat64clat_cfg)];
+ ipfw_nat64clat_cfg *cfg;
+ ipfw_obj_lheader *olh;
+ int tcmd, flags;
+ char *p;
+ struct in6_addr prefix;
+ uint8_t plen;
+
+ memset(buf, 0, sizeof(buf));
+ olh = (ipfw_obj_lheader *)buf;
+ cfg = (ipfw_nat64clat_cfg *)(olh + 1);
+
+ /* Some reasonable defaults */
+ inet_pton(AF_INET6, "64:ff9b::", &cfg->plat_prefix);
+ cfg->plat_plen = 96;
+ cfg->set = set;
+ flags = NAT64CLAT_HAS_PLAT_PREFIX;
+ while (ac > 0) {
+ tcmd = get_token(nat64newcmds, *av, "option");
+ ac--; av++;
+
+ switch (tcmd) {
+ case TOK_PLAT_PREFIX:
+ case TOK_CLAT_PREFIX:
+ if (tcmd == TOK_PLAT_PREFIX) {
+ NEED1("IPv6 plat_prefix required");
+ } else {
+ NEED1("IPv6 clat_prefix required");
+ }
+
+ if ((p = strchr(*av, '/')) != NULL)
+ *p++ = '\0';
+ if (inet_pton(AF_INET6, *av, &prefix) != 1)
+ errx(EX_USAGE,
+ "Bad prefix: %s", *av);
+ plen = strtol(p, NULL, 10);
+ if (ipfw_check_nat64prefix(&prefix, plen) != 0)
+ errx(EX_USAGE,
+ "Bad prefix length: %s", p);
+ if (tcmd == TOK_PLAT_PREFIX) {
+ flags |= NAT64CLAT_HAS_PLAT_PREFIX;
+ cfg->plat_prefix = prefix;
+ cfg->plat_plen = plen;
+ } else {
+ flags |= NAT64CLAT_HAS_CLAT_PREFIX;
+ cfg->clat_prefix = prefix;
+ cfg->clat_plen = plen;
+ }
+ ac--; av++;
+ break;
+ case TOK_LOG:
+ cfg->flags |= NAT64_LOG;
+ break;
+ case TOK_LOGOFF:
+ cfg->flags &= ~NAT64_LOG;
+ break;
+ case TOK_PRIVATE:
+ cfg->flags |= NAT64_ALLOW_PRIVATE;
+ break;
+ case TOK_PRIVATEOFF:
+ cfg->flags &= ~NAT64_ALLOW_PRIVATE;
+ break;
+ }
+ }
+
+ /* Check validness */
+ if ((flags & NAT64CLAT_HAS_PLAT_PREFIX) != NAT64CLAT_HAS_PLAT_PREFIX)
+ errx(EX_USAGE, "plat_prefix required");
+ if ((flags & NAT64CLAT_HAS_CLAT_PREFIX) != NAT64CLAT_HAS_CLAT_PREFIX)
+ errx(EX_USAGE, "clat_prefix required");
+
+ olh->count = 1;
+ olh->objsize = sizeof(*cfg);
+ olh->size = sizeof(buf);
+ strlcpy(cfg->name, name, sizeof(cfg->name));
+ if (do_set3(IP_FW_NAT64CLAT_CREATE, &olh->opheader, sizeof(buf)) != 0)
+ err(EX_OSERR, "nat64clat instance creation failed");
+}
+
+/*
+ * Configures existing nat64clat instance
+ * ipfw nat64clat <NAME> config <options>
+ * Request: [ ipfw_obj_header ipfw_nat64clat_cfg ]
+ */
+static void
+nat64clat_config(const char *name, uint8_t set, int ac, char **av)
+{
+ char buf[sizeof(ipfw_obj_header) + sizeof(ipfw_nat64clat_cfg)];
+ ipfw_nat64clat_cfg *cfg;
+ ipfw_obj_header *oh;
+ char *opt;
+ char *p;
+ size_t sz;
+ int tcmd;
+ struct in6_addr prefix;
+ uint8_t plen;
+
+ if (ac == 0)
+ errx(EX_USAGE, "config options required");
+ memset(&buf, 0, sizeof(buf));
+ oh = (ipfw_obj_header *)buf;
+ cfg = (ipfw_nat64clat_cfg *)(oh + 1);
+ sz = sizeof(buf);
+
+ nat64clat_fill_ntlv(&oh->ntlv, name, set);
+ if (do_get3(IP_FW_NAT64CLAT_CONFIG, &oh->opheader, &sz) != 0)
+ err(EX_OSERR, "failed to get config for instance %s", name);
+
+ while (ac > 0) {
+ tcmd = get_token(nat64newcmds, *av, "option");
+ opt = *av;
+ ac--; av++;
+
+ switch (tcmd) {
+ case TOK_PLAT_PREFIX:
+ case TOK_CLAT_PREFIX:
+ if (tcmd == TOK_PLAT_PREFIX) {
+ NEED1("IPv6 plat_prefix required");
+ } else {
+ NEED1("IPv6 clat_prefix required");
+ }
+
+ if ((p = strchr(*av, '/')) != NULL)
+ *p++ = '\0';
+ else
+ errx(EX_USAGE,
+ "Prefix length required: %s", *av);
+ if (inet_pton(AF_INET6, *av, &prefix) != 1)
+ errx(EX_USAGE,
+ "Bad prefix: %s", *av);
+ plen = strtol(p, NULL, 10);
+ if (ipfw_check_nat64prefix(&prefix, plen) != 0)
+ errx(EX_USAGE,
+ "Bad prefix length: %s", p);
+ if (tcmd == TOK_PLAT_PREFIX) {
+ cfg->plat_prefix = prefix;
+ cfg->plat_plen = plen;
+ } else {
+ cfg->clat_prefix = prefix;
+ cfg->clat_plen = plen;
+ }
+ ac--; av++;
+ break;
+ case TOK_LOG:
+ cfg->flags |= NAT64_LOG;
+ break;
+ case TOK_LOGOFF:
+ cfg->flags &= ~NAT64_LOG;
+ break;
+ case TOK_PRIVATE:
+ cfg->flags |= NAT64_ALLOW_PRIVATE;
+ break;
+ case TOK_PRIVATEOFF:
+ cfg->flags &= ~NAT64_ALLOW_PRIVATE;
+ break;
+ default:
+ errx(EX_USAGE, "Can't change %s option", opt);
+ }
+ }
+
+ if (do_set3(IP_FW_NAT64CLAT_CONFIG, &oh->opheader, sizeof(buf)) != 0)
+ err(EX_OSERR, "nat64clat instance configuration failed");
+}
+
+/*
+ * Destroys nat64clat instance.
+ * Request: [ ipfw_obj_header ]
+ */
+static void
+nat64clat_destroy(const char *name, uint8_t set)
+{
+ ipfw_obj_header oh;
+
+ memset(&oh, 0, sizeof(oh));
+ nat64clat_fill_ntlv(&oh.ntlv, name, set);
+ if (do_set3(IP_FW_NAT64CLAT_DESTROY, &oh.opheader, sizeof(oh)) != 0)
+ err(EX_OSERR, "failed to destroy nat instance %s", name);
+}
+
+/*
+ * Get nat64clat instance statistics.
+ * Request: [ ipfw_obj_header ]
+ * Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ] ]
+ */
+static int
+nat64clat_get_stats(const char *name, uint8_t set,
+ struct ipfw_nat64clat_stats *stats)
+{
+ ipfw_obj_header *oh;
+ ipfw_obj_ctlv *oc;
+ size_t sz;
+
+ sz = sizeof(*oh) + sizeof(*oc) + sizeof(*stats);
+ oh = calloc(1, sz);
+ nat64clat_fill_ntlv(&oh->ntlv, name, set);
+ if (do_get3(IP_FW_NAT64CLAT_STATS, &oh->opheader, &sz) == 0) {
+ oc = (ipfw_obj_ctlv *)(oh + 1);
+ memcpy(stats, oc + 1, sizeof(*stats));
+ free(oh);
+ return (0);
+ }
+ free(oh);
+ return (-1);
+}
+
+static void
+nat64clat_stats(const char *name, uint8_t set)
+{
+ struct ipfw_nat64clat_stats stats;
+
+ if (nat64clat_get_stats(name, set, &stats) != 0)
+ err(EX_OSERR, "Error retrieving stats");
+
+ if (g_co.use_set != 0 || set != 0)
+ printf("set %u ", set);
+ printf("nat64clat %s\n", name);
+
+ printf("\t%ju packets translated from IPv6 to IPv4\n",
+ (uintmax_t)stats.opcnt64);
+ printf("\t%ju packets translated from IPv4 to IPv6\n",
+ (uintmax_t)stats.opcnt46);
+ printf("\t%ju IPv6 fragments created\n",
+ (uintmax_t)stats.ofrags);
+ printf("\t%ju IPv4 fragments received\n",
+ (uintmax_t)stats.ifrags);
+ printf("\t%ju output packets dropped due to no bufs, etc.\n",
+ (uintmax_t)stats.oerrors);
+ printf("\t%ju output packets discarded due to no IPv4 route\n",
+ (uintmax_t)stats.noroute4);
+ printf("\t%ju output packets discarded due to no IPv6 route\n",
+ (uintmax_t)stats.noroute6);
+ printf("\t%ju packets discarded due to unsupported protocol\n",
+ (uintmax_t)stats.noproto);
+ printf("\t%ju packets discarded due to memory allocation problems\n",
+ (uintmax_t)stats.nomem);
+ printf("\t%ju packets discarded due to some errors\n",
+ (uintmax_t)stats.dropped);
+}
+
+/*
+ * Reset nat64clat instance statistics specified by @oh->ntlv.
+ * Request: [ ipfw_obj_header ]
+ */
+static void
+nat64clat_reset_stats(const char *name, uint8_t set)
+{
+ ipfw_obj_header oh;
+
+ memset(&oh, 0, sizeof(oh));
+ nat64clat_fill_ntlv(&oh.ntlv, name, set);
+ if (do_set3(IP_FW_NAT64CLAT_RESET_STATS, &oh.opheader, sizeof(oh)) != 0)
+ err(EX_OSERR, "failed to reset stats for instance %s", name);
+}
+
+static int
+nat64clat_show_cb(ipfw_nat64clat_cfg *cfg, const char *name, uint8_t set)
+{
+ char plat_buf[INET6_ADDRSTRLEN], clat_buf[INET6_ADDRSTRLEN];
+
+ if (name != NULL && strcmp(cfg->name, name) != 0)
+ return (ESRCH);
+
+ if (g_co.use_set != 0 && cfg->set != set)
+ return (ESRCH);
+
+ if (g_co.use_set != 0 || cfg->set != 0)
+ printf("set %u ", cfg->set);
+
+ inet_ntop(AF_INET6, &cfg->clat_prefix, clat_buf, sizeof(clat_buf));
+ inet_ntop(AF_INET6, &cfg->plat_prefix, plat_buf, sizeof(plat_buf));
+ printf("nat64clat %s clat_prefix %s/%u plat_prefix %s/%u",
+ cfg->name, clat_buf, cfg->clat_plen, plat_buf, cfg->plat_plen);
+ if (cfg->flags & NAT64_LOG)
+ printf(" log");
+ if (cfg->flags & NAT64_ALLOW_PRIVATE)
+ printf(" allow_private");
+ printf("\n");
+ return (0);
+}
+
+static int
+nat64clat_destroy_cb(ipfw_nat64clat_cfg *cfg, const char *name __unused,
+ uint8_t set)
+{
+
+ if (g_co.use_set != 0 && cfg->set != set)
+ return (ESRCH);
+
+ nat64clat_destroy(cfg->name, cfg->set);
+ return (0);
+}
+
+
+/*
+ * Compare nat64clat instances names.
+ * Honor number comparison.
+ */
+static int
+nat64name_cmp(const void *a, const void *b)
+{
+ const ipfw_nat64clat_cfg *ca, *cb;
+
+ ca = (const ipfw_nat64clat_cfg *)a;
+ cb = (const ipfw_nat64clat_cfg *)b;
+
+ if (ca->set > cb->set)
+ return (1);
+ else if (ca->set < cb->set)
+ return (-1);
+ return (stringnum_cmp(ca->name, cb->name));
+}
+
+/*
+ * Retrieves nat64clat instance list from kernel,
+ * optionally sorts it and calls requested function for each instance.
+ *
+ * Request: [ ipfw_obj_lheader ]
+ * Reply: [ ipfw_obj_lheader ipfw_nat64clat_cfg x N ]
+ */
+static int
+nat64clat_foreach(nat64clat_cb_t *f, const char *name, uint8_t set, int sort)
+{
+ ipfw_obj_lheader *olh;
+ ipfw_nat64clat_cfg *cfg;
+ size_t sz;
+ uint32_t i;
+
+ /* Start with reasonable default */
+ sz = sizeof(*olh) + 16 * sizeof(*cfg);
+ for (;;) {
+ if ((olh = calloc(1, sz)) == NULL)
+ return (ENOMEM);
+
+ olh->size = sz;
+ if (do_get3(IP_FW_NAT64CLAT_LIST, &olh->opheader, &sz) != 0) {
+ sz = olh->size;
+ free(olh);
+ if (errno != ENOMEM)
+ return (errno);
+ continue;
+ }
+
+ if (sort != 0)
+ qsort(olh + 1, olh->count, olh->objsize,
+ nat64name_cmp);
+
+ cfg = (ipfw_nat64clat_cfg *)(olh + 1);
+ for (i = 0; i < olh->count; i++) {
+ (void)f(cfg, name, set); /* Ignore errors for now */
+ cfg = (ipfw_nat64clat_cfg *)((caddr_t)cfg +
+ olh->objsize);
+ }
+ free(olh);
+ break;
+ }
+ return (0);
+}
+
diff --git a/sbin/ipfw15/nat64lsn.c b/sbin/ipfw15/nat64lsn.c
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/nat64lsn.c
@@ -0,0 +1,901 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2015-2019 Yandex LLC
+ * Copyright (c) 2015-2016 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ * Copyright (c) 2015-2019 Andrey V. Elsukov <ae@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "ipfw2.h"
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.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/ip_fw15.h>
+#include <netinet6/ip_fw_nat64_15.h>
+#include <arpa/inet.h>
+
+static void nat64lsn_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name,
+ uint8_t set);
+typedef int (nat64lsn_cb_t)(ipfw_nat64lsn_cfg *cfg, const char *name,
+ uint8_t set);
+static int nat64lsn_foreach(nat64lsn_cb_t *f, const char *name, uint8_t set,
+ int sort);
+
+static void nat64lsn_create(const char *name, uint8_t set, int ac, char **av);
+static void nat64lsn_config(const char *name, uint8_t set, int ac, char **av);
+static void nat64lsn_destroy(const char *name, uint8_t set);
+static void nat64lsn_stats(const char *name, uint8_t set);
+static void nat64lsn_reset_stats(const char *name, uint8_t set);
+static int nat64lsn_show_cb(ipfw_nat64lsn_cfg *cfg, const char *name,
+ uint8_t set);
+static int nat64lsn_destroy_cb(ipfw_nat64lsn_cfg *cfg, const char *name,
+ uint8_t set);
+static int nat64lsn_states_cb(ipfw_nat64lsn_cfg *cfg, const char *name,
+ uint8_t set);
+
+static struct _s_x nat64cmds[] = {
+ { "create", TOK_CREATE },
+ { "config", TOK_CONFIG },
+ { "destroy", TOK_DESTROY },
+ { "list", TOK_LIST },
+ { "show", TOK_LIST },
+ { "stats", TOK_STATS },
+ { NULL, 0 }
+};
+
+static uint64_t
+nat64lsn_print_states(void *buf)
+{
+ char s[INET6_ADDRSTRLEN], a[INET_ADDRSTRLEN], f[INET_ADDRSTRLEN];
+ const char *proto;
+ char sflags[4], *sf;
+ ipfw_obj_header *oh;
+ ipfw_obj_data *od;
+ ipfw_nat64lsn_stg_v1 *stg;
+ ipfw_nat64lsn_state_v1 *ste;
+ uint64_t next_idx;
+ uint32_t i;
+ int sz;
+
+ oh = (ipfw_obj_header *)buf;
+ od = (ipfw_obj_data *)(oh + 1);
+ stg = (ipfw_nat64lsn_stg_v1 *)(od + 1);
+ sz = od->head.length - sizeof(*od);
+ next_idx = 0;
+ proto = NULL;
+ while (sz > 0 && next_idx != 0xFF) {
+ next_idx = stg->next.index;
+ sz -= sizeof(*stg);
+ if (stg->count == 0) {
+ stg++;
+ continue;
+ }
+ /*
+ * NOTE: addresses are in network byte order,
+ * ports are in host byte order.
+ */
+ inet_ntop(AF_INET, &stg->alias4, a, sizeof(a));
+ ste = (ipfw_nat64lsn_state_v1 *)(stg + 1);
+ for (i = 0; i < stg->count && sz > 0; i++) {
+ sf = sflags;
+ inet_ntop(AF_INET6, &ste->host6, s, sizeof(s));
+ inet_ntop(AF_INET, &ste->daddr, f, sizeof(f));
+ switch (ste->proto) {
+ case IPPROTO_TCP:
+ proto = "TCP";
+ if (ste->flags & 0x02)
+ *sf++ = 'S';
+ if (ste->flags & 0x04)
+ *sf++ = 'E';
+ if (ste->flags & 0x01)
+ *sf++ = 'F';
+ break;
+ case IPPROTO_UDP:
+ proto = "UDP";
+ break;
+ case IPPROTO_ICMP:
+ proto = "ICMPv6";
+ break;
+ }
+ *sf = '\0';
+ switch (ste->proto) {
+ case IPPROTO_TCP:
+ case IPPROTO_UDP:
+ printf("%s:%d\t%s:%d\t%s\t%s\t%d\t%s:%d\n",
+ s, ste->sport, a, ste->aport, proto,
+ sflags, ste->idle, f, ste->dport);
+ break;
+ case IPPROTO_ICMP:
+ printf("%s\t%s\t%s\t\t%d\t%s\n",
+ s, a, proto, ste->idle, f);
+ break;
+ default:
+ printf("%s\t%s\t%d\t\t%d\t%s\n",
+ s, a, ste->proto, ste->idle, f);
+ }
+ ste++;
+ sz -= sizeof(*ste);
+ }
+ stg = (ipfw_nat64lsn_stg_v1 *)ste;
+ }
+ return (next_idx);
+}
+
+static int
+nat64lsn_states_cb(ipfw_nat64lsn_cfg *cfg, const char *name, uint8_t set)
+{
+ ipfw_obj_header *oh;
+ ipfw_obj_data *od;
+ void *buf;
+ uint64_t next_idx;
+ size_t sz;
+
+ if (name != NULL && strcmp(cfg->name, name) != 0)
+ return (ESRCH);
+
+ if (set != 0 && cfg->set != set)
+ return (ESRCH);
+
+ next_idx = 0;
+ sz = 4096;
+ if ((buf = calloc(1, sz)) == NULL)
+ err(EX_OSERR, NULL);
+ do {
+ oh = (ipfw_obj_header *)buf;
+ oh->opheader.version = 1; /* Force using ov new API */
+ od = (ipfw_obj_data *)(oh + 1);
+ nat64lsn_fill_ntlv(&oh->ntlv, cfg->name, set);
+ od->head.type = IPFW_TLV_OBJDATA;
+ od->head.length = sizeof(*od) + sizeof(next_idx);
+ *((uint64_t *)(od + 1)) = next_idx;
+ if (do_get3(IP_FW_NAT64LSN_LIST_STATES, &oh->opheader, &sz))
+ err(EX_OSERR, "Error reading nat64lsn states");
+ next_idx = nat64lsn_print_states(buf);
+ sz = 4096;
+ memset(buf, 0, sz);
+ } while (next_idx != 0xFF);
+
+ free(buf);
+ return (0);
+}
+
+static struct _s_x nat64statscmds[] = {
+ { "reset", TOK_RESET },
+ { NULL, 0 }
+};
+
+static void
+ipfw_nat64lsn_stats_handler(const char *name, uint8_t set, int ac, char *av[])
+{
+ int tcmd;
+
+ if (ac == 0) {
+ nat64lsn_stats(name, set);
+ return;
+ }
+ NEED1("nat64lsn stats needs command");
+ tcmd = get_token(nat64statscmds, *av, "nat64lsn stats command");
+ switch (tcmd) {
+ case TOK_RESET:
+ nat64lsn_reset_stats(name, set);
+ }
+}
+
+static struct _s_x nat64listcmds[] = {
+ { "states", TOK_STATES },
+ { "config", TOK_CONFIG },
+ { NULL, 0 }
+};
+
+static void
+ipfw_nat64lsn_list_handler(const char *name, uint8_t set, int ac, char *av[])
+{
+ int tcmd;
+
+ if (ac == 0) {
+ nat64lsn_foreach(nat64lsn_show_cb, name, set, 1);
+ return;
+ }
+ NEED1("nat64lsn list needs command");
+ tcmd = get_token(nat64listcmds, *av, "nat64lsn list command");
+ switch (tcmd) {
+ case TOK_STATES:
+ nat64lsn_foreach(nat64lsn_states_cb, name, set, 1);
+ break;
+ case TOK_CONFIG:
+ nat64lsn_foreach(nat64lsn_show_cb, name, set, 1);
+ }
+}
+
+/*
+ * This one handles all nat64lsn-related commands
+ * ipfw [set N] nat64lsn NAME {create | config} ...
+ * ipfw [set N] nat64lsn NAME stats
+ * ipfw [set N] nat64lsn {NAME | all} destroy
+ * ipfw [set N] nat64lsn {NAME | all} {list | show} [config | states]
+ */
+#define nat64lsn_check_name table_check_name
+void
+ipfw_nat64lsn_handler(int ac, char *av[])
+{
+ const char *name;
+ int tcmd;
+ uint8_t set;
+
+ if (g_co.use_set != 0)
+ set = g_co.use_set - 1;
+ else
+ set = 0;
+ ac--; av++;
+
+ NEED1("nat64lsn needs instance name");
+ name = *av;
+ if (nat64lsn_check_name(name) != 0) {
+ if (strcmp(name, "all") == 0)
+ name = NULL;
+ else
+ errx(EX_USAGE, "nat64lsn instance name %s is invalid",
+ name);
+ }
+ ac--; av++;
+ NEED1("nat64lsn needs command");
+
+ tcmd = get_token(nat64cmds, *av, "nat64lsn command");
+ if (name == NULL && tcmd != TOK_DESTROY && tcmd != TOK_LIST)
+ errx(EX_USAGE, "nat64lsn instance name required");
+ switch (tcmd) {
+ case TOK_CREATE:
+ ac--; av++;
+ nat64lsn_create(name, set, ac, av);
+ break;
+ case TOK_CONFIG:
+ ac--; av++;
+ nat64lsn_config(name, set, ac, av);
+ break;
+ case TOK_LIST:
+ ac--; av++;
+ ipfw_nat64lsn_list_handler(name, set, ac, av);
+ break;
+ case TOK_DESTROY:
+ if (name == NULL)
+ nat64lsn_foreach(nat64lsn_destroy_cb, NULL, set, 0);
+ else
+ nat64lsn_destroy(name, set);
+ break;
+ case TOK_STATS:
+ ac--; av++;
+ ipfw_nat64lsn_stats_handler(name, set, ac, av);
+ }
+}
+
+static void
+nat64lsn_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, uint8_t set)
+{
+
+ ntlv->head.type = IPFW_TLV_EACTION_NAME(1); /* it doesn't matter */
+ ntlv->head.length = sizeof(ipfw_obj_ntlv);
+ ntlv->idx = 1;
+ ntlv->set = set;
+ strlcpy(ntlv->name, name, sizeof(ntlv->name));
+}
+
+static void
+nat64lsn_apply_mask(int af, void *prefix, uint16_t plen)
+{
+ struct in6_addr mask6, *p6;
+ struct in_addr mask4, *p4;
+
+ if (af == AF_INET) {
+ p4 = (struct in_addr *)prefix;
+ mask4.s_addr = htonl(~((1 << (32 - plen)) - 1));
+ p4->s_addr &= mask4.s_addr;
+ } else if (af == AF_INET6) {
+ p6 = (struct in6_addr *)prefix;
+ n2mask(&mask6, plen);
+ APPLY_MASK(p6, &mask6);
+ }
+}
+
+static void
+nat64lsn_parse_prefix(const char *arg, int af, void *prefix, uint16_t *plen)
+{
+ char *p, *l;
+
+ p = strdup(arg);
+ if (p == NULL)
+ err(EX_OSERR, NULL);
+ if ((l = strchr(p, '/')) != NULL)
+ *l++ = '\0';
+ if (l == NULL)
+ errx(EX_USAGE, "Prefix length required");
+ if (inet_pton(af, p, prefix) != 1)
+ errx(EX_USAGE, "Bad prefix: %s", p);
+ *plen = (uint16_t)strtol(l, &l, 10);
+ if (*l != '\0' || *plen == 0 || (af == AF_INET && *plen > 32) ||
+ (af == AF_INET6 && *plen > 96))
+ errx(EX_USAGE, "Bad prefix length: %s", arg);
+ nat64lsn_apply_mask(af, prefix, *plen);
+ free(p);
+}
+
+static uint32_t
+nat64lsn_parse_int(const char *arg, const char *desc)
+{
+ char *p;
+ uint32_t val;
+
+ val = (uint32_t)strtol(arg, &p, 10);
+ if (*p != '\0')
+ errx(EX_USAGE, "Invalid %s value: %s\n", desc, arg);
+ return (val);
+}
+
+static struct _s_x nat64newcmds[] = {
+ { "prefix6", TOK_PREFIX6 },
+ { "jmaxlen", TOK_JMAXLEN },
+ { "prefix4", TOK_PREFIX4 },
+ { "host_del_age", TOK_HOST_DEL_AGE },
+ { "pg_del_age", TOK_PG_DEL_AGE },
+ { "tcp_syn_age", TOK_TCP_SYN_AGE },
+ { "tcp_close_age",TOK_TCP_CLOSE_AGE },
+ { "tcp_est_age", TOK_TCP_EST_AGE },
+ { "udp_age", TOK_UDP_AGE },
+ { "icmp_age", TOK_ICMP_AGE },
+ { "states_chunks",TOK_STATES_CHUNKS },
+ { "log", TOK_LOG },
+ { "-log", TOK_LOGOFF },
+ { "allow_private", TOK_PRIVATE },
+ { "-allow_private", TOK_PRIVATEOFF },
+ { "swap_conf", TOK_SWAPCONF },
+ { "-swap_conf", TOK_SWAPCONFOFF },
+ /* for compatibility with old configurations */
+ { "max_ports", TOK_MAX_PORTS }, /* unused */
+ { NULL, 0 }
+};
+
+/*
+ * Creates new nat64lsn instance
+ * ipfw nat64lsn <NAME> create
+ * [ max_ports <N> ]
+ * Request: [ ipfw_obj_lheader ipfw_nat64lsn_cfg ]
+ */
+#define NAT64LSN_HAS_PREFIX4 0x01
+#define NAT64LSN_HAS_PREFIX6 0x02
+static void
+nat64lsn_create(const char *name, uint8_t set, int ac, char **av)
+{
+ char buf[sizeof(ipfw_obj_lheader) + sizeof(ipfw_nat64lsn_cfg)];
+ ipfw_nat64lsn_cfg *cfg;
+ ipfw_obj_lheader *olh;
+ int tcmd, flags;
+ char *opt;
+
+ memset(&buf, 0, sizeof(buf));
+ olh = (ipfw_obj_lheader *)buf;
+ cfg = (ipfw_nat64lsn_cfg *)(olh + 1);
+
+ /* Some reasonable defaults */
+ inet_pton(AF_INET6, "64:ff9b::", &cfg->prefix6);
+ cfg->plen6 = 96;
+ cfg->set = set;
+ cfg->max_ports = NAT64LSN_MAX_PORTS;
+ cfg->jmaxlen = NAT64LSN_JMAXLEN;
+ cfg->nh_delete_delay = NAT64LSN_HOST_AGE;
+ cfg->pg_delete_delay = NAT64LSN_PG_AGE;
+ cfg->st_syn_ttl = NAT64LSN_TCP_SYN_AGE;
+ cfg->st_estab_ttl = NAT64LSN_TCP_EST_AGE;
+ cfg->st_close_ttl = NAT64LSN_TCP_FIN_AGE;
+ cfg->st_udp_ttl = NAT64LSN_UDP_AGE;
+ cfg->st_icmp_ttl = NAT64LSN_ICMP_AGE;
+ flags = NAT64LSN_HAS_PREFIX6;
+ while (ac > 0) {
+ tcmd = get_token(nat64newcmds, *av, "option");
+ opt = *av;
+ ac--; av++;
+
+ switch (tcmd) {
+ case TOK_PREFIX4:
+ NEED1("IPv4 prefix required");
+ nat64lsn_parse_prefix(*av, AF_INET, &cfg->prefix4,
+ &cfg->plen4);
+ flags |= NAT64LSN_HAS_PREFIX4;
+ ac--; av++;
+ break;
+ case TOK_PREFIX6:
+ NEED1("IPv6 prefix required");
+ nat64lsn_parse_prefix(*av, AF_INET6, &cfg->prefix6,
+ &cfg->plen6);
+ if (ipfw_check_nat64prefix(&cfg->prefix6,
+ cfg->plen6) != 0 &&
+ !IN6_IS_ADDR_UNSPECIFIED(&cfg->prefix6))
+ errx(EX_USAGE, "Bad prefix6 %s", *av);
+
+ ac--; av++;
+ break;
+ case TOK_JMAXLEN:
+ NEED1("job queue length required");
+ cfg->jmaxlen = nat64lsn_parse_int(*av, opt);
+ ac--; av++;
+ break;
+ case TOK_MAX_PORTS:
+ NEED1("Max per-user ports required");
+ cfg->max_ports = nat64lsn_parse_int(*av, opt);
+ ac--; av++;
+ break;
+ case TOK_HOST_DEL_AGE:
+ NEED1("host delete delay required");
+ cfg->nh_delete_delay = (uint16_t)nat64lsn_parse_int(
+ *av, opt);
+ ac--; av++;
+ break;
+ case TOK_PG_DEL_AGE:
+ NEED1("portgroup delete delay required");
+ cfg->pg_delete_delay = (uint16_t)nat64lsn_parse_int(
+ *av, opt);
+ ac--; av++;
+ break;
+ case TOK_TCP_SYN_AGE:
+ NEED1("tcp syn age required");
+ cfg->st_syn_ttl = (uint16_t)nat64lsn_parse_int(
+ *av, opt);
+ ac--; av++;
+ break;
+ case TOK_TCP_CLOSE_AGE:
+ NEED1("tcp close age required");
+ cfg->st_close_ttl = (uint16_t)nat64lsn_parse_int(
+ *av, opt);
+ ac--; av++;
+ break;
+ case TOK_TCP_EST_AGE:
+ NEED1("tcp est age required");
+ cfg->st_estab_ttl = (uint16_t)nat64lsn_parse_int(
+ *av, opt);
+ ac--; av++;
+ break;
+ case TOK_UDP_AGE:
+ NEED1("udp age required");
+ cfg->st_udp_ttl = (uint16_t)nat64lsn_parse_int(
+ *av, opt);
+ ac--; av++;
+ break;
+ case TOK_ICMP_AGE:
+ NEED1("icmp age required");
+ cfg->st_icmp_ttl = (uint16_t)nat64lsn_parse_int(
+ *av, opt);
+ ac--; av++;
+ break;
+ case TOK_STATES_CHUNKS:
+ NEED1("number of chunks required");
+ cfg->states_chunks = (uint8_t)nat64lsn_parse_int(
+ *av, opt);
+ ac--; av++;
+ break;
+ case TOK_LOG:
+ cfg->flags |= NAT64_LOG;
+ break;
+ case TOK_LOGOFF:
+ cfg->flags &= ~NAT64_LOG;
+ break;
+ case TOK_PRIVATE:
+ cfg->flags |= NAT64_ALLOW_PRIVATE;
+ break;
+ case TOK_PRIVATEOFF:
+ cfg->flags &= ~NAT64_ALLOW_PRIVATE;
+ break;
+ case TOK_SWAPCONF:
+ cfg->flags |= NAT64LSN_ALLOW_SWAPCONF;
+ break;
+ case TOK_SWAPCONFOFF:
+ cfg->flags &= ~NAT64LSN_ALLOW_SWAPCONF;
+ break;
+ }
+ }
+
+ /* Check validness */
+ if ((flags & NAT64LSN_HAS_PREFIX4) != NAT64LSN_HAS_PREFIX4)
+ errx(EX_USAGE, "prefix4 required");
+
+ olh->count = 1;
+ olh->objsize = sizeof(*cfg);
+ olh->size = sizeof(buf);
+ strlcpy(cfg->name, name, sizeof(cfg->name));
+ if (do_set3(IP_FW_NAT64LSN_CREATE, &olh->opheader, sizeof(buf)) != 0)
+ err(EX_OSERR, "nat64lsn instance creation failed");
+}
+
+/*
+ * Configures existing nat64lsn instance
+ * ipfw nat64lsn <NAME> config <options>
+ * Request: [ ipfw_obj_header ipfw_nat64lsn_cfg ]
+ */
+static void
+nat64lsn_config(const char *name, uint8_t set, int ac, char **av)
+{
+ char buf[sizeof(ipfw_obj_header) + sizeof(ipfw_nat64lsn_cfg)];
+ ipfw_nat64lsn_cfg *cfg;
+ ipfw_obj_header *oh;
+ size_t sz;
+ char *opt;
+ int tcmd;
+
+ if (ac == 0)
+ errx(EX_USAGE, "config options required");
+ memset(&buf, 0, sizeof(buf));
+ oh = (ipfw_obj_header *)buf;
+ cfg = (ipfw_nat64lsn_cfg *)(oh + 1);
+ sz = sizeof(buf);
+
+ nat64lsn_fill_ntlv(&oh->ntlv, name, set);
+ if (do_get3(IP_FW_NAT64LSN_CONFIG, &oh->opheader, &sz) != 0)
+ err(EX_OSERR, "failed to get config for instance %s", name);
+
+ while (ac > 0) {
+ tcmd = get_token(nat64newcmds, *av, "option");
+ opt = *av;
+ ac--; av++;
+
+ switch (tcmd) {
+ case TOK_MAX_PORTS:
+ NEED1("Max per-user ports required");
+ cfg->max_ports = nat64lsn_parse_int(*av, opt);
+ ac--; av++;
+ break;
+ case TOK_JMAXLEN:
+ NEED1("job queue length required");
+ cfg->jmaxlen = nat64lsn_parse_int(*av, opt);
+ ac--; av++;
+ break;
+ case TOK_HOST_DEL_AGE:
+ NEED1("host delete delay required");
+ cfg->nh_delete_delay = (uint16_t)nat64lsn_parse_int(
+ *av, opt);
+ ac--; av++;
+ break;
+ case TOK_PG_DEL_AGE:
+ NEED1("portgroup delete delay required");
+ cfg->pg_delete_delay = (uint16_t)nat64lsn_parse_int(
+ *av, opt);
+ ac--; av++;
+ break;
+ case TOK_TCP_SYN_AGE:
+ NEED1("tcp syn age required");
+ cfg->st_syn_ttl = (uint16_t)nat64lsn_parse_int(
+ *av, opt);
+ ac--; av++;
+ break;
+ case TOK_TCP_CLOSE_AGE:
+ NEED1("tcp close age required");
+ cfg->st_close_ttl = (uint16_t)nat64lsn_parse_int(
+ *av, opt);
+ ac--; av++;
+ break;
+ case TOK_TCP_EST_AGE:
+ NEED1("tcp est age required");
+ cfg->st_estab_ttl = (uint16_t)nat64lsn_parse_int(
+ *av, opt);
+ ac--; av++;
+ break;
+ case TOK_UDP_AGE:
+ NEED1("udp age required");
+ cfg->st_udp_ttl = (uint16_t)nat64lsn_parse_int(
+ *av, opt);
+ ac--; av++;
+ break;
+ case TOK_ICMP_AGE:
+ NEED1("icmp age required");
+ cfg->st_icmp_ttl = (uint16_t)nat64lsn_parse_int(
+ *av, opt);
+ ac--; av++;
+ break;
+ case TOK_STATES_CHUNKS:
+ NEED1("number of chunks required");
+ cfg->states_chunks = (uint8_t)nat64lsn_parse_int(
+ *av, opt);
+ ac--; av++;
+ break;
+ case TOK_LOG:
+ cfg->flags |= NAT64_LOG;
+ break;
+ case TOK_LOGOFF:
+ cfg->flags &= ~NAT64_LOG;
+ break;
+ case TOK_PRIVATE:
+ cfg->flags |= NAT64_ALLOW_PRIVATE;
+ break;
+ case TOK_PRIVATEOFF:
+ cfg->flags &= ~NAT64_ALLOW_PRIVATE;
+ break;
+ case TOK_SWAPCONF:
+ cfg->flags |= NAT64LSN_ALLOW_SWAPCONF;
+ break;
+ case TOK_SWAPCONFOFF:
+ cfg->flags &= ~NAT64LSN_ALLOW_SWAPCONF;
+ break;
+ default:
+ errx(EX_USAGE, "Can't change %s option", opt);
+ }
+ }
+
+ if (do_set3(IP_FW_NAT64LSN_CONFIG, &oh->opheader, sizeof(buf)) != 0)
+ err(EX_OSERR, "nat64lsn instance configuration failed");
+}
+
+/*
+ * Reset nat64lsn instance statistics specified by @oh->ntlv.
+ * Request: [ ipfw_obj_header ]
+ */
+static void
+nat64lsn_reset_stats(const char *name, uint8_t set)
+{
+ ipfw_obj_header oh;
+
+ memset(&oh, 0, sizeof(oh));
+ nat64lsn_fill_ntlv(&oh.ntlv, name, set);
+ if (do_set3(IP_FW_NAT64LSN_RESET_STATS, &oh.opheader, sizeof(oh)) != 0)
+ err(EX_OSERR, "failed to reset stats for instance %s", name);
+}
+
+/*
+ * Destroys nat64lsn instance specified by @oh->ntlv.
+ * Request: [ ipfw_obj_header ]
+ */
+static void
+nat64lsn_destroy(const char *name, uint8_t set)
+{
+ ipfw_obj_header oh;
+
+ memset(&oh, 0, sizeof(oh));
+ nat64lsn_fill_ntlv(&oh.ntlv, name, set);
+ if (do_set3(IP_FW_NAT64LSN_DESTROY, &oh.opheader, sizeof(oh)) != 0)
+ err(EX_OSERR, "failed to destroy nat instance %s", name);
+}
+
+/*
+ * Get nat64lsn instance statistics.
+ * Request: [ ipfw_obj_header ]
+ * Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ] ]
+ */
+static int
+nat64lsn_get_stats(const char *name, uint8_t set,
+ struct ipfw_nat64lsn_stats *stats)
+{
+ ipfw_obj_header *oh;
+ ipfw_obj_ctlv *oc;
+ size_t sz;
+
+ sz = sizeof(*oh) + sizeof(*oc) + sizeof(*stats);
+ oh = calloc(1, sz);
+ nat64lsn_fill_ntlv(&oh->ntlv, name, set);
+ if (do_get3(IP_FW_NAT64LSN_STATS, &oh->opheader, &sz) == 0) {
+ oc = (ipfw_obj_ctlv *)(oh + 1);
+ memcpy(stats, oc + 1, sizeof(*stats));
+ free(oh);
+ return (0);
+ }
+ free(oh);
+ return (-1);
+}
+
+static void
+nat64lsn_stats(const char *name, uint8_t set)
+{
+ struct ipfw_nat64lsn_stats stats;
+
+ if (nat64lsn_get_stats(name, set, &stats) != 0)
+ err(EX_OSERR, "Error retrieving stats");
+
+ if (g_co.use_set != 0 || set != 0)
+ printf("set %u ", set);
+ printf("nat64lsn %s\n", name);
+ printf("\t%ju packets translated from IPv6 to IPv4\n",
+ (uintmax_t)stats.opcnt64);
+ printf("\t%ju packets translated from IPv4 to IPv6\n",
+ (uintmax_t)stats.opcnt46);
+ printf("\t%ju IPv6 fragments created\n",
+ (uintmax_t)stats.ofrags);
+ printf("\t%ju IPv4 fragments received\n",
+ (uintmax_t)stats.ifrags);
+ printf("\t%ju output packets dropped due to no bufs, etc.\n",
+ (uintmax_t)stats.oerrors);
+ printf("\t%ju output packets discarded due to no IPv4 route\n",
+ (uintmax_t)stats.noroute4);
+ printf("\t%ju output packets discarded due to no IPv6 route\n",
+ (uintmax_t)stats.noroute6);
+ printf("\t%ju packets discarded due to unsupported protocol\n",
+ (uintmax_t)stats.noproto);
+ printf("\t%ju packets discarded due to memory allocation problems\n",
+ (uintmax_t)stats.nomem);
+ printf("\t%ju packets discarded due to some errors\n",
+ (uintmax_t)stats.dropped);
+ printf("\t%ju packets not matched with IPv4 prefix\n",
+ (uintmax_t)stats.nomatch4);
+
+ printf("\t%ju mbufs queued for post processing\n",
+ (uintmax_t)stats.jreinjected);
+ printf("\t%ju times the job queue was processed\n",
+ (uintmax_t)stats.jcalls);
+ printf("\t%ju job requests queued\n",
+ (uintmax_t)stats.jrequests);
+ printf("\t%ju job requests queue limit reached\n",
+ (uintmax_t)stats.jmaxlen);
+ printf("\t%ju job requests failed due to memory allocation problems\n",
+ (uintmax_t)stats.jnomem);
+
+ printf("\t%ju hosts allocated\n", (uintmax_t)stats.hostcount);
+ printf("\t%ju hosts requested\n", (uintmax_t)stats.jhostsreq);
+ printf("\t%ju host requests failed\n", (uintmax_t)stats.jhostfails);
+
+ printf("\t%ju portgroups requested\n", (uintmax_t)stats.jportreq);
+ printf("\t%ju portgroups allocated\n", (uintmax_t)stats.spgcreated);
+ printf("\t%ju portgroups deleted\n", (uintmax_t)stats.spgdeleted);
+ printf("\t%ju portgroup requests failed\n",
+ (uintmax_t)stats.jportfails);
+ printf("\t%ju portgroups allocated for TCP\n",
+ (uintmax_t)stats.tcpchunks);
+ printf("\t%ju portgroups allocated for UDP\n",
+ (uintmax_t)stats.udpchunks);
+ printf("\t%ju portgroups allocated for ICMP\n",
+ (uintmax_t)stats.icmpchunks);
+
+ printf("\t%ju states created\n", (uintmax_t)stats.screated);
+ printf("\t%ju states deleted\n", (uintmax_t)stats.sdeleted);
+}
+
+static int
+nat64lsn_show_cb(ipfw_nat64lsn_cfg *cfg, const char *name, uint8_t set)
+{
+ char abuf[INET6_ADDRSTRLEN];
+
+ if (name != NULL && strcmp(cfg->name, name) != 0)
+ return (ESRCH);
+
+ if (g_co.use_set != 0 && cfg->set != set)
+ return (ESRCH);
+
+ if (g_co.use_set != 0 || cfg->set != 0)
+ printf("set %u ", cfg->set);
+ inet_ntop(AF_INET, &cfg->prefix4, abuf, sizeof(abuf));
+ printf("nat64lsn %s prefix4 %s/%u", cfg->name, abuf, cfg->plen4);
+ inet_ntop(AF_INET6, &cfg->prefix6, abuf, sizeof(abuf));
+ printf(" prefix6 %s/%u", abuf, cfg->plen6);
+ if (g_co.verbose || cfg->states_chunks > 1)
+ printf(" states_chunks %u", cfg->states_chunks);
+ if (g_co.verbose || cfg->nh_delete_delay != NAT64LSN_HOST_AGE)
+ printf(" host_del_age %u", cfg->nh_delete_delay);
+ if (g_co.verbose || cfg->pg_delete_delay != NAT64LSN_PG_AGE)
+ printf(" pg_del_age %u", cfg->pg_delete_delay);
+ if (g_co.verbose || cfg->st_syn_ttl != NAT64LSN_TCP_SYN_AGE)
+ printf(" tcp_syn_age %u", cfg->st_syn_ttl);
+ if (g_co.verbose || cfg->st_close_ttl != NAT64LSN_TCP_FIN_AGE)
+ printf(" tcp_close_age %u", cfg->st_close_ttl);
+ if (g_co.verbose || cfg->st_estab_ttl != NAT64LSN_TCP_EST_AGE)
+ printf(" tcp_est_age %u", cfg->st_estab_ttl);
+ if (g_co.verbose || cfg->st_udp_ttl != NAT64LSN_UDP_AGE)
+ printf(" udp_age %u", cfg->st_udp_ttl);
+ if (g_co.verbose || cfg->st_icmp_ttl != NAT64LSN_ICMP_AGE)
+ printf(" icmp_age %u", cfg->st_icmp_ttl);
+ if (g_co.verbose || cfg->jmaxlen != NAT64LSN_JMAXLEN)
+ printf(" jmaxlen %u", cfg->jmaxlen);
+ if (cfg->flags & NAT64LSN_ALLOW_SWAPCONF)
+ printf(" swap_conf");
+ if (cfg->flags & NAT64_LOG)
+ printf(" log");
+ if (cfg->flags & NAT64_ALLOW_PRIVATE)
+ printf(" allow_private");
+ printf("\n");
+ return (0);
+}
+
+static int
+nat64lsn_destroy_cb(ipfw_nat64lsn_cfg *cfg, const char *name __unused,
+ uint8_t set)
+{
+
+ if (g_co.use_set != 0 && cfg->set != set)
+ return (ESRCH);
+
+ nat64lsn_destroy(cfg->name, cfg->set);
+ return (0);
+}
+
+
+/*
+ * Compare nat64lsn instances names.
+ * Honor number comparison.
+ */
+static int
+nat64name_cmp(const void *a, const void *b)
+{
+ const ipfw_nat64lsn_cfg *ca, *cb;
+
+ ca = (const ipfw_nat64lsn_cfg *)a;
+ cb = (const ipfw_nat64lsn_cfg *)b;
+
+ if (ca->set > cb->set)
+ return (1);
+ else if (ca->set < cb->set)
+ return (-1);
+ return (stringnum_cmp(ca->name, cb->name));
+}
+
+/*
+ * Retrieves nat64lsn instance list from kernel,
+ * optionally sorts it and calls requested function for each instance.
+ *
+ * Request: [ ipfw_obj_lheader ]
+ * Reply: [ ipfw_obj_lheader ipfw_nat64lsn_cfg x N ]
+ */
+static int
+nat64lsn_foreach(nat64lsn_cb_t *f, const char *name, uint8_t set, int sort)
+{
+ ipfw_obj_lheader *olh;
+ ipfw_nat64lsn_cfg *cfg;
+ size_t sz;
+ uint32_t i;
+
+ /* Start with reasonable default */
+ sz = sizeof(*olh) + 16 * sizeof(ipfw_nat64lsn_cfg);
+
+ for (;;) {
+ if ((olh = calloc(1, sz)) == NULL)
+ return (ENOMEM);
+
+ olh->size = sz;
+ if (do_get3(IP_FW_NAT64LSN_LIST, &olh->opheader, &sz) != 0) {
+ sz = olh->size;
+ free(olh);
+ if (errno != ENOMEM)
+ return (errno);
+ continue;
+ }
+
+ if (sort != 0)
+ qsort(olh + 1, olh->count, olh->objsize,
+ nat64name_cmp);
+
+ cfg = (ipfw_nat64lsn_cfg *)(olh + 1);
+ for (i = 0; i < olh->count; i++) {
+ (void)f(cfg, name, set); /* Ignore errors for now */
+ cfg = (ipfw_nat64lsn_cfg *)((caddr_t)cfg +
+ olh->objsize);
+ }
+ free(olh);
+ break;
+ }
+ return (0);
+}
+
diff --git a/sbin/ipfw15/nat64stl.c b/sbin/ipfw15/nat64stl.c
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/nat64stl.c
@@ -0,0 +1,552 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2015-2019 Yandex LLC
+ * Copyright (c) 2015-2019 Andrey V. Elsukov <ae@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "ipfw2.h"
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.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/ip_fw15.h>
+#include <netinet6/ip_fw_nat64_15.h>
+#include <arpa/inet.h>
+
+typedef int (nat64stl_cb_t)(ipfw_nat64stl_cfg *i, const char *name,
+ uint8_t set);
+static int nat64stl_foreach(nat64stl_cb_t *f, const char *name, uint8_t set,
+ int sort);
+
+static void nat64stl_create(const char *name, uint8_t set, int ac, char **av);
+static void nat64stl_config(const char *name, uint8_t set, int ac, char **av);
+static void nat64stl_destroy(const char *name, uint8_t set);
+static void nat64stl_stats(const char *name, uint8_t set);
+static void nat64stl_reset_stats(const char *name, uint8_t set);
+static int nat64stl_show_cb(ipfw_nat64stl_cfg *cfg, const char *name,
+ uint8_t set);
+static int nat64stl_destroy_cb(ipfw_nat64stl_cfg *cfg, const char *name,
+ uint8_t set);
+
+static struct _s_x nat64cmds[] = {
+ { "create", TOK_CREATE },
+ { "config", TOK_CONFIG },
+ { "destroy", TOK_DESTROY },
+ { "list", TOK_LIST },
+ { "show", TOK_LIST },
+ { "stats", TOK_STATS },
+ { NULL, 0 }
+};
+
+#define IPV6_ADDR_INT32_WKPFX htonl(0x64ff9b)
+#define IN6_IS_ADDR_WKPFX(a) \
+ ((a)->__u6_addr.__u6_addr32[0] == IPV6_ADDR_INT32_WKPFX && \
+ (a)->__u6_addr.__u6_addr32[1] == 0 && \
+ (a)->__u6_addr.__u6_addr32[2] == 0)
+int
+ipfw_check_nat64prefix(const struct in6_addr *prefix, int length)
+{
+
+ switch (length) {
+ case 32:
+ case 40:
+ case 48:
+ case 56:
+ case 64:
+ /* Well-known prefix has 96 prefix length */
+ if (IN6_IS_ADDR_WKPFX(prefix))
+ return (EINVAL);
+ /* FALLTHROUGH */
+ case 96:
+ /* Bits 64 to 71 must be set to zero */
+ if (prefix->__u6_addr.__u6_addr8[8] != 0)
+ return (EINVAL);
+ /* XXX: looks incorrect */
+ if (IN6_IS_ADDR_MULTICAST(prefix) ||
+ IN6_IS_ADDR_UNSPECIFIED(prefix) ||
+ IN6_IS_ADDR_LOOPBACK(prefix))
+ return (EINVAL);
+ return (0);
+ }
+ return (EINVAL);
+}
+
+static struct _s_x nat64statscmds[] = {
+ { "reset", TOK_RESET },
+ { NULL, 0 }
+};
+
+/*
+ * This one handles all nat64stl-related commands
+ * ipfw [set N] nat64stl NAME {create | config} ...
+ * ipfw [set N] nat64stl NAME stats [reset]
+ * ipfw [set N] nat64stl {NAME | all} destroy
+ * ipfw [set N] nat64stl {NAME | all} {list | show}
+ */
+#define nat64stl_check_name table_check_name
+void
+ipfw_nat64stl_handler(int ac, char *av[])
+{
+ const char *name;
+ int tcmd;
+ uint8_t set;
+
+ if (g_co.use_set != 0)
+ set = g_co.use_set - 1;
+ else
+ set = 0;
+ ac--; av++;
+
+ NEED1("nat64stl needs instance name");
+ name = *av;
+ if (nat64stl_check_name(name) != 0) {
+ if (strcmp(name, "all") == 0)
+ name = NULL;
+ else
+ errx(EX_USAGE, "nat64stl instance name %s is invalid",
+ name);
+ }
+ ac--; av++;
+ NEED1("nat64stl needs command");
+
+ tcmd = get_token(nat64cmds, *av, "nat64stl command");
+ if (name == NULL && tcmd != TOK_DESTROY && tcmd != TOK_LIST)
+ errx(EX_USAGE, "nat64stl instance name required");
+ switch (tcmd) {
+ case TOK_CREATE:
+ ac--; av++;
+ nat64stl_create(name, set, ac, av);
+ break;
+ case TOK_CONFIG:
+ ac--; av++;
+ nat64stl_config(name, set, ac, av);
+ break;
+ case TOK_LIST:
+ nat64stl_foreach(nat64stl_show_cb, name, set, 1);
+ break;
+ case TOK_DESTROY:
+ if (name == NULL)
+ nat64stl_foreach(nat64stl_destroy_cb, NULL, set, 0);
+ else
+ nat64stl_destroy(name, set);
+ break;
+ case TOK_STATS:
+ ac--; av++;
+ if (ac == 0) {
+ nat64stl_stats(name, set);
+ break;
+ }
+ tcmd = get_token(nat64statscmds, *av, "stats command");
+ if (tcmd == TOK_RESET)
+ nat64stl_reset_stats(name, set);
+ }
+}
+
+
+static void
+nat64stl_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, uint8_t set)
+{
+
+ ntlv->head.type = IPFW_TLV_EACTION_NAME(1); /* it doesn't matter */
+ ntlv->head.length = sizeof(ipfw_obj_ntlv);
+ ntlv->idx = 1;
+ ntlv->set = set;
+ strlcpy(ntlv->name, name, sizeof(ntlv->name));
+}
+
+static struct _s_x nat64newcmds[] = {
+ { "table4", TOK_TABLE4 },
+ { "table6", TOK_TABLE6 },
+ { "prefix6", TOK_PREFIX6 },
+ { "log", TOK_LOG },
+ { "-log", TOK_LOGOFF },
+ { "allow_private", TOK_PRIVATE },
+ { "-allow_private", TOK_PRIVATEOFF },
+ { NULL, 0 }
+};
+
+/*
+ * Creates new nat64stl instance
+ * ipfw nat64stl <NAME> create table4 <name> table6 <name> [ prefix6 <prefix>]
+ * Request: [ ipfw_obj_lheader ipfw_nat64stl_cfg ]
+ */
+#define NAT64STL_HAS_TABLE4 0x01
+#define NAT64STL_HAS_TABLE6 0x02
+#define NAT64STL_HAS_PREFIX6 0x04
+static void
+nat64stl_create(const char *name, uint8_t set, int ac, char *av[])
+{
+ char buf[sizeof(ipfw_obj_lheader) + sizeof(ipfw_nat64stl_cfg)];
+ ipfw_nat64stl_cfg *cfg;
+ ipfw_obj_lheader *olh;
+ int tcmd, flags;
+ char *p;
+
+ memset(buf, 0, sizeof(buf));
+ olh = (ipfw_obj_lheader *)buf;
+ cfg = (ipfw_nat64stl_cfg *)(olh + 1);
+
+ /* Some reasonable defaults */
+ inet_pton(AF_INET6, "64:ff9b::", &cfg->prefix6);
+ cfg->plen6 = 96;
+ cfg->set = set;
+ flags = NAT64STL_HAS_PREFIX6;
+ while (ac > 0) {
+ tcmd = get_token(nat64newcmds, *av, "option");
+ ac--; av++;
+
+ switch (tcmd) {
+ case TOK_TABLE4:
+ NEED1("table name required");
+ table_fill_ntlv(&cfg->ntlv4, *av, set, 4);
+ flags |= NAT64STL_HAS_TABLE4;
+ ac--; av++;
+ break;
+ case TOK_TABLE6:
+ NEED1("table name required");
+ table_fill_ntlv(&cfg->ntlv6, *av, set, 6);
+ flags |= NAT64STL_HAS_TABLE6;
+ ac--; av++;
+ break;
+ case TOK_PREFIX6:
+ NEED1("IPv6 prefix6 required");
+ if ((p = strchr(*av, '/')) != NULL)
+ *p++ = '\0';
+ else
+ errx(EX_USAGE,
+ "Prefix length required: %s", *av);
+ if (inet_pton(AF_INET6, *av, &cfg->prefix6) != 1)
+ errx(EX_USAGE,
+ "Bad prefix: %s", *av);
+ cfg->plen6 = strtol(p, NULL, 10);
+ if (ipfw_check_nat64prefix(&cfg->prefix6,
+ cfg->plen6) != 0)
+ errx(EX_USAGE,
+ "Bad prefix length: %s", p);
+ flags |= NAT64STL_HAS_PREFIX6;
+ ac--; av++;
+ break;
+ case TOK_LOG:
+ cfg->flags |= NAT64_LOG;
+ break;
+ case TOK_LOGOFF:
+ cfg->flags &= ~NAT64_LOG;
+ break;
+ case TOK_PRIVATE:
+ cfg->flags |= NAT64_ALLOW_PRIVATE;
+ break;
+ case TOK_PRIVATEOFF:
+ cfg->flags &= ~NAT64_ALLOW_PRIVATE;
+ break;
+ }
+ }
+
+ /* Check validness */
+ if ((flags & NAT64STL_HAS_TABLE4) != NAT64STL_HAS_TABLE4)
+ errx(EX_USAGE, "table4 required");
+ if ((flags & NAT64STL_HAS_TABLE6) != NAT64STL_HAS_TABLE6)
+ errx(EX_USAGE, "table6 required");
+ if ((flags & NAT64STL_HAS_PREFIX6) != NAT64STL_HAS_PREFIX6)
+ errx(EX_USAGE, "prefix6 required");
+
+ olh->count = 1;
+ olh->objsize = sizeof(*cfg);
+ olh->size = sizeof(buf);
+ strlcpy(cfg->name, name, sizeof(cfg->name));
+ if (do_set3(IP_FW_NAT64STL_CREATE, &olh->opheader, sizeof(buf)) != 0)
+ err(EX_OSERR, "nat64stl instance creation failed");
+}
+
+/*
+ * Configures existing nat64stl instance
+ * ipfw nat64stl <NAME> config <options>
+ * Request: [ ipfw_obj_header ipfw_nat64stl_cfg ]
+ */
+static void
+nat64stl_config(const char *name, uint8_t set, int ac, char **av)
+{
+ char buf[sizeof(ipfw_obj_header) + sizeof(ipfw_nat64stl_cfg)];
+ ipfw_nat64stl_cfg *cfg;
+ ipfw_obj_header *oh;
+ char *opt;
+ size_t sz;
+ int tcmd;
+
+ if (ac == 0)
+ errx(EX_USAGE, "config options required");
+ memset(&buf, 0, sizeof(buf));
+ oh = (ipfw_obj_header *)buf;
+ cfg = (ipfw_nat64stl_cfg *)(oh + 1);
+ sz = sizeof(buf);
+
+ nat64stl_fill_ntlv(&oh->ntlv, name, set);
+ if (do_get3(IP_FW_NAT64STL_CONFIG, &oh->opheader, &sz) != 0)
+ err(EX_OSERR, "failed to get config for instance %s", name);
+
+ while (ac > 0) {
+ tcmd = get_token(nat64newcmds, *av, "option");
+ opt = *av;
+ ac--; av++;
+
+ switch (tcmd) {
+#if 0
+ case TOK_TABLE4:
+ NEED1("table name required");
+ table_fill_ntlv(&cfg->ntlv4, *av, set, 4);
+ ac--; av++;
+ break;
+ case TOK_TABLE6:
+ NEED1("table name required");
+ table_fill_ntlv(&cfg->ntlv6, *av, set, 6);
+ ac--; av++;
+ break;
+#endif
+ case TOK_LOG:
+ cfg->flags |= NAT64_LOG;
+ break;
+ case TOK_LOGOFF:
+ cfg->flags &= ~NAT64_LOG;
+ break;
+ case TOK_PRIVATE:
+ cfg->flags |= NAT64_ALLOW_PRIVATE;
+ break;
+ case TOK_PRIVATEOFF:
+ cfg->flags &= ~NAT64_ALLOW_PRIVATE;
+ break;
+ default:
+ errx(EX_USAGE, "Can't change %s option", opt);
+ }
+ }
+
+ if (do_set3(IP_FW_NAT64STL_CONFIG, &oh->opheader, sizeof(buf)) != 0)
+ err(EX_OSERR, "nat64stl instance configuration failed");
+}
+
+/*
+ * Destroys nat64stl instance.
+ * Request: [ ipfw_obj_header ]
+ */
+static void
+nat64stl_destroy(const char *name, uint8_t set)
+{
+ ipfw_obj_header oh;
+
+ memset(&oh, 0, sizeof(oh));
+ nat64stl_fill_ntlv(&oh.ntlv, name, set);
+ if (do_set3(IP_FW_NAT64STL_DESTROY, &oh.opheader, sizeof(oh)) != 0)
+ err(EX_OSERR, "failed to destroy nat instance %s", name);
+}
+
+/*
+ * Get nat64stl instance statistics.
+ * Request: [ ipfw_obj_header ]
+ * Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ] ]
+ */
+static int
+nat64stl_get_stats(const char *name, uint8_t set,
+ struct ipfw_nat64stl_stats *stats)
+{
+ ipfw_obj_header *oh;
+ ipfw_obj_ctlv *oc;
+ size_t sz;
+
+ sz = sizeof(*oh) + sizeof(*oc) + sizeof(*stats);
+ oh = calloc(1, sz);
+ nat64stl_fill_ntlv(&oh->ntlv, name, set);
+ if (do_get3(IP_FW_NAT64STL_STATS, &oh->opheader, &sz) == 0) {
+ oc = (ipfw_obj_ctlv *)(oh + 1);
+ memcpy(stats, oc + 1, sizeof(*stats));
+ free(oh);
+ return (0);
+ }
+ free(oh);
+ return (-1);
+}
+
+static void
+nat64stl_stats(const char *name, uint8_t set)
+{
+ struct ipfw_nat64stl_stats stats;
+
+ if (nat64stl_get_stats(name, set, &stats) != 0)
+ err(EX_OSERR, "Error retrieving stats");
+
+ if (g_co.use_set != 0 || set != 0)
+ printf("set %u ", set);
+ printf("nat64stl %s\n", name);
+
+ printf("\t%ju packets translated from IPv6 to IPv4\n",
+ (uintmax_t)stats.opcnt64);
+ printf("\t%ju packets translated from IPv4 to IPv6\n",
+ (uintmax_t)stats.opcnt46);
+ printf("\t%ju IPv6 fragments created\n",
+ (uintmax_t)stats.ofrags);
+ printf("\t%ju IPv4 fragments received\n",
+ (uintmax_t)stats.ifrags);
+ printf("\t%ju output packets dropped due to no bufs, etc.\n",
+ (uintmax_t)stats.oerrors);
+ printf("\t%ju output packets discarded due to no IPv4 route\n",
+ (uintmax_t)stats.noroute4);
+ printf("\t%ju output packets discarded due to no IPv6 route\n",
+ (uintmax_t)stats.noroute6);
+ printf("\t%ju packets discarded due to unsupported protocol\n",
+ (uintmax_t)stats.noproto);
+ printf("\t%ju packets discarded due to memory allocation problems\n",
+ (uintmax_t)stats.nomem);
+ printf("\t%ju packets discarded due to some errors\n",
+ (uintmax_t)stats.dropped);
+}
+
+/*
+ * Reset nat64stl instance statistics specified by @oh->ntlv.
+ * Request: [ ipfw_obj_header ]
+ */
+static void
+nat64stl_reset_stats(const char *name, uint8_t set)
+{
+ ipfw_obj_header oh;
+
+ memset(&oh, 0, sizeof(oh));
+ nat64stl_fill_ntlv(&oh.ntlv, name, set);
+ if (do_set3(IP_FW_NAT64STL_RESET_STATS, &oh.opheader, sizeof(oh)) != 0)
+ err(EX_OSERR, "failed to reset stats for instance %s", name);
+}
+
+static int
+nat64stl_show_cb(ipfw_nat64stl_cfg *cfg, const char *name, uint8_t set)
+{
+ char abuf[INET6_ADDRSTRLEN];
+
+ if (name != NULL && strcmp(cfg->name, name) != 0)
+ return (ESRCH);
+
+ if (g_co.use_set != 0 && cfg->set != set)
+ return (ESRCH);
+
+ if (g_co.use_set != 0 || cfg->set != 0)
+ printf("set %u ", cfg->set);
+
+ printf("nat64stl %s table4 %s table6 %s",
+ cfg->name, cfg->ntlv4.name, cfg->ntlv6.name);
+ inet_ntop(AF_INET6, &cfg->prefix6, abuf, sizeof(abuf));
+ printf(" prefix6 %s/%u", abuf, cfg->plen6);
+ if (cfg->flags & NAT64_LOG)
+ printf(" log");
+ if (cfg->flags & NAT64_ALLOW_PRIVATE)
+ printf(" allow_private");
+ printf("\n");
+ return (0);
+}
+
+static int
+nat64stl_destroy_cb(ipfw_nat64stl_cfg *cfg, const char *name __unused,
+ uint8_t set)
+{
+
+ if (g_co.use_set != 0 && cfg->set != set)
+ return (ESRCH);
+
+ nat64stl_destroy(cfg->name, cfg->set);
+ return (0);
+}
+
+
+/*
+ * Compare nat64stl instances names.
+ * Honor number comparison.
+ */
+static int
+nat64name_cmp(const void *a, const void *b)
+{
+ const ipfw_nat64stl_cfg *ca, *cb;
+
+ ca = (const ipfw_nat64stl_cfg *)a;
+ cb = (const ipfw_nat64stl_cfg *)b;
+
+ if (ca->set > cb->set)
+ return (1);
+ else if (ca->set < cb->set)
+ return (-1);
+ return (stringnum_cmp(ca->name, cb->name));
+}
+
+/*
+ * Retrieves nat64stl instance list from kernel,
+ * optionally sorts it and calls requested function for each instance.
+ *
+ * Request: [ ipfw_obj_lheader ]
+ * Reply: [ ipfw_obj_lheader ipfw_nat64stl_cfg x N ]
+ */
+static int
+nat64stl_foreach(nat64stl_cb_t *f, const char *name, uint8_t set, int sort)
+{
+ ipfw_obj_lheader *olh;
+ ipfw_nat64stl_cfg *cfg;
+ size_t sz;
+ uint32_t i;
+
+ /* Start with reasonable default */
+ sz = sizeof(*olh) + 16 * sizeof(*cfg);
+ for (;;) {
+ if ((olh = calloc(1, sz)) == NULL)
+ return (ENOMEM);
+
+ olh->size = sz;
+ if (do_get3(IP_FW_NAT64STL_LIST, &olh->opheader, &sz) != 0) {
+ sz = olh->size;
+ free(olh);
+ if (errno != ENOMEM)
+ return (errno);
+ continue;
+ }
+
+ if (sort != 0)
+ qsort(olh + 1, olh->count, olh->objsize,
+ nat64name_cmp);
+
+ cfg = (ipfw_nat64stl_cfg *)(olh + 1);
+ for (i = 0; i < olh->count; i++) {
+ (void)f(cfg, name, set); /* Ignore errors for now */
+ cfg = (ipfw_nat64stl_cfg *)((caddr_t)cfg +
+ olh->objsize);
+ }
+ free(olh);
+ break;
+ }
+ return (0);
+}
+
diff --git a/sbin/ipfw15/nptv6.c b/sbin/ipfw15/nptv6.c
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/nptv6.c
@@ -0,0 +1,452 @@
+/*-
+ * Copyright (c) 2016 Yandex LLC
+ * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+
+#include "ipfw2.h"
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/ip_fw15.h>
+#include <netinet6/ip_fw_nptv6.h>
+#include <arpa/inet.h>
+
+
+typedef int (nptv6_cb_t)(ipfw_nptv6_cfg *i, const char *name, uint8_t set);
+static int nptv6_foreach(nptv6_cb_t *f, const char *name, uint8_t set,
+ int sort);
+
+static void nptv6_create(const char *name, uint8_t set, int ac, char **av);
+static void nptv6_destroy(const char *name, uint8_t set);
+static void nptv6_stats(const char *name, uint8_t set);
+static void nptv6_reset_stats(const char *name, uint8_t set);
+static int nptv6_show_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set);
+static int nptv6_destroy_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set);
+
+static struct _s_x nptv6cmds[] = {
+ { "create", TOK_CREATE },
+ { "destroy", TOK_DESTROY },
+ { "list", TOK_LIST },
+ { "show", TOK_LIST },
+ { "stats", TOK_STATS },
+ { NULL, 0 }
+};
+
+static struct _s_x nptv6statscmds[] = {
+ { "reset", TOK_RESET },
+ { NULL, 0 }
+};
+
+/*
+ * This one handles all NPTv6-related commands
+ * ipfw [set N] nptv6 NAME {create | config} ...
+ * ipfw [set N] nptv6 NAME stats [reset]
+ * ipfw [set N] nptv6 {NAME | all} destroy
+ * ipfw [set N] nptv6 {NAME | all} {list | show}
+ */
+#define nptv6_check_name table_check_name
+void
+ipfw_nptv6_handler(int ac, char *av[])
+{
+ const char *name;
+ int tcmd;
+ uint8_t set;
+
+ if (g_co.use_set != 0)
+ set = g_co.use_set - 1;
+ else
+ set = 0;
+ ac--; av++;
+
+ NEED1("nptv6 needs instance name");
+ name = *av;
+ if (nptv6_check_name(name) != 0) {
+ if (strcmp(name, "all") == 0) {
+ name = NULL;
+ } else
+ errx(EX_USAGE, "nptv6 instance name %s is invalid",
+ name);
+ }
+ ac--; av++;
+ NEED1("nptv6 needs command");
+
+ tcmd = get_token(nptv6cmds, *av, "nptv6 command");
+ if (name == NULL && tcmd != TOK_DESTROY && tcmd != TOK_LIST)
+ errx(EX_USAGE, "nptv6 instance name required");
+ switch (tcmd) {
+ case TOK_CREATE:
+ ac--; av++;
+ nptv6_create(name, set, ac, av);
+ break;
+ case TOK_LIST:
+ nptv6_foreach(nptv6_show_cb, name, set, 1);
+ break;
+ case TOK_DESTROY:
+ if (name == NULL)
+ nptv6_foreach(nptv6_destroy_cb, NULL, set, 0);
+ else
+ nptv6_destroy(name, set);
+ break;
+ case TOK_STATS:
+ ac--; av++;
+ if (ac == 0) {
+ nptv6_stats(name, set);
+ break;
+ }
+ tcmd = get_token(nptv6statscmds, *av, "stats command");
+ if (tcmd == TOK_RESET)
+ nptv6_reset_stats(name, set);
+ }
+}
+
+
+static void
+nptv6_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, uint8_t set)
+{
+
+ ntlv->head.type = IPFW_TLV_EACTION_NAME(1); /* it doesn't matter */
+ ntlv->head.length = sizeof(ipfw_obj_ntlv);
+ ntlv->idx = 1;
+ ntlv->set = set;
+ strlcpy(ntlv->name, name, sizeof(ntlv->name));
+}
+
+static struct _s_x nptv6newcmds[] = {
+ { "int_prefix", TOK_INTPREFIX },
+ { "ext_prefix", TOK_EXTPREFIX },
+ { "prefixlen", TOK_PREFIXLEN },
+ { "ext_if", TOK_EXTIF },
+ { NULL, 0 }
+};
+
+static void
+nptv6_parse_prefix(const char *arg, struct in6_addr *prefix, int *len)
+{
+ long plen;
+ char *p, *l;
+
+ p = strdup(arg);
+ if (p == NULL)
+ err(EX_OSERR, NULL);
+ if ((l = strchr(p, '/')) != NULL)
+ *l++ = '\0';
+ if (inet_pton(AF_INET6, p, prefix) != 1)
+ errx(EX_USAGE, "Bad prefix: %s", p);
+ if (l != NULL) {
+ plen = strtol(l, &l, 10);
+ if (*l != '\0' || plen < 8 || plen > 64)
+ errx(EX_USAGE, "Bad prefix length: %s", arg);
+ *len = plen;
+ } else
+ *len = 0;
+ free(p);
+}
+
+/*
+ * Creates new nptv6 instance
+ * ipfw nptv6 <NAME> create int_prefix <prefix> ext_prefix <prefix>
+ * Request: [ ipfw_obj_lheader ipfw_nptv6_cfg ]
+ */
+#define NPTV6_HAS_INTPREFIX 0x01
+#define NPTV6_HAS_EXTPREFIX 0x02
+#define NPTV6_HAS_PREFIXLEN 0x04
+static void
+nptv6_create(const char *name, uint8_t set, int ac, char *av[])
+{
+ char buf[sizeof(ipfw_obj_lheader) + sizeof(ipfw_nptv6_cfg)];
+ struct in6_addr mask;
+ ipfw_nptv6_cfg *cfg;
+ ipfw_obj_lheader *olh;
+ int tcmd, flags, iplen, eplen, pplen;
+ char *p;
+
+ iplen = eplen = pplen = 0;
+ memset(buf, 0, sizeof(buf));
+ olh = (ipfw_obj_lheader *)buf;
+ cfg = (ipfw_nptv6_cfg *)(olh + 1);
+ cfg->set = set;
+ flags = 0;
+ while (ac > 0) {
+ tcmd = get_token(nptv6newcmds, *av, "option");
+ ac--; av++;
+
+ switch (tcmd) {
+ case TOK_INTPREFIX:
+ NEED1("IPv6 prefix required");
+ nptv6_parse_prefix(*av, &cfg->internal, &iplen);
+ flags |= NPTV6_HAS_INTPREFIX;
+ ac--; av++;
+ break;
+ case TOK_EXTPREFIX:
+ if (flags & NPTV6_HAS_EXTPREFIX)
+ errx(EX_USAGE,
+ "Only one ext_prefix or ext_if allowed");
+ NEED1("IPv6 prefix required");
+ nptv6_parse_prefix(*av, &cfg->external, &eplen);
+ flags |= NPTV6_HAS_EXTPREFIX;
+ ac--; av++;
+ break;
+ case TOK_EXTIF:
+ if (flags & NPTV6_HAS_EXTPREFIX)
+ errx(EX_USAGE,
+ "Only one ext_prefix or ext_if allowed");
+ NEED1("Interface name required");
+ if (strlen(*av) >= sizeof(cfg->if_name))
+ errx(EX_USAGE, "Invalid interface name");
+ flags |= NPTV6_HAS_EXTPREFIX;
+ cfg->flags |= NPTV6_DYNAMIC_PREFIX;
+ strncpy(cfg->if_name, *av, sizeof(cfg->if_name));
+ ac--; av++;
+ break;
+ case TOK_PREFIXLEN:
+ NEED1("IPv6 prefix length required");
+ pplen = strtol(*av, &p, 10);
+ if (*p != '\0' || pplen < 8 || pplen > 64)
+ errx(EX_USAGE, "wrong prefix length: %s", *av);
+ ac--; av++;
+ break;
+ }
+ }
+
+ /* RFC 6296 Sec. 3.1 */
+ if (pplen != 0) {
+ if ((eplen != 0 && eplen != pplen) ||
+ (iplen != 0 && iplen != pplen))
+ errx(EX_USAGE, "prefix length mismatch");
+ cfg->plen = pplen;
+ flags |= NPTV6_HAS_PREFIXLEN;
+ } else if (eplen != 0 || iplen != 0) {
+ if (eplen != 0 && iplen != 0 && eplen != iplen)
+ errx(EX_USAGE, "prefix length mismatch");
+ warnx("use prefixlen instead");
+ cfg->plen = eplen ? eplen : iplen;
+ flags |= NPTV6_HAS_PREFIXLEN;
+ }
+
+ /* Check validness */
+ if ((flags & NPTV6_HAS_INTPREFIX) != NPTV6_HAS_INTPREFIX)
+ errx(EX_USAGE, "int_prefix required");
+ if ((flags & NPTV6_HAS_EXTPREFIX) != NPTV6_HAS_EXTPREFIX)
+ errx(EX_USAGE, "ext_prefix or ext_if required");
+ if ((flags & NPTV6_HAS_PREFIXLEN) != NPTV6_HAS_PREFIXLEN)
+ errx(EX_USAGE, "prefixlen required");
+
+ n2mask(&mask, cfg->plen);
+ APPLY_MASK(&cfg->internal, &mask);
+ if ((cfg->flags & NPTV6_DYNAMIC_PREFIX) == 0)
+ APPLY_MASK(&cfg->external, &mask);
+
+ olh->count = 1;
+ olh->objsize = sizeof(*cfg);
+ olh->size = sizeof(buf);
+ strlcpy(cfg->name, name, sizeof(cfg->name));
+ if (do_set3(IP_FW_NPTV6_CREATE, &olh->opheader, sizeof(buf)) != 0)
+ err(EX_OSERR, "nptv6 instance creation failed");
+}
+
+/*
+ * Destroys NPTv6 instance.
+ * Request: [ ipfw_obj_header ]
+ */
+static void
+nptv6_destroy(const char *name, uint8_t set)
+{
+ ipfw_obj_header oh;
+
+ memset(&oh, 0, sizeof(oh));
+ nptv6_fill_ntlv(&oh.ntlv, name, set);
+ if (do_set3(IP_FW_NPTV6_DESTROY, &oh.opheader, sizeof(oh)) != 0)
+ err(EX_OSERR, "failed to destroy nat instance %s", name);
+}
+
+/*
+ * Get NPTv6 instance statistics.
+ * Request: [ ipfw_obj_header ]
+ * Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ] ]
+ */
+static int
+nptv6_get_stats(const char *name, uint8_t set, struct ipfw_nptv6_stats *stats)
+{
+ ipfw_obj_header *oh;
+ ipfw_obj_ctlv *oc;
+ size_t sz;
+
+ sz = sizeof(*oh) + sizeof(*oc) + sizeof(*stats);
+ oh = calloc(1, sz);
+ nptv6_fill_ntlv(&oh->ntlv, name, set);
+ if (do_get3(IP_FW_NPTV6_STATS, &oh->opheader, &sz) == 0) {
+ oc = (ipfw_obj_ctlv *)(oh + 1);
+ memcpy(stats, oc + 1, sizeof(*stats));
+ free(oh);
+ return (0);
+ }
+ free(oh);
+ return (-1);
+}
+
+static void
+nptv6_stats(const char *name, uint8_t set)
+{
+ struct ipfw_nptv6_stats stats;
+
+ if (nptv6_get_stats(name, set, &stats) != 0)
+ err(EX_OSERR, "Error retrieving stats");
+
+ if (g_co.use_set != 0 || set != 0)
+ printf("set %u ", set);
+ printf("nptv6 %s\n", name);
+ printf("\t%ju packets translated (internal to external)\n",
+ (uintmax_t)stats.in2ex);
+ printf("\t%ju packets translated (external to internal)\n",
+ (uintmax_t)stats.ex2in);
+ printf("\t%ju packets dropped due to some error\n",
+ (uintmax_t)stats.dropped);
+}
+
+/*
+ * Reset NPTv6 instance statistics specified by @oh->ntlv.
+ * Request: [ ipfw_obj_header ]
+ */
+static void
+nptv6_reset_stats(const char *name, uint8_t set)
+{
+ ipfw_obj_header oh;
+
+ memset(&oh, 0, sizeof(oh));
+ nptv6_fill_ntlv(&oh.ntlv, name, set);
+ if (do_set3(IP_FW_NPTV6_RESET_STATS, &oh.opheader, sizeof(oh)) != 0)
+ err(EX_OSERR, "failed to reset stats for instance %s", name);
+}
+
+static int
+nptv6_show_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set)
+{
+ char abuf[INET6_ADDRSTRLEN];
+
+ if (name != NULL && strcmp(cfg->name, name) != 0)
+ return (ESRCH);
+
+ if (g_co.use_set != 0 && cfg->set != set)
+ return (ESRCH);
+
+ if (g_co.use_set != 0 || cfg->set != 0)
+ printf("set %u ", cfg->set);
+ inet_ntop(AF_INET6, &cfg->internal, abuf, sizeof(abuf));
+ printf("nptv6 %s int_prefix %s ", cfg->name, abuf);
+ if (cfg->flags & NPTV6_DYNAMIC_PREFIX)
+ printf("ext_if %s ", cfg->if_name);
+ else {
+ inet_ntop(AF_INET6, &cfg->external, abuf, sizeof(abuf));
+ printf("ext_prefix %s ", abuf);
+ }
+ printf("prefixlen %u\n", cfg->plen);
+ return (0);
+}
+
+static int
+nptv6_destroy_cb(ipfw_nptv6_cfg *cfg, const char *name __unused, uint8_t set)
+{
+
+ if (g_co.use_set != 0 && cfg->set != set)
+ return (ESRCH);
+
+ nptv6_destroy(cfg->name, cfg->set);
+ return (0);
+}
+
+
+/*
+ * Compare NPTv6 instances names.
+ * Honor number comparison.
+ */
+static int
+nptv6name_cmp(const void *a, const void *b)
+{
+ const ipfw_nptv6_cfg *ca, *cb;
+
+ ca = (const ipfw_nptv6_cfg *)a;
+ cb = (const ipfw_nptv6_cfg *)b;
+
+ if (ca->set > cb->set)
+ return (1);
+ else if (ca->set < cb->set)
+ return (-1);
+ return (stringnum_cmp(ca->name, cb->name));
+}
+
+/*
+ * Retrieves NPTv6 instance list from kernel,
+ * Request: [ ipfw_obj_lheader ]
+ * Reply: [ ipfw_obj_lheader ipfw_nptv6_cfg x N ]
+ */
+static int
+nptv6_foreach(nptv6_cb_t *f, const char *name, uint8_t set, int sort)
+{
+ ipfw_obj_lheader *olh;
+ ipfw_nptv6_cfg *cfg;
+ size_t sz;
+ uint32_t i;
+
+ /* Start with reasonable default */
+ sz = sizeof(*olh) + 16 * sizeof(*cfg);
+ for (;;) {
+ if ((olh = calloc(1, sz)) == NULL)
+ return (ENOMEM);
+
+ olh->size = sz;
+ if (do_get3(IP_FW_NPTV6_LIST, &olh->opheader, &sz) != 0) {
+ sz = olh->size;
+ free(olh);
+ if (errno != ENOMEM)
+ return (errno);
+ continue;
+ }
+
+ if (sort != 0)
+ qsort(olh + 1, olh->count, olh->objsize, nptv6name_cmp);
+
+ cfg = (ipfw_nptv6_cfg *)(olh + 1);
+ for (i = 0; i < olh->count; i++) {
+ (void)f(cfg, name, set);
+ cfg = (ipfw_nptv6_cfg *)((caddr_t)cfg + olh->objsize);
+ }
+ free(olh);
+ break;
+ }
+ return (0);
+}
+
diff --git a/sbin/ipfw15/tables.c b/sbin/ipfw15/tables.c
new file mode 100644
--- /dev/null
+++ b/sbin/ipfw15/tables.c
@@ -0,0 +1,2096 @@
+/*
+ * Copyright (c) 2014 Yandex LLC
+ * Copyright (c) 2014 Alexander V. Chernikov
+ *
+ * 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.
+ *
+ * in-kernel ipfw tables support.
+ */
+
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/ip_fw15.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "ipfw2.h"
+
+static void table_modify_record(ipfw_obj_header *oh, int ac, char *av[],
+ int add, int quiet, int update, int atomic);
+static int table_flush(ipfw_obj_header *oh);
+static int table_destroy(ipfw_obj_header *oh);
+static int table_do_create(ipfw_obj_header *oh, ipfw_xtable_info *i);
+static int table_do_modify(ipfw_obj_header *oh, ipfw_xtable_info *i);
+static int table_do_swap(ipfw_obj_header *oh, char *second);
+static void table_create(ipfw_obj_header *oh, int ac, char *av[]);
+static void table_modify(ipfw_obj_header *oh, int ac, char *av[]);
+static void table_lookup(ipfw_obj_header *oh, int ac, char *av[]);
+static void table_lock(ipfw_obj_header *oh, int lock);
+static int table_swap(ipfw_obj_header *oh, char *second);
+static int table_get_info(ipfw_obj_header *oh, ipfw_xtable_info *i);
+static int table_show_info(ipfw_xtable_info *i, void *arg);
+
+static int table_destroy_one(ipfw_xtable_info *i, void *arg);
+static int table_flush_one(ipfw_xtable_info *i, void *arg);
+static int table_show_one(ipfw_xtable_info *i, void *arg);
+static int table_do_get_list(ipfw_xtable_info *i, ipfw_obj_header **poh);
+static void table_show_list(ipfw_obj_header *oh, int need_header);
+static void table_show_entry(ipfw_xtable_info *i, ipfw_obj_tentry *tent);
+
+static void tentry_fill_key(ipfw_obj_header *oh, ipfw_obj_tentry *tent,
+ char *key, int add, uint8_t *ptype, uint32_t *pvmask, ipfw_xtable_info *xi);
+static void tentry_fill_value(ipfw_obj_header *oh, ipfw_obj_tentry *tent,
+ char *arg, uint8_t type, uint32_t vmask);
+static void table_show_value(char *buf, size_t bufsize, ipfw_table_value *v,
+ uint32_t vmask, int print_ip);
+
+typedef int (table_cb_t)(ipfw_xtable_info *i, void *arg);
+static int tables_foreach(table_cb_t *f, void *arg, int sort);
+
+#ifndef s6_addr32
+#define s6_addr32 __u6_addr.__u6_addr32
+#endif
+
+static struct _s_x tabletypes[] = {
+ { "addr", IPFW_TABLE_ADDR },
+ { "mac", IPFW_TABLE_MAC },
+ { "iface", IPFW_TABLE_INTERFACE },
+ { "number", IPFW_TABLE_NUMBER },
+ { "flow", IPFW_TABLE_FLOW },
+ { NULL, 0 }
+};
+
+/* Default algorithms for various table types */
+static struct _s_x tablealgos[] = {
+ { "addr:radix", IPFW_TABLE_ADDR },
+ { "flow:hash", IPFW_TABLE_FLOW },
+ { "iface:array", IPFW_TABLE_INTERFACE },
+ { "number:array", IPFW_TABLE_NUMBER },
+ { NULL, 0 }
+};
+
+static struct _s_x tablevaltypes[] = {
+ { "skipto", IPFW_VTYPE_SKIPTO },
+ { "pipe", IPFW_VTYPE_PIPE },
+ { "fib", IPFW_VTYPE_FIB },
+ { "nat", IPFW_VTYPE_NAT },
+ { "dscp", IPFW_VTYPE_DSCP },
+ { "tag", IPFW_VTYPE_TAG },
+ { "divert", IPFW_VTYPE_DIVERT },
+ { "netgraph", IPFW_VTYPE_NETGRAPH },
+ { "limit", IPFW_VTYPE_LIMIT },
+ { "ipv4", IPFW_VTYPE_NH4 },
+ { "ipv6", IPFW_VTYPE_NH6 },
+ { "mark", IPFW_VTYPE_MARK },
+ { NULL, 0 }
+};
+
+static struct _s_x tablecmds[] = {
+ { "add", TOK_ADD },
+ { "delete", TOK_DEL },
+ { "create", TOK_CREATE },
+ { "destroy", TOK_DESTROY },
+ { "flush", TOK_FLUSH },
+ { "modify", TOK_MODIFY },
+ { "swap", TOK_SWAP },
+ { "info", TOK_INFO },
+ { "detail", TOK_DETAIL },
+ { "list", TOK_LIST },
+ { "lookup", TOK_LOOKUP },
+ { "atomic", TOK_ATOMIC },
+ { "lock", TOK_LOCK },
+ { "unlock", TOK_UNLOCK },
+ { NULL, 0 }
+};
+
+static int
+lookup_host (char *host, struct in_addr *ipaddr)
+{
+ struct hostent *he;
+
+ if (!inet_aton(host, ipaddr)) {
+ if ((he = gethostbyname(host)) == NULL)
+ return(-1);
+ *ipaddr = *(struct in_addr *)he->h_addr_list[0];
+ }
+ return(0);
+}
+
+/*
+ * This one handles all table-related commands
+ * ipfw table NAME create ...
+ * ipfw table NAME modify ...
+ * ipfw table {NAME | all} destroy
+ * ipfw table NAME swap NAME
+ * ipfw table NAME lock
+ * ipfw table NAME unlock
+ * ipfw table NAME add addr[/masklen] [value]
+ * ipfw table NAME add [addr[/masklen] value] [addr[/masklen] value] ..
+ * ipfw table NAME delete addr[/masklen] [addr[/masklen]] ..
+ * ipfw table NAME lookup addr
+ * ipfw table {NAME | all} flush
+ * ipfw table {NAME | all} list
+ * ipfw table {NAME | all} info
+ * ipfw table {NAME | all} detail
+ */
+void
+ipfw_table_handler(int ac, char *av[])
+{
+ int do_add, is_all;
+ int atomic, error, tcmd;
+ ipfw_xtable_info i;
+ ipfw_obj_header oh;
+ char *tablename;
+ uint8_t set;
+ void *arg;
+
+ memset(&oh, 0, sizeof(oh));
+ is_all = 0;
+ if (g_co.use_set != 0)
+ set = g_co.use_set - 1;
+ else
+ set = 0;
+
+ ac--; av++;
+ NEED1("table needs name");
+ tablename = *av;
+
+ if (table_check_name(tablename) == 0) {
+ table_fill_ntlv(&oh.ntlv, *av, set, 1);
+ oh.idx = 1;
+ } else {
+ if (strcmp(tablename, "all") == 0)
+ is_all = 1;
+ else
+ errx(EX_USAGE, "table name %s is invalid", tablename);
+ }
+ ac--; av++;
+ NEED1("table needs command");
+
+ tcmd = get_token(tablecmds, *av, "table command");
+ /* Check if atomic operation was requested */
+ atomic = 0;
+ if (tcmd == TOK_ATOMIC) {
+ ac--; av++;
+ NEED1("atomic needs command");
+ tcmd = get_token(tablecmds, *av, "table command");
+ switch (tcmd) {
+ case TOK_ADD:
+ break;
+ default:
+ errx(EX_USAGE, "atomic is not compatible with %s", *av);
+ }
+ atomic = 1;
+ }
+
+ switch (tcmd) {
+ case TOK_LIST:
+ case TOK_INFO:
+ case TOK_DETAIL:
+ case TOK_FLUSH:
+ case TOK_DESTROY:
+ break;
+ default:
+ if (is_all != 0)
+ errx(EX_USAGE, "table name required");
+ }
+
+ switch (tcmd) {
+ case TOK_ADD:
+ case TOK_DEL:
+ do_add = **av == 'a';
+ ac--; av++;
+ table_modify_record(&oh, ac, av, do_add, g_co.do_quiet,
+ g_co.do_quiet, atomic);
+ break;
+ case TOK_CREATE:
+ ac--; av++;
+ table_create(&oh, ac, av);
+ break;
+ case TOK_MODIFY:
+ ac--; av++;
+ table_modify(&oh, ac, av);
+ break;
+ case TOK_DESTROY:
+ if (is_all == 0) {
+ if (table_destroy(&oh) == 0)
+ break;
+ if (errno != ESRCH)
+ err(EX_OSERR, "failed to destroy table %s",
+ tablename);
+ /* ESRCH isn't fatal, warn if not quiet mode */
+ if (g_co.do_quiet == 0)
+ warn("failed to destroy table %s", tablename);
+ } else {
+ error = tables_foreach(table_destroy_one, &oh, 1);
+ if (error != 0)
+ err(EX_OSERR,
+ "failed to destroy tables list");
+ }
+ break;
+ case TOK_FLUSH:
+ if (is_all == 0) {
+ if ((error = table_flush(&oh)) == 0)
+ break;
+ if (errno != ESRCH)
+ err(EX_OSERR, "failed to flush table %s info",
+ tablename);
+ /* ESRCH isn't fatal, warn if not quiet mode */
+ if (g_co.do_quiet == 0)
+ warn("failed to flush table %s info",
+ tablename);
+ } else {
+ error = tables_foreach(table_flush_one, &oh, 1);
+ if (error != 0)
+ err(EX_OSERR, "failed to flush tables list");
+ /* XXX: we ignore errors here */
+ }
+ break;
+ case TOK_SWAP:
+ ac--; av++;
+ NEED1("second table name required");
+ table_swap(&oh, *av);
+ break;
+ case TOK_LOCK:
+ case TOK_UNLOCK:
+ table_lock(&oh, (tcmd == TOK_LOCK));
+ break;
+ case TOK_DETAIL:
+ case TOK_INFO:
+ arg = (tcmd == TOK_DETAIL) ? (void *)1 : NULL;
+ if (is_all == 0) {
+ if ((error = table_get_info(&oh, &i)) != 0)
+ err(EX_OSERR, "failed to request table info");
+ table_show_info(&i, arg);
+ } else {
+ error = tables_foreach(table_show_info, arg, 1);
+ if (error != 0)
+ err(EX_OSERR, "failed to request tables list");
+ }
+ break;
+ case TOK_LIST:
+ arg = is_all ? (void*)1 : NULL;
+ if (is_all == 0) {
+ if ((error = table_get_info(&oh, &i)) != 0)
+ err(EX_OSERR, "failed to request table info");
+ table_show_one(&i, arg);
+ } else {
+ error = tables_foreach(table_show_one, arg, 1);
+ if (error != 0)
+ err(EX_OSERR, "failed to request tables list");
+ }
+ break;
+ case TOK_LOOKUP:
+ ac--; av++;
+ table_lookup(&oh, ac, av);
+ break;
+ }
+}
+
+void
+table_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, uint8_t set,
+ uint32_t uidx)
+{
+
+ ntlv->head.type = IPFW_TLV_TBL_NAME;
+ ntlv->head.length = sizeof(ipfw_obj_ntlv);
+ ntlv->idx = uidx;
+ ntlv->set = set;
+ strlcpy(ntlv->name, name, sizeof(ntlv->name));
+}
+
+static void
+table_fill_objheader(ipfw_obj_header *oh, ipfw_xtable_info *i)
+{
+
+ oh->idx = 1;
+ table_fill_ntlv(&oh->ntlv, i->tablename, i->set, 1);
+}
+
+static struct _s_x tablenewcmds[] = {
+ { "type", TOK_TYPE },
+ { "valtype", TOK_VALTYPE },
+ { "algo", TOK_ALGO },
+ { "limit", TOK_LIMIT },
+ { "locked", TOK_LOCK },
+ { "missing", TOK_MISSING },
+ { "or-flush", TOK_ORFLUSH },
+ { NULL, 0 }
+};
+
+static struct _s_x flowtypecmds[] = {
+ { "src-ip", IPFW_TFFLAG_SRCIP },
+ { "proto", IPFW_TFFLAG_PROTO },
+ { "src-port", IPFW_TFFLAG_SRCPORT },
+ { "dst-ip", IPFW_TFFLAG_DSTIP },
+ { "dst-port", IPFW_TFFLAG_DSTPORT },
+ { NULL, 0 }
+};
+
+static int
+table_parse_type(uint8_t ttype, char *p, uint8_t *tflags)
+{
+ uint32_t fset, fclear;
+ char *e;
+
+ /* Parse type options */
+ switch(ttype) {
+ case IPFW_TABLE_FLOW:
+ fset = fclear = 0;
+ if (fill_flags(flowtypecmds, p, &e, &fset, &fclear) != 0)
+ errx(EX_USAGE,
+ "unable to parse flow option %s", e);
+ *tflags = fset;
+ break;
+ default:
+ return (EX_USAGE);
+ }
+
+ return (0);
+}
+
+static void
+table_print_type(char *tbuf, size_t size, uint8_t type, uint8_t tflags)
+{
+ const char *tname;
+ int l;
+
+ if ((tname = match_value(tabletypes, type)) == NULL)
+ tname = "unknown";
+
+ l = snprintf(tbuf, size, "%s", tname);
+ tbuf += l;
+ size -= l;
+
+ switch(type) {
+ case IPFW_TABLE_FLOW:
+ if (tflags != 0) {
+ *tbuf++ = ':';
+ l--;
+ print_flags_buffer(tbuf, size, flowtypecmds, tflags);
+ }
+ break;
+ }
+}
+
+/*
+ * Creates new table
+ *
+ * ipfw table NAME create [ type { addr | iface | number | flow } ]
+ * [ algo algoname ] [missing] [or-flush]
+ */
+static void
+table_create(ipfw_obj_header *oh, int ac, char *av[])
+{
+ ipfw_xtable_info xi, xie;
+ int error, missing, orflush, tcmd, val;
+ uint32_t fset, fclear;
+ char *e, *p;
+ char tbuf[128];
+
+ missing = orflush = 0;
+ memset(&xi, 0, sizeof(xi));
+ while (ac > 0) {
+ tcmd = get_token(tablenewcmds, *av, "option");
+ ac--; av++;
+
+ switch (tcmd) {
+ case TOK_LIMIT:
+ NEED1("limit value required");
+ xi.limit = strtol(*av, NULL, 10);
+ ac--; av++;
+ break;
+ case TOK_TYPE:
+ NEED1("table type required");
+ /* Type may have suboptions after ':' */
+ if ((p = strchr(*av, ':')) != NULL)
+ *p++ = '\0';
+ val = match_token(tabletypes, *av);
+ if (val == -1) {
+ concat_tokens(tbuf, sizeof(tbuf), tabletypes,
+ ", ");
+ errx(EX_USAGE,
+ "Unknown tabletype: %s. Supported: %s",
+ *av, tbuf);
+ }
+ xi.type = val;
+ if (p != NULL) {
+ error = table_parse_type(val, p, &xi.tflags);
+ if (error != 0)
+ errx(EX_USAGE,
+ "Unsupported suboptions: %s", p);
+ }
+ ac--; av++;
+ break;
+ case TOK_VALTYPE:
+ NEED1("table value type required");
+ fset = fclear = 0;
+ val = fill_flags(tablevaltypes, *av, &e, &fset, &fclear);
+ if (val != -1) {
+ xi.vmask = fset;
+ ac--; av++;
+ break;
+ }
+ concat_tokens(tbuf, sizeof(tbuf), tablevaltypes, ", ");
+ errx(EX_USAGE, "Unknown value type: %s. Supported: %s",
+ e, tbuf);
+ break;
+ case TOK_ALGO:
+ NEED1("table algorithm name required");
+ if (strlen(*av) > sizeof(xi.algoname))
+ errx(EX_USAGE, "algorithm name too long");
+ strlcpy(xi.algoname, *av, sizeof(xi.algoname));
+ ac--; av++;
+ break;
+ case TOK_LOCK:
+ xi.flags |= IPFW_TGFLAGS_LOCKED;
+ break;
+ case TOK_ORFLUSH:
+ orflush = 1;
+ /* FALLTHROUGH */
+ case TOK_MISSING:
+ missing = 1;
+ break;
+ }
+ }
+
+ /* Set some defaults to preserve compatibility. */
+ if (xi.algoname[0] == '\0') {
+ const char *algo;
+
+ if (xi.type == 0)
+ xi.type = IPFW_TABLE_ADDR;
+ algo = match_value(tablealgos, xi.type);
+ if (algo != NULL)
+ strlcpy(xi.algoname, algo, sizeof(xi.algoname));
+ }
+ if (xi.vmask == 0)
+ xi.vmask = IPFW_VTYPE_LEGACY;
+
+ error = table_do_create(oh, &xi);
+
+ if (error == 0)
+ return;
+
+ if (errno != EEXIST || missing == 0)
+ err(EX_OSERR, "Table creation failed");
+
+ /* Check that existing table is the same we are trying to create */
+ if (table_get_info(oh, &xie) != 0)
+ err(EX_OSERR, "Existing table check failed");
+
+ if (xi.limit != xie.limit || xi.type != xie.type ||
+ xi.tflags != xie.tflags || xi.vmask != xie.vmask || (
+ xi.algoname[0] != '\0' && strcmp(xi.algoname,
+ xie.algoname) != 0) || xi.flags != xie.flags)
+ errx(EX_DATAERR, "The existing table is not compatible "
+ "with one you are creating.");
+
+ /* Flush existing table if instructed to do so */
+ if (orflush != 0 && table_flush(oh) != 0)
+ err(EX_OSERR, "Table flush on creation failed");
+}
+
+/*
+ * Creates new table
+ *
+ * Request: [ ipfw_obj_header ipfw_xtable_info ]
+ *
+ * Returns 0 on success.
+ */
+static int
+table_do_create(ipfw_obj_header *oh, ipfw_xtable_info *i)
+{
+ char tbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_xtable_info)];
+ int error;
+
+ memcpy(tbuf, oh, sizeof(*oh));
+ memcpy(tbuf + sizeof(*oh), i, sizeof(*i));
+ oh = (ipfw_obj_header *)tbuf;
+
+ error = do_set3(IP_FW_TABLE_XCREATE, &oh->opheader, sizeof(tbuf));
+
+ return (error);
+}
+
+/*
+ * Modifies existing table
+ *
+ * ipfw table NAME modify [ limit number ]
+ */
+static void
+table_modify(ipfw_obj_header *oh, int ac, char *av[])
+{
+ ipfw_xtable_info xi;
+ int tcmd;
+
+ memset(&xi, 0, sizeof(xi));
+
+ while (ac > 0) {
+ tcmd = get_token(tablenewcmds, *av, "option");
+ ac--; av++;
+
+ switch (tcmd) {
+ case TOK_LIMIT:
+ NEED1("limit value required");
+ xi.limit = strtol(*av, NULL, 10);
+ xi.mflags |= IPFW_TMFLAGS_LIMIT;
+ ac--; av++;
+ break;
+ default:
+ errx(EX_USAGE, "cmd is not supported for modification");
+ }
+ }
+
+ if (table_do_modify(oh, &xi) != 0)
+ err(EX_OSERR, "Table modification failed");
+}
+
+/*
+ * Modifies existing table.
+ *
+ * Request: [ ipfw_obj_header ipfw_xtable_info ]
+ *
+ * Returns 0 on success.
+ */
+static int
+table_do_modify(ipfw_obj_header *oh, ipfw_xtable_info *i)
+{
+ char tbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_xtable_info)];
+ int error;
+
+ memcpy(tbuf, oh, sizeof(*oh));
+ memcpy(tbuf + sizeof(*oh), i, sizeof(*i));
+ oh = (ipfw_obj_header *)tbuf;
+
+ error = do_set3(IP_FW_TABLE_XMODIFY, &oh->opheader, sizeof(tbuf));
+
+ return (error);
+}
+
+/*
+ * Locks or unlocks given table
+ */
+static void
+table_lock(ipfw_obj_header *oh, int lock)
+{
+ ipfw_xtable_info xi;
+
+ memset(&xi, 0, sizeof(xi));
+
+ xi.mflags |= IPFW_TMFLAGS_LOCK;
+ xi.flags |= (lock != 0) ? IPFW_TGFLAGS_LOCKED : 0;
+
+ if (table_do_modify(oh, &xi) != 0)
+ err(EX_OSERR, "Table %s failed", lock != 0 ? "lock" : "unlock");
+}
+
+/*
+ * Destroys given table specified by @oh->ntlv.
+ * Returns 0 on success.
+ */
+static int
+table_destroy(ipfw_obj_header *oh)
+{
+
+ if (do_set3(IP_FW_TABLE_XDESTROY, &oh->opheader, sizeof(*oh)) != 0)
+ return (-1);
+
+ return (0);
+}
+
+static int
+table_destroy_one(ipfw_xtable_info *i, void *arg)
+{
+ ipfw_obj_header *oh;
+
+ oh = (ipfw_obj_header *)arg;
+ table_fill_ntlv(&oh->ntlv, i->tablename, i->set, 1);
+ if (table_destroy(oh) != 0) {
+ if (g_co.do_quiet == 0)
+ warn("failed to destroy table(%s) in set %u",
+ i->tablename, i->set);
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Flushes given table specified by @oh->ntlv.
+ * Returns 0 on success.
+ */
+static int
+table_flush(ipfw_obj_header *oh)
+{
+
+ if (do_set3(IP_FW_TABLE_XFLUSH, &oh->opheader, sizeof(*oh)) != 0)
+ return (-1);
+
+ return (0);
+}
+
+static int
+table_do_swap(ipfw_obj_header *oh, char *second)
+{
+ char tbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_obj_ntlv)];
+ int error;
+
+ memset(tbuf, 0, sizeof(tbuf));
+ memcpy(tbuf, oh, sizeof(*oh));
+ oh = (ipfw_obj_header *)tbuf;
+ table_fill_ntlv((ipfw_obj_ntlv *)(oh + 1), second, oh->ntlv.set, 1);
+
+ error = do_set3(IP_FW_TABLE_XSWAP, &oh->opheader, sizeof(tbuf));
+
+ return (error);
+}
+
+/*
+ * Swaps given table with @second one.
+ */
+static int
+table_swap(ipfw_obj_header *oh, char *second)
+{
+
+ if (table_check_name(second) != 0)
+ errx(EX_USAGE, "table name %s is invalid", second);
+
+ if (table_do_swap(oh, second) == 0)
+ return (0);
+
+ switch (errno) {
+ case EINVAL:
+ errx(EX_USAGE, "Unable to swap table: check types");
+ case EFBIG:
+ errx(EX_USAGE, "Unable to swap table: check limits");
+ }
+
+ return (0);
+}
+
+
+/*
+ * Retrieves table in given table specified by @oh->ntlv.
+ * it inside @i.
+ * Returns 0 on success.
+ */
+static int
+table_get_info(ipfw_obj_header *oh, ipfw_xtable_info *i)
+{
+ char tbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_xtable_info)];
+ size_t sz;
+
+ sz = sizeof(tbuf);
+ memset(tbuf, 0, sizeof(tbuf));
+ memcpy(tbuf, oh, sizeof(*oh));
+ oh = (ipfw_obj_header *)tbuf;
+
+ if (do_get3(IP_FW_TABLE_XINFO, &oh->opheader, &sz) != 0)
+ return (errno);
+
+ if (sz < sizeof(tbuf))
+ return (EINVAL);
+
+ *i = *(ipfw_xtable_info *)(oh + 1);
+
+ return (0);
+}
+
+static struct _s_x tablealgoclass[] = {
+ { "hash", IPFW_TACLASS_HASH },
+ { "array", IPFW_TACLASS_ARRAY },
+ { "radix", IPFW_TACLASS_RADIX },
+ { NULL, 0 }
+};
+
+struct ta_cldata {
+ uint8_t taclass;
+ uint8_t spare4;
+ uint16_t itemsize;
+ uint16_t itemsize6;
+ uint32_t size;
+ uint32_t count;
+};
+
+/*
+ * Print global/per-AF table @i algorithm info.
+ */
+static void
+table_show_tainfo(ipfw_xtable_info *i __unused, struct ta_cldata *d,
+ const char *af, const char *taclass)
+{
+
+ switch (d->taclass) {
+ case IPFW_TACLASS_HASH:
+ case IPFW_TACLASS_ARRAY:
+ printf(" %salgorithm %s info\n", af, taclass);
+ if (d->itemsize == d->itemsize6)
+ printf(" size: %u items: %u itemsize: %u\n",
+ d->size, d->count, d->itemsize);
+ else
+ printf(" size: %u items: %u "
+ "itemsize4: %u itemsize6: %u\n",
+ d->size, d->count,
+ d->itemsize, d->itemsize6);
+ break;
+ case IPFW_TACLASS_RADIX:
+ printf(" %salgorithm %s info\n", af, taclass);
+ if (d->itemsize == d->itemsize6)
+ printf(" items: %u itemsize: %u\n",
+ d->count, d->itemsize);
+ else
+ printf(" items: %u "
+ "itemsize4: %u itemsize6: %u\n",
+ d->count, d->itemsize, d->itemsize6);
+ break;
+ default:
+ printf(" algo class: %s\n", taclass);
+ }
+}
+
+static void
+table_print_valheader(char *buf, size_t bufsize, uint32_t vmask)
+{
+
+ if (vmask == IPFW_VTYPE_LEGACY) {
+ snprintf(buf, bufsize, "legacy");
+ return;
+ }
+
+ memset(buf, 0, bufsize);
+ print_flags_buffer(buf, bufsize, tablevaltypes, vmask);
+}
+
+/*
+ * Prints table info struct @i in human-readable form.
+ */
+static int
+table_show_info(ipfw_xtable_info *i, void *arg)
+{
+ const char *vtype;
+ ipfw_ta_tinfo *tainfo;
+ int afdata, afitem;
+ struct ta_cldata d;
+ char ttype[64], tvtype[64];
+
+ table_print_type(ttype, sizeof(ttype), i->type, i->tflags);
+ table_print_valheader(tvtype, sizeof(tvtype), i->vmask);
+
+ printf("--- table(%s), set(%u) ---\n", i->tablename, i->set);
+ if ((i->flags & IPFW_TGFLAGS_LOCKED) != 0)
+ printf(" kindex: %d, type: %s, locked\n", i->kidx, ttype);
+ else
+ printf(" kindex: %d, type: %s\n", i->kidx, ttype);
+ printf(" references: %u, valtype: %s\n", i->refcnt, tvtype);
+ printf(" algorithm: %s\n", i->algoname);
+ printf(" items: %u, size: %u\n", i->count, i->size);
+ if (i->limit > 0)
+ printf(" limit: %u\n", i->limit);
+
+ /* Print algo-specific info if requested & set */
+ if (arg == NULL)
+ return (0);
+
+ if ((i->ta_info.flags & IPFW_TATFLAGS_DATA) == 0)
+ return (0);
+ tainfo = &i->ta_info;
+
+ afdata = 0;
+ afitem = 0;
+ if (tainfo->flags & IPFW_TATFLAGS_AFDATA)
+ afdata = 1;
+ if (tainfo->flags & IPFW_TATFLAGS_AFITEM)
+ afitem = 1;
+
+ memset(&d, 0, sizeof(d));
+ d.taclass = tainfo->taclass4;
+ d.size = tainfo->size4;
+ d.count = tainfo->count4;
+ d.itemsize = tainfo->itemsize4;
+ if (afdata == 0 && afitem != 0)
+ d.itemsize6 = tainfo->itemsize6;
+ else
+ d.itemsize6 = d.itemsize;
+ if ((vtype = match_value(tablealgoclass, d.taclass)) == NULL)
+ vtype = "unknown";
+
+ if (afdata == 0) {
+ table_show_tainfo(i, &d, "", vtype);
+ } else {
+ table_show_tainfo(i, &d, "IPv4 ", vtype);
+ memset(&d, 0, sizeof(d));
+ d.taclass = tainfo->taclass6;
+ if ((vtype = match_value(tablealgoclass, d.taclass)) == NULL)
+ vtype = "unknown";
+ d.size = tainfo->size6;
+ d.count = tainfo->count6;
+ d.itemsize = tainfo->itemsize6;
+ d.itemsize6 = d.itemsize;
+ table_show_tainfo(i, &d, "IPv6 ", vtype);
+ }
+
+ return (0);
+}
+
+
+/*
+ * Function wrappers which can be used either
+ * as is or as foreach function parameter.
+ */
+
+static int
+table_show_one(ipfw_xtable_info *i, void *arg)
+{
+ ipfw_obj_header *oh = NULL;
+ int error;
+ int is_all;
+
+ is_all = arg == NULL ? 0 : 1;
+
+ if ((error = table_do_get_list(i, &oh)) != 0) {
+ err(EX_OSERR, "Error requesting table %s list", i->tablename);
+ return (error);
+ }
+
+ table_show_list(oh, is_all);
+
+ free(oh);
+ return (0);
+}
+
+static int
+table_flush_one(ipfw_xtable_info *i, void *arg)
+{
+ ipfw_obj_header *oh;
+
+ oh = (ipfw_obj_header *)arg;
+
+ table_fill_ntlv(&oh->ntlv, i->tablename, i->set, 1);
+
+ return (table_flush(oh));
+}
+
+static int
+table_do_modify_record(int cmd, ipfw_obj_header *oh,
+ ipfw_obj_tentry *tent, int count, int atomic)
+{
+ ipfw_obj_ctlv *ctlv;
+ ipfw_obj_tentry *tent_base;
+ caddr_t pbuf;
+ char xbuf[sizeof(*oh) + sizeof(ipfw_obj_ctlv) + sizeof(*tent)];
+ int error, i;
+ size_t sz;
+
+ sz = sizeof(*ctlv) + sizeof(*tent) * count;
+ if (count == 1) {
+ memset(xbuf, 0, sizeof(xbuf));
+ pbuf = xbuf;
+ } else {
+ if ((pbuf = calloc(1, sizeof(*oh) + sz)) == NULL)
+ return (ENOMEM);
+ }
+
+ memcpy(pbuf, oh, sizeof(*oh));
+ oh = (ipfw_obj_header *)pbuf;
+ oh->opheader.version = 1; /* Current version */
+
+ ctlv = (ipfw_obj_ctlv *)(oh + 1);
+ ctlv->count = count;
+ ctlv->head.length = sz;
+ if (atomic != 0)
+ ctlv->flags |= IPFW_CTF_ATOMIC;
+
+ tent_base = tent;
+ memcpy(ctlv + 1, tent, sizeof(*tent) * count);
+ tent = (ipfw_obj_tentry *)(ctlv + 1);
+ for (i = 0; i < count; i++, tent++) {
+ tent->head.length = sizeof(ipfw_obj_tentry);
+ tent->idx = oh->idx;
+ }
+
+ sz += sizeof(*oh);
+ error = do_get3(cmd, &oh->opheader, &sz);
+ if (error != 0)
+ error = errno;
+ tent = (ipfw_obj_tentry *)(ctlv + 1);
+ /* Copy result back to provided buffer */
+ memcpy(tent_base, ctlv + 1, sizeof(*tent) * count);
+
+ if (pbuf != xbuf)
+ free(pbuf);
+
+ return (error);
+}
+
+static void
+table_modify_record(ipfw_obj_header *oh, int ac, char *av[], int add,
+ int quiet, int update, int atomic)
+{
+ ipfw_obj_tentry *ptent, tent, *tent_buf;
+ ipfw_xtable_info xi;
+ const char *etxt, *px, *texterr;
+ uint8_t type;
+ uint32_t vmask;
+ int cmd, count, error, i, ignored;
+
+ if (ac == 0)
+ errx(EX_USAGE, "address required");
+
+ if (add != 0) {
+ cmd = IP_FW_TABLE_XADD;
+ texterr = "Adding record failed";
+ } else {
+ cmd = IP_FW_TABLE_XDEL;
+ texterr = "Deleting record failed";
+ }
+
+ /*
+ * Calculate number of entries:
+ * Assume [key val] x N for add
+ * and
+ * key x N for delete
+ */
+ count = (add != 0) ? ac / 2 + 1 : ac;
+
+ if (count <= 1) {
+ /* Adding single entry with/without value */
+ memset(&tent, 0, sizeof(tent));
+ tent_buf = &tent;
+ } else {
+
+ if ((tent_buf = calloc(count, sizeof(tent))) == NULL)
+ errx(EX_OSERR,
+ "Unable to allocate memory for all entries");
+ }
+ ptent = tent_buf;
+
+ memset(&xi, 0, sizeof(xi));
+ count = 0;
+ while (ac > 0) {
+ tentry_fill_key(oh, ptent, *av, add, &type, &vmask, &xi);
+
+ /*
+ * Compatibility layer: auto-create table if not exists.
+ */
+ if (xi.tablename[0] == '\0') {
+ xi.type = type;
+ xi.vmask = vmask;
+ strlcpy(xi.tablename, oh->ntlv.name,
+ sizeof(xi.tablename));
+ if (quiet == 0)
+ warnx("DEPRECATED: inserting data into "
+ "non-existent table %s. (auto-created)",
+ xi.tablename);
+ table_do_create(oh, &xi);
+ }
+
+ oh->ntlv.type = type;
+ ac--; av++;
+
+ if (add != 0 && ac > 0) {
+ tentry_fill_value(oh, ptent, *av, type, vmask);
+ ac--; av++;
+ }
+
+ if (update != 0)
+ ptent->head.flags |= IPFW_TF_UPDATE;
+
+ count++;
+ ptent++;
+ }
+
+ error = table_do_modify_record(cmd, oh, tent_buf, count, atomic);
+
+ /*
+ * Compatibility stuff: do not yell on duplicate keys or
+ * failed deletions.
+ */
+ if (error == 0 || (error == EEXIST && add != 0) ||
+ (error == ENOENT && add == 0)) {
+ if (quiet != 0) {
+ if (tent_buf != &tent)
+ free(tent_buf);
+ return;
+ }
+ }
+
+ /* Report results back */
+ ptent = tent_buf;
+ for (i = 0; i < count; ptent++, i++) {
+ ignored = 0;
+ switch (ptent->result) {
+ case IPFW_TR_ADDED:
+ px = "added";
+ break;
+ case IPFW_TR_DELETED:
+ px = "deleted";
+ break;
+ case IPFW_TR_UPDATED:
+ px = "updated";
+ break;
+ case IPFW_TR_LIMIT:
+ px = "limit";
+ ignored = 1;
+ break;
+ case IPFW_TR_ERROR:
+ px = "error";
+ ignored = 1;
+ break;
+ case IPFW_TR_NOTFOUND:
+ px = "notfound";
+ ignored = 1;
+ break;
+ case IPFW_TR_EXISTS:
+ px = "exists";
+ ignored = 1;
+ break;
+ case IPFW_TR_IGNORED:
+ px = "ignored";
+ ignored = 1;
+ break;
+ default:
+ px = "unknown";
+ ignored = 1;
+ }
+
+ if (error != 0 && atomic != 0 && ignored == 0)
+ printf("%s(reverted): ", px);
+ else
+ printf("%s: ", px);
+
+ table_show_entry(&xi, ptent);
+ }
+
+ if (tent_buf != &tent)
+ free(tent_buf);
+
+ if (error == 0)
+ return;
+
+ /* Try to provide more human-readable error */
+ switch (error) {
+ case EEXIST:
+ etxt = "record already exists";
+ break;
+ case EFBIG:
+ etxt = "limit hit";
+ break;
+ case ESRCH:
+ etxt = "table not found";
+ break;
+ case ENOENT:
+ etxt = "record not found";
+ break;
+ case EACCES:
+ etxt = "table is locked";
+ break;
+ default:
+ etxt = strerror(error);
+ }
+
+ errx(EX_OSERR, "%s: %s", texterr, etxt);
+}
+
+static int
+table_do_lookup(ipfw_obj_header *oh, char *key, ipfw_xtable_info *xi,
+ ipfw_obj_tentry *xtent)
+{
+ char xbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_obj_tentry)];
+ ipfw_obj_tentry *tent;
+ uint8_t type;
+ uint32_t vmask;
+ size_t sz;
+
+ memcpy(xbuf, oh, sizeof(*oh));
+ oh = (ipfw_obj_header *)xbuf;
+ tent = (ipfw_obj_tentry *)(oh + 1);
+
+ memset(tent, 0, sizeof(*tent));
+ tent->head.length = sizeof(*tent);
+ tent->idx = 1;
+
+ tentry_fill_key(oh, tent, key, 0, &type, &vmask, xi);
+ oh->ntlv.type = type;
+
+ sz = sizeof(xbuf);
+ if (do_get3(IP_FW_TABLE_XFIND, &oh->opheader, &sz) != 0)
+ return (errno);
+
+ if (sz < sizeof(xbuf))
+ return (EINVAL);
+
+ *xtent = *tent;
+
+ return (0);
+}
+
+static void
+table_lookup(ipfw_obj_header *oh, int ac, char *av[])
+{
+ ipfw_obj_tentry xtent;
+ ipfw_xtable_info xi;
+ char key[64];
+ int error;
+
+ if (ac == 0)
+ errx(EX_USAGE, "address required");
+
+ strlcpy(key, *av, sizeof(key));
+
+ memset(&xi, 0, sizeof(xi));
+ error = table_do_lookup(oh, key, &xi, &xtent);
+
+ switch (error) {
+ case 0:
+ break;
+ case ESRCH:
+ errx(EX_UNAVAILABLE, "Table %s not found", oh->ntlv.name);
+ case ENOENT:
+ errx(EX_UNAVAILABLE, "Entry %s not found", *av);
+ case ENOTSUP:
+ errx(EX_UNAVAILABLE, "Table %s algo does not support "
+ "\"lookup\" method", oh->ntlv.name);
+ default:
+ err(EX_OSERR, "getsockopt(IP_FW_TABLE_XFIND)");
+ }
+
+ table_show_entry(&xi, &xtent);
+}
+
+static void
+tentry_fill_key_type(char *arg, ipfw_obj_tentry *tentry, uint8_t type,
+ uint8_t tflags)
+{
+ char *p, *pp;
+ int mask, af;
+ struct in6_addr *paddr, tmp;
+ struct ether_addr *mac;
+ struct tflow_entry *tfe;
+ uint32_t key, *pkey;
+ uint16_t port;
+ struct protoent *pent;
+ struct servent *sent;
+ int masklen;
+
+ mask = masklen = 0;
+ af = 0;
+ paddr = (struct in6_addr *)&tentry->k;
+
+ switch (type) {
+ case IPFW_TABLE_ADDR:
+ /* Remove / if exists */
+ if ((p = strchr(arg, '/')) != NULL) {
+ *p = '\0';
+ mask = atoi(p + 1);
+ }
+
+ if (inet_pton(AF_INET, arg, paddr) == 1) {
+ if (p != NULL && mask > 32)
+ errx(EX_DATAERR, "bad IPv4 mask width: %s",
+ p + 1);
+
+ masklen = p ? mask : 32;
+ af = AF_INET;
+ } else if (inet_pton(AF_INET6, arg, paddr) == 1) {
+ if (IN6_IS_ADDR_V4COMPAT(paddr))
+ errx(EX_DATAERR,
+ "Use IPv4 instead of v4-compatible");
+ if (p != NULL && mask > 128)
+ errx(EX_DATAERR, "bad IPv6 mask width: %s",
+ p + 1);
+
+ masklen = p ? mask : 128;
+ af = AF_INET6;
+ } else {
+ /* Assume FQDN */
+ if (lookup_host(arg, (struct in_addr *)paddr) != 0)
+ errx(EX_NOHOST, "hostname ``%s'' unknown", arg);
+
+ masklen = 32;
+ type = IPFW_TABLE_ADDR;
+ af = AF_INET;
+ }
+ break;
+ case IPFW_TABLE_MAC:
+ /* Remove / if exists */
+ if ((p = strchr(arg, '/')) != NULL) {
+ *p = '\0';
+ mask = atoi(p + 1);
+ }
+
+ if (p != NULL && mask > 8 * ETHER_ADDR_LEN)
+ errx(EX_DATAERR, "bad MAC mask width: %s",
+ p + 1);
+
+ if ((mac = ether_aton(arg)) == NULL)
+ errx(EX_DATAERR, "Incorrect MAC address");
+
+ memcpy(tentry->k.mac, mac->octet, ETHER_ADDR_LEN);
+ masklen = p ? mask : 8 * ETHER_ADDR_LEN;
+ af = AF_LINK;
+ break;
+ case IPFW_TABLE_INTERFACE:
+ /* Assume interface name. Copy significant data only */
+ mask = MIN(strlen(arg), IF_NAMESIZE - 1);
+ memcpy(paddr, arg, mask);
+ /* Set mask to exact match */
+ masklen = 8 * IF_NAMESIZE;
+ break;
+ case IPFW_TABLE_NUMBER:
+ /* Port or any other key */
+ key = strtol(arg, &p, 10);
+ if (*p != '\0')
+ errx(EX_DATAERR, "Invalid number: %s", arg);
+
+ pkey = (uint32_t *)paddr;
+ *pkey = key;
+ masklen = 32;
+ break;
+ case IPFW_TABLE_FLOW:
+ /* Assume [src-ip][,proto][,src-port][,dst-ip][,dst-port] */
+ tfe = &tentry->k.flow;
+ af = 0;
+
+ /* Handle <ipv4|ipv6> */
+ if ((tflags & IPFW_TFFLAG_SRCIP) != 0) {
+ if ((p = strchr(arg, ',')) != NULL)
+ *p++ = '\0';
+ /* Determine family using temporary storage */
+ if (inet_pton(AF_INET, arg, &tmp) == 1) {
+ if (af != 0 && af != AF_INET)
+ errx(EX_DATAERR,
+ "Inconsistent address family\n");
+ af = AF_INET;
+ memcpy(&tfe->a.a4.sip, &tmp, 4);
+ } else if (inet_pton(AF_INET6, arg, &tmp) == 1) {
+ if (af != 0 && af != AF_INET6)
+ errx(EX_DATAERR,
+ "Inconsistent address family\n");
+ af = AF_INET6;
+ memcpy(&tfe->a.a6.sip6, &tmp, 16);
+ }
+
+ arg = p;
+ }
+
+ /* Handle <proto-num|proto-name> */
+ if ((tflags & IPFW_TFFLAG_PROTO) != 0) {
+ if (arg == NULL)
+ errx(EX_DATAERR, "invalid key: proto missing");
+ if ((p = strchr(arg, ',')) != NULL)
+ *p++ = '\0';
+
+ key = strtol(arg, &pp, 10);
+ if (*pp != '\0') {
+ if ((pent = getprotobyname(arg)) == NULL)
+ errx(EX_DATAERR, "Unknown proto: %s",
+ arg);
+ else
+ key = pent->p_proto;
+ }
+
+ if (key > 255)
+ errx(EX_DATAERR, "Bad protocol number: %u",key);
+
+ tfe->proto = key;
+
+ arg = p;
+ }
+
+ /* Handle <port-num|service-name> */
+ if ((tflags & IPFW_TFFLAG_SRCPORT) != 0) {
+ if (arg == NULL)
+ errx(EX_DATAERR, "invalid key: src port missing");
+ if ((p = strchr(arg, ',')) != NULL)
+ *p++ = '\0';
+
+ port = htons(strtol(arg, &pp, 10));
+ if (*pp != '\0') {
+ if ((sent = getservbyname(arg, NULL)) == NULL)
+ errx(EX_DATAERR, "Unknown service: %s",
+ arg);
+ port = sent->s_port;
+ }
+ tfe->sport = port;
+ arg = p;
+ }
+
+ /* Handle <ipv4|ipv6>*/
+ if ((tflags & IPFW_TFFLAG_DSTIP) != 0) {
+ if (arg == NULL)
+ errx(EX_DATAERR, "invalid key: dst ip missing");
+ if ((p = strchr(arg, ',')) != NULL)
+ *p++ = '\0';
+ /* Determine family using temporary storage */
+ if (inet_pton(AF_INET, arg, &tmp) == 1) {
+ if (af != 0 && af != AF_INET)
+ errx(EX_DATAERR,
+ "Inconsistent address family");
+ af = AF_INET;
+ memcpy(&tfe->a.a4.dip, &tmp, 4);
+ } else if (inet_pton(AF_INET6, arg, &tmp) == 1) {
+ if (af != 0 && af != AF_INET6)
+ errx(EX_DATAERR,
+ "Inconsistent address family");
+ af = AF_INET6;
+ memcpy(&tfe->a.a6.dip6, &tmp, 16);
+ }
+
+ arg = p;
+ }
+
+ /* Handle <port-num|service-name> */
+ if ((tflags & IPFW_TFFLAG_DSTPORT) != 0) {
+ if (arg == NULL)
+ errx(EX_DATAERR, "invalid key: dst port missing");
+ if ((p = strchr(arg, ',')) != NULL)
+ *p++ = '\0';
+
+ port = htons(strtol(arg, &pp, 10));
+ if (*pp != '\0') {
+ if ((sent = getservbyname(arg, NULL)) == NULL)
+ errx(EX_DATAERR, "Unknown service: %s",
+ arg);
+ port = sent->s_port;
+ }
+ tfe->dport = port;
+ arg = p;
+ }
+
+ tfe->af = af;
+
+ break;
+
+ default:
+ errx(EX_DATAERR, "Unsupported table type: %d", type);
+ }
+
+ tentry->subtype = af;
+ tentry->masklen = masklen;
+}
+
+/*
+ * Tries to guess table key type.
+ * This procedure is used in legacy table auto-create
+ * code AND in `ipfw -n` ruleset checking.
+ *
+ * Imported from old table_fill_xentry() parse code.
+ */
+static int
+guess_key_type(char *key, uint8_t *ptype)
+{
+ char *p;
+ struct in6_addr addr;
+
+ if (ishexnumber(*key) != 0 || *key == ':') {
+ /* Remove / if exists */
+ if ((p = strchr(key, '/')) != NULL)
+ *p = '\0';
+
+ if ((inet_pton(AF_INET, key, &addr) == 1) ||
+ (inet_pton(AF_INET6, key, &addr) == 1)) {
+ *ptype = IPFW_TABLE_CIDR;
+ if (p != NULL)
+ *p = '/';
+ return (0);
+ } else {
+ /* Port or any other key */
+ /* Skip non-base 10 entries like 'fa1' */
+ (void)strtol(key, &p, 10);
+ if (*p == '\0') {
+ *ptype = IPFW_TABLE_NUMBER;
+ return (0);
+ } else if ((p != key) && (*p == '.')) {
+ /*
+ * Warn on IPv4 address strings
+ * which are "valid" for inet_aton() but not
+ * in inet_pton().
+ *
+ * Typical examples: '10.5' or '10.0.0.05'
+ */
+ return (1);
+ }
+ }
+ }
+
+ if (strchr(key, '.') == NULL) {
+ *ptype = IPFW_TABLE_INTERFACE;
+ return (0);
+ }
+
+ if (lookup_host(key, (struct in_addr *)&addr) != 0)
+ return (1);
+
+ *ptype = IPFW_TABLE_CIDR;
+ return (0);
+}
+
+static void
+tentry_fill_key(ipfw_obj_header *oh, ipfw_obj_tentry *tent, char *key,
+ int add, uint8_t *ptype, uint32_t *pvmask, ipfw_xtable_info *xi)
+{
+ uint8_t type, tflags;
+ uint32_t vmask;
+ int error;
+
+ type = 0;
+ tflags = 0;
+ vmask = 0;
+
+ if (xi->tablename[0] == '\0')
+ error = table_get_info(oh, xi);
+ else
+ error = 0;
+
+ if (error == 0) {
+ if (g_co.test_only == 0) {
+ /* Table found */
+ type = xi->type;
+ tflags = xi->tflags;
+ vmask = xi->vmask;
+ } else {
+ /*
+ * We're running `ipfw -n`
+ * Compatibility layer: try to guess key type
+ * before failing.
+ */
+ if (guess_key_type(key, &type) != 0) {
+ /* Inknown key */
+ errx(EX_USAGE, "Cannot guess "
+ "key '%s' type", key);
+ }
+ vmask = IPFW_VTYPE_LEGACY;
+ }
+ } else {
+ if (error != ESRCH)
+ errx(EX_OSERR, "Error requesting table %s info",
+ oh->ntlv.name);
+ if (add == 0)
+ errx(EX_DATAERR, "Table %s does not exist",
+ oh->ntlv.name);
+ /*
+ * Table does not exist
+ * Compatibility layer: try to guess key type before failing.
+ */
+ if (guess_key_type(key, &type) != 0) {
+ /* Inknown key */
+ errx(EX_USAGE, "Table %s does not exist, cannot guess "
+ "key '%s' type", oh->ntlv.name, key);
+ }
+
+ vmask = IPFW_VTYPE_LEGACY;
+ }
+
+ tentry_fill_key_type(key, tent, type, tflags);
+
+ *ptype = type;
+ *pvmask = vmask;
+}
+
+static void
+set_legacy_value(uint32_t val, ipfw_table_value *v)
+{
+ v->tag = val;
+ v->pipe = val;
+ v->divert = val;
+ v->skipto = val;
+ v->netgraph = val;
+ v->fib = val;
+ v->nat = val;
+ v->nh4 = val;
+ v->dscp = (uint8_t)val;
+ v->limit = val;
+}
+
+static void
+tentry_fill_value(ipfw_obj_header *oh __unused, ipfw_obj_tentry *tent,
+ char *arg, uint8_t type __unused, uint32_t vmask)
+{
+ struct addrinfo hints, *res;
+ struct in_addr ipaddr;
+ const char *etype;
+ char *comma, *e, *n, *p;
+ uint32_t a4, flag, val;
+ ipfw_table_value *v;
+ uint32_t i;
+ int dval;
+
+ v = &tent->v.value;
+
+ /* Compat layer: keep old behavior for legacy value types */
+ if (vmask == IPFW_VTYPE_LEGACY) {
+ /* Try to interpret as number first */
+ val = strtoul(arg, &p, 0);
+ if (*p == '\0') {
+ set_legacy_value(val, v);
+ return;
+ }
+ if (inet_pton(AF_INET, arg, &val) == 1) {
+ set_legacy_value(ntohl(val), v);
+ return;
+ }
+ /* Try hostname */
+ if (lookup_host(arg, &ipaddr) == 0) {
+ set_legacy_value(ntohl(ipaddr.s_addr), v);
+ return;
+ }
+ errx(EX_OSERR, "Unable to parse value %s", arg);
+ }
+
+ /*
+ * Shorthands: handle single value if vmask consists
+ * of numbers only. e.g.:
+ * vmask = "fib,skipto" -> treat input "1" as "1,1"
+ */
+
+ n = arg;
+ etype = NULL;
+ for (i = 1; i < (1u << 31); i *= 2) {
+ if ((flag = (vmask & i)) == 0)
+ continue;
+ vmask &= ~flag;
+
+ if ((comma = strchr(n, ',')) != NULL)
+ *comma = '\0';
+
+ switch (flag) {
+ case IPFW_VTYPE_TAG:
+ v->tag = strtol(n, &e, 10);
+ if (*e != '\0')
+ etype = "tag";
+ break;
+ case IPFW_VTYPE_PIPE:
+ v->pipe = strtol(n, &e, 10);
+ if (*e != '\0')
+ etype = "pipe";
+ break;
+ case IPFW_VTYPE_DIVERT:
+ v->divert = strtol(n, &e, 10);
+ if (*e != '\0')
+ etype = "divert";
+ break;
+ case IPFW_VTYPE_SKIPTO:
+ v->skipto = strtol(n, &e, 10);
+ if (*e != '\0')
+ etype = "skipto";
+ break;
+ case IPFW_VTYPE_NETGRAPH:
+ v->netgraph = strtol(n, &e, 10);
+ if (*e != '\0')
+ etype = "netgraph";
+ break;
+ case IPFW_VTYPE_FIB:
+ v->fib = strtol(n, &e, 10);
+ if (*e != '\0')
+ etype = "fib";
+ break;
+ case IPFW_VTYPE_NAT:
+ v->nat = strtol(n, &e, 10);
+ if (*e != '\0')
+ etype = "nat";
+ break;
+ case IPFW_VTYPE_LIMIT:
+ v->limit = strtol(n, &e, 10);
+ if (*e != '\0')
+ etype = "limit";
+ break;
+ case IPFW_VTYPE_NH4:
+ if (strchr(n, '.') != NULL &&
+ inet_pton(AF_INET, n, &a4) == 1) {
+ v->nh4 = ntohl(a4);
+ break;
+ }
+ if (lookup_host(n, &ipaddr) == 0) {
+ v->nh4 = ntohl(ipaddr.s_addr);
+ break;
+ }
+ etype = "ipv4";
+ break;
+ case IPFW_VTYPE_DSCP:
+ if (isalpha(*n)) {
+ if ((dval = match_token(f_ipdscp, n)) != -1) {
+ v->dscp = dval;
+ break;
+ } else
+ etype = "DSCP code";
+ } else {
+ v->dscp = strtol(n, &e, 10);
+ if (v->dscp > 63 || *e != '\0')
+ etype = "DSCP value";
+ }
+ break;
+ case IPFW_VTYPE_NH6:
+ if (strchr(n, ':') != NULL) {
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_flags = AI_NUMERICHOST;
+ if (getaddrinfo(n, NULL, &hints, &res) == 0) {
+ v->nh6 = ((struct sockaddr_in6 *)
+ res->ai_addr)->sin6_addr;
+ v->zoneid = ((struct sockaddr_in6 *)
+ res->ai_addr)->sin6_scope_id;
+ freeaddrinfo(res);
+ break;
+ }
+ }
+ etype = "ipv6";
+ break;
+ case IPFW_VTYPE_MARK:
+ v->mark = strtol(n, &e, 16);
+ if (*e != '\0')
+ etype = "mark";
+ break;
+ }
+
+ if (etype != NULL)
+ errx(EX_USAGE, "Unable to parse %s as %s", n, etype);
+
+ if (comma != NULL)
+ *comma++ = ',';
+
+ if ((n = comma) != NULL)
+ continue;
+
+ /* End of input. */
+ if (vmask != 0)
+ errx(EX_USAGE, "Not enough fields inside value");
+ }
+}
+
+/*
+ * Compare table names.
+ * Honor number comparison.
+ */
+static int
+tablename_cmp(const void *a, const void *b)
+{
+ const ipfw_xtable_info *ia, *ib;
+
+ ia = (const ipfw_xtable_info *)a;
+ ib = (const ipfw_xtable_info *)b;
+
+ return (stringnum_cmp(ia->tablename, ib->tablename));
+}
+
+/*
+ * Retrieves table list from kernel,
+ * optionally sorts it and calls requested function for each table.
+ * Returns 0 on success.
+ */
+static int
+tables_foreach(table_cb_t *f, void *arg, int sort)
+{
+ ipfw_obj_lheader *olh;
+ ipfw_xtable_info *info;
+ size_t sz;
+ uint32_t i;
+
+ /* Start with reasonable default */
+ sz = sizeof(*olh) + 16 * sizeof(ipfw_xtable_info);
+
+ for (;;) {
+ if ((olh = calloc(1, sz)) == NULL)
+ return (ENOMEM);
+
+ olh->size = sz;
+ if (do_get3(IP_FW_TABLES_XLIST, &olh->opheader, &sz) != 0) {
+ sz = olh->size;
+ free(olh);
+ if (errno != ENOMEM)
+ return (errno);
+ continue;
+ }
+
+ if (sort != 0)
+ qsort(olh + 1, olh->count, olh->objsize,
+ tablename_cmp);
+
+ info = (ipfw_xtable_info *)(olh + 1);
+ for (i = 0; i < olh->count; i++) {
+ if (g_co.use_set == 0 || info->set == g_co.use_set - 1)
+ (void)f(info, arg);
+ info = (ipfw_xtable_info *)((caddr_t)info +
+ olh->objsize);
+ }
+ free(olh);
+ break;
+ }
+ return (0);
+}
+
+
+/*
+ * Retrieves all entries for given table @i in
+ * eXtended format. Allocate buffer large enough
+ * to store result. Called needs to free it later.
+ *
+ * Returns 0 on success.
+ */
+static int
+table_do_get_list(ipfw_xtable_info *i, ipfw_obj_header **poh)
+{
+ ipfw_obj_header *oh;
+ size_t sz;
+ int c;
+
+ sz = 0;
+ oh = NULL;
+ for (c = 0; c < 8; c++) {
+ if (sz < i->size)
+ sz = i->size + 44;
+ if (oh != NULL)
+ free(oh);
+ if ((oh = calloc(1, sz)) == NULL)
+ continue;
+ table_fill_objheader(oh, i);
+ oh->opheader.version = 1; /* Current version */
+ if (do_get3(IP_FW_TABLE_XLIST, &oh->opheader, &sz) == 0) {
+ *poh = oh;
+ return (0);
+ }
+
+ if (errno != ENOMEM)
+ break;
+ }
+ free(oh);
+
+ return (errno);
+}
+
+/*
+ * Shows all entries from @oh in human-readable format
+ */
+static void
+table_show_list(ipfw_obj_header *oh, int need_header)
+{
+ ipfw_obj_tentry *tent;
+ uint32_t count;
+ ipfw_xtable_info *i;
+
+ i = (ipfw_xtable_info *)(oh + 1);
+ tent = (ipfw_obj_tentry *)(i + 1);
+
+ if (need_header)
+ printf("--- table(%s), set(%u) ---\n", i->tablename, i->set);
+
+ count = i->count;
+ while (count > 0) {
+ table_show_entry(i, tent);
+ tent = (ipfw_obj_tentry *)((caddr_t)tent + tent->head.length);
+ count--;
+ }
+}
+
+static void
+table_show_value(char *buf, size_t bufsize, ipfw_table_value *v,
+ uint32_t vmask, int print_ip)
+{
+ char abuf[INET6_ADDRSTRLEN + IF_NAMESIZE + 2];
+ struct sockaddr_in6 sa6;
+ uint32_t flag, i, l;
+ size_t sz;
+ struct in_addr a4;
+
+ sz = bufsize;
+
+ /*
+ * Some shorthands for printing values:
+ * legacy assumes all values are equal, so keep the first one.
+ */
+ if (vmask == IPFW_VTYPE_LEGACY) {
+ if (print_ip != 0) {
+ flag = htonl(v->tag);
+ inet_ntop(AF_INET, &flag, buf, sz);
+ } else
+ snprintf(buf, sz, "%u", v->tag);
+ return;
+ }
+
+ for (i = 1; i < (1u << 31); i *= 2) {
+ if ((flag = (vmask & i)) == 0)
+ continue;
+ l = 0;
+
+ switch (flag) {
+ case IPFW_VTYPE_TAG:
+ l = snprintf(buf, sz, "%u,", v->tag);
+ break;
+ case IPFW_VTYPE_PIPE:
+ l = snprintf(buf, sz, "%u,", v->pipe);
+ break;
+ case IPFW_VTYPE_DIVERT:
+ l = snprintf(buf, sz, "%d,", v->divert);
+ break;
+ case IPFW_VTYPE_SKIPTO:
+ l = snprintf(buf, sz, "%d,", v->skipto);
+ break;
+ case IPFW_VTYPE_NETGRAPH:
+ l = snprintf(buf, sz, "%u,", v->netgraph);
+ break;
+ case IPFW_VTYPE_FIB:
+ l = snprintf(buf, sz, "%u,", v->fib);
+ break;
+ case IPFW_VTYPE_NAT:
+ l = snprintf(buf, sz, "%u,", v->nat);
+ break;
+ case IPFW_VTYPE_LIMIT:
+ l = snprintf(buf, sz, "%u,", v->limit);
+ break;
+ case IPFW_VTYPE_NH4:
+ a4.s_addr = htonl(v->nh4);
+ inet_ntop(AF_INET, &a4, abuf, sizeof(abuf));
+ l = snprintf(buf, sz, "%s,", abuf);
+ break;
+ case IPFW_VTYPE_DSCP:
+ l = snprintf(buf, sz, "%d,", v->dscp);
+ break;
+ case IPFW_VTYPE_NH6:
+ sa6.sin6_family = AF_INET6;
+ sa6.sin6_len = sizeof(sa6);
+ sa6.sin6_addr = v->nh6;
+ sa6.sin6_port = 0;
+ sa6.sin6_scope_id = v->zoneid;
+ if (getnameinfo((const struct sockaddr *)&sa6,
+ sa6.sin6_len, abuf, sizeof(abuf), NULL, 0,
+ NI_NUMERICHOST) == 0)
+ l = snprintf(buf, sz, "%s,", abuf);
+ break;
+ case IPFW_VTYPE_MARK:
+ l = snprintf(buf, sz, "%#x,", v->mark);
+ break;
+ }
+
+ buf += l;
+ sz -= l;
+ }
+
+ if (sz != bufsize)
+ *(buf - 1) = '\0';
+}
+
+static void
+table_show_entry(ipfw_xtable_info *i, ipfw_obj_tentry *tent)
+{
+ char tbuf[128], pval[128];
+ const char *comma;
+ const u_char *mac;
+ void *paddr;
+ struct tflow_entry *tfe;
+
+ table_show_value(pval, sizeof(pval), &tent->v.value, i->vmask,
+ g_co.do_value_as_ip);
+
+ switch (i->type) {
+ case IPFW_TABLE_ADDR:
+ /* IPv4 or IPv6 prefixes */
+ inet_ntop(tent->subtype, &tent->k, tbuf, sizeof(tbuf));
+ printf("%s/%u %s\n", tbuf, tent->masklen, pval);
+ break;
+ case IPFW_TABLE_MAC:
+ /* MAC prefixes */
+ mac = tent->k.mac;
+ printf("%02x:%02x:%02x:%02x:%02x:%02x/%u %s\n",
+ mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
+ tent->masklen, pval);
+ break;
+ case IPFW_TABLE_INTERFACE:
+ /* Interface names */
+ printf("%s %s\n", tent->k.iface, pval);
+ break;
+ case IPFW_TABLE_NUMBER:
+ /* numbers */
+ printf("%u %s\n", tent->k.key, pval);
+ break;
+ case IPFW_TABLE_FLOW:
+ /* flows */
+ tfe = &tent->k.flow;
+ comma = "";
+
+ if ((i->tflags & IPFW_TFFLAG_SRCIP) != 0) {
+ if (tfe->af == AF_INET)
+ paddr = &tfe->a.a4.sip;
+ else
+ paddr = &tfe->a.a6.sip6;
+
+ inet_ntop(tfe->af, paddr, tbuf, sizeof(tbuf));
+ printf("%s%s", comma, tbuf);
+ comma = ",";
+ }
+
+ if ((i->tflags & IPFW_TFFLAG_PROTO) != 0) {
+ printf("%s%d", comma, tfe->proto);
+ comma = ",";
+ }
+
+ if ((i->tflags & IPFW_TFFLAG_SRCPORT) != 0) {
+ printf("%s%d", comma, ntohs(tfe->sport));
+ comma = ",";
+ }
+ if ((i->tflags & IPFW_TFFLAG_DSTIP) != 0) {
+ if (tfe->af == AF_INET)
+ paddr = &tfe->a.a4.dip;
+ else
+ paddr = &tfe->a.a6.dip6;
+
+ inet_ntop(tfe->af, paddr, tbuf, sizeof(tbuf));
+ printf("%s%s", comma, tbuf);
+ comma = ",";
+ }
+
+ if ((i->tflags & IPFW_TFFLAG_DSTPORT) != 0) {
+ printf("%s%d", comma, ntohs(tfe->dport));
+ comma = ",";
+ }
+
+ printf(" %s\n", pval);
+ }
+}
+
+static int
+table_do_get_stdlist(uint16_t opcode, ipfw_obj_lheader **polh)
+{
+ ipfw_obj_lheader req, *olh;
+ size_t sz;
+
+ memset(&req, 0, sizeof(req));
+ sz = sizeof(req);
+
+ if (do_get3(opcode, &req.opheader, &sz) != 0)
+ if (errno != ENOMEM)
+ return (errno);
+
+ sz = req.size;
+ if ((olh = calloc(1, sz)) == NULL)
+ return (ENOMEM);
+
+ olh->size = sz;
+ if (do_get3(opcode, &olh->opheader, &sz) != 0) {
+ free(olh);
+ return (errno);
+ }
+
+ *polh = olh;
+ return (0);
+}
+
+static int
+table_do_get_algolist(ipfw_obj_lheader **polh)
+{
+
+ return (table_do_get_stdlist(IP_FW_TABLES_ALIST, polh));
+}
+
+static int
+table_do_get_vlist(ipfw_obj_lheader **polh)
+{
+
+ return (table_do_get_stdlist(IP_FW_TABLE_VLIST, polh));
+}
+
+void
+ipfw_list_ta(int ac __unused, char *av[] __unused)
+{
+ ipfw_obj_lheader *olh;
+ ipfw_ta_info *info;
+ const char *atype;
+ uint32_t i;
+ int error;
+
+ error = table_do_get_algolist(&olh);
+ if (error != 0)
+ err(EX_OSERR, "Unable to request algorithm list");
+
+ info = (ipfw_ta_info *)(olh + 1);
+ for (i = 0; i < olh->count; i++) {
+ if ((atype = match_value(tabletypes, info->type)) == NULL)
+ atype = "unknown";
+ printf("--- %s ---\n", info->algoname);
+ printf(" type: %s\n refcount: %u\n", atype, info->refcnt);
+
+ info = (ipfw_ta_info *)((caddr_t)info + olh->objsize);
+ }
+
+ free(olh);
+}
+
+
+static int
+compare_values(const void *_a, const void *_b)
+{
+ const ipfw_table_value *a, *b;
+
+ a = (const ipfw_table_value *)_a;
+ b = (const ipfw_table_value *)_b;
+
+ if (a->kidx < b->kidx)
+ return (-1);
+ else if (a->kidx > b->kidx)
+ return (1);
+
+ return (0);
+}
+
+void
+ipfw_list_values(int ac __unused, char *av[] __unused)
+{
+ char buf[128];
+ ipfw_obj_lheader *olh;
+ ipfw_table_value *v;
+ uint32_t i, vmask;
+ int error;
+
+ error = table_do_get_vlist(&olh);
+ if (error != 0)
+ err(EX_OSERR, "Unable to request value list");
+
+ vmask = 0x7FFFFFFF; /* Similar to IPFW_VTYPE_LEGACY */
+
+ table_print_valheader(buf, sizeof(buf), vmask);
+ printf("HEADER: %s\n", buf);
+ v = (ipfw_table_value *)(olh + 1);
+ qsort(v, olh->count, olh->objsize, compare_values);
+ for (i = 0; i < olh->count; i++) {
+ table_show_value(buf, sizeof(buf), (ipfw_table_value *)v,
+ vmask, 0);
+ printf("[%u] refs=%lu %s\n", v->kidx, (u_long)v->refcnt, buf);
+ v = (ipfw_table_value *)((caddr_t)v + olh->objsize);
+ }
+
+ free(olh);
+}
+
+int
+table_check_name(const char *tablename)
+{
+
+ if (ipfw_check_object_name(tablename) != 0)
+ return (EINVAL);
+ /* Restrict some 'special' names */
+ if (strcmp(tablename, "all") == 0)
+ return (EINVAL);
+ return (0);
+}
+

File Metadata

Mime Type
text/plain
Expires
Sun, Mar 29, 1:14 AM (3 h, 38 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
28221093
Default Alt Text
D54763.1774746889.diff (653 KB)

Event Timeline