Suppord showing named tables in ipfw(8) rule listing.

Kernel changes:
* change base TLV header to be u64 (so size can be u32).
* Introduce ipfw_obj_ctlv generc container TLV.
* Add IP_FW_XGET opcode which is now used for atomic configuration
  retrieval. One can specify needed configuration pieces to retrieve
  via flags field. Currently supported are
  IPFW_CFG_GET_STATIC (static rules) and
  IPFW_CFG_GET_STATES (dynamic states).
  Other configuration pieces (tables, pipes, etc..) support is planned.

Userland changes:
* Switch ipfw(8) to use new IP_FW_XGET for rule listing.
* Split rule listing code get and show pieces.
* Make several steps forward towards libipfw:
  permit printing states and rules(paritally) to supplied buffer.
  do not die on malloc/kernel failure inside given printing functions.
  stop assuming cmdline_opts is global symbol.
This commit is contained in:
Alexander V. Chernikov 2014-06-28 23:20:24 +00:00
parent 2d99a3497d
commit 563b5ab132
11 changed files with 890 additions and 281 deletions

View File

@ -137,15 +137,15 @@ altq_qid_to_name(u_int32_t qid)
}
void
print_altq_cmd(ipfw_insn_altq *altqptr)
print_altq_cmd(struct buf_pr *bp, ipfw_insn_altq *altqptr)
{
if (altqptr) {
const char *qname;
qname = altq_qid_to_name(altqptr->qid);
if (qname == NULL)
printf(" altq ?<%u>", altqptr->qid);
bprintf(bp, " altq ?<%u>", altqptr->qid);
else
printf(" altq %s", qname);
bprintf(bp, " altq %s", qname);
}
}

View File

@ -174,48 +174,44 @@ print_header(struct ipfw_flow_id *id)
}
static void
list_flow(struct dn_flow *ni, int *print)
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;
if (*print) {
print_header(&ni->fid);
*print = 0;
}
pe = getprotobynumber(id->proto);
/* XXX: Should check for IPv4 flows */
printf("%3u%c", (ni->oid.id) & 0xff,
bprintf(bp, "%3u%c", (ni->oid.id) & 0xff,
id->extra ? '*' : ' ');
if (!IS_IP6_FLOW_ID(id)) {
if (pe)
printf("%-4s ", pe->p_name);
bprintf(bp, "%-4s ", pe->p_name);
else
printf("%4u ", id->proto);
bprintf(bp, "%4u ", id->proto);
ina.s_addr = htonl(id->src_ip);
printf("%15s/%-5d ",
bprintf(bp, "%15s/%-5d ",
inet_ntoa(ina), id->src_port);
ina.s_addr = htonl(id->dst_ip);
printf("%15s/%-5d ",
bprintf(bp, "%15s/%-5d ",
inet_ntoa(ina), id->dst_port);
} else {
/* Print IPv6 flows */
if (pe != NULL)
printf("%9s ", pe->p_name);
bprintf(bp, "%9s ", pe->p_name);
else
printf("%9u ", id->proto);
printf("%7d %39s/%-5d ", id->flow_id6,
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);
printf(" %39s/%-5d ",
bprintf(bp, " %39s/%-5d ",
inet_ntop(AF_INET6, &(id->dst_ip6), buff, sizeof(buff)),
id->dst_port);
}
pr_u64(&ni->tot_pkts, 4);
pr_u64(&ni->tot_bytes, 8);
printf("%2u %4u %3u\n",
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);
}
@ -303,8 +299,10 @@ 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);
@ -346,7 +344,12 @@ list_pipes(struct dn_id *oid, struct dn_id *end)
break;
case DN_FLOW:
list_flow((struct dn_flow *)oid, &toPrint);
if (toPrint != 0) {
print_header(&((struct dn_flow *)oid)->fid);
toPrint = 0;
}
list_flow(&bp, (struct dn_flow *)oid);
printf("%s\n", bp.buf);
break;
case DN_LINK: {
@ -384,6 +387,8 @@ list_pipes(struct dn_id *oid, struct dn_id *end)
}
flush_buf(buf); // XXX does it really go here ?
}
bp_free(&bp);
}
/*

File diff suppressed because it is too large Load Diff

View File

@ -213,7 +213,19 @@ enum tokens {
#define NEED(_p, msg) {if (!_p) errx(EX_USAGE, msg);}
#define NEED1(msg) {if (!(*av)) errx(EX_USAGE, msg);}
int pr_u64(uint64_t *pd, int width);
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, uint64_t *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, char *format, ...);
/* memory allocation support */
void *safe_calloc(size_t number, size_t size);
@ -274,7 +286,7 @@ void ipfw_list(int ac, char *av[], int show_counters);
/* altq.c */
void altq_set_enabled(int enabled);
u_int32_t altq_name_to_qid(const char *name);
void print_altq_cmd(struct _ipfw_insn_altq *altqptr);
void print_altq_cmd(struct buf_pr *bp, struct _ipfw_insn_altq *altqptr);
#else
#define NO_ALTQ
#endif
@ -298,3 +310,8 @@ void fill_flow6(struct _ipfw_insn_u32 *cmd, char *av, int cblen);
void fill_unreach6_code(u_short *codep, char *str);
void fill_icmp6types(struct _ipfw_insn_icmp6 *cmd, char *av, int cblen);
int fill_ext6hdr(struct _ipfw_insn *cmd, char *av);
/* tables.c */
struct _ipfw_obj_ctlv;
char *table_search_ctlv(struct _ipfw_obj_ctlv *ctlv, uint16_t idx);

