From 6c2997ffec9396c0ad019805f37677ab0a56a3b1 Mon Sep 17 00:00:00 2001 From: "Alexander V. Chernikov" Date: Sun, 29 Jun 2014 22:35:47 +0000 Subject: [PATCH] * 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. --- sbin/ipfw/ipfw2.c | 210 +++++++++++++--- sbin/ipfw/ipfw2.h | 2 + sbin/ipfw/tables.c | 58 ++++- sys/netinet/ip_fw.h | 3 +- sys/netpfil/ipfw/ip_fw_private.h | 5 +- sys/netpfil/ipfw/ip_fw_sockopt.c | 396 +++++++++++++++++++++++++++---- sys/netpfil/ipfw/ip_fw_table.c | 7 +- 7 files changed, 594 insertions(+), 87 deletions(-) diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c index 5ad95fcb647d..065f6b129020 100644 --- a/sbin/ipfw/ipfw2.c +++ b/sbin/ipfw/ipfw2.c @@ -2493,6 +2493,56 @@ lookup_host (char *host, struct in_addr *ipaddr) 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. * 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. */ 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; uint32_t *d = ((ipfw_insn_u32 *)cmd)->d; + uint16_t uidx; + char *p; 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) { - char *p = strchr(av + 6, ','); - + p = strchr(av + 6, ','); if (p) *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.arg1 = strtoul(av + 6, NULL, 0); + cmd->o.arg1 = uidx; if (p) { cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32); d[0] = strtoul(p, NULL, 0); @@ -2805,8 +2860,10 @@ ipfw_delete(char *av[]) * patterns which match interfaces. */ 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->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, ','); if (p) *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->p.glob = strtoul(arg + 6, NULL, 0); + cmd->p.glob = uidx; } else if (!isdigit(*arg)) { strlcpy(cmd->name, arg, sizeof(cmd->name)); 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 * -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 */ cmd->opcode = O_IP_SRC_SET; 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 * -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 */ ; 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 * -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; 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 */ if (ret == NULL && (proto == IPPROTO_IP || strcmp(av, "me") == 0 || 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) ret = cmd; @@ -3113,7 +3173,7 @@ add_src(ipfw_insn *cmd, char *av, u_char proto, int cblen) } 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; 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 */ if (ret == NULL && (proto == IPPROTO_IP || strcmp(av, "me") == 0 || 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) ret = cmd; @@ -3156,7 +3216,7 @@ add_dst(ipfw_insn *cmd, char *av, u_char proto, int cblen) * */ 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 @@ -3164,7 +3224,7 @@ ipfw_add(char *av[]) * Some things that need to go out of order (prob, action etc.) * go into actbuf[]. */ - static uint32_t rulebuf[255], actbuf[255], cmdbuf[255]; + static uint32_t actbuf[255], cmdbuf[255]; int rblen, ablen, cblen; ipfw_insn *src, *dst, *cmd, *action, *prev=NULL; @@ -3190,14 +3250,14 @@ ipfw_add(char *av[]) bzero(actbuf, sizeof(actbuf)); /* actions go here */ 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; action = (ipfw_insn *)actbuf; - rblen = sizeof(rulebuf) / sizeof(rulebuf[0]); - rblen -= offsetof(struct ip_fw, cmd) / sizeof(rulebuf[0]); + rblen = *rbufsize / sizeof(uint32_t); + rblen -= offsetof(struct ip_fw, cmd) / sizeof(uint32_t); ablen = sizeof(actbuf) / sizeof(actbuf[0]); cblen = sizeof(cmdbuf) / sizeof(cmdbuf[0]); cblen -= F_INSN_SIZE(ipfw_insn_u32) + 1; @@ -3685,7 +3745,7 @@ ipfw_add(char *av[]) OR_START(source_ip); NOT_BLOCK; /* optional "not" */ NEED1("missing source address"); - if (add_src(cmd, *av, proto, cblen)) { + if (add_src(cmd, *av, proto, cblen, tstate)) { av++; if (F_LEN(cmd) != 0) { /* ! any */ prev = cmd; @@ -3721,7 +3781,7 @@ ipfw_add(char *av[]) OR_START(dest_ip); NOT_BLOCK; /* optional "not" */ NEED1("missing dst address"); - if (add_dst(cmd, *av, proto, cblen)) { + if (add_dst(cmd, *av, proto, cblen, tstate)) { av++; if (F_LEN(cmd) != 0) { /* ! any */ prev = cmd; @@ -3828,7 +3888,7 @@ ipfw_add(char *av[]) case TOK_VIA: NEED1("recv, xmit, via require interface name" " or address"); - fill_iface((ipfw_insn_if *)cmd, av[0], cblen); + fill_iface((ipfw_insn_if *)cmd, av[0], cblen, tstate); av++; if (F_LEN(cmd) == 0) /* not a valid address */ break; @@ -4074,14 +4134,14 @@ ipfw_add(char *av[]) case TOK_SRCIP: NEED1("missing source IP"); - if (add_srcip(cmd, *av, cblen)) { + if (add_srcip(cmd, *av, cblen, tstate)) { av++; } break; case TOK_DSTIP: NEED1("missing destination IP"); - if (add_dstip(cmd, *av, cblen)) { + if (add_dstip(cmd, *av, cblen, tstate)) { av++; } break; @@ -4323,17 +4383,111 @@ ipfw_add(char *av[]) } rule->cmd_len = (uint32_t *)dst - (uint32_t *)(rule->cmd); - i = (char *)dst - (char *)rule; - if (do_cmd(IP_FW_ADD, rule, (uintptr_t)&i) == -1) - err(EX_UNAVAILABLE, "getsockopt(%s)", "IP_FW_ADD"); + *rbufsize = (char *)dst - (char *)rule; +} + +/* + * 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) { struct format_opts sfo; struct buf_pr bp; memset(&sfo, 0, sizeof(sfo)); + sfo.tstate = tstate; bp_alloc(&bp, 4096); show_static_rule(&co, &sfo, &bp, rule); bp_free(&bp); } + + if (tbuf != NULL) + free(tbuf); + + if (ts.idx != NULL) + free(ts.idx); } /* diff --git a/sbin/ipfw/ipfw2.h b/sbin/ipfw/ipfw2.h index a6a7e2c78e4b..9a4af8165ddf 100644 --- a/sbin/ipfw/ipfw2.h +++ b/sbin/ipfw/ipfw2.h @@ -314,4 +314,6 @@ 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); +void table_sort_ctlv(struct _ipfw_obj_ctlv *ctlv); +int table_check_name(char *tablename); diff --git a/sbin/ipfw/tables.c b/sbin/ipfw/tables.c index 6b4eff2e2bc6..e34b14033367 100644 --- a/sbin/ipfw/tables.c +++ b/sbin/ipfw/tables.c @@ -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 -compare_ntlv(const void *k, const void *v) +compare_kntlv(const void *k, const void *v) { ipfw_obj_ntlv *ntlv; uint16_t key; @@ -625,7 +645,7 @@ 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); + compare_kntlv); if (ntlv != 0) return (ntlv->name); @@ -633,3 +653,37 @@ table_search_ctlv(ipfw_obj_ctlv *ctlv, uint16_t idx) 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); +} + diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h index 51def3ba2b86..2f766f234d37 100644 --- a/sys/netinet/ip_fw.h +++ b/sys/netinet/ip_fw.h @@ -88,6 +88,7 @@ typedef struct _ip_fw3_opheader { #define IP_FW_TABLE_XCREATE 95 /* create new table */ #define IP_FW_TABLE_XMODIFY 96 /* modify existing table */ #define IP_FW_XGET 97 /* Retrieve configuration */ +#define IP_FW_XADD 98 /* add entry */ /* * Usage guidelines: @@ -695,7 +696,7 @@ typedef struct _ipfw_obj_ntlv { ipfw_obj_tlv head; /* TLV header */ uint16_t idx; /* Name index */ uint16_t spare0; /* unused */ - uint32_t spare1; /* unused */ + uint32_t set; /* set, if applicable */ char name[64]; /* Null-terminated name */ } ipfw_obj_ntlv; diff --git a/sys/netpfil/ipfw/ip_fw_private.h b/sys/netpfil/ipfw/ip_fw_private.h index 480e1208bb7a..213d7890be3e 100644 --- a/sys/netpfil/ipfw/ip_fw_private.h +++ b/sys/netpfil/ipfw/ip_fw_private.h @@ -331,10 +331,9 @@ struct rule_check_info { uint16_t table_opcodes; /* count of opcodes referencing table */ uint16_t new_tables; /* count of opcodes referencing table */ uint32_t tableset; /* ipfw set id for table */ - void *tlvs; /* Pointer to first TLV if any */ - int tlen; /* *Total TLV size block */ - uint8_t fw3; /* opcode is new */ + ipfw_obj_ctlv *ctlv; /* name TLV containter */ struct ip_fw *krule; /* resulting rule pointer */ + struct ip_fw *urule; /* original rule pointer */ struct obj_idx obuf[8]; /* table references storage */ }; diff --git a/sys/netpfil/ipfw/ip_fw_sockopt.c b/sys/netpfil/ipfw/ip_fw_sockopt.c index 693a5bd39c0b..fde823daa11f 100644 --- a/sys/netpfil/ipfw/ip_fw_sockopt.c +++ b/sys/netpfil/ipfw/ip_fw_sockopt.c @@ -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 - * possibly create a rule number and add the rule to the list. + * Copies rule @urule from userland format to kernel @krule. + */ +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. - * XXX DO NOT USE FOR THE DEFAULT RULE. * Must be called without IPFW_UH held */ static int -add_rule(struct ip_fw_chain *chain, struct ip_fw *input_rule, - struct rule_check_info *ci) +commit_rules(struct ip_fw_chain *chain, struct rule_check_info *rci, int count) { - struct ip_fw *rule; - int i, l, insert_before; + int error, i, l, insert_before, tcount; + struct rule_check_info *ci; + struct ip_fw *rule, *urule; 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 */ - if (ci->table_opcodes > 0) { - ci->krule = rule; - i = ipfw_rewrite_table_uidx(chain, ci); - if (i != 0) { - /* rewrite failed, return error */ - free(rule, M_IPFW); - return (i); + tcount = 0; + for (ci = rci, i = 0; i < count; ci++, i++) { + if (ci->table_opcodes == 0) + continue; + + /* + * 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 */ - map = get_map(chain, 1, 0 /* not locked */); + map = get_map(chain, count, 0 /* not locked */); if (map == NULL) { - if (ci->table_opcodes > 0) { - /* We need to unbind tables */ + if (tcount > 0) { + /* Unbind tables */ 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); } - free(rule, M_IPFW); return (ENOSPC); } @@ -223,6 +262,13 @@ add_rule(struct ip_fw_chain *chain, struct ip_fw *input_rule, V_autoinc_step = 1; else if (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 */ insert_before = rule->rulenum ? rule->rulenum + 1 : IPFW_DEFAULT_RULE; 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; if (rule->rulenum < IPFW_DEFAULT_RULE - V_autoinc_step) rule->rulenum += V_autoinc_step; - input_rule->rulenum = rule->rulenum; + urule->rulenum = rule->rulenum; } 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); return (EINVAL); } + + if (rule->rulenum > IPFW_DEFAULT_RULE - 1) + return (EINVAL); + /* * Now go for the individual checks. Very simple ones, basically only * 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_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. */ @@ -1181,7 +1443,7 @@ ipfw_ctl(struct sockopt *sopt) { #define RULE_MAXSIZE (256*sizeof(u_int32_t)) int error; - size_t size, len, valsize; + size_t bsize_max, size, len, valsize; struct ip_fw *buf, *rule; struct ip_fw_chain *chain; u_int32_t rulenum[2]; @@ -1195,37 +1457,62 @@ ipfw_ctl(struct sockopt *sopt) if (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; error = 0; /* Save original valsize before it is altered via sooptcopyin() */ valsize = sopt->sopt_valsize; memset(&sdata, 0, sizeof(sdata)); + /* Read op3 header first to determine actual operation */ 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 - * IP_FW3 get requests + * IP_FW3 get requests. */ + if (valsize <= sizeof(xbuf)) { sdata.kbuf = xbuf; sdata.ksize = sizeof(xbuf); sdata.kavail = valsize; } else { - if (valsize < IP_FW3_OPTBUF) + if (valsize < bsize_max) size = valsize; else - size = IP_FW3_OPTBUF; + size = bsize_max; sdata.kbuf = malloc(size, M_TEMP, M_WAITOK | M_ZERO); sdata.ksize = size; @@ -1236,8 +1523,8 @@ ipfw_ctl(struct sockopt *sopt) sdata.valsize = valsize; /* - * Copy either all request (if valsize < IP_FW3_OPTBUF) - * or first IP_FW3_OPTBUF bytes to guarantee most consumers + * Copy either all request (if valsize < bsize_max) + * or first bsize_max bytes to guarantee most consumers * that all necessary data has been copied). * Anyway, copy not less than sizeof(ip_fw3_opheader). */ @@ -1287,6 +1574,10 @@ ipfw_ctl(struct sockopt *sopt) error = dump_config(chain, &sdata); break; + case IP_FW_XADD: /* IP_FW3 */ + error = add_entry(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 */ @@ -1324,7 +1615,12 @@ ipfw_ctl(struct sockopt *sopt) } if (error == 0) { /* 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); if (!error && sopt->sopt_dir == SOPT_GET) { if (is7) { diff --git a/sys/netpfil/ipfw/ip_fw_table.c b/sys/netpfil/ipfw/ip_fw_table.c index 8b816095ca38..506a17511ba6 100644 --- a/sys/netpfil/ipfw/ip_fw_table.c +++ b/sys/netpfil/ipfw/ip_fw_table.c @@ -1623,8 +1623,10 @@ ipfw_rewrite_table_uidx(struct ip_fw_chain *chain, memset(&ti, 0, sizeof(ti)); ti.set = ci->tableset; - ti.tlvs = ci->tlvs; - ti.tlen = ci->tlen; + if (ci->ctlv != NULL) { + ti.tlvs = (void *)(ci->ctlv + 1); + ti.tlen = ci->ctlv->head.length - sizeof(ipfw_obj_ctlv); + } /* * Stage 1: reference existing tables, determine number @@ -1723,7 +1725,6 @@ ipfw_rewrite_table_uidx(struct ip_fw_chain *chain, goto free; } - /* * Attach new tables. * We need to set table pointers for each new table,