diff --git a/sys/netpfil/ipfw/ip_fw2.c b/sys/netpfil/ipfw/ip_fw2.c index c129c8c49921..3f810533b7fc 100644 --- a/sys/netpfil/ipfw/ip_fw2.c +++ b/sys/netpfil/ipfw/ip_fw2.c @@ -1,3838 +1,3838 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * 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. */ #include /* * The FreeBSD IP packet firewall, main file */ #include "opt_ipfw.h" #include "opt_ipdivert.h" #include "opt_inet.h" #ifndef INET #error "IPFIREWALL requires INET" #endif /* INET */ #include "opt_inet6.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for ETHERTYPE_IP */ #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 #ifdef INET6 #include #include #include #include #endif #include /* for struct grehdr */ #include #include /* XXX for in_cksum */ #ifdef MAC #include #endif #define IPFW_PROBE(probe, arg0, arg1, arg2, arg3, arg4, arg5) \ SDT_PROBE6(ipfw, , , probe, arg0, arg1, arg2, arg3, arg4, arg5) SDT_PROVIDER_DEFINE(ipfw); SDT_PROBE_DEFINE6(ipfw, , , rule__matched, "int", /* retval */ "int", /* af */ "void *", /* src addr */ "void *", /* dst addr */ "struct ip_fw_args *", /* args */ "struct ip_fw *" /* rule */); /* * static variables followed by global ones. * All ipfw global variables are here. */ VNET_DEFINE_STATIC(int, fw_deny_unknown_exthdrs); #define V_fw_deny_unknown_exthdrs VNET(fw_deny_unknown_exthdrs) VNET_DEFINE_STATIC(int, fw_permit_single_frag6) = 1; #define V_fw_permit_single_frag6 VNET(fw_permit_single_frag6) #ifdef IPFIREWALL_DEFAULT_TO_ACCEPT static int default_to_accept = 1; #else static int default_to_accept; #endif VNET_DEFINE(int, autoinc_step); VNET_DEFINE(int, fw_one_pass) = 1; VNET_DEFINE(unsigned int, fw_tables_max); VNET_DEFINE(unsigned int, fw_tables_sets) = 0; /* Don't use set-aware tables */ /* Use 128 tables by default */ static unsigned int default_fw_tables = IPFW_TABLES_DEFAULT; #ifndef IPFIREWALL_LINEAR_SKIPTO VNET_DEFINE(int, skipto_cache) = 0; #else VNET_DEFINE(int, skipto_cache) = 1; #endif static uint32_t jump(struct ip_fw_chain *chain, struct ip_fw *f, uint32_t num, int tablearg, bool jump_backwards); /* * Each rule belongs to one of 32 different sets (0..31). * The variable set_disable contains one bit per set. * If the bit is set, all rules in the corresponding set * are disabled. Set RESVD_SET(31) is reserved for the default rule * and rules that are not deleted by the flush command, * and CANNOT be disabled. * Rules in set RESVD_SET can only be deleted individually. */ VNET_DEFINE(u_int32_t, set_disable); #define V_set_disable VNET(set_disable) VNET_DEFINE(int, fw_verbose); /* counter for ipfw_log(NULL...) */ VNET_DEFINE(u_int64_t, norule_counter); VNET_DEFINE(int, verbose_limit); /* layer3_chain contains the list of rules for layer 3 */ VNET_DEFINE(struct ip_fw_chain, layer3_chain); /* ipfw_vnet_ready controls when we are open for business */ VNET_DEFINE(int, ipfw_vnet_ready) = 0; VNET_DEFINE(int, ipfw_nat_ready) = 0; ipfw_nat_t *ipfw_nat_ptr = NULL; struct cfg_nat *(*lookup_nat_ptr)(struct nat_list *, int); ipfw_nat_cfg_t *ipfw_nat_cfg_ptr; ipfw_nat_cfg_t *ipfw_nat_del_ptr; ipfw_nat_cfg_t *ipfw_nat_get_cfg_ptr; ipfw_nat_cfg_t *ipfw_nat_get_log_ptr; #ifdef SYSCTL_NODE uint32_t dummy_def = IPFW_DEFAULT_RULE; static int sysctl_ipfw_table_num(SYSCTL_HANDLER_ARGS); static int sysctl_ipfw_tables_sets(SYSCTL_HANDLER_ARGS); SYSBEGIN(f3) SYSCTL_NODE(_net_inet_ip, OID_AUTO, fw, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "Firewall"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, one_pass, CTLFLAG_VNET | CTLFLAG_RW | CTLFLAG_SECURE3, &VNET_NAME(fw_one_pass), 0, "Only do a single pass through ipfw when using dummynet(4), ipfw_nat or other divert(4)-like interfaces"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, autoinc_step, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(autoinc_step), 0, "Rule number auto-increment step"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, verbose, CTLFLAG_VNET | CTLFLAG_RW | CTLFLAG_SECURE3, &VNET_NAME(fw_verbose), 0, "Log matches to ipfw rules"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, verbose_limit, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(verbose_limit), 0, "Set upper limit of matches of ipfw rules logged"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, skipto_cache, CTLFLAG_VNET | CTLFLAG_RD, &VNET_NAME(skipto_cache), 0, "Status of linear skipto cache: 1 - enabled, 0 - disabled."); SYSCTL_UINT(_net_inet_ip_fw, OID_AUTO, default_rule, CTLFLAG_RD, &dummy_def, 0, "The default/max possible rule number."); SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, tables_max, CTLFLAG_VNET | CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE, 0, 0, sysctl_ipfw_table_num, "IU", "Maximum number of concurrently used tables"); SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, tables_sets, CTLFLAG_VNET | CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE, 0, 0, sysctl_ipfw_tables_sets, "IU", "Use per-set namespace for tables"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, default_to_accept, CTLFLAG_RDTUN, &default_to_accept, 0, "Make the default rule accept all packets."); TUNABLE_INT("net.inet.ip.fw.tables_max", (int *)&default_fw_tables); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, static_count, CTLFLAG_VNET | CTLFLAG_RD, &VNET_NAME(layer3_chain.n_rules), 0, "Number of static rules"); #ifdef INET6 SYSCTL_DECL(_net_inet6_ip6); SYSCTL_NODE(_net_inet6_ip6, OID_AUTO, fw, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "Firewall"); SYSCTL_INT(_net_inet6_ip6_fw, OID_AUTO, deny_unknown_exthdrs, CTLFLAG_VNET | CTLFLAG_RW | CTLFLAG_SECURE, &VNET_NAME(fw_deny_unknown_exthdrs), 0, "Deny packets with unknown IPv6 Extension Headers"); SYSCTL_INT(_net_inet6_ip6_fw, OID_AUTO, permit_single_frag6, CTLFLAG_VNET | CTLFLAG_RW | CTLFLAG_SECURE, &VNET_NAME(fw_permit_single_frag6), 0, "Permit single packet IPv6 fragments"); #endif /* INET6 */ SYSEND #endif /* SYSCTL_NODE */ /* * Some macros used in the various matching options. * L3HDR maps an ipv4 pointer into a layer3 header pointer of type T * Other macros just cast void * into the appropriate type */ #define L3HDR(T, ip) ((T *)((u_int32_t *)(ip) + (ip)->ip_hl)) #define TCP(p) ((struct tcphdr *)(p)) #define SCTP(p) ((struct sctphdr *)(p)) #define UDP(p) ((struct udphdr *)(p)) #define ICMP(p) ((struct icmphdr *)(p)) #define ICMP6(p) ((struct icmp6_hdr *)(p)) static __inline int icmptype_match(struct icmphdr *icmp, ipfw_insn_u32 *cmd) { int type = icmp->icmp_type; return (type <= ICMP_MAXTYPE && (cmd->d[0] & (1<icmp_type; return (type <= ICMP_MAXTYPE && (TT & (1<arg1 or cmd->d[0]. * * We scan options and store the bits we find set. We succeed if * * (want_set & ~bits) == 0 && (want_clear & ~bits) == want_clear * * The code is sometimes optimized not to store additional variables. */ static int flags_match(ipfw_insn *cmd, u_int8_t bits) { u_char want_clear; bits = ~bits; if ( ((cmd->arg1 & 0xff) & bits) != 0) return 0; /* some bits we want set were clear */ want_clear = (cmd->arg1 >> 8) & 0xff; if ( (want_clear & bits) != want_clear) return 0; /* some bits we want clear were set */ return 1; } static int ipopts_match(struct ip *ip, ipfw_insn *cmd) { int optlen, bits = 0; u_char *cp = (u_char *)(ip + 1); int x = (ip->ip_hl << 2) - sizeof (struct ip); for (; x > 0; x -= optlen, cp += optlen) { int opt = cp[IPOPT_OPTVAL]; if (opt == IPOPT_EOL) break; if (opt == IPOPT_NOP) optlen = 1; else { optlen = cp[IPOPT_OLEN]; if (optlen <= 0 || optlen > x) return 0; /* invalid or truncated */ } switch (opt) { default: break; case IPOPT_LSRR: bits |= IP_FW_IPOPT_LSRR; break; case IPOPT_SSRR: bits |= IP_FW_IPOPT_SSRR; break; case IPOPT_RR: bits |= IP_FW_IPOPT_RR; break; case IPOPT_TS: bits |= IP_FW_IPOPT_TS; break; } } return (flags_match(cmd, bits)); } /* * Parse TCP options. The logic copied from tcp_dooptions(). */ static int tcpopts_parse(const struct tcphdr *tcp, uint16_t *mss) { const u_char *cp = (const u_char *)(tcp + 1); int optlen, bits = 0; int cnt = (tcp->th_off << 2) - sizeof(struct tcphdr); for (; cnt > 0; cnt -= optlen, cp += optlen) { int opt = cp[0]; if (opt == TCPOPT_EOL) break; if (opt == TCPOPT_NOP) optlen = 1; else { if (cnt < 2) break; optlen = cp[1]; if (optlen < 2 || optlen > cnt) break; } switch (opt) { default: break; case TCPOPT_MAXSEG: if (optlen != TCPOLEN_MAXSEG) break; bits |= IP_FW_TCPOPT_MSS; if (mss != NULL) *mss = be16dec(cp + 2); break; case TCPOPT_WINDOW: if (optlen == TCPOLEN_WINDOW) bits |= IP_FW_TCPOPT_WINDOW; break; case TCPOPT_SACK_PERMITTED: if (optlen == TCPOLEN_SACK_PERMITTED) bits |= IP_FW_TCPOPT_SACK; break; case TCPOPT_SACK: if (optlen > 2 && (optlen - 2) % TCPOLEN_SACK == 0) bits |= IP_FW_TCPOPT_SACK; break; case TCPOPT_TIMESTAMP: if (optlen == TCPOLEN_TIMESTAMP) bits |= IP_FW_TCPOPT_TS; break; } } return (bits); } static int tcpopts_match(struct tcphdr *tcp, ipfw_insn *cmd) { return (flags_match(cmd, tcpopts_parse(tcp, NULL))); } static int iface_match(struct ifnet *ifp, ipfw_insn_if *cmd, struct ip_fw_chain *chain, uint32_t *tablearg) { if (ifp == NULL) /* no iface with this packet, match fails */ return (0); /* Check by name or by IP address */ if (cmd->name[0] != '\0') { /* match by name */ if (cmd->name[0] == '\1') /* use tablearg to match */ return ipfw_lookup_table(chain, cmd->p.kidx, 0, &ifp->if_index, tablearg); /* Check name */ if (cmd->p.glob) { if (fnmatch(cmd->name, ifp->if_xname, 0) == 0) return(1); } else { if (strncmp(ifp->if_xname, cmd->name, IFNAMSIZ) == 0) return(1); } } else { #if !defined(USERSPACE) && defined(__FreeBSD__) /* and OSX too ? */ struct ifaddr *ia; NET_EPOCH_ASSERT(); CK_STAILQ_FOREACH(ia, &ifp->if_addrhead, ifa_link) { if (ia->ifa_addr->sa_family != AF_INET) continue; if (cmd->p.ip.s_addr == ((struct sockaddr_in *) (ia->ifa_addr))->sin_addr.s_addr) return (1); /* match */ } #endif /* __FreeBSD__ */ } return(0); /* no match, fail ... */ } /* * The verify_path function checks if a route to the src exists and * if it is reachable via ifp (when provided). * * The 'verrevpath' option checks that the interface that an IP packet * arrives on is the same interface that traffic destined for the * packet's source address would be routed out of. * The 'versrcreach' option just checks that the source address is * reachable via any route (except default) in the routing table. * These two are a measure to block forged packets. This is also * commonly known as "anti-spoofing" or Unicast Reverse Path * Forwarding (Unicast RFP) in Cisco-ese. The name of the knobs * is purposely reminiscent of the Cisco IOS command, * * ip verify unicast reverse-path * ip verify unicast source reachable-via any * * which implements the same functionality. But note that the syntax * is misleading, and the check may be performed on all IP packets * whether unicast, multicast, or broadcast. */ static int verify_path(struct in_addr src, struct ifnet *ifp, u_int fib) { #if defined(USERSPACE) || !defined(__FreeBSD__) return 0; #else struct nhop_object *nh; nh = fib4_lookup(fib, src, 0, NHR_NONE, 0); if (nh == NULL) return (0); /* * If ifp is provided, check for equality with rtentry. * We should use rt->rt_ifa->ifa_ifp, instead of rt->rt_ifp, * in order to pass packets injected back by if_simloop(): * routing entry (via lo0) for our own address * may exist, so we need to handle routing assymetry. */ if (ifp != NULL && ifp != nh->nh_aifp) return (0); /* if no ifp provided, check if rtentry is not default route */ if (ifp == NULL && (nh->nh_flags & NHF_DEFAULT) != 0) return (0); /* or if this is a blackhole/reject route */ if (ifp == NULL && (nh->nh_flags & (NHF_REJECT|NHF_BLACKHOLE)) != 0) return (0); /* found valid route */ return 1; #endif /* __FreeBSD__ */ } /* * Generate an SCTP packet containing an ABORT chunk. The verification tag * is given by vtag. The T-bit is set in the ABORT chunk if and only if * reflected is not 0. */ static struct mbuf * ipfw_send_abort(struct mbuf *replyto, struct ipfw_flow_id *id, u_int32_t vtag, int reflected) { struct mbuf *m; struct ip *ip; #ifdef INET6 struct ip6_hdr *ip6; #endif struct sctphdr *sctp; struct sctp_chunkhdr *chunk; u_int16_t hlen, plen, tlen; MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) return (NULL); M_SETFIB(m, id->fib); #ifdef MAC if (replyto != NULL) mac_netinet_firewall_reply(replyto, m); else mac_netinet_firewall_send(m); #else (void)replyto; /* don't warn about unused arg */ #endif switch (id->addr_type) { case 4: hlen = sizeof(struct ip); break; #ifdef INET6 case 6: hlen = sizeof(struct ip6_hdr); break; #endif default: /* XXX: log me?!? */ FREE_PKT(m); return (NULL); } plen = sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr); tlen = hlen + plen; m->m_data += max_linkhdr; m->m_flags |= M_SKIP_FIREWALL; m->m_pkthdr.len = m->m_len = tlen; m->m_pkthdr.rcvif = NULL; bzero(m->m_data, tlen); switch (id->addr_type) { case 4: ip = mtod(m, struct ip *); ip->ip_v = 4; ip->ip_hl = sizeof(struct ip) >> 2; ip->ip_tos = IPTOS_LOWDELAY; ip->ip_len = htons(tlen); ip->ip_id = htons(0); ip->ip_off = htons(0); ip->ip_ttl = V_ip_defttl; ip->ip_p = IPPROTO_SCTP; ip->ip_sum = 0; ip->ip_src.s_addr = htonl(id->dst_ip); ip->ip_dst.s_addr = htonl(id->src_ip); sctp = (struct sctphdr *)(ip + 1); break; #ifdef INET6 case 6: ip6 = mtod(m, struct ip6_hdr *); ip6->ip6_vfc = IPV6_VERSION; ip6->ip6_plen = htons(plen); ip6->ip6_nxt = IPPROTO_SCTP; ip6->ip6_hlim = IPV6_DEFHLIM; ip6->ip6_src = id->dst_ip6; ip6->ip6_dst = id->src_ip6; sctp = (struct sctphdr *)(ip6 + 1); break; #endif } sctp->src_port = htons(id->dst_port); sctp->dest_port = htons(id->src_port); sctp->v_tag = htonl(vtag); sctp->checksum = htonl(0); chunk = (struct sctp_chunkhdr *)(sctp + 1); chunk->chunk_type = SCTP_ABORT_ASSOCIATION; chunk->chunk_flags = 0; if (reflected != 0) { chunk->chunk_flags |= SCTP_HAD_NO_TCB; } chunk->chunk_length = htons(sizeof(struct sctp_chunkhdr)); sctp->checksum = sctp_calculate_cksum(m, hlen); return (m); } /* * Generate a TCP packet, containing either a RST or a keepalive. * When flags & TH_RST, we are sending a RST packet, because of a * "reset" action matched the packet. * Otherwise we are sending a keepalive, and flags & TH_ * The 'replyto' mbuf is the mbuf being replied to, if any, and is required * so that MAC can label the reply appropriately. */ struct mbuf * ipfw_send_pkt(struct mbuf *replyto, struct ipfw_flow_id *id, u_int32_t seq, u_int32_t ack, int flags) { struct mbuf *m = NULL; /* stupid compiler */ struct ip *h = NULL; /* stupid compiler */ #ifdef INET6 struct ip6_hdr *h6 = NULL; #endif struct tcphdr *th = NULL; int len, dir; MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) return (NULL); M_SETFIB(m, id->fib); #ifdef MAC if (replyto != NULL) mac_netinet_firewall_reply(replyto, m); else mac_netinet_firewall_send(m); #else (void)replyto; /* don't warn about unused arg */ #endif switch (id->addr_type) { case 4: len = sizeof(struct ip) + sizeof(struct tcphdr); break; #ifdef INET6 case 6: len = sizeof(struct ip6_hdr) + sizeof(struct tcphdr); break; #endif default: /* XXX: log me?!? */ FREE_PKT(m); return (NULL); } dir = ((flags & (TH_SYN | TH_RST)) == TH_SYN); m->m_data += max_linkhdr; m->m_flags |= M_SKIP_FIREWALL; m->m_pkthdr.len = m->m_len = len; m->m_pkthdr.rcvif = NULL; bzero(m->m_data, len); switch (id->addr_type) { case 4: h = mtod(m, struct ip *); /* prepare for checksum */ h->ip_p = IPPROTO_TCP; h->ip_len = htons(sizeof(struct tcphdr)); if (dir) { h->ip_src.s_addr = htonl(id->src_ip); h->ip_dst.s_addr = htonl(id->dst_ip); } else { h->ip_src.s_addr = htonl(id->dst_ip); h->ip_dst.s_addr = htonl(id->src_ip); } th = (struct tcphdr *)(h + 1); break; #ifdef INET6 case 6: h6 = mtod(m, struct ip6_hdr *); /* prepare for checksum */ h6->ip6_nxt = IPPROTO_TCP; h6->ip6_plen = htons(sizeof(struct tcphdr)); if (dir) { h6->ip6_src = id->src_ip6; h6->ip6_dst = id->dst_ip6; } else { h6->ip6_src = id->dst_ip6; h6->ip6_dst = id->src_ip6; } th = (struct tcphdr *)(h6 + 1); break; #endif } if (dir) { th->th_sport = htons(id->src_port); th->th_dport = htons(id->dst_port); } else { th->th_sport = htons(id->dst_port); th->th_dport = htons(id->src_port); } th->th_off = sizeof(struct tcphdr) >> 2; if (flags & TH_RST) { if (flags & TH_ACK) { th->th_seq = htonl(ack); tcp_set_flags(th, TH_RST); } else { if (flags & TH_SYN) seq++; th->th_ack = htonl(seq); tcp_set_flags(th, TH_RST | TH_ACK); } } else { /* * Keepalive - use caller provided sequence numbers */ th->th_seq = htonl(seq); th->th_ack = htonl(ack); tcp_set_flags(th, TH_ACK); } switch (id->addr_type) { case 4: th->th_sum = in_cksum(m, len); /* finish the ip header */ h->ip_v = 4; h->ip_hl = sizeof(*h) >> 2; h->ip_tos = IPTOS_LOWDELAY; h->ip_off = htons(0); h->ip_len = htons(len); h->ip_ttl = V_ip_defttl; h->ip_sum = 0; break; #ifdef INET6 case 6: th->th_sum = in6_cksum(m, IPPROTO_TCP, sizeof(*h6), sizeof(struct tcphdr)); /* finish the ip6 header */ h6->ip6_vfc |= IPV6_VERSION; h6->ip6_hlim = IPV6_DEFHLIM; break; #endif } return (m); } #ifdef INET6 /* * ipv6 specific rules here... */ static __inline int icmp6type_match(int type, ipfw_insn_u32 *cmd) { return (type <= ICMP6_MAXTYPE && (cmd->d[type/32] & (1<<(type%32)) ) ); } static int flow6id_match(int curr_flow, ipfw_insn_u32 *cmd) { int i; for (i=0; i <= cmd->o.arg1; ++i) if (curr_flow == cmd->d[i]) return 1; return 0; } /* support for IP6_*_ME opcodes */ static const struct in6_addr lla_mask = {{{ 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}}; static int ipfw_localip6(struct in6_addr *in6) { struct rm_priotracker in6_ifa_tracker; struct in6_ifaddr *ia; if (IN6_IS_ADDR_MULTICAST(in6)) return (0); if (!IN6_IS_ADDR_LINKLOCAL(in6)) return (in6_localip(in6)); IN6_IFADDR_RLOCK(&in6_ifa_tracker); CK_STAILQ_FOREACH(ia, &V_in6_ifaddrhead, ia_link) { if (!IN6_IS_ADDR_LINKLOCAL(&ia->ia_addr.sin6_addr)) continue; if (IN6_ARE_MASKED_ADDR_EQUAL(&ia->ia_addr.sin6_addr, in6, &lla_mask)) { IN6_IFADDR_RUNLOCK(&in6_ifa_tracker); return (1); } } IN6_IFADDR_RUNLOCK(&in6_ifa_tracker); return (0); } static int verify_path6(struct in6_addr *src, struct ifnet *ifp, u_int fib) { struct nhop_object *nh; if (IN6_IS_SCOPE_LINKLOCAL(src)) return (1); nh = fib6_lookup(fib, src, 0, NHR_NONE, 0); if (nh == NULL) return (0); /* If ifp is provided, check for equality with route table. */ if (ifp != NULL && ifp != nh->nh_aifp) return (0); /* if no ifp provided, check if rtentry is not default route */ if (ifp == NULL && (nh->nh_flags & NHF_DEFAULT) != 0) return (0); /* or if this is a blackhole/reject route */ if (ifp == NULL && (nh->nh_flags & (NHF_REJECT|NHF_BLACKHOLE)) != 0) return (0); /* found valid route */ return 1; } static int is_icmp6_query(int icmp6_type) { if ((icmp6_type <= ICMP6_MAXTYPE) && (icmp6_type == ICMP6_ECHO_REQUEST || icmp6_type == ICMP6_MEMBERSHIP_QUERY || icmp6_type == ICMP6_WRUREQUEST || icmp6_type == ICMP6_FQDN_QUERY || icmp6_type == ICMP6_NI_QUERY)) return (1); return (0); } static int map_icmp_unreach(int code) { /* RFC 7915 p4.2 */ switch (code) { case ICMP_UNREACH_NET: case ICMP_UNREACH_HOST: case ICMP_UNREACH_SRCFAIL: case ICMP_UNREACH_NET_UNKNOWN: case ICMP_UNREACH_HOST_UNKNOWN: case ICMP_UNREACH_TOSNET: case ICMP_UNREACH_TOSHOST: return (ICMP6_DST_UNREACH_NOROUTE); case ICMP_UNREACH_PORT: return (ICMP6_DST_UNREACH_NOPORT); default: /* * Map the rest of codes into admit prohibited. * XXX: unreach proto should be mapped into ICMPv6 * parameter problem, but we use only unreach type. */ return (ICMP6_DST_UNREACH_ADMIN); } } static void send_reject6(struct ip_fw_args *args, int code, u_int hlen, const struct ip6_hdr *ip6) { struct mbuf *m; m = args->m; if (code == ICMP6_UNREACH_RST && args->f_id.proto == IPPROTO_TCP) { const struct tcphdr * tcp; tcp = (const struct tcphdr *)((const char *)ip6 + hlen); if ((tcp_get_flags(tcp) & TH_RST) == 0) { struct mbuf *m0; m0 = ipfw_send_pkt(args->m, &(args->f_id), ntohl(tcp->th_seq), ntohl(tcp->th_ack), tcp_get_flags(tcp) | TH_RST); if (m0 != NULL) ip6_output(m0, NULL, NULL, 0, NULL, NULL, NULL); } FREE_PKT(m); } else if (code == ICMP6_UNREACH_ABORT && args->f_id.proto == IPPROTO_SCTP) { struct mbuf *m0; const struct sctphdr *sctp; u_int32_t v_tag; int reflected; sctp = (const struct sctphdr *)((const char *)ip6 + hlen); reflected = 1; v_tag = ntohl(sctp->v_tag); /* Investigate the first chunk header if available */ if (m->m_len >= hlen + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr)) { const struct sctp_chunkhdr *chunk; chunk = (const struct sctp_chunkhdr *)(sctp + 1); switch (chunk->chunk_type) { case SCTP_INITIATION: /* * Packets containing an INIT chunk MUST have * a zero v-tag. */ if (v_tag != 0) { v_tag = 0; break; } /* INIT chunk MUST NOT be bundled */ if (m->m_pkthdr.len > hlen + sizeof(struct sctphdr) + ntohs(chunk->chunk_length) + 3) { break; } /* Use the initiate tag if available */ if ((m->m_len >= hlen + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr) + offsetof(struct sctp_init, a_rwnd))) { const struct sctp_init *init; init = (const struct sctp_init *)(chunk + 1); v_tag = ntohl(init->initiate_tag); reflected = 0; } break; case SCTP_ABORT_ASSOCIATION: /* * If the packet contains an ABORT chunk, don't * reply. * XXX: We should search through all chunks, * but do not do that to avoid attacks. */ v_tag = 0; break; } } if (v_tag == 0) { m0 = NULL; } else { m0 = ipfw_send_abort(args->m, &(args->f_id), v_tag, reflected); } if (m0 != NULL) ip6_output(m0, NULL, NULL, 0, NULL, NULL, NULL); FREE_PKT(m); } else if (code != ICMP6_UNREACH_RST && code != ICMP6_UNREACH_ABORT) { /* Send an ICMPv6 unreach. */ #if 0 /* * Unlike above, the mbufs need to line up with the ip6 hdr, * as the contents are read. We need to m_adj() the * needed amount. * The mbuf will however be thrown away so we can adjust it. * Remember we did an m_pullup on it already so we * can make some assumptions about contiguousness. */ if (args->L3offset) m_adj(m, args->L3offset); #endif icmp6_error(m, ICMP6_DST_UNREACH, code, 0); } else FREE_PKT(m); args->m = NULL; } #endif /* INET6 */ /* * sends a reject message, consuming the mbuf passed as an argument. */ static void send_reject(struct ip_fw_args *args, int code, uint16_t mtu, int iplen, const struct ip *ip) { #if 0 /* XXX When ip is not guaranteed to be at mtod() we will * need to account for this */ * The mbuf will however be thrown away so we can adjust it. * Remember we did an m_pullup on it already so we * can make some assumptions about contiguousness. */ if (args->L3offset) m_adj(m, args->L3offset); #endif if (code != ICMP_REJECT_RST && code != ICMP_REJECT_ABORT) { /* Send an ICMP unreach */ icmp_error(args->m, ICMP_UNREACH, code, 0L, mtu); } else if (code == ICMP_REJECT_RST && args->f_id.proto == IPPROTO_TCP) { struct tcphdr *const tcp = L3HDR(struct tcphdr, mtod(args->m, struct ip *)); if ( (tcp_get_flags(tcp) & TH_RST) == 0) { struct mbuf *m; m = ipfw_send_pkt(args->m, &(args->f_id), ntohl(tcp->th_seq), ntohl(tcp->th_ack), tcp_get_flags(tcp) | TH_RST); if (m != NULL) ip_output(m, NULL, NULL, 0, NULL, NULL); } FREE_PKT(args->m); } else if (code == ICMP_REJECT_ABORT && args->f_id.proto == IPPROTO_SCTP) { struct mbuf *m; struct sctphdr *sctp; struct sctp_chunkhdr *chunk; struct sctp_init *init; u_int32_t v_tag; int reflected; sctp = L3HDR(struct sctphdr, mtod(args->m, struct ip *)); reflected = 1; v_tag = ntohl(sctp->v_tag); if (iplen >= (ip->ip_hl << 2) + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr)) { /* Look at the first chunk header if available */ chunk = (struct sctp_chunkhdr *)(sctp + 1); switch (chunk->chunk_type) { case SCTP_INITIATION: /* * Packets containing an INIT chunk MUST have * a zero v-tag. */ if (v_tag != 0) { v_tag = 0; break; } /* INIT chunk MUST NOT be bundled */ if (iplen > (ip->ip_hl << 2) + sizeof(struct sctphdr) + ntohs(chunk->chunk_length) + 3) { break; } /* Use the initiate tag if available */ if ((iplen >= (ip->ip_hl << 2) + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr) + offsetof(struct sctp_init, a_rwnd))) { init = (struct sctp_init *)(chunk + 1); v_tag = ntohl(init->initiate_tag); reflected = 0; } break; case SCTP_ABORT_ASSOCIATION: /* * If the packet contains an ABORT chunk, don't * reply. * XXX: We should search through all chunks, * but do not do that to avoid attacks. */ v_tag = 0; break; } } if (v_tag == 0) { m = NULL; } else { m = ipfw_send_abort(args->m, &(args->f_id), v_tag, reflected); } if (m != NULL) ip_output(m, NULL, NULL, 0, NULL, NULL); FREE_PKT(args->m); } else FREE_PKT(args->m); args->m = NULL; } /* * Support for uid/gid/jail lookup. These tests are expensive * (because we may need to look into the list of active sockets) * so we cache the results. ugid_lookupp is 0 if we have not * yet done a lookup, 1 if we succeeded, and -1 if we tried * and failed. The function always returns the match value. * We could actually spare the variable and use *uc, setting * it to '(void *)check_uidgid if we have no info, NULL if * we tried and failed, or any other value if successful. */ static int check_uidgid(ipfw_insn_u32 *insn, struct ip_fw_args *args, int *ugid_lookupp, struct ucred **uc) { #if defined(USERSPACE) return 0; // not supported in userspace #else #ifndef __FreeBSD__ /* XXX */ return cred_check(insn, proto, oif, dst_ip, dst_port, src_ip, src_port, (struct bsd_ucred *)uc, ugid_lookupp, ((struct mbuf *)inp)->m_skb); #else /* FreeBSD */ struct in_addr src_ip, dst_ip; struct inpcbinfo *pi; struct ipfw_flow_id *id; struct inpcb *pcb, *inp; int lookupflags; int match; id = &args->f_id; inp = args->inp; /* * Check to see if the UDP or TCP stack supplied us with * the PCB. If so, rather then holding a lock and looking * up the PCB, we can use the one that was supplied. */ if (inp && *ugid_lookupp == 0) { INP_LOCK_ASSERT(inp); if (inp->inp_socket != NULL) { *uc = crhold(inp->inp_cred); *ugid_lookupp = 1; } else *ugid_lookupp = -1; } /* * If we have already been here and the packet has no * PCB entry associated with it, then we can safely * assume that this is a no match. */ if (*ugid_lookupp == -1) return (0); if (id->proto == IPPROTO_TCP) { lookupflags = 0; pi = &V_tcbinfo; } else if (id->proto == IPPROTO_UDP) { lookupflags = INPLOOKUP_WILDCARD; pi = &V_udbinfo; } else if (id->proto == IPPROTO_UDPLITE) { lookupflags = INPLOOKUP_WILDCARD; pi = &V_ulitecbinfo; } else return 0; lookupflags |= INPLOOKUP_RLOCKPCB; match = 0; if (*ugid_lookupp == 0) { if (id->addr_type == 6) { #ifdef INET6 if (args->flags & IPFW_ARGS_IN) pcb = in6_pcblookup_mbuf(pi, &id->src_ip6, htons(id->src_port), &id->dst_ip6, htons(id->dst_port), lookupflags, NULL, args->m); else pcb = in6_pcblookup_mbuf(pi, &id->dst_ip6, htons(id->dst_port), &id->src_ip6, htons(id->src_port), lookupflags, args->ifp, args->m); #else *ugid_lookupp = -1; return (0); #endif } else { src_ip.s_addr = htonl(id->src_ip); dst_ip.s_addr = htonl(id->dst_ip); if (args->flags & IPFW_ARGS_IN) pcb = in_pcblookup_mbuf(pi, src_ip, htons(id->src_port), dst_ip, htons(id->dst_port), lookupflags, NULL, args->m); else pcb = in_pcblookup_mbuf(pi, dst_ip, htons(id->dst_port), src_ip, htons(id->src_port), lookupflags, args->ifp, args->m); } if (pcb != NULL) { INP_RLOCK_ASSERT(pcb); *uc = crhold(pcb->inp_cred); *ugid_lookupp = 1; INP_RUNLOCK(pcb); } if (*ugid_lookupp == 0) { /* * We tried and failed, set the variable to -1 * so we will not try again on this packet. */ *ugid_lookupp = -1; return (0); } } if (insn->o.opcode == O_UID) match = ((*uc)->cr_uid == (uid_t)insn->d[0]); else if (insn->o.opcode == O_GID) match = groupmember((gid_t)insn->d[0], *uc); else if (insn->o.opcode == O_JAIL) match = ((*uc)->cr_prison->pr_id == (int)insn->d[0]); return (match); #endif /* __FreeBSD__ */ #endif /* not supported in userspace */ } /* * Helper function to set args with info on the rule after the matching * one. slot is precise, whereas we guess rule_id as they are * assigned sequentially. */ static inline void set_match(struct ip_fw_args *args, int slot, struct ip_fw_chain *chain) { args->rule.chain_id = chain->id; args->rule.slot = slot + 1; /* we use 0 as a marker */ args->rule.rule_id = 1 + chain->map[slot]->id; args->rule.rulenum = chain->map[slot]->rulenum; args->flags |= IPFW_ARGS_REF; } static uint32_t jump_lookup_pos(struct ip_fw_chain *chain, struct ip_fw *f, uint32_t num, int tablearg, bool jump_backwards) { int f_pos, i; /* * Make sure we do not jump backward. */ i = IP_FW_ARG_TABLEARG(chain, num, skipto); if (!jump_backwards && i <= f->rulenum) i = f->rulenum + 1; if (V_skipto_cache == 0) f_pos = ipfw_find_rule(chain, i, 0); else { /* * Make sure we do not do out of bounds access. */ if (i >= IPFW_DEFAULT_RULE) i = IPFW_DEFAULT_RULE - 1; f_pos = chain->idxmap[i]; } return (f_pos); } static uint32_t jump(struct ip_fw_chain *chain, struct ip_fw *f, uint32_t num, int tablearg, bool jump_backwards) { int f_pos; /* Can't use cache with IP_FW_TARG */ if (num == IP_FW_TARG) return jump_lookup_pos(chain, f, num, tablearg, jump_backwards); /* * If possible use cached f_pos (in f->cache.pos), * whose version is written in f->cache.id (horrible hacks * to avoid changing the ABI). * * Multiple threads can execute the same rule simultaneously, * we need to ensure that cache.pos is updated before cache.id. */ #ifdef __LP64__ struct ip_fw_jump_cache cache; cache.raw_value = f->cache.raw_value; if (cache.id == chain->id) return (cache.pos); f_pos = jump_lookup_pos(chain, f, num, tablearg, jump_backwards); cache.pos = f_pos; cache.id = chain->id; f->cache.raw_value = cache.raw_value; #else if (f->cache.id == chain->id) { /* Load pos after id */ atomic_thread_fence_acq(); return (f->cache.pos); } f_pos = jump_lookup_pos(chain, f, num, tablearg, jump_backwards); f->cache.pos = f_pos; /* Store id after pos */ atomic_thread_fence_rel(); f->cache.id = chain->id; #endif /* !__LP64__ */ return (f_pos); } #define TARG(k, f) IP_FW_ARG_TABLEARG(chain, k, f) static inline int tvalue_match(struct ip_fw_chain *ch, const ipfw_insn_table *cmd, uint32_t tablearg) { uint32_t tvalue; switch (IPFW_TVALUE_TYPE(&cmd->o)) { case TVALUE_PIPE: tvalue = TARG_VAL(ch, tablearg, pipe); break; case TVALUE_DIVERT: tvalue = TARG_VAL(ch, tablearg, divert); break; case TVALUE_SKIPTO: tvalue = TARG_VAL(ch, tablearg, skipto); break; case TVALUE_NETGRAPH: tvalue = TARG_VAL(ch, tablearg, netgraph); break; case TVALUE_FIB: tvalue = TARG_VAL(ch, tablearg, fib); break; case TVALUE_NAT: tvalue = TARG_VAL(ch, tablearg, nat); break; case TVALUE_NH4: tvalue = TARG_VAL(ch, tablearg, nh4); break; case TVALUE_DSCP: tvalue = TARG_VAL(ch, tablearg, dscp); break; case TVALUE_LIMIT: tvalue = TARG_VAL(ch, tablearg, limit); break; case TVALUE_MARK: tvalue = TARG_VAL(ch, tablearg, mark); break; case TVALUE_TAG: default: tvalue = TARG_VAL(ch, tablearg, tag); break; } return (tvalue == cmd->value); } /* * The main check routine for the firewall. * * All arguments are in args so we can modify them and return them * back to the caller. * * Parameters: * * args->m (in/out) The packet; we set to NULL when/if we nuke it. * Starts with the IP header. * args->L3offset Number of bytes bypassed if we came from L2. * e.g. often sizeof(eh) ** NOTYET ** * args->ifp Incoming or outgoing interface. * args->divert_rule (in/out) * Skip up to the first rule past this rule number; * upon return, non-zero port number for divert or tee. * * args->rule Pointer to the last matching rule (in/out) * args->next_hop Socket we are forwarding to (out). * args->next_hop6 IPv6 next hop we are forwarding to (out). * args->f_id Addresses grabbed from the packet (out) * args->rule.info a cookie depending on rule action * * Return value: * * IP_FW_PASS the packet must be accepted * IP_FW_DENY the packet must be dropped * IP_FW_DIVERT divert packet, port in m_tag * IP_FW_TEE tee packet, port in m_tag * IP_FW_DUMMYNET to dummynet, pipe in args->cookie * IP_FW_NETGRAPH into netgraph, cookie args->cookie * args->rule contains the matching rule, * args->rule.info has additional information. * */ int ipfw_chk(struct ip_fw_args *args) { /* * Local variables holding state while processing a packet: * * IMPORTANT NOTE: to speed up the processing of rules, there * are some assumption on the values of the variables, which * are documented here. Should you change them, please check * the implementation of the various instructions to make sure * that they still work. * * m | args->m Pointer to the mbuf, as received from the caller. * It may change if ipfw_chk() does an m_pullup, or if it * consumes the packet because it calls send_reject(). * XXX This has to change, so that ipfw_chk() never modifies * or consumes the buffer. * OR * args->mem Pointer to contigous memory chunk. * ip Is the beginning of the ip(4 or 6) header. * eh Ethernet header in case if input is Layer2. */ struct mbuf *m; struct ip *ip; struct ether_header *eh; /* * For rules which contain uid/gid or jail constraints, cache * a copy of the users credentials after the pcb lookup has been * executed. This will speed up the processing of rules with * these types of constraints, as well as decrease contention * on pcb related locks. */ #ifndef __FreeBSD__ struct bsd_ucred ucred_cache; #else struct ucred *ucred_cache = NULL; #endif uint32_t f_pos = 0; /* index of current rule in the array */ int ucred_lookup = 0; int retval = 0; struct ifnet *oif, *iif; /* * hlen The length of the IP header. */ u_int hlen = 0; /* hlen >0 means we have an IP pkt */ /* * offset The offset of a fragment. offset != 0 means that * we have a fragment at this offset of an IPv4 packet. * offset == 0 means that (if this is an IPv4 packet) * this is the first or only fragment. * For IPv6 offset|ip6f_mf == 0 means there is no Fragment Header * or there is a single packet fragment (fragment header added * without needed). We will treat a single packet fragment as if * there was no fragment header (or log/block depending on the * V_fw_permit_single_frag6 sysctl setting). */ u_short offset = 0; u_short ip6f_mf = 0; /* * Local copies of addresses. They are only valid if we have * an IP packet. * * proto The protocol. Set to 0 for non-ip packets, * or to the protocol read from the packet otherwise. * proto != 0 means that we have an IPv4 packet. * * src_port, dst_port port numbers, in HOST format. Only * valid for TCP and UDP packets. * * src_ip, dst_ip ip addresses, in NETWORK format. * Only valid for IPv4 packets. */ uint8_t proto; uint16_t src_port, dst_port; /* NOTE: host format */ struct in_addr src_ip, dst_ip; /* NOTE: network format */ int iplen = 0; int pktlen; struct ipfw_dyn_info dyn_info; struct ip_fw *q = NULL; struct ip_fw_chain *chain = &V_layer3_chain; /* * We store in ulp a pointer to the upper layer protocol header. * In the ipv4 case this is easy to determine from the header, * but for ipv6 we might have some additional headers in the middle. * ulp is NULL if not found. */ void *ulp = NULL; /* upper layer protocol pointer. */ /* XXX ipv6 variables */ int is_ipv6 = 0; #ifdef INET6 uint8_t icmp6_type = 0; #endif uint16_t ext_hd = 0; /* bits vector for extension header filtering */ /* end of ipv6 variables */ int is_ipv4 = 0; int done = 0; /* flag to exit the outer loop */ IPFW_RLOCK_TRACKER; bool mem; bool need_send_reject = false; int reject_code; uint16_t reject_mtu; if ((mem = (args->flags & IPFW_ARGS_LENMASK))) { if (args->flags & IPFW_ARGS_ETHER) { eh = (struct ether_header *)args->mem; if (eh->ether_type == htons(ETHERTYPE_VLAN)) ip = (struct ip *) ((struct ether_vlan_header *)eh + 1); else ip = (struct ip *)(eh + 1); } else { eh = NULL; ip = (struct ip *)args->mem; } pktlen = IPFW_ARGS_LENGTH(args->flags); args->f_id.fib = args->ifp->if_fib; /* best guess */ } else { m = args->m; if (m->m_flags & M_SKIP_FIREWALL || (! V_ipfw_vnet_ready)) return (IP_FW_PASS); /* accept */ if (args->flags & IPFW_ARGS_ETHER) { /* We need some amount of data to be contiguous. */ if (m->m_len < min(m->m_pkthdr.len, max_protohdr) && (args->m = m = m_pullup(m, min(m->m_pkthdr.len, max_protohdr))) == NULL) goto pullup_failed; eh = mtod(m, struct ether_header *); ip = (struct ip *)(eh + 1); } else { eh = NULL; ip = mtod(m, struct ip *); } pktlen = m->m_pkthdr.len; args->f_id.fib = M_GETFIB(m); /* mbuf not altered */ } dst_ip.s_addr = 0; /* make sure it is initialized */ src_ip.s_addr = 0; /* make sure it is initialized */ src_port = dst_port = 0; DYN_INFO_INIT(&dyn_info); /* * PULLUP_TO(len, p, T) makes sure that len + sizeof(T) is contiguous, * then it sets p to point at the offset "len" in the mbuf. WARNING: the * pointer might become stale after other pullups (but we never use it * this way). */ #define PULLUP_TO(_len, p, T) PULLUP_LEN(_len, p, sizeof(T)) #define EHLEN (eh != NULL ? ((char *)ip - (char *)eh) : 0) #define _PULLUP_LOCKED(_len, p, T, unlock) \ do { \ int x = (_len) + T + EHLEN; \ if (mem) { \ if (__predict_false(pktlen < x)) { \ unlock; \ goto pullup_failed; \ } \ p = (char *)args->mem + (_len) + EHLEN; \ } else { \ if (__predict_false((m)->m_len < x)) { \ args->m = m = m_pullup(m, x); \ if (m == NULL) { \ unlock; \ goto pullup_failed; \ } \ } \ p = mtod(m, char *) + (_len) + EHLEN; \ } \ } while (0) #define PULLUP_LEN(_len, p, T) _PULLUP_LOCKED(_len, p, T, ) #define PULLUP_LEN_LOCKED(_len, p, T) \ _PULLUP_LOCKED(_len, p, T, IPFW_PF_RUNLOCK(chain)); \ UPDATE_POINTERS() /* * In case pointers got stale after pullups, update them. */ #define UPDATE_POINTERS() \ do { \ if (!mem) { \ if (eh != NULL) { \ eh = mtod(m, struct ether_header *); \ ip = (struct ip *)(eh + 1); \ } else \ ip = mtod(m, struct ip *); \ args->m = m; \ } \ } while (0) /* Identify IP packets and fill up variables. */ if (pktlen >= sizeof(struct ip6_hdr) && (eh == NULL || eh->ether_type == htons(ETHERTYPE_IPV6)) && ip->ip_v == 6) { struct ip6_hdr *ip6 = (struct ip6_hdr *)ip; is_ipv6 = 1; args->flags |= IPFW_ARGS_IP6; hlen = sizeof(struct ip6_hdr); proto = ip6->ip6_nxt; /* Search extension headers to find upper layer protocols */ while (ulp == NULL && offset == 0) { switch (proto) { case IPPROTO_ICMPV6: PULLUP_TO(hlen, ulp, struct icmp6_hdr); #ifdef INET6 icmp6_type = ICMP6(ulp)->icmp6_type; #endif break; case IPPROTO_TCP: PULLUP_TO(hlen, ulp, struct tcphdr); dst_port = TCP(ulp)->th_dport; src_port = TCP(ulp)->th_sport; /* save flags for dynamic rules */ args->f_id._flags = tcp_get_flags(TCP(ulp)); break; case IPPROTO_SCTP: if (pktlen >= hlen + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr) + offsetof(struct sctp_init, a_rwnd)) PULLUP_LEN(hlen, ulp, sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr) + offsetof(struct sctp_init, a_rwnd)); else if (pktlen >= hlen + sizeof(struct sctphdr)) PULLUP_LEN(hlen, ulp, pktlen - hlen); else PULLUP_LEN(hlen, ulp, sizeof(struct sctphdr)); src_port = SCTP(ulp)->src_port; dst_port = SCTP(ulp)->dest_port; break; case IPPROTO_UDP: case IPPROTO_UDPLITE: PULLUP_TO(hlen, ulp, struct udphdr); dst_port = UDP(ulp)->uh_dport; src_port = UDP(ulp)->uh_sport; break; case IPPROTO_HOPOPTS: /* RFC 2460 */ PULLUP_TO(hlen, ulp, struct ip6_hbh); ext_hd |= EXT_HOPOPTS; hlen += (((struct ip6_hbh *)ulp)->ip6h_len + 1) << 3; proto = ((struct ip6_hbh *)ulp)->ip6h_nxt; ulp = NULL; break; case IPPROTO_ROUTING: /* RFC 2460 */ PULLUP_TO(hlen, ulp, struct ip6_rthdr); switch (((struct ip6_rthdr *)ulp)->ip6r_type) { case 0: ext_hd |= EXT_RTHDR0; break; case 2: ext_hd |= EXT_RTHDR2; break; default: if (V_fw_verbose) printf("IPFW2: IPV6 - Unknown " "Routing Header type(%d)\n", ((struct ip6_rthdr *) ulp)->ip6r_type); if (V_fw_deny_unknown_exthdrs) return (IP_FW_DENY); break; } ext_hd |= EXT_ROUTING; hlen += (((struct ip6_rthdr *)ulp)->ip6r_len + 1) << 3; proto = ((struct ip6_rthdr *)ulp)->ip6r_nxt; ulp = NULL; break; case IPPROTO_FRAGMENT: /* RFC 2460 */ PULLUP_TO(hlen, ulp, struct ip6_frag); ext_hd |= EXT_FRAGMENT; hlen += sizeof (struct ip6_frag); proto = ((struct ip6_frag *)ulp)->ip6f_nxt; offset = ((struct ip6_frag *)ulp)->ip6f_offlg & IP6F_OFF_MASK; ip6f_mf = ((struct ip6_frag *)ulp)->ip6f_offlg & IP6F_MORE_FRAG; if (V_fw_permit_single_frag6 == 0 && offset == 0 && ip6f_mf == 0) { if (V_fw_verbose) printf("IPFW2: IPV6 - Invalid " "Fragment Header\n"); if (V_fw_deny_unknown_exthdrs) return (IP_FW_DENY); break; } args->f_id.extra = ntohl(((struct ip6_frag *)ulp)->ip6f_ident); ulp = NULL; break; case IPPROTO_DSTOPTS: /* RFC 2460 */ PULLUP_TO(hlen, ulp, struct ip6_hbh); ext_hd |= EXT_DSTOPTS; hlen += (((struct ip6_hbh *)ulp)->ip6h_len + 1) << 3; proto = ((struct ip6_hbh *)ulp)->ip6h_nxt; ulp = NULL; break; case IPPROTO_AH: /* RFC 2402 */ PULLUP_TO(hlen, ulp, struct ip6_ext); ext_hd |= EXT_AH; hlen += (((struct ip6_ext *)ulp)->ip6e_len + 2) << 2; proto = ((struct ip6_ext *)ulp)->ip6e_nxt; ulp = NULL; break; case IPPROTO_ESP: /* RFC 2406 */ PULLUP_TO(hlen, ulp, uint32_t); /* SPI, Seq# */ /* Anything past Seq# is variable length and * data past this ext. header is encrypted. */ ext_hd |= EXT_ESP; break; case IPPROTO_NONE: /* RFC 2460 */ /* * Packet ends here, and IPv6 header has * already been pulled up. If ip6e_len!=0 * then octets must be ignored. */ ulp = ip; /* non-NULL to get out of loop. */ break; case IPPROTO_OSPFIGP: /* XXX OSPF header check? */ PULLUP_TO(hlen, ulp, struct ip6_ext); break; case IPPROTO_PIM: /* XXX PIM header check? */ PULLUP_TO(hlen, ulp, struct pim); break; case IPPROTO_GRE: /* RFC 1701 */ /* XXX GRE header check? */ PULLUP_TO(hlen, ulp, struct grehdr); break; case IPPROTO_CARP: PULLUP_TO(hlen, ulp, offsetof( struct carp_header, carp_counter)); if (CARP_ADVERTISEMENT != ((struct carp_header *)ulp)->carp_type) return (IP_FW_DENY); break; case IPPROTO_IPV6: /* RFC 2893 */ PULLUP_TO(hlen, ulp, struct ip6_hdr); break; case IPPROTO_IPV4: /* RFC 2893 */ PULLUP_TO(hlen, ulp, struct ip); break; case IPPROTO_PFSYNC: PULLUP_TO(hlen, ulp, struct pfsync_header); break; default: if (V_fw_verbose) printf("IPFW2: IPV6 - Unknown " "Extension Header(%d), ext_hd=%x\n", proto, ext_hd); if (V_fw_deny_unknown_exthdrs) return (IP_FW_DENY); PULLUP_TO(hlen, ulp, struct ip6_ext); break; } /*switch */ } UPDATE_POINTERS(); ip6 = (struct ip6_hdr *)ip; args->f_id.addr_type = 6; args->f_id.src_ip6 = ip6->ip6_src; args->f_id.dst_ip6 = ip6->ip6_dst; args->f_id.flow_id6 = ntohl(ip6->ip6_flow); iplen = ntohs(ip6->ip6_plen) + sizeof(*ip6); } else if (pktlen >= sizeof(struct ip) && (eh == NULL || eh->ether_type == htons(ETHERTYPE_IP)) && ip->ip_v == 4) { is_ipv4 = 1; args->flags |= IPFW_ARGS_IP4; hlen = ip->ip_hl << 2; /* * Collect parameters into local variables for faster * matching. */ proto = ip->ip_p; src_ip = ip->ip_src; dst_ip = ip->ip_dst; offset = ntohs(ip->ip_off) & IP_OFFMASK; iplen = ntohs(ip->ip_len); if (offset == 0) { switch (proto) { case IPPROTO_TCP: PULLUP_TO(hlen, ulp, struct tcphdr); dst_port = TCP(ulp)->th_dport; src_port = TCP(ulp)->th_sport; /* save flags for dynamic rules */ args->f_id._flags = tcp_get_flags(TCP(ulp)); break; case IPPROTO_SCTP: if (pktlen >= hlen + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr) + offsetof(struct sctp_init, a_rwnd)) PULLUP_LEN(hlen, ulp, sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr) + offsetof(struct sctp_init, a_rwnd)); else if (pktlen >= hlen + sizeof(struct sctphdr)) PULLUP_LEN(hlen, ulp, pktlen - hlen); else PULLUP_LEN(hlen, ulp, sizeof(struct sctphdr)); src_port = SCTP(ulp)->src_port; dst_port = SCTP(ulp)->dest_port; break; case IPPROTO_UDP: case IPPROTO_UDPLITE: PULLUP_TO(hlen, ulp, struct udphdr); dst_port = UDP(ulp)->uh_dport; src_port = UDP(ulp)->uh_sport; break; case IPPROTO_ICMP: PULLUP_TO(hlen, ulp, struct icmphdr); //args->f_id.flags = ICMP(ulp)->icmp_type; break; default: break; } } else { if (offset == 1 && proto == IPPROTO_TCP) { /* RFC 3128 */ goto pullup_failed; } } UPDATE_POINTERS(); args->f_id.addr_type = 4; args->f_id.src_ip = ntohl(src_ip.s_addr); args->f_id.dst_ip = ntohl(dst_ip.s_addr); } else { proto = 0; dst_ip.s_addr = src_ip.s_addr = 0; args->f_id.addr_type = 1; /* XXX */ } #undef PULLUP_TO pktlen = iplen < pktlen ? iplen: pktlen; /* Properly initialize the rest of f_id */ args->f_id.proto = proto; args->f_id.src_port = src_port = ntohs(src_port); args->f_id.dst_port = dst_port = ntohs(dst_port); IPFW_PF_RLOCK(chain); if (! V_ipfw_vnet_ready) { /* shutting down, leave NOW. */ IPFW_PF_RUNLOCK(chain); return (IP_FW_PASS); /* accept */ } if (args->flags & IPFW_ARGS_REF) { /* * Packet has already been tagged as a result of a previous * match on rule args->rule aka args->rule_id (PIPE, QUEUE, * REASS, NETGRAPH, DIVERT/TEE...) * Validate the slot and continue from the next one * if still present, otherwise do a lookup. */ f_pos = (args->rule.chain_id == chain->id) ? args->rule.slot : ipfw_find_rule(chain, args->rule.rulenum, args->rule.rule_id); } else { f_pos = 0; } if (args->flags & IPFW_ARGS_IN) { iif = args->ifp; oif = NULL; } else { MPASS(args->flags & IPFW_ARGS_OUT); iif = mem ? NULL : m_rcvif(m); oif = args->ifp; } /* * Now scan the rules, and parse microinstructions for each rule. * We have two nested loops and an inner switch. Sometimes we * need to break out of one or both loops, or re-enter one of * the loops with updated variables. Loop variables are: * * f_pos (outer loop) points to the current rule. * On output it points to the matching rule. * done (outer loop) is used as a flag to break the loop. * l (inner loop) residual length of current rule. * cmd points to the current microinstruction. * * We break the inner loop by setting l=0 and possibly * cmdlen=0 if we don't want to advance cmd. * We break the outer loop by setting done=1 * We can restart the inner loop by setting l>0 and f_pos, f, cmd * as needed. */ for (; f_pos < chain->n_rules; f_pos++) { ipfw_insn *cmd; uint32_t tablearg = 0; int l, cmdlen, skip_or; /* skip rest of OR block */ struct ip_fw *f; f = chain->map[f_pos]; if (V_set_disable & (1 << f->set) ) continue; skip_or = 0; for (l = f->cmd_len, cmd = f->cmd ; l > 0 ; l -= cmdlen, cmd += cmdlen) { int match; /* * check_body is a jump target used when we find a * CHECK_STATE, and need to jump to the body of * the target rule. */ /* check_body: */ cmdlen = F_LEN(cmd); /* * An OR block (insn_1 || .. || insn_n) has the * F_OR bit set in all but the last instruction. * The first match will set "skip_or", and cause * the following instructions to be skipped until * past the one with the F_OR bit clear. */ if (skip_or) { /* skip this instruction */ if ((cmd->len & F_OR) == 0) skip_or = 0; /* next one is good */ continue; } match = 0; /* set to 1 if we succeed */ switch (cmd->opcode) { /* * The first set of opcodes compares the packet's * fields with some pattern, setting 'match' if a * match is found. At the end of the loop there is * logic to deal with F_NOT and F_OR flags associated * with the opcode. */ case O_NOP: match = 1; break; case O_FORWARD_MAC: printf("ipfw: opcode %d unimplemented\n", cmd->opcode); break; case O_GID: case O_UID: case O_JAIL: /* * We only check offset == 0 && proto != 0, * as this ensures that we have a * packet with the ports info. */ if (offset != 0) break; if (proto == IPPROTO_TCP || proto == IPPROTO_UDP || proto == IPPROTO_UDPLITE) match = check_uidgid( (ipfw_insn_u32 *)cmd, args, &ucred_lookup, #ifdef __FreeBSD__ &ucred_cache); #else (void *)&ucred_cache); #endif break; case O_RECV: match = iface_match(iif, (ipfw_insn_if *)cmd, chain, &tablearg); break; case O_XMIT: match = iface_match(oif, (ipfw_insn_if *)cmd, chain, &tablearg); break; case O_VIA: match = iface_match(args->ifp, (ipfw_insn_if *)cmd, chain, &tablearg); break; case O_MACADDR2: if (args->flags & IPFW_ARGS_ETHER) { u_int32_t *want = (u_int32_t *) ((ipfw_insn_mac *)cmd)->addr; u_int32_t *mask = (u_int32_t *) ((ipfw_insn_mac *)cmd)->mask; u_int32_t *hdr = (u_int32_t *)eh; match = ( want[0] == (hdr[0] & mask[0]) && want[1] == (hdr[1] & mask[1]) && want[2] == (hdr[2] & mask[2]) ); } break; case O_MAC_TYPE: if (args->flags & IPFW_ARGS_ETHER) { u_int16_t *p = ((ipfw_insn_u16 *)cmd)->ports; int i; for (i = cmdlen - 1; !match && i>0; i--, p += 2) match = (ntohs(eh->ether_type) >= p[0] && ntohs(eh->ether_type) <= p[1]); } break; case O_FRAG: if (is_ipv4) { /* * Since flags_match() works with * uint8_t we pack ip_off into 8 bits. * For this match offset is a boolean. */ match = flags_match(cmd, ((ntohs(ip->ip_off) & ~IP_OFFMASK) >> 8) | (offset != 0)); } else { /* * Compatibility: historically bare * "frag" would match IPv6 fragments. */ match = (cmd->arg1 == 0x1 && (offset != 0)); } break; case O_IN: /* "out" is "not in" */ match = (oif == NULL); break; case O_LAYER2: match = (args->flags & IPFW_ARGS_ETHER); break; case O_DIVERTED: if ((args->flags & IPFW_ARGS_REF) == 0) break; /* * For diverted packets, args->rule.info * contains the divert port (in host format) * reason and direction. */ match = ((args->rule.info & IPFW_IS_MASK) == IPFW_IS_DIVERT) && ( ((args->rule.info & IPFW_INFO_IN) ? 1: 2) & cmd->arg1); break; case O_PROTO: /* * We do not allow an arg of 0 so the * check of "proto" only suffices. */ match = (proto == cmd->arg1); break; case O_IP_SRC: match = is_ipv4 && (((ipfw_insn_ip *)cmd)->addr.s_addr == src_ip.s_addr); break; case O_IP_DST_LOOKUP: if (IPFW_LOOKUP_TYPE(cmd) != LOOKUP_NONE) { void *pkey = NULL; uint32_t key, vidx; uint16_t keylen = 0; /* zero if can't match the packet */ uint8_t lookup_type; lookup_type = IPFW_LOOKUP_TYPE(cmd); switch (lookup_type) { case LOOKUP_DST_IP: case LOOKUP_SRC_IP: if (is_ipv4) { keylen = sizeof(in_addr_t); if (lookup_type == LOOKUP_DST_IP) pkey = &dst_ip; else pkey = &src_ip; } else if (is_ipv6) { keylen = sizeof(struct in6_addr); if (lookup_type == LOOKUP_DST_IP) pkey = &args->f_id.dst_ip6; else pkey = &args->f_id.src_ip6; } else /* only for L3 */ break; case LOOKUP_DSCP: if (is_ipv4) key = ip->ip_tos >> 2; else if (is_ipv6) key = IPV6_DSCP( (struct ip6_hdr *)ip) >> 2; else break; /* only for L3 */ key &= 0x3f; if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) key &= insntod(cmd, table)->value; pkey = &key; keylen = sizeof(key); break; case LOOKUP_DST_PORT: case LOOKUP_SRC_PORT: /* only for L3 */ if (is_ipv6 == 0 && is_ipv4 == 0) { break; } /* Skip fragments */ if (offset != 0) { break; } /* Skip proto without ports */ if (proto != IPPROTO_TCP && proto != IPPROTO_UDP && proto != IPPROTO_UDPLITE && proto != IPPROTO_SCTP) break; if (lookup_type == LOOKUP_DST_PORT) key = dst_port; else key = src_port; pkey = &key; if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) key &= insntod(cmd, table)->value; keylen = sizeof(key); break; case LOOKUP_DST_MAC: case LOOKUP_SRC_MAC: /* only for L2 */ if ((args->flags & IPFW_ARGS_ETHER) == 0) break; pkey = lookup_type == LOOKUP_DST_MAC ? eh->ether_dhost : eh->ether_shost; keylen = ETHER_ADDR_LEN; break; #ifndef USERSPACE case LOOKUP_UID: case LOOKUP_JAIL: check_uidgid(insntod(cmd, u32), args, &ucred_lookup, #ifdef __FreeBSD__ &ucred_cache); if (lookup_type == LOOKUP_UID) key = ucred_cache->cr_uid; else if (lookup_type == LOOKUP_JAIL) key = ucred_cache->cr_prison->pr_id; #else /* !__FreeBSD__ */ (void *)&ucred_cache); if (lookup_type == LOOKUP_UID) key = ucred_cache.uid; else if (lookup_type == LOOKUP_JAIL) key = ucred_cache.xid; #endif /* !__FreeBSD__ */ pkey = &key; if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) key &= insntod(cmd, table)->value; keylen = sizeof(key); break; #endif /* !USERSPACE */ case LOOKUP_MARK: key = args->rule.pkt_mark; if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) key &= insntod(cmd, table)->value; pkey = &key; keylen = sizeof(key); break; case LOOKUP_RULENUM: key = f->rulenum; if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) key &= insntod(cmd, table)->value; pkey = &key; keylen = sizeof(key); break; } /* unknown key type */ if (keylen == 0) break; match = ipfw_lookup_table(chain, insntod(cmd, kidx)->kidx, keylen, pkey, &vidx); if (match) tablearg = vidx; break; } /* LOOKUP_NONE */ /* FALLTHROUGH */ case O_IP_SRC_LOOKUP: { void *pkey; uint32_t vidx; uint16_t keylen; if (is_ipv4) { keylen = sizeof(in_addr_t); if (cmd->opcode == O_IP_DST_LOOKUP) pkey = &dst_ip; else pkey = &src_ip; } else if (is_ipv6) { keylen = sizeof(struct in6_addr); if (cmd->opcode == O_IP_DST_LOOKUP) pkey = &args->f_id.dst_ip6; else pkey = &args->f_id.src_ip6; } else break; match = ipfw_lookup_table(chain, insntod(cmd, kidx)->kidx, keylen, pkey, &vidx); if (!match) break; if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) { match = tvalue_match(chain, insntod(cmd, table), vidx); if (!match) break; } tablearg = vidx; break; } case O_MAC_SRC_LOOKUP: case O_MAC_DST_LOOKUP: { void *pkey; uint32_t vidx; uint16_t keylen = ETHER_ADDR_LEN; /* Need ether frame */ if ((args->flags & IPFW_ARGS_ETHER) == 0) break; if (cmd->opcode == O_MAC_DST_LOOKUP) pkey = eh->ether_dhost; else pkey = eh->ether_shost; match = ipfw_lookup_table(chain, insntod(cmd, kidx)->kidx, keylen, pkey, &vidx); if (!match) break; if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) { match = tvalue_match(chain, insntod(cmd, table), vidx); if (!match) break; } tablearg = vidx; break; } case O_IP_FLOW_LOOKUP: { uint32_t vidx = 0; match = ipfw_lookup_table(chain, insntod(cmd, kidx)->kidx, 0, &args->f_id, &vidx); if (!match) break; if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) match = tvalue_match(chain, insntod(cmd, table), vidx); if (match) tablearg = vidx; break; } case O_IP_SRC_MASK: case O_IP_DST_MASK: if (is_ipv4) { uint32_t a = (cmd->opcode == O_IP_DST_MASK) ? dst_ip.s_addr : src_ip.s_addr; uint32_t *p = ((ipfw_insn_u32 *)cmd)->d; int i = cmdlen-1; for (; !match && i>0; i-= 2, p+= 2) match = (p[0] == (a & p[1])); } break; case O_IP_SRC_ME: if (is_ipv4) { match = in_localip(src_ip); break; } #ifdef INET6 /* FALLTHROUGH */ case O_IP6_SRC_ME: match = is_ipv6 && ipfw_localip6(&args->f_id.src_ip6); #endif break; case O_IP_DST_SET: case O_IP_SRC_SET: if (is_ipv4) { u_int32_t *d = (u_int32_t *)(cmd+1); u_int32_t addr = cmd->opcode == O_IP_DST_SET ? args->f_id.dst_ip : args->f_id.src_ip; if (addr < d[0]) break; addr -= d[0]; /* subtract base */ match = (addr < cmd->arg1) && ( d[ 1 + (addr>>5)] & (1<<(addr & 0x1f)) ); } break; case O_IP_DST: match = is_ipv4 && (((ipfw_insn_ip *)cmd)->addr.s_addr == dst_ip.s_addr); break; case O_IP_DST_ME: if (is_ipv4) { match = in_localip(dst_ip); break; } #ifdef INET6 /* FALLTHROUGH */ case O_IP6_DST_ME: match = is_ipv6 && ipfw_localip6(&args->f_id.dst_ip6); #endif break; case O_IP_SRCPORT: case O_IP_DSTPORT: /* * offset == 0 && proto != 0 is enough * to guarantee that we have a * packet with port info. */ if ((proto == IPPROTO_UDP || proto == IPPROTO_UDPLITE || proto == IPPROTO_TCP || proto == IPPROTO_SCTP) && offset == 0) { u_int16_t x = (cmd->opcode == O_IP_SRCPORT) ? src_port : dst_port ; u_int16_t *p = ((ipfw_insn_u16 *)cmd)->ports; int i; for (i = cmdlen - 1; !match && i>0; i--, p += 2) match = (x>=p[0] && x<=p[1]); } break; case O_ICMPTYPE: match = (offset == 0 && proto==IPPROTO_ICMP && icmptype_match(ICMP(ulp), (ipfw_insn_u32 *)cmd) ); break; #ifdef INET6 case O_ICMP6TYPE: match = is_ipv6 && offset == 0 && proto==IPPROTO_ICMPV6 && icmp6type_match( ICMP6(ulp)->icmp6_type, (ipfw_insn_u32 *)cmd); break; #endif /* INET6 */ case O_IPOPT: match = (is_ipv4 && ipopts_match(ip, cmd) ); break; case O_IPVER: match = ((is_ipv4 || is_ipv6) && cmd->arg1 == ip->ip_v); break; case O_IPID: case O_IPTTL: if (!is_ipv4) break; case O_IPLEN: { /* only for IP packets */ uint16_t x; uint16_t *p; int i; if (cmd->opcode == O_IPLEN) x = iplen; else if (cmd->opcode == O_IPTTL) x = ip->ip_ttl; else /* must be IPID */ x = ntohs(ip->ip_id); if (cmdlen == 1) { match = (cmd->arg1 == x); break; } /* otherwise we have ranges */ p = ((ipfw_insn_u16 *)cmd)->ports; i = cmdlen - 1; for (; !match && i>0; i--, p += 2) match = (x >= p[0] && x <= p[1]); } break; case O_IPPRECEDENCE: match = (is_ipv4 && (cmd->arg1 == (ip->ip_tos & 0xe0)) ); break; case O_IPTOS: match = (is_ipv4 && flags_match(cmd, ip->ip_tos)); break; case O_DSCP: { uint32_t *p; uint16_t x; p = ((ipfw_insn_u32 *)cmd)->d; if (is_ipv4) x = ip->ip_tos >> 2; else if (is_ipv6) { x = IPV6_DSCP( (struct ip6_hdr *)ip) >> 2; x &= 0x3f; } else break; /* DSCP bitmask is stored as low_u32 high_u32 */ if (x >= 32) match = *(p + 1) & (1 << (x - 32)); else match = *p & (1 << x); } break; case O_TCPDATALEN: if (proto == IPPROTO_TCP && offset == 0) { struct tcphdr *tcp; uint16_t x; uint16_t *p; int i; #ifdef INET6 if (is_ipv6) { struct ip6_hdr *ip6; ip6 = (struct ip6_hdr *)ip; if (ip6->ip6_plen == 0) { /* * Jumbo payload is not * supported by this * opcode. */ break; } x = iplen - hlen; } else #endif /* INET6 */ x = iplen - (ip->ip_hl << 2); tcp = TCP(ulp); x -= tcp->th_off << 2; if (cmdlen == 1) { match = (cmd->arg1 == x); break; } /* otherwise we have ranges */ p = ((ipfw_insn_u16 *)cmd)->ports; i = cmdlen - 1; for (; !match && i>0; i--, p += 2) match = (x >= p[0] && x <= p[1]); } break; case O_TCPFLAGS: /* * Note that this is currently only set up to * match the lower 8 TCP header flag bits, not * the full compliment of all 12 flags. */ match = (proto == IPPROTO_TCP && offset == 0 && flags_match(cmd, tcp_get_flags(TCP(ulp)))); break; case O_TCPOPTS: if (proto == IPPROTO_TCP && offset == 0 && ulp){ PULLUP_LEN_LOCKED(hlen, ulp, (TCP(ulp)->th_off << 2)); match = tcpopts_match(TCP(ulp), cmd); } break; case O_TCPSEQ: match = (proto == IPPROTO_TCP && offset == 0 && ((ipfw_insn_u32 *)cmd)->d[0] == TCP(ulp)->th_seq); break; case O_TCPACK: match = (proto == IPPROTO_TCP && offset == 0 && ((ipfw_insn_u32 *)cmd)->d[0] == TCP(ulp)->th_ack); break; case O_TCPMSS: if (proto == IPPROTO_TCP && (args->f_id._flags & TH_SYN) != 0 && ulp != NULL) { uint16_t mss, *p; int i; PULLUP_LEN_LOCKED(hlen, ulp, (TCP(ulp)->th_off << 2)); if ((tcpopts_parse(TCP(ulp), &mss) & IP_FW_TCPOPT_MSS) == 0) break; if (cmdlen == 1) { match = (cmd->arg1 == mss); break; } /* Otherwise we have ranges. */ p = ((ipfw_insn_u16 *)cmd)->ports; i = cmdlen - 1; for (; !match && i > 0; i--, p += 2) match = (mss >= p[0] && mss <= p[1]); } break; case O_TCPWIN: if (proto == IPPROTO_TCP && offset == 0) { uint16_t x; uint16_t *p; int i; x = ntohs(TCP(ulp)->th_win); if (cmdlen == 1) { match = (cmd->arg1 == x); break; } /* Otherwise we have ranges. */ p = ((ipfw_insn_u16 *)cmd)->ports; i = cmdlen - 1; for (; !match && i > 0; i--, p += 2) match = (x >= p[0] && x <= p[1]); } break; case O_ESTAB: /* reject packets which have SYN only */ /* XXX should i also check for TH_ACK ? */ match = (proto == IPPROTO_TCP && offset == 0 && (tcp_get_flags(TCP(ulp)) & (TH_RST | TH_ACK | TH_SYN)) != TH_SYN); break; case O_ALTQ: { struct pf_mtag *at; struct m_tag *mtag; ipfw_insn_altq *altq = (ipfw_insn_altq *)cmd; /* * ALTQ uses mbuf tags from another * packet filtering system - pf(4). * We allocate a tag in its format * and fill it in, pretending to be pf(4). */ match = 1; at = pf_find_mtag(m); if (at != NULL && at->qid != 0) break; mtag = m_tag_get(PACKET_TAG_PF, sizeof(struct pf_mtag), M_NOWAIT | M_ZERO); if (mtag == NULL) { /* * Let the packet fall back to the * default ALTQ. */ break; } m_tag_prepend(m, mtag); at = (struct pf_mtag *)(mtag + 1); at->qid = altq->qid; at->hdr = ip; break; } case O_LOG: ipfw_log(chain, f, hlen, args, offset | ip6f_mf, tablearg, ip, eh); match = 1; break; case O_PROB: match = (random()<((ipfw_insn_u32 *)cmd)->d[0]); break; case O_VERREVPATH: /* Outgoing packets automatically pass/match */ match = (args->flags & IPFW_ARGS_OUT || ( #ifdef INET6 is_ipv6 ? verify_path6(&(args->f_id.src_ip6), iif, args->f_id.fib) : #endif verify_path(src_ip, iif, args->f_id.fib))); break; case O_VERSRCREACH: /* Outgoing packets automatically pass/match */ match = (hlen > 0 && ((oif != NULL) || ( #ifdef INET6 is_ipv6 ? verify_path6(&(args->f_id.src_ip6), NULL, args->f_id.fib) : #endif verify_path(src_ip, NULL, args->f_id.fib)))); break; case O_ANTISPOOF: /* Outgoing packets automatically pass/match */ if (oif == NULL && hlen > 0 && ( (is_ipv4 && in_localaddr(src_ip)) #ifdef INET6 || (is_ipv6 && in6_localaddr(&(args->f_id.src_ip6))) #endif )) match = #ifdef INET6 is_ipv6 ? verify_path6( &(args->f_id.src_ip6), iif, args->f_id.fib) : #endif verify_path(src_ip, iif, args->f_id.fib); else match = 1; break; case O_IPSEC: match = (m_tag_find(m, PACKET_TAG_IPSEC_IN_DONE, NULL) != NULL); /* otherwise no match */ break; #ifdef INET6 case O_IP6_SRC: match = is_ipv6 && IN6_ARE_ADDR_EQUAL(&args->f_id.src_ip6, &((ipfw_insn_ip6 *)cmd)->addr6); break; case O_IP6_DST: match = is_ipv6 && IN6_ARE_ADDR_EQUAL(&args->f_id.dst_ip6, &((ipfw_insn_ip6 *)cmd)->addr6); break; case O_IP6_SRC_MASK: case O_IP6_DST_MASK: if (is_ipv6) { int i = cmdlen - 1; struct in6_addr p; struct in6_addr *d = &((ipfw_insn_ip6 *)cmd)->addr6; for (; !match && i > 0; d += 2, i -= F_INSN_SIZE(struct in6_addr) * 2) { p = (cmd->opcode == O_IP6_SRC_MASK) ? args->f_id.src_ip6: args->f_id.dst_ip6; APPLY_MASK(&p, &d[1]); match = IN6_ARE_ADDR_EQUAL(&d[0], &p); } } break; case O_FLOW6ID: match = is_ipv6 && flow6id_match(args->f_id.flow_id6, (ipfw_insn_u32 *) cmd); break; case O_EXT_HDR: match = is_ipv6 && (ext_hd & ((ipfw_insn *) cmd)->arg1); break; case O_IP6: match = is_ipv6; break; #endif case O_IP4: match = is_ipv4; break; case O_TAG: { struct m_tag *mtag; uint32_t tag = TARG(cmd->arg1, tag); /* Packet is already tagged with this tag? */ mtag = m_tag_locate(m, MTAG_IPFW, tag, NULL); /* We have `untag' action when F_NOT flag is * present. And we must remove this mtag from * mbuf and reset `match' to zero (`match' will * be inversed later). * Otherwise we should allocate new mtag and * push it into mbuf. */ if (cmd->len & F_NOT) { /* `untag' action */ if (mtag != NULL) m_tag_delete(m, mtag); match = 0; } else { if (mtag == NULL) { mtag = m_tag_alloc( MTAG_IPFW, tag, 0, M_NOWAIT); if (mtag != NULL) m_tag_prepend(m, mtag); } match = 1; } break; } case O_FIB: /* try match the specified fib */ if (args->f_id.fib == cmd->arg1) match = 1; break; case O_SOCKARG: { #ifndef USERSPACE /* not supported in userspace */ struct inpcb *inp = args->inp; struct inpcbinfo *pi; bool inp_locked = false; if (proto == IPPROTO_TCP) pi = &V_tcbinfo; else if (proto == IPPROTO_UDP) pi = &V_udbinfo; else if (proto == IPPROTO_UDPLITE) pi = &V_ulitecbinfo; else break; /* * XXXRW: so_user_cookie should almost * certainly be inp_user_cookie? */ /* * For incoming packet lookup the inpcb * using the src/dest ip/port tuple. */ if (is_ipv4 && inp == NULL) { inp = in_pcblookup(pi, src_ip, htons(src_port), dst_ip, htons(dst_port), INPLOOKUP_RLOCKPCB, NULL); inp_locked = true; } #ifdef INET6 if (is_ipv6 && inp == NULL) { inp = in6_pcblookup(pi, &args->f_id.src_ip6, htons(src_port), &args->f_id.dst_ip6, htons(dst_port), INPLOOKUP_RLOCKPCB, NULL); inp_locked = true; } #endif /* INET6 */ if (inp != NULL) { if (inp->inp_socket) { tablearg = inp->inp_socket->so_user_cookie; if (tablearg) match = 1; } if (inp_locked) INP_RUNLOCK(inp); } #endif /* !USERSPACE */ break; } case O_TAGGED: { struct m_tag *mtag; uint32_t tag = TARG(cmd->arg1, tag); if (cmdlen == 1) { match = m_tag_locate(m, MTAG_IPFW, tag, NULL) != NULL; break; } /* we have ranges */ for (mtag = m_tag_first(m); mtag != NULL && !match; mtag = m_tag_next(m, mtag)) { uint16_t *p; int i; if (mtag->m_tag_cookie != MTAG_IPFW) continue; p = ((ipfw_insn_u16 *)cmd)->ports; i = cmdlen - 1; for(; !match && i > 0; i--, p += 2) match = mtag->m_tag_id >= p[0] && mtag->m_tag_id <= p[1]; } break; } case O_MARK: { uint32_t mark; if (cmd->arg1 == IP_FW_TARG) mark = TARG_VAL(chain, tablearg, mark); else mark = insntoc(cmd, u32)->d[0]; match = (args->rule.pkt_mark & insntoc(cmd, u32)->d[1]) == (mark & insntoc(cmd, u32)->d[1]); break; } /* * The second set of opcodes represents 'actions', * i.e. the terminal part of a rule once the packet * matches all previous patterns. * Typically there is only one action for each rule, * and the opcode is stored at the end of the rule * (but there are exceptions -- see below). * * In general, here we set retval and terminate the * outer loop (would be a 'break 3' in some language, * but we need to set l=0, done=1) * * Exceptions: * O_COUNT and O_SKIPTO actions: * instead of terminating, we jump to the next rule * (setting l=0), or to the SKIPTO target (setting * f/f_len, cmd and l as needed), respectively. * * O_TAG, O_LOG and O_ALTQ action parameters: * perform some action and set match = 1; * * O_LIMIT and O_KEEP_STATE: these opcodes are * not real 'actions', and are stored right * before the 'action' part of the rule (one * exception is O_SKIP_ACTION which could be * between these opcodes and 'action' one). * These opcodes try to install an entry in the * state tables; if successful, we continue with * the next opcode (match=1; break;), otherwise * the packet must be dropped (set retval, * break loops with l=0, done=1) * * O_PROBE_STATE and O_CHECK_STATE: these opcodes * cause a lookup of the state table, and a jump * to the 'action' part of the parent rule * if an entry is found, or * (CHECK_STATE only) a jump to the next rule if * the entry is not found. * The result of the lookup is cached so that * further instances of these opcodes become NOPs. * The jump to the next rule is done by setting * l=0, cmdlen=0. * * O_SKIP_ACTION: this opcode is not a real 'action' * either, and is stored right before the 'action' * part of the rule, right after the O_KEEP_STATE * opcode. It causes match failure so the real * 'action' could be executed only if the rule * is checked via dynamic rule from the state * table, as in such case execution starts * from the true 'action' opcode directly. * */ case O_LIMIT: case O_KEEP_STATE: if (ipfw_dyn_install_state(chain, f, (ipfw_insn_limit *)cmd, args, ulp, pktlen, &dyn_info, tablearg)) { /* error or limit violation */ retval = IP_FW_DENY; l = 0; /* exit inner loop */ done = 1; /* exit outer loop */ } match = 1; break; case O_PROBE_STATE: case O_CHECK_STATE: /* * dynamic rules are checked at the first * keep-state or check-state occurrence, * with the result being stored in dyn_info. * The compiler introduces a PROBE_STATE * instruction for us when we have a * KEEP_STATE (because PROBE_STATE needs * to be run first). */ if (DYN_LOOKUP_NEEDED(&dyn_info, cmd) && (q = ipfw_dyn_lookup_state(args, ulp, pktlen, cmd, &dyn_info)) != NULL) { /* * Found dynamic entry, jump to the * 'action' part of the parent rule * by setting f, cmd, l and clearing * cmdlen. */ f = q; f_pos = dyn_info.f_pos; cmd = ACTION_PTR(f); l = f->cmd_len - f->act_ofs; cmdlen = 0; continue; } /* * Dynamic entry not found. If CHECK_STATE, * skip to next rule, if PROBE_STATE just * ignore and continue with next opcode. */ if (cmd->opcode == O_CHECK_STATE) l = 0; /* exit inner loop */ match = 1; break; case O_SKIP_ACTION: match = 0; /* skip to the next rule */ l = 0; /* exit inner loop */ break; case O_ACCEPT: retval = 0; /* accept */ l = 0; /* exit inner loop */ done = 1; /* exit outer loop */ break; case O_PIPE: case O_QUEUE: set_match(args, f_pos, chain); args->rule.info = TARG(cmd->arg1, pipe); if (cmd->opcode == O_PIPE) args->rule.info |= IPFW_IS_PIPE; if (V_fw_one_pass) args->rule.info |= IPFW_ONEPASS; retval = IP_FW_DUMMYNET; l = 0; /* exit inner loop */ done = 1; /* exit outer loop */ break; case O_DIVERT: case O_TEE: if (args->flags & IPFW_ARGS_ETHER) break; /* not on layer 2 */ /* otherwise this is terminal */ l = 0; /* exit inner loop */ done = 1; /* exit outer loop */ retval = (cmd->opcode == O_DIVERT) ? IP_FW_DIVERT : IP_FW_TEE; set_match(args, f_pos, chain); args->rule.info = TARG(cmd->arg1, divert); break; case O_COUNT: IPFW_INC_RULE_COUNTER(f, pktlen); l = 0; /* exit inner loop */ break; case O_SKIPTO: IPFW_INC_RULE_COUNTER(f, pktlen); f_pos = jump(chain, f, insntod(cmd, u32)->d[0], tablearg, false); /* * Skip disabled rules, and re-enter * the inner loop with the correct * f_pos, f, l and cmd. * Also clear cmdlen and skip_or */ for (; f_pos < chain->n_rules - 1 && (V_set_disable & (1 << chain->map[f_pos]->set)); f_pos++) ; /* Re-enter the inner loop at the skipto rule. */ f = chain->map[f_pos]; l = f->cmd_len; cmd = f->cmd; match = 1; cmdlen = 0; skip_or = 0; continue; break; /* not reached */ case O_CALLRETURN: { /* * Implementation of `subroutine' call/return, * in the stack carried in an mbuf tag. This * is different from `skipto' in that any call * address is possible (`skipto' must prevent * backward jumps to avoid endless loops). * We have `return' action when F_NOT flag is * present. The `m_tag_id' field is used as * stack pointer. */ struct m_tag *mtag; uint32_t jmpto, *stack; #define IS_CALL ((cmd->len & F_NOT) == 0) #define IS_RETURN ((cmd->len & F_NOT) != 0) /* * Hand-rolled version of m_tag_locate() with * wildcard `type'. * If not already tagged, allocate new tag. */ mtag = m_tag_first(m); while (mtag != NULL) { if (mtag->m_tag_cookie == MTAG_IPFW_CALL) break; mtag = m_tag_next(m, mtag); } /* * We keep ruleset id in the first element * of stack. If it doesn't match chain->id, * then we can't trust information in the * stack, since rules were changed. * We reset stack pointer to be able reuse * tag if it will be needed. */ if (mtag != NULL) { stack = (uint32_t *)(mtag + 1); if (stack[0] != chain->id) { stack[0] = chain->id; mtag->m_tag_id = 0; } } /* * If there is no mtag or stack is empty, * `return` continues with next rule. */ if (IS_RETURN && (mtag == NULL || mtag->m_tag_id == 0)) { l = 0; /* exit inner loop */ break; } if (mtag == NULL) { MPASS(IS_CALL); mtag = m_tag_alloc(MTAG_IPFW_CALL, 0, IPFW_CALLSTACK_SIZE * sizeof(uint32_t), M_NOWAIT); if (mtag != NULL) { m_tag_prepend(m, mtag); stack = (uint32_t *)(mtag + 1); stack[0] = chain->id; } } if (mtag == NULL) { printf("ipfw: rule %u: failed to " "allocate call stack. " "Denying packet.\n", f->rulenum); l = 0; /* exit inner loop */ done = 1; /* exit outer loop */ retval = IP_FW_DENY; /* drop packet */ break; } if (IS_CALL && mtag->m_tag_id >= IPFW_CALLSTACK_SIZE - 1) { printf("ipfw: rule %u: call stack " "overflow. Denying packet.\n", f->rulenum); l = 0; /* exit inner loop */ done = 1; /* exit outer loop */ retval = IP_FW_DENY; /* drop packet */ break; } MPASS(stack == (uint32_t *)(mtag + 1)); IPFW_INC_RULE_COUNTER(f, pktlen); if (IS_CALL) { stack[++mtag->m_tag_id] = f_pos; f_pos = jump(chain, f, insntod(cmd, u32)->d[0], tablearg, true); } else { /* `return' action */ jmpto = stack[mtag->m_tag_id--]; if (cmd->arg1 == RETURN_NEXT_RULE) f_pos = jmpto + 1; else /* RETURN_NEXT_RULENUM */ f_pos = ipfw_find_rule(chain, chain->map[ jmpto]->rulenum + 1, 0); } /* * Skip disabled rules, and re-enter * the inner loop with the correct * f_pos, f, l and cmd. * Also clear cmdlen and skip_or */ MPASS(f_pos < chain->n_rules - 1); for (; f_pos < chain->n_rules - 1 && (V_set_disable & (1 << chain->map[f_pos]->set)); f_pos++) ; /* * Re-enter the inner loop at the dest * rule. */ f = chain->map[f_pos]; l = f->cmd_len; cmd = f->cmd; cmdlen = 0; skip_or = 0; continue; break; /* NOTREACHED */ } #undef IS_CALL #undef IS_RETURN case O_REJECT: /* * Drop the packet and send a reject notice * if the packet is not ICMP (or is an ICMP * query), and it is not multicast/broadcast. */ if (hlen > 0 && is_ipv4 && offset == 0 && (proto != IPPROTO_ICMP || is_icmp_query(ICMP(ulp))) && !(m->m_flags & (M_BCAST|M_MCAST)) && !IN_MULTICAST(ntohl(dst_ip.s_addr))) { KASSERT(!need_send_reject, ("o_reject - need_send_reject was set previously")); if ((reject_code = cmd->arg1) == ICMP_UNREACH_NEEDFRAG && cmd->len == F_INSN_SIZE(ipfw_insn_u16)) { reject_mtu = ((ipfw_insn_u16 *)cmd)->ports[0]; } else { reject_mtu = 0; } need_send_reject = true; } /* FALLTHROUGH */ #ifdef INET6 case O_UNREACH6: if (hlen > 0 && is_ipv6 && ((offset & IP6F_OFF_MASK) == 0) && (proto != IPPROTO_ICMPV6 || (is_icmp6_query(icmp6_type) == 1)) && !(m->m_flags & (M_BCAST|M_MCAST)) && !IN6_IS_ADDR_MULTICAST( &args->f_id.dst_ip6)) { KASSERT(!need_send_reject, ("o_unreach6 - need_send_reject was set previously")); reject_code = cmd->arg1; if (cmd->opcode == O_REJECT) { reject_code = map_icmp_unreach(reject_code); } need_send_reject = true; } /* FALLTHROUGH */ #endif case O_DENY: retval = IP_FW_DENY; l = 0; /* exit inner loop */ done = 1; /* exit outer loop */ break; case O_FORWARD_IP: if (args->flags & IPFW_ARGS_ETHER) break; /* not valid on layer2 pkts */ if (q != f || dyn_info.direction == MATCH_FORWARD) { struct sockaddr_in *sa; sa = &(((ipfw_insn_sa *)cmd)->sa); if (sa->sin_addr.s_addr == INADDR_ANY) { #ifdef INET6 /* * We use O_FORWARD_IP opcode for * fwd rule with tablearg, but tables * now support IPv6 addresses. And * when we are inspecting IPv6 packet, * we can use nh6 field from * table_value as next_hop6 address. */ if (is_ipv6) { struct ip_fw_nh6 *nh6; args->flags |= IPFW_ARGS_NH6; nh6 = &args->hopstore6; nh6->sin6_addr = TARG_VAL( chain, tablearg, nh6); nh6->sin6_port = sa->sin_port; nh6->sin6_scope_id = TARG_VAL( chain, tablearg, zoneid); } else #endif { args->flags |= IPFW_ARGS_NH4; args->hopstore.sin_port = sa->sin_port; sa = &args->hopstore; sa->sin_family = AF_INET; sa->sin_len = sizeof(*sa); sa->sin_addr.s_addr = htonl( TARG_VAL(chain, tablearg, nh4)); } } else { args->flags |= IPFW_ARGS_NH4PTR; args->next_hop = sa; } } retval = IP_FW_PASS; l = 0; /* exit inner loop */ done = 1; /* exit outer loop */ break; #ifdef INET6 case O_FORWARD_IP6: if (args->flags & IPFW_ARGS_ETHER) break; /* not valid on layer2 pkts */ if (q != f || dyn_info.direction == MATCH_FORWARD) { struct sockaddr_in6 *sin6; sin6 = &(((ipfw_insn_sa6 *)cmd)->sa); args->flags |= IPFW_ARGS_NH6PTR; args->next_hop6 = sin6; } retval = IP_FW_PASS; l = 0; /* exit inner loop */ done = 1; /* exit outer loop */ break; #endif case O_NETGRAPH: case O_NGTEE: set_match(args, f_pos, chain); args->rule.info = TARG(cmd->arg1, netgraph); if (V_fw_one_pass) args->rule.info |= IPFW_ONEPASS; retval = (cmd->opcode == O_NETGRAPH) ? IP_FW_NETGRAPH : IP_FW_NGTEE; l = 0; /* exit inner loop */ done = 1; /* exit outer loop */ break; case O_SETFIB: { uint32_t fib; IPFW_INC_RULE_COUNTER(f, pktlen); fib = TARG(cmd->arg1, fib) & 0x7FFF; if (fib >= rt_numfibs) fib = 0; M_SETFIB(m, fib); args->f_id.fib = fib; /* XXX */ l = 0; /* exit inner loop */ break; } case O_SETDSCP: { uint16_t code; code = TARG(cmd->arg1, dscp) & 0x3F; l = 0; /* exit inner loop */ if (is_ipv4) { uint16_t old; old = *(uint16_t *)ip; ip->ip_tos = (code << 2) | (ip->ip_tos & 0x03); ip->ip_sum = cksum_adjust(ip->ip_sum, old, *(uint16_t *)ip); } else if (is_ipv6) { /* update cached value */ args->f_id.flow_id6 = ntohl(*(uint32_t *)ip) & ~0x0FC00000; args->f_id.flow_id6 |= code << 22; *((uint32_t *)ip) = htonl(args->f_id.flow_id6); } else break; IPFW_INC_RULE_COUNTER(f, pktlen); break; } case O_NAT: l = 0; /* exit inner loop */ done = 1; /* exit outer loop */ /* * Ensure that we do not invoke NAT handler for * non IPv4 packets. Libalias expects only IPv4. */ if (!is_ipv4 || !IPFW_NAT_LOADED) { retval = IP_FW_DENY; break; } struct cfg_nat *t; int nat_id; args->rule.info = 0; set_match(args, f_pos, chain); /* Check if this is 'global' nat rule */ if (cmd->arg1 == IP_FW_NAT44_GLOBAL) { retval = ipfw_nat_ptr(args, NULL, m); break; } t = ((ipfw_insn_nat *)cmd)->nat; if (t == NULL) { nat_id = TARG(cmd->arg1, nat); t = (*lookup_nat_ptr)(&chain->nat, nat_id); if (t == NULL) { retval = IP_FW_DENY; break; } if (cmd->arg1 != IP_FW_TARG) ((ipfw_insn_nat *)cmd)->nat = t; } retval = ipfw_nat_ptr(args, t, m); break; case O_REASS: { int ip_off; l = 0; /* in any case exit inner loop */ if (is_ipv6) /* IPv6 is not supported yet */ break; IPFW_INC_RULE_COUNTER(f, pktlen); ip_off = ntohs(ip->ip_off); /* if not fragmented, go to next rule */ if ((ip_off & (IP_MF | IP_OFFMASK)) == 0) break; args->m = m = ip_reass(m); /* * do IP header checksum fixup. */ if (m == NULL) { /* fragment got swallowed */ retval = IP_FW_DENY; } else { /* good, packet complete */ int hlen; ip = mtod(m, struct ip *); hlen = ip->ip_hl << 2; ip->ip_sum = 0; if (hlen == sizeof(struct ip)) ip->ip_sum = in_cksum_hdr(ip); else ip->ip_sum = in_cksum(m, hlen); retval = IP_FW_REASS; args->rule.info = 0; set_match(args, f_pos, chain); } done = 1; /* exit outer loop */ break; } case O_SETMARK: { l = 0; /* exit inner loop */ args->rule.pkt_mark = ( (cmd->arg1 == IP_FW_TARG) ? TARG_VAL(chain, tablearg, mark) : insntoc(cmd, u32)->d[0]); IPFW_INC_RULE_COUNTER(f, pktlen); break; } case O_EXTERNAL_ACTION: l = 0; /* in any case exit inner loop */ retval = ipfw_run_eaction(chain, args, cmd, &done); /* * If both @retval and @done are zero, * consider this as rule matching and * update counters. */ if (retval == 0 && done == 0) { IPFW_INC_RULE_COUNTER(f, pktlen); /* * Reset the result of the last * dynamic state lookup. * External action can change * @args content, and it may be * used for new state lookup later. */ DYN_INFO_INIT(&dyn_info); } break; default: panic("ipfw: rule %u: unknown opcode %d\n", f->rulenum, cmd->opcode); } /* end of switch() on opcodes */ /* * if we get here with l=0, then match is irrelevant. */ if (cmd->len & F_NOT) match = !match; if (match) { if (cmd->len & F_OR) skip_or = 1; } else { if (!(cmd->len & F_OR)) /* not an OR block, */ break; /* try next rule */ } } /* end of inner loop, scan opcodes */ #undef PULLUP_LEN #undef PULLUP_LEN_LOCKED if (done) break; /* next_rule:; */ /* try next rule */ } /* end of outer for, scan rules */ if (done) { struct ip_fw *rule = chain->map[f_pos]; /* Update statistics */ IPFW_INC_RULE_COUNTER(rule, pktlen); IPFW_PROBE(rule__matched, retval, is_ipv4 ? AF_INET : AF_INET6, is_ipv4 ? (uintptr_t)&src_ip : (uintptr_t)&args->f_id.src_ip6, is_ipv4 ? (uintptr_t)&dst_ip : (uintptr_t)&args->f_id.dst_ip6, args, rule); } else { retval = IP_FW_DENY; printf("ipfw: ouch!, skip past end of rules, denying packet\n"); } IPFW_PF_RUNLOCK(chain); if (need_send_reject) { #ifdef INET6 if (is_ipv6) send_reject6(args, reject_code, hlen, (struct ip6_hdr *)ip); else #endif send_reject(args, reject_code, reject_mtu, iplen, ip); } #ifdef __FreeBSD__ if (ucred_cache != NULL) crfree(ucred_cache); #endif return (retval); pullup_failed: if (V_fw_verbose) printf("ipfw: pullup failed\n"); return (IP_FW_DENY); } /* * Set maximum number of tables that can be used in given VNET ipfw instance. */ #ifdef SYSCTL_NODE static int sysctl_ipfw_table_num(SYSCTL_HANDLER_ARGS) { int error; unsigned int ntables; ntables = V_fw_tables_max; error = sysctl_handle_int(oidp, &ntables, 0, req); /* Read operation or some error */ if ((error != 0) || (req->newptr == NULL)) return (error); return (ipfw_resize_tables(&V_layer3_chain, ntables)); } /* * Switches table namespace between global and per-set. */ static int sysctl_ipfw_tables_sets(SYSCTL_HANDLER_ARGS) { int error; unsigned int sets; sets = V_fw_tables_sets; error = sysctl_handle_int(oidp, &sets, 0, req); /* Read operation or some error */ if ((error != 0) || (req->newptr == NULL)) return (error); return (ipfw_switch_tables_namespace(&V_layer3_chain, sets)); } #endif /* * Module and VNET glue */ /* * Stuff that must be initialised only on boot or module load */ static int ipfw_init(void) { int error = 0; /* * Only print out this stuff the first time around, * when called from the sysinit code. */ printf("ipfw2 " #ifdef INET6 "(+ipv6) " #endif "initialized, divert %s, nat %s, " "default to %s, logging ", #ifdef IPDIVERT "enabled", #else "loadable", #endif #ifdef IPFIREWALL_NAT "enabled", #else "loadable", #endif default_to_accept ? "accept" : "deny"); /* * Note: V_xxx variables can be accessed here but the vnet specific * initializer may not have been called yet for the VIMAGE case. * Tuneables will have been processed. We will print out values for * the default vnet. * XXX This should all be rationalized AFTER 8.0 */ if (V_fw_verbose == 0) printf("disabled\n"); else if (V_verbose_limit == 0) printf("unlimited\n"); else printf("limited to %d packets/entry by default\n", V_verbose_limit); /* Check user-supplied table count for validness */ if (default_fw_tables > IPFW_TABLES_MAX) default_fw_tables = IPFW_TABLES_MAX; ipfw_init_sopt_handler(); ipfw_init_obj_rewriter(); ipfw_iface_init(); return (error); } /* * Called for the removal of the last instance only on module unload. */ static void ipfw_destroy(void) { ipfw_iface_destroy(); ipfw_destroy_sopt_handler(); ipfw_destroy_obj_rewriter(); printf("IP firewall unloaded\n"); } /* * Stuff that must be initialized for every instance * (including the first of course). */ static int vnet_ipfw_init(const void *unused) { int error, first; struct ip_fw *rule = NULL; struct ip_fw_chain *chain; chain = &V_layer3_chain; first = IS_DEFAULT_VNET(curvnet) ? 1 : 0; /* First set up some values that are compile time options */ V_autoinc_step = 100; /* bounded to 1..1000 in add_rule() */ V_fw_deny_unknown_exthdrs = 1; #ifdef IPFIREWALL_VERBOSE V_fw_verbose = 1; #endif #ifdef IPFIREWALL_VERBOSE_LIMIT V_verbose_limit = IPFIREWALL_VERBOSE_LIMIT; #endif #ifdef IPFIREWALL_NAT LIST_INIT(&chain->nat); #endif /* Init shared services hash table */ ipfw_init_srv(chain); ipfw_init_counters(); /* Set initial number of tables */ V_fw_tables_max = default_fw_tables; error = ipfw_init_tables(chain, first); if (error) { printf("ipfw2: setting up tables failed\n"); free(chain->map, M_IPFW); free(rule, M_IPFW); return (ENOSPC); } IPFW_LOCK_INIT(chain); + ipfw_dyn_init(chain); /* fill and insert the default rule */ rule = ipfw_alloc_rule(chain, sizeof(struct ip_fw)); rule->flags |= IPFW_RULE_NOOPT; rule->cmd_len = 1; rule->cmd[0].len = 1; rule->cmd[0].opcode = default_to_accept ? O_ACCEPT : O_DENY; chain->default_rule = rule; ipfw_add_protected_rule(chain, rule, 0); - ipfw_dyn_init(chain); ipfw_eaction_init(chain, first); ipfw_init_skipto_cache(chain); ipfw_bpf_init(first); /* First set up some values that are compile time options */ V_ipfw_vnet_ready = 1; /* Open for business */ /* * Hook the sockopt handler and pfil hooks for ipv4 and ipv6. * Even if the latter two fail we still keep the module alive * because the sockopt and layer2 paths are still useful. * ipfw[6]_hook return 0 on success, ENOENT on failure, * so we can ignore the exact return value and just set a flag. * * Note that V_fw[6]_enable are manipulated by a SYSCTL_PROC so * changes in the underlying (per-vnet) variables trigger * immediate hook()/unhook() calls. * In layer2 we have the same behaviour, except that V_ether_ipfw * is checked on each packet because there are no pfil hooks. */ V_ip_fw_ctl_ptr = ipfw_ctl3; error = ipfw_attach_hooks(); return (error); } /* * Called for the removal of each instance. */ static int vnet_ipfw_uninit(const void *unused) { struct ip_fw *reap; struct ip_fw_chain *chain = &V_layer3_chain; int i, last; V_ipfw_vnet_ready = 0; /* tell new callers to go away */ /* * disconnect from ipv4, ipv6, layer2 and sockopt. * Then grab, release and grab again the WLOCK so we make * sure the update is propagated and nobody will be in. */ ipfw_detach_hooks(); V_ip_fw_ctl_ptr = NULL; last = IS_DEFAULT_VNET(curvnet) ? 1 : 0; IPFW_UH_WLOCK(chain); IPFW_UH_WUNLOCK(chain); ipfw_dyn_uninit(0); /* run the callout_drain */ IPFW_UH_WLOCK(chain); reap = NULL; IPFW_WLOCK(chain); for (i = 0; i < chain->n_rules; i++) ipfw_reap_add(chain, &reap, chain->map[i]); free(chain->map, M_IPFW); ipfw_destroy_skipto_cache(chain); IPFW_WUNLOCK(chain); IPFW_UH_WUNLOCK(chain); ipfw_destroy_tables(chain, last); ipfw_eaction_uninit(chain, last); if (reap != NULL) ipfw_reap_rules(reap); vnet_ipfw_iface_destroy(chain); ipfw_destroy_srv(chain); IPFW_LOCK_DESTROY(chain); ipfw_dyn_uninit(1); /* free the remaining parts */ ipfw_destroy_counters(); ipfw_bpf_uninit(last); return (0); } /* * Module event handler. * In general we have the choice of handling most of these events by the * event handler or by the (VNET_)SYS(UN)INIT handlers. I have chosen to * use the SYSINIT handlers as they are more capable of expressing the * flow of control during module and vnet operations, so this is just * a skeleton. Note there is no SYSINIT equivalent of the module * SHUTDOWN handler, but we don't have anything to do in that case anyhow. */ static int ipfw_modevent(module_t mod, int type, void *unused) { int err = 0; switch (type) { case MOD_LOAD: /* Called once at module load or * system boot if compiled in. */ break; case MOD_QUIESCE: /* Called before unload. May veto unloading. */ break; case MOD_UNLOAD: /* Called during unload. */ break; case MOD_SHUTDOWN: /* Called during system shutdown. */ break; default: err = EOPNOTSUPP; break; } return err; } static moduledata_t ipfwmod = { "ipfw", ipfw_modevent, 0 }; /* Define startup order. */ #define IPFW_SI_SUB_FIREWALL SI_SUB_PROTO_FIREWALL #define IPFW_MODEVENT_ORDER (SI_ORDER_ANY - 255) /* On boot slot in here. */ #define IPFW_MODULE_ORDER (IPFW_MODEVENT_ORDER + 1) /* A little later. */ #define IPFW_VNET_ORDER (IPFW_MODEVENT_ORDER + 2) /* Later still. */ DECLARE_MODULE(ipfw, ipfwmod, IPFW_SI_SUB_FIREWALL, IPFW_MODEVENT_ORDER); FEATURE(ipfw_ctl3, "ipfw new sockopt calls"); MODULE_VERSION(ipfw, 3); /* should declare some dependencies here */ /* * Starting up. Done in order after ipfwmod() has been called. * VNET_SYSINIT is also called for each existing vnet and each new vnet. */ SYSINIT(ipfw_init, IPFW_SI_SUB_FIREWALL, IPFW_MODULE_ORDER, ipfw_init, NULL); VNET_SYSINIT(vnet_ipfw_init, IPFW_SI_SUB_FIREWALL, IPFW_VNET_ORDER, vnet_ipfw_init, NULL); /* * Closing up shop. These are done in REVERSE ORDER, but still * after ipfwmod() has been called. Not called on reboot. * VNET_SYSUNINIT is also called for each exiting vnet as it exits. * or when the module is unloaded. */ SYSUNINIT(ipfw_destroy, IPFW_SI_SUB_FIREWALL, IPFW_MODULE_ORDER, ipfw_destroy, NULL); VNET_SYSUNINIT(vnet_ipfw_uninit, IPFW_SI_SUB_FIREWALL, IPFW_VNET_ORDER, vnet_ipfw_uninit, NULL); /* end of file */ diff --git a/sys/netpfil/ipfw/ip_fw_dynamic.c b/sys/netpfil/ipfw/ip_fw_dynamic.c index 9694c145e112..cfb686594c7c 100644 --- a/sys/netpfil/ipfw/ip_fw_dynamic.c +++ b/sys/netpfil/ipfw/ip_fw_dynamic.c @@ -1,3273 +1,3312 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2017-2025 Yandex LLC * Copyright (c) 2017-2025 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 #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 /* 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; uint8_t flags; /* internal flags */ uint32_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; /* packets counter in forward */ uint64_t bcnt_fwd; /* bytes counter in forward */ uint64_t pcnt_rev; /* packets counter in reverse */ uint64_t bcnt_rev; /* bytes 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 */ uint32_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 spare; uint32_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 | CTLFLAG_NEEDGIANT, 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 | CTLFLAG_NEEDGIANT, 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 | CTLFLAG_NEEDGIANT, 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, uint32_t); static struct dyn_ipv6_state *dyn_alloc_ipv6_state( const struct ipfw_flow_id *, uint32_t, uint32_t, uint8_t); static int dyn_add_ipv6_state(void *, uint32_t, uint32_t, const struct ipfw_flow_id *, uint32_t, const void *, int, uint32_t, struct ipfw_dyn_info *, uint16_t, uint32_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, uint32_t, uint32_t); static struct dyn_ipv6_state *dyn_lookup_ipv6_parent_locked( const struct ipfw_flow_id *, uint32_t, const void *, uint32_t, uint32_t, uint32_t); static struct dyn_ipv6_state *dyn_add_ipv6_parent(void *, uint32_t, uint32_t, const struct ipfw_flow_id *, uint32_t, uint32_t, uint32_t, uint32_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, uint32_t); static struct dyn_ipv4_state *dyn_lookup_ipv4_parent( const struct ipfw_flow_id *, const void *, uint32_t, uint32_t, uint32_t); static struct dyn_ipv4_state *dyn_lookup_ipv4_parent_locked( const struct ipfw_flow_id *, const void *, uint32_t, uint32_t, uint32_t); static struct dyn_parent *dyn_alloc_parent(void *, uint32_t, uint32_t, uint32_t); static struct dyn_ipv4_state *dyn_add_ipv4_parent(void *, uint32_t, uint32_t, const struct ipfw_flow_id *, uint32_t, uint32_t, uint32_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 *, uint32_t, uint8_t, ipfw_dyn_rule *); static void dyn_export_data(const struct dyn_data *, uint32_t, uint8_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, uint32_t); static struct dyn_ipv4_state *dyn_alloc_ipv4_state( const struct ipfw_flow_id *, uint32_t, uint8_t); static int dyn_add_ipv4_state(void *, uint32_t, uint32_t, const struct ipfw_flow_id *, const void *, int, uint32_t, struct ipfw_dyn_info *, uint16_t, uint32_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]; }; /* * Classifier callback. * Return 0 if opcode contains object that should be referenced * or rewritten. */ static int dyn_classify(ipfw_insn *cmd0, uint32_t *puidx, uint8_t *ptype) { ipfw_insn_kidx *cmd; if (F_LEN(cmd0) < 2) return (EINVAL); /* * NOTE: ipfw_insn_kidx and ipfw_insn_limit has overlapped kidx * field, so we can use one type to get access to kidx field. */ cmd = insntod(cmd0, kidx); DYN_DEBUG("opcode %u, kidx %u", cmd0->opcode, cmd->kidx); /* Don't rewrite "check-state any" */ if (cmd->kidx == 0 && cmd0->opcode == O_CHECK_STATE) return (1); *puidx = cmd->kidx; *ptype = 0; return (0); } static void dyn_update(ipfw_insn *cmd0, uint32_t idx) { insntod(cmd0, kidx)->kidx = idx; DYN_DEBUG("opcode %u, kidx %u", cmd0->opcode, idx); } 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 %u", 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, uint32_t idx) { DYN_DEBUG("kidx %u", idx); return (ipfw_objhash_lookup_kidx(CHAIN_TO_SRV(ch), idx)); } static int dyn_create(struct ip_fw_chain *ch, struct tid_info *ti, uint32_t *pkidx) { struct namedobj_instance *ni; struct dyn_state_obj *obj; struct named_object *no; ipfw_obj_ntlv *ntlv; char *name; DYN_DEBUG("uidx %u", 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 %u for name '%s'", *pkidx, no->name); 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 %u for name '%s'", *pkidx, name); 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 %u", 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 BOTH_RST (TH_RST | (TH_RST << 8)) #define TCP_FLAGS (BOTH_SYN | BOTH_FIN | BOTH_RST) #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, uint32_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, uint32_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, uint32_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)) return (in6_getscopezone(args->ifp, 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, uint32_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, uint32_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, uint32_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 */ static int dyn_handle_orphaned(struct ip_fw *old_rule, struct dyn_data *data) { struct ip_fw *rule; const ipfw_insn *cmd, *old_cmd; old_cmd = ACTION_PTR(old_rule); switch (old_cmd->opcode) { case O_SETMARK: case O_SKIPTO: /* * Rule pointer was changed. For O_SKIPTO action it can be * dangerous to keep use old rule. If new rule has the same * action and the same destination number, then use this dynamic * state. Otherwise it is better to create new one. */ rule = V_layer3_chain.map[data->f_pos]; cmd = ACTION_PTR(rule); if (cmd->opcode != old_cmd->opcode || cmd->len != old_cmd->len || cmd->arg1 != old_cmd->arg1 || insntoc(cmd, u32)->d[0] != insntoc(old_cmd, u32)->d[0]) return (-1); break; } return (0); } /* * 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); MPASS(F_LEN(cmd) >= F_INSN_SIZE(ipfw_insn_kidx)); data = NULL; rule = NULL; info->kidx = insntoc(cmd, kidx)->kidx; 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 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; } else if (V_dyn_keep_states != 0) { /* * 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); if (dyn_handle_orphaned(rule, data) == 0) { data->chain_id = V_layer3_chain.id; data->f_pos = V_layer3_chain.n_rules - 2; } else { rule = NULL; info->direction = MATCH_NONE; } } 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. */ } } 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, uint32_t rulenum, 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->hashval = hashval; limit->expire = time_uptime + V_dyn_short_lifetime; return (limit); } static struct dyn_data * dyn_alloc_dyndata(void *parent, uint32_t ruleid, uint32_t rulenum, 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->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, uint32_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, uint32_t rulenum, const struct ipfw_flow_id *pkt, uint32_t hashval, uint32_t version, uint32_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, 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, uint32_t rulenum, const struct ipfw_flow_id *pkt, const void *ulp, int pktlen, uint32_t hashval, struct ipfw_dyn_info *info, uint16_t fibnum, uint32_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, 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, uint32_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, uint32_t rulenum, const struct ipfw_flow_id *pkt, uint32_t zoneid, uint32_t hashval, uint32_t version, uint32_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, 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, uint32_t rulenum, 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, uint32_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, 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, uint32_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, 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, 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 '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, struct ip_fw *rule, struct ipfw_dyn_info *info, uint32_t limit, uint16_t limit_mask, uint32_t kidx, uint8_t type) { struct ipfw_flow_id id; uint32_t hashval, parent_hashval, ruleid, rulenum; int ret; MPASS(type == O_LIMIT || type == O_KEEP_STATE); ruleid = rule->id; rulenum = rule->rulenum; 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, 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, 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; } /* * NOTE: we assume that kidx field of struct ipfw_insn_kidx * located in the same place as kidx field of ipfw_insn_limit. */ 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, info, limit, limit_mask, cmd->kidx, 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: * 0 when state is not matched by specified range; * 1 when state is matched by specified range; * 2 when state is matched by specified range and requested deletion of * dynamic states. */ static int dyn_match_range(uint32_t rulenum, uint8_t set, const ipfw_range_tlv *rt) { MPASS(rt != NULL); /* flush all states */ if (rt->flags & IPFW_RCFLAG_ALL) { if (rt->flags & IPFW_RCFLAG_DYNAMIC) return (2); /* forced */ 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); if (rt->flags & IPFW_RCFLAG_DYNAMIC) return (2); return (1); } static void dyn_acquire_rule(struct ip_fw_chain *ch, struct dyn_data *data, struct ip_fw *rule, uint32_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, uint32_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 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) { rule = s->limit->parent; return (dyn_match_range(s->limit->rulenum, rule->set, rt)); } rule = s->data->parent; if (s->type == O_LIMIT) rule = ((struct dyn_ipv4_state *)rule)->limit->parent; ret = dyn_match_range(s->data->rulenum, rule->set, rt); if (ret == 0 || V_dyn_keep_states == 0 || ret > 1) return (ret); dyn_acquire_rule(ch, s->data, rule, s->kidx); return (0); } #ifdef INET6 static int 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) { rule = s->limit->parent; return (dyn_match_range(s->limit->rulenum, rule->set, rt)); } rule = s->data->parent; if (s->type == O_LIMIT) rule = ((struct dyn_ipv6_state *)rule)->limit->parent; ret = dyn_match_range(s->data->rulenum, rule->set, rt); if (ret == 0 || V_dyn_keep_states == 0 || ret > 1) return (ret); 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 *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(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_set_flags(tcp, 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 within 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_set_flags(tcp, 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 within 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, int flags) { #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. */ bucket_lock = malloc(new * sizeof(struct mtx), M_IPFW, flags | M_ZERO); if (bucket_lock == NULL) return; ipv4 = ipv4_parent = NULL; ipv4_add = ipv4_del = ipv4_parent_add = ipv4_parent_del = NULL; #ifdef INET6 ipv6 = ipv6_parent = NULL; ipv6_add = ipv6_del = ipv6_parent_add = ipv6_parent_del = NULL; #endif ipv4 = malloc(new * sizeof(struct dyn_ipv4ck_slist), M_IPFW, flags | M_ZERO); if (ipv4 == NULL) goto bad; ipv4_parent = malloc(new * sizeof(struct dyn_ipv4ck_slist), M_IPFW, flags | M_ZERO); if (ipv4_parent == NULL) goto bad; ipv4_add = malloc(new * sizeof(uint32_t), M_IPFW, flags | M_ZERO); if (ipv4_add == NULL) goto bad; ipv4_del = malloc(new * sizeof(uint32_t), M_IPFW, flags | M_ZERO); if (ipv4_del == NULL) goto bad; ipv4_parent_add = malloc(new * sizeof(uint32_t), M_IPFW, flags | M_ZERO); if (ipv4_parent_add == NULL) goto bad; ipv4_parent_del = malloc(new * sizeof(uint32_t), M_IPFW, flags | M_ZERO); if (ipv4_parent_del == NULL) goto bad; #ifdef INET6 ipv6 = malloc(new * sizeof(struct dyn_ipv6ck_slist), M_IPFW, flags | M_ZERO); if (ipv6 == NULL) goto bad; ipv6_parent = malloc(new * sizeof(struct dyn_ipv6ck_slist), M_IPFW, flags | M_ZERO); if (ipv6_parent == NULL) goto bad; ipv6_add = malloc(new * sizeof(uint32_t), M_IPFW, flags | M_ZERO); if (ipv6_add == NULL) goto bad; ipv6_del = malloc(new * sizeof(uint32_t), M_IPFW, flags | M_ZERO); if (ipv6_del == NULL) goto bad; ipv6_parent_add = malloc(new * sizeof(uint32_t), M_IPFW, flags | M_ZERO); if (ipv6_parent_add == NULL) goto bad; ipv6_parent_del = malloc(new * sizeof(uint32_t), M_IPFW, flags | M_ZERO); if (ipv6_parent_del == NULL) goto bad; #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); bad: 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) { struct epoch_tracker et; 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; NET_EPOCH_ENTER(et); dyn_send_keepalive_ipv4(&V_layer3_chain); #ifdef INET6 dyn_send_keepalive_ipv6(&V_layer3_chain); #endif NET_EPOCH_EXIT(et); } /* * 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, M_NOWAIT); } 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, uint32_t eaction_id, uint32_t default_id, uint32_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(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, uint32_t kidx, uint8_t set, ipfw_dyn_rule *dst) { dst->type = O_LIMIT_PARENT; dst->set = set; dst->kidx = kidx; dst->rulenum = p->rulenum; dst->count = DPARENT_COUNT(p); dst->expire = TIME_LEQ(p->expire, time_uptime) ? 0: p->expire - time_uptime; dst->hashval = p->hashval; /* unused fields */ dst->pad = 0; dst->pcnt = 0; dst->bcnt = 0; dst->ack_fwd = 0; dst->ack_rev = 0; } static void dyn_export_data(const struct dyn_data *data, uint32_t kidx, uint8_t type, uint8_t set, ipfw_dyn_rule *dst) { dst->type = type; dst->set = set; dst->kidx = kidx; dst->rulenum = data->rulenum; 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; dst->state = data->state; if (data->flags & DYN_REFERENCED) dst->state |= IPFW_DYN_ORPHANED; dst->ack_fwd = data->ack_fwd; dst->ack_rev = data->ack_rev; dst->hashval = data->hashval; } static void dyn_export_ipv4_state(const struct dyn_ipv4_state *s, ipfw_dyn_rule *dst) { struct ip_fw *rule; switch (s->type) { case O_LIMIT_PARENT: rule = s->limit->parent; dyn_export_parent(s->limit, s->kidx, rule->set, dst); break; default: rule = s->data->parent; if (s->type == O_LIMIT) rule = ((struct dyn_ipv4_state *)rule)->limit->parent; dyn_export_data(s->data, s->kidx, s->type, rule->set, 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) { struct ip_fw *rule; switch (s->type) { case O_LIMIT_PARENT: rule = s->limit->parent; dyn_export_parent(s->limit, s->kidx, rule->set, dst); break; default: rule = s->data->parent; if (s->type == O_LIMIT) rule = ((struct dyn_ipv6_state *)rule)->limit->parent; dyn_export_data(s->data, s->kidx, s->type, rule->set, 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 } +/* + * When we have enabled V_dyn_keep_states, states that become ORPHANED + * will keep pointer to original rule. Then this rule pointer is used + * to apply rule action after ipfw_dyn_lookup_state(). + * Some rule actions use IPFW_INC_RULE_COUNTER() directly to this rule + * pointer, but other actions use chain->map[f_pos] instead. The last + * case leads to incrementing counters on the wrong rule, because + * ORPHANED states have not parent rule in chain->map[]. + * To solve this we add protected rule: + * count ip from any to any not // comment + * It will be matched only by packets that are handled by ORPHANED states. + */ +static void +dyn_add_protected_rule(struct ip_fw_chain *chain) +{ + static const char *comment = + "orphaned dynamic states counter"; + struct ip_fw *rule; + ipfw_insn *cmd; + size_t l; + + l = roundup(strlen(comment) + 1, sizeof(uint32_t)); + rule = ipfw_alloc_rule(chain, sizeof(*rule) + sizeof(ipfw_insn) + l); + cmd = rule->cmd; + cmd->opcode = O_NOP; + cmd->len = 1 + l/sizeof(uint32_t); + cmd->len |= F_NOT; /* make rule to be not matched */ + strcpy((char *)(cmd + 1), comment); + cmd += F_LEN(cmd); + + cmd->len = 1; + cmd->opcode = O_COUNT; + rule->act_ofs = cmd - rule->cmd; + rule->cmd_len = rule->act_ofs + 1; + ipfw_add_protected_rule(chain, rule, 0); +} + 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, M_WAITOK); 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); + + dyn_add_protected_rule(chain); } 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); }