View File

@ -305,7 +305,7 @@ static void
table_fill_ntlv(ipfw_obj_ntlv *ntlv, char *name, uint16_t uidx)
{
ntlv->head.type = IPFW_TLV_NAME;
ntlv->head.type = IPFW_TLV_TBL_NAME;
ntlv->head.length = sizeof(ipfw_obj_ntlv);
ntlv->idx = uidx;
strlcpy(ntlv->name, name, sizeof(ntlv->name));
@ -593,3 +593,43 @@ table_show_list(ipfw_obj_header *oh, int need_header)
}
}
int
compare_ntlv(const void *k, const void *v)
{
ipfw_obj_ntlv *ntlv;
uint16_t key;
key = *((uint16_t *)k);
ntlv = (ipfw_obj_ntlv *)v;
if (key < ntlv->idx)
return (-1);
else if (key > ntlv->idx)
return (1);
return (0);
}
/*
* Finds table name in @ctlv by @idx.
* Uses the following facts:
* 1) All TLVs are the same size
* 2) Kernel implementation provides already sorted list.
*
* Returns table name or NULL.
*/
char *
table_search_ctlv(ipfw_obj_ctlv *ctlv, uint16_t idx)
{
ipfw_obj_ntlv *ntlv;
ntlv = bsearch(&idx, (ctlv + 1), ctlv->count, ctlv->objsize,
compare_ntlv);
if (ntlv != 0)
return (ntlv->name);
return (NULL);
}

View File

