Index: head/sys/netpfil/ipfw/ip_fw_dynamic.c =================================================================== --- head/sys/netpfil/ipfw/ip_fw_dynamic.c (revision 341470) +++ head/sys/netpfil/ipfw/ip_fw_dynamic.c (revision 341471) @@ -1,3093 +1,3248 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2017-2018 Yandex LLC * Copyright (c) 2017-2018 Andrey V. Elsukov * Copyright (c) 2002 Luigi Rizzo, Universita` di Pisa * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_inet.h" #include "opt_inet6.h" #include "opt_ipfw.h" #ifndef INET #error IPFIREWALL requires INET. #endif /* INET */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* IN6_ARE_ADDR_EQUAL */ #ifdef INET6 #include #include #include #endif #include #include /* XXX for in_cksum */ #ifdef MAC #include #endif /* * Description of dynamic states. * * Dynamic states are stored in lists accessed through a hash tables * whose size is curr_dyn_buckets. This value can be modified through * the sysctl variable dyn_buckets. * * Currently there are four tables: dyn_ipv4, dyn_ipv6, dyn_ipv4_parent, * and dyn_ipv6_parent. * * When a packet is received, its address fields hashed, then matched * against the entries in the corresponding list by addr_type. * Dynamic states can be used for different purposes: * + stateful rules; * + enforcing limits on the number of sessions; * + in-kernel NAT (not implemented yet) * * The lifetime of dynamic states is regulated by dyn_*_lifetime, * measured in seconds and depending on the flags. * * The total number of dynamic states is equal to UMA zone items count. * The max number of dynamic states is dyn_max. When we reach * the maximum number of rules we do not create anymore. This is * done to avoid consuming too much memory, but also too much * time when searching on each packet (ideally, we should try instead * to put a limit on the length of the list on each bucket...). * * Each state holds a pointer to the parent ipfw rule so we know what * action to perform. Dynamic rules are removed when the parent rule is * deleted. * * There are some limitations with dynamic rules -- we do not * obey the 'randomized match', and we do not do multiple * passes through the firewall. XXX check the latter!!! */ /* By default use jenkins hash function */ #define IPFIREWALL_JENKINSHASH #define DYN_COUNTER_INC(d, dir, pktlen) do { \ (d)->pcnt_ ## dir++; \ (d)->bcnt_ ## dir += pktlen; \ } while (0) +#define DYN_REFERENCED 0x01 +/* + * DYN_REFERENCED flag is used to show that state keeps reference to named + * object, and this reference should be released when state becomes expired. + */ + struct dyn_data { void *parent; /* pointer to parent rule */ uint32_t chain_id; /* cached ruleset id */ uint32_t f_pos; /* cached rule index */ uint32_t hashval; /* hash value used for hash resize */ uint16_t fibnum; /* fib used to send keepalives */ - uint8_t _pad[3]; + uint8_t _pad[2]; + uint8_t flags; /* internal flags */ uint8_t set; /* parent rule set number */ uint16_t rulenum; /* parent rule number */ uint32_t ruleid; /* parent rule id */ uint32_t state; /* TCP session state and flags */ uint32_t ack_fwd; /* most recent ACKs in forward */ uint32_t ack_rev; /* and reverse direction (used */ /* to generate keepalives) */ uint32_t sync; /* synchronization time */ uint32_t expire; /* expire time */ uint64_t pcnt_fwd; /* bytes counter in forward */ uint64_t bcnt_fwd; /* packets counter in forward */ uint64_t pcnt_rev; /* bytes counter in reverse */ uint64_t bcnt_rev; /* packets counter in reverse */ }; #define DPARENT_COUNT_DEC(p) do { \ MPASS(p->count > 0); \ ck_pr_dec_32(&(p)->count); \ } while (0) #define DPARENT_COUNT_INC(p) ck_pr_inc_32(&(p)->count) #define DPARENT_COUNT(p) ck_pr_load_32(&(p)->count) struct dyn_parent { void *parent; /* pointer to parent rule */ uint32_t count; /* number of linked states */ uint8_t _pad; uint8_t set; /* parent rule set number */ uint16_t rulenum; /* parent rule number */ uint32_t ruleid; /* parent rule id */ uint32_t hashval; /* hash value used for hash resize */ uint32_t expire; /* expire time */ }; struct dyn_ipv4_state { uint8_t type; /* State type */ uint8_t proto; /* UL Protocol */ uint16_t kidx; /* named object index */ uint16_t sport, dport; /* ULP source and destination ports */ in_addr_t src, dst; /* IPv4 source and destination */ union { struct dyn_data *data; struct dyn_parent *limit; }; CK_SLIST_ENTRY(dyn_ipv4_state) entry; SLIST_ENTRY(dyn_ipv4_state) expired; }; CK_SLIST_HEAD(dyn_ipv4ck_slist, dyn_ipv4_state); VNET_DEFINE_STATIC(struct dyn_ipv4ck_slist *, dyn_ipv4); VNET_DEFINE_STATIC(struct dyn_ipv4ck_slist *, dyn_ipv4_parent); SLIST_HEAD(dyn_ipv4_slist, dyn_ipv4_state); VNET_DEFINE_STATIC(struct dyn_ipv4_slist, dyn_expired_ipv4); #define V_dyn_ipv4 VNET(dyn_ipv4) #define V_dyn_ipv4_parent VNET(dyn_ipv4_parent) #define V_dyn_expired_ipv4 VNET(dyn_expired_ipv4) #ifdef INET6 struct dyn_ipv6_state { uint8_t type; /* State type */ uint8_t proto; /* UL Protocol */ uint16_t kidx; /* named object index */ uint16_t sport, dport; /* ULP source and destination ports */ struct in6_addr src, dst; /* IPv6 source and destination */ uint32_t zoneid; /* IPv6 scope zone id */ union { struct dyn_data *data; struct dyn_parent *limit; }; CK_SLIST_ENTRY(dyn_ipv6_state) entry; SLIST_ENTRY(dyn_ipv6_state) expired; }; CK_SLIST_HEAD(dyn_ipv6ck_slist, dyn_ipv6_state); VNET_DEFINE_STATIC(struct dyn_ipv6ck_slist *, dyn_ipv6); VNET_DEFINE_STATIC(struct dyn_ipv6ck_slist *, dyn_ipv6_parent); SLIST_HEAD(dyn_ipv6_slist, dyn_ipv6_state); VNET_DEFINE_STATIC(struct dyn_ipv6_slist, dyn_expired_ipv6); #define V_dyn_ipv6 VNET(dyn_ipv6) #define V_dyn_ipv6_parent VNET(dyn_ipv6_parent) #define V_dyn_expired_ipv6 VNET(dyn_expired_ipv6) #endif /* INET6 */ /* * Per-CPU pointer indicates that specified state is currently in use * and must not be reclaimed by expiration callout. */ static void **dyn_hp_cache; DPCPU_DEFINE_STATIC(void *, dyn_hp); #define DYNSTATE_GET(cpu) ck_pr_load_ptr(DPCPU_ID_PTR((cpu), dyn_hp)) #define DYNSTATE_PROTECT(v) ck_pr_store_ptr(DPCPU_PTR(dyn_hp), (v)) #define DYNSTATE_RELEASE() DYNSTATE_PROTECT(NULL) #define DYNSTATE_CRITICAL_ENTER() critical_enter() #define DYNSTATE_CRITICAL_EXIT() do { \ DYNSTATE_RELEASE(); \ critical_exit(); \ } while (0); /* * We keep two version numbers, one is updated when new entry added to * the list. Second is updated when an entry deleted from the list. * Versions are updated under bucket lock. * * Bucket "add" version number is used to know, that in the time between * state lookup (i.e. ipfw_dyn_lookup_state()) and the followed state * creation (i.e. ipfw_dyn_install_state()) another concurrent thread did * not install some state in this bucket. Using this info we can avoid * additional state lookup, because we are sure that we will not install * the state twice. * * Also doing the tracking of bucket "del" version during lookup we can * be sure, that state entry was not unlinked and freed in time between * we read the state pointer and protect it with hazard pointer. * * An entry unlinked from CK list keeps unchanged until it is freed. * Unlinked entries are linked into expired lists using "expired" field. */ /* * dyn_expire_lock is used to protect access to dyn_expired_xxx lists. * dyn_bucket_lock is used to get write access to lists in specific bucket. * Currently one dyn_bucket_lock is used for all ipv4, ipv4_parent, ipv6, * and ipv6_parent lists. */ VNET_DEFINE_STATIC(struct mtx, dyn_expire_lock); VNET_DEFINE_STATIC(struct mtx *, dyn_bucket_lock); #define V_dyn_expire_lock VNET(dyn_expire_lock) #define V_dyn_bucket_lock VNET(dyn_bucket_lock) /* * Bucket's add/delete generation versions. */ VNET_DEFINE_STATIC(uint32_t *, dyn_ipv4_add); VNET_DEFINE_STATIC(uint32_t *, dyn_ipv4_del); VNET_DEFINE_STATIC(uint32_t *, dyn_ipv4_parent_add); VNET_DEFINE_STATIC(uint32_t *, dyn_ipv4_parent_del); #define V_dyn_ipv4_add VNET(dyn_ipv4_add) #define V_dyn_ipv4_del VNET(dyn_ipv4_del) #define V_dyn_ipv4_parent_add VNET(dyn_ipv4_parent_add) #define V_dyn_ipv4_parent_del VNET(dyn_ipv4_parent_del) #ifdef INET6 VNET_DEFINE_STATIC(uint32_t *, dyn_ipv6_add); VNET_DEFINE_STATIC(uint32_t *, dyn_ipv6_del); VNET_DEFINE_STATIC(uint32_t *, dyn_ipv6_parent_add); VNET_DEFINE_STATIC(uint32_t *, dyn_ipv6_parent_del); #define V_dyn_ipv6_add VNET(dyn_ipv6_add) #define V_dyn_ipv6_del VNET(dyn_ipv6_del) #define V_dyn_ipv6_parent_add VNET(dyn_ipv6_parent_add) #define V_dyn_ipv6_parent_del VNET(dyn_ipv6_parent_del) #endif /* INET6 */ #define DYN_BUCKET(h, b) ((h) & (b - 1)) #define DYN_BUCKET_VERSION(b, v) ck_pr_load_32(&V_dyn_ ## v[(b)]) #define DYN_BUCKET_VERSION_BUMP(b, v) ck_pr_inc_32(&V_dyn_ ## v[(b)]) #define DYN_BUCKET_LOCK_INIT(lock, b) \ mtx_init(&lock[(b)], "IPFW dynamic bucket", NULL, MTX_DEF) #define DYN_BUCKET_LOCK_DESTROY(lock, b) mtx_destroy(&lock[(b)]) #define DYN_BUCKET_LOCK(b) mtx_lock(&V_dyn_bucket_lock[(b)]) #define DYN_BUCKET_UNLOCK(b) mtx_unlock(&V_dyn_bucket_lock[(b)]) #define DYN_BUCKET_ASSERT(b) mtx_assert(&V_dyn_bucket_lock[(b)], MA_OWNED) #define DYN_EXPIRED_LOCK_INIT() \ mtx_init(&V_dyn_expire_lock, "IPFW expired states list", NULL, MTX_DEF) #define DYN_EXPIRED_LOCK_DESTROY() mtx_destroy(&V_dyn_expire_lock) #define DYN_EXPIRED_LOCK() mtx_lock(&V_dyn_expire_lock) #define DYN_EXPIRED_UNLOCK() mtx_unlock(&V_dyn_expire_lock) VNET_DEFINE_STATIC(uint32_t, dyn_buckets_max); VNET_DEFINE_STATIC(uint32_t, curr_dyn_buckets); VNET_DEFINE_STATIC(struct callout, dyn_timeout); #define V_dyn_buckets_max VNET(dyn_buckets_max) #define V_curr_dyn_buckets VNET(curr_dyn_buckets) #define V_dyn_timeout VNET(dyn_timeout) /* Maximum length of states chain in a bucket */ VNET_DEFINE_STATIC(uint32_t, curr_max_length); #define V_curr_max_length VNET(curr_max_length) VNET_DEFINE_STATIC(uint32_t, dyn_keep_states); #define V_dyn_keep_states VNET(dyn_keep_states) VNET_DEFINE_STATIC(uma_zone_t, dyn_data_zone); VNET_DEFINE_STATIC(uma_zone_t, dyn_parent_zone); VNET_DEFINE_STATIC(uma_zone_t, dyn_ipv4_zone); #ifdef INET6 VNET_DEFINE_STATIC(uma_zone_t, dyn_ipv6_zone); #define V_dyn_ipv6_zone VNET(dyn_ipv6_zone) #endif /* INET6 */ #define V_dyn_data_zone VNET(dyn_data_zone) #define V_dyn_parent_zone VNET(dyn_parent_zone) #define V_dyn_ipv4_zone VNET(dyn_ipv4_zone) /* * Timeouts for various events in handing dynamic rules. */ VNET_DEFINE_STATIC(uint32_t, dyn_ack_lifetime); VNET_DEFINE_STATIC(uint32_t, dyn_syn_lifetime); VNET_DEFINE_STATIC(uint32_t, dyn_fin_lifetime); VNET_DEFINE_STATIC(uint32_t, dyn_rst_lifetime); VNET_DEFINE_STATIC(uint32_t, dyn_udp_lifetime); VNET_DEFINE_STATIC(uint32_t, dyn_short_lifetime); #define V_dyn_ack_lifetime VNET(dyn_ack_lifetime) #define V_dyn_syn_lifetime VNET(dyn_syn_lifetime) #define V_dyn_fin_lifetime VNET(dyn_fin_lifetime) #define V_dyn_rst_lifetime VNET(dyn_rst_lifetime) #define V_dyn_udp_lifetime VNET(dyn_udp_lifetime) #define V_dyn_short_lifetime VNET(dyn_short_lifetime) /* * Keepalives are sent if dyn_keepalive is set. They are sent every * dyn_keepalive_period seconds, in the last dyn_keepalive_interval * seconds of lifetime of a rule. * dyn_rst_lifetime and dyn_fin_lifetime should be strictly lower * than dyn_keepalive_period. */ VNET_DEFINE_STATIC(uint32_t, dyn_keepalive_interval); VNET_DEFINE_STATIC(uint32_t, dyn_keepalive_period); VNET_DEFINE_STATIC(uint32_t, dyn_keepalive); VNET_DEFINE_STATIC(time_t, dyn_keepalive_last); #define V_dyn_keepalive_interval VNET(dyn_keepalive_interval) #define V_dyn_keepalive_period VNET(dyn_keepalive_period) #define V_dyn_keepalive VNET(dyn_keepalive) #define V_dyn_keepalive_last VNET(dyn_keepalive_last) VNET_DEFINE_STATIC(uint32_t, dyn_max); /* max # of dynamic states */ VNET_DEFINE_STATIC(uint32_t, dyn_count); /* number of states */ VNET_DEFINE_STATIC(uint32_t, dyn_parent_max); /* max # of parent states */ VNET_DEFINE_STATIC(uint32_t, dyn_parent_count); /* number of parent states */ #define V_dyn_max VNET(dyn_max) #define V_dyn_count VNET(dyn_count) #define V_dyn_parent_max VNET(dyn_parent_max) #define V_dyn_parent_count VNET(dyn_parent_count) #define DYN_COUNT_DEC(name) do { \ MPASS((V_ ## name) > 0); \ ck_pr_dec_32(&(V_ ## name)); \ } while (0) #define DYN_COUNT_INC(name) ck_pr_inc_32(&(V_ ## name)) #define DYN_COUNT(name) ck_pr_load_32(&(V_ ## name)) static time_t last_log; /* Log ratelimiting */ /* * Get/set maximum number of dynamic states in given VNET instance. */ static int sysctl_dyn_max(SYSCTL_HANDLER_ARGS) { uint32_t nstates; int error; nstates = V_dyn_max; error = sysctl_handle_32(oidp, &nstates, 0, req); /* Read operation or some error */ if ((error != 0) || (req->newptr == NULL)) return (error); V_dyn_max = nstates; uma_zone_set_max(V_dyn_data_zone, V_dyn_max); return (0); } static int sysctl_dyn_parent_max(SYSCTL_HANDLER_ARGS) { uint32_t nstates; int error; nstates = V_dyn_parent_max; error = sysctl_handle_32(oidp, &nstates, 0, req); /* Read operation or some error */ if ((error != 0) || (req->newptr == NULL)) return (error); V_dyn_parent_max = nstates; uma_zone_set_max(V_dyn_parent_zone, V_dyn_parent_max); return (0); } static int sysctl_dyn_buckets(SYSCTL_HANDLER_ARGS) { uint32_t nbuckets; int error; nbuckets = V_dyn_buckets_max; error = sysctl_handle_32(oidp, &nbuckets, 0, req); /* Read operation or some error */ if ((error != 0) || (req->newptr == NULL)) return (error); if (nbuckets > 256) V_dyn_buckets_max = 1 << fls(nbuckets - 1); else return (EINVAL); return (0); } SYSCTL_DECL(_net_inet_ip_fw); SYSCTL_U32(_net_inet_ip_fw, OID_AUTO, dyn_count, CTLFLAG_VNET | CTLFLAG_RD, &VNET_NAME(dyn_count), 0, "Current number of dynamic states."); SYSCTL_U32(_net_inet_ip_fw, OID_AUTO, dyn_parent_count, CTLFLAG_VNET | CTLFLAG_RD, &VNET_NAME(dyn_parent_count), 0, "Current number of parent states. "); SYSCTL_U32(_net_inet_ip_fw, OID_AUTO, curr_dyn_buckets, CTLFLAG_VNET | CTLFLAG_RD, &VNET_NAME(curr_dyn_buckets), 0, "Current number of buckets for states hash table."); SYSCTL_U32(_net_inet_ip_fw, OID_AUTO, curr_max_length, CTLFLAG_VNET | CTLFLAG_RD, &VNET_NAME(curr_max_length), 0, "Current maximum length of states chains in hash buckets."); SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, dyn_buckets, CTLFLAG_VNET | CTLTYPE_U32 | CTLFLAG_RW, 0, 0, sysctl_dyn_buckets, "IU", "Max number of buckets for dynamic states hash table."); SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, dyn_max, CTLFLAG_VNET | CTLTYPE_U32 | CTLFLAG_RW, 0, 0, sysctl_dyn_max, "IU", "Max number of dynamic states."); SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, dyn_parent_max, CTLFLAG_VNET | CTLTYPE_U32 | CTLFLAG_RW, 0, 0, sysctl_dyn_parent_max, "IU", "Max number of parent dynamic states."); SYSCTL_U32(_net_inet_ip_fw, OID_AUTO, dyn_ack_lifetime, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(dyn_ack_lifetime), 0, "Lifetime of dynamic states for TCP ACK."); SYSCTL_U32(_net_inet_ip_fw, OID_AUTO, dyn_syn_lifetime, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(dyn_syn_lifetime), 0, "Lifetime of dynamic states for TCP SYN."); SYSCTL_U32(_net_inet_ip_fw, OID_AUTO, dyn_fin_lifetime, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(dyn_fin_lifetime), 0, "Lifetime of dynamic states for TCP FIN."); SYSCTL_U32(_net_inet_ip_fw, OID_AUTO, dyn_rst_lifetime, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(dyn_rst_lifetime), 0, "Lifetime of dynamic states for TCP RST."); SYSCTL_U32(_net_inet_ip_fw, OID_AUTO, dyn_udp_lifetime, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(dyn_udp_lifetime), 0, "Lifetime of dynamic states for UDP."); SYSCTL_U32(_net_inet_ip_fw, OID_AUTO, dyn_short_lifetime, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(dyn_short_lifetime), 0, "Lifetime of dynamic states for other situations."); SYSCTL_U32(_net_inet_ip_fw, OID_AUTO, dyn_keepalive, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(dyn_keepalive), 0, "Enable keepalives for dynamic states."); SYSCTL_U32(_net_inet_ip_fw, OID_AUTO, dyn_keep_states, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(dyn_keep_states), 0, "Do not flush dynamic states on rule deletion"); #ifdef IPFIREWALL_DYNDEBUG #define DYN_DEBUG(fmt, ...) do { \ printf("%s: " fmt "\n", __func__, __VA_ARGS__); \ } while (0) #else #define DYN_DEBUG(fmt, ...) #endif /* !IPFIREWALL_DYNDEBUG */ #ifdef INET6 /* Functions to work with IPv6 states */ static struct dyn_ipv6_state *dyn_lookup_ipv6_state( const struct ipfw_flow_id *, uint32_t, const void *, struct ipfw_dyn_info *, int); static int dyn_lookup_ipv6_state_locked(const struct ipfw_flow_id *, uint32_t, const void *, int, uint32_t, uint16_t); static struct dyn_ipv6_state *dyn_alloc_ipv6_state( const struct ipfw_flow_id *, uint32_t, uint16_t, uint8_t); static int dyn_add_ipv6_state(void *, uint32_t, uint16_t, uint8_t, const struct ipfw_flow_id *, uint32_t, const void *, int, uint32_t, struct ipfw_dyn_info *, uint16_t, uint16_t, uint8_t); static void dyn_export_ipv6_state(const struct dyn_ipv6_state *, ipfw_dyn_rule *); static uint32_t dyn_getscopeid(const struct ip_fw_args *); static void dyn_make_keepalive_ipv6(struct mbuf *, const struct in6_addr *, const struct in6_addr *, uint32_t, uint32_t, uint32_t, uint16_t, uint16_t); static void dyn_enqueue_keepalive_ipv6(struct mbufq *, const struct dyn_ipv6_state *); static void dyn_send_keepalive_ipv6(struct ip_fw_chain *); static struct dyn_ipv6_state *dyn_lookup_ipv6_parent( const struct ipfw_flow_id *, uint32_t, const void *, uint32_t, uint16_t, uint32_t); static struct dyn_ipv6_state *dyn_lookup_ipv6_parent_locked( const struct ipfw_flow_id *, uint32_t, const void *, uint32_t, uint16_t, uint32_t); static struct dyn_ipv6_state *dyn_add_ipv6_parent(void *, uint32_t, uint16_t, uint8_t, const struct ipfw_flow_id *, uint32_t, uint32_t, uint32_t, uint16_t); #endif /* INET6 */ /* Functions to work with limit states */ static void *dyn_get_parent_state(const struct ipfw_flow_id *, uint32_t, struct ip_fw *, uint32_t, uint32_t, uint16_t); static struct dyn_ipv4_state *dyn_lookup_ipv4_parent( const struct ipfw_flow_id *, const void *, uint32_t, uint16_t, uint32_t); static struct dyn_ipv4_state *dyn_lookup_ipv4_parent_locked( const struct ipfw_flow_id *, const void *, uint32_t, uint16_t, uint32_t); static struct dyn_parent *dyn_alloc_parent(void *, uint32_t, uint16_t, uint8_t, uint32_t); static struct dyn_ipv4_state *dyn_add_ipv4_parent(void *, uint32_t, uint16_t, uint8_t, const struct ipfw_flow_id *, uint32_t, uint32_t, uint16_t); static void dyn_tick(void *); static void dyn_expire_states(struct ip_fw_chain *, ipfw_range_tlv *); static void dyn_free_states(struct ip_fw_chain *); static void dyn_export_parent(const struct dyn_parent *, uint16_t, ipfw_dyn_rule *); static void dyn_export_data(const struct dyn_data *, uint16_t, uint8_t, ipfw_dyn_rule *); static uint32_t dyn_update_tcp_state(struct dyn_data *, const struct ipfw_flow_id *, const struct tcphdr *, int); static void dyn_update_proto_state(struct dyn_data *, const struct ipfw_flow_id *, const void *, int, int); /* Functions to work with IPv4 states */ struct dyn_ipv4_state *dyn_lookup_ipv4_state(const struct ipfw_flow_id *, const void *, struct ipfw_dyn_info *, int); static int dyn_lookup_ipv4_state_locked(const struct ipfw_flow_id *, const void *, int, uint32_t, uint16_t); static struct dyn_ipv4_state *dyn_alloc_ipv4_state( const struct ipfw_flow_id *, uint16_t, uint8_t); static int dyn_add_ipv4_state(void *, uint32_t, uint16_t, uint8_t, const struct ipfw_flow_id *, const void *, int, uint32_t, struct ipfw_dyn_info *, uint16_t, uint16_t, uint8_t); static void dyn_export_ipv4_state(const struct dyn_ipv4_state *, ipfw_dyn_rule *); /* * Named states support. */ static char *default_state_name = "default"; struct dyn_state_obj { struct named_object no; char name[64]; }; #define DYN_STATE_OBJ(ch, cmd) \ ((struct dyn_state_obj *)SRV_OBJECT(ch, (cmd)->arg1)) /* * Classifier callback. * Return 0 if opcode contains object that should be referenced * or rewritten. */ static int dyn_classify(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype) { DYN_DEBUG("opcode %d, arg1 %d", cmd->opcode, cmd->arg1); /* Don't rewrite "check-state any" */ if (cmd->arg1 == 0 && cmd->opcode == O_CHECK_STATE) return (1); *puidx = cmd->arg1; *ptype = 0; return (0); } static void dyn_update(ipfw_insn *cmd, uint16_t idx) { cmd->arg1 = idx; DYN_DEBUG("opcode %d, arg1 %d", cmd->opcode, cmd->arg1); } static int dyn_findbyname(struct ip_fw_chain *ch, struct tid_info *ti, struct named_object **pno) { ipfw_obj_ntlv *ntlv; const char *name; DYN_DEBUG("uidx %d", ti->uidx); if (ti->uidx != 0) { if (ti->tlvs == NULL) return (EINVAL); /* Search ntlv in the buffer provided by user */ ntlv = ipfw_find_name_tlv_type(ti->tlvs, ti->tlen, ti->uidx, IPFW_TLV_STATE_NAME); if (ntlv == NULL) return (EINVAL); name = ntlv->name; } else name = default_state_name; /* * Search named object with corresponding name. * Since states objects are global - ignore the set value * and use zero instead. */ *pno = ipfw_objhash_lookup_name_type(CHAIN_TO_SRV(ch), 0, IPFW_TLV_STATE_NAME, name); /* * We always return success here. * The caller will check *pno and mark object as unresolved, * then it will automatically create "default" object. */ return (0); } static struct named_object * dyn_findbykidx(struct ip_fw_chain *ch, uint16_t idx) { DYN_DEBUG("kidx %d", idx); return (ipfw_objhash_lookup_kidx(CHAIN_TO_SRV(ch), idx)); } static int dyn_create(struct ip_fw_chain *ch, struct tid_info *ti, uint16_t *pkidx) { struct namedobj_instance *ni; struct dyn_state_obj *obj; struct named_object *no; ipfw_obj_ntlv *ntlv; char *name; DYN_DEBUG("uidx %d", ti->uidx); if (ti->uidx != 0) { if (ti->tlvs == NULL) return (EINVAL); ntlv = ipfw_find_name_tlv_type(ti->tlvs, ti->tlen, ti->uidx, IPFW_TLV_STATE_NAME); if (ntlv == NULL) return (EINVAL); name = ntlv->name; } else name = default_state_name; ni = CHAIN_TO_SRV(ch); obj = malloc(sizeof(*obj), M_IPFW, M_WAITOK | M_ZERO); obj->no.name = obj->name; obj->no.etlv = IPFW_TLV_STATE_NAME; strlcpy(obj->name, name, sizeof(obj->name)); IPFW_UH_WLOCK(ch); no = ipfw_objhash_lookup_name_type(ni, 0, IPFW_TLV_STATE_NAME, name); if (no != NULL) { /* * Object is already created. * Just return its kidx and bump refcount. */ *pkidx = no->kidx; no->refcnt++; IPFW_UH_WUNLOCK(ch); free(obj, M_IPFW); DYN_DEBUG("\tfound kidx %d", *pkidx); return (0); } if (ipfw_objhash_alloc_idx(ni, &obj->no.kidx) != 0) { DYN_DEBUG("\talloc_idx failed for %s", name); IPFW_UH_WUNLOCK(ch); free(obj, M_IPFW); return (ENOSPC); } ipfw_objhash_add(ni, &obj->no); SRV_OBJECT(ch, obj->no.kidx) = obj; obj->no.refcnt++; *pkidx = obj->no.kidx; IPFW_UH_WUNLOCK(ch); DYN_DEBUG("\tcreated kidx %d", *pkidx); return (0); } static void dyn_destroy(struct ip_fw_chain *ch, struct named_object *no) { struct dyn_state_obj *obj; IPFW_UH_WLOCK_ASSERT(ch); KASSERT(no->etlv == IPFW_TLV_STATE_NAME, ("%s: wrong object type %u", __func__, no->etlv)); KASSERT(no->refcnt == 1, ("Destroying object '%s' (type %u, idx %u) with refcnt %u", no->name, no->etlv, no->kidx, no->refcnt)); DYN_DEBUG("kidx %d", no->kidx); obj = SRV_OBJECT(ch, no->kidx); SRV_OBJECT(ch, no->kidx) = NULL; ipfw_objhash_del(CHAIN_TO_SRV(ch), no); ipfw_objhash_free_idx(CHAIN_TO_SRV(ch), no->kidx); free(obj, M_IPFW); } static struct opcode_obj_rewrite dyn_opcodes[] = { { O_KEEP_STATE, IPFW_TLV_STATE_NAME, dyn_classify, dyn_update, dyn_findbyname, dyn_findbykidx, dyn_create, dyn_destroy }, { O_CHECK_STATE, IPFW_TLV_STATE_NAME, dyn_classify, dyn_update, dyn_findbyname, dyn_findbykidx, dyn_create, dyn_destroy }, { O_PROBE_STATE, IPFW_TLV_STATE_NAME, dyn_classify, dyn_update, dyn_findbyname, dyn_findbykidx, dyn_create, dyn_destroy }, { O_LIMIT, IPFW_TLV_STATE_NAME, dyn_classify, dyn_update, dyn_findbyname, dyn_findbykidx, dyn_create, dyn_destroy }, }; /* * IMPORTANT: the hash function for dynamic rules must be commutative * in source and destination (ip,port), because rules are bidirectional * and we want to find both in the same bucket. */ #ifndef IPFIREWALL_JENKINSHASH static __inline uint32_t hash_packet(const struct ipfw_flow_id *id) { uint32_t i; #ifdef INET6 if (IS_IP6_FLOW_ID(id)) i = ntohl((id->dst_ip6.__u6_addr.__u6_addr32[2]) ^ (id->dst_ip6.__u6_addr.__u6_addr32[3]) ^ (id->src_ip6.__u6_addr.__u6_addr32[2]) ^ (id->src_ip6.__u6_addr.__u6_addr32[3])); else #endif /* INET6 */ i = (id->dst_ip) ^ (id->src_ip); i ^= (id->dst_port) ^ (id->src_port); return (i); } static __inline uint32_t hash_parent(const struct ipfw_flow_id *id, const void *rule) { return (hash_packet(id) ^ ((uintptr_t)rule)); } #else /* IPFIREWALL_JENKINSHASH */ VNET_DEFINE_STATIC(uint32_t, dyn_hashseed); #define V_dyn_hashseed VNET(dyn_hashseed) static __inline int addrcmp4(const struct ipfw_flow_id *id) { if (id->src_ip < id->dst_ip) return (0); if (id->src_ip > id->dst_ip) return (1); if (id->src_port <= id->dst_port) return (0); return (1); } #ifdef INET6 static __inline int addrcmp6(const struct ipfw_flow_id *id) { int ret; ret = memcmp(&id->src_ip6, &id->dst_ip6, sizeof(struct in6_addr)); if (ret < 0) return (0); if (ret > 0) return (1); if (id->src_port <= id->dst_port) return (0); return (1); } static __inline uint32_t hash_packet6(const struct ipfw_flow_id *id) { struct tuple6 { struct in6_addr addr[2]; uint16_t port[2]; } t6; if (addrcmp6(id) == 0) { t6.addr[0] = id->src_ip6; t6.addr[1] = id->dst_ip6; t6.port[0] = id->src_port; t6.port[1] = id->dst_port; } else { t6.addr[0] = id->dst_ip6; t6.addr[1] = id->src_ip6; t6.port[0] = id->dst_port; t6.port[1] = id->src_port; } return (jenkins_hash32((const uint32_t *)&t6, sizeof(t6) / sizeof(uint32_t), V_dyn_hashseed)); } #endif static __inline uint32_t hash_packet(const struct ipfw_flow_id *id) { struct tuple4 { in_addr_t addr[2]; uint16_t port[2]; } t4; if (IS_IP4_FLOW_ID(id)) { /* All fields are in host byte order */ if (addrcmp4(id) == 0) { t4.addr[0] = id->src_ip; t4.addr[1] = id->dst_ip; t4.port[0] = id->src_port; t4.port[1] = id->dst_port; } else { t4.addr[0] = id->dst_ip; t4.addr[1] = id->src_ip; t4.port[0] = id->dst_port; t4.port[1] = id->src_port; } return (jenkins_hash32((const uint32_t *)&t4, sizeof(t4) / sizeof(uint32_t), V_dyn_hashseed)); } else #ifdef INET6 if (IS_IP6_FLOW_ID(id)) return (hash_packet6(id)); #endif return (0); } static __inline uint32_t hash_parent(const struct ipfw_flow_id *id, const void *rule) { return (jenkins_hash32((const uint32_t *)&rule, sizeof(rule) / sizeof(uint32_t), hash_packet(id))); } #endif /* IPFIREWALL_JENKINSHASH */ /* * Print customizable flow id description via log(9) facility. */ static void print_dyn_rule_flags(const struct ipfw_flow_id *id, int dyn_type, int log_flags, char *prefix, char *postfix) { struct in_addr da; #ifdef INET6 char src[INET6_ADDRSTRLEN], dst[INET6_ADDRSTRLEN]; #else char src[INET_ADDRSTRLEN], dst[INET_ADDRSTRLEN]; #endif #ifdef INET6 if (IS_IP6_FLOW_ID(id)) { ip6_sprintf(src, &id->src_ip6); ip6_sprintf(dst, &id->dst_ip6); } else #endif { da.s_addr = htonl(id->src_ip); inet_ntop(AF_INET, &da, src, sizeof(src)); da.s_addr = htonl(id->dst_ip); inet_ntop(AF_INET, &da, dst, sizeof(dst)); } log(log_flags, "ipfw: %s type %d %s %d -> %s %d, %d %s\n", prefix, dyn_type, src, id->src_port, dst, id->dst_port, V_dyn_count, postfix); } #define print_dyn_rule(id, dtype, prefix, postfix) \ print_dyn_rule_flags(id, dtype, LOG_DEBUG, prefix, postfix) #define TIME_LEQ(a,b) ((int)((a)-(b)) <= 0) #define TIME_LE(a,b) ((int)((a)-(b)) < 0) #define _SEQ_GE(a,b) ((int)((a)-(b)) >= 0) #define BOTH_SYN (TH_SYN | (TH_SYN << 8)) #define BOTH_FIN (TH_FIN | (TH_FIN << 8)) #define TCP_FLAGS (TH_FLAGS | (TH_FLAGS << 8)) #define ACK_FWD 0x00010000 /* fwd ack seen */ #define ACK_REV 0x00020000 /* rev ack seen */ #define ACK_BOTH (ACK_FWD | ACK_REV) static uint32_t dyn_update_tcp_state(struct dyn_data *data, const struct ipfw_flow_id *pkt, const struct tcphdr *tcp, int dir) { uint32_t ack, expire; uint32_t state, old; uint8_t th_flags; expire = data->expire; old = state = data->state; th_flags = pkt->_flags & (TH_FIN | TH_SYN | TH_RST); state |= (dir == MATCH_FORWARD) ? th_flags: (th_flags << 8); switch (state & TCP_FLAGS) { case TH_SYN: /* opening */ expire = time_uptime + V_dyn_syn_lifetime; break; case BOTH_SYN: /* move to established */ case BOTH_SYN | TH_FIN: /* one side tries to close */ case BOTH_SYN | (TH_FIN << 8): if (tcp == NULL) break; ack = ntohl(tcp->th_ack); if (dir == MATCH_FORWARD) { if (data->ack_fwd == 0 || _SEQ_GE(ack, data->ack_fwd)) { state |= ACK_FWD; if (data->ack_fwd != ack) ck_pr_store_32(&data->ack_fwd, ack); } } else { if (data->ack_rev == 0 || _SEQ_GE(ack, data->ack_rev)) { state |= ACK_REV; if (data->ack_rev != ack) ck_pr_store_32(&data->ack_rev, ack); } } if ((state & ACK_BOTH) == ACK_BOTH) { /* * Set expire time to V_dyn_ack_lifetime only if * we got ACKs for both directions. * We use XOR here to avoid possible state * overwriting in concurrent thread. */ expire = time_uptime + V_dyn_ack_lifetime; ck_pr_xor_32(&data->state, ACK_BOTH); } else if ((data->state & ACK_BOTH) != (state & ACK_BOTH)) ck_pr_or_32(&data->state, state & ACK_BOTH); break; case BOTH_SYN | BOTH_FIN: /* both sides closed */ if (V_dyn_fin_lifetime >= V_dyn_keepalive_period) V_dyn_fin_lifetime = V_dyn_keepalive_period - 1; expire = time_uptime + V_dyn_fin_lifetime; break; default: if (V_dyn_keepalive != 0 && V_dyn_rst_lifetime >= V_dyn_keepalive_period) V_dyn_rst_lifetime = V_dyn_keepalive_period - 1; expire = time_uptime + V_dyn_rst_lifetime; } /* Save TCP state if it was changed */ if ((state & TCP_FLAGS) != (old & TCP_FLAGS)) ck_pr_or_32(&data->state, state & TCP_FLAGS); return (expire); } /* * Update ULP specific state. * For TCP we keep sequence numbers and flags. For other protocols * currently we update only expire time. Packets and bytes counters * are also updated here. */ static void dyn_update_proto_state(struct dyn_data *data, const struct ipfw_flow_id *pkt, const void *ulp, int pktlen, int dir) { uint32_t expire; /* NOTE: we are in critical section here. */ switch (pkt->proto) { case IPPROTO_UDP: case IPPROTO_UDPLITE: expire = time_uptime + V_dyn_udp_lifetime; break; case IPPROTO_TCP: expire = dyn_update_tcp_state(data, pkt, ulp, dir); break; default: expire = time_uptime + V_dyn_short_lifetime; } /* * Expiration timer has the per-second granularity, no need to update * it every time when state is matched. */ if (data->expire != expire) ck_pr_store_32(&data->expire, expire); if (dir == MATCH_FORWARD) DYN_COUNTER_INC(data, fwd, pktlen); else DYN_COUNTER_INC(data, rev, pktlen); } /* * Lookup IPv4 state. * Must be called in critical section. */ struct dyn_ipv4_state * dyn_lookup_ipv4_state(const struct ipfw_flow_id *pkt, const void *ulp, struct ipfw_dyn_info *info, int pktlen) { struct dyn_ipv4_state *s; uint32_t version, bucket; bucket = DYN_BUCKET(info->hashval, V_curr_dyn_buckets); info->version = DYN_BUCKET_VERSION(bucket, ipv4_add); restart: version = DYN_BUCKET_VERSION(bucket, ipv4_del); CK_SLIST_FOREACH(s, &V_dyn_ipv4[bucket], entry) { DYNSTATE_PROTECT(s); if (version != DYN_BUCKET_VERSION(bucket, ipv4_del)) goto restart; if (s->proto != pkt->proto) continue; if (info->kidx != 0 && s->kidx != info->kidx) continue; if (s->sport == pkt->src_port && s->dport == pkt->dst_port && s->src == pkt->src_ip && s->dst == pkt->dst_ip) { info->direction = MATCH_FORWARD; break; } if (s->sport == pkt->dst_port && s->dport == pkt->src_port && s->src == pkt->dst_ip && s->dst == pkt->src_ip) { info->direction = MATCH_REVERSE; break; } } if (s != NULL) dyn_update_proto_state(s->data, pkt, ulp, pktlen, info->direction); return (s); } /* * Lookup IPv4 state. * Simplifed version is used to check that matching state doesn't exist. */ static int dyn_lookup_ipv4_state_locked(const struct ipfw_flow_id *pkt, const void *ulp, int pktlen, uint32_t bucket, uint16_t kidx) { struct dyn_ipv4_state *s; int dir; dir = MATCH_NONE; DYN_BUCKET_ASSERT(bucket); CK_SLIST_FOREACH(s, &V_dyn_ipv4[bucket], entry) { if (s->proto != pkt->proto || s->kidx != kidx) continue; if (s->sport == pkt->src_port && s->dport == pkt->dst_port && s->src == pkt->src_ip && s->dst == pkt->dst_ip) { dir = MATCH_FORWARD; break; } if (s->sport == pkt->dst_port && s->dport == pkt->src_port && s->src == pkt->dst_ip && s->dst == pkt->src_ip) { dir = MATCH_REVERSE; break; } } if (s != NULL) dyn_update_proto_state(s->data, pkt, ulp, pktlen, dir); return (s != NULL); } struct dyn_ipv4_state * dyn_lookup_ipv4_parent(const struct ipfw_flow_id *pkt, const void *rule, uint32_t ruleid, uint16_t rulenum, uint32_t hashval) { struct dyn_ipv4_state *s; uint32_t version, bucket; bucket = DYN_BUCKET(hashval, V_curr_dyn_buckets); restart: version = DYN_BUCKET_VERSION(bucket, ipv4_parent_del); CK_SLIST_FOREACH(s, &V_dyn_ipv4_parent[bucket], entry) { DYNSTATE_PROTECT(s); if (version != DYN_BUCKET_VERSION(bucket, ipv4_parent_del)) goto restart; /* * NOTE: we do not need to check kidx, because parent rule * can not create states with different kidx. * And parent rule always created for forward direction. */ if (s->limit->parent == rule && s->limit->ruleid == ruleid && s->limit->rulenum == rulenum && s->proto == pkt->proto && s->sport == pkt->src_port && s->dport == pkt->dst_port && s->src == pkt->src_ip && s->dst == pkt->dst_ip) { if (s->limit->expire != time_uptime + V_dyn_short_lifetime) ck_pr_store_32(&s->limit->expire, time_uptime + V_dyn_short_lifetime); break; } } return (s); } static struct dyn_ipv4_state * dyn_lookup_ipv4_parent_locked(const struct ipfw_flow_id *pkt, const void *rule, uint32_t ruleid, uint16_t rulenum, uint32_t bucket) { struct dyn_ipv4_state *s; DYN_BUCKET_ASSERT(bucket); CK_SLIST_FOREACH(s, &V_dyn_ipv4_parent[bucket], entry) { if (s->limit->parent == rule && s->limit->ruleid == ruleid && s->limit->rulenum == rulenum && s->proto == pkt->proto && s->sport == pkt->src_port && s->dport == pkt->dst_port && s->src == pkt->src_ip && s->dst == pkt->dst_ip) break; } return (s); } #ifdef INET6 static uint32_t dyn_getscopeid(const struct ip_fw_args *args) { /* * If source or destination address is an scopeid address, we need * determine the scope zone id to resolve address scope ambiguity. */ if (IN6_IS_ADDR_LINKLOCAL(&args->f_id.src_ip6) || IN6_IS_ADDR_LINKLOCAL(&args->f_id.dst_ip6)) { MPASS(args->oif != NULL || args->m->m_pkthdr.rcvif != NULL); return (in6_getscopezone(args->oif != NULL ? args->oif: args->m->m_pkthdr.rcvif, IPV6_ADDR_SCOPE_LINKLOCAL)); } return (0); } /* * Lookup IPv6 state. * Must be called in critical section. */ static struct dyn_ipv6_state * dyn_lookup_ipv6_state(const struct ipfw_flow_id *pkt, uint32_t zoneid, const void *ulp, struct ipfw_dyn_info *info, int pktlen) { struct dyn_ipv6_state *s; uint32_t version, bucket; bucket = DYN_BUCKET(info->hashval, V_curr_dyn_buckets); info->version = DYN_BUCKET_VERSION(bucket, ipv6_add); restart: version = DYN_BUCKET_VERSION(bucket, ipv6_del); CK_SLIST_FOREACH(s, &V_dyn_ipv6[bucket], entry) { DYNSTATE_PROTECT(s); if (version != DYN_BUCKET_VERSION(bucket, ipv6_del)) goto restart; if (s->proto != pkt->proto || s->zoneid != zoneid) continue; if (info->kidx != 0 && s->kidx != info->kidx) continue; if (s->sport == pkt->src_port && s->dport == pkt->dst_port && IN6_ARE_ADDR_EQUAL(&s->src, &pkt->src_ip6) && IN6_ARE_ADDR_EQUAL(&s->dst, &pkt->dst_ip6)) { info->direction = MATCH_FORWARD; break; } if (s->sport == pkt->dst_port && s->dport == pkt->src_port && IN6_ARE_ADDR_EQUAL(&s->src, &pkt->dst_ip6) && IN6_ARE_ADDR_EQUAL(&s->dst, &pkt->src_ip6)) { info->direction = MATCH_REVERSE; break; } } if (s != NULL) dyn_update_proto_state(s->data, pkt, ulp, pktlen, info->direction); return (s); } /* * Lookup IPv6 state. * Simplifed version is used to check that matching state doesn't exist. */ static int dyn_lookup_ipv6_state_locked(const struct ipfw_flow_id *pkt, uint32_t zoneid, const void *ulp, int pktlen, uint32_t bucket, uint16_t kidx) { struct dyn_ipv6_state *s; int dir; dir = MATCH_NONE; DYN_BUCKET_ASSERT(bucket); CK_SLIST_FOREACH(s, &V_dyn_ipv6[bucket], entry) { if (s->proto != pkt->proto || s->kidx != kidx || s->zoneid != zoneid) continue; if (s->sport == pkt->src_port && s->dport == pkt->dst_port && IN6_ARE_ADDR_EQUAL(&s->src, &pkt->src_ip6) && IN6_ARE_ADDR_EQUAL(&s->dst, &pkt->dst_ip6)) { dir = MATCH_FORWARD; break; } if (s->sport == pkt->dst_port && s->dport == pkt->src_port && IN6_ARE_ADDR_EQUAL(&s->src, &pkt->dst_ip6) && IN6_ARE_ADDR_EQUAL(&s->dst, &pkt->src_ip6)) { dir = MATCH_REVERSE; break; } } if (s != NULL) dyn_update_proto_state(s->data, pkt, ulp, pktlen, dir); return (s != NULL); } static struct dyn_ipv6_state * dyn_lookup_ipv6_parent(const struct ipfw_flow_id *pkt, uint32_t zoneid, const void *rule, uint32_t ruleid, uint16_t rulenum, uint32_t hashval) { struct dyn_ipv6_state *s; uint32_t version, bucket; bucket = DYN_BUCKET(hashval, V_curr_dyn_buckets); restart: version = DYN_BUCKET_VERSION(bucket, ipv6_parent_del); CK_SLIST_FOREACH(s, &V_dyn_ipv6_parent[bucket], entry) { DYNSTATE_PROTECT(s); if (version != DYN_BUCKET_VERSION(bucket, ipv6_parent_del)) goto restart; /* * NOTE: we do not need to check kidx, because parent rule * can not create states with different kidx. * Also parent rule always created for forward direction. */ if (s->limit->parent == rule && s->limit->ruleid == ruleid && s->limit->rulenum == rulenum && s->proto == pkt->proto && s->sport == pkt->src_port && s->dport == pkt->dst_port && s->zoneid == zoneid && IN6_ARE_ADDR_EQUAL(&s->src, &pkt->src_ip6) && IN6_ARE_ADDR_EQUAL(&s->dst, &pkt->dst_ip6)) { if (s->limit->expire != time_uptime + V_dyn_short_lifetime) ck_pr_store_32(&s->limit->expire, time_uptime + V_dyn_short_lifetime); break; } } return (s); } static struct dyn_ipv6_state * dyn_lookup_ipv6_parent_locked(const struct ipfw_flow_id *pkt, uint32_t zoneid, const void *rule, uint32_t ruleid, uint16_t rulenum, uint32_t bucket) { struct dyn_ipv6_state *s; DYN_BUCKET_ASSERT(bucket); CK_SLIST_FOREACH(s, &V_dyn_ipv6_parent[bucket], entry) { if (s->limit->parent == rule && s->limit->ruleid == ruleid && s->limit->rulenum == rulenum && s->proto == pkt->proto && s->sport == pkt->src_port && s->dport == pkt->dst_port && s->zoneid == zoneid && IN6_ARE_ADDR_EQUAL(&s->src, &pkt->src_ip6) && IN6_ARE_ADDR_EQUAL(&s->dst, &pkt->dst_ip6)) break; } return (s); } #endif /* INET6 */ /* * Lookup dynamic state. * pkt - filled by ipfw_chk() ipfw_flow_id; * ulp - determined by ipfw_chk() upper level protocol header; * dyn_info - info about matched state to return back; * Returns pointer to state's parent rule and dyn_info. If there is * no state, NULL is returned. * On match ipfw_dyn_lookup() updates state's counters. */ struct ip_fw * ipfw_dyn_lookup_state(const struct ip_fw_args *args, const void *ulp, int pktlen, const ipfw_insn *cmd, struct ipfw_dyn_info *info) { struct dyn_data *data; struct ip_fw *rule; IPFW_RLOCK_ASSERT(&V_layer3_chain); data = NULL; rule = NULL; info->kidx = cmd->arg1; info->direction = MATCH_NONE; info->hashval = hash_packet(&args->f_id); DYNSTATE_CRITICAL_ENTER(); if (IS_IP4_FLOW_ID(&args->f_id)) { struct dyn_ipv4_state *s; s = dyn_lookup_ipv4_state(&args->f_id, ulp, info, pktlen); if (s != NULL) { /* * Dynamic states are created using the same 5-tuple, * so it is assumed, that parent rule for O_LIMIT * state has the same address family. */ data = s->data; if (s->type == O_LIMIT) { s = data->parent; rule = s->limit->parent; } else rule = data->parent; } } #ifdef INET6 else if (IS_IP6_FLOW_ID(&args->f_id)) { struct dyn_ipv6_state *s; s = dyn_lookup_ipv6_state(&args->f_id, dyn_getscopeid(args), ulp, info, pktlen); if (s != NULL) { data = s->data; if (s->type == O_LIMIT) { s = data->parent; rule = s->limit->parent; } else rule = data->parent; } } #endif if (data != NULL) { /* * If cached chain id is the same, we can avoid rule index * lookup. Otherwise do lookup and update chain_id and f_pos. * It is safe even if there is concurrent thread that want * update the same state, because chain->id can be changed * only under IPFW_WLOCK(). */ if (data->chain_id != V_layer3_chain.id) { data->f_pos = ipfw_find_rule(&V_layer3_chain, data->rulenum, data->ruleid); /* * Check that found state has not orphaned. * When chain->id being changed the parent * rule can be deleted. If found rule doesn't * match the parent pointer, consider this * result as MATCH_NONE and return NULL. * * This will lead to creation of new similar state * that will be added into head of this bucket. * And the state that we currently have matched * should be deleted by dyn_expire_states(). * * In case when dyn_keep_states is enabled, return - * pointer to default rule and corresponding f_pos - * value. - * XXX: In this case we lose the cache efficiency, - * since f_pos is not cached, because it seems - * there is no easy way to atomically switch - * all fields related to parent rule of given - * state. + * pointer to deleted rule and f_pos value + * corresponding to penultimate rule. + * When we have enabled V_dyn_keep_states, states + * that become orphaned will get the DYN_REFERENCED + * flag and rule will keep around. So we can return + * it. But since it is not in the rules map, we need + * return such f_pos value, so after the state + * handling if the search will continue, the next rule + * will be the last one - the default rule. */ if (V_layer3_chain.map[data->f_pos] == rule) { data->chain_id = V_layer3_chain.id; info->f_pos = data->f_pos; } else if (V_dyn_keep_states != 0) { - rule = V_layer3_chain.default_rule; - info->f_pos = V_layer3_chain.n_rules - 1; + /* + * The original rule pointer is still usable. + * So, we return it, but f_pos need to be + * changed to point to the penultimate rule. + */ + MPASS(V_layer3_chain.n_rules > 1); + data->chain_id = V_layer3_chain.id; + data->f_pos = V_layer3_chain.n_rules - 2; + info->f_pos = data->f_pos; } else { rule = NULL; info->direction = MATCH_NONE; DYN_DEBUG("rule %p [%u, %u] is considered " "invalid in data %p", rule, data->ruleid, data->rulenum, data); /* info->f_pos doesn't matter here. */ } } else info->f_pos = data->f_pos; } DYNSTATE_CRITICAL_EXIT(); #if 0 /* * Return MATCH_NONE if parent rule is in disabled set. * This will lead to creation of new similar state that * will be added into head of this bucket. * * XXXAE: we need to be able update state's set when parent * rule set is changed. */ if (rule != NULL && (V_set_disable & (1 << rule->set))) { rule = NULL; info->direction = MATCH_NONE; } #endif return (rule); } static struct dyn_parent * dyn_alloc_parent(void *parent, uint32_t ruleid, uint16_t rulenum, uint8_t set, uint32_t hashval) { struct dyn_parent *limit; limit = uma_zalloc(V_dyn_parent_zone, M_NOWAIT | M_ZERO); if (limit == NULL) { if (last_log != time_uptime) { last_log = time_uptime; log(LOG_DEBUG, "ipfw: Cannot allocate parent dynamic state, " "consider increasing " "net.inet.ip.fw.dyn_parent_max\n"); } return (NULL); } limit->parent = parent; limit->ruleid = ruleid; limit->rulenum = rulenum; limit->set = set; limit->hashval = hashval; limit->expire = time_uptime + V_dyn_short_lifetime; return (limit); } static struct dyn_data * dyn_alloc_dyndata(void *parent, uint32_t ruleid, uint16_t rulenum, uint8_t set, const struct ipfw_flow_id *pkt, const void *ulp, int pktlen, uint32_t hashval, uint16_t fibnum) { struct dyn_data *data; data = uma_zalloc(V_dyn_data_zone, M_NOWAIT | M_ZERO); if (data == NULL) { if (last_log != time_uptime) { last_log = time_uptime; log(LOG_DEBUG, "ipfw: Cannot allocate dynamic state, " "consider increasing net.inet.ip.fw.dyn_max\n"); } return (NULL); } data->parent = parent; data->ruleid = ruleid; data->rulenum = rulenum; data->set = set; data->fibnum = fibnum; data->hashval = hashval; data->expire = time_uptime + V_dyn_syn_lifetime; dyn_update_proto_state(data, pkt, ulp, pktlen, MATCH_FORWARD); return (data); } static struct dyn_ipv4_state * dyn_alloc_ipv4_state(const struct ipfw_flow_id *pkt, uint16_t kidx, uint8_t type) { struct dyn_ipv4_state *s; s = uma_zalloc(V_dyn_ipv4_zone, M_NOWAIT | M_ZERO); if (s == NULL) return (NULL); s->type = type; s->kidx = kidx; s->proto = pkt->proto; s->sport = pkt->src_port; s->dport = pkt->dst_port; s->src = pkt->src_ip; s->dst = pkt->dst_ip; return (s); } /* * Add IPv4 parent state. * Returns pointer to parent state. When it is not NULL we are in * critical section and pointer protected by hazard pointer. * When some error occurs, it returns NULL and exit from critical section * is not needed. */ static struct dyn_ipv4_state * dyn_add_ipv4_parent(void *rule, uint32_t ruleid, uint16_t rulenum, uint8_t set, const struct ipfw_flow_id *pkt, uint32_t hashval, uint32_t version, uint16_t kidx) { struct dyn_ipv4_state *s; struct dyn_parent *limit; uint32_t bucket; bucket = DYN_BUCKET(hashval, V_curr_dyn_buckets); DYN_BUCKET_LOCK(bucket); if (version != DYN_BUCKET_VERSION(bucket, ipv4_parent_add)) { /* * Bucket version has been changed since last lookup, * do lookup again to be sure that state does not exist. */ s = dyn_lookup_ipv4_parent_locked(pkt, rule, ruleid, rulenum, bucket); if (s != NULL) { /* * Simultaneous thread has already created this * state. Just return it. */ DYNSTATE_CRITICAL_ENTER(); DYNSTATE_PROTECT(s); DYN_BUCKET_UNLOCK(bucket); return (s); } } limit = dyn_alloc_parent(rule, ruleid, rulenum, set, hashval); if (limit == NULL) { DYN_BUCKET_UNLOCK(bucket); return (NULL); } s = dyn_alloc_ipv4_state(pkt, kidx, O_LIMIT_PARENT); if (s == NULL) { DYN_BUCKET_UNLOCK(bucket); uma_zfree(V_dyn_parent_zone, limit); return (NULL); } s->limit = limit; CK_SLIST_INSERT_HEAD(&V_dyn_ipv4_parent[bucket], s, entry); DYN_COUNT_INC(dyn_parent_count); DYN_BUCKET_VERSION_BUMP(bucket, ipv4_parent_add); DYNSTATE_CRITICAL_ENTER(); DYNSTATE_PROTECT(s); DYN_BUCKET_UNLOCK(bucket); return (s); } static int dyn_add_ipv4_state(void *parent, uint32_t ruleid, uint16_t rulenum, uint8_t set, const struct ipfw_flow_id *pkt, const void *ulp, int pktlen, uint32_t hashval, struct ipfw_dyn_info *info, uint16_t fibnum, uint16_t kidx, uint8_t type) { struct dyn_ipv4_state *s; void *data; uint32_t bucket; bucket = DYN_BUCKET(hashval, V_curr_dyn_buckets); DYN_BUCKET_LOCK(bucket); if (info->direction == MATCH_UNKNOWN || info->kidx != kidx || info->hashval != hashval || info->version != DYN_BUCKET_VERSION(bucket, ipv4_add)) { /* * Bucket version has been changed since last lookup, * do lookup again to be sure that state does not exist. */ if (dyn_lookup_ipv4_state_locked(pkt, ulp, pktlen, bucket, kidx) != 0) { DYN_BUCKET_UNLOCK(bucket); return (EEXIST); } } data = dyn_alloc_dyndata(parent, ruleid, rulenum, set, pkt, ulp, pktlen, hashval, fibnum); if (data == NULL) { DYN_BUCKET_UNLOCK(bucket); return (ENOMEM); } s = dyn_alloc_ipv4_state(pkt, kidx, type); if (s == NULL) { DYN_BUCKET_UNLOCK(bucket); uma_zfree(V_dyn_data_zone, data); return (ENOMEM); } s->data = data; CK_SLIST_INSERT_HEAD(&V_dyn_ipv4[bucket], s, entry); DYN_COUNT_INC(dyn_count); DYN_BUCKET_VERSION_BUMP(bucket, ipv4_add); DYN_BUCKET_UNLOCK(bucket); return (0); } #ifdef INET6 static struct dyn_ipv6_state * dyn_alloc_ipv6_state(const struct ipfw_flow_id *pkt, uint32_t zoneid, uint16_t kidx, uint8_t type) { struct dyn_ipv6_state *s; s = uma_zalloc(V_dyn_ipv6_zone, M_NOWAIT | M_ZERO); if (s == NULL) return (NULL); s->type = type; s->kidx = kidx; s->zoneid = zoneid; s->proto = pkt->proto; s->sport = pkt->src_port; s->dport = pkt->dst_port; s->src = pkt->src_ip6; s->dst = pkt->dst_ip6; return (s); } /* * Add IPv6 parent state. * Returns pointer to parent state. When it is not NULL we are in * critical section and pointer protected by hazard pointer. * When some error occurs, it return NULL and exit from critical section * is not needed. */ static struct dyn_ipv6_state * dyn_add_ipv6_parent(void *rule, uint32_t ruleid, uint16_t rulenum, uint8_t set, const struct ipfw_flow_id *pkt, uint32_t zoneid, uint32_t hashval, uint32_t version, uint16_t kidx) { struct dyn_ipv6_state *s; struct dyn_parent *limit; uint32_t bucket; bucket = DYN_BUCKET(hashval, V_curr_dyn_buckets); DYN_BUCKET_LOCK(bucket); if (version != DYN_BUCKET_VERSION(bucket, ipv6_parent_add)) { /* * Bucket version has been changed since last lookup, * do lookup again to be sure that state does not exist. */ s = dyn_lookup_ipv6_parent_locked(pkt, zoneid, rule, ruleid, rulenum, bucket); if (s != NULL) { /* * Simultaneous thread has already created this * state. Just return it. */ DYNSTATE_CRITICAL_ENTER(); DYNSTATE_PROTECT(s); DYN_BUCKET_UNLOCK(bucket); return (s); } } limit = dyn_alloc_parent(rule, ruleid, rulenum, set, hashval); if (limit == NULL) { DYN_BUCKET_UNLOCK(bucket); return (NULL); } s = dyn_alloc_ipv6_state(pkt, zoneid, kidx, O_LIMIT_PARENT); if (s == NULL) { DYN_BUCKET_UNLOCK(bucket); uma_zfree(V_dyn_parent_zone, limit); return (NULL); } s->limit = limit; CK_SLIST_INSERT_HEAD(&V_dyn_ipv6_parent[bucket], s, entry); DYN_COUNT_INC(dyn_parent_count); DYN_BUCKET_VERSION_BUMP(bucket, ipv6_parent_add); DYNSTATE_CRITICAL_ENTER(); DYNSTATE_PROTECT(s); DYN_BUCKET_UNLOCK(bucket); return (s); } static int dyn_add_ipv6_state(void *parent, uint32_t ruleid, uint16_t rulenum, uint8_t set, const struct ipfw_flow_id *pkt, uint32_t zoneid, const void *ulp, int pktlen, uint32_t hashval, struct ipfw_dyn_info *info, uint16_t fibnum, uint16_t kidx, uint8_t type) { struct dyn_ipv6_state *s; struct dyn_data *data; uint32_t bucket; bucket = DYN_BUCKET(hashval, V_curr_dyn_buckets); DYN_BUCKET_LOCK(bucket); if (info->direction == MATCH_UNKNOWN || info->kidx != kidx || info->hashval != hashval || info->version != DYN_BUCKET_VERSION(bucket, ipv6_add)) { /* * Bucket version has been changed since last lookup, * do lookup again to be sure that state does not exist. */ if (dyn_lookup_ipv6_state_locked(pkt, zoneid, ulp, pktlen, bucket, kidx) != 0) { DYN_BUCKET_UNLOCK(bucket); return (EEXIST); } } data = dyn_alloc_dyndata(parent, ruleid, rulenum, set, pkt, ulp, pktlen, hashval, fibnum); if (data == NULL) { DYN_BUCKET_UNLOCK(bucket); return (ENOMEM); } s = dyn_alloc_ipv6_state(pkt, zoneid, kidx, type); if (s == NULL) { DYN_BUCKET_UNLOCK(bucket); uma_zfree(V_dyn_data_zone, data); return (ENOMEM); } s->data = data; CK_SLIST_INSERT_HEAD(&V_dyn_ipv6[bucket], s, entry); DYN_COUNT_INC(dyn_count); DYN_BUCKET_VERSION_BUMP(bucket, ipv6_add); DYN_BUCKET_UNLOCK(bucket); return (0); } #endif /* INET6 */ static void * dyn_get_parent_state(const struct ipfw_flow_id *pkt, uint32_t zoneid, struct ip_fw *rule, uint32_t hashval, uint32_t limit, uint16_t kidx) { char sbuf[24]; struct dyn_parent *p; void *ret; uint32_t bucket, version; p = NULL; ret = NULL; bucket = DYN_BUCKET(hashval, V_curr_dyn_buckets); DYNSTATE_CRITICAL_ENTER(); if (IS_IP4_FLOW_ID(pkt)) { struct dyn_ipv4_state *s; version = DYN_BUCKET_VERSION(bucket, ipv4_parent_add); s = dyn_lookup_ipv4_parent(pkt, rule, rule->id, rule->rulenum, bucket); if (s == NULL) { /* * Exit from critical section because dyn_add_parent() * will acquire bucket lock. */ DYNSTATE_CRITICAL_EXIT(); s = dyn_add_ipv4_parent(rule, rule->id, rule->rulenum, rule->set, pkt, hashval, version, kidx); if (s == NULL) return (NULL); /* Now we are in critical section again. */ } ret = s; p = s->limit; } #ifdef INET6 else if (IS_IP6_FLOW_ID(pkt)) { struct dyn_ipv6_state *s; version = DYN_BUCKET_VERSION(bucket, ipv6_parent_add); s = dyn_lookup_ipv6_parent(pkt, zoneid, rule, rule->id, rule->rulenum, bucket); if (s == NULL) { /* * Exit from critical section because dyn_add_parent() * can acquire bucket mutex. */ DYNSTATE_CRITICAL_EXIT(); s = dyn_add_ipv6_parent(rule, rule->id, rule->rulenum, rule->set, pkt, zoneid, hashval, version, kidx); if (s == NULL) return (NULL); /* Now we are in critical section again. */ } ret = s; p = s->limit; } #endif else { DYNSTATE_CRITICAL_EXIT(); return (NULL); } /* Check the limit */ if (DPARENT_COUNT(p) >= limit) { DYNSTATE_CRITICAL_EXIT(); if (V_fw_verbose && last_log != time_uptime) { last_log = time_uptime; snprintf(sbuf, sizeof(sbuf), "%u drop session", rule->rulenum); print_dyn_rule_flags(pkt, O_LIMIT, LOG_SECURITY | LOG_DEBUG, sbuf, "too many entries"); } return (NULL); } /* Take new session into account. */ DPARENT_COUNT_INC(p); /* * We must exit from critical section because the following code * can acquire bucket mutex. * We rely on the the 'count' field. The state will not expire * until it has some child states, i.e. 'count' field is not zero. * Return state pointer, it will be used by child states as parent. */ DYNSTATE_CRITICAL_EXIT(); return (ret); } static int dyn_install_state(const struct ipfw_flow_id *pkt, uint32_t zoneid, uint16_t fibnum, const void *ulp, int pktlen, void *rule, uint32_t ruleid, uint16_t rulenum, uint8_t set, struct ipfw_dyn_info *info, uint32_t limit, uint16_t limit_mask, uint16_t kidx, uint8_t type) { struct ipfw_flow_id id; uint32_t hashval, parent_hashval; int ret; MPASS(type == O_LIMIT || type == O_KEEP_STATE); if (type == O_LIMIT) { /* Create masked flow id and calculate bucket */ id.addr_type = pkt->addr_type; id.proto = pkt->proto; id.fib = fibnum; /* unused */ id.src_port = (limit_mask & DYN_SRC_PORT) ? pkt->src_port: 0; id.dst_port = (limit_mask & DYN_DST_PORT) ? pkt->dst_port: 0; if (IS_IP4_FLOW_ID(pkt)) { id.src_ip = (limit_mask & DYN_SRC_ADDR) ? pkt->src_ip: 0; id.dst_ip = (limit_mask & DYN_DST_ADDR) ? pkt->dst_ip: 0; } #ifdef INET6 else if (IS_IP6_FLOW_ID(pkt)) { if (limit_mask & DYN_SRC_ADDR) id.src_ip6 = pkt->src_ip6; else memset(&id.src_ip6, 0, sizeof(id.src_ip6)); if (limit_mask & DYN_DST_ADDR) id.dst_ip6 = pkt->dst_ip6; else memset(&id.dst_ip6, 0, sizeof(id.dst_ip6)); } #endif else return (EAFNOSUPPORT); parent_hashval = hash_parent(&id, rule); rule = dyn_get_parent_state(&id, zoneid, rule, parent_hashval, limit, kidx); if (rule == NULL) { #if 0 if (V_fw_verbose && last_log != time_uptime) { last_log = time_uptime; snprintf(sbuf, sizeof(sbuf), "%u drop session", rule->rulenum); print_dyn_rule_flags(pkt, O_LIMIT, LOG_SECURITY | LOG_DEBUG, sbuf, "too many entries"); } #endif return (EACCES); } /* * Limit is not reached, create new state. * Now rule points to parent state. */ } hashval = hash_packet(pkt); if (IS_IP4_FLOW_ID(pkt)) ret = dyn_add_ipv4_state(rule, ruleid, rulenum, set, pkt, ulp, pktlen, hashval, info, fibnum, kidx, type); #ifdef INET6 else if (IS_IP6_FLOW_ID(pkt)) ret = dyn_add_ipv6_state(rule, ruleid, rulenum, set, pkt, zoneid, ulp, pktlen, hashval, info, fibnum, kidx, type); #endif /* INET6 */ else ret = EAFNOSUPPORT; if (type == O_LIMIT) { if (ret != 0) { /* * We failed to create child state for O_LIMIT * opcode. Since we already counted it in the parent, * we must revert counter back. The 'rule' points to * parent state, use it to get dyn_parent. * * XXXAE: it should be safe to use 'rule' pointer * without extra lookup, parent state is referenced * and should not be freed. */ if (IS_IP4_FLOW_ID(&id)) DPARENT_COUNT_DEC( ((struct dyn_ipv4_state *)rule)->limit); #ifdef INET6 else if (IS_IP6_FLOW_ID(&id)) DPARENT_COUNT_DEC( ((struct dyn_ipv6_state *)rule)->limit); #endif } } /* * EEXIST means that simultaneous thread has created this * state. Consider this as success. * * XXXAE: should we invalidate 'info' content here? */ if (ret == EEXIST) return (0); return (ret); } /* * Install dynamic state. * chain - ipfw's instance; * rule - the parent rule that installs the state; * cmd - opcode that installs the state; * args - ipfw arguments; * ulp - upper level protocol header; * pktlen - packet length; * info - dynamic state lookup info; * tablearg - tablearg id. * * Returns non-zero value (failure) if state is not installed because * of errors or because session limitations are enforced. */ int ipfw_dyn_install_state(struct ip_fw_chain *chain, struct ip_fw *rule, const ipfw_insn_limit *cmd, const struct ip_fw_args *args, const void *ulp, int pktlen, struct ipfw_dyn_info *info, uint32_t tablearg) { uint32_t limit; uint16_t limit_mask; if (cmd->o.opcode == O_LIMIT) { limit = IP_FW_ARG_TABLEARG(chain, cmd->conn_limit, limit); limit_mask = cmd->limit_mask; } else { limit = 0; limit_mask = 0; } return (dyn_install_state(&args->f_id, #ifdef INET6 IS_IP6_FLOW_ID(&args->f_id) ? dyn_getscopeid(args): #endif 0, M_GETFIB(args->m), ulp, pktlen, rule, rule->id, rule->rulenum, rule->set, info, limit, limit_mask, cmd->o.arg1, cmd->o.opcode)); } /* * Free safe to remove state entries from expired lists. */ static void dyn_free_states(struct ip_fw_chain *chain) { struct dyn_ipv4_state *s4, *s4n; #ifdef INET6 struct dyn_ipv6_state *s6, *s6n; #endif int cached_count, i; /* * We keep pointers to objects that are in use on each CPU * in the per-cpu dyn_hp pointer. When object is going to be * removed, first of it is unlinked from the corresponding * list. This leads to changing of dyn_bucket_xxx_delver version. * Unlinked objects is placed into corresponding dyn_expired_xxx * list. Reader that is going to dereference object pointer checks * dyn_bucket_xxx_delver version before and after storing pointer * into dyn_hp. If version is the same, the object is protected * from freeing and it is safe to dereference. Othervise reader * tries to iterate list again from the beginning, but this object * now unlinked and thus will not be accessible. * * Copy dyn_hp pointers for each CPU into dyn_hp_cache array. * It does not matter that some pointer can be changed in * time while we are copying. We need to check, that objects * removed in the previous pass are not in use. And if dyn_hp * pointer does not contain it in the time when we are copying, * it will not appear there, because it is already unlinked. * And for new pointers we will not free objects that will be * unlinked in this pass. */ cached_count = 0; CPU_FOREACH(i) { dyn_hp_cache[cached_count] = DYNSTATE_GET(i); if (dyn_hp_cache[cached_count] != NULL) cached_count++; } /* * Free expired states that are safe to free. * Check each entry from previous pass in the dyn_expired_xxx * list, if pointer to the object is in the dyn_hp_cache array, * keep it until next pass. Otherwise it is safe to free the * object. * * XXXAE: optimize this to use SLIST_REMOVE_AFTER. */ #define DYN_FREE_STATES(s, next, name) do { \ s = SLIST_FIRST(&V_dyn_expired_ ## name); \ while (s != NULL) { \ next = SLIST_NEXT(s, expired); \ for (i = 0; i < cached_count; i++) \ if (dyn_hp_cache[i] == s) \ break; \ if (i == cached_count) { \ if (s->type == O_LIMIT_PARENT && \ s->limit->count != 0) { \ s = next; \ continue; \ } \ SLIST_REMOVE(&V_dyn_expired_ ## name, \ s, dyn_ ## name ## _state, expired); \ if (s->type == O_LIMIT_PARENT) \ uma_zfree(V_dyn_parent_zone, s->limit); \ else \ uma_zfree(V_dyn_data_zone, s->data); \ uma_zfree(V_dyn_ ## name ## _zone, s); \ } \ s = next; \ } \ } while (0) /* * Protect access to expired lists with DYN_EXPIRED_LOCK. * Userland can invoke ipfw_expire_dyn_states() to delete * specific states, this will lead to modification of expired * lists. * * XXXAE: do we need DYN_EXPIRED_LOCK? We can just use * IPFW_UH_WLOCK to protect access to these lists. */ DYN_EXPIRED_LOCK(); DYN_FREE_STATES(s4, s4n, ipv4); #ifdef INET6 DYN_FREE_STATES(s6, s6n, ipv6); #endif DYN_EXPIRED_UNLOCK(); #undef DYN_FREE_STATES } /* * Returns 1 when state is matched by specified range, otherwise returns 0. */ static int dyn_match_range(uint16_t rulenum, uint8_t set, const ipfw_range_tlv *rt) { MPASS(rt != NULL); /* flush all states */ if (rt->flags & IPFW_RCFLAG_ALL) return (1); if ((rt->flags & IPFW_RCFLAG_SET) != 0 && set != rt->set) return (0); if ((rt->flags & IPFW_RCFLAG_RANGE) != 0 && (rulenum < rt->start_rule || rulenum > rt->end_rule)) return (0); return (1); } +static void +dyn_acquire_rule(struct ip_fw_chain *ch, struct dyn_data *data, + struct ip_fw *rule, uint16_t kidx) +{ + struct dyn_state_obj *obj; + + /* + * Do not acquire reference twice. + * This can happen when rule deletion executed for + * the same range, but different ruleset id. + */ + if (data->flags & DYN_REFERENCED) + return; + + IPFW_UH_WLOCK_ASSERT(ch); + MPASS(kidx != 0); + + data->flags |= DYN_REFERENCED; + /* Reference the named object */ + obj = SRV_OBJECT(ch, kidx); + obj->no.refcnt++; + MPASS(obj->no.etlv == IPFW_TLV_STATE_NAME); + + /* Reference the parent rule */ + rule->refcnt++; +} + +static void +dyn_release_rule(struct ip_fw_chain *ch, struct dyn_data *data, + struct ip_fw *rule, uint16_t kidx) +{ + struct dyn_state_obj *obj; + + IPFW_UH_WLOCK_ASSERT(ch); + MPASS(kidx != 0); + + obj = SRV_OBJECT(ch, kidx); + if (obj->no.refcnt == 1) + dyn_destroy(ch, &obj->no); + else + obj->no.refcnt--; + + if (--rule->refcnt == 1) + ipfw_free_rule(rule); +} + +/* + * We do not keep O_LIMIT_PARENT states when V_dyn_keep_states is enabled. + * O_LIMIT state is created when new connection is going to be established + * and there is no matching state. So, since the old parent rule was deleted + * we can't create new states with old parent, and thus we can not account + * new connections with already established connections, and can not do + * proper limiting. + */ static int -dyn_match_ipv4_state(struct dyn_ipv4_state *s, const ipfw_range_tlv *rt) +dyn_match_ipv4_state(struct ip_fw_chain *ch, struct dyn_ipv4_state *s, + const ipfw_range_tlv *rt) { + struct ip_fw *rule; + int ret; if (s->type == O_LIMIT_PARENT) return (dyn_match_range(s->limit->rulenum, s->limit->set, rt)); - if (s->type == O_LIMIT) - return (dyn_match_range(s->data->rulenum, s->data->set, rt)); + ret = dyn_match_range(s->data->rulenum, s->data->set, rt); + if (ret == 0 || V_dyn_keep_states == 0) + return (ret); - if (V_dyn_keep_states == 0 && - dyn_match_range(s->data->rulenum, s->data->set, rt)) - return (1); - + rule = s->data->parent; + if (s->type == O_LIMIT) + rule = ((struct dyn_ipv4_state *)rule)->limit->parent; + dyn_acquire_rule(ch, s->data, rule, s->kidx); return (0); } #ifdef INET6 static int -dyn_match_ipv6_state(struct dyn_ipv6_state *s, const ipfw_range_tlv *rt) +dyn_match_ipv6_state(struct ip_fw_chain *ch, struct dyn_ipv6_state *s, + const ipfw_range_tlv *rt) { + struct ip_fw *rule; + int ret; if (s->type == O_LIMIT_PARENT) return (dyn_match_range(s->limit->rulenum, s->limit->set, rt)); - if (s->type == O_LIMIT) - return (dyn_match_range(s->data->rulenum, s->data->set, rt)); + ret = dyn_match_range(s->data->rulenum, s->data->set, rt); + if (ret == 0 || V_dyn_keep_states == 0) + return (ret); - if (V_dyn_keep_states == 0 && - dyn_match_range(s->data->rulenum, s->data->set, rt)) - return (1); - + rule = s->data->parent; + if (s->type == O_LIMIT) + rule = ((struct dyn_ipv6_state *)rule)->limit->parent; + dyn_acquire_rule(ch, s->data, rule, s->kidx); return (0); } #endif /* * Unlink expired entries from states lists. * @rt can be used to specify the range of states for deletion. */ static void -dyn_expire_states(struct ip_fw_chain *chain, ipfw_range_tlv *rt) +dyn_expire_states(struct ip_fw_chain *ch, ipfw_range_tlv *rt) { struct dyn_ipv4_slist expired_ipv4; #ifdef INET6 struct dyn_ipv6_slist expired_ipv6; struct dyn_ipv6_state *s6, *s6n, *s6p; #endif struct dyn_ipv4_state *s4, *s4n, *s4p; + void *rule; int bucket, removed, length, max_length; + IPFW_UH_WLOCK_ASSERT(ch); + /* * Unlink expired states from each bucket. * With acquired bucket lock iterate entries of each lists: * ipv4, ipv4_parent, ipv6, and ipv6_parent. Check expired time * and unlink entry from the list, link entry into temporary * expired_xxx lists then bump "del" bucket version. * * When an entry is removed, corresponding states counter is * decremented. If entry has O_LIMIT type, parent's reference * counter is decremented. * * NOTE: this function can be called from userspace context * when user deletes rules. In this case all matched states * will be forcedly unlinked. O_LIMIT_PARENT states will be kept * in the expired lists until reference counter become zero. */ #define DYN_UNLINK_STATES(s, prev, next, exp, af, name, extra) do { \ length = 0; \ removed = 0; \ prev = NULL; \ s = CK_SLIST_FIRST(&V_dyn_ ## name [bucket]); \ while (s != NULL) { \ next = CK_SLIST_NEXT(s, entry); \ if ((TIME_LEQ((s)->exp, time_uptime) && extra) || \ - (rt != NULL && dyn_match_ ## af ## _state(s, rt))) {\ + (rt != NULL && \ + dyn_match_ ## af ## _state(ch, s, rt))) { \ if (prev != NULL) \ CK_SLIST_REMOVE_AFTER(prev, entry); \ else \ CK_SLIST_REMOVE_HEAD( \ &V_dyn_ ## name [bucket], entry); \ removed++; \ SLIST_INSERT_HEAD(&expired_ ## af, s, expired); \ if (s->type == O_LIMIT_PARENT) \ DYN_COUNT_DEC(dyn_parent_count); \ else { \ DYN_COUNT_DEC(dyn_count); \ + if (s->data->flags & DYN_REFERENCED) { \ + rule = s->data->parent; \ + if (s->type == O_LIMIT) \ + rule = ((__typeof(s)) \ + rule)->limit->parent;\ + dyn_release_rule(ch, s->data, \ + rule, s->kidx); \ + } \ if (s->type == O_LIMIT) { \ s = s->data->parent; \ DPARENT_COUNT_DEC(s->limit); \ } \ } \ } else { \ prev = s; \ length++; \ } \ s = next; \ } \ if (removed != 0) \ DYN_BUCKET_VERSION_BUMP(bucket, name ## _del); \ if (length > max_length) \ max_length = length; \ } while (0) SLIST_INIT(&expired_ipv4); #ifdef INET6 SLIST_INIT(&expired_ipv6); #endif max_length = 0; for (bucket = 0; bucket < V_curr_dyn_buckets; bucket++) { DYN_BUCKET_LOCK(bucket); DYN_UNLINK_STATES(s4, s4p, s4n, data->expire, ipv4, ipv4, 1); DYN_UNLINK_STATES(s4, s4p, s4n, limit->expire, ipv4, ipv4_parent, (s4->limit->count == 0)); #ifdef INET6 DYN_UNLINK_STATES(s6, s6p, s6n, data->expire, ipv6, ipv6, 1); DYN_UNLINK_STATES(s6, s6p, s6n, limit->expire, ipv6, ipv6_parent, (s6->limit->count == 0)); #endif DYN_BUCKET_UNLOCK(bucket); } /* Update curr_max_length for statistics. */ V_curr_max_length = max_length; /* * Concatenate temporary lists with global expired lists. */ DYN_EXPIRED_LOCK(); SLIST_CONCAT(&V_dyn_expired_ipv4, &expired_ipv4, dyn_ipv4_state, expired); #ifdef INET6 SLIST_CONCAT(&V_dyn_expired_ipv6, &expired_ipv6, dyn_ipv6_state, expired); #endif DYN_EXPIRED_UNLOCK(); #undef DYN_UNLINK_STATES #undef DYN_UNREF_STATES } static struct mbuf * dyn_mgethdr(int len, uint16_t fibnum) { struct mbuf *m; m = m_gethdr(M_NOWAIT, MT_DATA); if (m == NULL) return (NULL); #ifdef MAC mac_netinet_firewall_send(m); #endif M_SETFIB(m, fibnum); m->m_data += max_linkhdr; m->m_flags |= M_SKIP_FIREWALL; m->m_len = m->m_pkthdr.len = len; bzero(m->m_data, len); return (m); } static void dyn_make_keepalive_ipv4(struct mbuf *m, in_addr_t src, in_addr_t dst, uint32_t seq, uint32_t ack, uint16_t sport, uint16_t dport) { struct tcphdr *tcp; struct ip *ip; ip = mtod(m, struct ip *); ip->ip_v = 4; ip->ip_hl = sizeof(*ip) >> 2; ip->ip_tos = IPTOS_LOWDELAY; ip->ip_len = htons(m->m_len); ip->ip_off |= htons(IP_DF); ip->ip_ttl = V_ip_defttl; ip->ip_p = IPPROTO_TCP; ip->ip_src.s_addr = htonl(src); ip->ip_dst.s_addr = htonl(dst); tcp = mtodo(m, sizeof(struct ip)); tcp->th_sport = htons(sport); tcp->th_dport = htons(dport); tcp->th_off = sizeof(struct tcphdr) >> 2; tcp->th_seq = htonl(seq); tcp->th_ack = htonl(ack); tcp->th_flags = TH_ACK; tcp->th_sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, htons(sizeof(struct tcphdr) + IPPROTO_TCP)); m->m_pkthdr.csum_data = offsetof(struct tcphdr, th_sum); m->m_pkthdr.csum_flags = CSUM_TCP; } static void dyn_enqueue_keepalive_ipv4(struct mbufq *q, const struct dyn_ipv4_state *s) { struct mbuf *m; if ((s->data->state & ACK_FWD) == 0 && s->data->ack_fwd > 0) { m = dyn_mgethdr(sizeof(struct ip) + sizeof(struct tcphdr), s->data->fibnum); if (m != NULL) { dyn_make_keepalive_ipv4(m, s->dst, s->src, s->data->ack_fwd - 1, s->data->ack_rev, s->dport, s->sport); if (mbufq_enqueue(q, m)) { m_freem(m); log(LOG_DEBUG, "ipfw: limit for IPv4 " "keepalive queue is reached.\n"); return; } } } if ((s->data->state & ACK_REV) == 0 && s->data->ack_rev > 0) { m = dyn_mgethdr(sizeof(struct ip) + sizeof(struct tcphdr), s->data->fibnum); if (m != NULL) { dyn_make_keepalive_ipv4(m, s->src, s->dst, s->data->ack_rev - 1, s->data->ack_fwd, s->sport, s->dport); if (mbufq_enqueue(q, m)) { m_freem(m); log(LOG_DEBUG, "ipfw: limit for IPv4 " "keepalive queue is reached.\n"); return; } } } } /* * Prepare and send keep-alive packets. */ static void dyn_send_keepalive_ipv4(struct ip_fw_chain *chain) { struct mbufq q; struct mbuf *m; struct dyn_ipv4_state *s; uint32_t bucket; mbufq_init(&q, INT_MAX); IPFW_UH_RLOCK(chain); /* * It is safe to not use hazard pointer and just do lockless * access to the lists, because states entries can not be deleted * while we hold IPFW_UH_RLOCK. */ for (bucket = 0; bucket < V_curr_dyn_buckets; bucket++) { CK_SLIST_FOREACH(s, &V_dyn_ipv4[bucket], entry) { /* * Only established TCP connections that will * become expired withing dyn_keepalive_interval. */ if (s->proto != IPPROTO_TCP || (s->data->state & BOTH_SYN) != BOTH_SYN || TIME_LEQ(time_uptime + V_dyn_keepalive_interval, s->data->expire)) continue; dyn_enqueue_keepalive_ipv4(&q, s); } } IPFW_UH_RUNLOCK(chain); while ((m = mbufq_dequeue(&q)) != NULL) ip_output(m, NULL, NULL, 0, NULL, NULL); } #ifdef INET6 static void dyn_make_keepalive_ipv6(struct mbuf *m, const struct in6_addr *src, const struct in6_addr *dst, uint32_t zoneid, uint32_t seq, uint32_t ack, uint16_t sport, uint16_t dport) { struct tcphdr *tcp; struct ip6_hdr *ip6; ip6 = mtod(m, struct ip6_hdr *); ip6->ip6_vfc |= IPV6_VERSION; ip6->ip6_plen = htons(sizeof(struct tcphdr)); ip6->ip6_nxt = IPPROTO_TCP; ip6->ip6_hlim = IPV6_DEFHLIM; ip6->ip6_src = *src; if (IN6_IS_ADDR_LINKLOCAL(src)) ip6->ip6_src.s6_addr16[1] = htons(zoneid & 0xffff); ip6->ip6_dst = *dst; if (IN6_IS_ADDR_LINKLOCAL(dst)) ip6->ip6_dst.s6_addr16[1] = htons(zoneid & 0xffff); tcp = mtodo(m, sizeof(struct ip6_hdr)); tcp->th_sport = htons(sport); tcp->th_dport = htons(dport); tcp->th_off = sizeof(struct tcphdr) >> 2; tcp->th_seq = htonl(seq); tcp->th_ack = htonl(ack); tcp->th_flags = TH_ACK; tcp->th_sum = in6_cksum_pseudo(ip6, sizeof(struct tcphdr), IPPROTO_TCP, 0); m->m_pkthdr.csum_data = offsetof(struct tcphdr, th_sum); m->m_pkthdr.csum_flags = CSUM_TCP_IPV6; } static void dyn_enqueue_keepalive_ipv6(struct mbufq *q, const struct dyn_ipv6_state *s) { struct mbuf *m; if ((s->data->state & ACK_FWD) == 0 && s->data->ack_fwd > 0) { m = dyn_mgethdr(sizeof(struct ip6_hdr) + sizeof(struct tcphdr), s->data->fibnum); if (m != NULL) { dyn_make_keepalive_ipv6(m, &s->dst, &s->src, s->zoneid, s->data->ack_fwd - 1, s->data->ack_rev, s->dport, s->sport); if (mbufq_enqueue(q, m)) { m_freem(m); log(LOG_DEBUG, "ipfw: limit for IPv6 " "keepalive queue is reached.\n"); return; } } } if ((s->data->state & ACK_REV) == 0 && s->data->ack_rev > 0) { m = dyn_mgethdr(sizeof(struct ip6_hdr) + sizeof(struct tcphdr), s->data->fibnum); if (m != NULL) { dyn_make_keepalive_ipv6(m, &s->src, &s->dst, s->zoneid, s->data->ack_rev - 1, s->data->ack_fwd, s->sport, s->dport); if (mbufq_enqueue(q, m)) { m_freem(m); log(LOG_DEBUG, "ipfw: limit for IPv6 " "keepalive queue is reached.\n"); return; } } } } static void dyn_send_keepalive_ipv6(struct ip_fw_chain *chain) { struct mbufq q; struct mbuf *m; struct dyn_ipv6_state *s; uint32_t bucket; mbufq_init(&q, INT_MAX); IPFW_UH_RLOCK(chain); /* * It is safe to not use hazard pointer and just do lockless * access to the lists, because states entries can not be deleted * while we hold IPFW_UH_RLOCK. */ for (bucket = 0; bucket < V_curr_dyn_buckets; bucket++) { CK_SLIST_FOREACH(s, &V_dyn_ipv6[bucket], entry) { /* * Only established TCP connections that will * become expired withing dyn_keepalive_interval. */ if (s->proto != IPPROTO_TCP || (s->data->state & BOTH_SYN) != BOTH_SYN || TIME_LEQ(time_uptime + V_dyn_keepalive_interval, s->data->expire)) continue; dyn_enqueue_keepalive_ipv6(&q, s); } } IPFW_UH_RUNLOCK(chain); while ((m = mbufq_dequeue(&q)) != NULL) ip6_output(m, NULL, NULL, 0, NULL, NULL, NULL); } #endif /* INET6 */ static void dyn_grow_hashtable(struct ip_fw_chain *chain, uint32_t new) { #ifdef INET6 struct dyn_ipv6ck_slist *ipv6, *ipv6_parent; uint32_t *ipv6_add, *ipv6_del, *ipv6_parent_add, *ipv6_parent_del; struct dyn_ipv6_state *s6; #endif struct dyn_ipv4ck_slist *ipv4, *ipv4_parent; uint32_t *ipv4_add, *ipv4_del, *ipv4_parent_add, *ipv4_parent_del; struct dyn_ipv4_state *s4; struct mtx *bucket_lock; void *tmp; uint32_t bucket; MPASS(powerof2(new)); DYN_DEBUG("grow hash size %u -> %u", V_curr_dyn_buckets, new); /* * Allocate and initialize new lists. * XXXAE: on memory pressure this can disable callout timer. */ bucket_lock = malloc(new * sizeof(struct mtx), M_IPFW, M_WAITOK | M_ZERO); ipv4 = malloc(new * sizeof(struct dyn_ipv4ck_slist), M_IPFW, M_WAITOK | M_ZERO); ipv4_parent = malloc(new * sizeof(struct dyn_ipv4ck_slist), M_IPFW, M_WAITOK | M_ZERO); ipv4_add = malloc(new * sizeof(uint32_t), M_IPFW, M_WAITOK | M_ZERO); ipv4_del = malloc(new * sizeof(uint32_t), M_IPFW, M_WAITOK | M_ZERO); ipv4_parent_add = malloc(new * sizeof(uint32_t), M_IPFW, M_WAITOK | M_ZERO); ipv4_parent_del = malloc(new * sizeof(uint32_t), M_IPFW, M_WAITOK | M_ZERO); #ifdef INET6 ipv6 = malloc(new * sizeof(struct dyn_ipv6ck_slist), M_IPFW, M_WAITOK | M_ZERO); ipv6_parent = malloc(new * sizeof(struct dyn_ipv6ck_slist), M_IPFW, M_WAITOK | M_ZERO); ipv6_add = malloc(new * sizeof(uint32_t), M_IPFW, M_WAITOK | M_ZERO); ipv6_del = malloc(new * sizeof(uint32_t), M_IPFW, M_WAITOK | M_ZERO); ipv6_parent_add = malloc(new * sizeof(uint32_t), M_IPFW, M_WAITOK | M_ZERO); ipv6_parent_del = malloc(new * sizeof(uint32_t), M_IPFW, M_WAITOK | M_ZERO); #endif for (bucket = 0; bucket < new; bucket++) { DYN_BUCKET_LOCK_INIT(bucket_lock, bucket); CK_SLIST_INIT(&ipv4[bucket]); CK_SLIST_INIT(&ipv4_parent[bucket]); #ifdef INET6 CK_SLIST_INIT(&ipv6[bucket]); CK_SLIST_INIT(&ipv6_parent[bucket]); #endif } #define DYN_RELINK_STATES(s, hval, i, head, ohead) do { \ while ((s = CK_SLIST_FIRST(&V_dyn_ ## ohead[i])) != NULL) { \ CK_SLIST_REMOVE_HEAD(&V_dyn_ ## ohead[i], entry); \ CK_SLIST_INSERT_HEAD(&head[DYN_BUCKET(s->hval, new)], \ s, entry); \ } \ } while (0) /* * Prevent rules changing from userland. */ IPFW_UH_WLOCK(chain); /* * Hold traffic processing until we finish resize to * prevent access to states lists. */ IPFW_WLOCK(chain); /* Re-link all dynamic states */ for (bucket = 0; bucket < V_curr_dyn_buckets; bucket++) { DYN_RELINK_STATES(s4, data->hashval, bucket, ipv4, ipv4); DYN_RELINK_STATES(s4, limit->hashval, bucket, ipv4_parent, ipv4_parent); #ifdef INET6 DYN_RELINK_STATES(s6, data->hashval, bucket, ipv6, ipv6); DYN_RELINK_STATES(s6, limit->hashval, bucket, ipv6_parent, ipv6_parent); #endif } #define DYN_SWAP_PTR(old, new, tmp) do { \ tmp = old; \ old = new; \ new = tmp; \ } while (0) /* Swap pointers */ DYN_SWAP_PTR(V_dyn_bucket_lock, bucket_lock, tmp); DYN_SWAP_PTR(V_dyn_ipv4, ipv4, tmp); DYN_SWAP_PTR(V_dyn_ipv4_parent, ipv4_parent, tmp); DYN_SWAP_PTR(V_dyn_ipv4_add, ipv4_add, tmp); DYN_SWAP_PTR(V_dyn_ipv4_parent_add, ipv4_parent_add, tmp); DYN_SWAP_PTR(V_dyn_ipv4_del, ipv4_del, tmp); DYN_SWAP_PTR(V_dyn_ipv4_parent_del, ipv4_parent_del, tmp); #ifdef INET6 DYN_SWAP_PTR(V_dyn_ipv6, ipv6, tmp); DYN_SWAP_PTR(V_dyn_ipv6_parent, ipv6_parent, tmp); DYN_SWAP_PTR(V_dyn_ipv6_add, ipv6_add, tmp); DYN_SWAP_PTR(V_dyn_ipv6_parent_add, ipv6_parent_add, tmp); DYN_SWAP_PTR(V_dyn_ipv6_del, ipv6_del, tmp); DYN_SWAP_PTR(V_dyn_ipv6_parent_del, ipv6_parent_del, tmp); #endif bucket = V_curr_dyn_buckets; V_curr_dyn_buckets = new; IPFW_WUNLOCK(chain); IPFW_UH_WUNLOCK(chain); /* Release old resources */ while (bucket-- != 0) DYN_BUCKET_LOCK_DESTROY(bucket_lock, bucket); free(bucket_lock, M_IPFW); free(ipv4, M_IPFW); free(ipv4_parent, M_IPFW); free(ipv4_add, M_IPFW); free(ipv4_parent_add, M_IPFW); free(ipv4_del, M_IPFW); free(ipv4_parent_del, M_IPFW); #ifdef INET6 free(ipv6, M_IPFW); free(ipv6_parent, M_IPFW); free(ipv6_add, M_IPFW); free(ipv6_parent_add, M_IPFW); free(ipv6_del, M_IPFW); free(ipv6_parent_del, M_IPFW); #endif } /* * This function is used to perform various maintenance * on dynamic hash lists. Currently it is called every second. */ static void dyn_tick(void *vnetx) { uint32_t buckets; CURVNET_SET((struct vnet *)vnetx); /* * First free states unlinked in previous passes. */ dyn_free_states(&V_layer3_chain); /* * Now unlink others expired states. * We use IPFW_UH_WLOCK to avoid concurrent call of * dyn_expire_states(). It is the only function that does * deletion of state entries from states lists. */ IPFW_UH_WLOCK(&V_layer3_chain); dyn_expire_states(&V_layer3_chain, NULL); IPFW_UH_WUNLOCK(&V_layer3_chain); /* * Send keepalives if they are enabled and the time has come. */ if (V_dyn_keepalive != 0 && V_dyn_keepalive_last + V_dyn_keepalive_period <= time_uptime) { V_dyn_keepalive_last = time_uptime; dyn_send_keepalive_ipv4(&V_layer3_chain); #ifdef INET6 dyn_send_keepalive_ipv6(&V_layer3_chain); #endif } /* * Check if we need to resize the hash: * if current number of states exceeds number of buckets in hash, * and dyn_buckets_max permits to grow the number of buckets, then * do it. Grow hash size to the minimum power of 2 which is bigger * than current states count. */ if (V_curr_dyn_buckets < V_dyn_buckets_max && (V_curr_dyn_buckets < V_dyn_count / 2 || ( V_curr_dyn_buckets < V_dyn_count && V_curr_max_length > 8))) { buckets = 1 << fls(V_dyn_count); if (buckets > V_dyn_buckets_max) buckets = V_dyn_buckets_max; dyn_grow_hashtable(&V_layer3_chain, buckets); } callout_reset_on(&V_dyn_timeout, hz, dyn_tick, vnetx, 0); CURVNET_RESTORE(); } void ipfw_expire_dyn_states(struct ip_fw_chain *chain, ipfw_range_tlv *rt) { /* * Do not perform any checks if we currently have no dynamic states */ if (V_dyn_count == 0) return; IPFW_UH_WLOCK_ASSERT(chain); dyn_expire_states(chain, rt); } /* + * Pass through all states and reset eaction for orphaned rules. + */ +void +ipfw_dyn_reset_eaction(struct ip_fw_chain *ch, uint16_t eaction_id, + uint16_t default_id, uint16_t instance_id) +{ +#ifdef INET6 + struct dyn_ipv6_state *s6; +#endif + struct dyn_ipv4_state *s4; + struct ip_fw *rule; + uint32_t bucket; + +#define DYN_RESET_EACTION(s, h, b) \ + CK_SLIST_FOREACH(s, &V_dyn_ ## h[b], entry) { \ + if ((s->data->flags & DYN_REFERENCED) == 0) \ + continue; \ + rule = s->data->parent; \ + if (s->type == O_LIMIT) \ + rule = ((__typeof(s))rule)->limit->parent; \ + ipfw_reset_eaction(ch, rule, eaction_id, \ + default_id, instance_id); \ + } + + IPFW_UH_WLOCK_ASSERT(ch); + if (V_dyn_count == 0) + return; + for (bucket = 0; bucket < V_curr_dyn_buckets; bucket++) { + DYN_RESET_EACTION(s4, ipv4, bucket); +#ifdef INET6 + DYN_RESET_EACTION(s6, ipv6, bucket); +#endif + } +} + +/* * Returns size of dynamic states in legacy format */ int ipfw_dyn_len(void) { return ((V_dyn_count + V_dyn_parent_count) * sizeof(ipfw_dyn_rule)); } /* * Returns number of dynamic states. + * Marks every named object index used by dynamic states with bit in @bmask. + * Returns number of named objects accounted in bmask via @nocnt. * Used by dump format v1 (current). */ uint32_t -ipfw_dyn_get_count(void) +ipfw_dyn_get_count(uint32_t *bmask, int *nocnt) { +#ifdef INET6 + struct dyn_ipv6_state *s6; +#endif + struct dyn_ipv4_state *s4; + uint32_t bucket; + +#define DYN_COUNT_OBJECTS(s, h, b) \ + CK_SLIST_FOREACH(s, &V_dyn_ ## h[b], entry) { \ + MPASS(s->kidx != 0); \ + if (ipfw_mark_object_kidx(bmask, IPFW_TLV_STATE_NAME, \ + s->kidx) != 0) \ + (*nocnt)++; \ + } + + IPFW_UH_RLOCK_ASSERT(&V_layer3_chain); + + /* No need to pass through all the buckets. */ + *nocnt = 0; + if (V_dyn_count + V_dyn_parent_count == 0) + return (0); + + for (bucket = 0; bucket < V_curr_dyn_buckets; bucket++) { + DYN_COUNT_OBJECTS(s4, ipv4, bucket); +#ifdef INET6 + DYN_COUNT_OBJECTS(s6, ipv6, bucket); +#endif + } return (V_dyn_count + V_dyn_parent_count); } /* * Check if rule contains at least one dynamic opcode. * * Returns 1 if such opcode is found, 0 otherwise. */ int ipfw_is_dyn_rule(struct ip_fw *rule) { int cmdlen, l; ipfw_insn *cmd; l = rule->cmd_len; cmd = rule->cmd; cmdlen = 0; for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); switch (cmd->opcode) { case O_LIMIT: case O_KEEP_STATE: case O_PROBE_STATE: case O_CHECK_STATE: return (1); } } return (0); } static void dyn_export_parent(const struct dyn_parent *p, uint16_t kidx, ipfw_dyn_rule *dst) { dst->dyn_type = O_LIMIT_PARENT; dst->kidx = kidx; dst->count = (uint16_t)DPARENT_COUNT(p); dst->expire = TIME_LEQ(p->expire, time_uptime) ? 0: p->expire - time_uptime; /* 'rule' is used to pass up the rule number and set */ memcpy(&dst->rule, &p->rulenum, sizeof(p->rulenum)); /* store set number into high word of dst->rule pointer. */ memcpy((char *)&dst->rule + sizeof(p->rulenum), &p->set, sizeof(p->set)); /* unused fields */ dst->pcnt = 0; dst->bcnt = 0; dst->parent = NULL; dst->state = 0; dst->ack_fwd = 0; dst->ack_rev = 0; dst->bucket = p->hashval; /* * The legacy userland code will interpret a NULL here as a marker * for the last dynamic rule. */ dst->next = (ipfw_dyn_rule *)1; } static void dyn_export_data(const struct dyn_data *data, uint16_t kidx, uint8_t type, ipfw_dyn_rule *dst) { dst->dyn_type = type; dst->kidx = kidx; dst->pcnt = data->pcnt_fwd + data->pcnt_rev; dst->bcnt = data->bcnt_fwd + data->bcnt_rev; dst->expire = TIME_LEQ(data->expire, time_uptime) ? 0: data->expire - time_uptime; /* 'rule' is used to pass up the rule number and set */ memcpy(&dst->rule, &data->rulenum, sizeof(data->rulenum)); /* store set number into high word of dst->rule pointer. */ memcpy((char *)&dst->rule + sizeof(data->rulenum), &data->set, sizeof(data->set)); /* unused fields */ dst->parent = NULL; dst->state = data->state; dst->ack_fwd = data->ack_fwd; dst->ack_rev = data->ack_rev; dst->count = 0; dst->bucket = data->hashval; /* * The legacy userland code will interpret a NULL here as a marker * for the last dynamic rule. */ dst->next = (ipfw_dyn_rule *)1; } static void dyn_export_ipv4_state(const struct dyn_ipv4_state *s, ipfw_dyn_rule *dst) { switch (s->type) { case O_LIMIT_PARENT: dyn_export_parent(s->limit, s->kidx, dst); break; default: dyn_export_data(s->data, s->kidx, s->type, dst); } dst->id.dst_ip = s->dst; dst->id.src_ip = s->src; dst->id.dst_port = s->dport; dst->id.src_port = s->sport; dst->id.fib = s->data->fibnum; dst->id.proto = s->proto; dst->id._flags = 0; dst->id.addr_type = 4; memset(&dst->id.dst_ip6, 0, sizeof(dst->id.dst_ip6)); memset(&dst->id.src_ip6, 0, sizeof(dst->id.src_ip6)); dst->id.flow_id6 = dst->id.extra = 0; } #ifdef INET6 static void dyn_export_ipv6_state(const struct dyn_ipv6_state *s, ipfw_dyn_rule *dst) { switch (s->type) { case O_LIMIT_PARENT: dyn_export_parent(s->limit, s->kidx, dst); break; default: dyn_export_data(s->data, s->kidx, s->type, dst); } dst->id.src_ip6 = s->src; dst->id.dst_ip6 = s->dst; dst->id.dst_port = s->dport; dst->id.src_port = s->sport; dst->id.fib = s->data->fibnum; dst->id.proto = s->proto; dst->id._flags = 0; dst->id.addr_type = 6; dst->id.dst_ip = dst->id.src_ip = 0; dst->id.flow_id6 = dst->id.extra = 0; } #endif /* INET6 */ /* * Fills the buffer given by @sd with dynamic states. * Used by dump format v1 (current). * * Returns 0 on success. */ int ipfw_dump_states(struct ip_fw_chain *chain, struct sockopt_data *sd) { #ifdef INET6 struct dyn_ipv6_state *s6; #endif struct dyn_ipv4_state *s4; ipfw_obj_dyntlv *dst, *last; ipfw_obj_ctlv *ctlv; uint32_t bucket; if (V_dyn_count == 0) return (0); /* * IPFW_UH_RLOCK garantees that another userland request * and callout thread will not delete entries from states * lists. */ IPFW_UH_RLOCK_ASSERT(chain); ctlv = (ipfw_obj_ctlv *)ipfw_get_sopt_space(sd, sizeof(*ctlv)); if (ctlv == NULL) return (ENOMEM); ctlv->head.type = IPFW_TLV_DYNSTATE_LIST; ctlv->objsize = sizeof(ipfw_obj_dyntlv); last = NULL; #define DYN_EXPORT_STATES(s, af, h, b) \ CK_SLIST_FOREACH(s, &V_dyn_ ## h[b], entry) { \ dst = (ipfw_obj_dyntlv *)ipfw_get_sopt_space(sd, \ sizeof(ipfw_obj_dyntlv)); \ if (dst == NULL) \ return (ENOMEM); \ dyn_export_ ## af ## _state(s, &dst->state); \ dst->head.length = sizeof(ipfw_obj_dyntlv); \ dst->head.type = IPFW_TLV_DYN_ENT; \ last = dst; \ } for (bucket = 0; bucket < V_curr_dyn_buckets; bucket++) { DYN_EXPORT_STATES(s4, ipv4, ipv4_parent, bucket); DYN_EXPORT_STATES(s4, ipv4, ipv4, bucket); #ifdef INET6 DYN_EXPORT_STATES(s6, ipv6, ipv6_parent, bucket); DYN_EXPORT_STATES(s6, ipv6, ipv6, bucket); #endif /* INET6 */ } /* mark last dynamic rule */ if (last != NULL) last->head.flags = IPFW_DF_LAST; /* XXX: unused */ return (0); #undef DYN_EXPORT_STATES } /* * Fill given buffer with dynamic states (legacy format). * IPFW_UH_RLOCK has to be held while calling. */ void ipfw_get_dynamic(struct ip_fw_chain *chain, char **pbp, const char *ep) { #ifdef INET6 struct dyn_ipv6_state *s6; #endif struct dyn_ipv4_state *s4; ipfw_dyn_rule *p, *last = NULL; char *bp; uint32_t bucket; if (V_dyn_count == 0) return; bp = *pbp; IPFW_UH_RLOCK_ASSERT(chain); #define DYN_EXPORT_STATES(s, af, head, b) \ CK_SLIST_FOREACH(s, &V_dyn_ ## head[b], entry) { \ if (bp + sizeof(*p) > ep) \ break; \ p = (ipfw_dyn_rule *)bp; \ dyn_export_ ## af ## _state(s, p); \ last = p; \ bp += sizeof(*p); \ } for (bucket = 0; bucket < V_curr_dyn_buckets; bucket++) { DYN_EXPORT_STATES(s4, ipv4, ipv4_parent, bucket); DYN_EXPORT_STATES(s4, ipv4, ipv4, bucket); #ifdef INET6 DYN_EXPORT_STATES(s6, ipv6, ipv6_parent, bucket); DYN_EXPORT_STATES(s6, ipv6, ipv6, bucket); #endif /* INET6 */ } if (last != NULL) /* mark last dynamic rule */ last->next = NULL; *pbp = bp; #undef DYN_EXPORT_STATES } void ipfw_dyn_init(struct ip_fw_chain *chain) { #ifdef IPFIREWALL_JENKINSHASH V_dyn_hashseed = arc4random(); #endif V_dyn_max = 16384; /* max # of states */ V_dyn_parent_max = 4096; /* max # of parent states */ V_dyn_buckets_max = 8192; /* must be power of 2 */ V_dyn_ack_lifetime = 300; V_dyn_syn_lifetime = 20; V_dyn_fin_lifetime = 1; V_dyn_rst_lifetime = 1; V_dyn_udp_lifetime = 10; V_dyn_short_lifetime = 5; V_dyn_keepalive_interval = 20; V_dyn_keepalive_period = 5; V_dyn_keepalive = 1; /* send keepalives */ V_dyn_keepalive_last = time_uptime; V_dyn_data_zone = uma_zcreate("IPFW dynamic states data", sizeof(struct dyn_data), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); uma_zone_set_max(V_dyn_data_zone, V_dyn_max); V_dyn_parent_zone = uma_zcreate("IPFW parent dynamic states", sizeof(struct dyn_parent), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); uma_zone_set_max(V_dyn_parent_zone, V_dyn_parent_max); SLIST_INIT(&V_dyn_expired_ipv4); V_dyn_ipv4 = NULL; V_dyn_ipv4_parent = NULL; V_dyn_ipv4_zone = uma_zcreate("IPFW IPv4 dynamic states", sizeof(struct dyn_ipv4_state), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); #ifdef INET6 SLIST_INIT(&V_dyn_expired_ipv6); V_dyn_ipv6 = NULL; V_dyn_ipv6_parent = NULL; V_dyn_ipv6_zone = uma_zcreate("IPFW IPv6 dynamic states", sizeof(struct dyn_ipv6_state), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); #endif /* Initialize buckets. */ V_curr_dyn_buckets = 0; V_dyn_bucket_lock = NULL; dyn_grow_hashtable(chain, 256); if (IS_DEFAULT_VNET(curvnet)) dyn_hp_cache = malloc(mp_ncpus * sizeof(void *), M_IPFW, M_WAITOK | M_ZERO); DYN_EXPIRED_LOCK_INIT(); callout_init(&V_dyn_timeout, 1); callout_reset(&V_dyn_timeout, hz, dyn_tick, curvnet); IPFW_ADD_OBJ_REWRITER(IS_DEFAULT_VNET(curvnet), dyn_opcodes); } void ipfw_dyn_uninit(int pass) { #ifdef INET6 struct dyn_ipv6_state *s6; #endif struct dyn_ipv4_state *s4; int bucket; if (pass == 0) { callout_drain(&V_dyn_timeout); return; } IPFW_DEL_OBJ_REWRITER(IS_DEFAULT_VNET(curvnet), dyn_opcodes); DYN_EXPIRED_LOCK_DESTROY(); #define DYN_FREE_STATES_FORCED(CK, s, af, name, en) do { \ while ((s = CK ## SLIST_FIRST(&V_dyn_ ## name)) != NULL) { \ CK ## SLIST_REMOVE_HEAD(&V_dyn_ ## name, en); \ if (s->type == O_LIMIT_PARENT) \ uma_zfree(V_dyn_parent_zone, s->limit); \ else \ uma_zfree(V_dyn_data_zone, s->data); \ uma_zfree(V_dyn_ ## af ## _zone, s); \ } \ } while (0) for (bucket = 0; bucket < V_curr_dyn_buckets; bucket++) { DYN_BUCKET_LOCK_DESTROY(V_dyn_bucket_lock, bucket); DYN_FREE_STATES_FORCED(CK_, s4, ipv4, ipv4[bucket], entry); DYN_FREE_STATES_FORCED(CK_, s4, ipv4, ipv4_parent[bucket], entry); #ifdef INET6 DYN_FREE_STATES_FORCED(CK_, s6, ipv6, ipv6[bucket], entry); DYN_FREE_STATES_FORCED(CK_, s6, ipv6, ipv6_parent[bucket], entry); #endif /* INET6 */ } DYN_FREE_STATES_FORCED(, s4, ipv4, expired_ipv4, expired); #ifdef INET6 DYN_FREE_STATES_FORCED(, s6, ipv6, expired_ipv6, expired); #endif #undef DYN_FREE_STATES_FORCED uma_zdestroy(V_dyn_ipv4_zone); uma_zdestroy(V_dyn_data_zone); uma_zdestroy(V_dyn_parent_zone); #ifdef INET6 uma_zdestroy(V_dyn_ipv6_zone); free(V_dyn_ipv6, M_IPFW); free(V_dyn_ipv6_parent, M_IPFW); free(V_dyn_ipv6_add, M_IPFW); free(V_dyn_ipv6_parent_add, M_IPFW); free(V_dyn_ipv6_del, M_IPFW); free(V_dyn_ipv6_parent_del, M_IPFW); #endif free(V_dyn_bucket_lock, M_IPFW); free(V_dyn_ipv4, M_IPFW); free(V_dyn_ipv4_parent, M_IPFW); free(V_dyn_ipv4_add, M_IPFW); free(V_dyn_ipv4_parent_add, M_IPFW); free(V_dyn_ipv4_del, M_IPFW); free(V_dyn_ipv4_parent_del, M_IPFW); if (IS_DEFAULT_VNET(curvnet)) free(dyn_hp_cache, M_IPFW); } Index: head/sys/netpfil/ipfw/ip_fw_eaction.c =================================================================== --- head/sys/netpfil/ipfw/ip_fw_eaction.c (revision 341470) +++ head/sys/netpfil/ipfw/ip_fw_eaction.c (revision 341471) @@ -1,386 +1,441 @@ /*- * Copyright (c) 2016-2017 Yandex LLC * Copyright (c) 2016-2017 Andrey V. Elsukov * * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include /* ip_fw.h requires IFNAMSIZ */ #include #include /* struct ipfw_rule_ref */ #include #include #include "opt_ipfw.h" /* * External actions support for ipfw. * * This code provides KPI for implementing loadable modules, that * can provide handlers for external action opcodes in the ipfw's * rules. * Module should implement opcode handler with type ipfw_eaction_t. * This handler will be called by ipfw_chk() function when * O_EXTERNAL_ACTION opcode is matched. The handler must return * value used as return value in ipfw_chk(), i.e. IP_FW_PASS, * IP_FW_DENY (see ip_fw_private.h). * Also the last argument must be set by handler. If it is zero, * the search continues to the next rule. If it has non zero value, * the search terminates. * * The module that implements external action should register its * handler and name with ipfw_add_eaction() function. * This function will return eaction_id, that can be used by module. * * It is possible to pass some additional information to external * action handler using O_EXTERNAL_INSTANCE and O_EXTERNAL_DATA opcodes. * Such opcodes should be next after the O_EXTERNAL_ACTION opcode. * For the O_EXTERNAL_INSTANCE opcode the cmd->arg1 contains index of named * object related to an instance of external action. * For the O_EXTERNAL_DATA opcode the cmd contains the data that can be used * by external action handler without needing to create named instance. * * In case when eaction module uses named instances, it should register * opcode rewriting routines for O_EXTERNAL_INSTANCE opcode. The * classifier callback can look back into O_EXTERNAL_ACTION opcode (it * must be in the (ipfw_insn *)(cmd - 1)). By arg1 from O_EXTERNAL_ACTION * it can deteremine eaction_id and compare it with its own. * The macro IPFW_TLV_EACTION_NAME(eaction_id) can be used to deteremine * the type of named_object related to external action instance. * * On module unload handler should be deregistered with ipfw_del_eaction() * function using known eaction_id. */ struct eaction_obj { struct named_object no; ipfw_eaction_t *handler; char name[64]; }; #define EACTION_OBJ(ch, cmd) \ ((struct eaction_obj *)SRV_OBJECT((ch), (cmd)->arg1)) #if 0 #define EACTION_DEBUG(fmt, ...) do { \ printf("%s: " fmt "\n", __func__, ## __VA_ARGS__); \ } while (0) #else #define EACTION_DEBUG(fmt, ...) #endif const char *default_eaction_typename = "drop"; static int default_eaction(struct ip_fw_chain *ch, struct ip_fw_args *args, ipfw_insn *cmd, int *done) { *done = 1; /* terminate the search */ return (IP_FW_DENY); } /* * Opcode rewriting callbacks. */ static int eaction_classify(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype) { EACTION_DEBUG("opcode %d, arg1 %d", cmd->opcode, cmd->arg1); *puidx = cmd->arg1; *ptype = 0; return (0); } static void eaction_update(ipfw_insn *cmd, uint16_t idx) { cmd->arg1 = idx; EACTION_DEBUG("opcode %d, arg1 -> %d", cmd->opcode, cmd->arg1); } static int eaction_findbyname(struct ip_fw_chain *ch, struct tid_info *ti, struct named_object **pno) { ipfw_obj_ntlv *ntlv; if (ti->tlvs == NULL) return (EINVAL); /* Search ntlv in the buffer provided by user */ ntlv = ipfw_find_name_tlv_type(ti->tlvs, ti->tlen, ti->uidx, IPFW_TLV_EACTION); if (ntlv == NULL) return (EINVAL); EACTION_DEBUG("name %s, uidx %u, type %u", ntlv->name, ti->uidx, ti->type); /* * Search named object with corresponding name. * Since eaction objects are global - ignore the set value * and use zero instead. */ *pno = ipfw_objhash_lookup_name_type(CHAIN_TO_SRV(ch), 0, IPFW_TLV_EACTION, ntlv->name); if (*pno == NULL) return (ESRCH); return (0); } static struct named_object * eaction_findbykidx(struct ip_fw_chain *ch, uint16_t idx) { EACTION_DEBUG("kidx %u", idx); return (ipfw_objhash_lookup_kidx(CHAIN_TO_SRV(ch), idx)); } static struct opcode_obj_rewrite eaction_opcodes[] = { { .opcode = O_EXTERNAL_ACTION, .etlv = IPFW_TLV_EACTION, .classifier = eaction_classify, .update = eaction_update, .find_byname = eaction_findbyname, .find_bykidx = eaction_findbykidx, }, }; static int create_eaction_obj(struct ip_fw_chain *ch, ipfw_eaction_t handler, const char *name, uint16_t *eaction_id) { struct namedobj_instance *ni; struct eaction_obj *obj; IPFW_UH_UNLOCK_ASSERT(ch); ni = CHAIN_TO_SRV(ch); obj = malloc(sizeof(*obj), M_IPFW, M_WAITOK | M_ZERO); obj->no.name = obj->name; obj->no.etlv = IPFW_TLV_EACTION; obj->handler = handler; strlcpy(obj->name, name, sizeof(obj->name)); IPFW_UH_WLOCK(ch); if (ipfw_objhash_lookup_name_type(ni, 0, IPFW_TLV_EACTION, name) != NULL) { /* * Object is already created. * We don't allow eactions with the same name. */ IPFW_UH_WUNLOCK(ch); free(obj, M_IPFW); EACTION_DEBUG("External action with typename " "'%s' already exists", name); return (EEXIST); } if (ipfw_objhash_alloc_idx(ni, &obj->no.kidx) != 0) { IPFW_UH_WUNLOCK(ch); free(obj, M_IPFW); EACTION_DEBUG("alloc_idx failed"); return (ENOSPC); } ipfw_objhash_add(ni, &obj->no); IPFW_WLOCK(ch); SRV_OBJECT(ch, obj->no.kidx) = obj; IPFW_WUNLOCK(ch); obj->no.refcnt++; IPFW_UH_WUNLOCK(ch); if (eaction_id != NULL) *eaction_id = obj->no.kidx; return (0); } static void destroy_eaction_obj(struct ip_fw_chain *ch, struct named_object *no) { struct namedobj_instance *ni; struct eaction_obj *obj; IPFW_UH_WLOCK_ASSERT(ch); ni = CHAIN_TO_SRV(ch); IPFW_WLOCK(ch); obj = SRV_OBJECT(ch, no->kidx); SRV_OBJECT(ch, no->kidx) = NULL; IPFW_WUNLOCK(ch); ipfw_objhash_del(ni, no); ipfw_objhash_free_idx(ni, no->kidx); free(obj, M_IPFW); } /* * Resets all eaction opcodes to default handlers. */ static void -reset_eaction_obj(struct ip_fw_chain *ch, uint16_t eaction_id) +reset_eaction_rules(struct ip_fw_chain *ch, uint16_t eaction_id, + uint16_t instance_id, bool reset_rules) { struct named_object *no; - struct ip_fw *rule; - ipfw_insn *cmd; int i; IPFW_UH_WLOCK_ASSERT(ch); no = ipfw_objhash_lookup_name_type(CHAIN_TO_SRV(ch), 0, IPFW_TLV_EACTION, default_eaction_typename); if (no == NULL) panic("Default external action handler is not found"); if (eaction_id == no->kidx) panic("Wrong eaction_id"); - EACTION_DEBUG("replace id %u with %u", eaction_id, no->kidx); + + EACTION_DEBUG("Going to replace id %u with %u", eaction_id, no->kidx); IPFW_WLOCK(ch); - for (i = 0; i < ch->n_rules; i++) { - rule = ch->map[i]; - cmd = ACTION_PTR(rule); - if (cmd->opcode != O_EXTERNAL_ACTION) - continue; - if (cmd->arg1 != eaction_id) - continue; - cmd->arg1 = no->kidx; /* Set to default id */ - /* - * XXX: we only bump refcount on default_eaction. - * Refcount on the original object will be just - * ignored on destroy. But on default_eaction it - * will be decremented on rule deletion. - */ - no->refcnt++; - /* - * Since named_object related to this instance will be - * also destroyed, truncate the chain of opcodes to - * remove the rest of cmd chain just after O_EXTERNAL_ACTION - * opcode. - */ - if (rule->act_ofs < rule->cmd_len - 1) { - EACTION_DEBUG("truncate rule %d: len %u -> %u", - rule->rulenum, rule->cmd_len, rule->act_ofs + 1); - rule->cmd_len = rule->act_ofs + 1; + /* + * Reset eaction objects only if it is referenced by rules. + * But always reset objects for orphaned dynamic states. + */ + if (reset_rules) { + for (i = 0; i < ch->n_rules; i++) { + /* + * Refcount on the original object will be just + * ignored on destroy. Refcount on default_eaction + * will be decremented on rule deletion, thus we + * need to reference default_eaction object. + */ + if (ipfw_reset_eaction(ch, ch->map[i], eaction_id, + no->kidx, instance_id) != 0) + no->refcnt++; } } + /* + * Reset eaction opcodes for orphaned dynamic states. + * Since parent rules are already deleted, we don't need to + * reference named object of default_eaction. + */ + ipfw_dyn_reset_eaction(ch, eaction_id, no->kidx, instance_id); IPFW_WUNLOCK(ch); } /* * Initialize external actions framework. * Create object with default eaction handler "drop". */ int ipfw_eaction_init(struct ip_fw_chain *ch, int first) { int error; error = create_eaction_obj(ch, default_eaction, default_eaction_typename, NULL); if (error != 0) return (error); IPFW_ADD_OBJ_REWRITER(first, eaction_opcodes); EACTION_DEBUG("External actions support initialized"); return (0); } void ipfw_eaction_uninit(struct ip_fw_chain *ch, int last) { struct namedobj_instance *ni; struct named_object *no; ni = CHAIN_TO_SRV(ch); IPFW_UH_WLOCK(ch); no = ipfw_objhash_lookup_name_type(ni, 0, IPFW_TLV_EACTION, default_eaction_typename); if (no != NULL) destroy_eaction_obj(ch, no); IPFW_UH_WUNLOCK(ch); IPFW_DEL_OBJ_REWRITER(last, eaction_opcodes); EACTION_DEBUG("External actions support uninitialized"); } /* * Registers external action handler to the global array. * On success it returns eaction id, otherwise - zero. */ uint16_t ipfw_add_eaction(struct ip_fw_chain *ch, ipfw_eaction_t handler, const char *name) { uint16_t eaction_id; eaction_id = 0; if (ipfw_check_object_name_generic(name) == 0) { create_eaction_obj(ch, handler, name, &eaction_id); EACTION_DEBUG("Registered external action '%s' with id %u", name, eaction_id); } return (eaction_id); } /* * Deregisters external action handler with id eaction_id. */ int ipfw_del_eaction(struct ip_fw_chain *ch, uint16_t eaction_id) { struct named_object *no; IPFW_UH_WLOCK(ch); no = ipfw_objhash_lookup_kidx(CHAIN_TO_SRV(ch), eaction_id); if (no == NULL || no->etlv != IPFW_TLV_EACTION) { IPFW_UH_WUNLOCK(ch); return (EINVAL); } - if (no->refcnt > 1) - reset_eaction_obj(ch, eaction_id); + reset_eaction_rules(ch, eaction_id, 0, (no->refcnt > 1)); EACTION_DEBUG("External action '%s' with id %u unregistered", no->name, eaction_id); destroy_eaction_obj(ch, no); IPFW_UH_WUNLOCK(ch); + return (0); +} + +int +ipfw_reset_eaction(struct ip_fw_chain *ch, struct ip_fw *rule, + uint16_t eaction_id, uint16_t default_id, uint16_t instance_id) +{ + ipfw_insn *cmd, *icmd; + + IPFW_UH_WLOCK_ASSERT(ch); + IPFW_WLOCK_ASSERT(ch); + + cmd = ACTION_PTR(rule); + if (cmd->opcode != O_EXTERNAL_ACTION || + cmd->arg1 != eaction_id) + return (0); + + if (instance_id != 0 && rule->act_ofs < rule->cmd_len - 1) { + icmd = cmd + 1; + if (icmd->opcode != O_EXTERNAL_INSTANCE || + icmd->arg1 != instance_id) + return (0); + /* FALLTHROUGH */ + } + + cmd->arg1 = default_id; /* Set to default id */ + /* + * Since named_object related to this instance will be + * also destroyed, truncate the chain of opcodes to + * remove the rest of cmd chain just after O_EXTERNAL_ACTION + * opcode. + */ + if (rule->act_ofs < rule->cmd_len - 1) { + EACTION_DEBUG("truncate rule %d: len %u -> %u", + rule->rulenum, rule->cmd_len, rule->act_ofs + 1); + rule->cmd_len = rule->act_ofs + 1; + } + /* + * Return 1 when reset successfully happened. + */ + return (1); +} + +/* + * This function should be called before external action instance is + * destroyed. It will reset eaction_id to default_id for rules, where + * eaction has instance with id == kidx. + */ +int +ipfw_reset_eaction_instance(struct ip_fw_chain *ch, uint16_t eaction_id, + uint16_t kidx) +{ + struct named_object *no; + + IPFW_UH_WLOCK_ASSERT(ch); + no = ipfw_objhash_lookup_kidx(CHAIN_TO_SRV(ch), eaction_id); + if (no == NULL || no->etlv != IPFW_TLV_EACTION) + return (EINVAL); + + reset_eaction_rules(ch, eaction_id, kidx, 0); return (0); } int ipfw_run_eaction(struct ip_fw_chain *ch, struct ip_fw_args *args, ipfw_insn *cmd, int *done) { return (EACTION_OBJ(ch, cmd)->handler(ch, args, cmd, done)); } Index: head/sys/netpfil/ipfw/ip_fw_private.h =================================================================== --- head/sys/netpfil/ipfw/ip_fw_private.h (revision 341470) +++ head/sys/netpfil/ipfw/ip_fw_private.h (revision 341471) @@ -1,814 +1,817 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2002-2009 Luigi Rizzo, Universita` di Pisa * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _IPFW2_PRIVATE_H #define _IPFW2_PRIVATE_H /* * Internal constants and data structures used by ipfw components * and not meant to be exported outside the kernel. */ #ifdef _KERNEL /* * For platforms that do not have SYSCTL support, we wrap the * SYSCTL_* into a function (one per file) to collect the values * into an array at module initialization. The wrapping macros, * SYSBEGIN() and SYSEND, are empty in the default case. */ #ifndef SYSBEGIN #define SYSBEGIN(x) #endif #ifndef SYSEND #define SYSEND #endif /* Return values from ipfw_chk() */ enum { IP_FW_PASS = 0, IP_FW_DENY, IP_FW_DIVERT, IP_FW_TEE, IP_FW_DUMMYNET, IP_FW_NETGRAPH, IP_FW_NGTEE, IP_FW_NAT, IP_FW_REASS, }; /* * Structure for collecting parameters to dummynet for ip6_output forwarding */ struct _ip6dn_args { struct ip6_pktopts *opt_or; int flags_or; struct ip6_moptions *im6o_or; struct ifnet *origifp_or; struct ifnet *ifp_or; struct sockaddr_in6 dst_or; u_long mtu_or; }; /* * Arguments for calling ipfw_chk() and dummynet_io(). We put them * all into a structure because this way it is easier and more * efficient to pass variables around and extend the interface. */ struct ip_fw_args { struct mbuf *m; /* the mbuf chain */ struct ifnet *oif; /* output interface */ struct sockaddr_in *next_hop; /* forward address */ struct sockaddr_in6 *next_hop6; /* ipv6 forward address */ /* * On return, it points to the matching rule. * On entry, rule.slot > 0 means the info is valid and * contains the starting rule for an ipfw search. * If chain_id == chain->id && slot >0 then jump to that slot. * Otherwise, we locate the first rule >= rulenum:rule_id */ struct ipfw_rule_ref rule; /* match/restart info */ struct ether_header *eh; /* for bridged packets */ struct ipfw_flow_id f_id; /* grabbed from IP header */ //uint32_t cookie; /* a cookie depending on rule action */ struct inpcb *inp; struct _ip6dn_args dummypar; /* dummynet->ip6_output */ union { /* store here if cannot use a pointer */ struct sockaddr_in hopstore; struct sockaddr_in6 hopstore6; }; }; MALLOC_DECLARE(M_IPFW); /* * Hooks sometime need to know the direction of the packet * (divert, dummynet, netgraph, ...) * We use a generic definition here, with bit0-1 indicating the * direction, bit 2 indicating layer2 or 3, bit 3-4 indicating the * specific protocol * indicating the protocol (if necessary) */ enum { DIR_MASK = 0x3, DIR_OUT = 0, DIR_IN = 1, DIR_FWD = 2, DIR_DROP = 3, PROTO_LAYER2 = 0x4, /* set for layer 2 */ /* PROTO_DEFAULT = 0, */ PROTO_IPV4 = 0x08, PROTO_IPV6 = 0x10, PROTO_IFB = 0x0c, /* layer2 + ifbridge */ /* PROTO_OLDBDG = 0x14, unused, old bridge */ }; /* wrapper for freeing a packet, in case we need to do more work */ #ifndef FREE_PKT #if defined(__linux__) || defined(_WIN32) #define FREE_PKT(m) netisr_dispatch(-1, m) #else #define FREE_PKT(m) m_freem(m) #endif #endif /* !FREE_PKT */ /* * Function definitions. */ +int ipfw_chk(struct ip_fw_args *args); +struct mbuf *ipfw_send_pkt(struct mbuf *, struct ipfw_flow_id *, + u_int32_t, u_int32_t, int); /* attach (arg = 1) or detach (arg = 0) hooks */ int ipfw_attach_hooks(int); #ifdef NOTYET void ipfw_nat_destroy(void); #endif /* In ip_fw_log.c */ struct ip; struct ip_fw_chain; + void ipfw_bpf_init(int); void ipfw_bpf_uninit(int); void ipfw_bpf_mtap2(void *, u_int, struct mbuf *); void ipfw_log(struct ip_fw_chain *chain, struct ip_fw *f, u_int hlen, struct ip_fw_args *args, struct mbuf *m, struct ifnet *oif, u_short offset, uint32_t tablearg, struct ip *ip); VNET_DECLARE(u_int64_t, norule_counter); #define V_norule_counter VNET(norule_counter) VNET_DECLARE(int, verbose_limit); #define V_verbose_limit VNET(verbose_limit) /* In ip_fw_dynamic.c */ +struct sockopt_data; enum { /* result for matching dynamic rules */ MATCH_REVERSE = 0, MATCH_FORWARD, MATCH_NONE, MATCH_UNKNOWN, }; /* - * The lock for dynamic rules is only used once outside the file, - * and only to release the result of lookup_dyn_rule(). - * Eventually we may implement it with a callback on the function. - */ -struct ip_fw_chain; -struct sockopt_data; -int ipfw_is_dyn_rule(struct ip_fw *rule); -void ipfw_expire_dyn_states(struct ip_fw_chain *, ipfw_range_tlv *); - -struct tcphdr; -struct mbuf *ipfw_send_pkt(struct mbuf *, struct ipfw_flow_id *, - u_int32_t, u_int32_t, int); -/* * Macro to determine that we need to do or redo dynamic state lookup. * direction == MATCH_UNKNOWN means that this is first lookup, then we need * to do lookup. * Otherwise check the state name, if previous lookup was for "any" name, * this means there is no state with specific name. Thus no need to do * lookup. If previous name was not "any", redo lookup for specific name. */ #define DYN_LOOKUP_NEEDED(p, cmd) \ ((p)->direction == MATCH_UNKNOWN || \ ((p)->kidx != 0 && (p)->kidx != (cmd)->arg1)) #define DYN_INFO_INIT(p) do { \ (p)->direction = MATCH_UNKNOWN; \ (p)->kidx = 0; \ } while (0) struct ipfw_dyn_info { uint16_t direction; /* match direction */ uint16_t kidx; /* state name kidx */ uint32_t hashval; /* hash value */ uint32_t version; /* bucket version */ uint32_t f_pos; }; int ipfw_dyn_install_state(struct ip_fw_chain *chain, struct ip_fw *rule, const ipfw_insn_limit *cmd, const struct ip_fw_args *args, const void *ulp, int pktlen, struct ipfw_dyn_info *info, uint32_t tablearg); struct ip_fw *ipfw_dyn_lookup_state(const struct ip_fw_args *args, const void *ulp, int pktlen, const ipfw_insn *cmd, struct ipfw_dyn_info *info); +int ipfw_is_dyn_rule(struct ip_fw *rule); +void ipfw_expire_dyn_states(struct ip_fw_chain *, ipfw_range_tlv *); void ipfw_get_dynamic(struct ip_fw_chain *chain, char **bp, const char *ep); int ipfw_dump_states(struct ip_fw_chain *chain, struct sockopt_data *sd); void ipfw_dyn_init(struct ip_fw_chain *); /* per-vnet initialization */ void ipfw_dyn_uninit(int); /* per-vnet deinitialization */ int ipfw_dyn_len(void); -uint32_t ipfw_dyn_get_count(void); +uint32_t ipfw_dyn_get_count(uint32_t *, int *); +void ipfw_dyn_reset_eaction(struct ip_fw_chain *ch, uint16_t eaction_id, + uint16_t default_id, uint16_t instance_id); /* common variables */ VNET_DECLARE(int, fw_one_pass); #define V_fw_one_pass VNET(fw_one_pass) VNET_DECLARE(int, fw_verbose); #define V_fw_verbose VNET(fw_verbose) VNET_DECLARE(struct ip_fw_chain, layer3_chain); #define V_layer3_chain VNET(layer3_chain) VNET_DECLARE(int, ipfw_vnet_ready); #define V_ipfw_vnet_ready VNET(ipfw_vnet_ready) VNET_DECLARE(u_int32_t, set_disable); #define V_set_disable VNET(set_disable) VNET_DECLARE(int, autoinc_step); #define V_autoinc_step VNET(autoinc_step) VNET_DECLARE(unsigned int, fw_tables_max); #define V_fw_tables_max VNET(fw_tables_max) VNET_DECLARE(unsigned int, fw_tables_sets); #define V_fw_tables_sets VNET(fw_tables_sets) struct tables_config; #ifdef _KERNEL /* * Here we have the structure representing an ipfw rule. * * It starts with a general area * followed by an array of one or more instructions, which the code * accesses as an array of 32-bit values. * * Given a rule pointer r: * * r->cmd is the start of the first instruction. * ACTION_PTR(r) is the start of the first action (things to do * once a rule matched). */ struct ip_fw { uint16_t act_ofs; /* offset of action in 32-bit units */ uint16_t cmd_len; /* # of 32-bit words in cmd */ uint16_t rulenum; /* rule number */ uint8_t set; /* rule set (0..31) */ uint8_t flags; /* currently unused */ counter_u64_t cntr; /* Pointer to rule counters */ uint32_t timestamp; /* tv_sec of last match */ uint32_t id; /* rule id */ uint32_t cached_id; /* used by jump_fast */ uint32_t cached_pos; /* used by jump_fast */ + uint32_t refcnt; /* number of references */ + struct ip_fw *next; /* linked list of deleted rules */ ipfw_insn cmd[1]; /* storage for commands */ }; #define IPFW_RULE_CNTR_SIZE (2 * sizeof(uint64_t)) #endif struct ip_fw_chain { struct ip_fw **map; /* array of rule ptrs to ease lookup */ uint32_t id; /* ruleset id */ int n_rules; /* number of static rules */ void *tablestate; /* runtime table info */ void *valuestate; /* runtime table value info */ int *idxmap; /* skipto array of rules */ void **srvstate; /* runtime service mappings */ #if defined( __linux__ ) || defined( _WIN32 ) spinlock_t rwmtx; #endif int static_len; /* total len of static rules (v0) */ uint32_t gencnt; /* NAT generation count */ LIST_HEAD(nat_list, cfg_nat) nat; /* list of nat entries */ struct ip_fw *default_rule; struct tables_config *tblcfg; /* tables module data */ void *ifcfg; /* interface module data */ int *idxmap_back; /* standby skipto array of rules */ struct namedobj_instance *srvmap; /* cfg name->number mappings */ #if defined( __linux__ ) || defined( _WIN32 ) spinlock_t uh_lock; #else struct rwlock uh_lock; /* lock for upper half */ #endif }; /* 64-byte structure representing multi-field table value */ struct table_value { uint32_t tag; /* O_TAG/O_TAGGED */ uint32_t pipe; /* O_PIPE/O_QUEUE */ uint16_t divert; /* O_DIVERT/O_TEE */ uint16_t skipto; /* skipto, CALLRET */ uint32_t netgraph; /* O_NETGRAPH/O_NGTEE */ uint32_t fib; /* O_SETFIB */ uint32_t nat; /* O_NAT */ uint32_t nh4; uint8_t dscp; uint8_t spare0; uint16_t spare1; /* -- 32 bytes -- */ struct in6_addr nh6; uint32_t limit; /* O_LIMIT */ uint32_t zoneid; /* scope zone id for nh6 */ uint64_t refcnt; /* Number of references */ }; struct named_object { TAILQ_ENTRY(named_object) nn_next; /* namehash */ TAILQ_ENTRY(named_object) nv_next; /* valuehash */ char *name; /* object name */ uint16_t etlv; /* Export TLV id */ uint8_t subtype;/* object subtype within class */ uint8_t set; /* set object belongs to */ uint16_t kidx; /* object kernel index */ uint16_t spare; uint32_t ocnt; /* object counter for internal use */ 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 */ size_t ksize; /* given buffer size */ size_t koff; /* data already used */ size_t kavail; /* number of bytes available */ size_t ktotal; /* total bytes pushed */ struct sockopt *sopt; /* socket data */ caddr_t sopt_val; /* sopt user buffer */ 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; }; /* Macro for working with various counters */ #define IPFW_INC_RULE_COUNTER(_cntr, _bytes) do { \ counter_u64_add((_cntr)->cntr, 1); \ counter_u64_add((_cntr)->cntr + 1, _bytes); \ if ((_cntr)->timestamp != time_uptime) \ (_cntr)->timestamp = time_uptime; \ } while (0) #define IPFW_INC_DYN_COUNTER(_cntr, _bytes) do { \ (_cntr)->pcnt++; \ (_cntr)->bcnt += _bytes; \ } while (0) #define IPFW_ZERO_RULE_COUNTER(_cntr) do { \ counter_u64_zero((_cntr)->cntr); \ counter_u64_zero((_cntr)->cntr + 1); \ (_cntr)->timestamp = 0; \ } while (0) #define IPFW_ZERO_DYN_COUNTER(_cntr) do { \ (_cntr)->pcnt = 0; \ (_cntr)->bcnt = 0; \ } while (0) #define TARG_VAL(ch, k, f) ((struct table_value *)((ch)->valuestate))[k].f #define IP_FW_ARG_TABLEARG(ch, a, f) \ (((a) == IP_FW_TARG) ? TARG_VAL(ch, tablearg, f) : (a)) /* * The lock is heavily used by ip_fw2.c (the main file) and ip_fw_nat.c * so the variable and the macros must be here. */ #if defined( __linux__ ) || defined( _WIN32 ) #define IPFW_LOCK_INIT(_chain) do { \ rw_init(&(_chain)->rwmtx, "IPFW static rules"); \ rw_init(&(_chain)->uh_lock, "IPFW UH lock"); \ } while (0) #define IPFW_LOCK_DESTROY(_chain) do { \ rw_destroy(&(_chain)->rwmtx); \ rw_destroy(&(_chain)->uh_lock); \ } while (0) #define IPFW_RLOCK_ASSERT(_chain) rw_assert(&(_chain)->rwmtx, RA_RLOCKED) #define IPFW_WLOCK_ASSERT(_chain) rw_assert(&(_chain)->rwmtx, RA_WLOCKED) #define IPFW_RLOCK_TRACKER #define IPFW_RLOCK(p) rw_rlock(&(p)->rwmtx) #define IPFW_RUNLOCK(p) rw_runlock(&(p)->rwmtx) #define IPFW_WLOCK(p) rw_wlock(&(p)->rwmtx) #define IPFW_WUNLOCK(p) rw_wunlock(&(p)->rwmtx) #define IPFW_PF_RLOCK(p) IPFW_RLOCK(p) #define IPFW_PF_RUNLOCK(p) IPFW_RUNLOCK(p) #else /* FreeBSD */ #define IPFW_LOCK_INIT(_chain) do { \ rw_init(&(_chain)->uh_lock, "IPFW UH lock"); \ } while (0) #define IPFW_LOCK_DESTROY(_chain) do { \ rw_destroy(&(_chain)->uh_lock); \ } while (0) #define IPFW_RLOCK_ASSERT(_chain) rm_assert(&V_pfil_lock, RA_RLOCKED) #define IPFW_WLOCK_ASSERT(_chain) rm_assert(&V_pfil_lock, RA_WLOCKED) #define IPFW_RLOCK_TRACKER struct rm_priotracker _tracker #define IPFW_RLOCK(p) rm_rlock(&V_pfil_lock, &_tracker) #define IPFW_RUNLOCK(p) rm_runlock(&V_pfil_lock, &_tracker) #define IPFW_WLOCK(p) rm_wlock(&V_pfil_lock) #define IPFW_WUNLOCK(p) rm_wunlock(&V_pfil_lock) #define IPFW_PF_RLOCK(p) #define IPFW_PF_RUNLOCK(p) #endif #define IPFW_UH_RLOCK_ASSERT(_chain) rw_assert(&(_chain)->uh_lock, RA_RLOCKED) #define IPFW_UH_WLOCK_ASSERT(_chain) rw_assert(&(_chain)->uh_lock, RA_WLOCKED) #define IPFW_UH_UNLOCK_ASSERT(_chain) rw_assert(&(_chain)->uh_lock, RA_UNLOCKED) #define IPFW_UH_RLOCK(p) rw_rlock(&(p)->uh_lock) #define IPFW_UH_RUNLOCK(p) rw_runlock(&(p)->uh_lock) #define IPFW_UH_WLOCK(p) rw_wlock(&(p)->uh_lock) #define IPFW_UH_WUNLOCK(p) rw_wunlock(&(p)->uh_lock) struct obj_idx { uint16_t uidx; /* internal index supplied by userland */ uint16_t kidx; /* kernel object index */ uint16_t off; /* tlv offset from rule end in 4-byte words */ uint8_t spare; uint8_t type; /* object type within its category */ }; struct rule_check_info { uint16_t flags; /* rule-specific check flags */ uint16_t object_opcodes; /* num of opcodes referencing objects */ uint16_t urule_numoff; /* offset of rulenum in bytes */ uint8_t version; /* rule version */ uint8_t spare; ipfw_obj_ctlv *ctlv; /* name TLV containter */ struct ip_fw *krule; /* resulting rule pointer */ caddr_t urule; /* original rule pointer */ struct obj_idx obuf[8]; /* table references storage */ }; /* Legacy interface support */ /* * FreeBSD 8 export rule format */ struct ip_fw_rule0 { struct ip_fw *x_next; /* linked list of rules */ struct ip_fw *next_rule; /* ptr to next [skipto] rule */ /* 'next_rule' is used to pass up 'set_disable' status */ uint16_t act_ofs; /* offset of action in 32-bit units */ uint16_t cmd_len; /* # of 32-bit words in cmd */ uint16_t rulenum; /* rule number */ uint8_t set; /* rule set (0..31) */ uint8_t _pad; /* padding */ uint32_t id; /* rule id */ /* These fields are present in all rules. */ uint64_t pcnt; /* Packet counter */ uint64_t bcnt; /* Byte counter */ uint32_t timestamp; /* tv_sec of last match */ ipfw_insn cmd[1]; /* storage for commands */ }; struct ip_fw_bcounter0 { uint64_t pcnt; /* Packet counter */ uint64_t bcnt; /* Byte counter */ uint32_t timestamp; /* tv_sec of last match */ }; /* Kernel rule length */ /* * RULE _K_ SIZE _V_ -> * get kernel size from userland rool version _V_. * RULE _U_ SIZE _V_ -> * get user size version _V_ from kernel rule * RULESIZE _V_ -> * get user size rule length */ /* FreeBSD8 <> current kernel format */ #define RULEUSIZE0(r) (sizeof(struct ip_fw_rule0) + (r)->cmd_len * 4 - 4) #define RULEKSIZE0(r) roundup2((sizeof(struct ip_fw) + (r)->cmd_len*4 - 4), 8) /* FreeBSD11 <> current kernel format */ #define RULEUSIZE1(r) (roundup2(sizeof(struct ip_fw_rule) + \ (r)->cmd_len * 4 - 4, 8)) #define RULEKSIZE1(r) roundup2((sizeof(struct ip_fw) + (r)->cmd_len*4 - 4), 8) /* * Tables/Objects index rewriting code */ /* Default and maximum number of ipfw tables/objects. */ #define IPFW_TABLES_MAX 65536 #define IPFW_TABLES_DEFAULT 128 #define IPFW_OBJECTS_MAX 65536 #define IPFW_OBJECTS_DEFAULT 1024 #define CHAIN_TO_SRV(ch) ((ch)->srvmap) #define SRV_OBJECT(ch, idx) ((ch)->srvstate[(idx)]) struct tid_info { uint32_t set; /* table set */ uint16_t uidx; /* table index */ uint8_t type; /* table type */ uint8_t atype; uint8_t spare; int tlen; /* Total TLV size block */ void *tlvs; /* Pointer to first TLV */ }; /* * Classifier callback. Checks if @cmd opcode contains kernel object reference. * If true, returns its index and type. * Returns 0 if match is found, 1 overwise. */ typedef int (ipfw_obj_rw_cl)(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype); /* * Updater callback. Sets kernel object reference index to @puidx */ typedef void (ipfw_obj_rw_upd)(ipfw_insn *cmd, uint16_t puidx); /* * Finder callback. Tries to find named object by name (specified via @ti). * Stores found named object pointer in @pno. * If object was not found, NULL is stored. * * Return 0 if input data was valid. */ typedef int (ipfw_obj_fname_cb)(struct ip_fw_chain *ch, struct tid_info *ti, struct named_object **pno); /* * Another finder callback. Tries to findex named object by kernel index. * * Returns pointer to named object or NULL. */ typedef struct named_object *(ipfw_obj_fidx_cb)(struct ip_fw_chain *ch, uint16_t kidx); /* * Object creator callback. Tries to create object specified by @ti. * Stores newly-allocated object index in @pkidx. * * Returns 0 on success. */ typedef int (ipfw_obj_create_cb)(struct ip_fw_chain *ch, struct tid_info *ti, uint16_t *pkidx); /* * Object destroy callback. Intended to free resources allocated by * create_object callback. */ typedef void (ipfw_obj_destroy_cb)(struct ip_fw_chain *ch, struct named_object *no); /* * Sets handler callback. Handles moving and swaping set of named object. * SWAP_ALL moves all named objects from set `set' to `new_set' and vise versa; * TEST_ALL checks that there aren't any named object with conflicting names; * MOVE_ALL moves all named objects from set `set' to `new_set'; * COUNT_ONE used to count number of references used by object with kidx `set'; * TEST_ONE checks that named object with kidx `set' can be moved to `new_set`; * MOVE_ONE moves named object with kidx `set' to set `new_set'. */ enum ipfw_sets_cmd { SWAP_ALL = 0, TEST_ALL, MOVE_ALL, COUNT_ONE, TEST_ONE, MOVE_ONE }; typedef int (ipfw_obj_sets_cb)(struct ip_fw_chain *ch, uint16_t set, uint8_t new_set, enum ipfw_sets_cmd cmd); struct opcode_obj_rewrite { uint32_t opcode; /* Opcode to act upon */ uint32_t etlv; /* Relevant export TLV id */ ipfw_obj_rw_cl *classifier; /* Check if rewrite is needed */ ipfw_obj_rw_upd *update; /* update cmd with new value */ ipfw_obj_fname_cb *find_byname; /* Find named object by name */ ipfw_obj_fidx_cb *find_bykidx; /* Find named object by kidx */ ipfw_obj_create_cb *create_object; /* Create named object */ ipfw_obj_destroy_cb *destroy_object;/* Destroy named object */ ipfw_obj_sets_cb *manage_sets; /* Swap or move sets */ }; #define IPFW_ADD_OBJ_REWRITER(f, c) do { \ if ((f) != 0) \ ipfw_add_obj_rewriter(c, \ sizeof(c) / sizeof(c[0])); \ } while(0) #define IPFW_DEL_OBJ_REWRITER(l, c) do { \ if ((l) != 0) \ ipfw_del_obj_rewriter(c, \ sizeof(c) / sizeof(c[0])); \ } while(0) /* 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); /* In ip_fw_sockopt.c */ void ipfw_init_skipto_cache(struct ip_fw_chain *chain); void ipfw_destroy_skipto_cache(struct ip_fw_chain *chain); int ipfw_find_rule(struct ip_fw_chain *chain, uint32_t key, uint32_t id); int ipfw_ctl3(struct sockopt *sopt); -int ipfw_chk(struct ip_fw_args *args); int ipfw_add_protected_rule(struct ip_fw_chain *chain, struct ip_fw *rule, int locked); void ipfw_reap_add(struct ip_fw_chain *chain, struct ip_fw **head, struct ip_fw *rule); void ipfw_reap_rules(struct ip_fw *head); void ipfw_init_counters(void); void ipfw_destroy_counters(void); struct ip_fw *ipfw_alloc_rule(struct ip_fw_chain *chain, size_t rulesize); +void ipfw_free_rule(struct ip_fw *rule); int ipfw_match_range(struct ip_fw *rule, ipfw_range_tlv *rt); +int ipfw_mark_object_kidx(uint32_t *bmask, uint16_t etlv, uint16_t kidx); typedef int (sopt_handler_f)(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd); struct ipfw_sopt_handler { uint16_t opcode; uint8_t version; uint8_t dir; sopt_handler_f *handler; uint64_t refcnt; }; #define HDIR_SET 0x01 /* Handler is used to set some data */ #define HDIR_GET 0x02 /* Handler is used to retrieve data */ #define HDIR_BOTH HDIR_GET|HDIR_SET void ipfw_init_sopt_handler(void); void ipfw_destroy_sopt_handler(void); void ipfw_add_sopt_handler(struct ipfw_sopt_handler *sh, size_t count); int ipfw_del_sopt_handler(struct ipfw_sopt_handler *sh, size_t count); 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); #define IPFW_ADD_SOPT_HANDLER(f, c) do { \ if ((f) != 0) \ ipfw_add_sopt_handler(c, \ sizeof(c) / sizeof(c[0])); \ } while(0) #define IPFW_DEL_SOPT_HANDLER(l, c) do { \ if ((l) != 0) \ ipfw_del_sopt_handler(c, \ sizeof(c) / sizeof(c[0])); \ } while(0) struct namedobj_instance; typedef int (objhash_cb_t)(struct namedobj_instance *ni, struct named_object *, void *arg); typedef uint32_t (objhash_hash_f)(struct namedobj_instance *ni, const void *key, uint32_t kopt); typedef int (objhash_cmp_f)(struct named_object *no, const void *key, uint32_t kopt); struct namedobj_instance *ipfw_objhash_create(uint32_t items); void ipfw_objhash_destroy(struct namedobj_instance *); void ipfw_objhash_bitmap_alloc(uint32_t items, void **idx, int *pblocks); void ipfw_objhash_bitmap_merge(struct namedobj_instance *ni, void **idx, int *blocks); void ipfw_objhash_bitmap_swap(struct namedobj_instance *ni, void **idx, int *blocks); void ipfw_objhash_bitmap_free(void *idx, int blocks); void ipfw_objhash_set_hashf(struct namedobj_instance *ni, objhash_hash_f *f); struct named_object *ipfw_objhash_lookup_name(struct namedobj_instance *ni, uint32_t set, char *name); struct named_object *ipfw_objhash_lookup_name_type(struct namedobj_instance *ni, uint32_t set, uint32_t type, const char *name); struct named_object *ipfw_objhash_lookup_kidx(struct namedobj_instance *ni, uint16_t idx); int ipfw_objhash_same_name(struct namedobj_instance *ni, struct named_object *a, struct named_object *b); void ipfw_objhash_add(struct namedobj_instance *ni, struct named_object *no); void ipfw_objhash_del(struct namedobj_instance *ni, struct named_object *no); uint32_t ipfw_objhash_count(struct namedobj_instance *ni); uint32_t ipfw_objhash_count_type(struct namedobj_instance *ni, uint16_t type); int ipfw_objhash_foreach(struct namedobj_instance *ni, objhash_cb_t *f, void *arg); int ipfw_objhash_foreach_type(struct namedobj_instance *ni, objhash_cb_t *f, void *arg, uint16_t type); int ipfw_objhash_free_idx(struct namedobj_instance *ni, uint16_t idx); int ipfw_objhash_alloc_idx(void *n, uint16_t *pidx); void ipfw_objhash_set_funcs(struct namedobj_instance *ni, objhash_hash_f *hash_f, objhash_cmp_f *cmp_f); int ipfw_objhash_find_type(struct namedobj_instance *ni, struct tid_info *ti, uint32_t etlv, struct named_object **pno); void ipfw_export_obj_ntlv(struct named_object *no, ipfw_obj_ntlv *ntlv); ipfw_obj_ntlv *ipfw_find_name_tlv_type(void *tlvs, int len, uint16_t uidx, uint32_t etlv); void ipfw_init_obj_rewriter(void); void ipfw_destroy_obj_rewriter(void); void ipfw_add_obj_rewriter(struct opcode_obj_rewrite *rw, size_t count); int ipfw_del_obj_rewriter(struct opcode_obj_rewrite *rw, size_t count); int create_objects_compat(struct ip_fw_chain *ch, ipfw_insn *cmd, struct obj_idx *oib, struct obj_idx *pidx, struct tid_info *ti); void update_opcode_kidx(ipfw_insn *cmd, uint16_t idx); int classify_opcode_kidx(ipfw_insn *cmd, uint16_t *puidx); void ipfw_init_srv(struct ip_fw_chain *ch); void ipfw_destroy_srv(struct ip_fw_chain *ch); int ipfw_check_object_name_generic(const char *name); int ipfw_obj_manage_sets(struct namedobj_instance *ni, uint16_t type, uint16_t set, uint8_t new_set, enum ipfw_sets_cmd cmd); /* In ip_fw_eaction.c */ typedef int (ipfw_eaction_t)(struct ip_fw_chain *ch, struct ip_fw_args *args, ipfw_insn *cmd, int *done); int ipfw_eaction_init(struct ip_fw_chain *ch, int first); void ipfw_eaction_uninit(struct ip_fw_chain *ch, int last); uint16_t ipfw_add_eaction(struct ip_fw_chain *ch, ipfw_eaction_t handler, const char *name); int ipfw_del_eaction(struct ip_fw_chain *ch, uint16_t eaction_id); int ipfw_run_eaction(struct ip_fw_chain *ch, struct ip_fw_args *args, ipfw_insn *cmd, int *done); +int ipfw_reset_eaction(struct ip_fw_chain *ch, struct ip_fw *rule, + uint16_t eaction_id, uint16_t default_id, uint16_t instance_id); +int ipfw_reset_eaction_instance(struct ip_fw_chain *ch, uint16_t eaction_id, + uint16_t instance_id); /* In ip_fw_table.c */ struct table_info; typedef int (table_lookup_t)(struct table_info *ti, void *key, uint32_t keylen, uint32_t *val); int ipfw_lookup_table(struct ip_fw_chain *ch, uint16_t tbl, uint16_t plen, void *paddr, uint32_t *val); struct named_object *ipfw_objhash_lookup_table_kidx(struct ip_fw_chain *ch, uint16_t kidx); int ipfw_ref_table(struct ip_fw_chain *ch, ipfw_obj_ntlv *ntlv, uint16_t *kidx); void ipfw_unref_table(struct ip_fw_chain *ch, uint16_t kidx); int ipfw_init_tables(struct ip_fw_chain *ch, int first); int ipfw_resize_tables(struct ip_fw_chain *ch, unsigned int ntables); int ipfw_switch_tables_namespace(struct ip_fw_chain *ch, unsigned int nsets); void ipfw_destroy_tables(struct ip_fw_chain *ch, int last); /* In ip_fw_nat.c -- XXX to be moved to ip_var.h */ extern struct cfg_nat *(*lookup_nat_ptr)(struct nat_list *, int); typedef int ipfw_nat_t(struct ip_fw_args *, struct cfg_nat *, struct mbuf *); typedef int ipfw_nat_cfg_t(struct sockopt *); VNET_DECLARE(int, ipfw_nat_ready); #define V_ipfw_nat_ready VNET(ipfw_nat_ready) #define IPFW_NAT_LOADED (V_ipfw_nat_ready) extern ipfw_nat_t *ipfw_nat_ptr; extern ipfw_nat_cfg_t *ipfw_nat_cfg_ptr; extern ipfw_nat_cfg_t *ipfw_nat_del_ptr; extern ipfw_nat_cfg_t *ipfw_nat_get_cfg_ptr; extern ipfw_nat_cfg_t *ipfw_nat_get_log_ptr; /* Helper functions for IP checksum adjustment */ static __inline uint16_t cksum_add(uint16_t sum, uint16_t a) { uint16_t res; res = sum + a; return (res + (res < a)); } static __inline uint16_t cksum_adjust(uint16_t oldsum, uint16_t old, uint16_t new) { return (~cksum_add(cksum_add(~oldsum, ~old), new)); } #endif /* _KERNEL */ #endif /* _IPFW2_PRIVATE_H */ Index: head/sys/netpfil/ipfw/ip_fw_sockopt.c =================================================================== --- head/sys/netpfil/ipfw/ip_fw_sockopt.c (revision 341470) +++ head/sys/netpfil/ipfw/ip_fw_sockopt.c (revision 341471) @@ -1,4652 +1,4675 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2002-2009 Luigi Rizzo, Universita` di Pisa * Copyright (c) 2014 Yandex LLC * Copyright (c) 2014 Alexander V. Chernikov * * Supported by: Valeria Paoli * * 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$"); /* * Control socket and rule management routines for ipfw. * Control is currently implemented via IP_FW3 setsockopt() code. */ #include "opt_ipfw.h" #include "opt_inet.h" #ifndef INET #error IPFIREWALL requires INET. #endif /* INET */ #include "opt_inet6.h" #include #include #include #include /* struct m_tag used by nested headers */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* hooks */ #include #include #include #ifdef MAC #include #endif static int ipfw_ctl(struct sockopt *sopt); static int check_ipfw_rule_body(ipfw_insn *cmd, int cmd_len, struct rule_check_info *ci); static int check_ipfw_rule1(struct ip_fw_rule *rule, int size, struct rule_check_info *ci); static int check_ipfw_rule0(struct ip_fw_rule0 *rule, int size, struct rule_check_info *ci); static int rewrite_rule_uidx(struct ip_fw_chain *chain, struct rule_check_info *ci); #define NAMEDOBJ_HASH_SIZE 32 struct namedobj_instance { struct namedobjects_head *names; struct namedobjects_head *values; uint32_t nn_size; /* names hash size */ uint32_t nv_size; /* number hash size */ u_long *idx_mask; /* used items bitmask */ uint32_t max_blocks; /* number of "long" blocks in bitmask */ uint32_t count; /* number of items */ uint16_t free_off[IPFW_MAX_SETS]; /* first possible free offset */ objhash_hash_f *hash_f; objhash_cmp_f *cmp_f; }; #define BLOCK_ITEMS (8 * sizeof(u_long)) /* Number of items for ffsl() */ static uint32_t objhash_hash_name(struct namedobj_instance *ni, const void *key, uint32_t kopt); static uint32_t objhash_hash_idx(struct namedobj_instance *ni, uint32_t val); static int objhash_cmp_name(struct named_object *no, const void *name, uint32_t set); MALLOC_DEFINE(M_IPFW, "IpFw/IpAcct", "IpFw/IpAcct chain's"); static int dump_config(struct ip_fw_chain *chain, ip_fw3_opheader *op3, struct sockopt_data *sd); static int add_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3, struct sockopt_data *sd); static int del_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3, struct sockopt_data *sd); static int clear_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3, struct sockopt_data *sd); static int move_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3, struct sockopt_data *sd); static int manage_sets(struct ip_fw_chain *chain, ip_fw3_opheader *op3, struct sockopt_data *sd); static int dump_soptcodes(struct ip_fw_chain *chain, ip_fw3_opheader *op3, struct sockopt_data *sd); static int dump_srvobjects(struct ip_fw_chain *chain, ip_fw3_opheader *op3, struct sockopt_data *sd); /* ctl3 handler data */ struct mtx ctl3_lock; #define CTL3_LOCK_INIT() mtx_init(&ctl3_lock, "ctl3_lock", NULL, MTX_DEF) #define CTL3_LOCK_DESTROY() mtx_destroy(&ctl3_lock) #define CTL3_LOCK() mtx_lock(&ctl3_lock) #define CTL3_UNLOCK() mtx_unlock(&ctl3_lock) static struct ipfw_sopt_handler *ctl3_handlers; static size_t ctl3_hsize; static uint64_t ctl3_refct, ctl3_gencnt; #define CTL3_SMALLBUF 4096 /* small page-size write buffer */ #define CTL3_LARGEBUF 16 * 1024 * 1024 /* handle large rulesets */ static int ipfw_flush_sopt_data(struct sockopt_data *sd); static struct ipfw_sopt_handler scodes[] = { { IP_FW_XGET, 0, HDIR_GET, dump_config }, { IP_FW_XADD, 0, HDIR_BOTH, add_rules }, { IP_FW_XDEL, 0, HDIR_BOTH, del_rules }, { IP_FW_XZERO, 0, HDIR_SET, clear_rules }, { IP_FW_XRESETLOG, 0, HDIR_SET, clear_rules }, { IP_FW_XMOVE, 0, HDIR_SET, move_rules }, { IP_FW_SET_SWAP, 0, HDIR_SET, manage_sets }, { IP_FW_SET_MOVE, 0, HDIR_SET, manage_sets }, { IP_FW_SET_ENABLE, 0, HDIR_SET, manage_sets }, { IP_FW_DUMP_SOPTCODES, 0, HDIR_GET, dump_soptcodes }, { IP_FW_DUMP_SRVOBJECTS,0, HDIR_GET, dump_srvobjects }, }; static int set_legacy_obj_kidx(struct ip_fw_chain *ch, struct ip_fw_rule0 *rule); static struct opcode_obj_rewrite *find_op_rw(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype); -static int mark_object_kidx(struct ip_fw_chain *ch, struct ip_fw *rule, - uint32_t *bmask); static int ref_rule_objects(struct ip_fw_chain *ch, struct ip_fw *rule, struct rule_check_info *ci, struct obj_idx *oib, struct tid_info *ti); static int ref_opcode_object(struct ip_fw_chain *ch, ipfw_insn *cmd, struct tid_info *ti, struct obj_idx *pidx, int *unresolved); static void unref_rule_objects(struct ip_fw_chain *chain, struct ip_fw *rule); static void unref_oib_objects(struct ip_fw_chain *ch, ipfw_insn *cmd, struct obj_idx *oib, struct obj_idx *end); static int export_objhash_ntlv(struct namedobj_instance *ni, uint16_t kidx, struct sockopt_data *sd); /* * Opcode object rewriter variables */ struct opcode_obj_rewrite *ctl3_rewriters; static size_t ctl3_rsize; /* * static variables followed by global ones */ VNET_DEFINE_STATIC(uma_zone_t, ipfw_cntr_zone); #define V_ipfw_cntr_zone VNET(ipfw_cntr_zone) void ipfw_init_counters() { V_ipfw_cntr_zone = uma_zcreate("IPFW counters", IPFW_RULE_CNTR_SIZE, NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_PCPU); } void ipfw_destroy_counters() { uma_zdestroy(V_ipfw_cntr_zone); } struct ip_fw * ipfw_alloc_rule(struct ip_fw_chain *chain, size_t rulesize) { struct ip_fw *rule; rule = malloc(rulesize, M_IPFW, M_WAITOK | M_ZERO); rule->cntr = uma_zalloc_pcpu(V_ipfw_cntr_zone, M_WAITOK | M_ZERO); + rule->refcnt = 1; return (rule); } -static void -free_rule(struct ip_fw *rule) +void +ipfw_free_rule(struct ip_fw *rule) { + /* + * We don't release refcnt here, since this function + * can be called without any locks held. The caller + * must release reference under IPFW_UH_WLOCK, and then + * call this function if refcount becomes 1. + */ + if (rule->refcnt > 1) + return; uma_zfree_pcpu(V_ipfw_cntr_zone, rule->cntr); free(rule, M_IPFW); } /* * Find the smallest rule >= key, id. * We could use bsearch but it is so simple that we code it directly */ int ipfw_find_rule(struct ip_fw_chain *chain, uint32_t key, uint32_t id) { int i, lo, hi; struct ip_fw *r; for (lo = 0, hi = chain->n_rules - 1; lo < hi;) { i = (lo + hi) / 2; r = chain->map[i]; if (r->rulenum < key) lo = i + 1; /* continue from the next one */ else if (r->rulenum > key) hi = i; /* this might be good */ else if (r->id < id) lo = i + 1; /* continue from the next one */ else /* r->id >= id */ hi = i; /* this might be good */ } return hi; } /* * Builds skipto cache on rule set @map. */ static void update_skipto_cache(struct ip_fw_chain *chain, struct ip_fw **map) { int *smap, rulenum; int i, mi; IPFW_UH_WLOCK_ASSERT(chain); mi = 0; rulenum = map[mi]->rulenum; smap = chain->idxmap_back; if (smap == NULL) return; for (i = 0; i < 65536; i++) { smap[i] = mi; /* Use the same rule index until i < rulenum */ if (i != rulenum || i == 65535) continue; /* Find next rule with num > i */ rulenum = map[++mi]->rulenum; while (rulenum == i) rulenum = map[++mi]->rulenum; } } /* * Swaps prepared (backup) index with current one. */ static void swap_skipto_cache(struct ip_fw_chain *chain) { int *map; IPFW_UH_WLOCK_ASSERT(chain); IPFW_WLOCK_ASSERT(chain); map = chain->idxmap; chain->idxmap = chain->idxmap_back; chain->idxmap_back = map; } /* * Allocate and initialize skipto cache. */ void ipfw_init_skipto_cache(struct ip_fw_chain *chain) { int *idxmap, *idxmap_back; idxmap = malloc(65536 * sizeof(int), M_IPFW, M_WAITOK | M_ZERO); idxmap_back = malloc(65536 * sizeof(int), M_IPFW, M_WAITOK); /* * Note we may be called at any time after initialization, * for example, on first skipto rule, so we need to * provide valid chain->idxmap on return */ IPFW_UH_WLOCK(chain); if (chain->idxmap != NULL) { IPFW_UH_WUNLOCK(chain); free(idxmap, M_IPFW); free(idxmap_back, M_IPFW); return; } /* Set backup pointer first to permit building cache */ chain->idxmap_back = idxmap_back; update_skipto_cache(chain, chain->map); IPFW_WLOCK(chain); /* It is now safe to set chain->idxmap ptr */ chain->idxmap = idxmap; swap_skipto_cache(chain); IPFW_WUNLOCK(chain); IPFW_UH_WUNLOCK(chain); } /* * Destroys skipto cache. */ void ipfw_destroy_skipto_cache(struct ip_fw_chain *chain) { if (chain->idxmap != NULL) free(chain->idxmap, M_IPFW); if (chain->idxmap != NULL) free(chain->idxmap_back, M_IPFW); } /* * allocate a new map, returns the chain locked. extra is the number * of entries to add or delete. */ static struct ip_fw ** get_map(struct ip_fw_chain *chain, int extra, int locked) { for (;;) { struct ip_fw **map; u_int i, mflags; mflags = M_ZERO | ((locked != 0) ? M_NOWAIT : M_WAITOK); i = chain->n_rules + extra; map = malloc(i * sizeof(struct ip_fw *), M_IPFW, mflags); if (map == NULL) { printf("%s: cannot allocate map\n", __FUNCTION__); return NULL; } if (!locked) IPFW_UH_WLOCK(chain); if (i >= chain->n_rules + extra) /* good */ return map; /* otherwise we lost the race, free and retry */ if (!locked) IPFW_UH_WUNLOCK(chain); free(map, M_IPFW); } } /* * swap the maps. It is supposed to be called with IPFW_UH_WLOCK */ static struct ip_fw ** swap_map(struct ip_fw_chain *chain, struct ip_fw **new_map, int new_len) { struct ip_fw **old_map; IPFW_WLOCK(chain); chain->id++; chain->n_rules = new_len; old_map = chain->map; chain->map = new_map; swap_skipto_cache(chain); IPFW_WUNLOCK(chain); return old_map; } static void export_cntr1_base(struct ip_fw *krule, struct ip_fw_bcounter *cntr) { struct timeval boottime; cntr->size = sizeof(*cntr); if (krule->cntr != NULL) { cntr->pcnt = counter_u64_fetch(krule->cntr); cntr->bcnt = counter_u64_fetch(krule->cntr + 1); cntr->timestamp = krule->timestamp; } if (cntr->timestamp > 0) { getboottime(&boottime); cntr->timestamp += boottime.tv_sec; } } static void export_cntr0_base(struct ip_fw *krule, struct ip_fw_bcounter0 *cntr) { struct timeval boottime; if (krule->cntr != NULL) { cntr->pcnt = counter_u64_fetch(krule->cntr); cntr->bcnt = counter_u64_fetch(krule->cntr + 1); cntr->timestamp = krule->timestamp; } if (cntr->timestamp > 0) { getboottime(&boottime); cntr->timestamp += boottime.tv_sec; } } /* * Copies rule @urule from v1 userland format (current). * to kernel @krule. * Assume @krule is zeroed. */ static void import_rule1(struct rule_check_info *ci) { struct ip_fw_rule *urule; struct ip_fw *krule; urule = (struct ip_fw_rule *)ci->urule; krule = (struct ip_fw *)ci->krule; /* copy header */ krule->act_ofs = urule->act_ofs; krule->cmd_len = urule->cmd_len; krule->rulenum = urule->rulenum; krule->set = urule->set; krule->flags = urule->flags; /* Save rulenum offset */ ci->urule_numoff = offsetof(struct ip_fw_rule, rulenum); /* Copy opcodes */ memcpy(krule->cmd, urule->cmd, krule->cmd_len * sizeof(uint32_t)); } /* * Export rule into v1 format (Current). * Layout: * [ ipfw_obj_tlv(IPFW_TLV_RULE_ENT) * [ ip_fw_rule ] OR * [ ip_fw_bcounter ip_fw_rule] (depends on rcntrs). * ] * Assume @data is zeroed. */ static void export_rule1(struct ip_fw *krule, caddr_t data, int len, int rcntrs) { struct ip_fw_bcounter *cntr; struct ip_fw_rule *urule; ipfw_obj_tlv *tlv; /* Fill in TLV header */ tlv = (ipfw_obj_tlv *)data; tlv->type = IPFW_TLV_RULE_ENT; tlv->length = len; if (rcntrs != 0) { /* Copy counters */ cntr = (struct ip_fw_bcounter *)(tlv + 1); urule = (struct ip_fw_rule *)(cntr + 1); export_cntr1_base(krule, cntr); } else urule = (struct ip_fw_rule *)(tlv + 1); /* copy header */ urule->act_ofs = krule->act_ofs; urule->cmd_len = krule->cmd_len; urule->rulenum = krule->rulenum; urule->set = krule->set; urule->flags = krule->flags; urule->id = krule->id; /* Copy opcodes */ memcpy(urule->cmd, krule->cmd, krule->cmd_len * sizeof(uint32_t)); } /* * Copies rule @urule from FreeBSD8 userland format (v0) * to kernel @krule. * Assume @krule is zeroed. */ static void import_rule0(struct rule_check_info *ci) { struct ip_fw_rule0 *urule; struct ip_fw *krule; int cmdlen, l; ipfw_insn *cmd; ipfw_insn_limit *lcmd; ipfw_insn_if *cmdif; urule = (struct ip_fw_rule0 *)ci->urule; krule = (struct ip_fw *)ci->krule; /* copy header */ krule->act_ofs = urule->act_ofs; krule->cmd_len = urule->cmd_len; krule->rulenum = urule->rulenum; krule->set = urule->set; if ((urule->_pad & 1) != 0) krule->flags |= IPFW_RULE_NOOPT; /* Save rulenum offset */ ci->urule_numoff = offsetof(struct ip_fw_rule0, rulenum); /* Copy opcodes */ memcpy(krule->cmd, urule->cmd, krule->cmd_len * sizeof(uint32_t)); /* * Alter opcodes: * 1) convert tablearg value from 65535 to 0 * 2) Add high bit to O_SETFIB/O_SETDSCP values (to make room * for targ). * 3) convert table number in iface opcodes to u16 * 4) convert old `nat global` into new 65535 */ l = krule->cmd_len; cmd = krule->cmd; cmdlen = 0; for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); switch (cmd->opcode) { /* Opcodes supporting tablearg */ case O_TAG: case O_TAGGED: case O_PIPE: case O_QUEUE: case O_DIVERT: case O_TEE: case O_SKIPTO: case O_CALLRETURN: case O_NETGRAPH: case O_NGTEE: case O_NAT: if (cmd->arg1 == IP_FW_TABLEARG) cmd->arg1 = IP_FW_TARG; else if (cmd->arg1 == 0) cmd->arg1 = IP_FW_NAT44_GLOBAL; break; case O_SETFIB: case O_SETDSCP: if (cmd->arg1 == IP_FW_TABLEARG) cmd->arg1 = IP_FW_TARG; else cmd->arg1 |= 0x8000; break; case O_LIMIT: lcmd = (ipfw_insn_limit *)cmd; if (lcmd->conn_limit == IP_FW_TABLEARG) lcmd->conn_limit = IP_FW_TARG; break; /* Interface tables */ case O_XMIT: case O_RECV: case O_VIA: /* Interface table, possibly */ cmdif = (ipfw_insn_if *)cmd; if (cmdif->name[0] != '\1') break; cmdif->p.kidx = (uint16_t)cmdif->p.glob; break; } } } /* * Copies rule @krule from kernel to FreeBSD8 userland format (v0) */ static void export_rule0(struct ip_fw *krule, struct ip_fw_rule0 *urule, int len) { int cmdlen, l; ipfw_insn *cmd; ipfw_insn_limit *lcmd; ipfw_insn_if *cmdif; /* copy header */ memset(urule, 0, len); urule->act_ofs = krule->act_ofs; urule->cmd_len = krule->cmd_len; urule->rulenum = krule->rulenum; urule->set = krule->set; if ((krule->flags & IPFW_RULE_NOOPT) != 0) urule->_pad |= 1; /* Copy opcodes */ memcpy(urule->cmd, krule->cmd, krule->cmd_len * sizeof(uint32_t)); /* Export counters */ export_cntr0_base(krule, (struct ip_fw_bcounter0 *)&urule->pcnt); /* * Alter opcodes: * 1) convert tablearg value from 0 to 65535 * 2) Remove highest bit from O_SETFIB/O_SETDSCP values. * 3) convert table number in iface opcodes to int */ l = urule->cmd_len; cmd = urule->cmd; cmdlen = 0; for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); switch (cmd->opcode) { /* Opcodes supporting tablearg */ case O_TAG: case O_TAGGED: case O_PIPE: case O_QUEUE: case O_DIVERT: case O_TEE: case O_SKIPTO: case O_CALLRETURN: case O_NETGRAPH: case O_NGTEE: case O_NAT: if (cmd->arg1 == IP_FW_TARG) cmd->arg1 = IP_FW_TABLEARG; else if (cmd->arg1 == IP_FW_NAT44_GLOBAL) cmd->arg1 = 0; break; case O_SETFIB: case O_SETDSCP: if (cmd->arg1 == IP_FW_TARG) cmd->arg1 = IP_FW_TABLEARG; else cmd->arg1 &= ~0x8000; break; case O_LIMIT: lcmd = (ipfw_insn_limit *)cmd; if (lcmd->conn_limit == IP_FW_TARG) lcmd->conn_limit = IP_FW_TABLEARG; break; /* Interface tables */ case O_XMIT: case O_RECV: case O_VIA: /* Interface table, possibly */ cmdif = (ipfw_insn_if *)cmd; if (cmdif->name[0] != '\1') break; cmdif->p.glob = cmdif->p.kidx; break; } } } /* * Add new rule(s) to the list possibly creating rule number for each. * Update the rule_number in the input struct so the caller knows it as well. * Must be called without IPFW_UH held */ static int commit_rules(struct ip_fw_chain *chain, struct rule_check_info *rci, int count) { int error, i, insert_before, tcount; uint16_t rulenum, *pnum; struct rule_check_info *ci; struct ip_fw *krule; struct ip_fw **map; /* the new array of pointers */ /* Check if we need to do table/obj index remap */ tcount = 0; for (ci = rci, i = 0; i < count; ci++, i++) { if (ci->object_opcodes == 0) continue; /* * Rule has some object opcodes. * We need to find (and create non-existing) * kernel objects, and reference existing ones. */ error = rewrite_rule_uidx(chain, ci); if (error != 0) { /* * rewrite failed, state for current rule * has been reverted. Check if we need to * revert more. */ if (tcount > 0) { /* * We have some more table rules * we need to rollback. */ IPFW_UH_WLOCK(chain); while (ci != rci) { ci--; if (ci->object_opcodes == 0) continue; unref_rule_objects(chain,ci->krule); } IPFW_UH_WUNLOCK(chain); } return (error); } tcount++; } /* get_map returns with IPFW_UH_WLOCK if successful */ map = get_map(chain, count, 0 /* not locked */); if (map == NULL) { if (tcount > 0) { /* Unbind tables */ IPFW_UH_WLOCK(chain); for (ci = rci, i = 0; i < count; ci++, i++) { if (ci->object_opcodes == 0) continue; unref_rule_objects(chain, ci->krule); } IPFW_UH_WUNLOCK(chain); } return (ENOSPC); } if (V_autoinc_step < 1) V_autoinc_step = 1; else if (V_autoinc_step > 1000) V_autoinc_step = 1000; /* FIXME: Handle count > 1 */ ci = rci; krule = ci->krule; rulenum = krule->rulenum; /* find the insertion point, we will insert before */ insert_before = rulenum ? rulenum + 1 : IPFW_DEFAULT_RULE; i = ipfw_find_rule(chain, insert_before, 0); /* duplicate first part */ if (i > 0) bcopy(chain->map, map, i * sizeof(struct ip_fw *)); map[i] = krule; /* duplicate remaining part, we always have the default rule */ bcopy(chain->map + i, map + i + 1, sizeof(struct ip_fw *) *(chain->n_rules - i)); if (rulenum == 0) { /* Compute rule number and write it back */ rulenum = i > 0 ? map[i-1]->rulenum : 0; if (rulenum < IPFW_DEFAULT_RULE - V_autoinc_step) rulenum += V_autoinc_step; krule->rulenum = rulenum; /* Save number to userland rule */ pnum = (uint16_t *)((caddr_t)ci->urule + ci->urule_numoff); *pnum = rulenum; } krule->id = chain->id + 1; update_skipto_cache(chain, map); map = swap_map(chain, map, chain->n_rules + 1); chain->static_len += RULEUSIZE0(krule); IPFW_UH_WUNLOCK(chain); if (map) free(map, M_IPFW); return (0); } int ipfw_add_protected_rule(struct ip_fw_chain *chain, struct ip_fw *rule, int locked) { struct ip_fw **map; map = get_map(chain, 1, locked); if (map == NULL) return (ENOMEM); if (chain->n_rules > 0) bcopy(chain->map, map, chain->n_rules * sizeof(struct ip_fw *)); map[chain->n_rules] = rule; rule->rulenum = IPFW_DEFAULT_RULE; rule->set = RESVD_SET; rule->id = chain->id + 1; /* We add rule in the end of chain, no need to update skipto cache */ map = swap_map(chain, map, chain->n_rules + 1); chain->static_len += RULEUSIZE0(rule); IPFW_UH_WUNLOCK(chain); free(map, M_IPFW); return (0); } /* * Adds @rule to the list of rules to reap */ void ipfw_reap_add(struct ip_fw_chain *chain, struct ip_fw **head, struct ip_fw *rule) { IPFW_UH_WLOCK_ASSERT(chain); /* Unlink rule from everywhere */ unref_rule_objects(chain, rule); - *((struct ip_fw **)rule) = *head; + rule->next = *head; *head = rule; } /* * Reclaim storage associated with a list of rules. This is * typically the list created using remove_rule. * A NULL pointer on input is handled correctly. */ void ipfw_reap_rules(struct ip_fw *head) { struct ip_fw *rule; while ((rule = head) != NULL) { - head = *((struct ip_fw **)head); - free_rule(rule); + head = head->next; + ipfw_free_rule(rule); } } /* * Rules to keep are * (default || reserved || !match_set || !match_number) * where * default ::= (rule->rulenum == IPFW_DEFAULT_RULE) * // the default rule is always protected * * reserved ::= (cmd == 0 && n == 0 && rule->set == RESVD_SET) * // RESVD_SET is protected only if cmd == 0 and n == 0 ("ipfw flush") * * match_set ::= (cmd == 0 || rule->set == set) * // set number is ignored for cmd == 0 * * match_number ::= (cmd == 1 || n == 0 || n == rule->rulenum) * // number is ignored for cmd == 1 or n == 0 * */ int ipfw_match_range(struct ip_fw *rule, ipfw_range_tlv *rt) { /* Don't match default rule for modification queries */ if (rule->rulenum == IPFW_DEFAULT_RULE && (rt->flags & IPFW_RCFLAG_DEFAULT) == 0) return (0); /* Don't match rules in reserved set for flush requests */ if ((rt->flags & IPFW_RCFLAG_ALL) != 0 && rule->set == RESVD_SET) return (0); /* If we're filtering by set, don't match other sets */ if ((rt->flags & IPFW_RCFLAG_SET) != 0 && rule->set != rt->set) return (0); if ((rt->flags & IPFW_RCFLAG_RANGE) != 0 && (rule->rulenum < rt->start_rule || rule->rulenum > rt->end_rule)) return (0); return (1); } struct manage_sets_args { uint16_t set; uint8_t new_set; }; static int swap_sets_cb(struct namedobj_instance *ni, struct named_object *no, void *arg) { struct manage_sets_args *args; args = (struct manage_sets_args *)arg; if (no->set == (uint8_t)args->set) no->set = args->new_set; else if (no->set == args->new_set) no->set = (uint8_t)args->set; return (0); } static int move_sets_cb(struct namedobj_instance *ni, struct named_object *no, void *arg) { struct manage_sets_args *args; args = (struct manage_sets_args *)arg; if (no->set == (uint8_t)args->set) no->set = args->new_set; return (0); } static int test_sets_cb(struct namedobj_instance *ni, struct named_object *no, void *arg) { struct manage_sets_args *args; args = (struct manage_sets_args *)arg; if (no->set != (uint8_t)args->set) return (0); if (ipfw_objhash_lookup_name_type(ni, args->new_set, no->etlv, no->name) != NULL) return (EEXIST); return (0); } /* * Generic function to handler moving and swapping sets. */ int ipfw_obj_manage_sets(struct namedobj_instance *ni, uint16_t type, uint16_t set, uint8_t new_set, enum ipfw_sets_cmd cmd) { struct manage_sets_args args; struct named_object *no; args.set = set; args.new_set = new_set; switch (cmd) { case SWAP_ALL: return (ipfw_objhash_foreach_type(ni, swap_sets_cb, &args, type)); case TEST_ALL: return (ipfw_objhash_foreach_type(ni, test_sets_cb, &args, type)); case MOVE_ALL: return (ipfw_objhash_foreach_type(ni, move_sets_cb, &args, type)); case COUNT_ONE: /* * @set used to pass kidx. * When @new_set is zero - reset object counter, * otherwise increment it. */ no = ipfw_objhash_lookup_kidx(ni, set); if (new_set != 0) no->ocnt++; else no->ocnt = 0; return (0); case TEST_ONE: /* @set used to pass kidx */ no = ipfw_objhash_lookup_kidx(ni, set); /* * First check number of references: * when it differs, this mean other rules are holding * reference to given object, so it is not possible to * change its set. Note that refcnt may account references * to some going-to-be-added rules. Since we don't know * their numbers (and even if they will be added) it is * perfectly OK to return error here. */ if (no->ocnt != no->refcnt) return (EBUSY); if (ipfw_objhash_lookup_name_type(ni, new_set, type, no->name) != NULL) return (EEXIST); return (0); case MOVE_ONE: /* @set used to pass kidx */ no = ipfw_objhash_lookup_kidx(ni, set); no->set = new_set; return (0); } return (EINVAL); } /* * Delete rules matching range @rt. * Saves number of deleted rules in @ndel. * * Returns 0 on success. */ static int delete_range(struct ip_fw_chain *chain, ipfw_range_tlv *rt, int *ndel) { struct ip_fw *reap, *rule, **map; int end, start; int i, n, ndyn, ofs; reap = NULL; IPFW_UH_WLOCK(chain); /* arbitrate writers */ /* * Stage 1: Determine range to inspect. * Range is half-inclusive, e.g [start, end). */ start = 0; end = chain->n_rules - 1; if ((rt->flags & IPFW_RCFLAG_RANGE) != 0) { start = ipfw_find_rule(chain, rt->start_rule, 0); if (rt->end_rule >= IPFW_DEFAULT_RULE) rt->end_rule = IPFW_DEFAULT_RULE - 1; end = ipfw_find_rule(chain, rt->end_rule, UINT32_MAX); } /* Allocate new map of the same size */ map = get_map(chain, 0, 1 /* locked */); if (map == NULL) { IPFW_UH_WUNLOCK(chain); return (ENOMEM); } n = 0; ndyn = 0; ofs = start; /* 1. bcopy the initial part of the map */ if (start > 0) bcopy(chain->map, map, start * sizeof(struct ip_fw *)); /* 2. copy active rules between start and end */ for (i = start; i < end; i++) { rule = chain->map[i]; if (ipfw_match_range(rule, rt) == 0) { map[ofs++] = rule; continue; } n++; if (ipfw_is_dyn_rule(rule) != 0) ndyn++; } /* 3. copy the final part of the map */ bcopy(chain->map + end, map + ofs, (chain->n_rules - end) * sizeof(struct ip_fw *)); /* 4. recalculate skipto cache */ update_skipto_cache(chain, map); /* 5. swap the maps (under UH_WLOCK + WHLOCK) */ map = swap_map(chain, map, chain->n_rules - n); /* 6. Remove all dynamic states originated by deleted rules */ if (ndyn > 0) ipfw_expire_dyn_states(chain, rt); /* 7. now remove the rules deleted from the old map */ for (i = start; i < end; i++) { rule = map[i]; if (ipfw_match_range(rule, rt) == 0) continue; chain->static_len -= RULEUSIZE0(rule); ipfw_reap_add(chain, &reap, rule); } IPFW_UH_WUNLOCK(chain); ipfw_reap_rules(reap); if (map != NULL) free(map, M_IPFW); *ndel = n; return (0); } static int move_objects(struct ip_fw_chain *ch, ipfw_range_tlv *rt) { struct opcode_obj_rewrite *rw; struct ip_fw *rule; ipfw_insn *cmd; int cmdlen, i, l, c; uint16_t kidx; IPFW_UH_WLOCK_ASSERT(ch); /* Stage 1: count number of references by given rules */ for (c = 0, i = 0; i < ch->n_rules - 1; i++) { rule = ch->map[i]; if (ipfw_match_range(rule, rt) == 0) continue; if (rule->set == rt->new_set) /* nothing to do */ continue; /* Search opcodes with named objects */ for (l = rule->cmd_len, cmdlen = 0, cmd = rule->cmd; l > 0; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); rw = find_op_rw(cmd, &kidx, NULL); if (rw == NULL || rw->manage_sets == NULL) continue; /* * When manage_sets() returns non-zero value to * COUNT_ONE command, consider this as an object * doesn't support sets (e.g. disabled with sysctl). * So, skip checks for this object. */ if (rw->manage_sets(ch, kidx, 1, COUNT_ONE) != 0) continue; c++; } } if (c == 0) /* No objects found */ return (0); /* Stage 2: verify "ownership" */ for (c = 0, i = 0; (i < ch->n_rules - 1) && c == 0; i++) { rule = ch->map[i]; if (ipfw_match_range(rule, rt) == 0) continue; if (rule->set == rt->new_set) /* nothing to do */ continue; /* Search opcodes with named objects */ for (l = rule->cmd_len, cmdlen = 0, cmd = rule->cmd; l > 0 && c == 0; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); rw = find_op_rw(cmd, &kidx, NULL); if (rw == NULL || rw->manage_sets == NULL) continue; /* Test for ownership and conflicting names */ c = rw->manage_sets(ch, kidx, (uint8_t)rt->new_set, TEST_ONE); } } /* Stage 3: change set and cleanup */ for (i = 0; i < ch->n_rules - 1; i++) { rule = ch->map[i]; if (ipfw_match_range(rule, rt) == 0) continue; if (rule->set == rt->new_set) /* nothing to do */ continue; /* Search opcodes with named objects */ for (l = rule->cmd_len, cmdlen = 0, cmd = rule->cmd; l > 0; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); rw = find_op_rw(cmd, &kidx, NULL); if (rw == NULL || rw->manage_sets == NULL) continue; /* cleanup object counter */ rw->manage_sets(ch, kidx, 0 /* reset counter */, COUNT_ONE); if (c != 0) continue; /* change set */ rw->manage_sets(ch, kidx, (uint8_t)rt->new_set, MOVE_ONE); } } return (c); }/* * Changes set of given rule rannge @rt * with each other. * * Returns 0 on success. */ static int move_range(struct ip_fw_chain *chain, ipfw_range_tlv *rt) { struct ip_fw *rule; int i; IPFW_UH_WLOCK(chain); /* * Move rules with matching paramenerts to a new set. * This one is much more complex. We have to ensure * that all referenced tables (if any) are referenced * by given rule subset only. Otherwise, we can't move * them to new set and have to return error. */ if ((i = move_objects(chain, rt)) != 0) { IPFW_UH_WUNLOCK(chain); return (i); } /* XXX: We have to do swap holding WLOCK */ for (i = 0; i < chain->n_rules; i++) { rule = chain->map[i]; if (ipfw_match_range(rule, rt) == 0) continue; rule->set = rt->new_set; } IPFW_UH_WUNLOCK(chain); return (0); } /* * Clear counters for a specific rule. * Normally run under IPFW_UH_RLOCK, but these are idempotent ops * so we only care that rules do not disappear. */ static void clear_counters(struct ip_fw *rule, int log_only) { ipfw_insn_log *l = (ipfw_insn_log *)ACTION_PTR(rule); if (log_only == 0) IPFW_ZERO_RULE_COUNTER(rule); if (l->o.opcode == O_LOG) l->log_left = l->max_log; } /* * Flushes rules counters and/or log values on matching range. * * Returns number of items cleared. */ static int clear_range(struct ip_fw_chain *chain, ipfw_range_tlv *rt, int log_only) { struct ip_fw *rule; int num; int i; num = 0; rt->flags |= IPFW_RCFLAG_DEFAULT; IPFW_UH_WLOCK(chain); /* arbitrate writers */ for (i = 0; i < chain->n_rules; i++) { rule = chain->map[i]; if (ipfw_match_range(rule, rt) == 0) continue; clear_counters(rule, log_only); num++; } IPFW_UH_WUNLOCK(chain); return (num); } static int check_range_tlv(ipfw_range_tlv *rt) { if (rt->head.length != sizeof(*rt)) return (1); if (rt->start_rule > rt->end_rule) return (1); if (rt->set >= IPFW_MAX_SETS || rt->new_set >= IPFW_MAX_SETS) return (1); if ((rt->flags & IPFW_RCFLAG_USER) != rt->flags) return (1); return (0); } /* * Delete rules matching specified parameters * Data layout (v0)(current): * Request: [ ipfw_obj_header ipfw_range_tlv ] * Reply: [ ipfw_obj_header ipfw_range_tlv ] * * Saves number of deleted rules in ipfw_range_tlv->new_set. * * Returns 0 on success. */ static int del_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3, struct sockopt_data *sd) { ipfw_range_header *rh; int error, ndel; if (sd->valsize != sizeof(*rh)) return (EINVAL); rh = (ipfw_range_header *)ipfw_get_sopt_space(sd, sd->valsize); if (check_range_tlv(&rh->range) != 0) return (EINVAL); ndel = 0; if ((error = delete_range(chain, &rh->range, &ndel)) != 0) return (error); /* Save number of rules deleted */ rh->range.new_set = ndel; return (0); } /* * Move rules/sets matching specified parameters * Data layout (v0)(current): * Request: [ ipfw_obj_header ipfw_range_tlv ] * * Returns 0 on success. */ static int move_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3, struct sockopt_data *sd) { ipfw_range_header *rh; if (sd->valsize != sizeof(*rh)) return (EINVAL); rh = (ipfw_range_header *)ipfw_get_sopt_space(sd, sd->valsize); if (check_range_tlv(&rh->range) != 0) return (EINVAL); return (move_range(chain, &rh->range)); } /* * Clear rule accounting data matching specified parameters * Data layout (v0)(current): * Request: [ ipfw_obj_header ipfw_range_tlv ] * Reply: [ ipfw_obj_header ipfw_range_tlv ] * * Saves number of cleared rules in ipfw_range_tlv->new_set. * * Returns 0 on success. */ static int clear_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3, struct sockopt_data *sd) { ipfw_range_header *rh; int log_only, num; char *msg; if (sd->valsize != sizeof(*rh)) return (EINVAL); rh = (ipfw_range_header *)ipfw_get_sopt_space(sd, sd->valsize); if (check_range_tlv(&rh->range) != 0) return (EINVAL); log_only = (op3->opcode == IP_FW_XRESETLOG); num = clear_range(chain, &rh->range, log_only); if (rh->range.flags & IPFW_RCFLAG_ALL) msg = log_only ? "All logging counts reset" : "Accounting cleared"; else msg = log_only ? "logging count reset" : "cleared"; if (V_fw_verbose) { int lev = LOG_SECURITY | LOG_NOTICE; log(lev, "ipfw: %s.\n", msg); } /* Save number of rules cleared */ rh->range.new_set = num; return (0); } static void enable_sets(struct ip_fw_chain *chain, ipfw_range_tlv *rt) { uint32_t v_set; IPFW_UH_WLOCK_ASSERT(chain); /* Change enabled/disabled sets mask */ v_set = (V_set_disable | rt->set) & ~rt->new_set; v_set &= ~(1 << RESVD_SET); /* set RESVD_SET always enabled */ IPFW_WLOCK(chain); V_set_disable = v_set; IPFW_WUNLOCK(chain); } static int swap_sets(struct ip_fw_chain *chain, ipfw_range_tlv *rt, int mv) { struct opcode_obj_rewrite *rw; struct ip_fw *rule; int i; IPFW_UH_WLOCK_ASSERT(chain); if (rt->set == rt->new_set) /* nothing to do */ return (0); if (mv != 0) { /* * Berfore moving the rules we need to check that * there aren't any conflicting named objects. */ for (rw = ctl3_rewriters; rw < ctl3_rewriters + ctl3_rsize; rw++) { if (rw->manage_sets == NULL) continue; i = rw->manage_sets(chain, (uint8_t)rt->set, (uint8_t)rt->new_set, TEST_ALL); if (i != 0) return (EEXIST); } } /* Swap or move two sets */ for (i = 0; i < chain->n_rules - 1; i++) { rule = chain->map[i]; if (rule->set == (uint8_t)rt->set) rule->set = (uint8_t)rt->new_set; else if (rule->set == (uint8_t)rt->new_set && mv == 0) rule->set = (uint8_t)rt->set; } for (rw = ctl3_rewriters; rw < ctl3_rewriters + ctl3_rsize; rw++) { if (rw->manage_sets == NULL) continue; rw->manage_sets(chain, (uint8_t)rt->set, (uint8_t)rt->new_set, mv != 0 ? MOVE_ALL: SWAP_ALL); } return (0); } /* * Swaps or moves set * Data layout (v0)(current): * Request: [ ipfw_obj_header ipfw_range_tlv ] * * Returns 0 on success. */ static int manage_sets(struct ip_fw_chain *chain, ip_fw3_opheader *op3, struct sockopt_data *sd) { ipfw_range_header *rh; int ret; if (sd->valsize != sizeof(*rh)) return (EINVAL); rh = (ipfw_range_header *)ipfw_get_sopt_space(sd, sd->valsize); if (rh->range.head.length != sizeof(ipfw_range_tlv)) return (1); /* enable_sets() expects bitmasks. */ if (op3->opcode != IP_FW_SET_ENABLE && (rh->range.set >= IPFW_MAX_SETS || rh->range.new_set >= IPFW_MAX_SETS)) return (EINVAL); ret = 0; IPFW_UH_WLOCK(chain); switch (op3->opcode) { case IP_FW_SET_SWAP: case IP_FW_SET_MOVE: ret = swap_sets(chain, &rh->range, op3->opcode == IP_FW_SET_MOVE); break; case IP_FW_SET_ENABLE: enable_sets(chain, &rh->range); break; } IPFW_UH_WUNLOCK(chain); return (ret); } /** * Remove all rules with given number, or do set manipulation. * Assumes chain != NULL && *chain != NULL. * * The argument is an uint32_t. The low 16 bit are the rule or set number; * the next 8 bits are the new set; the top 8 bits indicate the command: * * 0 delete rules numbered "rulenum" * 1 delete rules in set "rulenum" * 2 move rules "rulenum" to set "new_set" * 3 move rules from set "rulenum" to set "new_set" * 4 swap sets "rulenum" and "new_set" * 5 delete rules "rulenum" and set "new_set" */ static int del_entry(struct ip_fw_chain *chain, uint32_t arg) { uint32_t num; /* rule number or old_set */ uint8_t cmd, new_set; int do_del, ndel; int error = 0; ipfw_range_tlv rt; num = arg & 0xffff; cmd = (arg >> 24) & 0xff; new_set = (arg >> 16) & 0xff; if (cmd > 5 || new_set > RESVD_SET) return EINVAL; if (cmd == 0 || cmd == 2 || cmd == 5) { if (num >= IPFW_DEFAULT_RULE) return EINVAL; } else { if (num > RESVD_SET) /* old_set */ return EINVAL; } /* Convert old requests into new representation */ memset(&rt, 0, sizeof(rt)); rt.start_rule = num; rt.end_rule = num; rt.set = num; rt.new_set = new_set; do_del = 0; switch (cmd) { case 0: /* delete rules numbered "rulenum" */ if (num == 0) rt.flags |= IPFW_RCFLAG_ALL; else rt.flags |= IPFW_RCFLAG_RANGE; do_del = 1; break; case 1: /* delete rules in set "rulenum" */ rt.flags |= IPFW_RCFLAG_SET; do_del = 1; break; case 5: /* delete rules "rulenum" and set "new_set" */ rt.flags |= IPFW_RCFLAG_RANGE | IPFW_RCFLAG_SET; rt.set = new_set; rt.new_set = 0; do_del = 1; break; case 2: /* move rules "rulenum" to set "new_set" */ rt.flags |= IPFW_RCFLAG_RANGE; break; case 3: /* move rules from set "rulenum" to set "new_set" */ IPFW_UH_WLOCK(chain); error = swap_sets(chain, &rt, 1); IPFW_UH_WUNLOCK(chain); return (error); case 4: /* swap sets "rulenum" and "new_set" */ IPFW_UH_WLOCK(chain); error = swap_sets(chain, &rt, 0); IPFW_UH_WUNLOCK(chain); return (error); default: return (ENOTSUP); } if (do_del != 0) { if ((error = delete_range(chain, &rt, &ndel)) != 0) return (error); if (ndel == 0 && (cmd != 1 && num != 0)) return (EINVAL); return (0); } return (move_range(chain, &rt)); } /** * Reset some or all counters on firewall rules. * The argument `arg' is an u_int32_t. The low 16 bit are the rule number, * the next 8 bits are the set number, the top 8 bits are the command: * 0 work with rules from all set's; * 1 work with rules only from specified set. * Specified rule number is zero if we want to clear all entries. * log_only is 1 if we only want to reset logs, zero otherwise. */ static int zero_entry(struct ip_fw_chain *chain, u_int32_t arg, int log_only) { struct ip_fw *rule; char *msg; int i; uint16_t rulenum = arg & 0xffff; uint8_t set = (arg >> 16) & 0xff; uint8_t cmd = (arg >> 24) & 0xff; if (cmd > 1) return (EINVAL); if (cmd == 1 && set > RESVD_SET) return (EINVAL); IPFW_UH_RLOCK(chain); if (rulenum == 0) { V_norule_counter = 0; for (i = 0; i < chain->n_rules; i++) { rule = chain->map[i]; /* Skip rules not in our set. */ if (cmd == 1 && rule->set != set) continue; clear_counters(rule, log_only); } msg = log_only ? "All logging counts reset" : "Accounting cleared"; } else { int cleared = 0; for (i = 0; i < chain->n_rules; i++) { rule = chain->map[i]; if (rule->rulenum == rulenum) { if (cmd == 0 || rule->set == set) clear_counters(rule, log_only); cleared = 1; } if (rule->rulenum > rulenum) break; } if (!cleared) { /* we did not find any matching rules */ IPFW_UH_RUNLOCK(chain); return (EINVAL); } msg = log_only ? "logging count reset" : "cleared"; } IPFW_UH_RUNLOCK(chain); if (V_fw_verbose) { int lev = LOG_SECURITY | LOG_NOTICE; if (rulenum) log(lev, "ipfw: Entry %d %s.\n", rulenum, msg); else log(lev, "ipfw: %s.\n", msg); } return (0); } /* * Check rule head in FreeBSD11 format * */ static int check_ipfw_rule1(struct ip_fw_rule *rule, int size, struct rule_check_info *ci) { int l; if (size < sizeof(*rule)) { printf("ipfw: rule too short\n"); return (EINVAL); } /* Check for valid cmd_len */ l = roundup2(RULESIZE(rule), sizeof(uint64_t)); if (l != size) { printf("ipfw: size mismatch (have %d want %d)\n", size, l); return (EINVAL); } if (rule->act_ofs >= rule->cmd_len) { printf("ipfw: bogus action offset (%u > %u)\n", rule->act_ofs, rule->cmd_len - 1); return (EINVAL); } if (rule->rulenum > IPFW_DEFAULT_RULE - 1) return (EINVAL); return (check_ipfw_rule_body(rule->cmd, rule->cmd_len, ci)); } /* * Check rule head in FreeBSD8 format * */ static int check_ipfw_rule0(struct ip_fw_rule0 *rule, int size, struct rule_check_info *ci) { int l; if (size < sizeof(*rule)) { printf("ipfw: rule too short\n"); return (EINVAL); } /* Check for valid cmd_len */ l = sizeof(*rule) + rule->cmd_len * 4 - 4; if (l != size) { printf("ipfw: size mismatch (have %d want %d)\n", size, l); return (EINVAL); } if (rule->act_ofs >= rule->cmd_len) { printf("ipfw: bogus action offset (%u > %u)\n", rule->act_ofs, rule->cmd_len - 1); return (EINVAL); } if (rule->rulenum > IPFW_DEFAULT_RULE - 1) return (EINVAL); return (check_ipfw_rule_body(rule->cmd, rule->cmd_len, ci)); } static int check_ipfw_rule_body(ipfw_insn *cmd, int cmd_len, struct rule_check_info *ci) { int cmdlen, l; int have_action; have_action = 0; /* * Now go for the individual checks. Very simple ones, basically only * instruction sizes. */ for (l = cmd_len; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); if (cmdlen > l) { printf("ipfw: opcode %d size truncated\n", cmd->opcode); return EINVAL; } switch (cmd->opcode) { case O_PROBE_STATE: case O_KEEP_STATE: if (cmdlen != F_INSN_SIZE(ipfw_insn)) goto bad_size; ci->object_opcodes++; break; case O_PROTO: case O_IP_SRC_ME: case O_IP_DST_ME: case O_LAYER2: case O_IN: case O_FRAG: case O_DIVERTED: case O_IPOPT: case O_IPTOS: case O_IPPRECEDENCE: case O_IPVER: case O_SOCKARG: case O_TCPFLAGS: case O_TCPOPTS: case O_ESTAB: case O_VERREVPATH: case O_VERSRCREACH: case O_ANTISPOOF: case O_IPSEC: #ifdef INET6 case O_IP6_SRC_ME: case O_IP6_DST_ME: case O_EXT_HDR: case O_IP6: #endif case O_IP4: case O_TAG: case O_SKIP_ACTION: if (cmdlen != F_INSN_SIZE(ipfw_insn)) goto bad_size; break; case O_EXTERNAL_ACTION: if (cmd->arg1 == 0 || cmdlen != F_INSN_SIZE(ipfw_insn)) { printf("ipfw: invalid external " "action opcode\n"); return (EINVAL); } ci->object_opcodes++; /* * Do we have O_EXTERNAL_INSTANCE or O_EXTERNAL_DATA * opcode? */ if (l != cmdlen) { l -= cmdlen; cmd += cmdlen; cmdlen = F_LEN(cmd); if (cmd->opcode == O_EXTERNAL_DATA) goto check_action; if (cmd->opcode != O_EXTERNAL_INSTANCE) { printf("ipfw: invalid opcode " "next to external action %u\n", cmd->opcode); return (EINVAL); } if (cmd->arg1 == 0 || cmdlen != F_INSN_SIZE(ipfw_insn)) { printf("ipfw: invalid external " "action instance opcode\n"); return (EINVAL); } ci->object_opcodes++; } goto check_action; case O_FIB: if (cmdlen != F_INSN_SIZE(ipfw_insn)) goto bad_size; if (cmd->arg1 >= rt_numfibs) { printf("ipfw: invalid fib number %d\n", cmd->arg1); return EINVAL; } break; case O_SETFIB: if (cmdlen != F_INSN_SIZE(ipfw_insn)) goto bad_size; if ((cmd->arg1 != IP_FW_TARG) && ((cmd->arg1 & 0x7FFF) >= rt_numfibs)) { printf("ipfw: invalid fib number %d\n", cmd->arg1 & 0x7FFF); return EINVAL; } goto check_action; case O_UID: case O_GID: case O_JAIL: case O_IP_SRC: case O_IP_DST: case O_TCPSEQ: case O_TCPACK: case O_PROB: case O_ICMPTYPE: if (cmdlen != F_INSN_SIZE(ipfw_insn_u32)) goto bad_size; break; case O_LIMIT: if (cmdlen != F_INSN_SIZE(ipfw_insn_limit)) goto bad_size; ci->object_opcodes++; break; case O_LOG: if (cmdlen != F_INSN_SIZE(ipfw_insn_log)) goto bad_size; ((ipfw_insn_log *)cmd)->log_left = ((ipfw_insn_log *)cmd)->max_log; break; case O_IP_SRC_MASK: case O_IP_DST_MASK: /* only odd command lengths */ if ((cmdlen & 1) == 0) goto bad_size; break; case O_IP_SRC_SET: case O_IP_DST_SET: if (cmd->arg1 == 0 || cmd->arg1 > 256) { printf("ipfw: invalid set size %d\n", cmd->arg1); return EINVAL; } if (cmdlen != F_INSN_SIZE(ipfw_insn_u32) + (cmd->arg1+31)/32 ) goto bad_size; break; case O_IP_SRC_LOOKUP: if (cmdlen > F_INSN_SIZE(ipfw_insn_u32)) goto bad_size; case O_IP_DST_LOOKUP: if (cmd->arg1 >= V_fw_tables_max) { printf("ipfw: invalid table number %d\n", cmd->arg1); return (EINVAL); } if (cmdlen != F_INSN_SIZE(ipfw_insn) && cmdlen != F_INSN_SIZE(ipfw_insn_u32) + 1 && cmdlen != F_INSN_SIZE(ipfw_insn_u32)) goto bad_size; ci->object_opcodes++; break; case O_IP_FLOW_LOOKUP: if (cmd->arg1 >= V_fw_tables_max) { printf("ipfw: invalid table number %d\n", cmd->arg1); return (EINVAL); } if (cmdlen != F_INSN_SIZE(ipfw_insn) && cmdlen != F_INSN_SIZE(ipfw_insn_u32)) goto bad_size; ci->object_opcodes++; break; case O_MACADDR2: if (cmdlen != F_INSN_SIZE(ipfw_insn_mac)) goto bad_size; break; case O_NOP: case O_IPID: case O_IPTTL: case O_IPLEN: case O_TCPDATALEN: case O_TCPWIN: case O_TAGGED: if (cmdlen < 1 || cmdlen > 31) goto bad_size; break; case O_DSCP: if (cmdlen != F_INSN_SIZE(ipfw_insn_u32) + 1) goto bad_size; break; case O_MAC_TYPE: case O_IP_SRCPORT: case O_IP_DSTPORT: /* XXX artificial limit, 30 port pairs */ if (cmdlen < 2 || cmdlen > 31) goto bad_size; break; case O_RECV: case O_XMIT: case O_VIA: if (cmdlen != F_INSN_SIZE(ipfw_insn_if)) goto bad_size; ci->object_opcodes++; break; case O_ALTQ: if (cmdlen != F_INSN_SIZE(ipfw_insn_altq)) goto bad_size; break; case O_PIPE: case O_QUEUE: if (cmdlen != F_INSN_SIZE(ipfw_insn)) goto bad_size; goto check_action; case O_FORWARD_IP: if (cmdlen != F_INSN_SIZE(ipfw_insn_sa)) goto bad_size; goto check_action; #ifdef INET6 case O_FORWARD_IP6: if (cmdlen != F_INSN_SIZE(ipfw_insn_sa6)) goto bad_size; goto check_action; #endif /* INET6 */ case O_DIVERT: case O_TEE: if (ip_divert_ptr == NULL) return EINVAL; else goto check_size; case O_NETGRAPH: case O_NGTEE: if (ng_ipfw_input_p == NULL) return EINVAL; else goto check_size; case O_NAT: if (!IPFW_NAT_LOADED) return EINVAL; if (cmdlen != F_INSN_SIZE(ipfw_insn_nat)) goto bad_size; goto check_action; case O_CHECK_STATE: ci->object_opcodes++; /* FALLTHROUGH */ case O_FORWARD_MAC: /* XXX not implemented yet */ case O_COUNT: case O_ACCEPT: case O_DENY: case O_REJECT: case O_SETDSCP: #ifdef INET6 case O_UNREACH6: #endif case O_SKIPTO: case O_REASS: case O_CALLRETURN: check_size: if (cmdlen != F_INSN_SIZE(ipfw_insn)) goto bad_size; check_action: if (have_action) { printf("ipfw: opcode %d, multiple actions" " not allowed\n", cmd->opcode); return (EINVAL); } have_action = 1; if (l != cmdlen) { printf("ipfw: opcode %d, action must be" " last opcode\n", cmd->opcode); return (EINVAL); } break; #ifdef INET6 case O_IP6_SRC: case O_IP6_DST: if (cmdlen != F_INSN_SIZE(struct in6_addr) + F_INSN_SIZE(ipfw_insn)) goto bad_size; break; case O_FLOW6ID: if (cmdlen != F_INSN_SIZE(ipfw_insn_u32) + ((ipfw_insn_u32 *)cmd)->o.arg1) goto bad_size; break; case O_IP6_SRC_MASK: case O_IP6_DST_MASK: if ( !(cmdlen & 1) || cmdlen > 127) goto bad_size; break; case O_ICMP6TYPE: if( cmdlen != F_INSN_SIZE( ipfw_insn_icmp6 ) ) goto bad_size; break; #endif default: switch (cmd->opcode) { #ifndef INET6 case O_IP6_SRC_ME: case O_IP6_DST_ME: case O_EXT_HDR: case O_IP6: case O_UNREACH6: case O_IP6_SRC: case O_IP6_DST: case O_FLOW6ID: case O_IP6_SRC_MASK: case O_IP6_DST_MASK: case O_ICMP6TYPE: printf("ipfw: no IPv6 support in kernel\n"); return (EPROTONOSUPPORT); #endif default: printf("ipfw: opcode %d, unknown opcode\n", cmd->opcode); return (EINVAL); } } } if (have_action == 0) { printf("ipfw: missing action\n"); return (EINVAL); } return 0; bad_size: printf("ipfw: opcode %d size %d wrong\n", cmd->opcode, cmdlen); return (EINVAL); } /* * Translation of requests for compatibility with FreeBSD 7.2/8. * a static variable tells us if we have an old client from userland, * and if necessary we translate requests and responses between the * two formats. */ static int is7 = 0; struct ip_fw7 { struct ip_fw7 *next; /* linked list of rules */ struct ip_fw7 *next_rule; /* ptr to next [skipto] rule */ /* 'next_rule' is used to pass up 'set_disable' status */ uint16_t act_ofs; /* offset of action in 32-bit units */ uint16_t cmd_len; /* # of 32-bit words in cmd */ uint16_t rulenum; /* rule number */ uint8_t set; /* rule set (0..31) */ // #define RESVD_SET 31 /* set for default and persistent rules */ uint8_t _pad; /* padding */ // uint32_t id; /* rule id, only in v.8 */ /* These fields are present in all rules. */ uint64_t pcnt; /* Packet counter */ uint64_t bcnt; /* Byte counter */ uint32_t timestamp; /* tv_sec of last match */ ipfw_insn cmd[1]; /* storage for commands */ }; static int convert_rule_to_7(struct ip_fw_rule0 *rule); static int convert_rule_to_8(struct ip_fw_rule0 *rule); #ifndef RULESIZE7 #define RULESIZE7(rule) (sizeof(struct ip_fw7) + \ ((struct ip_fw7 *)(rule))->cmd_len * 4 - 4) #endif /* * Copy the static and dynamic rules to the supplied buffer * and return the amount of space actually used. * Must be run under IPFW_UH_RLOCK */ static size_t ipfw_getrules(struct ip_fw_chain *chain, void *buf, size_t space) { char *bp = buf; char *ep = bp + space; struct ip_fw *rule; struct ip_fw_rule0 *dst; struct timeval boottime; int error, i, l, warnflag; time_t boot_seconds; warnflag = 0; getboottime(&boottime); boot_seconds = boottime.tv_sec; for (i = 0; i < chain->n_rules; i++) { rule = chain->map[i]; if (is7) { /* Convert rule to FreeBSd 7.2 format */ l = RULESIZE7(rule); if (bp + l + sizeof(uint32_t) <= ep) { bcopy(rule, bp, l + sizeof(uint32_t)); error = set_legacy_obj_kidx(chain, (struct ip_fw_rule0 *)bp); if (error != 0) return (0); error = convert_rule_to_7((struct ip_fw_rule0 *) bp); if (error) return 0; /*XXX correct? */ /* * XXX HACK. Store the disable mask in the "next" * pointer in a wild attempt to keep the ABI the same. * Why do we do this on EVERY rule? */ bcopy(&V_set_disable, &(((struct ip_fw7 *)bp)->next_rule), sizeof(V_set_disable)); if (((struct ip_fw7 *)bp)->timestamp) ((struct ip_fw7 *)bp)->timestamp += boot_seconds; bp += l; } continue; /* go to next rule */ } l = RULEUSIZE0(rule); if (bp + l > ep) { /* should not happen */ printf("overflow dumping static rules\n"); break; } dst = (struct ip_fw_rule0 *)bp; export_rule0(rule, dst, l); error = set_legacy_obj_kidx(chain, dst); /* * XXX HACK. Store the disable mask in the "next" * pointer in a wild attempt to keep the ABI the same. * Why do we do this on EVERY rule? * * XXX: "ipfw set show" (ab)uses IP_FW_GET to read disabled mask * so we need to fail _after_ saving at least one mask. */ bcopy(&V_set_disable, &dst->next_rule, sizeof(V_set_disable)); if (dst->timestamp) dst->timestamp += boot_seconds; bp += l; if (error != 0) { if (error == 2) { /* Non-fatal table rewrite error. */ warnflag = 1; continue; } printf("Stop on rule %d. Fail to convert table\n", rule->rulenum); break; } } if (warnflag != 0) printf("ipfw: process %s is using legacy interfaces," " consider rebuilding\n", ""); ipfw_get_dynamic(chain, &bp, ep); /* protected by the dynamic lock */ return (bp - (char *)buf); } struct dump_args { uint32_t b; /* start rule */ uint32_t e; /* end rule */ uint32_t rcount; /* number of rules */ uint32_t rsize; /* rules size */ uint32_t tcount; /* number of tables */ int rcounters; /* counters */ + uint32_t *bmask; /* index bitmask of used named objects */ }; void ipfw_export_obj_ntlv(struct named_object *no, ipfw_obj_ntlv *ntlv) { ntlv->head.type = no->etlv; ntlv->head.length = sizeof(*ntlv); ntlv->idx = no->kidx; strlcpy(ntlv->name, no->name, sizeof(ntlv->name)); } /* * Export named object info in instance @ni, identified by @kidx * to ipfw_obj_ntlv. TLV is allocated from @sd space. * * Returns 0 on success. */ static int export_objhash_ntlv(struct namedobj_instance *ni, uint16_t kidx, struct sockopt_data *sd) { struct named_object *no; ipfw_obj_ntlv *ntlv; no = ipfw_objhash_lookup_kidx(ni, kidx); KASSERT(no != NULL, ("invalid object kernel index passed")); ntlv = (ipfw_obj_ntlv *)ipfw_get_sopt_space(sd, sizeof(*ntlv)); if (ntlv == NULL) return (ENOMEM); ipfw_export_obj_ntlv(no, ntlv); return (0); } +static int +export_named_objects(struct namedobj_instance *ni, struct dump_args *da, + struct sockopt_data *sd) +{ + int error, i; + + for (i = 0; i < IPFW_TABLES_MAX && da->tcount > 0; i++) { + if ((da->bmask[i / 32] & (1 << (i % 32))) == 0) + continue; + if ((error = export_objhash_ntlv(ni, i, sd)) != 0) + return (error); + da->tcount--; + } + return (0); +} + +static int +dump_named_objects(struct ip_fw_chain *ch, struct dump_args *da, + struct sockopt_data *sd) +{ + ipfw_obj_ctlv *ctlv; + int error; + + MPASS(da->tcount > 0); + /* Header first */ + ctlv = (ipfw_obj_ctlv *)ipfw_get_sopt_space(sd, sizeof(*ctlv)); + if (ctlv == NULL) + return (ENOMEM); + ctlv->head.type = IPFW_TLV_TBLNAME_LIST; + ctlv->head.length = da->tcount * sizeof(ipfw_obj_ntlv) + + sizeof(*ctlv); + ctlv->count = da->tcount; + ctlv->objsize = sizeof(ipfw_obj_ntlv); + + /* Dump table names first (if any) */ + error = export_named_objects(ipfw_get_table_objhash(ch), da, sd); + if (error != 0) + return (error); + /* Then dump another named objects */ + da->bmask += IPFW_TABLES_MAX / 32; + return (export_named_objects(CHAIN_TO_SRV(ch), da, sd)); +} + /* * Dumps static rules with table TLVs in buffer @sd. * * Returns 0 on success. */ static int dump_static_rules(struct ip_fw_chain *chain, struct dump_args *da, - uint32_t *bmask, struct sockopt_data *sd) + struct sockopt_data *sd) { - int error; - int i, l; - uint32_t tcount; ipfw_obj_ctlv *ctlv; struct ip_fw *krule; - struct namedobj_instance *ni; caddr_t dst; + int i, l; - /* Dump table names first (if any) */ - if (da->tcount > 0) { - /* Header first */ - ctlv = (ipfw_obj_ctlv *)ipfw_get_sopt_space(sd, sizeof(*ctlv)); - if (ctlv == NULL) - return (ENOMEM); - ctlv->head.type = IPFW_TLV_TBLNAME_LIST; - ctlv->head.length = da->tcount * sizeof(ipfw_obj_ntlv) + - sizeof(*ctlv); - ctlv->count = da->tcount; - ctlv->objsize = sizeof(ipfw_obj_ntlv); - } - - i = 0; - tcount = da->tcount; - ni = ipfw_get_table_objhash(chain); - while (tcount > 0) { - if ((bmask[i / 32] & (1 << (i % 32))) == 0) { - i++; - continue; - } - - /* Jump to shared named object bitmask */ - if (i >= IPFW_TABLES_MAX) { - ni = CHAIN_TO_SRV(chain); - i -= IPFW_TABLES_MAX; - bmask += IPFW_TABLES_MAX / 32; - } - - if ((error = export_objhash_ntlv(ni, i, sd)) != 0) - return (error); - - i++; - tcount--; - } - /* Dump rules */ ctlv = (ipfw_obj_ctlv *)ipfw_get_sopt_space(sd, sizeof(*ctlv)); if (ctlv == NULL) return (ENOMEM); ctlv->head.type = IPFW_TLV_RULE_LIST; ctlv->head.length = da->rsize + sizeof(*ctlv); ctlv->count = da->rcount; for (i = da->b; i < da->e; i++) { krule = chain->map[i]; l = RULEUSIZE1(krule) + sizeof(ipfw_obj_tlv); if (da->rcounters != 0) l += sizeof(struct ip_fw_bcounter); dst = (caddr_t)ipfw_get_sopt_space(sd, l); if (dst == NULL) return (ENOMEM); export_rule1(krule, dst, l, da->rcounters); } return (0); } +int +ipfw_mark_object_kidx(uint32_t *bmask, uint16_t etlv, uint16_t kidx) +{ + uint32_t bidx; + + /* + * Maintain separate bitmasks for table and non-table objects. + */ + bidx = (etlv == IPFW_TLV_TBL_NAME) ? 0: IPFW_TABLES_MAX / 32; + bidx += kidx / 32; + if ((bmask[bidx] & (1 << (kidx % 32))) != 0) + return (0); + + bmask[bidx] |= 1 << (kidx % 32); + return (1); +} + /* * Marks every object index used in @rule with bit in @bmask. * Used to generate bitmask of referenced tables/objects for given ruleset * or its part. - * - * Returns number of newly-referenced objects. */ -static int -mark_object_kidx(struct ip_fw_chain *ch, struct ip_fw *rule, - uint32_t *bmask) +static void +mark_rule_objects(struct ip_fw_chain *ch, struct ip_fw *rule, + struct dump_args *da) { struct opcode_obj_rewrite *rw; ipfw_insn *cmd; - int bidx, cmdlen, l, count; + int cmdlen, l; uint16_t kidx; uint8_t subtype; l = rule->cmd_len; cmd = rule->cmd; cmdlen = 0; - count = 0; for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); rw = find_op_rw(cmd, &kidx, &subtype); if (rw == NULL) continue; - bidx = kidx / 32; - /* - * Maintain separate bitmasks for table and - * non-table objects. - */ - if (rw->etlv != IPFW_TLV_TBL_NAME) - bidx += IPFW_TABLES_MAX / 32; - - if ((bmask[bidx] & (1 << (kidx % 32))) == 0) - count++; - - bmask[bidx] |= 1 << (kidx % 32); + if (ipfw_mark_object_kidx(da->bmask, rw->etlv, kidx)) + da->tcount++; } - - return (count); } /* * Dumps requested objects data * Data layout (version 0)(current): * Request: [ ipfw_cfg_lheader ] + IPFW_CFG_GET_* flags * size = ipfw_cfg_lheader.size * Reply: [ ipfw_cfg_lheader * [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional) * [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) * ipfw_obj_tlv(IPFW_TLV_RULE_ENT) [ ip_fw_bcounter (optional) ip_fw_rule ] * ] (optional) * [ ipfw_obj_ctlv(IPFW_TLV_STATE_LIST) ipfw_obj_dyntlv x N ] (optional) * ] * * NOTE IPFW_TLV_STATE_LIST has the single valid field: objsize. * The rest (size, count) are set to zero and needs to be ignored. * * Returns 0 on success. */ static int dump_config(struct ip_fw_chain *chain, ip_fw3_opheader *op3, struct sockopt_data *sd) { + struct dump_args da; ipfw_cfg_lheader *hdr; struct ip_fw *rule; size_t sz, rnum; - uint32_t hdr_flags; + uint32_t hdr_flags, *bmask; int error, i; - struct dump_args da; - uint32_t *bmask; hdr = (ipfw_cfg_lheader *)ipfw_get_sopt_header(sd, sizeof(*hdr)); if (hdr == NULL) return (EINVAL); error = 0; bmask = NULL; - /* Allocate needed state. Note we allocate 2xspace mask, for table&srv */ - if (hdr->flags & IPFW_CFG_GET_STATIC) - bmask = malloc(IPFW_TABLES_MAX / 4, M_TEMP, M_WAITOK | M_ZERO); + memset(&da, 0, sizeof(da)); + /* + * Allocate needed state. + * Note we allocate 2xspace mask, for table & srv + */ + if (hdr->flags & (IPFW_CFG_GET_STATIC | IPFW_CFG_GET_STATES)) + da.bmask = bmask = malloc( + sizeof(uint32_t) * IPFW_TABLES_MAX * 2 / 32, M_TEMP, + M_WAITOK | M_ZERO); IPFW_UH_RLOCK(chain); /* * STAGE 1: Determine size/count for objects in range. * Prepare used tables bitmask. */ sz = sizeof(ipfw_cfg_lheader); - memset(&da, 0, sizeof(da)); - - da.b = 0; da.e = chain->n_rules; if (hdr->end_rule != 0) { /* Handle custom range */ if ((rnum = hdr->start_rule) > IPFW_DEFAULT_RULE) rnum = IPFW_DEFAULT_RULE; da.b = ipfw_find_rule(chain, rnum, 0); rnum = (hdr->end_rule < IPFW_DEFAULT_RULE) ? hdr->end_rule + 1: IPFW_DEFAULT_RULE; da.e = ipfw_find_rule(chain, rnum, UINT32_MAX) + 1; } if (hdr->flags & IPFW_CFG_GET_STATIC) { for (i = da.b; i < da.e; i++) { rule = chain->map[i]; da.rsize += RULEUSIZE1(rule) + sizeof(ipfw_obj_tlv); da.rcount++; /* Update bitmask of used objects for given range */ - da.tcount += mark_object_kidx(chain, rule, bmask); + mark_rule_objects(chain, rule, &da); } /* Add counters if requested */ if (hdr->flags & IPFW_CFG_GET_COUNTERS) { da.rsize += sizeof(struct ip_fw_bcounter) * da.rcount; da.rcounters = 1; } - - if (da.tcount > 0) - sz += da.tcount * sizeof(ipfw_obj_ntlv) + - sizeof(ipfw_obj_ctlv); sz += da.rsize + sizeof(ipfw_obj_ctlv); } - if (hdr->flags & IPFW_CFG_GET_STATES) - sz += ipfw_dyn_get_count() * sizeof(ipfw_obj_dyntlv) + - sizeof(ipfw_obj_ctlv); + if (hdr->flags & IPFW_CFG_GET_STATES) { + sz += sizeof(ipfw_obj_ctlv) + + ipfw_dyn_get_count(bmask, &i) * sizeof(ipfw_obj_dyntlv); + da.tcount += i; + } + if (da.tcount > 0) + sz += da.tcount * sizeof(ipfw_obj_ntlv) + + sizeof(ipfw_obj_ctlv); /* * Fill header anyway. * Note we have to save header fields to stable storage * buffer inside @sd can be flushed after dumping rules */ hdr->size = sz; hdr->set_mask = ~V_set_disable; hdr_flags = hdr->flags; hdr = NULL; if (sd->valsize < sz) { error = ENOMEM; goto cleanup; } /* STAGE2: Store actual data */ + if (da.tcount > 0) { + error = dump_named_objects(chain, &da, sd); + if (error != 0) + goto cleanup; + } + if (hdr_flags & IPFW_CFG_GET_STATIC) { - error = dump_static_rules(chain, &da, bmask, sd); + error = dump_static_rules(chain, &da, sd); if (error != 0) goto cleanup; } if (hdr_flags & IPFW_CFG_GET_STATES) error = ipfw_dump_states(chain, sd); cleanup: IPFW_UH_RUNLOCK(chain); if (bmask != NULL) free(bmask, M_TEMP); return (error); } int ipfw_check_object_name_generic(const char *name) { int nsize; nsize = sizeof(((ipfw_obj_ntlv *)0)->name); if (strnlen(name, nsize) == nsize) return (EINVAL); if (name[0] == '\0') return (EINVAL); return (0); } /* * Creates non-existent objects referenced by rule. * * Return 0 on success. */ int create_objects_compat(struct ip_fw_chain *ch, ipfw_insn *cmd, struct obj_idx *oib, struct obj_idx *pidx, struct tid_info *ti) { struct opcode_obj_rewrite *rw; struct obj_idx *p; uint16_t kidx; int error; /* * Compatibility stuff: do actual creation for non-existing, * but referenced objects. */ for (p = oib; p < pidx; p++) { if (p->kidx != 0) continue; ti->uidx = p->uidx; ti->type = p->type; ti->atype = 0; rw = find_op_rw(cmd + p->off, NULL, NULL); KASSERT(rw != NULL, ("Unable to find handler for op %d", (cmd + p->off)->opcode)); if (rw->create_object == NULL) error = EOPNOTSUPP; else error = rw->create_object(ch, ti, &kidx); if (error == 0) { p->kidx = kidx; continue; } /* * Error happened. We have to rollback everything. * Drop all already acquired references. */ IPFW_UH_WLOCK(ch); unref_oib_objects(ch, cmd, oib, pidx); IPFW_UH_WUNLOCK(ch); return (error); } return (0); } /* * Compatibility function for old ipfw(8) binaries. * Rewrites table/nat kernel indices with userland ones. * Convert tables matching '/^\d+$/' to their atoi() value. * Use number 65535 for other tables. * * Returns 0 on success. */ static int set_legacy_obj_kidx(struct ip_fw_chain *ch, struct ip_fw_rule0 *rule) { struct opcode_obj_rewrite *rw; struct named_object *no; ipfw_insn *cmd; char *end; long val; int cmdlen, error, l; uint16_t kidx, uidx; uint8_t subtype; error = 0; l = rule->cmd_len; cmd = rule->cmd; cmdlen = 0; for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); /* Check if is index in given opcode */ rw = find_op_rw(cmd, &kidx, &subtype); if (rw == NULL) continue; /* Try to find referenced kernel object */ no = rw->find_bykidx(ch, kidx); if (no == NULL) continue; val = strtol(no->name, &end, 10); if (*end == '\0' && val < 65535) { uidx = val; } else { /* * We are called via legacy opcode. * Save error and show table as fake number * not to make ipfw(8) hang. */ uidx = 65535; error = 2; } rw->update(cmd, uidx); } return (error); } /* * Unreferences all already-referenced objects in given @cmd rule, * using information in @oib. * * Used to rollback partially converted rule on error. */ static void unref_oib_objects(struct ip_fw_chain *ch, ipfw_insn *cmd, struct obj_idx *oib, struct obj_idx *end) { struct opcode_obj_rewrite *rw; struct named_object *no; struct obj_idx *p; IPFW_UH_WLOCK_ASSERT(ch); for (p = oib; p < end; p++) { if (p->kidx == 0) continue; rw = find_op_rw(cmd + p->off, NULL, NULL); KASSERT(rw != NULL, ("Unable to find handler for op %d", (cmd + p->off)->opcode)); /* Find & unref by existing idx */ no = rw->find_bykidx(ch, p->kidx); KASSERT(no != NULL, ("Ref'd object %d disappeared", p->kidx)); no->refcnt--; } } /* * Remove references from every object used in @rule. * Used at rule removal code. */ static void unref_rule_objects(struct ip_fw_chain *ch, struct ip_fw *rule) { struct opcode_obj_rewrite *rw; struct named_object *no; ipfw_insn *cmd; int cmdlen, l; uint16_t kidx; uint8_t subtype; IPFW_UH_WLOCK_ASSERT(ch); l = rule->cmd_len; cmd = rule->cmd; cmdlen = 0; for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); rw = find_op_rw(cmd, &kidx, &subtype); if (rw == NULL) continue; no = rw->find_bykidx(ch, kidx); KASSERT(no != NULL, ("object id %d not found", kidx)); KASSERT(no->subtype == subtype, ("wrong type %d (%d) for object id %d", no->subtype, subtype, kidx)); KASSERT(no->refcnt > 0, ("refcount for object %d is %d", kidx, no->refcnt)); if (no->refcnt == 1 && rw->destroy_object != NULL) rw->destroy_object(ch, no); else no->refcnt--; } } /* * Find and reference object (if any) stored in instruction @cmd. * * Saves object info in @pidx, sets * - @unresolved to 1 if object should exists but not found * * Returns non-zero value in case of error. */ static int ref_opcode_object(struct ip_fw_chain *ch, ipfw_insn *cmd, struct tid_info *ti, struct obj_idx *pidx, int *unresolved) { struct named_object *no; struct opcode_obj_rewrite *rw; int error; /* Check if this opcode is candidate for rewrite */ rw = find_op_rw(cmd, &ti->uidx, &ti->type); if (rw == NULL) return (0); /* Need to rewrite. Save necessary fields */ pidx->uidx = ti->uidx; pidx->type = ti->type; /* Try to find referenced kernel object */ error = rw->find_byname(ch, ti, &no); if (error != 0) return (error); if (no == NULL) { /* * Report about unresolved object for automaic * creation. */ *unresolved = 1; return (0); } /* * Object is already exist. * Its subtype should match with expected value. */ if (ti->type != no->subtype) return (EINVAL); /* Bump refcount and update kidx. */ no->refcnt++; rw->update(cmd, no->kidx); return (0); } /* * Finds and bumps refcount for objects referenced by given @rule. * Auto-creates non-existing tables. * Fills in @oib array with userland/kernel indexes. * * Returns 0 on success. */ static int ref_rule_objects(struct ip_fw_chain *ch, struct ip_fw *rule, struct rule_check_info *ci, struct obj_idx *oib, struct tid_info *ti) { struct obj_idx *pidx; ipfw_insn *cmd; int cmdlen, error, l, unresolved; pidx = oib; l = rule->cmd_len; cmd = rule->cmd; cmdlen = 0; error = 0; IPFW_UH_WLOCK(ch); /* Increase refcount on each existing referenced table. */ for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); unresolved = 0; error = ref_opcode_object(ch, cmd, ti, pidx, &unresolved); if (error != 0) break; /* * Compatibility stuff for old clients: * prepare to automaitcally create non-existing objects. */ if (unresolved != 0) { pidx->off = rule->cmd_len - l; pidx++; } } if (error != 0) { /* Unref everything we have already done */ unref_oib_objects(ch, rule->cmd, oib, pidx); IPFW_UH_WUNLOCK(ch); return (error); } IPFW_UH_WUNLOCK(ch); /* Perform auto-creation for non-existing objects */ if (pidx != oib) error = create_objects_compat(ch, rule->cmd, oib, pidx, ti); /* Calculate real number of dynamic objects */ ci->object_opcodes = (uint16_t)(pidx - oib); return (error); } /* * Checks is opcode is referencing table of appropriate type. * Adds reference count for found table if true. * Rewrites user-supplied opcode values with kernel ones. * * Returns 0 on success and appropriate error code otherwise. */ static int rewrite_rule_uidx(struct ip_fw_chain *chain, struct rule_check_info *ci) { int error; ipfw_insn *cmd; uint8_t type; struct obj_idx *p, *pidx_first, *pidx_last; struct tid_info ti; /* * Prepare an array for storing opcode indices. * Use stack allocation by default. */ if (ci->object_opcodes <= (sizeof(ci->obuf)/sizeof(ci->obuf[0]))) { /* Stack */ pidx_first = ci->obuf; } else pidx_first = malloc( ci->object_opcodes * sizeof(struct obj_idx), M_IPFW, M_WAITOK | M_ZERO); error = 0; type = 0; memset(&ti, 0, sizeof(ti)); /* Use set rule is assigned to. */ ti.set = ci->krule->set; if (ci->ctlv != NULL) { ti.tlvs = (void *)(ci->ctlv + 1); ti.tlen = ci->ctlv->head.length - sizeof(ipfw_obj_ctlv); } /* Reference all used tables and other objects */ error = ref_rule_objects(chain, ci->krule, ci, pidx_first, &ti); if (error != 0) goto free; /* * Note that ref_rule_objects() might have updated ci->object_opcodes * to reflect actual number of object opcodes. */ /* Perform rewrite of remaining opcodes */ p = pidx_first; pidx_last = pidx_first + ci->object_opcodes; for (p = pidx_first; p < pidx_last; p++) { cmd = ci->krule->cmd + p->off; update_opcode_kidx(cmd, p->kidx); } free: if (pidx_first != ci->obuf) free(pidx_first, M_IPFW); return (error); } /* * Adds one or more rules to ipfw @chain. * Data layout (version 0)(current): * Request: * [ * ip_fw3_opheader * [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional *1) * [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) ip_fw x N ] (*2) (*3) * ] * Reply: * [ * ip_fw3_opheader * [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional) * [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) ip_fw x N ] * ] * * Rules in reply are modified to store their actual ruleset number. * * (*1) TLVs inside IPFW_TLV_TBL_LIST needs to be sorted ascending * according to their idx field and there has to be no duplicates. * (*2) Numbered rules inside IPFW_TLV_RULE_LIST needs to be sorted ascending. * (*3) Each ip_fw structure needs to be aligned to u64 boundary. * * Returns 0 on success. */ static int add_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3, struct sockopt_data *sd) { ipfw_obj_ctlv *ctlv, *rtlv, *tstate; ipfw_obj_ntlv *ntlv; int clen, error, idx; uint32_t count, read; struct ip_fw_rule *r; struct rule_check_info rci, *ci, *cbuf; int i, rsize; op3 = (ip_fw3_opheader *)ipfw_get_sopt_space(sd, sd->valsize); ctlv = (ipfw_obj_ctlv *)(op3 + 1); read = sizeof(ip_fw3_opheader); rtlv = NULL; tstate = NULL; cbuf = NULL; memset(&rci, 0, sizeof(struct rule_check_info)); if (read + sizeof(*ctlv) > sd->valsize) return (EINVAL); if (ctlv->head.type == IPFW_TLV_TBLNAME_LIST) { clen = ctlv->head.length; /* Check size and alignment */ if (clen > sd->valsize || clen < sizeof(*ctlv)) return (EINVAL); if ((clen % sizeof(uint64_t)) != 0) return (EINVAL); /* * Some table names or other named objects. * Check for validness. */ count = (ctlv->head.length - sizeof(*ctlv)) / sizeof(*ntlv); if (ctlv->count != count || ctlv->objsize != sizeof(*ntlv)) return (EINVAL); /* * Check each TLV. * Ensure TLVs are sorted ascending and * there are no duplicates. */ idx = -1; ntlv = (ipfw_obj_ntlv *)(ctlv + 1); while (count > 0) { if (ntlv->head.length != sizeof(ipfw_obj_ntlv)) return (EINVAL); error = ipfw_check_object_name_generic(ntlv->name); if (error != 0) return (error); if (ntlv->idx <= idx) return (EINVAL); idx = ntlv->idx; count--; ntlv++; } tstate = ctlv; read += ctlv->head.length; ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv + ctlv->head.length); } if (read + sizeof(*ctlv) > sd->valsize) return (EINVAL); if (ctlv->head.type == IPFW_TLV_RULE_LIST) { clen = ctlv->head.length; if (clen + read > sd->valsize || clen < sizeof(*ctlv)) return (EINVAL); if ((clen % sizeof(uint64_t)) != 0) return (EINVAL); /* * TODO: Permit adding multiple rules at once */ if (ctlv->count != 1) return (ENOTSUP); clen -= sizeof(*ctlv); if (ctlv->count > clen / sizeof(struct ip_fw_rule)) return (EINVAL); /* Allocate state for each rule or use stack */ if (ctlv->count == 1) { memset(&rci, 0, sizeof(struct rule_check_info)); cbuf = &rci; } else cbuf = malloc(ctlv->count * sizeof(*ci), M_TEMP, M_WAITOK | M_ZERO); ci = cbuf; /* * Check each rule for validness. * Ensure numbered rules are sorted ascending * and properly aligned */ idx = 0; r = (struct ip_fw_rule *)(ctlv + 1); count = 0; error = 0; while (clen > 0) { rsize = roundup2(RULESIZE(r), sizeof(uint64_t)); if (rsize > clen || ctlv->count <= count) { error = EINVAL; break; } ci->ctlv = tstate; error = check_ipfw_rule1(r, rsize, ci); if (error != 0) break; /* Check sorting */ if (r->rulenum != 0 && r->rulenum < idx) { printf("rulenum %d idx %d\n", r->rulenum, idx); error = EINVAL; break; } idx = r->rulenum; ci->urule = (caddr_t)r; rsize = roundup2(rsize, sizeof(uint64_t)); clen -= rsize; r = (struct ip_fw_rule *)((caddr_t)r + rsize); count++; ci++; } if (ctlv->count != count || error != 0) { if (cbuf != &rci) free(cbuf, M_TEMP); return (EINVAL); } rtlv = ctlv; read += ctlv->head.length; ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv + ctlv->head.length); } if (read != sd->valsize || rtlv == NULL || rtlv->count == 0) { if (cbuf != NULL && cbuf != &rci) free(cbuf, M_TEMP); return (EINVAL); } /* * Passed rules seems to be valid. * Allocate storage and try to add them to chain. */ for (i = 0, ci = cbuf; i < rtlv->count; i++, ci++) { clen = RULEKSIZE1((struct ip_fw_rule *)ci->urule); ci->krule = ipfw_alloc_rule(chain, clen); import_rule1(ci); } if ((error = commit_rules(chain, cbuf, rtlv->count)) != 0) { /* Free allocate krules */ for (i = 0, ci = cbuf; i < rtlv->count; i++, ci++) - free_rule(ci->krule); + ipfw_free_rule(ci->krule); } if (cbuf != NULL && cbuf != &rci) free(cbuf, M_TEMP); return (error); } /* * Lists all sopts currently registered. * Data layout (v0)(current): * Request: [ ipfw_obj_lheader ], size = ipfw_obj_lheader.size * Reply: [ ipfw_obj_lheader ipfw_sopt_info x N ] * * Returns 0 on success */ static int dump_soptcodes(struct ip_fw_chain *chain, ip_fw3_opheader *op3, struct sockopt_data *sd) { struct _ipfw_obj_lheader *olh; ipfw_sopt_info *i; struct ipfw_sopt_handler *sh; uint32_t count, n, size; olh = (struct _ipfw_obj_lheader *)ipfw_get_sopt_header(sd,sizeof(*olh)); if (olh == NULL) return (EINVAL); if (sd->valsize < olh->size) return (EINVAL); CTL3_LOCK(); count = ctl3_hsize; size = count * sizeof(ipfw_sopt_info) + sizeof(ipfw_obj_lheader); /* Fill in header regadless of buffer size */ olh->count = count; olh->objsize = sizeof(ipfw_sopt_info); if (size > olh->size) { olh->size = size; CTL3_UNLOCK(); return (ENOMEM); } olh->size = size; for (n = 1; n <= count; n++) { i = (ipfw_sopt_info *)ipfw_get_sopt_space(sd, sizeof(*i)); KASSERT(i != NULL, ("previously checked buffer is not enough")); sh = &ctl3_handlers[n]; i->opcode = sh->opcode; i->version = sh->version; i->refcnt = sh->refcnt; } CTL3_UNLOCK(); return (0); } /* * Compares two opcodes. * Used both in qsort() and bsearch(). * * Returns 0 if match is found. */ static int compare_opcodes(const void *_a, const void *_b) { const struct opcode_obj_rewrite *a, *b; a = (const struct opcode_obj_rewrite *)_a; b = (const struct opcode_obj_rewrite *)_b; if (a->opcode < b->opcode) return (-1); else if (a->opcode > b->opcode) return (1); return (0); } /* * XXX: Rewrite bsearch() */ static int find_op_rw_range(uint16_t op, struct opcode_obj_rewrite **plo, struct opcode_obj_rewrite **phi) { struct opcode_obj_rewrite *ctl3_max, *lo, *hi, h, *rw; memset(&h, 0, sizeof(h)); h.opcode = op; rw = (struct opcode_obj_rewrite *)bsearch(&h, ctl3_rewriters, ctl3_rsize, sizeof(h), compare_opcodes); if (rw == NULL) return (1); /* Find the first element matching the same opcode */ lo = rw; for ( ; lo > ctl3_rewriters && (lo - 1)->opcode == op; lo--) ; /* Find the last element matching the same opcode */ hi = rw; ctl3_max = ctl3_rewriters + ctl3_rsize; for ( ; (hi + 1) < ctl3_max && (hi + 1)->opcode == op; hi++) ; *plo = lo; *phi = hi; return (0); } /* * Finds opcode object rewriter based on @code. * * Returns pointer to handler or NULL. */ static struct opcode_obj_rewrite * find_op_rw(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype) { struct opcode_obj_rewrite *rw, *lo, *hi; uint16_t uidx; uint8_t subtype; if (find_op_rw_range(cmd->opcode, &lo, &hi) != 0) return (NULL); for (rw = lo; rw <= hi; rw++) { if (rw->classifier(cmd, &uidx, &subtype) == 0) { if (puidx != NULL) *puidx = uidx; if (ptype != NULL) *ptype = subtype; return (rw); } } return (NULL); } int classify_opcode_kidx(ipfw_insn *cmd, uint16_t *puidx) { if (find_op_rw(cmd, puidx, NULL) == NULL) return (1); return (0); } void update_opcode_kidx(ipfw_insn *cmd, uint16_t idx) { struct opcode_obj_rewrite *rw; rw = find_op_rw(cmd, NULL, NULL); KASSERT(rw != NULL, ("No handler to update opcode %d", cmd->opcode)); rw->update(cmd, idx); } void ipfw_init_obj_rewriter() { ctl3_rewriters = NULL; ctl3_rsize = 0; } void ipfw_destroy_obj_rewriter() { if (ctl3_rewriters != NULL) free(ctl3_rewriters, M_IPFW); ctl3_rewriters = NULL; ctl3_rsize = 0; } /* * Adds one or more opcode object rewrite handlers to the global array. * Function may sleep. */ void ipfw_add_obj_rewriter(struct opcode_obj_rewrite *rw, size_t count) { size_t sz; struct opcode_obj_rewrite *tmp; CTL3_LOCK(); for (;;) { sz = ctl3_rsize + count; CTL3_UNLOCK(); tmp = malloc(sizeof(*rw) * sz, M_IPFW, M_WAITOK | M_ZERO); CTL3_LOCK(); if (ctl3_rsize + count <= sz) break; /* Retry */ free(tmp, M_IPFW); } /* Merge old & new arrays */ sz = ctl3_rsize + count; memcpy(tmp, ctl3_rewriters, ctl3_rsize * sizeof(*rw)); memcpy(&tmp[ctl3_rsize], rw, count * sizeof(*rw)); qsort(tmp, sz, sizeof(*rw), compare_opcodes); /* Switch new and free old */ if (ctl3_rewriters != NULL) free(ctl3_rewriters, M_IPFW); ctl3_rewriters = tmp; ctl3_rsize = sz; CTL3_UNLOCK(); } /* * Removes one or more object rewrite handlers from the global array. */ int ipfw_del_obj_rewriter(struct opcode_obj_rewrite *rw, size_t count) { size_t sz; struct opcode_obj_rewrite *ctl3_max, *ktmp, *lo, *hi; int i; CTL3_LOCK(); for (i = 0; i < count; i++) { if (find_op_rw_range(rw[i].opcode, &lo, &hi) != 0) continue; for (ktmp = lo; ktmp <= hi; ktmp++) { if (ktmp->classifier != rw[i].classifier) continue; ctl3_max = ctl3_rewriters + ctl3_rsize; sz = (ctl3_max - (ktmp + 1)) * sizeof(*ktmp); memmove(ktmp, ktmp + 1, sz); ctl3_rsize--; break; } } if (ctl3_rsize == 0) { if (ctl3_rewriters != NULL) free(ctl3_rewriters, M_IPFW); ctl3_rewriters = NULL; } CTL3_UNLOCK(); return (0); } static int export_objhash_ntlv_internal(struct namedobj_instance *ni, struct named_object *no, void *arg) { struct sockopt_data *sd; ipfw_obj_ntlv *ntlv; sd = (struct sockopt_data *)arg; ntlv = (ipfw_obj_ntlv *)ipfw_get_sopt_space(sd, sizeof(*ntlv)); if (ntlv == NULL) return (ENOMEM); ipfw_export_obj_ntlv(no, ntlv); return (0); } /* * Lists all service objects. * Data layout (v0)(current): * Request: [ ipfw_obj_lheader ] size = ipfw_obj_lheader.size * Reply: [ ipfw_obj_lheader [ ipfw_obj_ntlv x N ] (optional) ] * Returns 0 on success */ static int dump_srvobjects(struct ip_fw_chain *chain, ip_fw3_opheader *op3, struct sockopt_data *sd) { ipfw_obj_lheader *hdr; int count; hdr = (ipfw_obj_lheader *)ipfw_get_sopt_header(sd, sizeof(*hdr)); if (hdr == NULL) return (EINVAL); IPFW_UH_RLOCK(chain); count = ipfw_objhash_count(CHAIN_TO_SRV(chain)); hdr->size = sizeof(ipfw_obj_lheader) + count * sizeof(ipfw_obj_ntlv); if (sd->valsize < hdr->size) { IPFW_UH_RUNLOCK(chain); return (ENOMEM); } hdr->count = count; hdr->objsize = sizeof(ipfw_obj_ntlv); if (count > 0) ipfw_objhash_foreach(CHAIN_TO_SRV(chain), export_objhash_ntlv_internal, sd); IPFW_UH_RUNLOCK(chain); return (0); } /* * Compares two sopt handlers (code, version and handler ptr). * Used both as qsort() and bsearch(). * Does not compare handler for latter case. * * Returns 0 if match is found. */ static int compare_sh(const void *_a, const void *_b) { const struct ipfw_sopt_handler *a, *b; a = (const struct ipfw_sopt_handler *)_a; b = (const struct ipfw_sopt_handler *)_b; if (a->opcode < b->opcode) return (-1); else if (a->opcode > b->opcode) return (1); if (a->version < b->version) return (-1); else if (a->version > b->version) return (1); /* bsearch helper */ if (a->handler == NULL) return (0); if ((uintptr_t)a->handler < (uintptr_t)b->handler) return (-1); else if ((uintptr_t)a->handler > (uintptr_t)b->handler) return (1); return (0); } /* * Finds sopt handler based on @code and @version. * * Returns pointer to handler or NULL. */ static struct ipfw_sopt_handler * find_sh(uint16_t code, uint8_t version, sopt_handler_f *handler) { struct ipfw_sopt_handler *sh, h; memset(&h, 0, sizeof(h)); h.opcode = code; h.version = version; h.handler = handler; sh = (struct ipfw_sopt_handler *)bsearch(&h, ctl3_handlers, ctl3_hsize, sizeof(h), compare_sh); return (sh); } static int find_ref_sh(uint16_t opcode, uint8_t version, struct ipfw_sopt_handler *psh) { struct ipfw_sopt_handler *sh; CTL3_LOCK(); if ((sh = find_sh(opcode, version, NULL)) == NULL) { CTL3_UNLOCK(); printf("ipfw: ipfw_ctl3 invalid option %d""v""%d\n", opcode, version); return (EINVAL); } sh->refcnt++; ctl3_refct++; /* Copy handler data to requested buffer */ *psh = *sh; CTL3_UNLOCK(); return (0); } static void find_unref_sh(struct ipfw_sopt_handler *psh) { struct ipfw_sopt_handler *sh; CTL3_LOCK(); sh = find_sh(psh->opcode, psh->version, NULL); KASSERT(sh != NULL, ("ctl3 handler disappeared")); sh->refcnt--; ctl3_refct--; CTL3_UNLOCK(); } void ipfw_init_sopt_handler() { CTL3_LOCK_INIT(); IPFW_ADD_SOPT_HANDLER(1, scodes); } void ipfw_destroy_sopt_handler() { IPFW_DEL_SOPT_HANDLER(1, scodes); CTL3_LOCK_DESTROY(); } /* * Adds one or more sockopt handlers to the global array. * Function may sleep. */ void ipfw_add_sopt_handler(struct ipfw_sopt_handler *sh, size_t count) { size_t sz; struct ipfw_sopt_handler *tmp; CTL3_LOCK(); for (;;) { sz = ctl3_hsize + count; CTL3_UNLOCK(); tmp = malloc(sizeof(*sh) * sz, M_IPFW, M_WAITOK | M_ZERO); CTL3_LOCK(); if (ctl3_hsize + count <= sz) break; /* Retry */ free(tmp, M_IPFW); } /* Merge old & new arrays */ sz = ctl3_hsize + count; memcpy(tmp, ctl3_handlers, ctl3_hsize * sizeof(*sh)); memcpy(&tmp[ctl3_hsize], sh, count * sizeof(*sh)); qsort(tmp, sz, sizeof(*sh), compare_sh); /* Switch new and free old */ if (ctl3_handlers != NULL) free(ctl3_handlers, M_IPFW); ctl3_handlers = tmp; ctl3_hsize = sz; ctl3_gencnt++; CTL3_UNLOCK(); } /* * Removes one or more sockopt handlers from the global array. */ int ipfw_del_sopt_handler(struct ipfw_sopt_handler *sh, size_t count) { size_t sz; struct ipfw_sopt_handler *tmp, *h; int i; CTL3_LOCK(); for (i = 0; i < count; i++) { tmp = &sh[i]; h = find_sh(tmp->opcode, tmp->version, tmp->handler); if (h == NULL) continue; sz = (ctl3_handlers + ctl3_hsize - (h + 1)) * sizeof(*h); memmove(h, h + 1, sz); ctl3_hsize--; } if (ctl3_hsize == 0) { if (ctl3_handlers != NULL) free(ctl3_handlers, M_IPFW); ctl3_handlers = NULL; } ctl3_gencnt++; CTL3_UNLOCK(); return (0); } /* * Writes data accumulated in @sd to sockopt buffer. * Zeroes internal @sd buffer. */ static int ipfw_flush_sopt_data(struct sockopt_data *sd) { struct sockopt *sopt; int error; size_t sz; sz = sd->koff; if (sz == 0) return (0); sopt = sd->sopt; if (sopt->sopt_dir == SOPT_GET) { error = copyout(sd->kbuf, sopt->sopt_val, sz); if (error != 0) return (error); } memset(sd->kbuf, 0, sd->ksize); sd->ktotal += sz; sd->koff = 0; if (sd->ktotal + sd->ksize < sd->valsize) sd->kavail = sd->ksize; else sd->kavail = sd->valsize - sd->ktotal; /* Update sopt buffer data */ sopt->sopt_valsize = sd->ktotal; sopt->sopt_val = sd->sopt_val + sd->ktotal; return (0); } /* * Ensures that @sd buffer has contiguous @neeeded number of * bytes. * * Returns pointer to requested space or NULL. */ caddr_t ipfw_get_sopt_space(struct sockopt_data *sd, size_t needed) { int error; caddr_t addr; if (sd->kavail < needed) { /* * Flush data and try another time. */ error = ipfw_flush_sopt_data(sd); if (sd->kavail < needed || error != 0) return (NULL); } addr = sd->kbuf + sd->koff; sd->koff += needed; sd->kavail -= needed; return (addr); } /* * Requests @needed contiguous bytes from @sd buffer. * Function is used to notify subsystem that we are * interesed in first @needed bytes (request header) * and the rest buffer can be safely zeroed. * * Returns pointer to requested space or NULL. */ caddr_t ipfw_get_sopt_header(struct sockopt_data *sd, size_t needed) { caddr_t addr; if ((addr = ipfw_get_sopt_space(sd, needed)) == NULL) return (NULL); if (sd->kavail > 0) memset(sd->kbuf + sd->koff, 0, sd->kavail); return (addr); } /* * New sockopt handler. */ int ipfw_ctl3(struct sockopt *sopt) { int error, locked; size_t size, valsize; struct ip_fw_chain *chain; char xbuf[256]; struct sockopt_data sdata; struct ipfw_sopt_handler h; ip_fw3_opheader *op3 = NULL; error = priv_check(sopt->sopt_td, PRIV_NETINET_IPFW); if (error != 0) return (error); if (sopt->sopt_name != IP_FW3) return (ipfw_ctl(sopt)); chain = &V_layer3_chain; error = 0; /* Save original valsize before it is altered via sooptcopyin() */ valsize = sopt->sopt_valsize; memset(&sdata, 0, sizeof(sdata)); /* Read op3 header first to determine actual operation */ op3 = (ip_fw3_opheader *)xbuf; error = sooptcopyin(sopt, op3, sizeof(*op3), sizeof(*op3)); if (error != 0) return (error); sopt->sopt_valsize = valsize; /* * Find and reference command. */ error = find_ref_sh(op3->opcode, op3->version, &h); if (error != 0) return (error); /* * Disallow modifications in really-really secure mode, but still allow * the logging counters to be reset. */ if ((h.dir & HDIR_SET) != 0 && h.opcode != IP_FW_XRESETLOG) { error = securelevel_ge(sopt->sopt_td->td_ucred, 3); if (error != 0) { find_unref_sh(&h); return (error); } } /* * Fill in sockopt_data structure that may be useful for * IP_FW3 get requests. */ locked = 0; if (valsize <= sizeof(xbuf)) { /* use on-stack buffer */ sdata.kbuf = xbuf; sdata.ksize = sizeof(xbuf); sdata.kavail = valsize; } else { /* * Determine opcode type/buffer size: * allocate sliding-window buf for data export or * contiguous buffer for special ops. */ if ((h.dir & HDIR_SET) != 0) { /* Set request. Allocate contigous buffer. */ if (valsize > CTL3_LARGEBUF) { find_unref_sh(&h); return (EFBIG); } size = valsize; } else { /* Get request. Allocate sliding window buffer */ size = (valsizesopt_val, valsize); if (error != 0) return (error); locked = 1; } } sdata.kbuf = malloc(size, M_TEMP, M_WAITOK | M_ZERO); sdata.ksize = size; sdata.kavail = size; } sdata.sopt = sopt; sdata.sopt_val = sopt->sopt_val; sdata.valsize = valsize; /* * Copy either all request (if valsize < bsize_max) * or first bsize_max bytes to guarantee most consumers * that all necessary data has been copied). * Anyway, copy not less than sizeof(ip_fw3_opheader). */ if ((error = sooptcopyin(sopt, sdata.kbuf, sdata.ksize, sizeof(ip_fw3_opheader))) != 0) return (error); op3 = (ip_fw3_opheader *)sdata.kbuf; /* Finally, run handler */ error = h.handler(chain, op3, &sdata); find_unref_sh(&h); /* Flush state and free buffers */ if (error == 0) error = ipfw_flush_sopt_data(&sdata); else ipfw_flush_sopt_data(&sdata); if (locked != 0) vsunlock(sdata.sopt_val, valsize); /* Restore original pointer and set number of bytes written */ sopt->sopt_val = sdata.sopt_val; sopt->sopt_valsize = sdata.ktotal; if (sdata.kbuf != xbuf) free(sdata.kbuf, M_TEMP); return (error); } /** * {set|get}sockopt parser. */ int ipfw_ctl(struct sockopt *sopt) { #define RULE_MAXSIZE (512*sizeof(u_int32_t)) int error; size_t size, valsize; struct ip_fw *buf; struct ip_fw_rule0 *rule; struct ip_fw_chain *chain; u_int32_t rulenum[2]; uint32_t opt; struct rule_check_info ci; IPFW_RLOCK_TRACKER; chain = &V_layer3_chain; error = 0; /* Save original valsize before it is altered via sooptcopyin() */ valsize = sopt->sopt_valsize; opt = sopt->sopt_name; /* * Disallow modifications in really-really secure mode, but still allow * the logging counters to be reset. */ if (opt == IP_FW_ADD || (sopt->sopt_dir == SOPT_SET && opt != IP_FW_RESETLOG)) { error = securelevel_ge(sopt->sopt_td->td_ucred, 3); if (error != 0) return (error); } switch (opt) { case IP_FW_GET: /* * pass up a copy of the current rules. Static rules * come first (the last of which has number IPFW_DEFAULT_RULE), * followed by a possibly empty list of dynamic rule. * The last dynamic rule has NULL in the "next" field. * * Note that the calculated size is used to bound the * amount of data returned to the user. The rule set may * change between calculating the size and returning the * data in which case we'll just return what fits. */ for (;;) { int len = 0, want; size = chain->static_len; size += ipfw_dyn_len(); if (size >= sopt->sopt_valsize) break; buf = malloc(size, M_TEMP, M_WAITOK | M_ZERO); IPFW_UH_RLOCK(chain); /* check again how much space we need */ want = chain->static_len + ipfw_dyn_len(); if (size >= want) len = ipfw_getrules(chain, buf, size); IPFW_UH_RUNLOCK(chain); if (size >= want) error = sooptcopyout(sopt, buf, len); free(buf, M_TEMP); if (size >= want) break; } break; case IP_FW_FLUSH: /* locking is done within del_entry() */ error = del_entry(chain, 0); /* special case, rule=0, cmd=0 means all */ break; case IP_FW_ADD: rule = malloc(RULE_MAXSIZE, M_TEMP, M_WAITOK); error = sooptcopyin(sopt, rule, RULE_MAXSIZE, sizeof(struct ip_fw7) ); memset(&ci, 0, sizeof(struct rule_check_info)); /* * If the size of commands equals RULESIZE7 then we assume * a FreeBSD7.2 binary is talking to us (set is7=1). * is7 is persistent so the next 'ipfw list' command * will use this format. * NOTE: If wrong version is guessed (this can happen if * the first ipfw command is 'ipfw [pipe] list') * the ipfw binary may crash or loop infinitly... */ size = sopt->sopt_valsize; if (size == RULESIZE7(rule)) { is7 = 1; error = convert_rule_to_8(rule); if (error) { free(rule, M_TEMP); return error; } size = RULESIZE(rule); } else is7 = 0; if (error == 0) error = check_ipfw_rule0(rule, size, &ci); if (error == 0) { /* locking is done within add_rule() */ struct ip_fw *krule; krule = ipfw_alloc_rule(chain, RULEKSIZE0(rule)); ci.urule = (caddr_t)rule; ci.krule = krule; import_rule0(&ci); error = commit_rules(chain, &ci, 1); if (error != 0) - free_rule(ci.krule); + ipfw_free_rule(ci.krule); else if (sopt->sopt_dir == SOPT_GET) { if (is7) { error = convert_rule_to_7(rule); size = RULESIZE7(rule); if (error) { free(rule, M_TEMP); return error; } } error = sooptcopyout(sopt, rule, size); } } free(rule, M_TEMP); break; case IP_FW_DEL: /* * IP_FW_DEL is used for deleting single rules or sets, * and (ab)used to atomically manipulate sets. Argument size * is used to distinguish between the two: * sizeof(u_int32_t) * delete single rule or set of rules, * or reassign rules (or sets) to a different set. * 2*sizeof(u_int32_t) * atomic disable/enable sets. * first u_int32_t contains sets to be disabled, * second u_int32_t contains sets to be enabled. */ error = sooptcopyin(sopt, rulenum, 2*sizeof(u_int32_t), sizeof(u_int32_t)); if (error) break; size = sopt->sopt_valsize; if (size == sizeof(u_int32_t) && rulenum[0] != 0) { /* delete or reassign, locking done in del_entry() */ error = del_entry(chain, rulenum[0]); } else if (size == 2*sizeof(u_int32_t)) { /* set enable/disable */ IPFW_UH_WLOCK(chain); V_set_disable = (V_set_disable | rulenum[0]) & ~rulenum[1] & ~(1<sopt_val != 0) { error = sooptcopyin(sopt, rulenum, sizeof(u_int32_t), sizeof(u_int32_t)); if (error) break; } error = zero_entry(chain, rulenum[0], sopt->sopt_name == IP_FW_RESETLOG); break; /*--- TABLE opcodes ---*/ case IP_FW_TABLE_ADD: case IP_FW_TABLE_DEL: { ipfw_table_entry ent; struct tentry_info tei; struct tid_info ti; struct table_value v; error = sooptcopyin(sopt, &ent, sizeof(ent), sizeof(ent)); if (error) break; memset(&tei, 0, sizeof(tei)); tei.paddr = &ent.addr; tei.subtype = AF_INET; tei.masklen = ent.masklen; ipfw_import_table_value_legacy(ent.value, &v); tei.pvalue = &v; memset(&ti, 0, sizeof(ti)); ti.uidx = ent.tbl; ti.type = IPFW_TABLE_CIDR; error = (opt == IP_FW_TABLE_ADD) ? add_table_entry(chain, &ti, &tei, 0, 1) : del_table_entry(chain, &ti, &tei, 0, 1); } break; case IP_FW_TABLE_FLUSH: { u_int16_t tbl; struct tid_info ti; error = sooptcopyin(sopt, &tbl, sizeof(tbl), sizeof(tbl)); if (error) break; memset(&ti, 0, sizeof(ti)); ti.uidx = tbl; error = flush_table(chain, &ti); } break; case IP_FW_TABLE_GETSIZE: { u_int32_t tbl, cnt; struct tid_info ti; if ((error = sooptcopyin(sopt, &tbl, sizeof(tbl), sizeof(tbl)))) break; memset(&ti, 0, sizeof(ti)); ti.uidx = tbl; IPFW_RLOCK(chain); error = ipfw_count_table(chain, &ti, &cnt); IPFW_RUNLOCK(chain); if (error) break; error = sooptcopyout(sopt, &cnt, sizeof(cnt)); } break; case IP_FW_TABLE_LIST: { ipfw_table *tbl; struct tid_info ti; if (sopt->sopt_valsize < sizeof(*tbl)) { error = EINVAL; break; } size = sopt->sopt_valsize; tbl = malloc(size, M_TEMP, M_WAITOK); error = sooptcopyin(sopt, tbl, size, sizeof(*tbl)); if (error) { free(tbl, M_TEMP); break; } tbl->size = (size - sizeof(*tbl)) / sizeof(ipfw_table_entry); memset(&ti, 0, sizeof(ti)); ti.uidx = tbl->tbl; IPFW_RLOCK(chain); error = ipfw_dump_table_legacy(chain, &ti, tbl); IPFW_RUNLOCK(chain); if (error) { free(tbl, M_TEMP); break; } error = sooptcopyout(sopt, tbl, size); free(tbl, M_TEMP); } break; /*--- NAT operations are protected by the IPFW_LOCK ---*/ case IP_FW_NAT_CFG: if (IPFW_NAT_LOADED) error = ipfw_nat_cfg_ptr(sopt); else { printf("IP_FW_NAT_CFG: %s\n", "ipfw_nat not present, please load it"); error = EINVAL; } break; case IP_FW_NAT_DEL: if (IPFW_NAT_LOADED) error = ipfw_nat_del_ptr(sopt); else { printf("IP_FW_NAT_DEL: %s\n", "ipfw_nat not present, please load it"); error = EINVAL; } break; case IP_FW_NAT_GET_CONFIG: if (IPFW_NAT_LOADED) error = ipfw_nat_get_cfg_ptr(sopt); else { printf("IP_FW_NAT_GET_CFG: %s\n", "ipfw_nat not present, please load it"); error = EINVAL; } break; case IP_FW_NAT_GET_LOG: if (IPFW_NAT_LOADED) error = ipfw_nat_get_log_ptr(sopt); else { printf("IP_FW_NAT_GET_LOG: %s\n", "ipfw_nat not present, please load it"); error = EINVAL; } break; default: printf("ipfw: ipfw_ctl invalid option %d\n", sopt->sopt_name); error = EINVAL; } return (error); #undef RULE_MAXSIZE } #define RULE_MAXSIZE (256*sizeof(u_int32_t)) /* Functions to convert rules 7.2 <==> 8.0 */ static int convert_rule_to_7(struct ip_fw_rule0 *rule) { /* Used to modify original rule */ struct ip_fw7 *rule7 = (struct ip_fw7 *)rule; /* copy of original rule, version 8 */ struct ip_fw_rule0 *tmp; /* Used to copy commands */ ipfw_insn *ccmd, *dst; int ll = 0, ccmdlen = 0; tmp = malloc(RULE_MAXSIZE, M_TEMP, M_NOWAIT | M_ZERO); if (tmp == NULL) { return 1; //XXX error } bcopy(rule, tmp, RULE_MAXSIZE); /* Copy fields */ //rule7->_pad = tmp->_pad; rule7->set = tmp->set; rule7->rulenum = tmp->rulenum; rule7->cmd_len = tmp->cmd_len; rule7->act_ofs = tmp->act_ofs; rule7->next_rule = (struct ip_fw7 *)tmp->next_rule; rule7->cmd_len = tmp->cmd_len; rule7->pcnt = tmp->pcnt; rule7->bcnt = tmp->bcnt; rule7->timestamp = tmp->timestamp; /* Copy commands */ for (ll = tmp->cmd_len, ccmd = tmp->cmd, dst = rule7->cmd ; ll > 0 ; ll -= ccmdlen, ccmd += ccmdlen, dst += ccmdlen) { ccmdlen = F_LEN(ccmd); bcopy(ccmd, dst, F_LEN(ccmd)*sizeof(uint32_t)); if (dst->opcode > O_NAT) /* O_REASS doesn't exists in 7.2 version, so * decrement opcode if it is after O_REASS */ dst->opcode--; if (ccmdlen > ll) { printf("ipfw: opcode %d size truncated\n", ccmd->opcode); return EINVAL; } } free(tmp, M_TEMP); return 0; } static int convert_rule_to_8(struct ip_fw_rule0 *rule) { /* Used to modify original rule */ struct ip_fw7 *rule7 = (struct ip_fw7 *) rule; /* Used to copy commands */ ipfw_insn *ccmd, *dst; int ll = 0, ccmdlen = 0; /* Copy of original rule */ struct ip_fw7 *tmp = malloc(RULE_MAXSIZE, M_TEMP, M_NOWAIT | M_ZERO); if (tmp == NULL) { return 1; //XXX error } bcopy(rule7, tmp, RULE_MAXSIZE); for (ll = tmp->cmd_len, ccmd = tmp->cmd, dst = rule->cmd ; ll > 0 ; ll -= ccmdlen, ccmd += ccmdlen, dst += ccmdlen) { ccmdlen = F_LEN(ccmd); bcopy(ccmd, dst, F_LEN(ccmd)*sizeof(uint32_t)); if (dst->opcode > O_NAT) /* O_REASS doesn't exists in 7.2 version, so * increment opcode if it is after O_REASS */ dst->opcode++; if (ccmdlen > ll) { printf("ipfw: opcode %d size truncated\n", ccmd->opcode); return EINVAL; } } rule->_pad = tmp->_pad; rule->set = tmp->set; rule->rulenum = tmp->rulenum; rule->cmd_len = tmp->cmd_len; rule->act_ofs = tmp->act_ofs; rule->next_rule = (struct ip_fw *)tmp->next_rule; rule->cmd_len = tmp->cmd_len; rule->id = 0; /* XXX see if is ok = 0 */ rule->pcnt = tmp->pcnt; rule->bcnt = tmp->bcnt; rule->timestamp = tmp->timestamp; free (tmp, M_TEMP); return 0; } /* * Named object api * */ void ipfw_init_srv(struct ip_fw_chain *ch) { ch->srvmap = ipfw_objhash_create(IPFW_OBJECTS_DEFAULT); ch->srvstate = malloc(sizeof(void *) * IPFW_OBJECTS_DEFAULT, M_IPFW, M_WAITOK | M_ZERO); } void ipfw_destroy_srv(struct ip_fw_chain *ch) { free(ch->srvstate, M_IPFW); ipfw_objhash_destroy(ch->srvmap); } /* * Allocate new bitmask which can be used to enlarge/shrink * named instance index. */ void ipfw_objhash_bitmap_alloc(uint32_t items, void **idx, int *pblocks) { size_t size; int max_blocks; u_long *idx_mask; KASSERT((items % BLOCK_ITEMS) == 0, ("bitmask size needs to power of 2 and greater or equal to %zu", BLOCK_ITEMS)); max_blocks = items / BLOCK_ITEMS; size = items / 8; idx_mask = malloc(size * IPFW_MAX_SETS, M_IPFW, M_WAITOK); /* Mark all as free */ memset(idx_mask, 0xFF, size * IPFW_MAX_SETS); *idx_mask &= ~(u_long)1; /* Skip index 0 */ *idx = idx_mask; *pblocks = max_blocks; } /* * Copy current bitmask index to new one. */ void ipfw_objhash_bitmap_merge(struct namedobj_instance *ni, void **idx, int *blocks) { int old_blocks, new_blocks; u_long *old_idx, *new_idx; int i; old_idx = ni->idx_mask; old_blocks = ni->max_blocks; new_idx = *idx; new_blocks = *blocks; for (i = 0; i < IPFW_MAX_SETS; i++) { memcpy(&new_idx[new_blocks * i], &old_idx[old_blocks * i], old_blocks * sizeof(u_long)); } } /* * Swaps current @ni index with new one. */ void ipfw_objhash_bitmap_swap(struct namedobj_instance *ni, void **idx, int *blocks) { int old_blocks; u_long *old_idx; old_idx = ni->idx_mask; old_blocks = ni->max_blocks; ni->idx_mask = *idx; ni->max_blocks = *blocks; /* Save old values */ *idx = old_idx; *blocks = old_blocks; } void ipfw_objhash_bitmap_free(void *idx, int blocks) { free(idx, M_IPFW); } /* * Creates named hash instance. * Must be called without holding any locks. * Return pointer to new instance. */ struct namedobj_instance * ipfw_objhash_create(uint32_t items) { struct namedobj_instance *ni; int i; size_t size; size = sizeof(struct namedobj_instance) + sizeof(struct namedobjects_head) * NAMEDOBJ_HASH_SIZE + sizeof(struct namedobjects_head) * NAMEDOBJ_HASH_SIZE; ni = malloc(size, M_IPFW, M_WAITOK | M_ZERO); ni->nn_size = NAMEDOBJ_HASH_SIZE; ni->nv_size = NAMEDOBJ_HASH_SIZE; ni->names = (struct namedobjects_head *)(ni +1); ni->values = &ni->names[ni->nn_size]; for (i = 0; i < ni->nn_size; i++) TAILQ_INIT(&ni->names[i]); for (i = 0; i < ni->nv_size; i++) TAILQ_INIT(&ni->values[i]); /* Set default hashing/comparison functions */ ni->hash_f = objhash_hash_name; ni->cmp_f = objhash_cmp_name; /* Allocate bitmask separately due to possible resize */ ipfw_objhash_bitmap_alloc(items, (void*)&ni->idx_mask, &ni->max_blocks); return (ni); } void ipfw_objhash_destroy(struct namedobj_instance *ni) { free(ni->idx_mask, M_IPFW); free(ni, M_IPFW); } void ipfw_objhash_set_funcs(struct namedobj_instance *ni, objhash_hash_f *hash_f, objhash_cmp_f *cmp_f) { ni->hash_f = hash_f; ni->cmp_f = cmp_f; } static uint32_t objhash_hash_name(struct namedobj_instance *ni, const void *name, uint32_t set) { return (fnv_32_str((const char *)name, FNV1_32_INIT)); } static int objhash_cmp_name(struct named_object *no, const void *name, uint32_t set) { if ((strcmp(no->name, (const char *)name) == 0) && (no->set == set)) return (0); return (1); } static uint32_t objhash_hash_idx(struct namedobj_instance *ni, uint32_t val) { uint32_t v; v = val % (ni->nv_size - 1); return (v); } struct named_object * ipfw_objhash_lookup_name(struct namedobj_instance *ni, uint32_t set, char *name) { struct named_object *no; uint32_t hash; hash = ni->hash_f(ni, name, set) % ni->nn_size; TAILQ_FOREACH(no, &ni->names[hash], nn_next) { if (ni->cmp_f(no, name, set) == 0) return (no); } return (NULL); } /* * Find named object by @uid. * Check @tlvs for valid data inside. * * Returns pointer to found TLV or NULL. */ ipfw_obj_ntlv * ipfw_find_name_tlv_type(void *tlvs, int len, uint16_t uidx, uint32_t etlv) { ipfw_obj_ntlv *ntlv; uintptr_t pa, pe; int l; pa = (uintptr_t)tlvs; pe = pa + len; l = 0; for (; pa < pe; pa += l) { ntlv = (ipfw_obj_ntlv *)pa; l = ntlv->head.length; if (l != sizeof(*ntlv)) return (NULL); if (ntlv->idx != uidx) continue; /* * When userland has specified zero TLV type, do * not compare it with eltv. In some cases userland * doesn't know what type should it have. Use only * uidx and name for search named_object. */ if (ntlv->head.type != 0 && ntlv->head.type != (uint16_t)etlv) continue; if (ipfw_check_object_name_generic(ntlv->name) != 0) return (NULL); return (ntlv); } return (NULL); } /* * Finds object config based on either legacy index * or name in ntlv. * Note @ti structure contains unchecked data from userland. * * Returns 0 in success and fills in @pno with found config */ int ipfw_objhash_find_type(struct namedobj_instance *ni, struct tid_info *ti, uint32_t etlv, struct named_object **pno) { char *name; ipfw_obj_ntlv *ntlv; uint32_t set; if (ti->tlvs == NULL) return (EINVAL); ntlv = ipfw_find_name_tlv_type(ti->tlvs, ti->tlen, ti->uidx, etlv); if (ntlv == NULL) return (EINVAL); name = ntlv->name; /* * Use set provided by @ti instead of @ntlv one. * This is needed due to different sets behavior * controlled by V_fw_tables_sets. */ set = ti->set; *pno = ipfw_objhash_lookup_name(ni, set, name); if (*pno == NULL) return (ESRCH); return (0); } /* * Find named object by name, considering also its TLV type. */ struct named_object * ipfw_objhash_lookup_name_type(struct namedobj_instance *ni, uint32_t set, uint32_t type, const char *name) { struct named_object *no; uint32_t hash; hash = ni->hash_f(ni, name, set) % ni->nn_size; TAILQ_FOREACH(no, &ni->names[hash], nn_next) { if (ni->cmp_f(no, name, set) == 0 && no->etlv == (uint16_t)type) return (no); } return (NULL); } struct named_object * ipfw_objhash_lookup_kidx(struct namedobj_instance *ni, uint16_t kidx) { struct named_object *no; uint32_t hash; hash = objhash_hash_idx(ni, kidx); TAILQ_FOREACH(no, &ni->values[hash], nv_next) { if (no->kidx == kidx) return (no); } return (NULL); } int ipfw_objhash_same_name(struct namedobj_instance *ni, struct named_object *a, struct named_object *b) { if ((strcmp(a->name, b->name) == 0) && a->set == b->set) return (1); return (0); } void ipfw_objhash_add(struct namedobj_instance *ni, struct named_object *no) { uint32_t hash; hash = ni->hash_f(ni, no->name, no->set) % ni->nn_size; TAILQ_INSERT_HEAD(&ni->names[hash], no, nn_next); hash = objhash_hash_idx(ni, no->kidx); TAILQ_INSERT_HEAD(&ni->values[hash], no, nv_next); ni->count++; } void ipfw_objhash_del(struct namedobj_instance *ni, struct named_object *no) { uint32_t hash; hash = ni->hash_f(ni, no->name, no->set) % ni->nn_size; TAILQ_REMOVE(&ni->names[hash], no, nn_next); hash = objhash_hash_idx(ni, no->kidx); TAILQ_REMOVE(&ni->values[hash], no, nv_next); ni->count--; } uint32_t ipfw_objhash_count(struct namedobj_instance *ni) { return (ni->count); } uint32_t ipfw_objhash_count_type(struct namedobj_instance *ni, uint16_t type) { struct named_object *no; uint32_t count; int i; count = 0; for (i = 0; i < ni->nn_size; i++) { TAILQ_FOREACH(no, &ni->names[i], nn_next) { if (no->etlv == type) count++; } } return (count); } /* * Runs @func for each found named object. * It is safe to delete objects from callback */ int ipfw_objhash_foreach(struct namedobj_instance *ni, objhash_cb_t *f, void *arg) { struct named_object *no, *no_tmp; int i, ret; for (i = 0; i < ni->nn_size; i++) { TAILQ_FOREACH_SAFE(no, &ni->names[i], nn_next, no_tmp) { ret = f(ni, no, arg); if (ret != 0) return (ret); } } return (0); } /* * Runs @f for each found named object with type @type. * It is safe to delete objects from callback */ int ipfw_objhash_foreach_type(struct namedobj_instance *ni, objhash_cb_t *f, void *arg, uint16_t type) { struct named_object *no, *no_tmp; int i, ret; for (i = 0; i < ni->nn_size; i++) { TAILQ_FOREACH_SAFE(no, &ni->names[i], nn_next, no_tmp) { if (no->etlv != type) continue; ret = f(ni, no, arg); if (ret != 0) return (ret); } } return (0); } /* * Removes index from given set. * Returns 0 on success. */ int ipfw_objhash_free_idx(struct namedobj_instance *ni, uint16_t idx) { u_long *mask; int i, v; i = idx / BLOCK_ITEMS; v = idx % BLOCK_ITEMS; if (i >= ni->max_blocks) return (1); mask = &ni->idx_mask[i]; if ((*mask & ((u_long)1 << v)) != 0) return (1); /* Mark as free */ *mask |= (u_long)1 << v; /* Update free offset */ if (ni->free_off[0] > i) ni->free_off[0] = i; return (0); } /* * Allocate new index in given instance and stores in in @pidx. * Returns 0 on success. */ int ipfw_objhash_alloc_idx(void *n, uint16_t *pidx) { struct namedobj_instance *ni; u_long *mask; int i, off, v; ni = (struct namedobj_instance *)n; off = ni->free_off[0]; mask = &ni->idx_mask[off]; for (i = off; i < ni->max_blocks; i++, mask++) { if ((v = ffsl(*mask)) == 0) continue; /* Mark as busy */ *mask &= ~ ((u_long)1 << (v - 1)); ni->free_off[0] = i; v = BLOCK_ITEMS * i + v - 1; *pidx = v; return (0); } return (1); } /* end of file */ Index: head/sys/netpfil/ipfw/nat64/nat64lsn_control.c =================================================================== --- head/sys/netpfil/ipfw/nat64/nat64lsn_control.c (revision 341470) +++ head/sys/netpfil/ipfw/nat64/nat64lsn_control.c (revision 341471) @@ -1,912 +1,913 @@ /*- * Copyright (c) 2015 Yandex LLC * Copyright (c) 2015 Alexander V. Chernikov * Copyright (c) 2016 Andrey V. Elsukov * All rights reserved. * * 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 ``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 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nat64lsn.h" VNET_DEFINE(uint16_t, nat64lsn_eid) = 0; static struct nat64lsn_cfg * nat64lsn_find(struct namedobj_instance *ni, const char *name, uint8_t set) { struct nat64lsn_cfg *cfg; cfg = (struct nat64lsn_cfg *)ipfw_objhash_lookup_name_type(ni, set, IPFW_TLV_NAT64LSN_NAME, name); return (cfg); } static void nat64lsn_default_config(ipfw_nat64lsn_cfg *uc) { if (uc->max_ports == 0) uc->max_ports = NAT64LSN_MAX_PORTS; else uc->max_ports = roundup(uc->max_ports, NAT64_CHUNK_SIZE); if (uc->max_ports > NAT64_CHUNK_SIZE * NAT64LSN_MAXPGPTR) uc->max_ports = NAT64_CHUNK_SIZE * NAT64LSN_MAXPGPTR; if (uc->jmaxlen == 0) uc->jmaxlen = NAT64LSN_JMAXLEN; if (uc->jmaxlen > 65536) uc->jmaxlen = 65536; if (uc->nh_delete_delay == 0) uc->nh_delete_delay = NAT64LSN_HOST_AGE; if (uc->pg_delete_delay == 0) uc->pg_delete_delay = NAT64LSN_PG_AGE; if (uc->st_syn_ttl == 0) uc->st_syn_ttl = NAT64LSN_TCP_SYN_AGE; if (uc->st_close_ttl == 0) uc->st_close_ttl = NAT64LSN_TCP_FIN_AGE; if (uc->st_estab_ttl == 0) uc->st_estab_ttl = NAT64LSN_TCP_EST_AGE; if (uc->st_udp_ttl == 0) uc->st_udp_ttl = NAT64LSN_UDP_AGE; if (uc->st_icmp_ttl == 0) uc->st_icmp_ttl = NAT64LSN_ICMP_AGE; } /* * Creates new nat64lsn instance. * Data layout (v0)(current): * Request: [ ipfw_obj_lheader ipfw_nat64lsn_cfg ] * * Returns 0 on success */ static int nat64lsn_create(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { ipfw_obj_lheader *olh; ipfw_nat64lsn_cfg *uc; struct nat64lsn_cfg *cfg; struct namedobj_instance *ni; uint32_t addr4, mask4; if (sd->valsize != sizeof(*olh) + sizeof(*uc)) return (EINVAL); olh = (ipfw_obj_lheader *)sd->kbuf; uc = (ipfw_nat64lsn_cfg *)(olh + 1); if (ipfw_check_object_name_generic(uc->name) != 0) return (EINVAL); if (uc->agg_prefix_len > 127 || uc->set >= IPFW_MAX_SETS) return (EINVAL); if (uc->plen4 > 32) return (EINVAL); if (nat64_check_prefix6(&uc->prefix6, uc->plen6) != 0) return (EINVAL); /* XXX: Check prefix4 to be global */ addr4 = ntohl(uc->prefix4.s_addr); mask4 = ~((1 << (32 - uc->plen4)) - 1); if ((addr4 & mask4) != addr4) return (EINVAL); if (uc->min_port == 0) uc->min_port = NAT64_MIN_PORT; if (uc->max_port == 0) uc->max_port = 65535; if (uc->min_port > uc->max_port) return (EINVAL); uc->min_port = roundup(uc->min_port, NAT64_CHUNK_SIZE); uc->max_port = roundup(uc->max_port, NAT64_CHUNK_SIZE); nat64lsn_default_config(uc); ni = CHAIN_TO_SRV(ch); IPFW_UH_RLOCK(ch); if (nat64lsn_find(ni, uc->name, uc->set) != NULL) { IPFW_UH_RUNLOCK(ch); return (EEXIST); } IPFW_UH_RUNLOCK(ch); cfg = nat64lsn_init_instance(ch, 1 << (32 - uc->plen4)); strlcpy(cfg->name, uc->name, sizeof(cfg->name)); cfg->no.name = cfg->name; cfg->no.etlv = IPFW_TLV_NAT64LSN_NAME; cfg->no.set = uc->set; cfg->base.prefix6 = uc->prefix6; cfg->base.plen6 = uc->plen6; cfg->base.flags = uc->flags & NAT64LSN_FLAGSMASK; if (IN6_IS_ADDR_WKPFX(&cfg->base.prefix6)) cfg->base.flags |= NAT64_WKPFX; cfg->prefix4 = addr4; cfg->pmask4 = addr4 | ~mask4; cfg->plen4 = uc->plen4; cfg->max_chunks = uc->max_ports / NAT64_CHUNK_SIZE; cfg->agg_prefix_len = uc->agg_prefix_len; cfg->agg_prefix_max = uc->agg_prefix_max; cfg->min_chunk = uc->min_port / NAT64_CHUNK_SIZE; cfg->max_chunk = uc->max_port / NAT64_CHUNK_SIZE; cfg->jmaxlen = uc->jmaxlen; cfg->nh_delete_delay = uc->nh_delete_delay; cfg->pg_delete_delay = uc->pg_delete_delay; cfg->st_syn_ttl = uc->st_syn_ttl; cfg->st_close_ttl = uc->st_close_ttl; cfg->st_estab_ttl = uc->st_estab_ttl; cfg->st_udp_ttl = uc->st_udp_ttl; cfg->st_icmp_ttl = uc->st_icmp_ttl; cfg->nomatch_verdict = IP_FW_DENY; IPFW_UH_WLOCK(ch); if (nat64lsn_find(ni, uc->name, uc->set) != NULL) { IPFW_UH_WUNLOCK(ch); nat64lsn_destroy_instance(cfg); return (EEXIST); } if (ipfw_objhash_alloc_idx(CHAIN_TO_SRV(ch), &cfg->no.kidx) != 0) { IPFW_UH_WUNLOCK(ch); nat64lsn_destroy_instance(cfg); return (ENOSPC); } ipfw_objhash_add(CHAIN_TO_SRV(ch), &cfg->no); /* Okay, let's link data */ SRV_OBJECT(ch, cfg->no.kidx) = cfg; nat64lsn_start_instance(cfg); IPFW_UH_WUNLOCK(ch); return (0); } static void nat64lsn_detach_config(struct ip_fw_chain *ch, struct nat64lsn_cfg *cfg) { IPFW_UH_WLOCK_ASSERT(ch); ipfw_objhash_del(CHAIN_TO_SRV(ch), &cfg->no); ipfw_objhash_free_idx(CHAIN_TO_SRV(ch), cfg->no.kidx); } /* * Destroys nat64 instance. * Data layout (v0)(current): * Request: [ ipfw_obj_header ] * * Returns 0 on success */ static int nat64lsn_destroy(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { struct nat64lsn_cfg *cfg; ipfw_obj_header *oh; if (sd->valsize != sizeof(*oh)) return (EINVAL); oh = (ipfw_obj_header *)op3; IPFW_UH_WLOCK(ch); cfg = nat64lsn_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); if (cfg == NULL) { IPFW_UH_WUNLOCK(ch); return (ESRCH); } if (cfg->no.refcnt > 0) { IPFW_UH_WUNLOCK(ch); return (EBUSY); } + ipfw_reset_eaction_instance(ch, V_nat64lsn_eid, cfg->no.kidx); SRV_OBJECT(ch, cfg->no.kidx) = NULL; nat64lsn_detach_config(ch, cfg); IPFW_UH_WUNLOCK(ch); nat64lsn_destroy_instance(cfg); return (0); } #define __COPY_STAT_FIELD(_cfg, _stats, _field) \ (_stats)->_field = NAT64STAT_FETCH(&(_cfg)->base.stats, _field) static void export_stats(struct ip_fw_chain *ch, struct nat64lsn_cfg *cfg, struct ipfw_nat64lsn_stats *stats) { __COPY_STAT_FIELD(cfg, stats, opcnt64); __COPY_STAT_FIELD(cfg, stats, opcnt46); __COPY_STAT_FIELD(cfg, stats, ofrags); __COPY_STAT_FIELD(cfg, stats, ifrags); __COPY_STAT_FIELD(cfg, stats, oerrors); __COPY_STAT_FIELD(cfg, stats, noroute4); __COPY_STAT_FIELD(cfg, stats, noroute6); __COPY_STAT_FIELD(cfg, stats, nomatch4); __COPY_STAT_FIELD(cfg, stats, noproto); __COPY_STAT_FIELD(cfg, stats, nomem); __COPY_STAT_FIELD(cfg, stats, dropped); __COPY_STAT_FIELD(cfg, stats, jcalls); __COPY_STAT_FIELD(cfg, stats, jrequests); __COPY_STAT_FIELD(cfg, stats, jhostsreq); __COPY_STAT_FIELD(cfg, stats, jportreq); __COPY_STAT_FIELD(cfg, stats, jhostfails); __COPY_STAT_FIELD(cfg, stats, jportfails); __COPY_STAT_FIELD(cfg, stats, jmaxlen); __COPY_STAT_FIELD(cfg, stats, jnomem); __COPY_STAT_FIELD(cfg, stats, jreinjected); __COPY_STAT_FIELD(cfg, stats, screated); __COPY_STAT_FIELD(cfg, stats, sdeleted); __COPY_STAT_FIELD(cfg, stats, spgcreated); __COPY_STAT_FIELD(cfg, stats, spgdeleted); stats->hostcount = cfg->ihcount; stats->tcpchunks = cfg->protochunks[NAT_PROTO_TCP]; stats->udpchunks = cfg->protochunks[NAT_PROTO_UDP]; stats->icmpchunks = cfg->protochunks[NAT_PROTO_ICMP]; } #undef __COPY_STAT_FIELD static void nat64lsn_export_config(struct ip_fw_chain *ch, struct nat64lsn_cfg *cfg, ipfw_nat64lsn_cfg *uc) { uc->flags = cfg->base.flags & NAT64LSN_FLAGSMASK; uc->max_ports = cfg->max_chunks * NAT64_CHUNK_SIZE; uc->agg_prefix_len = cfg->agg_prefix_len; uc->agg_prefix_max = cfg->agg_prefix_max; uc->jmaxlen = cfg->jmaxlen; uc->nh_delete_delay = cfg->nh_delete_delay; uc->pg_delete_delay = cfg->pg_delete_delay; uc->st_syn_ttl = cfg->st_syn_ttl; uc->st_close_ttl = cfg->st_close_ttl; uc->st_estab_ttl = cfg->st_estab_ttl; uc->st_udp_ttl = cfg->st_udp_ttl; uc->st_icmp_ttl = cfg->st_icmp_ttl; uc->prefix4.s_addr = htonl(cfg->prefix4); uc->prefix6 = cfg->base.prefix6; uc->plen4 = cfg->plen4; uc->plen6 = cfg->base.plen6; uc->set = cfg->no.set; strlcpy(uc->name, cfg->no.name, sizeof(uc->name)); } struct nat64_dump_arg { struct ip_fw_chain *ch; struct sockopt_data *sd; }; static int export_config_cb(struct namedobj_instance *ni, struct named_object *no, void *arg) { struct nat64_dump_arg *da = (struct nat64_dump_arg *)arg; ipfw_nat64lsn_cfg *uc; uc = (struct _ipfw_nat64lsn_cfg *)ipfw_get_sopt_space(da->sd, sizeof(*uc)); nat64lsn_export_config(da->ch, (struct nat64lsn_cfg *)no, uc); return (0); } /* * Lists all nat64 lsn instances currently available in kernel. * Data layout (v0)(current): * Request: [ ipfw_obj_lheader ] * Reply: [ ipfw_obj_lheader ipfw_nat64lsn_cfg x N ] * * Returns 0 on success */ static int nat64lsn_list(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { ipfw_obj_lheader *olh; struct nat64_dump_arg da; /* Check minimum header size */ if (sd->valsize < sizeof(ipfw_obj_lheader)) return (EINVAL); olh = (ipfw_obj_lheader *)ipfw_get_sopt_header(sd, sizeof(*olh)); IPFW_UH_RLOCK(ch); olh->count = ipfw_objhash_count_type(CHAIN_TO_SRV(ch), IPFW_TLV_NAT64LSN_NAME); olh->objsize = sizeof(ipfw_nat64lsn_cfg); olh->size = sizeof(*olh) + olh->count * olh->objsize; if (sd->valsize < olh->size) { IPFW_UH_RUNLOCK(ch); return (ENOMEM); } memset(&da, 0, sizeof(da)); da.ch = ch; da.sd = sd; ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), export_config_cb, &da, IPFW_TLV_NAT64LSN_NAME); IPFW_UH_RUNLOCK(ch); return (0); } /* * Change existing nat64lsn instance configuration. * Data layout (v0)(current): * Request: [ ipfw_obj_header ipfw_nat64lsn_cfg ] * Reply: [ ipfw_obj_header ipfw_nat64lsn_cfg ] * * Returns 0 on success */ static int nat64lsn_config(struct ip_fw_chain *ch, ip_fw3_opheader *op, struct sockopt_data *sd) { ipfw_obj_header *oh; ipfw_nat64lsn_cfg *uc; struct nat64lsn_cfg *cfg; struct namedobj_instance *ni; if (sd->valsize != sizeof(*oh) + sizeof(*uc)) return (EINVAL); oh = (ipfw_obj_header *)ipfw_get_sopt_space(sd, sizeof(*oh) + sizeof(*uc)); uc = (ipfw_nat64lsn_cfg *)(oh + 1); if (ipfw_check_object_name_generic(oh->ntlv.name) != 0 || oh->ntlv.set >= IPFW_MAX_SETS) return (EINVAL); ni = CHAIN_TO_SRV(ch); if (sd->sopt->sopt_dir == SOPT_GET) { IPFW_UH_RLOCK(ch); cfg = nat64lsn_find(ni, oh->ntlv.name, oh->ntlv.set); if (cfg == NULL) { IPFW_UH_RUNLOCK(ch); return (EEXIST); } nat64lsn_export_config(ch, cfg, uc); IPFW_UH_RUNLOCK(ch); return (0); } nat64lsn_default_config(uc); IPFW_UH_WLOCK(ch); cfg = nat64lsn_find(ni, oh->ntlv.name, oh->ntlv.set); if (cfg == NULL) { IPFW_UH_WUNLOCK(ch); return (EEXIST); } /* * For now allow to change only following values: * jmaxlen, nh_del_age, pg_del_age, tcp_syn_age, tcp_close_age, * tcp_est_age, udp_age, icmp_age, flags, max_ports. */ cfg->max_chunks = uc->max_ports / NAT64_CHUNK_SIZE; cfg->jmaxlen = uc->jmaxlen; cfg->nh_delete_delay = uc->nh_delete_delay; cfg->pg_delete_delay = uc->pg_delete_delay; cfg->st_syn_ttl = uc->st_syn_ttl; cfg->st_close_ttl = uc->st_close_ttl; cfg->st_estab_ttl = uc->st_estab_ttl; cfg->st_udp_ttl = uc->st_udp_ttl; cfg->st_icmp_ttl = uc->st_icmp_ttl; cfg->base.flags &= ~NAT64LSN_FLAGSMASK; cfg->base.flags |= uc->flags & NAT64LSN_FLAGSMASK; IPFW_UH_WUNLOCK(ch); return (0); } /* * Get nat64lsn statistics. * Data layout (v0)(current): * Request: [ ipfw_obj_header ] * Reply: [ ipfw_obj_header ipfw_counter_tlv ] * * Returns 0 on success */ static int nat64lsn_stats(struct ip_fw_chain *ch, ip_fw3_opheader *op, struct sockopt_data *sd) { struct ipfw_nat64lsn_stats stats; struct nat64lsn_cfg *cfg; ipfw_obj_header *oh; ipfw_obj_ctlv *ctlv; size_t sz; sz = sizeof(ipfw_obj_header) + sizeof(ipfw_obj_ctlv) + sizeof(stats); if (sd->valsize % sizeof(uint64_t)) return (EINVAL); if (sd->valsize < sz) return (ENOMEM); oh = (ipfw_obj_header *)ipfw_get_sopt_header(sd, sz); if (oh == NULL) return (EINVAL); memset(&stats, 0, sizeof(stats)); IPFW_UH_RLOCK(ch); cfg = nat64lsn_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); if (cfg == NULL) { IPFW_UH_RUNLOCK(ch); return (ESRCH); } export_stats(ch, cfg, &stats); IPFW_UH_RUNLOCK(ch); ctlv = (ipfw_obj_ctlv *)(oh + 1); memset(ctlv, 0, sizeof(*ctlv)); ctlv->head.type = IPFW_TLV_COUNTERS; ctlv->head.length = sz - sizeof(ipfw_obj_header); ctlv->count = sizeof(stats) / sizeof(uint64_t); ctlv->objsize = sizeof(uint64_t); ctlv->version = IPFW_NAT64_VERSION; memcpy(ctlv + 1, &stats, sizeof(stats)); return (0); } /* * Reset nat64lsn statistics. * Data layout (v0)(current): * Request: [ ipfw_obj_header ] * * Returns 0 on success */ static int nat64lsn_reset_stats(struct ip_fw_chain *ch, ip_fw3_opheader *op, struct sockopt_data *sd) { struct nat64lsn_cfg *cfg; ipfw_obj_header *oh; if (sd->valsize != sizeof(*oh)) return (EINVAL); oh = (ipfw_obj_header *)sd->kbuf; if (ipfw_check_object_name_generic(oh->ntlv.name) != 0 || oh->ntlv.set >= IPFW_MAX_SETS) return (EINVAL); IPFW_UH_WLOCK(ch); cfg = nat64lsn_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); if (cfg == NULL) { IPFW_UH_WUNLOCK(ch); return (ESRCH); } COUNTER_ARRAY_ZERO(cfg->base.stats.cnt, NAT64STATS); IPFW_UH_WUNLOCK(ch); return (0); } /* * Reply: [ ipfw_obj_header ipfw_obj_data [ ipfw_nat64lsn_stg * ipfw_nat64lsn_state x count, ... ] ] */ static int export_pg_states(struct nat64lsn_cfg *cfg, struct nat64lsn_portgroup *pg, ipfw_nat64lsn_stg *stg, struct sockopt_data *sd) { ipfw_nat64lsn_state *ste; struct nat64lsn_state *st; int i, count; NAT64_LOCK(pg->host); count = 0; for (i = 0; i < 64; i++) { if (PG_IS_BUSY_IDX(pg, i)) count++; } DPRINTF(DP_STATE, "EXPORT PG %d, count %d", pg->idx, count); if (count == 0) { stg->count = 0; NAT64_UNLOCK(pg->host); return (0); } ste = (ipfw_nat64lsn_state *)ipfw_get_sopt_space(sd, count * sizeof(ipfw_nat64lsn_state)); if (ste == NULL) { NAT64_UNLOCK(pg->host); return (1); } stg->alias4.s_addr = pg->aaddr; stg->proto = nat64lsn_rproto_map[pg->nat_proto]; stg->flags = 0; stg->host6 = pg->host->addr; stg->count = count; for (i = 0; i < 64; i++) { if (PG_IS_FREE_IDX(pg, i)) continue; st = &pg->states[i]; ste->daddr.s_addr = st->u.s.faddr; ste->dport = st->u.s.fport; ste->aport = pg->aport + i; ste->sport = st->u.s.lport; ste->flags = st->flags; /* XXX filter flags */ ste->idle = GET_AGE(st->timestamp); ste++; } NAT64_UNLOCK(pg->host); return (0); } static int get_next_idx(struct nat64lsn_cfg *cfg, uint32_t *addr, uint8_t *nat_proto, uint16_t *port) { if (*port < 65536 - NAT64_CHUNK_SIZE) { *port += NAT64_CHUNK_SIZE; return (0); } *port = 0; if (*nat_proto < NAT_MAX_PROTO - 1) { *nat_proto += 1; return (0); } *nat_proto = 1; if (*addr < cfg->pmask4) { *addr += 1; return (0); } /* End of space. */ return (1); } #define PACK_IDX(addr, proto, port) \ ((uint64_t)addr << 32) | ((uint32_t)port << 16) | (proto << 8) #define UNPACK_IDX(idx, addr, proto, port) \ (addr) = (uint32_t)((idx) >> 32); \ (port) = (uint16_t)(((idx) >> 16) & 0xFFFF); \ (proto) = (uint8_t)(((idx) >> 8) & 0xFF) static struct nat64lsn_portgroup * get_next_pg(struct nat64lsn_cfg *cfg, uint32_t *addr, uint8_t *nat_proto, uint16_t *port) { struct nat64lsn_portgroup *pg; uint64_t pre_pack, post_pack; pg = NULL; pre_pack = PACK_IDX(*addr, *nat_proto, *port); for (;;) { if (get_next_idx(cfg, addr, nat_proto, port) != 0) { /* End of states */ return (pg); } pg = GET_PORTGROUP(cfg, *addr, *nat_proto, *port); if (pg != NULL) break; } post_pack = PACK_IDX(*addr, *nat_proto, *port); if (pre_pack == post_pack) DPRINTF(DP_STATE, "XXX: PACK_IDX %u %d %d", *addr, *nat_proto, *port); return (pg); } static NAT64NOINLINE struct nat64lsn_portgroup * get_first_pg(struct nat64lsn_cfg *cfg, uint32_t *addr, uint8_t *nat_proto, uint16_t *port) { struct nat64lsn_portgroup *pg; pg = GET_PORTGROUP(cfg, *addr, *nat_proto, *port); if (pg == NULL) pg = get_next_pg(cfg, addr, nat_proto, port); return (pg); } /* * Lists nat64lsn states. * Data layout (v0)(current): * Request: [ ipfw_obj_header ipfw_obj_data [ uint64_t ]] * Reply: [ ipfw_obj_header ipfw_obj_data [ * ipfw_nat64lsn_stg ipfw_nat64lsn_state x N] ] * * Returns 0 on success */ static int nat64lsn_states(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { ipfw_obj_header *oh; ipfw_obj_data *od; ipfw_nat64lsn_stg *stg; struct nat64lsn_cfg *cfg; struct nat64lsn_portgroup *pg, *pg_next; uint64_t next_idx; size_t sz; uint32_t addr, states; uint16_t port; uint8_t nat_proto; sz = sizeof(ipfw_obj_header) + sizeof(ipfw_obj_data) + sizeof(uint64_t); /* Check minimum header size */ if (sd->valsize < sz) return (EINVAL); oh = (ipfw_obj_header *)sd->kbuf; od = (ipfw_obj_data *)(oh + 1); if (od->head.type != IPFW_TLV_OBJDATA || od->head.length != sz - sizeof(ipfw_obj_header)) return (EINVAL); next_idx = *(uint64_t *)(od + 1); /* Translate index to the request position to start from */ UNPACK_IDX(next_idx, addr, nat_proto, port); if (nat_proto >= NAT_MAX_PROTO) return (EINVAL); if (nat_proto == 0 && addr != 0) return (EINVAL); IPFW_UH_RLOCK(ch); cfg = nat64lsn_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); if (cfg == NULL) { IPFW_UH_RUNLOCK(ch); return (ESRCH); } /* Fill in starting point */ if (addr == 0) { addr = cfg->prefix4; nat_proto = 1; port = 0; } if (addr < cfg->prefix4 || addr > cfg->pmask4) { IPFW_UH_RUNLOCK(ch); DPRINTF(DP_GENERIC | DP_STATE, "XXX: %ju %u %u", (uintmax_t)next_idx, addr, cfg->pmask4); return (EINVAL); } sz = sizeof(ipfw_obj_header) + sizeof(ipfw_obj_data) + sizeof(ipfw_nat64lsn_stg); if (sd->valsize < sz) return (ENOMEM); oh = (ipfw_obj_header *)ipfw_get_sopt_space(sd, sz); od = (ipfw_obj_data *)(oh + 1); od->head.type = IPFW_TLV_OBJDATA; od->head.length = sz - sizeof(ipfw_obj_header); stg = (ipfw_nat64lsn_stg *)(od + 1); pg = get_first_pg(cfg, &addr, &nat_proto, &port); if (pg == NULL) { /* No states */ stg->next_idx = 0xFF; stg->count = 0; IPFW_UH_RUNLOCK(ch); return (0); } states = 0; pg_next = NULL; while (pg != NULL) { pg_next = get_next_pg(cfg, &addr, &nat_proto, &port); if (pg_next == NULL) stg->next_idx = 0xFF; else stg->next_idx = PACK_IDX(addr, nat_proto, port); if (export_pg_states(cfg, pg, stg, sd) != 0) { IPFW_UH_RUNLOCK(ch); return (states == 0 ? ENOMEM: 0); } states += stg->count; od->head.length += stg->count * sizeof(ipfw_nat64lsn_state); sz += stg->count * sizeof(ipfw_nat64lsn_state); if (pg_next != NULL) { sz += sizeof(ipfw_nat64lsn_stg); if (sd->valsize < sz) break; stg = (ipfw_nat64lsn_stg *)ipfw_get_sopt_space(sd, sizeof(ipfw_nat64lsn_stg)); } pg = pg_next; } IPFW_UH_RUNLOCK(ch); return (0); } static struct ipfw_sopt_handler scodes[] = { { IP_FW_NAT64LSN_CREATE, 0, HDIR_BOTH, nat64lsn_create }, { IP_FW_NAT64LSN_DESTROY,0, HDIR_SET, nat64lsn_destroy }, { IP_FW_NAT64LSN_CONFIG, 0, HDIR_BOTH, nat64lsn_config }, { IP_FW_NAT64LSN_LIST, 0, HDIR_GET, nat64lsn_list }, { IP_FW_NAT64LSN_STATS, 0, HDIR_GET, nat64lsn_stats }, { IP_FW_NAT64LSN_RESET_STATS,0, HDIR_SET, nat64lsn_reset_stats }, { IP_FW_NAT64LSN_LIST_STATES,0, HDIR_GET, nat64lsn_states }, }; static int nat64lsn_classify(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype) { ipfw_insn *icmd; icmd = cmd - 1; if (icmd->opcode != O_EXTERNAL_ACTION || icmd->arg1 != V_nat64lsn_eid) return (1); *puidx = cmd->arg1; *ptype = 0; return (0); } static void nat64lsn_update_arg1(ipfw_insn *cmd, uint16_t idx) { cmd->arg1 = idx; } static int nat64lsn_findbyname(struct ip_fw_chain *ch, struct tid_info *ti, struct named_object **pno) { int err; err = ipfw_objhash_find_type(CHAIN_TO_SRV(ch), ti, IPFW_TLV_NAT64LSN_NAME, pno); return (err); } static struct named_object * nat64lsn_findbykidx(struct ip_fw_chain *ch, uint16_t idx) { struct namedobj_instance *ni; struct named_object *no; IPFW_UH_WLOCK_ASSERT(ch); ni = CHAIN_TO_SRV(ch); no = ipfw_objhash_lookup_kidx(ni, idx); KASSERT(no != NULL, ("NAT64LSN with index %d not found", idx)); return (no); } static int nat64lsn_manage_sets(struct ip_fw_chain *ch, uint16_t set, uint8_t new_set, enum ipfw_sets_cmd cmd) { return (ipfw_obj_manage_sets(CHAIN_TO_SRV(ch), IPFW_TLV_NAT64LSN_NAME, set, new_set, cmd)); } static struct opcode_obj_rewrite opcodes[] = { { .opcode = O_EXTERNAL_INSTANCE, .etlv = IPFW_TLV_EACTION /* just show it isn't table */, .classifier = nat64lsn_classify, .update = nat64lsn_update_arg1, .find_byname = nat64lsn_findbyname, .find_bykidx = nat64lsn_findbykidx, .manage_sets = nat64lsn_manage_sets, }, }; static int destroy_config_cb(struct namedobj_instance *ni, struct named_object *no, void *arg) { struct nat64lsn_cfg *cfg; struct ip_fw_chain *ch; ch = (struct ip_fw_chain *)arg; cfg = (struct nat64lsn_cfg *)SRV_OBJECT(ch, no->kidx); SRV_OBJECT(ch, no->kidx) = NULL; nat64lsn_detach_config(ch, cfg); nat64lsn_destroy_instance(cfg); return (0); } int nat64lsn_init(struct ip_fw_chain *ch, int first) { if (first != 0) nat64lsn_init_internal(); V_nat64lsn_eid = ipfw_add_eaction(ch, ipfw_nat64lsn, "nat64lsn"); if (V_nat64lsn_eid == 0) return (ENXIO); IPFW_ADD_SOPT_HANDLER(first, scodes); IPFW_ADD_OBJ_REWRITER(first, opcodes); return (0); } void nat64lsn_uninit(struct ip_fw_chain *ch, int last) { IPFW_DEL_OBJ_REWRITER(last, opcodes); IPFW_DEL_SOPT_HANDLER(last, scodes); ipfw_del_eaction(ch, V_nat64lsn_eid); /* * Since we already have deregistered external action, * our named objects become unaccessible via rules, because * all rules were truncated by ipfw_del_eaction(). * So, we can unlink and destroy our named objects without holding * IPFW_WLOCK(). */ IPFW_UH_WLOCK(ch); ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), destroy_config_cb, ch, IPFW_TLV_NAT64LSN_NAME); V_nat64lsn_eid = 0; IPFW_UH_WUNLOCK(ch); if (last != 0) nat64lsn_uninit_internal(); } Index: head/sys/netpfil/ipfw/nat64/nat64stl_control.c =================================================================== --- head/sys/netpfil/ipfw/nat64/nat64stl_control.c (revision 341470) +++ head/sys/netpfil/ipfw/nat64/nat64stl_control.c (revision 341471) @@ -1,618 +1,619 @@ /*- * Copyright (c) 2015-2016 Yandex LLC * Copyright (c) 2015-2016 Andrey V. Elsukov * Copyright (c) 2015 Alexander V. Chernikov * All rights reserved. * * 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 ``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 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nat64stl.h" VNET_DEFINE(uint16_t, nat64stl_eid) = 0; static struct nat64stl_cfg *nat64stl_alloc_config(const char *name, uint8_t set); static void nat64stl_free_config(struct nat64stl_cfg *cfg); static struct nat64stl_cfg *nat64stl_find(struct namedobj_instance *ni, const char *name, uint8_t set); static struct nat64stl_cfg * nat64stl_alloc_config(const char *name, uint8_t set) { struct nat64stl_cfg *cfg; cfg = malloc(sizeof(struct nat64stl_cfg), M_IPFW, M_WAITOK | M_ZERO); COUNTER_ARRAY_ALLOC(cfg->base.stats.cnt, NAT64STATS, M_WAITOK); cfg->no.name = cfg->name; cfg->no.etlv = IPFW_TLV_NAT64STL_NAME; cfg->no.set = set; strlcpy(cfg->name, name, sizeof(cfg->name)); return (cfg); } static void nat64stl_free_config(struct nat64stl_cfg *cfg) { COUNTER_ARRAY_FREE(cfg->base.stats.cnt, NAT64STATS); free(cfg, M_IPFW); } static void nat64stl_export_config(struct ip_fw_chain *ch, struct nat64stl_cfg *cfg, ipfw_nat64stl_cfg *uc) { struct named_object *no; uc->prefix6 = cfg->base.prefix6; uc->plen6 = cfg->base.plen6; uc->flags = cfg->base.flags & NAT64STL_FLAGSMASK; uc->set = cfg->no.set; strlcpy(uc->name, cfg->no.name, sizeof(uc->name)); no = ipfw_objhash_lookup_table_kidx(ch, cfg->map64); ipfw_export_obj_ntlv(no, &uc->ntlv6); no = ipfw_objhash_lookup_table_kidx(ch, cfg->map46); ipfw_export_obj_ntlv(no, &uc->ntlv4); } struct nat64stl_dump_arg { struct ip_fw_chain *ch; struct sockopt_data *sd; }; static int export_config_cb(struct namedobj_instance *ni, struct named_object *no, void *arg) { struct nat64stl_dump_arg *da = (struct nat64stl_dump_arg *)arg; ipfw_nat64stl_cfg *uc; uc = (ipfw_nat64stl_cfg *)ipfw_get_sopt_space(da->sd, sizeof(*uc)); nat64stl_export_config(da->ch, (struct nat64stl_cfg *)no, uc); return (0); } static struct nat64stl_cfg * nat64stl_find(struct namedobj_instance *ni, const char *name, uint8_t set) { struct nat64stl_cfg *cfg; cfg = (struct nat64stl_cfg *)ipfw_objhash_lookup_name_type(ni, set, IPFW_TLV_NAT64STL_NAME, name); return (cfg); } static int nat64stl_create_internal(struct ip_fw_chain *ch, struct nat64stl_cfg *cfg, ipfw_nat64stl_cfg *i) { IPFW_UH_WLOCK_ASSERT(ch); if (ipfw_objhash_alloc_idx(CHAIN_TO_SRV(ch), &cfg->no.kidx) != 0) return (ENOSPC); cfg->base.flags |= NAT64STL_KIDX; if (ipfw_ref_table(ch, &i->ntlv4, &cfg->map46) != 0) return (EINVAL); cfg->base.flags |= NAT64STL_46T; if (ipfw_ref_table(ch, &i->ntlv6, &cfg->map64) != 0) return (EINVAL); cfg->base.flags |= NAT64STL_64T; ipfw_objhash_add(CHAIN_TO_SRV(ch), &cfg->no); return (0); } /* * Creates new nat64 instance. * Data layout (v0)(current): * Request: [ ipfw_obj_lheader ipfw_nat64stl_cfg ] * * Returns 0 on success */ static int nat64stl_create(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { ipfw_obj_lheader *olh; ipfw_nat64stl_cfg *uc; struct namedobj_instance *ni; struct nat64stl_cfg *cfg; int error; if (sd->valsize != sizeof(*olh) + sizeof(*uc)) return (EINVAL); olh = (ipfw_obj_lheader *)sd->kbuf; uc = (ipfw_nat64stl_cfg *)(olh + 1); if (ipfw_check_object_name_generic(uc->name) != 0) return (EINVAL); if (uc->set >= IPFW_MAX_SETS || nat64_check_prefix6(&uc->prefix6, uc->plen6) != 0) return (EINVAL); /* XXX: check types of tables */ ni = CHAIN_TO_SRV(ch); error = 0; IPFW_UH_RLOCK(ch); if (nat64stl_find(ni, uc->name, uc->set) != NULL) { IPFW_UH_RUNLOCK(ch); return (EEXIST); } IPFW_UH_RUNLOCK(ch); cfg = nat64stl_alloc_config(uc->name, uc->set); cfg->base.prefix6 = uc->prefix6; cfg->base.plen6 = uc->plen6; cfg->base.flags = uc->flags & NAT64STL_FLAGSMASK; if (IN6_IS_ADDR_WKPFX(&cfg->base.prefix6)) cfg->base.flags |= NAT64_WKPFX; IPFW_UH_WLOCK(ch); if (nat64stl_find(ni, uc->name, uc->set) != NULL) { IPFW_UH_WUNLOCK(ch); nat64stl_free_config(cfg); return (EEXIST); } error = nat64stl_create_internal(ch, cfg, uc); if (error == 0) { /* Okay, let's link data */ SRV_OBJECT(ch, cfg->no.kidx) = cfg; IPFW_UH_WUNLOCK(ch); return (0); } if (cfg->base.flags & NAT64STL_KIDX) ipfw_objhash_free_idx(ni, cfg->no.kidx); if (cfg->base.flags & NAT64STL_46T) ipfw_unref_table(ch, cfg->map46); if (cfg->base.flags & NAT64STL_64T) ipfw_unref_table(ch, cfg->map64); IPFW_UH_WUNLOCK(ch); nat64stl_free_config(cfg); return (error); } /* * Change existing nat64stl instance configuration. * Data layout (v0)(current): * Request: [ ipfw_obj_header ipfw_nat64stl_cfg ] * Reply: [ ipfw_obj_header ipfw_nat64stl_cfg ] * * Returns 0 on success */ static int nat64stl_config(struct ip_fw_chain *ch, ip_fw3_opheader *op, struct sockopt_data *sd) { ipfw_obj_header *oh; ipfw_nat64stl_cfg *uc; struct nat64stl_cfg *cfg; struct namedobj_instance *ni; if (sd->valsize != sizeof(*oh) + sizeof(*uc)) return (EINVAL); oh = (ipfw_obj_header *)ipfw_get_sopt_space(sd, sizeof(*oh) + sizeof(*uc)); uc = (ipfw_nat64stl_cfg *)(oh + 1); if (ipfw_check_object_name_generic(oh->ntlv.name) != 0 || oh->ntlv.set >= IPFW_MAX_SETS) return (EINVAL); ni = CHAIN_TO_SRV(ch); if (sd->sopt->sopt_dir == SOPT_GET) { IPFW_UH_RLOCK(ch); cfg = nat64stl_find(ni, oh->ntlv.name, oh->ntlv.set); if (cfg == NULL) { IPFW_UH_RUNLOCK(ch); return (EEXIST); } nat64stl_export_config(ch, cfg, uc); IPFW_UH_RUNLOCK(ch); return (0); } IPFW_UH_WLOCK(ch); cfg = nat64stl_find(ni, oh->ntlv.name, oh->ntlv.set); if (cfg == NULL) { IPFW_UH_WUNLOCK(ch); return (EEXIST); } /* * For now allow to change only following values: * flags. */ cfg->base.flags &= ~NAT64STL_FLAGSMASK; cfg->base.flags |= uc->flags & NAT64STL_FLAGSMASK; IPFW_UH_WUNLOCK(ch); return (0); } static void nat64stl_detach_config(struct ip_fw_chain *ch, struct nat64stl_cfg *cfg) { IPFW_UH_WLOCK_ASSERT(ch); ipfw_objhash_del(CHAIN_TO_SRV(ch), &cfg->no); ipfw_objhash_free_idx(CHAIN_TO_SRV(ch), cfg->no.kidx); ipfw_unref_table(ch, cfg->map46); ipfw_unref_table(ch, cfg->map64); } /* * Destroys nat64 instance. * Data layout (v0)(current): * Request: [ ipfw_obj_header ] * * Returns 0 on success */ static int nat64stl_destroy(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { ipfw_obj_header *oh; struct nat64stl_cfg *cfg; if (sd->valsize != sizeof(*oh)) return (EINVAL); oh = (ipfw_obj_header *)sd->kbuf; if (ipfw_check_object_name_generic(oh->ntlv.name) != 0) return (EINVAL); IPFW_UH_WLOCK(ch); cfg = nat64stl_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); if (cfg == NULL) { IPFW_UH_WUNLOCK(ch); return (ESRCH); } if (cfg->no.refcnt > 0) { IPFW_UH_WUNLOCK(ch); return (EBUSY); } + ipfw_reset_eaction_instance(ch, V_nat64stl_eid, cfg->no.kidx); SRV_OBJECT(ch, cfg->no.kidx) = NULL; nat64stl_detach_config(ch, cfg); IPFW_UH_WUNLOCK(ch); nat64stl_free_config(cfg); return (0); } /* * Lists all nat64stl instances currently available in kernel. * Data layout (v0)(current): * Request: [ ipfw_obj_lheader ] * Reply: [ ipfw_obj_lheader ipfw_nat64stl_cfg x N ] * * Returns 0 on success */ static int nat64stl_list(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { ipfw_obj_lheader *olh; struct nat64stl_dump_arg da; /* Check minimum header size */ if (sd->valsize < sizeof(ipfw_obj_lheader)) return (EINVAL); olh = (ipfw_obj_lheader *)ipfw_get_sopt_header(sd, sizeof(*olh)); IPFW_UH_RLOCK(ch); olh->count = ipfw_objhash_count_type(CHAIN_TO_SRV(ch), IPFW_TLV_NAT64STL_NAME); olh->objsize = sizeof(ipfw_nat64stl_cfg); olh->size = sizeof(*olh) + olh->count * olh->objsize; if (sd->valsize < olh->size) { IPFW_UH_RUNLOCK(ch); return (ENOMEM); } memset(&da, 0, sizeof(da)); da.ch = ch; da.sd = sd; ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), export_config_cb, &da, IPFW_TLV_NAT64STL_NAME); IPFW_UH_RUNLOCK(ch); return (0); } #define __COPY_STAT_FIELD(_cfg, _stats, _field) \ (_stats)->_field = NAT64STAT_FETCH(&(_cfg)->base.stats, _field) static void export_stats(struct ip_fw_chain *ch, struct nat64stl_cfg *cfg, struct ipfw_nat64stl_stats *stats) { __COPY_STAT_FIELD(cfg, stats, opcnt64); __COPY_STAT_FIELD(cfg, stats, opcnt46); __COPY_STAT_FIELD(cfg, stats, ofrags); __COPY_STAT_FIELD(cfg, stats, ifrags); __COPY_STAT_FIELD(cfg, stats, oerrors); __COPY_STAT_FIELD(cfg, stats, noroute4); __COPY_STAT_FIELD(cfg, stats, noroute6); __COPY_STAT_FIELD(cfg, stats, noproto); __COPY_STAT_FIELD(cfg, stats, nomem); __COPY_STAT_FIELD(cfg, stats, dropped); } /* * Get nat64stl statistics. * Data layout (v0)(current): * Request: [ ipfw_obj_header ] * Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ]] * * Returns 0 on success */ static int nat64stl_stats(struct ip_fw_chain *ch, ip_fw3_opheader *op, struct sockopt_data *sd) { struct ipfw_nat64stl_stats stats; struct nat64stl_cfg *cfg; ipfw_obj_header *oh; ipfw_obj_ctlv *ctlv; size_t sz; sz = sizeof(ipfw_obj_header) + sizeof(ipfw_obj_ctlv) + sizeof(stats); if (sd->valsize % sizeof(uint64_t)) return (EINVAL); if (sd->valsize < sz) return (ENOMEM); oh = (ipfw_obj_header *)ipfw_get_sopt_header(sd, sz); if (oh == NULL) return (EINVAL); memset(&stats, 0, sizeof(stats)); IPFW_UH_RLOCK(ch); cfg = nat64stl_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); if (cfg == NULL) { IPFW_UH_RUNLOCK(ch); return (ESRCH); } export_stats(ch, cfg, &stats); IPFW_UH_RUNLOCK(ch); ctlv = (ipfw_obj_ctlv *)(oh + 1); memset(ctlv, 0, sizeof(*ctlv)); ctlv->head.type = IPFW_TLV_COUNTERS; ctlv->head.length = sz - sizeof(ipfw_obj_header); ctlv->count = sizeof(stats) / sizeof(uint64_t); ctlv->objsize = sizeof(uint64_t); ctlv->version = IPFW_NAT64_VERSION; memcpy(ctlv + 1, &stats, sizeof(stats)); return (0); } /* * Reset nat64stl statistics. * Data layout (v0)(current): * Request: [ ipfw_obj_header ] * * Returns 0 on success */ static int nat64stl_reset_stats(struct ip_fw_chain *ch, ip_fw3_opheader *op, struct sockopt_data *sd) { struct nat64stl_cfg *cfg; ipfw_obj_header *oh; if (sd->valsize != sizeof(*oh)) return (EINVAL); oh = (ipfw_obj_header *)sd->kbuf; if (ipfw_check_object_name_generic(oh->ntlv.name) != 0 || oh->ntlv.set >= IPFW_MAX_SETS) return (EINVAL); IPFW_UH_WLOCK(ch); cfg = nat64stl_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); if (cfg == NULL) { IPFW_UH_WUNLOCK(ch); return (ESRCH); } COUNTER_ARRAY_ZERO(cfg->base.stats.cnt, NAT64STATS); IPFW_UH_WUNLOCK(ch); return (0); } static struct ipfw_sopt_handler scodes[] = { { IP_FW_NAT64STL_CREATE, 0, HDIR_SET, nat64stl_create }, { IP_FW_NAT64STL_DESTROY,0, HDIR_SET, nat64stl_destroy }, { IP_FW_NAT64STL_CONFIG, 0, HDIR_BOTH, nat64stl_config }, { IP_FW_NAT64STL_LIST, 0, HDIR_GET, nat64stl_list }, { IP_FW_NAT64STL_STATS, 0, HDIR_GET, nat64stl_stats }, { IP_FW_NAT64STL_RESET_STATS,0, HDIR_SET, nat64stl_reset_stats }, }; static int nat64stl_classify(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype) { ipfw_insn *icmd; icmd = cmd - 1; if (icmd->opcode != O_EXTERNAL_ACTION || icmd->arg1 != V_nat64stl_eid) return (1); *puidx = cmd->arg1; *ptype = 0; return (0); } static void nat64stl_update_arg1(ipfw_insn *cmd, uint16_t idx) { cmd->arg1 = idx; } static int nat64stl_findbyname(struct ip_fw_chain *ch, struct tid_info *ti, struct named_object **pno) { int err; err = ipfw_objhash_find_type(CHAIN_TO_SRV(ch), ti, IPFW_TLV_NAT64STL_NAME, pno); return (err); } static struct named_object * nat64stl_findbykidx(struct ip_fw_chain *ch, uint16_t idx) { struct namedobj_instance *ni; struct named_object *no; IPFW_UH_WLOCK_ASSERT(ch); ni = CHAIN_TO_SRV(ch); no = ipfw_objhash_lookup_kidx(ni, idx); KASSERT(no != NULL, ("NAT with index %d not found", idx)); return (no); } static int nat64stl_manage_sets(struct ip_fw_chain *ch, uint16_t set, uint8_t new_set, enum ipfw_sets_cmd cmd) { return (ipfw_obj_manage_sets(CHAIN_TO_SRV(ch), IPFW_TLV_NAT64STL_NAME, set, new_set, cmd)); } static struct opcode_obj_rewrite opcodes[] = { { .opcode = O_EXTERNAL_INSTANCE, .etlv = IPFW_TLV_EACTION /* just show it isn't table */, .classifier = nat64stl_classify, .update = nat64stl_update_arg1, .find_byname = nat64stl_findbyname, .find_bykidx = nat64stl_findbykidx, .manage_sets = nat64stl_manage_sets, }, }; static int destroy_config_cb(struct namedobj_instance *ni, struct named_object *no, void *arg) { struct nat64stl_cfg *cfg; struct ip_fw_chain *ch; ch = (struct ip_fw_chain *)arg; cfg = (struct nat64stl_cfg *)SRV_OBJECT(ch, no->kidx); SRV_OBJECT(ch, no->kidx) = NULL; nat64stl_detach_config(ch, cfg); nat64stl_free_config(cfg); return (0); } int nat64stl_init(struct ip_fw_chain *ch, int first) { V_nat64stl_eid = ipfw_add_eaction(ch, ipfw_nat64stl, "nat64stl"); if (V_nat64stl_eid == 0) return (ENXIO); IPFW_ADD_SOPT_HANDLER(first, scodes); IPFW_ADD_OBJ_REWRITER(first, opcodes); return (0); } void nat64stl_uninit(struct ip_fw_chain *ch, int last) { IPFW_DEL_OBJ_REWRITER(last, opcodes); IPFW_DEL_SOPT_HANDLER(last, scodes); ipfw_del_eaction(ch, V_nat64stl_eid); /* * Since we already have deregistered external action, * our named objects become unaccessible via rules, because * all rules were truncated by ipfw_del_eaction(). * So, we can unlink and destroy our named objects without holding * IPFW_WLOCK(). */ IPFW_UH_WLOCK(ch); ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), destroy_config_cb, ch, IPFW_TLV_NAT64STL_NAME); V_nat64stl_eid = 0; IPFW_UH_WUNLOCK(ch); } Index: head/sys/netpfil/ipfw/nptv6/nptv6.c =================================================================== --- head/sys/netpfil/ipfw/nptv6/nptv6.c (revision 341470) +++ head/sys/netpfil/ipfw/nptv6/nptv6.c (revision 341471) @@ -1,1040 +1,1041 @@ /*- * Copyright (c) 2016 Yandex LLC * Copyright (c) 2016 Andrey V. Elsukov * All rights reserved. * * 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 ``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 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include VNET_DEFINE_STATIC(uint16_t, nptv6_eid) = 0; #define V_nptv6_eid VNET(nptv6_eid) #define IPFW_TLV_NPTV6_NAME IPFW_TLV_EACTION_NAME(V_nptv6_eid) static eventhandler_tag nptv6_ifaddr_event; static struct nptv6_cfg *nptv6_alloc_config(const char *name, uint8_t set); static void nptv6_free_config(struct nptv6_cfg *cfg); static struct nptv6_cfg *nptv6_find(struct namedobj_instance *ni, const char *name, uint8_t set); static int nptv6_rewrite_internal(struct nptv6_cfg *cfg, struct mbuf **mp, int offset); static int nptv6_rewrite_external(struct nptv6_cfg *cfg, struct mbuf **mp, int offset); #define NPTV6_LOOKUP(chain, cmd) \ (struct nptv6_cfg *)SRV_OBJECT((chain), (cmd)->arg1) #ifndef IN6_MASK_ADDR #define IN6_MASK_ADDR(a, m) do { \ (a)->s6_addr32[0] &= (m)->s6_addr32[0]; \ (a)->s6_addr32[1] &= (m)->s6_addr32[1]; \ (a)->s6_addr32[2] &= (m)->s6_addr32[2]; \ (a)->s6_addr32[3] &= (m)->s6_addr32[3]; \ } while (0) #endif #ifndef IN6_ARE_MASKED_ADDR_EQUAL #define IN6_ARE_MASKED_ADDR_EQUAL(d, a, m) ( \ (((d)->s6_addr32[0] ^ (a)->s6_addr32[0]) & (m)->s6_addr32[0]) == 0 && \ (((d)->s6_addr32[1] ^ (a)->s6_addr32[1]) & (m)->s6_addr32[1]) == 0 && \ (((d)->s6_addr32[2] ^ (a)->s6_addr32[2]) & (m)->s6_addr32[2]) == 0 && \ (((d)->s6_addr32[3] ^ (a)->s6_addr32[3]) & (m)->s6_addr32[3]) == 0 ) #endif #if 0 #define NPTV6_DEBUG(fmt, ...) do { \ printf("%s: " fmt "\n", __func__, ## __VA_ARGS__); \ } while (0) #define NPTV6_IPDEBUG(fmt, ...) do { \ char _s[INET6_ADDRSTRLEN], _d[INET6_ADDRSTRLEN]; \ printf("%s: " fmt "\n", __func__, ## __VA_ARGS__); \ } while (0) #else #define NPTV6_DEBUG(fmt, ...) #define NPTV6_IPDEBUG(fmt, ...) #endif static int nptv6_getlasthdr(struct nptv6_cfg *cfg, struct mbuf *m, int *offset) { struct ip6_hdr *ip6; struct ip6_hbh *hbh; int proto, hlen; hlen = (offset == NULL) ? 0: *offset; if (m->m_len < hlen) return (-1); ip6 = mtodo(m, hlen); hlen += sizeof(*ip6); proto = ip6->ip6_nxt; while (proto == IPPROTO_HOPOPTS || proto == IPPROTO_ROUTING || proto == IPPROTO_DSTOPTS) { hbh = mtodo(m, hlen); if (m->m_len < hlen) return (-1); proto = hbh->ip6h_nxt; hlen += (hbh->ip6h_len + 1) << 3; } if (offset != NULL) *offset = hlen; return (proto); } static int nptv6_translate_icmpv6(struct nptv6_cfg *cfg, struct mbuf **mp, int offset) { struct icmp6_hdr *icmp6; struct ip6_hdr *ip6; struct mbuf *m; m = *mp; if (offset > m->m_len) return (-1); icmp6 = mtodo(m, offset); NPTV6_DEBUG("ICMPv6 type %d", icmp6->icmp6_type); switch (icmp6->icmp6_type) { case ICMP6_DST_UNREACH: case ICMP6_PACKET_TOO_BIG: case ICMP6_TIME_EXCEEDED: case ICMP6_PARAM_PROB: break; case ICMP6_ECHO_REQUEST: case ICMP6_ECHO_REPLY: /* nothing to translate */ return (0); default: /* * XXX: We can add some checks to not translate NDP and MLD * messages. Currently user must explicitly allow these message * types, otherwise packets will be dropped. */ return (-1); } offset += sizeof(*icmp6); if (offset + sizeof(*ip6) > m->m_pkthdr.len) return (-1); if (offset + sizeof(*ip6) > m->m_len) *mp = m = m_pullup(m, offset + sizeof(*ip6)); if (m == NULL) return (-1); ip6 = mtodo(m, offset); NPTV6_IPDEBUG("offset %d, %s -> %s %d", offset, inet_ntop(AF_INET6, &ip6->ip6_src, _s, sizeof(_s)), inet_ntop(AF_INET6, &ip6->ip6_dst, _d, sizeof(_d)), ip6->ip6_nxt); if (IN6_ARE_MASKED_ADDR_EQUAL(&ip6->ip6_src, &cfg->external, &cfg->mask)) return (nptv6_rewrite_external(cfg, mp, offset)); else if (IN6_ARE_MASKED_ADDR_EQUAL(&ip6->ip6_dst, &cfg->internal, &cfg->mask)) return (nptv6_rewrite_internal(cfg, mp, offset)); /* * Addresses in the inner IPv6 header doesn't matched to * our prefixes. */ return (-1); } static int nptv6_search_index(struct nptv6_cfg *cfg, struct in6_addr *a) { int idx; if (cfg->flags & NPTV6_48PLEN) return (3); /* Search suitable word index for adjustment */ for (idx = 4; idx < 8; idx++) if (a->s6_addr16[idx] != 0xffff) break; /* * RFC 6296 p3.7: If an NPTv6 Translator discovers a datagram with * an IID of all-zeros while performing address mapping, that * datagram MUST be dropped, and an ICMPv6 Parameter Problem error * SHOULD be generated. */ if (idx == 8 || (a->s6_addr32[2] == 0 && a->s6_addr32[3] == 0)) return (-1); return (idx); } static void nptv6_copy_addr(struct in6_addr *src, struct in6_addr *dst, struct in6_addr *mask) { int i; for (i = 0; i < 8 && mask->s6_addr8[i] != 0; i++) { dst->s6_addr8[i] &= ~mask->s6_addr8[i]; dst->s6_addr8[i] |= src->s6_addr8[i] & mask->s6_addr8[i]; } } static int nptv6_rewrite_internal(struct nptv6_cfg *cfg, struct mbuf **mp, int offset) { struct in6_addr *addr; struct ip6_hdr *ip6; int idx, proto; uint16_t adj; ip6 = mtodo(*mp, offset); NPTV6_IPDEBUG("offset %d, %s -> %s %d", offset, inet_ntop(AF_INET6, &ip6->ip6_src, _s, sizeof(_s)), inet_ntop(AF_INET6, &ip6->ip6_dst, _d, sizeof(_d)), ip6->ip6_nxt); if (offset == 0) addr = &ip6->ip6_src; else { /* * When we rewriting inner IPv6 header, we need to rewrite * destination address back to external prefix. The datagram in * the ICMPv6 payload should looks like it was send from * external prefix. */ addr = &ip6->ip6_dst; } idx = nptv6_search_index(cfg, addr); if (idx < 0) { /* * Do not send ICMPv6 error when offset isn't zero. * This means we are rewriting inner IPv6 header in the * ICMPv6 error message. */ if (offset == 0) { icmp6_error2(*mp, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR, 0, (*mp)->m_pkthdr.rcvif); *mp = NULL; } return (IP_FW_DENY); } adj = addr->s6_addr16[idx]; nptv6_copy_addr(&cfg->external, addr, &cfg->mask); adj = cksum_add(adj, cfg->adjustment); if (adj == 0xffff) adj = 0; addr->s6_addr16[idx] = adj; if (offset == 0) { /* * We may need to translate addresses in the inner IPv6 * header for ICMPv6 error messages. */ proto = nptv6_getlasthdr(cfg, *mp, &offset); if (proto < 0 || (proto == IPPROTO_ICMPV6 && nptv6_translate_icmpv6(cfg, mp, offset) != 0)) return (IP_FW_DENY); NPTV6STAT_INC(cfg, in2ex); } return (0); } static int nptv6_rewrite_external(struct nptv6_cfg *cfg, struct mbuf **mp, int offset) { struct in6_addr *addr; struct ip6_hdr *ip6; int idx, proto; uint16_t adj; ip6 = mtodo(*mp, offset); NPTV6_IPDEBUG("offset %d, %s -> %s %d", offset, inet_ntop(AF_INET6, &ip6->ip6_src, _s, sizeof(_s)), inet_ntop(AF_INET6, &ip6->ip6_dst, _d, sizeof(_d)), ip6->ip6_nxt); if (offset == 0) addr = &ip6->ip6_dst; else { /* * When we rewriting inner IPv6 header, we need to rewrite * source address back to internal prefix. The datagram in * the ICMPv6 payload should looks like it was send from * internal prefix. */ addr = &ip6->ip6_src; } idx = nptv6_search_index(cfg, addr); if (idx < 0) { /* * Do not send ICMPv6 error when offset isn't zero. * This means we are rewriting inner IPv6 header in the * ICMPv6 error message. */ if (offset == 0) { icmp6_error2(*mp, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR, 0, (*mp)->m_pkthdr.rcvif); *mp = NULL; } return (IP_FW_DENY); } adj = addr->s6_addr16[idx]; nptv6_copy_addr(&cfg->internal, addr, &cfg->mask); adj = cksum_add(adj, ~cfg->adjustment); if (adj == 0xffff) adj = 0; addr->s6_addr16[idx] = adj; if (offset == 0) { /* * We may need to translate addresses in the inner IPv6 * header for ICMPv6 error messages. */ proto = nptv6_getlasthdr(cfg, *mp, &offset); if (proto < 0 || (proto == IPPROTO_ICMPV6 && nptv6_translate_icmpv6(cfg, mp, offset) != 0)) return (IP_FW_DENY); NPTV6STAT_INC(cfg, ex2in); } return (0); } /* * ipfw external action handler. */ static int ipfw_nptv6(struct ip_fw_chain *chain, struct ip_fw_args *args, ipfw_insn *cmd, int *done) { struct ip6_hdr *ip6; struct nptv6_cfg *cfg; ipfw_insn *icmd; int ret; *done = 0; /* try next rule if not matched */ ret = IP_FW_DENY; icmd = cmd + 1; if (cmd->opcode != O_EXTERNAL_ACTION || cmd->arg1 != V_nptv6_eid || icmd->opcode != O_EXTERNAL_INSTANCE || (cfg = NPTV6_LOOKUP(chain, icmd)) == NULL || (cfg->flags & NPTV6_READY) == 0) return (ret); /* * We need act as router, so when forwarding is disabled - * do nothing. */ if (V_ip6_forwarding == 0 || args->f_id.addr_type != 6) return (ret); /* * NOTE: we expect ipfw_chk() did m_pullup() up to upper level * protocol's headers. Also we skip some checks, that ip6_input(), * ip6_forward(), ip6_fastfwd() and ipfw_chk() already did. */ ip6 = mtod(args->m, struct ip6_hdr *); NPTV6_IPDEBUG("eid %u, oid %u, %s -> %s %d", cmd->arg1, icmd->arg1, inet_ntop(AF_INET6, &ip6->ip6_src, _s, sizeof(_s)), inet_ntop(AF_INET6, &ip6->ip6_dst, _d, sizeof(_d)), ip6->ip6_nxt); if (IN6_ARE_MASKED_ADDR_EQUAL(&ip6->ip6_src, &cfg->internal, &cfg->mask)) { /* * XXX: Do not translate packets when both src and dst * are from internal prefix. */ if (IN6_ARE_MASKED_ADDR_EQUAL(&ip6->ip6_dst, &cfg->internal, &cfg->mask)) return (ret); ret = nptv6_rewrite_internal(cfg, &args->m, 0); } else if (IN6_ARE_MASKED_ADDR_EQUAL(&ip6->ip6_dst, &cfg->external, &cfg->mask)) ret = nptv6_rewrite_external(cfg, &args->m, 0); else return (ret); /* * If address wasn't rewrited - free mbuf and terminate the search. */ if (ret != 0) { if (args->m != NULL) { m_freem(args->m); args->m = NULL; /* mark mbuf as consumed */ } NPTV6STAT_INC(cfg, dropped); *done = 1; } else { /* Terminate the search if one_pass is set */ *done = V_fw_one_pass; /* Update args->f_id when one_pass is off */ if (*done == 0) { ip6 = mtod(args->m, struct ip6_hdr *); args->f_id.src_ip6 = ip6->ip6_src; args->f_id.dst_ip6 = ip6->ip6_dst; } } return (ret); } static struct nptv6_cfg * nptv6_alloc_config(const char *name, uint8_t set) { struct nptv6_cfg *cfg; cfg = malloc(sizeof(struct nptv6_cfg), M_IPFW, M_WAITOK | M_ZERO); COUNTER_ARRAY_ALLOC(cfg->stats, NPTV6STATS, M_WAITOK); cfg->no.name = cfg->name; cfg->no.etlv = IPFW_TLV_NPTV6_NAME; cfg->no.set = set; strlcpy(cfg->name, name, sizeof(cfg->name)); return (cfg); } static void nptv6_free_config(struct nptv6_cfg *cfg) { COUNTER_ARRAY_FREE(cfg->stats, NPTV6STATS); free(cfg, M_IPFW); } static void nptv6_export_config(struct ip_fw_chain *ch, struct nptv6_cfg *cfg, ipfw_nptv6_cfg *uc) { uc->internal = cfg->internal; if (cfg->flags & NPTV6_DYNAMIC_PREFIX) memcpy(uc->if_name, cfg->if_name, IF_NAMESIZE); else uc->external = cfg->external; uc->plen = cfg->plen; uc->flags = cfg->flags & NPTV6_FLAGSMASK; uc->set = cfg->no.set; strlcpy(uc->name, cfg->no.name, sizeof(uc->name)); } struct nptv6_dump_arg { struct ip_fw_chain *ch; struct sockopt_data *sd; }; static int export_config_cb(struct namedobj_instance *ni, struct named_object *no, void *arg) { struct nptv6_dump_arg *da = (struct nptv6_dump_arg *)arg; ipfw_nptv6_cfg *uc; uc = (ipfw_nptv6_cfg *)ipfw_get_sopt_space(da->sd, sizeof(*uc)); nptv6_export_config(da->ch, (struct nptv6_cfg *)no, uc); return (0); } static struct nptv6_cfg * nptv6_find(struct namedobj_instance *ni, const char *name, uint8_t set) { struct nptv6_cfg *cfg; cfg = (struct nptv6_cfg *)ipfw_objhash_lookup_name_type(ni, set, IPFW_TLV_NPTV6_NAME, name); return (cfg); } static void nptv6_calculate_adjustment(struct nptv6_cfg *cfg) { uint16_t i, e; uint16_t *p; /* Calculate checksum of internal prefix */ for (i = 0, p = (uint16_t *)&cfg->internal; p < (uint16_t *)(&cfg->internal + 1); p++) i = cksum_add(i, *p); /* Calculate checksum of external prefix */ for (e = 0, p = (uint16_t *)&cfg->external; p < (uint16_t *)(&cfg->external + 1); p++) e = cksum_add(e, *p); /* Adjustment value for Int->Ext direction */ cfg->adjustment = cksum_add(~e, i); } static int nptv6_check_prefix(const struct in6_addr *addr) { if (IN6_IS_ADDR_MULTICAST(addr) || IN6_IS_ADDR_LINKLOCAL(addr) || IN6_IS_ADDR_LOOPBACK(addr) || IN6_IS_ADDR_UNSPECIFIED(addr)) return (EINVAL); return (0); } static void nptv6_set_external(struct nptv6_cfg *cfg, struct in6_addr *addr) { cfg->external = *addr; IN6_MASK_ADDR(&cfg->external, &cfg->mask); nptv6_calculate_adjustment(cfg); cfg->flags |= NPTV6_READY; } /* * Try to determine what prefix to use as external for * configured interface name. */ static void nptv6_find_prefix(struct ip_fw_chain *ch, struct nptv6_cfg *cfg, struct ifnet *ifp) { struct ifaddr *ifa; struct in6_ifaddr *ia; MPASS(cfg->flags & NPTV6_DYNAMIC_PREFIX); IPFW_UH_WLOCK_ASSERT(ch); if (ifp == NULL) { ifp = ifunit_ref(cfg->if_name); if (ifp == NULL) return; } if_addr_rlock(ifp); CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { if (ifa->ifa_addr->sa_family != AF_INET6) continue; ia = (struct in6_ifaddr *)ifa; if (nptv6_check_prefix(&ia->ia_addr.sin6_addr) || IN6_ARE_MASKED_ADDR_EQUAL(&ia->ia_addr.sin6_addr, &cfg->internal, &cfg->mask)) continue; /* Suitable address is found. */ nptv6_set_external(cfg, &ia->ia_addr.sin6_addr); break; } if_addr_runlock(ifp); if_rele(ifp); } struct ifaddr_event_args { struct ifnet *ifp; const struct in6_addr *addr; int event; }; static int ifaddr_cb(struct namedobj_instance *ni, struct named_object *no, void *arg) { struct ifaddr_event_args *args; struct ip_fw_chain *ch; struct nptv6_cfg *cfg; ch = &V_layer3_chain; cfg = (struct nptv6_cfg *)SRV_OBJECT(ch, no->kidx); if ((cfg->flags & NPTV6_DYNAMIC_PREFIX) == 0) return (0); args = arg; /* If interface name doesn't match, ignore */ if (strncmp(args->ifp->if_xname, cfg->if_name, IF_NAMESIZE)) return (0); if (args->ifp->if_flags & IFF_DYING) { /* XXX: is it possible? */ cfg->flags &= ~NPTV6_READY; return (0); } if (args->event == IFADDR_EVENT_DEL) { /* If instance is not ready, ignore */ if ((cfg->flags & NPTV6_READY) == 0) return (0); /* If address does not match the external prefix, ignore */ if (IN6_ARE_MASKED_ADDR_EQUAL(&cfg->external, args->addr, &cfg->mask) != 0) return (0); /* Otherwise clear READY flag */ cfg->flags &= ~NPTV6_READY; } else {/* IFADDR_EVENT_ADD */ /* If instance is already ready, ignore */ if (cfg->flags & NPTV6_READY) return (0); /* If address is not suitable for prefix, ignore */ if (nptv6_check_prefix(args->addr) || IN6_ARE_MASKED_ADDR_EQUAL(args->addr, &cfg->internal, &cfg->mask)) return (0); /* FALLTHROUGH */ } MPASS(!(cfg->flags & NPTV6_READY)); /* Try to determine the prefix */ if_ref(args->ifp); nptv6_find_prefix(ch, cfg, args->ifp); return (0); } static void nptv6_ifaddrevent_handler(void *arg __unused, struct ifnet *ifp, struct ifaddr *ifa, int event) { struct ifaddr_event_args args; struct ip_fw_chain *ch; if (ifa->ifa_addr->sa_family != AF_INET6) return; args.ifp = ifp; args.addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; args.event = event; ch = &V_layer3_chain; IPFW_UH_WLOCK(ch); ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), ifaddr_cb, &args, IPFW_TLV_NPTV6_NAME); IPFW_UH_WUNLOCK(ch); } /* * Creates new NPTv6 instance. * Data layout (v0)(current): * Request: [ ipfw_obj_lheader ipfw_nptv6_cfg ] * * Returns 0 on success */ static int nptv6_create(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { struct in6_addr mask; ipfw_obj_lheader *olh; ipfw_nptv6_cfg *uc; struct namedobj_instance *ni; struct nptv6_cfg *cfg; if (sd->valsize != sizeof(*olh) + sizeof(*uc)) return (EINVAL); olh = (ipfw_obj_lheader *)sd->kbuf; uc = (ipfw_nptv6_cfg *)(olh + 1); if (ipfw_check_object_name_generic(uc->name) != 0) return (EINVAL); if (uc->plen < 8 || uc->plen > 64 || uc->set >= IPFW_MAX_SETS) return (EINVAL); if (nptv6_check_prefix(&uc->internal)) return (EINVAL); in6_prefixlen2mask(&mask, uc->plen); if ((uc->flags & NPTV6_DYNAMIC_PREFIX) == 0 && ( nptv6_check_prefix(&uc->external) || IN6_ARE_MASKED_ADDR_EQUAL(&uc->external, &uc->internal, &mask))) return (EINVAL); ni = CHAIN_TO_SRV(ch); IPFW_UH_RLOCK(ch); if (nptv6_find(ni, uc->name, uc->set) != NULL) { IPFW_UH_RUNLOCK(ch); return (EEXIST); } IPFW_UH_RUNLOCK(ch); cfg = nptv6_alloc_config(uc->name, uc->set); cfg->plen = uc->plen; cfg->flags = uc->flags & NPTV6_FLAGSMASK; if (cfg->plen <= 48) cfg->flags |= NPTV6_48PLEN; cfg->mask = mask; cfg->internal = uc->internal; IN6_MASK_ADDR(&cfg->internal, &mask); if (cfg->flags & NPTV6_DYNAMIC_PREFIX) memcpy(cfg->if_name, uc->if_name, IF_NAMESIZE); else nptv6_set_external(cfg, &uc->external); if ((uc->flags & NPTV6_DYNAMIC_PREFIX) != 0 && nptv6_ifaddr_event == NULL) nptv6_ifaddr_event = EVENTHANDLER_REGISTER( ifaddr_event_ext, nptv6_ifaddrevent_handler, NULL, EVENTHANDLER_PRI_ANY); IPFW_UH_WLOCK(ch); if (ipfw_objhash_alloc_idx(ni, &cfg->no.kidx) != 0) { IPFW_UH_WUNLOCK(ch); nptv6_free_config(cfg); return (ENOSPC); } ipfw_objhash_add(ni, &cfg->no); SRV_OBJECT(ch, cfg->no.kidx) = cfg; if (cfg->flags & NPTV6_DYNAMIC_PREFIX) nptv6_find_prefix(ch, cfg, NULL); IPFW_UH_WUNLOCK(ch); return (0); } /* * Destroys NPTv6 instance. * Data layout (v0)(current): * Request: [ ipfw_obj_header ] * * Returns 0 on success */ static int nptv6_destroy(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { ipfw_obj_header *oh; struct nptv6_cfg *cfg; if (sd->valsize != sizeof(*oh)) return (EINVAL); oh = (ipfw_obj_header *)sd->kbuf; if (ipfw_check_object_name_generic(oh->ntlv.name) != 0) return (EINVAL); IPFW_UH_WLOCK(ch); cfg = nptv6_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); if (cfg == NULL) { IPFW_UH_WUNLOCK(ch); return (ESRCH); } if (cfg->no.refcnt > 0) { IPFW_UH_WUNLOCK(ch); return (EBUSY); } + ipfw_reset_eaction_instance(ch, V_nptv6_eid, cfg->no.kidx); SRV_OBJECT(ch, cfg->no.kidx) = NULL; ipfw_objhash_del(CHAIN_TO_SRV(ch), &cfg->no); ipfw_objhash_free_idx(CHAIN_TO_SRV(ch), cfg->no.kidx); IPFW_UH_WUNLOCK(ch); nptv6_free_config(cfg); return (0); } /* * Get or change nptv6 instance config. * Request: [ ipfw_obj_header [ ipfw_nptv6_cfg ] ] */ static int nptv6_config(struct ip_fw_chain *chain, ip_fw3_opheader *op, struct sockopt_data *sd) { return (EOPNOTSUPP); } /* * Lists all NPTv6 instances currently available in kernel. * Data layout (v0)(current): * Request: [ ipfw_obj_lheader ] * Reply: [ ipfw_obj_lheader ipfw_nptv6_cfg x N ] * * Returns 0 on success */ static int nptv6_list(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd) { ipfw_obj_lheader *olh; struct nptv6_dump_arg da; /* Check minimum header size */ if (sd->valsize < sizeof(ipfw_obj_lheader)) return (EINVAL); olh = (ipfw_obj_lheader *)ipfw_get_sopt_header(sd, sizeof(*olh)); IPFW_UH_RLOCK(ch); olh->count = ipfw_objhash_count_type(CHAIN_TO_SRV(ch), IPFW_TLV_NPTV6_NAME); olh->objsize = sizeof(ipfw_nptv6_cfg); olh->size = sizeof(*olh) + olh->count * olh->objsize; if (sd->valsize < olh->size) { IPFW_UH_RUNLOCK(ch); return (ENOMEM); } memset(&da, 0, sizeof(da)); da.ch = ch; da.sd = sd; ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), export_config_cb, &da, IPFW_TLV_NPTV6_NAME); IPFW_UH_RUNLOCK(ch); return (0); } #define __COPY_STAT_FIELD(_cfg, _stats, _field) \ (_stats)->_field = NPTV6STAT_FETCH(_cfg, _field) static void export_stats(struct ip_fw_chain *ch, struct nptv6_cfg *cfg, struct ipfw_nptv6_stats *stats) { __COPY_STAT_FIELD(cfg, stats, in2ex); __COPY_STAT_FIELD(cfg, stats, ex2in); __COPY_STAT_FIELD(cfg, stats, dropped); } /* * Get NPTv6 statistics. * Data layout (v0)(current): * Request: [ ipfw_obj_header ] * Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ]] * * Returns 0 on success */ static int nptv6_stats(struct ip_fw_chain *ch, ip_fw3_opheader *op, struct sockopt_data *sd) { struct ipfw_nptv6_stats stats; struct nptv6_cfg *cfg; ipfw_obj_header *oh; ipfw_obj_ctlv *ctlv; size_t sz; sz = sizeof(ipfw_obj_header) + sizeof(ipfw_obj_ctlv) + sizeof(stats); if (sd->valsize % sizeof(uint64_t)) return (EINVAL); if (sd->valsize < sz) return (ENOMEM); oh = (ipfw_obj_header *)ipfw_get_sopt_header(sd, sz); if (oh == NULL) return (EINVAL); if (ipfw_check_object_name_generic(oh->ntlv.name) != 0 || oh->ntlv.set >= IPFW_MAX_SETS) return (EINVAL); memset(&stats, 0, sizeof(stats)); IPFW_UH_RLOCK(ch); cfg = nptv6_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); if (cfg == NULL) { IPFW_UH_RUNLOCK(ch); return (ESRCH); } export_stats(ch, cfg, &stats); IPFW_UH_RUNLOCK(ch); ctlv = (ipfw_obj_ctlv *)(oh + 1); memset(ctlv, 0, sizeof(*ctlv)); ctlv->head.type = IPFW_TLV_COUNTERS; ctlv->head.length = sz - sizeof(ipfw_obj_header); ctlv->count = sizeof(stats) / sizeof(uint64_t); ctlv->objsize = sizeof(uint64_t); ctlv->version = 1; memcpy(ctlv + 1, &stats, sizeof(stats)); return (0); } /* * Reset NPTv6 statistics. * Data layout (v0)(current): * Request: [ ipfw_obj_header ] * * Returns 0 on success */ static int nptv6_reset_stats(struct ip_fw_chain *ch, ip_fw3_opheader *op, struct sockopt_data *sd) { struct nptv6_cfg *cfg; ipfw_obj_header *oh; if (sd->valsize != sizeof(*oh)) return (EINVAL); oh = (ipfw_obj_header *)sd->kbuf; if (ipfw_check_object_name_generic(oh->ntlv.name) != 0 || oh->ntlv.set >= IPFW_MAX_SETS) return (EINVAL); IPFW_UH_WLOCK(ch); cfg = nptv6_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); if (cfg == NULL) { IPFW_UH_WUNLOCK(ch); return (ESRCH); } COUNTER_ARRAY_ZERO(cfg->stats, NPTV6STATS); IPFW_UH_WUNLOCK(ch); return (0); } static struct ipfw_sopt_handler scodes[] = { { IP_FW_NPTV6_CREATE, 0, HDIR_SET, nptv6_create }, { IP_FW_NPTV6_DESTROY,0, HDIR_SET, nptv6_destroy }, { IP_FW_NPTV6_CONFIG, 0, HDIR_BOTH, nptv6_config }, { IP_FW_NPTV6_LIST, 0, HDIR_GET, nptv6_list }, { IP_FW_NPTV6_STATS, 0, HDIR_GET, nptv6_stats }, { IP_FW_NPTV6_RESET_STATS,0, HDIR_SET, nptv6_reset_stats }, }; static int nptv6_classify(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype) { ipfw_insn *icmd; icmd = cmd - 1; NPTV6_DEBUG("opcode %d, arg1 %d, opcode0 %d, arg1 %d", cmd->opcode, cmd->arg1, icmd->opcode, icmd->arg1); if (icmd->opcode != O_EXTERNAL_ACTION || icmd->arg1 != V_nptv6_eid) return (1); *puidx = cmd->arg1; *ptype = 0; return (0); } static void nptv6_update_arg1(ipfw_insn *cmd, uint16_t idx) { cmd->arg1 = idx; NPTV6_DEBUG("opcode %d, arg1 -> %d", cmd->opcode, cmd->arg1); } static int nptv6_findbyname(struct ip_fw_chain *ch, struct tid_info *ti, struct named_object **pno) { int err; err = ipfw_objhash_find_type(CHAIN_TO_SRV(ch), ti, IPFW_TLV_NPTV6_NAME, pno); NPTV6_DEBUG("uidx %u, type %u, err %d", ti->uidx, ti->type, err); return (err); } static struct named_object * nptv6_findbykidx(struct ip_fw_chain *ch, uint16_t idx) { struct namedobj_instance *ni; struct named_object *no; IPFW_UH_WLOCK_ASSERT(ch); ni = CHAIN_TO_SRV(ch); no = ipfw_objhash_lookup_kidx(ni, idx); KASSERT(no != NULL, ("NPT with index %d not found", idx)); NPTV6_DEBUG("kidx %u -> %s", idx, no->name); return (no); } static int nptv6_manage_sets(struct ip_fw_chain *ch, uint16_t set, uint8_t new_set, enum ipfw_sets_cmd cmd) { return (ipfw_obj_manage_sets(CHAIN_TO_SRV(ch), IPFW_TLV_NPTV6_NAME, set, new_set, cmd)); } static struct opcode_obj_rewrite opcodes[] = { { .opcode = O_EXTERNAL_INSTANCE, .etlv = IPFW_TLV_EACTION /* just show it isn't table */, .classifier = nptv6_classify, .update = nptv6_update_arg1, .find_byname = nptv6_findbyname, .find_bykidx = nptv6_findbykidx, .manage_sets = nptv6_manage_sets, }, }; static int destroy_config_cb(struct namedobj_instance *ni, struct named_object *no, void *arg) { struct nptv6_cfg *cfg; struct ip_fw_chain *ch; ch = (struct ip_fw_chain *)arg; IPFW_UH_WLOCK_ASSERT(ch); cfg = (struct nptv6_cfg *)SRV_OBJECT(ch, no->kidx); SRV_OBJECT(ch, no->kidx) = NULL; ipfw_objhash_del(ni, &cfg->no); ipfw_objhash_free_idx(ni, cfg->no.kidx); nptv6_free_config(cfg); return (0); } int nptv6_init(struct ip_fw_chain *ch, int first) { V_nptv6_eid = ipfw_add_eaction(ch, ipfw_nptv6, "nptv6"); if (V_nptv6_eid == 0) return (ENXIO); IPFW_ADD_SOPT_HANDLER(first, scodes); IPFW_ADD_OBJ_REWRITER(first, opcodes); return (0); } void nptv6_uninit(struct ip_fw_chain *ch, int last) { if (last && nptv6_ifaddr_event != NULL) EVENTHANDLER_DEREGISTER(ifaddr_event_ext, nptv6_ifaddr_event); IPFW_DEL_OBJ_REWRITER(last, opcodes); IPFW_DEL_SOPT_HANDLER(last, scodes); ipfw_del_eaction(ch, V_nptv6_eid); /* * Since we already have deregistered external action, * our named objects become unaccessible via rules, because * all rules were truncated by ipfw_del_eaction(). * So, we can unlink and destroy our named objects without holding * IPFW_WLOCK(). */ IPFW_UH_WLOCK(ch); ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), destroy_config_cb, ch, IPFW_TLV_NPTV6_NAME); V_nptv6_eid = 0; IPFW_UH_WUNLOCK(ch); }