Date: Mon, 11 Aug 2014 17:34:26 +0000 (UTC) From: Alexander V. Chernikov <melifaro@FreeBSD.org> To: src-committers@freebsd.org, svn-src-projects@freebsd.org Subject: svn commit: r269821 - in projects/ipfw: sbin/ipfw sys/netinet sys/netpfil/ipfw Message-ID: <53e8fea2.296d.53afb556@svn.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: melifaro Date: Mon Aug 11 17:34:25 2014 New Revision: 269821 URL: http://svnweb.freebsd.org/changeset/base/269821 Log: * Add support for batched add/delete for ipfw tables * Add support for atomic batches add (all or none). * Fix panic on deleting non-existing entry in radix algo. Examples: # si is empty # ipfw table si add 1.1.1.1/32 1111 2.2.2.2/32 2222 added: 1.1.1.1/32 1111 added: 2.2.2.2/32 2222 # ipfw table si add 2.2.2.2/32 2200 4.4.4.4/32 4444 exists: 2.2.2.2/32 2200 added: 4.4.4.4/32 4444 ipfw: Adding record failed: record already exists ^^^^^ Returns error but keeps inserted items # ipfw table si list +++ table(si), set(0) +++ 1.1.1.1/32 1111 2.2.2.2/32 2222 4.4.4.4/32 4444 # ipfw table si atomic add 3.3.3.3/32 3333 4.4.4.4/32 4400 5.5.5.5/32 5555 added(reverted): 3.3.3.3/32 3333 exists: 4.4.4.4/32 4400 ignored: 5.5.5.5/32 5555 ipfw: Adding record failed: record already exists ^^^^^ Returns error and reverts added records # ipfw table si list +++ table(si), set(0) +++ 1.1.1.1/32 1111 2.2.2.2/32 2222 4.4.4.4/32 4444 Modified: projects/ipfw/sbin/ipfw/ipfw2.h projects/ipfw/sbin/ipfw/tables.c projects/ipfw/sys/netinet/ip_fw.h projects/ipfw/sys/netpfil/ipfw/ip_fw_private.h projects/ipfw/sys/netpfil/ipfw/ip_fw_sockopt.c projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c projects/ipfw/sys/netpfil/ipfw/ip_fw_table.h projects/ipfw/sys/netpfil/ipfw/ip_fw_table_algo.c Modified: projects/ipfw/sbin/ipfw/ipfw2.h ============================================================================== --- projects/ipfw/sbin/ipfw/ipfw2.h Mon Aug 11 17:04:04 2014 (r269820) +++ projects/ipfw/sbin/ipfw/ipfw2.h Mon Aug 11 17:34:25 2014 (r269821) @@ -224,6 +224,7 @@ enum tokens { TOK_ALGO, TOK_TALIST, TOK_FTYPE, + TOK_ATOMIC, }; /* * the following macro returns an error message if we run out of Modified: projects/ipfw/sbin/ipfw/tables.c ============================================================================== --- projects/ipfw/sbin/ipfw/tables.c Mon Aug 11 17:04:04 2014 (r269820) +++ projects/ipfw/sbin/ipfw/tables.c Mon Aug 11 17:34:25 2014 (r269821) @@ -50,7 +50,7 @@ static void table_list(ipfw_xtable_info *i, int need_header); static void table_modify_record(ipfw_obj_header *oh, int ac, char *av[], - int add, int update); + int add, int quiet, int update, int atomic); static int table_flush(ipfw_obj_header *oh); static int table_destroy(ipfw_obj_header *oh); static int table_do_create(ipfw_obj_header *oh, ipfw_xtable_info *i); @@ -114,6 +114,7 @@ static struct _s_x tablecmds[] = { { "detail", TOK_DETAIL }, { "list", TOK_LIST }, { "lookup", TOK_LOOKUP }, + { "atomic", TOK_ATOMIC }, { NULL, 0 } }; @@ -144,7 +145,7 @@ void ipfw_table_handler(int ac, char *av[]) { int do_add, is_all; - int error, tcmd; + int atomic, error, tcmd; ipfw_xtable_info i; ipfw_obj_header oh; char *tablename; @@ -176,6 +177,21 @@ ipfw_table_handler(int ac, char *av[]) if ((tcmd = match_token(tablecmds, *av)) == -1) errx(EX_USAGE, "invalid table command %s", *av); + /* Check if atomic operation was requested */ + atomic = 0; + if (tcmd == TOK_ATOMIC) { + ac--; av++; + NEED1("atomic needs command"); + if ((tcmd = match_token(tablecmds, *av)) == -1) + errx(EX_USAGE, "invalid table command %s", *av); + switch (tcmd) { + case TOK_ADD: + break; + default: + errx(EX_USAGE, "atomic is not compatible with %s", *av); + } + atomic = 1; + } switch (tcmd) { case TOK_LIST: @@ -193,7 +209,8 @@ ipfw_table_handler(int ac, char *av[]) case TOK_DEL: do_add = **av == 'a'; ac--; av++; - table_modify_record(&oh, ac, av, do_add, co.do_quiet); + table_modify_record(&oh, ac, av, do_add, co.do_quiet, + co.do_quiet, atomic); break; case TOK_CREATE: ac--; av++; @@ -785,76 +802,195 @@ table_flush_one(ipfw_xtable_info *i, voi static int table_do_modify_record(int cmd, ipfw_obj_header *oh, - ipfw_obj_tentry *tent, int update) + ipfw_obj_tentry *tent, int count, int atomic) { ipfw_obj_ctlv *ctlv; + ipfw_obj_tentry *tent_base; + caddr_t pbuf; char xbuf[sizeof(*oh) + sizeof(ipfw_obj_ctlv) + sizeof(*tent)]; - int error; + int error, i; + size_t sz; - memset(xbuf, 0, sizeof(xbuf)); - memcpy(xbuf, oh, sizeof(*oh)); - oh = (ipfw_obj_header *)xbuf; + sz = sizeof(*ctlv) + sizeof(*tent) * count; + if (count == 1) { + memset(xbuf, 0, sizeof(xbuf)); + pbuf = xbuf; + } else { + if ((pbuf = calloc(1, sizeof(*oh) + sz)) == NULL) + return (ENOMEM); + } + + memcpy(pbuf, oh, sizeof(*oh)); + oh = (ipfw_obj_header *)pbuf; oh->opheader.version = 1; ctlv = (ipfw_obj_ctlv *)(oh + 1); - ctlv->count = 1; - ctlv->head.length = sizeof(*ctlv) + sizeof(*tent); + ctlv->count = count; + ctlv->head.length = sz; + if (atomic != 0) + ctlv->flags |= IPFW_CTF_ATOMIC; + + tent_base = tent; + memcpy(ctlv + 1, tent, sizeof(*tent) * count); + tent = (ipfw_obj_tentry *)(ctlv + 1); + for (i = 0; i < count; i++, tent++) { + tent->head.length = sizeof(ipfw_obj_tentry); + tent->idx = oh->idx; + } - memcpy(ctlv + 1, tent, sizeof(*tent)); + sz += sizeof(*oh); + error = do_get3(cmd, &oh->opheader, &sz); tent = (ipfw_obj_tentry *)(ctlv + 1); - if (update != 0) - tent->head.flags |= IPFW_TF_UPDATE; - tent->head.length = sizeof(ipfw_obj_tentry); + /* Copy result back to provided buffer */ + memcpy(tent_base, ctlv + 1, sizeof(*tent) * count); - error = do_set3(cmd, &oh->opheader, sizeof(xbuf)); + if (pbuf != xbuf) + free(pbuf); return (error); } static void -table_modify_record(ipfw_obj_header *oh, int ac, char *av[], int add, int update) +table_modify_record(ipfw_obj_header *oh, int ac, char *av[], int add, + int quiet, int update, int atomic) { - ipfw_obj_tentry tent; + ipfw_obj_tentry *ptent, tent, *tent_buf; ipfw_xtable_info xi; uint8_t type, vtype; - int cmd, error; - char *texterr, *etxt; + int cmd, count, error, i, ignored; + char *texterr, *etxt, *px; if (ac == 0) errx(EX_USAGE, "address required"); - memset(&tent, 0, sizeof(tent)); - tent.head.length = sizeof(tent); - tent.idx = 1; + if (add != 0) { + cmd = IP_FW_TABLE_XADD; + texterr = "Adding record failed"; + } else { + cmd = IP_FW_TABLE_XDEL; + texterr = "Deleting record failed"; + } + + /* + * Calculate number of entries: + * Assume [key val] x N for add + * and + * key x N for delete + */ + count = (add != 0) ? ac / 2 + 1 : ac; + + if (count <= 1) { + /* Adding single entry with/without value */ + memset(&tent, 0, sizeof(tent)); + tent_buf = &tent; + } else { + + if ((tent_buf = calloc(count, sizeof(tent))) == NULL) + errx(EX_OSERR, + "Unable to allocate memory for all entries"); + } + ptent = tent_buf; + + memset(&xi, 0, sizeof(xi)); + count = 0; + while (ac > 0) { + tentry_fill_key(oh, ptent, *av, &type, &vtype, &xi); + + /* + * compability layer: auto-create table if not exists + */ + if (xi.tablename[0] == '\0') { + xi.type = type; + xi.vtype = vtype; + strlcpy(xi.tablename, oh->ntlv.name, + sizeof(xi.tablename)); + fprintf(stderr, "DEPRECATED: inserting data info " + "non-existent table %s. (auto-created)\n", + xi.tablename); + table_do_create(oh, &xi); + } + + oh->ntlv.type = type; + ac--; av++; + + if (add != 0 && ac > 0) { + tentry_fill_value(oh, ptent, *av, type, vtype); + ac--; av++; + } - tentry_fill_key(oh, &tent, *av, &type, &vtype, &xi); + if (update != 0) + ptent->head.flags |= IPFW_TF_UPDATE; + + count++; + ptent++; + } + + error = table_do_modify_record(cmd, oh, tent_buf, count, atomic); /* - * compability layer: auto-create table if not exists + * Compatibility stuff: do not yell on duplicate keys or + * failed deletions. */ - if (xi.tablename[0] == '\0') { - xi.type = type; - xi.vtype = vtype; - strlcpy(xi.tablename, oh->ntlv.name, sizeof(xi.tablename)); - fprintf(stderr, "DEPRECATED: inserting data info non-existent " - "table %s. (auto-created)\n", xi.tablename); - table_do_create(oh, &xi); + if (error == 0 || (error == EEXIST && add != 0) || + (error == ENOENT && add == 0)) { + if (quiet != 0) { + if (tent_buf != &tent) + free(tent_buf); + return; + } } - oh->ntlv.type = type; - ac--; av++; + /* Report results back */ + ptent = tent_buf; + for (i = 0; i < count; ptent++, i++) { + ignored = 0; + switch (ptent->result) { + case IPFW_TR_ADDED: + px = "added"; + break; + case IPFW_TR_DELETED: + px = "deleted"; + break; + case IPFW_TR_UPDATED: + px = "updated"; + break; + case IPFW_TR_LIMIT: + px = "limit"; + ignored = 1; + break; + case IPFW_TR_ERROR: + px = "error"; + ignored = 1; + break; + case IPFW_TR_NOTFOUND: + px = "notfound"; + ignored = 1; + break; + case IPFW_TR_EXISTS: + px = "exists"; + ignored = 1; + break; + case IPFW_TR_IGNORED: + px = "ignored"; + ignored = 1; + break; + default: + px = "unknown"; + ignored = 1; + } - if (add != 0) { - if (ac > 0) - tentry_fill_value(oh, &tent, *av, type, vtype); - cmd = IP_FW_TABLE_XADD; - texterr = "Adding record failed"; - } else { - cmd = IP_FW_TABLE_XDEL; - texterr = "Deleting record failed"; + if (error != 0 && atomic != 0 && ignored == 0) + printf("%s(reverted): ", px); + else + printf("%s: ", px); + + table_show_entry(&xi, ptent); } - if ((error = table_do_modify_record(cmd, oh, &tent, update)) == 0) + if (tent_buf != &tent) + free(tent_buf); + + if (error == 0) return; /* Try to provide more human-readable error */ @@ -924,6 +1060,7 @@ table_lookup(ipfw_obj_header *oh, int ac strlcpy(key, *av, sizeof(key)); + memset(&xi, 0, sizeof(xi)); error = table_do_lookup(oh, key, &xi, &xtent); switch (error) { @@ -1144,7 +1281,10 @@ tentry_fill_key(ipfw_obj_header *oh, ipf tflags = 0; vtype = 0; - error = table_get_info(oh, xi); + if (xi->tablename[0] == '\0') + error = table_get_info(oh, xi); + else + error = 0; if (error == 0) { /* Table found. */ Modified: projects/ipfw/sys/netinet/ip_fw.h ============================================================================== --- projects/ipfw/sys/netinet/ip_fw.h Mon Aug 11 17:04:04 2014 (r269820) +++ projects/ipfw/sys/netinet/ip_fw.h Mon Aug 11 17:34:25 2014 (r269821) @@ -587,7 +587,7 @@ struct ip_fw { uint16_t act_ofs; /* offset of action in 32-bit units */ uint16_t cmd_len; /* # of 32-bit words in cmd */ uint16_t rulenum; /* rule number */ - uint8_t set; /* rule set (0..31) */ + uint8_t set; /* rule set (0..31) */ uint8_t _pad; /* padding */ uint32_t id; /* rule id */ @@ -784,7 +784,10 @@ typedef struct _ipfw_obj_tentry { uint8_t masklen; /* mask length */ uint16_t idx; /* Table name index */ uint32_t value; /* value */ - uint64_t spare; + uint8_t result; /* request result */ + uint8_t spare0; + uint16_t spare1; + uint32_t spare2; union { /* Longest field needs to be aligned by 8-byte boundary */ struct in_addr addr; /* IPv4 address */ @@ -795,6 +798,17 @@ typedef struct _ipfw_obj_tentry { } k; } ipfw_obj_tentry; #define IPFW_TF_UPDATE 0x01 /* Update record if exists */ +/* Container TLV */ +#define IPFW_CTF_ATOMIC 0x01 /* Perform atomic operation */ +/* Operation results */ +#define IPFW_TR_IGNORED 0 /* Entry was ignored (rollback) */ +#define IPFW_TR_ADDED 1 /* Entry was succesfully added */ +#define IPFW_TR_UPDATED 2 /* Entry was succesfully updated*/ +#define IPFW_TR_DELETED 3 /* Entry was succesfully deleted*/ +#define IPFW_TR_LIMIT 4 /* Entry was ignored (limit) */ +#define IPFW_TR_NOTFOUND 5 /* Entry was not found */ +#define IPFW_TR_EXISTS 6 /* Entry already exists */ +#define IPFW_TR_ERROR 7 /* Request has failed (unknown) */ typedef struct _ipfw_obj_dyntlv { ipfw_obj_tlv head; @@ -808,7 +822,7 @@ typedef struct _ipfw_obj_ctlv { uint32_t count; /* Number of sub-TLVs */ uint16_t objsize; /* Single object size */ uint8_t version; /* TLV version */ - uint8_t spare; + uint8_t flags; /* TLV-specific flags */ } ipfw_obj_ctlv; /* Range TLV */ Modified: projects/ipfw/sys/netpfil/ipfw/ip_fw_private.h ============================================================================== --- projects/ipfw/sys/netpfil/ipfw/ip_fw_private.h Mon Aug 11 17:04:04 2014 (r269820) +++ projects/ipfw/sys/netpfil/ipfw/ip_fw_private.h Mon Aug 11 17:34:25 2014 (r269821) @@ -427,15 +427,18 @@ struct obj_idx { }; struct rule_check_info { + uint16_t flags; /* rule-specific check flags */ uint16_t table_opcodes; /* count of opcodes referencing table */ - uint16_t new_tables; /* count of opcodes referencing table */ uint16_t urule_numoff; /* offset of rulenum in bytes */ uint8_t version; /* rule version */ + uint8_t spare; ipfw_obj_ctlv *ctlv; /* name TLV containter */ struct ip_fw *krule; /* resulting rule pointer */ caddr_t urule; /* original rule pointer */ struct obj_idx obuf[8]; /* table references storage */ }; +#define IPFW_RCF_TABLES 0x01 /* Has table-referencing opcode */ + /* Legacy interface support */ /* Modified: projects/ipfw/sys/netpfil/ipfw/ip_fw_sockopt.c ============================================================================== --- projects/ipfw/sys/netpfil/ipfw/ip_fw_sockopt.c Mon Aug 11 17:04:04 2014 (r269820) +++ projects/ipfw/sys/netpfil/ipfw/ip_fw_sockopt.c Mon Aug 11 17:34:25 2014 (r269821) @@ -2267,7 +2267,7 @@ ipfw_get_sopt_header(struct sockopt_data int ipfw_ctl3(struct sockopt *sopt) { - int error; + int error, ctype; size_t bsize_max, size, valsize; struct ip_fw_chain *chain; uint32_t opt; @@ -2297,27 +2297,35 @@ ipfw_ctl3(struct sockopt *sopt) sopt->sopt_valsize = valsize; /* + * Determine opcode type/buffer size: + * use on-stack xbuf for short request, + * allocate sliding-window buf for data export or + * contigious buffer for special ops. + */ + ctype = (sopt->sopt_dir == SOPT_GET) ? SOPT_GET : SOPT_SET; + switch (opt) { + case IP_FW_XADD: + case IP_FW_XDEL: + case IP_FW_TABLE_XADD: + case IP_FW_TABLE_XDEL: + ctype = SOPT_SET; + bsize_max = IP_FW3_READBUF; + break; + default: + bsize_max = IP_FW3_WRITEBUF; + } + + /* * Disallow modifications in really-really secure mode, but still allow * the logging counters to be reset. */ - if (opt == IP_FW_XADD || opt == IP_FW_XDEL || - (sopt->sopt_dir == SOPT_SET && opt != IP_FW_XRESETLOG)) { + if (ctype == SOPT_SET && opt != IP_FW_XRESETLOG) { error = securelevel_ge(sopt->sopt_td->td_ucred, 3); if (error != 0) return (error); } /* - * 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. */ @@ -2664,8 +2672,8 @@ ipfw_ctl(struct sockopt *sopt) ti.type = IPFW_TABLE_CIDR; error = (opt == IP_FW_TABLE_ADD) ? - add_table_entry(chain, &ti, &tei, 1) : - del_table_entry(chain, &ti, &tei, 1); + add_table_entry(chain, &ti, &tei, 0, 1) : + del_table_entry(chain, &ti, &tei, 0, 1); } break; Modified: projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c ============================================================================== --- projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c Mon Aug 11 17:04:04 2014 (r269820) +++ projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c Mon Aug 11 17:34:25 2014 (r269821) @@ -143,19 +143,58 @@ static int classify_table_opcode(ipfw_in #define TA_BUF_SZ 128 /* On-stack buffer for add/delete state */ +/* + * Checks if we're able to insert/update entry @tei into table + * w.r.t @tc limits. + * May alter @tei to indicate insertion error / insert + * options. + * + * Returns 0 if operation can be performed/ + */ +static int +check_table_limit(struct table_config *tc, struct tentry_info *tei) +{ + if (tc->limit == 0 || tc->count < tc->limit) + return (0); + + if ((tei->flags & TEI_FLAGS_UPDATE) == 0) { + /* Notify userland on error cause */ + tei->flags |= TEI_FLAGS_LIMIT; + return (EFBIG); + } + + /* + * We have UPDATE flag set. + * Permit updating record (if found), + * but restrict adding new one since we've + * already hit the limit. + */ + tei->flags |= TEI_FLAGS_DONTADD; + + return (0); +} + +/* + * Adds/updates one or more entries in table @ti. + * + * Returns 0 on success. + */ int add_table_entry(struct ip_fw_chain *ch, struct tid_info *ti, - struct tentry_info *tei, uint32_t count) + struct tentry_info *tei, uint8_t flags, uint32_t count) { struct table_config *tc; struct table_algo *ta; struct namedobj_instance *ni; uint16_t kidx; - int error; - uint32_t num; + int error, first_error, i, j, rerror, rollback; + uint32_t num, numadd; ipfw_xtable_info *xi; + struct tentry_info *ptei; char ta_buf[TA_BUF_SZ]; + size_t ta_buf_sz; + caddr_t ta_buf_m, v, vv; IPFW_UH_WLOCK(ch); ni = CHAIN_TO_NI(ch); @@ -172,8 +211,7 @@ add_table_entry(struct ip_fw_chain *ch, } /* Try to exit early on limit hit */ - if (tc->limit != 0 && tc->count >= tc->limit && - (tei->flags & TEI_FLAGS_UPDATE) == 0) { + if ((error = check_table_limit(tc, tei)) != 0 && count == 1) { IPFW_UH_WUNLOCK(ch); return (EFBIG); } @@ -218,10 +256,34 @@ add_table_entry(struct ip_fw_chain *ch, } /* Prepare record (allocate memory) */ - memset(&ta_buf, 0, sizeof(ta_buf)); - error = ta->prepare_add(ch, tei, &ta_buf); - if (error != 0) - return (error); + ta_buf_sz = ta->ta_buf_size; + rollback = 0; + if (count == 1) { + memset(&ta_buf, 0, sizeof(ta_buf)); + ta_buf_m = ta_buf; + } else { + + /* + * Multiple adds, allocate larger buffer + * sufficient to hold both ADD state + * and DELETE state (this may be needed + * if we need to rollback all changes) + */ + ta_buf_m = malloc(2 * count * ta_buf_sz, M_TEMP, + M_WAITOK | M_ZERO); + } + v = ta_buf_m; + for (i = 0; i < count; i++, v += ta_buf_sz) { + error = ta->prepare_add(ch, &tei[i], v); + + /* + * Some syntax error (incorrect mask, or address, or + * anything). Return error regardless of atomicity + * settings. + */ + if (error != 0) + goto cleanup; + } IPFW_UH_WLOCK(ch); @@ -233,67 +295,142 @@ add_table_entry(struct ip_fw_chain *ch, error = check_table_space(ch, tc, KIDX_TO_TI(ch, kidx), count); if (error != 0) { IPFW_UH_WUNLOCK(ch); - ta->flush_entry(ch, tei, &ta_buf); - return (error); + goto cleanup; } ni = CHAIN_TO_NI(ch); /* Drop reference we've used in first search */ tc->no.refcnt--; - - /* Check limit before adding */ - if (tc->limit != 0 && tc->count >= tc->limit) { - if ((tei->flags & TEI_FLAGS_UPDATE) == 0) { - IPFW_UH_WUNLOCK(ch); - ta->flush_entry(ch, tei, &ta_buf); - return (EFBIG); + /* We've got valid table in @tc. Let's try to add data */ + kidx = tc->no.kidx; + ta = tc->ta; + numadd = 0; + first_error = 0; + + IPFW_WLOCK(ch); + + v = ta_buf_m; + for (i = 0; i < count; i++, v += ta_buf_sz) { + ptei = &tei[i]; + num = 0; + /* check limit before adding */ + if ((error = check_table_limit(tc, ptei)) == 0) { + error = ta->add(tc->astate, KIDX_TO_TI(ch, kidx), + ptei, v, &num); + /* Set status flag to inform userland */ + if (error == 0 && num != 0) + ptei->flags |= TEI_FLAGS_ADDED; + else if (error == ENOENT) + ptei->flags |= TEI_FLAGS_NOTFOUND; + else if (error == EEXIST) + ptei->flags |= TEI_FLAGS_EXISTS; + else + ptei->flags |= TEI_FLAGS_ERROR; + } + if (error == 0) { + /* Update number of records to ease limit checking */ + tc->count += num; + numadd += num; + continue; } + if (first_error == 0) + first_error = error; + /* - * We have UPDATE flag set. - * Permit updating record (if found), - * but restrict adding new one since we've - * already hit the limit. + * Some error have happened. Check our atomicity + * settings: continue if atomicity is not required, + * rollback changes otherwise. */ - tei->flags |= TEI_FLAGS_DONTADD; - } + if ((flags & IPFW_CTF_ATOMIC) == 0) + continue; - /* We've got valid table in @tc. Let's add data */ - kidx = tc->no.kidx; - ta = tc->ta; - num = 0; + /* + * We need to rollback changes. + * This is tricky since some entries may have been + * updated, so we need to change their value back + * instead of deletion. + */ + rollback = 1; + v = ta_buf_m; + vv = v + count * ta_buf_sz; + for (j = 0; j < i; j++, v += ta_buf_sz, vv += ta_buf_sz) { + ptei = &tei[j]; + if ((ptei->flags & TEI_FLAGS_UPDATED) != 0) { - IPFW_WLOCK(ch); - error = ta->add(tc->astate, KIDX_TO_TI(ch, kidx), tei, &ta_buf, &num); - IPFW_WUNLOCK(ch); + /* + * We have old value stored by previous + * call in @ptei->value. Do add once again + * to restore it. + */ + rerror = ta->add(tc->astate, + KIDX_TO_TI(ch, kidx), ptei, v, &num); + KASSERT(rerror == 0, ("rollback UPDATE fail")); + KASSERT(num == 0, ("rollback UPDATE fail2")); + continue; + } - /* Update number of records. */ - if (error == 0) { - tc->count += num; - /* Permit post-add algorithm grow/rehash. */ - error = check_table_space(ch, tc, KIDX_TO_TI(ch, kidx), 0); + rerror = ta->prepare_del(ch, ptei, vv); + KASSERT(rerror == 0, ("pre-rollback INSERT failed")); + rerror = ta->del(tc->astate, KIDX_TO_TI(ch, kidx), ptei, + vv, &num); + KASSERT(rerror == 0, ("rollback INSERT failed")); + tc->count -= num; + } + + break; } + IPFW_WUNLOCK(ch); + + /* Permit post-add algorithm grow/rehash. */ + if (numadd != 0) + check_table_space(ch, tc, KIDX_TO_TI(ch, kidx), 0); + IPFW_UH_WUNLOCK(ch); + /* Return first error to user, if any */ + error = first_error; + +cleanup: /* Run cleaning callback anyway */ - ta->flush_entry(ch, tei, &ta_buf); + v = ta_buf_m; + for (i = 0; i < count; i++, v += ta_buf_sz) + ta->flush_entry(ch, &tei[i], v); + + /* Clean up "deleted" state in case of rollback */ + if (rollback != 0) { + vv = ta_buf_m + count * ta_buf_sz; + for (i = 0; i < count; i++, vv += ta_buf_sz) + ta->flush_entry(ch, &tei[i], vv); + } + + if (ta_buf_m != ta_buf) + free(ta_buf_m, M_TEMP); return (error); } +/* + * Deletes one or more entries in table @ti. + * + * Returns 0 on success. + */ int del_table_entry(struct ip_fw_chain *ch, struct tid_info *ti, - struct tentry_info *tei, uint32_t count) + struct tentry_info *tei, uint8_t flags, uint32_t count) { struct table_config *tc; struct table_algo *ta; struct namedobj_instance *ni; + struct tentry_info *ptei; uint16_t kidx; - int error; - uint32_t num; + int error, first_error, i; + uint32_t num, numdel; char ta_buf[TA_BUF_SZ]; + size_t ta_buf_sz; + caddr_t ta_buf_m, v; IPFW_UH_WLOCK(ch); ni = CHAIN_TO_NI(ch); @@ -307,8 +444,6 @@ del_table_entry(struct ip_fw_chain *ch, return (EINVAL); } - ta = tc->ta; - /* * Give a chance for algorithm to shrink. * May release/reacquire UH_WLOCK. @@ -317,36 +452,89 @@ del_table_entry(struct ip_fw_chain *ch, error = check_table_space(ch, tc, KIDX_TO_TI(ch, kidx), 0); if (error != 0) { IPFW_UH_WUNLOCK(ch); - ta->flush_entry(ch, tei, &ta_buf); return (error); } - /* - * We assume ta_buf size is enough for storing - * prepare_del() key, so we're running under UH_WLOCK here. - */ - memset(&ta_buf, 0, sizeof(ta_buf)); - if ((error = ta->prepare_del(ch, tei, &ta_buf)) != 0) { - IPFW_UH_WUNLOCK(ch); - return (error); + /* Reference and unlock */ + tc->no.refcnt++; + ta = tc->ta; + + IPFW_UH_WUNLOCK(ch); + + /* Prepare record (allocate memory) */ + ta_buf_sz = ta->ta_buf_size; + if (count == 1) { + memset(&ta_buf, 0, sizeof(ta_buf)); + ta_buf_m = ta_buf; + } else { + + /* + * Multiple deletes, allocate larger buffer + * sufficient to hold delete state. + */ + ta_buf_m = malloc(count * ta_buf_sz, M_TEMP, + M_WAITOK | M_ZERO); + } + v = ta_buf_m; + for (i = 0; i < count; i++, v += ta_buf_sz) { + error = ta->prepare_del(ch, &tei[i], v); + + /* + * Some syntax error (incorrect mask, or address, or + * anything). Return error immediately. + */ + if (error != 0) + goto cleanup; } + IPFW_UH_WLOCK(ch); + + /* Drop reference we've used in first search */ + tc->no.refcnt--; + kidx = tc->no.kidx; - num = 0; + numdel = 0; + first_error = 0; IPFW_WLOCK(ch); - error = ta->del(tc->astate, KIDX_TO_TI(ch, kidx), tei, &ta_buf, &num); + v = ta_buf_m; + for (i = 0; i < count; i++, v += ta_buf_sz) { + ptei = &tei[i]; + num = 0; + error = ta->del(tc->astate, KIDX_TO_TI(ch, kidx), ptei, v, + &num); + /* Save state for userland */ + if (error == 0) + ptei->flags |= TEI_FLAGS_DELETED; + else if (error == ENOENT) + ptei->flags |= TEI_FLAGS_NOTFOUND; + else + ptei->flags |= TEI_FLAGS_ERROR; + if (error != 0 && first_error == 0) + first_error = error; + tc->count -= num; + numdel += num; + } IPFW_WUNLOCK(ch); - if (error == 0) { - tc->count -= num; + if (numdel != 0) { /* Run post-del hook to permit shrinking */ error = check_table_space(ch, tc, KIDX_TO_TI(ch, kidx), 0); } IPFW_UH_WUNLOCK(ch); - ta->flush_entry(ch, tei, &ta_buf); + /* Return first error to user, if any */ + error = first_error; + +cleanup: + /* Run cleaning callback anyway */ + v = ta_buf_m; + for (i = 0; i < count; i++, v += ta_buf_sz) + ta->flush_entry(ch, &tei[i], v); + + if (ta_buf_m != ta_buf) + free(ta_buf_m, M_TEMP); return (error); } @@ -432,8 +620,10 @@ check_table_space(struct ip_fw_chain *ch return (error); } - - +/* + * Selects appropriate table operation handler + * depending on opcode version. + */ int ipfw_manage_table_ent(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) @@ -501,8 +691,8 @@ ipfw_manage_table_ent_v0(struct ip_fw_ch ti.type = xent->type; error = (op3->opcode == IP_FW_TABLE_XADD) ? - add_table_entry(ch, &ti, &tei, 1) : - del_table_entry(ch, &ti, &tei, 1); + add_table_entry(ch, &ti, &tei, 0, 1) : + del_table_entry(ch, &ti, &tei, 0, 1); return (error); } @@ -520,12 +710,12 @@ static int ipfw_manage_table_ent_v1(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { - ipfw_obj_tentry *tent; + ipfw_obj_tentry *tent, *ptent; ipfw_obj_ctlv *ctlv; ipfw_obj_header *oh; - struct tentry_info tei; + struct tentry_info *ptei, tei, *tei_buf; struct tid_info ti; - int error, read; + int error, i, kidx, read; /* Check minimum header size */ if (sd->valsize < (sizeof(*oh) + sizeof(*ctlv))) @@ -547,37 +737,81 @@ ipfw_manage_table_ent_v1(struct ip_fw_ch if (ctlv->head.length + read != sd->valsize) return (EINVAL); - /* - * TODO: permit adding multiple entries for given table - * at once - */ - if (ctlv->count != 1) - return (EOPNOTSUPP); - read += sizeof(*ctlv); - - /* Assume tentry may grow to support larger keys */ tent = (ipfw_obj_tentry *)(ctlv + 1); - if (tent->head.length < sizeof(*tent) || - tent->head.length + read > sd->valsize) + if (ctlv->count * sizeof(*tent) + read != sd->valsize) return (EINVAL); - /* Convert data into kernel request objects */ - memset(&tei, 0, sizeof(tei)); - tei.paddr = &tent->k; - tei.subtype = tent->subtype; - tei.masklen = tent->masklen; - if (tent->head.flags & IPFW_TF_UPDATE) - tei.flags |= TEI_FLAGS_UPDATE; - tei.value = tent->value; + if (ctlv->count == 0) + return (0); + + /* + * Mark entire buffer as "read". + * This makes sopt api write it back + * after function return. + */ + ipfw_get_sopt_header(sd, sd->valsize); + + /* Perform basic checks for each entry */ + ptent = tent; + kidx = tent->idx; + for (i = 0; i < ctlv->count; i++, ptent++) { + if (ptent->head.length != sizeof(*ptent)) + return (EINVAL); + if (ptent->idx != kidx) + return (ENOTSUP); + } + /* Convert data into kernel request objects */ objheader_to_ti(oh, &ti); ti.type = oh->ntlv.type; - ti.uidx = tent->idx; + ti.uidx = kidx; + + /* Use on-stack buffer for single add/del */ + if (ctlv->count == 1) { + memset(&tei, 0, sizeof(tei)); + tei_buf = &tei; + } else + tei_buf = malloc(ctlv->count * sizeof(tei), M_TEMP, + M_WAITOK | M_ZERO); + + ptei = tei_buf; + ptent = tent; + for (i = 0; i < ctlv->count; i++, ptent++, ptei++) { + ptei->paddr = &ptent->k; + ptei->subtype = ptent->subtype; + ptei->masklen = ptent->masklen; + if (ptent->head.flags & IPFW_TF_UPDATE) + ptei->flags |= TEI_FLAGS_UPDATE; + ptei->value = ptent->value; + } error = (oh->opheader.opcode == IP_FW_TABLE_XADD) ? - add_table_entry(ch, &ti, &tei, 1) : - del_table_entry(ch, &ti, &tei, 1); + add_table_entry(ch, &ti, tei_buf, ctlv->flags, ctlv->count) : + del_table_entry(ch, &ti, tei_buf, ctlv->flags, ctlv->count); + + /* Translate result back to userland */ + ptei = tei_buf; + ptent = tent; + for (i = 0; i < ctlv->count; i++, ptent++, ptei++) { + if (ptei->flags & TEI_FLAGS_ADDED) + ptent->result = IPFW_TR_ADDED; + else if (ptei->flags & TEI_FLAGS_DELETED) + ptent->result = IPFW_TR_DELETED; + else if (ptei->flags & TEI_FLAGS_UPDATED) + ptent->result = IPFW_TR_UPDATED; + else if (ptei->flags & TEI_FLAGS_LIMIT) + ptent->result = IPFW_TR_LIMIT; + else if (ptei->flags & TEI_FLAGS_ERROR) + ptent->result = IPFW_TR_ERROR; *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?53e8fea2.296d.53afb556>