@ -87,6 +87,7 @@ typedef struct _ip_fw3_opheader {
#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 */
/*
* Usage guidelines:
@ -681,18 +682,30 @@ typedef struct _ipfw_xtable {
typedef struct _ipfw_obj_tlv {
uint16_t type; /* TLV type */
uint16_t length; /* Total length, aligned to u32 */
uint16_t flags; /* unused */
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_STATE_LIST 4
#define IPFW_TLV_NAME 1
/* Object name TLV */
typedef struct _ipfw_obj_ntlv {
ipfw_obj_tlv head; /* TLV header */
uint16_t idx; /* Name index */
uint16_t spare; /* unused */
char name[64]; /* Null-terminated name */
ipfw_obj_tlv head; /* TLV header */
uint16_t idx; /* Name index */
uint16_t spare0; /* unused */
uint32_t spare1; /* unused */
char name[64]; /* Null-terminated name */
} ipfw_obj_ntlv;
/* Containter TLVs */
typedef struct _ipfw_obj_ctlv {
ipfw_obj_tlv head; /* TLV header */
uint32_t count; /* Number of sub-TLVs */
uint32_t objsize; /* Single object size */
} ipfw_obj_ctlv;
typedef struct _ipfw_xtable_info {
uint8_t type; /* table type (cidr,iface,..) */
uint8_t ftype; /* table value format type */
@ -725,4 +738,15 @@ typedef struct _ipfw_obj_lheader {
uint32_t objsize; /* Size of one object */
} ipfw_obj_lheader;
#define IPFW_CFG_GET_STATIC 1
#define IPFW_CFG_GET_STATES 2
typedef struct _ipfw_cfg_lheader {
ip_fw3_opheader opheader; /* IP_FW3 opcode */
uint32_t set_mask; /* disabled set mask */
uint32_t flags; /* Request flags */
uint32_t size; /* neded buffer size */
uint32_t start_rule;
uint32_t end_rule;
} ipfw_cfg_lheader;
#endif /* _IPFW2_H */

View File

@ -1454,6 +1454,74 @@ ipfw_dyn_len(void)
(DYN_COUNT * sizeof(ipfw_dyn_rule));
}
static void
export_dyn_rule(ipfw_dyn_rule *src, ipfw_dyn_rule *dst)
{
memcpy(dst, src, sizeof(*src));
memcpy(&(dst->rule), &(src->rule->rulenum), sizeof(src->rule->rulenum));
/*
* store set number into high word of
* dst->rule pointer.
*/
memcpy((char *)&dst->rule + sizeof(src->rule->rulenum),
&(src->rule->set), sizeof(src->rule->set));
/*
* store a non-null value in "next".
* The userland code will interpret a
* NULL here as a marker
* for the last dynamic rule.
*/
memcpy(&dst->next, &dst, sizeof(dst));
dst->expire =
TIME_LEQ(dst->expire, time_uptime) ? 0 : dst->expire - time_uptime;
}
/*
* Fills int buffer given by @sd with dynamic states.
*
* Returns 0 on success.
*/
int
ipfw_dump_states(struct ip_fw_chain *chain, struct sockopt_data *sd)
{
ipfw_dyn_rule *p, *dst, *last = NULL;
ipfw_obj_ctlv *ctlv;
int i;
if (V_ipfw_dyn_v == NULL)
return (0);
IPFW_UH_RLOCK_ASSERT(chain);
ctlv = (ipfw_obj_ctlv *)ipfw_get_sopt_space(sd, sizeof(*ctlv));
if (ctlv == NULL)
return (ENOMEM);
ctlv->head.type = IPFW_TLV_TBLNAME_LIST;
ctlv->objsize = sizeof(ipfw_dyn_rule);
for (i = 0 ; i < V_curr_dyn_buckets; i++) {
IPFW_BUCK_LOCK(i);
for (p = V_ipfw_dyn_v[i].head ; p != NULL; p = p->next) {
dst = (ipfw_dyn_rule *)ipfw_get_sopt_space(sd,
sizeof(*dst));
if (dst == NULL) {
IPFW_BUCK_UNLOCK(i);
return (ENOMEM);
}
export_dyn_rule(p, dst);
last = dst;
}
IPFW_BUCK_UNLOCK(i);
}
if (last != NULL) /* mark last dynamic rule */
bzero(&last->next, sizeof(last));
return (0);
}
/*
* Fill given buffer with dynamic states.
* IPFW_UH_RLOCK has to be held while calling.
@ -1477,28 +1545,9 @@ ipfw_get_dynamic(struct ip_fw_chain *chain, char **pbp, const char *ep)
if (bp + sizeof *p <= ep) {
ipfw_dyn_rule *dst =
(ipfw_dyn_rule *)bp;
bcopy(p, dst, sizeof *p);
bcopy(&(p->rule->rulenum), &(dst->rule),
sizeof(p->rule->rulenum));
/*
* store set number into high word of
* dst->rule pointer.
*/
bcopy(&(p->rule->set),
(char *)&dst->rule +
sizeof(p->rule->rulenum),
sizeof(p->rule->set));
/*
* store a non-null value in "next".
* The userland code will interpret a
* NULL here as a marker
* for the last dynamic rule.
*/
bcopy(&dst, &dst->next, sizeof(dst));
export_dyn_rule(p, dst);
last = dst;
dst->expire =
TIME_LEQ(dst->expire, time_uptime) ?
0 : dst->expire - time_uptime ;
bp += sizeof(ipfw_dyn_rule);
}
}

