summaryrefslogtreecommitdiffstats
path: root/sys/netinet
diff options
context:
space:
mode:
authormelifaro <melifaro@FreeBSD.org>2012-03-12 14:07:57 +0000
committermelifaro <melifaro@FreeBSD.org>2012-03-12 14:07:57 +0000
commitc614ff641f951a75a93d083b1980b4bd3480b949 (patch)
treebc4e5f2db723245d6f66a54bec8c2de91cede71d /sys/netinet
parent4861f7911395459349e837d48f6d239d6c1e80d7 (diff)
downloadFreeBSD-src-c614ff641f951a75a93d083b1980b4bd3480b949.zip
FreeBSD-src-c614ff641f951a75a93d083b1980b4bd3480b949.tar.gz
- Add ipfw eXtended tables permitting radix to be used for any kind of keys.
- Add support for IPv6 and interface extended tables - Make number of tables to be loader tunable in range 0..65534. - Use IP_FW3 opcode for all new extended table cmds No ABI changes are introduced. Old userland will see valid tables for IPv4 tables and no entries otherwise. Flush works for any table. IP_FW3 socket option is used to encapsulate all new opcodes: /* IP_FW3 header/opcodes */ typedef struct _ip_fw3_opheader { uint16_t opcode; /* Operation opcode */ uint16_t reserved[3]; /* Align to 64-bit boundary */ } ip_fw3_opheader; New opcodes added: IP_FW_TABLE_XADD, IP_FW_TABLE_XDEL, IP_FW_TABLE_XGETSIZE, IP_FW_TABLE_XLIST ipfw(8) table argument parsing behavior is changed: 'ipfw table 999 add host' now assumes 'host' to be interface name instead of hostname. New tunable: net.inet.ip.fw.tables_max controls number of table supported by ipfw in given VNET instance. 128 is still the default value. New syntax: ipfw add skipto tablearg ip from any to any via table(42) in ipfw add skipto tablearg ip from any to any via table(4242) out This is a bit hackish, special interface name '\1' is used to signal interface table number is passed in p.glob field. Sponsored by Yandex LLC Reviewed by: ae Approved by: ae (mentor) MFC after: 4 weeks
Diffstat (limited to 'sys/netinet')
-rw-r--r--sys/netinet/ip_fw.h43
-rw-r--r--sys/netinet/ipfw/ip_fw2.c57
-rw-r--r--sys/netinet/ipfw/ip_fw_private.h21
-rw-r--r--sys/netinet/ipfw/ip_fw_sockopt.c113
-rw-r--r--sys/netinet/ipfw/ip_fw_table.c529
5 files changed, 671 insertions, 92 deletions
diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h
index f6f8fcd..9dba29b 100644
--- a/sys/netinet/ip_fw.h
+++ b/sys/netinet/ip_fw.h
@@ -37,8 +37,7 @@
#define IPFW_DEFAULT_RULE 65535
/*
- * The number of ipfw tables. The maximum allowed table number is the
- * (IPFW_TABLES_MAX - 1).
+ * Default number of ipfw tables.
*/
#define IPFW_TABLES_MAX 128
@@ -62,6 +61,19 @@
*/
#define IPFW_CALLSTACK_SIZE 16
+/* IP_FW3 header/opcodes */
+typedef struct _ip_fw3_opheader {
+ uint16_t opcode; /* Operation opcode */
+ uint16_t reserved[3]; /* Align to 64-bit boundary */
+} ip_fw3_opheader;
+
+
+/* IPFW extented tables support */
+#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 */
+#define IP_FW_TABLE_XLIST 89 /* list table contents */
+
/*
* The kernel representation of ipfw rules is made of a list of
* 'instructions' (for all practical purposes equivalent to BPF
@@ -581,6 +593,11 @@ struct _ipfw_dyn_rule {
/*
* These are used for lookup tables.
*/
+
+#define IPFW_TABLE_CIDR 1 /* Table for holding IPv4/IPv6 prefixes */
+#define IPFW_TABLE_INTERFACE 2 /* Table for holding interface names */
+#define IPFW_TABLE_MAXTYPE 2 /* Maximum valid number */
+
typedef struct _ipfw_table_entry {
in_addr_t addr; /* network address */
u_int32_t value; /* value */
@@ -588,6 +605,19 @@ typedef struct _ipfw_table_entry {
u_int8_t masklen; /* mask length */
} ipfw_table_entry;
+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 */
+ 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;
+
typedef struct _ipfw_table {
u_int32_t size; /* size of entries in bytes */
u_int32_t cnt; /* # of entries */
@@ -595,4 +625,13 @@ typedef struct _ipfw_table {
ipfw_table_entry ent[0]; /* entries */
} ipfw_table;
+typedef struct _ipfw_xtable {
+ ip_fw3_opheader opheader; /* eXtended tables are controlled via IP_FW3 */
+ 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;
+
#endif /* _IPFW2_H */
diff --git a/sys/netinet/ipfw/ip_fw2.c b/sys/netinet/ipfw/ip_fw2.c
index cbb4ed7..74cf463 100644
--- a/sys/netinet/ipfw/ip_fw2.c
+++ b/sys/netinet/ipfw/ip_fw2.c
@@ -116,6 +116,9 @@ static int default_to_accept;
VNET_DEFINE(int, autoinc_step);
VNET_DEFINE(int, fw_one_pass) = 1;
+/* Use 128 tables by default */
+VNET_DEFINE(int, fw_tables_max) = IPFW_TABLES_MAX;
+
/*
* Each rule belongs to one of 32 different sets (0..31).
* The variable set_disable contains one bit per set.
@@ -145,7 +148,6 @@ ipfw_nat_cfg_t *ipfw_nat_get_log_ptr;
#ifdef SYSCTL_NODE
uint32_t dummy_def = IPFW_DEFAULT_RULE;
-uint32_t dummy_tables_max = IPFW_TABLES_MAX;
SYSBEGIN(f3)
@@ -166,12 +168,13 @@ SYSCTL_UINT(_net_inet_ip_fw, OID_AUTO, default_rule, CTLFLAG_RD,
&dummy_def, 0,
"The default/max possible rule number.");
SYSCTL_UINT(_net_inet_ip_fw, OID_AUTO, tables_max, CTLFLAG_RD,
- &dummy_tables_max, 0,
+ &V_fw_tables_max, 0,
"The maximum number of tables.");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, default_to_accept, CTLFLAG_RDTUN,
&default_to_accept, 0,
"Make the default rule accept all packets.");
TUNABLE_INT("net.inet.ip.fw.default_to_accept", &default_to_accept);
+TUNABLE_INT("net.inet.ip.fw.tables_max", &V_fw_tables_max);
SYSCTL_VNET_INT(_net_inet_ip_fw, OID_AUTO, static_count,
CTLFLAG_RD, &VNET_NAME(layer3_chain.n_rules), 0,
"Number of static rules");
@@ -341,12 +344,15 @@ tcpopts_match(struct tcphdr *tcp, ipfw_insn *cmd)
}
static int
-iface_match(struct ifnet *ifp, ipfw_insn_if *cmd)
+iface_match(struct ifnet *ifp, ipfw_insn_if *cmd, struct ip_fw_chain *chain, uint32_t *tablearg)
{
if (ifp == NULL) /* no iface with this packet, match fails */
return 0;
/* Check by name or by IP address */
if (cmd->name[0] != '\0') { /* match by name */
+ if (cmd->name[0] == '\1') /* use tablearg to match */
+ return ipfw_lookup_table_extended(chain, cmd->p.glob,
+ ifp->if_xname, tablearg, IPFW_TABLE_INTERFACE);
/* Check name */
if (cmd->p.glob) {
if (fnmatch(cmd->name, ifp->if_xname, 0) == 0)
@@ -1312,16 +1318,18 @@ do { \
case O_RECV:
match = iface_match(m->m_pkthdr.rcvif,
- (ipfw_insn_if *)cmd);
+ (ipfw_insn_if *)cmd, chain, &tablearg);
break;
case O_XMIT:
- match = iface_match(oif, (ipfw_insn_if *)cmd);
+ match = iface_match(oif, (ipfw_insn_if *)cmd,
+ chain, &tablearg);
break;
case O_VIA:
match = iface_match(oif ? oif :
- m->m_pkthdr.rcvif, (ipfw_insn_if *)cmd);
+ m->m_pkthdr.rcvif, (ipfw_insn_if *)cmd,
+ chain, &tablearg);
break;
case O_MACADDR2:
@@ -1448,6 +1456,17 @@ do { \
((ipfw_insn_u32 *)cmd)->d[0] == v;
else
tablearg = v;
+ } else if (is_ipv6) {
+ uint32_t v = 0;
+ void *pkey = (cmd->opcode == O_IP_DST_LOOKUP) ?
+ &args->f_id.dst_ip6: &args->f_id.src_ip6;
+ match = ipfw_lookup_table_extended(chain,
+ cmd->arg1, pkey, &v,
+ IPFW_TABLE_CIDR);
+ if (cmdlen == F_INSN_SIZE(ipfw_insn_u32))
+ match = ((ipfw_insn_u32 *)cmd)->d[0] == v;
+ if (match)
+ tablearg = v;
}
break;
@@ -2566,22 +2585,24 @@ vnet_ipfw_init(const void *unused)
LIST_INIT(&chain->nat);
#endif
+ /* Check user-supplied number for validness */
+ if (V_fw_tables_max < 0)
+ V_fw_tables_max = IPFW_TABLES_MAX;
+ if (V_fw_tables_max > 65534)
+ V_fw_tables_max = 65534;
+
/* insert the default rule and create the initial map */
chain->n_rules = 1;
chain->static_len = sizeof(struct ip_fw);
- chain->map = malloc(sizeof(struct ip_fw *), M_IPFW, M_NOWAIT | M_ZERO);
+ chain->map = malloc(sizeof(struct ip_fw *), M_IPFW, M_WAITOK | M_ZERO);
if (chain->map)
- rule = malloc(chain->static_len, M_IPFW, M_NOWAIT | M_ZERO);
- if (rule == NULL) {
- if (chain->map)
- free(chain->map, M_IPFW);
- printf("ipfw2: ENOSPC initializing default rule "
- "(support disabled)\n");
- return (ENOSPC);
- }
+ rule = malloc(chain->static_len, M_IPFW, M_WAITOK | M_ZERO);
error = ipfw_init_tables(chain);
if (error) {
- panic("init_tables"); /* XXX Marko fix this ! */
+ printf("ipfw2: setting up tables failed\n");
+ free(chain->map, M_IPFW);
+ free(rule, M_IPFW);
+ return (ENOSPC);
}
/* fill and insert the default rule */
@@ -2644,12 +2665,12 @@ vnet_ipfw_uninit(const void *unused)
IPFW_UH_WLOCK(chain);
IPFW_WLOCK(chain);
+ ipfw_dyn_uninit(0); /* run the callout_drain */
IPFW_WUNLOCK(chain);
- IPFW_WLOCK(chain);
- ipfw_dyn_uninit(0); /* run the callout_drain */
ipfw_destroy_tables(chain);
reap = NULL;
+ IPFW_WLOCK(chain);
for (i = 0; i < chain->n_rules; i++) {
rule = chain->map[i];
rule->x_next = reap;
diff --git a/sys/netinet/ipfw/ip_fw_private.h b/sys/netinet/ipfw/ip_fw_private.h
index fdb2b77..a963380 100644
--- a/sys/netinet/ipfw/ip_fw_private.h
+++ b/sys/netinet/ipfw/ip_fw_private.h
@@ -209,6 +209,9 @@ VNET_DECLARE(u_int32_t, set_disable);
VNET_DECLARE(int, autoinc_step);
#define V_autoinc_step VNET(autoinc_step)
+VNET_DECLARE(int, fw_tables_max);
+#define V_fw_tables_max VNET(fw_tables_max)
+
struct ip_fw_chain {
struct ip_fw *rules; /* list of rules */
struct ip_fw *reap; /* list of rules to reap */
@@ -217,7 +220,9 @@ struct ip_fw_chain {
int static_len; /* total len of static rules */
struct ip_fw **map; /* array of rule ptrs to ease lookup */
LIST_HEAD(nat_list, cfg_nat) nat; /* list of nat entries */
- struct radix_node_head *tables[IPFW_TABLES_MAX];
+ struct radix_node_head **tables; /* IPv4 tables */
+ struct radix_node_head **xtables; /* extended tables */
+ uint8_t *tabletype; /* Array of table types */
#if defined( __linux__ ) || defined( _WIN32 )
spinlock_t rwmtx;
spinlock_t uh_lock;
@@ -273,16 +278,20 @@ int ipfw_check_hook(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir,
struct radix_node;
int ipfw_lookup_table(struct ip_fw_chain *ch, uint16_t tbl, in_addr_t addr,
uint32_t *val);
+int ipfw_lookup_table_extended(struct ip_fw_chain *ch, uint16_t tbl, void *paddr,
+ uint32_t *val, int type);
int ipfw_init_tables(struct ip_fw_chain *ch);
void ipfw_destroy_tables(struct ip_fw_chain *ch);
int ipfw_flush_table(struct ip_fw_chain *ch, uint16_t tbl);
-int ipfw_add_table_entry(struct ip_fw_chain *ch, uint16_t tbl, in_addr_t addr,
- uint8_t mlen, uint32_t value);
-int ipfw_dump_table_entry(struct radix_node *rn, void *arg);
-int ipfw_del_table_entry(struct ip_fw_chain *ch, uint16_t tbl, in_addr_t addr,
- uint8_t mlen);
+int ipfw_add_table_entry(struct ip_fw_chain *ch, uint16_t tbl, void *paddr,
+ uint8_t plen, uint8_t mlen, uint8_t type, uint32_t value);
+int ipfw_del_table_entry(struct ip_fw_chain *ch, uint16_t tbl, void *paddr,
+ uint8_t plen, uint8_t mlen, uint8_t type);
int ipfw_count_table(struct ip_fw_chain *ch, uint32_t tbl, uint32_t *cnt);
+int ipfw_dump_table_entry(struct radix_node *rn, void *arg);
int ipfw_dump_table(struct ip_fw_chain *ch, ipfw_table *tbl);
+int ipfw_count_xtable(struct ip_fw_chain *ch, uint32_t tbl, uint32_t *cnt);
+int ipfw_dump_xtable(struct ip_fw_chain *ch, ipfw_xtable *tbl);
/* In ip_fw_nat.c -- XXX to be moved to ip_var.h */
diff --git a/sys/netinet/ipfw/ip_fw_sockopt.c b/sys/netinet/ipfw/ip_fw_sockopt.c
index 1302452..8aa6fd4 100644
--- a/sys/netinet/ipfw/ip_fw_sockopt.c
+++ b/sys/netinet/ipfw/ip_fw_sockopt.c
@@ -667,7 +667,6 @@ check_ipfw_struct(struct ip_fw *rule, int size)
cmdlen != F_INSN_SIZE(ipfw_insn_u32))
goto bad_size;
break;
-
case O_MACADDR2:
if (cmdlen != F_INSN_SIZE(ipfw_insn_mac))
goto bad_size;
@@ -941,6 +940,7 @@ ipfw_getrules(struct ip_fw_chain *chain, void *buf, size_t space)
}
+#define IP_FW3_OPLENGTH(x) ((x)->sopt_valsize - sizeof(ip_fw3_opheader))
/**
* {set|get}sockopt parser.
*/
@@ -949,10 +949,13 @@ ipfw_ctl(struct sockopt *sopt)
{
#define RULE_MAXSIZE (256*sizeof(u_int32_t))
int error;
- size_t size;
+ size_t size, len, valsize;
struct ip_fw *buf, *rule;
struct ip_fw_chain *chain;
u_int32_t rulenum[2];
+ uint32_t opt;
+ char xbuf[128];
+ ip_fw3_opheader *op3 = NULL;
error = priv_check(sopt->sopt_td, PRIV_NETINET_IPFW);
if (error)
@@ -972,7 +975,21 @@ ipfw_ctl(struct sockopt *sopt)
chain = &V_layer3_chain;
error = 0;
- switch (sopt->sopt_name) {
+ /* Save original valsize before it is altered via sooptcopyin() */
+ valsize = sopt->sopt_valsize;
+ if ((opt = sopt->sopt_name) == IP_FW3) {
+ /*
+ * Copy not less than sizeof(ip_fw3_opheader).
+ * We hope any IP_FW3 command will fit into 128-byte buffer.
+ */
+ if ((error = sooptcopyin(sopt, xbuf, sizeof(xbuf),
+ sizeof(ip_fw3_opheader))) != 0)
+ return (error);
+ op3 = (ip_fw3_opheader *)xbuf;
+ opt = op3->opcode;
+ }
+
+ switch (opt) {
case IP_FW_GET:
/*
* pass up a copy of the current rules. Static rules
@@ -1111,7 +1128,8 @@ ipfw_ctl(struct sockopt *sopt)
if (error)
break;
error = ipfw_add_table_entry(chain, ent.tbl,
- ent.addr, ent.masklen, ent.value);
+ &ent.addr, sizeof(ent.addr), ent.masklen,
+ IPFW_TABLE_CIDR, ent.value);
}
break;
@@ -1124,7 +1142,34 @@ ipfw_ctl(struct sockopt *sopt)
if (error)
break;
error = ipfw_del_table_entry(chain, ent.tbl,
- ent.addr, ent.masklen);
+ &ent.addr, sizeof(ent.addr), ent.masklen, IPFW_TABLE_CIDR);
+ }
+ break;
+
+ case IP_FW_TABLE_XADD: /* IP_FW3 */
+ case IP_FW_TABLE_XDEL: /* IP_FW3 */
+ {
+ ipfw_table_xentry *xent = (ipfw_table_xentry *)(op3 + 1);
+
+ /* Check minimum header size */
+ if (IP_FW3_OPLENGTH(sopt) < offsetof(ipfw_table_xentry, k)) {
+ error = EINVAL;
+ break;
+ }
+
+ /* Check if len field is valid */
+ if (xent->len > sizeof(ipfw_table_xentry)) {
+ error = EINVAL;
+ break;
+ }
+
+ len = xent->len - offsetof(ipfw_table_xentry, k);
+
+ error = (opt == IP_FW_TABLE_XADD) ?
+ ipfw_add_table_entry(chain, xent->tbl, &xent->k,
+ len, xent->masklen, xent->type, xent->value) :
+ ipfw_del_table_entry(chain, xent->tbl, &xent->k,
+ len, xent->masklen, xent->type);
}
break;
@@ -1136,9 +1181,7 @@ ipfw_ctl(struct sockopt *sopt)
sizeof(tbl), sizeof(tbl));
if (error)
break;
- IPFW_WLOCK(chain);
error = ipfw_flush_table(chain, tbl);
- IPFW_WUNLOCK(chain);
}
break;
@@ -1187,6 +1230,62 @@ ipfw_ctl(struct sockopt *sopt)
}
break;
+ case IP_FW_TABLE_XGETSIZE: /* IP_FW3 */
+ {
+ uint32_t *tbl;
+
+ if (IP_FW3_OPLENGTH(sopt) < sizeof(uint32_t)) {
+ error = EINVAL;
+ break;
+ }
+
+ tbl = (uint32_t *)(op3 + 1);
+
+ IPFW_RLOCK(chain);
+ error = ipfw_count_xtable(chain, *tbl, tbl);
+ IPFW_RUNLOCK(chain);
+ if (error)
+ break;
+ error = sooptcopyout(sopt, op3, sopt->sopt_valsize);
+ }
+ break;
+
+ case IP_FW_TABLE_XLIST: /* IP_FW3 */
+ {
+ ipfw_xtable *tbl;
+
+ if ((size = valsize) < sizeof(ipfw_xtable)) {
+ error = EINVAL;
+ break;
+ }
+
+ tbl = malloc(size, M_TEMP, M_ZERO | M_WAITOK);
+ memcpy(tbl, op3, sizeof(ipfw_xtable));
+
+ /* Get maximum number of entries we can store */
+ tbl->size = (size - sizeof(ipfw_xtable)) /
+ sizeof(ipfw_table_xentry);
+ IPFW_RLOCK(chain);
+ error = ipfw_dump_xtable(chain, tbl);
+ IPFW_RUNLOCK(chain);
+ if (error) {
+ free(tbl, M_TEMP);
+ break;
+ }
+
+ /* Revert size field back to bytes */
+ tbl->size = tbl->size * sizeof(ipfw_table_xentry) +
+ sizeof(ipfw_table);
+ /*
+ * Since we call sooptcopyin() with small buffer, sopt_valsize is
+ * decreased to reflect supplied buffer size. Set it back to original value
+ */
+ sopt->sopt_valsize = valsize;
+ error = sooptcopyout(sopt, tbl, size);
+ free(tbl, M_TEMP);
+ }
+ break;
+
/*--- NAT operations are protected by the IPFW_LOCK ---*/
case IP_FW_NAT_CFG:
if (IPFW_NAT_LOADED)
diff --git a/sys/netinet/ipfw/ip_fw_table.c b/sys/netinet/ipfw/ip_fw_table.c
index 117a205..8ca8ef7 100644
--- a/sys/netinet/ipfw/ip_fw_table.c
+++ b/sys/netinet/ipfw/ip_fw_table.c
@@ -76,6 +76,29 @@ struct table_entry {
u_int32_t value;
};
+struct xaddr_iface {
+ uint8_t if_len; /* length of this struct */
+ uint8_t pad[7]; /* Align name */
+ char ifname[IF_NAMESIZE]; /* Interface name */
+};
+
+struct table_xentry {
+ struct radix_node rn[2];
+ union {
+#ifdef INET6
+ struct sockaddr_in6 addr6;
+#endif
+ struct xaddr_iface iface;
+ } a;
+ union {
+#ifdef INET6
+ struct sockaddr_in6 mask6;
+#endif
+ struct xaddr_iface ifmask;
+ } m;
+ u_int32_t value;
+};
+
/*
* The radix code expects addr and mask to be array of bytes,
* with the first byte being the length of the array. rn_inithead
@@ -87,57 +110,275 @@ struct table_entry {
*/
#define KEY_LEN(v) *((uint8_t *)&(v))
#define KEY_OFS (8*offsetof(struct sockaddr_in, sin_addr))
+/*
+ * Do not require radix to compare more than actual IPv4/IPv6 address
+ */
+#define KEY_LEN_INET (offsetof(struct sockaddr_in, sin_addr) + sizeof(in_addr_t))
+#define KEY_LEN_INET6 (offsetof(struct sockaddr_in6, sin6_addr) + sizeof(struct in6_addr))
+#define KEY_LEN_IFACE (offsetof(struct xaddr_iface, ifname))
+
+#define OFF_LEN_INET (8 * offsetof(struct sockaddr_in, sin_addr))
+#define OFF_LEN_INET6 (8 * offsetof(struct sockaddr_in6, sin6_addr))
+#define OFF_LEN_IFACE (8 * offsetof(struct xaddr_iface, ifname))
+
+
+static inline void
+ipv6_writemask(struct in6_addr *addr6, uint8_t mask)
+{
+ uint32_t *cp;
+
+ for (cp = (uint32_t *)addr6; mask >= 32; mask -= 32)
+ *cp++ = 0xFFFFFFFF;
+ *cp = htonl(mask ? ~((1 << (32 - mask)) - 1) : 0);
+}
int
-ipfw_add_table_entry(struct ip_fw_chain *ch, uint16_t tbl, in_addr_t addr,
- uint8_t mlen, uint32_t value)
+ipfw_add_table_entry(struct ip_fw_chain *ch, uint16_t tbl, void *paddr,
+ uint8_t plen, uint8_t mlen, uint8_t type, uint32_t value)
{
- struct radix_node_head *rnh;
+ struct radix_node_head *rnh, **rnh_ptr;
struct table_entry *ent;
+ struct table_xentry *xent;
struct radix_node *rn;
+ in_addr_t addr;
+ int offset;
+ void *ent_ptr;
+ struct sockaddr *addr_ptr, *mask_ptr;
+ char c;
- if (tbl >= IPFW_TABLES_MAX)
+ if (tbl >= V_fw_tables_max)
return (EINVAL);
- rnh = ch->tables[tbl];
- ent = malloc(sizeof(*ent), M_IPFW_TBL, M_NOWAIT | M_ZERO);
- if (ent == NULL)
- return (ENOMEM);
- ent->value = value;
- KEY_LEN(ent->addr) = KEY_LEN(ent->mask) = 8;
- ent->mask.sin_addr.s_addr = htonl(mlen ? ~((1 << (32 - mlen)) - 1) : 0);
- ent->addr.sin_addr.s_addr = addr & ent->mask.sin_addr.s_addr;
+
+ switch (type) {
+ case IPFW_TABLE_CIDR:
+ if (plen == sizeof(in_addr_t)) {
+#ifdef INET
+ ent = malloc(sizeof(*ent), M_IPFW_TBL, M_WAITOK | M_ZERO);
+ ent->value = value;
+ /* Set 'total' structure length */
+ KEY_LEN(ent->addr) = KEY_LEN_INET;
+ KEY_LEN(ent->mask) = KEY_LEN_INET;
+ /* Set offset of IPv4 address in bits */
+ offset = OFF_LEN_INET;
+ ent->mask.sin_addr.s_addr = htonl(mlen ? ~((1 << (32 - mlen)) - 1) : 0);
+ addr = *((in_addr_t *)paddr);
+ ent->addr.sin_addr.s_addr = addr & ent->mask.sin_addr.s_addr;
+ /* Set pointers */
+ rnh_ptr = &ch->tables[tbl];
+ ent_ptr = ent;
+ addr_ptr = (struct sockaddr *)&ent->addr;
+ mask_ptr = (struct sockaddr *)&ent->mask;
+#endif
+#ifdef INET6
+ } else if (plen == sizeof(struct in6_addr)) {
+ /* IPv6 case */
+ if (mlen > 128)
+ return (EINVAL);
+ xent = malloc(sizeof(*xent), M_IPFW_TBL, M_WAITOK | M_ZERO);
+ xent->value = value;
+ /* Set 'total' structure length */
+ KEY_LEN(xent->a.addr6) = KEY_LEN_INET6;
+ KEY_LEN(xent->m.mask6) = KEY_LEN_INET6;
+ /* Set offset of IPv6 address in bits */
+ offset = OFF_LEN_INET6;
+ ipv6_writemask(&xent->m.mask6.sin6_addr, mlen);
+ memcpy(&xent->a.addr6.sin6_addr, paddr, sizeof(struct in6_addr));
+ APPLY_MASK(&xent->a.addr6.sin6_addr, &xent->m.mask6.sin6_addr);
+ /* Set pointers */
+ rnh_ptr = &ch->xtables[tbl];
+ ent_ptr = xent;
+ addr_ptr = (struct sockaddr *)&xent->a.addr6;
+ mask_ptr = (struct sockaddr *)&xent->m.mask6;
+#endif
+ } else {
+ /* Unknown CIDR type */
+ return (EINVAL);
+ }
+ break;
+
+ case IPFW_TABLE_INTERFACE:
+ /* Check if string is terminated */
+ c = ((char *)paddr)[IF_NAMESIZE - 1];
+ ((char *)paddr)[IF_NAMESIZE - 1] = '\0';
+ if (((mlen = strlen((char *)paddr)) == IF_NAMESIZE - 1) && (c != '\0'))
+ return (EINVAL);
+
+ /* Include last \0 into comparison */
+ mlen++;
+
+ xent = malloc(sizeof(*xent), M_IPFW_TBL, M_WAITOK | M_ZERO);
+ xent->value = value;
+ /* Set 'total' structure length */
+ KEY_LEN(xent->a.iface) = KEY_LEN_IFACE + mlen;
+ KEY_LEN(xent->m.ifmask) = KEY_LEN_IFACE + mlen;
+ /* Set offset of interface name in bits */
+ offset = OFF_LEN_IFACE;
+ memcpy(xent->a.iface.ifname, paddr, mlen);
+ /* Assume direct match */
+ /* TODO: Add interface pattern matching */
+#if 0
+ memset(xent->m.ifmask.ifname, 0xFF, IF_NAMESIZE);
+ mask_ptr = (struct sockaddr *)&xent->m.ifmask;
+#endif
+ /* Set pointers */
+ rnh_ptr = &ch->xtables[tbl];
+ ent_ptr = xent;
+ addr_ptr = (struct sockaddr *)&xent->a.iface;
+ mask_ptr = NULL;
+ break;
+
+ default:
+ return (EINVAL);
+ }
+
IPFW_WLOCK(ch);
- rn = rnh->rnh_addaddr(&ent->addr, &ent->mask, rnh, (void *)ent);
- if (rn == NULL) {
+
+ /* Check if tabletype is valid */
+ if ((ch->tabletype[tbl] != 0) && (ch->tabletype[tbl] != type)) {
IPFW_WUNLOCK(ch);
- free(ent, M_IPFW_TBL);
- return (EEXIST);
+ free(ent_ptr, M_IPFW_TBL);
+ return (EINVAL);
+ }
+
+ /* Check if radix tree exists */
+ if ((rnh = *rnh_ptr) == NULL) {
+ IPFW_WUNLOCK(ch);
+ /* Create radix for a new table */
+ if (!rn_inithead((void **)&rnh, offset)) {
+ free(ent_ptr, M_IPFW_TBL);
+ return (ENOMEM);
+ }
+
+ IPFW_WLOCK(ch);
+ if (*rnh_ptr != NULL) {
+ /* Tree is already attached by other thread */
+ rn_detachhead((void **)&rnh);
+ rnh = *rnh_ptr;
+ /* Check table type another time */
+ if (ch->tabletype[tbl] != type) {
+ IPFW_WUNLOCK(ch);
+ free(ent_ptr, M_IPFW_TBL);
+ return (EINVAL);
+ }
+ } else {
+ *rnh_ptr = rnh;
+ /*
+ * Set table type. It can be set already
+ * (if we have IPv6-only table) but setting
+ * it another time does not hurt
+ */
+ ch->tabletype[tbl] = type;
+ }
}
+
+ rn = rnh->rnh_addaddr(addr_ptr, mask_ptr, rnh, ent_ptr);
IPFW_WUNLOCK(ch);
+
+ if (rn == NULL) {
+ free(ent_ptr, M_IPFW_TBL);
+ return (EEXIST);
+ }
return (0);
}
int
-ipfw_del_table_entry(struct ip_fw_chain *ch, uint16_t tbl, in_addr_t addr,
- uint8_t mlen)
+ipfw_del_table_entry(struct ip_fw_chain *ch, uint16_t tbl, void *paddr,
+ uint8_t plen, uint8_t mlen, uint8_t type)
{
- struct radix_node_head *rnh;
+ struct radix_node_head *rnh, **rnh_ptr;
struct table_entry *ent;
+ in_addr_t addr;
struct sockaddr_in sa, mask;
+ struct sockaddr *sa_ptr, *mask_ptr;
+ char c;
+
+ if (tbl >= V_fw_tables_max)
+ return (EINVAL);
+
+ switch (type) {
+ case IPFW_TABLE_CIDR:
+ if (plen == sizeof(in_addr_t)) {
+ /* Set 'total' structure length */
+ KEY_LEN(sa) = KEY_LEN_INET;
+ KEY_LEN(mask) = KEY_LEN_INET;
+ mask.sin_addr.s_addr = htonl(mlen ? ~((1 << (32 - mlen)) - 1) : 0);
+ addr = *((in_addr_t *)paddr);
+ sa.sin_addr.s_addr = addr & mask.sin_addr.s_addr;
+ rnh_ptr = &ch->tables[tbl];
+ sa_ptr = (struct sockaddr *)&sa;
+ mask_ptr = (struct sockaddr *)&mask;
+#ifdef INET6
+ } else if (plen == sizeof(struct in6_addr)) {
+ /* IPv6 case */
+ if (mlen > 128)
+ return (EINVAL);
+ struct sockaddr_in6 sa6, mask6;
+ memset(&sa6, 0, sizeof(struct sockaddr_in6));
+ memset(&mask6, 0, sizeof(struct sockaddr_in6));
+ /* Set 'total' structure length */
+ KEY_LEN(sa6) = KEY_LEN_INET6;
+ KEY_LEN(mask6) = KEY_LEN_INET6;
+ ipv6_writemask(&mask6.sin6_addr, mlen);
+ memcpy(&sa6.sin6_addr, paddr, sizeof(struct in6_addr));
+ APPLY_MASK(&sa6.sin6_addr, &mask6.sin6_addr);
+ rnh_ptr = &ch->xtables[tbl];
+ sa_ptr = (struct sockaddr *)&sa6;
+ mask_ptr = (struct sockaddr *)&mask6;
+#endif
+ } else {
+ /* Unknown CIDR type */
+ return (EINVAL);
+ }
+ break;
+
+ case IPFW_TABLE_INTERFACE:
+ /* Check if string is terminated */
+ c = ((char *)paddr)[IF_NAMESIZE - 1];
+ ((char *)paddr)[IF_NAMESIZE - 1] = '\0';
+ if (((mlen = strlen((char *)paddr)) == IF_NAMESIZE - 1) && (c != '\0'))
+ return (EINVAL);
+
+ struct xaddr_iface ifname, ifmask;
+ memset(&ifname, 0, sizeof(ifname));
+
+ /* Set 'total' structure length */
+ KEY_LEN(ifname) = mlen;
+ KEY_LEN(ifmask) = mlen;
+ /* Assume direct match */
+ /* FIXME: Add interface pattern matching */
+#if 0
+ memset(ifmask.ifname, 0xFF, IF_NAMESIZE);
+ mask_ptr = (struct sockaddr *)&ifmask;
+#endif
+ mask_ptr = NULL;
+ memcpy(ifname.ifname, paddr, mlen);
+ /* Set pointers */
+ rnh_ptr = &ch->xtables[tbl];
+ sa_ptr = (struct sockaddr *)&ifname;
+
+ break;
- if (tbl >= IPFW_TABLES_MAX)
+ default:
return (EINVAL);
- rnh = ch->tables[tbl];
- KEY_LEN(sa) = KEY_LEN(mask) = 8;
- mask.sin_addr.s_addr = htonl(mlen ? ~((1 << (32 - mlen)) - 1) : 0);
- sa.sin_addr.s_addr = addr & mask.sin_addr.s_addr;
+ }
+
IPFW_WLOCK(ch);
- ent = (struct table_entry *)rnh->rnh_deladdr(&sa, &mask, rnh);
- if (ent == NULL) {
+ if ((rnh = *rnh_ptr) == NULL) {
IPFW_WUNLOCK(ch);
return (ESRCH);
}
+
+ if (ch->tabletype[tbl] != type) {
+ IPFW_WUNLOCK(ch);
+ return (EINVAL);
+ }
+
+ ent = (struct table_entry *)rnh->rnh_deladdr(sa_ptr, mask_ptr, rnh);
IPFW_WUNLOCK(ch);
+
+ if (ent == NULL)
+ return (ESRCH);
+
free(ent, M_IPFW_TBL);
return (0);
}
@@ -158,15 +399,38 @@ flush_table_entry(struct radix_node *rn, void *arg)
int
ipfw_flush_table(struct ip_fw_chain *ch, uint16_t tbl)
{
- struct radix_node_head *rnh;
-
- IPFW_WLOCK_ASSERT(ch);
+ struct radix_node_head *rnh, *xrnh;
- if (tbl >= IPFW_TABLES_MAX)
+ if (tbl >= V_fw_tables_max)
return (EINVAL);
- rnh = ch->tables[tbl];
- KASSERT(rnh != NULL, ("NULL IPFW table"));
- rnh->rnh_walktree(rnh, flush_table_entry, rnh);
+
+ /*
+ * We free both (IPv4 and extended) radix trees and
+ * clear table type here to permit table to be reused
+ * for different type without module reload
+ */
+
+ IPFW_WLOCK(ch);
+ /* Set IPv4 table pointer to zero */
+ if ((rnh = ch->tables[tbl]) != NULL)
+ ch->tables[tbl] = NULL;
+ /* Set extended table pointer to zero */
+ if ((xrnh = ch->xtables[tbl]) != NULL)
+ ch->xtables[tbl] = NULL;
+ /* Zero table type */
+ ch->tabletype[tbl] = 0;
+ IPFW_WUNLOCK(ch);
+
+ if (rnh != NULL) {
+ rnh->rnh_walktree(rnh, flush_table_entry, rnh);
+ rn_detachhead((void **)&rnh);
+ }
+
+ if (xrnh != NULL) {
+ xrnh->rnh_walktree(xrnh, flush_table_entry, xrnh);
+ rn_detachhead((void **)&xrnh);
+ }
+
return (0);
}
@@ -174,31 +438,24 @@ void
ipfw_destroy_tables(struct ip_fw_chain *ch)
{
uint16_t tbl;
- struct radix_node_head *rnh;
-
- IPFW_WLOCK_ASSERT(ch);
- for (tbl = 0; tbl < IPFW_TABLES_MAX; tbl++) {
+ /* Flush all tables */
+ for (tbl = 0; tbl < V_fw_tables_max; tbl++)
ipfw_flush_table(ch, tbl);
- rnh = ch->tables[tbl];
- rn_detachhead((void **)&rnh);
- }
+
+ /* Free pointers itself */
+ free(ch->tables, M_IPFW);
+ free(ch->xtables, M_IPFW);
+ free(ch->tabletype, M_IPFW);
}
int
ipfw_init_tables(struct ip_fw_chain *ch)
-{
- int i;
- uint16_t j;
-
- for (i = 0; i < IPFW_TABLES_MAX; i++) {
- if (!rn_inithead((void **)&ch->tables[i], KEY_OFS)) {
- for (j = 0; j < i; j++) {
- (void) ipfw_flush_table(ch, j);
- }
- return (ENOMEM);
- }
- }
+{
+ /* Allocate pointers */
+ ch->tables = malloc(V_fw_tables_max * sizeof(void *), M_IPFW, M_WAITOK | M_ZERO);
+ ch->xtables = malloc(V_fw_tables_max * sizeof(void *), M_IPFW, M_WAITOK | M_ZERO);
+ ch->tabletype = malloc(V_fw_tables_max * sizeof(uint8_t), M_IPFW, M_WAITOK | M_ZERO);
return (0);
}
@@ -210,10 +467,11 @@ ipfw_lookup_table(struct ip_fw_chain *ch, uint16_t tbl, in_addr_t addr,
struct table_entry *ent;
struct sockaddr_in sa;
- if (tbl >= IPFW_TABLES_MAX)
+ if (tbl >= V_fw_tables_max)
return (0);
- rnh = ch->tables[tbl];
- KEY_LEN(sa) = 8;
+ if ((rnh = ch->tables[tbl]) == NULL)
+ return (0);
+ KEY_LEN(sa) = KEY_LEN_INET;
sa.sin_addr.s_addr = addr;
ent = (struct table_entry *)(rnh->rnh_lookup(&sa, NULL, rnh));
if (ent != NULL) {
@@ -223,6 +481,45 @@ ipfw_lookup_table(struct ip_fw_chain *ch, uint16_t tbl, in_addr_t addr,
return (0);
}
+int
+ipfw_lookup_table_extended(struct ip_fw_chain *ch, uint16_t tbl, void *paddr,
+ uint32_t *val, int type)
+{
+ struct radix_node_head *rnh;
+ struct table_xentry *xent;
+ struct sockaddr_in6 sa6;
+ struct xaddr_iface iface;
+
+ if (tbl >= V_fw_tables_max)
+ return (0);
+ if ((rnh = ch->xtables[tbl]) == NULL)
+ return (0);
+
+ switch (type) {
+ case IPFW_TABLE_CIDR:
+ KEY_LEN(sa6) = KEY_LEN_INET6;
+ memcpy(&sa6.sin6_addr, paddr, sizeof(struct in6_addr));
+ xent = (struct table_xentry *)(rnh->rnh_lookup(&sa6, NULL, rnh));
+ break;
+
+ case IPFW_TABLE_INTERFACE:
+ KEY_LEN(iface) = strlcpy(iface.ifname, (char *)paddr, IF_NAMESIZE);
+ /* Assume direct match */
+ /* FIXME: Add interface pattern matching */
+ xent = (struct table_xentry *)(rnh->rnh_lookup(&iface, NULL, rnh));
+ break;
+
+ default:
+ return (0);
+ }
+
+ if (xent != NULL) {
+ *val = xent->value;
+ return (1);
+ }
+ return (0);
+}
+
static int
count_table_entry(struct radix_node *rn, void *arg)
{
@@ -237,10 +534,11 @@ ipfw_count_table(struct ip_fw_chain *ch, uint32_t tbl, uint32_t *cnt)
{
struct radix_node_head *rnh;
- if (tbl >= IPFW_TABLES_MAX)
+ if (tbl >= V_fw_tables_max)
return (EINVAL);
- rnh = ch->tables[tbl];
*cnt = 0;
+ if ((rnh = ch->tables[tbl]) == NULL)
+ return (0);
rnh->rnh_walktree(rnh, count_table_entry, cnt);
return (0);
}
@@ -271,11 +569,124 @@ ipfw_dump_table(struct ip_fw_chain *ch, ipfw_table *tbl)
{
struct radix_node_head *rnh;
- if (tbl->tbl >= IPFW_TABLES_MAX)
+ if (tbl->tbl >= V_fw_tables_max)
return (EINVAL);
- rnh = ch->tables[tbl->tbl];
tbl->cnt = 0;
+ if ((rnh = ch->tables[tbl->tbl]) == NULL)
+ return (0);
rnh->rnh_walktree(rnh, dump_table_entry, tbl);
return (0);
}
+
+static int
+count_table_xentry(struct radix_node *rn, void *arg)
+{
+ uint32_t * const cnt = arg;
+
+ (*cnt) += sizeof(ipfw_table_xentry);
+ return (0);
+}
+
+int
+ipfw_count_xtable(struct ip_fw_chain *ch, uint32_t tbl, uint32_t *cnt)
+{
+ struct radix_node_head *rnh;
+
+ if (tbl >= V_fw_tables_max)
+ return (EINVAL);
+ *cnt = 0;
+ if ((rnh = ch->tables[tbl]) != NULL)
+ rnh->rnh_walktree(rnh, count_table_xentry, cnt);
+ if ((rnh = ch->xtables[tbl]) != NULL)
+ rnh->rnh_walktree(rnh, count_table_xentry, cnt);
+ /* Return zero if table is empty */
+ if (*cnt > 0)
+ (*cnt) += sizeof(ipfw_xtable);
+ return (0);
+}
+
+
+static int
+dump_table_xentry_base(struct radix_node *rn, void *arg)
+{
+ struct table_entry * const n = (struct table_entry *)rn;
+ ipfw_xtable * const tbl = arg;
+ ipfw_table_xentry *xent;
+
+ /* Out of memory, returning */
+ if (tbl->cnt == tbl->size)
+ return (1);
+ xent = &tbl->xent[tbl->cnt];
+ xent->len = sizeof(ipfw_table_xentry);
+ xent->tbl = tbl->tbl;
+ if (in_nullhost(n->mask.sin_addr))
+ xent->masklen = 0;
+ else
+ xent->masklen = 33 - ffs(ntohl(n->mask.sin_addr.s_addr));
+ /* Save IPv4 address as deprecated IPv6 compatible */
+ xent->k.addr6.s6_addr32[3] = n->addr.sin_addr.s_addr;
+ xent->value = n->value;
+ tbl->cnt++;
+ return (0);
+}
+
+static int
+dump_table_xentry_extended(struct radix_node *rn, void *arg)
+{
+ struct table_xentry * const n = (struct table_xentry *)rn;
+ ipfw_xtable * const tbl = arg;
+ ipfw_table_xentry *xent;
+#ifdef INET6
+ int i;
+ uint32_t *v;
+#endif
+ /* Out of memory, returning */
+ if (tbl->cnt == tbl->size)
+ return (1);
+ xent = &tbl->xent[tbl->cnt];
+ xent->len = sizeof(ipfw_table_xentry);
+ xent->tbl = tbl->tbl;
+
+ switch (tbl->type) {
+#ifdef INET6
+ case IPFW_TABLE_CIDR:
+ /* Count IPv6 mask */
+ v = (uint32_t *)&n->m.mask6.sin6_addr;
+ for (i = 0; i < sizeof(struct in6_addr) / 4; i++, v++)
+ xent->masklen += bitcount32(*v);
+ memcpy(&xent->k, &n->a.addr6.sin6_addr, sizeof(struct in6_addr));
+ break;
+#endif
+ case IPFW_TABLE_INTERFACE:
+ /* Assume exact mask */
+ xent->masklen = 8 * IF_NAMESIZE;
+ memcpy(&xent->k, &n->a.iface.ifname, IF_NAMESIZE);
+ break;
+
+ default:
+ /* unknown, skip entry */
+ return (0);
+ }
+
+ xent->value = n->value;
+ tbl->cnt++;
+ return (0);
+}
+
+int
+ipfw_dump_xtable(struct ip_fw_chain *ch, ipfw_xtable *tbl)
+{
+ struct radix_node_head *rnh;
+
+ if (tbl->tbl >= V_fw_tables_max)
+ return (EINVAL);
+ tbl->cnt = 0;
+ tbl->type = ch->tabletype[tbl->tbl];
+ if ((rnh = ch->tables[tbl->tbl]) != NULL)
+ rnh->rnh_walktree(rnh, dump_table_xentry_base, tbl);
+ if ((rnh = ch->xtables[tbl->tbl]) != NULL)
+ rnh->rnh_walktree(rnh, dump_table_xentry_extended, tbl);
+ return (0);
+}
+
/* end of file */
OpenPOWER on IntegriCloud