Index: projects/ipfw/sbin/ipfw/tables.c =================================================================== --- projects/ipfw/sbin/ipfw/tables.c (revision 269472) +++ projects/ipfw/sbin/ipfw/tables.c (revision 269473) @@ -1,1310 +1,1404 @@ /* * Copyright (c) 2002-2003 Luigi Rizzo * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp * Copyright (c) 1994 Ugen J.S.Antsilevich * * Idea and grammar partially left from: * Copyright (c) 1993 Daniel Boulet * * Redistribution and use in source forms, with and without modification, * are permitted provided that this entire comment appears intact. * * Redistribution in binary form may occur without any restrictions. * Obviously, it would be nice if you gave credit where credit is due * but requiring it would be too onerous. * * This software is provided ``AS IS'' without any warranties of any kind. * * in-kernel tables support * * $FreeBSD: projects/ipfw/sbin/ipfw/ipfw2.c 267467 2014-06-14 10:58:39Z melifaro $ */ #include #include #include #include #include #include #include #include #include /* offsetof */ #include #include #include #include #define IPFW_INTERNAL /* Access to protected structures in ip_fw.h. */ #include #include #include /* def. of struct route */ #include #include #include #include #include "ipfw2.h" 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); 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); static void table_create(ipfw_obj_header *oh, int ac, char *av[]); static void table_lookup(ipfw_obj_header *oh, int ac, char *av[]); static int table_get_info(ipfw_obj_header *oh, ipfw_xtable_info *i); static int table_show_info(ipfw_xtable_info *i, void *arg); static void table_fill_ntlv(ipfw_obj_ntlv *ntlv, char *name, uint32_t set, uint16_t uidx); static int table_flush_one(ipfw_xtable_info *i, void *arg); static int table_show_one(ipfw_xtable_info *i, void *arg); static int table_get_list(ipfw_xtable_info *i, ipfw_obj_header *oh); static void table_show_list(ipfw_obj_header *oh, int need_header); static void table_show_entry(ipfw_xtable_info *i, ipfw_obj_tentry *tent); static void tentry_fill_key(ipfw_obj_header *oh, ipfw_obj_tentry *tent, char *key, uint8_t *ptype, uint8_t *pvtype, ipfw_xtable_info *xi); static void tentry_fill_value(ipfw_obj_header *oh, ipfw_obj_tentry *tent, char *arg, uint8_t type, uint8_t vtype); typedef int (table_cb_t)(ipfw_xtable_info *i, void *arg); static int tables_foreach(table_cb_t *f, void *arg, int sort); #ifndef s6_addr32 #define s6_addr32 __u6_addr.__u6_addr32 #endif static struct _s_x tabletypes[] = { { "cidr", IPFW_TABLE_CIDR }, { "iface", IPFW_TABLE_INTERFACE }, { "number", IPFW_TABLE_NUMBER }, { "flow", IPFW_TABLE_FLOW }, { NULL, 0 } }; static struct _s_x tablevaltypes[] = { { "dscp", IPFW_VTYPE_DSCP }, { "ip", IPFW_VTYPE_IP }, { "number", IPFW_VTYPE_U32 }, { NULL, 0 } }; static struct _s_x tablecmds[] = { { "add", TOK_ADD }, { "create", TOK_CREATE }, { "delete", TOK_DEL }, { "destroy", TOK_DESTROY }, { "flush", TOK_FLUSH }, { "info", TOK_INFO }, { "list", TOK_LIST }, { "lookup", TOK_LOOKUP }, { NULL, 0 } }; static int lookup_host (char *host, struct in_addr *ipaddr) { struct hostent *he; if (!inet_aton(host, ipaddr)) { if ((he = gethostbyname(host)) == NULL) return(-1); *ipaddr = *(struct in_addr *)he->h_addr_list[0]; } return(0); } /* * This one handles all table-related commands * ipfw table NAME create ... * ipfw table NAME destroy * ipfw table NAME add addr[/masklen] [value] * ipfw table NAME delete addr[/masklen] * ipfw table {NAME | all} flush * ipfw table {NAME | all} list * ipfw table {NAME | all} info */ void ipfw_table_handler(int ac, char *av[]) { int do_add, is_all; int error, tcmd; ipfw_xtable_info i; ipfw_obj_header oh; char *tablename; uint32_t set; memset(&oh, 0, sizeof(oh)); is_all = 0; if (co.use_set != 0) set = co.use_set - 1; else set = 0; ac--; av++; NEED1("table needs name"); tablename = *av; if (table_check_name(tablename) == 0) { table_fill_ntlv(&oh.ntlv, *av, set, 1); oh.idx = 1; } else { if (strcmp(tablename, "all") == 0) is_all = 1; else errx(EX_USAGE, "table name %s is invalid", tablename); } ac--; av++; NEED1("table needs command"); if ((tcmd = match_token(tablecmds, *av)) == -1) errx(EX_USAGE, "invalid table command %s", *av); switch (tcmd) { case TOK_LIST: case TOK_INFO: case TOK_FLUSH: break; default: if (is_all != 0) errx(EX_USAGE, "table name required"); } switch (tcmd) { case TOK_ADD: case TOK_DEL: do_add = **av == 'a'; ac--; av++; table_modify_record(&oh, ac, av, do_add, co.do_quiet); break; case TOK_CREATE: ac--; av++; table_create(&oh, ac, av); break; case TOK_DESTROY: if (table_destroy(&oh) != 0) err(EX_OSERR, "failed to destroy table %s", tablename); break; case TOK_FLUSH: if (is_all == 0) { if ((error = table_flush(&oh)) != 0) err(EX_OSERR, "failed to flush table %s info", tablename); } else { error = tables_foreach(table_flush_one, &oh, 1); if (error != 0) err(EX_OSERR, "failed to flush tables list"); } break; case TOK_INFO: if (is_all == 0) { if ((error = table_get_info(&oh, &i)) != 0) err(EX_OSERR, "failed to request table info"); table_show_info(&i, NULL); } else { error = tables_foreach(table_show_info, NULL, 1); if (error != 0) err(EX_OSERR, "failed to request tables list"); } break; case TOK_LIST: if (is_all == 0) { ipfw_xtable_info i; if ((error = table_get_info(&oh, &i)) != 0) err(EX_OSERR, "failed to request table info"); table_show_one(&i, NULL); } else { error = tables_foreach(table_show_one, NULL, 1); if (error != 0) err(EX_OSERR, "failed to request tables list"); } break; case TOK_LOOKUP: ac--; av++; table_lookup(&oh, ac, av); break; } } static void table_fill_ntlv(ipfw_obj_ntlv *ntlv, char *name, uint32_t set, uint16_t uidx) { ntlv->head.type = IPFW_TLV_TBL_NAME; ntlv->head.length = sizeof(ipfw_obj_ntlv); ntlv->idx = uidx; ntlv->set = set; strlcpy(ntlv->name, name, sizeof(ntlv->name)); } static void table_fill_objheader(ipfw_obj_header *oh, ipfw_xtable_info *i) { oh->idx = 1; table_fill_ntlv(&oh->ntlv, i->tablename, i->set, 1); } static struct _s_x tablenewcmds[] = { { "type", TOK_TYPE }, { "valtype", TOK_VALTYPE }, { "algo", TOK_ALGO }, { "limit", TOK_LIMIT }, { NULL, 0 } }; static struct _s_x flowtypecmds[] = { { "src-ip", IPFW_TFFLAG_SRCIP }, { "proto", IPFW_TFFLAG_PROTO }, { "src-port", IPFW_TFFLAG_SRCPORT }, { "dst-ip", IPFW_TFFLAG_DSTIP }, { "dst-port", IPFW_TFFLAG_DSTPORT }, { NULL, 0 } }; int table_parse_type(uint8_t ttype, char *p, uint8_t *tflags) { uint8_t fset, fclear; /* Parse type options */ switch(ttype) { case IPFW_TABLE_FLOW: fset = fclear = 0; fill_flags(flowtypecmds, p, &fset, &fclear); *tflags = fset; break; default: return (EX_USAGE); } return (0); } void table_print_type(char *tbuf, size_t size, uint8_t type, uint8_t tflags) { const char *tname; int l; if ((tname = match_value(tabletypes, type)) == NULL) tname = "unknown"; l = snprintf(tbuf, size, "%s", tname); tbuf += l; size -= l; switch(type) { case IPFW_TABLE_FLOW: if (tflags != 0) { *tbuf++ = ':'; l--; print_flags_buffer(tbuf, size, flowtypecmds, tflags); } break; } } /* * Creates new table * * ipfw table NAME create [ type { cidr | iface | u32 } ] * [ valtype { number | ip | dscp } ] * [ algo algoname ] * * Request: [ ipfw_obj_header ipfw_xtable_info ] */ static void table_create(ipfw_obj_header *oh, int ac, char *av[]) { ipfw_xtable_info xi; int error, tcmd, val; size_t sz; char *p; char tbuf[128]; sz = sizeof(tbuf); memset(&xi, 0, sizeof(xi)); /* Set some defaults to preserve compability */ xi.type = IPFW_TABLE_CIDR; xi.vtype = IPFW_VTYPE_U32; while (ac > 0) { if ((tcmd = match_token(tablenewcmds, *av)) == -1) errx(EX_USAGE, "unknown option: %s", *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 ':' */ if ((p = strchr(*av, ':')) != NULL) *p++ = '\0'; val = match_token(tabletypes, *av); if (val == -1) { concat_tokens(tbuf, sizeof(tbuf), tabletypes, ", "); errx(EX_USAGE, "Unknown tabletype: %s. Supported: %s", *av, tbuf); } xi.type = val; if (p != NULL) { error = table_parse_type(val, p, &xi.tflags); if (error != 0) errx(EX_USAGE, "Unsupported suboptions: %s", p); } ac--; av++; break; case TOK_VALTYPE: NEED1("table value type required"); val = match_token(tablevaltypes, *av); if (val != -1) { xi.vtype = val; ac--; av++; break; } concat_tokens(tbuf, sizeof(tbuf), tablevaltypes, ", "); errx(EX_USAGE, "Unknown value type: %s. Supported: %s", *av, tbuf); break; case TOK_ALGO: NEED1("table algorithm name required"); if (strlen(*av) > sizeof(xi.algoname)) errx(EX_USAGE, "algorithm name too long"); strlcpy(xi.algoname, *av, sizeof(xi.algoname)); ac--; av++; break; } } if ((error = table_do_create(oh, &xi)) != 0) err(EX_OSERR, "Table creation failed"); } /* * Creates new table * * Request: [ ipfw_obj_header ipfw_xtable_info ] * * Returns 0 on success. */ static int table_do_create(ipfw_obj_header *oh, ipfw_xtable_info *i) { char tbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_xtable_info)]; int error; memcpy(tbuf, oh, sizeof(*oh)); memcpy(tbuf + sizeof(*oh), i, sizeof(*i)); oh = (ipfw_obj_header *)tbuf; error = do_set3(IP_FW_TABLE_XCREATE, &oh->opheader, sizeof(tbuf)); return (error); } /* * Destroys given table specified by @oh->ntlv. * Returns 0 on success. */ static int table_destroy(ipfw_obj_header *oh) { if (do_set3(IP_FW_TABLE_XDESTROY, &oh->opheader, sizeof(*oh)) != 0) return (-1); return (0); } /* * Flushes given table specified by @oh->ntlv. * Returns 0 on success. */ static int table_flush(ipfw_obj_header *oh) { if (do_set3(IP_FW_TABLE_XFLUSH, &oh->opheader, sizeof(*oh)) != 0) return (-1); return (0); } /* * Retrieves table in given table specified by @oh->ntlv. * it inside @i. * Returns 0 on success. */ static int table_get_info(ipfw_obj_header *oh, ipfw_xtable_info *i) { char tbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_xtable_info)]; int error; size_t sz; sz = sizeof(tbuf); memset(tbuf, 0, sizeof(tbuf)); memcpy(tbuf, oh, sizeof(*oh)); oh = (ipfw_obj_header *)tbuf; if ((error = do_get3(IP_FW_TABLE_XINFO, &oh->opheader, &sz)) != 0) return (error); if (sz < sizeof(tbuf)) return (EINVAL); *i = *(ipfw_xtable_info *)(oh + 1); return (0); } +static struct _s_x tablealgoclass[] = { + { "hash", IPFW_TACLASS_HASH }, + { "array", IPFW_TACLASS_ARRAY }, + { "radix", IPFW_TACLASS_RADIX }, + { NULL, 0 } +}; + +struct ta_cldata { + uint8_t taclass; + uint8_t spare4; + uint16_t itemsize; + uint16_t itemsize6; + uint32_t size; + uint32_t count; +}; + /* + * Print global/per-AF table @i algorithm info. + */ +static void +table_show_tainfo(ipfw_xtable_info *i, struct ta_cldata *d, + const char *af, const char *taclass) +{ + + switch (d->taclass) { + case IPFW_TACLASS_HASH: + case IPFW_TACLASS_ARRAY: + printf(" %salgorithm %s info\n", af, taclass); + if (d->itemsize == d->itemsize6) + printf(" size: %u items: %u itemsize: %u\n", + d->size, d->count, d->itemsize); + else + printf(" size: %u items: %u " + "itemsize4: %u itemsize6: %u\n", + d->size, d->count, + d->itemsize, d->itemsize6); + break; + case IPFW_TACLASS_RADIX: + printf(" %salgorithm %s info\n", af, taclass); + if (d->itemsize == d->itemsize6) + printf(" items: %u itemsize: %u\n", + d->count, d->itemsize); + else + printf(" items: %u " + "itemsize4: %u itemsize6: %u\n", + d->count, d->itemsize, d->itemsize6); + break; + default: + printf(" algo class: %s\n", taclass); + } +} + +/* * Prints table info struct @i in human-readable form. */ static int table_show_info(ipfw_xtable_info *i, void *arg) { const char *vtype; + ipfw_ta_tinfo *tainfo; + int afdata, afitem; + struct ta_cldata d; char ttype[64]; table_print_type(ttype, sizeof(ttype), i->type, i->tflags); if ((vtype = match_value(tablevaltypes, i->vtype)) == NULL) vtype = "unknown"; printf("--- table(%s), set(%u) ---\n", i->tablename, i->set); printf(" kindex: %d, type: %s\n", i->kidx, ttype); 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); + + /* Print algo-specific info if any */ + if ((i->ta_info.flags & IPFW_TATFLAGS_DATA) == 0) + return (0); + tainfo = &i->ta_info; + + afdata = 0; + afitem = 0; + if (tainfo->flags & IPFW_TATFLAGS_AFDATA) + afdata = 1; + if (tainfo->flags & IPFW_TATFLAGS_AFITEM) + afitem = 1; + + memset(&d, 0, sizeof(d)); + d.taclass = tainfo->taclass4; + d.size = tainfo->size4; + d.count = tainfo->count4; + d.itemsize = tainfo->itemsize4; + if (afdata == 0 && afitem != 0) + d.itemsize6 = tainfo->itemsize6; + else + d.itemsize6 = d.itemsize; + if ((vtype = match_value(tablealgoclass, d.taclass)) == NULL) + vtype = "unknown"; + + if (afdata == 0) { + table_show_tainfo(i, &d, "", vtype); + } else { + table_show_tainfo(i, &d, "IPv4 ", vtype); + memset(&d, 0, sizeof(d)); + d.taclass = tainfo->taclass6; + if ((vtype = match_value(tablealgoclass, d.taclass)) == NULL) + vtype = "unknown"; + d.size = tainfo->size6; + d.count = tainfo->count6; + d.itemsize = tainfo->itemsize6; + d.itemsize6 = d.itemsize; + table_show_tainfo(i, &d, "IPv6 ", vtype); + } return (0); } /* * Function wrappers which can be used either * as is or as foreach function parameter. */ static int table_show_one(ipfw_xtable_info *i, void *arg) { ipfw_obj_header *oh; int error; if ((oh = calloc(1, i->size)) == NULL) return (ENOMEM); if ((error = table_get_list(i, oh)) != 0) { err(EX_OSERR, "Error requesting table %s list", i->tablename); return (error); } table_show_list(oh, 1); free(oh); return (0); } static int table_flush_one(ipfw_xtable_info *i, void *arg) { ipfw_obj_header *oh; oh = (ipfw_obj_header *)arg; table_fill_ntlv(&oh->ntlv, i->tablename, i->set, 1); return (table_flush(oh)); } static int table_do_modify_record(int cmd, ipfw_obj_header *oh, ipfw_obj_tentry *tent, int update) { ipfw_obj_ctlv *ctlv; char xbuf[sizeof(*oh) + sizeof(ipfw_obj_ctlv) + sizeof(*tent)]; int error; memset(xbuf, 0, sizeof(xbuf)); memcpy(xbuf, oh, sizeof(*oh)); oh = (ipfw_obj_header *)xbuf; oh->opheader.version = 1; ctlv = (ipfw_obj_ctlv *)(oh + 1); ctlv->count = 1; ctlv->head.length = sizeof(*ctlv) + sizeof(*tent); memcpy(ctlv + 1, tent, sizeof(*tent)); tent = (ipfw_obj_tentry *)(ctlv + 1); if (update != 0) tent->head.flags |= IPFW_TF_UPDATE; tent->head.length = sizeof(ipfw_obj_tentry); error = do_set3(cmd, &oh->opheader, sizeof(xbuf)); return (error); } static void 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, error; char *texterr, *etxt; if (ac == 0) errx(EX_USAGE, "address required"); memset(&tent, 0, sizeof(tent)); tent.head.length = sizeof(tent); tent.idx = 1; tentry_fill_key(oh, &tent, *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) { 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 = 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 table_do_lookup(ipfw_obj_header *oh, char *key, ipfw_xtable_info *xi, ipfw_obj_tentry *xtent) { char xbuf[sizeof(ipfw_obj_header) + sizeof(ipfw_obj_tentry)]; ipfw_obj_tentry *tent; uint8_t type, vtype; int error; size_t sz; memcpy(xbuf, oh, sizeof(*oh)); oh = (ipfw_obj_header *)xbuf; tent = (ipfw_obj_tentry *)(oh + 1); memset(tent, 0, sizeof(*tent)); tent->head.length = sizeof(*tent); tent->idx = 1; tentry_fill_key(oh, tent, key, &type, &vtype, xi); oh->ntlv.type = type; sz = sizeof(xbuf); if ((error = do_get3(IP_FW_TABLE_XFIND, &oh->opheader, &sz)) != 0) return (error); if (sz < sizeof(xbuf)) return (EINVAL); *xtent = *tent; return (0); } static void table_lookup(ipfw_obj_header *oh, int ac, char *av[]) { ipfw_obj_tentry xtent; ipfw_xtable_info xi; char key[64]; int error; if (ac == 0) errx(EX_USAGE, "address required"); strlcpy(key, *av, sizeof(key)); error = table_do_lookup(oh, key, &xi, &xtent); switch (error) { case 0: break; case ESRCH: errx(EX_UNAVAILABLE, "Table %s not found", oh->ntlv.name); case ENOENT: errx(EX_UNAVAILABLE, "Entry %s not found", *av); case ENOTSUP: errx(EX_UNAVAILABLE, "Table %s algo does not support " "\"lookup\" method", oh->ntlv.name); default: err(EX_OSERR, "getsockopt(IP_FW_TABLE_XFIND)"); } table_show_entry(&xi, &xtent); } static void tentry_fill_key_type(char *arg, ipfw_obj_tentry *tentry, uint8_t type, uint8_t tflags) { char *p, *pp; int mask, af; struct in6_addr *paddr, tmp; struct tflow_entry *tfe; uint32_t key, *pkey; uint16_t port; struct protoent *pent; struct servent *sent; int masklen; masklen = 0; af = 0; paddr = (struct in6_addr *)&tentry->k; switch (type) { case IPFW_TABLE_CIDR: /* Remove / if exists */ if ((p = strchr(arg, '/')) != NULL) { *p = '\0'; mask = atoi(p + 1); } if (inet_pton(AF_INET, arg, paddr) == 1) { if (p != NULL && mask > 32) errx(EX_DATAERR, "bad IPv4 mask width: %s", p + 1); masklen = p ? mask : 32; af = AF_INET; } else if (inet_pton(AF_INET6, arg, paddr) == 1) { if (IN6_IS_ADDR_V4COMPAT(paddr)) errx(EX_DATAERR, "Use IPv4 instead of v4-compatible"); if (p != NULL && mask > 128) errx(EX_DATAERR, "bad IPv6 mask width: %s", p + 1); masklen = p ? mask : 128; af = AF_INET6; } else { /* Assume FQDN */ if (lookup_host(arg, (struct in_addr *)paddr) != 0) errx(EX_NOHOST, "hostname ``%s'' unknown", arg); masklen = 32; type = IPFW_TABLE_CIDR; af = AF_INET; } break; case IPFW_TABLE_INTERFACE: /* Assume interface name. Copy significant data only */ mask = MIN(strlen(arg), IF_NAMESIZE - 1); memcpy(paddr, arg, mask); /* Set mask to exact match */ masklen = 8 * IF_NAMESIZE; break; case IPFW_TABLE_NUMBER: /* Port or any other key */ key = strtol(arg, &p, 10); if (*p != '\0') errx(EX_DATAERR, "Invalid number: %s", arg); pkey = (uint32_t *)paddr; *pkey = key; masklen = 32; break; case IPFW_TABLE_FLOW: /* Assume [src-ip][,proto][,src-port][,dst-ip][,dst-port] */ tfe = &tentry->k.flow; af = 0; /* Handle */ if ((tflags & IPFW_TFFLAG_SRCIP) != 0) { if ((p = strchr(arg, ',')) != NULL) *p++ = '\0'; /* Determine family using temporary storage */ if (inet_pton(AF_INET, arg, &tmp) == 1) { if (af != 0 && af != AF_INET) errx(EX_DATAERR, "Inconsistent address family\n"); af = AF_INET; memcpy(&tfe->a.a4.sip, &tmp, 4); } else if (inet_pton(AF_INET6, arg, &tmp) == 1) { if (af != 0 && af != AF_INET6) errx(EX_DATAERR, "Inconsistent address family\n"); af = AF_INET6; memcpy(&tfe->a.a6.sip6, &tmp, 16); } arg = p; } /* Handle */ if ((tflags & IPFW_TFFLAG_PROTO) != 0) { if ((p = strchr(arg, ',')) != NULL) *p++ = '\0'; key = strtol(arg, &pp, 10); if (*pp != '\0') { if ((pent = getprotobyname(arg)) == NULL) errx(EX_DATAERR, "Unknown proto: %s", arg); else key = pent->p_proto; } if (key > 255) errx(EX_DATAERR, "Bad protocol number: %u",key); tfe->proto = key; arg = p; } /* Handle */ if ((tflags & IPFW_TFFLAG_SRCPORT) != 0) { if ((p = strchr(arg, ',')) != NULL) *p++ = '\0'; if ((port = htons(strtol(arg, NULL, 10))) == 0) { if ((sent = getservbyname(arg, NULL)) == NULL) errx(EX_DATAERR, "Unknown service: %s", arg); else key = sent->s_port; } tfe->sport = port; arg = p; } /* Handle */ if ((tflags & IPFW_TFFLAG_DSTIP) != 0) { if ((p = strchr(arg, ',')) != NULL) *p++ = '\0'; /* Determine family using temporary storage */ if (inet_pton(AF_INET, arg, &tmp) == 1) { if (af != 0 && af != AF_INET) errx(EX_DATAERR, "Inconsistent address family"); af = AF_INET; memcpy(&tfe->a.a4.dip, &tmp, 4); } else if (inet_pton(AF_INET6, arg, &tmp) == 1) { if (af != 0 && af != AF_INET6) errx(EX_DATAERR, "Inconsistent address family"); af = AF_INET6; memcpy(&tfe->a.a6.dip6, &tmp, 16); } arg = p; } /* Handle */ if ((tflags & IPFW_TFFLAG_DSTPORT) != 0) { if ((p = strchr(arg, ',')) != NULL) *p++ = '\0'; if ((port = htons(strtol(arg, NULL, 10))) == 0) { if ((sent = getservbyname(arg, NULL)) == NULL) errx(EX_DATAERR, "Unknown service: %s", arg); else key = sent->s_port; } tfe->dport = port; arg = p; } tfe->af = af; break; default: errx(EX_DATAERR, "Unsupported table type: %d", type); } tentry->subtype = af; tentry->masklen = masklen; } static void tentry_fill_key(ipfw_obj_header *oh, ipfw_obj_tentry *tent, char *key, uint8_t *ptype, uint8_t *pvtype, ipfw_xtable_info *xi) { uint8_t type, tflags, vtype; int error; char *del; type = 0; tflags = 0; vtype = 0; error = table_get_info(oh, xi); if (error == 0) { /* Table found. */ type = xi->type; tflags = xi->tflags; vtype = xi->vtype; } else { if (error != ESRCH) errx(EX_OSERR, "Error requesting table %s info", oh->ntlv.name); /* * Table does not exist. * Compability layer: try to interpret data as CIDR * before failing. */ if ((del = strchr(key, '/')) != NULL) *del = '\0'; if (inet_pton(AF_INET, key, &tent->k.addr6) == 1 || inet_pton(AF_INET6, key, &tent->k.addr6) == 1) { /* OK Prepare and send */ type = IPFW_TABLE_CIDR; /* * XXX: Value type is forced to be u32. * This should be changed for MFC. */ vtype = IPFW_VTYPE_U32; } else { /* Inknown key */ errx(EX_USAGE, "Table %s does not exist, cannot guess " "key '%s' type", oh->ntlv.name, key); } if (del != NULL) *del = '/'; } tentry_fill_key_type(key, tent, type, tflags); *ptype = type; *pvtype = vtype; } static void tentry_fill_value(ipfw_obj_header *oh, ipfw_obj_tentry *tent, char *arg, uint8_t type, uint8_t vtype) { int code; char *p; switch (vtype) { case IPFW_VTYPE_U32: tent->value = strtoul(arg, &p, 0); if (*p != '\0') errx(EX_USAGE, "Invalid number: %s", arg); break; case IPFW_VTYPE_IP: if (inet_pton(AF_INET, arg, &tent->value) == 1) break; /* Try hostname */ if (lookup_host(arg, (struct in_addr *)&tent->value) != 0) errx(EX_USAGE, "Invalid IPv4 address: %s", arg); break; case IPFW_VTYPE_DSCP: if (isalpha(*arg)) { if ((code = match_token(f_ipdscp, arg)) == -1) errx(EX_DATAERR, "Unknown DSCP code"); } else { code = strtoul(arg, NULL, 10); if (code < 0 || code > 63) errx(EX_DATAERR, "Invalid DSCP value"); } tent->value = code; break; default: errx(EX_OSERR, "Unsupported format type %d", vtype); } } /* * Compare table names. * Honor number comparison. */ static int tablename_cmp(const void *a, const void *b) { ipfw_xtable_info *ia, *ib; ia = (ipfw_xtable_info *)a; ib = (ipfw_xtable_info *)b; return (stringnum_cmp(ia->tablename, ib->tablename)); } /* * Retrieves table list from kernel, * optionally sorts it and calls requested function for each table. * Returns 0 on success. */ static int tables_foreach(table_cb_t *f, void *arg, int sort) { ipfw_obj_lheader req, *olh; ipfw_xtable_info *info; size_t sz; int i, error; memset(&req, 0, sizeof(req)); sz = sizeof(req); if ((error = do_get3(IP_FW_TABLES_XGETSIZE, &req.opheader, &sz)) != 0) return (errno); sz = req.size; if ((olh = calloc(1, sz)) == NULL) return (ENOMEM); olh->size = sz; if ((error = do_get3(IP_FW_TABLES_XLIST, &olh->opheader, &sz)) != 0) { free(olh); return (errno); } if (sort != 0) qsort(olh + 1, olh->count, olh->objsize, tablename_cmp); info = (ipfw_xtable_info *)(olh + 1); for (i = 0; i < olh->count; i++) { error = f(info, arg); /* Ignore errors for now */ info = (ipfw_xtable_info *)((caddr_t)info + olh->objsize); } free(olh); return (0); } /* * Retrieves all entries for given table @i in * eXtended format. Assumes buffer of size * @i->size has already been allocated by caller. * * Returns 0 on success. */ static int table_get_list(ipfw_xtable_info *i, ipfw_obj_header *oh) { size_t sz; int error, c; sz = 0; for (c = 0; c < 3; c++) { table_fill_objheader(oh, i); if (sz < i->size) sz = i->size; oh->opheader.version = 1; /* Current version */ error = do_get3(IP_FW_TABLE_XLIST, &oh->opheader, &sz); if (error != ENOMEM) return (errno); } return (ENOMEM); } /* * Shows all entries from @oh in human-readable format */ static void table_show_list(ipfw_obj_header *oh, int need_header) { ipfw_obj_tentry *tent; uint32_t count; ipfw_xtable_info *i; i = (ipfw_xtable_info *)(oh + 1); tent = (ipfw_obj_tentry *)(i + 1); if (need_header) printf("--- table(%s), set(%u) ---\n", i->tablename, i->set); count = i->count; while (count > 0) { table_show_entry(i, tent); tent = (ipfw_obj_tentry *)((caddr_t)tent + tent->head.length); count--; } } static void table_show_entry(ipfw_xtable_info *i, ipfw_obj_tentry *tent) { char *comma, tbuf[128], pval[32]; void *paddr; uint32_t tval; struct tflow_entry *tfe; tval = tent->value; if (co.do_value_as_ip) { tval = htonl(tval); inet_ntop(AF_INET, &tval, pval, sizeof(pval)); } else snprintf(pval, sizeof(pval), "%u", tval); switch (i->type) { case IPFW_TABLE_CIDR: /* IPv4 or IPv6 prefixes */ inet_ntop(tent->subtype, &tent->k, tbuf, sizeof(tbuf)); printf("%s/%u %s\n", tbuf, tent->masklen, pval); break; case IPFW_TABLE_INTERFACE: /* Interface names */ printf("%s %s\n", tent->k.iface, pval); break; case IPFW_TABLE_NUMBER: /* numbers */ printf("%u %s\n", tent->k.key, pval); break; case IPFW_TABLE_FLOW: /* flows */ tfe = &tent->k.flow; comma = ""; if ((i->tflags & IPFW_TFFLAG_SRCIP) != 0) { if (tfe->af == AF_INET) paddr = &tfe->a.a4.sip; else paddr = &tfe->a.a6.sip6; inet_ntop(tfe->af, paddr, tbuf, sizeof(tbuf)); printf("%s%s", comma, tbuf); comma = ","; } if ((i->tflags & IPFW_TFFLAG_PROTO) != 0) { printf("%s%d", comma, tfe->proto); comma = ","; } if ((i->tflags & IPFW_TFFLAG_SRCPORT) != 0) { printf("%s%d", comma, ntohs(tfe->sport)); comma = ","; } if ((i->tflags & IPFW_TFFLAG_DSTIP) != 0) { if (tfe->af == AF_INET) paddr = &tfe->a.a4.dip; else paddr = &tfe->a.a6.dip6; inet_ntop(tfe->af, paddr, tbuf, sizeof(tbuf)); printf("%s%s", comma, tbuf); comma = ","; } if ((i->tflags & IPFW_TFFLAG_DSTPORT) != 0) { printf("%s%d", comma, ntohs(tfe->dport)); comma = ","; } printf(" %s\n", pval); } } static int table_do_get_algolist(ipfw_obj_lheader **polh) { ipfw_obj_lheader req, *olh; size_t sz; int error; memset(&req, 0, sizeof(req)); sz = sizeof(req); error = do_get3(IP_FW_TABLES_ALIST, &req.opheader, &sz); if (error != 0 && error != ENOMEM) return (error); sz = req.size; if ((olh = calloc(1, sz)) == NULL) return (ENOMEM); olh->size = sz; if ((error = do_get3(IP_FW_TABLES_ALIST, &olh->opheader, &sz)) != 0) { free(olh); return (error); } *polh = olh; return (0); } void ipfw_list_ta(int ac, char *av[]) { ipfw_obj_lheader *olh; ipfw_ta_info *info; int error, i; const char *atype; error = table_do_get_algolist(&olh); if (error != 0) err(EX_OSERR, "Unable to request algorithm list"); info = (ipfw_ta_info *)(olh + 1); for (i = 0; i < olh->count; i++) { if ((atype = match_value(tabletypes, info->type)) == NULL) atype = "unknown"; printf("--- %s ---\n", info->algoname); printf(" type: %s\n refcount: %u\n", atype, info->refcnt); info = (ipfw_ta_info *)((caddr_t)info + olh->objsize); } free(olh); } 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_kntlv(const void *k, const void *v) { ipfw_obj_ntlv *ntlv; uint16_t key; key = *((uint16_t *)k); ntlv = (ipfw_obj_ntlv *)v; if (key < ntlv->idx) return (-1); else if (key > ntlv->idx) return (1); return (0); } /* * Finds table name in @ctlv by @idx. * Uses the following facts: * 1) All TLVs are the same size * 2) Kernel implementation provides already sorted list. * * Returns table name or NULL. */ char * table_search_ctlv(ipfw_obj_ctlv *ctlv, uint16_t idx) { ipfw_obj_ntlv *ntlv; ntlv = bsearch(&idx, (ctlv + 1), ctlv->count, ctlv->objsize, compare_kntlv); if (ntlv != 0) return (ntlv->name); 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-Z0-9\-_\.]{1,63} */ l = strlen(tablename); if (l == 0 || l >= 64) return (EINVAL); for (i = 0; i < l; i++) { c = tablename[i]; if (isalpha(c) || isdigit(c) || c == '_' || c == '-' || c == '.') continue; return (EINVAL); } /* Restrict some 'special' names */ if (strcmp(tablename, "all") == 0) return (EINVAL); return (0); } Index: projects/ipfw/sys/netinet/ip_fw.h =================================================================== --- projects/ipfw/sys/netinet/ip_fw.h (revision 269472) +++ projects/ipfw/sys/netinet/ip_fw.h (revision 269473) @@ -1,892 +1,895 @@ /*- * Copyright (c) 2002-2009 Luigi Rizzo, Universita` di Pisa * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _IPFW2_H #define _IPFW2_H /* * The default rule number. By the design of ip_fw, the default rule * is the last one, so its number can also serve as the highest number * allowed for a rule. The ip_fw code relies on both meanings of this * constant. */ #define IPFW_DEFAULT_RULE 65535 #define RESVD_SET 31 /*set for default and persistent rules*/ #define IPFW_MAX_SETS 32 /* Number of sets supported by ipfw*/ /* * Default number of ipfw tables. */ #define IPFW_TABLES_MAX 65535 #define IPFW_TABLES_DEFAULT 128 /* * Most commands (queue, pipe, tag, untag, limit...) can have a 16-bit * argument between 1 and 65534. The value 0 is unused, the value * 65535 (IP_FW_TABLEARG) is used to represent 'tablearg', i.e. the * can be 1..65534, or 65535 to indicate the use of a 'tablearg' * result of the most recent table() lookup. * Note that 16bit is only a historical limit, resulting from * the use of a 16-bit fields for that value. In reality, we can have * 2^32 pipes, queues, tag values and so on, and use 0 as a tablearg. */ #define IPFW_ARG_MIN 1 #define IPFW_ARG_MAX 65534 #define IP_FW_TABLEARG 65535 /* XXX should use 0 */ /* * Number of entries in the call stack of the call/return commands. * Call stack currently is an uint16_t array with rule numbers. */ #define IPFW_CALLSTACK_SIZE 16 /* IP_FW3 header/opcodes */ typedef struct _ip_fw3_opheader { uint16_t opcode; /* Operation opcode */ uint16_t version; /* Opcode version */ uint16_t reserved[2]; /* Align to 64-bit boundary */ } ip_fw3_opheader; /* IPFW extented tables support */ #define IP_FW_TABLE_XADD 86 /* add entry */ #define IP_FW_TABLE_XDEL 87 /* delete entry */ #define IP_FW_TABLE_XGETSIZE 88 /* get table size (deprecated) */ #define IP_FW_TABLE_XLIST 89 /* list table contents */ #define IP_FW_TABLE_XDESTROY 90 /* destroy table */ #define IP_FW_TABLES_XGETSIZE 91 /* get size for tables list */ #define IP_FW_TABLES_XLIST 92 /* list all tables */ #define IP_FW_TABLE_XINFO 93 /* request info for one table */ #define IP_FW_TABLE_XFLUSH 94 /* flush table data */ #define IP_FW_TABLE_XCREATE 95 /* create new table */ //#define IP_FW_TABLE_XMODIFY 96 /* modify existing table */ #define IP_FW_XGET 97 /* Retrieve configuration */ #define IP_FW_XADD 98 /* add entry */ #define IP_FW_TABLE_XFIND 99 /* finds an entry */ #define IP_FW_XIFLIST 100 /* list tracked interfaces */ #define IP_FW_TABLES_ALIST 101 /* list table algorithms */ /* * Usage guidelines: * * IP_FW_TABLE_XLIST(ver 1): Dumps all table data * Request(getsockopt): [ ipfw_obj_lheader ], size = ipfw_xtable_info.size * Reply: [ ipfw_obj_lheader ipfw_xtable_info ipfw_table_xentry x N ] * * IP_FW_TABLE_XDESTROY: Destroys given table * Request(setsockopt): [ ipfw_obj_header ] * * IP_FW_TABLES_XGETSIZE: Get buffer size needed to list info for all tables. * Request(getsockopt): [ empty ], size = sizeof(ipfw_obj_lheader) * Reply: [ ipfw_obj_lheader ] * * IP_FW_TABLES_XLIST: Lists all tables currently available in kernel. * Request(getsockopt): [ ipfw_obj_lheader ], size = ipfw_obj_lheader.size * Reply: [ ipfw_obj_lheader ipfw_xtable_info x N ] * * IP_FW_TABLE_XINFO: Store table info to buffer. * Request(getsockopt): [ ipfw_obj_header ipfw_xtable_info(empty)] * Reply: [ ipfw_obj_header ipfw_xtable_info ] * * IP_FW_TABLE_XFLUSH: Removes all data from given table leaving type etc.. * Request(setsockopt): [ ipfw_obj_header ] */ /* * The kernel representation of ipfw rules is made of a list of * 'instructions' (for all practical purposes equivalent to BPF * instructions), which specify which fields of the packet * (or its metadata) should be analysed. * * Each instruction is stored in a structure which begins with * "ipfw_insn", and can contain extra fields depending on the * instruction type (listed below). * Note that the code is written so that individual instructions * have a size which is a multiple of 32 bits. This means that, if * such structures contain pointers or other 64-bit entities, * (there is just one instance now) they may end up unaligned on * 64-bit architectures, so the must be handled with care. * * "enum ipfw_opcodes" are the opcodes supported. We can have up * to 256 different opcodes. When adding new opcodes, they should * be appended to the end of the opcode list before O_LAST_OPCODE, * this will prevent the ABI from being broken, otherwise users * will have to recompile ipfw(8) when they update the kernel. */ enum ipfw_opcodes { /* arguments (4 byte each) */ O_NOP, O_IP_SRC, /* u32 = IP */ O_IP_SRC_MASK, /* ip = IP/mask */ O_IP_SRC_ME, /* none */ O_IP_SRC_SET, /* u32=base, arg1=len, bitmap */ O_IP_DST, /* u32 = IP */ O_IP_DST_MASK, /* ip = IP/mask */ O_IP_DST_ME, /* none */ O_IP_DST_SET, /* u32=base, arg1=len, bitmap */ O_IP_SRCPORT, /* (n)port list:mask 4 byte ea */ O_IP_DSTPORT, /* (n)port list:mask 4 byte ea */ O_PROTO, /* arg1=protocol */ O_MACADDR2, /* 2 mac addr:mask */ O_MAC_TYPE, /* same as srcport */ O_LAYER2, /* none */ O_IN, /* none */ O_FRAG, /* none */ O_RECV, /* none */ O_XMIT, /* none */ O_VIA, /* none */ O_IPOPT, /* arg1 = 2*u8 bitmap */ O_IPLEN, /* arg1 = len */ O_IPID, /* arg1 = id */ O_IPTOS, /* arg1 = id */ O_IPPRECEDENCE, /* arg1 = precedence << 5 */ O_IPTTL, /* arg1 = TTL */ O_IPVER, /* arg1 = version */ O_UID, /* u32 = id */ O_GID, /* u32 = id */ O_ESTAB, /* none (tcp established) */ O_TCPFLAGS, /* arg1 = 2*u8 bitmap */ O_TCPWIN, /* arg1 = desired win */ O_TCPSEQ, /* u32 = desired seq. */ O_TCPACK, /* u32 = desired seq. */ O_ICMPTYPE, /* u32 = icmp bitmap */ O_TCPOPTS, /* arg1 = 2*u8 bitmap */ O_VERREVPATH, /* none */ O_VERSRCREACH, /* none */ O_PROBE_STATE, /* none */ O_KEEP_STATE, /* none */ O_LIMIT, /* ipfw_insn_limit */ O_LIMIT_PARENT, /* dyn_type, not an opcode. */ /* * These are really 'actions'. */ O_LOG, /* ipfw_insn_log */ O_PROB, /* u32 = match probability */ O_CHECK_STATE, /* none */ O_ACCEPT, /* none */ O_DENY, /* none */ O_REJECT, /* arg1=icmp arg (same as deny) */ O_COUNT, /* none */ O_SKIPTO, /* arg1=next rule number */ O_PIPE, /* arg1=pipe number */ O_QUEUE, /* arg1=queue number */ O_DIVERT, /* arg1=port number */ O_TEE, /* arg1=port number */ O_FORWARD_IP, /* fwd sockaddr */ O_FORWARD_MAC, /* fwd mac */ O_NAT, /* nope */ O_REASS, /* none */ /* * More opcodes. */ O_IPSEC, /* has ipsec history */ O_IP_SRC_LOOKUP, /* arg1=table number, u32=value */ O_IP_DST_LOOKUP, /* arg1=table number, u32=value */ O_ANTISPOOF, /* none */ O_JAIL, /* u32 = id */ O_ALTQ, /* u32 = altq classif. qid */ O_DIVERTED, /* arg1=bitmap (1:loop, 2:out) */ O_TCPDATALEN, /* arg1 = tcp data len */ O_IP6_SRC, /* address without mask */ O_IP6_SRC_ME, /* my addresses */ O_IP6_SRC_MASK, /* address with the mask */ O_IP6_DST, O_IP6_DST_ME, O_IP6_DST_MASK, O_FLOW6ID, /* for flow id tag in the ipv6 pkt */ O_ICMP6TYPE, /* icmp6 packet type filtering */ O_EXT_HDR, /* filtering for ipv6 extension header */ O_IP6, /* * actions for ng_ipfw */ O_NETGRAPH, /* send to ng_ipfw */ O_NGTEE, /* copy to ng_ipfw */ O_IP4, O_UNREACH6, /* arg1=icmpv6 code arg (deny) */ O_TAG, /* arg1=tag number */ O_TAGGED, /* arg1=tag number */ O_SETFIB, /* arg1=FIB number */ O_FIB, /* arg1=FIB desired fib number */ O_SOCKARG, /* socket argument */ O_CALLRETURN, /* arg1=called rule number */ O_FORWARD_IP6, /* fwd sockaddr_in6 */ O_DSCP, /* 2 u32 = DSCP mask */ O_SETDSCP, /* arg1=DSCP value */ O_IP_FLOW_LOOKUP, /* arg1=table number, u32=value */ O_LAST_OPCODE /* not an opcode! */ }; /* * The extension header are filtered only for presence using a bit * vector with a flag for each header. */ #define EXT_FRAGMENT 0x1 #define EXT_HOPOPTS 0x2 #define EXT_ROUTING 0x4 #define EXT_AH 0x8 #define EXT_ESP 0x10 #define EXT_DSTOPTS 0x20 #define EXT_RTHDR0 0x40 #define EXT_RTHDR2 0x80 /* * Template for instructions. * * ipfw_insn is used for all instructions which require no operands, * a single 16-bit value (arg1), or a couple of 8-bit values. * * For other instructions which require different/larger arguments * we have derived structures, ipfw_insn_*. * * The size of the instruction (in 32-bit words) is in the low * 6 bits of "len". The 2 remaining bits are used to implement * NOT and OR on individual instructions. Given a type, you can * compute the length to be put in "len" using F_INSN_SIZE(t) * * F_NOT negates the match result of the instruction. * * F_OR is used to build or blocks. By default, instructions * are evaluated as part of a logical AND. An "or" block * { X or Y or Z } contains F_OR set in all but the last * instruction of the block. A match will cause the code * to skip past the last instruction of the block. * * NOTA BENE: in a couple of places we assume that * sizeof(ipfw_insn) == sizeof(u_int32_t) * this needs to be fixed. * */ typedef struct _ipfw_insn { /* template for instructions */ u_int8_t opcode; u_int8_t len; /* number of 32-bit words */ #define F_NOT 0x80 #define F_OR 0x40 #define F_LEN_MASK 0x3f #define F_LEN(cmd) ((cmd)->len & F_LEN_MASK) u_int16_t arg1; } ipfw_insn; /* * The F_INSN_SIZE(type) computes the size, in 4-byte words, of * a given type. */ #define F_INSN_SIZE(t) ((sizeof (t))/sizeof(u_int32_t)) /* * This is used to store an array of 16-bit entries (ports etc.) */ typedef struct _ipfw_insn_u16 { ipfw_insn o; u_int16_t ports[2]; /* there may be more */ } ipfw_insn_u16; /* * This is used to store an array of 32-bit entries * (uid, single IPv4 addresses etc.) */ typedef struct _ipfw_insn_u32 { ipfw_insn o; u_int32_t d[1]; /* one or more */ } ipfw_insn_u32; /* * This is used to store IP addr-mask pairs. */ typedef struct _ipfw_insn_ip { ipfw_insn o; struct in_addr addr; struct in_addr mask; } ipfw_insn_ip; /* * This is used to forward to a given address (ip). */ typedef struct _ipfw_insn_sa { ipfw_insn o; struct sockaddr_in sa; } ipfw_insn_sa; /* * This is used to forward to a given address (ipv6). */ typedef struct _ipfw_insn_sa6 { ipfw_insn o; struct sockaddr_in6 sa; } ipfw_insn_sa6; /* * This is used for MAC addr-mask pairs. */ typedef struct _ipfw_insn_mac { ipfw_insn o; u_char addr[12]; /* dst[6] + src[6] */ u_char mask[12]; /* dst[6] + src[6] */ } ipfw_insn_mac; /* * This is used for interface match rules (recv xx, xmit xx). */ typedef struct _ipfw_insn_if { ipfw_insn o; union { struct in_addr ip; int glob; } p; char name[IFNAMSIZ]; } ipfw_insn_if; /* * This is used for storing an altq queue id number. */ typedef struct _ipfw_insn_altq { ipfw_insn o; u_int32_t qid; } ipfw_insn_altq; /* * This is used for limit rules. */ typedef struct _ipfw_insn_limit { ipfw_insn o; u_int8_t _pad; u_int8_t limit_mask; /* combination of DYN_* below */ #define DYN_SRC_ADDR 0x1 #define DYN_SRC_PORT 0x2 #define DYN_DST_ADDR 0x4 #define DYN_DST_PORT 0x8 u_int16_t conn_limit; } ipfw_insn_limit; /* * This is used for log instructions. */ typedef struct _ipfw_insn_log { ipfw_insn o; u_int32_t max_log; /* how many do we log -- 0 = all */ u_int32_t log_left; /* how many left to log */ } ipfw_insn_log; /* * Data structures required by both ipfw(8) and ipfw(4) but not part of the * management API are protected by IPFW_INTERNAL. */ #ifdef IPFW_INTERNAL /* Server pool support (LSNAT). */ struct cfg_spool { LIST_ENTRY(cfg_spool) _next; /* chain of spool instances */ struct in_addr addr; u_short port; }; #endif /* Redirect modes id. */ #define REDIR_ADDR 0x01 #define REDIR_PORT 0x02 #define REDIR_PROTO 0x04 #ifdef IPFW_INTERNAL /* Nat redirect configuration. */ struct cfg_redir { LIST_ENTRY(cfg_redir) _next; /* chain of redir instances */ u_int16_t mode; /* type of redirect mode */ struct in_addr laddr; /* local ip address */ struct in_addr paddr; /* public ip address */ struct in_addr raddr; /* remote ip address */ u_short lport; /* local port */ u_short pport; /* public port */ u_short rport; /* remote port */ u_short pport_cnt; /* number of public ports */ u_short rport_cnt; /* number of remote ports */ int proto; /* protocol: tcp/udp */ struct alias_link **alink; /* num of entry in spool chain */ u_int16_t spool_cnt; /* chain of spool instances */ LIST_HEAD(spool_chain, cfg_spool) spool_chain; }; #endif #ifdef IPFW_INTERNAL /* Nat configuration data struct. */ struct cfg_nat { /* chain of nat instances */ LIST_ENTRY(cfg_nat) _next; int id; /* nat id */ struct in_addr ip; /* nat ip address */ char if_name[IF_NAMESIZE]; /* interface name */ int mode; /* aliasing mode */ struct libalias *lib; /* libalias instance */ /* number of entry in spool chain */ int redir_cnt; /* chain of redir instances */ LIST_HEAD(redir_chain, cfg_redir) redir_chain; }; #endif #define SOF_NAT sizeof(struct cfg_nat) #define SOF_REDIR sizeof(struct cfg_redir) #define SOF_SPOOL sizeof(struct cfg_spool) /* Nat command. */ typedef struct _ipfw_insn_nat { ipfw_insn o; struct cfg_nat *nat; } ipfw_insn_nat; /* Apply ipv6 mask on ipv6 addr */ #define APPLY_MASK(addr,mask) \ (addr)->__u6_addr.__u6_addr32[0] &= (mask)->__u6_addr.__u6_addr32[0]; \ (addr)->__u6_addr.__u6_addr32[1] &= (mask)->__u6_addr.__u6_addr32[1]; \ (addr)->__u6_addr.__u6_addr32[2] &= (mask)->__u6_addr.__u6_addr32[2]; \ (addr)->__u6_addr.__u6_addr32[3] &= (mask)->__u6_addr.__u6_addr32[3]; /* Structure for ipv6 */ typedef struct _ipfw_insn_ip6 { ipfw_insn o; struct in6_addr addr6; struct in6_addr mask6; } ipfw_insn_ip6; /* Used to support icmp6 types */ typedef struct _ipfw_insn_icmp6 { ipfw_insn o; uint32_t d[7]; /* XXX This number si related to the netinet/icmp6.h * define ICMP6_MAXTYPE * as follows: n = ICMP6_MAXTYPE/32 + 1 * Actually is 203 */ } ipfw_insn_icmp6; /* * Here we have the structure representing an ipfw rule. * * Layout: * struct ip_fw_rule * [ counter block, size = rule->cntr_len ] * [ one or more instructions, size = rule->cmd_len * 4 ] * * It starts with a general area (with link fields). * Counter block may be next (if rule->cntr_len > 0), * followed by an array of one or more instructions, which the code * accesses as an array of 32-bit values. rule->cmd_len represents * the total instructions legth in u32 worrd, while act_ofs represents * rule action offset in u32 words. * * When assembling instruction, remember the following: * * + if a rule has a "keep-state" (or "limit") option, then the * first instruction (at r->cmd) MUST BE an O_PROBE_STATE * + if a rule has a "log" option, then the first action * (at ACTION_PTR(r)) MUST be O_LOG * + if a rule has an "altq" option, it comes after "log" * + if a rule has an O_TAG option, it comes after "log" and "altq" * * * All structures (excluding instructions) are u64-aligned. * Please keep this. */ struct ip_fw_rule { uint16_t act_ofs; /* offset of action in 32-bit units */ uint16_t cmd_len; /* # of 32-bit words in cmd */ uint16_t spare; uint8_t set; /* rule set (0..31) */ uint8_t flags; /* rule flags */ uint32_t rulenum; /* rule number */ uint32_t id; /* rule id */ ipfw_insn cmd[1]; /* storage for commands */ }; #define IPFW_RULE_NOOPT 0x01 /* Has no options in body */ /* Unaligned version */ /* Base ipfw rule counter block. */ struct ip_fw_bcounter { uint16_t size; /* Size of counter block, bytes */ uint8_t flags; /* flags for given block */ uint8_t spare; uint32_t timestamp; /* tv_sec of last match */ uint64_t pcnt; /* Packet counter */ uint64_t bcnt; /* Byte counter */ }; #ifndef _KERNEL /* * Legacy rule format */ struct ip_fw { struct ip_fw *x_next; /* linked list of rules */ struct ip_fw *next_rule; /* ptr to next [skipto] rule */ /* 'next_rule' is used to pass up 'set_disable' status */ 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 _pad; /* padding */ uint32_t id; /* rule id */ /* These fields are present in all rules. */ uint64_t pcnt; /* Packet counter */ uint64_t bcnt; /* Byte counter */ uint32_t timestamp; /* tv_sec of last match */ ipfw_insn cmd[1]; /* storage for commands */ }; #endif #define ACTION_PTR(rule) \ (ipfw_insn *)( (u_int32_t *)((rule)->cmd) + ((rule)->act_ofs) ) #define RULESIZE(rule) (sizeof(*(rule)) + (rule)->cmd_len * 4 - 4) #if 1 // should be moved to in.h /* * This structure is used as a flow mask and a flow id for various * parts of the code. * addr_type is used in userland and kernel to mark the address type. * fib is used in the kernel to record the fib in use. * _flags is used in the kernel to store tcp flags for dynamic rules. */ struct ipfw_flow_id { uint32_t dst_ip; uint32_t src_ip; uint16_t dst_port; uint16_t src_port; uint8_t fib; uint8_t proto; uint8_t _flags; /* protocol-specific flags */ uint8_t addr_type; /* 4=ip4, 6=ip6, 1=ether ? */ struct in6_addr dst_ip6; struct in6_addr src_ip6; uint32_t flow_id6; uint32_t extra; /* queue/pipe or frag_id */ }; #endif #define IS_IP6_FLOW_ID(id) ((id)->addr_type == 6) /* * Dynamic ipfw rule. */ typedef struct _ipfw_dyn_rule ipfw_dyn_rule; struct _ipfw_dyn_rule { ipfw_dyn_rule *next; /* linked list of rules. */ struct ip_fw *rule; /* pointer to rule */ /* 'rule' is used to pass up the rule number (from the parent) */ ipfw_dyn_rule *parent; /* pointer to parent rule */ u_int64_t pcnt; /* packet match counter */ u_int64_t bcnt; /* byte match counter */ struct ipfw_flow_id id; /* (masked) flow id */ u_int32_t expire; /* expire time */ u_int32_t bucket; /* which bucket in hash table */ u_int32_t state; /* state of this rule (typically a * combination of TCP flags) */ u_int32_t ack_fwd; /* most recent ACKs in forward */ u_int32_t ack_rev; /* and reverse directions (used */ /* to generate keepalives) */ u_int16_t dyn_type; /* rule type */ u_int16_t count; /* refcount */ }; /* * Definitions for IP option names. */ #define IP_FW_IPOPT_LSRR 0x01 #define IP_FW_IPOPT_SSRR 0x02 #define IP_FW_IPOPT_RR 0x04 #define IP_FW_IPOPT_TS 0x08 /* * Definitions for TCP option names. */ #define IP_FW_TCPOPT_MSS 0x01 #define IP_FW_TCPOPT_WINDOW 0x02 #define IP_FW_TCPOPT_SACK 0x04 #define IP_FW_TCPOPT_TS 0x08 #define IP_FW_TCPOPT_CC 0x10 #define ICMP_REJECT_RST 0x100 /* fake ICMP code (send a TCP RST) */ #define ICMP6_UNREACH_RST 0x100 /* fake ICMPv6 code (send a TCP RST) */ /* * These are used for lookup tables. */ #define IPFW_TABLE_CIDR 1 /* Table for holding IPv4/IPv6 prefixes */ #define IPFW_TABLE_INTERFACE 2 /* Table for holding interface names */ #define IPFW_TABLE_NUMBER 3 /* Table for holding ports/uid/gid/etc */ #define IPFW_TABLE_FLOW 4 /* Table for holding flow data */ #define IPFW_TABLE_MAXTYPE 4 /* Maximum valid number */ #define IPFW_VTYPE_U32 1 /* Skipto/tablearg integer */ #define IPFW_VTYPE_IP 2 /* Nexthop IP address */ #define IPFW_VTYPE_DSCP 3 /* DiffServ codepoints */ typedef struct _ipfw_table_entry { in_addr_t addr; /* network address */ u_int32_t value; /* value */ u_int16_t tbl; /* table number */ u_int8_t masklen; /* mask length */ } ipfw_table_entry; typedef struct _ipfw_table_xentry { uint16_t len; /* Total entry length */ uint8_t type; /* entry type */ uint8_t masklen; /* mask length */ uint16_t tbl; /* table number */ uint16_t flags; /* record flags */ uint32_t value; /* value */ union { /* Longest field needs to be aligned by 4-byte boundary */ struct in6_addr addr6; /* IPv6 address */ char iface[IF_NAMESIZE]; /* interface name */ } k; } ipfw_table_xentry; #define IPFW_TCF_INET 0x01 /* CIDR flags: IPv4 record */ typedef struct _ipfw_table { u_int32_t size; /* size of entries in bytes */ u_int32_t cnt; /* # of entries */ u_int16_t tbl; /* table number */ ipfw_table_entry ent[0]; /* entries */ } ipfw_table; typedef struct _ipfw_xtable { ip_fw3_opheader opheader; /* IP_FW3 opcode */ uint32_t size; /* size of entries in bytes */ uint32_t cnt; /* # of entries */ uint16_t tbl; /* table number */ uint8_t type; /* table type */ ipfw_table_xentry xent[0]; /* entries */ } ipfw_xtable; typedef struct _ipfw_obj_tlv { uint16_t type; /* TLV type */ uint16_t flags; /* TLV-specific flags */ uint32_t length; /* Total length, aligned to u64 */ } ipfw_obj_tlv; #define IPFW_TLV_TBL_NAME 1 #define IPFW_TLV_TBLNAME_LIST 2 #define IPFW_TLV_RULE_LIST 3 #define IPFW_TLV_DYNSTATE_LIST 4 #define IPFW_TLV_TBL_ENT 5 #define IPFW_TLV_DYN_ENT 6 #define IPFW_TLV_RULE_ENT 7 #define IPFW_TLV_TBLENT_LIST 8 /* Object name TLV */ typedef struct _ipfw_obj_ntlv { ipfw_obj_tlv head; /* TLV header */ uint16_t idx; /* Name index */ uint8_t spare; /* unused */ uint8_t type; /* object type, if applicable */ uint32_t set; /* set, if applicable */ char name[64]; /* Null-terminated name */ } ipfw_obj_ntlv; /* IPv4/IPv6 L4 flow description */ struct tflow_entry { uint8_t af; uint8_t proto; uint16_t spare; uint16_t sport; uint16_t dport; union { struct { struct in_addr sip; struct in_addr dip; } a4; struct { struct in6_addr sip6; struct in6_addr dip6; } a6; } a; }; /* Table entry TLV */ typedef struct _ipfw_obj_tentry { ipfw_obj_tlv head; /* TLV header */ uint8_t subtype; /* subtype (IPv4,IPv6) */ uint8_t masklen; /* mask length */ uint16_t idx; /* Table name index */ uint32_t value; /* value */ uint64_t spare; union { /* Longest field needs to be aligned by 8-byte boundary */ struct in_addr addr; /* IPv4 address */ uint32_t key; /* uid/gid/port */ struct in6_addr addr6; /* IPv6 address */ char iface[IF_NAMESIZE]; /* interface name */ struct tflow_entry flow; } k; } ipfw_obj_tentry; #define IPFW_TF_UPDATE 0x01 /* Update record if exists */ typedef struct _ipfw_obj_dyntlv { ipfw_obj_tlv head; ipfw_dyn_rule state; } ipfw_obj_dyntlv; #define IPFW_DF_LAST 0x01 /* Last state in chain */ /* Containter TLVs */ typedef struct _ipfw_obj_ctlv { ipfw_obj_tlv head; /* TLV header */ uint32_t count; /* Number of sub-TLVs */ uint16_t objsize; /* Single object size */ uint8_t version; /* TLV version */ uint8_t spare; } ipfw_obj_ctlv; -typedef struct _ifpw_ta_tinfo { +typedef struct _ipfw_ta_tinfo { uint32_t flags; /* Format flags */ - uint8_t taclass; /* algorithm class */ - uint8_t spare0; - uint16_t spare1; - uint32_t rssize4; /* runtime structure size */ - uint32_t rcount4; /* number of items in runtime */ - uint32_t rsize4; /* item size in runtime */ - uint32_t rssize6; /* runtime structure size */ - uint32_t rcount6; /* number of items in runtime */ - uint32_t rsize6; /* item size in runtime */ -} ifpw_ta_tinfo; + uint32_t spare; + uint8_t taclass4; /* algorithm class */ + uint8_t spare4; + uint16_t itemsize4; /* item size in runtime */ + uint32_t size4; /* runtime structure size */ + uint32_t count4; /* number of items in runtime */ + uint8_t taclass6; /* algorithm class */ + uint8_t spare6; + uint16_t itemsize6; /* item size in runtime */ + uint32_t size6; /* runtime structure size */ + uint32_t count6; /* number of items in runtime */ +} ipfw_ta_tinfo; #define IPFW_TACLASS_HASH 1 /* algo is based on hash */ #define IPFW_TACLASS_ARRAY 2 /* algo is based on array */ #define IPFW_TACLASS_RADIX 3 /* algo is based on radix tree */ #define IPFW_TATFLAGS_DATA 0x0001 /* Has data filled in */ -#define IPFW_TATFLAGS_AF 0x0002 /* Separate data per AF */ +#define IPFW_TATFLAGS_AFDATA 0x0002 /* Separate data per AF */ +#define IPFW_TATFLAGS_AFITEM 0x0004 /* diff. items per AF */ typedef struct _ipfw_xtable_info { uint8_t type; /* table type (cidr,iface,..) */ uint8_t tflags; /* type flags */ uint8_t ftype; /* table value format type */ uint8_t vtype; /* value type */ uint32_t set; /* set table is in */ uint32_t kidx; /* kernel index */ 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 */ + ipfw_ta_tinfo ta_info; /* additional algo stats */ } ipfw_xtable_info; #define IPFW_TFFLAG_SRCIP 0x01 #define IPFW_TFFLAG_DSTIP 0x02 #define IPFW_TFFLAG_SRCPORT 0x04 #define IPFW_TFFLAG_DSTPORT 0x08 #define IPFW_TFFLAG_PROTO 0x10 typedef struct _ipfw_iface_info { char ifname[64]; /* interface name */ uint32_t ifindex; /* interface index */ uint32_t flags; /* flags */ uint32_t refcnt; /* number of references */ uint32_t gencnt; /* number of changes */ uint64_t spare; } ipfw_iface_info; #define IPFW_IFFLAG_RESOLVED 0x01 /* Interface exists */ typedef struct _ipfw_ta_info { char algoname[64]; /* algorithm name */ uint32_t type; /* lookup type */ uint32_t flags; uint32_t refcnt; uint32_t spare0; uint64_t spare1; } ipfw_ta_info; #define IPFW_OBJTYPE_TABLE 1 typedef struct _ipfw_obj_header { ip_fw3_opheader opheader; /* IP_FW3 opcode */ uint32_t spare; uint16_t idx; /* object name index */ uint8_t objtype; /* object type */ uint8_t objsubtype; /* object subtype */ ipfw_obj_ntlv ntlv; /* object name tlv */ } ipfw_obj_header; typedef struct _ipfw_obj_lheader { ip_fw3_opheader opheader; /* IP_FW3 opcode */ uint32_t set_mask; /* disabled set mask */ uint32_t count; /* Total objects count */ uint32_t size; /* Total size (incl. header) */ uint32_t objsize; /* Size of one object */ } ipfw_obj_lheader; #define IPFW_CFG_GET_STATIC 0x01 #define IPFW_CFG_GET_STATES 0x02 #define IPFW_CFG_GET_COUNTERS 0x04 typedef struct _ipfw_cfg_lheader { ip_fw3_opheader opheader; /* IP_FW3 opcode */ uint32_t set_mask; /* enabled set mask */ uint32_t spare; uint32_t flags; /* Request flags */ uint32_t size; /* neded buffer size */ uint32_t start_rule; uint32_t end_rule; } ipfw_cfg_lheader; #endif /* _IPFW2_H */ Index: projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c =================================================================== --- projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c (revision 269472) +++ projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c (revision 269473) @@ -1,2545 +1,2552 @@ /*- * Copyright (c) 2004 Ruslan Ermilov and Vsevolod Lobko. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * Lookup table support for ipfw. * * This file contains handlers for all generic tables' operations: * add/del/flush entries, list/dump tables etc.. * * Table data modification is protected by both UH and runtimg lock * while reading configuration/data is protected by UH lock. * * Lookup algorithms for all table types are located in ip_fw_table_algo.c */ #include "opt_ipfw.h" #include #include #include #include #include #include #include #include #include #include /* ip_fw.h requires IFNAMSIZ */ #include #include /* struct ipfw_rule_ref */ #include #include #include /* * Table has the following `type` concepts: * * `no.type` represents lookup key type (cidr, ifp, uid, etc..) * `ta->atype` represents exact lookup algorithm. * For example, we can use more efficient search schemes if we plan * to use some specific table for storing host-routes only. * `ftype` (at the moment )is pure userland field helping to properly * format value data e.g. "value is IPv4 nexthop" or "value is DSCP" * or "value is port". * */ struct table_config { struct named_object no; uint8_t vtype; /* format table type */ uint8_t linked; /* 1 if already linked */ uint8_t tflags; /* type flags */ uint8_t spare; uint32_t count; /* Number of records */ uint32_t limit; /* Max number of records */ char tablename[64]; /* table name */ struct table_algo *ta; /* Callbacks for given algo */ void *astate; /* algorithm state */ struct table_info ti; /* data to put to table_info */ }; struct tables_config { struct namedobj_instance *namehash; int algo_count; struct table_algo *algo[256]; struct table_algo *def_algo[IPFW_TABLE_MAXTYPE + 1]; }; static struct table_config *find_table(struct namedobj_instance *ni, struct tid_info *ti); static struct table_config *alloc_table_config(struct ip_fw_chain *ch, struct tid_info *ti, struct table_algo *ta, char *adata, uint8_t tflags, uint8_t vtype); 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, 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); static int export_tables(struct ip_fw_chain *ch, ipfw_obj_lheader *olh, struct sockopt_data *sd); static void export_table_info(struct ip_fw_chain *ch, struct table_config *tc, ipfw_xtable_info *i); static int dump_table_tentry(void *e, void *arg); static int dump_table_xentry(void *e, void *arg); static int ipfw_dump_table_v0(struct ip_fw_chain *ch, struct sockopt_data *sd); static int ipfw_dump_table_v1(struct ip_fw_chain *ch, struct sockopt_data *sd); static int ipfw_manage_table_ent_v0(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd); static int ipfw_manage_table_ent_v1(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd); static int check_table_space(struct ip_fw_chain *ch, struct table_config *tc, struct table_info *ti, uint32_t count); static int destroy_table(struct ip_fw_chain *ch, struct tid_info *ti); static struct table_algo *find_table_algo(struct tables_config *tableconf, struct tid_info *ti, char *name); #define CHAIN_TO_TCFG(chain) ((struct tables_config *)(chain)->tblcfg) #define CHAIN_TO_NI(chain) (CHAIN_TO_TCFG(chain)->namehash) #define KIDX_TO_TI(ch, k) (&(((struct table_info *)(ch)->tablestate)[k])) #define TA_BUF_SZ 128 /* On-stack buffer for add/delete state */ int add_table_entry(struct ip_fw_chain *ch, struct tid_info *ti, struct tentry_info *tei, uint32_t count) { struct table_config *tc; struct table_algo *ta; struct namedobj_instance *ni; uint16_t kidx; int error; uint32_t num; ipfw_xtable_info *xi; char ta_buf[TA_BUF_SZ]; IPFW_UH_WLOCK(ch); ni = CHAIN_TO_NI(ch); /* * Find and reference existing table. */ ta = NULL; if ((tc = find_table(ni, ti)) != NULL) { /* check table type */ if (tc->no.type != ti->type) { IPFW_UH_WUNLOCK(ch); 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; } IPFW_UH_WUNLOCK(ch); if (tc == NULL) { /* Compability mode: create new table for old clients */ if ((tei->flags & TEI_FLAGS_COMPAT) == 0) return (ESRCH); xi = malloc(sizeof(ipfw_xtable_info), M_TEMP, M_WAITOK|M_ZERO); xi->vtype = IPFW_VTYPE_U32; error = create_table_internal(ch, ti, NULL, xi); free(xi, M_TEMP); if (error != 0) return (error); /* Let's try to find & reference another time */ IPFW_UH_WLOCK(ch); if ((tc = find_table(ni, ti)) == NULL) { IPFW_UH_WUNLOCK(ch); return (EINVAL); } if (tc->no.type != ti->type) { IPFW_UH_WUNLOCK(ch); return (EINVAL); } /* Reference and unlock */ tc->no.refcnt++; ta = tc->ta; IPFW_UH_WUNLOCK(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); IPFW_UH_WLOCK(ch); /* * Ensure we are able to add all entries without additional * memory allocations. May release/reacquire UH_WLOCK. */ kidx = tc->no.kidx; 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); } 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 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; num = 0; IPFW_WLOCK(ch); error = ta->add(tc->astate, KIDX_TO_TI(ch, kidx), tei, &ta_buf, &num); IPFW_WUNLOCK(ch); /* 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); } IPFW_UH_WUNLOCK(ch); /* Run cleaning callback anyway */ ta->flush_entry(ch, tei, &ta_buf); return (error); } int del_table_entry(struct ip_fw_chain *ch, struct tid_info *ti, struct tentry_info *tei, uint32_t count) { struct table_config *tc; struct table_algo *ta; struct namedobj_instance *ni; uint16_t kidx; int error; uint32_t num; char ta_buf[TA_BUF_SZ]; IPFW_UH_WLOCK(ch); ni = CHAIN_TO_NI(ch); if ((tc = find_table(ni, ti)) == NULL) { IPFW_UH_WUNLOCK(ch); return (ESRCH); } if (tc->no.type != ti->type) { IPFW_UH_WUNLOCK(ch); return (EINVAL); } ta = tc->ta; /* * Give a chance for algorithm to shrink. * May release/reacquire UH_WLOCK. */ kidx = tc->no.kidx; 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); } kidx = tc->no.kidx; num = 0; IPFW_WLOCK(ch); error = ta->del(tc->astate, KIDX_TO_TI(ch, kidx), tei, &ta_buf, &num); IPFW_WUNLOCK(ch); if (error == 0) { tc->count -= num; /* 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 (error); } /* * Ensure that table @tc has enough space to add @count entries without * need for reallocation. * * Callbacks order: * 0) has_space() (UH_WLOCK) - checks if @count items can be added w/o resize. * * 1) alloc_modify (no locks, M_WAITOK) - alloc new state based on @pflags. * 2) prepare_modifyt (UH_WLOCK) - copy old data into new storage * 3) modify (UH_WLOCK + WLOCK) - switch pointers * 4) flush_modify (UH_WLOCK) - free state, if needed * * Returns 0 on success. */ static int check_table_space(struct ip_fw_chain *ch, struct table_config *tc, struct table_info *ti, uint32_t count) { struct table_algo *ta; uint64_t pflags; char ta_buf[TA_BUF_SZ]; int error; IPFW_UH_WLOCK_ASSERT(ch); error = 0; ta = tc->ta; /* Acquire reference not to loose @tc between locks/unlocks */ tc->no.refcnt++; /* * TODO: think about avoiding race between large add/large delete * operation on algorithm which implements shrinking along with * growing. */ while (true) { pflags = 0; if (ta->has_space(tc->astate, ti, count, &pflags) != 0) { tc->no.refcnt--; return (0); } /* We have to shrink/grow table */ IPFW_UH_WUNLOCK(ch); memset(&ta_buf, 0, sizeof(ta_buf)); if ((error = ta->prepare_mod(ta_buf, &pflags)) != 0) { IPFW_UH_WLOCK(ch); break; } IPFW_UH_WLOCK(ch); /* Check if we still need to alter table */ ti = KIDX_TO_TI(ch, tc->no.kidx); if (ta->has_space(tc->astate, ti, count, &pflags) != 0) { /* * Other threads has already performed resize. * Flush our state and return/ */ ta->flush_mod(ta_buf); break; } error = ta->fill_mod(tc->astate, ti, ta_buf, &pflags); if (error == 0) { /* Do actual modification */ IPFW_WLOCK(ch); ta->modify(tc->astate, ti, ta_buf, pflags); IPFW_WUNLOCK(ch); } /* Anyway, flush data and retry */ ta->flush_mod(ta_buf); } tc->no.refcnt--; return (error); } int ipfw_manage_table_ent(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { int error; switch (op3->version) { case 0: error = ipfw_manage_table_ent_v0(ch, op3, sd); break; case 1: error = ipfw_manage_table_ent_v1(ch, op3, sd); break; default: error = ENOTSUP; } return (error); } /* * Adds or deletes record in table. * Data layout (v0): * Request: [ ip_fw3_opheader ipfw_table_xentry ] * * Returns 0 on success */ static int ipfw_manage_table_ent_v0(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { ipfw_table_xentry *xent; struct tentry_info tei; struct tid_info ti; int error, hdrlen, read; hdrlen = offsetof(ipfw_table_xentry, k); /* Check minimum header size */ if (sd->valsize < (sizeof(*op3) + hdrlen)) return (EINVAL); read = sizeof(ip_fw3_opheader); /* Check if xentry len field is valid */ xent = (ipfw_table_xentry *)(op3 + 1); if (xent->len < hdrlen || xent->len + read > sd->valsize) return (EINVAL); memset(&tei, 0, sizeof(tei)); tei.paddr = &xent->k; tei.masklen = xent->masklen; tei.value = xent->value; /* Old requests compability */ tei.flags = TEI_FLAGS_COMPAT; if (xent->type == IPFW_TABLE_CIDR) { if (xent->len - hdrlen == sizeof(in_addr_t)) tei.subtype = AF_INET; else tei.subtype = AF_INET6; } memset(&ti, 0, sizeof(ti)); ti.uidx = xent->tbl; 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); return (error); } /* * Adds or deletes record in table. * Data layout (v1)(current): * Request: [ ipfw_obj_header * ipfw_obj_ctlv(IPFW_TLV_TBLENT_LIST) [ ipfw_obj_tentry x N ] * ] * * Returns 0 on success */ 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_ctlv *ctlv; ipfw_obj_header *oh; struct tentry_info tei; struct tid_info ti; int error, read; /* Check minimum header size */ if (sd->valsize < (sizeof(*oh) + sizeof(*ctlv))) return (EINVAL); /* Check if passed data is too long */ if (sd->valsize != sd->kavail) return (EINVAL); oh = (ipfw_obj_header *)sd->kbuf; /* Basic length checks for TLVs */ if (oh->ntlv.head.length != sizeof(oh->ntlv)) return (EINVAL); read = sizeof(*oh); ctlv = (ipfw_obj_ctlv *)(oh + 1); 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) 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; objheader_to_ti(oh, &ti); ti.type = oh->ntlv.type; ti.uidx = tent->idx; error = (oh->opheader.opcode == IP_FW_TABLE_XADD) ? add_table_entry(ch, &ti, &tei, 1) : del_table_entry(ch, &ti, &tei, 1); return (error); } /* * Looks up an entry in given table. * Data layout (v0)(current): * Request: [ ipfw_obj_header ipfw_obj_tentry ] * Reply: [ ipfw_obj_header ipfw_obj_tentry ] * * Returns 0 on success */ int ipfw_find_table_entry(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { ipfw_obj_tentry *tent; ipfw_obj_header *oh; struct tid_info ti; struct table_config *tc; struct table_algo *ta; struct table_info *kti; struct namedobj_instance *ni; int error; size_t sz; /* Check minimum header size */ sz = sizeof(*oh) + sizeof(*tent); if (sd->valsize != sz) return (EINVAL); oh = (struct _ipfw_obj_header *)ipfw_get_sopt_header(sd, sz); tent = (ipfw_obj_tentry *)(oh + 1); /* Basic length checks for TLVs */ if (oh->ntlv.head.length != sizeof(oh->ntlv)) return (EINVAL); objheader_to_ti(oh, &ti); ti.type = oh->ntlv.type; ti.uidx = tent->idx; IPFW_UH_RLOCK(ch); ni = CHAIN_TO_NI(ch); /* * Find existing table and check its type . */ ta = NULL; if ((tc = find_table(ni, &ti)) == NULL) { IPFW_UH_RUNLOCK(ch); return (ESRCH); } /* check table type */ if (tc->no.type != ti.type) { IPFW_UH_RUNLOCK(ch); return (EINVAL); } kti = KIDX_TO_TI(ch, tc->no.kidx); ta = tc->ta; if (ta->find_tentry == NULL) return (ENOTSUP); error = ta->find_tentry(tc->astate, kti, tent); IPFW_UH_RUNLOCK(ch); return (error); } int ipfw_flush_table(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { int error; struct _ipfw_obj_header *oh; struct tid_info ti; if (sd->valsize != sizeof(*oh)) return (EINVAL); oh = (struct _ipfw_obj_header *)op3; objheader_to_ti(oh, &ti); if (op3->opcode == IP_FW_TABLE_XDESTROY) error = destroy_table(ch, &ti); else if (op3->opcode == IP_FW_TABLE_XFLUSH) error = flush_table(ch, &ti); else return (ENOTSUP); return (error); } /* * Flushes all entries in given table. * Data layout (v0)(current): * Request: [ ip_fw3_opheader ] * * Returns 0 on success */ int flush_table(struct ip_fw_chain *ch, struct tid_info *ti) { struct namedobj_instance *ni; struct table_config *tc; struct table_algo *ta; struct table_info ti_old, ti_new, *tablestate; void *astate_old, *astate_new; char algostate[64], *pstate; int error; uint16_t kidx; uint8_t tflags; /* * Stage 1: save table algoritm. * Reference found table to ensure it won't disappear. */ IPFW_UH_WLOCK(ch); ni = CHAIN_TO_NI(ch); if ((tc = find_table(ni, ti)) == NULL) { IPFW_UH_WUNLOCK(ch); return (ESRCH); } ta = tc->ta; tc->no.refcnt++; /* Save statup algo parameters */ if (ta->print_config != NULL) { ta->print_config(tc->astate, KIDX_TO_TI(ch, tc->no.kidx), algostate, sizeof(algostate)); pstate = algostate; } else pstate = NULL; tflags = tc->tflags; IPFW_UH_WUNLOCK(ch); /* * Stage 2: allocate new table instance using same algo. */ memset(&ti_new, 0, sizeof(struct table_info)); if ((error = ta->init(ch, &astate_new, &ti_new, pstate, tflags)) != 0) { IPFW_UH_WLOCK(ch); tc->no.refcnt--; IPFW_UH_WUNLOCK(ch); return (error); } /* * Stage 3: swap old state pointers with newly-allocated ones. * Decrease refcount. */ IPFW_UH_WLOCK(ch); ni = CHAIN_TO_NI(ch); kidx = tc->no.kidx; tablestate = (struct table_info *)ch->tablestate; IPFW_WLOCK(ch); ti_old = tablestate[kidx]; tablestate[kidx] = ti_new; IPFW_WUNLOCK(ch); astate_old = tc->astate; tc->astate = astate_new; tc->ti = ti_new; tc->count = 0; tc->no.refcnt--; IPFW_UH_WUNLOCK(ch); /* * Stage 4: perform real flush. */ ta->destroy(astate_old, &ti_old); return (0); } /* * Destroys table specified by @ti. * Data layout (v0)(current): * Request: [ ip_fw3_opheader ] * * Returns 0 on success */ static int destroy_table(struct ip_fw_chain *ch, struct tid_info *ti) { struct namedobj_instance *ni; struct table_config *tc; IPFW_UH_WLOCK(ch); ni = CHAIN_TO_NI(ch); if ((tc = find_table(ni, ti)) == NULL) { IPFW_UH_WUNLOCK(ch); return (ESRCH); } /* Do not permit destroying referenced tables */ if (tc->no.refcnt > 0) { IPFW_UH_WUNLOCK(ch); return (EBUSY); } IPFW_WLOCK(ch); unlink_table(ch, tc); IPFW_WUNLOCK(ch); /* Free obj index */ if (ipfw_objhash_free_idx(ni, tc->no.kidx) != 0) printf("Error unlinking kidx %d from table %s\n", tc->no.kidx, tc->tablename); IPFW_UH_WUNLOCK(ch); free_table_config(ni, tc); return (0); } static void destroy_table_locked(struct namedobj_instance *ni, struct named_object *no, void *arg) { unlink_table((struct ip_fw_chain *)arg, (struct table_config *)no); if (ipfw_objhash_free_idx(ni, no->kidx) != 0) printf("Error unlinking kidx %d from table %s\n", no->kidx, no->name); free_table_config(ni, (struct table_config *)no); } void ipfw_destroy_tables(struct ip_fw_chain *ch) { /* Remove all tables from working set */ IPFW_UH_WLOCK(ch); IPFW_WLOCK(ch); ipfw_objhash_foreach(CHAIN_TO_NI(ch), destroy_table_locked, ch); IPFW_WUNLOCK(ch); IPFW_UH_WUNLOCK(ch); /* Free pointers itself */ free(ch->tablestate, M_IPFW); ipfw_table_algo_destroy(ch); ipfw_objhash_destroy(CHAIN_TO_NI(ch)); free(CHAIN_TO_TCFG(ch), M_IPFW); } int ipfw_init_tables(struct ip_fw_chain *ch) { struct tables_config *tcfg; /* Allocate pointers */ ch->tablestate = malloc(V_fw_tables_max * sizeof(struct table_info), M_IPFW, M_WAITOK | M_ZERO); tcfg = malloc(sizeof(struct tables_config), M_IPFW, M_WAITOK | M_ZERO); tcfg->namehash = ipfw_objhash_create(V_fw_tables_max); ch->tblcfg = tcfg; ipfw_table_algo_init(ch); return (0); } int ipfw_resize_tables(struct ip_fw_chain *ch, unsigned int ntables) { unsigned int ntables_old, tbl; struct namedobj_instance *ni; void *new_idx, *old_tablestate, *tablestate; struct table_info *ti; struct table_config *tc; int i, new_blocks; /* Check new value for validity */ if (ntables > IPFW_TABLES_MAX) ntables = IPFW_TABLES_MAX; /* Allocate new pointers */ tablestate = malloc(ntables * sizeof(struct table_info), M_IPFW, M_WAITOK | M_ZERO); ipfw_objhash_bitmap_alloc(ntables, (void *)&new_idx, &new_blocks); IPFW_UH_WLOCK(ch); tbl = (ntables >= V_fw_tables_max) ? V_fw_tables_max : ntables; ni = CHAIN_TO_NI(ch); /* Temporary restrict decreasing max_tables */ if (ntables < V_fw_tables_max) { /* * FIXME: Check if we really can shrink */ IPFW_UH_WUNLOCK(ch); return (EINVAL); } /* Copy table info/indices */ memcpy(tablestate, ch->tablestate, sizeof(struct table_info) * tbl); ipfw_objhash_bitmap_merge(ni, &new_idx, &new_blocks); IPFW_WLOCK(ch); /* Change pointers */ old_tablestate = ch->tablestate; ch->tablestate = tablestate; ipfw_objhash_bitmap_swap(ni, &new_idx, &new_blocks); ntables_old = V_fw_tables_max; V_fw_tables_max = ntables; IPFW_WUNLOCK(ch); /* Notify all consumers that their @ti pointer has changed */ ti = (struct table_info *)ch->tablestate; for (i = 0; i < tbl; i++, ti++) { if (ti->lookup == NULL) continue; tc = (struct table_config *)ipfw_objhash_lookup_kidx(ni, i); if (tc == NULL || tc->ta->change_ti == NULL) continue; tc->ta->change_ti(tc->astate, ti); } IPFW_UH_WUNLOCK(ch); /* Free old pointers */ free(old_tablestate, M_IPFW); ipfw_objhash_bitmap_free(new_idx, new_blocks); return (0); } int ipfw_lookup_table(struct ip_fw_chain *ch, uint16_t tbl, in_addr_t addr, uint32_t *val) { struct table_info *ti; ti = &(((struct table_info *)ch->tablestate)[tbl]); return (ti->lookup(ti, &addr, sizeof(in_addr_t), val)); } int ipfw_lookup_table_extended(struct ip_fw_chain *ch, uint16_t tbl, uint16_t plen, void *paddr, uint32_t *val) { struct table_info *ti; ti = &(((struct table_info *)ch->tablestate)[tbl]); return (ti->lookup(ti, paddr, plen, val)); } /* * Info/List/dump support for tables. * */ /* * High-level 'get' cmds sysctl handlers */ /* * Get buffer size needed to list info for all tables. * Data layout (v0)(current): * Request: [ empty ], size = sizeof(ipfw_obj_lheader) * Reply: [ ipfw_obj_lheader ] * * Returns 0 on success */ int ipfw_listsize_tables(struct ip_fw_chain *ch, struct sockopt_data *sd) { struct _ipfw_obj_lheader *olh; olh = (struct _ipfw_obj_lheader *)ipfw_get_sopt_header(sd,sizeof(*olh)); if (olh == NULL) return (EINVAL); olh->size = sizeof(*olh); /* Make export_table store needed size */ IPFW_UH_RLOCK(ch); export_tables(ch, olh, sd); IPFW_UH_RUNLOCK(ch); return (0); } /* * Lists all tables currently available in kernel. * Data layout (v0)(current): * Request: [ ipfw_obj_lheader ], size = ipfw_obj_lheader.size * Reply: [ ipfw_obj_lheader ipfw_xtable_info x N ] * * Returns 0 on success */ int ipfw_list_tables(struct ip_fw_chain *ch, struct sockopt_data *sd) { struct _ipfw_obj_lheader *olh; int error; olh = (struct _ipfw_obj_lheader *)ipfw_get_sopt_header(sd,sizeof(*olh)); if (olh == NULL) return (EINVAL); if (sd->valsize < olh->size) return (EINVAL); IPFW_UH_RLOCK(ch); error = export_tables(ch, olh, sd); IPFW_UH_RUNLOCK(ch); return (error); } /* * Store table info to buffer provided by @sd. * Data layout (v0)(current): * Request: [ ipfw_obj_header ipfw_xtable_info(empty)] * Reply: [ ipfw_obj_header ipfw_xtable_info ] * * Returns 0 on success. */ int ipfw_describe_table(struct ip_fw_chain *ch, struct sockopt_data *sd) { struct _ipfw_obj_header *oh; struct table_config *tc; struct tid_info ti; size_t sz; sz = sizeof(*oh) + sizeof(ipfw_xtable_info); oh = (struct _ipfw_obj_header *)ipfw_get_sopt_header(sd, sz); if (oh == NULL) return (EINVAL); objheader_to_ti(oh, &ti); IPFW_UH_RLOCK(ch); if ((tc = find_table(CHAIN_TO_NI(ch), &ti)) == NULL) { IPFW_UH_RUNLOCK(ch); return (ESRCH); } export_table_info(ch, tc, (ipfw_xtable_info *)(oh + 1)); IPFW_UH_RUNLOCK(ch); return (0); } struct dump_args { struct table_info *ti; struct table_config *tc; struct sockopt_data *sd; uint32_t cnt; uint16_t uidx; int error; ipfw_table_entry *ent; uint32_t size; ipfw_obj_tentry tent; }; int ipfw_dump_table(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { int error; switch (op3->version) { case 0: error = ipfw_dump_table_v0(ch, sd); break; case 1: error = ipfw_dump_table_v1(ch, sd); break; default: error = ENOTSUP; } return (error); } /* * Dumps all table data * Data layout (v1)(current): * Request: [ ipfw_obj_header ], size = ipfw_xtable_info.size * Reply: [ ipfw_obj_header ipfw_xtable_info ipfw_obj_tentry x N ] * * Returns 0 on success */ static int ipfw_dump_table_v1(struct ip_fw_chain *ch, struct sockopt_data *sd) { struct _ipfw_obj_header *oh; ipfw_xtable_info *i; struct tid_info ti; struct table_config *tc; struct table_algo *ta; struct dump_args da; uint32_t sz; sz = sizeof(ipfw_obj_header) + sizeof(ipfw_xtable_info); oh = (struct _ipfw_obj_header *)ipfw_get_sopt_header(sd, sz); if (oh == NULL) return (EINVAL); i = (ipfw_xtable_info *)(oh + 1); objheader_to_ti(oh, &ti); IPFW_UH_RLOCK(ch); if ((tc = find_table(CHAIN_TO_NI(ch), &ti)) == NULL) { IPFW_UH_RUNLOCK(ch); return (ESRCH); } export_table_info(ch, tc, i); sz = tc->count; if (sd->valsize < sz + tc->count * sizeof(ipfw_obj_tentry)) { /* * Submitted buffer size is not enough. * WE've already filled in @i structure with * relevant table info including size, so we * can return. Buffer will be flushed automatically. */ IPFW_UH_RUNLOCK(ch); return (ENOMEM); } /* * Do the actual dump in eXtended format */ memset(&da, 0, sizeof(da)); da.ti = KIDX_TO_TI(ch, tc->no.kidx); da.tc = tc; da.sd = sd; ta = tc->ta; ta->foreach(tc->astate, da.ti, dump_table_tentry, &da); IPFW_UH_RUNLOCK(ch); return (da.error); } /* * Dumps all table data * Data layout (version 0)(legacy): * Request: [ ipfw_xtable ], size = IP_FW_TABLE_XGETSIZE() * Reply: [ ipfw_xtable ipfw_table_xentry x N ] * * Returns 0 on success */ static int ipfw_dump_table_v0(struct ip_fw_chain *ch, struct sockopt_data *sd) { ipfw_xtable *xtbl; struct tid_info ti; struct table_config *tc; struct table_algo *ta; struct dump_args da; size_t sz; xtbl = (ipfw_xtable *)ipfw_get_sopt_header(sd, sizeof(ipfw_xtable)); if (xtbl == NULL) return (EINVAL); memset(&ti, 0, sizeof(ti)); ti.uidx = xtbl->tbl; IPFW_UH_RLOCK(ch); if ((tc = find_table(CHAIN_TO_NI(ch), &ti)) == NULL) { IPFW_UH_RUNLOCK(ch); return (0); } sz = tc->count * sizeof(ipfw_table_xentry) + sizeof(ipfw_xtable); xtbl->cnt = tc->count; xtbl->size = sz; xtbl->type = tc->no.type; xtbl->tbl = ti.uidx; if (sd->valsize < sz) { /* * Submitted buffer size is not enough. * WE've already filled in @i structure with * relevant table info including size, so we * can return. Buffer will be flushed automatically. */ IPFW_UH_RUNLOCK(ch); return (ENOMEM); } /* Do the actual dump in eXtended format */ memset(&da, 0, sizeof(da)); da.ti = KIDX_TO_TI(ch, tc->no.kidx); da.tc = tc; da.sd = sd; ta = tc->ta; ta->foreach(tc->astate, da.ti, dump_table_xentry, &da); IPFW_UH_RUNLOCK(ch); return (0); } /* * Creates new table. * Data layout (v0)(current): * Request: [ ipfw_obj_header ipfw_xtable_info ] * * Returns 0 on success */ int ipfw_create_table(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { struct _ipfw_obj_header *oh; ipfw_xtable_info *i; char *tname, *aname; struct tid_info ti; struct namedobj_instance *ni; struct table_config *tc; if (sd->valsize != sizeof(*oh) + sizeof(ipfw_xtable_info)) return (EINVAL); oh = (struct _ipfw_obj_header *)sd->kbuf; i = (ipfw_xtable_info *)(oh + 1); /* * Verify user-supplied strings. * Check for null-terminated/zero-length strings/ */ tname = oh->ntlv.name; aname = i->algoname; if (ipfw_check_table_name(tname) != 0 || strnlen(aname, sizeof(i->algoname)) == sizeof(i->algoname)) return (EINVAL); if (aname[0] == '\0') { /* Use default algorithm */ aname = NULL; } objheader_to_ti(oh, &ti); ti.type = i->type; ni = CHAIN_TO_NI(ch); IPFW_UH_RLOCK(ch); if ((tc = find_table(ni, &ti)) != NULL) { IPFW_UH_RUNLOCK(ch); return (EEXIST); } IPFW_UH_RUNLOCK(ch); return (create_table_internal(ch, &ti, aname, i)); } /* * Creates new table based on @ti and @aname. * * Relies on table name checking inside find_name_tlv() * Assume @aname to be checked and valid. * * Returns 0 on success. */ static int create_table_internal(struct ip_fw_chain *ch, struct tid_info *ti, char *aname, ipfw_xtable_info *i) { struct namedobj_instance *ni; struct table_config *tc; struct table_algo *ta; uint16_t kidx; ni = CHAIN_TO_NI(ch); ta = find_table_algo(CHAIN_TO_TCFG(ch), ti, aname); if (ta == NULL) return (ENOTSUP); 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 */ if (find_table(ni, ti) != NULL) { IPFW_UH_WUNLOCK(ch); free_table_config(ni, tc); return (EEXIST); } if (ipfw_objhash_alloc_idx(ni, &kidx) != 0) { IPFW_UH_WUNLOCK(ch); printf("Unable to allocate table index." " Consider increasing net.inet.ip.fw.tables_max"); free_table_config(ni, tc); return (EBUSY); } tc->no.kidx = kidx; IPFW_WLOCK(ch); link_table(ch, tc); IPFW_WUNLOCK(ch); IPFW_UH_WUNLOCK(ch); return (0); } void objheader_to_ti(struct _ipfw_obj_header *oh, struct tid_info *ti) { memset(ti, 0, sizeof(struct tid_info)); ti->set = oh->ntlv.set; ti->uidx = oh->idx; ti->tlvs = &oh->ntlv; ti->tlen = oh->ntlv.head.length; } int ipfw_export_table_ntlv(struct ip_fw_chain *ch, uint16_t kidx, struct sockopt_data *sd) { struct namedobj_instance *ni; struct named_object *no; ipfw_obj_ntlv *ntlv; ni = CHAIN_TO_NI(ch); no = ipfw_objhash_lookup_kidx(ni, kidx); KASSERT(no != NULL, ("invalid table kidx passed")); ntlv = (ipfw_obj_ntlv *)ipfw_get_sopt_space(sd, sizeof(*ntlv)); if (ntlv == NULL) return (ENOMEM); ntlv->head.type = IPFW_TLV_TBL_NAME; ntlv->head.length = sizeof(*ntlv); ntlv->idx = no->kidx; strlcpy(ntlv->name, no->name, sizeof(ntlv->name)); return (0); } static void export_table_info(struct ip_fw_chain *ch, struct table_config *tc, ipfw_xtable_info *i) { struct table_info *ti; + struct table_algo *ta; i->type = tc->no.type; i->tflags = tc->tflags; i->vtype = tc->vtype; i->set = tc->no.set; 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)); - if (tc->ta->print_config != NULL) { + ti = KIDX_TO_TI(ch, tc->no.kidx); + ta = tc->ta; + if (ta->print_config != NULL) { /* Use algo function to print table config to string */ - ti = KIDX_TO_TI(ch, tc->no.kidx); - tc->ta->print_config(tc->astate, ti, i->algoname, + ta->print_config(tc->astate, ti, i->algoname, sizeof(i->algoname)); } else - strlcpy(i->algoname, tc->ta->name, sizeof(i->algoname)); + strlcpy(i->algoname, ta->name, sizeof(i->algoname)); + /* Dump algo-specific data, if possible */ + if (ta->dump_tinfo != NULL) { + ta->dump_tinfo(tc->astate, ti, &i->ta_info); + i->ta_info.flags |= IPFW_TATFLAGS_DATA; + } } struct dump_table_args { struct ip_fw_chain *ch; struct sockopt_data *sd; }; static void export_table_internal(struct namedobj_instance *ni, struct named_object *no, void *arg) { ipfw_xtable_info *i; struct dump_table_args *dta; dta = (struct dump_table_args *)arg; i = (ipfw_xtable_info *)ipfw_get_sopt_space(dta->sd, sizeof(*i)); KASSERT(i != 0, ("previously checked buffer is not enough")); export_table_info(dta->ch, (struct table_config *)no, i); } /* * Export all tables as ipfw_xtable_info structures to * storage provided by @sd. * Returns 0 on success. */ static int export_tables(struct ip_fw_chain *ch, ipfw_obj_lheader *olh, struct sockopt_data *sd) { uint32_t size; uint32_t count; struct dump_table_args dta; count = ipfw_objhash_count(CHAIN_TO_NI(ch)); size = count * sizeof(ipfw_xtable_info) + sizeof(ipfw_obj_lheader); /* Fill in header regadless of buffer size */ olh->count = count; olh->objsize = sizeof(ipfw_xtable_info); if (size > olh->size) { olh->size = size; return (ENOMEM); } olh->size = size; dta.ch = ch; dta.sd = sd; ipfw_objhash_foreach(CHAIN_TO_NI(ch), export_table_internal, &dta); return (0); } /* * Legacy IP_FW_TABLE_GETSIZE handler */ int ipfw_count_table(struct ip_fw_chain *ch, struct tid_info *ti, uint32_t *cnt) { struct table_config *tc; if ((tc = find_table(CHAIN_TO_NI(ch), ti)) == NULL) return (ESRCH); *cnt = tc->count; return (0); } /* * Legacy IP_FW_TABLE_XGETSIZE handler */ int ipfw_count_xtable(struct ip_fw_chain *ch, struct tid_info *ti, uint32_t *cnt) { struct table_config *tc; if ((tc = find_table(CHAIN_TO_NI(ch), ti)) == NULL) { *cnt = 0; return (0); /* 'table all list' requires success */ } *cnt = tc->count * sizeof(ipfw_table_xentry); if (tc->count > 0) *cnt += sizeof(ipfw_xtable); return (0); } static int dump_table_entry(void *e, void *arg) { struct dump_args *da; struct table_config *tc; struct table_algo *ta; ipfw_table_entry *ent; int error; da = (struct dump_args *)arg; tc = da->tc; ta = tc->ta; /* Out of memory, returning */ if (da->cnt == da->size) return (1); ent = da->ent++; ent->tbl = da->uidx; da->cnt++; error = ta->dump_tentry(tc->astate, da->ti, e, &da->tent); if (error != 0) return (error); ent->addr = da->tent.k.addr.s_addr; ent->masklen = da->tent.masklen; ent->value = da->tent.value; return (0); } /* * Dumps table in pre-8.1 legacy format. */ int ipfw_dump_table_legacy(struct ip_fw_chain *ch, struct tid_info *ti, ipfw_table *tbl) { struct table_config *tc; struct table_algo *ta; struct dump_args da; tbl->cnt = 0; if ((tc = find_table(CHAIN_TO_NI(ch), ti)) == NULL) return (0); /* XXX: We should return ESRCH */ ta = tc->ta; /* This dump format supports IPv4 only */ if (tc->no.type != IPFW_TABLE_CIDR) return (0); memset(&da, 0, sizeof(da)); da.ti = KIDX_TO_TI(ch, tc->no.kidx); da.tc = tc; da.ent = &tbl->ent[0]; da.size = tbl->size; tbl->cnt = 0; ta->foreach(tc->astate, da.ti, dump_table_entry, &da); tbl->cnt = da.cnt; return (0); } /* * Dumps table entry in eXtended format (v1)(current). */ static int dump_table_tentry(void *e, void *arg) { struct dump_args *da; struct table_config *tc; struct table_algo *ta; ipfw_obj_tentry *tent; da = (struct dump_args *)arg; tc = da->tc; ta = tc->ta; tent = (ipfw_obj_tentry *)ipfw_get_sopt_space(da->sd, sizeof(*tent)); /* Out of memory, returning */ if (tent == NULL) { da->error = ENOMEM; return (1); } tent->head.length = sizeof(ipfw_obj_tentry); tent->idx = da->uidx; return (ta->dump_tentry(tc->astate, da->ti, e, tent)); } /* * Dumps table entry in eXtended format (v0). */ static int dump_table_xentry(void *e, void *arg) { struct dump_args *da; struct table_config *tc; struct table_algo *ta; ipfw_table_xentry *xent; ipfw_obj_tentry *tent; int error; da = (struct dump_args *)arg; tc = da->tc; ta = tc->ta; xent = (ipfw_table_xentry *)ipfw_get_sopt_space(da->sd, sizeof(*xent)); /* Out of memory, returning */ if (xent == NULL) return (1); xent->len = sizeof(ipfw_table_xentry); xent->tbl = da->uidx; memset(&da->tent, 0, sizeof(da->tent)); tent = &da->tent; error = ta->dump_tentry(tc->astate, da->ti, e, tent); if (error != 0) return (error); /* Convert current format to previous one */ xent->masklen = tent->masklen; xent->value = tent->value; /* Apply some hacks */ if (tc->no.type == IPFW_TABLE_CIDR && tent->subtype == AF_INET) { xent->k.addr6.s6_addr32[3] = tent->k.addr.s_addr; xent->flags = IPFW_TCF_INET; } else memcpy(&xent->k, &tent->k, sizeof(xent->k)); return (0); } /* * Table algorithms */ /* * Finds algoritm by index, table type or supplied name */ static struct table_algo * find_table_algo(struct tables_config *tcfg, struct tid_info *ti, char *name) { int i, l; struct table_algo *ta; if (ti->type > IPFW_TABLE_MAXTYPE) return (NULL); /* Search by index */ if (ti->atype != 0) { if (ti->atype > tcfg->algo_count) return (NULL); return (tcfg->algo[ti->atype]); } /* Search by name if supplied */ if (name != NULL) { /* TODO: better search */ for (i = 1; i <= tcfg->algo_count; i++) { ta = tcfg->algo[i]; /* * One can supply additional algorithm * parameters so we compare only the first word * of supplied name: * 'hash_cidr hsize=32' * '^^^^^^^^^' * */ l = strlen(ta->name); if (strncmp(name, ta->name, l) == 0) { if (name[l] == '\0' || name[l] == ' ') return (ta); } } return (NULL); } /* Return default algorithm for given type if set */ return (tcfg->def_algo[ti->type]); } /* * Register new table algo @ta. * Stores algo id iside @idx. * * Returns 0 on success. */ int ipfw_add_table_algo(struct ip_fw_chain *ch, struct table_algo *ta, size_t size, int *idx) { struct tables_config *tcfg; struct table_algo *ta_new; size_t sz; if (size > sizeof(struct table_algo)) return (EINVAL); /* Check for the required on-stack size for add/del */ sz = roundup2(ta->ta_buf_size, sizeof(void *)); if (sz > TA_BUF_SZ) return (EINVAL); KASSERT(ta->type >= IPFW_TABLE_MAXTYPE,("Increase IPFW_TABLE_MAXTYPE")); ta_new = malloc(sizeof(struct table_algo), M_IPFW, M_WAITOK | M_ZERO); memcpy(ta_new, ta, size); tcfg = CHAIN_TO_TCFG(ch); KASSERT(tcfg->algo_count < 255, ("Increase algo array size")); tcfg->algo[++tcfg->algo_count] = ta_new; ta_new->idx = tcfg->algo_count; /* Set algorithm as default one for given type */ if ((ta_new->flags & TA_FLAG_DEFAULT) != 0 && tcfg->def_algo[ta_new->type] == NULL) tcfg->def_algo[ta_new->type] = ta_new; *idx = ta_new->idx; return (0); } /* * Unregisters table algo using @idx as id. */ void ipfw_del_table_algo(struct ip_fw_chain *ch, int idx) { struct tables_config *tcfg; struct table_algo *ta; tcfg = CHAIN_TO_TCFG(ch); KASSERT(idx <= tcfg->algo_count, ("algo idx %d out of range 1..%d", idx, tcfg->algo_count)); ta = tcfg->algo[idx]; KASSERT(ta != NULL, ("algo idx %d is NULL", idx)); if (tcfg->def_algo[ta->type] == ta) tcfg->def_algo[ta->type] = NULL; free(ta, M_IPFW); } /* * Lists all table algorithms currently available. * Data layout (v0)(current): * Request: [ ipfw_obj_lheader ], size = ipfw_obj_lheader.size * Reply: [ ipfw_obj_lheader ipfw_ta_info x N ] * * Returns 0 on success */ int ipfw_list_table_algo(struct ip_fw_chain *ch, struct sockopt_data *sd) { struct _ipfw_obj_lheader *olh; struct tables_config *tcfg; ipfw_ta_info *i; struct table_algo *ta; uint32_t count, n, size; olh = (struct _ipfw_obj_lheader *)ipfw_get_sopt_header(sd,sizeof(*olh)); if (olh == NULL) return (EINVAL); if (sd->valsize < olh->size) return (EINVAL); IPFW_UH_RLOCK(ch); tcfg = CHAIN_TO_TCFG(ch); count = tcfg->algo_count; size = count * sizeof(ipfw_ta_info) + sizeof(ipfw_obj_lheader); /* Fill in header regadless of buffer size */ olh->count = count; olh->objsize = sizeof(ipfw_ta_info); if (size > olh->size) { olh->size = size; IPFW_UH_RUNLOCK(ch); return (ENOMEM); } olh->size = size; for (n = 1; n <= count; n++) { i = (ipfw_ta_info *)ipfw_get_sopt_space(sd, sizeof(*i)); KASSERT(i != 0, ("previously checked buffer is not enough")); ta = tcfg->algo[n]; strlcpy(i->algoname, ta->name, sizeof(i->algoname)); i->type = ta->type; i->refcnt = ta->refcnt; } IPFW_UH_RUNLOCK(ch); return (0); } /* * Tables rewriting code * */ /* * Determine table number and lookup type for @cmd. * Fill @tbl and @type with appropriate values. * Returns 0 for relevant opcodes, 1 otherwise. */ static int classify_table_opcode(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype) { ipfw_insn_if *cmdif; int skip; uint16_t v; skip = 1; switch (cmd->opcode) { case O_IP_SRC_LOOKUP: case O_IP_DST_LOOKUP: /* Basic IPv4/IPv6 or u32 lookups */ *puidx = cmd->arg1; /* Assume CIDR by default */ *ptype = IPFW_TABLE_CIDR; skip = 0; if (F_LEN(cmd) > F_INSN_SIZE(ipfw_insn_u32)) { /* * generic lookup. The key must be * in 32bit big-endian format. */ v = ((ipfw_insn_u32 *)cmd)->d[1]; switch (v) { case 0: case 1: /* IPv4 src/dst */ break; case 2: case 3: /* src/dst port */ *ptype = IPFW_TABLE_NUMBER; break; case 4: /* uid/gid */ *ptype = IPFW_TABLE_NUMBER; break; case 5: /* jid */ *ptype = IPFW_TABLE_NUMBER; break; case 6: /* dscp */ *ptype = IPFW_TABLE_NUMBER; break; } } break; case O_XMIT: case O_RECV: case O_VIA: /* Interface table, possibly */ cmdif = (ipfw_insn_if *)cmd; if (cmdif->name[0] != '\1') break; *ptype = IPFW_TABLE_INTERFACE; *puidx = cmdif->p.glob; skip = 0; break; case O_IP_FLOW_LOOKUP: *puidx = cmd->arg1; *ptype = IPFW_TABLE_FLOW; skip = 0; break; } return (skip); } /* * Sets new table value for given opcode. * Assume the same opcodes as classify_table_opcode() */ static void update_table_opcode(ipfw_insn *cmd, uint16_t idx) { ipfw_insn_if *cmdif; switch (cmd->opcode) { case O_IP_SRC_LOOKUP: case O_IP_DST_LOOKUP: /* Basic IPv4/IPv6 or u32 lookups */ cmd->arg1 = idx; break; case O_XMIT: case O_RECV: case O_VIA: /* Interface table, possibly */ cmdif = (ipfw_insn_if *)cmd; cmdif->p.glob = idx; break; case O_IP_FLOW_LOOKUP: cmd->arg1 = idx; break; } } /* * Checks table name for validity. * Enforce basic length checks, the rest * should be done in userland. * * Returns 0 if name is considered valid. */ int ipfw_check_table_name(char *name) { int nsize; ipfw_obj_ntlv *ntlv = NULL; nsize = sizeof(ntlv->name); if (strnlen(name, nsize) == nsize) return (EINVAL); if (name[0] == '\0') return (EINVAL); /* * TODO: do some more complicated checks */ return (0); } /* * Find tablename TLV by @uid. * Check @tlvs for valid data inside. * * Returns pointer to found TLV or NULL. */ static ipfw_obj_ntlv * find_name_tlv(void *tlvs, int len, uint16_t uidx) { ipfw_obj_ntlv *ntlv; uintptr_t pa, pe; int l; pa = (uintptr_t)tlvs; pe = pa + len; l = 0; for (; pa < pe; pa += l) { ntlv = (ipfw_obj_ntlv *)pa; l = ntlv->head.length; if (l != sizeof(*ntlv)) return (NULL); if (ntlv->head.type != IPFW_TLV_TBL_NAME) continue; if (ntlv->idx != uidx) continue; if (ipfw_check_table_name(ntlv->name) != 0) return (NULL); return (ntlv); } return (NULL); } /* * Finds table config based on either legacy index * or name in ntlv. * Note @ti structure contains unchecked data from userland. * * Returns pointer to table_config or NULL. */ static struct table_config * find_table(struct namedobj_instance *ni, struct tid_info *ti) { char *name, bname[16]; struct named_object *no; ipfw_obj_ntlv *ntlv; uint32_t set; if (ti->tlvs != NULL) { ntlv = find_name_tlv(ti->tlvs, ti->tlen, ti->uidx); if (ntlv == NULL) return (NULL); name = ntlv->name; set = ntlv->set; } else { snprintf(bname, sizeof(bname), "%d", ti->uidx); name = bname; set = 0; } no = ipfw_objhash_lookup_name(ni, set, name); return ((struct table_config *)no); } static struct table_config * alloc_table_config(struct ip_fw_chain *ch, struct tid_info *ti, struct table_algo *ta, char *aname, uint8_t tflags, uint8_t vtype) { char *name, bname[16]; struct table_config *tc; int error; ipfw_obj_ntlv *ntlv; uint32_t set; if (ti->tlvs != NULL) { ntlv = find_name_tlv(ti->tlvs, ti->tlen, ti->uidx); if (ntlv == NULL) return (NULL); name = ntlv->name; set = ntlv->set; } else { snprintf(bname, sizeof(bname), "%d", ti->uidx); name = bname; set = 0; } tc = malloc(sizeof(struct table_config), M_IPFW, M_WAITOK | M_ZERO); tc->no.name = tc->tablename; tc->no.type = ti->type; tc->no.set = set; tc->tflags = tflags; tc->ta = ta; strlcpy(tc->tablename, name, sizeof(tc->tablename)); /* Set default value type to u32 for compability reasons */ if (vtype == 0) tc->vtype = IPFW_VTYPE_U32; else tc->vtype = vtype; if (ti->tlvs == NULL) { tc->no.compat = 1; tc->no.uidx = ti->uidx; } /* Preallocate data structures for new tables */ error = ta->init(ch, &tc->astate, &tc->ti, aname, tflags); if (error != 0) { free(tc, M_IPFW); return (NULL); } return (tc); } static void free_table_config(struct namedobj_instance *ni, struct table_config *tc) { if (tc->linked == 0) tc->ta->destroy(tc->astate, &tc->ti); free(tc, M_IPFW); } /* * Links @tc to @chain table named instance. * Sets appropriate type/states in @chain table info. */ static void link_table(struct ip_fw_chain *ch, struct table_config *tc) { struct namedobj_instance *ni; struct table_info *ti; uint16_t kidx; IPFW_UH_WLOCK_ASSERT(ch); IPFW_WLOCK_ASSERT(ch); ni = CHAIN_TO_NI(ch); kidx = tc->no.kidx; ipfw_objhash_add(ni, &tc->no); ti = KIDX_TO_TI(ch, kidx); *ti = tc->ti; /* Notify algo on real @ti address */ if (tc->ta->change_ti != NULL) tc->ta->change_ti(tc->astate, ti); tc->linked = 1; tc->ta->refcnt++; } /* * Unlinks @tc from @chain table named instance. * Zeroes states in @chain and stores them in @tc. */ static void unlink_table(struct ip_fw_chain *ch, struct table_config *tc) { struct namedobj_instance *ni; struct table_info *ti; uint16_t kidx; IPFW_UH_WLOCK_ASSERT(ch); IPFW_WLOCK_ASSERT(ch); ni = CHAIN_TO_NI(ch); kidx = tc->no.kidx; /* Clear state. @ti copy is already saved inside @tc */ ipfw_objhash_del(ni, &tc->no); ti = KIDX_TO_TI(ch, kidx); memset(ti, 0, sizeof(struct table_info)); tc->linked = 0; tc->ta->refcnt--; /* Notify algo on real @ti address */ if (tc->ta->change_ti != NULL) tc->ta->change_ti(tc->astate, NULL); } /* * Finds named object by @uidx number. * Refs found object, allocate new index for non-existing object. * Fills in @oib with userland/kernel indexes. * First free oidx pointer is saved back in @oib. * * Returns 0 on success. */ static int bind_table_rule(struct ip_fw_chain *ch, struct ip_fw *rule, struct rule_check_info *ci, struct obj_idx **oib, struct tid_info *ti) { struct table_config *tc; struct namedobj_instance *ni; struct named_object *no; int error, l, cmdlen; ipfw_insn *cmd; struct obj_idx *pidx, *p; pidx = *oib; l = rule->cmd_len; cmd = rule->cmd; cmdlen = 0; error = 0; IPFW_UH_WLOCK(ch); ni = CHAIN_TO_NI(ch); for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); if (classify_table_opcode(cmd, &ti->uidx, &ti->type) != 0) continue; pidx->uidx = ti->uidx; pidx->type = ti->type; if ((tc = find_table(ni, ti)) != NULL) { if (tc->no.type != ti->type) { /* Incompatible types */ error = EINVAL; break; } /* Reference found table and save kidx */ tc->no.refcnt++; pidx->kidx = tc->no.kidx; pidx++; continue; } /* Table not found. Allocate new index and save for later */ if (ipfw_objhash_alloc_idx(ni, &pidx->kidx) != 0) { printf("Unable to allocate table %s index in set %u." " Consider increasing net.inet.ip.fw.tables_max", "", ti->set); error = EBUSY; break; } ci->new_tables++; pidx->new = 1; pidx++; } if (error != 0) { /* Unref everything we have already done */ for (p = *oib; p < pidx; p++) { if (p->new != 0) { ipfw_objhash_free_idx(ni, p->kidx); continue; } /* Find & unref by existing idx */ no = ipfw_objhash_lookup_kidx(ni, p->kidx); KASSERT(no != NULL, ("Ref'd table %d disappeared", p->kidx)); no->refcnt--; } } IPFW_UH_WUNLOCK(ch); *oib = pidx; return (error); } /* * Compatibility function for old ipfw(8) binaries. * Rewrites table kernel indices with userland ones. * Works for \d+ talbes only (e.g. for tables, converted * from old numbered system calls). * * Returns 0 on success. * Raises error on any other tables. */ int ipfw_rewrite_table_kidx(struct ip_fw_chain *chain, struct ip_fw_rule0 *rule) { int cmdlen, error, l; ipfw_insn *cmd; uint16_t kidx, uidx; uint8_t type; struct named_object *no; struct namedobj_instance *ni; ni = CHAIN_TO_NI(chain); error = 0; l = rule->cmd_len; cmd = rule->cmd; cmdlen = 0; for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); if (classify_table_opcode(cmd, &kidx, &type) != 0) continue; if ((no = ipfw_objhash_lookup_kidx(ni, kidx)) == NULL) return (1); uidx = no->uidx; if (no->compat == 0) { /* * We are called via legacy opcode. * Save error and show table as fake number * not to make ipfw(8) hang. */ uidx = 65535; error = 2; } update_table_opcode(cmd, uidx); } return (error); } /* * Sets every table kidx in @bmask which is used in rule @rule. * * Returns number of newly-referenced tables. */ int ipfw_mark_table_kidx(struct ip_fw_chain *chain, struct ip_fw *rule, uint32_t *bmask) { int cmdlen, l, count; ipfw_insn *cmd; uint16_t kidx; uint8_t type; l = rule->cmd_len; cmd = rule->cmd; cmdlen = 0; count = 0; for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); if (classify_table_opcode(cmd, &kidx, &type) != 0) continue; if ((bmask[kidx / 32] & (1 << (kidx % 32))) == 0) count++; bmask[kidx / 32] |= 1 << (kidx % 32); } return (count); } /* * Checks is opcode is referencing table of appropriate type. * Adds reference count for found table if true. * Rewrites user-supplied opcode values with kernel ones. * * Returns 0 on success and appropriate error code otherwise. */ int ipfw_rewrite_table_uidx(struct ip_fw_chain *chain, struct rule_check_info *ci) { int cmdlen, error, ftype, l; ipfw_insn *cmd; uint16_t uidx; uint8_t type; struct table_config *tc; struct table_algo *ta; struct namedobj_instance *ni; struct named_object *no, *no_n, *no_tmp; struct obj_idx *p, *pidx_first, *pidx_last; struct namedobjects_head nh; struct tid_info ti; ni = CHAIN_TO_NI(chain); /* Prepare queue to store configs */ TAILQ_INIT(&nh); /* * Prepare an array for storing opcode indices. * Use stack allocation by default. */ if (ci->table_opcodes <= (sizeof(ci->obuf)/sizeof(ci->obuf[0]))) { /* Stack */ pidx_first = ci->obuf; } else pidx_first = malloc(ci->table_opcodes * sizeof(struct obj_idx), M_IPFW, M_WAITOK | M_ZERO); pidx_last = pidx_first; error = 0; type = 0; ftype = 0; memset(&ti, 0, sizeof(ti)); /* * Use default set for looking up tables (old way) or * use set rule is assigned to (new way). */ ti.set = (V_fw_tables_sets != 0) ? ci->krule->set : 0; 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 * of tables we need to allocate and allocate indexes for each. */ error = bind_table_rule(chain, ci->krule, ci, &pidx_last, &ti); if (error != 0) { if (pidx_first != ci->obuf) free(pidx_first, M_IPFW); return (error); } /* * Stage 2: allocate table configs for every non-existent table */ if (ci->new_tables > 0) { for (p = pidx_first; p < pidx_last; p++) { if (p->new == 0) continue; ti.uidx = p->uidx; ti.type = p->type; ti.atype = 0; ta = find_table_algo(CHAIN_TO_TCFG(chain), &ti, NULL); if (ta == NULL) { error = ENOTSUP; goto free; } tc = alloc_table_config(chain, &ti, ta, NULL, 0, IPFW_VTYPE_U32); if (tc == NULL) { error = ENOMEM; goto free; } tc->no.kidx = p->kidx; tc->no.refcnt = 1; /* Add to list */ TAILQ_INSERT_TAIL(&nh, &tc->no, nn_next); } /* * Stage 2.1: Check if we're going to create 2 tables * with the same name, but different table types. */ TAILQ_FOREACH(no, &nh, nn_next) { TAILQ_FOREACH(no_tmp, &nh, nn_next) { if (ipfw_objhash_same_name(ni, no, no_tmp) == 0) continue; if (no->type != no_tmp->type) { error = EINVAL; goto free; } } } } IPFW_UH_WLOCK(chain); if (ci->new_tables > 0) { /* * Stage 3: link & reference new table configs */ /* * Step 3.1: Check if some tables we need to create have been * already created with different table type. */ error = 0; TAILQ_FOREACH_SAFE(no, &nh, nn_next, no_tmp) { no_n = ipfw_objhash_lookup_name(ni, no->set, no->name); if (no_n == NULL) continue; if (no_n->type != no->type) { error = EINVAL; break; } } if (error != 0) { /* * Someone has allocated table with different table type. * We have to rollback everything. */ IPFW_UH_WUNLOCK(chain); goto free; } /* * Attach new tables. * We need to set table pointers for each new table, * so we have to acquire main WLOCK. */ IPFW_WLOCK(chain); TAILQ_FOREACH_SAFE(no, &nh, nn_next, no_tmp) { no_n = ipfw_objhash_lookup_name(ni, no->set, no->name); if (no_n == NULL) { /* New table. Attach to runtime hash */ TAILQ_REMOVE(&nh, no, nn_next); link_table(chain, (struct table_config *)no); continue; } /* * Newly-allocated table with the same type. * Reference it and update out @pidx array * rewrite info. */ no_n->refcnt++; /* Keep oib array in sync: update kidx */ for (p = pidx_first; p < pidx_last; p++) { if (p->kidx != no->kidx) continue; /* Update kidx */ p->kidx = no_n->kidx; break; } } IPFW_WUNLOCK(chain); } /* Perform rule rewrite */ l = ci->krule->cmd_len; cmd = ci->krule->cmd; cmdlen = 0; p = pidx_first; for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); if (classify_table_opcode(cmd, &uidx, &type) != 0) continue; update_table_opcode(cmd, p->kidx); p++; } IPFW_UH_WUNLOCK(chain); error = 0; /* * Stage 4: free resources */ free: if (!TAILQ_EMPTY(&nh)) { /* Free indexes first */ IPFW_UH_WLOCK(chain); TAILQ_FOREACH_SAFE(no, &nh, nn_next, no_tmp) { ipfw_objhash_free_idx(ni, no->kidx); } IPFW_UH_WUNLOCK(chain); /* Free configs */ TAILQ_FOREACH_SAFE(no, &nh, nn_next, no_tmp) free_table_config(ni, tc); } if (pidx_first != ci->obuf) free(pidx_first, M_IPFW); return (error); } /* * Remove references from every table used in @rule. */ void ipfw_unbind_table_rule(struct ip_fw_chain *chain, struct ip_fw *rule) { int cmdlen, l; ipfw_insn *cmd; struct namedobj_instance *ni; struct named_object *no; uint16_t kidx; uint8_t type; ni = CHAIN_TO_NI(chain); l = rule->cmd_len; cmd = rule->cmd; cmdlen = 0; for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); if (classify_table_opcode(cmd, &kidx, &type) != 0) continue; no = ipfw_objhash_lookup_kidx(ni, kidx); KASSERT(no != NULL, ("table id %d not found", kidx)); KASSERT(no->type == type, ("wrong type %d (%d) for table id %d", no->type, type, kidx)); KASSERT(no->refcnt > 0, ("refcount for table %d is %d", kidx, no->refcnt)); no->refcnt--; } } /* * Removes table bindings for every rule in rule chain @head. */ void ipfw_unbind_table_list(struct ip_fw_chain *chain, struct ip_fw *head) { struct ip_fw *rule; while ((rule = head) != NULL) { head = head->x_next; ipfw_unbind_table_rule(chain, rule); } } /* end of file */ Index: projects/ipfw/sys/netpfil/ipfw/ip_fw_table.h =================================================================== --- projects/ipfw/sys/netpfil/ipfw/ip_fw_table.h (revision 269472) +++ projects/ipfw/sys/netpfil/ipfw/ip_fw_table.h (revision 269473) @@ -1,187 +1,187 @@ /*- * Copyright (c) 2002-2009 Luigi Rizzo, Universita` di Pisa * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD: projects/ipfw/sys/netpfil/ipfw/ip_fw_private.h 267467 2014-06-14 10:58:39Z melifaro $ */ #ifndef _IPFW2_TABLE_H #define _IPFW2_TABLE_H /* * Internal constants and data structures used by ipfw tables * not meant to be exported outside the kernel. */ #ifdef _KERNEL struct table_info { table_lookup_t *lookup; /* Lookup function */ void *state; /* Lookup radix/other structure */ void *xstate; /* eXtended state */ u_long data; /* Hints for given func */ }; /* Internal structures for handling sockopt data */ struct tid_info { uint32_t set; /* table set */ uint16_t uidx; /* table index */ uint8_t type; /* table type */ uint8_t atype; void *tlvs; /* Pointer to first TLV */ int tlen; /* Total TLV size block */ }; struct tentry_info { void *paddr; uint8_t masklen; /* mask length */ uint8_t subtype; uint16_t flags; /* record flags */ uint32_t value; /* value */ }; #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); typedef void (ta_destroy)(void *ta_state, struct table_info *ti); typedef int (ta_prepare_add)(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf); typedef int (ta_prepare_del)(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf); typedef int (ta_add)(void *ta_state, struct table_info *ti, struct tentry_info *tei, void *ta_buf, uint32_t *pnum); typedef int (ta_del)(void *ta_state, struct table_info *ti, struct tentry_info *tei, void *ta_buf, uint32_t *pnum); typedef void (ta_flush_entry)(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf); typedef int (ta_has_space)(void *ta_state, struct table_info *ti, uint32_t count, uint64_t *pflags); typedef int (ta_prepare_mod)(void *ta_buf, uint64_t *pflags); typedef int (ta_fill_mod)(void *ta_state, struct table_info *ti, void *ta_buf, uint64_t *pflags); typedef int (ta_modify)(void *ta_state, struct table_info *ti, void *ta_buf, uint64_t pflags); typedef void (ta_flush_mod)(void *ta_buf); typedef void (ta_change_ti)(void *ta_state, struct table_info *ti); typedef void (ta_print_config)(void *ta_state, struct table_info *ti, char *buf, size_t bufsize); typedef int ta_foreach_f(void *node, void *arg); typedef void ta_foreach(void *ta_state, struct table_info *ti, ta_foreach_f *f, void *arg); typedef int ta_dump_tentry(void *ta_state, struct table_info *ti, void *e, ipfw_obj_tentry *tent); typedef int ta_find_tentry(void *ta_state, struct table_info *ti, ipfw_obj_tentry *tent); -typedef int ta_dump_tinfo(void *ta_state, struct table_info *ti, - ifpw_ta_tinfo *tinfo); +typedef void ta_dump_tinfo(void *ta_state, struct table_info *ti, + ipfw_ta_tinfo *tinfo); struct table_algo { char name[16]; uint32_t idx; uint32_t type; uint32_t refcnt; uint32_t flags; size_t ta_buf_size; ta_init *init; ta_destroy *destroy; ta_prepare_add *prepare_add; ta_prepare_del *prepare_del; ta_add *add; ta_del *del; ta_flush_entry *flush_entry; ta_find_tentry *find_tentry; ta_has_space *has_space; ta_prepare_mod *prepare_mod; ta_fill_mod *fill_mod; ta_modify *modify; ta_flush_mod *flush_mod; ta_change_ti *change_ti; ta_foreach *foreach; ta_dump_tentry *dump_tentry; ta_print_config *print_config; ta_dump_tinfo *dump_tinfo; }; #define TA_FLAG_DEFAULT 0x01 /* Algorithm is default for given type */ int ipfw_add_table_algo(struct ip_fw_chain *ch, struct table_algo *ta, size_t size, int *idx); void ipfw_del_table_algo(struct ip_fw_chain *ch, int idx); void ipfw_table_algo_init(struct ip_fw_chain *chain); void ipfw_table_algo_destroy(struct ip_fw_chain *chain); /* direct ipfw_ctl handlers */ int ipfw_listsize_tables(struct ip_fw_chain *ch, struct sockopt_data *sd); int ipfw_list_tables(struct ip_fw_chain *ch, struct sockopt_data *sd); int ipfw_dump_table(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd); int ipfw_describe_table(struct ip_fw_chain *ch, struct sockopt_data *sd); int ipfw_find_table_entry(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd); int ipfw_create_table(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd); int ipfw_manage_table_ent(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd); int ipfw_flush_table(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd); int ipfw_list_table_algo(struct ip_fw_chain *ch, struct sockopt_data *sd); /* Exported to support legacy opcodes */ int add_table_entry(struct ip_fw_chain *ch, struct tid_info *ti, struct tentry_info *tei, uint32_t count); int del_table_entry(struct ip_fw_chain *ch, struct tid_info *ti, struct tentry_info *tei, uint32_t count); int flush_table(struct ip_fw_chain *ch, struct tid_info *ti); int ipfw_rewrite_table_uidx(struct ip_fw_chain *chain, struct rule_check_info *ci); int ipfw_rewrite_table_kidx(struct ip_fw_chain *chain, struct ip_fw_rule0 *rule); int ipfw_mark_table_kidx(struct ip_fw_chain *chain, struct ip_fw *rule, uint32_t *bmask); int ipfw_export_table_ntlv(struct ip_fw_chain *ch, uint16_t kidx, struct sockopt_data *sd); void ipfw_unbind_table_rule(struct ip_fw_chain *chain, struct ip_fw *rule); void ipfw_unbind_table_list(struct ip_fw_chain *chain, struct ip_fw *head); /* utility functions */ void objheader_to_ti(struct _ipfw_obj_header *oh, struct tid_info *ti); int ipfw_check_table_name(char *name); /* Legacy interfaces */ int ipfw_count_table(struct ip_fw_chain *ch, struct tid_info *ti, uint32_t *cnt); int ipfw_count_xtable(struct ip_fw_chain *ch, struct tid_info *ti, uint32_t *cnt); int ipfw_dump_table_legacy(struct ip_fw_chain *ch, struct tid_info *ti, ipfw_table *tbl); #endif /* _KERNEL */ #endif /* _IPFW2_TABLE_H */ Index: projects/ipfw/sys/netpfil/ipfw/ip_fw_table_algo.c =================================================================== --- projects/ipfw/sys/netpfil/ipfw/ip_fw_table_algo.c (revision 269472) +++ projects/ipfw/sys/netpfil/ipfw/ip_fw_table_algo.c (revision 269473) @@ -1,3227 +1,3346 @@ /*- * Copyright (c) 2004 Ruslan Ermilov and Vsevolod Lobko. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD: projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c 267384 2014-06-12 09:59:11Z melifaro $"); /* * Lookup table algorithms. * */ #include "opt_ipfw.h" #include "opt_inet.h" #ifndef INET #error IPFIREWALL requires INET. #endif /* INET */ #include "opt_inet6.h" #include #include #include #include #include #include #include #include #include /* ip_fw.h requires IFNAMSIZ */ #include #include #include /* struct ipfw_rule_ref */ #include #include #include static MALLOC_DEFINE(M_IPFW_TBL, "ipfw_tbl", "IpFw tables"); /* * Utility structures/functions common to more than one algo */ struct mod_item { void *main_ptr; size_t size; void *main_ptr6; size_t size6; }; static int badd(const void *key, void *item, void *base, size_t nmemb, size_t size, int (*compar) (const void *, const void *)); static int bdel(const void *key, void *base, size_t nmemb, size_t size, int (*compar) (const void *, const void *)); /* * CIDR implementation using radix * */ /* * The radix code expects addr and mask to be array of bytes, * with the first byte being the length of the array. rn_inithead * is called with the offset in bits of the lookup key within the * array. If we use a sockaddr_in as the underlying type, * sin_len is conveniently located at offset 0, sin_addr is at * offset 4 and normally aligned. * But for portability, let's avoid assumption and make the code explicit */ #define KEY_LEN(v) *((uint8_t *)&(v)) /* * Do not require radix to compare more than actual IPv4/IPv6 address */ #define KEY_LEN_INET (offsetof(struct sockaddr_in, sin_addr) + sizeof(in_addr_t)) #define KEY_LEN_INET6 (offsetof(struct sa_in6, sin6_addr) + sizeof(struct in6_addr)) #define OFF_LEN_INET (8 * offsetof(struct sockaddr_in, sin_addr)) #define OFF_LEN_INET6 (8 * offsetof(struct sa_in6, sin6_addr)) struct radix_cidr_entry { struct radix_node rn[2]; struct sockaddr_in addr; uint32_t value; uint8_t masklen; }; struct sa_in6 { uint8_t sin6_len; uint8_t sin6_family; uint8_t pad[2]; struct in6_addr sin6_addr; }; struct radix_cidr_xentry { struct radix_node rn[2]; struct sa_in6 addr6; uint32_t value; uint8_t masklen; }; +struct radix_cfg { + struct radix_node_head *head4; + struct radix_node_head *head6; + size_t count4; + size_t count6; +}; + struct ta_buf_cidr { void *ent_ptr; struct sockaddr *addr_ptr; struct sockaddr *mask_ptr; union { struct { struct sockaddr_in sa; struct sockaddr_in ma; } a4; struct { struct sa_in6 sa; struct sa_in6 ma; } a6; } addr; }; static int ta_lookup_radix(struct table_info *ti, void *key, uint32_t keylen, uint32_t *val) { struct radix_node_head *rnh; if (keylen == sizeof(in_addr_t)) { struct radix_cidr_entry *ent; struct sockaddr_in sa; KEY_LEN(sa) = KEY_LEN_INET; sa.sin_addr.s_addr = *((in_addr_t *)key); rnh = (struct radix_node_head *)ti->state; ent = (struct radix_cidr_entry *)(rnh->rnh_matchaddr(&sa, rnh)); if (ent != NULL) { *val = ent->value; return (1); } } else { struct radix_cidr_xentry *xent; struct sa_in6 sa6; KEY_LEN(sa6) = KEY_LEN_INET6; memcpy(&sa6.sin6_addr, key, sizeof(struct in6_addr)); rnh = (struct radix_node_head *)ti->xstate; xent = (struct radix_cidr_xentry *)(rnh->rnh_matchaddr(&sa6, rnh)); if (xent != NULL) { *val = xent->value; return (1); } } return (0); } /* * New table */ static int ta_init_radix(struct ip_fw_chain *ch, void **ta_state, struct table_info *ti, char *data, uint8_t tflags) { + struct radix_cfg *cfg; if (!rn_inithead(&ti->state, OFF_LEN_INET)) return (ENOMEM); if (!rn_inithead(&ti->xstate, OFF_LEN_INET6)) { rn_detachhead(&ti->state); return (ENOMEM); } - *ta_state = NULL; + cfg = malloc(sizeof(struct radix_cfg), M_IPFW, M_WAITOK | M_ZERO); + + *ta_state = cfg; ti->lookup = ta_lookup_radix; return (0); } static int flush_radix_entry(struct radix_node *rn, void *arg) { struct radix_node_head * const rnh = arg; struct radix_cidr_entry *ent; ent = (struct radix_cidr_entry *) rnh->rnh_deladdr(rn->rn_key, rn->rn_mask, rnh); if (ent != NULL) free(ent, M_IPFW_TBL); return (0); } static void ta_destroy_radix(void *ta_state, struct table_info *ti) { + struct radix_cfg *cfg; struct radix_node_head *rnh; + cfg = (struct radix_cfg *)ta_state; + rnh = (struct radix_node_head *)(ti->state); rnh->rnh_walktree(rnh, flush_radix_entry, rnh); rn_detachhead(&ti->state); rnh = (struct radix_node_head *)(ti->xstate); rnh->rnh_walktree(rnh, flush_radix_entry, rnh); rn_detachhead(&ti->xstate); + + free(cfg, M_IPFW); } +/* + * Provide algo-specific table info + */ +static void +ta_dump_radix_tinfo(void *ta_state, struct table_info *ti, ipfw_ta_tinfo *tinfo) +{ + struct radix_cfg *cfg; + + cfg = (struct radix_cfg *)ta_state; + + tinfo->flags = IPFW_TATFLAGS_AFDATA | IPFW_TATFLAGS_AFITEM; + tinfo->taclass4 = IPFW_TACLASS_RADIX; + tinfo->count4 = cfg->count4; + tinfo->itemsize4 = sizeof(struct radix_cidr_entry); + tinfo->taclass6 = IPFW_TACLASS_RADIX; + tinfo->count6 = cfg->count6; + tinfo->itemsize6 = sizeof(struct radix_cidr_xentry); +} + static int ta_dump_radix_tentry(void *ta_state, struct table_info *ti, void *e, ipfw_obj_tentry *tent) { struct radix_cidr_entry *n; struct radix_cidr_xentry *xn; n = (struct radix_cidr_entry *)e; /* Guess IPv4/IPv6 radix by sockaddr family */ if (n->addr.sin_family == AF_INET) { tent->k.addr.s_addr = n->addr.sin_addr.s_addr; tent->masklen = n->masklen; tent->subtype = AF_INET; tent->value = n->value; #ifdef INET6 } else { xn = (struct radix_cidr_xentry *)e; memcpy(&tent->k, &xn->addr6.sin6_addr, sizeof(struct in6_addr)); tent->masklen = xn->masklen; tent->subtype = AF_INET6; tent->value = xn->value; #endif } return (0); } static int ta_find_radix_tentry(void *ta_state, struct table_info *ti, ipfw_obj_tentry *tent) { struct radix_node_head *rnh; void *e; e = NULL; if (tent->subtype == AF_INET) { struct sockaddr_in sa; KEY_LEN(sa) = KEY_LEN_INET; sa.sin_addr.s_addr = tent->k.addr.s_addr; rnh = (struct radix_node_head *)ti->state; e = rnh->rnh_matchaddr(&sa, rnh); } else { struct sa_in6 sa6; KEY_LEN(sa6) = KEY_LEN_INET6; memcpy(&sa6.sin6_addr, &tent->k.addr6, sizeof(struct in6_addr)); rnh = (struct radix_node_head *)ti->xstate; e = rnh->rnh_matchaddr(&sa6, rnh); } if (e != NULL) { ta_dump_radix_tentry(ta_state, ti, e, tent); return (0); } return (ENOENT); } static void ta_foreach_radix(void *ta_state, struct table_info *ti, ta_foreach_f *f, void *arg) { struct radix_node_head *rnh; rnh = (struct radix_node_head *)(ti->state); rnh->rnh_walktree(rnh, (walktree_f_t *)f, arg); rnh = (struct radix_node_head *)(ti->xstate); rnh->rnh_walktree(rnh, (walktree_f_t *)f, arg); } #ifdef INET6 static inline void ipv6_writemask(struct in6_addr *addr6, uint8_t mask) { uint32_t *cp; for (cp = (uint32_t *)addr6; mask >= 32; mask -= 32) *cp++ = 0xFFFFFFFF; *cp = htonl(mask ? ~((1 << (32 - mask)) - 1) : 0); } #endif static void tei_to_sockaddr_ent(struct tentry_info *tei, struct sockaddr *sa, struct sockaddr *ma, int *set_mask) { int mlen; struct sockaddr_in *addr, *mask; struct sockaddr_in6 *addr6, *mask6; in_addr_t a4; mlen = tei->masklen; if (tei->subtype == AF_INET) { #ifdef INET addr = (struct sockaddr_in *)sa; mask = (struct sockaddr_in *)ma; /* Set 'total' structure length */ KEY_LEN(*addr) = KEY_LEN_INET; KEY_LEN(*mask) = KEY_LEN_INET; addr->sin_family = AF_INET; mask->sin_addr.s_addr = htonl(mlen ? ~((1 << (32 - mlen)) - 1) : 0); a4 = *((in_addr_t *)tei->paddr); addr->sin_addr.s_addr = a4 & mask->sin_addr.s_addr; if (mlen != 32) *set_mask = 1; else *set_mask = 0; #endif #ifdef INET6 } else if (tei->subtype == AF_INET6) { /* IPv6 case */ addr6 = (struct sockaddr_in6 *)sa; mask6 = (struct sockaddr_in6 *)ma; /* Set 'total' structure length */ KEY_LEN(*addr6) = KEY_LEN_INET6; KEY_LEN(*mask6) = KEY_LEN_INET6; addr6->sin6_family = AF_INET6; ipv6_writemask(&mask6->sin6_addr, mlen); memcpy(&addr6->sin6_addr, tei->paddr, sizeof(struct in6_addr)); APPLY_MASK(&addr6->sin6_addr, &mask6->sin6_addr); if (mlen != 128) *set_mask = 1; else *set_mask = 0; } #endif } static int ta_prepare_add_radix(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf) { struct ta_buf_cidr *tb; struct radix_cidr_entry *ent; struct radix_cidr_xentry *xent; struct sockaddr *addr, *mask; int mlen, set_mask; tb = (struct ta_buf_cidr *)ta_buf; mlen = tei->masklen; set_mask = 0; if (tei->subtype == AF_INET) { #ifdef INET if (mlen > 32) return (EINVAL); ent = malloc(sizeof(*ent), M_IPFW_TBL, M_WAITOK | M_ZERO); ent->value = tei->value; ent->masklen = mlen; addr = (struct sockaddr *)&ent->addr; mask = (struct sockaddr *)&tb->addr.a4.ma; tb->ent_ptr = ent; #endif #ifdef INET6 } else if (tei->subtype == AF_INET6) { /* IPv6 case */ if (mlen > 128) return (EINVAL); xent = malloc(sizeof(*xent), M_IPFW_TBL, M_WAITOK | M_ZERO); xent->value = tei->value; xent->masklen = mlen; addr = (struct sockaddr *)&xent->addr6; mask = (struct sockaddr *)&tb->addr.a6.ma; tb->ent_ptr = xent; #endif } else { /* Unknown CIDR type */ return (EINVAL); } tei_to_sockaddr_ent(tei, addr, mask, &set_mask); /* Set pointers */ tb->addr_ptr = addr; if (set_mask != 0) tb->mask_ptr = mask; return (0); } static int ta_add_radix(void *ta_state, struct table_info *ti, struct tentry_info *tei, void *ta_buf, uint32_t *pnum) { + struct radix_cfg *cfg; struct radix_node_head *rnh; struct radix_node *rn; struct ta_buf_cidr *tb; uint32_t *old_value, value; + cfg = (struct radix_cfg *)ta_state; tb = (struct ta_buf_cidr *)ta_buf; if (tei->subtype == AF_INET) rnh = ti->state; else rnh = ti->xstate; /* 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 */ if (tei->subtype == AF_INET) old_value = &((struct radix_cidr_entry *)rn)->value; else old_value = &((struct radix_cidr_xentry *)rn)->value; value = *old_value; *old_value = tei->value; tei->value = value; /* Indicate that update has happened instead of addition */ tei->flags |= TEI_FLAGS_UPDATED; *pnum = 0; 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); } + if (tei->subtype == AF_INET) + cfg->count4++; + else + cfg->count6++; tb->ent_ptr = NULL; *pnum = 1; return (0); } static int ta_prepare_del_radix(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf) { struct ta_buf_cidr *tb; struct sockaddr *addr, *mask; int mlen, set_mask; tb = (struct ta_buf_cidr *)ta_buf; mlen = tei->masklen; set_mask = 0; if (tei->subtype == AF_INET) { if (mlen > 32) return (EINVAL); addr = (struct sockaddr *)&tb->addr.a4.sa; mask = (struct sockaddr *)&tb->addr.a4.ma; #ifdef INET6 } else if (tei->subtype == AF_INET6) { if (mlen > 128) return (EINVAL); addr = (struct sockaddr *)&tb->addr.a6.sa; mask = (struct sockaddr *)&tb->addr.a6.ma; #endif } else return (EINVAL); tei_to_sockaddr_ent(tei, addr, mask, &set_mask); tb->addr_ptr = addr; if (set_mask != 0) tb->mask_ptr = mask; return (0); } static int ta_del_radix(void *ta_state, struct table_info *ti, struct tentry_info *tei, void *ta_buf, uint32_t *pnum) { + struct radix_cfg *cfg; struct radix_node_head *rnh; struct radix_node *rn; struct ta_buf_cidr *tb; + cfg = (struct radix_cfg *)ta_state; tb = (struct ta_buf_cidr *)ta_buf; if (tei->subtype == AF_INET) rnh = ti->state; else rnh = ti->xstate; rn = rnh->rnh_deladdr(tb->addr_ptr, tb->mask_ptr, rnh); /* Save entry value to @tei */ if (tei->subtype == AF_INET) tei->value = ((struct radix_cidr_entry *)rn)->value; else tei->value = ((struct radix_cidr_xentry *)rn)->value; tb->ent_ptr = rn; if (rn == NULL) return (ENOENT); + if (tei->subtype == AF_INET) + cfg->count4--; + else + cfg->count6--; *pnum = 1; return (0); } static void ta_flush_radix_entry(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf) { struct ta_buf_cidr *tb; tb = (struct ta_buf_cidr *)ta_buf; if (tb->ent_ptr != NULL) free(tb->ent_ptr, M_IPFW_TBL); } static int ta_has_space_radix(void *ta_state, struct table_info *ti, uint32_t count, uint64_t *pflags) { /* * radix does not require additional memory allocations * other than nodes itself. Adding new masks to the tree do * but we don't have any API to call (and we don't known which * sizes do we need). */ return (1); } struct table_algo cidr_radix = { .name = "cidr:radix", .type = IPFW_TABLE_CIDR, .flags = TA_FLAG_DEFAULT, .ta_buf_size = sizeof(struct ta_buf_cidr), .init = ta_init_radix, .destroy = ta_destroy_radix, .prepare_add = ta_prepare_add_radix, .prepare_del = ta_prepare_del_radix, .add = ta_add_radix, .del = ta_del_radix, .flush_entry = ta_flush_radix_entry, .foreach = ta_foreach_radix, .dump_tentry = ta_dump_radix_tentry, .find_tentry = ta_find_radix_tentry, + .dump_tinfo = ta_dump_radix_tinfo, .has_space = ta_has_space_radix, }; /* * cidr:hash cmds * * * ti->data: * [inv.mask4][inv.mask6][log2hsize4][log2hsize6] * [ 8][ 8[ 8][ 8] * * inv.mask4: 32 - mask * inv.mask6: * 1) _slow lookup: mask * 2) _aligned: (128 - mask) / 8 * 3) _64: 8 * * * pflags: * [v4=1/v6=0][hsize] * [ 32][ 32] */ struct chashentry; SLIST_HEAD(chashbhead, chashentry); struct chash_cfg { struct chashbhead *head4; struct chashbhead *head6; size_t size4; size_t size6; size_t items4; size_t items6; uint8_t mask4; uint8_t mask6; }; struct chashentry { SLIST_ENTRY(chashentry) next; uint32_t value; uint32_t type; union { uint32_t a4; /* Host format */ struct in6_addr a6; /* Network format */ } a; }; struct ta_buf_chash { void *ent_ptr; struct chashentry ent; }; static __inline uint32_t hash_ip(uint32_t addr, int hsize) { return (addr % (hsize - 1)); } static __inline uint32_t hash_ip6(struct in6_addr *addr6, int hsize) { uint32_t i; i = addr6->s6_addr32[0] ^ addr6->s6_addr32[1] ^ addr6->s6_addr32[2] ^ addr6->s6_addr32[3]; return (i % (hsize - 1)); } static __inline uint16_t hash_ip64(struct in6_addr *addr6, int hsize) { uint32_t i; i = addr6->s6_addr32[0] ^ addr6->s6_addr32[1]; return (i % (hsize - 1)); } static __inline uint32_t hash_ip6_slow(struct in6_addr *addr6, void *key, int mask, int hsize) { struct in6_addr mask6; ipv6_writemask(&mask6, mask); memcpy(addr6, key, sizeof(struct in6_addr)); APPLY_MASK(addr6, &mask6); return (hash_ip6(addr6, hsize)); } static __inline uint32_t hash_ip6_al(struct in6_addr *addr6, void *key, int mask, int hsize) { uint64_t *paddr; paddr = (uint64_t *)addr6; *paddr = 0; *(paddr + 1) = 0; memcpy(addr6, key, mask); return (hash_ip6(addr6, hsize)); } static int ta_lookup_chash_slow(struct table_info *ti, void *key, uint32_t keylen, uint32_t *val) { struct chashbhead *head; struct chashentry *ent; uint16_t hash, hsize; uint8_t imask; if (keylen == sizeof(in_addr_t)) { head = (struct chashbhead *)ti->state; imask = ti->data >> 24; hsize = 1 << ((ti->data & 0xFFFF) >> 8); uint32_t a; a = ntohl(*((in_addr_t *)key)); a = a >> imask; hash = hash_ip(a, hsize); SLIST_FOREACH(ent, &head[hash], next) { if (ent->a.a4 == a) { *val = ent->value; return (1); } } } else { /* IPv6: worst scenario: non-round mask */ struct in6_addr addr6; head = (struct chashbhead *)ti->xstate; imask = (ti->data & 0xFF0000) >> 16; hsize = 1 << (ti->data & 0xFF); hash = hash_ip6_slow(&addr6, key, imask, hsize); SLIST_FOREACH(ent, &head[hash], next) { if (memcmp(&ent->a.a6, &addr6, 16) == 0) { *val = ent->value; return (1); } } } return (0); } static int ta_lookup_chash_aligned(struct table_info *ti, void *key, uint32_t keylen, uint32_t *val) { struct chashbhead *head; struct chashentry *ent; uint16_t hash, hsize; uint8_t imask; if (keylen == sizeof(in_addr_t)) { head = (struct chashbhead *)ti->state; imask = ti->data >> 24; hsize = 1 << ((ti->data & 0xFFFF) >> 8); uint32_t a; a = ntohl(*((in_addr_t *)key)); a = a >> imask; hash = hash_ip(a, hsize); SLIST_FOREACH(ent, &head[hash], next) { if (ent->a.a4 == a) { *val = ent->value; return (1); } } } else { /* IPv6: aligned to 8bit mask */ struct in6_addr addr6; uint64_t *paddr, *ptmp; head = (struct chashbhead *)ti->xstate; imask = (ti->data & 0xFF0000) >> 16; hsize = 1 << (ti->data & 0xFF); hash = hash_ip6_al(&addr6, key, imask, hsize); paddr = (uint64_t *)&addr6; SLIST_FOREACH(ent, &head[hash], next) { ptmp = (uint64_t *)&ent->a.a6; if (paddr[0] == ptmp[0] && paddr[1] == ptmp[1]) { *val = ent->value; return (1); } } } return (0); } static int ta_lookup_chash_64(struct table_info *ti, void *key, uint32_t keylen, uint32_t *val) { struct chashbhead *head; struct chashentry *ent; uint16_t hash, hsize; uint8_t imask; if (keylen == sizeof(in_addr_t)) { head = (struct chashbhead *)ti->state; imask = ti->data >> 24; hsize = 1 << ((ti->data & 0xFFFF) >> 8); uint32_t a; a = ntohl(*((in_addr_t *)key)); a = a >> imask; hash = hash_ip(a, hsize); SLIST_FOREACH(ent, &head[hash], next) { if (ent->a.a4 == a) { *val = ent->value; return (1); } } } else { /* IPv6: /64 */ uint64_t a6, *paddr; head = (struct chashbhead *)ti->xstate; paddr = (uint64_t *)key; hsize = 1 << (ti->data & 0xFF); a6 = *paddr; hash = hash_ip64((struct in6_addr *)key, hsize); SLIST_FOREACH(ent, &head[hash], next) { paddr = (uint64_t *)&ent->a.a6; if (a6 == *paddr) { *val = ent->value; return (1); } } } return (0); } static int chash_parse_opts(struct chash_cfg *cfg, char *data) { char *pdel, *pend, *s; int mask4, mask6; mask4 = cfg->mask4; mask6 = cfg->mask6; if (data == NULL) return (0); if ((pdel = strchr(data, ' ')) == NULL) return (0); while (*pdel == ' ') pdel++; if (strncmp(pdel, "masks=", 6) != 0) return (EINVAL); if ((s = strchr(pdel, ' ')) != NULL) *s++ = '\0'; pdel += 6; /* Need /XX[,/YY] */ if (*pdel++ != '/') return (EINVAL); mask4 = strtol(pdel, &pend, 10); if (*pend == ',') { /* ,/YY */ pdel = pend + 1; if (*pdel++ != '/') return (EINVAL); mask6 = strtol(pdel, &pend, 10); if (*pend != '\0') return (EINVAL); } else if (*pend != '\0') return (EINVAL); if (mask4 < 0 || mask4 > 32 || mask6 < 0 || mask6 > 128) return (EINVAL); cfg->mask4 = mask4; cfg->mask6 = mask6; return (0); } static void ta_print_chash_config(void *ta_state, struct table_info *ti, char *buf, size_t bufsize) { struct chash_cfg *cfg; cfg = (struct chash_cfg *)ta_state; if (cfg->mask4 != 32 || cfg->mask6 != 128) snprintf(buf, bufsize, "%s masks=/%d,/%d", "cidr:hash", cfg->mask4, cfg->mask6); else snprintf(buf, bufsize, "%s", "cidr:hash"); } static int log2(uint32_t v) { uint32_t r; r = 0; while (v >>= 1) r++; return (r); } /* * New table. * We assume 'data' to be either NULL or the following format: * 'cidr:hash [masks=/32[,/128]]' */ static int ta_init_chash(struct ip_fw_chain *ch, void **ta_state, struct table_info *ti, char *data, uint8_t tflags) { int error, i; uint32_t hsize; struct chash_cfg *cfg; cfg = malloc(sizeof(struct chash_cfg), M_IPFW, M_WAITOK | M_ZERO); cfg->mask4 = 32; cfg->mask6 = 128; if ((error = chash_parse_opts(cfg, data)) != 0) { free(cfg, M_IPFW); return (error); } cfg->size4 = 128; cfg->size6 = 128; cfg->head4 = malloc(sizeof(struct chashbhead) * cfg->size4, M_IPFW, M_WAITOK | M_ZERO); cfg->head6 = malloc(sizeof(struct chashbhead) * cfg->size6, M_IPFW, M_WAITOK | M_ZERO); for (i = 0; i < cfg->size4; i++) SLIST_INIT(&cfg->head4[i]); for (i = 0; i < cfg->size6; i++) SLIST_INIT(&cfg->head6[i]); *ta_state = cfg; ti->state = cfg->head4; ti->xstate = cfg->head6; /* Store data depending on v6 mask length */ hsize = log2(cfg->size4) << 8 | log2(cfg->size6); if (cfg->mask6 == 64) { ti->data = (32 - cfg->mask4) << 24 | (128 - cfg->mask6) << 16| hsize; ti->lookup = ta_lookup_chash_64; } else if ((cfg->mask6 % 8) == 0) { ti->data = (32 - cfg->mask4) << 24 | cfg->mask6 << 13 | hsize; ti->lookup = ta_lookup_chash_aligned; } else { /* don't do that! */ ti->data = (32 - cfg->mask4) << 24 | cfg->mask6 << 16 | hsize; ti->lookup = ta_lookup_chash_slow; } return (0); } static void ta_destroy_chash(void *ta_state, struct table_info *ti) { struct chash_cfg *cfg; struct chashentry *ent, *ent_next; int i; cfg = (struct chash_cfg *)ta_state; for (i = 0; i < cfg->size4; i++) SLIST_FOREACH_SAFE(ent, &cfg->head4[i], next, ent_next) free(ent, M_IPFW_TBL); for (i = 0; i < cfg->size6; i++) SLIST_FOREACH_SAFE(ent, &cfg->head6[i], next, ent_next) free(ent, M_IPFW_TBL); free(cfg->head4, M_IPFW); free(cfg->head6, M_IPFW); free(cfg, M_IPFW); } +static void +ta_dump_chash_tinfo(void *ta_state, struct table_info *ti, ipfw_ta_tinfo *tinfo) +{ + struct chash_cfg *cfg; + + cfg = (struct chash_cfg *)ta_state; + + tinfo->flags = IPFW_TATFLAGS_AFDATA | IPFW_TATFLAGS_AFITEM; + tinfo->taclass4 = IPFW_TACLASS_HASH; + tinfo->size4 = cfg->size4; + tinfo->count4 = cfg->items4; + tinfo->itemsize4 = sizeof(struct chashentry); + tinfo->taclass6 = IPFW_TACLASS_HASH; + tinfo->size6 = cfg->size6; + tinfo->count6 = cfg->items6; + tinfo->itemsize6 = sizeof(struct chashentry); +} + static int ta_dump_chash_tentry(void *ta_state, struct table_info *ti, void *e, ipfw_obj_tentry *tent) { struct chash_cfg *cfg; struct chashentry *ent; cfg = (struct chash_cfg *)ta_state; ent = (struct chashentry *)e; if (ent->type == AF_INET) { tent->k.addr.s_addr = htonl(ent->a.a4 << (32 - cfg->mask4)); tent->masklen = cfg->mask4; tent->subtype = AF_INET; tent->value = ent->value; #ifdef INET6 } else { memcpy(&tent->k, &ent->a.a6, sizeof(struct in6_addr)); tent->masklen = cfg->mask6; tent->subtype = AF_INET6; tent->value = ent->value; #endif } return (0); } static uint32_t hash_ent(struct chashentry *ent, int af, int mlen, uint32_t size) { uint32_t hash; if (af == AF_INET) { hash = hash_ip(ent->a.a4, size); } else { if (mlen == 64) hash = hash_ip64(&ent->a.a6, size); else hash = hash_ip6(&ent->a.a6, size); } return (hash); } static int tei_to_chash_ent(struct tentry_info *tei, struct chashentry *ent) { struct in6_addr mask6; int mlen; mlen = tei->masklen; if (tei->subtype == AF_INET) { #ifdef INET if (mlen > 32) return (EINVAL); ent->type = AF_INET; /* Calculate masked address */ ent->a.a4 = ntohl(*((in_addr_t *)tei->paddr)) >> (32 - mlen); #endif #ifdef INET6 } else if (tei->subtype == AF_INET6) { /* IPv6 case */ if (mlen > 128) return (EINVAL); ent->type = AF_INET6; ipv6_writemask(&mask6, mlen); memcpy(&ent->a.a6, tei->paddr, sizeof(struct in6_addr)); APPLY_MASK(&ent->a.a6, &mask6); #endif } else { /* Unknown CIDR type */ return (EINVAL); } ent->value = tei->value; return (0); } static int ta_find_chash_tentry(void *ta_state, struct table_info *ti, ipfw_obj_tentry *tent) { struct chash_cfg *cfg; struct chashbhead *head; struct chashentry ent, *tmp; struct tentry_info tei; int error; uint32_t hash; cfg = (struct chash_cfg *)ta_state; memset(&ent, 0, sizeof(ent)); memset(&tei, 0, sizeof(tei)); if (tent->subtype == AF_INET) { tei.paddr = &tent->k.addr; tei.masklen = cfg->mask4; tei.subtype = AF_INET; if ((error = tei_to_chash_ent(&tei, &ent)) != 0) return (error); head = cfg->head4; hash = hash_ent(&ent, AF_INET, cfg->mask4, cfg->size4); /* Check for existence */ SLIST_FOREACH(tmp, &head[hash], next) { if (tmp->a.a4 != ent.a.a4) continue; ta_dump_chash_tentry(ta_state, ti, tmp, tent); return (0); } } else { tei.paddr = &tent->k.addr6; tei.masklen = cfg->mask6; tei.subtype = AF_INET6; if ((error = tei_to_chash_ent(&tei, &ent)) != 0) return (error); head = cfg->head6; hash = hash_ent(&ent, AF_INET6, cfg->mask6, cfg->size6); /* Check for existence */ SLIST_FOREACH(tmp, &head[hash], next) { if (memcmp(&tmp->a.a6, &ent.a.a6, 16) != 0) continue; ta_dump_chash_tentry(ta_state, ti, tmp, tent); return (0); } } return (ENOENT); } static void ta_foreach_chash(void *ta_state, struct table_info *ti, ta_foreach_f *f, void *arg) { struct chash_cfg *cfg; struct chashentry *ent, *ent_next; int i; cfg = (struct chash_cfg *)ta_state; for (i = 0; i < cfg->size4; i++) SLIST_FOREACH_SAFE(ent, &cfg->head4[i], next, ent_next) f(ent, arg); for (i = 0; i < cfg->size6; i++) SLIST_FOREACH_SAFE(ent, &cfg->head6[i], next, ent_next) f(ent, arg); } static int ta_prepare_add_chash(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf) { struct ta_buf_chash *tb; struct chashentry *ent; int error; tb = (struct ta_buf_chash *)ta_buf; ent = malloc(sizeof(*ent), M_IPFW_TBL, M_WAITOK | M_ZERO); error = tei_to_chash_ent(tei, ent); if (error != 0) { free(ent, M_IPFW_TBL); return (error); } tb->ent_ptr = ent; return (0); } static int ta_add_chash(void *ta_state, struct table_info *ti, struct tentry_info *tei, void *ta_buf, uint32_t *pnum) { struct chash_cfg *cfg; struct chashbhead *head; struct chashentry *ent, *tmp; struct ta_buf_chash *tb; int exists; uint32_t hash, value; cfg = (struct chash_cfg *)ta_state; tb = (struct ta_buf_chash *)ta_buf; ent = (struct chashentry *)tb->ent_ptr; hash = 0; exists = 0; if (tei->subtype == AF_INET) { if (tei->masklen != cfg->mask4) return (EINVAL); head = cfg->head4; hash = hash_ent(ent, AF_INET, cfg->mask4, cfg->size4); /* Check for existence */ SLIST_FOREACH(tmp, &head[hash], next) { if (tmp->a.a4 == ent->a.a4) { exists = 1; break; } } } else { if (tei->masklen != cfg->mask6) return (EINVAL); head = cfg->head6; hash = hash_ent(ent, AF_INET6, cfg->mask6, cfg->size6); /* Check for existence */ SLIST_FOREACH(tmp, &head[hash], next) { if (memcmp(&tmp->a.a6, &ent->a.a6, 16) == 0) { exists = 1; break; } } } if (exists == 1) { if ((tei->flags & TEI_FLAGS_UPDATE) == 0) return (EEXIST); /* Record already exists. Update value if we're asked to */ value = tmp->value; tmp->value = tei->value; tei->value = value; /* Indicate that update has happened instead of addition */ 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; /* Update counters */ if (tei->subtype == AF_INET) cfg->items4++; else cfg->items6++; } return (0); } static int ta_prepare_del_chash(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf) { struct ta_buf_chash *tb; tb = (struct ta_buf_chash *)ta_buf; return (tei_to_chash_ent(tei, &tb->ent)); } static int ta_del_chash(void *ta_state, struct table_info *ti, struct tentry_info *tei, void *ta_buf, uint32_t *pnum) { struct chash_cfg *cfg; struct chashbhead *head; struct chashentry *tmp, *tmp_next, *ent; struct ta_buf_chash *tb; uint32_t hash; cfg = (struct chash_cfg *)ta_state; tb = (struct ta_buf_chash *)ta_buf; ent = &tb->ent; if (tei->subtype == AF_INET) { if (tei->masklen != cfg->mask4) return (EINVAL); head = cfg->head4; hash = hash_ent(ent, AF_INET, cfg->mask4, cfg->size4); SLIST_FOREACH_SAFE(tmp, &head[hash], next, tmp_next) { if (tmp->a.a4 != ent->a.a4) continue; SLIST_REMOVE(&head[hash], tmp, chashentry, next); cfg->items4--; tb->ent_ptr = tmp; tei->value = tmp->value; *pnum = 1; return (0); } } else { if (tei->masklen != cfg->mask6) return (EINVAL); head = cfg->head6; hash = hash_ent(ent, AF_INET6, cfg->mask6, cfg->size6); SLIST_FOREACH_SAFE(tmp, &head[hash], next, tmp_next) { if (memcmp(&tmp->a.a6, &ent->a.a6, 16) != 0) continue; SLIST_REMOVE(&head[hash], tmp, chashentry, next); cfg->items6--; tb->ent_ptr = tmp; tei->value = tmp->value; *pnum = 1; return (0); } } return (ENOENT); } static void ta_flush_chash_entry(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf) { struct ta_buf_chash *tb; tb = (struct ta_buf_chash *)ta_buf; if (tb->ent_ptr != NULL) free(tb->ent_ptr, M_IPFW_TBL); } /* * Hash growing callbacks. */ static int ta_has_space_chash(void *ta_state, struct table_info *ti, uint32_t count, uint64_t *pflags) { struct chash_cfg *cfg; uint64_t data; /* * Since we don't know exact number of IPv4/IPv6 records in @count, * ignore non-zero @count value at all. Check current hash sizes * and return appropriate data. */ cfg = (struct chash_cfg *)ta_state; data = 0; if (cfg->items4 > cfg->size4 && cfg->size4 < 65536) data |= (cfg->size4 * 2) << 16; if (cfg->items6 > cfg->size6 && cfg->size6 < 65536) data |= cfg->size6 * 2; if (data != 0) { *pflags = data; return (0); } return (1); } /* * Allocate new, larger chash. */ static int ta_prepare_mod_chash(void *ta_buf, uint64_t *pflags) { struct mod_item *mi; struct chashbhead *head; int i; mi = (struct mod_item *)ta_buf; memset(mi, 0, sizeof(struct mod_item)); mi->size = (*pflags >> 16) & 0xFFFF; mi->size6 = *pflags & 0xFFFF; if (mi->size > 0) { head = malloc(sizeof(struct chashbhead) * mi->size, M_IPFW, M_WAITOK | M_ZERO); for (i = 0; i < mi->size; i++) SLIST_INIT(&head[i]); mi->main_ptr = head; } if (mi->size6 > 0) { head = malloc(sizeof(struct chashbhead) * mi->size6, M_IPFW, M_WAITOK | M_ZERO); for (i = 0; i < mi->size6; i++) SLIST_INIT(&head[i]); mi->main_ptr6 = head; } return (0); } /* * Copy data from old runtime array to new one. */ static int ta_fill_mod_chash(void *ta_state, struct table_info *ti, void *ta_buf, uint64_t *pflags) { /* In is not possible to do rehash if we're not holidng WLOCK. */ return (0); } /* * Switch old & new arrays. */ static int ta_modify_chash(void *ta_state, struct table_info *ti, void *ta_buf, uint64_t pflags) { struct mod_item *mi; struct chash_cfg *cfg; struct chashbhead *old_head, *new_head; struct chashentry *ent, *ent_next; int af, i, mlen; uint32_t nhash; size_t old_size, new_size; mi = (struct mod_item *)ta_buf; cfg = (struct chash_cfg *)ta_state; /* Check which hash we need to grow and do we still need that */ if (mi->size > 0 && cfg->size4 < mi->size) { new_head = (struct chashbhead *)mi->main_ptr; new_size = mi->size; old_size = cfg->size4; old_head = ti->state; mlen = cfg->mask4; af = AF_INET; for (i = 0; i < old_size; i++) { SLIST_FOREACH_SAFE(ent, &old_head[i], next, ent_next) { nhash = hash_ent(ent, af, mlen, new_size); SLIST_INSERT_HEAD(&new_head[nhash], ent, next); } } ti->state = new_head; cfg->head4 = new_head; cfg->size4 = mi->size; mi->main_ptr = old_head; } if (mi->size6 > 0 && cfg->size6 < mi->size6) { new_head = (struct chashbhead *)mi->main_ptr6; new_size = mi->size6; old_size = cfg->size6; old_head = ti->xstate; mlen = cfg->mask6; af = AF_INET6; for (i = 0; i < old_size; i++) { SLIST_FOREACH_SAFE(ent, &old_head[i], next, ent_next) { nhash = hash_ent(ent, af, mlen, new_size); SLIST_INSERT_HEAD(&new_head[nhash], ent, next); } } ti->xstate = new_head; cfg->head6 = new_head; cfg->size6 = mi->size6; mi->main_ptr6 = old_head; } /* Update lower 32 bits with new values */ ti->data &= 0xFFFFFFFF00000000; ti->data |= log2(cfg->size4) << 8 | log2(cfg->size6); return (0); } /* * Free unneded array. */ static void ta_flush_mod_chash(void *ta_buf) { struct mod_item *mi; mi = (struct mod_item *)ta_buf; if (mi->main_ptr != NULL) free(mi->main_ptr, M_IPFW); if (mi->main_ptr6 != NULL) free(mi->main_ptr6, M_IPFW); } struct table_algo cidr_hash = { .name = "cidr:hash", .type = IPFW_TABLE_CIDR, .ta_buf_size = sizeof(struct ta_buf_chash), .init = ta_init_chash, .destroy = ta_destroy_chash, .prepare_add = ta_prepare_add_chash, .prepare_del = ta_prepare_del_chash, .add = ta_add_chash, .del = ta_del_chash, .flush_entry = ta_flush_chash_entry, .foreach = ta_foreach_chash, .dump_tentry = ta_dump_chash_tentry, .find_tentry = ta_find_chash_tentry, .print_config = ta_print_chash_config, + .dump_tinfo = ta_dump_chash_tinfo, .has_space = ta_has_space_chash, .prepare_mod = ta_prepare_mod_chash, .fill_mod = ta_fill_mod_chash, .modify = ta_modify_chash, .flush_mod = ta_flush_mod_chash, }; /* * Iface table cmds. * * Implementation: * * Runtime part: * - sorted array of "struct ifidx" pointed by ti->state. * Array is allocated with rounding up to IFIDX_CHUNK. Only existing * interfaces are stored in array, however its allocated size is * sufficient to hold all table records if needed. * - current array size is stored in ti->data * * Table data: * - "struct iftable_cfg" is allocated to store table state (ta_state). * - All table records are stored inside namedobj instance. * */ struct ifidx { uint16_t kidx; uint16_t spare; uint32_t value; }; struct iftable_cfg; struct ifentry { struct named_object no; struct ipfw_ifc ic; struct iftable_cfg *icfg; uint32_t value; int linked; }; struct iftable_cfg { struct namedobj_instance *ii; struct ip_fw_chain *ch; struct table_info *ti; void *main_ptr; size_t size; /* Number of items allocated in array */ size_t count; /* Number of all items */ size_t used; /* Number of items _active_ now */ }; struct ta_buf_ifidx { struct ifentry *ife; uint32_t value; }; int compare_ifidx(const void *k, const void *v); static void if_notifier(struct ip_fw_chain *ch, void *cbdata, uint16_t ifindex); int compare_ifidx(const void *k, const void *v) { struct ifidx *ifidx; uint16_t key; key = *((uint16_t *)k); ifidx = (struct ifidx *)v; if (key < ifidx->kidx) return (-1); else if (key > ifidx->kidx) return (1); return (0); } /* * Adds item @item with key @key into ascending-sorted array @base. * Assumes @base has enough additional storage. * * Returns 1 on success, 0 on duplicate key. */ static int badd(const void *key, void *item, void *base, size_t nmemb, size_t size, int (*compar) (const void *, const void *)) { int min, max, mid, shift, res; caddr_t paddr; if (nmemb == 0) { memcpy(base, item, size); return (1); } /* Binary search */ min = 0; max = nmemb - 1; mid = 0; while (min <= max) { mid = (min + max) / 2; res = compar(key, (const void *)((caddr_t)base + mid * size)); if (res == 0) return (0); if (res > 0) min = mid + 1; else max = mid - 1; } /* Item not found. */ res = compar(key, (const void *)((caddr_t)base + mid * size)); if (res > 0) shift = mid + 1; else shift = mid; paddr = (caddr_t)base + shift * size; if (nmemb > shift) memmove(paddr + size, paddr, (nmemb - shift) * size); memcpy(paddr, item, size); return (1); } /* * Deletes item with key @key from ascending-sorted array @base. * * Returns 1 on success, 0 for non-existent key. */ static int bdel(const void *key, void *base, size_t nmemb, size_t size, int (*compar) (const void *, const void *)) { caddr_t item; size_t sz; item = (caddr_t)bsearch(key, base, nmemb, size, compar); if (item == NULL) return (0); sz = (caddr_t)base + nmemb * size - item; if (sz > 0) memmove(item, item + size, sz); return (1); } static struct ifidx * ifidx_find(struct table_info *ti, void *key) { struct ifidx *ifi; ifi = bsearch(key, ti->state, ti->data, sizeof(struct ifidx), compare_ifidx); return (ifi); } static int ta_lookup_ifidx(struct table_info *ti, void *key, uint32_t keylen, uint32_t *val) { struct ifidx *ifi; ifi = ifidx_find(ti, key); if (ifi != NULL) { *val = ifi->value; return (1); } return (0); } static int ta_init_ifidx(struct ip_fw_chain *ch, void **ta_state, struct table_info *ti, char *data, uint8_t tflags) { struct iftable_cfg *icfg; icfg = malloc(sizeof(struct iftable_cfg), M_IPFW, M_WAITOK | M_ZERO); icfg->ii = ipfw_objhash_create(16); icfg->size = 16; icfg->main_ptr = malloc(sizeof(struct ifidx) * icfg->size, M_IPFW, M_WAITOK | M_ZERO); icfg->ch = ch; *ta_state = icfg; ti->state = icfg->main_ptr; ti->lookup = ta_lookup_ifidx; return (0); } /* * Handle tableinfo @ti pointer change (on table array resize). */ static void ta_change_ti_ifidx(void *ta_state, struct table_info *ti) { struct iftable_cfg *icfg; icfg = (struct iftable_cfg *)ta_state; icfg->ti = ti; } static void destroy_ifidx_locked(struct namedobj_instance *ii, struct named_object *no, void *arg) { struct ifentry *ife; struct ip_fw_chain *ch; ch = (struct ip_fw_chain *)arg; ife = (struct ifentry *)no; ipfw_iface_del_notify(ch, &ife->ic); free(ife, M_IPFW_TBL); } /* * Destroys table @ti */ static void ta_destroy_ifidx(void *ta_state, struct table_info *ti) { struct iftable_cfg *icfg; struct ip_fw_chain *ch; icfg = (struct iftable_cfg *)ta_state; ch = icfg->ch; if (icfg->main_ptr != NULL) free(icfg->main_ptr, M_IPFW); ipfw_objhash_foreach(icfg->ii, destroy_ifidx_locked, ch); ipfw_objhash_destroy(icfg->ii); free(icfg, M_IPFW); } /* + * Provide algo-specific table info + */ +static void +ta_dump_ifidx_tinfo(void *ta_state, struct table_info *ti, ipfw_ta_tinfo *tinfo) +{ + struct iftable_cfg *cfg; + + cfg = (struct iftable_cfg *)ta_state; + + tinfo->taclass4 = IPFW_TACLASS_ARRAY; + tinfo->size4 = cfg->size; + tinfo->count4 = cfg->used; + tinfo->itemsize4 = sizeof(struct ifidx); +} + +/* * Prepare state to add to the table: * allocate ifentry and reference needed interface. */ static int ta_prepare_add_ifidx(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf) { struct ta_buf_ifidx *tb; char *ifname; struct ifentry *ife; tb = (struct ta_buf_ifidx *)ta_buf; /* Check if string is terminated */ ifname = (char *)tei->paddr; if (strnlen(ifname, IF_NAMESIZE) == IF_NAMESIZE) return (EINVAL); ife = malloc(sizeof(struct ifentry), M_IPFW_TBL, M_WAITOK | M_ZERO); ife->value = tei->value; ife->ic.cb = if_notifier; ife->ic.cbdata = ife; if (ipfw_iface_ref(ch, ifname, &ife->ic) != 0) return (EINVAL); /* Use ipfw_iface 'ifname' field as stable storage */ ife->no.name = ife->ic.iface->ifname; tb->ife = ife; return (0); } static int ta_add_ifidx(void *ta_state, struct table_info *ti, struct tentry_info *tei, void *ta_buf, uint32_t *pnum) { struct iftable_cfg *icfg; struct ifentry *ife, *tmp; struct ta_buf_ifidx *tb; struct ipfw_iface *iif; struct ifidx *ifi; char *ifname; uint32_t value; tb = (struct ta_buf_ifidx *)ta_buf; ifname = (char *)tei->paddr; icfg = (struct iftable_cfg *)ta_state; ife = tb->ife; ife->icfg = icfg; tmp = (struct ifentry *)ipfw_objhash_lookup_name(icfg->ii, 0, ifname); if (tmp != NULL) { if ((tei->flags & TEI_FLAGS_UPDATE) == 0) return (EEXIST); /* Exchange values in @tmp and @tei */ value = tmp->value; tmp->value = tei->value; tei->value = value; iif = tmp->ic.iface; if (iif->resolved != 0) { /* We have to update runtime value, too */ ifi = ifidx_find(ti, &iif->ifindex); ifi->value = ife->value; } /* Indicate that update has happened instead of addition */ tei->flags |= TEI_FLAGS_UPDATED; *pnum = 0; return (0); } if ((tei->flags & TEI_FLAGS_DONTADD) != 0) return (EFBIG); /* Link to internal list */ ipfw_objhash_add(icfg->ii, &ife->no); /* Link notifier (possible running its callback) */ ipfw_iface_add_notify(icfg->ch, &ife->ic); icfg->count++; tb->ife = NULL; *pnum = 1; return (0); } /* * Prepare to delete key from table. * Do basic interface name checks. */ static int ta_prepare_del_ifidx(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf) { struct ta_buf_ifidx *tb; char *ifname; tb = (struct ta_buf_ifidx *)ta_buf; /* Check if string is terminated */ ifname = (char *)tei->paddr; if (strnlen(ifname, IF_NAMESIZE) == IF_NAMESIZE) return (EINVAL); return (0); } /* * Remove key from both configuration list and * runtime array. Removed interface notification. */ static int ta_del_ifidx(void *ta_state, struct table_info *ti, struct tentry_info *tei, void *ta_buf, uint32_t *pnum) { struct iftable_cfg *icfg; struct ifentry *ife; struct ta_buf_ifidx *tb; char *ifname; uint16_t ifindex; int res; tb = (struct ta_buf_ifidx *)ta_buf; ifname = (char *)tei->paddr; icfg = (struct iftable_cfg *)ta_state; ife = tb->ife; ife = (struct ifentry *)ipfw_objhash_lookup_name(icfg->ii, 0, ifname); if (ife == NULL) return (ENOENT); if (ife->linked != 0) { /* We have to remove item from runtime */ ifindex = ife->ic.iface->ifindex; res = bdel(&ifindex, icfg->main_ptr, icfg->used, sizeof(struct ifidx), compare_ifidx); KASSERT(res == 1, ("index %d does not exist", ifindex)); icfg->used--; ti->data = icfg->used; ife->linked = 0; } /* Unlink from local list */ ipfw_objhash_del(icfg->ii, &ife->no); /* Unlink notifier */ ipfw_iface_del_notify(icfg->ch, &ife->ic); icfg->count--; tei->value = ife->value; tb->ife = ife; *pnum = 1; return (0); } /* * Flush deleted entry. * Drops interface reference and frees entry. */ static void ta_flush_ifidx_entry(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf) { struct ta_buf_ifidx *tb; tb = (struct ta_buf_ifidx *)ta_buf; if (tb->ife != NULL) { /* Unlink first */ ipfw_iface_unref(ch, &tb->ife->ic); free(tb->ife, M_IPFW_TBL); } } /* * Handle interface announce/withdrawal for particular table. * Every real runtime array modification happens here. */ static void if_notifier(struct ip_fw_chain *ch, void *cbdata, uint16_t ifindex) { struct ifentry *ife; struct ifidx ifi; struct iftable_cfg *icfg; struct table_info *ti; int res; ife = (struct ifentry *)cbdata; icfg = ife->icfg; ti = icfg->ti; KASSERT(ti != NULL, ("ti=NULL, check change_ti handler")); if (ife->linked == 0 && ifindex != 0) { /* Interface announce */ ifi.kidx = ifindex; ifi.spare = 0; ifi.value = ife->value; res = badd(&ifindex, &ifi, icfg->main_ptr, icfg->used, sizeof(struct ifidx), compare_ifidx); KASSERT(res == 1, ("index %d already exists", ifindex)); icfg->used++; ti->data = icfg->used; ife->linked = 1; } else if (ife->linked != 0 && ifindex == 0) { /* Interface withdrawal */ ifindex = ife->ic.iface->ifindex; res = bdel(&ifindex, icfg->main_ptr, icfg->used, sizeof(struct ifidx), compare_ifidx); KASSERT(res == 1, ("index %d does not exist", ifindex)); icfg->used--; ti->data = icfg->used; ife->linked = 0; } } /* * Table growing callbacks. */ static int ta_has_space_ifidx(void *ta_state, struct table_info *ti, uint32_t count, uint64_t *pflags) { struct iftable_cfg *cfg; uint32_t size; cfg = (struct iftable_cfg *)ta_state; size = cfg->size; while (size < cfg->count + count) size *= 2; if (size != cfg->size) { *pflags = size; return (0); } return (1); } /* * Allocate ned, larger runtime ifidx array. */ static int ta_prepare_mod_ifidx(void *ta_buf, uint64_t *pflags) { struct mod_item *mi; mi = (struct mod_item *)ta_buf; memset(mi, 0, sizeof(struct mod_item)); mi->size = *pflags; mi->main_ptr = malloc(sizeof(struct ifidx) * mi->size, M_IPFW, M_WAITOK | M_ZERO); return (0); } /* * Copy data from old runtime array to new one. */ static int ta_fill_mod_ifidx(void *ta_state, struct table_info *ti, void *ta_buf, uint64_t *pflags) { struct mod_item *mi; struct iftable_cfg *icfg; mi = (struct mod_item *)ta_buf; icfg = (struct iftable_cfg *)ta_state; /* Check if we still need to grow array */ if (icfg->size >= mi->size) { *pflags = 0; return (0); } memcpy(mi->main_ptr, icfg->main_ptr, icfg->used * sizeof(struct ifidx)); return (0); } /* * Switch old & new arrays. */ static int ta_modify_ifidx(void *ta_state, struct table_info *ti, void *ta_buf, uint64_t pflags) { struct mod_item *mi; struct iftable_cfg *icfg; void *old_ptr; mi = (struct mod_item *)ta_buf; icfg = (struct iftable_cfg *)ta_state; old_ptr = icfg->main_ptr; icfg->main_ptr = mi->main_ptr; icfg->size = mi->size; ti->state = icfg->main_ptr; mi->main_ptr = old_ptr; return (0); } /* * Free unneded array. */ static void ta_flush_mod_ifidx(void *ta_buf) { struct mod_item *mi; mi = (struct mod_item *)ta_buf; if (mi->main_ptr != NULL) free(mi->main_ptr, M_IPFW); } static int ta_dump_ifidx_tentry(void *ta_state, struct table_info *ti, void *e, ipfw_obj_tentry *tent) { struct ifentry *ife; ife = (struct ifentry *)e; tent->masklen = 8 * IF_NAMESIZE; memcpy(&tent->k, ife->no.name, IF_NAMESIZE); tent->value = ife->value; return (0); } static int ta_find_ifidx_tentry(void *ta_state, struct table_info *ti, ipfw_obj_tentry *tent) { struct iftable_cfg *icfg; struct ifentry *ife; char *ifname; icfg = (struct iftable_cfg *)ta_state; ifname = tent->k.iface; if (strnlen(ifname, IF_NAMESIZE) == IF_NAMESIZE) return (EINVAL); ife = (struct ifentry *)ipfw_objhash_lookup_name(icfg->ii, 0, ifname); if (ife != NULL) { ta_dump_ifidx_tentry(ta_state, ti, ife, tent); return (0); } return (ENOENT); } struct wa_ifidx { ta_foreach_f *f; void *arg; }; static void foreach_ifidx(struct namedobj_instance *ii, struct named_object *no, void *arg) { struct ifentry *ife; struct wa_ifidx *wa; ife = (struct ifentry *)no; wa = (struct wa_ifidx *)arg; wa->f(ife, wa->arg); } static void ta_foreach_ifidx(void *ta_state, struct table_info *ti, ta_foreach_f *f, void *arg) { struct iftable_cfg *icfg; struct wa_ifidx wa; icfg = (struct iftable_cfg *)ta_state; wa.f = f; wa.arg = arg; ipfw_objhash_foreach(icfg->ii, foreach_ifidx, &wa); } struct table_algo iface_idx = { .name = "iface:array", .type = IPFW_TABLE_INTERFACE, .flags = TA_FLAG_DEFAULT, .ta_buf_size = sizeof(struct ta_buf_ifidx), .init = ta_init_ifidx, .destroy = ta_destroy_ifidx, .prepare_add = ta_prepare_add_ifidx, .prepare_del = ta_prepare_del_ifidx, .add = ta_add_ifidx, .del = ta_del_ifidx, .flush_entry = ta_flush_ifidx_entry, .foreach = ta_foreach_ifidx, .dump_tentry = ta_dump_ifidx_tentry, .find_tentry = ta_find_ifidx_tentry, + .dump_tinfo = ta_dump_ifidx_tinfo, .has_space = ta_has_space_ifidx, .prepare_mod = ta_prepare_mod_ifidx, .fill_mod = ta_fill_mod_ifidx, .modify = ta_modify_ifidx, .flush_mod = ta_flush_mod_ifidx, .change_ti = ta_change_ti_ifidx, }; /* * Number array cmds. * * Implementation: * * Runtime part: * - sorted array of "struct numarray" pointed by ti->state. * Array is allocated with rounding up to NUMARRAY_CHUNK. * - current array size is stored in ti->data * */ struct numarray { uint32_t number; uint32_t value; }; struct numarray_cfg { void *main_ptr; size_t size; /* Number of items allocated in array */ size_t used; /* Number of items _active_ now */ }; struct ta_buf_numarray { struct numarray na; }; int compare_numarray(const void *k, const void *v); int compare_numarray(const void *k, const void *v) { struct numarray *na; uint32_t key; key = *((uint32_t *)k); na = (struct numarray *)v; if (key < na->number) return (-1); else if (key > na->number) return (1); return (0); } static struct numarray * numarray_find(struct table_info *ti, void *key) { struct numarray *ri; ri = bsearch(key, ti->state, ti->data, sizeof(struct numarray), compare_ifidx); return (ri); } static int ta_lookup_numarray(struct table_info *ti, void *key, uint32_t keylen, uint32_t *val) { struct numarray *ri; ri = numarray_find(ti, key); if (ri != NULL) { *val = ri->value; return (1); } return (0); } static int ta_init_numarray(struct ip_fw_chain *ch, void **ta_state, struct table_info *ti, char *data, uint8_t tflags) { struct numarray_cfg *cfg; cfg = malloc(sizeof(*cfg), M_IPFW, M_WAITOK | M_ZERO); cfg->size = 16; cfg->main_ptr = malloc(sizeof(struct numarray) * cfg->size, M_IPFW, M_WAITOK | M_ZERO); *ta_state = cfg; ti->state = cfg->main_ptr; ti->lookup = ta_lookup_numarray; return (0); } /* * Destroys table @ti */ static void ta_destroy_numarray(void *ta_state, struct table_info *ti) { struct numarray_cfg *cfg; cfg = (struct numarray_cfg *)ta_state; if (cfg->main_ptr != NULL) free(cfg->main_ptr, M_IPFW); free(cfg, M_IPFW); } /* + * Provide algo-specific table info + */ +static void +ta_dump_numarray_tinfo(void *ta_state, struct table_info *ti, ipfw_ta_tinfo *tinfo) +{ + struct numarray_cfg *cfg; + + cfg = (struct numarray_cfg *)ta_state; + + tinfo->taclass4 = IPFW_TACLASS_ARRAY; + tinfo->size4 = cfg->size; + tinfo->count4 = cfg->used; + tinfo->itemsize4 = sizeof(struct numarray); +} + +/* * Prepare for addition/deletion to an array. */ static int ta_prepare_add_numarray(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf) { struct ta_buf_numarray *tb; tb = (struct ta_buf_numarray *)ta_buf; tb->na.number = *((uint32_t *)tei->paddr); tb->na.value = tei->value; return (0); } static int ta_add_numarray(void *ta_state, struct table_info *ti, struct tentry_info *tei, void *ta_buf, uint32_t *pnum) { struct numarray_cfg *cfg; struct ta_buf_numarray *tb; struct numarray *ri; int res; uint32_t value; tb = (struct ta_buf_numarray *)ta_buf; cfg = (struct numarray_cfg *)ta_state; ri = numarray_find(ti, &tb->na.number); if (ri != NULL) { if ((tei->flags & TEI_FLAGS_UPDATE) == 0) return (EEXIST); /* Exchange values between ri and @tei */ value = ri->value; ri->value = tei->value; tei->value = value; /* Indicate that update has happened instead of addition */ tei->flags |= TEI_FLAGS_UPDATED; *pnum = 0; 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); KASSERT(res == 1, ("number %d already exists", tb->na.number)); cfg->used++; ti->data = cfg->used; *pnum = 1; return (0); } /* * Remove key from both configuration list and * runtime array. Removed interface notification. */ static int ta_del_numarray(void *ta_state, struct table_info *ti, struct tentry_info *tei, void *ta_buf, uint32_t *pnum) { struct numarray_cfg *cfg; struct ta_buf_numarray *tb; struct numarray *ri; int res; tb = (struct ta_buf_numarray *)ta_buf; cfg = (struct numarray_cfg *)ta_state; ri = numarray_find(ti, &tb->na.number); if (ri == NULL) return (ENOENT); tei->value = ri->value; res = bdel(&tb->na.number, cfg->main_ptr, cfg->used, sizeof(struct numarray), compare_numarray); KASSERT(res == 1, ("number %u does not exist", tb->na.number)); cfg->used--; ti->data = cfg->used; *pnum = 1; return (0); } static void ta_flush_numarray_entry(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf) { /* We don't have any state, do nothing */ } /* * Table growing callbacks. */ static int ta_has_space_numarray(void *ta_state, struct table_info *ti, uint32_t count, uint64_t *pflags) { struct numarray_cfg *cfg; size_t size; cfg = (struct numarray_cfg *)ta_state; size = cfg->size; while (size < cfg->used + count) size *= 2; if (size != cfg->size) { *pflags = size; return (0); } return (1); } /* * Allocate new, larger runtime array. */ static int ta_prepare_mod_numarray(void *ta_buf, uint64_t *pflags) { struct mod_item *mi; mi = (struct mod_item *)ta_buf; memset(mi, 0, sizeof(struct mod_item)); mi->size = *pflags; mi->main_ptr = malloc(sizeof(struct numarray) * mi->size, M_IPFW, M_WAITOK | M_ZERO); return (0); } /* * Copy data from old runtime array to new one. */ static int ta_fill_mod_numarray(void *ta_state, struct table_info *ti, void *ta_buf, uint64_t *pflags) { struct mod_item *mi; struct numarray_cfg *cfg; mi = (struct mod_item *)ta_buf; cfg = (struct numarray_cfg *)ta_state; /* Check if we still need to grow array */ if (cfg->size >= mi->size) { *pflags = 0; return (0); } memcpy(mi->main_ptr, cfg->main_ptr, cfg->used * sizeof(struct numarray)); return (0); } /* * Switch old & new arrays. */ static int ta_modify_numarray(void *ta_state, struct table_info *ti, void *ta_buf, uint64_t pflags) { struct mod_item *mi; struct numarray_cfg *cfg; void *old_ptr; mi = (struct mod_item *)ta_buf; cfg = (struct numarray_cfg *)ta_state; old_ptr = cfg->main_ptr; cfg->main_ptr = mi->main_ptr; cfg->size = mi->size; ti->state = cfg->main_ptr; mi->main_ptr = old_ptr; return (0); } /* * Free unneded array. */ static void ta_flush_mod_numarray(void *ta_buf) { struct mod_item *mi; mi = (struct mod_item *)ta_buf; if (mi->main_ptr != NULL) free(mi->main_ptr, M_IPFW); } static int ta_dump_numarray_tentry(void *ta_state, struct table_info *ti, void *e, ipfw_obj_tentry *tent) { struct numarray *na; na = (struct numarray *)e; tent->k.key = na->number; tent->value = na->value; return (0); } static int ta_find_numarray_tentry(void *ta_state, struct table_info *ti, ipfw_obj_tentry *tent) { struct numarray_cfg *cfg; struct numarray *ri; cfg = (struct numarray_cfg *)ta_state; ri = numarray_find(ti, &tent->k.key); if (ri != NULL) { ta_dump_numarray_tentry(ta_state, ti, ri, tent); return (0); } return (ENOENT); } static void ta_foreach_numarray(void *ta_state, struct table_info *ti, ta_foreach_f *f, void *arg) { struct numarray_cfg *cfg; struct numarray *array; int i; cfg = (struct numarray_cfg *)ta_state; array = cfg->main_ptr; for (i = 0; i < cfg->used; i++) f(&array[i], arg); } struct table_algo number_array = { .name = "number:array", .type = IPFW_TABLE_NUMBER, .ta_buf_size = sizeof(struct ta_buf_numarray), .init = ta_init_numarray, .destroy = ta_destroy_numarray, .prepare_add = ta_prepare_add_numarray, .prepare_del = ta_prepare_add_numarray, .add = ta_add_numarray, .del = ta_del_numarray, .flush_entry = ta_flush_numarray_entry, .foreach = ta_foreach_numarray, .dump_tentry = ta_dump_numarray_tentry, .find_tentry = ta_find_numarray_tentry, + .dump_tinfo = ta_dump_numarray_tinfo, .has_space = ta_has_space_numarray, .prepare_mod = ta_prepare_mod_numarray, .fill_mod = ta_fill_mod_numarray, .modify = ta_modify_numarray, .flush_mod = ta_flush_mod_numarray, }; /* * flow:hash cmds * * * ti->data: * [inv.mask4][inv.mask6][log2hsize4][log2hsize6] * [ 8][ 8[ 8][ 8] * * inv.mask4: 32 - mask * inv.mask6: * 1) _slow lookup: mask * 2) _aligned: (128 - mask) / 8 * 3) _64: 8 * * * pflags: * [hsize4][hsize6] * [ 16][ 16] */ struct fhashentry; SLIST_HEAD(fhashbhead, fhashentry); struct fhashentry { SLIST_ENTRY(fhashentry) next; uint8_t af; uint8_t proto; uint16_t spare0; uint16_t dport; uint16_t sport; uint32_t value; uint32_t spare1; }; struct fhashentry4 { struct fhashentry e; struct in_addr dip; struct in_addr sip; }; struct fhashentry6 { struct fhashentry e; struct in6_addr dip6; struct in6_addr sip6; }; struct fhash_cfg { struct fhashbhead *head; size_t size; size_t items; struct fhashentry4 fe4; struct fhashentry6 fe6; }; struct ta_buf_fhash { void *ent_ptr; struct fhashentry6 fe6; }; static __inline int cmp_flow_ent(struct fhashentry *a, struct fhashentry *b, size_t sz) { uint64_t *ka, *kb; ka = (uint64_t *)(&a->next + 1); kb = (uint64_t *)(&b->next + 1); if (*ka == *kb && (memcmp(a + 1, b + 1, sz) == 0)) return (1); return (0); } static __inline uint32_t hash_flow4(struct fhashentry4 *f, int hsize) { uint32_t i; i = (f->dip.s_addr) ^ (f->sip.s_addr) ^ (f->e.dport) ^ (f->e.sport); return (i % (hsize - 1)); } static __inline uint32_t hash_flow6(struct fhashentry6 *f, int hsize) { uint32_t i; i = (f->dip6.__u6_addr.__u6_addr32[2]) ^ (f->dip6.__u6_addr.__u6_addr32[3]) ^ (f->sip6.__u6_addr.__u6_addr32[2]) ^ (f->sip6.__u6_addr.__u6_addr32[3]) ^ (f->e.dport) ^ (f->e.sport); return (i % (hsize - 1)); } static uint32_t hash_flow_ent(struct fhashentry *ent, uint32_t size) { uint32_t hash; if (ent->af == AF_INET) { hash = hash_flow4((struct fhashentry4 *)ent, size); } else { hash = hash_flow6((struct fhashentry6 *)ent, size); } return (hash); } static int ta_lookup_fhash(struct table_info *ti, void *key, uint32_t keylen, uint32_t *val) { struct fhashbhead *head; struct fhashentry *ent; struct fhashentry4 *m4; struct ipfw_flow_id *id; uint16_t hash, hsize; id = (struct ipfw_flow_id *)key; head = (struct fhashbhead *)ti->state; hsize = ti->data; m4 = (struct fhashentry4 *)ti->xstate; if (id->addr_type == 4) { struct fhashentry4 f; /* Copy hash mask */ f = *m4; f.dip.s_addr &= id->dst_ip; f.sip.s_addr &= id->src_ip; f.e.dport &= id->dst_port; f.e.sport &= id->src_port; f.e.proto &= id->proto; hash = hash_flow4(&f, hsize); SLIST_FOREACH(ent, &head[hash], next) { if (cmp_flow_ent(ent, &f.e, 2 * 4) != 0) { *val = ent->value; return (1); } } } else if (id->addr_type == 6) { struct fhashentry6 f; uint64_t *fp, *idp; /* Copy hash mask */ f = *((struct fhashentry6 *)(m4 + 1)); /* Handle lack of __u6_addr.__u6_addr64 */ fp = (uint64_t *)&f.dip6; idp = (uint64_t *)&id->dst_ip6; /* src IPv6 is stored after dst IPv6 */ *fp++ &= *idp++; *fp++ &= *idp++; *fp++ &= *idp++; *fp &= *idp; f.e.dport &= id->dst_port; f.e.sport &= id->src_port; f.e.proto &= id->proto; hash = hash_flow6(&f, hsize); SLIST_FOREACH(ent, &head[hash], next) { if (cmp_flow_ent(ent, &f.e, 2 * 16) != 0) { *val = ent->value; return (1); } } } return (0); } /* * New table. */ static int ta_init_fhash(struct ip_fw_chain *ch, void **ta_state, struct table_info *ti, char *data, uint8_t tflags) { int i; struct fhash_cfg *cfg; struct fhashentry4 *fe4; struct fhashentry6 *fe6; cfg = malloc(sizeof(struct fhash_cfg), M_IPFW, M_WAITOK | M_ZERO); cfg->size = 512; cfg->head = malloc(sizeof(struct fhashbhead) * cfg->size, M_IPFW, M_WAITOK | M_ZERO); for (i = 0; i < cfg->size; i++) SLIST_INIT(&cfg->head[i]); /* Fill in fe masks based on @tflags */ fe4 = &cfg->fe4; fe6 = &cfg->fe6; if (tflags & IPFW_TFFLAG_SRCIP) { memset(&fe4->sip, 0xFF, sizeof(fe4->sip)); memset(&fe6->sip6, 0xFF, sizeof(fe6->sip6)); } if (tflags & IPFW_TFFLAG_DSTIP) { memset(&fe4->dip, 0xFF, sizeof(fe4->dip)); memset(&fe6->dip6, 0xFF, sizeof(fe6->dip6)); } if (tflags & IPFW_TFFLAG_SRCPORT) { memset(&fe4->e.sport, 0xFF, sizeof(fe4->e.sport)); memset(&fe6->e.sport, 0xFF, sizeof(fe6->e.sport)); } if (tflags & IPFW_TFFLAG_DSTPORT) { memset(&fe4->e.dport, 0xFF, sizeof(fe4->e.dport)); memset(&fe6->e.dport, 0xFF, sizeof(fe6->e.dport)); } if (tflags & IPFW_TFFLAG_PROTO) { memset(&fe4->e.proto, 0xFF, sizeof(fe4->e.proto)); memset(&fe6->e.proto, 0xFF, sizeof(fe6->e.proto)); } fe4->e.af = AF_INET; fe6->e.af = AF_INET6; *ta_state = cfg; ti->state = cfg->head; ti->xstate = &cfg->fe4; ti->data = cfg->size; ti->lookup = ta_lookup_fhash; return (0); } static void ta_destroy_fhash(void *ta_state, struct table_info *ti) { struct fhash_cfg *cfg; struct fhashentry *ent, *ent_next; int i; cfg = (struct fhash_cfg *)ta_state; for (i = 0; i < cfg->size; i++) SLIST_FOREACH_SAFE(ent, &cfg->head[i], next, ent_next) free(ent, M_IPFW_TBL); free(cfg->head, M_IPFW); free(cfg, M_IPFW); } +/* + * Provide algo-specific table info + */ +static void +ta_dump_fhash_tinfo(void *ta_state, struct table_info *ti, ipfw_ta_tinfo *tinfo) +{ + struct fhash_cfg *cfg; + + cfg = (struct fhash_cfg *)ta_state; + + tinfo->flags = IPFW_TATFLAGS_AFITEM; + tinfo->taclass4 = IPFW_TACLASS_HASH; + tinfo->size4 = cfg->size; + tinfo->count4 = cfg->items; + tinfo->itemsize4 = sizeof(struct fhashentry4); + tinfo->itemsize6 = sizeof(struct fhashentry6); +} + static int ta_dump_fhash_tentry(void *ta_state, struct table_info *ti, void *e, ipfw_obj_tentry *tent) { struct fhash_cfg *cfg; struct fhashentry *ent; struct fhashentry4 *fe4; struct fhashentry6 *fe6; struct tflow_entry *tfe; cfg = (struct fhash_cfg *)ta_state; ent = (struct fhashentry *)e; tfe = &tent->k.flow; tfe->af = ent->af; tfe->proto = ent->proto; tfe->dport = htons(ent->dport); tfe->sport = htons(ent->sport); tent->value = ent->value; tent->subtype = ent->af; if (ent->af == AF_INET) { fe4 = (struct fhashentry4 *)ent; tfe->a.a4.sip.s_addr = htonl(fe4->sip.s_addr); tfe->a.a4.dip.s_addr = htonl(fe4->dip.s_addr); tent->masklen = 32; #ifdef INET6 } else { fe6 = (struct fhashentry6 *)ent; tfe->a.a6.sip6 = fe6->sip6; tfe->a.a6.dip6 = fe6->dip6; tent->masklen = 128; #endif } return (0); } static int tei_to_fhash_ent(struct tentry_info *tei, struct fhashentry *ent) { struct fhashentry4 *fe4; struct fhashentry6 *fe6; struct tflow_entry *tfe; tfe = (struct tflow_entry *)tei->paddr; ent->af = tei->subtype; ent->proto = tfe->proto; ent->value = tei->value; ent->dport = ntohs(tfe->dport); ent->sport = ntohs(tfe->sport); if (tei->subtype == AF_INET) { #ifdef INET fe4 = (struct fhashentry4 *)ent; fe4->sip.s_addr = ntohl(tfe->a.a4.sip.s_addr); fe4->dip.s_addr = ntohl(tfe->a.a4.dip.s_addr); #endif #ifdef INET6 } else if (tei->subtype == AF_INET6) { fe6 = (struct fhashentry6 *)ent; fe6->sip6 = tfe->a.a6.sip6; fe6->dip6 = tfe->a.a6.dip6; #endif } else { /* Unknown CIDR type */ return (EINVAL); } return (0); } static int ta_find_fhash_tentry(void *ta_state, struct table_info *ti, ipfw_obj_tentry *tent) { struct fhash_cfg *cfg; struct fhashbhead *head; struct fhashentry *ent, *tmp; struct fhashentry6 fe6; struct tentry_info tei; int error; uint32_t hash; size_t sz; cfg = (struct fhash_cfg *)ta_state; ent = &fe6.e; memset(&fe6, 0, sizeof(fe6)); memset(&tei, 0, sizeof(tei)); tei.paddr = &tent->k.flow; tei.subtype = tent->subtype; if ((error = tei_to_fhash_ent(&tei, ent)) != 0) return (error); head = cfg->head; hash = hash_flow_ent(ent, cfg->size); if (tei.subtype == AF_INET) sz = 2 * sizeof(struct in_addr); else sz = 2 * sizeof(struct in6_addr); /* Check for existence */ SLIST_FOREACH(tmp, &head[hash], next) { if (cmp_flow_ent(tmp, ent, sz) != 0) { ta_dump_fhash_tentry(ta_state, ti, tmp, tent); return (0); } } return (ENOENT); } static void ta_foreach_fhash(void *ta_state, struct table_info *ti, ta_foreach_f *f, void *arg) { struct fhash_cfg *cfg; struct fhashentry *ent, *ent_next; int i; cfg = (struct fhash_cfg *)ta_state; for (i = 0; i < cfg->size; i++) SLIST_FOREACH_SAFE(ent, &cfg->head[i], next, ent_next) f(ent, arg); } static int ta_prepare_add_fhash(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf) { struct ta_buf_fhash *tb; struct fhashentry *ent; size_t sz; int error; tb = (struct ta_buf_fhash *)ta_buf; if (tei->subtype == AF_INET) sz = sizeof(struct fhashentry4); else if (tei->subtype == AF_INET6) sz = sizeof(struct fhashentry6); else return (EINVAL); ent = malloc(sz, M_IPFW_TBL, M_WAITOK | M_ZERO); error = tei_to_fhash_ent(tei, ent); if (error != 0) { free(ent, M_IPFW_TBL); return (error); } tb->ent_ptr = ent; return (0); } static int ta_add_fhash(void *ta_state, struct table_info *ti, struct tentry_info *tei, void *ta_buf, uint32_t *pnum) { struct fhash_cfg *cfg; struct fhashbhead *head; struct fhashentry *ent, *tmp; struct ta_buf_fhash *tb; int exists; uint32_t hash, value; size_t sz; cfg = (struct fhash_cfg *)ta_state; tb = (struct ta_buf_fhash *)ta_buf; ent = (struct fhashentry *)tb->ent_ptr; exists = 0; head = cfg->head; hash = hash_flow_ent(ent, cfg->size); if (tei->subtype == AF_INET) sz = 2 * sizeof(struct in_addr); else sz = 2 * sizeof(struct in6_addr); /* Check for existence */ SLIST_FOREACH(tmp, &head[hash], next) { if (cmp_flow_ent(tmp, ent, sz) != 0) { exists = 1; break; } } if (exists == 1) { if ((tei->flags & TEI_FLAGS_UPDATE) == 0) return (EEXIST); /* Record already exists. Update value if we're asked to */ /* Exchange values between tmp and @tei */ value = tmp->value; tmp->value = tei->value; tei->value = value; /* Indicate that update has happened instead of addition */ 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; /* Update counters and check if we need to grow hash */ cfg->items++; } return (0); } static int ta_prepare_del_fhash(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf) { struct ta_buf_fhash *tb; tb = (struct ta_buf_fhash *)ta_buf; return (tei_to_fhash_ent(tei, &tb->fe6.e)); } static int ta_del_fhash(void *ta_state, struct table_info *ti, struct tentry_info *tei, void *ta_buf, uint32_t *pnum) { struct fhash_cfg *cfg; struct fhashbhead *head; struct fhashentry *ent, *tmp; struct ta_buf_fhash *tb; uint32_t hash; size_t sz; cfg = (struct fhash_cfg *)ta_state; tb = (struct ta_buf_fhash *)ta_buf; ent = &tb->fe6.e; head = cfg->head; hash = hash_flow_ent(ent, cfg->size); if (tei->subtype == AF_INET) sz = 2 * sizeof(struct in_addr); else sz = 2 * sizeof(struct in6_addr); /* Check for existence */ SLIST_FOREACH(tmp, &head[hash], next) { if (cmp_flow_ent(tmp, ent, sz) == 0) continue; SLIST_REMOVE(&head[hash], tmp, fhashentry, next); tei->value = tmp->value; *pnum = 1; cfg->items--; tb->ent_ptr = tmp; return (0); } return (ENOENT); } static void ta_flush_fhash_entry(struct ip_fw_chain *ch, struct tentry_info *tei, void *ta_buf) { struct ta_buf_fhash *tb; tb = (struct ta_buf_fhash *)ta_buf; if (tb->ent_ptr != NULL) free(tb->ent_ptr, M_IPFW_TBL); } /* * Hash growing callbacks. */ static int ta_has_space_fhash(void *ta_state, struct table_info *ti, uint32_t count, uint64_t *pflags) { struct fhash_cfg *cfg; cfg = (struct fhash_cfg *)ta_state; if (cfg->items > cfg->size && cfg->size < 65536) { *pflags = cfg->size * 2; return (0); } return (1); } /* * Allocate new, larger fhash. */ static int ta_prepare_mod_fhash(void *ta_buf, uint64_t *pflags) { struct mod_item *mi; struct fhashbhead *head; int i; mi = (struct mod_item *)ta_buf; memset(mi, 0, sizeof(struct mod_item)); mi->size = *pflags; head = malloc(sizeof(struct fhashbhead) * mi->size, M_IPFW, M_WAITOK | M_ZERO); for (i = 0; i < mi->size; i++) SLIST_INIT(&head[i]); mi->main_ptr = head; return (0); } /* * Copy data from old runtime array to new one. */ static int ta_fill_mod_fhash(void *ta_state, struct table_info *ti, void *ta_buf, uint64_t *pflags) { /* In is not possible to do rehash if we're not holidng WLOCK. */ return (0); } /* * Switch old & new arrays. */ static int ta_modify_fhash(void *ta_state, struct table_info *ti, void *ta_buf, uint64_t pflags) { struct mod_item *mi; struct fhash_cfg *cfg; struct fhashbhead *old_head, *new_head; struct fhashentry *ent, *ent_next; int i; uint32_t nhash; size_t old_size; mi = (struct mod_item *)ta_buf; cfg = (struct fhash_cfg *)ta_state; /* Check which hash we need to grow and do we still need that */ old_size = cfg->size; old_head = ti->state; if (old_size >= mi->size) return (0); new_head = (struct fhashbhead *)mi->main_ptr; for (i = 0; i < old_size; i++) { SLIST_FOREACH_SAFE(ent, &old_head[i], next, ent_next) { nhash = hash_flow_ent(ent, mi->size); SLIST_INSERT_HEAD(&new_head[nhash], ent, next); } } ti->state = new_head; ti->data = mi->size; cfg->head = new_head; cfg->size = mi->size; mi->main_ptr = old_head; return (0); } /* * Free unneded array. */ static void ta_flush_mod_fhash(void *ta_buf) { struct mod_item *mi; mi = (struct mod_item *)ta_buf; if (mi->main_ptr != NULL) free(mi->main_ptr, M_IPFW); } struct table_algo flow_hash = { .name = "flow:hash", .type = IPFW_TABLE_FLOW, .flags = TA_FLAG_DEFAULT, .ta_buf_size = sizeof(struct ta_buf_fhash), .init = ta_init_fhash, .destroy = ta_destroy_fhash, .prepare_add = ta_prepare_add_fhash, .prepare_del = ta_prepare_del_fhash, .add = ta_add_fhash, .del = ta_del_fhash, .flush_entry = ta_flush_fhash_entry, .foreach = ta_foreach_fhash, .dump_tentry = ta_dump_fhash_tentry, .find_tentry = ta_find_fhash_tentry, + .dump_tinfo = ta_dump_fhash_tinfo, .has_space = ta_has_space_fhash, .prepare_mod = ta_prepare_mod_fhash, .fill_mod = ta_fill_mod_fhash, .modify = ta_modify_fhash, .flush_mod = ta_flush_mod_fhash, }; void ipfw_table_algo_init(struct ip_fw_chain *ch) { size_t sz; /* * Register all algorithms presented here. */ sz = sizeof(struct table_algo); ipfw_add_table_algo(ch, &cidr_radix, sz, &cidr_radix.idx); ipfw_add_table_algo(ch, &cidr_hash, sz, &cidr_hash.idx); ipfw_add_table_algo(ch, &iface_idx, sz, &iface_idx.idx); ipfw_add_table_algo(ch, &number_array, sz, &number_array.idx); ipfw_add_table_algo(ch, &flow_hash, sz, &flow_hash.idx); } void ipfw_table_algo_destroy(struct ip_fw_chain *ch) { ipfw_del_table_algo(ch, cidr_radix.idx); ipfw_del_table_algo(ch, cidr_hash.idx); ipfw_del_table_algo(ch, iface_idx.idx); ipfw_del_table_algo(ch, number_array.idx); ipfw_del_table_algo(ch, flow_hash.idx); }