diff options
-rw-r--r-- | usr/src/uts/common/inet/ipf/ip_fil_solaris.c | 336 | ||||
-rw-r--r-- | usr/src/uts/common/inet/ipf/netinet/ipf_stack.h | 17 | ||||
-rw-r--r-- | usr/src/uts/common/io/hook.c | 8 | ||||
-rw-r--r-- | usr/src/uts/common/sys/hook_impl.h | 4 | ||||
-rw-r--r-- | usr/src/uts/common/sys/neti.h | 5 | ||||
-rw-r--r-- | usr/src/uts/i86pc/io/viona/viona.c | 581 | ||||
-rw-r--r-- | usr/src/uts/i86pc/viona/Makefile | 3 | ||||
-rw-r--r-- | usr/src/uts/intel/ipf/ipf.global-objs.debug64 | 9 |
8 files changed, 948 insertions, 15 deletions
diff --git a/usr/src/uts/common/inet/ipf/ip_fil_solaris.c b/usr/src/uts/common/inet/ipf/ip_fil_solaris.c index 1d8247e52a..4cb67a2dab 100644 --- a/usr/src/uts/common/inet/ipf/ip_fil_solaris.c +++ b/usr/src/uts/common/inet/ipf/ip_fil_solaris.c @@ -22,6 +22,7 @@ static const char rcsid[] = "@(#)$Id: ip_fil_solaris.c,v 2.62.2.19 2005/07/13 21 #include <sys/filio.h> #include <sys/systm.h> #include <sys/strsubr.h> +#include <sys/strsun.h> #include <sys/cred.h> #include <sys/ddi.h> #include <sys/sunddi.h> @@ -92,9 +93,19 @@ static int ipf_hookvndl3v4_out __P((hook_event_token_t, hook_data_t, void *)); static int ipf_hookvndl3v6_out __P((hook_event_token_t, hook_data_t, void *)); + +static int ipf_hookviona_in __P((hook_event_token_t, hook_data_t, void *)); +static int ipf_hookviona_out __P((hook_event_token_t, hook_data_t, + void *)); + extern int ipf_geniter __P((ipftoken_t *, ipfgeniter_t *, ipf_stack_t *)); extern int ipf_frruleiter __P((void *, int, void *, ipf_stack_t *)); +static int ipf_hook_protocol_notify __P((hook_notify_cmd_t, void *, + const char *, const char *, const char *)); +static int ipf_hook_instance_notify __P((hook_notify_cmd_t, void *, + const char *, const char *, const char *)); + #if SOLARIS2 < 10 #if SOLARIS2 >= 7 u_int *ip_ttl_ptr = NULL; @@ -171,6 +182,12 @@ char *hook4_vnd_out_gz = "ipfilter_hookvndl3v4_out_gz"; char *hook6_vnd_out = "ipfilter_hookvndl3v6_out"; char *hook6_vnd_out_gz = "ipfilter_hookvndl3v6_out_gz"; +/* viona hook names */ +char *hook_viona_in = "ipfilter_hookviona_in"; +char *hook_viona_in_gz = "ipfilter_hookviona_in_gz"; +char *hook_viona_out = "ipfilter_hookviona_out"; +char *hook_viona_out_gz = "ipfilter_hookviona_out_gz"; + /* ------------------------------------------------------------------------ */ /* Function: ipldetach */ /* Returns: int - 0 == success, else error. */ @@ -292,8 +309,40 @@ ipf_stack_t *ifs; ifs->ifs_ipf_vndl3v6 = NULL; } + /* + * Remove notification of viona hooks + */ + net_instance_notify_unregister(ifs->ifs_netid, + ipf_hook_instance_notify); + #undef UNDO_HOOK + /* + * Normally, viona will unregister itself before ipldetach() is called, + * so these will be no-ops, but out of caution, we try to make sure + * we've removed any of our references. + */ + (void) ipf_hook_protocol_notify(HN_UNREGISTER, ifs, Hn_VIONA, NULL, + NH_PHYSICAL_IN); + (void) ipf_hook_protocol_notify(HN_UNREGISTER, ifs, Hn_VIONA, NULL, + NH_PHYSICAL_OUT); + + { + char netidstr[12]; /* Large enough for INT_MAX + NUL */ + (void) snprintf(netidstr, sizeof (netidstr), "%d", + ifs->ifs_netid); + + /* + * The notify callbacks expect the netid value passed as a + * string in the third argument. To prevent confusion if + * traced, we pass the same value the nethook framework would + * pass, even though the callback does not currently use the + * value. + */ + (void) ipf_hook_instance_notify(HN_UNREGISTER, ifs, netidstr, + NULL, Hn_VIONA); + } + #ifdef IPFDEBUG cmn_err(CE_CONT, "ipldetach()\n"); #endif @@ -530,6 +579,22 @@ ipf_stack_t *ifs; NH_PHYSICAL_OUT, ifs->ifs_ipfhookvndl3v6_out) == 0); if (!ifs->ifs_hookvndl3v6_physical_out) goto hookup_failed; + + /* + * VIONA INET hooks. While the nethook framework allows us to register + * hooks for events that haven't been registered yet, we instead + * register and unregister our hooks in response to notifications + * about the viona hooks from the nethook framework. This prevents + * problems when the viona module gets unloaded while the ipf module + * does not. If we do not unregister our hooks after the viona module + * is unloaded, the viona module cannot later re-register them if it + * gets reloaded. As the ip, vnd, and ipf modules are rarely unloaded + * even on DEBUG kernels, they do not experience this issue. + */ + if (net_instance_notify_register(id, ipf_hook_instance_notify, + ifs) != 0) + goto hookup_failed; + /* * Reacquire ipf_global, now it is safe. */ @@ -593,6 +658,155 @@ hookup_failed: return -1; } +/* ------------------------------------------------------------------------ */ +/* + * Called whenever a nethook protocol is registered or unregistered. Currently + * only used to add or remove the hooks for viona. + * + * While the function signature requires returning int, nothing + * in usr/src/uts/common/io/hook.c that invokes the callbacks + * captures the return value (nor is there currently any documentation + * on what return values should be). For now at least, we'll return 0 + * on success (or 'not applicable') or an error value. Even if the + * nethook framework doesn't use the return address, it can be observed via + * dtrace if needed. + */ +static int +ipf_hook_protocol_notify(hook_notify_cmd_t command, void *arg, + const char *name, const char *dummy __unused, const char *he_name) +{ + ipf_stack_t *ifs = arg; + hook_t **hookpp; + char *hook_name, *hint_name; + hook_func_t hookfn; + boolean_t *hookedp; + hook_hint_t hint; + boolean_t out; + int ret = 0; + + const boolean_t gz = ifs->ifs_gz_controlled; + + /* We currently only care about viona hooks notifications */ + if (strcmp(name, Hn_VIONA) != 0) + return (0); + + if (strcmp(he_name, NH_PHYSICAL_IN) == 0) { + out = B_FALSE; + } else if (strcmp(he_name, NH_PHYSICAL_OUT) == 0) { + out = B_TRUE; + } else { + /* + * If we've added more hook events to viona, we must add + * the corresponding handling here (even if it's just to + * ignore it) to prevent the firewall from not working as + * intended. + */ + cmn_err(CE_PANIC, "%s: unhandled hook event %s", __func__, + he_name); + + return (0); + } + + if (out) { + hookpp = &ifs->ifs_ipfhookviona_out; + hookfn = ipf_hookviona_out; + hookedp = &ifs->ifs_hookviona_physical_out; + name = gz ? hook_viona_out_gz : hook_viona_out; + hint = gz ? HH_AFTER : HH_BEFORE; + hint_name = gz ? hook_viona_out : hook_viona_out_gz; + } else { + hookpp = &ifs->ifs_ipfhookviona_in; + hookfn = ipf_hookviona_in; + hookedp = &ifs->ifs_hookviona_physical_in; + name = gz ? hook_viona_in_gz : hook_viona_in; + hint = gz ? HH_BEFORE : HH_AFTER; + hint_name = gz ? hook_viona_in : hook_viona_in_gz; + } + + switch (command) { + default: + case HN_NONE: + break; + case HN_REGISTER: + HOOK_INIT(*hookpp, hookfn, (char *)name, ifs); + (*hookpp)->h_hint = hint; + (*hookpp)->h_hintvalue = (uintptr_t)hint_name; + ret = net_hook_register(ifs->ifs_ipf_viona, + (char *)he_name, *hookpp); + if (ret != 0) { + cmn_err(CE_NOTE, "%s: could not register hook " + "(hook family=%s hook=%s) err=%d", __func__, + name, he_name, ret); + *hookedp = B_FALSE; + return (ret); + } + *hookedp = B_TRUE; + break; + case HN_UNREGISTER: + if (ifs->ifs_ipf_viona == NULL) + break; + + ret = *hookedp ? net_hook_unregister(ifs->ifs_ipf_viona, + (char *)he_name, *hookpp) : 0; + if ((ret == 0 || ret == ENXIO)) { + if (*hookpp != NULL) { + hook_free(*hookpp); + *hookpp = NULL; + } + *hookedp = B_FALSE; + } + break; + } + + return (ret); +} + +/* + * Called whenever a new nethook instance is created. Currently only used + * with the Hn_VIONA nethooks. Similar to ipf_hook_protocol_notify, the out + * function signature must return an int, though the result is never used. + * We elect to return 0 on success (or not applicable) or a non-zero value + * on error. + */ +static int +ipf_hook_instance_notify(hook_notify_cmd_t command, void *arg, + const char *netid, const char *dummy __unused, const char *instance) +{ + ipf_stack_t *ifs = arg; + int ret = 0; + + /* We currently only care about viona hooks */ + if (strcmp(instance, Hn_VIONA) != 0) + return (0); + + switch (command) { + case HN_NONE: + default: + return (0); + case HN_REGISTER: + ifs->ifs_ipf_viona = net_protocol_lookup(ifs->ifs_netid, + NHF_VIONA); + + if (ifs->ifs_ipf_viona == NULL) + return (EPROTONOSUPPORT); + + ret = net_protocol_notify_register(ifs->ifs_ipf_viona, + ipf_hook_protocol_notify, ifs); + VERIFY(ret == 0 || ret == ESHUTDOWN); + break; + case HN_UNREGISTER: + if (ifs->ifs_ipf_viona == NULL) + break; + VERIFY0(net_protocol_notify_unregister(ifs->ifs_ipf_viona, + ipf_hook_protocol_notify)); + VERIFY0(net_protocol_release(ifs->ifs_ipf_viona)); + ifs->ifs_ipf_viona = NULL; + break; + } + + return (ret); +} + static int fr_setipfloopback(set, ifs) int set; ipf_stack_t *ifs; @@ -2167,6 +2381,124 @@ int ipf_hookvndl3v6_out(hook_event_token_t token, hook_data_t info, void *arg) return ipf_hook6_out(token, info, arg); } +/* Static constants used by ipf_hook_ether */ +static uint8_t ipf_eth_bcast_addr[ETHERADDRL] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; +static uint8_t ipf_eth_ipv4_mcast[3] = { 0x01, 0x00, 0x5E }; +static uint8_t ipf_eth_ipv6_mcast[2] = { 0x33, 0x33 }; + +/* ------------------------------------------------------------------------ */ +/* Function: ipf_hook_ether */ +/* Returns: int - 0 == packet ok, else problem, free packet if not done */ +/* Parameters: token(I) - pointer to event */ +/* info(I) - pointer to hook information for firewalling */ +/* */ +/* The ipf_hook_ether hook is currently private to illumos. It represents */ +/* a layer 2 datapath generally used by virtual machines. Currently the */ +/* hook is only used by the viona driver to pass along L2 frames for */ +/* inspection. It requires that the L2 ethernet header is contained within */ +/* a single dblk_t (however layers above the L2 header have no restrctions */ +/* in ipf). ipf does not currently support filtering on L2 fields (e.g. */ +/* filtering on a MAC address or ethertype), however virtual machines do */ +/* not have native IP stack instances where ipf traditionally hooks in. */ +/* Instead this entry point is used to determine if the packet is unicast, */ +/* broadcast, or multicast. The IPv4 or IPv6 packet is then passed to the */ +/* traditional ip hooks for filtering. Non IPv4 or non IPv6 packets are */ +/* not subject to examination. */ +/* ------------------------------------------------------------------------ */ +int ipf_hook_ether(hook_event_token_t token, hook_data_t info, void *arg, + boolean_t out) +{ + struct ether_header *ethp; + hook_pkt_event_t *hpe = (hook_pkt_event_t *)info; + mblk_t *mp; + size_t offset, len; + uint16_t etype; + boolean_t v6; + + /* + * viona will only pass us mblks with the L2 header contained in a + * single data block. + */ + mp = *hpe->hpe_mp; + len = MBLKL(mp); + + VERIFY3S(len, >=, sizeof (struct ether_header)); + + ethp = (struct ether_header *)mp->b_rptr; + if ((etype = ntohs(ethp->ether_type)) == ETHERTYPE_VLAN) { + struct ether_vlan_header *evh = + (struct ether_vlan_header *)ethp; + + VERIFY3S(len, >=, sizeof (struct ether_vlan_header)); + + etype = ntohs(evh->ether_type); + offset = sizeof (*evh); + } else { + offset = sizeof (*ethp); + } + + /* + * ipf only support filtering IPv4 and IPv6. Ignore other types. + */ + if (etype == ETHERTYPE_IP) + v6 = B_FALSE; + else if (etype == ETHERTYPE_IPV6) + v6 = B_TRUE; + else + return (0); + + if (bcmp(ipf_eth_bcast_addr, ethp, ETHERADDRL) == 0) + hpe->hpe_flags |= HPE_BROADCAST; + else if (bcmp(ipf_eth_ipv4_mcast, ethp, + sizeof (ipf_eth_ipv4_mcast)) == 0) + hpe->hpe_flags |= HPE_MULTICAST; + else if (bcmp(ipf_eth_ipv6_mcast, ethp, + sizeof (ipf_eth_ipv6_mcast)) == 0) + hpe->hpe_flags |= HPE_MULTICAST; + + /* Find the start of the IPv4 or IPv6 header */ + for (; offset >= len; len = MBLKL(mp)) { + offset -= len; + mp = mp->b_cont; + if (mp == NULL) { + freemsg(*hpe->hpe_mp); + *hpe->hpe_mp = NULL; + return (-1); + } + } + hpe->hpe_mb = mp; + hpe->hpe_hdr = mp->b_rptr + offset; + + return (v6 ? ipf_hook6(info, out, 0, arg) : + ipf_hook(info, out, 0, arg)); +} + +/* ------------------------------------------------------------------------ */ +/* Function: ipf_hookviona_{in,out} */ +/* Returns: int - 0 == packet ok, else problem, free packet if not done */ +/* Parameters: event(I) - pointer to event */ +/* info(I) - pointer to hook information for firewalling */ +/* */ +/* The viona hooks are private hooks to illumos. They represents a layer 2 */ +/* datapath generally used to implement virtual machines. */ +/* along L2 packets. */ +/* */ +/* They end up calling the appropriate traditional ip hooks. */ +/* ------------------------------------------------------------------------ */ +int +ipf_hookviona_in(hook_event_token_t token, hook_data_t info, void *arg) +{ + return (ipf_hook_ether(token, info, arg, B_FALSE)); +} + +int +ipf_hookviona_out(hook_event_token_t token, hook_data_t info, void *arg) +{ + return (ipf_hook_ether(token, info, arg, B_TRUE)); +} + /* ------------------------------------------------------------------------ */ /* Function: ipf_hook4_loop_in */ /* Returns: int - 0 == packet ok, else problem, free packet if not done */ @@ -2510,7 +2842,7 @@ fr_info_t *fin; #ifdef USE_INET6 struct in6_addr tmp_src6; #endif - + ASSERT(fin->fin_p == IPPROTO_TCP); /* @@ -2552,7 +2884,7 @@ fr_info_t *fin; #endif if (tcp != NULL) { - /* + /* * Adjust TCP header: * swap ports, * set flags, diff --git a/usr/src/uts/common/inet/ipf/netinet/ipf_stack.h b/usr/src/uts/common/inet/ipf/netinet/ipf_stack.h index 9aa2478c6a..5c156e9c44 100644 --- a/usr/src/uts/common/inet/ipf/netinet/ipf_stack.h +++ b/usr/src/uts/common/inet/ipf/netinet/ipf_stack.h @@ -6,7 +6,7 @@ * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * - * Copyright 2014 Joyent, Inc. All rights reserved. + * Copyright 2018 Joyent, Inc. All rights reserved. */ #ifndef __IPF_STACK_H__ @@ -87,8 +87,8 @@ struct ipf_stack { #endif int ifs_ipf_locks_done; - ipftoken_t *ifs_ipftokenhead; - ipftoken_t **ifs_ipftokentail; + ipftoken_t *ifs_ipftokenhead; + ipftoken_t **ifs_ipftokentail; ipfmutex_t ifs_ipl_mutex; ipfmutex_t ifs_ipf_authmx; @@ -125,11 +125,15 @@ struct ipf_stack { hook_t *ifs_ipfhook6_loop_in; hook_t *ifs_ipfhook6_loop_out; hook_t *ifs_ipfhook6_nicevents; + hook_t *ifs_ipfhookvndl3v4_in; hook_t *ifs_ipfhookvndl3v6_in; hook_t *ifs_ipfhookvndl3v4_out; hook_t *ifs_ipfhookvndl3v6_out; + hook_t *ifs_ipfhookviona_in; + hook_t *ifs_ipfhookviona_out; + /* flags to indicate whether hooks are registered. */ boolean_t ifs_hook4_physical_in; boolean_t ifs_hook4_physical_out; @@ -145,12 +149,15 @@ struct ipf_stack { boolean_t ifs_hookvndl3v6_physical_in; boolean_t ifs_hookvndl3v4_physical_out; boolean_t ifs_hookvndl3v6_physical_out; + boolean_t ifs_hookviona_physical_in; + boolean_t ifs_hookviona_physical_out; int ifs_ipf_loopback; net_handle_t ifs_ipf_ipv4; net_handle_t ifs_ipf_ipv6; net_handle_t ifs_ipf_vndl3v4; net_handle_t ifs_ipf_vndl3v6; + net_handle_t ifs_ipf_viona; /* ip_auth.c */ int ifs_fr_authsize; @@ -177,8 +184,8 @@ struct ipf_stack { ipfr_t **ifs_ipfr_nattail; ipfr_t **ifs_ipfr_nattab; - ipfr_t *ifs_ipfr_ipidlist; - ipfr_t **ifs_ipfr_ipidtail; + ipfr_t *ifs_ipfr_ipidlist; + ipfr_t **ifs_ipfr_ipidtail; ipfr_t **ifs_ipfr_ipidtab; ipfrstat_t ifs_ipfr_stats; diff --git a/usr/src/uts/common/io/hook.c b/usr/src/uts/common/io/hook.c index 2edbe752ce..b32da5a369 100644 --- a/usr/src/uts/common/io/hook.c +++ b/usr/src/uts/common/io/hook.c @@ -22,7 +22,7 @@ * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * - * Copyright 2013 Joyent, Inc. All rights reserved. + * Copyright 2018 Joyent, Inc. All rights reserved. * Copyright (c) 2016 by Delphix. All rights reserved. */ #include <sys/param.h> @@ -1035,7 +1035,7 @@ hook_family_free(hook_family_int_t *hfi, hook_stack_t *hks) /* Free container */ kmem_free(hfi, sizeof (*hfi)); - if (hks->hks_shutdown == 2) + if (hks != NULL && hks->hks_shutdown == 2) hook_stack_remove(hks); mutex_exit(&hook_stack_lock); @@ -1126,7 +1126,7 @@ hook_family_copy(hook_family_t *src) * Parameters: family(I) - family name string * * Search family list with family name - * A lock on hfi_lock must be held when called. + * A lock on hfi_lock must be held when called. */ static hook_family_int_t * hook_family_find(char *family, hook_stack_t *hks) @@ -1651,7 +1651,7 @@ hook_event_copy(hook_event_t *src) * event(I) - event name string * * Search event list with event name - * A lock on hfi->hfi_lock must be held when called. + * A lock on hfi->hfi_lock must be held when called. */ static hook_event_int_t * hook_event_find(hook_family_int_t *hfi, char *event) diff --git a/usr/src/uts/common/sys/hook_impl.h b/usr/src/uts/common/sys/hook_impl.h index d8a15f0fe5..f3337bbacf 100644 --- a/usr/src/uts/common/sys/hook_impl.h +++ b/usr/src/uts/common/sys/hook_impl.h @@ -21,6 +21,7 @@ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. + * Copyright 2018, Joyent, Inc. */ /* @@ -171,7 +172,7 @@ typedef struct hook_family_int { cvwaitlock_t hfi_lock; SLIST_ENTRY(hook_family_int) hfi_entry; hook_event_int_head_t hfi_head; - hook_family_t hfi_family; + hook_family_t hfi_family; kstat_t *hfi_kstat; struct hook_stack *hfi_stack; hook_notify_head_t hfi_nhead; @@ -209,6 +210,7 @@ typedef struct hook_stack_head hook_stack_head_t; #define Hn_ARP "arp" #define Hn_IPV4 "inet" #define Hn_IPV6 "inet6" +#define Hn_VIONA "viona_inet" extern int hook_run(hook_family_int_t *, hook_event_token_t, hook_data_t); extern int hook_register(hook_family_int_t *, char *, hook_t *); diff --git a/usr/src/uts/common/sys/neti.h b/usr/src/uts/common/sys/neti.h index fe7746fc81..92bd5b897d 100644 --- a/usr/src/uts/common/sys/neti.h +++ b/usr/src/uts/common/sys/neti.h @@ -21,6 +21,8 @@ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. + * + * Copyright 2018, Joyent, Inc. */ #ifndef _SYS_NETI_H @@ -48,6 +50,7 @@ struct msgb; /* avoiding sys/stream.h here */ #define NHF_ARP "NHF_ARP" #define NHF_VND_INET "NHF_VND_INET" #define NHF_VND_INET6 "NHF_VND_INET6" +#define NHF_VIONA "NHF_VIONA" /* * Event identification @@ -63,7 +66,7 @@ struct msgb; /* avoiding sys/stream.h here */ /* * Network NIC hardware checksum capability */ -#define NET_HCK_NONE 0x00 +#define NET_HCK_NONE 0x00 #define NET_HCK_L3_FULL 0x01 #define NET_HCK_L3_PART 0x02 #define NET_HCK_L4_FULL 0x10 diff --git a/usr/src/uts/i86pc/io/viona/viona.c b/usr/src/uts/i86pc/io/viona/viona.c index 31eac09580..edcda0058c 100644 --- a/usr/src/uts/i86pc/io/viona/viona.c +++ b/usr/src/uts/i86pc/io/viona/viona.c @@ -207,6 +207,23 @@ * slow path for interrupts. It will poll(2) the viona handle, receiving * notification when ring events necessitate the assertion of an interrupt. * + * + * --------------- + * Nethook Support + * --------------- + * + * Viona provides four nethook events that consumers (e.g. ipf) can hook into + * to intercept packets as they go up or down the stack. Unfortunately, + * the nethook framework does not understand raw packets, so we can only + * generate events (in, out) for IPv4 and IPv6 packets. At driver attach, + * we register callbacks with the neti (netinfo) module that will be invoked + * for each netstack already present, as well as for any additional netstack + * instances created as the system operates. These callbacks will + * register/unregister the hooks with the nethook framework for each + * netstack instance. This registration occurs prior to creating any + * viona instances for a given netstack, and the unregistration for a netstack + * instance occurs after all viona instances of the netstack instance have + * been deleted. */ #include <sys/conf.h> @@ -225,9 +242,13 @@ #include <sys/pattr.h> #include <sys/dls.h> #include <sys/dlpi.h> +#include <sys/hook.h> +#include <sys/hook_event.h> +#include <sys/list.h> #include <sys/mac_client.h> #include <sys/mac_provider.h> #include <sys/mac_client_priv.h> +#include <sys/neti.h> #include <sys/vlan.h> #include <inet/ip.h> #include <inet/ip_impl.h> @@ -348,6 +369,8 @@ struct viona_link; typedef struct viona_link viona_link_t; struct viona_desb; typedef struct viona_desb viona_desb_t; +struct viona_net; +typedef struct viona_neti viona_neti_t; enum viona_ring_state { VRS_RESET = 0x0, /* just allocated or reset */ @@ -364,6 +387,11 @@ enum viona_ring_state_flags { (((ring)->vr_state_flags & VRSF_REQ_STOP) != 0 || \ ((proc)->p_flag & SEXITING) != 0) +#define VNETHOOK_INTERESTED_IN(neti) \ + (neti)->vni_nethook.vnh_event_in.he_interested +#define VNETHOOK_INTERESTED_OUT(neti) \ + (neti)->vni_nethook.vnh_event_out.he_interested + typedef struct viona_vring { viona_link_t *vr_link; @@ -422,6 +450,9 @@ typedef struct viona_vring { uint64_t rs_rx_pad_short; uint64_t rs_too_short; uint64_t rs_tx_absent; + + uint64_t rs_rx_hookdrop; + uint64_t rs_tx_hookdrop; } vr_stats; } viona_vring_t; @@ -443,6 +474,32 @@ struct viona_link { mac_client_handle_t l_mch; pollhead_t l_pollhead; + + viona_neti_t *l_neti; +}; + +typedef struct viona_nethook { + net_handle_t vnh_neti; + hook_family_t vnh_family; + hook_event_t vnh_event_in; + hook_event_t vnh_event_out; + hook_event_token_t vnh_token_in; + hook_event_token_t vnh_token_out; + boolean_t vnh_hooked; +} viona_nethook_t; + +struct viona_neti { + list_node_t vni_node; + + netid_t vni_netid; + zoneid_t vni_zid; + + viona_nethook_t vni_nethook; + + kmutex_t vni_lock; /* Protects remaining members */ + kcondvar_t vni_ref_change; /* Protected by vni_lock */ + uint_t vni_ref; /* Protected by vni_lock */ + list_t vni_dev_list; /* Protected by vni_lock */ }; struct viona_desb { @@ -457,6 +514,7 @@ struct viona_desb { typedef struct viona_soft_state { kmutex_t ss_lock; viona_link_t *ss_link; + list_node_t ss_node; } viona_soft_state_t; typedef struct used_elem { @@ -470,6 +528,18 @@ static id_space_t *viona_minors; static mblk_t *viona_vlan_pad_mp; /* + * Global linked list of viona_neti_ts. Access is protected by viona_neti_lock + */ +static kmutex_t viona_neti_lock; +static list_t viona_neti_list; + +/* + * viona_neti is allocated and initialized during attach, and read-only + * until detach (where it's also freed) + */ +static net_instance_t *viona_neti; + +/* * copy tx mbufs from virtio ring to avoid necessitating a wait for packet * transmission to free resources. */ @@ -510,6 +580,15 @@ static void viona_desb_release(viona_desb_t *); static void viona_rx(void *, mac_resource_handle_t, mblk_t *, boolean_t); static void viona_tx(viona_link_t *, viona_vring_t *); +static viona_neti_t *viona_neti_lookup_by_zid(zoneid_t); +static void viona_neti_rele(viona_neti_t *); + +static void *viona_neti_create(const netid_t); +static void viona_neti_shutdown(const netid_t, void *); +static void viona_neti_destroy(const netid_t, void *); + +static int viona_hook(viona_link_t *, viona_vring_t *, mblk_t **, boolean_t); + static struct cb_ops viona_cb_ops = { viona_open, viona_close, @@ -658,6 +737,21 @@ viona_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) viona_dip = dip; ddi_report_dev(viona_dip); + mutex_init(&viona_neti_lock, NULL, MUTEX_DRIVER, NULL); + list_create(&viona_neti_list, sizeof (viona_neti_t), + offsetof(viona_neti_t, vni_node)); + + /* This can only fail if NETINFO_VERSION is wrong */ + viona_neti = net_instance_alloc(NETINFO_VERSION); + VERIFY(viona_neti != NULL); + + viona_neti->nin_name = "viona"; + viona_neti->nin_create = viona_neti_create; + viona_neti->nin_shutdown = viona_neti_shutdown; + viona_neti->nin_destroy = viona_neti_destroy; + /* This can only fail if we've registered ourselves multiple times */ + VERIFY3S(net_instance_register(viona_neti), ==, DDI_SUCCESS); + return (DDI_SUCCESS); } @@ -680,6 +774,14 @@ viona_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) ddi_remove_minor_node(viona_dip, NULL); viona_dip = NULL; + /* This can only fail if we've not registered previously */ + VERIFY3S(net_instance_unregister(viona_neti), ==, DDI_SUCCESS); + net_instance_free(viona_neti); + viona_neti = NULL; + + list_destroy(&viona_neti_list); + mutex_destroy(&viona_neti_lock); + return (DDI_SUCCESS); } @@ -740,6 +842,7 @@ viona_close(dev_t dev, int flag, int otype, cred_t *credp) } VERIFY0(viona_ioc_delete(ss, B_TRUE)); + VERIFY(!list_link_active(&ss->ss_node)); ddi_soft_state_free(viona_state, minor); id_free(viona_minors, minor); @@ -883,6 +986,8 @@ viona_ioc_create(viona_soft_state_t *ss, void *dptr, int md, cred_t *cr) int err = 0; file_t *fp; vmm_hold_t *hold = NULL; + viona_neti_t *nip = NULL; + zoneid_t zid; ASSERT(MUTEX_NOT_HELD(&ss->ss_lock)); @@ -890,9 +995,21 @@ viona_ioc_create(viona_soft_state_t *ss, void *dptr, int md, cred_t *cr) return (EFAULT); } + zid = crgetzoneid(cr); + nip = viona_neti_lookup_by_zid(zid); + if (nip == NULL) { + return (EIO); + } + + if (!nip->vni_nethook.vnh_hooked) { + viona_neti_rele(nip); + return (EIO); + } + mutex_enter(&ss->ss_lock); if (ss->ss_link != NULL) { mutex_exit(&ss->ss_lock); + viona_neti_rele(nip); return (EEXIST); } @@ -924,11 +1041,18 @@ viona_ioc_create(viona_soft_state_t *ss, void *dptr, int md, cred_t *cr) goto bail; } + link->l_neti = nip; + viona_ring_alloc(link, &link->l_vrings[VIONA_VQ_RX]); viona_ring_alloc(link, &link->l_vrings[VIONA_VQ_TX]); ss->ss_link = link; mutex_exit(&ss->ss_lock); + + mutex_enter(&nip->vni_lock); + list_insert_tail(&nip->vni_dev_list, ss); + mutex_exit(&nip->vni_lock); + return (0); bail: @@ -944,6 +1068,7 @@ bail: if (hold != NULL) { vmm_drv_rele(hold); } + viona_neti_rele(nip); mutex_exit(&ss->ss_lock); return (err); @@ -953,6 +1078,7 @@ static int viona_ioc_delete(viona_soft_state_t *ss, boolean_t on_close) { viona_link_t *link; + viona_neti_t *nip = NULL; mutex_enter(&ss->ss_lock); if ((link = ss->ss_link) == NULL) { @@ -1003,12 +1129,21 @@ viona_ioc_delete(viona_soft_state_t *ss, boolean_t on_close) link->l_vm_hold = NULL; } + nip = link->l_neti; + link->l_neti = NULL; + viona_ring_free(&link->l_vrings[VIONA_VQ_RX]); viona_ring_free(&link->l_vrings[VIONA_VQ_TX]); pollhead_clean(&link->l_pollhead); ss->ss_link = NULL; mutex_exit(&ss->ss_lock); + mutex_enter(&nip->vni_lock); + list_remove(&nip->vni_dev_list, ss); + mutex_exit(&nip->vni_lock); + + viona_neti_rele(nip); + kmem_free(link, sizeof (viona_link_t)); return (0); } @@ -2098,6 +2233,30 @@ viona_rx(void *arg, mac_resource_handle_t mrh, mblk_t *mp, boolean_t loopback) size = msgsize(mp); /* + * We treat both a 'drop' response and errors the same here + * and put the packet on the drop chain. As packets may be + * subject to different actions in ipf (which do not all + * return the same set of error values), an error processing + * one packet doesn't mean the next packet will also generate + * an error. + */ + if (VNETHOOK_INTERESTED_IN(link->l_neti) && + viona_hook(link, ring, &mp, B_FALSE) != 0) { + if (mp != NULL) { + *mpdrop_prevp = mp; + mpdrop_prevp = &mp->b_next; + } else { + /* + * If the hook consumer (e.g. ipf) already + * freed the mblk_t, update the drop count now. + */ + ndrop++; + } + mp = next; + continue; + } + + /* * Ethernet frames are expected to be padded out in order to * meet the minimum size. * @@ -2155,6 +2314,14 @@ viona_rx(void *arg, mac_resource_handle_t mrh, mblk_t *mp, boolean_t loopback) } pad_drop: + /* + * While an error during rx processing + * (viona_recv_{merged,plain}) does not free mp on error, + * hook processing might or might not free mp. Handle either + * scenario -- if mp is not yet free, it is queued up and + * freed after the guest has been notified. If mp is + * already NULL, just proceed on. + */ if (err != 0) { *mpdrop_prevp = mp; mpdrop_prevp = &mp->b_next; @@ -2241,6 +2408,10 @@ viona_desb_release(viona_desb_t *dp) mutex_exit(&ring->vr_lock); } +/* + * Get the 8-bit value residing at the given offset and write it into *off. + * Returns 0 on success, 1 if offset is out of range. + */ static int viona_mb_get_uint8(mblk_t *mp, off_t off, uint8_t *out) { @@ -2482,6 +2653,38 @@ viona_tx(viona_link_t *link, viona_vring_t *ring) mp_tail = mp; } + if (VNETHOOK_INTERESTED_OUT(link->l_neti)) { + /* + * The hook consumer may elect to free the mblk_t and set + * our mblk_t ** to NULL. When using a viona_desb_t + * (dp != NULL), we do not want the corresponding cleanup to + * occur during the viona_hook() call. We instead want to + * reset and recycle dp for future use. To prevent cleanup + * during the viona_hook() call, we take a ref on dp (if being + * used), and release it on success. On failure, the + * freemsgchain() call will release all the refs taken earlier + * in viona_tx() (aside from the initial ref and the one we + * take), and drop_hook will reset dp for reuse. + */ + if (dp != NULL) + dp->d_ref++; + + /* + * Pass &mp instead of &mp_head so we don't lose track of + * mp_head if the hook consumer (i.e. ipf) elects to free mp + * and set mp to NULL. + */ + mp = mp_head; + if (viona_hook(link, ring, &mp, B_TRUE) != 0) { + if (mp != NULL) + freemsgchain(mp); + goto drop_hook; + } + + if (dp != NULL) + dp->d_ref--; + } + /* Request hardware checksumming, if necessary */ if ((link->l_features & VIRTIO_NET_F_CSUM) != 0 && (hdr->vrh_flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) != 0) { @@ -2535,6 +2738,8 @@ drop_fail: * dropped data to be released to the used ring. */ freemsgchain(mp_head); + +drop_hook: len = 0; for (uint_t i = 0; i < n; i++) { len += iov[i].iov_len; @@ -2553,3 +2758,379 @@ drop_fail: uint16_t, cookie); viona_tx_done(ring, len, cookie); } + +/* + * Generate a hook event for the packet in *mpp headed in the direction + * indicated by 'out'. If the packet is accepted, 0 is returned. If the + * packet is rejected, an error is returned. The hook function may or may not + * alter or even free *mpp. The caller is expected to deal with either + * situation. + */ +static int +viona_hook(viona_link_t *link, viona_vring_t *ring, mblk_t **mpp, boolean_t out) +{ + viona_neti_t *nip = link->l_neti; + viona_nethook_t *vnh = &nip->vni_nethook; + hook_pkt_event_t info; + hook_event_t he; + hook_event_token_t het; + int ret; + + he = out ? vnh->vnh_event_out : vnh->vnh_event_in; + het = out ? vnh->vnh_token_out : vnh->vnh_token_in; + + if (!he.he_interested) + return (0); + + info.hpe_protocol = vnh->vnh_neti; + info.hpe_ifp = (phy_if_t)link; + info.hpe_ofp = (phy_if_t)link; + info.hpe_mp = mpp; + info.hpe_flags = 0; + + ret = hook_run(vnh->vnh_neti->netd_hooks, het, (hook_data_t)&info); + if (ret == 0) + return (0); + + if (out) { + VIONA_PROBE3(tx_hook_drop, viona_vring_t *, ring, + mblk_t *, *mpp, int, ret); + VIONA_RING_STAT_INCR(ring, tx_hookdrop); + } else { + VIONA_PROBE3(rx_hook_drop, viona_vring_t *, ring, + mblk_t *, *mpp, int, ret); + VIONA_RING_STAT_INCR(ring, rx_hookdrop); + } + return (ret); +} + +/* + * netinfo stubs - required by the nethook framework, but otherwise unused + * + * Currently, all ipf rules are applied against all interfaces in a given + * netstack (e.g. all interfaces in a zone). In the future if we want to + * support being able to apply different rules to different interfaces, I + * believe we would need to implement some of these stubs to map an interface + * name in a rule (e.g. 'net0', back to an index or viona_link_t); + */ +static int +viona_neti_getifname(net_handle_t neti __unused, phy_if_t phy __unused, + char *buf __unused, const size_t len __unused) +{ + return (-1); +} + +static int +viona_neti_getmtu(net_handle_t neti __unused, phy_if_t phy __unused, + lif_if_t ifdata __unused) +{ + return (-1); +} + +static int +viona_neti_getptmue(net_handle_t neti __unused) +{ + return (-1); +} + +static int +viona_neti_getlifaddr(net_handle_t neti __unused, phy_if_t phy __unused, + lif_if_t ifdata __unused, size_t nelem __unused, + net_ifaddr_t type[] __unused, void *storage __unused) +{ + return (-1); +} + +static int +viona_neti_getlifzone(net_handle_t neti __unused, phy_if_t phy __unused, + lif_if_t ifdata __unused, zoneid_t *zid __unused) +{ + return (-1); +} + +static int +viona_neti_getlifflags(net_handle_t neti __unused, phy_if_t phy __unused, + lif_if_t ifdata __unused, uint64_t *flags __unused) +{ + return (-1); +} + +static phy_if_t +viona_neti_phygetnext(net_handle_t neti __unused, phy_if_t phy __unused) +{ + return ((phy_if_t)-1); +} + +static phy_if_t +viona_neti_phylookup(net_handle_t neti __unused, const char *name __unused) +{ + return ((phy_if_t)-1); +} + +static lif_if_t +viona_neti_lifgetnext(net_handle_t neti __unused, phy_if_t phy __unused, + lif_if_t ifdata __unused) +{ + return (-1); +} + +static int +viona_neti_inject(net_handle_t neti __unused, inject_t style __unused, + net_inject_t *packet __unused) +{ + return (-1); +} + +static phy_if_t +viona_neti_route(net_handle_t neti __unused, struct sockaddr *address __unused, + struct sockaddr *next __unused) +{ + return ((phy_if_t)-1); +} + +static int +viona_neti_ispchksum(net_handle_t neti __unused, mblk_t *mp __unused) +{ + return (-1); +} + +static int +viona_neti_isvchksum(net_handle_t neti __unused, mblk_t *mp __unused) +{ + return (-1); +} + +static net_protocol_t viona_netinfo = { + NETINFO_VERSION, + NHF_VIONA, + viona_neti_getifname, + viona_neti_getmtu, + viona_neti_getptmue, + viona_neti_getlifaddr, + viona_neti_getlifzone, + viona_neti_getlifflags, + viona_neti_phygetnext, + viona_neti_phylookup, + viona_neti_lifgetnext, + viona_neti_inject, + viona_neti_route, + viona_neti_ispchksum, + viona_neti_isvchksum +}; + +/* + * Create/register our nethooks + */ +static int +viona_nethook_init(netid_t nid, viona_nethook_t *vnh, char *nh_name, + net_protocol_t *netip) +{ + int ret; + + if ((vnh->vnh_neti = net_protocol_register(nid, netip)) == NULL) { + cmn_err(CE_NOTE, "%s: net_protocol_register failed " + "(netid=%d name=%s)", __func__, nid, nh_name); + goto fail_init_proto; + } + + HOOK_FAMILY_INIT(&vnh->vnh_family, nh_name); + if ((ret = net_family_register(vnh->vnh_neti, &vnh->vnh_family)) != 0) { + cmn_err(CE_NOTE, "%s: net_family_register failed " + "(netid=%d name=%s err=%d)", __func__, + nid, nh_name, ret); + goto fail_init_family; + } + + HOOK_EVENT_INIT(&vnh->vnh_event_in, NH_PHYSICAL_IN); + if ((vnh->vnh_token_in = net_event_register(vnh->vnh_neti, + &vnh->vnh_event_in)) == NULL) { + cmn_err(CE_NOTE, "%s: net_event_register %s failed " + "(netid=%d name=%s)", __func__, NH_PHYSICAL_IN, nid, + nh_name); + goto fail_init_event_in; + } + + HOOK_EVENT_INIT(&vnh->vnh_event_out, NH_PHYSICAL_OUT); + if ((vnh->vnh_token_out = net_event_register(vnh->vnh_neti, + &vnh->vnh_event_out)) == NULL) { + cmn_err(CE_NOTE, "%s: net_event_register %s failed " + "(netid=%d name=%s)", __func__, NH_PHYSICAL_OUT, nid, + nh_name); + goto fail_init_event_out; + } + return (0); + + /* + * On failure, we undo all the steps that succeeded in the + * reverse order of initialization, starting at the last + * successful step (the labels denoting the failing step). + */ +fail_init_event_out: + VERIFY0(net_event_shutdown(vnh->vnh_neti, &vnh->vnh_event_in)); + VERIFY0(net_event_unregister(vnh->vnh_neti, &vnh->vnh_event_in)); + vnh->vnh_token_in = NULL; + +fail_init_event_in: + VERIFY0(net_family_shutdown(vnh->vnh_neti, &vnh->vnh_family)); + VERIFY0(net_family_unregister(vnh->vnh_neti, &vnh->vnh_family)); + +fail_init_family: + VERIFY0(net_protocol_unregister(vnh->vnh_neti)); + vnh->vnh_neti = NULL; + +fail_init_proto: + return (1); +} + +/* + * Shutdown the nethooks for a protocol family. This triggers notification + * callbacks to anything that has registered interest to allow hook consumers + * to unhook prior to the removal of the hooks as well as makes them unavailable + * to any future consumers as the first step of removal. + */ +static void +viona_nethook_shutdown(viona_nethook_t *vnh) +{ + VERIFY0(net_event_shutdown(vnh->vnh_neti, &vnh->vnh_event_out)); + VERIFY0(net_event_shutdown(vnh->vnh_neti, &vnh->vnh_event_in)); + VERIFY0(net_family_shutdown(vnh->vnh_neti, &vnh->vnh_family)); +} + +/* + * Remove the nethooks for a protocol family. + */ +static void +viona_nethook_fini(viona_nethook_t *vnh) +{ + VERIFY0(net_event_unregister(vnh->vnh_neti, &vnh->vnh_event_out)); + VERIFY0(net_event_unregister(vnh->vnh_neti, &vnh->vnh_event_in)); + VERIFY0(net_family_unregister(vnh->vnh_neti, &vnh->vnh_family)); + VERIFY0(net_protocol_unregister(vnh->vnh_neti)); + vnh->vnh_neti = NULL; +} + +/* + * Callback invoked by the neti module. This creates/registers our hooks + * {IPv4,IPv6}{in,out} with the nethook framework so they are available to + * interested consumers (e.g. ipf). + * + * During attach, viona_neti_create is called once for every netstack + * present on the system at the time of attach. Thereafter, it is called + * during the creation of additional netstack instances (i.e. zone boot). As a + * result, the viona_neti_t that is created during this call always occurs + * prior to any viona instances that will use it to send hook events. + * + * It should never return NULL. If we cannot register our hooks, we do not + * set vnh_hooked of the respective protocol family, which will prevent the + * creation of any viona instances on this netstack (see viona_ioc_create). + * This can only occur if after a shutdown event (which means destruction is + * imminent) we are trying to create a new instance. + */ +static void * +viona_neti_create(const netid_t netid) +{ + viona_neti_t *nip; + + VERIFY(netid != -1); + + nip = kmem_zalloc(sizeof (*nip), KM_SLEEP); + nip->vni_netid = netid; + nip->vni_zid = net_getzoneidbynetid(netid); + mutex_init(&nip->vni_lock, NULL, MUTEX_DRIVER, NULL); + list_create(&nip->vni_dev_list, sizeof (viona_soft_state_t), + offsetof(viona_soft_state_t, ss_node)); + + if (viona_nethook_init(netid, &nip->vni_nethook, Hn_VIONA, + &viona_netinfo) == 0) + nip->vni_nethook.vnh_hooked = B_TRUE; + + mutex_enter(&viona_neti_lock); + list_insert_tail(&viona_neti_list, nip); + mutex_exit(&viona_neti_lock); + + return (nip); +} + +/* + * Called during netstack teardown by the neti module. During teardown, all + * the shutdown callbacks are invoked, allowing consumers to release any holds + * and otherwise quiesce themselves prior to destruction, followed by the + * actual destruction callbacks. + */ +static void +viona_neti_shutdown(netid_t nid, void *arg) +{ + viona_neti_t *nip = arg; + + ASSERT(nip != NULL); + VERIFY(nid == nip->vni_netid); + + mutex_enter(&viona_neti_lock); + list_remove(&viona_neti_list, nip); + mutex_exit(&viona_neti_lock); + + if (nip->vni_nethook.vnh_hooked) + viona_nethook_shutdown(&nip->vni_nethook); +} + +/* + * Called during netstack teardown by the neti module. Destroys the viona + * netinst data. This is invoked after all the netstack and neti shutdown + * callbacks have been invoked. + */ +static void +viona_neti_destroy(netid_t nid, void *arg) +{ + viona_neti_t *nip = arg; + + ASSERT(nip != NULL); + VERIFY(nid == nip->vni_netid); + + mutex_enter(&nip->vni_lock); + while (nip->vni_ref != 0) + cv_wait(&nip->vni_ref_change, &nip->vni_lock); + mutex_exit(&nip->vni_lock); + + VERIFY(!list_link_active(&nip->vni_node)); + + if (nip->vni_nethook.vnh_hooked) + viona_nethook_fini(&nip->vni_nethook); + + mutex_destroy(&nip->vni_lock); + list_destroy(&nip->vni_dev_list); + kmem_free(nip, sizeof (*nip)); +} + +/* + * Find the viona netinst data by zone id. This is only used during + * viona instance creation (and thus is only called by a zone that is running). + */ +static viona_neti_t * +viona_neti_lookup_by_zid(zoneid_t zid) +{ + viona_neti_t *nip; + + mutex_enter(&viona_neti_lock); + for (nip = list_head(&viona_neti_list); nip != NULL; + nip = list_next(&viona_neti_list, nip)) { + if (nip->vni_zid == zid) { + mutex_enter(&nip->vni_lock); + nip->vni_ref++; + mutex_exit(&nip->vni_lock); + mutex_exit(&viona_neti_lock); + return (nip); + } + } + mutex_exit(&viona_neti_lock); + return (NULL); +} + +static void +viona_neti_rele(viona_neti_t *nip) +{ + mutex_enter(&nip->vni_lock); + VERIFY3S(nip->vni_ref, >, 0); + nip->vni_ref--; + mutex_exit(&nip->vni_lock); + cv_broadcast(&nip->vni_ref_change); +} diff --git a/usr/src/uts/i86pc/viona/Makefile b/usr/src/uts/i86pc/viona/Makefile index cbf0be9539..a1ebcd4f8b 100644 --- a/usr/src/uts/i86pc/viona/Makefile +++ b/usr/src/uts/i86pc/viona/Makefile @@ -53,7 +53,8 @@ ALL_BUILDS = $(ALL_BUILDSONLY64) DEF_BUILDS = $(DEF_BUILDSONLY64) CFLAGS += $(CCVERBOSE) -LDFLAGS += -dy -Ndrv/dld -Nmisc/mac -Nmisc/dls -Ndrv/vmm +LDFLAGS += -dy -Ndrv/dld -Nmisc/mac -Nmisc/dls -Ndrv/vmm -Nmisc/neti +LDFLAGS += -Nmisc/hook # # Default build targets. diff --git a/usr/src/uts/intel/ipf/ipf.global-objs.debug64 b/usr/src/uts/intel/ipf/ipf.global-objs.debug64 index 0af6da41fa..5ebc7eed2b 100644 --- a/usr/src/uts/intel/ipf/ipf.global-objs.debug64 +++ b/usr/src/uts/intel/ipf/ipf.global-objs.debug64 @@ -22,13 +22,17 @@ # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# Copyright 2013 Joyent, Inc. All rights reserved +# Copyright 2018 Joyent, Inc. All rights reserved # fr_availfuncs fr_features fr_objbytes hdrsizes +hook_viona_in +hook_viona_in_gz +hook_viona_out +hook_viona_out_gz hook4_in hook4_in_gz hook4_loop_in @@ -66,6 +70,9 @@ ip6exthdr ipf_cb_ops ipf_dev_info ipf_devfiles +ipf_eth_bcast_addr +ipf_eth_ipv4_mcast +ipf_eth_ipv6_mcast ipf_kstat_tmp ipf_minor ipf_ops |