diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c index a31451b5d41c..4595b04f3713 100644 --- a/sbin/ipfw/ipfw2.c +++ b/sbin/ipfw/ipfw2.c @@ -580,11 +580,12 @@ do_cmd(int optname, void *optval, uintptr_t optlen) * * Assumes op3 header is already embedded. * Calls setsockopt() with IP_FW3 as kernel-visible opcode. - * Returns 0 on success or -1 otherwise. + * Returns 0 on success or errno otherwise. */ int do_set3(int optname, ip_fw3_opheader *op3, uintptr_t optlen) { + int errno; if (co.test_only) return (0); @@ -596,7 +597,10 @@ do_set3(int optname, ip_fw3_opheader *op3, uintptr_t optlen) op3->opcode = optname; - return (setsockopt(ipfw_socket, IPPROTO_IP, IP_FW3, op3, optlen)); + if (setsockopt(ipfw_socket, IPPROTO_IP, IP_FW3, op3, optlen) != 0) + return (errno); + + return (0); } int diff --git a/sbin/ipfw/ipfw2.h b/sbin/ipfw/ipfw2.h index 1ed2ee8062ff..ff9bc5b52e0c 100644 --- a/sbin/ipfw/ipfw2.h +++ b/sbin/ipfw/ipfw2.h @@ -255,8 +255,6 @@ char const *match_value(struct _s_x *p, int value); size_t concat_tokens(char *buf, size_t bufsize, struct _s_x *table, char *delimiter); void fill_flags(struct _s_x *flags, char *p, uint8_t *set, uint8_t *clear); -void print_flags(char const *name, struct _s_x *list, uint8_t set, - uint8_t clear); void print_flags_buffer(char *buf, size_t sz, struct _s_x *list, uint8_t set); struct _ip_fw3_opheader; diff --git a/sbin/ipfw/tables.c b/sbin/ipfw/tables.c index f94ca72c2934..e36def6adfdf 100644 --- a/sbin/ipfw/tables.c +++ b/sbin/ipfw/tables.c @@ -251,9 +251,10 @@ table_fill_objheader(ipfw_obj_header *oh, ipfw_xtable_info *i) } static struct _s_x tablenewcmds[] = { - { "type", TOK_TYPE}, + { "type", TOK_TYPE }, { "valtype", TOK_VALTYPE }, { "algo", TOK_ALGO }, + { "limit", TOK_LIMIT }, { NULL, 0 } }; @@ -341,6 +342,11 @@ table_create(ipfw_obj_header *oh, int ac, char *av[]) ac--; av++; switch (tcmd) { + case TOK_LIMIT: + NEED1("limit value required"); + xi.limit = strtol(*av, NULL, 10); + ac--; av++; + break; case TOK_TYPE: NEED1("table type required"); /* Type may have suboptions after ':' */ @@ -485,6 +491,8 @@ table_show_info(ipfw_xtable_info *i, void *arg) printf(" valtype: %s, references: %u\n", vtype, i->refcnt); printf(" algorithm: %s\n", i->algoname); printf(" items: %u, size: %u\n", i->count, i->size); + if (i->limit > 0) + printf(" limit: %u\n", i->limit); return (0); } @@ -561,8 +569,8 @@ table_modify_record(ipfw_obj_header *oh, int ac, char *av[], int add, int update ipfw_obj_tentry tent; ipfw_xtable_info xi; uint8_t type, vtype; - int cmd; - char *texterr; + int cmd, error; + char *texterr, *etxt; if (ac == 0) errx(EX_USAGE, "address required"); @@ -592,14 +600,34 @@ table_modify_record(ipfw_obj_header *oh, int ac, char *av[], int add, int update if (ac > 0) tentry_fill_value(oh, &tent, *av, type, vtype); cmd = IP_FW_TABLE_XADD; - texterr = "setsockopt(IP_FW_TABLE_XADD)"; + texterr = "Adding record failed"; } else { cmd = IP_FW_TABLE_XDEL; - texterr = "setsockopt(IP_FW_TABLE_XDEL)"; + texterr = "Deleting record failed"; } - if (table_do_modify_record(cmd, oh, &tent, update) != 0) - err(EX_OSERR, "%s", texterr); + if ((error = table_do_modify_record(cmd, oh, &tent, update)) == 0) + return; + + /* Try to provide more human-readable error */ + switch (error) { + case EEXIST: + etxt = "record already exists"; + break; + case EFBIG: + etxt = "limit hit"; + break; + case ESRCH: + etxt = "table not found"; + break; + case ENOENT: + etxt = "record not found"; + break; + default: + etxt = strerror(error); + } + + errx(EX_OSERR, "%s: %s", texterr, etxt); } static int diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h index a1459aed6ae5..ff0aadb688d4 100644 --- a/sys/netinet/ip_fw.h +++ b/sys/netinet/ip_fw.h @@ -827,6 +827,8 @@ typedef struct _ipfw_xtable_info { uint32_t refcnt; /* number of references */ uint32_t count; /* Number of records */ uint32_t size; /* Total size of records(export)*/ + uint32_t limit; /* Max number of records */ + uint32_t spare; char tablename[64]; /* table name */ char algoname[64]; /* algorithm name */ ifpw_ta_tinfo ta_info; /* additional algo stats */ diff --git a/sys/netpfil/ipfw/ip_fw_table.c b/sys/netpfil/ipfw/ip_fw_table.c index be585747e7ef..ad48dc9ef3b4 100644 --- a/sys/netpfil/ipfw/ip_fw_table.c +++ b/sys/netpfil/ipfw/ip_fw_table.c @@ -78,8 +78,9 @@ struct table_config { uint8_t vtype; /* format table type */ uint8_t linked; /* 1 if already linked */ uint8_t tflags; /* type flags */ - uint8_t spare; + uint8_t spare; uint32_t count; /* Number of records */ + uint32_t limit; /* Max number of records */ uint64_t flags; /* state flags */ char tablename[64]; /* table name */ struct table_algo *ta; /* Callbacks for given algo */ @@ -102,7 +103,7 @@ static struct table_config *alloc_table_config(struct ip_fw_chain *ch, static void free_table_config(struct namedobj_instance *ni, struct table_config *tc); static int create_table_internal(struct ip_fw_chain *ch, struct tid_info *ti, - char *aname, uint8_t tflags, uint8_t vtype); + char *aname, ipfw_xtable_info *i); static void link_table(struct ip_fw_chain *chain, struct table_config *tc); static void unlink_table(struct ip_fw_chain *chain, struct table_config *tc); static void free_table_state(void **state, void **xstate, uint8_t type); @@ -132,7 +133,6 @@ static struct table_algo *find_table_algo(struct tables_config *tableconf, #define KIDX_TO_TI(ch, k) (&(((struct table_info *)(ch)->tablestate)[k])) - int add_table_entry(struct ip_fw_chain *ch, struct tid_info *ti, struct tentry_info *tei) @@ -144,6 +144,7 @@ add_table_entry(struct ip_fw_chain *ch, struct tid_info *ti, int error; uint32_t num; uint64_t aflags; + ipfw_xtable_info xi; char ta_buf[128]; IPFW_UH_WLOCK(ch); @@ -160,6 +161,13 @@ add_table_entry(struct ip_fw_chain *ch, struct tid_info *ti, return (EINVAL); } + /* Try to exit early on limit hit */ + if (tc->limit != 0 && tc->count == tc->limit && + (tei->flags & TEI_FLAGS_UPDATE) == 0) { + IPFW_UH_WUNLOCK(ch); + return (EFBIG); + } + /* Reference and unlock */ tc->no.refcnt++; ta = tc->ta; @@ -172,7 +180,10 @@ add_table_entry(struct ip_fw_chain *ch, struct tid_info *ti, if ((tei->flags & TEI_FLAGS_COMPAT) == 0) return (ESRCH); - error = create_table_internal(ch, ti, NULL, 0, IPFW_VTYPE_U32); + memset(&xi, 0, sizeof(xi)); + xi.vtype = IPFW_VTYPE_U32; + + error = create_table_internal(ch, ti, NULL, &xi); if (error != 0) return (error); @@ -223,6 +234,22 @@ add_table_entry(struct ip_fw_chain *ch, struct tid_info *ti, /* Update aflags since it can be changed after previous read */ aflags = tc->flags; + /* Check limit before adding */ + if (tc->limit != 0 && tc->count == tc->limit) { + if ((tei->flags & TEI_FLAGS_UPDATE) == 0) { + IPFW_UH_WUNLOCK(ch); + 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; + } + /* We've got valid table in @tc. Let's add data */ kidx = tc->no.kidx; ta = tc->ta; @@ -1187,7 +1214,7 @@ ipfw_create_table(struct ip_fw_chain *ch, ip_fw3_opheader *op3, } IPFW_UH_RUNLOCK(ch); - return (create_table_internal(ch, &ti, aname, i->tflags, i->vtype)); + return (create_table_internal(ch, &ti, aname, i)); } /* @@ -1200,7 +1227,7 @@ ipfw_create_table(struct ip_fw_chain *ch, ip_fw3_opheader *op3, */ static int create_table_internal(struct ip_fw_chain *ch, struct tid_info *ti, - char *aname, uint8_t tflags, uint8_t vtype) + char *aname, ipfw_xtable_info *i) { struct namedobj_instance *ni; struct table_config *tc; @@ -1212,10 +1239,13 @@ create_table_internal(struct ip_fw_chain *ch, struct tid_info *ti, ta = find_table_algo(CHAIN_TO_TCFG(ch), ti, aname); if (ta == NULL) return (ENOTSUP); - - if ((tc = alloc_table_config(ch, ti, ta, aname, tflags, vtype)) == NULL) + + tc = alloc_table_config(ch, ti, ta, aname, i->tflags, i->vtype); + if (tc == NULL) return (ENOMEM); + tc->limit = i->limit; + IPFW_UH_WLOCK(ch); /* Check if table has been already created */ @@ -1293,6 +1323,7 @@ export_table_info(struct ip_fw_chain *ch, struct table_config *tc, i->kidx = tc->no.kidx; i->refcnt = tc->no.refcnt; i->count = tc->count; + i->limit = tc->limit; i->size = tc->count * sizeof(ipfw_obj_tentry); i->size += sizeof(ipfw_obj_header) + sizeof(ipfw_xtable_info); strlcpy(i->tablename, tc->tablename, sizeof(i->tablename)); diff --git a/sys/netpfil/ipfw/ip_fw_table.h b/sys/netpfil/ipfw/ip_fw_table.h index 371c41517ad5..3d449cf4f2c4 100644 --- a/sys/netpfil/ipfw/ip_fw_table.h +++ b/sys/netpfil/ipfw/ip_fw_table.h @@ -58,9 +58,10 @@ struct tentry_info { uint16_t flags; /* record flags */ uint32_t value; /* value */ }; -#define TEI_FLAGS_UPDATE 0x01 /* Update record if exists */ +#define TEI_FLAGS_UPDATE 0x01 /* Add or update rec if exists */ #define TEI_FLAGS_UPDATED 0x02 /* Entry has been updated */ #define TEI_FLAGS_COMPAT 0x04 /* Called from old ABI */ +#define TEI_FLAGS_DONTADD 0x08 /* Do not create new rec */ typedef int (ta_init)(struct ip_fw_chain *ch, void **ta_state, struct table_info *ti, char *data, uint8_t tflags); diff --git a/sys/netpfil/ipfw/ip_fw_table_algo.c b/sys/netpfil/ipfw/ip_fw_table_algo.c index d1ecbfdbb85f..10484ae5baed 100644 --- a/sys/netpfil/ipfw/ip_fw_table_algo.c +++ b/sys/netpfil/ipfw/ip_fw_table_algo.c @@ -411,22 +411,12 @@ ta_add_cidr(void *ta_state, struct table_info *ti, struct tentry_info *tei, else rnh = ti->xstate; - rn = rnh->rnh_addaddr(tb->addr_ptr, tb->mask_ptr, rnh, tb->ent_ptr); - - if (rn == NULL) { + /* Search for an entry first */ + rn = rnh->rnh_lookup(tb->addr_ptr, tb->mask_ptr, rnh); + if (rn != NULL) { if ((tei->flags & TEI_FLAGS_UPDATE) == 0) return (EEXIST); /* Record already exists. Update value if we're asked to */ - rn = rnh->rnh_lookup(tb->addr_ptr, tb->mask_ptr, rnh); - if (rn == NULL) { - - /* - * Radix may have failed addition for other reasons - * like failure in mask allocation code. - */ - return (EINVAL); - } - if (tei->subtype == AF_INET) { /* IPv4. */ value = ((struct radix_cidr_entry *)tb->ent_ptr)->value; @@ -444,6 +434,15 @@ ta_add_cidr(void *ta_state, struct table_info *ti, struct tentry_info *tei, return (0); } + if ((tei->flags & TEI_FLAGS_DONTADD) != 0) + return (EFBIG); + + rn = rnh->rnh_addaddr(tb->addr_ptr, tb->mask_ptr, rnh, tb->ent_ptr); + if (rn == NULL) { + /* Unknown error */ + return (EINVAL); + } + tb->ent_ptr = NULL; *pnum = 1; @@ -1167,6 +1166,8 @@ ta_add_chash(void *ta_state, struct table_info *ti, struct tentry_info *tei, tei->flags |= TEI_FLAGS_UPDATED; *pnum = 0; } else { + if ((tei->flags & TEI_FLAGS_DONTADD) != 0) + return (EFBIG); SLIST_INSERT_HEAD(&head[hash], ent, next); tb->ent_ptr = NULL; *pnum = 1; @@ -1715,6 +1716,9 @@ ta_add_ifidx(void *ta_state, struct table_info *ti, struct tentry_info *tei, return (0); } + if ((tei->flags & TEI_FLAGS_DONTADD) != 0) + return (EFBIG); + /* Link to internal list */ ipfw_objhash_add(icfg->ii, &ife->no); @@ -2206,6 +2210,9 @@ ta_add_numarray(void *ta_state, struct table_info *ti, struct tentry_info *tei, return (0); } + if ((tei->flags & TEI_FLAGS_DONTADD) != 0) + return (EFBIG); + res = badd(&tb->na.number, &tb->na, cfg->main_ptr, cfg->used, sizeof(struct numarray), compare_numarray); @@ -2891,6 +2898,9 @@ ta_add_fhash(void *ta_state, struct table_info *ti, struct tentry_info *tei, tei->flags |= TEI_FLAGS_UPDATED; *pnum = 0; } else { + if ((tei->flags & TEI_FLAGS_DONTADD) != 0) + return (EFBIG); + SLIST_INSERT_HEAD(&head[hash], ent, next); tb->ent_ptr = NULL; *pnum = 1;