View File

@ -176,6 +176,7 @@ enum { /* result for matching dynamic rules */
* Eventually we may implement it with a callback on the function.
*/
struct ip_fw_chain;
struct sockopt_data;
void ipfw_expire_dyn_rules(struct ip_fw_chain *, struct ip_fw *, int);
void ipfw_dyn_unlock(ipfw_dyn_rule *q);
@ -188,6 +189,7 @@ ipfw_dyn_rule *ipfw_lookup_dyn_rule(struct ipfw_flow_id *pkt,
int *match_direction, struct tcphdr *tcp);
void ipfw_remove_dyn_children(struct ip_fw *rule);
void ipfw_get_dynamic(struct ip_fw_chain *chain, char **bp, const char *ep);
int ipfw_dump_states(struct ip_fw_chain *chain, struct sockopt_data *sd);
void ipfw_dyn_init(struct ip_fw_chain *); /* per-vnet initialization */
void ipfw_dyn_uninit(int); /* per-vnet deinitialization */

View File

@ -989,6 +989,178 @@ ipfw_getrules(struct ip_fw_chain *chain, void *buf, size_t space)
}
struct dump_args {
uint32_t b; /* start rule */
uint32_t e; /* end rule */
uint32_t rcount; /* number of rules */
uint32_t rsize; /* rules size */
uint32_t tcount; /* number of tables */
};
/*
* Dumps static rules with table TLVs in buffer @sd.
*
* Returns 0 on success.
*/
static int
dump_static_rules(struct ip_fw_chain *chain, struct dump_args *da,
uint32_t *bmask, struct sockopt_data *sd)
{
int error;
int i, l;
uint32_t tcount;
ipfw_obj_ctlv *ctlv;
struct ip_fw *dst, *rule;
time_t boot_seconds;
/* Dump table names first (if any) */
if (da->tcount > 0) {
/* Header first */
ctlv = (ipfw_obj_ctlv *)ipfw_get_sopt_space(sd, sizeof(*ctlv));
if (ctlv == NULL)
return (ENOMEM);
ctlv->head.type = IPFW_TLV_TBLNAME_LIST;
ctlv->head.length = da->tcount * sizeof(ipfw_obj_ntlv) +
sizeof(*ctlv);
ctlv->count = da->tcount;
ctlv->objsize = sizeof(ipfw_obj_ntlv);
}
i = 0;
tcount = da->tcount;
while (tcount > 0) {
if ((bmask[i / 32] & (1 << (i % 32))) == 0) {
i++;
continue;
}
if ((error = ipfw_export_table_ntlv(chain, i, sd)) != 0)
return (error);
i++;
tcount--;
}
/* Dump rules */
ctlv = (ipfw_obj_ctlv *)ipfw_get_sopt_space(sd, sizeof(*ctlv));
if (ctlv == NULL)
return (ENOMEM);
ctlv->head.type = IPFW_TLV_RULE_LIST;
ctlv->head.length = da->rsize + sizeof(*ctlv);
ctlv->count = da->rcount;
boot_seconds = boottime.tv_sec;
for (i = da->b; i < da->e; i++) {
rule = chain->map[i];
l = RULESIZE(rule);
/* XXX: align to u64 */
dst = (struct ip_fw *)ipfw_get_sopt_space(sd, l);
if (rule == NULL)
return (ENOMEM);
bcopy(rule, dst, l);
if (dst->timestamp != 0)
dst->timestamp += boot_seconds;
}
return (0);
}
/*
* Dumps requested objects data
* Data layout (version 0)(current):
* Request: [ ipfw_cfg_lheader ] + IPFW_CFG_GET_* flags
* size = ipfw_cfg_lheader.size
* Reply: [ ipfw_rules_lheader
* [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional)
* [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) ip_fw x N ] (optional)
* [ ipfw_obj_ctlv(IPFW_TLV_STATE_LIST) ipfw_dyn_rule x N ] (optional)
* ]
* * NOTE IPFW_TLV_STATE_LIST has the single valid field: objsize.
* The rest (size, count) are set to zero and needs to be ignored.
*
* Returns 0 on success.
*/
static int
dump_config(struct ip_fw_chain *chain, struct sockopt_data *sd)
{
ipfw_cfg_lheader *hdr;
struct ip_fw *rule;
uint32_t sz;
int error, i;
struct dump_args da;
uint32_t *bmask;
hdr = (ipfw_cfg_lheader *)ipfw_get_sopt_header(sd, sizeof(*hdr));
if (hdr == NULL)
return (EINVAL);
/* Allocate needed state */
error = 0;
bmask = NULL;
if (hdr->flags & IPFW_CFG_GET_STATIC)
bmask = malloc(IPFW_TABLES_MAX / 8, M_TEMP, M_WAITOK | M_ZERO);
IPFW_UH_RLOCK(chain);
/*
* STAGE 1: Determine size/count for objects in range.
* Prepare used tables bitmask.
*/
sz = 0;
memset(&da, 0, sizeof(da));
da.b = 0;
da.e = chain->n_rules;
if (hdr->flags & IPFW_CFG_GET_STATIC) {
for (i = da.b; i < da.e; i++) {
rule = chain->map[i];
da.rsize += RULESIZE(rule);
da.rcount++;
da.tcount += ipfw_mark_table_kidx(chain, rule, bmask);
}
if (da.tcount > 0)
sz += da.tcount * sizeof(ipfw_obj_ntlv) +
sizeof(ipfw_obj_ctlv);
sz += da.rsize + sizeof(ipfw_obj_ctlv);
}
if (hdr->flags & IPFW_CFG_GET_STATES) {
sz += ipfw_dyn_len();
}
/* Fill header anyway */
hdr->size = sz;
hdr->set_mask = V_set_disable;
if (sd->valsize < sz) {
IPFW_UH_RUNLOCK(chain);
return (ENOMEM);
}
/* STAGE2: Store actual data */
if (hdr->flags & IPFW_CFG_GET_STATIC) {
error = dump_static_rules(chain, &da, bmask, sd);
if (error != 0) {
IPFW_UH_RUNLOCK(chain);
return (error);
}
}
if (hdr->flags & IPFW_CFG_GET_STATES)
error = ipfw_dump_states(chain, sd);
IPFW_UH_RUNLOCK(chain);
if (bmask != NULL)
free(bmask, M_TEMP);
return (error);
}
#define IP_FW3_OPLENGTH(x) ((x)->sopt_valsize - sizeof(ip_fw3_opheader))
#define IP_FW3_OPTBUF 4096 /* page-size */
/**
@ -1101,6 +1273,10 @@ ipfw_ctl(struct sockopt *sopt)
}
break;
case IP_FW_XGET: /* IP_FW3 */
error = dump_config(chain, &sdata);
break;
case IP_FW_FLUSH:
/* locking is done within del_entry() */
error = del_entry(chain, 0); /* special case, rule=0, cmd=0 means all */

