* Add new IP_FW_XADD opcode which permits to

a) specify table ids as names
  b) add multiple rules at once.
Partially convert current code for atomic addition of multiple rules.
This commit is contained in:
Alexander V. Chernikov 2014-06-29 22:35:47 +00:00
parent 2aa75134b7
commit 6c2997ffec
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/projects/ipfw/; revision=268021
7 changed files with 594 additions and 87 deletions

View File

@ -2493,6 +2493,56 @@ lookup_host (char *host, struct in_addr *ipaddr)
return(0); return(0);
} }
struct tidx {
ipfw_obj_ntlv *idx;
uint32_t count;
uint32_t size;
uint16_t counter;
};
static uint16_t
pack_table(struct tidx *tstate, char *name, uint32_t set)
{
char *p;
int i;
ipfw_obj_ntlv *ntlv;
if ((p = strchr(name, ')')) == NULL)
return (0);
*p = '\0';
if (table_check_name(name) != 0)
return (0);
for (i = 0; i < tstate->count; i++) {
if (strcmp(tstate->idx[i].name, name) != 0)
continue;
if (tstate->idx[i].set != set)
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 = IPFW_TLV_TBL_NAME;
ntlv->head.length = sizeof(ipfw_obj_ntlv);
ntlv->set = set;
ntlv->idx = ++tstate->counter;
tstate->count++;
return (ntlv->idx);
}
/* /*
* fills the addr and mask fields in the instruction as appropriate from av. * fills the addr and mask fields in the instruction as appropriate from av.
* Update length as appropriate. * Update length as appropriate.
@ -2505,10 +2555,12 @@ lookup_host (char *host, struct in_addr *ipaddr)
* We can have multiple comma-separated address/mask entries. * We can have multiple comma-separated address/mask entries.
*/ */
static void static void
fill_ip(ipfw_insn_ip *cmd, char *av, int cblen) fill_ip(ipfw_insn_ip *cmd, char *av, int cblen, struct tidx *tstate)
{ {
int len = 0; int len = 0;
uint32_t *d = ((ipfw_insn_u32 *)cmd)->d; uint32_t *d = ((ipfw_insn_u32 *)cmd)->d;
uint16_t uidx;
char *p;
cmd->o.len &= ~F_LEN_MASK; /* zero len */ cmd->o.len &= ~F_LEN_MASK; /* zero len */
@ -2521,12 +2573,15 @@ fill_ip(ipfw_insn_ip *cmd, char *av, int cblen)
} }
if (strncmp(av, "table(", 6) == 0) { if (strncmp(av, "table(", 6) == 0) {
char *p = strchr(av + 6, ','); p = strchr(av + 6, ',');
if (p) if (p)
*p++ = '\0'; *p++ = '\0';
if ((uidx = pack_table(tstate, av + 6, 0)) == 0)
errx(EX_DATAERR, "Invalid table name: %s", av + 6);
cmd->o.opcode = O_IP_DST_LOOKUP; cmd->o.opcode = O_IP_DST_LOOKUP;
cmd->o.arg1 = strtoul(av + 6, NULL, 0); cmd->o.arg1 = uidx;
if (p) { if (p) {
cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32); cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32);
d[0] = strtoul(p, NULL, 0); d[0] = strtoul(p, NULL, 0);
@ -2805,8 +2860,10 @@ ipfw_delete(char *av[])
* patterns which match interfaces. * patterns which match interfaces.
*/ */
static void static void
fill_iface(ipfw_insn_if *cmd, char *arg, int cblen) fill_iface(ipfw_insn_if *cmd, char *arg, int cblen, struct tidx *tstate)
{ {
uint16_t uidx;
cmd->name[0] = '\0'; cmd->name[0] = '\0';
cmd->o.len |= F_INSN_SIZE(ipfw_insn_if); cmd->o.len |= F_INSN_SIZE(ipfw_insn_if);
@ -2819,8 +2876,11 @@ fill_iface(ipfw_insn_if *cmd, char *arg, int cblen)
char *p = strchr(arg + 6, ','); char *p = strchr(arg + 6, ',');
if (p) if (p)
*p++ = '\0'; *p++ = '\0';
if ((uidx = pack_table(tstate, arg + 6, 0)) == 0)
errx(EX_DATAERR, "Invalid table name: %s", arg + 6);
cmd->name[0] = '\1'; /* Special value indicating table */ cmd->name[0] = '\1'; /* Special value indicating table */
cmd->p.glob = strtoul(arg + 6, NULL, 0); cmd->p.glob = uidx;
} else if (!isdigit(*arg)) { } else if (!isdigit(*arg)) {
strlcpy(cmd->name, arg, sizeof(cmd->name)); strlcpy(cmd->name, arg, sizeof(cmd->name));
cmd->p.glob = strpbrk(arg, "*?[") != NULL ? 1 : 0; cmd->p.glob = strpbrk(arg, "*?[") != NULL ? 1 : 0;
@ -3034,9 +3094,9 @@ add_proto_compat(ipfw_insn *cmd, char *av, u_char *protop)
} }
static ipfw_insn * static ipfw_insn *
add_srcip(ipfw_insn *cmd, char *av, int cblen) add_srcip(ipfw_insn *cmd, char *av, int cblen, struct tidx *tstate)
{ {
fill_ip((ipfw_insn_ip *)cmd, av, cblen); fill_ip((ipfw_insn_ip *)cmd, av, cblen, tstate);
if (cmd->opcode == O_IP_DST_SET) /* set */ if (cmd->opcode == O_IP_DST_SET) /* set */
cmd->opcode = O_IP_SRC_SET; cmd->opcode = O_IP_SRC_SET;
else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */ else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */
@ -3051,9 +3111,9 @@ add_srcip(ipfw_insn *cmd, char *av, int cblen)
} }
static ipfw_insn * static ipfw_insn *
add_dstip(ipfw_insn *cmd, char *av, int cblen) add_dstip(ipfw_insn *cmd, char *av, int cblen, struct tidx *tstate)
{ {
fill_ip((ipfw_insn_ip *)cmd, av, cblen); fill_ip((ipfw_insn_ip *)cmd, av, cblen, tstate);
if (cmd->opcode == O_IP_DST_SET) /* set */ if (cmd->opcode == O_IP_DST_SET) /* set */
; ;
else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */ else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */
@ -3082,7 +3142,7 @@ add_ports(ipfw_insn *cmd, char *av, u_char proto, int opcode, int cblen)
} }
static ipfw_insn * static ipfw_insn *
add_src(ipfw_insn *cmd, char *av, u_char proto, int cblen) add_src(ipfw_insn *cmd, char *av, u_char proto, int cblen, struct tidx *tstate)
{ {
struct in6_addr a; struct in6_addr a;
char *host, *ch, buf[INET6_ADDRSTRLEN]; char *host, *ch, buf[INET6_ADDRSTRLEN];
@ -3105,7 +3165,7 @@ add_src(ipfw_insn *cmd, char *av, u_char proto, int cblen)
/* XXX: should check for IPv4, not !IPv6 */ /* XXX: should check for IPv4, not !IPv6 */
if (ret == NULL && (proto == IPPROTO_IP || strcmp(av, "me") == 0 || if (ret == NULL && (proto == IPPROTO_IP || strcmp(av, "me") == 0 ||
inet_pton(AF_INET6, host, &a) != 1)) inet_pton(AF_INET6, host, &a) != 1))
ret = add_srcip(cmd, av, cblen); ret = add_srcip(cmd, av, cblen, tstate);
if (ret == NULL && strcmp(av, "any") != 0) if (ret == NULL && strcmp(av, "any") != 0)
ret = cmd; ret = cmd;
@ -3113,7 +3173,7 @@ add_src(ipfw_insn *cmd, char *av, u_char proto, int cblen)
} }
static ipfw_insn * static ipfw_insn *
add_dst(ipfw_insn *cmd, char *av, u_char proto, int cblen) add_dst(ipfw_insn *cmd, char *av, u_char proto, int cblen, struct tidx *tstate)
{ {
struct in6_addr a; struct in6_addr a;
char *host, *ch, buf[INET6_ADDRSTRLEN]; char *host, *ch, buf[INET6_ADDRSTRLEN];
@ -3136,7 +3196,7 @@ add_dst(ipfw_insn *cmd, char *av, u_char proto, int cblen)
/* XXX: should check for IPv4, not !IPv6 */ /* XXX: should check for IPv4, not !IPv6 */
if (ret == NULL && (proto == IPPROTO_IP || strcmp(av, "me") == 0 || if (ret == NULL && (proto == IPPROTO_IP || strcmp(av, "me") == 0 ||
inet_pton(AF_INET6, host, &a) != 1)) inet_pton(AF_INET6, host, &a) != 1))
ret = add_dstip(cmd, av, cblen); ret = add_dstip(cmd, av, cblen, tstate);
if (ret == NULL && strcmp(av, "any") != 0) if (ret == NULL && strcmp(av, "any") != 0)
ret = cmd; ret = cmd;
@ -3156,7 +3216,7 @@ add_dst(ipfw_insn *cmd, char *av, u_char proto, int cblen)
* *
*/ */
void void
ipfw_add(char *av[]) compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate)
{ {
/* /*
* rules are added into the 'rulebuf' and then copied in * rules are added into the 'rulebuf' and then copied in
@ -3164,7 +3224,7 @@ ipfw_add(char *av[])
* Some things that need to go out of order (prob, action etc.) * Some things that need to go out of order (prob, action etc.)
* go into actbuf[]. * go into actbuf[].
*/ */
static uint32_t rulebuf[255], actbuf[255], cmdbuf[255]; static uint32_t actbuf[255], cmdbuf[255];
int rblen, ablen, cblen; int rblen, ablen, cblen;
ipfw_insn *src, *dst, *cmd, *action, *prev=NULL; ipfw_insn *src, *dst, *cmd, *action, *prev=NULL;
@ -3190,14 +3250,14 @@ ipfw_add(char *av[])
bzero(actbuf, sizeof(actbuf)); /* actions go here */ bzero(actbuf, sizeof(actbuf)); /* actions go here */
bzero(cmdbuf, sizeof(cmdbuf)); bzero(cmdbuf, sizeof(cmdbuf));
bzero(rulebuf, sizeof(rulebuf)); bzero(rbuf, *rbufsize);
rule = (struct ip_fw *)rulebuf; rule = (struct ip_fw *)rbuf;
cmd = (ipfw_insn *)cmdbuf; cmd = (ipfw_insn *)cmdbuf;
action = (ipfw_insn *)actbuf; action = (ipfw_insn *)actbuf;
rblen = sizeof(rulebuf) / sizeof(rulebuf[0]); rblen = *rbufsize / sizeof(uint32_t);
rblen -= offsetof(struct ip_fw, cmd) / sizeof(rulebuf[0]); rblen -= offsetof(struct ip_fw, cmd) / sizeof(uint32_t);
ablen = sizeof(actbuf) / sizeof(actbuf[0]); ablen = sizeof(actbuf) / sizeof(actbuf[0]);
cblen = sizeof(cmdbuf) / sizeof(cmdbuf[0]); cblen = sizeof(cmdbuf) / sizeof(cmdbuf[0]);
cblen -= F_INSN_SIZE(ipfw_insn_u32) + 1; cblen -= F_INSN_SIZE(ipfw_insn_u32) + 1;
@ -3685,7 +3745,7 @@ ipfw_add(char *av[])
OR_START(source_ip); OR_START(source_ip);
NOT_BLOCK; /* optional "not" */ NOT_BLOCK; /* optional "not" */
NEED1("missing source address"); NEED1("missing source address");
if (add_src(cmd, *av, proto, cblen)) { if (add_src(cmd, *av, proto, cblen, tstate)) {
av++; av++;
if (F_LEN(cmd) != 0) { /* ! any */ if (F_LEN(cmd) != 0) { /* ! any */
prev = cmd; prev = cmd;
@ -3721,7 +3781,7 @@ ipfw_add(char *av[])
OR_START(dest_ip); OR_START(dest_ip);
NOT_BLOCK; /* optional "not" */ NOT_BLOCK; /* optional "not" */
NEED1("missing dst address"); NEED1("missing dst address");
if (add_dst(cmd, *av, proto, cblen)) { if (add_dst(cmd, *av, proto, cblen, tstate)) {
av++; av++;
if (F_LEN(cmd) != 0) { /* ! any */ if (F_LEN(cmd) != 0) { /* ! any */
prev = cmd; prev = cmd;
@ -3828,7 +3888,7 @@ ipfw_add(char *av[])
case TOK_VIA: case TOK_VIA:
NEED1("recv, xmit, via require interface name" NEED1("recv, xmit, via require interface name"
" or address"); " or address");
fill_iface((ipfw_insn_if *)cmd, av[0], cblen); fill_iface((ipfw_insn_if *)cmd, av[0], cblen, tstate);
av++; av++;
if (F_LEN(cmd) == 0) /* not a valid address */ if (F_LEN(cmd) == 0) /* not a valid address */
break; break;
@ -4074,14 +4134,14 @@ ipfw_add(char *av[])
case TOK_SRCIP: case TOK_SRCIP:
NEED1("missing source IP"); NEED1("missing source IP");
if (add_srcip(cmd, *av, cblen)) { if (add_srcip(cmd, *av, cblen, tstate)) {
av++; av++;
} }
break; break;
case TOK_DSTIP: case TOK_DSTIP:
NEED1("missing destination IP"); NEED1("missing destination IP");
if (add_dstip(cmd, *av, cblen)) { if (add_dstip(cmd, *av, cblen, tstate)) {
av++; av++;
} }
break; break;
@ -4323,17 +4383,111 @@ ipfw_add(char *av[])
} }
rule->cmd_len = (uint32_t *)dst - (uint32_t *)(rule->cmd); rule->cmd_len = (uint32_t *)dst - (uint32_t *)(rule->cmd);
i = (char *)dst - (char *)rule; *rbufsize = (char *)dst - (char *)rule;
if (do_cmd(IP_FW_ADD, rule, (uintptr_t)&i) == -1) }
err(EX_UNAVAILABLE, "getsockopt(%s)", "IP_FW_ADD");
/*
* 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 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 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
* accoring 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;
caddr_t tbuf;
ip_fw3_opheader *op3;
ipfw_obj_ctlv *ctlv, *tstate;
rbufsize = sizeof(rulebuf);
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 *)(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_ADD");
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);
table_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 (!co.do_quiet) { if (!co.do_quiet) {
struct format_opts sfo; struct format_opts sfo;
struct buf_pr bp; struct buf_pr bp;
memset(&sfo, 0, sizeof(sfo)); memset(&sfo, 0, sizeof(sfo));
sfo.tstate = tstate;
bp_alloc(&bp, 4096); bp_alloc(&bp, 4096);
show_static_rule(&co, &sfo, &bp, rule); show_static_rule(&co, &sfo, &bp, rule);
bp_free(&bp); bp_free(&bp);
} }
if (tbuf != NULL)
free(tbuf);
if (ts.idx != NULL)
free(ts.idx);
} }
/* /*

View File

@ -314,4 +314,6 @@ int fill_ext6hdr(struct _ipfw_insn *cmd, char *av);
/* tables.c */ /* tables.c */
struct _ipfw_obj_ctlv; struct _ipfw_obj_ctlv;
char *table_search_ctlv(struct _ipfw_obj_ctlv *ctlv, uint16_t idx); char *table_search_ctlv(struct _ipfw_obj_ctlv *ctlv, uint16_t idx);
void table_sort_ctlv(struct _ipfw_obj_ctlv *ctlv);
int table_check_name(char *tablename);

View File

@ -593,9 +593,29 @@ table_show_list(ipfw_obj_header *oh, int need_header)
} }
} }
int
compare_ntlv(const void *_a, const void *_b)
{
ipfw_obj_ntlv *a, *b;
a = (ipfw_obj_ntlv *)_a;
b = (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);
return (0);
}
int int
compare_ntlv(const void *k, const void *v) compare_kntlv(const void *k, const void *v)
{ {
ipfw_obj_ntlv *ntlv; ipfw_obj_ntlv *ntlv;
uint16_t key; uint16_t key;
@ -625,7 +645,7 @@ table_search_ctlv(ipfw_obj_ctlv *ctlv, uint16_t idx)
ipfw_obj_ntlv *ntlv; ipfw_obj_ntlv *ntlv;
ntlv = bsearch(&idx, (ctlv + 1), ctlv->count, ctlv->objsize, ntlv = bsearch(&idx, (ctlv + 1), ctlv->count, ctlv->objsize,
compare_ntlv); compare_kntlv);
if (ntlv != 0) if (ntlv != 0)
return (ntlv->name); return (ntlv->name);
@ -633,3 +653,37 @@ table_search_ctlv(ipfw_obj_ctlv *ctlv, uint16_t idx)
return (NULL); return (NULL);
} }
void
table_sort_ctlv(ipfw_obj_ctlv *ctlv)
{
qsort(ctlv + 1, ctlv->count, ctlv->objsize, compare_ntlv);
}
int
table_check_name(char *tablename)
{
int c, i, l;
/*
* Check if tablename is null-terminated and contains
* valid symbols only. Valid mask is:
* [a-zA-Z\-\.][a-zA-Z0-9\-_\.]{0,62}
*/
l = strlen(tablename);
if (l == 0 || l >= 64)
return (EINVAL);
/* Restrict first symbol to non-digit */
if (isdigit(tablename[0]))
return (EINVAL);
for (i = 0; i < l; i++) {
c = tablename[i];
if (isalpha(c) || isdigit(c) || c == '_' ||
c == '-' || c == '.')
continue;
return (EINVAL);
}
return (0);
}

View File

@ -88,6 +88,7 @@ typedef struct _ip_fw3_opheader {
#define IP_FW_TABLE_XCREATE 95 /* create new table */ #define IP_FW_TABLE_XCREATE 95 /* create new table */
#define IP_FW_TABLE_XMODIFY 96 /* modify existing table */ #define IP_FW_TABLE_XMODIFY 96 /* modify existing table */
#define IP_FW_XGET 97 /* Retrieve configuration */ #define IP_FW_XGET 97 /* Retrieve configuration */
#define IP_FW_XADD 98 /* add entry */
/* /*
* Usage guidelines: * Usage guidelines:
@ -695,7 +696,7 @@ typedef struct _ipfw_obj_ntlv {
ipfw_obj_tlv head; /* TLV header */ ipfw_obj_tlv head; /* TLV header */
uint16_t idx; /* Name index */ uint16_t idx; /* Name index */
uint16_t spare0; /* unused */ uint16_t spare0; /* unused */
uint32_t spare1; /* unused */ uint32_t set; /* set, if applicable */
char name[64]; /* Null-terminated name */ char name[64]; /* Null-terminated name */
} ipfw_obj_ntlv; } ipfw_obj_ntlv;

View File

@ -331,10 +331,9 @@ struct rule_check_info {
uint16_t table_opcodes; /* count of opcodes referencing table */ uint16_t table_opcodes; /* count of opcodes referencing table */
uint16_t new_tables; /* count of opcodes referencing table */ uint16_t new_tables; /* count of opcodes referencing table */
uint32_t tableset; /* ipfw set id for table */ uint32_t tableset; /* ipfw set id for table */
void *tlvs; /* Pointer to first TLV if any */ ipfw_obj_ctlv *ctlv; /* name TLV containter */
int tlen; /* *Total TLV size block */
uint8_t fw3; /* opcode is new */
struct ip_fw *krule; /* resulting rule pointer */ struct ip_fw *krule; /* resulting rule pointer */
struct ip_fw *urule; /* original rule pointer */
struct obj_idx obuf[8]; /* table references storage */ struct obj_idx obuf[8]; /* table references storage */
}; };

View File

@ -169,53 +169,92 @@ swap_map(struct ip_fw_chain *chain, struct ip_fw **new_map, int new_len)
} }
/* /*
* Add a new rule to the list. Copy the rule into a malloc'ed area, then * Copies rule @urule from userland format to kernel @krule.
* possibly create a rule number and add the rule to the list. */
static void
copy_rule(struct ip_fw *urule, struct ip_fw *krule)
{
int l;
l = RULESIZE(urule);
bcopy(urule, krule, l);
/* clear fields not settable from userland */
krule->x_next = NULL;
krule->next_rule = NULL;
IPFW_ZERO_RULE_COUNTER(krule);
}
/*
* Add new rule(s) to the list possibly creating rule number for each.
* Update the rule_number in the input struct so the caller knows it as well. * Update the rule_number in the input struct so the caller knows it as well.
* XXX DO NOT USE FOR THE DEFAULT RULE.
* Must be called without IPFW_UH held * Must be called without IPFW_UH held
*/ */
static int static int
add_rule(struct ip_fw_chain *chain, struct ip_fw *input_rule, commit_rules(struct ip_fw_chain *chain, struct rule_check_info *rci, int count)
struct rule_check_info *ci)
{ {
struct ip_fw *rule; int error, i, l, insert_before, tcount;
int i, l, insert_before; struct rule_check_info *ci;
struct ip_fw *rule, *urule;
struct ip_fw **map; /* the new array of pointers */ struct ip_fw **map; /* the new array of pointers */
if (chain->map == NULL || input_rule->rulenum > IPFW_DEFAULT_RULE - 1)
return (EINVAL);
l = RULESIZE(input_rule);
rule = malloc(l, M_IPFW, M_WAITOK | M_ZERO);
bcopy(input_rule, rule, l);
/* clear fields not settable from userland */
rule->x_next = NULL;
rule->next_rule = NULL;
IPFW_ZERO_RULE_COUNTER(rule);
/* Check if we need to do table remap */ /* Check if we need to do table remap */
if (ci->table_opcodes > 0) { tcount = 0;
ci->krule = rule; for (ci = rci, i = 0; i < count; ci++, i++) {
i = ipfw_rewrite_table_uidx(chain, ci); if (ci->table_opcodes == 0)
if (i != 0) { continue;
/* rewrite failed, return error */
free(rule, M_IPFW); /*
return (i); * Rule has some table opcodes.
* Reference & allocate needed tables/
*/
error = ipfw_rewrite_table_uidx(chain, ci);
if (error != 0) {
/*
* rewrite failed, state for current rule
* has been reverted. Check if we need to
* revert more.
*/
if (tcount > 0) {
/*
* We have some more table rules
* we need to rollback.
*/
IPFW_UH_WLOCK(chain);
while (ci != rci) {
ci--;
if (ci->table_opcodes == 0)
continue;
ipfw_unbind_table_rule(chain,ci->krule);
}
IPFW_UH_WUNLOCK(chain);
}
return (error);
} }
tcount++;
} }
/* get_map returns with IPFW_UH_WLOCK if successful */ /* get_map returns with IPFW_UH_WLOCK if successful */
map = get_map(chain, 1, 0 /* not locked */); map = get_map(chain, count, 0 /* not locked */);
if (map == NULL) { if (map == NULL) {
if (ci->table_opcodes > 0) { if (tcount > 0) {
/* We need to unbind tables */ /* Unbind tables */
IPFW_UH_WLOCK(chain); IPFW_UH_WLOCK(chain);
ipfw_unbind_table_rule(chain, rule); for (ci = rci, i = 0; i < count; ci++, i++) {
if (ci->table_opcodes == 0)
continue;
ipfw_unbind_table_rule(chain, ci->krule);
}
IPFW_UH_WUNLOCK(chain); IPFW_UH_WUNLOCK(chain);
} }
free(rule, M_IPFW);
return (ENOSPC); return (ENOSPC);
} }
@ -223,6 +262,13 @@ add_rule(struct ip_fw_chain *chain, struct ip_fw *input_rule,
V_autoinc_step = 1; V_autoinc_step = 1;
else if (V_autoinc_step > 1000) else if (V_autoinc_step > 1000)
V_autoinc_step = 1000; V_autoinc_step = 1000;
/* FIXME: Handle count > 1 */
ci = rci;
rule = ci->krule;
urule = ci->urule;
l = RULESIZE(rule);
/* find the insertion point, we will insert before */ /* find the insertion point, we will insert before */
insert_before = rule->rulenum ? rule->rulenum + 1 : IPFW_DEFAULT_RULE; insert_before = rule->rulenum ? rule->rulenum + 1 : IPFW_DEFAULT_RULE;
i = ipfw_find_rule(chain, insert_before, 0); i = ipfw_find_rule(chain, insert_before, 0);
@ -238,7 +284,7 @@ add_rule(struct ip_fw_chain *chain, struct ip_fw *input_rule,
rule->rulenum = i > 0 ? map[i-1]->rulenum : 0; rule->rulenum = i > 0 ? map[i-1]->rulenum : 0;
if (rule->rulenum < IPFW_DEFAULT_RULE - V_autoinc_step) if (rule->rulenum < IPFW_DEFAULT_RULE - V_autoinc_step)
rule->rulenum += V_autoinc_step; rule->rulenum += V_autoinc_step;
input_rule->rulenum = rule->rulenum; urule->rulenum = rule->rulenum;
} }
rule->id = chain->id + 1; rule->id = chain->id + 1;
@ -581,6 +627,10 @@ check_ipfw_struct(struct ip_fw *rule, int size, struct rule_check_info *ci)
rule->act_ofs, rule->cmd_len - 1); rule->act_ofs, rule->cmd_len - 1);
return (EINVAL); return (EINVAL);
} }
if (rule->rulenum > IPFW_DEFAULT_RULE - 1)
return (EINVAL);
/* /*
* Now go for the individual checks. Very simple ones, basically only * Now go for the individual checks. Very simple ones, basically only
* instruction sizes. * instruction sizes.
@ -1172,7 +1222,219 @@ dump_config(struct ip_fw_chain *chain, struct sockopt_data *sd)
} }
#define IP_FW3_OPLENGTH(x) ((x)->sopt_valsize - sizeof(ip_fw3_opheader)) #define IP_FW3_OPLENGTH(x) ((x)->sopt_valsize - sizeof(ip_fw3_opheader))
#define IP_FW3_OPTBUF 4096 /* page-size */ #define IP_FW3_WRITEBUF 4096 /* small page-size write buffer */
#define IP_FW3_READBUF 16 * 1024 * 1024 /* handle large rulesets */
static int
check_object_name(ipfw_obj_ntlv *ntlv)
{
if (strnlen(ntlv->name, sizeof(ntlv->name)) == sizeof(ntlv->name))
return (EINVAL);
/*
* TODO: do some more complicated checks
*/
return (0);
}
/*
* Adds one or more rules to ipfw @chain.
* Data layout (version 0)(current):
* 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 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 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
* accoring 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.
*
* Returns 0 on success.
*/
static int
add_entry(struct ip_fw_chain *chain, struct sockopt_data *sd)
{
ipfw_obj_ctlv *ctlv, *rtlv, *tstate;
ipfw_obj_ntlv *ntlv;
int clen, error, idx;
uint32_t count, read;
struct ip_fw *r;
struct rule_check_info rci, *ci, *cbuf;
ip_fw3_opheader *op3;
int i, rsize;
if (sd->valsize > IP_FW3_READBUF)
return (EINVAL);
op3 = (ip_fw3_opheader *)ipfw_get_sopt_space(sd, sd->valsize);
ctlv = (ipfw_obj_ctlv *)(op3 + 1);
read = sizeof(ip_fw3_opheader);
rtlv = NULL;
tstate = NULL;
cbuf = NULL;
memset(&rci, 0, sizeof(struct rule_check_info));
if (read + sizeof(*ctlv) > sd->valsize)
return (EINVAL);
if (ctlv->head.type == IPFW_TLV_TBLNAME_LIST) {
clen = ctlv->head.length;
if (clen > sd->valsize || clen < sizeof(*ctlv))
return (EINVAL);
/*
* Some table names or other named objects.
* Check for validness.
*/
count = (ctlv->head.length - sizeof(*ctlv)) / sizeof(*ntlv);
if (ctlv->count != count || ctlv->objsize != sizeof(*ntlv))
return (EINVAL);
/*
* Check each TLV.
* Ensure TLVs are sorted ascending and
* there are no duplicates.
*/
idx = -1;
ntlv = (ipfw_obj_ntlv *)(ctlv + 1);
while (count > 0) {
if (ntlv->head.length != sizeof(ipfw_obj_ntlv))
return (EINVAL);
error = check_object_name(ntlv);
if (error != 0)
return (error);
if (ntlv->idx <= idx)
return (EINVAL);
idx = ntlv->idx;
count--;
ntlv++;
}
tstate = ctlv;
read += ctlv->head.length;
ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv + ctlv->head.length);
}
if (read + sizeof(*ctlv) > sd->valsize)
return (EINVAL);
if (ctlv->head.type == IPFW_TLV_RULE_LIST) {
clen = ctlv->head.length;
if (clen + read > sd->valsize || clen < sizeof(*ctlv))
return (EINVAL);
/*
* TODO: Permit adding multiple rules at once
*/
if (ctlv->count != 1)
return (ENOTSUP);
clen -= sizeof(*ctlv);
if (ctlv->count > clen / sizeof(struct ip_fw))
return (EINVAL);
/* Allocate state for each rule or use stack */
if (ctlv->count == 1) {
memset(&rci, 0, sizeof(struct rule_check_info));
cbuf = &rci;
} else
cbuf = malloc(ctlv->count * sizeof(*ci), M_TEMP,
M_WAITOK | M_ZERO);
ci = cbuf;
/*
* Check each rule for validness.
* Ensure numbered rules are sorted ascending.
*/
idx = -1;
r = (struct ip_fw *)(ctlv + 1);
count = 0;
error = 0;
while (clen > 0) {
rsize = RULESIZE(r);
if (rsize > clen || ctlv->count <= count) {
error = EINVAL;
break;
}
ci->ctlv = tstate;
error = check_ipfw_struct(r, rsize, ci);
if (error != 0)
break;
/* Check sorting */
if (r->rulenum != 0 && r->rulenum < idx) {
error = EINVAL;
break;
}
idx = r->rulenum;
ci->urule = r;
rsize = roundup2(rsize, sizeof(uint64_t));
clen -= rsize;
r = (struct ip_fw *)((caddr_t)r + rsize);
count++;
ci++;
}
if (ctlv->count != count || error != 0) {
if (cbuf != &rci)
free(cbuf, M_TEMP);
return (EINVAL);
}
rtlv = ctlv;
read += ctlv->head.length;
ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv + ctlv->head.length);
}
if (read != sd->valsize || rtlv == NULL || rtlv->count == 0) {
if (cbuf != NULL && cbuf != &rci)
free(cbuf, M_TEMP);
return (EINVAL);
}
/*
* Passed rules seems to be valid.
* Allocate storage and try to add them to chain.
*/
for (i = 0, ci = cbuf; i < rtlv->count; i++, ci++) {
ci->krule = malloc(RULESIZE(ci->urule), M_IPFW, M_WAITOK);
copy_rule(ci->urule, ci->krule);
}
if ((error = commit_rules(chain, cbuf, rtlv->count)) != 0) {
/* Free allocate krules */
for (i = 0, ci = cbuf; i < rtlv->count; i++, ci++)
free(ci->krule, M_IPFW);
}
if (cbuf != NULL && cbuf != &rci)
free(cbuf, M_TEMP);
return (error);
}
/** /**
* {set|get}sockopt parser. * {set|get}sockopt parser.
*/ */
@ -1181,7 +1443,7 @@ ipfw_ctl(struct sockopt *sopt)
{ {
#define RULE_MAXSIZE (256*sizeof(u_int32_t)) #define RULE_MAXSIZE (256*sizeof(u_int32_t))
int error; int error;
size_t size, len, valsize; size_t bsize_max, size, len, valsize;
struct ip_fw *buf, *rule; struct ip_fw *buf, *rule;
struct ip_fw_chain *chain; struct ip_fw_chain *chain;
u_int32_t rulenum[2]; u_int32_t rulenum[2];
@ -1195,37 +1457,62 @@ ipfw_ctl(struct sockopt *sopt)
if (error) if (error)
return (error); return (error);
/*
* Disallow modifications in really-really secure mode, but still allow
* the logging counters to be reset.
*/
if (sopt->sopt_name == IP_FW_ADD ||
(sopt->sopt_dir == SOPT_SET && sopt->sopt_name != IP_FW_RESETLOG)) {
error = securelevel_ge(sopt->sopt_td->td_ucred, 3);
if (error)
return (error);
}
chain = &V_layer3_chain; chain = &V_layer3_chain;
error = 0; error = 0;
/* Save original valsize before it is altered via sooptcopyin() */ /* Save original valsize before it is altered via sooptcopyin() */
valsize = sopt->sopt_valsize; valsize = sopt->sopt_valsize;
memset(&sdata, 0, sizeof(sdata)); memset(&sdata, 0, sizeof(sdata));
/* Read op3 header first to determine actual operation */
if ((opt = sopt->sopt_name) == IP_FW3) { if ((opt = sopt->sopt_name) == IP_FW3) {
op3 = (ip_fw3_opheader *)xbuf;
error = sooptcopyin(sopt, op3, sizeof(*op3), sizeof(*op3));
if (error != 0)
return (error);
opt = op3->opcode;
sopt->sopt_valsize = valsize;
}
/*
* Disallow modifications in really-really secure mode, but still allow
* the logging counters to be reset.
*/
if (opt == IP_FW_ADD ||
(sopt->sopt_dir == SOPT_SET && opt != IP_FW_RESETLOG)) {
error = securelevel_ge(sopt->sopt_td->td_ucred, 3);
if (error != 0) {
if (sdata.kbuf != xbuf)
free(sdata.kbuf, M_TEMP);
return (error);
}
}
if (op3 != NULL) {
/*
* Determine buffer size:
* use on-stack xbuf for short request,
* allocate sliding-window buf for data export or
* contigious buffer for special ops.
*/
bsize_max = IP_FW3_WRITEBUF;
if (opt == IP_FW_ADD)
bsize_max = IP_FW3_READBUF;
/* /*
* Fill in sockopt_data structure that may be useful for * Fill in sockopt_data structure that may be useful for
* IP_FW3 get requests * IP_FW3 get requests.
*/ */
if (valsize <= sizeof(xbuf)) { if (valsize <= sizeof(xbuf)) {
sdata.kbuf = xbuf; sdata.kbuf = xbuf;
sdata.ksize = sizeof(xbuf); sdata.ksize = sizeof(xbuf);
sdata.kavail = valsize; sdata.kavail = valsize;
} else { } else {
if (valsize < IP_FW3_OPTBUF) if (valsize < bsize_max)
size = valsize; size = valsize;
else else
size = IP_FW3_OPTBUF; size = bsize_max;
sdata.kbuf = malloc(size, M_TEMP, M_WAITOK | M_ZERO); sdata.kbuf = malloc(size, M_TEMP, M_WAITOK | M_ZERO);
sdata.ksize = size; sdata.ksize = size;
@ -1236,8 +1523,8 @@ ipfw_ctl(struct sockopt *sopt)
sdata.valsize = valsize; sdata.valsize = valsize;
/* /*
* Copy either all request (if valsize < IP_FW3_OPTBUF) * Copy either all request (if valsize < bsize_max)
* or first IP_FW3_OPTBUF bytes to guarantee most consumers * or first bsize_max bytes to guarantee most consumers
* that all necessary data has been copied). * that all necessary data has been copied).
* Anyway, copy not less than sizeof(ip_fw3_opheader). * Anyway, copy not less than sizeof(ip_fw3_opheader).
*/ */
@ -1287,6 +1574,10 @@ ipfw_ctl(struct sockopt *sopt)
error = dump_config(chain, &sdata); error = dump_config(chain, &sdata);
break; break;
case IP_FW_XADD: /* IP_FW3 */
error = add_entry(chain, &sdata);
break;
case IP_FW_FLUSH: case IP_FW_FLUSH:
/* locking is done within del_entry() */ /* locking is done within del_entry() */
error = del_entry(chain, 0); /* special case, rule=0, cmd=0 means all */ error = del_entry(chain, 0); /* special case, rule=0, cmd=0 means all */
@ -1324,7 +1615,12 @@ ipfw_ctl(struct sockopt *sopt)
} }
if (error == 0) { if (error == 0) {
/* locking is done within add_rule() */ /* locking is done within add_rule() */
error = add_rule(chain, rule, &ci); struct ip_fw *krule;
krule = malloc(RULESIZE(rule), M_IPFW, M_WAITOK);
copy_rule(rule, krule);
ci.urule = rule;
ci.krule = krule;
error = commit_rules(chain, &ci, 1);
size = RULESIZE(rule); size = RULESIZE(rule);
if (!error && sopt->sopt_dir == SOPT_GET) { if (!error && sopt->sopt_dir == SOPT_GET) {
if (is7) { if (is7) {

View File

@ -1623,8 +1623,10 @@ ipfw_rewrite_table_uidx(struct ip_fw_chain *chain,
memset(&ti, 0, sizeof(ti)); memset(&ti, 0, sizeof(ti));
ti.set = ci->tableset; ti.set = ci->tableset;
ti.tlvs = ci->tlvs; if (ci->ctlv != NULL) {
ti.tlen = ci->tlen; ti.tlvs = (void *)(ci->ctlv + 1);
ti.tlen = ci->ctlv->head.length - sizeof(ipfw_obj_ctlv);
}
/* /*
* Stage 1: reference existing tables, determine number * Stage 1: reference existing tables, determine number
@ -1723,7 +1725,6 @@ ipfw_rewrite_table_uidx(struct ip_fw_chain *chain,
goto free; goto free;
} }
/* /*
* Attach new tables. * Attach new tables.
* We need to set table pointers for each new table, * We need to set table pointers for each new table,