diff options
Diffstat (limited to 'sys')
-rw-r--r-- | sys/netinet/ip_fw.h | 43 | ||||
-rw-r--r-- | sys/netinet/ipfw/ip_fw2.c | 57 | ||||
-rw-r--r-- | sys/netinet/ipfw/ip_fw_private.h | 21 | ||||
-rw-r--r-- | sys/netinet/ipfw/ip_fw_sockopt.c | 113 | ||||
-rw-r--r-- | sys/netinet/ipfw/ip_fw_table.c | 529 |
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 */ |