diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c index 8cb8d8bd5df1..db59a1ea320c 100644 --- a/sbin/ipfw/ipfw2.c +++ b/sbin/ipfw/ipfw2.c @@ -519,6 +519,26 @@ safe_realloc(void *ptr, size_t size) return ret; } +/* + * Compare things like interface or table names. + */ +int +stringnum_cmp(const char *a, const char *b) +{ + int la, lb; + + la = strlen(a); + lb = strlen(b); + + if (la > lb) + return (1); + else if (la < lb) + return (-01); + + return (strcmp(a, b)); +} + + /* * conditionally runs the command. * Selected options or negative -> getsockopt @@ -4682,3 +4702,78 @@ ipfw_flush(int force) printf("Flushed all %s.\n", co.do_pipe ? "pipes" : "rules"); } +int +ipfw_get_tracked_ifaces(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_XIFLIST, &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_XIFLIST, &olh->opheader, &sz)) != 0) { + free(olh); + return (error); + } + + *polh = olh; + return (0); +} + +static int +ifinfo_cmp(const void *a, const void *b) +{ + ipfw_iface_info *ia, *ib; + + ia = (ipfw_iface_info *)a; + ib = (ipfw_iface_info *)b; + + return (stringnum_cmp(ia->ifname, ib->ifname)); +} + +/* + * Retrieves table list from kernel, + * optionally sorts it and calls requested function for each table. + * Returns 0 on success. + */ +void +ipfw_list_tifaces() +{ + ipfw_obj_lheader *olh; + ipfw_iface_info *info; + int i, error; + + if ((error = ipfw_get_tracked_ifaces(&olh)) != 0) + err(EX_OSERR, "Unable to request ipfw tracked interface list"); + + + qsort(olh + 1, olh->count, olh->objsize, ifinfo_cmp); + + info = (ipfw_iface_info *)(olh + 1); + for (i = 0; i < olh->count; i++) { + if (info->flags & IPFW_IFFLAG_RESOLVED) + printf("%s ifindex: %d refcount: %u changes: %u\n", + info->ifname, info->ifindex, info->refcnt, + info->gencnt); + else + printf("%s ifindex: unresolved refcount: %u changes: %u\n", + info->ifname, info->refcnt, info->gencnt); + info = (ipfw_iface_info *)((caddr_t)info + olh->objsize); + } + + free(olh); +} + + + + diff --git a/sbin/ipfw/ipfw2.h b/sbin/ipfw/ipfw2.h index 26ca429b6745..51a31186916d 100644 --- a/sbin/ipfw/ipfw2.h +++ b/sbin/ipfw/ipfw2.h @@ -246,6 +246,7 @@ void *safe_realloc(void *ptr, size_t size); /* string comparison functions used for historical compatibility */ int _substrcmp(const char *str1, const char* str2); int _substrcmp2(const char *str1, const char* str2, const char* str3); +int stringnum_cmp(const char *a, const char *b); /* utility functions */ int match_token(struct _s_x *table, char *string); @@ -295,6 +296,7 @@ void ipfw_delete(char *av[]); void ipfw_flush(int force); void ipfw_zero(int ac, char *av[], int optname); void ipfw_list(int ac, char *av[], int show_counters); +void ipfw_list_tifaces(void); #ifdef PF /* altq.c */ diff --git a/sbin/ipfw/main.c b/sbin/ipfw/main.c index 6d6e456946b3..4dbe7b7b64dd 100644 --- a/sbin/ipfw/main.c +++ b/sbin/ipfw/main.c @@ -438,6 +438,8 @@ ipfw_main(int oldac, char **oldav) ipfw_list(ac, av, 1 /* show counters */); else if (_substrcmp(*av, "table") == 0) ipfw_table_handler(ac, av); + else if (_substrcmp(*av, "iflist") == 0) + ipfw_list_tifaces(); else errx(EX_USAGE, "bad command `%s'", *av); } diff --git a/sbin/ipfw/tables.c b/sbin/ipfw/tables.c index c0393e1be6fe..bd46dc317bd9 100644 --- a/sbin/ipfw/tables.c +++ b/sbin/ipfw/tables.c @@ -767,19 +767,11 @@ static int tablename_cmp(const void *a, const void *b) { ipfw_xtable_info *ia, *ib; - int la, lb; ia = (ipfw_xtable_info *)a; ib = (ipfw_xtable_info *)b; - la = strlen(ia->tablename); - lb = strlen(ib->tablename); - if (la > lb) - return (1); - else if (la < lb) - return (-01); - - return (strcmp(ia->tablename, ib->tablename)); + return (stringnum_cmp(ia->tablename, ib->tablename)); } /* diff --git a/sys/conf/files b/sys/conf/files index de9c1e1876ca..46e7776091c3 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -3450,6 +3450,7 @@ netpfil/ipfw/ip_fw_pfil.c optional inet ipfirewall netpfil/ipfw/ip_fw_sockopt.c optional inet ipfirewall netpfil/ipfw/ip_fw_table.c optional inet ipfirewall netpfil/ipfw/ip_fw_table_algo.c optional inet ipfirewall +netpfil/ipfw/ip_fw_iface.c optional inet ipfirewall netpfil/ipfw/ip_fw_nat.c optional inet ipfirewall_nat netpfil/pf/if_pflog.c optional pflog pf inet netpfil/pf/if_pfsync.c optional pfsync pf inet diff --git a/sys/modules/ipfw/Makefile b/sys/modules/ipfw/Makefile index 10d36a97df3f..9a90c4a25dbf 100644 --- a/sys/modules/ipfw/Makefile +++ b/sys/modules/ipfw/Makefile @@ -7,7 +7,7 @@ KMOD= ipfw SRCS= ip_fw2.c ip_fw_pfil.c SRCS+= ip_fw_dynamic.c ip_fw_log.c -SRCS+= ip_fw_sockopt.c ip_fw_table.c ip_fw_table_algo.c +SRCS+= ip_fw_sockopt.c ip_fw_table.c ip_fw_table_algo.c ip_fw_iface.c SRCS+= opt_inet.h opt_inet6.h opt_ipdivert.h opt_ipfw.h opt_ipsec.h CFLAGS+= -DIPFIREWALL diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h index 463353a958c1..d25b48828407 100644 --- a/sys/netinet/ip_fw.h +++ b/sys/netinet/ip_fw.h @@ -88,6 +88,7 @@ typedef struct _ip_fw3_opheader { #define IP_FW_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 */ /* * Usage guidelines: @@ -729,6 +730,7 @@ typedef struct _ipfw_obj_tlv { #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 { @@ -787,6 +789,16 @@ typedef struct _ipfw_xtable_info { char algoname[32]; /* algorithm name */ } ipfw_xtable_info; +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 */ + #define IPFW_OBJTYPE_TABLE 1 typedef struct _ipfw_obj_header { ip_fw3_opheader opheader; /* IP_FW3 opcode */ @@ -801,7 +813,7 @@ 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 objects size */ + uint32_t size; /* Total size (incl. header) */ uint32_t objsize; /* Size of one object */ } ipfw_obj_lheader; diff --git a/sys/netpfil/ipfw/ip_fw2.c b/sys/netpfil/ipfw/ip_fw2.c index d20156f375aa..76479dc3f52b 100644 --- a/sys/netpfil/ipfw/ip_fw2.c +++ b/sys/netpfil/ipfw/ip_fw2.c @@ -357,15 +357,18 @@ tcpopts_match(struct tcphdr *tcp, ipfw_insn *cmd) } static int -iface_match(struct ifnet *ifp, ipfw_insn_if *cmd, struct ip_fw_chain *chain, uint32_t *tablearg) +iface_match(struct ifnet *ifp, ipfw_insn_if *cmd, struct ip_fw_chain *chain, + uint32_t *tablearg) { + if (ifp == NULL) /* no iface with this packet, match fails */ - return 0; + return (0); + /* Check by name or by IP address */ if (cmd->name[0] != '\0') { /* match by name */ if (cmd->name[0] == '\1') /* use tablearg to match */ return ipfw_lookup_table_extended(chain, cmd->p.glob, 0, - ifp->if_xname, tablearg); + &ifp->if_index, tablearg); /* Check name */ if (cmd->p.glob) { if (fnmatch(cmd->name, ifp->if_xname, 0) == 0) @@ -2608,6 +2611,7 @@ ipfw_init(void) default_fw_tables = IPFW_TABLES_MAX; ipfw_log_bpf(1); /* init */ + ipfw_iface_init(); return (error); } @@ -2618,6 +2622,7 @@ static void ipfw_destroy(void) { + ipfw_iface_destroy(); ipfw_log_bpf(0); /* uninit */ printf("IP firewall unloaded\n"); } @@ -2740,6 +2745,7 @@ vnet_ipfw_uninit(const void *unused) ipfw_destroy_tables(chain); if (reap != NULL) ipfw_reap_rules(reap); + vnet_ipfw_iface_destroy(chain); IPFW_LOCK_DESTROY(chain); ipfw_dyn_uninit(1); /* free the remaining parts */ ipfw_destroy_counters(); diff --git a/sys/netpfil/ipfw/ip_fw_iface.c b/sys/netpfil/ipfw/ip_fw_iface.c new file mode 100644 index 000000000000..4655ecbf65a7 --- /dev/null +++ b/sys/netpfil/ipfw/ip_fw_iface.c @@ -0,0 +1,529 @@ +/*- + * Copyright (c) 2014 Yandex LLC. + * + * 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_iface.c 267384 2014-06-12 09:59:11Z melifaro $"); + +/* + * Kernel interface tracking API. + * + */ + +#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 +#include +#include +#include + +#include +#include /* struct ipfw_rule_ref */ +#include + +#include + +#define CHAIN_TO_II(ch) ((struct namedobj_instance *)ch->ifcfg) + +#define DEFAULT_IFACES 128 + +static void handle_ifdetach(struct ip_fw_chain *ch, struct ipfw_iface *iif, + uint16_t ifindex); +static void handle_ifattach(struct ip_fw_chain *ch, struct ipfw_iface *iif, + uint16_t ifindex); + +/* + * FreeBSD Kernel interface. + */ + +static void ipfw_kifhandler(void *arg, struct ifnet *ifp); +static int ipfw_kiflookup(char *name); +static void iface_khandler_register(void); +static void iface_khandler_deregister(void); + +static eventhandler_tag ipfw_ifdetach_event, ipfw_ifattach_event; +static int num_vnets = 0; +struct mtx vnet_mtx; + +/* + * Checks if kernel interface is contained in our tracked + * interface list and calls attach/detach handler. + */ +static void +ipfw_kifhandler(void *arg, struct ifnet *ifp) +{ + struct ip_fw_chain *ch; + struct ipfw_iface *iif; + struct namedobj_instance *ii; + uintptr_t htype; + + ch = &V_layer3_chain; + htype = (uintptr_t)arg; + + if (ch == NULL) + return; + + IPFW_UH_WLOCK(ch); + ii = CHAIN_TO_II(ch); + if (ii == NULL) { + IPFW_UH_WUNLOCK(ch); + return; + } + iif = (struct ipfw_iface*)ipfw_objhash_lookup_name(ii, 0,ifp->if_xname); + if (iif != NULL) { + if (htype == 1) + handle_ifattach(ch, iif, ifp->if_index); + else + handle_ifdetach(ch, iif, ifp->if_index); + } + IPFW_UH_WUNLOCK(ch); +} + +/* + * Reference current VNET as iface tracking API user. + * Registers interface tracking handlers for first VNET. + */ +static void +iface_khandler_register() +{ + int create; + + create = 0; + + mtx_lock(&vnet_mtx); + if (num_vnets == 0) + create = 1; + num_vnets++; + mtx_unlock(&vnet_mtx); + + if (create == 0) + return; + + printf("IPFW: starting up interface tracker\n"); + + ipfw_ifdetach_event = EVENTHANDLER_REGISTER( + ifnet_departure_event, ipfw_kifhandler, NULL, + EVENTHANDLER_PRI_ANY); + ipfw_ifattach_event = EVENTHANDLER_REGISTER( + ifnet_arrival_event, ipfw_kifhandler, (void*)((uintptr_t)1), + EVENTHANDLER_PRI_ANY); +} + +/* + * + * Detach interface event handlers on last VNET instance + * detach. + */ +static void +iface_khandler_deregister() +{ + int destroy; + + destroy = 0; + mtx_lock(&vnet_mtx); + if (--num_vnets == 0) + destroy = 1; + mtx_unlock(&vnet_mtx); + + if (destroy == 0) + return; + + EVENTHANDLER_DEREGISTER(ifnet_arrival_event, + ipfw_ifattach_event); + EVENTHANDLER_DEREGISTER(ifnet_departure_event, + ipfw_ifdetach_event); +} + +/* + * Retrieves ifindex for given @name. + * + * Returns ifindex or 0. + */ +static int +ipfw_kiflookup(char *name) +{ + struct ifnet *ifp; + int ifindex; + + ifindex = 0; + + if ((ifp = ifunit_ref(name)) != NULL) { + ifindex = ifp->if_index; + if_rele(ifp); + } + + return (ifindex); +} + + + +/* + * Global ipfw startup hook. + * Since we perform lazy initialization, do nothing except + * mutex init. + */ +int +ipfw_iface_init() +{ + + mtx_init(&vnet_mtx, "IPFW ifhandler mtx", NULL, MTX_DEF); + return (0); +} + +/* + * Global ipfw destroy hook. + * Unregister khandlers iff init has been done. + */ +void +ipfw_iface_destroy() +{ + + mtx_destroy(&vnet_mtx); +} + +/* + * Perform actual init on internal request. + * Inits both namehash and global khandler. + */ +static void +vnet_ipfw_iface_init(struct ip_fw_chain *ch) +{ + struct namedobj_instance *ii; + + ii = ipfw_objhash_create(DEFAULT_IFACES); + IPFW_UH_WLOCK(ch); + if (ch->ifcfg == NULL) { + ch->ifcfg = ii; + ii = NULL; + } + IPFW_UH_WUNLOCK(ch); + + if (ii != NULL) { + /* Already initialized. Free namehash. */ + ipfw_objhash_destroy(ii); + } else { + /* We're the first ones. Init kernel hooks. */ + iface_khandler_register(); + } +} + +static void +destroy_iface(struct namedobj_instance *ii, struct named_object *no, + void *arg) +{ + struct ipfw_iface *iif; + struct ip_fw_chain *ch; + + ch = (struct ip_fw_chain *)arg; + iif = (struct ipfw_iface *)no; + + /* Assume all consumers have been already detached */ + free(iif, M_IPFW); +} + +/* + * Per-VNET ipfw detach hook. + * + */ +void +vnet_ipfw_iface_destroy(struct ip_fw_chain *ch) +{ + struct namedobj_instance *ii; + + IPFW_UH_WLOCK(ch); + ii = CHAIN_TO_II(ch); + ch->ifcfg = NULL; + IPFW_UH_WUNLOCK(ch); + + if (ii != NULL) { + ipfw_objhash_foreach(ii, destroy_iface, ch); + ipfw_objhash_destroy(ii); + iface_khandler_deregister(); + } +} + +/* + * Notify the subsystem that we are interested in tracking + * interface @name. This function has to be called without + * holding any locks to permit allocating the necessary states + * for proper interface tracking. + * + * Returns 0 on success. + */ +int +ipfw_iface_ref(struct ip_fw_chain *ch, char *name, + struct ipfw_ifc *ic) +{ + struct namedobj_instance *ii; + struct ipfw_iface *iif, *tmp; + + if (strlen(name) >= sizeof(iif->ifname)) + return (EINVAL); + + IPFW_UH_WLOCK(ch); + + ii = CHAIN_TO_II(ch); + if (ii == NULL) { + + /* + * First request to subsystem. + * Let's perform init. + */ + IPFW_UH_WUNLOCK(ch); + vnet_ipfw_iface_init(ch); + IPFW_UH_WLOCK(ch); + ii = CHAIN_TO_II(ch); + } + + iif = (struct ipfw_iface *)ipfw_objhash_lookup_name(ii, 0, name); + + if (iif != NULL) { + iif->no.refcnt++; + ic->iface = iif; + IPFW_UH_WUNLOCK(ch); + return (0); + } + + IPFW_UH_WUNLOCK(ch); + + /* Not found. Let's create one */ + iif = malloc(sizeof(struct ipfw_iface), M_IPFW, M_WAITOK | M_ZERO); + TAILQ_INIT(&iif->consumers); + iif->no.name = iif->ifname; + strlcpy(iif->ifname, name, sizeof(iif->ifname)); + + /* + * Ref & link to the list. + * + * We assume ifnet_arrival_event / ifnet_departure_event + * are not holding any locks. + */ + iif->no.refcnt = 1; + IPFW_UH_WLOCK(ch); + + tmp = (struct ipfw_iface *)ipfw_objhash_lookup_name(ii, 0, name); + if (tmp != NULL) { + /* Interface has been created since unlock. Ref and return */ + tmp->no.refcnt++; + ic->iface = tmp; + IPFW_UH_WUNLOCK(ch); + free(iif, M_IPFW); + return (0); + } + + iif->ifindex = ipfw_kiflookup(name); + if (iif->ifindex != 0) + iif->resolved = 1; + + ipfw_objhash_add(ii, &iif->no); + ic->iface = iif; + + IPFW_UH_WUNLOCK(ch); + + return (0); +} + +/* + * Adds @ic to the list of iif interface consumers. + * Must be called with holding both UH+WLOCK. + * Callback may be immediately called (if interface exists). + */ +void +ipfw_iface_add_notify(struct ip_fw_chain *ch, struct ipfw_ifc *ic) +{ + struct ipfw_iface *iif; + + IPFW_UH_WLOCK_ASSERT(ch); + IPFW_WLOCK_ASSERT(ch); + + iif = ic->iface; + + TAILQ_INSERT_TAIL(&iif->consumers, ic, next); + if (iif->resolved != 0) + ic->cb(ch, ic->cbdata, iif->ifindex); +} + +/* + * Unlinks interface tracker object @ic from interface. + * Must be called whi holding UH lock. + */ +void +ipfw_iface_del_notify(struct ip_fw_chain *ch, struct ipfw_ifc *ic) +{ + struct ipfw_iface *iif; + + IPFW_UH_WLOCK_ASSERT(ch); + + iif = ic->iface; + if (ic->linked != 0) + TAILQ_REMOVE(&iif->consumers, ic, next); +} + +/* + * Unreference interface specified by @ic. + * Must be called without holding any locks. + */ +void +ipfw_iface_unref(struct ip_fw_chain *ch, struct ipfw_ifc *ic) +{ + struct ipfw_iface *iif; + + iif = ic->iface; + ic->iface = NULL; + + IPFW_UH_WLOCK(ch); + iif->no.refcnt--; + /* TODO: check for references & delete */ + IPFW_UH_WUNLOCK(ch); +} + +/* + * Interface arrival handler. + */ +static void +handle_ifattach(struct ip_fw_chain *ch, struct ipfw_iface *iif, + uint16_t ifindex) +{ + struct ipfw_ifc *ic; + + IPFW_UH_WLOCK_ASSERT(ch); + + iif->gencnt++; + iif->resolved = 1; + iif->ifindex = ifindex; + + IPFW_WLOCK(ch); + TAILQ_FOREACH(ic, &iif->consumers, next) + ic->cb(ch, ic->cbdata, iif->ifindex); + IPFW_WUNLOCK(ch); +} + +/* + * Interface departure handler. + */ +static void +handle_ifdetach(struct ip_fw_chain *ch, struct ipfw_iface *iif, + uint16_t ifindex) +{ + struct ipfw_ifc *ic; + + IPFW_UH_WLOCK_ASSERT(ch); + + IPFW_WLOCK(ch); + TAILQ_FOREACH(ic, &iif->consumers, next) + ic->cb(ch, ic->cbdata, 0); + IPFW_WUNLOCK(ch); + + iif->gencnt++; + iif->resolved = 0; + iif->ifindex = 0; +} + +struct dump_iface_args { + struct ip_fw_chain *ch; + struct sockopt_data *sd; +}; + +static void +export_iface_internal(struct namedobj_instance *ii, struct named_object *no, + void *arg) +{ + ipfw_iface_info *i; + struct dump_iface_args *da; + struct ipfw_iface *iif; + + da = (struct dump_iface_args *)arg; + + i = (ipfw_iface_info *)ipfw_get_sopt_space(da->sd, sizeof(*i)); + KASSERT(i != 0, ("previously checked buffer is not enough")); + + iif = (struct ipfw_iface *)no; + + strlcpy(i->ifname, iif->ifname, sizeof(i->ifname)); + if (iif->resolved) + i->flags |= IPFW_IFFLAG_RESOLVED; + i->ifindex = iif->ifindex; + i->refcnt = iif->no.refcnt; + i->gencnt = iif->gencnt; +} + +/* + * Lists all interface currently tracked by ipfw. + * Data layout (v0)(current): + * Request: [ ipfw_obj_lheader ], size = ipfw_obj_lheader.size + * Reply: [ ipfw_obj_lheader ipfw_iface_info x N ] + * + * Returns 0 on success + */ +int +ipfw_list_ifaces(struct ip_fw_chain *ch, struct sockopt_data *sd) +{ + struct _ipfw_obj_lheader *olh; + struct dump_iface_args da; + uint32_t count, 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); + count = ipfw_objhash_count(CHAIN_TO_II(ch)); + size = count * sizeof(ipfw_iface_info) + sizeof(ipfw_obj_lheader); + + /* Fill in header regadless of buffer size */ + olh->count = count; + olh->objsize = sizeof(ipfw_iface_info); + + if (size > olh->size) { + olh->size = size; + IPFW_UH_RUNLOCK(ch); + return (ENOMEM); + } + olh->size = size; + + da.ch = ch; + da.sd = sd; + + ipfw_objhash_foreach(CHAIN_TO_II(ch), export_iface_internal, &da); + IPFW_UH_RUNLOCK(ch); + + return (0); +} + + diff --git a/sys/netpfil/ipfw/ip_fw_private.h b/sys/netpfil/ipfw/ip_fw_private.h index a7e578c0fd12..5d3a5badcb85 100644 --- a/sys/netpfil/ipfw/ip_fw_private.h +++ b/sys/netpfil/ipfw/ip_fw_private.h @@ -274,6 +274,7 @@ struct ip_fw_chain { struct ip_fw *reap; /* list of rules to reap */ struct ip_fw *default_rule; struct tables_config *tblcfg; /* tables module data */ + void *ifcfg; /* interface module data */ #if defined( __linux__ ) || defined( _WIN32 ) spinlock_t uh_lock; #else @@ -281,6 +282,21 @@ struct ip_fw_chain { #endif }; +struct namedobj_instance; + +struct named_object { + TAILQ_ENTRY(named_object) nn_next; /* namehash */ + TAILQ_ENTRY(named_object) nv_next; /* valuehash */ + char *name; /* object name */ + uint8_t type; /* object type */ + uint8_t compat; /* Object name is number */ + uint16_t kidx; /* object kernel index */ + uint16_t uidx; /* userland idx for compat records */ + uint32_t set; /* set object belongs to */ + uint32_t refcnt; /* number of references */ +}; +TAILQ_HEAD(namedobjects_head, named_object); + struct sockopt; /* used by tcp_var.h */ struct sockopt_data { caddr_t kbuf; /* allocated buffer */ @@ -292,6 +308,30 @@ struct sockopt_data { size_t valsize; /* original data size */ }; +struct ipfw_ifc; + +typedef void (ipfw_ifc_cb)(struct ip_fw_chain *ch, void *cbdata, + uint16_t ifindex); + +struct ipfw_iface { + struct named_object no; + char ifname[64]; + int resolved; + uint16_t ifindex; + uint16_t spare; + uint64_t gencnt; + TAILQ_HEAD(, ipfw_ifc) consumers; +}; + +struct ipfw_ifc { + TAILQ_ENTRY(ipfw_ifc) next; + struct ipfw_iface *iface; + ipfw_ifc_cb *cb; + void *cbdata; + int linked; + int spare; +}; + /* Macro for working with various counters */ #ifdef USERSPACE #define IPFW_INC_RULE_COUNTER(_cntr, _bytes) do { \ @@ -442,6 +482,17 @@ struct ip_fw_bcounter0 { #define RULEKSIZE1(r) roundup2((sizeof(struct ip_fw) + (r)->cmd_len*4 - 4), 8) +/* In ip_fw_iface.c */ +int ipfw_iface_init(void); +void ipfw_iface_destroy(void); +void vnet_ipfw_iface_destroy(struct ip_fw_chain *ch); +int ipfw_iface_ref(struct ip_fw_chain *ch, char *name, + struct ipfw_ifc *ic); +void ipfw_iface_unref(struct ip_fw_chain *ch, struct ipfw_ifc *ic); +void ipfw_iface_add_notify(struct ip_fw_chain *ch, struct ipfw_ifc *ic); +void ipfw_iface_del_notify(struct ip_fw_chain *ch, struct ipfw_ifc *ic); +int ipfw_list_ifaces(struct ip_fw_chain *ch, struct sockopt_data *sd); + /* In ip_fw_sockopt.c */ int ipfw_find_rule(struct ip_fw_chain *chain, uint32_t key, uint32_t id); int ipfw_ctl(struct sockopt *sopt); @@ -454,21 +505,6 @@ struct ip_fw *ipfw_alloc_rule(struct ip_fw_chain *chain, size_t rulesize); caddr_t ipfw_get_sopt_space(struct sockopt_data *sd, size_t needed); caddr_t ipfw_get_sopt_header(struct sockopt_data *sd, size_t needed); -struct namedobj_instance; - -struct named_object { - TAILQ_ENTRY(named_object) nn_next; /* namehash */ - TAILQ_ENTRY(named_object) nv_next; /* valuehash */ - char *name; /* object name */ - uint8_t type; /* object type */ - uint8_t compat; /* Object name is number */ - uint16_t kidx; /* object kernel index */ - uint16_t uidx; /* userland idx for compat records */ - uint32_t set; /* set object belongs to */ - uint32_t refcnt; /* number of references */ -}; -TAILQ_HEAD(namedobjects_head, named_object); - typedef void (objhash_cb_t)(struct namedobj_instance *ni, struct named_object *, void *arg); struct namedobj_instance *ipfw_objhash_create(uint32_t items); diff --git a/sys/netpfil/ipfw/ip_fw_sockopt.c b/sys/netpfil/ipfw/ip_fw_sockopt.c index 64f9fb401cef..17b6f5bc24e1 100644 --- a/sys/netpfil/ipfw/ip_fw_sockopt.c +++ b/sys/netpfil/ipfw/ip_fw_sockopt.c @@ -1859,6 +1859,10 @@ ipfw_ctl(struct sockopt *sopt) error = dump_config(chain, &sdata); break; + case IP_FW_XIFLIST: /* IP_FW3 */ + error = ipfw_list_ifaces(chain, &sdata); + break; + case IP_FW_XADD: /* IP_FW3 */ error = add_entry(chain, &sdata); break; diff --git a/sys/netpfil/ipfw/ip_fw_table.c b/sys/netpfil/ipfw/ip_fw_table.c index 766c4a19eaaa..b328bed26834 100644 --- a/sys/netpfil/ipfw/ip_fw_table.c +++ b/sys/netpfil/ipfw/ip_fw_table.c @@ -94,7 +94,7 @@ struct tables_config { static struct table_config *find_table(struct namedobj_instance *ni, struct tid_info *ti); -static struct table_config *alloc_table_config(struct namedobj_instance *ni, +static struct table_config *alloc_table_config(struct ip_fw_chain *ch, struct tid_info *ti, struct table_algo *ta, char *adata, uint8_t vtype); static void free_table_config(struct namedobj_instance *ni, struct table_config *tc); @@ -206,7 +206,7 @@ add_table_entry(struct ip_fw_chain *ch, struct tid_info *ti, /* Prepare record (allocate memory) */ memset(&ta_buf, 0, sizeof(ta_buf)); - error = ta->prepare_add(tei, &ta_buf); + error = ta->prepare_add(ch, tei, &ta_buf); if (error != 0) return (error); @@ -238,7 +238,7 @@ add_table_entry(struct ip_fw_chain *ch, struct tid_info *ti, IPFW_UH_WUNLOCK(ch); /* Run cleaning callback anyway */ - ta->flush_entry(tei, &ta_buf); + ta->flush_entry(ch, tei, &ta_buf); return (error); } @@ -296,7 +296,7 @@ del_table_entry(struct ip_fw_chain *ch, struct tid_info *ti, * prepare_del() key, so we're running under UH_LOCK here. */ memset(&ta_buf, 0, sizeof(ta_buf)); - if ((error = ta->prepare_del(tei, &ta_buf)) != 0) { + if ((error = ta->prepare_del(ch, tei, &ta_buf)) != 0) { IPFW_UH_WUNLOCK(ch); return (error); } @@ -313,7 +313,7 @@ del_table_entry(struct ip_fw_chain *ch, struct tid_info *ti, IPFW_UH_WUNLOCK(ch); - ta->flush_entry(tei, &ta_buf); + ta->flush_entry(ch, tei, &ta_buf); return (error); } @@ -341,7 +341,7 @@ modify_table(struct ip_fw_chain *ch, struct table_config *tc, IPFW_UH_WLOCK(ch); ti = KIDX_TO_TI(ch, tc->no.kidx); - error = ta->fill_mod(tc->astate, ti, &ta_buf, &pflags); + error = ta->fill_mod(tc->astate, ti, ta_buf, &pflags); /* * prepare_mofify may return zero in @pflags to @@ -351,7 +351,7 @@ modify_table(struct ip_fw_chain *ch, struct table_config *tc, if (error == 0 && pflags != 0) { /* Do actual modification */ IPFW_WLOCK(ch); - ta->modify(tc->astate, ti, &ta_buf, pflags); + ta->modify(tc->astate, ti, ta_buf, pflags); IPFW_WUNLOCK(ch); } @@ -666,7 +666,7 @@ flush_table(struct ip_fw_chain *ch, struct tid_info *ti) * TODO: pass startup parametes somehow. */ memset(&ti_new, 0, sizeof(struct table_info)); - if ((error = ta->init(&astate_new, &ti_new, NULL)) != 0) { + if ((error = ta->init(ch, &astate_new, &ti_new, NULL)) != 0) { IPFW_UH_WLOCK(ch); tc->no.refcnt--; IPFW_UH_WUNLOCK(ch); @@ -803,7 +803,9 @@ 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; - int new_blocks; + struct table_info *ti; + struct table_config *tc; + int i, new_blocks; /* Check new value for validity */ if (ntables > IPFW_TABLES_MAX) @@ -845,6 +847,19 @@ ipfw_resize_tables(struct ip_fw_chain *ch, unsigned int ntables) 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 */ @@ -923,21 +938,15 @@ int ipfw_list_tables(struct ip_fw_chain *ch, struct sockopt_data *sd) { struct _ipfw_obj_lheader *olh; - uint32_t sz; 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); - sz = ipfw_objhash_count(CHAIN_TO_NI(ch)); - - if (sd->valsize < sz) { - IPFW_UH_RUNLOCK(ch); - return (ENOMEM); - } - error = export_tables(ch, olh, sd); IPFW_UH_RUNLOCK(ch); @@ -1214,7 +1223,7 @@ create_table_internal(struct ip_fw_chain *ch, struct tid_info *ti, if (ta == NULL) return (ENOTSUP); - if ((tc = alloc_table_config(ni, ti, ta, aname, vtype)) == NULL) + if ((tc = alloc_table_config(ch, ti, ta, aname, vtype)) == NULL) return (ENOMEM); IPFW_UH_WLOCK(ch); @@ -1320,7 +1329,7 @@ export_table_internal(struct namedobj_instance *ni, struct named_object *no, 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")); + KASSERT(i != 0, ("previously checked buffer is not enough")); export_table_info(dta->ch, (struct table_config *)no, i); } @@ -1346,10 +1355,10 @@ export_tables(struct ip_fw_chain *ch, ipfw_obj_lheader *olh, olh->objsize = sizeof(ipfw_xtable_info); if (size > olh->size) { - /* Store necessary size */ olh->size = size; return (ENOMEM); } + olh->size = size; dta.ch = ch; @@ -1581,7 +1590,8 @@ find_table_algo(struct tables_config *tcfg, struct tid_info *ti, char *name) case IPFW_TABLE_CIDR: return (&radix_cidr); case IPFW_TABLE_INTERFACE: - return (&radix_iface); + return (&idx_iface); + //return (&radix_iface); } return (NULL); @@ -1799,7 +1809,7 @@ find_table(struct namedobj_instance *ni, struct tid_info *ti) } static struct table_config * -alloc_table_config(struct namedobj_instance *ni, struct tid_info *ti, +alloc_table_config(struct ip_fw_chain *ch, struct tid_info *ti, struct table_algo *ta, char *aname, uint8_t vtype) { char *name, bname[16]; @@ -1838,7 +1848,7 @@ alloc_table_config(struct namedobj_instance *ni, struct tid_info *ti, } /* Preallocate data structures for new tables */ - error = ta->init(&tc->astate, &tc->ti, aname); + error = ta->init(ch, &tc->astate, &tc->ti, aname); if (error != 0) { free(tc, M_IPFW); return (NULL); @@ -1852,7 +1862,7 @@ free_table_config(struct namedobj_instance *ni, struct table_config *tc) { if (tc->linked == 0) - tc->ta->destroy(&tc->astate, &tc->ti); + tc->ta->destroy(tc->astate, &tc->ti); free(tc, M_IPFW); } @@ -1879,6 +1889,10 @@ link_table(struct ip_fw_chain *ch, struct table_config *tc) 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; } @@ -1904,6 +1918,10 @@ unlink_table(struct ip_fw_chain *ch, struct table_config *tc) ti = KIDX_TO_TI(ch, kidx); memset(ti, 0, sizeof(struct table_info)); tc->linked = 0; + + /* Notify algo on real @ti address */ + if (tc->ta->change_ti != NULL) + tc->ta->change_ti(tc->astate, NULL); } /* @@ -2169,7 +2187,8 @@ ipfw_rewrite_table_uidx(struct ip_fw_chain *chain, error = ENOTSUP; goto free; } - tc = alloc_table_config(ni, &ti, ta, NULL, IPFW_VTYPE_U32); + tc = alloc_table_config(chain, &ti, ta, NULL, + IPFW_VTYPE_U32); if (tc == NULL) { error = ENOMEM; diff --git a/sys/netpfil/ipfw/ip_fw_table.h b/sys/netpfil/ipfw/ip_fw_table.h index d6875abdc953..59f52d8454de 100644 --- a/sys/netpfil/ipfw/ip_fw_table.h +++ b/sys/netpfil/ipfw/ip_fw_table.h @@ -62,15 +62,19 @@ struct tentry_info { #define TEI_FLAGS_UPDATED 0x02 /* Entry has been updated */ #define TEI_FLAGS_COMPAT 0x04 /* Called from old ABI */ -typedef int (ta_init)(void **ta_state, struct table_info *ti, char *data); +typedef int (ta_init)(struct ip_fw_chain *ch, void **ta_state, + struct table_info *ti, char *data); typedef void (ta_destroy)(void *ta_state, struct table_info *ti); -typedef int (ta_prepare_add)(struct tentry_info *tei, void *ta_buf); -typedef int (ta_prepare_del)(struct tentry_info *tei, void *ta_buf); +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, uint64_t *pflags); typedef int (ta_del)(void *ta_state, struct table_info *ti, struct tentry_info *tei, void *ta_buf, uint64_t *pflags); -typedef void (ta_flush_entry)(struct tentry_info *tei, void *ta_buf); +typedef void (ta_flush_entry)(struct ip_fw_chain *ch, struct tentry_info *tei, + void *ta_buf); typedef int (ta_prepare_mod)(void *ta_buf, uint64_t *pflags); typedef int (ta_fill_mod)(void *ta_state, struct table_info *ti, @@ -79,6 +83,7 @@ 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); @@ -109,9 +114,11 @@ struct table_algo { ta_dump_tentry *dump_tentry; ta_print_config *print_config; ta_find_tentry *find_tentry; + ta_change_ti *change_ti; }; + void ipfw_add_table_algo(struct ip_fw_chain *ch, struct table_algo *ta); -extern struct table_algo radix_cidr, radix_iface; +extern struct table_algo radix_cidr, idx_iface; void ipfw_table_algo_init(struct ip_fw_chain *chain); void ipfw_table_algo_destroy(struct ip_fw_chain *chain); diff --git a/sys/netpfil/ipfw/ip_fw_table_algo.c b/sys/netpfil/ipfw/ip_fw_table_algo.c index 7dcfe6ac1192..3147464c7cc4 100644 --- a/sys/netpfil/ipfw/ip_fw_table_algo.c +++ b/sys/netpfil/ipfw/ip_fw_table_algo.c @@ -29,14 +29,6 @@ __FBSDID("$FreeBSD: projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c 267384 2014-06- /* * Lookup table algorithms. * - * Lookup tables are implemented (at the moment) using the radix - * tree used for routing tables. Tables store key-value entries, where - * keys are network prefixes (addr/masklen), and values are integers. - * As a degenerate case we can interpret keys as 32-bit integers - * (with a /32 mask). - * - * The table is protected by the IPFW lock even for manipulation coming - * from userland, because operations are typically fast. */ #include "opt_ipfw.h" @@ -68,6 +60,17 @@ __FBSDID("$FreeBSD: projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c 267384 2014-06- static MALLOC_DEFINE(M_IPFW_TBL, "ipfw_tbl", "IpFw tables"); +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 @@ -83,11 +86,9 @@ static MALLOC_DEFINE(M_IPFW_TBL, "ipfw_tbl", "IpFw tables"); */ #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 KEY_LEN_IFACE (offsetof(struct xaddr_iface, ifname)) #define OFF_LEN_INET (8 * offsetof(struct sockaddr_in, sin_addr)) #define OFF_LEN_INET6 (8 * offsetof(struct sa_in6, sin6_addr)) -#define OFF_LEN_IFACE (8 * offsetof(struct xaddr_iface, ifname)) struct radix_cidr_entry { struct radix_node rn[2]; @@ -110,23 +111,6 @@ struct radix_cidr_xentry { uint8_t masklen; }; -struct xaddr_iface { - uint8_t if_len; /* length of this struct */ - uint8_t pad[7]; /* Align name */ - char ifname[IF_NAMESIZE]; /* Interface name */ -}; - -struct radix_iface { - struct radix_node rn[2]; - struct xaddr_iface iface; - uint32_t value; -}; - -/* - * CIDR implementation using radix - * - */ - static int ta_lookup_radix(struct table_info *ti, void *key, uint32_t keylen, uint32_t *val) @@ -164,7 +148,8 @@ ta_lookup_radix(struct table_info *ti, void *key, uint32_t keylen, * New table */ static int -ta_init_radix(void **ta_state, struct table_info *ti, char *data) +ta_init_radix(struct ip_fw_chain *ch, void **ta_state, struct table_info *ti, + char *data) { if (!rn_inithead(&ti->state, OFF_LEN_INET)) @@ -310,7 +295,8 @@ ipv6_writemask(struct in6_addr *addr6, uint8_t mask) static int -ta_prepare_add_cidr(struct tentry_info *tei, void *ta_buf) +ta_prepare_add_cidr(struct ip_fw_chain *ch, struct tentry_info *tei, + void *ta_buf) { struct ta_buf_cidr *tb; struct radix_cidr_entry *ent; @@ -432,7 +418,8 @@ ta_add_cidr(void *ta_state, struct table_info *ti, } static int -ta_prepare_del_cidr(struct tentry_info *tei, void *ta_buf) +ta_prepare_del_cidr(struct ip_fw_chain *ch, struct tentry_info *tei, + void *ta_buf) { struct ta_buf_cidr *tb; struct sockaddr_in sa, mask; @@ -512,7 +499,8 @@ ta_del_cidr(void *ta_state, struct table_info *ti, } static void -ta_flush_cidr_entry(struct tentry_info *tei, void *ta_buf) +ta_flush_cidr_entry(struct ip_fw_chain *ch, struct tentry_info *tei, + void *ta_buf) { struct ta_buf_cidr *tb; @@ -539,25 +527,168 @@ struct table_algo radix_cidr = { /* - * Iface table cmds + * Iface table cmds. + * + * Implementation: + * + * Runtime part: + * - sorted array of "struct ifidx" pointed by ti->state. + * Array is allocated with routing 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; + TAILQ_ENTRY(ifentry) next; + 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 */ +}; + +#define IFIDX_CHUNK 16 + +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 -ta_lookup_iface(struct table_info *ti, void *key, uint32_t keylen, +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 radix_node_head *rnh; - struct xaddr_iface iface; - struct radix_iface *xent; + struct ifidx *ifi; - KEY_LEN(iface) = KEY_LEN_IFACE + - strlcpy(iface.ifname, (char *)key, IF_NAMESIZE) + 1; + ifi = ifidx_find(ti, key); - rnh = (struct radix_node_head *)ti->xstate; - xent = (struct radix_iface *)(rnh->rnh_matchaddr(&iface, rnh)); - if (xent != NULL) { - *val = xent->value; + if (ifi != NULL) { + *val = ifi->value; return (1); } @@ -565,56 +696,182 @@ ta_lookup_iface(struct table_info *ti, void *key, uint32_t keylen, } static int -flush_iface_entry(struct radix_node *rn, void *arg) +ta_init_ifidx(struct ip_fw_chain *ch, void **ta_state, struct table_info *ti, + char *data) { - struct radix_node_head * const rnh = arg; - struct radix_iface *xent; + struct iftable_cfg *icfg; - xent = (struct radix_iface *) - rnh->rnh_deladdr(rn->rn_key, rn->rn_mask, rnh); - if (xent != NULL) - free(xent, M_IPFW_TBL); - return (0); -} + icfg = malloc(sizeof(struct iftable_cfg), M_IPFW, M_WAITOK | M_ZERO); -static int -ta_init_iface(void **ta_state, struct table_info *ti, char *data) -{ + icfg->ii = ipfw_objhash_create(16); + icfg->main_ptr = malloc(sizeof(struct ifidx) * IFIDX_CHUNK, M_IPFW, + M_WAITOK | M_ZERO); + icfg->size = IFIDX_CHUNK; + icfg->ch = ch; - if (!rn_inithead(&ti->xstate, OFF_LEN_IFACE)) - return (ENOMEM); - - *ta_state = NULL; - ti->lookup = ta_lookup_iface; + *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 -ta_destroy_iface(void *ta_state, struct table_info *ti) +destroy_ifidx_locked(struct namedobj_instance *ii, struct named_object *no, + void *arg) { - struct radix_node_head *rnh; + struct ifentry *ife; + struct ip_fw_chain *ch; - rnh = (struct radix_node_head *)(ti->xstate); - rnh->rnh_walktree(rnh, flush_iface_entry, rnh); - rn_detachhead(&ti->xstate); + ch = (struct ip_fw_chain *)arg; + ife = (struct ifentry *)no; + + ipfw_iface_del_notify(ch, &ife->ic); + free(ife, M_IPFW_TBL); } -struct ta_buf_iface + +/* + * Destroys table @ti + */ +static void +ta_destroy_ifidx(void *ta_state, struct table_info *ti) { - void *addr_ptr; - void *mask_ptr; - void *ent_ptr; - struct xaddr_iface iface; + 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); +} + +struct ta_buf_ifidx +{ + struct ifentry *ife; + uint32_t value; }; +/* + * Prepare state to add to the table: + * allocate ifentry and reference needed interface. + */ static int -ta_prepare_add_iface(struct tentry_info *tei, void *ta_buf) +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; + memset(tb, 0, sizeof(struct ta_buf_cidr)); + + /* 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, uint64_t *pflags) +{ + struct iftable_cfg *icfg; + struct ifentry *ife, *tmp; + struct ta_buf_ifidx *tb; + struct ipfw_iface *iif; + struct ifidx *ifi; + char *ifname; + + 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); + + /* We need to update value */ + iif = tmp->ic.iface; + tmp->value = ife->value; + + if (iif->resolved != 0) { + /* We need 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; + return (0); + } + + /* 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++; + + if (icfg->count + 1 == icfg->size) { + /* Notify core we need to grow */ + *pflags = icfg->size + IFIDX_CHUNK; + } + + tb->ife = NULL; + + 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_iface *tb; - struct radix_iface *xent; - int mlen; char *ifname; tb = (struct ta_buf_iface *)ta_buf; @@ -625,189 +882,302 @@ ta_prepare_add_iface(struct tentry_info *tei, void *ta_buf) if (strnlen(ifname, IF_NAMESIZE) == IF_NAMESIZE) return (EINVAL); - /* Include last \0 into comparison */ - mlen = strlen(ifname) + 1; + return (0); +} - xent = malloc(sizeof(*xent), M_IPFW_TBL, M_WAITOK | M_ZERO); - xent->value = tei->value; - /* Set 'total' structure length */ - KEY_LEN(xent->iface) = KEY_LEN_IFACE + mlen; - memcpy(xent->iface.ifname, tei->paddr, mlen); - /* Set pointers */ - tb->ent_ptr = xent; - tb->addr_ptr = (struct sockaddr *)&xent->iface; - /* Assume direct match */ - tb->mask_ptr = NULL; +/* + * 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, uint64_t *pflags) +{ + 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--; + + tb->ife = ife; return (0); } -static int -ta_add_iface(void *ta_state, struct table_info *ti, - struct tentry_info *tei, void *ta_buf, uint64_t *pflags) +/* + * 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 radix_node_head *rnh; - struct radix_node *rn; - struct ta_buf_iface *tb; - uint32_t value; + struct ta_buf_ifidx *tb; - tb = (struct ta_buf_iface *)ta_buf; + tb = (struct ta_buf_ifidx *)ta_buf; - rnh = ti->xstate; - rn = rnh->rnh_addaddr(tb->addr_ptr, tb->mask_ptr, rnh, tb->ent_ptr); - - if (rn == NULL) { - if ((tei->flags & TEI_FLAGS_UPDATE) == 0) - return (EEXIST); - /* Record already exists. Update value if we're asked to */ - rn = rnh->rnh_lookup(tb->addr_ptr, tb->mask_ptr, rnh); - if (rn == NULL) { - /* Radix may have failed addition for other reasons */ - return (EINVAL); - } - - value = ((struct radix_iface *)tb->ent_ptr)->value; - ((struct radix_iface *)rn)->value = value; + if (tb->ife != NULL) { + /* Unlink first */ + ipfw_iface_unref(ch, &tb->ife->ic); + free(tb->ife, M_IPFW_TBL); + } +} - /* Indicate that update has happened instead of addition */ - tei->flags |= TEI_FLAGS_UPDATED; +/* + * 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. + */ + +struct mod_ifidx { + void *main_ptr; + size_t size; +}; + +/* + * Allocate ned, larger runtime ifidx array. + */ +static int +ta_prepare_mod_ifidx(void *ta_buf, uint64_t *pflags) +{ + struct mod_ifidx *mi; + + mi = (struct mod_ifidx *)ta_buf; + + memset(mi, 0, sizeof(struct mod_ifidx)); + 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_ifidx *mi; + struct iftable_cfg *icfg; + + mi = (struct mod_ifidx *)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); } - tb->ent_ptr = NULL; + memcpy(mi->main_ptr, icfg->main_ptr, icfg->used * sizeof(struct ifidx)); return (0); } +/* + * Switch old & new arrays. + */ static int -ta_prepare_del_iface(struct tentry_info *tei, void *ta_buf) +ta_modify_ifidx(void *ta_state, struct table_info *ti, void *ta_buf, + uint64_t pflags) { - struct ta_buf_iface *tb; - int mlen; - char c; + struct mod_ifidx *mi; + struct iftable_cfg *icfg; + void *old_ptr; - tb = (struct ta_buf_iface *)ta_buf; - memset(tb, 0, sizeof(struct ta_buf_cidr)); + mi = (struct mod_ifidx *)ta_buf; + icfg = (struct iftable_cfg *)ta_state; - /* Check if string is terminated */ - c = ((char *)tei->paddr)[IF_NAMESIZE - 1]; - ((char *)tei->paddr)[IF_NAMESIZE - 1] = '\0'; - mlen = strlen((char *)tei->paddr); - if ((mlen == IF_NAMESIZE - 1) && (c != '\0')) - return (EINVAL); + old_ptr = icfg->main_ptr; + icfg->main_ptr = mi->main_ptr; + icfg->size = mi->size; + ti->state = icfg->main_ptr; - struct xaddr_iface ifname, ifmask; - memset(&ifname, 0, sizeof(ifname)); - - /* Include last \0 into comparison */ - mlen++; - - /* Set 'total' structure length */ - KEY_LEN(ifname) = KEY_LEN_IFACE + mlen; - KEY_LEN(ifmask) = KEY_LEN_IFACE + mlen; - /* Assume direct match */ - memcpy(ifname.ifname, tei->paddr, mlen); - /* Set pointers */ - tb->iface = ifname; - tb->addr_ptr = &tb->iface; - tb->mask_ptr = NULL; - - return (0); -} - -static int -ta_del_iface(void *ta_state, struct table_info *ti, - struct tentry_info *tei, void *ta_buf, uint64_t *pflags) -{ - struct radix_node_head *rnh; - struct radix_node *rn; - struct ta_buf_iface *tb; - - tb = (struct ta_buf_iface *)ta_buf; - - rnh = ti->xstate; - rn = rnh->rnh_deladdr(tb->addr_ptr, tb->mask_ptr, rnh); - - tb->ent_ptr = rn; - - if (rn == NULL) - return (ENOENT); + mi->main_ptr = old_ptr; return (0); } +/* + * Free unneded array. + */ static void -ta_flush_iface_entry(struct tentry_info *tei, void *ta_buf) +ta_flush_mod_ifidx(void *ta_buf) { - struct ta_buf_iface *tb; + struct mod_ifidx *mi; - tb = (struct ta_buf_iface *)ta_buf; - - if (tb->ent_ptr != NULL) - free(tb->ent_ptr, M_IPFW_TBL); + mi = (struct mod_ifidx *)ta_buf; + if (mi->main_ptr != NULL) + free(mi->main_ptr, M_IPFW); } static int -ta_dump_iface_tentry(void *ta_state, struct table_info *ti, void *e, +ta_dump_ifidx_tentry(void *ta_state, struct table_info *ti, void *e, ipfw_obj_tentry *tent) { - struct radix_iface *xn; + struct ifentry *ife; + + ife = (struct ifentry *)e; - xn = (struct radix_iface *)e; tent->masklen = 8 * IF_NAMESIZE; - memcpy(&tent->k, &xn->iface.ifname, IF_NAMESIZE); - tent->value = xn->value; + memcpy(&tent->k, ife->no.name, IF_NAMESIZE); + tent->value = ife->value; return (0); } static int -ta_find_iface_tentry(void *ta_state, struct table_info *ti, void *key, +ta_find_ifidx_tentry(void *ta_state, struct table_info *ti, void *key, uint32_t keylen, ipfw_obj_tentry *tent) { - struct radix_node_head *rnh; - struct xaddr_iface iface; - void *e; - e = NULL; + struct iftable_cfg *icfg; + struct ifentry *ife; + char *ifname; - KEY_LEN(iface) = KEY_LEN_IFACE + - strlcpy(iface.ifname, (char *)key, IF_NAMESIZE) + 1; + icfg = (struct iftable_cfg *)ta_state; + ifname = (char *)key; - rnh = (struct radix_node_head *)ti->xstate; - e = rnh->rnh_matchaddr(&iface, rnh); + if (strnlen(ifname, IF_NAMESIZE) == IF_NAMESIZE) + return (EINVAL); - if (e != NULL) { - ta_dump_iface_tentry(ta_state, ti, e, tent); + 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 -ta_foreach_iface(void *ta_state, struct table_info *ti, ta_foreach_f *f, +foreach_ifidx(struct namedobj_instance *ii, struct named_object *no, void *arg) { - struct radix_node_head *rnh; + struct ifentry *ife; + struct wa_ifidx *wa; - rnh = (struct radix_node_head *)(ti->xstate); - rnh->rnh_walktree(rnh, (walktree_f_t *)f, arg); + ife = (struct ifentry *)no; + wa = (struct wa_ifidx *)arg; + + wa->f(ife, wa->arg); } -struct table_algo radix_iface = { - .name = "radix_iface", - .lookup = ta_lookup_iface, - .init = ta_init_iface, - .destroy = ta_destroy_iface, - .prepare_add = ta_prepare_add_iface, - .prepare_del = ta_prepare_del_iface, - .add = ta_add_iface, - .del = ta_del_iface, - .flush_entry = ta_flush_iface_entry, - .foreach = ta_foreach_iface, - .dump_tentry = ta_dump_iface_tentry, - .find_tentry = ta_find_iface_tentry, +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 idx_iface = { + .name = "idx_iface", + .lookup = ta_lookup_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, + .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, }; void @@ -817,7 +1187,7 @@ ipfw_table_algo_init(struct ip_fw_chain *chain) * Register all algorithms presented here. */ ipfw_add_table_algo(chain, &radix_cidr); - ipfw_add_table_algo(chain, &radix_iface); + ipfw_add_table_algo(chain, &idx_iface); } void