View File

@ -27,16 +27,15 @@
__FBSDID("$FreeBSD$");
/*
* Lookup table support for ipfw
* Lookup table support for ipfw.
*
* Lookup tables are implemented (at the moment) using the radix
* tree used for routing tables. Tables store key-value entries, where
* keys are network prefixes (addr/masklen), and values are integers.
* As a degenerate case we can interpret keys as 32-bit integers
* (with a /32 mask).
* This file containg handlers for all generic tables operations:
* add/del/flush entries, list/dump tables etc..
*
* The table is protected by the IPFW lock even for manipulation coming
* from userland, because operations are typically fast.
* Table data modification is protected by both UH and runtimg lock
* while reading configuration/data is protected by UH lock.
*
* Lookup algorithms for all table types are located in ip_fw_table_algo.c
*/
#include "opt_ipfw.h"
@ -901,6 +900,31 @@ objheader_to_ti(struct _ipfw_obj_header *oh, struct tid_info *ti)
ti->tlen = oh->ntlv.head.length;
}
int
ipfw_export_table_ntlv(struct ip_fw_chain *ch, uint16_t kidx,
struct sockopt_data *sd)
{
struct namedobj_instance *ni;
struct named_object *no;
ipfw_obj_ntlv *ntlv;
ni = CHAIN_TO_NI(ch);
no = ipfw_objhash_lookup_idx(ni, 0, kidx);
KASSERT(no != NULL, ("invalid table kidx passed"));
ntlv = (ipfw_obj_ntlv *)ipfw_get_sopt_space(sd, sizeof(*ntlv));
if (ntlv == NULL)
return (ENOMEM);
ntlv->head.type = IPFW_TLV_TBL_NAME;
ntlv->head.length = sizeof(*ntlv);
ntlv->idx = no->kidx;
strlcpy(ntlv->name, no->name, sizeof(ntlv->name));
return (0);
}
static void
export_table_info(struct table_config *tc, ipfw_xtable_info *i)
{
@ -1253,7 +1277,7 @@ find_name_tlv(void *tlvs, int len, uint16_t uidx)
for (; pa < pe; pa += l) {
ntlv = (ipfw_obj_ntlv *)pa;
l = ntlv->head.length;
if (ntlv->head.type != IPFW_TLV_NAME)
if (ntlv->head.type != IPFW_TLV_TBL_NAME)
continue;
if (ntlv->idx != uidx)
continue;
@ -1515,6 +1539,40 @@ ipfw_rewrite_table_kidx(struct ip_fw_chain *chain, struct ip_fw *rule)
return (0);
}
/*
* Sets every table kidx in @bmask which is used in rule @rule.
*
* Returns number of newly-referenced tables.
*/
int
ipfw_mark_table_kidx(struct ip_fw_chain *chain, struct ip_fw *rule,
uint32_t *bmask)
{
int cmdlen, l, count;
ipfw_insn *cmd;
uint16_t kidx;
uint8_t type;
l = rule->cmd_len;
cmd = rule->cmd;
cmdlen = 0;
count = 0;
for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
if (classify_table_opcode(cmd, &kidx, &type) != 0)
continue;
if ((bmask[kidx / 32] & (1 << (kidx % 32))) == 0)
count++;
bmask[kidx / 32] |= 1 << (kidx % 32);
}
return (count);
}
/*
* Checks is opcode is referencing table of appropriate type.

View File

@ -111,6 +111,10 @@ int ipfw_del_table_entry(struct ip_fw_chain *ch, struct tid_info *ti,
int ipfw_rewrite_table_uidx(struct ip_fw_chain *chain,
struct rule_check_info *ci);
int ipfw_rewrite_table_kidx(struct ip_fw_chain *chain, struct ip_fw *rule);
int ipfw_mark_table_kidx(struct ip_fw_chain *chain, struct ip_fw *rule,
uint32_t *bmask);
int ipfw_export_table_ntlv(struct ip_fw_chain *ch, uint16_t kidx,
struct sockopt_data *sd);
void ipfw_unbind_table_rule(struct ip_fw_chain *chain, struct ip_fw *rule);
void ipfw_unbind_table_list(struct ip_fw_chain *chain, struct ip_fw *head);