Index: stable/9/sys/contrib/pf/net/pf.c =================================================================== --- stable/9/sys/contrib/pf/net/pf.c (revision 292730) +++ stable/9/sys/contrib/pf/net/pf.c (revision 292731) @@ -1,7620 +1,7670 @@ /* $OpenBSD: pf.c,v 1.634 2009/02/27 12:37:45 henning Exp $ */ /* * Copyright (c) 2001 Daniel Hartmeier * Copyright (c) 2002 - 2008 Henning Brauer * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - 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 COPYRIGHT HOLDERS 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 * COPYRIGHT HOLDERS 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. * * Effort sponsored in part by the Defense Advanced Research Projects * Agency (DARPA) and Air Force Research Laboratory, Air Force * Materiel Command, USAF, under agreement number F30602-01-2-0537. * */ #ifdef __FreeBSD__ #include "opt_inet.h" #include "opt_inet6.h" #include __FBSDID("$FreeBSD$"); #endif #ifdef __FreeBSD__ #include "opt_bpf.h" #include "opt_pf.h" #define NPFSYNC 1 #ifdef DEV_PFLOW #define NPFLOW DEV_PFLOW #else #define NPFLOW 0 #endif #else #include "bpfilter.h" #include "pflog.h" #include "pfsync.h" #include "pflow.h" #endif #include #include #include #include #include #include #include #include #ifdef __FreeBSD__ #include #include #include #define betoh64 be64toh #else #include #endif #include #ifdef __FreeBSD__ #include #include #include #else #include #endif #ifdef __FreeBSD__ #include #else #include #endif #include #include #include #include #ifdef __FreeBSD__ #ifdef RADIX_MPATH #include #endif #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __FreeBSD__ #include #include /* XXX: only for DIR_IN/DIR_OUT */ #endif #ifndef __FreeBSD__ #include #endif #include #include #include #include #ifdef INET6 #include #include #include #include #ifdef __FreeBSD__ #include #include #endif #endif /* INET6 */ #ifdef __FreeBSD__ #include #include #include #include extern int ip_optcopy(struct ip *, struct ip *); #endif #ifdef __FreeBSD__ #define DPFPRINTF(n, x) if (V_pf_status.debug >= (n)) printf x #else #define DPFPRINTF(n, x) if (pf_status.debug >= (n)) printf x #endif /* * Global variables */ /* state tables */ #ifdef __FreeBSD__ VNET_DEFINE(struct pf_state_tree, pf_statetbl); VNET_DEFINE(struct pf_altqqueue, pf_altqs[2]); VNET_DEFINE(struct pf_palist, pf_pabuf); VNET_DEFINE(struct pf_altqqueue *, pf_altqs_active); VNET_DEFINE(struct pf_altqqueue *, pf_altqs_inactive); VNET_DEFINE(struct pf_status, pf_status); VNET_DEFINE(u_int32_t, ticket_altqs_active); VNET_DEFINE(u_int32_t, ticket_altqs_inactive); VNET_DEFINE(int, altqs_inactive_open); VNET_DEFINE(u_int32_t, ticket_pabuf); VNET_DEFINE(MD5_CTX, pf_tcp_secret_ctx); #define V_pf_tcp_secret_ctx VNET(pf_tcp_secret_ctx) VNET_DEFINE(u_char, pf_tcp_secret[16]); #define V_pf_tcp_secret VNET(pf_tcp_secret) VNET_DEFINE(int, pf_tcp_secret_init); #define V_pf_tcp_secret_init VNET(pf_tcp_secret_init) VNET_DEFINE(int, pf_tcp_iss_off); #define V_pf_tcp_iss_off VNET(pf_tcp_iss_off) struct pf_anchor_stackframe { struct pf_ruleset *rs; struct pf_rule *r; struct pf_anchor_node *parent; struct pf_anchor *child; }; VNET_DEFINE(struct pf_anchor_stackframe, pf_anchor_stack[64]); #define V_pf_anchor_stack VNET(pf_anchor_stack) VNET_DEFINE(uma_zone_t, pf_src_tree_pl); VNET_DEFINE(uma_zone_t, pf_rule_pl); VNET_DEFINE(uma_zone_t, pf_pooladdr_pl); VNET_DEFINE(uma_zone_t, pf_state_pl); VNET_DEFINE(uma_zone_t, pf_state_key_pl); VNET_DEFINE(uma_zone_t, pf_state_item_pl); VNET_DEFINE(uma_zone_t, pf_altq_pl); #else struct pf_state_tree pf_statetbl; struct pf_altqqueue pf_altqs[2]; struct pf_palist pf_pabuf; struct pf_altqqueue *pf_altqs_active; struct pf_altqqueue *pf_altqs_inactive; struct pf_status pf_status; u_int32_t ticket_altqs_active; u_int32_t ticket_altqs_inactive; int altqs_inactive_open; u_int32_t ticket_pabuf; MD5_CTX pf_tcp_secret_ctx; u_char pf_tcp_secret[16]; int pf_tcp_secret_init; int pf_tcp_iss_off; struct pf_anchor_stackframe { struct pf_ruleset *rs; struct pf_rule *r; struct pf_anchor_node *parent; struct pf_anchor *child; } pf_anchor_stack[64]; struct pool pf_src_tree_pl, pf_rule_pl, pf_pooladdr_pl; struct pool pf_state_pl, pf_state_key_pl, pf_state_item_pl; struct pool pf_altq_pl; #endif void pf_init_threshold(struct pf_threshold *, u_int32_t, u_int32_t); void pf_add_threshold(struct pf_threshold *); int pf_check_threshold(struct pf_threshold *); -void pf_change_ap(struct pf_addr *, u_int16_t *, +void pf_change_ap(struct mbuf *, struct pf_addr *, u_int16_t *, u_int16_t *, u_int16_t *, struct pf_addr *, u_int16_t, u_int8_t, sa_family_t); int pf_modulate_sack(struct mbuf *, int, struct pf_pdesc *, struct tcphdr *, struct pf_state_peer *); #ifdef INET6 void pf_change_a6(struct pf_addr *, u_int16_t *, struct pf_addr *, u_int8_t); #endif /* INET6 */ void pf_change_icmp(struct pf_addr *, u_int16_t *, struct pf_addr *, struct pf_addr *, u_int16_t, u_int16_t *, u_int16_t *, u_int16_t *, u_int16_t *, u_int8_t, sa_family_t); #ifdef __FreeBSD__ void pf_send_tcp(struct mbuf *, const struct pf_rule *, sa_family_t, #else void pf_send_tcp(const struct pf_rule *, sa_family_t, #endif const struct pf_addr *, const struct pf_addr *, u_int16_t, u_int16_t, u_int32_t, u_int32_t, u_int8_t, u_int16_t, u_int16_t, u_int8_t, int, u_int16_t, struct ether_header *, struct ifnet *); static void pf_send_icmp(struct mbuf *, u_int8_t, u_int8_t, sa_family_t, struct pf_rule *); void pf_detach_state(struct pf_state *); void pf_state_key_detach(struct pf_state *, int); u_int32_t pf_tcp_iss(struct pf_pdesc *); int pf_test_rule(struct pf_rule **, struct pf_state **, int, struct pfi_kif *, struct mbuf *, int, void *, struct pf_pdesc *, struct pf_rule **, #ifdef __FreeBSD__ struct pf_ruleset **, struct ifqueue *, struct inpcb *); #else struct pf_ruleset **, struct ifqueue *); #endif static __inline int pf_create_state(struct pf_rule *, struct pf_rule *, struct pf_rule *, struct pf_pdesc *, struct pf_src_node *, struct pf_state_key *, struct pf_state_key *, struct pf_state_key *, struct pf_state_key *, struct mbuf *, int, u_int16_t, u_int16_t, int *, struct pfi_kif *, struct pf_state **, int, u_int16_t, u_int16_t, int); int pf_test_fragment(struct pf_rule **, int, struct pfi_kif *, struct mbuf *, void *, struct pf_pdesc *, struct pf_rule **, struct pf_ruleset **); int pf_tcp_track_full(struct pf_state_peer *, struct pf_state_peer *, struct pf_state **, struct pfi_kif *, struct mbuf *, int, struct pf_pdesc *, u_short *, int *); int pf_tcp_track_sloppy(struct pf_state_peer *, struct pf_state_peer *, struct pf_state **, struct pf_pdesc *, u_short *); int pf_test_state_tcp(struct pf_state **, int, struct pfi_kif *, struct mbuf *, int, void *, struct pf_pdesc *, u_short *); int pf_test_state_udp(struct pf_state **, int, struct pfi_kif *, struct mbuf *, int, void *, struct pf_pdesc *); int pf_test_state_icmp(struct pf_state **, int, struct pfi_kif *, struct mbuf *, int, void *, struct pf_pdesc *, u_short *); int pf_test_state_other(struct pf_state **, int, struct pfi_kif *, struct mbuf *, struct pf_pdesc *); void pf_route(struct mbuf **, struct pf_rule *, int, struct ifnet *, struct pf_state *, struct pf_pdesc *); void pf_route6(struct mbuf **, struct pf_rule *, int, struct ifnet *, struct pf_state *, struct pf_pdesc *); #ifndef __FreeBSD__ int pf_socket_lookup(int, struct pf_pdesc *); #endif u_int8_t pf_get_wscale(struct mbuf *, int, u_int16_t, sa_family_t); u_int16_t pf_get_mss(struct mbuf *, int, u_int16_t, sa_family_t); u_int16_t pf_calc_mss(struct pf_addr *, sa_family_t, int, u_int16_t); void pf_set_rt_ifp(struct pf_state *, struct pf_addr *); int pf_check_proto_cksum(struct mbuf *, int, int, u_int8_t, sa_family_t); #ifndef __FreeBSD__ struct pf_divert *pf_get_divert(struct mbuf *); #endif void pf_print_state_parts(struct pf_state *, struct pf_state_key *, struct pf_state_key *); int pf_addr_wrap_neq(struct pf_addr_wrap *, struct pf_addr_wrap *); int pf_compare_state_keys(struct pf_state_key *, struct pf_state_key *, struct pfi_kif *, u_int); #ifdef __FreeBSD__ struct pf_state *pf_find_state(struct pfi_kif *, struct pf_state_key_cmp *, u_int, struct mbuf *, struct pf_mtag *); #else struct pf_state *pf_find_state(struct pfi_kif *, struct pf_state_key_cmp *, u_int, struct mbuf *); #endif int pf_src_connlimit(struct pf_state **); int pf_check_congestion(struct ifqueue *); #ifdef __FreeBSD__ int in4_cksum(struct mbuf *m, u_int8_t nxt, int off, int len); VNET_DECLARE(int, pf_end_threads); VNET_DEFINE(struct pf_pool_limit, pf_pool_limits[PF_LIMIT_MAX]); #else extern struct pool pfr_ktable_pl; extern struct pool pfr_kentry_pl; struct pf_pool_limit pf_pool_limits[PF_LIMIT_MAX] = { { &pf_state_pl, PFSTATE_HIWAT }, { &pf_src_tree_pl, PFSNODE_HIWAT }, { &pf_frent_pl, PFFRAG_FRENT_HIWAT }, { &pfr_ktable_pl, PFR_KTABLE_HIWAT }, { &pfr_kentry_pl, PFR_KENTRY_HIWAT } }; #endif #ifdef __FreeBSD__ #define PPACKET_LOOPED() \ (pd->pf_mtag->flags & PF_PACKET_LOOPED) #define PACKET_LOOPED() \ (pd.pf_mtag->flags & PF_PACKET_LOOPED) #define STATE_LOOKUP(i, k, d, s, m, pt) \ do { \ s = pf_find_state(i, k, d, m, pt); \ if (s == NULL || (s)->timeout == PFTM_PURGE) \ return (PF_DROP); \ if (PPACKET_LOOPED()) \ return (PF_PASS); \ if (d == PF_OUT && \ (((s)->rule.ptr->rt == PF_ROUTETO && \ (s)->rule.ptr->direction == PF_OUT) || \ ((s)->rule.ptr->rt == PF_REPLYTO && \ (s)->rule.ptr->direction == PF_IN)) && \ (s)->rt_kif != NULL && \ (s)->rt_kif != i) \ return (PF_PASS); \ } while (0) #else #define STATE_LOOKUP(i, k, d, s, m) \ do { \ s = pf_find_state(i, k, d, m); \ if (s == NULL || (s)->timeout == PFTM_PURGE) \ return (PF_DROP); \ if (d == PF_OUT && \ (((s)->rule.ptr->rt == PF_ROUTETO && \ (s)->rule.ptr->direction == PF_OUT) || \ ((s)->rule.ptr->rt == PF_REPLYTO && \ (s)->rule.ptr->direction == PF_IN)) && \ (s)->rt_kif != NULL && \ (s)->rt_kif != i) \ return (PF_PASS); \ } while (0) #endif #ifdef __FreeBSD__ #define BOUND_IFACE(r, k) \ ((r)->rule_flag & PFRULE_IFBOUND) ? (k) : V_pfi_all #else #define BOUND_IFACE(r, k) \ ((r)->rule_flag & PFRULE_IFBOUND) ? (k) : pfi_all #endif #define STATE_INC_COUNTERS(s) \ do { \ s->rule.ptr->states_cur++; \ s->rule.ptr->states_tot++; \ if (s->anchor.ptr != NULL) { \ s->anchor.ptr->states_cur++; \ s->anchor.ptr->states_tot++; \ } \ if (s->nat_rule.ptr != NULL) { \ s->nat_rule.ptr->states_cur++; \ s->nat_rule.ptr->states_tot++; \ } \ } while (0) #define STATE_DEC_COUNTERS(s) \ do { \ if (s->nat_rule.ptr != NULL) \ s->nat_rule.ptr->states_cur--; \ if (s->anchor.ptr != NULL) \ s->anchor.ptr->states_cur--; \ s->rule.ptr->states_cur--; \ } while (0) static __inline int pf_src_compare(struct pf_src_node *, struct pf_src_node *); static __inline int pf_state_compare_key(struct pf_state_key *, struct pf_state_key *); static __inline int pf_state_compare_id(struct pf_state *, struct pf_state *); #ifdef __FreeBSD__ VNET_DEFINE(struct pf_src_tree, tree_src_tracking); VNET_DEFINE(struct pf_state_tree_id, tree_id); VNET_DEFINE(struct pf_state_queue, state_list); #else struct pf_src_tree tree_src_tracking; struct pf_state_tree_id tree_id; struct pf_state_queue state_list; #endif RB_GENERATE(pf_src_tree, pf_src_node, entry, pf_src_compare); RB_GENERATE(pf_state_tree, pf_state_key, entry, pf_state_compare_key); RB_GENERATE(pf_state_tree_id, pf_state, entry_id, pf_state_compare_id); static __inline int pf_src_compare(struct pf_src_node *a, struct pf_src_node *b) { int diff; if (a->rule.ptr > b->rule.ptr) return (1); if (a->rule.ptr < b->rule.ptr) return (-1); if ((diff = a->af - b->af) != 0) return (diff); switch (a->af) { #ifdef INET case AF_INET: if (a->addr.addr32[0] > b->addr.addr32[0]) return (1); if (a->addr.addr32[0] < b->addr.addr32[0]) return (-1); break; #endif /* INET */ #ifdef INET6 case AF_INET6: if (a->addr.addr32[3] > b->addr.addr32[3]) return (1); if (a->addr.addr32[3] < b->addr.addr32[3]) return (-1); if (a->addr.addr32[2] > b->addr.addr32[2]) return (1); if (a->addr.addr32[2] < b->addr.addr32[2]) return (-1); if (a->addr.addr32[1] > b->addr.addr32[1]) return (1); if (a->addr.addr32[1] < b->addr.addr32[1]) return (-1); if (a->addr.addr32[0] > b->addr.addr32[0]) return (1); if (a->addr.addr32[0] < b->addr.addr32[0]) return (-1); break; #endif /* INET6 */ } return (0); } #ifdef INET6 void pf_addrcpy(struct pf_addr *dst, struct pf_addr *src, sa_family_t af) { switch (af) { #ifdef INET case AF_INET: dst->addr32[0] = src->addr32[0]; break; #endif /* INET */ case AF_INET6: dst->addr32[0] = src->addr32[0]; dst->addr32[1] = src->addr32[1]; dst->addr32[2] = src->addr32[2]; dst->addr32[3] = src->addr32[3]; break; } } #endif /* INET6 */ void pf_init_threshold(struct pf_threshold *threshold, u_int32_t limit, u_int32_t seconds) { threshold->limit = limit * PF_THRESHOLD_MULT; threshold->seconds = seconds; threshold->count = 0; threshold->last = time_second; } void pf_add_threshold(struct pf_threshold *threshold) { u_int32_t t = time_second, diff = t - threshold->last; if (diff >= threshold->seconds) threshold->count = 0; else threshold->count -= threshold->count * diff / threshold->seconds; threshold->count += PF_THRESHOLD_MULT; threshold->last = t; } int pf_check_threshold(struct pf_threshold *threshold) { return (threshold->count > threshold->limit); } int pf_src_connlimit(struct pf_state **state) { int bad = 0; (*state)->src_node->conn++; (*state)->src.tcp_est = 1; pf_add_threshold(&(*state)->src_node->conn_rate); if ((*state)->rule.ptr->max_src_conn && (*state)->rule.ptr->max_src_conn < (*state)->src_node->conn) { #ifdef __FreeBSD__ V_pf_status.lcounters[LCNT_SRCCONN]++; #else pf_status.lcounters[LCNT_SRCCONN]++; #endif bad++; } if ((*state)->rule.ptr->max_src_conn_rate.limit && pf_check_threshold(&(*state)->src_node->conn_rate)) { #ifdef __FreeBSD__ V_pf_status.lcounters[LCNT_SRCCONNRATE]++; #else pf_status.lcounters[LCNT_SRCCONNRATE]++; #endif bad++; } if (!bad) return (0); if ((*state)->rule.ptr->overload_tbl) { struct pfr_addr p; u_int32_t killed = 0; #ifdef __FreeBSD__ V_pf_status.lcounters[LCNT_OVERLOAD_TABLE]++; if (V_pf_status.debug >= PF_DEBUG_MISC) { #else pf_status.lcounters[LCNT_OVERLOAD_TABLE]++; if (pf_status.debug >= PF_DEBUG_MISC) { #endif printf("pf_src_connlimit: blocking address "); pf_print_host(&(*state)->src_node->addr, 0, (*state)->key[PF_SK_WIRE]->af); } bzero(&p, sizeof(p)); p.pfra_af = (*state)->key[PF_SK_WIRE]->af; switch ((*state)->key[PF_SK_WIRE]->af) { #ifdef INET case AF_INET: p.pfra_net = 32; p.pfra_ip4addr = (*state)->src_node->addr.v4; break; #endif /* INET */ #ifdef INET6 case AF_INET6: p.pfra_net = 128; p.pfra_ip6addr = (*state)->src_node->addr.v6; break; #endif /* INET6 */ } pfr_insert_kentry((*state)->rule.ptr->overload_tbl, &p, time_second); /* kill existing states if that's required. */ if ((*state)->rule.ptr->flush) { struct pf_state_key *sk; struct pf_state *st; #ifdef __FreeBSD__ V_pf_status.lcounters[LCNT_OVERLOAD_FLUSH]++; RB_FOREACH(st, pf_state_tree_id, &V_tree_id) { #else pf_status.lcounters[LCNT_OVERLOAD_FLUSH]++; RB_FOREACH(st, pf_state_tree_id, &tree_id) { #endif sk = st->key[PF_SK_WIRE]; /* * Kill states from this source. (Only those * from the same rule if PF_FLUSH_GLOBAL is not * set) */ if (sk->af == (*state)->key[PF_SK_WIRE]->af && (((*state)->direction == PF_OUT && PF_AEQ(&(*state)->src_node->addr, &sk->addr[1], sk->af)) || ((*state)->direction == PF_IN && PF_AEQ(&(*state)->src_node->addr, &sk->addr[0], sk->af))) && ((*state)->rule.ptr->flush & PF_FLUSH_GLOBAL || (*state)->rule.ptr == st->rule.ptr)) { st->timeout = PFTM_PURGE; st->src.state = st->dst.state = TCPS_CLOSED; killed++; } } #ifdef __FreeBSD__ if (V_pf_status.debug >= PF_DEBUG_MISC) #else if (pf_status.debug >= PF_DEBUG_MISC) #endif printf(", %u states killed", killed); } #ifdef __FreeBSD__ if (V_pf_status.debug >= PF_DEBUG_MISC) #else if (pf_status.debug >= PF_DEBUG_MISC) #endif printf("\n"); } /* kill this state */ (*state)->timeout = PFTM_PURGE; (*state)->src.state = (*state)->dst.state = TCPS_CLOSED; return (1); } int pf_insert_src_node(struct pf_src_node **sn, struct pf_rule *rule, struct pf_addr *src, sa_family_t af) { struct pf_src_node k; if (*sn == NULL) { k.af = af; PF_ACPY(&k.addr, src, af); if (rule->rule_flag & PFRULE_RULESRCTRACK || rule->rpool.opts & PF_POOL_STICKYADDR) k.rule.ptr = rule; else k.rule.ptr = NULL; #ifdef __FreeBSD__ V_pf_status.scounters[SCNT_SRC_NODE_SEARCH]++; *sn = RB_FIND(pf_src_tree, &V_tree_src_tracking, &k); #else pf_status.scounters[SCNT_SRC_NODE_SEARCH]++; *sn = RB_FIND(pf_src_tree, &tree_src_tracking, &k); #endif } if (*sn == NULL) { if (!rule->max_src_nodes || rule->src_nodes < rule->max_src_nodes) #ifdef __FreeBSD__ (*sn) = pool_get(&V_pf_src_tree_pl, PR_NOWAIT | PR_ZERO); #else (*sn) = pool_get(&pf_src_tree_pl, PR_NOWAIT | PR_ZERO); #endif else #ifdef __FreeBSD__ V_pf_status.lcounters[LCNT_SRCNODES]++; #else pf_status.lcounters[LCNT_SRCNODES]++; #endif if ((*sn) == NULL) return (-1); pf_init_threshold(&(*sn)->conn_rate, rule->max_src_conn_rate.limit, rule->max_src_conn_rate.seconds); (*sn)->af = af; if (rule->rule_flag & PFRULE_RULESRCTRACK || rule->rpool.opts & PF_POOL_STICKYADDR) (*sn)->rule.ptr = rule; else (*sn)->rule.ptr = NULL; PF_ACPY(&(*sn)->addr, src, af); if (RB_INSERT(pf_src_tree, #ifdef __FreeBSD__ &V_tree_src_tracking, *sn) != NULL) { if (V_pf_status.debug >= PF_DEBUG_MISC) { #else &tree_src_tracking, *sn) != NULL) { if (pf_status.debug >= PF_DEBUG_MISC) { #endif printf("pf: src_tree insert failed: "); pf_print_host(&(*sn)->addr, 0, af); printf("\n"); } #ifdef __FreeBSD__ pool_put(&V_pf_src_tree_pl, *sn); #else pool_put(&pf_src_tree_pl, *sn); #endif return (-1); } (*sn)->creation = time_second; (*sn)->ruletype = rule->action; if ((*sn)->rule.ptr != NULL) (*sn)->rule.ptr->src_nodes++; #ifdef __FreeBSD__ V_pf_status.scounters[SCNT_SRC_NODE_INSERT]++; V_pf_status.src_nodes++; #else pf_status.scounters[SCNT_SRC_NODE_INSERT]++; pf_status.src_nodes++; #endif } else { if (rule->max_src_states && (*sn)->states >= rule->max_src_states) { #ifdef __FreeBSD__ V_pf_status.lcounters[LCNT_SRCSTATES]++; #else pf_status.lcounters[LCNT_SRCSTATES]++; #endif return (-1); } } return (0); } /* state table stuff */ static __inline int pf_state_compare_key(struct pf_state_key *a, struct pf_state_key *b) { int diff; if ((diff = a->proto - b->proto) != 0) return (diff); if ((diff = a->af - b->af) != 0) return (diff); switch (a->af) { #ifdef INET case AF_INET: if (a->addr[0].addr32[0] > b->addr[0].addr32[0]) return (1); if (a->addr[0].addr32[0] < b->addr[0].addr32[0]) return (-1); if (a->addr[1].addr32[0] > b->addr[1].addr32[0]) return (1); if (a->addr[1].addr32[0] < b->addr[1].addr32[0]) return (-1); break; #endif /* INET */ #ifdef INET6 case AF_INET6: if (a->addr[0].addr32[3] > b->addr[0].addr32[3]) return (1); if (a->addr[0].addr32[3] < b->addr[0].addr32[3]) return (-1); if (a->addr[1].addr32[3] > b->addr[1].addr32[3]) return (1); if (a->addr[1].addr32[3] < b->addr[1].addr32[3]) return (-1); if (a->addr[0].addr32[2] > b->addr[0].addr32[2]) return (1); if (a->addr[0].addr32[2] < b->addr[0].addr32[2]) return (-1); if (a->addr[1].addr32[2] > b->addr[1].addr32[2]) return (1); if (a->addr[1].addr32[2] < b->addr[1].addr32[2]) return (-1); if (a->addr[0].addr32[1] > b->addr[0].addr32[1]) return (1); if (a->addr[0].addr32[1] < b->addr[0].addr32[1]) return (-1); if (a->addr[1].addr32[1] > b->addr[1].addr32[1]) return (1); if (a->addr[1].addr32[1] < b->addr[1].addr32[1]) return (-1); if (a->addr[0].addr32[0] > b->addr[0].addr32[0]) return (1); if (a->addr[0].addr32[0] < b->addr[0].addr32[0]) return (-1); if (a->addr[1].addr32[0] > b->addr[1].addr32[0]) return (1); if (a->addr[1].addr32[0] < b->addr[1].addr32[0]) return (-1); break; #endif /* INET6 */ } if ((diff = a->port[0] - b->port[0]) != 0) return (diff); if ((diff = a->port[1] - b->port[1]) != 0) return (diff); return (0); } static __inline int pf_state_compare_id(struct pf_state *a, struct pf_state *b) { if (a->id > b->id) return (1); if (a->id < b->id) return (-1); if (a->creatorid > b->creatorid) return (1); if (a->creatorid < b->creatorid) return (-1); return (0); } int pf_state_key_attach(struct pf_state_key *sk, struct pf_state *s, int idx) { struct pf_state_item *si; struct pf_state_key *cur; struct pf_state *olds = NULL; #ifdef __FreeBSD__ KASSERT(s->key[idx] == NULL, ("%s: key is null!", __FUNCTION__)); #else KASSERT(s->key[idx] == NULL); /* XXX handle this? */ #endif #ifdef __FreeBSD__ if ((cur = RB_INSERT(pf_state_tree, &V_pf_statetbl, sk)) != NULL) { #else if ((cur = RB_INSERT(pf_state_tree, &pf_statetbl, sk)) != NULL) { #endif /* key exists. check for same kif, if none, add to key */ TAILQ_FOREACH(si, &cur->states, entry) if (si->s->kif == s->kif && si->s->direction == s->direction) { if (sk->proto == IPPROTO_TCP && si->s->src.state >= TCPS_FIN_WAIT_2 && si->s->dst.state >= TCPS_FIN_WAIT_2) { si->s->src.state = si->s->dst.state = TCPS_CLOSED; /* unlink late or sks can go away */ olds = si->s; } else { #ifdef __FreeBSD__ if (V_pf_status.debug >= PF_DEBUG_MISC) { #else if (pf_status.debug >= PF_DEBUG_MISC) { #endif printf("pf: %s key attach " "failed on %s: ", (idx == PF_SK_WIRE) ? "wire" : "stack", s->kif->pfik_name); pf_print_state_parts(s, (idx == PF_SK_WIRE) ? sk : NULL, (idx == PF_SK_STACK) ? sk : NULL); printf(", existing: "); pf_print_state_parts(si->s, (idx == PF_SK_WIRE) ? sk : NULL, (idx == PF_SK_STACK) ? sk : NULL); printf("\n"); } #ifdef __FreeBSD__ pool_put(&V_pf_state_key_pl, sk); #else pool_put(&pf_state_key_pl, sk); #endif return (-1); /* collision! */ } } #ifdef __FreeBSD__ pool_put(&V_pf_state_key_pl, sk); #else pool_put(&pf_state_key_pl, sk); #endif s->key[idx] = cur; } else s->key[idx] = sk; #ifdef __FreeBSD__ if ((si = pool_get(&V_pf_state_item_pl, PR_NOWAIT)) == NULL) { #else if ((si = pool_get(&pf_state_item_pl, PR_NOWAIT)) == NULL) { #endif pf_state_key_detach(s, idx); return (-1); } si->s = s; /* list is sorted, if-bound states before floating */ #ifdef __FreeBSD__ if (s->kif == V_pfi_all) #else if (s->kif == pfi_all) #endif TAILQ_INSERT_TAIL(&s->key[idx]->states, si, entry); else TAILQ_INSERT_HEAD(&s->key[idx]->states, si, entry); if (olds) pf_unlink_state(olds); return (0); } void pf_detach_state(struct pf_state *s) { if (s->key[PF_SK_WIRE] == s->key[PF_SK_STACK]) s->key[PF_SK_WIRE] = NULL; if (s->key[PF_SK_STACK] != NULL) pf_state_key_detach(s, PF_SK_STACK); if (s->key[PF_SK_WIRE] != NULL) pf_state_key_detach(s, PF_SK_WIRE); } void pf_state_key_detach(struct pf_state *s, int idx) { struct pf_state_item *si; si = TAILQ_FIRST(&s->key[idx]->states); while (si && si->s != s) si = TAILQ_NEXT(si, entry); if (si) { TAILQ_REMOVE(&s->key[idx]->states, si, entry); #ifdef __FreeBSD__ pool_put(&V_pf_state_item_pl, si); #else pool_put(&pf_state_item_pl, si); #endif } if (TAILQ_EMPTY(&s->key[idx]->states)) { #ifdef __FreeBSD__ RB_REMOVE(pf_state_tree, &V_pf_statetbl, s->key[idx]); #else RB_REMOVE(pf_state_tree, &pf_statetbl, s->key[idx]); #endif if (s->key[idx]->reverse) s->key[idx]->reverse->reverse = NULL; #ifdef __FreeBSD__ /* XXX: implement this */ #else if (s->key[idx]->inp) s->key[idx]->inp->inp_pf_sk = NULL; #endif #ifdef __FreeBSD__ pool_put(&V_pf_state_key_pl, s->key[idx]); #else pool_put(&pf_state_key_pl, s->key[idx]); #endif } s->key[idx] = NULL; } struct pf_state_key * pf_alloc_state_key(int pool_flags) { struct pf_state_key *sk; #ifdef __FreeBSD__ if ((sk = pool_get(&V_pf_state_key_pl, pool_flags)) == NULL) #else if ((sk = pool_get(&pf_state_key_pl, pool_flags)) == NULL) #endif return (NULL); TAILQ_INIT(&sk->states); return (sk); } int pf_state_key_setup(struct pf_pdesc *pd, struct pf_rule *nr, struct pf_state_key **skw, struct pf_state_key **sks, struct pf_state_key **skp, struct pf_state_key **nkp, struct pf_addr *saddr, struct pf_addr *daddr, u_int16_t sport, u_int16_t dport) { #ifdef __FreeBSD__ KASSERT((*skp == NULL && *nkp == NULL), ("%s: skp == NULL && nkp == NULL", __FUNCTION__)); #else KASSERT((*skp == NULL && *nkp == NULL)); #endif if ((*skp = pf_alloc_state_key(PR_NOWAIT | PR_ZERO)) == NULL) return (ENOMEM); PF_ACPY(&(*skp)->addr[pd->sidx], saddr, pd->af); PF_ACPY(&(*skp)->addr[pd->didx], daddr, pd->af); (*skp)->port[pd->sidx] = sport; (*skp)->port[pd->didx] = dport; (*skp)->proto = pd->proto; (*skp)->af = pd->af; if (nr != NULL) { if ((*nkp = pf_alloc_state_key(PR_NOWAIT | PR_ZERO)) == NULL) return (ENOMEM); /* caller must handle cleanup */ /* XXX maybe just bcopy and TAILQ_INIT(&(*nkp)->states) */ PF_ACPY(&(*nkp)->addr[0], &(*skp)->addr[0], pd->af); PF_ACPY(&(*nkp)->addr[1], &(*skp)->addr[1], pd->af); (*nkp)->port[0] = (*skp)->port[0]; (*nkp)->port[1] = (*skp)->port[1]; (*nkp)->proto = pd->proto; (*nkp)->af = pd->af; } else *nkp = *skp; if (pd->dir == PF_IN) { *skw = *skp; *sks = *nkp; } else { *sks = *skp; *skw = *nkp; } return (0); } int pf_state_insert(struct pfi_kif *kif, struct pf_state_key *skw, struct pf_state_key *sks, struct pf_state *s) { #ifndef __FreeBSD__ splassert(IPL_SOFTNET); #endif s->kif = kif; if (skw == sks) { if (pf_state_key_attach(skw, s, PF_SK_WIRE)) return (-1); s->key[PF_SK_STACK] = s->key[PF_SK_WIRE]; } else { if (pf_state_key_attach(skw, s, PF_SK_WIRE)) { #ifdef __FreeBSD__ pool_put(&V_pf_state_key_pl, sks); #else pool_put(&pf_state_key_pl, sks); #endif return (-1); } if (pf_state_key_attach(sks, s, PF_SK_STACK)) { pf_state_key_detach(s, PF_SK_WIRE); return (-1); } } if (s->id == 0 && s->creatorid == 0) { #ifdef __FreeBSD__ s->id = htobe64(V_pf_status.stateid++); s->creatorid = V_pf_status.hostid; #else s->id = htobe64(pf_status.stateid++); s->creatorid = pf_status.hostid; #endif } #ifdef __FreeBSD__ if (RB_INSERT(pf_state_tree_id, &V_tree_id, s) != NULL) { if (V_pf_status.debug >= PF_DEBUG_MISC) { #else if (RB_INSERT(pf_state_tree_id, &tree_id, s) != NULL) { if (pf_status.debug >= PF_DEBUG_MISC) { #endif printf("pf: state insert failed: " "id: %016llx creatorid: %08x", #ifdef __FreeBSD__ (unsigned long long)betoh64(s->id), ntohl(s->creatorid)); #else betoh64(s->id), ntohl(s->creatorid)); #endif printf("\n"); } pf_detach_state(s); return (-1); } #ifdef __FreeBSD__ TAILQ_INSERT_TAIL(&V_state_list, s, entry_list); V_pf_status.fcounters[FCNT_STATE_INSERT]++; V_pf_status.states++; #else TAILQ_INSERT_TAIL(&state_list, s, entry_list); pf_status.fcounters[FCNT_STATE_INSERT]++; pf_status.states++; #endif pfi_kif_ref(kif, PFI_KIF_REF_STATE); #if NPFSYNC > 0 #ifdef __FreeBSD__ if (pfsync_insert_state_ptr != NULL) pfsync_insert_state_ptr(s); #else pfsync_insert_state(s); #endif #endif return (0); } struct pf_state * pf_find_state_byid(struct pf_state_cmp *key) { #ifdef __FreeBSD__ V_pf_status.fcounters[FCNT_STATE_SEARCH]++; return (RB_FIND(pf_state_tree_id, &V_tree_id, (struct pf_state *)key)); #else pf_status.fcounters[FCNT_STATE_SEARCH]++; return (RB_FIND(pf_state_tree_id, &tree_id, (struct pf_state *)key)); #endif } /* XXX debug function, intended to be removed one day */ int pf_compare_state_keys(struct pf_state_key *a, struct pf_state_key *b, struct pfi_kif *kif, u_int dir) { /* a (from hdr) and b (new) must be exact opposites of each other */ if (a->af == b->af && a->proto == b->proto && PF_AEQ(&a->addr[0], &b->addr[1], a->af) && PF_AEQ(&a->addr[1], &b->addr[0], a->af) && a->port[0] == b->port[1] && a->port[1] == b->port[0]) return (0); else { /* mismatch. must not happen. */ printf("pf: state key linking mismatch! dir=%s, " "if=%s, stored af=%u, a0: ", dir == PF_OUT ? "OUT" : "IN", kif->pfik_name, a->af); pf_print_host(&a->addr[0], a->port[0], a->af); printf(", a1: "); pf_print_host(&a->addr[1], a->port[1], a->af); printf(", proto=%u", a->proto); printf(", found af=%u, a0: ", b->af); pf_print_host(&b->addr[0], b->port[0], b->af); printf(", a1: "); pf_print_host(&b->addr[1], b->port[1], b->af); printf(", proto=%u", b->proto); printf(".\n"); return (-1); } } struct pf_state * #ifdef __FreeBSD__ pf_find_state(struct pfi_kif *kif, struct pf_state_key_cmp *key, u_int dir, struct mbuf *m, struct pf_mtag *pftag) #else pf_find_state(struct pfi_kif *kif, struct pf_state_key_cmp *key, u_int dir, struct mbuf *m) #endif { struct pf_state_key *sk; struct pf_state_item *si; #ifdef __FreeBSD__ V_pf_status.fcounters[FCNT_STATE_SEARCH]++; #else pf_status.fcounters[FCNT_STATE_SEARCH]++; #endif #ifdef __FreeBSD__ if (dir == PF_OUT && pftag->statekey && ((struct pf_state_key *)pftag->statekey)->reverse) sk = ((struct pf_state_key *)pftag->statekey)->reverse; else { #ifdef __FreeBSD__ if ((sk = RB_FIND(pf_state_tree, &V_pf_statetbl, #else if ((sk = RB_FIND(pf_state_tree, &pf_statetbl, #endif (struct pf_state_key *)key)) == NULL) return (NULL); if (dir == PF_OUT && pftag->statekey && pf_compare_state_keys(pftag->statekey, sk, kif, dir) == 0) { ((struct pf_state_key *) pftag->statekey)->reverse = sk; sk->reverse = pftag->statekey; } } #else if (dir == PF_OUT && m->m_pkthdr.pf.statekey && ((struct pf_state_key *)m->m_pkthdr.pf.statekey)->reverse) sk = ((struct pf_state_key *)m->m_pkthdr.pf.statekey)->reverse; else { #ifdef __FreeBSD__ if ((sk = RB_FIND(pf_state_tree, &V_pf_statetbl, #else if ((sk = RB_FIND(pf_state_tree, &pf_statetbl, #endif (struct pf_state_key *)key)) == NULL) return (NULL); if (dir == PF_OUT && m->m_pkthdr.pf.statekey && pf_compare_state_keys(m->m_pkthdr.pf.statekey, sk, kif, dir) == 0) { ((struct pf_state_key *) m->m_pkthdr.pf.statekey)->reverse = sk; sk->reverse = m->m_pkthdr.pf.statekey; } } #endif if (dir == PF_OUT) #ifdef __FreeBSD__ pftag->statekey = NULL; #else m->m_pkthdr.pf.statekey = NULL; #endif /* list is sorted, if-bound states before floating ones */ TAILQ_FOREACH(si, &sk->states, entry) #ifdef __FreeBSD__ if ((si->s->kif == V_pfi_all || si->s->kif == kif) && #else if ((si->s->kif == pfi_all || si->s->kif == kif) && #endif sk == (dir == PF_IN ? si->s->key[PF_SK_WIRE] : si->s->key[PF_SK_STACK])) return (si->s); return (NULL); } struct pf_state * pf_find_state_all(struct pf_state_key_cmp *key, u_int dir, int *more) { struct pf_state_key *sk; struct pf_state_item *si, *ret = NULL; #ifdef __FreeBSD__ V_pf_status.fcounters[FCNT_STATE_SEARCH]++; #else pf_status.fcounters[FCNT_STATE_SEARCH]++; #endif #ifdef __FreeBSD__ sk = RB_FIND(pf_state_tree, &V_pf_statetbl, (struct pf_state_key *)key); #else sk = RB_FIND(pf_state_tree, &pf_statetbl, (struct pf_state_key *)key); #endif if (sk != NULL) { TAILQ_FOREACH(si, &sk->states, entry) if (dir == PF_INOUT || (sk == (dir == PF_IN ? si->s->key[PF_SK_WIRE] : si->s->key[PF_SK_STACK]))) { if (more == NULL) return (si->s); if (ret) (*more)++; else ret = si; } } return (ret ? ret->s : NULL); } /* END state table stuff */ void pf_purge_thread(void *v) { int nloops = 0, s; #ifdef __FreeBSD__ int locked; #endif CURVNET_SET((struct vnet *)v); for (;;) { tsleep(pf_purge_thread, PWAIT, "pftm", 1 * hz); #ifdef __FreeBSD__ sx_slock(&V_pf_consistency_lock); PF_LOCK(); locked = 0; if (V_pf_end_threads) { PF_UNLOCK(); sx_sunlock(&V_pf_consistency_lock); sx_xlock(&V_pf_consistency_lock); PF_LOCK(); pf_purge_expired_states(V_pf_status.states, 1); pf_purge_expired_fragments(); pf_purge_expired_src_nodes(1); V_pf_end_threads++; sx_xunlock(&V_pf_consistency_lock); PF_UNLOCK(); wakeup(pf_purge_thread); kproc_exit(0); } #endif s = splsoftnet(); /* process a fraction of the state table every second */ #ifdef __FreeBSD__ if (!pf_purge_expired_states(1 + (V_pf_status.states / V_pf_default_rule.timeout[PFTM_INTERVAL]), 0)) { PF_UNLOCK(); sx_sunlock(&V_pf_consistency_lock); sx_xlock(&V_pf_consistency_lock); PF_LOCK(); locked = 1; pf_purge_expired_states(1 + (V_pf_status.states / V_pf_default_rule.timeout[PFTM_INTERVAL]), 1); } #else pf_purge_expired_states(1 + (pf_status.states / pf_default_rule.timeout[PFTM_INTERVAL])); #endif /* purge other expired types every PFTM_INTERVAL seconds */ #ifdef __FreeBSD__ if (++nloops >= V_pf_default_rule.timeout[PFTM_INTERVAL]) { #else if (++nloops >= pf_default_rule.timeout[PFTM_INTERVAL]) { #endif pf_purge_expired_fragments(); pf_purge_expired_src_nodes(0); nloops = 0; } splx(s); #ifdef __FreeBSD__ PF_UNLOCK(); if (locked) sx_xunlock(&V_pf_consistency_lock); else sx_sunlock(&V_pf_consistency_lock); #endif } CURVNET_RESTORE(); } u_int32_t pf_state_expires(const struct pf_state *state) { u_int32_t timeout; u_int32_t start; u_int32_t end; u_int32_t states; /* handle all PFTM_* > PFTM_MAX here */ if (state->timeout == PFTM_PURGE) return (time_second); if (state->timeout == PFTM_UNTIL_PACKET) return (0); #ifdef __FreeBSD__ KASSERT(state->timeout != PFTM_UNLINKED, ("pf_state_expires: timeout == PFTM_UNLINKED")); KASSERT((state->timeout < PFTM_MAX), ("pf_state_expires: timeout > PFTM_MAX")); #else KASSERT(state->timeout != PFTM_UNLINKED); KASSERT(state->timeout < PFTM_MAX); #endif timeout = state->rule.ptr->timeout[state->timeout]; if (!timeout) #ifdef __FreeBSD__ timeout = V_pf_default_rule.timeout[state->timeout]; #else timeout = pf_default_rule.timeout[state->timeout]; #endif start = state->rule.ptr->timeout[PFTM_ADAPTIVE_START]; if (start) { end = state->rule.ptr->timeout[PFTM_ADAPTIVE_END]; states = state->rule.ptr->states_cur; } else { #ifdef __FreeBSD__ start = V_pf_default_rule.timeout[PFTM_ADAPTIVE_START]; end = V_pf_default_rule.timeout[PFTM_ADAPTIVE_END]; states = V_pf_status.states; #else start = pf_default_rule.timeout[PFTM_ADAPTIVE_START]; end = pf_default_rule.timeout[PFTM_ADAPTIVE_END]; states = pf_status.states; #endif } if (end && states > start && start < end) { if (states < end) return (state->expire + timeout * (end - states) / (end - start)); else return (time_second); } return (state->expire + timeout); } #ifdef __FreeBSD__ int pf_purge_expired_src_nodes(int waslocked) #else void pf_purge_expired_src_nodes(int waslocked) #endif { struct pf_src_node *cur, *next; int locked = waslocked; #ifdef __FreeBSD__ for (cur = RB_MIN(pf_src_tree, &V_tree_src_tracking); cur; cur = next) { next = RB_NEXT(pf_src_tree, &V_tree_src_tracking, cur); #else for (cur = RB_MIN(pf_src_tree, &tree_src_tracking); cur; cur = next) { next = RB_NEXT(pf_src_tree, &tree_src_tracking, cur); #endif if (cur->states <= 0 && cur->expire <= time_second) { if (! locked) { #ifdef __FreeBSD__ if (!sx_try_upgrade(&V_pf_consistency_lock)) return (0); #else rw_enter_write(&pf_consistency_lock); #endif next = RB_NEXT(pf_src_tree, #ifdef __FreeBSD__ &V_tree_src_tracking, cur); #else &tree_src_tracking, cur); #endif locked = 1; } if (cur->rule.ptr != NULL) { cur->rule.ptr->src_nodes--; if (cur->rule.ptr->states_cur <= 0 && cur->rule.ptr->max_src_nodes <= 0) pf_rm_rule(NULL, cur->rule.ptr); } #ifdef __FreeBSD__ RB_REMOVE(pf_src_tree, &V_tree_src_tracking, cur); V_pf_status.scounters[SCNT_SRC_NODE_REMOVALS]++; V_pf_status.src_nodes--; pool_put(&V_pf_src_tree_pl, cur); #else RB_REMOVE(pf_src_tree, &tree_src_tracking, cur); pf_status.scounters[SCNT_SRC_NODE_REMOVALS]++; pf_status.src_nodes--; pool_put(&pf_src_tree_pl, cur); #endif } } if (locked && !waslocked) #ifdef __FreeBSD__ { sx_downgrade(&V_pf_consistency_lock); } return (1); #else rw_exit_write(&pf_consistency_lock); #endif } void pf_src_tree_remove_state(struct pf_state *s) { u_int32_t timeout; if (s->src_node != NULL) { if (s->src.tcp_est) --s->src_node->conn; if (--s->src_node->states <= 0) { timeout = s->rule.ptr->timeout[PFTM_SRC_NODE]; if (!timeout) timeout = #ifdef __FreeBSD__ V_pf_default_rule.timeout[PFTM_SRC_NODE]; #else pf_default_rule.timeout[PFTM_SRC_NODE]; #endif s->src_node->expire = time_second + timeout; } } if (s->nat_src_node != s->src_node && s->nat_src_node != NULL) { if (--s->nat_src_node->states <= 0) { timeout = s->rule.ptr->timeout[PFTM_SRC_NODE]; if (!timeout) timeout = #ifdef __FreeBSD__ V_pf_default_rule.timeout[PFTM_SRC_NODE]; #else pf_default_rule.timeout[PFTM_SRC_NODE]; #endif s->nat_src_node->expire = time_second + timeout; } } s->src_node = s->nat_src_node = NULL; } /* callers should be at splsoftnet */ void pf_unlink_state(struct pf_state *cur) { #ifdef __FreeBSD__ if (cur->local_flags & PFSTATE_EXPIRING) return; cur->local_flags |= PFSTATE_EXPIRING; #else splassert(IPL_SOFTNET); #endif if (cur->src.state == PF_TCPS_PROXY_DST) { /* XXX wire key the right one? */ #ifdef __FreeBSD__ pf_send_tcp(NULL, cur->rule.ptr, cur->key[PF_SK_WIRE]->af, #else pf_send_tcp(cur->rule.ptr, cur->key[PF_SK_WIRE]->af, #endif &cur->key[PF_SK_WIRE]->addr[1], &cur->key[PF_SK_WIRE]->addr[0], cur->key[PF_SK_WIRE]->port[1], cur->key[PF_SK_WIRE]->port[0], cur->src.seqhi, cur->src.seqlo + 1, TH_RST|TH_ACK, 0, 0, 0, 1, cur->tag, NULL, NULL); } #ifdef __FreeBSD__ RB_REMOVE(pf_state_tree_id, &V_tree_id, cur); #else RB_REMOVE(pf_state_tree_id, &tree_id, cur); #endif #if NPFLOW > 0 if (cur->state_flags & PFSTATE_PFLOW) #ifdef __FreeBSD__ if (export_pflow_ptr != NULL) export_pflow_ptr(cur); #else export_pflow(cur); #endif #endif #if NPFSYNC > 0 #ifdef __FreeBSD__ if (pfsync_delete_state_ptr != NULL) pfsync_delete_state_ptr(cur); #else pfsync_delete_state(cur); #endif #endif cur->timeout = PFTM_UNLINKED; pf_src_tree_remove_state(cur); pf_detach_state(cur); } /* callers should be at splsoftnet and hold the * write_lock on pf_consistency_lock */ void pf_free_state(struct pf_state *cur) { #ifndef __FreeBSD__ splassert(IPL_SOFTNET); #endif #if NPFSYNC > 0 #ifdef __FreeBSD__ if (pfsync_state_in_use_ptr != NULL && pfsync_state_in_use_ptr(cur)) #else if (pfsync_state_in_use(cur)) #endif return; #endif #ifdef __FreeBSD__ KASSERT(cur->timeout == PFTM_UNLINKED, ("pf_free_state: cur->timeout != PFTM_UNLINKED")); #else KASSERT(cur->timeout == PFTM_UNLINKED); #endif if (--cur->rule.ptr->states_cur <= 0 && cur->rule.ptr->src_nodes <= 0) pf_rm_rule(NULL, cur->rule.ptr); if (cur->nat_rule.ptr != NULL) if (--cur->nat_rule.ptr->states_cur <= 0 && cur->nat_rule.ptr->src_nodes <= 0) pf_rm_rule(NULL, cur->nat_rule.ptr); if (cur->anchor.ptr != NULL) if (--cur->anchor.ptr->states_cur <= 0) pf_rm_rule(NULL, cur->anchor.ptr); pf_normalize_tcp_cleanup(cur); pfi_kif_unref(cur->kif, PFI_KIF_REF_STATE); #ifdef __FreeBSD__ TAILQ_REMOVE(&V_state_list, cur, entry_list); #else TAILQ_REMOVE(&state_list, cur, entry_list); #endif if (cur->tag) pf_tag_unref(cur->tag); #ifdef __FreeBSD__ pool_put(&V_pf_state_pl, cur); V_pf_status.fcounters[FCNT_STATE_REMOVALS]++; V_pf_status.states--; #else pool_put(&pf_state_pl, cur); pf_status.fcounters[FCNT_STATE_REMOVALS]++; pf_status.states--; #endif } #ifdef __FreeBSD__ int pf_purge_expired_states(u_int32_t maxcheck, int waslocked) #else void pf_purge_expired_states(u_int32_t maxcheck) #endif { static struct pf_state *cur = NULL; struct pf_state *next; #ifdef __FreeBSD__ int locked = waslocked; #else int locked = 0; #endif while (maxcheck--) { /* wrap to start of list when we hit the end */ if (cur == NULL) { #ifdef __FreeBSD__ cur = TAILQ_FIRST(&V_state_list); #else cur = TAILQ_FIRST(&state_list); #endif if (cur == NULL) break; /* list empty */ } /* get next state, as cur may get deleted */ next = TAILQ_NEXT(cur, entry_list); if (cur->timeout == PFTM_UNLINKED) { /* free unlinked state */ if (! locked) { #ifdef __FreeBSD__ if (!sx_try_upgrade(&V_pf_consistency_lock)) return (0); #else rw_enter_write(&pf_consistency_lock); #endif locked = 1; } pf_free_state(cur); } else if (pf_state_expires(cur) <= time_second) { /* unlink and free expired state */ pf_unlink_state(cur); if (! locked) { #ifdef __FreeBSD__ if (!sx_try_upgrade(&V_pf_consistency_lock)) return (0); #else rw_enter_write(&pf_consistency_lock); #endif locked = 1; } pf_free_state(cur); } cur = next; } #ifdef __FreeBSD__ if (!waslocked && locked) sx_downgrade(&V_pf_consistency_lock); return (1); #else if (locked) rw_exit_write(&pf_consistency_lock); #endif } int pf_tbladdr_setup(struct pf_ruleset *rs, struct pf_addr_wrap *aw) { if (aw->type != PF_ADDR_TABLE) return (0); if ((aw->p.tbl = pfr_attach_table(rs, aw->v.tblname, 1)) == NULL) return (1); return (0); } void pf_tbladdr_remove(struct pf_addr_wrap *aw) { if (aw->type != PF_ADDR_TABLE || aw->p.tbl == NULL) return; pfr_detach_table(aw->p.tbl); aw->p.tbl = NULL; } void pf_tbladdr_copyout(struct pf_addr_wrap *aw) { struct pfr_ktable *kt = aw->p.tbl; if (aw->type != PF_ADDR_TABLE || kt == NULL) return; if (!(kt->pfrkt_flags & PFR_TFLAG_ACTIVE) && kt->pfrkt_root != NULL) kt = kt->pfrkt_root; aw->p.tbl = NULL; aw->p.tblcnt = (kt->pfrkt_flags & PFR_TFLAG_ACTIVE) ? kt->pfrkt_cnt : -1; } void pf_print_host(struct pf_addr *addr, u_int16_t p, sa_family_t af) { switch (af) { #ifdef INET case AF_INET: { u_int32_t a = ntohl(addr->addr32[0]); printf("%u.%u.%u.%u", (a>>24)&255, (a>>16)&255, (a>>8)&255, a&255); if (p) { p = ntohs(p); printf(":%u", p); } break; } #endif /* INET */ #ifdef INET6 case AF_INET6: { u_int16_t b; u_int8_t i, curstart, curend, maxstart, maxend; curstart = curend = maxstart = maxend = 255; for (i = 0; i < 8; i++) { if (!addr->addr16[i]) { if (curstart == 255) curstart = i; curend = i; } else { if ((curend - curstart) > (maxend - maxstart)) { maxstart = curstart; maxend = curend; } curstart = curend = 255; } } if ((curend - curstart) > (maxend - maxstart)) { maxstart = curstart; maxend = curend; } for (i = 0; i < 8; i++) { if (i >= maxstart && i <= maxend) { if (i == 0) printf(":"); if (i == maxend) printf(":"); } else { b = ntohs(addr->addr16[i]); printf("%x", b); if (i < 7) printf(":"); } } if (p) { p = ntohs(p); printf("[%u]", p); } break; } #endif /* INET6 */ } } void pf_print_state(struct pf_state *s) { pf_print_state_parts(s, NULL, NULL); } void pf_print_state_parts(struct pf_state *s, struct pf_state_key *skwp, struct pf_state_key *sksp) { struct pf_state_key *skw, *sks; u_int8_t proto, dir; /* Do our best to fill these, but they're skipped if NULL */ skw = skwp ? skwp : (s ? s->key[PF_SK_WIRE] : NULL); sks = sksp ? sksp : (s ? s->key[PF_SK_STACK] : NULL); proto = skw ? skw->proto : (sks ? sks->proto : 0); dir = s ? s->direction : 0; switch (proto) { case IPPROTO_IPV4: printf("IPv4"); break; case IPPROTO_IPV6: printf("IPv6"); break; case IPPROTO_TCP: printf("TCP"); break; case IPPROTO_UDP: printf("UDP"); break; case IPPROTO_ICMP: printf("ICMP"); break; case IPPROTO_ICMPV6: printf("ICMPv6"); break; default: printf("%u", skw->proto); break; } switch (dir) { case PF_IN: printf(" in"); break; case PF_OUT: printf(" out"); break; } if (skw) { printf(" wire: "); pf_print_host(&skw->addr[0], skw->port[0], skw->af); printf(" "); pf_print_host(&skw->addr[1], skw->port[1], skw->af); } if (sks) { printf(" stack: "); if (sks != skw) { pf_print_host(&sks->addr[0], sks->port[0], sks->af); printf(" "); pf_print_host(&sks->addr[1], sks->port[1], sks->af); } else printf("-"); } if (s) { if (proto == IPPROTO_TCP) { printf(" [lo=%u high=%u win=%u modulator=%u", s->src.seqlo, s->src.seqhi, s->src.max_win, s->src.seqdiff); if (s->src.wscale && s->dst.wscale) printf(" wscale=%u", s->src.wscale & PF_WSCALE_MASK); printf("]"); printf(" [lo=%u high=%u win=%u modulator=%u", s->dst.seqlo, s->dst.seqhi, s->dst.max_win, s->dst.seqdiff); if (s->src.wscale && s->dst.wscale) printf(" wscale=%u", s->dst.wscale & PF_WSCALE_MASK); printf("]"); } printf(" %u:%u", s->src.state, s->dst.state); } } void pf_print_flags(u_int8_t f) { if (f) printf(" "); if (f & TH_FIN) printf("F"); if (f & TH_SYN) printf("S"); if (f & TH_RST) printf("R"); if (f & TH_PUSH) printf("P"); if (f & TH_ACK) printf("A"); if (f & TH_URG) printf("U"); if (f & TH_ECE) printf("E"); if (f & TH_CWR) printf("W"); } #define PF_SET_SKIP_STEPS(i) \ do { \ while (head[i] != cur) { \ head[i]->skip[i].ptr = cur; \ head[i] = TAILQ_NEXT(head[i], entries); \ } \ } while (0) void pf_calc_skip_steps(struct pf_rulequeue *rules) { struct pf_rule *cur, *prev, *head[PF_SKIP_COUNT]; int i; cur = TAILQ_FIRST(rules); prev = cur; for (i = 0; i < PF_SKIP_COUNT; ++i) head[i] = cur; while (cur != NULL) { if (cur->kif != prev->kif || cur->ifnot != prev->ifnot) PF_SET_SKIP_STEPS(PF_SKIP_IFP); if (cur->direction != prev->direction) PF_SET_SKIP_STEPS(PF_SKIP_DIR); if (cur->af != prev->af) PF_SET_SKIP_STEPS(PF_SKIP_AF); if (cur->proto != prev->proto) PF_SET_SKIP_STEPS(PF_SKIP_PROTO); if (cur->src.neg != prev->src.neg || pf_addr_wrap_neq(&cur->src.addr, &prev->src.addr)) PF_SET_SKIP_STEPS(PF_SKIP_SRC_ADDR); if (cur->src.port[0] != prev->src.port[0] || cur->src.port[1] != prev->src.port[1] || cur->src.port_op != prev->src.port_op) PF_SET_SKIP_STEPS(PF_SKIP_SRC_PORT); if (cur->dst.neg != prev->dst.neg || pf_addr_wrap_neq(&cur->dst.addr, &prev->dst.addr)) PF_SET_SKIP_STEPS(PF_SKIP_DST_ADDR); if (cur->dst.port[0] != prev->dst.port[0] || cur->dst.port[1] != prev->dst.port[1] || cur->dst.port_op != prev->dst.port_op) PF_SET_SKIP_STEPS(PF_SKIP_DST_PORT); prev = cur; cur = TAILQ_NEXT(cur, entries); } for (i = 0; i < PF_SKIP_COUNT; ++i) PF_SET_SKIP_STEPS(i); } int pf_addr_wrap_neq(struct pf_addr_wrap *aw1, struct pf_addr_wrap *aw2) { if (aw1->type != aw2->type) return (1); switch (aw1->type) { case PF_ADDR_ADDRMASK: case PF_ADDR_RANGE: if (PF_ANEQ(&aw1->v.a.addr, &aw2->v.a.addr, 0)) return (1); if (PF_ANEQ(&aw1->v.a.mask, &aw2->v.a.mask, 0)) return (1); return (0); case PF_ADDR_DYNIFTL: return (aw1->p.dyn->pfid_kt != aw2->p.dyn->pfid_kt); case PF_ADDR_NOROUTE: case PF_ADDR_URPFFAILED: return (0); case PF_ADDR_TABLE: return (aw1->p.tbl != aw2->p.tbl); case PF_ADDR_RTLABEL: return (aw1->v.rtlabel != aw2->v.rtlabel); default: printf("invalid address type: %d\n", aw1->type); return (1); } } +/** + * Checksum updates are a little complicated because the checksum in the TCP/UDP + * header isn't always a full checksum. In some cases (i.e. output) it's a + * pseudo-header checksum, which is a partial checksum over src/dst IP + * addresses, protocol number and length. + * + * That means we have the following cases: + * * Input or forwarding: we don't have TSO, the checksum fields are full + * checksums, we need to update the checksum whenever we change anything. + * * Output (i.e. the checksum is a pseudo-header checksum): + * x The field being updated is src/dst address or affects the length of + * the packet. We need to update the pseudo-header checksum (note that this + * checksum is not ones' complement). + * x Some other field is being modified (e.g. src/dst port numbers): We + * don't have to update anything. + **/ u_int16_t pf_cksum_fixup(u_int16_t cksum, u_int16_t old, u_int16_t new, u_int8_t udp) { u_int32_t l; if (udp && !cksum) return (0x0000); l = cksum + old - new; l = (l >> 16) + (l & 65535); l = l & 65535; if (udp && !l) return (0xFFFF); return (l); } +u_int16_t +pf_proto_cksum_fixup(struct mbuf *m, u_int16_t cksum, u_int16_t old, + u_int16_t new, u_int8_t udp) +{ + if (m->m_pkthdr.csum_flags & (CSUM_DELAY_DATA | CSUM_DELAY_DATA_IPV6)) + return (cksum); + + return (pf_cksum_fixup(cksum, old, new, udp)); +} + void -pf_change_ap(struct pf_addr *a, u_int16_t *p, u_int16_t *ic, u_int16_t *pc, - struct pf_addr *an, u_int16_t pn, u_int8_t u, sa_family_t af) +pf_change_ap(struct mbuf *m, struct pf_addr *a, u_int16_t *p, u_int16_t *ic, + u_int16_t *pc, struct pf_addr *an, u_int16_t pn, u_int8_t u, + sa_family_t af) { struct pf_addr ao; u_int16_t po = *p; PF_ACPY(&ao, a, af); PF_ACPY(a, an, af); + if (m->m_pkthdr.csum_flags & (CSUM_DELAY_DATA | CSUM_DELAY_DATA_IPV6)) + *pc = ~*pc; + *p = pn; switch (af) { #ifdef INET case AF_INET: *ic = pf_cksum_fixup(pf_cksum_fixup(*ic, ao.addr16[0], an->addr16[0], 0), ao.addr16[1], an->addr16[1], 0); *p = pn; - *pc = pf_cksum_fixup(pf_cksum_fixup(pf_cksum_fixup(*pc, + + *pc = pf_cksum_fixup(pf_cksum_fixup(*pc, ao.addr16[0], an->addr16[0], u), - ao.addr16[1], an->addr16[1], u), - po, pn, u); + ao.addr16[1], an->addr16[1], u); + + *pc = pf_proto_cksum_fixup(m, *pc, po, pn, u); break; #endif /* INET */ #ifdef INET6 case AF_INET6: *pc = pf_cksum_fixup(pf_cksum_fixup(pf_cksum_fixup( pf_cksum_fixup(pf_cksum_fixup(pf_cksum_fixup( - pf_cksum_fixup(pf_cksum_fixup(pf_cksum_fixup(*pc, + pf_cksum_fixup(pf_cksum_fixup(*pc, ao.addr16[0], an->addr16[0], u), ao.addr16[1], an->addr16[1], u), ao.addr16[2], an->addr16[2], u), ao.addr16[3], an->addr16[3], u), ao.addr16[4], an->addr16[4], u), ao.addr16[5], an->addr16[5], u), ao.addr16[6], an->addr16[6], u), - ao.addr16[7], an->addr16[7], u), - po, pn, u); + ao.addr16[7], an->addr16[7], u); + + *pc = pf_proto_cksum_fixup(m, *pc, po, pn, u); break; #endif /* INET6 */ } + + if (m->m_pkthdr.csum_flags & (CSUM_DELAY_DATA | + CSUM_DELAY_DATA_IPV6)) { + *pc = ~*pc; + if (! *pc) + *pc = 0xffff; + } } - /* Changes a u_int32_t. Uses a void * so there are no align restrictions */ void pf_change_a(void *a, u_int16_t *c, u_int32_t an, u_int8_t u) { u_int32_t ao; memcpy(&ao, a, sizeof(ao)); memcpy(a, &an, sizeof(u_int32_t)); *c = pf_cksum_fixup(pf_cksum_fixup(*c, ao / 65536, an / 65536, u), ao % 65536, an % 65536, u); } +void +pf_change_proto_a(struct mbuf *m, void *a, u_int16_t *c, u_int32_t an, u_int8_t udp) +{ + u_int32_t ao; + + memcpy(&ao, a, sizeof(ao)); + memcpy(a, &an, sizeof(u_int32_t)); + + *c = pf_proto_cksum_fixup(m, + pf_proto_cksum_fixup(m, *c, ao / 65536, an / 65536, udp), + ao % 65536, an % 65536, udp); +} + #ifdef INET6 void pf_change_a6(struct pf_addr *a, u_int16_t *c, struct pf_addr *an, u_int8_t u) { struct pf_addr ao; PF_ACPY(&ao, a, AF_INET6); PF_ACPY(a, an, AF_INET6); *c = pf_cksum_fixup(pf_cksum_fixup(pf_cksum_fixup( pf_cksum_fixup(pf_cksum_fixup(pf_cksum_fixup( pf_cksum_fixup(pf_cksum_fixup(*c, ao.addr16[0], an->addr16[0], u), ao.addr16[1], an->addr16[1], u), ao.addr16[2], an->addr16[2], u), ao.addr16[3], an->addr16[3], u), ao.addr16[4], an->addr16[4], u), ao.addr16[5], an->addr16[5], u), ao.addr16[6], an->addr16[6], u), ao.addr16[7], an->addr16[7], u); } #endif /* INET6 */ void pf_change_icmp(struct pf_addr *ia, u_int16_t *ip, struct pf_addr *oa, struct pf_addr *na, u_int16_t np, u_int16_t *pc, u_int16_t *h2c, u_int16_t *ic, u_int16_t *hc, u_int8_t u, sa_family_t af) { struct pf_addr oia, ooa; PF_ACPY(&oia, ia, af); if (oa) PF_ACPY(&ooa, oa, af); /* Change inner protocol port, fix inner protocol checksum. */ if (ip != NULL) { u_int16_t oip = *ip; u_int32_t opc; if (pc != NULL) opc = *pc; *ip = np; if (pc != NULL) *pc = pf_cksum_fixup(*pc, oip, *ip, u); *ic = pf_cksum_fixup(*ic, oip, *ip, 0); if (pc != NULL) *ic = pf_cksum_fixup(*ic, opc, *pc, 0); } /* Change inner ip address, fix inner ip and icmp checksums. */ PF_ACPY(ia, na, af); switch (af) { #ifdef INET case AF_INET: { u_int32_t oh2c = *h2c; *h2c = pf_cksum_fixup(pf_cksum_fixup(*h2c, oia.addr16[0], ia->addr16[0], 0), oia.addr16[1], ia->addr16[1], 0); *ic = pf_cksum_fixup(pf_cksum_fixup(*ic, oia.addr16[0], ia->addr16[0], 0), oia.addr16[1], ia->addr16[1], 0); *ic = pf_cksum_fixup(*ic, oh2c, *h2c, 0); break; } #endif /* INET */ #ifdef INET6 case AF_INET6: *ic = pf_cksum_fixup(pf_cksum_fixup(pf_cksum_fixup( pf_cksum_fixup(pf_cksum_fixup(pf_cksum_fixup( pf_cksum_fixup(pf_cksum_fixup(*ic, oia.addr16[0], ia->addr16[0], u), oia.addr16[1], ia->addr16[1], u), oia.addr16[2], ia->addr16[2], u), oia.addr16[3], ia->addr16[3], u), oia.addr16[4], ia->addr16[4], u), oia.addr16[5], ia->addr16[5], u), oia.addr16[6], ia->addr16[6], u), oia.addr16[7], ia->addr16[7], u); break; #endif /* INET6 */ } /* Outer ip address, fix outer ip or icmpv6 checksum, if necessary. */ if (oa) { PF_ACPY(oa, na, af); switch (af) { #ifdef INET case AF_INET: *hc = pf_cksum_fixup(pf_cksum_fixup(*hc, ooa.addr16[0], oa->addr16[0], 0), ooa.addr16[1], oa->addr16[1], 0); break; #endif /* INET */ #ifdef INET6 case AF_INET6: *ic = pf_cksum_fixup(pf_cksum_fixup(pf_cksum_fixup( pf_cksum_fixup(pf_cksum_fixup(pf_cksum_fixup( pf_cksum_fixup(pf_cksum_fixup(*ic, ooa.addr16[0], oa->addr16[0], u), ooa.addr16[1], oa->addr16[1], u), ooa.addr16[2], oa->addr16[2], u), ooa.addr16[3], oa->addr16[3], u), ooa.addr16[4], oa->addr16[4], u), ooa.addr16[5], oa->addr16[5], u), ooa.addr16[6], oa->addr16[6], u), ooa.addr16[7], oa->addr16[7], u); break; #endif /* INET6 */ } } } /* * Need to modulate the sequence numbers in the TCP SACK option * (credits to Krzysztof Pfaff for report and patch) */ int pf_modulate_sack(struct mbuf *m, int off, struct pf_pdesc *pd, struct tcphdr *th, struct pf_state_peer *dst) { int hlen = (th->th_off << 2) - sizeof(*th), thoptlen = hlen; #ifdef __FreeBSD__ u_int8_t opts[TCP_MAXOLEN], *opt = opts; #else u_int8_t opts[MAX_TCPOPTLEN], *opt = opts; #endif int copyback = 0, i, olen; struct sackblk sack; #define TCPOLEN_SACKLEN (TCPOLEN_SACK + 2) if (hlen < TCPOLEN_SACKLEN || !pf_pull_hdr(m, off + sizeof(*th), opts, hlen, NULL, NULL, pd->af)) return 0; while (hlen >= TCPOLEN_SACKLEN) { olen = opt[1]; switch (*opt) { case TCPOPT_EOL: /* FALLTHROUGH */ case TCPOPT_NOP: opt++; hlen--; break; case TCPOPT_SACK: if (olen > hlen) olen = hlen; if (olen >= TCPOLEN_SACKLEN) { for (i = 2; i + TCPOLEN_SACK <= olen; i += TCPOLEN_SACK) { memcpy(&sack, &opt[i], sizeof(sack)); - pf_change_a(&sack.start, &th->th_sum, - htonl(ntohl(sack.start) - - dst->seqdiff), 0); - pf_change_a(&sack.end, &th->th_sum, - htonl(ntohl(sack.end) - - dst->seqdiff), 0); + pf_change_proto_a(m, &sack.start, &th->th_sum, + htonl(ntohl(sack.start) - dst->seqdiff), 0); + pf_change_proto_a(m, &sack.end, &th->th_sum, + htonl(ntohl(sack.end) - dst->seqdiff), 0); memcpy(&opt[i], &sack, sizeof(sack)); } copyback = 1; } /* FALLTHROUGH */ default: if (olen < 2) olen = 2; hlen -= olen; opt += olen; } } if (copyback) #ifdef __FreeBSD__ m_copyback(m, off + sizeof(*th), thoptlen, (caddr_t)opts); #else m_copyback(m, off + sizeof(*th), thoptlen, opts); #endif return (copyback); } void #ifdef __FreeBSD__ pf_send_tcp(struct mbuf *replyto, const struct pf_rule *r, sa_family_t af, #else pf_send_tcp(const struct pf_rule *r, sa_family_t af, #endif const struct pf_addr *saddr, const struct pf_addr *daddr, u_int16_t sport, u_int16_t dport, u_int32_t seq, u_int32_t ack, u_int8_t flags, u_int16_t win, u_int16_t mss, u_int8_t ttl, int tag, u_int16_t rtag, struct ether_header *eh, struct ifnet *ifp) { struct mbuf *m; int len, tlen; #ifdef INET struct ip *h; #endif /* INET */ #ifdef INET6 struct ip6_hdr *h6; #endif /* INET6 */ struct tcphdr *th; char *opt; #ifdef __FreeBSD__ struct pf_mtag *pf_mtag; KASSERT( #ifdef INET af == AF_INET #else 0 #endif || #ifdef INET6 af == AF_INET6 #else 0 #endif , ("Unsupported AF %d", af)); len = 0; th = NULL; #ifdef INET h = NULL; #endif #ifdef INET6 h6 = NULL; #endif #endif /* __FreeBSD__ */ /* maximum segment size tcp option */ tlen = sizeof(struct tcphdr); if (mss) tlen += 4; switch (af) { #ifdef INET case AF_INET: len = sizeof(struct ip) + tlen; break; #endif /* INET */ #ifdef INET6 case AF_INET6: len = sizeof(struct ip6_hdr) + tlen; break; #endif /* INET6 */ } /* create outgoing mbuf */ m = m_gethdr(M_DONTWAIT, MT_HEADER); if (m == NULL) return; #ifdef __FreeBSD__ #ifdef MAC mac_netinet_firewall_send(m); #endif if ((pf_mtag = pf_get_mtag(m)) == NULL) { m_freem(m); return; } #endif if (tag) #ifdef __FreeBSD__ m->m_flags |= M_SKIP_FIREWALL; pf_mtag->tag = rtag; #else m->m_pkthdr.pf.flags |= PF_TAG_GENERATED; m->m_pkthdr.pf.tag = rtag; #endif if (r != NULL && r->rtableid >= 0) #ifdef __FreeBSD__ { M_SETFIB(m, r->rtableid); pf_mtag->rtableid = r->rtableid; #else m->m_pkthdr.pf.rtableid = r->rtableid; #endif #ifdef __FreeBSD__ } #endif #ifdef ALTQ if (r != NULL && r->qid) { #ifdef __FreeBSD__ pf_mtag->qid = r->qid; /* add hints for ecn */ pf_mtag->hdr = mtod(m, struct ip *); #else m->m_pkthdr.pf.qid = r->qid; /* add hints for ecn */ m->m_pkthdr.pf.hdr = mtod(m, struct ip *); #endif } #endif /* ALTQ */ m->m_data += max_linkhdr; m->m_pkthdr.len = m->m_len = len; m->m_pkthdr.rcvif = NULL; bzero(m->m_data, len); switch (af) { #ifdef INET case AF_INET: h = mtod(m, struct ip *); /* IP header fields included in the TCP checksum */ h->ip_p = IPPROTO_TCP; h->ip_len = htons(tlen); h->ip_src.s_addr = saddr->v4.s_addr; h->ip_dst.s_addr = daddr->v4.s_addr; th = (struct tcphdr *)((caddr_t)h + sizeof(struct ip)); break; #endif /* INET */ #ifdef INET6 case AF_INET6: h6 = mtod(m, struct ip6_hdr *); /* IP header fields included in the TCP checksum */ h6->ip6_nxt = IPPROTO_TCP; h6->ip6_plen = htons(tlen); memcpy(&h6->ip6_src, &saddr->v6, sizeof(struct in6_addr)); memcpy(&h6->ip6_dst, &daddr->v6, sizeof(struct in6_addr)); th = (struct tcphdr *)((caddr_t)h6 + sizeof(struct ip6_hdr)); break; #endif /* INET6 */ } /* TCP header */ th->th_sport = sport; th->th_dport = dport; th->th_seq = htonl(seq); th->th_ack = htonl(ack); th->th_off = tlen >> 2; th->th_flags = flags; th->th_win = htons(win); if (mss) { opt = (char *)(th + 1); opt[0] = TCPOPT_MAXSEG; opt[1] = 4; HTONS(mss); bcopy((caddr_t)&mss, (caddr_t)(opt + 2), 2); } switch (af) { #ifdef INET case AF_INET: /* TCP checksum */ 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; #ifdef __FreeBSD__ h->ip_off = V_path_mtu_discovery ? IP_DF : 0; h->ip_len = len; h->ip_ttl = ttl ? ttl : V_ip_defttl; #else h->ip_len = htons(len); h->ip_off = htons(ip_mtudisc ? IP_DF : 0); h->ip_ttl = ttl ? ttl : ip_defttl; #endif h->ip_sum = 0; if (eh == NULL) { #ifdef __FreeBSD__ PF_UNLOCK(); ip_output(m, (void *)NULL, (void *)NULL, 0, (void *)NULL, (void *)NULL); PF_LOCK(); #else /* ! __FreeBSD__ */ ip_output(m, (void *)NULL, (void *)NULL, 0, (void *)NULL, (void *)NULL); #endif } else { struct route ro; struct rtentry rt; struct ether_header *e = (void *)ro.ro_dst.sa_data; if (ifp == NULL) { m_freem(m); return; } rt.rt_ifp = ifp; ro.ro_rt = &rt; ro.ro_dst.sa_len = sizeof(ro.ro_dst); ro.ro_dst.sa_family = pseudo_AF_HDRCMPLT; bcopy(eh->ether_dhost, e->ether_shost, ETHER_ADDR_LEN); bcopy(eh->ether_shost, e->ether_dhost, ETHER_ADDR_LEN); e->ether_type = eh->ether_type; #ifdef __FreeBSD__ PF_UNLOCK(); /* XXX_IMPORT: later */ ip_output(m, (void *)NULL, &ro, 0, (void *)NULL, (void *)NULL); PF_LOCK(); #else /* ! __FreeBSD__ */ ip_output(m, (void *)NULL, &ro, IP_ROUTETOETHER, (void *)NULL, (void *)NULL); #endif } break; #endif /* INET */ #ifdef INET6 case AF_INET6: /* TCP checksum */ th->th_sum = in6_cksum(m, IPPROTO_TCP, sizeof(struct ip6_hdr), tlen); h6->ip6_vfc |= IPV6_VERSION; h6->ip6_hlim = IPV6_DEFHLIM; #ifdef __FreeBSD__ PF_UNLOCK(); ip6_output(m, NULL, NULL, 0, NULL, NULL, NULL); PF_LOCK(); #else ip6_output(m, NULL, NULL, 0, NULL, NULL, NULL); #endif break; #endif /* INET6 */ } } static void pf_send_icmp(struct mbuf *m, u_int8_t type, u_int8_t code, sa_family_t af, struct pf_rule *r) { struct mbuf *m0; #ifdef __FreeBSD__ #ifdef INET struct ip *ip; #endif struct pf_mtag *pf_mtag; #endif #ifdef __FreeBSD__ m0 = m_copypacket(m, M_DONTWAIT); if (m0 == NULL) return; #else if ((m0 = m_copy(m, 0, M_COPYALL)) == NULL) return; #endif #ifdef __FreeBSD__ if ((pf_mtag = pf_get_mtag(m0)) == NULL) return; /* XXX: revisit */ m0->m_flags |= M_SKIP_FIREWALL; #else m0->m_pkthdr.pf.flags |= PF_TAG_GENERATED; #endif if (r->rtableid >= 0) #ifdef __FreeBSD__ { M_SETFIB(m0, r->rtableid); pf_mtag->rtableid = r->rtableid; #else m0->m_pkthdr.pf.rtableid = r->rtableid; #endif #ifdef __FreeBSD__ } #endif #ifdef ALTQ if (r->qid) { #ifdef __FreeBSD__ pf_mtag->qid = r->qid; /* add hints for ecn */ pf_mtag->hdr = mtod(m0, struct ip *); #else m0->m_pkthdr.pf.qid = r->qid; /* add hints for ecn */ m0->m_pkthdr.pf.hdr = mtod(m0, struct ip *); #endif } #endif /* ALTQ */ switch (af) { #ifdef INET case AF_INET: #ifdef __FreeBSD__ /* icmp_error() expects host byte ordering */ ip = mtod(m0, struct ip *); NTOHS(ip->ip_len); NTOHS(ip->ip_off); PF_UNLOCK(); icmp_error(m0, type, code, 0, 0); PF_LOCK(); #else icmp_error(m0, type, code, 0, 0); #endif break; #endif /* INET */ #ifdef INET6 case AF_INET6: #ifdef __FreeBSD__ PF_UNLOCK(); #endif icmp6_error(m0, type, code, 0); #ifdef __FreeBSD__ PF_LOCK(); #endif break; #endif /* INET6 */ } } /* * Return 1 if the addresses a and b match (with mask m), otherwise return 0. * If n is 0, they match if they are equal. If n is != 0, they match if they * are different. */ int pf_match_addr(u_int8_t n, struct pf_addr *a, struct pf_addr *m, struct pf_addr *b, sa_family_t af) { int match = 0; switch (af) { #ifdef INET case AF_INET: if ((a->addr32[0] & m->addr32[0]) == (b->addr32[0] & m->addr32[0])) match++; break; #endif /* INET */ #ifdef INET6 case AF_INET6: if (((a->addr32[0] & m->addr32[0]) == (b->addr32[0] & m->addr32[0])) && ((a->addr32[1] & m->addr32[1]) == (b->addr32[1] & m->addr32[1])) && ((a->addr32[2] & m->addr32[2]) == (b->addr32[2] & m->addr32[2])) && ((a->addr32[3] & m->addr32[3]) == (b->addr32[3] & m->addr32[3]))) match++; break; #endif /* INET6 */ } if (match) { if (n) return (0); else return (1); } else { if (n) return (1); else return (0); } } /* * Return 1 if b <= a <= e, otherwise return 0. */ int pf_match_addr_range(struct pf_addr *b, struct pf_addr *e, struct pf_addr *a, sa_family_t af) { switch (af) { #ifdef INET case AF_INET: if ((a->addr32[0] < b->addr32[0]) || (a->addr32[0] > e->addr32[0])) return (0); break; #endif /* INET */ #ifdef INET6 case AF_INET6: { int i; /* check a >= b */ for (i = 0; i < 4; ++i) if (a->addr32[i] > b->addr32[i]) break; else if (a->addr32[i] < b->addr32[i]) return (0); /* check a <= e */ for (i = 0; i < 4; ++i) if (a->addr32[i] < e->addr32[i]) break; else if (a->addr32[i] > e->addr32[i]) return (0); break; } #endif /* INET6 */ } return (1); } int pf_match(u_int8_t op, u_int32_t a1, u_int32_t a2, u_int32_t p) { switch (op) { case PF_OP_IRG: return ((p > a1) && (p < a2)); case PF_OP_XRG: return ((p < a1) || (p > a2)); case PF_OP_RRG: return ((p >= a1) && (p <= a2)); case PF_OP_EQ: return (p == a1); case PF_OP_NE: return (p != a1); case PF_OP_LT: return (p < a1); case PF_OP_LE: return (p <= a1); case PF_OP_GT: return (p > a1); case PF_OP_GE: return (p >= a1); } return (0); /* never reached */ } int pf_match_port(u_int8_t op, u_int16_t a1, u_int16_t a2, u_int16_t p) { NTOHS(a1); NTOHS(a2); NTOHS(p); return (pf_match(op, a1, a2, p)); } int pf_match_uid(u_int8_t op, uid_t a1, uid_t a2, uid_t u) { if (u == UID_MAX && op != PF_OP_EQ && op != PF_OP_NE) return (0); return (pf_match(op, a1, a2, u)); } int pf_match_gid(u_int8_t op, gid_t a1, gid_t a2, gid_t g) { if (g == GID_MAX && op != PF_OP_EQ && op != PF_OP_NE) return (0); return (pf_match(op, a1, a2, g)); } int #ifdef __FreeBSD__ pf_match_tag(struct mbuf *m, struct pf_rule *r, int *tag, struct pf_mtag *pf_mtag) #else pf_match_tag(struct mbuf *m, struct pf_rule *r, int *tag) #endif { if (*tag == -1) #ifdef __FreeBSD__ *tag = pf_mtag->tag; #else *tag = m->m_pkthdr.pf.tag; #endif return ((!r->match_tag_not && r->match_tag == *tag) || (r->match_tag_not && r->match_tag != *tag)); } int #ifdef __FreeBSD__ pf_tag_packet(struct mbuf *m, int tag, int rtableid, struct pf_mtag *pf_mtag) #else pf_tag_packet(struct mbuf *m, int tag, int rtableid) #endif { if (tag <= 0 && rtableid < 0) return (0); if (tag > 0) #ifdef __FreeBSD__ pf_mtag->tag = tag; #else m->m_pkthdr.pf.tag = tag; #endif if (rtableid >= 0) #ifdef __FreeBSD__ { M_SETFIB(m, rtableid); } #else m->m_pkthdr.pf.rtableid = rtableid; #endif return (0); } void pf_step_into_anchor(int *depth, struct pf_ruleset **rs, int n, struct pf_rule **r, struct pf_rule **a, int *match) { struct pf_anchor_stackframe *f; (*r)->anchor->match = 0; if (match) *match = 0; #ifdef __FreeBSD__ if (*depth >= sizeof(V_pf_anchor_stack) / sizeof(V_pf_anchor_stack[0])) { #else if (*depth >= sizeof(pf_anchor_stack) / sizeof(pf_anchor_stack[0])) { #endif printf("pf_step_into_anchor: stack overflow\n"); *r = TAILQ_NEXT(*r, entries); return; } else if (*depth == 0 && a != NULL) *a = *r; #ifdef __FreeBSD__ f = V_pf_anchor_stack + (*depth)++; #else f = pf_anchor_stack + (*depth)++; #endif f->rs = *rs; f->r = *r; if ((*r)->anchor_wildcard) { f->parent = &(*r)->anchor->children; if ((f->child = RB_MIN(pf_anchor_node, f->parent)) == NULL) { *r = NULL; return; } *rs = &f->child->ruleset; } else { f->parent = NULL; f->child = NULL; *rs = &(*r)->anchor->ruleset; } *r = TAILQ_FIRST((*rs)->rules[n].active.ptr); } int pf_step_out_of_anchor(int *depth, struct pf_ruleset **rs, int n, struct pf_rule **r, struct pf_rule **a, int *match) { struct pf_anchor_stackframe *f; int quick = 0; do { if (*depth <= 0) break; #ifdef __FreeBSD__ f = V_pf_anchor_stack + *depth - 1; #else f = pf_anchor_stack + *depth - 1; #endif if (f->parent != NULL && f->child != NULL) { if (f->child->match || (match != NULL && *match)) { f->r->anchor->match = 1; *match = 0; } f->child = RB_NEXT(pf_anchor_node, f->parent, f->child); if (f->child != NULL) { *rs = &f->child->ruleset; *r = TAILQ_FIRST((*rs)->rules[n].active.ptr); if (*r == NULL) continue; else break; } } (*depth)--; if (*depth == 0 && a != NULL) *a = NULL; *rs = f->rs; if (f->r->anchor->match || (match != NULL && *match)) quick = f->r->quick; *r = TAILQ_NEXT(f->r, entries); } while (*r == NULL); return (quick); } #ifdef INET6 void pf_poolmask(struct pf_addr *naddr, struct pf_addr *raddr, struct pf_addr *rmask, struct pf_addr *saddr, sa_family_t af) { switch (af) { #ifdef INET case AF_INET: naddr->addr32[0] = (raddr->addr32[0] & rmask->addr32[0]) | ((rmask->addr32[0] ^ 0xffffffff ) & saddr->addr32[0]); break; #endif /* INET */ case AF_INET6: naddr->addr32[0] = (raddr->addr32[0] & rmask->addr32[0]) | ((rmask->addr32[0] ^ 0xffffffff ) & saddr->addr32[0]); naddr->addr32[1] = (raddr->addr32[1] & rmask->addr32[1]) | ((rmask->addr32[1] ^ 0xffffffff ) & saddr->addr32[1]); naddr->addr32[2] = (raddr->addr32[2] & rmask->addr32[2]) | ((rmask->addr32[2] ^ 0xffffffff ) & saddr->addr32[2]); naddr->addr32[3] = (raddr->addr32[3] & rmask->addr32[3]) | ((rmask->addr32[3] ^ 0xffffffff ) & saddr->addr32[3]); break; } } void pf_addr_inc(struct pf_addr *addr, sa_family_t af) { switch (af) { #ifdef INET case AF_INET: addr->addr32[0] = htonl(ntohl(addr->addr32[0]) + 1); break; #endif /* INET */ case AF_INET6: if (addr->addr32[3] == 0xffffffff) { addr->addr32[3] = 0; if (addr->addr32[2] == 0xffffffff) { addr->addr32[2] = 0; if (addr->addr32[1] == 0xffffffff) { addr->addr32[1] = 0; addr->addr32[0] = htonl(ntohl(addr->addr32[0]) + 1); } else addr->addr32[1] = htonl(ntohl(addr->addr32[1]) + 1); } else addr->addr32[2] = htonl(ntohl(addr->addr32[2]) + 1); } else addr->addr32[3] = htonl(ntohl(addr->addr32[3]) + 1); break; } } #endif /* INET6 */ int #ifdef __FreeBSD__ pf_socket_lookup(int direction, struct pf_pdesc *pd, struct inpcb *inp_arg) #else pf_socket_lookup(int direction, struct pf_pdesc *pd) #endif { struct pf_addr *saddr, *daddr; u_int16_t sport, dport; #ifdef __FreeBSD__ struct inpcbinfo *pi; #else struct inpcbtable *tb; #endif struct inpcb *inp; if (pd == NULL) return (-1); pd->lookup.uid = UID_MAX; pd->lookup.gid = GID_MAX; pd->lookup.pid = NO_PID; #ifdef __FreeBSD__ if (inp_arg != NULL) { INP_LOCK_ASSERT(inp_arg); pd->lookup.uid = inp_arg->inp_cred->cr_uid; pd->lookup.gid = inp_arg->inp_cred->cr_groups[0]; return (1); } #endif switch (pd->proto) { case IPPROTO_TCP: if (pd->hdr.tcp == NULL) return (-1); sport = pd->hdr.tcp->th_sport; dport = pd->hdr.tcp->th_dport; #ifdef __FreeBSD__ pi = &V_tcbinfo; #else tb = &tcbtable; #endif break; case IPPROTO_UDP: if (pd->hdr.udp == NULL) return (-1); sport = pd->hdr.udp->uh_sport; dport = pd->hdr.udp->uh_dport; #ifdef __FreeBSD__ pi = &V_udbinfo; #else tb = &udbtable; #endif break; default: return (-1); } if (direction == PF_IN) { saddr = pd->src; daddr = pd->dst; } else { u_int16_t p; p = sport; sport = dport; dport = p; saddr = pd->dst; daddr = pd->src; } switch (pd->af) { #ifdef INET case AF_INET: #ifdef __FreeBSD__ /* * XXXRW: would be nice if we had an mbuf here so that we * could use in_pcblookup_mbuf(). */ inp = in_pcblookup(pi, saddr->v4, sport, daddr->v4, dport, INPLOOKUP_RLOCKPCB, NULL); if (inp == NULL) { inp = in_pcblookup(pi, saddr->v4, sport, daddr->v4, dport, INPLOOKUP_WILDCARD | INPLOOKUP_RLOCKPCB, NULL); if (inp == NULL) return (-1); } #else inp = in_pcbhashlookup(tb, saddr->v4, sport, daddr->v4, dport); if (inp == NULL) { inp = in_pcblookup_listen(tb, daddr->v4, dport, 0, NULL); if (inp == NULL) return (-1); } #endif break; #endif /* INET */ #ifdef INET6 case AF_INET6: #ifdef __FreeBSD__ /* * XXXRW: would be nice if we had an mbuf here so that we * could use in6_pcblookup_mbuf(). */ inp = in6_pcblookup(pi, &saddr->v6, sport, &daddr->v6, dport, INPLOOKUP_RLOCKPCB, NULL); if (inp == NULL) { inp = in6_pcblookup(pi, &saddr->v6, sport, &daddr->v6, dport, INPLOOKUP_WILDCARD | INPLOOKUP_RLOCKPCB, NULL); if (inp == NULL) return (-1); } #else inp = in6_pcbhashlookup(tb, &saddr->v6, sport, &daddr->v6, dport); if (inp == NULL) { inp = in6_pcblookup_listen(tb, &daddr->v6, dport, 0, NULL); if (inp == NULL) return (-1); } #endif break; #endif /* INET6 */ default: return (-1); } #ifdef __FreeBSD__ INP_RLOCK_ASSERT(inp); pd->lookup.uid = inp->inp_cred->cr_uid; pd->lookup.gid = inp->inp_cred->cr_groups[0]; INP_RUNLOCK(inp); #else pd->lookup.uid = inp->inp_socket->so_euid; pd->lookup.gid = inp->inp_socket->so_egid; pd->lookup.pid = inp->inp_socket->so_cpid; #endif return (1); } u_int8_t pf_get_wscale(struct mbuf *m, int off, u_int16_t th_off, sa_family_t af) { int hlen; u_int8_t hdr[60]; u_int8_t *opt, optlen; u_int8_t wscale = 0; hlen = th_off << 2; /* hlen <= sizeof(hdr) */ if (hlen <= sizeof(struct tcphdr)) return (0); if (!pf_pull_hdr(m, off, hdr, hlen, NULL, NULL, af)) return (0); opt = hdr + sizeof(struct tcphdr); hlen -= sizeof(struct tcphdr); while (hlen >= 3) { switch (*opt) { case TCPOPT_EOL: case TCPOPT_NOP: ++opt; --hlen; break; case TCPOPT_WINDOW: wscale = opt[2]; if (wscale > TCP_MAX_WINSHIFT) wscale = TCP_MAX_WINSHIFT; wscale |= PF_WSCALE_FLAG; /* FALLTHROUGH */ default: optlen = opt[1]; if (optlen < 2) optlen = 2; hlen -= optlen; opt += optlen; break; } } return (wscale); } u_int16_t pf_get_mss(struct mbuf *m, int off, u_int16_t th_off, sa_family_t af) { int hlen; u_int8_t hdr[60]; u_int8_t *opt, optlen; #ifdef __FreeBSD__ u_int16_t mss = V_tcp_mssdflt; #else u_int16_t mss = tcp_mssdflt; #endif hlen = th_off << 2; /* hlen <= sizeof(hdr) */ if (hlen <= sizeof(struct tcphdr)) return (0); if (!pf_pull_hdr(m, off, hdr, hlen, NULL, NULL, af)) return (0); opt = hdr + sizeof(struct tcphdr); hlen -= sizeof(struct tcphdr); while (hlen >= TCPOLEN_MAXSEG) { switch (*opt) { case TCPOPT_EOL: case TCPOPT_NOP: ++opt; --hlen; break; case TCPOPT_MAXSEG: bcopy((caddr_t)(opt + 2), (caddr_t)&mss, 2); NTOHS(mss); /* FALLTHROUGH */ default: optlen = opt[1]; if (optlen < 2) optlen = 2; hlen -= optlen; opt += optlen; break; } } return (mss); } u_int16_t pf_calc_mss(struct pf_addr *addr, sa_family_t af, int rtableid, u_int16_t offer) { #ifdef INET struct sockaddr_in *dst; struct route ro; #endif /* INET */ #ifdef INET6 struct sockaddr_in6 *dst6; struct route_in6 ro6; #endif /* INET6 */ struct rtentry *rt = NULL; #ifdef __FreeBSD__ int hlen = 0; u_int16_t mss = V_tcp_mssdflt; #else int hlen; u_int16_t mss = tcp_mssdflt; #endif switch (af) { #ifdef INET case AF_INET: hlen = sizeof(struct ip); bzero(&ro, sizeof(ro)); dst = (struct sockaddr_in *)&ro.ro_dst; dst->sin_family = AF_INET; dst->sin_len = sizeof(*dst); dst->sin_addr = addr->v4; #ifdef __FreeBSD__ in_rtalloc_ign(&ro, 0, rtableid); #else /* ! __FreeBSD__ */ rtalloc_noclone(&ro, NO_CLONING); #endif rt = ro.ro_rt; break; #endif /* INET */ #ifdef INET6 case AF_INET6: hlen = sizeof(struct ip6_hdr); bzero(&ro6, sizeof(ro6)); dst6 = (struct sockaddr_in6 *)&ro6.ro_dst; dst6->sin6_family = AF_INET6; dst6->sin6_len = sizeof(*dst6); dst6->sin6_addr = addr->v6; #ifdef __FreeBSD__ in6_rtalloc_ign(&ro6, 0, rtableid); #else /* ! __FreeBSD__ */ rtalloc_noclone((struct route *)&ro6, NO_CLONING); #endif rt = ro6.ro_rt; break; #endif /* INET6 */ } if (rt && rt->rt_ifp) { mss = rt->rt_ifp->if_mtu - hlen - sizeof(struct tcphdr); #ifdef __FreeBSD__ mss = max(V_tcp_mssdflt, mss); #else mss = max(tcp_mssdflt, mss); #endif RTFREE(rt); } mss = min(mss, offer); mss = max(mss, 64); /* sanity - at least max opt space */ return (mss); } void pf_set_rt_ifp(struct pf_state *s, struct pf_addr *saddr) { struct pf_rule *r = s->rule.ptr; struct pf_src_node *sn = NULL; s->rt_kif = NULL; if (!r->rt || r->rt == PF_FASTROUTE) return; switch (s->key[PF_SK_WIRE]->af) { #ifdef INET case AF_INET: pf_map_addr(AF_INET, r, saddr, &s->rt_addr, NULL, &sn); s->rt_kif = r->rpool.cur->kif; break; #endif /* INET */ #ifdef INET6 case AF_INET6: pf_map_addr(AF_INET6, r, saddr, &s->rt_addr, NULL, &sn); s->rt_kif = r->rpool.cur->kif; break; #endif /* INET6 */ } } u_int32_t pf_tcp_iss(struct pf_pdesc *pd) { MD5_CTX ctx; u_int32_t digest[4]; #ifdef __FreeBSD__ if (V_pf_tcp_secret_init == 0) { read_random(&V_pf_tcp_secret, sizeof(V_pf_tcp_secret)); MD5Init(&V_pf_tcp_secret_ctx); MD5Update(&V_pf_tcp_secret_ctx, V_pf_tcp_secret, sizeof(V_pf_tcp_secret)); V_pf_tcp_secret_init = 1; } ctx = V_pf_tcp_secret_ctx; #else if (pf_tcp_secret_init == 0) { arc4random_buf(pf_tcp_secret, sizeof(pf_tcp_secret)); MD5Init(&pf_tcp_secret_ctx); MD5Update(&pf_tcp_secret_ctx, pf_tcp_secret, sizeof(pf_tcp_secret)); pf_tcp_secret_init = 1; } ctx = pf_tcp_secret_ctx; #endif MD5Update(&ctx, (char *)&pd->hdr.tcp->th_sport, sizeof(u_short)); MD5Update(&ctx, (char *)&pd->hdr.tcp->th_dport, sizeof(u_short)); if (pd->af == AF_INET6) { MD5Update(&ctx, (char *)&pd->src->v6, sizeof(struct in6_addr)); MD5Update(&ctx, (char *)&pd->dst->v6, sizeof(struct in6_addr)); } else { MD5Update(&ctx, (char *)&pd->src->v4, sizeof(struct in_addr)); MD5Update(&ctx, (char *)&pd->dst->v4, sizeof(struct in_addr)); } MD5Final((u_char *)digest, &ctx); #ifdef __FreeBSD__ V_pf_tcp_iss_off += 4096; #define ISN_RANDOM_INCREMENT (4096 - 1) return (digest[0] + (arc4random() & ISN_RANDOM_INCREMENT) + V_pf_tcp_iss_off); #undef ISN_RANDOM_INCREMENT #else pf_tcp_iss_off += 4096; return (digest[0] + tcp_iss + pf_tcp_iss_off); #endif } int pf_test_rule(struct pf_rule **rm, struct pf_state **sm, int direction, struct pfi_kif *kif, struct mbuf *m, int off, void *h, struct pf_pdesc *pd, struct pf_rule **am, struct pf_ruleset **rsm, #ifdef __FreeBSD__ struct ifqueue *ifq, struct inpcb *inp) #else struct ifqueue *ifq) #endif { struct pf_rule *nr = NULL; struct pf_addr *saddr = pd->src, *daddr = pd->dst; sa_family_t af = pd->af; struct pf_rule *r, *a = NULL; struct pf_ruleset *ruleset = NULL; struct pf_src_node *nsn = NULL; struct tcphdr *th = pd->hdr.tcp; struct pf_state_key *skw = NULL, *sks = NULL; struct pf_state_key *sk = NULL, *nk = NULL; u_short reason; int rewrite = 0, hdrlen = 0; int tag = -1, rtableid = -1; int asd = 0; int match = 0; int state_icmp = 0; #ifdef __FreeBSD__ u_int16_t sport = 0, dport = 0; u_int16_t bproto_sum = 0, bip_sum = 0; #else u_int16_t sport, dport; u_int16_t bproto_sum = 0, bip_sum; #endif u_int8_t icmptype = 0, icmpcode = 0; if (direction == PF_IN && pf_check_congestion(ifq)) { REASON_SET(&reason, PFRES_CONGEST); return (PF_DROP); } #ifdef __FreeBSD__ if (inp != NULL) pd->lookup.done = pf_socket_lookup(direction, pd, inp); else if (V_debug_pfugidhack) { PF_UNLOCK(); DPFPRINTF(PF_DEBUG_MISC, ("pf: unlocked lookup\n")); pd->lookup.done = pf_socket_lookup(direction, pd, inp); PF_LOCK(); } #endif switch (pd->proto) { case IPPROTO_TCP: sport = th->th_sport; dport = th->th_dport; hdrlen = sizeof(*th); break; case IPPROTO_UDP: sport = pd->hdr.udp->uh_sport; dport = pd->hdr.udp->uh_dport; hdrlen = sizeof(*pd->hdr.udp); break; #ifdef INET case IPPROTO_ICMP: if (pd->af != AF_INET) break; sport = dport = pd->hdr.icmp->icmp_id; hdrlen = sizeof(*pd->hdr.icmp); icmptype = pd->hdr.icmp->icmp_type; icmpcode = pd->hdr.icmp->icmp_code; if (icmptype == ICMP_UNREACH || icmptype == ICMP_SOURCEQUENCH || icmptype == ICMP_REDIRECT || icmptype == ICMP_TIMXCEED || icmptype == ICMP_PARAMPROB) state_icmp++; break; #endif /* INET */ #ifdef INET6 case IPPROTO_ICMPV6: if (af != AF_INET6) break; sport = dport = pd->hdr.icmp6->icmp6_id; hdrlen = sizeof(*pd->hdr.icmp6); icmptype = pd->hdr.icmp6->icmp6_type; icmpcode = pd->hdr.icmp6->icmp6_code; if (icmptype == ICMP6_DST_UNREACH || icmptype == ICMP6_PACKET_TOO_BIG || icmptype == ICMP6_TIME_EXCEEDED || icmptype == ICMP6_PARAM_PROB) state_icmp++; break; #endif /* INET6 */ default: sport = dport = hdrlen = 0; break; } r = TAILQ_FIRST(pf_main_ruleset.rules[PF_RULESET_FILTER].active.ptr); /* check packet for BINAT/NAT/RDR */ if ((nr = pf_get_translation(pd, m, off, direction, kif, &nsn, &skw, &sks, &sk, &nk, saddr, daddr, sport, dport)) != NULL) { if (nk == NULL || sk == NULL) { REASON_SET(&reason, PFRES_MEMORY); goto cleanup; } if (pd->ip_sum) bip_sum = *pd->ip_sum; switch (pd->proto) { case IPPROTO_TCP: bproto_sum = th->th_sum; pd->proto_sum = &th->th_sum; if (PF_ANEQ(saddr, &nk->addr[pd->sidx], af) || nk->port[pd->sidx] != sport) { - pf_change_ap(saddr, &th->th_sport, pd->ip_sum, + pf_change_ap(m, saddr, &th->th_sport, pd->ip_sum, &th->th_sum, &nk->addr[pd->sidx], nk->port[pd->sidx], 0, af); pd->sport = &th->th_sport; sport = th->th_sport; } if (PF_ANEQ(daddr, &nk->addr[pd->didx], af) || nk->port[pd->didx] != dport) { - pf_change_ap(daddr, &th->th_dport, pd->ip_sum, + pf_change_ap(m, daddr, &th->th_dport, pd->ip_sum, &th->th_sum, &nk->addr[pd->didx], nk->port[pd->didx], 0, af); dport = th->th_dport; pd->dport = &th->th_dport; } rewrite++; break; case IPPROTO_UDP: bproto_sum = pd->hdr.udp->uh_sum; pd->proto_sum = &pd->hdr.udp->uh_sum; if (PF_ANEQ(saddr, &nk->addr[pd->sidx], af) || nk->port[pd->sidx] != sport) { - pf_change_ap(saddr, &pd->hdr.udp->uh_sport, + pf_change_ap(m, saddr, &pd->hdr.udp->uh_sport, pd->ip_sum, &pd->hdr.udp->uh_sum, &nk->addr[pd->sidx], nk->port[pd->sidx], 1, af); sport = pd->hdr.udp->uh_sport; pd->sport = &pd->hdr.udp->uh_sport; } if (PF_ANEQ(daddr, &nk->addr[pd->didx], af) || nk->port[pd->didx] != dport) { - pf_change_ap(daddr, &pd->hdr.udp->uh_dport, + pf_change_ap(m, daddr, &pd->hdr.udp->uh_dport, pd->ip_sum, &pd->hdr.udp->uh_sum, &nk->addr[pd->didx], nk->port[pd->didx], 1, af); dport = pd->hdr.udp->uh_dport; pd->dport = &pd->hdr.udp->uh_dport; } rewrite++; break; #ifdef INET case IPPROTO_ICMP: nk->port[0] = nk->port[1]; if (PF_ANEQ(saddr, &nk->addr[pd->sidx], AF_INET)) pf_change_a(&saddr->v4.s_addr, pd->ip_sum, nk->addr[pd->sidx].v4.s_addr, 0); if (PF_ANEQ(daddr, &nk->addr[pd->didx], AF_INET)) pf_change_a(&daddr->v4.s_addr, pd->ip_sum, nk->addr[pd->didx].v4.s_addr, 0); if (nk->port[1] != pd->hdr.icmp->icmp_id) { pd->hdr.icmp->icmp_cksum = pf_cksum_fixup( pd->hdr.icmp->icmp_cksum, sport, nk->port[1], 0); pd->hdr.icmp->icmp_id = nk->port[1]; pd->sport = &pd->hdr.icmp->icmp_id; } m_copyback(m, off, ICMP_MINLEN, (caddr_t)pd->hdr.icmp); break; #endif /* INET */ #ifdef INET6 case IPPROTO_ICMPV6: nk->port[0] = nk->port[1]; if (PF_ANEQ(saddr, &nk->addr[pd->sidx], AF_INET6)) pf_change_a6(saddr, &pd->hdr.icmp6->icmp6_cksum, &nk->addr[pd->sidx], 0); if (PF_ANEQ(daddr, &nk->addr[pd->didx], AF_INET6)) pf_change_a6(daddr, &pd->hdr.icmp6->icmp6_cksum, &nk->addr[pd->didx], 0); rewrite++; break; #endif /* INET */ default: switch (af) { #ifdef INET case AF_INET: if (PF_ANEQ(saddr, &nk->addr[pd->sidx], AF_INET)) pf_change_a(&saddr->v4.s_addr, pd->ip_sum, nk->addr[pd->sidx].v4.s_addr, 0); if (PF_ANEQ(daddr, &nk->addr[pd->didx], AF_INET)) pf_change_a(&daddr->v4.s_addr, pd->ip_sum, nk->addr[pd->didx].v4.s_addr, 0); break; #endif /* INET */ #ifdef INET6 case AF_INET6: if (PF_ANEQ(saddr, &nk->addr[pd->sidx], AF_INET6)) PF_ACPY(saddr, &nk->addr[pd->sidx], af); if (PF_ANEQ(daddr, &nk->addr[pd->didx], AF_INET6)) PF_ACPY(saddr, &nk->addr[pd->didx], af); break; #endif /* INET */ } break; } if (nr->natpass) r = NULL; pd->nat_rule = nr; } while (r != NULL) { r->evaluations++; if (pfi_kif_match(r->kif, kif) == r->ifnot) r = r->skip[PF_SKIP_IFP].ptr; else if (r->direction && r->direction != direction) r = r->skip[PF_SKIP_DIR].ptr; else if (r->af && r->af != af) r = r->skip[PF_SKIP_AF].ptr; else if (r->proto && r->proto != pd->proto) r = r->skip[PF_SKIP_PROTO].ptr; else if (PF_MISMATCHAW(&r->src.addr, saddr, af, r->src.neg, kif, M_GETFIB(m))) r = r->skip[PF_SKIP_SRC_ADDR].ptr; /* tcp/udp only. port_op always 0 in other cases */ else if (r->src.port_op && !pf_match_port(r->src.port_op, r->src.port[0], r->src.port[1], sport)) r = r->skip[PF_SKIP_SRC_PORT].ptr; else if (PF_MISMATCHAW(&r->dst.addr, daddr, af, r->dst.neg, NULL, M_GETFIB(m))) r = r->skip[PF_SKIP_DST_ADDR].ptr; /* tcp/udp only. port_op always 0 in other cases */ else if (r->dst.port_op && !pf_match_port(r->dst.port_op, r->dst.port[0], r->dst.port[1], dport)) r = r->skip[PF_SKIP_DST_PORT].ptr; /* icmp only. type always 0 in other cases */ else if (r->type && r->type != icmptype + 1) r = TAILQ_NEXT(r, entries); /* icmp only. type always 0 in other cases */ else if (r->code && r->code != icmpcode + 1) r = TAILQ_NEXT(r, entries); else if (r->tos && !(r->tos == pd->tos)) r = TAILQ_NEXT(r, entries); else if (r->rule_flag & PFRULE_FRAGMENT) r = TAILQ_NEXT(r, entries); else if (pd->proto == IPPROTO_TCP && (r->flagset & th->th_flags) != r->flags) r = TAILQ_NEXT(r, entries); /* tcp/udp only. uid.op always 0 in other cases */ else if (r->uid.op && (pd->lookup.done || (pd->lookup.done = #ifdef __FreeBSD__ pf_socket_lookup(direction, pd, inp), 1)) && #else pf_socket_lookup(direction, pd), 1)) && #endif !pf_match_uid(r->uid.op, r->uid.uid[0], r->uid.uid[1], pd->lookup.uid)) r = TAILQ_NEXT(r, entries); /* tcp/udp only. gid.op always 0 in other cases */ else if (r->gid.op && (pd->lookup.done || (pd->lookup.done = #ifdef __FreeBSD__ pf_socket_lookup(direction, pd, inp), 1)) && #else pf_socket_lookup(direction, pd), 1)) && #endif !pf_match_gid(r->gid.op, r->gid.gid[0], r->gid.gid[1], pd->lookup.gid)) r = TAILQ_NEXT(r, entries); else if (r->prob && #ifdef __FreeBSD__ r->prob <= arc4random()) #else r->prob <= arc4random_uniform(UINT_MAX - 1) + 1) #endif r = TAILQ_NEXT(r, entries); #ifdef __FreeBSD__ else if (r->match_tag && !pf_match_tag(m, r, &tag, pd->pf_mtag)) #else else if (r->match_tag && !pf_match_tag(m, r, &tag)) #endif r = TAILQ_NEXT(r, entries); else if (r->os_fingerprint != PF_OSFP_ANY && (pd->proto != IPPROTO_TCP || !pf_osfp_match( pf_osfp_fingerprint(pd, m, off, th), r->os_fingerprint))) r = TAILQ_NEXT(r, entries); else { if (r->tag) tag = r->tag; if (r->rtableid >= 0) rtableid = r->rtableid; if (r->anchor == NULL) { match = 1; *rm = r; *am = a; *rsm = ruleset; if ((*rm)->quick) break; r = TAILQ_NEXT(r, entries); } else pf_step_into_anchor(&asd, &ruleset, PF_RULESET_FILTER, &r, &a, &match); } if (r == NULL && pf_step_out_of_anchor(&asd, &ruleset, PF_RULESET_FILTER, &r, &a, &match)) break; } r = *rm; a = *am; ruleset = *rsm; REASON_SET(&reason, PFRES_MATCH); if (r->log || (nr != NULL && nr->log)) { if (rewrite) m_copyback(m, off, hdrlen, pd->hdr.any); PFLOG_PACKET(kif, h, m, af, direction, reason, r->log ? r : nr, a, ruleset, pd); } if ((r->action == PF_DROP) && ((r->rule_flag & PFRULE_RETURNRST) || (r->rule_flag & PFRULE_RETURNICMP) || (r->rule_flag & PFRULE_RETURN))) { /* undo NAT changes, if they have taken place */ if (nr != NULL) { PF_ACPY(saddr, &sk->addr[pd->sidx], af); PF_ACPY(daddr, &sk->addr[pd->didx], af); if (pd->sport) *pd->sport = sk->port[pd->sidx]; if (pd->dport) *pd->dport = sk->port[pd->didx]; if (pd->proto_sum) *pd->proto_sum = bproto_sum; if (pd->ip_sum) *pd->ip_sum = bip_sum; m_copyback(m, off, hdrlen, pd->hdr.any); } if (pd->proto == IPPROTO_TCP && ((r->rule_flag & PFRULE_RETURNRST) || (r->rule_flag & PFRULE_RETURN)) && !(th->th_flags & TH_RST)) { u_int32_t ack = ntohl(th->th_seq) + pd->p_len; int len = 0; #ifdef INET struct ip *h4; #endif #ifdef INET6 struct ip6_hdr *h6; #endif switch (af) { #ifdef INET case AF_INET: h4 = mtod(m, struct ip *); len = ntohs(h4->ip_len) - off; break; #endif #ifdef INET6 case AF_INET6: h6 = mtod(m, struct ip6_hdr *); len = ntohs(h6->ip6_plen) - (off - sizeof(*h6)); break; #endif } if (pf_check_proto_cksum(m, off, len, IPPROTO_TCP, af)) REASON_SET(&reason, PFRES_PROTCKSUM); else { if (th->th_flags & TH_SYN) ack++; if (th->th_flags & TH_FIN) ack++; #ifdef __FreeBSD__ pf_send_tcp(m, r, af, pd->dst, #else pf_send_tcp(r, af, pd->dst, #endif pd->src, th->th_dport, th->th_sport, ntohl(th->th_ack), ack, TH_RST|TH_ACK, 0, 0, r->return_ttl, 1, 0, pd->eh, kif->pfik_ifp); } } else if (pd->proto != IPPROTO_ICMP && af == AF_INET && r->return_icmp) pf_send_icmp(m, r->return_icmp >> 8, r->return_icmp & 255, af, r); else if (pd->proto != IPPROTO_ICMPV6 && af == AF_INET6 && r->return_icmp6) pf_send_icmp(m, r->return_icmp6 >> 8, r->return_icmp6 & 255, af, r); } if (r->action == PF_DROP) goto cleanup; #ifdef __FreeBSD__ if (pf_tag_packet(m, tag, rtableid, pd->pf_mtag)) { #else if (pf_tag_packet(m, tag, rtableid)) { #endif REASON_SET(&reason, PFRES_MEMORY); goto cleanup; } if (!state_icmp && (r->keep_state || nr != NULL || (pd->flags & PFDESC_TCP_NORM))) { int action; action = pf_create_state(r, nr, a, pd, nsn, skw, sks, nk, sk, m, off, sport, dport, &rewrite, kif, sm, tag, bproto_sum, bip_sum, hdrlen); if (action != PF_PASS) return (action); } else { #ifdef __FreeBSD__ if (sk != NULL) pool_put(&V_pf_state_key_pl, sk); if (nk != NULL) pool_put(&V_pf_state_key_pl, nk); #else if (sk != NULL) pool_put(&pf_state_key_pl, sk); if (nk != NULL) pool_put(&pf_state_key_pl, nk); #endif } /* copy back packet headers if we performed NAT operations */ if (rewrite) m_copyback(m, off, hdrlen, pd->hdr.any); #if NPFSYNC > 0 if (*sm != NULL && !ISSET((*sm)->state_flags, PFSTATE_NOSYNC) && #ifdef __FreeBSD__ direction == PF_OUT && pfsync_up_ptr != NULL && pfsync_up_ptr()) { #else direction == PF_OUT && pfsync_up()) { #endif /* * We want the state created, but we dont * want to send this in case a partner * firewall has to know about it to allow * replies through it. */ #ifdef __FreeBSD__ if (pfsync_defer_ptr != NULL && pfsync_defer_ptr(*sm, m)) #else if (pfsync_defer(*sm, m)) #endif return (PF_DEFER); } #endif return (PF_PASS); cleanup: #ifdef __FreeBSD__ if (sk != NULL) pool_put(&V_pf_state_key_pl, sk); if (nk != NULL) pool_put(&V_pf_state_key_pl, nk); #else if (sk != NULL) pool_put(&pf_state_key_pl, sk); if (nk != NULL) pool_put(&pf_state_key_pl, nk); #endif return (PF_DROP); } static __inline int pf_create_state(struct pf_rule *r, struct pf_rule *nr, struct pf_rule *a, struct pf_pdesc *pd, struct pf_src_node *nsn, struct pf_state_key *skw, struct pf_state_key *sks, struct pf_state_key *nk, struct pf_state_key *sk, struct mbuf *m, int off, u_int16_t sport, u_int16_t dport, int *rewrite, struct pfi_kif *kif, struct pf_state **sm, int tag, u_int16_t bproto_sum, u_int16_t bip_sum, int hdrlen) { struct pf_state *s = NULL; struct pf_src_node *sn = NULL; struct tcphdr *th = pd->hdr.tcp; #ifdef __FreeBSD__ u_int16_t mss = V_tcp_mssdflt; #else u_int16_t mss = tcp_mssdflt; #endif u_short reason; /* check maximums */ if (r->max_states && (r->states_cur >= r->max_states)) { #ifdef __FreeBSD__ V_pf_status.lcounters[LCNT_STATES]++; #else pf_status.lcounters[LCNT_STATES]++; #endif REASON_SET(&reason, PFRES_MAXSTATES); return (PF_DROP); } /* src node for filter rule */ if ((r->rule_flag & PFRULE_SRCTRACK || r->rpool.opts & PF_POOL_STICKYADDR) && pf_insert_src_node(&sn, r, pd->src, pd->af) != 0) { REASON_SET(&reason, PFRES_SRCLIMIT); goto csfailed; } /* src node for translation rule */ if (nr != NULL && (nr->rpool.opts & PF_POOL_STICKYADDR) && pf_insert_src_node(&nsn, nr, &sk->addr[pd->sidx], pd->af)) { REASON_SET(&reason, PFRES_SRCLIMIT); goto csfailed; } #ifdef __FreeBSD__ s = pool_get(&V_pf_state_pl, PR_NOWAIT | PR_ZERO); #else s = pool_get(&pf_state_pl, PR_NOWAIT | PR_ZERO); #endif if (s == NULL) { REASON_SET(&reason, PFRES_MEMORY); goto csfailed; } s->rule.ptr = r; s->nat_rule.ptr = nr; s->anchor.ptr = a; STATE_INC_COUNTERS(s); if (r->allow_opts) s->state_flags |= PFSTATE_ALLOWOPTS; if (r->rule_flag & PFRULE_STATESLOPPY) s->state_flags |= PFSTATE_SLOPPY; if (r->rule_flag & PFRULE_PFLOW) s->state_flags |= PFSTATE_PFLOW; s->log = r->log & PF_LOG_ALL; s->sync_state = PFSYNC_S_NONE; if (nr != NULL) s->log |= nr->log & PF_LOG_ALL; switch (pd->proto) { case IPPROTO_TCP: s->src.seqlo = ntohl(th->th_seq); s->src.seqhi = s->src.seqlo + pd->p_len + 1; if ((th->th_flags & (TH_SYN|TH_ACK)) == TH_SYN && r->keep_state == PF_STATE_MODULATE) { /* Generate sequence number modulator */ if ((s->src.seqdiff = pf_tcp_iss(pd) - s->src.seqlo) == 0) s->src.seqdiff = 1; - pf_change_a(&th->th_seq, &th->th_sum, + pf_change_proto_a(m, &th->th_seq, &th->th_sum, htonl(s->src.seqlo + s->src.seqdiff), 0); *rewrite = 1; } else s->src.seqdiff = 0; if (th->th_flags & TH_SYN) { s->src.seqhi++; s->src.wscale = pf_get_wscale(m, off, th->th_off, pd->af); } s->src.max_win = MAX(ntohs(th->th_win), 1); if (s->src.wscale & PF_WSCALE_MASK) { /* Remove scale factor from initial window */ int win = s->src.max_win; win += 1 << (s->src.wscale & PF_WSCALE_MASK); s->src.max_win = (win - 1) >> (s->src.wscale & PF_WSCALE_MASK); } if (th->th_flags & TH_FIN) s->src.seqhi++; s->dst.seqhi = 1; s->dst.max_win = 1; s->src.state = TCPS_SYN_SENT; s->dst.state = TCPS_CLOSED; s->timeout = PFTM_TCP_FIRST_PACKET; break; case IPPROTO_UDP: s->src.state = PFUDPS_SINGLE; s->dst.state = PFUDPS_NO_TRAFFIC; s->timeout = PFTM_UDP_FIRST_PACKET; break; case IPPROTO_ICMP: #ifdef INET6 case IPPROTO_ICMPV6: #endif s->timeout = PFTM_ICMP_FIRST_PACKET; break; default: s->src.state = PFOTHERS_SINGLE; s->dst.state = PFOTHERS_NO_TRAFFIC; s->timeout = PFTM_OTHER_FIRST_PACKET; } s->creation = time_second; s->expire = time_second; if (sn != NULL) { s->src_node = sn; s->src_node->states++; } if (nsn != NULL) { /* XXX We only modify one side for now. */ PF_ACPY(&nsn->raddr, &nk->addr[1], pd->af); s->nat_src_node = nsn; s->nat_src_node->states++; } if (pd->proto == IPPROTO_TCP) { if ((pd->flags & PFDESC_TCP_NORM) && pf_normalize_tcp_init(m, off, pd, th, &s->src, &s->dst)) { REASON_SET(&reason, PFRES_MEMORY); pf_src_tree_remove_state(s); STATE_DEC_COUNTERS(s); #ifdef __FreeBSD__ pool_put(&V_pf_state_pl, s); #else pool_put(&pf_state_pl, s); #endif return (PF_DROP); } if ((pd->flags & PFDESC_TCP_NORM) && s->src.scrub && pf_normalize_tcp_stateful(m, off, pd, &reason, th, s, &s->src, &s->dst, rewrite)) { /* This really shouldn't happen!!! */ DPFPRINTF(PF_DEBUG_URGENT, ("pf_normalize_tcp_stateful failed on first pkt")); pf_normalize_tcp_cleanup(s); pf_src_tree_remove_state(s); STATE_DEC_COUNTERS(s); #ifdef __FreeBSD__ pool_put(&V_pf_state_pl, s); #else pool_put(&pf_state_pl, s); #endif return (PF_DROP); } } s->direction = pd->dir; if (sk == NULL && pf_state_key_setup(pd, nr, &skw, &sks, &sk, &nk, pd->src, pd->dst, sport, dport)) goto csfailed; if (pf_state_insert(BOUND_IFACE(r, kif), skw, sks, s)) { if (pd->proto == IPPROTO_TCP) pf_normalize_tcp_cleanup(s); REASON_SET(&reason, PFRES_STATEINS); pf_src_tree_remove_state(s); STATE_DEC_COUNTERS(s); #ifdef __FreeBSD__ pool_put(&V_pf_state_pl, s); #else pool_put(&pf_state_pl, s); #endif return (PF_DROP); } else *sm = s; pf_set_rt_ifp(s, pd->src); /* needs s->state_key set */ if (tag > 0) { pf_tag_ref(tag); s->tag = tag; } if (pd->proto == IPPROTO_TCP && (th->th_flags & (TH_SYN|TH_ACK)) == TH_SYN && r->keep_state == PF_STATE_SYNPROXY) { s->src.state = PF_TCPS_PROXY_SRC; /* undo NAT changes, if they have taken place */ if (nr != NULL) { struct pf_state_key *skt = s->key[PF_SK_WIRE]; if (pd->dir == PF_OUT) skt = s->key[PF_SK_STACK]; PF_ACPY(pd->src, &skt->addr[pd->sidx], pd->af); PF_ACPY(pd->dst, &skt->addr[pd->didx], pd->af); if (pd->sport) *pd->sport = skt->port[pd->sidx]; if (pd->dport) *pd->dport = skt->port[pd->didx]; if (pd->proto_sum) *pd->proto_sum = bproto_sum; if (pd->ip_sum) *pd->ip_sum = bip_sum; m_copyback(m, off, hdrlen, pd->hdr.any); } s->src.seqhi = htonl(arc4random()); /* Find mss option */ int rtid = M_GETFIB(m); mss = pf_get_mss(m, off, th->th_off, pd->af); mss = pf_calc_mss(pd->src, pd->af, rtid, mss); mss = pf_calc_mss(pd->dst, pd->af, rtid, mss); s->src.mss = mss; #ifdef __FreeBSD__ pf_send_tcp(NULL, r, pd->af, pd->dst, pd->src, th->th_dport, #else pf_send_tcp(r, pd->af, pd->dst, pd->src, th->th_dport, #endif th->th_sport, s->src.seqhi, ntohl(th->th_seq) + 1, TH_SYN|TH_ACK, 0, s->src.mss, 0, 1, 0, NULL, NULL); REASON_SET(&reason, PFRES_SYNPROXY); return (PF_SYNPROXY_DROP); } return (PF_PASS); csfailed: #ifdef __FreeBSD__ if (sk != NULL) pool_put(&V_pf_state_key_pl, sk); if (nk != NULL) pool_put(&V_pf_state_key_pl, nk); #else if (sk != NULL) pool_put(&pf_state_key_pl, sk); if (nk != NULL) pool_put(&pf_state_key_pl, nk); #endif if (sn != NULL && sn->states == 0 && sn->expire == 0) { #ifdef __FreeBSD__ RB_REMOVE(pf_src_tree, &V_tree_src_tracking, sn); V_pf_status.scounters[SCNT_SRC_NODE_REMOVALS]++; V_pf_status.src_nodes--; pool_put(&V_pf_src_tree_pl, sn); #else RB_REMOVE(pf_src_tree, &tree_src_tracking, sn); pf_status.scounters[SCNT_SRC_NODE_REMOVALS]++; pf_status.src_nodes--; pool_put(&pf_src_tree_pl, sn); #endif } if (nsn != sn && nsn != NULL && nsn->states == 0 && nsn->expire == 0) { #ifdef __FreeBSD__ RB_REMOVE(pf_src_tree, &V_tree_src_tracking, nsn); V_pf_status.scounters[SCNT_SRC_NODE_REMOVALS]++; V_pf_status.src_nodes--; pool_put(&V_pf_src_tree_pl, nsn); #else RB_REMOVE(pf_src_tree, &tree_src_tracking, nsn); pf_status.scounters[SCNT_SRC_NODE_REMOVALS]++; pf_status.src_nodes--; pool_put(&pf_src_tree_pl, nsn); #endif } return (PF_DROP); } int pf_test_fragment(struct pf_rule **rm, int direction, struct pfi_kif *kif, struct mbuf *m, void *h, struct pf_pdesc *pd, struct pf_rule **am, struct pf_ruleset **rsm) { struct pf_rule *r, *a = NULL; struct pf_ruleset *ruleset = NULL; sa_family_t af = pd->af; u_short reason; int tag = -1; int asd = 0; int match = 0; r = TAILQ_FIRST(pf_main_ruleset.rules[PF_RULESET_FILTER].active.ptr); while (r != NULL) { r->evaluations++; if (pfi_kif_match(r->kif, kif) == r->ifnot) r = r->skip[PF_SKIP_IFP].ptr; else if (r->direction && r->direction != direction) r = r->skip[PF_SKIP_DIR].ptr; else if (r->af && r->af != af) r = r->skip[PF_SKIP_AF].ptr; else if (r->proto && r->proto != pd->proto) r = r->skip[PF_SKIP_PROTO].ptr; else if (PF_MISMATCHAW(&r->src.addr, pd->src, af, r->src.neg, kif, M_GETFIB(m))) r = r->skip[PF_SKIP_SRC_ADDR].ptr; else if (PF_MISMATCHAW(&r->dst.addr, pd->dst, af, r->dst.neg, NULL, M_GETFIB(m))) r = r->skip[PF_SKIP_DST_ADDR].ptr; else if (r->tos && !(r->tos == pd->tos)) r = TAILQ_NEXT(r, entries); else if (r->os_fingerprint != PF_OSFP_ANY) r = TAILQ_NEXT(r, entries); else if (pd->proto == IPPROTO_UDP && (r->src.port_op || r->dst.port_op)) r = TAILQ_NEXT(r, entries); else if (pd->proto == IPPROTO_TCP && (r->src.port_op || r->dst.port_op || r->flagset)) r = TAILQ_NEXT(r, entries); else if ((pd->proto == IPPROTO_ICMP || pd->proto == IPPROTO_ICMPV6) && (r->type || r->code)) r = TAILQ_NEXT(r, entries); else if (r->prob && r->prob <= (arc4random() % (UINT_MAX - 1) + 1)) r = TAILQ_NEXT(r, entries); #ifdef __FreeBSD__ else if (r->match_tag && !pf_match_tag(m, r, &tag, pd->pf_mtag)) #else else if (r->match_tag && !pf_match_tag(m, r, &tag)) #endif r = TAILQ_NEXT(r, entries); else { if (r->anchor == NULL) { match = 1; *rm = r; *am = a; *rsm = ruleset; if ((*rm)->quick) break; r = TAILQ_NEXT(r, entries); } else pf_step_into_anchor(&asd, &ruleset, PF_RULESET_FILTER, &r, &a, &match); } if (r == NULL && pf_step_out_of_anchor(&asd, &ruleset, PF_RULESET_FILTER, &r, &a, &match)) break; } r = *rm; a = *am; ruleset = *rsm; REASON_SET(&reason, PFRES_MATCH); if (r->log) PFLOG_PACKET(kif, h, m, af, direction, reason, r, a, ruleset, pd); if (r->action != PF_PASS) return (PF_DROP); #ifdef __FreeBSD__ if (pf_tag_packet(m, tag, -1, pd->pf_mtag)) { #else if (pf_tag_packet(m, tag, -1)) { #endif REASON_SET(&reason, PFRES_MEMORY); return (PF_DROP); } return (PF_PASS); } int pf_tcp_track_full(struct pf_state_peer *src, struct pf_state_peer *dst, struct pf_state **state, struct pfi_kif *kif, struct mbuf *m, int off, struct pf_pdesc *pd, u_short *reason, int *copyback) { struct tcphdr *th = pd->hdr.tcp; u_int16_t win = ntohs(th->th_win); u_int32_t ack, end, seq, orig_seq; u_int8_t sws, dws; int ackskew; if (src->wscale && dst->wscale && !(th->th_flags & TH_SYN)) { sws = src->wscale & PF_WSCALE_MASK; dws = dst->wscale & PF_WSCALE_MASK; } else sws = dws = 0; /* * Sequence tracking algorithm from Guido van Rooij's paper: * http://www.madison-gurkha.com/publications/tcp_filtering/ * tcp_filtering.ps */ orig_seq = seq = ntohl(th->th_seq); if (src->seqlo == 0) { /* First packet from this end. Set its state */ if ((pd->flags & PFDESC_TCP_NORM || dst->scrub) && src->scrub == NULL) { if (pf_normalize_tcp_init(m, off, pd, th, src, dst)) { REASON_SET(reason, PFRES_MEMORY); return (PF_DROP); } } /* Deferred generation of sequence number modulator */ if (dst->seqdiff && !src->seqdiff) { /* use random iss for the TCP server */ while ((src->seqdiff = arc4random() - seq) == 0) ; ack = ntohl(th->th_ack) - dst->seqdiff; - pf_change_a(&th->th_seq, &th->th_sum, htonl(seq + + pf_change_proto_a(m, &th->th_seq, &th->th_sum, htonl(seq + src->seqdiff), 0); - pf_change_a(&th->th_ack, &th->th_sum, htonl(ack), 0); + pf_change_proto_a(m, &th->th_ack, &th->th_sum, htonl(ack), 0); *copyback = 1; } else { ack = ntohl(th->th_ack); } end = seq + pd->p_len; if (th->th_flags & TH_SYN) { end++; if (dst->wscale & PF_WSCALE_FLAG) { src->wscale = pf_get_wscale(m, off, th->th_off, pd->af); if (src->wscale & PF_WSCALE_FLAG) { /* Remove scale factor from initial * window */ sws = src->wscale & PF_WSCALE_MASK; win = ((u_int32_t)win + (1 << sws) - 1) >> sws; dws = dst->wscale & PF_WSCALE_MASK; } else { /* fixup other window */ dst->max_win <<= dst->wscale & PF_WSCALE_MASK; /* in case of a retrans SYN|ACK */ dst->wscale = 0; } } } if (th->th_flags & TH_FIN) end++; src->seqlo = seq; if (src->state < TCPS_SYN_SENT) src->state = TCPS_SYN_SENT; /* * May need to slide the window (seqhi may have been set by * the crappy stack check or if we picked up the connection * after establishment) */ if (src->seqhi == 1 || SEQ_GEQ(end + MAX(1, dst->max_win << dws), src->seqhi)) src->seqhi = end + MAX(1, dst->max_win << dws); if (win > src->max_win) src->max_win = win; } else { ack = ntohl(th->th_ack) - dst->seqdiff; if (src->seqdiff) { /* Modulate sequence numbers */ - pf_change_a(&th->th_seq, &th->th_sum, htonl(seq + + pf_change_proto_a(m, &th->th_seq, &th->th_sum, htonl(seq + src->seqdiff), 0); - pf_change_a(&th->th_ack, &th->th_sum, htonl(ack), 0); + pf_change_proto_a(m, &th->th_ack, &th->th_sum, htonl(ack), 0); *copyback = 1; } end = seq + pd->p_len; if (th->th_flags & TH_SYN) end++; if (th->th_flags & TH_FIN) end++; } if ((th->th_flags & TH_ACK) == 0) { /* Let it pass through the ack skew check */ ack = dst->seqlo; } else if ((ack == 0 && (th->th_flags & (TH_ACK|TH_RST)) == (TH_ACK|TH_RST)) || /* broken tcp stacks do not set ack */ (dst->state < TCPS_SYN_SENT)) { /* * Many stacks (ours included) will set the ACK number in an * FIN|ACK if the SYN times out -- no sequence to ACK. */ ack = dst->seqlo; } if (seq == end) { /* Ease sequencing restrictions on no data packets */ seq = src->seqlo; end = seq; } ackskew = dst->seqlo - ack; /* * Need to demodulate the sequence numbers in any TCP SACK options * (Selective ACK). We could optionally validate the SACK values * against the current ACK window, either forwards or backwards, but * I'm not confident that SACK has been implemented properly * everywhere. It wouldn't surprise me if several stacks accidently * SACK too far backwards of previously ACKed data. There really aren't * any security implications of bad SACKing unless the target stack * doesn't validate the option length correctly. Someone trying to * spoof into a TCP connection won't bother blindly sending SACK * options anyway. */ if (dst->seqdiff && (th->th_off << 2) > sizeof(struct tcphdr)) { if (pf_modulate_sack(m, off, pd, th, dst)) *copyback = 1; } #define MAXACKWINDOW (0xffff + 1500) /* 1500 is an arbitrary fudge factor */ if (SEQ_GEQ(src->seqhi, end) && /* Last octet inside other's window space */ SEQ_GEQ(seq, src->seqlo - (dst->max_win << dws)) && /* Retrans: not more than one window back */ (ackskew >= -MAXACKWINDOW) && /* Acking not more than one reassembled fragment backwards */ (ackskew <= (MAXACKWINDOW << sws)) && /* Acking not more than one window forward */ ((th->th_flags & TH_RST) == 0 || orig_seq == src->seqlo || (orig_seq == src->seqlo + 1) || (orig_seq + 1 == src->seqlo) || (pd->flags & PFDESC_IP_REAS) == 0)) { /* Require an exact/+1 sequence match on resets when possible */ if (dst->scrub || src->scrub) { if (pf_normalize_tcp_stateful(m, off, pd, reason, th, *state, src, dst, copyback)) return (PF_DROP); } /* update max window */ if (src->max_win < win) src->max_win = win; /* synchronize sequencing */ if (SEQ_GT(end, src->seqlo)) src->seqlo = end; /* slide the window of what the other end can send */ if (SEQ_GEQ(ack + (win << sws), dst->seqhi)) dst->seqhi = ack + MAX((win << sws), 1); /* update states */ if (th->th_flags & TH_SYN) if (src->state < TCPS_SYN_SENT) src->state = TCPS_SYN_SENT; if (th->th_flags & TH_FIN) if (src->state < TCPS_CLOSING) src->state = TCPS_CLOSING; if (th->th_flags & TH_ACK) { if (dst->state == TCPS_SYN_SENT) { dst->state = TCPS_ESTABLISHED; if (src->state == TCPS_ESTABLISHED && (*state)->src_node != NULL && pf_src_connlimit(state)) { REASON_SET(reason, PFRES_SRCLIMIT); return (PF_DROP); } } else if (dst->state == TCPS_CLOSING) dst->state = TCPS_FIN_WAIT_2; } if (th->th_flags & TH_RST) src->state = dst->state = TCPS_TIME_WAIT; /* update expire time */ (*state)->expire = time_second; if (src->state >= TCPS_FIN_WAIT_2 && dst->state >= TCPS_FIN_WAIT_2) (*state)->timeout = PFTM_TCP_CLOSED; else if (src->state >= TCPS_CLOSING && dst->state >= TCPS_CLOSING) (*state)->timeout = PFTM_TCP_FIN_WAIT; else if (src->state < TCPS_ESTABLISHED || dst->state < TCPS_ESTABLISHED) (*state)->timeout = PFTM_TCP_OPENING; else if (src->state >= TCPS_CLOSING || dst->state >= TCPS_CLOSING) (*state)->timeout = PFTM_TCP_CLOSING; else (*state)->timeout = PFTM_TCP_ESTABLISHED; /* Fall through to PASS packet */ } else if ((dst->state < TCPS_SYN_SENT || dst->state >= TCPS_FIN_WAIT_2 || src->state >= TCPS_FIN_WAIT_2) && SEQ_GEQ(src->seqhi + MAXACKWINDOW, end) && /* Within a window forward of the originating packet */ SEQ_GEQ(seq, src->seqlo - MAXACKWINDOW)) { /* Within a window backward of the originating packet */ /* * This currently handles three situations: * 1) Stupid stacks will shotgun SYNs before their peer * replies. * 2) When PF catches an already established stream (the * firewall rebooted, the state table was flushed, routes * changed...) * 3) Packets get funky immediately after the connection * closes (this should catch Solaris spurious ACK|FINs * that web servers like to spew after a close) * * This must be a little more careful than the above code * since packet floods will also be caught here. We don't * update the TTL here to mitigate the damage of a packet * flood and so the same code can handle awkward establishment * and a loosened connection close. * In the establishment case, a correct peer response will * validate the connection, go through the normal state code * and keep updating the state TTL. */ #ifdef __FreeBSD__ if (V_pf_status.debug >= PF_DEBUG_MISC) { #else if (pf_status.debug >= PF_DEBUG_MISC) { #endif printf("pf: loose state match: "); pf_print_state(*state); pf_print_flags(th->th_flags); printf(" seq=%u (%u) ack=%u len=%u ackskew=%d " "pkts=%llu:%llu dir=%s,%s\n", seq, orig_seq, ack, #ifdef __FreeBSD__ pd->p_len, ackskew, (unsigned long long)(*state)->packets[0], (unsigned long long)(*state)->packets[1], #else pd->p_len, ackskew, (*state)->packets[0], (*state)->packets[1], #endif pd->dir == PF_IN ? "in" : "out", pd->dir == (*state)->direction ? "fwd" : "rev"); } if (dst->scrub || src->scrub) { if (pf_normalize_tcp_stateful(m, off, pd, reason, th, *state, src, dst, copyback)) return (PF_DROP); } /* update max window */ if (src->max_win < win) src->max_win = win; /* synchronize sequencing */ if (SEQ_GT(end, src->seqlo)) src->seqlo = end; /* slide the window of what the other end can send */ if (SEQ_GEQ(ack + (win << sws), dst->seqhi)) dst->seqhi = ack + MAX((win << sws), 1); /* * Cannot set dst->seqhi here since this could be a shotgunned * SYN and not an already established connection. */ if (th->th_flags & TH_FIN) if (src->state < TCPS_CLOSING) src->state = TCPS_CLOSING; if (th->th_flags & TH_RST) src->state = dst->state = TCPS_TIME_WAIT; /* Fall through to PASS packet */ } else { if ((*state)->dst.state == TCPS_SYN_SENT && (*state)->src.state == TCPS_SYN_SENT) { /* Send RST for state mismatches during handshake */ if (!(th->th_flags & TH_RST)) #ifdef __FreeBSD__ pf_send_tcp(NULL, (*state)->rule.ptr, pd->af, #else pf_send_tcp((*state)->rule.ptr, pd->af, #endif pd->dst, pd->src, th->th_dport, th->th_sport, ntohl(th->th_ack), 0, TH_RST, 0, 0, (*state)->rule.ptr->return_ttl, 1, 0, pd->eh, kif->pfik_ifp); src->seqlo = 0; src->seqhi = 1; src->max_win = 1; #ifdef __FreeBSD__ } else if (V_pf_status.debug >= PF_DEBUG_MISC) { #else } else if (pf_status.debug >= PF_DEBUG_MISC) { #endif printf("pf: BAD state: "); pf_print_state(*state); pf_print_flags(th->th_flags); printf(" seq=%u (%u) ack=%u len=%u ackskew=%d " "pkts=%llu:%llu dir=%s,%s\n", seq, orig_seq, ack, pd->p_len, ackskew, #ifdef __FreeBSD__ (unsigned long long)(*state)->packets[0], (unsigned long long)(*state)->packets[1], #else (*state)->packets[0], (*state)->packets[1], #endif pd->dir == PF_IN ? "in" : "out", pd->dir == (*state)->direction ? "fwd" : "rev"); printf("pf: State failure on: %c %c %c %c | %c %c\n", SEQ_GEQ(src->seqhi, end) ? ' ' : '1', SEQ_GEQ(seq, src->seqlo - (dst->max_win << dws)) ? ' ': '2', (ackskew >= -MAXACKWINDOW) ? ' ' : '3', (ackskew <= (MAXACKWINDOW << sws)) ? ' ' : '4', SEQ_GEQ(src->seqhi + MAXACKWINDOW, end) ?' ' :'5', SEQ_GEQ(seq, src->seqlo - MAXACKWINDOW) ?' ' :'6'); } REASON_SET(reason, PFRES_BADSTATE); return (PF_DROP); } return (PF_PASS); } int pf_tcp_track_sloppy(struct pf_state_peer *src, struct pf_state_peer *dst, struct pf_state **state, struct pf_pdesc *pd, u_short *reason) { struct tcphdr *th = pd->hdr.tcp; if (th->th_flags & TH_SYN) if (src->state < TCPS_SYN_SENT) src->state = TCPS_SYN_SENT; if (th->th_flags & TH_FIN) if (src->state < TCPS_CLOSING) src->state = TCPS_CLOSING; if (th->th_flags & TH_ACK) { if (dst->state == TCPS_SYN_SENT) { dst->state = TCPS_ESTABLISHED; if (src->state == TCPS_ESTABLISHED && (*state)->src_node != NULL && pf_src_connlimit(state)) { REASON_SET(reason, PFRES_SRCLIMIT); return (PF_DROP); } } else if (dst->state == TCPS_CLOSING) { dst->state = TCPS_FIN_WAIT_2; } else if (src->state == TCPS_SYN_SENT && dst->state < TCPS_SYN_SENT) { /* * Handle a special sloppy case where we only see one * half of the connection. If there is a ACK after * the initial SYN without ever seeing a packet from * the destination, set the connection to established. */ dst->state = src->state = TCPS_ESTABLISHED; if ((*state)->src_node != NULL && pf_src_connlimit(state)) { REASON_SET(reason, PFRES_SRCLIMIT); return (PF_DROP); } } else if (src->state == TCPS_CLOSING && dst->state == TCPS_ESTABLISHED && dst->seqlo == 0) { /* * Handle the closing of half connections where we * don't see the full bidirectional FIN/ACK+ACK * handshake. */ dst->state = TCPS_CLOSING; } } if (th->th_flags & TH_RST) src->state = dst->state = TCPS_TIME_WAIT; /* update expire time */ (*state)->expire = time_second; if (src->state >= TCPS_FIN_WAIT_2 && dst->state >= TCPS_FIN_WAIT_2) (*state)->timeout = PFTM_TCP_CLOSED; else if (src->state >= TCPS_CLOSING && dst->state >= TCPS_CLOSING) (*state)->timeout = PFTM_TCP_FIN_WAIT; else if (src->state < TCPS_ESTABLISHED || dst->state < TCPS_ESTABLISHED) (*state)->timeout = PFTM_TCP_OPENING; else if (src->state >= TCPS_CLOSING || dst->state >= TCPS_CLOSING) (*state)->timeout = PFTM_TCP_CLOSING; else (*state)->timeout = PFTM_TCP_ESTABLISHED; return (PF_PASS); } int pf_test_state_tcp(struct pf_state **state, int direction, struct pfi_kif *kif, struct mbuf *m, int off, void *h, struct pf_pdesc *pd, u_short *reason) { struct pf_state_key_cmp key; struct tcphdr *th = pd->hdr.tcp; int copyback = 0; struct pf_state_peer *src, *dst; struct pf_state_key *sk; key.af = pd->af; key.proto = IPPROTO_TCP; if (direction == PF_IN) { /* wire side, straight */ PF_ACPY(&key.addr[0], pd->src, key.af); PF_ACPY(&key.addr[1], pd->dst, key.af); key.port[0] = th->th_sport; key.port[1] = th->th_dport; } else { /* stack side, reverse */ PF_ACPY(&key.addr[1], pd->src, key.af); PF_ACPY(&key.addr[0], pd->dst, key.af); key.port[1] = th->th_sport; key.port[0] = th->th_dport; } #ifdef __FreeBSD__ STATE_LOOKUP(kif, &key, direction, *state, m, pd->pf_mtag); #else STATE_LOOKUP(kif, &key, direction, *state, m); #endif if (direction == (*state)->direction) { src = &(*state)->src; dst = &(*state)->dst; } else { src = &(*state)->dst; dst = &(*state)->src; } sk = (*state)->key[pd->didx]; if ((*state)->src.state == PF_TCPS_PROXY_SRC) { if (direction != (*state)->direction) { REASON_SET(reason, PFRES_SYNPROXY); return (PF_SYNPROXY_DROP); } if (th->th_flags & TH_SYN) { if (ntohl(th->th_seq) != (*state)->src.seqlo) { REASON_SET(reason, PFRES_SYNPROXY); return (PF_DROP); } #ifdef __FreeBSD__ pf_send_tcp(NULL, (*state)->rule.ptr, pd->af, pd->dst, #else pf_send_tcp((*state)->rule.ptr, pd->af, pd->dst, #endif pd->src, th->th_dport, th->th_sport, (*state)->src.seqhi, ntohl(th->th_seq) + 1, TH_SYN|TH_ACK, 0, (*state)->src.mss, 0, 1, 0, NULL, NULL); REASON_SET(reason, PFRES_SYNPROXY); return (PF_SYNPROXY_DROP); } else if (!(th->th_flags & TH_ACK) || (ntohl(th->th_ack) != (*state)->src.seqhi + 1) || (ntohl(th->th_seq) != (*state)->src.seqlo + 1)) { REASON_SET(reason, PFRES_SYNPROXY); return (PF_DROP); } else if ((*state)->src_node != NULL && pf_src_connlimit(state)) { REASON_SET(reason, PFRES_SRCLIMIT); return (PF_DROP); } else (*state)->src.state = PF_TCPS_PROXY_DST; } if ((*state)->src.state == PF_TCPS_PROXY_DST) { if (direction == (*state)->direction) { if (((th->th_flags & (TH_SYN|TH_ACK)) != TH_ACK) || (ntohl(th->th_ack) != (*state)->src.seqhi + 1) || (ntohl(th->th_seq) != (*state)->src.seqlo + 1)) { REASON_SET(reason, PFRES_SYNPROXY); return (PF_DROP); } (*state)->src.max_win = MAX(ntohs(th->th_win), 1); if ((*state)->dst.seqhi == 1) (*state)->dst.seqhi = htonl(arc4random()); #ifdef __FreeBSD__ pf_send_tcp(NULL, (*state)->rule.ptr, pd->af, #else pf_send_tcp((*state)->rule.ptr, pd->af, #endif &sk->addr[pd->sidx], &sk->addr[pd->didx], sk->port[pd->sidx], sk->port[pd->didx], (*state)->dst.seqhi, 0, TH_SYN, 0, (*state)->src.mss, 0, 0, (*state)->tag, NULL, NULL); REASON_SET(reason, PFRES_SYNPROXY); return (PF_SYNPROXY_DROP); } else if (((th->th_flags & (TH_SYN|TH_ACK)) != (TH_SYN|TH_ACK)) || (ntohl(th->th_ack) != (*state)->dst.seqhi + 1)) { REASON_SET(reason, PFRES_SYNPROXY); return (PF_DROP); } else { (*state)->dst.max_win = MAX(ntohs(th->th_win), 1); (*state)->dst.seqlo = ntohl(th->th_seq); #ifdef __FreeBSD__ pf_send_tcp(NULL, (*state)->rule.ptr, pd->af, pd->dst, #else pf_send_tcp((*state)->rule.ptr, pd->af, pd->dst, #endif pd->src, th->th_dport, th->th_sport, ntohl(th->th_ack), ntohl(th->th_seq) + 1, TH_ACK, (*state)->src.max_win, 0, 0, 0, (*state)->tag, NULL, NULL); #ifdef __FreeBSD__ pf_send_tcp(NULL, (*state)->rule.ptr, pd->af, #else pf_send_tcp((*state)->rule.ptr, pd->af, #endif &sk->addr[pd->sidx], &sk->addr[pd->didx], sk->port[pd->sidx], sk->port[pd->didx], (*state)->src.seqhi + 1, (*state)->src.seqlo + 1, TH_ACK, (*state)->dst.max_win, 0, 0, 1, 0, NULL, NULL); (*state)->src.seqdiff = (*state)->dst.seqhi - (*state)->src.seqlo; (*state)->dst.seqdiff = (*state)->src.seqhi - (*state)->dst.seqlo; (*state)->src.seqhi = (*state)->src.seqlo + (*state)->dst.max_win; (*state)->dst.seqhi = (*state)->dst.seqlo + (*state)->src.max_win; (*state)->src.wscale = (*state)->dst.wscale = 0; (*state)->src.state = (*state)->dst.state = TCPS_ESTABLISHED; REASON_SET(reason, PFRES_SYNPROXY); return (PF_SYNPROXY_DROP); } } if (((th->th_flags & (TH_SYN|TH_ACK)) == TH_SYN) && dst->state >= TCPS_FIN_WAIT_2 && src->state >= TCPS_FIN_WAIT_2) { #ifdef __FreeBSD__ if (V_pf_status.debug >= PF_DEBUG_MISC) { #else if (pf_status.debug >= PF_DEBUG_MISC) { #endif printf("pf: state reuse "); pf_print_state(*state); pf_print_flags(th->th_flags); printf("\n"); } /* XXX make sure it's the same direction ?? */ (*state)->src.state = (*state)->dst.state = TCPS_CLOSED; pf_unlink_state(*state); *state = NULL; return (PF_DROP); } if ((*state)->state_flags & PFSTATE_SLOPPY) { if (pf_tcp_track_sloppy(src, dst, state, pd, reason) == PF_DROP) return (PF_DROP); } else { if (pf_tcp_track_full(src, dst, state, kif, m, off, pd, reason, ©back) == PF_DROP) return (PF_DROP); } /* translate source/destination address, if necessary */ if ((*state)->key[PF_SK_WIRE] != (*state)->key[PF_SK_STACK]) { struct pf_state_key *nk = (*state)->key[pd->didx]; if (PF_ANEQ(pd->src, &nk->addr[pd->sidx], pd->af) || nk->port[pd->sidx] != th->th_sport) - pf_change_ap(pd->src, &th->th_sport, pd->ip_sum, - &th->th_sum, &nk->addr[pd->sidx], + pf_change_ap(m, pd->src, &th->th_sport, + pd->ip_sum, &th->th_sum, &nk->addr[pd->sidx], nk->port[pd->sidx], 0, pd->af); if (PF_ANEQ(pd->dst, &nk->addr[pd->didx], pd->af) || nk->port[pd->didx] != th->th_dport) - pf_change_ap(pd->dst, &th->th_dport, pd->ip_sum, - &th->th_sum, &nk->addr[pd->didx], + pf_change_ap(m, pd->dst, &th->th_dport, + pd->ip_sum, &th->th_sum, &nk->addr[pd->didx], nk->port[pd->didx], 0, pd->af); copyback = 1; } /* Copyback sequence modulation or stateful scrub changes if needed */ if (copyback) #ifdef __FreeBSD__ m_copyback(m, off, sizeof(*th), (caddr_t)th); #else m_copyback(m, off, sizeof(*th), th); #endif return (PF_PASS); } int pf_test_state_udp(struct pf_state **state, int direction, struct pfi_kif *kif, struct mbuf *m, int off, void *h, struct pf_pdesc *pd) { struct pf_state_peer *src, *dst; struct pf_state_key_cmp key; struct udphdr *uh = pd->hdr.udp; key.af = pd->af; key.proto = IPPROTO_UDP; if (direction == PF_IN) { /* wire side, straight */ PF_ACPY(&key.addr[0], pd->src, key.af); PF_ACPY(&key.addr[1], pd->dst, key.af); key.port[0] = uh->uh_sport; key.port[1] = uh->uh_dport; } else { /* stack side, reverse */ PF_ACPY(&key.addr[1], pd->src, key.af); PF_ACPY(&key.addr[0], pd->dst, key.af); key.port[1] = uh->uh_sport; key.port[0] = uh->uh_dport; } #ifdef __FreeBSD__ STATE_LOOKUP(kif, &key, direction, *state, m, pd->pf_mtag); #else STATE_LOOKUP(kif, &key, direction, *state, m); #endif if (direction == (*state)->direction) { src = &(*state)->src; dst = &(*state)->dst; } else { src = &(*state)->dst; dst = &(*state)->src; } /* update states */ if (src->state < PFUDPS_SINGLE) src->state = PFUDPS_SINGLE; if (dst->state == PFUDPS_SINGLE) dst->state = PFUDPS_MULTIPLE; /* update expire time */ (*state)->expire = time_second; if (src->state == PFUDPS_MULTIPLE && dst->state == PFUDPS_MULTIPLE) (*state)->timeout = PFTM_UDP_MULTIPLE; else (*state)->timeout = PFTM_UDP_SINGLE; /* translate source/destination address, if necessary */ if ((*state)->key[PF_SK_WIRE] != (*state)->key[PF_SK_STACK]) { struct pf_state_key *nk = (*state)->key[pd->didx]; if (PF_ANEQ(pd->src, &nk->addr[pd->sidx], pd->af) || nk->port[pd->sidx] != uh->uh_sport) - pf_change_ap(pd->src, &uh->uh_sport, pd->ip_sum, + pf_change_ap(m, pd->src, &uh->uh_sport, pd->ip_sum, &uh->uh_sum, &nk->addr[pd->sidx], nk->port[pd->sidx], 1, pd->af); if (PF_ANEQ(pd->dst, &nk->addr[pd->didx], pd->af) || nk->port[pd->didx] != uh->uh_dport) - pf_change_ap(pd->dst, &uh->uh_dport, pd->ip_sum, + pf_change_ap(m, pd->dst, &uh->uh_dport, pd->ip_sum, &uh->uh_sum, &nk->addr[pd->didx], nk->port[pd->didx], 1, pd->af); #ifdef __FreeBSD__ m_copyback(m, off, sizeof(*uh), (caddr_t)uh); #else m_copyback(m, off, sizeof(*uh), uh); #endif } return (PF_PASS); } int pf_test_state_icmp(struct pf_state **state, int direction, struct pfi_kif *kif, struct mbuf *m, int off, void *h, struct pf_pdesc *pd, u_short *reason) { struct pf_addr *saddr = pd->src, *daddr = pd->dst; #ifdef __FreeBSD__ u_int16_t icmpid = 0, *icmpsum; #else u_int16_t icmpid, *icmpsum; #endif u_int8_t icmptype; int state_icmp = 0; struct pf_state_key_cmp key; switch (pd->proto) { #ifdef INET case IPPROTO_ICMP: icmptype = pd->hdr.icmp->icmp_type; icmpid = pd->hdr.icmp->icmp_id; icmpsum = &pd->hdr.icmp->icmp_cksum; if (icmptype == ICMP_UNREACH || icmptype == ICMP_SOURCEQUENCH || icmptype == ICMP_REDIRECT || icmptype == ICMP_TIMXCEED || icmptype == ICMP_PARAMPROB) state_icmp++; break; #endif /* INET */ #ifdef INET6 case IPPROTO_ICMPV6: icmptype = pd->hdr.icmp6->icmp6_type; icmpid = pd->hdr.icmp6->icmp6_id; icmpsum = &pd->hdr.icmp6->icmp6_cksum; if (icmptype == ICMP6_DST_UNREACH || icmptype == ICMP6_PACKET_TOO_BIG || icmptype == ICMP6_TIME_EXCEEDED || icmptype == ICMP6_PARAM_PROB) state_icmp++; break; #endif /* INET6 */ } if (!state_icmp) { /* * ICMP query/reply message not related to a TCP/UDP packet. * Search for an ICMP state. */ key.af = pd->af; key.proto = pd->proto; key.port[0] = key.port[1] = icmpid; if (direction == PF_IN) { /* wire side, straight */ PF_ACPY(&key.addr[0], pd->src, key.af); PF_ACPY(&key.addr[1], pd->dst, key.af); } else { /* stack side, reverse */ PF_ACPY(&key.addr[1], pd->src, key.af); PF_ACPY(&key.addr[0], pd->dst, key.af); } #ifdef __FreeBSD__ STATE_LOOKUP(kif, &key, direction, *state, m, pd->pf_mtag); #else STATE_LOOKUP(kif, &key, direction, *state, m); #endif (*state)->expire = time_second; (*state)->timeout = PFTM_ICMP_ERROR_REPLY; /* translate source/destination address, if necessary */ if ((*state)->key[PF_SK_WIRE] != (*state)->key[PF_SK_STACK]) { struct pf_state_key *nk = (*state)->key[pd->didx]; switch (pd->af) { #ifdef INET case AF_INET: if (PF_ANEQ(pd->src, &nk->addr[pd->sidx], AF_INET)) pf_change_a(&saddr->v4.s_addr, pd->ip_sum, nk->addr[pd->sidx].v4.s_addr, 0); if (PF_ANEQ(pd->dst, &nk->addr[pd->didx], AF_INET)) pf_change_a(&daddr->v4.s_addr, pd->ip_sum, nk->addr[pd->didx].v4.s_addr, 0); if (nk->port[0] != pd->hdr.icmp->icmp_id) { pd->hdr.icmp->icmp_cksum = pf_cksum_fixup( pd->hdr.icmp->icmp_cksum, icmpid, nk->port[pd->sidx], 0); pd->hdr.icmp->icmp_id = nk->port[pd->sidx]; } m_copyback(m, off, ICMP_MINLEN, #ifdef __FreeBSD__ (caddr_t) #endif pd->hdr.icmp); break; #endif /* INET */ #ifdef INET6 case AF_INET6: if (PF_ANEQ(pd->src, &nk->addr[pd->sidx], AF_INET6)) pf_change_a6(saddr, &pd->hdr.icmp6->icmp6_cksum, &nk->addr[pd->sidx], 0); if (PF_ANEQ(pd->dst, &nk->addr[pd->didx], AF_INET6)) pf_change_a6(daddr, &pd->hdr.icmp6->icmp6_cksum, &nk->addr[pd->didx], 0); m_copyback(m, off, sizeof(struct icmp6_hdr), #ifdef __FreeBSD__ (caddr_t) #endif pd->hdr.icmp6); break; #endif /* INET6 */ } } return (PF_PASS); } else { /* * ICMP error message in response to a TCP/UDP packet. * Extract the inner TCP/UDP header and search for that state. */ struct pf_pdesc pd2; #ifdef __FreeBSD__ bzero(&pd2, sizeof pd2); #endif #ifdef INET struct ip h2; #endif /* INET */ #ifdef INET6 struct ip6_hdr h2_6; int terminal = 0; #endif /* INET6 */ #ifdef __FreeBSD__ int ipoff2 = 0; int off2 = 0; #else int ipoff2; int off2; #endif pd2.af = pd->af; /* Payload packet is from the opposite direction. */ pd2.sidx = (direction == PF_IN) ? 1 : 0; pd2.didx = (direction == PF_IN) ? 0 : 1; switch (pd->af) { #ifdef INET case AF_INET: /* offset of h2 in mbuf chain */ ipoff2 = off + ICMP_MINLEN; if (!pf_pull_hdr(m, ipoff2, &h2, sizeof(h2), NULL, reason, pd2.af)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: ICMP error message too short " "(ip)\n")); return (PF_DROP); } /* * ICMP error messages don't refer to non-first * fragments */ if (h2.ip_off & htons(IP_OFFMASK)) { REASON_SET(reason, PFRES_FRAG); return (PF_DROP); } /* offset of protocol header that follows h2 */ off2 = ipoff2 + (h2.ip_hl << 2); pd2.proto = h2.ip_p; pd2.src = (struct pf_addr *)&h2.ip_src; pd2.dst = (struct pf_addr *)&h2.ip_dst; pd2.ip_sum = &h2.ip_sum; break; #endif /* INET */ #ifdef INET6 case AF_INET6: ipoff2 = off + sizeof(struct icmp6_hdr); if (!pf_pull_hdr(m, ipoff2, &h2_6, sizeof(h2_6), NULL, reason, pd2.af)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: ICMP error message too short " "(ip6)\n")); return (PF_DROP); } pd2.proto = h2_6.ip6_nxt; pd2.src = (struct pf_addr *)&h2_6.ip6_src; pd2.dst = (struct pf_addr *)&h2_6.ip6_dst; pd2.ip_sum = NULL; off2 = ipoff2 + sizeof(h2_6); do { switch (pd2.proto) { case IPPROTO_FRAGMENT: /* * ICMPv6 error messages for * non-first fragments */ REASON_SET(reason, PFRES_FRAG); return (PF_DROP); case IPPROTO_AH: case IPPROTO_HOPOPTS: case IPPROTO_ROUTING: case IPPROTO_DSTOPTS: { /* get next header and header length */ struct ip6_ext opt6; if (!pf_pull_hdr(m, off2, &opt6, sizeof(opt6), NULL, reason, pd2.af)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: ICMPv6 short opt\n")); return (PF_DROP); } if (pd2.proto == IPPROTO_AH) off2 += (opt6.ip6e_len + 2) * 4; else off2 += (opt6.ip6e_len + 1) * 8; pd2.proto = opt6.ip6e_nxt; /* goto the next header */ break; } default: terminal++; break; } } while (!terminal); break; #endif /* INET6 */ } switch (pd2.proto) { case IPPROTO_TCP: { struct tcphdr th; u_int32_t seq; struct pf_state_peer *src, *dst; u_int8_t dws; int copyback = 0; /* * Only the first 8 bytes of the TCP header can be * expected. Don't access any TCP header fields after * th_seq, an ackskew test is not possible. */ if (!pf_pull_hdr(m, off2, &th, 8, NULL, reason, pd2.af)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: ICMP error message too short " "(tcp)\n")); return (PF_DROP); } key.af = pd2.af; key.proto = IPPROTO_TCP; PF_ACPY(&key.addr[pd2.sidx], pd2.src, key.af); PF_ACPY(&key.addr[pd2.didx], pd2.dst, key.af); key.port[pd2.sidx] = th.th_sport; key.port[pd2.didx] = th.th_dport; #ifdef __FreeBSD__ STATE_LOOKUP(kif, &key, direction, *state, m, pd->pf_mtag); #else STATE_LOOKUP(kif, &key, direction, *state, m); #endif if (direction == (*state)->direction) { src = &(*state)->dst; dst = &(*state)->src; } else { src = &(*state)->src; dst = &(*state)->dst; } if (src->wscale && dst->wscale) dws = dst->wscale & PF_WSCALE_MASK; else dws = 0; /* Demodulate sequence number */ seq = ntohl(th.th_seq) - src->seqdiff; if (src->seqdiff) { pf_change_a(&th.th_seq, icmpsum, htonl(seq), 0); copyback = 1; } if (!((*state)->state_flags & PFSTATE_SLOPPY) && (!SEQ_GEQ(src->seqhi, seq) || !SEQ_GEQ(seq, src->seqlo - (dst->max_win << dws)))) { #ifdef __FreeBSD__ if (V_pf_status.debug >= PF_DEBUG_MISC) { #else if (pf_status.debug >= PF_DEBUG_MISC) { #endif printf("pf: BAD ICMP %d:%d ", icmptype, pd->hdr.icmp->icmp_code); pf_print_host(pd->src, 0, pd->af); printf(" -> "); pf_print_host(pd->dst, 0, pd->af); printf(" state: "); pf_print_state(*state); printf(" seq=%u\n", seq); } REASON_SET(reason, PFRES_BADSTATE); return (PF_DROP); } else { #ifdef __FreeBSD__ if (V_pf_status.debug >= PF_DEBUG_MISC) { #else if (pf_status.debug >= PF_DEBUG_MISC) { #endif printf("pf: OK ICMP %d:%d ", icmptype, pd->hdr.icmp->icmp_code); pf_print_host(pd->src, 0, pd->af); printf(" -> "); pf_print_host(pd->dst, 0, pd->af); printf(" state: "); pf_print_state(*state); printf(" seq=%u\n", seq); } } /* translate source/destination address, if necessary */ if ((*state)->key[PF_SK_WIRE] != (*state)->key[PF_SK_STACK]) { struct pf_state_key *nk = (*state)->key[pd->didx]; if (PF_ANEQ(pd2.src, &nk->addr[pd2.sidx], pd2.af) || nk->port[pd2.sidx] != th.th_sport) pf_change_icmp(pd2.src, &th.th_sport, daddr, &nk->addr[pd2.sidx], nk->port[pd2.sidx], NULL, pd2.ip_sum, icmpsum, pd->ip_sum, 0, pd2.af); if (PF_ANEQ(pd2.dst, &nk->addr[pd2.didx], pd2.af) || nk->port[pd2.didx] != th.th_dport) pf_change_icmp(pd2.dst, &th.th_dport, NULL, /* XXX Inbound NAT? */ &nk->addr[pd2.didx], nk->port[pd2.didx], NULL, pd2.ip_sum, icmpsum, pd->ip_sum, 0, pd2.af); copyback = 1; } if (copyback) { switch (pd2.af) { #ifdef INET case AF_INET: m_copyback(m, off, ICMP_MINLEN, #ifdef __FreeBSD__ (caddr_t) #endif pd->hdr.icmp); m_copyback(m, ipoff2, sizeof(h2), #ifdef __FreeBSD__ (caddr_t) #endif &h2); break; #endif /* INET */ #ifdef INET6 case AF_INET6: m_copyback(m, off, sizeof(struct icmp6_hdr), #ifdef __FreeBSD__ (caddr_t) #endif pd->hdr.icmp6); m_copyback(m, ipoff2, sizeof(h2_6), #ifdef __FreeBSD__ (caddr_t) #endif &h2_6); break; #endif /* INET6 */ } #ifdef __FreeBSD__ m_copyback(m, off2, 8, (caddr_t)&th); #else m_copyback(m, off2, 8, &th); #endif } return (PF_PASS); break; } case IPPROTO_UDP: { struct udphdr uh; if (!pf_pull_hdr(m, off2, &uh, sizeof(uh), NULL, reason, pd2.af)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: ICMP error message too short " "(udp)\n")); return (PF_DROP); } key.af = pd2.af; key.proto = IPPROTO_UDP; PF_ACPY(&key.addr[pd2.sidx], pd2.src, key.af); PF_ACPY(&key.addr[pd2.didx], pd2.dst, key.af); key.port[pd2.sidx] = uh.uh_sport; key.port[pd2.didx] = uh.uh_dport; #ifdef __FreeBSD__ STATE_LOOKUP(kif, &key, direction, *state, m, pd->pf_mtag); #else STATE_LOOKUP(kif, &key, direction, *state, m); #endif /* translate source/destination address, if necessary */ if ((*state)->key[PF_SK_WIRE] != (*state)->key[PF_SK_STACK]) { struct pf_state_key *nk = (*state)->key[pd->didx]; if (PF_ANEQ(pd2.src, &nk->addr[pd2.sidx], pd2.af) || nk->port[pd2.sidx] != uh.uh_sport) pf_change_icmp(pd2.src, &uh.uh_sport, daddr, &nk->addr[pd2.sidx], nk->port[pd2.sidx], &uh.uh_sum, pd2.ip_sum, icmpsum, pd->ip_sum, 1, pd2.af); if (PF_ANEQ(pd2.dst, &nk->addr[pd2.didx], pd2.af) || nk->port[pd2.didx] != uh.uh_dport) pf_change_icmp(pd2.dst, &uh.uh_dport, NULL, /* XXX Inbound NAT? */ &nk->addr[pd2.didx], nk->port[pd2.didx], &uh.uh_sum, pd2.ip_sum, icmpsum, pd->ip_sum, 1, pd2.af); switch (pd2.af) { #ifdef INET case AF_INET: m_copyback(m, off, ICMP_MINLEN, #ifdef __FreeBSD__ (caddr_t) #endif pd->hdr.icmp); #ifdef __FreeBSD__ m_copyback(m, ipoff2, sizeof(h2), (caddr_t)&h2); #else m_copyback(m, ipoff2, sizeof(h2), &h2); #endif break; #endif /* INET */ #ifdef INET6 case AF_INET6: m_copyback(m, off, sizeof(struct icmp6_hdr), #ifdef __FreeBSD__ (caddr_t) #endif pd->hdr.icmp6); m_copyback(m, ipoff2, sizeof(h2_6), #ifdef __FreeBSD__ (caddr_t) #endif &h2_6); break; #endif /* INET6 */ } #ifdef __FreeBSD__ m_copyback(m, off2, sizeof(uh), (caddr_t)&uh); #else m_copyback(m, off2, sizeof(uh), &uh); #endif } return (PF_PASS); break; } #ifdef INET case IPPROTO_ICMP: { struct icmp iih; if (!pf_pull_hdr(m, off2, &iih, ICMP_MINLEN, NULL, reason, pd2.af)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: ICMP error message too short i" "(icmp)\n")); return (PF_DROP); } key.af = pd2.af; key.proto = IPPROTO_ICMP; PF_ACPY(&key.addr[pd2.sidx], pd2.src, key.af); PF_ACPY(&key.addr[pd2.didx], pd2.dst, key.af); key.port[0] = key.port[1] = iih.icmp_id; #ifdef __FreeBSD__ STATE_LOOKUP(kif, &key, direction, *state, m, pd->pf_mtag); #else STATE_LOOKUP(kif, &key, direction, *state, m); #endif /* translate source/destination address, if necessary */ if ((*state)->key[PF_SK_WIRE] != (*state)->key[PF_SK_STACK]) { struct pf_state_key *nk = (*state)->key[pd->didx]; if (PF_ANEQ(pd2.src, &nk->addr[pd2.sidx], pd2.af) || nk->port[pd2.sidx] != iih.icmp_id) pf_change_icmp(pd2.src, &iih.icmp_id, daddr, &nk->addr[pd2.sidx], nk->port[pd2.sidx], NULL, pd2.ip_sum, icmpsum, pd->ip_sum, 0, AF_INET); if (PF_ANEQ(pd2.dst, &nk->addr[pd2.didx], pd2.af) || nk->port[pd2.didx] != iih.icmp_id) pf_change_icmp(pd2.dst, &iih.icmp_id, NULL, /* XXX Inbound NAT? */ &nk->addr[pd2.didx], nk->port[pd2.didx], NULL, pd2.ip_sum, icmpsum, pd->ip_sum, 0, AF_INET); #ifdef __FreeBSD__ m_copyback(m, off, ICMP_MINLEN, (caddr_t)pd->hdr.icmp); m_copyback(m, ipoff2, sizeof(h2), (caddr_t)&h2); m_copyback(m, off2, ICMP_MINLEN, (caddr_t)&iih); #else m_copyback(m, off, ICMP_MINLEN, pd->hdr.icmp); m_copyback(m, ipoff2, sizeof(h2), &h2); m_copyback(m, off2, ICMP_MINLEN, &iih); #endif } return (PF_PASS); break; } #endif /* INET */ #ifdef INET6 case IPPROTO_ICMPV6: { struct icmp6_hdr iih; if (!pf_pull_hdr(m, off2, &iih, sizeof(struct icmp6_hdr), NULL, reason, pd2.af)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: ICMP error message too short " "(icmp6)\n")); return (PF_DROP); } key.af = pd2.af; key.proto = IPPROTO_ICMPV6; PF_ACPY(&key.addr[pd2.sidx], pd2.src, key.af); PF_ACPY(&key.addr[pd2.didx], pd2.dst, key.af); key.port[0] = key.port[1] = iih.icmp6_id; #ifdef __FreeBSD__ STATE_LOOKUP(kif, &key, direction, *state, m, pd->pf_mtag); #else STATE_LOOKUP(kif, &key, direction, *state, m); #endif /* translate source/destination address, if necessary */ if ((*state)->key[PF_SK_WIRE] != (*state)->key[PF_SK_STACK]) { struct pf_state_key *nk = (*state)->key[pd->didx]; if (PF_ANEQ(pd2.src, &nk->addr[pd2.sidx], pd2.af) || nk->port[pd2.sidx] != iih.icmp6_id) pf_change_icmp(pd2.src, &iih.icmp6_id, daddr, &nk->addr[pd2.sidx], nk->port[pd2.sidx], NULL, pd2.ip_sum, icmpsum, pd->ip_sum, 0, AF_INET6); if (PF_ANEQ(pd2.dst, &nk->addr[pd2.didx], pd2.af) || nk->port[pd2.didx] != iih.icmp6_id) pf_change_icmp(pd2.dst, &iih.icmp6_id, NULL, /* XXX Inbound NAT? */ &nk->addr[pd2.didx], nk->port[pd2.didx], NULL, pd2.ip_sum, icmpsum, pd->ip_sum, 0, AF_INET6); #ifdef __FreeBSD__ m_copyback(m, off, sizeof(struct icmp6_hdr), (caddr_t)pd->hdr.icmp6); m_copyback(m, ipoff2, sizeof(h2_6), (caddr_t)&h2_6); m_copyback(m, off2, sizeof(struct icmp6_hdr), (caddr_t)&iih); #else m_copyback(m, off, sizeof(struct icmp6_hdr), pd->hdr.icmp6); m_copyback(m, ipoff2, sizeof(h2_6), &h2_6); m_copyback(m, off2, sizeof(struct icmp6_hdr), &iih); #endif } return (PF_PASS); break; } #endif /* INET6 */ default: { key.af = pd2.af; key.proto = pd2.proto; PF_ACPY(&key.addr[pd2.sidx], pd2.src, key.af); PF_ACPY(&key.addr[pd2.didx], pd2.dst, key.af); key.port[0] = key.port[1] = 0; #ifdef __FreeBSD__ STATE_LOOKUP(kif, &key, direction, *state, m, pd->pf_mtag); #else STATE_LOOKUP(kif, &key, direction, *state, m); #endif /* translate source/destination address, if necessary */ if ((*state)->key[PF_SK_WIRE] != (*state)->key[PF_SK_STACK]) { struct pf_state_key *nk = (*state)->key[pd->didx]; if (PF_ANEQ(pd2.src, &nk->addr[pd2.sidx], pd2.af)) pf_change_icmp(pd2.src, NULL, daddr, &nk->addr[pd2.sidx], 0, NULL, pd2.ip_sum, icmpsum, pd->ip_sum, 0, pd2.af); if (PF_ANEQ(pd2.dst, &nk->addr[pd2.didx], pd2.af)) pf_change_icmp(pd2.src, NULL, NULL, /* XXX Inbound NAT? */ &nk->addr[pd2.didx], 0, NULL, pd2.ip_sum, icmpsum, pd->ip_sum, 0, pd2.af); switch (pd2.af) { #ifdef INET case AF_INET: #ifdef __FreeBSD__ m_copyback(m, off, ICMP_MINLEN, (caddr_t)pd->hdr.icmp); m_copyback(m, ipoff2, sizeof(h2), (caddr_t)&h2); #else m_copyback(m, off, ICMP_MINLEN, pd->hdr.icmp); m_copyback(m, ipoff2, sizeof(h2), &h2); #endif break; #endif /* INET */ #ifdef INET6 case AF_INET6: m_copyback(m, off, sizeof(struct icmp6_hdr), #ifdef __FreeBSD__ (caddr_t) #endif pd->hdr.icmp6); m_copyback(m, ipoff2, sizeof(h2_6), #ifdef __FreeBSD__ (caddr_t) #endif &h2_6); break; #endif /* INET6 */ } } return (PF_PASS); break; } } } } int pf_test_state_other(struct pf_state **state, int direction, struct pfi_kif *kif, struct mbuf *m, struct pf_pdesc *pd) { struct pf_state_peer *src, *dst; struct pf_state_key_cmp key; key.af = pd->af; key.proto = pd->proto; if (direction == PF_IN) { PF_ACPY(&key.addr[0], pd->src, key.af); PF_ACPY(&key.addr[1], pd->dst, key.af); key.port[0] = key.port[1] = 0; } else { PF_ACPY(&key.addr[1], pd->src, key.af); PF_ACPY(&key.addr[0], pd->dst, key.af); key.port[1] = key.port[0] = 0; } #ifdef __FreeBSD__ STATE_LOOKUP(kif, &key, direction, *state, m, pd->pf_mtag); #else STATE_LOOKUP(kif, &key, direction, *state, m); #endif if (direction == (*state)->direction) { src = &(*state)->src; dst = &(*state)->dst; } else { src = &(*state)->dst; dst = &(*state)->src; } /* update states */ if (src->state < PFOTHERS_SINGLE) src->state = PFOTHERS_SINGLE; if (dst->state == PFOTHERS_SINGLE) dst->state = PFOTHERS_MULTIPLE; /* update expire time */ (*state)->expire = time_second; if (src->state == PFOTHERS_MULTIPLE && dst->state == PFOTHERS_MULTIPLE) (*state)->timeout = PFTM_OTHER_MULTIPLE; else (*state)->timeout = PFTM_OTHER_SINGLE; /* translate source/destination address, if necessary */ if ((*state)->key[PF_SK_WIRE] != (*state)->key[PF_SK_STACK]) { struct pf_state_key *nk = (*state)->key[pd->didx]; #ifdef __FreeBSD__ KASSERT(nk, ("%s: nk is null", __FUNCTION__)); KASSERT(pd, ("%s: pd is null", __FUNCTION__)); KASSERT(pd->src, ("%s: pd->src is null", __FUNCTION__)); KASSERT(pd->dst, ("%s: pd->dst is null", __FUNCTION__)); #else KASSERT(nk); KASSERT(pd); KASSERT(pd->src); KASSERT(pd->dst); #endif switch (pd->af) { #ifdef INET case AF_INET: if (PF_ANEQ(pd->src, &nk->addr[pd->sidx], AF_INET)) pf_change_a(&pd->src->v4.s_addr, pd->ip_sum, nk->addr[pd->sidx].v4.s_addr, 0); if (PF_ANEQ(pd->dst, &nk->addr[pd->didx], AF_INET)) pf_change_a(&pd->dst->v4.s_addr, pd->ip_sum, nk->addr[pd->didx].v4.s_addr, 0); break; #endif /* INET */ #ifdef INET6 case AF_INET6: if (PF_ANEQ(pd->src, &nk->addr[pd->sidx], AF_INET)) PF_ACPY(pd->src, &nk->addr[pd->sidx], pd->af); if (PF_ANEQ(pd->dst, &nk->addr[pd->didx], AF_INET)) PF_ACPY(pd->dst, &nk->addr[pd->didx], pd->af); #endif /* INET6 */ } } return (PF_PASS); } /* * ipoff and off are measured from the start of the mbuf chain. * h must be at "ipoff" on the mbuf chain. */ void * pf_pull_hdr(struct mbuf *m, int off, void *p, int len, u_short *actionp, u_short *reasonp, sa_family_t af) { switch (af) { #ifdef INET case AF_INET: { struct ip *h = mtod(m, struct ip *); u_int16_t fragoff = (ntohs(h->ip_off) & IP_OFFMASK) << 3; if (fragoff) { if (fragoff >= len) ACTION_SET(actionp, PF_PASS); else { ACTION_SET(actionp, PF_DROP); REASON_SET(reasonp, PFRES_FRAG); } return (NULL); } if (m->m_pkthdr.len < off + len || ntohs(h->ip_len) < off + len) { ACTION_SET(actionp, PF_DROP); REASON_SET(reasonp, PFRES_SHORT); return (NULL); } break; } #endif /* INET */ #ifdef INET6 case AF_INET6: { struct ip6_hdr *h = mtod(m, struct ip6_hdr *); if (m->m_pkthdr.len < off + len || (ntohs(h->ip6_plen) + sizeof(struct ip6_hdr)) < (unsigned)(off + len)) { ACTION_SET(actionp, PF_DROP); REASON_SET(reasonp, PFRES_SHORT); return (NULL); } break; } #endif /* INET6 */ } m_copydata(m, off, len, p); return (p); } int pf_routable(struct pf_addr *addr, sa_family_t af, struct pfi_kif *kif, int rtableid) { #ifdef __FreeBSD__ #ifdef RADIX_MPATH struct radix_node_head *rnh; #endif #endif struct sockaddr_in *dst; int ret = 1; int check_mpath; #ifndef __FreeBSD__ extern int ipmultipath; #endif #ifdef INET6 #ifndef __FreeBSD__ extern int ip6_multipath; #endif struct sockaddr_in6 *dst6; struct route_in6 ro; #else struct route ro; #endif struct radix_node *rn; struct rtentry *rt; struct ifnet *ifp; check_mpath = 0; #ifdef __FreeBSD__ #ifdef RADIX_MPATH /* XXX: stick to table 0 for now */ rnh = rt_tables_get_rnh(0, af); if (rnh != NULL && rn_mpath_capable(rnh)) check_mpath = 1; #endif #endif bzero(&ro, sizeof(ro)); switch (af) { case AF_INET: dst = satosin(&ro.ro_dst); dst->sin_family = AF_INET; dst->sin_len = sizeof(*dst); dst->sin_addr = addr->v4; #ifndef __FreeBSD__ if (ipmultipath) check_mpath = 1; #endif break; #ifdef INET6 case AF_INET6: /* * Skip check for addresses with embedded interface scope, * as they would always match anyway. */ if (IN6_IS_SCOPE_EMBED(&addr->v6)) goto out; dst6 = (struct sockaddr_in6 *)&ro.ro_dst; dst6->sin6_family = AF_INET6; dst6->sin6_len = sizeof(*dst6); dst6->sin6_addr = addr->v6; #ifndef __FreeBSD__ if (ip6_multipath) check_mpath = 1; #endif break; #endif /* INET6 */ default: return (0); } /* Skip checks for ipsec interfaces */ if (kif != NULL && kif->pfik_ifp->if_type == IFT_ENC) goto out; #ifdef __FreeBSD__ switch (af) { #ifdef INET6 case AF_INET6: in6_rtalloc_ign(&ro, 0, rtableid); break; #endif #ifdef INET case AF_INET: in_rtalloc_ign((struct route *)&ro, 0, rtableid); break; #endif default: rtalloc_ign((struct route *)&ro, 0); /* No/default FIB. */ break; } #else /* ! __FreeBSD__ */ rtalloc_noclone((struct route *)&ro, NO_CLONING); #endif if (ro.ro_rt != NULL) { /* No interface given, this is a no-route check */ if (kif == NULL) goto out; if (kif->pfik_ifp == NULL) { ret = 0; goto out; } /* Perform uRPF check if passed input interface */ ret = 0; rn = (struct radix_node *)ro.ro_rt; do { rt = (struct rtentry *)rn; #ifndef __FreeBSD__ /* CARPDEV */ if (rt->rt_ifp->if_type == IFT_CARP) ifp = rt->rt_ifp->if_carpdev; else #endif ifp = rt->rt_ifp; if (kif->pfik_ifp == ifp) ret = 1; #ifdef __FreeBSD__ #ifdef RADIX_MPATH rn = rn_mpath_next(rn); #endif #else rn = rn_mpath_next(rn, 0); #endif } while (check_mpath == 1 && rn != NULL && ret == 0); } else ret = 0; out: if (ro.ro_rt != NULL) RTFREE(ro.ro_rt); return (ret); } int pf_rtlabel_match(struct pf_addr *addr, sa_family_t af, struct pf_addr_wrap *aw, int rtableid) { struct sockaddr_in *dst; #ifdef INET6 struct sockaddr_in6 *dst6; struct route_in6 ro; #else struct route ro; #endif int ret = 0; bzero(&ro, sizeof(ro)); switch (af) { case AF_INET: dst = satosin(&ro.ro_dst); dst->sin_family = AF_INET; dst->sin_len = sizeof(*dst); dst->sin_addr = addr->v4; break; #ifdef INET6 case AF_INET6: dst6 = (struct sockaddr_in6 *)&ro.ro_dst; dst6->sin6_family = AF_INET6; dst6->sin6_len = sizeof(*dst6); dst6->sin6_addr = addr->v6; break; #endif /* INET6 */ default: return (0); } #ifdef __FreeBSD__ switch (af) { #ifdef INET6 case AF_INET6: in6_rtalloc_ign(&ro, 0, rtableid); break; #endif #ifdef INET case AF_INET: in_rtalloc_ign((struct route *)&ro, 0, rtableid); break; #endif default: rtalloc_ign((struct route *)&ro, 0); break; } #else /* ! __FreeBSD__ */ rtalloc_noclone((struct route *)&ro, NO_CLONING); #endif if (ro.ro_rt != NULL) { #ifdef __FreeBSD__ /* XXX_IMPORT: later */ #else if (ro.ro_rt->rt_labelid == aw->v.rtlabel) ret = 1; #endif RTFREE(ro.ro_rt); } return (ret); } #ifdef INET void pf_route(struct mbuf **m, struct pf_rule *r, int dir, struct ifnet *oifp, struct pf_state *s, struct pf_pdesc *pd) { struct mbuf *m0, *m1; struct route iproute; struct route *ro = NULL; struct sockaddr_in *dst; struct ip *ip; struct ifnet *ifp = NULL; struct pf_addr naddr; struct pf_src_node *sn = NULL; int error = 0; #ifdef __FreeBSD__ int sw_csum; #endif #ifdef IPSEC struct m_tag *mtag; #endif /* IPSEC */ if (m == NULL || *m == NULL || r == NULL || (dir != PF_IN && dir != PF_OUT) || oifp == NULL) panic("pf_route: invalid parameters"); #ifdef __FreeBSD__ if (pd->pf_mtag->routed++ > 3) { #else if ((*m)->m_pkthdr.pf.routed++ > 3) { #endif m0 = *m; *m = NULL; goto bad; } if (r->rt == PF_DUPTO) { #ifdef __FreeBSD__ if ((m0 = m_dup(*m, M_DONTWAIT)) == NULL) #else if ((m0 = m_copym2(*m, 0, M_COPYALL, M_NOWAIT)) == NULL) #endif return; } else { if ((r->rt == PF_REPLYTO) == (r->direction == dir)) return; m0 = *m; } if (m0->m_len < sizeof(struct ip)) { DPFPRINTF(PF_DEBUG_URGENT, ("pf_route: m0->m_len < sizeof(struct ip)\n")); goto bad; } ip = mtod(m0, struct ip *); ro = &iproute; bzero((caddr_t)ro, sizeof(*ro)); dst = satosin(&ro->ro_dst); dst->sin_family = AF_INET; dst->sin_len = sizeof(*dst); dst->sin_addr = ip->ip_dst; if (r->rt == PF_FASTROUTE) { #ifdef __FreeBSD__ in_rtalloc_ign(ro, 0, M_GETFIB(m0)); #else rtalloc(ro); #endif if (ro->ro_rt == 0) { #ifdef __FreeBSD__ KMOD_IPSTAT_INC(ips_noroute); #else ipstat.ips_noroute++; #endif goto bad; } ifp = ro->ro_rt->rt_ifp; ro->ro_rt->rt_use++; if (ro->ro_rt->rt_flags & RTF_GATEWAY) dst = satosin(ro->ro_rt->rt_gateway); } else { if (TAILQ_EMPTY(&r->rpool.list)) { DPFPRINTF(PF_DEBUG_URGENT, ("pf_route: TAILQ_EMPTY(&r->rpool.list)\n")); goto bad; } if (s == NULL) { pf_map_addr(AF_INET, r, (struct pf_addr *)&ip->ip_src, &naddr, NULL, &sn); if (!PF_AZERO(&naddr, AF_INET)) dst->sin_addr.s_addr = naddr.v4.s_addr; ifp = r->rpool.cur->kif ? r->rpool.cur->kif->pfik_ifp : NULL; } else { if (!PF_AZERO(&s->rt_addr, AF_INET)) dst->sin_addr.s_addr = s->rt_addr.v4.s_addr; ifp = s->rt_kif ? s->rt_kif->pfik_ifp : NULL; } } if (ifp == NULL) goto bad; if (oifp != ifp) { #ifdef __FreeBSD__ PF_UNLOCK(); if (pf_test(PF_OUT, ifp, &m0, NULL, NULL) != PF_PASS) { PF_LOCK(); goto bad; } else if (m0 == NULL) { PF_LOCK(); goto done; } PF_LOCK(); #else if (pf_test(PF_OUT, ifp, &m0, NULL) != PF_PASS) goto bad; else if (m0 == NULL) goto done; #endif if (m0->m_len < sizeof(struct ip)) { DPFPRINTF(PF_DEBUG_URGENT, ("pf_route: m0->m_len < sizeof(struct ip)\n")); goto bad; } ip = mtod(m0, struct ip *); } #ifdef __FreeBSD__ /* Copied from FreeBSD 5.1-CURRENT ip_output. */ m0->m_pkthdr.csum_flags |= CSUM_IP; sw_csum = m0->m_pkthdr.csum_flags & ~ifp->if_hwassist; if (sw_csum & CSUM_DELAY_DATA) { /* * XXX: in_delayed_cksum assumes HBO for ip->ip_len (at least) */ NTOHS(ip->ip_len); NTOHS(ip->ip_off); /* XXX: needed? */ in_delayed_cksum(m0); HTONS(ip->ip_len); HTONS(ip->ip_off); sw_csum &= ~CSUM_DELAY_DATA; } m0->m_pkthdr.csum_flags &= ifp->if_hwassist; if (ntohs(ip->ip_len) <= ifp->if_mtu || (ifp->if_hwassist & CSUM_FRAGMENT && ((ip->ip_off & htons(IP_DF)) == 0))) { /* * ip->ip_len = htons(ip->ip_len); * ip->ip_off = htons(ip->ip_off); */ ip->ip_sum = 0; if (sw_csum & CSUM_DELAY_IP) { /* From KAME */ if (ip->ip_v == IPVERSION && (ip->ip_hl << 2) == sizeof(*ip)) { ip->ip_sum = in_cksum_hdr(ip); } else { ip->ip_sum = in_cksum(m0, ip->ip_hl << 2); } } PF_UNLOCK(); error = (*ifp->if_output)(ifp, m0, sintosa(dst), ro); PF_LOCK(); goto done; } #else /* Copied from ip_output. */ #ifdef IPSEC /* * If deferred crypto processing is needed, check that the * interface supports it. */ if ((mtag = m_tag_find(m0, PACKET_TAG_IPSEC_OUT_CRYPTO_NEEDED, NULL)) != NULL && (ifp->if_capabilities & IFCAP_IPSEC) == 0) { /* Notify IPsec to do its own crypto. */ ipsp_skipcrypto_unmark((struct tdb_ident *)(mtag + 1)); goto bad; } #endif /* IPSEC */ /* Catch routing changes wrt. hardware checksumming for TCP or UDP. */ if (m0->m_pkthdr.csum_flags & M_TCPV4_CSUM_OUT) { if (!(ifp->if_capabilities & IFCAP_CSUM_TCPv4) || ifp->if_bridge != NULL) { in_delayed_cksum(m0); m0->m_pkthdr.csum_flags &= ~M_TCPV4_CSUM_OUT; /* Clr */ } } else if (m0->m_pkthdr.csum_flags & M_UDPV4_CSUM_OUT) { if (!(ifp->if_capabilities & IFCAP_CSUM_UDPv4) || ifp->if_bridge != NULL) { in_delayed_cksum(m0); m0->m_pkthdr.csum_flags &= ~M_UDPV4_CSUM_OUT; /* Clr */ } } if (ntohs(ip->ip_len) <= ifp->if_mtu) { ip->ip_sum = 0; if ((ifp->if_capabilities & IFCAP_CSUM_IPv4) && ifp->if_bridge == NULL) { m0->m_pkthdr.csum_flags |= M_IPV4_CSUM_OUT; #ifdef __FreeBSD__ KMOD_IPSTAT_INC(ips_outhwcsum); #else ipstat.ips_outhwcsum++; #endif } else ip->ip_sum = in_cksum(m0, ip->ip_hl << 2); /* Update relevant hardware checksum stats for TCP/UDP */ if (m0->m_pkthdr.csum_flags & M_TCPV4_CSUM_OUT) KMOD_TCPSTAT_INC(tcps_outhwcsum); else if (m0->m_pkthdr.csum_flags & M_UDPV4_CSUM_OUT) KMOD_UDPSTAT_INC(udps_outhwcsum); error = (*ifp->if_output)(ifp, m0, sintosa(dst), NULL); goto done; } #endif /* * Too large for interface; fragment if possible. * Must be able to put at least 8 bytes per fragment. */ if (ip->ip_off & htons(IP_DF)) { #ifdef __FreeBSD__ KMOD_IPSTAT_INC(ips_cantfrag); #else ipstat.ips_cantfrag++; #endif if (r->rt != PF_DUPTO) { #ifdef __FreeBSD__ /* icmp_error() expects host byte ordering */ NTOHS(ip->ip_len); NTOHS(ip->ip_off); PF_UNLOCK(); icmp_error(m0, ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG, 0, ifp->if_mtu); PF_LOCK(); #else icmp_error(m0, ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG, 0, ifp->if_mtu); #endif goto done; } else goto bad; } m1 = m0; #ifdef __FreeBSD__ /* * XXX: is cheaper + less error prone than own function */ NTOHS(ip->ip_len); NTOHS(ip->ip_off); error = ip_fragment(ip, &m0, ifp->if_mtu, ifp->if_hwassist, sw_csum); #else error = ip_fragment(m0, ifp, ifp->if_mtu); #endif if (error) { #ifndef __FreeBSD__ /* ip_fragment does not do m_freem() on FreeBSD */ m0 = NULL; #endif goto bad; } for (m0 = m1; m0; m0 = m1) { m1 = m0->m_nextpkt; m0->m_nextpkt = 0; #ifdef __FreeBSD__ if (error == 0) { PF_UNLOCK(); error = (*ifp->if_output)(ifp, m0, sintosa(dst), NULL); PF_LOCK(); } else #else if (error == 0) error = (*ifp->if_output)(ifp, m0, sintosa(dst), NULL); else #endif m_freem(m0); } if (error == 0) #ifdef __FreeBSD__ KMOD_IPSTAT_INC(ips_fragmented); #else ipstat.ips_fragmented++; #endif done: if (r->rt != PF_DUPTO) *m = NULL; if (ro == &iproute && ro->ro_rt) RTFREE(ro->ro_rt); return; bad: m_freem(m0); goto done; } #endif /* INET */ #ifdef INET6 void pf_route6(struct mbuf **m, struct pf_rule *r, int dir, struct ifnet *oifp, struct pf_state *s, struct pf_pdesc *pd) { struct mbuf *m0; struct route_in6 ip6route; struct route_in6 *ro; struct sockaddr_in6 *dst; struct ip6_hdr *ip6; struct ifnet *ifp = NULL; struct pf_addr naddr; struct pf_src_node *sn = NULL; if (m == NULL || *m == NULL || r == NULL || (dir != PF_IN && dir != PF_OUT) || oifp == NULL) panic("pf_route6: invalid parameters"); #ifdef __FreeBSD__ if (pd->pf_mtag->routed++ > 3) { #else if ((*m)->m_pkthdr.pf.routed++ > 3) { #endif m0 = *m; *m = NULL; goto bad; } if (r->rt == PF_DUPTO) { #ifdef __FreeBSD__ if ((m0 = m_dup(*m, M_DONTWAIT)) == NULL) #else if ((m0 = m_copym2(*m, 0, M_COPYALL, M_NOWAIT)) == NULL) #endif return; } else { if ((r->rt == PF_REPLYTO) == (r->direction == dir)) return; m0 = *m; } if (m0->m_len < sizeof(struct ip6_hdr)) { DPFPRINTF(PF_DEBUG_URGENT, ("pf_route6: m0->m_len < sizeof(struct ip6_hdr)\n")); goto bad; } ip6 = mtod(m0, struct ip6_hdr *); ro = &ip6route; bzero((caddr_t)ro, sizeof(*ro)); dst = (struct sockaddr_in6 *)&ro->ro_dst; dst->sin6_family = AF_INET6; dst->sin6_len = sizeof(*dst); dst->sin6_addr = ip6->ip6_dst; /* Cheat. XXX why only in the v6 case??? */ if (r->rt == PF_FASTROUTE) { #ifdef __FreeBSD__ m0->m_flags |= M_SKIP_FIREWALL; PF_UNLOCK(); ip6_output(m0, NULL, NULL, 0, NULL, NULL, NULL); #else m0->m_pkthdr.pf.flags |= PF_TAG_GENERATED; ip6_output(m0, NULL, NULL, 0, NULL, NULL, NULL); #endif return; } if (TAILQ_EMPTY(&r->rpool.list)) { DPFPRINTF(PF_DEBUG_URGENT, ("pf_route6: TAILQ_EMPTY(&r->rpool.list)\n")); goto bad; } if (s == NULL) { pf_map_addr(AF_INET6, r, (struct pf_addr *)&ip6->ip6_src, &naddr, NULL, &sn); if (!PF_AZERO(&naddr, AF_INET6)) PF_ACPY((struct pf_addr *)&dst->sin6_addr, &naddr, AF_INET6); ifp = r->rpool.cur->kif ? r->rpool.cur->kif->pfik_ifp : NULL; } else { if (!PF_AZERO(&s->rt_addr, AF_INET6)) PF_ACPY((struct pf_addr *)&dst->sin6_addr, &s->rt_addr, AF_INET6); ifp = s->rt_kif ? s->rt_kif->pfik_ifp : NULL; } if (ifp == NULL) goto bad; if (oifp != ifp) { #ifdef __FreeBSD__ PF_UNLOCK(); if (pf_test6(PF_OUT, ifp, &m0, NULL, NULL) != PF_PASS) { PF_LOCK(); goto bad; } else if (m0 == NULL) { PF_LOCK(); goto done; } PF_LOCK(); #else if (pf_test6(PF_OUT, ifp, &m0, NULL) != PF_PASS) goto bad; else if (m0 == NULL) goto done; #endif if (m0->m_len < sizeof(struct ip6_hdr)) { DPFPRINTF(PF_DEBUG_URGENT, ("pf_route6: m0->m_len < sizeof(struct ip6_hdr)\n")); goto bad; } ip6 = mtod(m0, struct ip6_hdr *); } /* * If the packet is too large for the outgoing interface, * send back an icmp6 error. */ if (IN6_IS_SCOPE_EMBED(&dst->sin6_addr)) dst->sin6_addr.s6_addr16[1] = htons(ifp->if_index); if ((u_long)m0->m_pkthdr.len <= ifp->if_mtu) { #ifdef __FreeBSD__ PF_UNLOCK(); #endif nd6_output(ifp, ifp, m0, dst, NULL); #ifdef __FreeBSD__ PF_LOCK(); #endif } else { in6_ifstat_inc(ifp, ifs6_in_toobig); #ifdef __FreeBSD__ if (r->rt != PF_DUPTO) { PF_UNLOCK(); icmp6_error(m0, ICMP6_PACKET_TOO_BIG, 0, ifp->if_mtu); PF_LOCK(); } else #else if (r->rt != PF_DUPTO) icmp6_error(m0, ICMP6_PACKET_TOO_BIG, 0, ifp->if_mtu); else #endif goto bad; } done: if (r->rt != PF_DUPTO) *m = NULL; return; bad: m_freem(m0); goto done; } #endif /* INET6 */ #ifdef __FreeBSD__ /* * FreeBSD supports cksum offloads for the following drivers. * em(4), fxp(4), ixgb(4), lge(4), ndis(4), nge(4), re(4), * ti(4), txp(4), xl(4) * * CSUM_DATA_VALID | CSUM_PSEUDO_HDR : * network driver performed cksum including pseudo header, need to verify * csum_data * CSUM_DATA_VALID : * network driver performed cksum, needs to additional pseudo header * cksum computation with partial csum_data(i.e. lack of H/W support for * pseudo header, for instance hme(4), sk(4) and possibly gem(4)) * * After validating the cksum of packet, set both flag CSUM_DATA_VALID and * CSUM_PSEUDO_HDR in order to avoid recomputation of the cksum in upper * TCP/UDP layer. * Also, set csum_data to 0xffff to force cksum validation. */ int pf_check_proto_cksum(struct mbuf *m, int off, int len, u_int8_t p, sa_family_t af) { u_int16_t sum = 0; int hw_assist = 0; struct ip *ip; if (off < sizeof(struct ip) || len < sizeof(struct udphdr)) return (1); if (m->m_pkthdr.len < off + len) return (1); switch (p) { case IPPROTO_TCP: if (m->m_pkthdr.csum_flags & CSUM_DATA_VALID) { if (m->m_pkthdr.csum_flags & CSUM_PSEUDO_HDR) { sum = m->m_pkthdr.csum_data; } else { ip = mtod(m, struct ip *); sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, htonl((u_short)len + m->m_pkthdr.csum_data + IPPROTO_TCP)); } sum ^= 0xffff; ++hw_assist; } break; case IPPROTO_UDP: if (m->m_pkthdr.csum_flags & CSUM_DATA_VALID) { if (m->m_pkthdr.csum_flags & CSUM_PSEUDO_HDR) { sum = m->m_pkthdr.csum_data; } else { ip = mtod(m, struct ip *); sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, htonl((u_short)len + m->m_pkthdr.csum_data + IPPROTO_UDP)); } sum ^= 0xffff; ++hw_assist; } break; case IPPROTO_ICMP: #ifdef INET6 case IPPROTO_ICMPV6: #endif /* INET6 */ break; default: return (1); } if (!hw_assist) { switch (af) { case AF_INET: if (p == IPPROTO_ICMP) { if (m->m_len < off) return (1); m->m_data += off; m->m_len -= off; sum = in_cksum(m, len); m->m_data -= off; m->m_len += off; } else { if (m->m_len < sizeof(struct ip)) return (1); sum = in4_cksum(m, p, off, len); } break; #ifdef INET6 case AF_INET6: if (m->m_len < sizeof(struct ip6_hdr)) return (1); sum = in6_cksum(m, p, off, len); break; #endif /* INET6 */ default: return (1); } } if (sum) { switch (p) { case IPPROTO_TCP: { KMOD_TCPSTAT_INC(tcps_rcvbadsum); break; } case IPPROTO_UDP: { KMOD_UDPSTAT_INC(udps_badsum); break; } #ifdef INET case IPPROTO_ICMP: { KMOD_ICMPSTAT_INC(icps_checksum); break; } #endif #ifdef INET6 case IPPROTO_ICMPV6: { KMOD_ICMP6STAT_INC(icp6s_checksum); break; } #endif /* INET6 */ } return (1); } else { if (p == IPPROTO_TCP || p == IPPROTO_UDP) { m->m_pkthdr.csum_flags |= (CSUM_DATA_VALID | CSUM_PSEUDO_HDR); m->m_pkthdr.csum_data = 0xffff; } } return (0); } #else /* !__FreeBSD__ */ /* * check protocol (tcp/udp/icmp/icmp6) checksum and set mbuf flag * off is the offset where the protocol header starts * len is the total length of protocol header plus payload * returns 0 when the checksum is valid, otherwise returns 1. */ int pf_check_proto_cksum(struct mbuf *m, int off, int len, u_int8_t p, sa_family_t af) { u_int16_t flag_ok, flag_bad; u_int16_t sum; switch (p) { case IPPROTO_TCP: flag_ok = M_TCP_CSUM_IN_OK; flag_bad = M_TCP_CSUM_IN_BAD; break; case IPPROTO_UDP: flag_ok = M_UDP_CSUM_IN_OK; flag_bad = M_UDP_CSUM_IN_BAD; break; case IPPROTO_ICMP: #ifdef INET6 case IPPROTO_ICMPV6: #endif /* INET6 */ flag_ok = flag_bad = 0; break; default: return (1); } if (m->m_pkthdr.csum_flags & flag_ok) return (0); if (m->m_pkthdr.csum_flags & flag_bad) return (1); if (off < sizeof(struct ip) || len < sizeof(struct udphdr)) return (1); if (m->m_pkthdr.len < off + len) return (1); switch (af) { #ifdef INET case AF_INET: if (p == IPPROTO_ICMP) { if (m->m_len < off) return (1); m->m_data += off; m->m_len -= off; sum = in_cksum(m, len); m->m_data -= off; m->m_len += off; } else { if (m->m_len < sizeof(struct ip)) return (1); sum = in4_cksum(m, p, off, len); } break; #endif /* INET */ #ifdef INET6 case AF_INET6: if (m->m_len < sizeof(struct ip6_hdr)) return (1); sum = in6_cksum(m, p, off, len); break; #endif /* INET6 */ default: return (1); } if (sum) { m->m_pkthdr.csum_flags |= flag_bad; switch (p) { case IPPROTO_TCP: KMOD_TCPSTAT_INC(tcps_rcvbadsum); break; case IPPROTO_UDP: KMOD_UDPSTAT_INC(udps_badsum); break; #ifdef INET case IPPROTO_ICMP: KMOD_ICMPSTAT_INC(icps_checksum); break; #endif #ifdef INET6 case IPPROTO_ICMPV6: KMOD_ICMP6STAT_INC(icp6s_checksum); break; #endif /* INET6 */ } return (1); } m->m_pkthdr.csum_flags |= flag_ok; return (0); } #endif #ifndef __FreeBSD__ struct pf_divert * pf_find_divert(struct mbuf *m) { struct m_tag *mtag; if ((mtag = m_tag_find(m, PACKET_TAG_PF_DIVERT, NULL)) == NULL) return (NULL); return ((struct pf_divert *)(mtag + 1)); } struct pf_divert * pf_get_divert(struct mbuf *m) { struct m_tag *mtag; if ((mtag = m_tag_find(m, PACKET_TAG_PF_DIVERT, NULL)) == NULL) { mtag = m_tag_get(PACKET_TAG_PF_DIVERT, sizeof(struct pf_divert), M_NOWAIT); if (mtag == NULL) return (NULL); bzero(mtag + 1, sizeof(struct pf_divert)); m_tag_prepend(m, mtag); } return ((struct pf_divert *)(mtag + 1)); } #endif #ifdef INET int #ifdef __FreeBSD__ pf_test(int dir, struct ifnet *ifp, struct mbuf **m0, struct ether_header *eh, struct inpcb *inp) #else pf_test(int dir, struct ifnet *ifp, struct mbuf **m0, struct ether_header *eh) #endif { struct pfi_kif *kif; u_short action, reason = 0, log = 0; struct mbuf *m = *m0; #ifdef __FreeBSD__ struct ip *h = NULL; struct m_tag *ipfwtag; struct pf_rule *a = NULL, *r = &V_pf_default_rule, *tr, *nr; #else struct ip *h; struct pf_rule *a = NULL, *r = &pf_default_rule, *tr, *nr; #endif struct pf_state *s = NULL; struct pf_ruleset *ruleset = NULL; struct pf_pdesc pd; int off, dirndx, pqid = 0; #ifdef __FreeBSD__ PF_LOCK(); if (!V_pf_status.running) { PF_UNLOCK(); return (PF_PASS); } #else if (!pf_status.running) return (PF_PASS); #endif memset(&pd, 0, sizeof(pd)); #ifdef __FreeBSD__ if ((pd.pf_mtag = pf_get_mtag(m)) == NULL) { PF_UNLOCK(); DPFPRINTF(PF_DEBUG_URGENT, ("pf_test: pf_get_mtag returned NULL\n")); return (PF_DROP); } #endif #ifndef __FreeBSD__ if (ifp->if_type == IFT_CARP && ifp->if_carpdev) kif = (struct pfi_kif *)ifp->if_carpdev->if_pf_kif; else #endif kif = (struct pfi_kif *)ifp->if_pf_kif; if (kif == NULL) { #ifdef __FreeBSD__ PF_UNLOCK(); #endif DPFPRINTF(PF_DEBUG_URGENT, ("pf_test: kif == NULL, if_xname %s\n", ifp->if_xname)); return (PF_DROP); } if (kif->pfik_flags & PFI_IFLAG_SKIP) #ifdef __FreeBSD__ { PF_UNLOCK(); #endif return (PF_PASS); #ifdef __FreeBSD__ } #endif #ifdef __FreeBSD__ M_ASSERTPKTHDR(m); #else #ifdef DIAGNOSTIC if ((m->m_flags & M_PKTHDR) == 0) panic("non-M_PKTHDR is passed to pf_test"); #endif /* DIAGNOSTIC */ #endif if (m->m_pkthdr.len < (int)sizeof(*h)) { action = PF_DROP; REASON_SET(&reason, PFRES_SHORT); log = 1; goto done; } #ifdef __FreeBSD__ if (m->m_flags & M_SKIP_FIREWALL) { PF_UNLOCK(); return (PF_PASS); } #else if (m->m_pkthdr.pf.flags & PF_TAG_GENERATED) return (PF_PASS); #endif #ifdef __FreeBSD__ if (ip_divert_ptr != NULL && ((ipfwtag = m_tag_locate(m, MTAG_IPFW_RULE, 0, NULL)) != NULL)) { struct ipfw_rule_ref *rr = (struct ipfw_rule_ref *)(ipfwtag+1); if (rr->info & IPFW_IS_DIVERT && rr->rulenum == 0) { pd.pf_mtag->flags |= PF_PACKET_LOOPED; m_tag_delete(m, ipfwtag); } if (pd.pf_mtag->flags & PF_FASTFWD_OURS_PRESENT) { m->m_flags |= M_FASTFWD_OURS; pd.pf_mtag->flags &= ~PF_FASTFWD_OURS_PRESENT; } } else #endif /* We do IP header normalization and packet reassembly here */ if (pf_normalize_ip(m0, dir, kif, &reason, &pd) != PF_PASS) { action = PF_DROP; goto done; } m = *m0; /* pf_normalize messes with m0 */ h = mtod(m, struct ip *); off = h->ip_hl << 2; if (off < (int)sizeof(*h)) { action = PF_DROP; REASON_SET(&reason, PFRES_SHORT); log = 1; goto done; } pd.src = (struct pf_addr *)&h->ip_src; pd.dst = (struct pf_addr *)&h->ip_dst; pd.sport = pd.dport = NULL; pd.ip_sum = &h->ip_sum; pd.proto_sum = NULL; pd.proto = h->ip_p; pd.dir = dir; pd.sidx = (dir == PF_IN) ? 0 : 1; pd.didx = (dir == PF_IN) ? 1 : 0; pd.af = AF_INET; pd.tos = h->ip_tos; pd.tot_len = ntohs(h->ip_len); pd.eh = eh; /* handle fragments that didn't get reassembled by normalization */ if (h->ip_off & htons(IP_MF | IP_OFFMASK)) { action = pf_test_fragment(&r, dir, kif, m, h, &pd, &a, &ruleset); goto done; } switch (h->ip_p) { case IPPROTO_TCP: { struct tcphdr th; pd.hdr.tcp = &th; if (!pf_pull_hdr(m, off, &th, sizeof(th), &action, &reason, AF_INET)) { log = action != PF_PASS; goto done; } pd.p_len = pd.tot_len - off - (th.th_off << 2); if ((th.th_flags & TH_ACK) && pd.p_len == 0) pqid = 1; action = pf_normalize_tcp(dir, kif, m, 0, off, h, &pd); if (action == PF_DROP) goto done; action = pf_test_state_tcp(&s, dir, kif, m, off, h, &pd, &reason); if (action == PF_PASS) { #if NPFSYNC > 0 #ifdef __FreeBSD__ if (pfsync_update_state_ptr != NULL) pfsync_update_state_ptr(s); #else pfsync_update_state(s); #endif #endif /* NPFSYNC */ r = s->rule.ptr; a = s->anchor.ptr; log = s->log; } else if (s == NULL) #ifdef __FreeBSD__ action = pf_test_rule(&r, &s, dir, kif, m, off, h, &pd, &a, &ruleset, NULL, inp); #else action = pf_test_rule(&r, &s, dir, kif, m, off, h, &pd, &a, &ruleset, &ipintrq); #endif break; } case IPPROTO_UDP: { struct udphdr uh; pd.hdr.udp = &uh; if (!pf_pull_hdr(m, off, &uh, sizeof(uh), &action, &reason, AF_INET)) { log = action != PF_PASS; goto done; } if (uh.uh_dport == 0 || ntohs(uh.uh_ulen) > m->m_pkthdr.len - off || ntohs(uh.uh_ulen) < sizeof(struct udphdr)) { action = PF_DROP; REASON_SET(&reason, PFRES_SHORT); goto done; } action = pf_test_state_udp(&s, dir, kif, m, off, h, &pd); if (action == PF_PASS) { #if NPFSYNC > 0 #ifdef __FreeBSD__ if (pfsync_update_state_ptr != NULL) pfsync_update_state_ptr(s); #else pfsync_update_state(s); #endif #endif /* NPFSYNC */ r = s->rule.ptr; a = s->anchor.ptr; log = s->log; } else if (s == NULL) #ifdef __FreeBSD__ action = pf_test_rule(&r, &s, dir, kif, m, off, h, &pd, &a, &ruleset, NULL, inp); #else action = pf_test_rule(&r, &s, dir, kif, m, off, h, &pd, &a, &ruleset, &ipintrq); #endif break; } case IPPROTO_ICMP: { struct icmp ih; pd.hdr.icmp = &ih; if (!pf_pull_hdr(m, off, &ih, ICMP_MINLEN, &action, &reason, AF_INET)) { log = action != PF_PASS; goto done; } action = pf_test_state_icmp(&s, dir, kif, m, off, h, &pd, &reason); if (action == PF_PASS) { #if NPFSYNC > 0 #ifdef __FreeBSD__ if (pfsync_update_state_ptr != NULL) pfsync_update_state_ptr(s); #else pfsync_update_state(s); #endif #endif /* NPFSYNC */ r = s->rule.ptr; a = s->anchor.ptr; log = s->log; } else if (s == NULL) #ifdef __FreeBSD__ action = pf_test_rule(&r, &s, dir, kif, m, off, h, &pd, &a, &ruleset, NULL, inp); #else action = pf_test_rule(&r, &s, dir, kif, m, off, h, &pd, &a, &ruleset, &ipintrq); #endif break; } #ifdef INET6 case IPPROTO_ICMPV6: { action = PF_DROP; DPFPRINTF(PF_DEBUG_MISC, ("pf: dropping IPv4 packet with ICMPv6 payload\n")); goto done; } #endif default: action = pf_test_state_other(&s, dir, kif, m, &pd); if (action == PF_PASS) { #if NPFSYNC > 0 #ifdef __FreeBSD__ if (pfsync_update_state_ptr != NULL) pfsync_update_state_ptr(s); #else pfsync_update_state(s); #endif #endif /* NPFSYNC */ r = s->rule.ptr; a = s->anchor.ptr; log = s->log; } else if (s == NULL) #ifdef __FreeBSD__ action = pf_test_rule(&r, &s, dir, kif, m, off, h, &pd, &a, &ruleset, NULL, inp); #else action = pf_test_rule(&r, &s, dir, kif, m, off, h, &pd, &a, &ruleset, &ipintrq); #endif break; } done: if (action == PF_PASS && h->ip_hl > 5 && !((s && s->state_flags & PFSTATE_ALLOWOPTS) || r->allow_opts)) { action = PF_DROP; REASON_SET(&reason, PFRES_IPOPTIONS); log = 1; DPFPRINTF(PF_DEBUG_MISC, ("pf: dropping packet with ip options\n")); } if ((s && s->tag) || r->rtableid >= 0) #ifdef __FreeBSD__ pf_tag_packet(m, s ? s->tag : 0, r->rtableid, pd.pf_mtag); #else pf_tag_packet(m, s ? s->tag : 0, r->rtableid); #endif if (dir == PF_IN && s && s->key[PF_SK_STACK]) #ifdef __FreeBSD__ pd.pf_mtag->statekey = s->key[PF_SK_STACK]; #else m->m_pkthdr.pf.statekey = s->key[PF_SK_STACK]; #endif #ifdef ALTQ if (action == PF_PASS && r->qid) { #ifdef __FreeBSD__ if (pqid || (pd.tos & IPTOS_LOWDELAY)) pd.pf_mtag->qid = r->pqid; else pd.pf_mtag->qid = r->qid; /* add hints for ecn */ pd.pf_mtag->hdr = h; #else if (pqid || (pd.tos & IPTOS_LOWDELAY)) m->m_pkthdr.pf.qid = r->pqid; else m->m_pkthdr.pf.qid = r->qid; /* add hints for ecn */ m->m_pkthdr.pf.hdr = h; #endif } #endif /* ALTQ */ /* * connections redirected to loopback should not match sockets * bound specifically to loopback due to security implications, * see tcp_input() and in_pcblookup_listen(). */ if (dir == PF_IN && action == PF_PASS && (pd.proto == IPPROTO_TCP || pd.proto == IPPROTO_UDP) && s != NULL && s->nat_rule.ptr != NULL && (s->nat_rule.ptr->action == PF_RDR || s->nat_rule.ptr->action == PF_BINAT) && (ntohl(pd.dst->v4.s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) #ifdef __FreeBSD__ m->m_flags |= M_SKIP_FIREWALL; #else m->m_pkthdr.pf.flags |= PF_TAG_TRANSLATE_LOCALHOST; #endif #ifdef __FreeBSD__ if (action == PF_PASS && r->divert.port && ip_divert_ptr != NULL && !PACKET_LOOPED()) { ipfwtag = m_tag_alloc(MTAG_IPFW_RULE, 0, sizeof(struct ipfw_rule_ref), M_NOWAIT | M_ZERO); if (ipfwtag != NULL) { ((struct ipfw_rule_ref *)(ipfwtag+1))->info = ntohs(r->divert.port); ((struct ipfw_rule_ref *)(ipfwtag+1))->rulenum = dir; m_tag_prepend(m, ipfwtag); PF_UNLOCK(); if (m->m_flags & M_FASTFWD_OURS) { pd.pf_mtag->flags |= PF_FASTFWD_OURS_PRESENT; m->m_flags &= ~M_FASTFWD_OURS; } ip_divert_ptr(*m0, dir == PF_IN ? DIR_IN : DIR_OUT); *m0 = NULL; return (action); } else { /* XXX: ipfw has the same behaviour! */ action = PF_DROP; REASON_SET(&reason, PFRES_MEMORY); log = 1; DPFPRINTF(PF_DEBUG_MISC, ("pf: failed to allocate divert tag\n")); } } #else if (dir == PF_IN && action == PF_PASS && r->divert.port) { struct pf_divert *divert; if ((divert = pf_get_divert(m))) { m->m_pkthdr.pf.flags |= PF_TAG_DIVERTED; divert->port = r->divert.port; divert->addr.ipv4 = r->divert.addr.v4; } } #endif if (log) { struct pf_rule *lr; if (s != NULL && s->nat_rule.ptr != NULL && s->nat_rule.ptr->log & PF_LOG_ALL) lr = s->nat_rule.ptr; else lr = r; PFLOG_PACKET(kif, h, m, AF_INET, dir, reason, lr, a, ruleset, &pd); } kif->pfik_bytes[0][dir == PF_OUT][action != PF_PASS] += pd.tot_len; kif->pfik_packets[0][dir == PF_OUT][action != PF_PASS]++; if (action == PF_PASS || r->action == PF_DROP) { dirndx = (dir == PF_OUT); r->packets[dirndx]++; r->bytes[dirndx] += pd.tot_len; if (a != NULL) { a->packets[dirndx]++; a->bytes[dirndx] += pd.tot_len; } if (s != NULL) { if (s->nat_rule.ptr != NULL) { s->nat_rule.ptr->packets[dirndx]++; s->nat_rule.ptr->bytes[dirndx] += pd.tot_len; } if (s->src_node != NULL) { s->src_node->packets[dirndx]++; s->src_node->bytes[dirndx] += pd.tot_len; } if (s->nat_src_node != NULL) { s->nat_src_node->packets[dirndx]++; s->nat_src_node->bytes[dirndx] += pd.tot_len; } dirndx = (dir == s->direction) ? 0 : 1; s->packets[dirndx]++; s->bytes[dirndx] += pd.tot_len; } tr = r; nr = (s != NULL) ? s->nat_rule.ptr : pd.nat_rule; #ifdef __FreeBSD__ if (nr != NULL && r == &V_pf_default_rule) #else if (nr != NULL && r == &pf_default_rule) #endif tr = nr; if (tr->src.addr.type == PF_ADDR_TABLE) pfr_update_stats(tr->src.addr.p.tbl, (s == NULL) ? pd.src : &s->key[(s->direction == PF_IN)]-> addr[(s->direction == PF_OUT)], pd.af, pd.tot_len, dir == PF_OUT, r->action == PF_PASS, tr->src.neg); if (tr->dst.addr.type == PF_ADDR_TABLE) pfr_update_stats(tr->dst.addr.p.tbl, (s == NULL) ? pd.dst : &s->key[(s->direction == PF_IN)]-> addr[(s->direction == PF_IN)], pd.af, pd.tot_len, dir == PF_OUT, r->action == PF_PASS, tr->dst.neg); } switch (action) { case PF_SYNPROXY_DROP: m_freem(*m0); case PF_DEFER: *m0 = NULL; action = PF_PASS; break; default: /* pf_route can free the mbuf causing *m0 to become NULL */ if (r->rt) pf_route(m0, r, dir, kif->pfik_ifp, s, &pd); break; } #ifdef __FreeBSD__ PF_UNLOCK(); #endif return (action); } #endif /* INET */ #ifdef INET6 int #ifdef __FreeBSD__ pf_test6(int dir, struct ifnet *ifp, struct mbuf **m0, struct ether_header *eh, struct inpcb *inp) #else pf_test6(int dir, struct ifnet *ifp, struct mbuf **m0, struct ether_header *eh) #endif { struct pfi_kif *kif; u_short action, reason = 0, log = 0; struct mbuf *m = *m0, *n = NULL; #ifdef __FreeBSD__ struct ip6_hdr *h = NULL; struct pf_rule *a = NULL, *r = &V_pf_default_rule, *tr, *nr; #else struct ip6_hdr *h; struct pf_rule *a = NULL, *r = &pf_default_rule, *tr, *nr; #endif struct pf_state *s = NULL; struct pf_ruleset *ruleset = NULL; struct pf_pdesc pd; int off, terminal = 0, dirndx, rh_cnt = 0; #ifdef __FreeBSD__ PF_LOCK(); if (!V_pf_status.running) { PF_UNLOCK(); return (PF_PASS); } #else if (!pf_status.running) return (PF_PASS); #endif memset(&pd, 0, sizeof(pd)); #ifdef __FreeBSD__ if ((pd.pf_mtag = pf_get_mtag(m)) == NULL) { PF_UNLOCK(); DPFPRINTF(PF_DEBUG_URGENT, ("pf_test: pf_get_mtag returned NULL\n")); return (PF_DROP); } #endif #ifndef __FreeBSD__ if (ifp->if_type == IFT_CARP && ifp->if_carpdev) kif = (struct pfi_kif *)ifp->if_carpdev->if_pf_kif; else #endif kif = (struct pfi_kif *)ifp->if_pf_kif; if (kif == NULL) { #ifdef __FreeBSD__ PF_UNLOCK(); #endif DPFPRINTF(PF_DEBUG_URGENT, ("pf_test6: kif == NULL, if_xname %s\n", ifp->if_xname)); return (PF_DROP); } if (kif->pfik_flags & PFI_IFLAG_SKIP) #ifdef __FreeBSD__ { PF_UNLOCK(); #endif return (PF_PASS); #ifdef __FreeBSD__ } #endif #ifdef __FreeBSD__ M_ASSERTPKTHDR(m); #else #ifdef DIAGNOSTIC if ((m->m_flags & M_PKTHDR) == 0) panic("non-M_PKTHDR is passed to pf_test6"); #endif /* DIAGNOSTIC */ #endif if (m->m_pkthdr.len < (int)sizeof(*h)) { action = PF_DROP; REASON_SET(&reason, PFRES_SHORT); log = 1; goto done; } #ifdef __FreeBSD__ if (pd.pf_mtag->flags & PF_TAG_GENERATED) { PF_UNLOCK(); #else if (m->m_pkthdr.pf.flags & PF_TAG_GENERATED) #endif return (PF_PASS); #ifdef __FreeBSD__ } #endif /* We do IP header normalization and packet reassembly here */ if (pf_normalize_ip6(m0, dir, kif, &reason, &pd) != PF_PASS) { action = PF_DROP; goto done; } m = *m0; /* pf_normalize messes with m0 */ h = mtod(m, struct ip6_hdr *); #if 1 /* * we do not support jumbogram yet. if we keep going, zero ip6_plen * will do something bad, so drop the packet for now. */ if (htons(h->ip6_plen) == 0) { action = PF_DROP; REASON_SET(&reason, PFRES_NORM); /*XXX*/ goto done; } #endif pd.src = (struct pf_addr *)&h->ip6_src; pd.dst = (struct pf_addr *)&h->ip6_dst; pd.sport = pd.dport = NULL; pd.ip_sum = NULL; pd.proto_sum = NULL; pd.dir = dir; pd.sidx = (dir == PF_IN) ? 0 : 1; pd.didx = (dir == PF_IN) ? 1 : 0; pd.af = AF_INET6; pd.tos = 0; pd.tot_len = ntohs(h->ip6_plen) + sizeof(struct ip6_hdr); pd.eh = eh; off = ((caddr_t)h - m->m_data) + sizeof(struct ip6_hdr); pd.proto = h->ip6_nxt; do { switch (pd.proto) { case IPPROTO_FRAGMENT: action = pf_test_fragment(&r, dir, kif, m, h, &pd, &a, &ruleset); if (action == PF_DROP) REASON_SET(&reason, PFRES_FRAG); goto done; case IPPROTO_ROUTING: { struct ip6_rthdr rthdr; if (rh_cnt++) { DPFPRINTF(PF_DEBUG_MISC, ("pf: IPv6 more than one rthdr\n")); action = PF_DROP; REASON_SET(&reason, PFRES_IPOPTIONS); log = 1; goto done; } if (!pf_pull_hdr(m, off, &rthdr, sizeof(rthdr), NULL, &reason, pd.af)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: IPv6 short rthdr\n")); action = PF_DROP; REASON_SET(&reason, PFRES_SHORT); log = 1; goto done; } if (rthdr.ip6r_type == IPV6_RTHDR_TYPE_0) { DPFPRINTF(PF_DEBUG_MISC, ("pf: IPv6 rthdr0\n")); action = PF_DROP; REASON_SET(&reason, PFRES_IPOPTIONS); log = 1; goto done; } /* FALLTHROUGH */ } case IPPROTO_AH: case IPPROTO_HOPOPTS: case IPPROTO_DSTOPTS: { /* get next header and header length */ struct ip6_ext opt6; if (!pf_pull_hdr(m, off, &opt6, sizeof(opt6), NULL, &reason, pd.af)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: IPv6 short opt\n")); action = PF_DROP; log = 1; goto done; } if (pd.proto == IPPROTO_AH) off += (opt6.ip6e_len + 2) * 4; else off += (opt6.ip6e_len + 1) * 8; pd.proto = opt6.ip6e_nxt; /* goto the next header */ break; } default: terminal++; break; } } while (!terminal); /* if there's no routing header, use unmodified mbuf for checksumming */ if (!n) n = m; switch (pd.proto) { case IPPROTO_TCP: { struct tcphdr th; pd.hdr.tcp = &th; if (!pf_pull_hdr(m, off, &th, sizeof(th), &action, &reason, AF_INET6)) { log = action != PF_PASS; goto done; } pd.p_len = pd.tot_len - off - (th.th_off << 2); action = pf_normalize_tcp(dir, kif, m, 0, off, h, &pd); if (action == PF_DROP) goto done; action = pf_test_state_tcp(&s, dir, kif, m, off, h, &pd, &reason); if (action == PF_PASS) { #if NPFSYNC > 0 #ifdef __FreeBSD__ if (pfsync_update_state_ptr != NULL) pfsync_update_state_ptr(s); #else pfsync_update_state(s); #endif #endif /* NPFSYNC */ r = s->rule.ptr; a = s->anchor.ptr; log = s->log; } else if (s == NULL) #ifdef __FreeBSD__ action = pf_test_rule(&r, &s, dir, kif, m, off, h, &pd, &a, &ruleset, NULL, inp); #else action = pf_test_rule(&r, &s, dir, kif, m, off, h, &pd, &a, &ruleset, &ip6intrq); #endif break; } case IPPROTO_UDP: { struct udphdr uh; pd.hdr.udp = &uh; if (!pf_pull_hdr(m, off, &uh, sizeof(uh), &action, &reason, AF_INET6)) { log = action != PF_PASS; goto done; } if (uh.uh_dport == 0 || ntohs(uh.uh_ulen) > m->m_pkthdr.len - off || ntohs(uh.uh_ulen) < sizeof(struct udphdr)) { action = PF_DROP; REASON_SET(&reason, PFRES_SHORT); goto done; } action = pf_test_state_udp(&s, dir, kif, m, off, h, &pd); if (action == PF_PASS) { #if NPFSYNC > 0 #ifdef __FreeBSD__ if (pfsync_update_state_ptr != NULL) pfsync_update_state_ptr(s); #else pfsync_update_state(s); #endif #endif /* NPFSYNC */ r = s->rule.ptr; a = s->anchor.ptr; log = s->log; } else if (s == NULL) #ifdef __FreeBSD__ action = pf_test_rule(&r, &s, dir, kif, m, off, h, &pd, &a, &ruleset, NULL, inp); #else action = pf_test_rule(&r, &s, dir, kif, m, off, h, &pd, &a, &ruleset, &ip6intrq); #endif break; } case IPPROTO_ICMP: { action = PF_DROP; DPFPRINTF(PF_DEBUG_MISC, ("pf: dropping IPv6 packet with ICMPv4 payload\n")); goto done; } case IPPROTO_ICMPV6: { struct icmp6_hdr ih; pd.hdr.icmp6 = &ih; if (!pf_pull_hdr(m, off, &ih, sizeof(ih), &action, &reason, AF_INET6)) { log = action != PF_PASS; goto done; } action = pf_test_state_icmp(&s, dir, kif, m, off, h, &pd, &reason); if (action == PF_PASS) { #if NPFSYNC > 0 #ifdef __FreeBSD__ if (pfsync_update_state_ptr != NULL) pfsync_update_state_ptr(s); #else pfsync_update_state(s); #endif #endif /* NPFSYNC */ r = s->rule.ptr; a = s->anchor.ptr; log = s->log; } else if (s == NULL) #ifdef __FreeBSD__ action = pf_test_rule(&r, &s, dir, kif, m, off, h, &pd, &a, &ruleset, NULL, inp); #else action = pf_test_rule(&r, &s, dir, kif, m, off, h, &pd, &a, &ruleset, &ip6intrq); #endif break; } default: action = pf_test_state_other(&s, dir, kif, m, &pd); if (action == PF_PASS) { #if NPFSYNC > 0 #ifdef __FreeBSD__ if (pfsync_update_state_ptr != NULL) pfsync_update_state_ptr(s); #else pfsync_update_state(s); #endif #endif /* NPFSYNC */ r = s->rule.ptr; a = s->anchor.ptr; log = s->log; } else if (s == NULL) #ifdef __FreeBSD__ action = pf_test_rule(&r, &s, dir, kif, m, off, h, &pd, &a, &ruleset, NULL, inp); #else action = pf_test_rule(&r, &s, dir, kif, m, off, h, &pd, &a, &ruleset, &ip6intrq); #endif break; } done: if (n != m) { m_freem(n); n = NULL; } /* handle dangerous IPv6 extension headers. */ if (action == PF_PASS && rh_cnt && !((s && s->state_flags & PFSTATE_ALLOWOPTS) || r->allow_opts)) { action = PF_DROP; REASON_SET(&reason, PFRES_IPOPTIONS); log = 1; DPFPRINTF(PF_DEBUG_MISC, ("pf: dropping packet with dangerous v6 headers\n")); } if ((s && s->tag) || r->rtableid >= 0) #ifdef __FreeBSD__ pf_tag_packet(m, s ? s->tag : 0, r->rtableid, pd.pf_mtag); #else pf_tag_packet(m, s ? s->tag : 0, r->rtableid); #endif if (dir == PF_IN && s && s->key[PF_SK_STACK]) #ifdef __FreeBSD__ pd.pf_mtag->statekey = s->key[PF_SK_STACK]; #else m->m_pkthdr.pf.statekey = s->key[PF_SK_STACK]; #endif #ifdef ALTQ if (action == PF_PASS && r->qid) { #ifdef __FreeBSD__ if (pd.tos & IPTOS_LOWDELAY) pd.pf_mtag->qid = r->pqid; else pd.pf_mtag->qid = r->qid; /* add hints for ecn */ pd.pf_mtag->hdr = h; #else if (pd.tos & IPTOS_LOWDELAY) m->m_pkthdr.pf.qid = r->pqid; else m->m_pkthdr.pf.qid = r->qid; /* add hints for ecn */ m->m_pkthdr.pf.hdr = h; #endif } #endif /* ALTQ */ if (dir == PF_IN && action == PF_PASS && (pd.proto == IPPROTO_TCP || pd.proto == IPPROTO_UDP) && s != NULL && s->nat_rule.ptr != NULL && (s->nat_rule.ptr->action == PF_RDR || s->nat_rule.ptr->action == PF_BINAT) && IN6_IS_ADDR_LOOPBACK(&pd.dst->v6)) #ifdef __FreeBSD__ m->m_flags |= M_SKIP_FIREWALL; #else m->m_pkthdr.pf.flags |= PF_TAG_TRANSLATE_LOCALHOST; #endif #ifdef __FreeBSD__ /* XXX: Anybody working on it?! */ if (r->divert.port) printf("pf: divert(9) is not supported for IPv6\n"); #else if (dir == PF_IN && action == PF_PASS && r->divert.port) { struct pf_divert *divert; if ((divert = pf_get_divert(m))) { m->m_pkthdr.pf.flags |= PF_TAG_DIVERTED; divert->port = r->divert.port; divert->addr.ipv6 = r->divert.addr.v6; } } #endif if (log) { struct pf_rule *lr; if (s != NULL && s->nat_rule.ptr != NULL && s->nat_rule.ptr->log & PF_LOG_ALL) lr = s->nat_rule.ptr; else lr = r; PFLOG_PACKET(kif, h, m, AF_INET6, dir, reason, lr, a, ruleset, &pd); } kif->pfik_bytes[1][dir == PF_OUT][action != PF_PASS] += pd.tot_len; kif->pfik_packets[1][dir == PF_OUT][action != PF_PASS]++; if (action == PF_PASS || r->action == PF_DROP) { dirndx = (dir == PF_OUT); r->packets[dirndx]++; r->bytes[dirndx] += pd.tot_len; if (a != NULL) { a->packets[dirndx]++; a->bytes[dirndx] += pd.tot_len; } if (s != NULL) { if (s->nat_rule.ptr != NULL) { s->nat_rule.ptr->packets[dirndx]++; s->nat_rule.ptr->bytes[dirndx] += pd.tot_len; } if (s->src_node != NULL) { s->src_node->packets[dirndx]++; s->src_node->bytes[dirndx] += pd.tot_len; } if (s->nat_src_node != NULL) { s->nat_src_node->packets[dirndx]++; s->nat_src_node->bytes[dirndx] += pd.tot_len; } dirndx = (dir == s->direction) ? 0 : 1; s->packets[dirndx]++; s->bytes[dirndx] += pd.tot_len; } tr = r; nr = (s != NULL) ? s->nat_rule.ptr : pd.nat_rule; #ifdef __FreeBSD__ if (nr != NULL && r == &V_pf_default_rule) #else if (nr != NULL && r == &pf_default_rule) #endif tr = nr; if (tr->src.addr.type == PF_ADDR_TABLE) pfr_update_stats(tr->src.addr.p.tbl, (s == NULL) ? pd.src : &s->key[(s->direction == PF_IN)]->addr[0], pd.af, pd.tot_len, dir == PF_OUT, r->action == PF_PASS, tr->src.neg); if (tr->dst.addr.type == PF_ADDR_TABLE) pfr_update_stats(tr->dst.addr.p.tbl, (s == NULL) ? pd.dst : &s->key[(s->direction == PF_IN)]->addr[1], pd.af, pd.tot_len, dir == PF_OUT, r->action == PF_PASS, tr->dst.neg); } switch (action) { case PF_SYNPROXY_DROP: m_freem(*m0); case PF_DEFER: *m0 = NULL; action = PF_PASS; break; default: /* pf_route6 can free the mbuf causing *m0 to become NULL */ if (r->rt) pf_route6(m0, r, dir, kif->pfik_ifp, s, &pd); break; } #ifdef __FreeBSD__ PF_UNLOCK(); #endif return (action); } #endif /* INET6 */ int pf_check_congestion(struct ifqueue *ifq) { #ifdef __FreeBSD__ /* XXX_IMPORT: later */ return (0); #else if (ifq->ifq_congestion) return (1); else return (0); #endif } /* * must be called whenever any addressing information such as * address, port, protocol has changed */ void pf_pkt_addr_changed(struct mbuf *m) { #ifdef __FreeBSD__ struct pf_mtag *pf_tag; if ((pf_tag = pf_find_mtag(m)) != NULL) pf_tag->statekey = NULL; #else m->m_pkthdr.pf.statekey = NULL; #endif } Index: stable/9/sys/contrib/pf/net/pf_ioctl.c =================================================================== --- stable/9/sys/contrib/pf/net/pf_ioctl.c (revision 292730) +++ stable/9/sys/contrib/pf/net/pf_ioctl.c (revision 292731) @@ -1,4420 +1,4407 @@ /* $OpenBSD: pf_ioctl.c,v 1.213 2009/02/15 21:46:12 mbalmer Exp $ */ /* * Copyright (c) 2001 Daniel Hartmeier * Copyright (c) 2002,2003 Henning Brauer * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - 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 COPYRIGHT HOLDERS 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 * COPYRIGHT HOLDERS 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. * * Effort sponsored in part by the Defense Advanced Research Projects * Agency (DARPA) and Air Force Research Laboratory, Air Force * Materiel Command, USAF, under agreement number F30602-01-2-0537. * */ #ifdef __FreeBSD__ #include __FBSDID("$FreeBSD$"); #include "opt_inet.h" #include "opt_inet6.h" #include "opt_bpf.h" #include "opt_pf.h" #define NPFSYNC 1 #ifdef DEV_PFLOG #define NPFLOG DEV_PFLOG #else #define NPFLOG 0 #endif #else /* !__FreeBSD__ */ #include "pfsync.h" #include "pflog.h" #endif /* __FreeBSD__ */ #include #include #include #include #include #include #include #include #include #ifdef __FreeBSD__ #include #include #include #include #include #include #else #include #include #endif #include #include #include #ifndef __FreeBSD__ #include #include #endif #include #include #ifdef __FreeBSD__ #include #endif #include #include #include #include #include #include #include #ifdef __FreeBSD__ #include #else #include #include #endif #include #include #if NPFLOG > 0 #include #endif /* NPFLOG > 0 */ #ifdef INET6 #include #include #endif /* INET6 */ #ifdef ALTQ #include #endif #ifdef __FreeBSD__ #include #include #include #include #endif /* __FreeBSD__ */ #ifdef __FreeBSD__ void init_zone_var(void); void cleanup_pf_zone(void); int pfattach(void); #else void pfattach(int); void pf_thread_create(void *); int pfopen(dev_t, int, int, struct proc *); int pfclose(dev_t, int, int, struct proc *); #endif struct pf_pool *pf_get_pool(char *, u_int32_t, u_int8_t, u_int32_t, u_int8_t, u_int8_t, u_int8_t); void pf_mv_pool(struct pf_palist *, struct pf_palist *); void pf_empty_pool(struct pf_palist *); #ifdef __FreeBSD__ int pfioctl(struct cdev *, u_long, caddr_t, int, struct thread *); #else int pfioctl(dev_t, u_long, caddr_t, int, struct proc *); #endif #ifdef ALTQ int pf_begin_altq(u_int32_t *); int pf_rollback_altq(u_int32_t); int pf_commit_altq(u_int32_t); int pf_enable_altq(struct pf_altq *); int pf_disable_altq(struct pf_altq *); #endif /* ALTQ */ int pf_begin_rules(u_int32_t *, int, const char *); int pf_rollback_rules(u_int32_t, int, char *); int pf_setup_pfsync_matching(struct pf_ruleset *); void pf_hash_rule(MD5_CTX *, struct pf_rule *); void pf_hash_rule_addr(MD5_CTX *, struct pf_rule_addr *); int pf_commit_rules(u_int32_t, int, char *); int pf_addr_setup(struct pf_ruleset *, struct pf_addr_wrap *, sa_family_t); void pf_addr_copyout(struct pf_addr_wrap *); #define TAGID_MAX 50000 #ifdef __FreeBSD__ VNET_DEFINE(struct pf_rule, pf_default_rule); VNET_DEFINE(struct sx, pf_consistency_lock); #ifdef ALTQ static VNET_DEFINE(int, pf_altq_running); #define V_pf_altq_running VNET(pf_altq_running) #endif TAILQ_HEAD(pf_tags, pf_tagname); #define V_pf_tags VNET(pf_tags) VNET_DEFINE(struct pf_tags, pf_tags); #define V_pf_qids VNET(pf_qids) VNET_DEFINE(struct pf_tags, pf_qids); #else /* !__FreeBSD__ */ struct pf_rule pf_default_rule; struct rwlock pf_consistency_lock = RWLOCK_INITIALIZER("pfcnslk"); #ifdef ALTQ static int pf_altq_running; #endif TAILQ_HEAD(pf_tags, pf_tagname) pf_tags = TAILQ_HEAD_INITIALIZER(pf_tags), pf_qids = TAILQ_HEAD_INITIALIZER(pf_qids); #endif /* __FreeBSD__ */ #if (PF_QNAME_SIZE != PF_TAG_NAME_SIZE) #error PF_QNAME_SIZE must be equal to PF_TAG_NAME_SIZE #endif u_int16_t tagname2tag(struct pf_tags *, char *); void tag2tagname(struct pf_tags *, u_int16_t, char *); void tag_unref(struct pf_tags *, u_int16_t); int pf_rtlabel_add(struct pf_addr_wrap *); void pf_rtlabel_remove(struct pf_addr_wrap *); void pf_rtlabel_copyout(struct pf_addr_wrap *); #ifdef __FreeBSD__ #define DPFPRINTF(n, x) if (V_pf_status.debug >= (n)) printf x #else #define DPFPRINTF(n, x) if (pf_status.debug >= (n)) printf x #endif #ifdef __FreeBSD__ struct cdev *pf_dev; /* * XXX - These are new and need to be checked when moveing to a new version */ static void pf_clear_states(void); static int pf_clear_tables(void); static void pf_clear_srcnodes(void); /* * XXX - These are new and need to be checked when moveing to a new version */ /* * Wrapper functions for pfil(9) hooks */ #ifdef INET static int pf_check_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, struct inpcb *inp); static int pf_check_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, struct inpcb *inp); #endif #ifdef INET6 static int pf_check6_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, struct inpcb *inp); static int pf_check6_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, struct inpcb *inp); #endif static int hook_pf(void); static int dehook_pf(void); static int shutdown_pf(void); static int pf_load(void); static int pf_unload(void); static struct cdevsw pf_cdevsw = { .d_ioctl = pfioctl, .d_name = PF_NAME, .d_version = D_VERSION, }; static volatile VNET_DEFINE(int, pf_pfil_hooked); #define V_pf_pfil_hooked VNET(pf_pfil_hooked) VNET_DEFINE(int, pf_end_threads); struct mtx pf_task_mtx; /* pfsync */ pfsync_state_import_t *pfsync_state_import_ptr = NULL; pfsync_insert_state_t *pfsync_insert_state_ptr = NULL; pfsync_update_state_t *pfsync_update_state_ptr = NULL; pfsync_delete_state_t *pfsync_delete_state_ptr = NULL; pfsync_clear_states_t *pfsync_clear_states_ptr = NULL; pfsync_state_in_use_t *pfsync_state_in_use_ptr = NULL; pfsync_defer_t *pfsync_defer_ptr = NULL; pfsync_up_t *pfsync_up_ptr = NULL; /* pflow */ export_pflow_t *export_pflow_ptr = NULL; /* pflog */ pflog_packet_t *pflog_packet_ptr = NULL; VNET_DEFINE(int, debug_pfugidhack); SYSCTL_VNET_INT(_debug, OID_AUTO, pfugidhack, CTLFLAG_RW, &VNET_NAME(debug_pfugidhack), 0, "Enable/disable pf user/group rules mpsafe hack"); static void init_pf_mutex(void) { mtx_init(&pf_task_mtx, "pf task mtx", NULL, MTX_DEF); } static void destroy_pf_mutex(void) { mtx_destroy(&pf_task_mtx); } void init_zone_var(void) { V_pf_src_tree_pl = V_pf_rule_pl = NULL; V_pf_state_pl = V_pf_state_key_pl = V_pf_state_item_pl = NULL; V_pf_altq_pl = V_pf_pooladdr_pl = NULL; V_pf_frent_pl = V_pf_frag_pl = V_pf_cache_pl = V_pf_cent_pl = NULL; V_pf_state_scrub_pl = NULL; V_pfr_ktable_pl = V_pfr_kentry_pl = V_pfr_kcounters_pl = NULL; } void cleanup_pf_zone(void) { UMA_DESTROY(V_pf_src_tree_pl); UMA_DESTROY(V_pf_rule_pl); UMA_DESTROY(V_pf_state_pl); UMA_DESTROY(V_pf_state_key_pl); UMA_DESTROY(V_pf_state_item_pl); UMA_DESTROY(V_pf_altq_pl); UMA_DESTROY(V_pf_pooladdr_pl); UMA_DESTROY(V_pf_frent_pl); UMA_DESTROY(V_pf_frag_pl); UMA_DESTROY(V_pf_cache_pl); UMA_DESTROY(V_pf_cent_pl); UMA_DESTROY(V_pfr_ktable_pl); UMA_DESTROY(V_pfr_kentry_pl); UMA_DESTROY(V_pfr_kcounters_pl); UMA_DESTROY(V_pf_state_scrub_pl); UMA_DESTROY(V_pfi_addr_pl); } int pfattach(void) { u_int32_t *my_timeout = V_pf_default_rule.timeout; int error = 1; do { UMA_CREATE(V_pf_src_tree_pl, struct pf_src_node, "pfsrctrpl"); UMA_CREATE(V_pf_rule_pl, struct pf_rule, "pfrulepl"); UMA_CREATE(V_pf_state_pl, struct pf_state, "pfstatepl"); UMA_CREATE(V_pf_state_key_pl, struct pf_state, "pfstatekeypl"); UMA_CREATE(V_pf_state_item_pl, struct pf_state, "pfstateitempl"); UMA_CREATE(V_pf_altq_pl, struct pf_altq, "pfaltqpl"); UMA_CREATE(V_pf_pooladdr_pl, struct pf_pooladdr, "pfpooladdrpl"); UMA_CREATE(V_pfr_ktable_pl, struct pfr_ktable, "pfrktable"); UMA_CREATE(V_pfr_kentry_pl, struct pfr_kentry, "pfrkentry"); UMA_CREATE(V_pfr_kcounters_pl, struct pfr_kcounters, "pfrkcounters"); UMA_CREATE(V_pf_frent_pl, struct pf_frent, "pffrent"); UMA_CREATE(V_pf_frag_pl, struct pf_fragment, "pffrag"); UMA_CREATE(V_pf_cache_pl, struct pf_fragment, "pffrcache"); UMA_CREATE(V_pf_cent_pl, struct pf_frcache, "pffrcent"); UMA_CREATE(V_pf_state_scrub_pl, struct pf_state_scrub, "pfstatescrub"); UMA_CREATE(V_pfi_addr_pl, struct pfi_dynaddr, "pfiaddrpl"); error = 0; } while(0); if (error) { cleanup_pf_zone(); return (error); } pfr_initialize(); pfi_initialize(); if ( (error = pf_osfp_initialize()) ) { cleanup_pf_zone(); pf_osfp_cleanup(); return (error); } V_pf_pool_limits[PF_LIMIT_STATES].pp = V_pf_state_pl; V_pf_pool_limits[PF_LIMIT_STATES].limit = PFSTATE_HIWAT; V_pf_pool_limits[PF_LIMIT_SRC_NODES].pp = V_pf_src_tree_pl; V_pf_pool_limits[PF_LIMIT_SRC_NODES].limit = PFSNODE_HIWAT; V_pf_pool_limits[PF_LIMIT_FRAGS].pp = V_pf_frent_pl; V_pf_pool_limits[PF_LIMIT_FRAGS].limit = PFFRAG_FRENT_HIWAT; V_pf_pool_limits[PF_LIMIT_TABLES].pp = V_pfr_ktable_pl; V_pf_pool_limits[PF_LIMIT_TABLES].limit = PFR_KTABLE_HIWAT; V_pf_pool_limits[PF_LIMIT_TABLE_ENTRIES].pp = V_pfr_kentry_pl; V_pf_pool_limits[PF_LIMIT_TABLE_ENTRIES].limit = PFR_KENTRY_HIWAT; uma_zone_set_max(V_pf_pool_limits[PF_LIMIT_STATES].pp, V_pf_pool_limits[PF_LIMIT_STATES].limit); RB_INIT(&V_tree_src_tracking); RB_INIT(&V_pf_anchors); pf_init_ruleset(&pf_main_ruleset); TAILQ_INIT(&V_pf_altqs[0]); TAILQ_INIT(&V_pf_altqs[1]); TAILQ_INIT(&V_pf_pabuf); V_pf_altqs_active = &V_pf_altqs[0]; V_pf_altqs_inactive = &V_pf_altqs[1]; TAILQ_INIT(&V_state_list); /* default rule should never be garbage collected */ V_pf_default_rule.entries.tqe_prev = &V_pf_default_rule.entries.tqe_next; V_pf_default_rule.action = PF_PASS; V_pf_default_rule.nr = -1; V_pf_default_rule.rtableid = -1; /* initialize default timeouts */ my_timeout[PFTM_TCP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL; my_timeout[PFTM_TCP_OPENING] = PFTM_TCP_OPENING_VAL; my_timeout[PFTM_TCP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL; my_timeout[PFTM_TCP_CLOSING] = PFTM_TCP_CLOSING_VAL; my_timeout[PFTM_TCP_FIN_WAIT] = PFTM_TCP_FIN_WAIT_VAL; my_timeout[PFTM_TCP_CLOSED] = PFTM_TCP_CLOSED_VAL; my_timeout[PFTM_UDP_FIRST_PACKET] = PFTM_UDP_FIRST_PACKET_VAL; my_timeout[PFTM_UDP_SINGLE] = PFTM_UDP_SINGLE_VAL; my_timeout[PFTM_UDP_MULTIPLE] = PFTM_UDP_MULTIPLE_VAL; my_timeout[PFTM_ICMP_FIRST_PACKET] = PFTM_ICMP_FIRST_PACKET_VAL; my_timeout[PFTM_ICMP_ERROR_REPLY] = PFTM_ICMP_ERROR_REPLY_VAL; my_timeout[PFTM_OTHER_FIRST_PACKET] = PFTM_OTHER_FIRST_PACKET_VAL; my_timeout[PFTM_OTHER_SINGLE] = PFTM_OTHER_SINGLE_VAL; my_timeout[PFTM_OTHER_MULTIPLE] = PFTM_OTHER_MULTIPLE_VAL; my_timeout[PFTM_FRAG] = PFTM_FRAG_VAL; my_timeout[PFTM_INTERVAL] = PFTM_INTERVAL_VAL; my_timeout[PFTM_SRC_NODE] = PFTM_SRC_NODE_VAL; my_timeout[PFTM_TS_DIFF] = PFTM_TS_DIFF_VAL; my_timeout[PFTM_ADAPTIVE_START] = PFSTATE_ADAPT_START; my_timeout[PFTM_ADAPTIVE_END] = PFSTATE_ADAPT_END; pf_normalize_init(); bzero(&V_pf_status, sizeof(V_pf_status)); V_pf_status.debug = PF_DEBUG_URGENT; V_pf_pfil_hooked = 0; /* XXX do our best to avoid a conflict */ V_pf_status.hostid = arc4random(); if (kproc_create(pf_purge_thread, curvnet, NULL, 0, 0, "pfpurge")) return (ENXIO); m_addr_chg_pf_p = pf_pkt_addr_changed; return (error); } #else /* !__FreeBSD__ */ void pfattach(int num) { u_int32_t *timeout = pf_default_rule.timeout; pool_init(&pf_rule_pl, sizeof(struct pf_rule), 0, 0, 0, "pfrulepl", &pool_allocator_nointr); pool_init(&pf_src_tree_pl, sizeof(struct pf_src_node), 0, 0, 0, "pfsrctrpl", NULL); pool_init(&pf_state_pl, sizeof(struct pf_state), 0, 0, 0, "pfstatepl", NULL); pool_init(&pf_state_key_pl, sizeof(struct pf_state_key), 0, 0, 0, "pfstatekeypl", NULL); pool_init(&pf_state_item_pl, sizeof(struct pf_state_item), 0, 0, 0, "pfstateitempl", NULL); pool_init(&pf_altq_pl, sizeof(struct pf_altq), 0, 0, 0, "pfaltqpl", &pool_allocator_nointr); pool_init(&pf_pooladdr_pl, sizeof(struct pf_pooladdr), 0, 0, 0, "pfpooladdrpl", &pool_allocator_nointr); pfr_initialize(); pfi_initialize(); pf_osfp_initialize(); pool_sethardlimit(pf_pool_limits[PF_LIMIT_STATES].pp, pf_pool_limits[PF_LIMIT_STATES].limit, NULL, 0); if (physmem <= atop(100*1024*1024)) pf_pool_limits[PF_LIMIT_TABLE_ENTRIES].limit = PFR_KENTRY_HIWAT_SMALL; RB_INIT(&tree_src_tracking); RB_INIT(&pf_anchors); pf_init_ruleset(&pf_main_ruleset); TAILQ_INIT(&pf_altqs[0]); TAILQ_INIT(&pf_altqs[1]); TAILQ_INIT(&pf_pabuf); pf_altqs_active = &pf_altqs[0]; pf_altqs_inactive = &pf_altqs[1]; TAILQ_INIT(&state_list); /* default rule should never be garbage collected */ pf_default_rule.entries.tqe_prev = &pf_default_rule.entries.tqe_next; pf_default_rule.action = PF_PASS; pf_default_rule.nr = -1; pf_default_rule.rtableid = -1; /* initialize default timeouts */ timeout[PFTM_TCP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL; timeout[PFTM_TCP_OPENING] = PFTM_TCP_OPENING_VAL; timeout[PFTM_TCP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL; timeout[PFTM_TCP_CLOSING] = PFTM_TCP_CLOSING_VAL; timeout[PFTM_TCP_FIN_WAIT] = PFTM_TCP_FIN_WAIT_VAL; timeout[PFTM_TCP_CLOSED] = PFTM_TCP_CLOSED_VAL; timeout[PFTM_UDP_FIRST_PACKET] = PFTM_UDP_FIRST_PACKET_VAL; timeout[PFTM_UDP_SINGLE] = PFTM_UDP_SINGLE_VAL; timeout[PFTM_UDP_MULTIPLE] = PFTM_UDP_MULTIPLE_VAL; timeout[PFTM_ICMP_FIRST_PACKET] = PFTM_ICMP_FIRST_PACKET_VAL; timeout[PFTM_ICMP_ERROR_REPLY] = PFTM_ICMP_ERROR_REPLY_VAL; timeout[PFTM_OTHER_FIRST_PACKET] = PFTM_OTHER_FIRST_PACKET_VAL; timeout[PFTM_OTHER_SINGLE] = PFTM_OTHER_SINGLE_VAL; timeout[PFTM_OTHER_MULTIPLE] = PFTM_OTHER_MULTIPLE_VAL; timeout[PFTM_FRAG] = PFTM_FRAG_VAL; timeout[PFTM_INTERVAL] = PFTM_INTERVAL_VAL; timeout[PFTM_SRC_NODE] = PFTM_SRC_NODE_VAL; timeout[PFTM_TS_DIFF] = PFTM_TS_DIFF_VAL; timeout[PFTM_ADAPTIVE_START] = PFSTATE_ADAPT_START; timeout[PFTM_ADAPTIVE_END] = PFSTATE_ADAPT_END; pf_normalize_init(); bzero(&pf_status, sizeof(pf_status)); pf_status.debug = PF_DEBUG_URGENT; /* XXX do our best to avoid a conflict */ pf_status.hostid = arc4random(); /* require process context to purge states, so perform in a thread */ kthread_create_deferred(pf_thread_create, NULL); } void pf_thread_create(void *v) { if (kthread_create(pf_purge_thread, NULL, NULL, "pfpurge")) panic("pfpurge thread"); } int pfopen(dev_t dev, int flags, int fmt, struct proc *p) { if (minor(dev) >= 1) return (ENXIO); return (0); } int pfclose(dev_t dev, int flags, int fmt, struct proc *p) { if (minor(dev) >= 1) return (ENXIO); return (0); } #endif struct pf_pool * pf_get_pool(char *anchor, u_int32_t ticket, u_int8_t rule_action, u_int32_t rule_number, u_int8_t r_last, u_int8_t active, u_int8_t check_ticket) { struct pf_ruleset *ruleset; struct pf_rule *rule; int rs_num; ruleset = pf_find_ruleset(anchor); if (ruleset == NULL) return (NULL); rs_num = pf_get_ruleset_number(rule_action); if (rs_num >= PF_RULESET_MAX) return (NULL); if (active) { if (check_ticket && ticket != ruleset->rules[rs_num].active.ticket) return (NULL); if (r_last) rule = TAILQ_LAST(ruleset->rules[rs_num].active.ptr, pf_rulequeue); else rule = TAILQ_FIRST(ruleset->rules[rs_num].active.ptr); } else { if (check_ticket && ticket != ruleset->rules[rs_num].inactive.ticket) return (NULL); if (r_last) rule = TAILQ_LAST(ruleset->rules[rs_num].inactive.ptr, pf_rulequeue); else rule = TAILQ_FIRST(ruleset->rules[rs_num].inactive.ptr); } if (!r_last) { while ((rule != NULL) && (rule->nr != rule_number)) rule = TAILQ_NEXT(rule, entries); } if (rule == NULL) return (NULL); return (&rule->rpool); } void pf_mv_pool(struct pf_palist *poola, struct pf_palist *poolb) { struct pf_pooladdr *mv_pool_pa; while ((mv_pool_pa = TAILQ_FIRST(poola)) != NULL) { TAILQ_REMOVE(poola, mv_pool_pa, entries); TAILQ_INSERT_TAIL(poolb, mv_pool_pa, entries); } } void pf_empty_pool(struct pf_palist *poola) { struct pf_pooladdr *empty_pool_pa; while ((empty_pool_pa = TAILQ_FIRST(poola)) != NULL) { pfi_dynaddr_remove(&empty_pool_pa->addr); pf_tbladdr_remove(&empty_pool_pa->addr); pfi_kif_unref(empty_pool_pa->kif, PFI_KIF_REF_RULE); TAILQ_REMOVE(poola, empty_pool_pa, entries); #ifdef __FreeBSD__ pool_put(&V_pf_pooladdr_pl, empty_pool_pa); #else pool_put(&pf_pooladdr_pl, empty_pool_pa); #endif } } void pf_rm_rule(struct pf_rulequeue *rulequeue, struct pf_rule *rule) { if (rulequeue != NULL) { if (rule->states_cur <= 0) { /* * XXX - we need to remove the table *before* detaching * the rule to make sure the table code does not delete * the anchor under our feet. */ pf_tbladdr_remove(&rule->src.addr); pf_tbladdr_remove(&rule->dst.addr); if (rule->overload_tbl) pfr_detach_table(rule->overload_tbl); } TAILQ_REMOVE(rulequeue, rule, entries); rule->entries.tqe_prev = NULL; rule->nr = -1; } if (rule->states_cur > 0 || rule->src_nodes > 0 || rule->entries.tqe_prev != NULL) return; pf_tag_unref(rule->tag); pf_tag_unref(rule->match_tag); #ifdef ALTQ if (rule->pqid != rule->qid) pf_qid_unref(rule->pqid); pf_qid_unref(rule->qid); #endif pf_rtlabel_remove(&rule->src.addr); pf_rtlabel_remove(&rule->dst.addr); pfi_dynaddr_remove(&rule->src.addr); pfi_dynaddr_remove(&rule->dst.addr); if (rulequeue == NULL) { pf_tbladdr_remove(&rule->src.addr); pf_tbladdr_remove(&rule->dst.addr); if (rule->overload_tbl) pfr_detach_table(rule->overload_tbl); } pfi_kif_unref(rule->kif, PFI_KIF_REF_RULE); pf_anchor_remove(rule); pf_empty_pool(&rule->rpool.list); #ifdef __FreeBSD__ pool_put(&V_pf_rule_pl, rule); #else pool_put(&pf_rule_pl, rule); #endif } u_int16_t tagname2tag(struct pf_tags *head, char *tagname) { struct pf_tagname *tag, *p = NULL; u_int16_t new_tagid = 1; TAILQ_FOREACH(tag, head, entries) if (strcmp(tagname, tag->name) == 0) { tag->ref++; return (tag->tag); } /* * to avoid fragmentation, we do a linear search from the beginning * and take the first free slot we find. if there is none or the list * is empty, append a new entry at the end. */ /* new entry */ if (!TAILQ_EMPTY(head)) for (p = TAILQ_FIRST(head); p != NULL && p->tag == new_tagid; p = TAILQ_NEXT(p, entries)) new_tagid = p->tag + 1; if (new_tagid > TAGID_MAX) return (0); /* allocate and fill new struct pf_tagname */ tag = malloc(sizeof(*tag), M_TEMP, M_NOWAIT|M_ZERO); if (tag == NULL) return (0); strlcpy(tag->name, tagname, sizeof(tag->name)); tag->tag = new_tagid; tag->ref++; if (p != NULL) /* insert new entry before p */ TAILQ_INSERT_BEFORE(p, tag, entries); else /* either list empty or no free slot in between */ TAILQ_INSERT_TAIL(head, tag, entries); return (tag->tag); } void tag2tagname(struct pf_tags *head, u_int16_t tagid, char *p) { struct pf_tagname *tag; TAILQ_FOREACH(tag, head, entries) if (tag->tag == tagid) { strlcpy(p, tag->name, PF_TAG_NAME_SIZE); return; } } void tag_unref(struct pf_tags *head, u_int16_t tag) { struct pf_tagname *p, *next; if (tag == 0) return; for (p = TAILQ_FIRST(head); p != NULL; p = next) { next = TAILQ_NEXT(p, entries); if (tag == p->tag) { if (--p->ref == 0) { TAILQ_REMOVE(head, p, entries); free(p, M_TEMP); } break; } } } u_int16_t pf_tagname2tag(char *tagname) { #ifdef __FreeBSD__ return (tagname2tag(&V_pf_tags, tagname)); #else return (tagname2tag(&pf_tags, tagname)); #endif } void pf_tag2tagname(u_int16_t tagid, char *p) { #ifdef __FreeBSD__ tag2tagname(&V_pf_tags, tagid, p); #else tag2tagname(&pf_tags, tagid, p); #endif } void pf_tag_ref(u_int16_t tag) { struct pf_tagname *t; #ifdef __FreeBSD__ TAILQ_FOREACH(t, &V_pf_tags, entries) #else TAILQ_FOREACH(t, &pf_tags, entries) #endif if (t->tag == tag) break; if (t != NULL) t->ref++; } void pf_tag_unref(u_int16_t tag) { #ifdef __FreeBSD__ tag_unref(&V_pf_tags, tag); #else tag_unref(&pf_tags, tag); #endif } int pf_rtlabel_add(struct pf_addr_wrap *a) { #ifdef __FreeBSD__ /* XXX_IMPORT: later */ return (0); #else if (a->type == PF_ADDR_RTLABEL && (a->v.rtlabel = rtlabel_name2id(a->v.rtlabelname)) == 0) return (-1); return (0); #endif } void pf_rtlabel_remove(struct pf_addr_wrap *a) { #ifdef __FreeBSD__ /* XXX_IMPORT: later */ #else if (a->type == PF_ADDR_RTLABEL) rtlabel_unref(a->v.rtlabel); #endif } void pf_rtlabel_copyout(struct pf_addr_wrap *a) { #ifdef __FreeBSD__ /* XXX_IMPORT: later */ if (a->type == PF_ADDR_RTLABEL && a->v.rtlabel) strlcpy(a->v.rtlabelname, "?", sizeof(a->v.rtlabelname)); #else const char *name; if (a->type == PF_ADDR_RTLABEL && a->v.rtlabel) { if ((name = rtlabel_id2name(a->v.rtlabel)) == NULL) strlcpy(a->v.rtlabelname, "?", sizeof(a->v.rtlabelname)); else strlcpy(a->v.rtlabelname, name, sizeof(a->v.rtlabelname)); } #endif } #ifdef ALTQ u_int32_t pf_qname2qid(char *qname) { #ifdef __FreeBSD__ return ((u_int32_t)tagname2tag(&V_pf_qids, qname)); #else return ((u_int32_t)tagname2tag(&pf_qids, qname)); #endif } void pf_qid2qname(u_int32_t qid, char *p) { #ifdef __FreeBSD__ tag2tagname(&V_pf_qids, (u_int16_t)qid, p); #else tag2tagname(&pf_qids, (u_int16_t)qid, p); #endif } void pf_qid_unref(u_int32_t qid) { #ifdef __FreeBSD__ tag_unref(&V_pf_qids, (u_int16_t)qid); #else tag_unref(&pf_qids, (u_int16_t)qid); #endif } int pf_begin_altq(u_int32_t *ticket) { struct pf_altq *altq; int error = 0; /* Purge the old altq list */ #ifdef __FreeBSD__ while ((altq = TAILQ_FIRST(V_pf_altqs_inactive)) != NULL) { TAILQ_REMOVE(V_pf_altqs_inactive, altq, entries); if (altq->qname[0] == 0 && (altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { #else while ((altq = TAILQ_FIRST(pf_altqs_inactive)) != NULL) { TAILQ_REMOVE(pf_altqs_inactive, altq, entries); if (altq->qname[0] == 0) { #endif /* detach and destroy the discipline */ error = altq_remove(altq); } else pf_qid_unref(altq->qid); #ifdef __FreeBSD__ pool_put(&V_pf_altq_pl, altq); #else pool_put(&pf_altq_pl, altq); #endif } if (error) return (error); #ifdef __FreeBSD__ *ticket = ++V_ticket_altqs_inactive; V_altqs_inactive_open = 1; #else *ticket = ++ticket_altqs_inactive; altqs_inactive_open = 1; #endif return (0); } int pf_rollback_altq(u_int32_t ticket) { struct pf_altq *altq; int error = 0; #ifdef __FreeBSD__ if (!V_altqs_inactive_open || ticket != V_ticket_altqs_inactive) return (0); /* Purge the old altq list */ while ((altq = TAILQ_FIRST(V_pf_altqs_inactive)) != NULL) { TAILQ_REMOVE(V_pf_altqs_inactive, altq, entries); if (altq->qname[0] == 0 && (altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { #else if (!altqs_inactive_open || ticket != ticket_altqs_inactive) return (0); /* Purge the old altq list */ while ((altq = TAILQ_FIRST(pf_altqs_inactive)) != NULL) { TAILQ_REMOVE(pf_altqs_inactive, altq, entries); if (altq->qname[0] == 0) { #endif /* detach and destroy the discipline */ error = altq_remove(altq); } else pf_qid_unref(altq->qid); #ifdef __FreeBSD__ pool_put(&V_pf_altq_pl, altq); #else pool_put(&pf_altq_pl, altq); #endif } #ifdef __FreeBSD__ V_altqs_inactive_open = 0; #else altqs_inactive_open = 0; #endif return (error); } int pf_commit_altq(u_int32_t ticket) { struct pf_altqqueue *old_altqs; struct pf_altq *altq; int s, err, error = 0; #ifdef __FreeBSD__ if (!V_altqs_inactive_open || ticket != V_ticket_altqs_inactive) #else if (!altqs_inactive_open || ticket != ticket_altqs_inactive) #endif return (EBUSY); /* swap altqs, keep the old. */ s = splsoftnet(); #ifdef __FreeBSD__ old_altqs = V_pf_altqs_active; V_pf_altqs_active = V_pf_altqs_inactive; V_pf_altqs_inactive = old_altqs; V_ticket_altqs_active = V_ticket_altqs_inactive; #else old_altqs = pf_altqs_active; pf_altqs_active = pf_altqs_inactive; pf_altqs_inactive = old_altqs; ticket_altqs_active = ticket_altqs_inactive; #endif /* Attach new disciplines */ #ifdef __FreeBSD__ TAILQ_FOREACH(altq, V_pf_altqs_active, entries) { if (altq->qname[0] == 0 && (altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { #else TAILQ_FOREACH(altq, pf_altqs_active, entries) { if (altq->qname[0] == 0) { #endif /* attach the discipline */ error = altq_pfattach(altq); #ifdef __FreeBSD__ if (error == 0 && V_pf_altq_running) #else if (error == 0 && pf_altq_running) #endif error = pf_enable_altq(altq); if (error != 0) { splx(s); return (error); } } } /* Purge the old altq list */ #ifdef __FreeBSD__ while ((altq = TAILQ_FIRST(V_pf_altqs_inactive)) != NULL) { TAILQ_REMOVE(V_pf_altqs_inactive, altq, entries); if (altq->qname[0] == 0 && (altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { #else while ((altq = TAILQ_FIRST(pf_altqs_inactive)) != NULL) { TAILQ_REMOVE(pf_altqs_inactive, altq, entries); if (altq->qname[0] == 0) { #endif /* detach and destroy the discipline */ #ifdef __FreeBSD__ if (V_pf_altq_running) #else if (pf_altq_running) #endif error = pf_disable_altq(altq); err = altq_pfdetach(altq); if (err != 0 && error == 0) error = err; err = altq_remove(altq); if (err != 0 && error == 0) error = err; } else pf_qid_unref(altq->qid); #ifdef __FreeBSD__ pool_put(&V_pf_altq_pl, altq); #else pool_put(&pf_altq_pl, altq); #endif } splx(s); #ifdef __FreeBSD__ V_altqs_inactive_open = 0; #else altqs_inactive_open = 0; #endif return (error); } int pf_enable_altq(struct pf_altq *altq) { struct ifnet *ifp; struct tb_profile tb; int s, error = 0; if ((ifp = ifunit(altq->ifname)) == NULL) return (EINVAL); if (ifp->if_snd.altq_type != ALTQT_NONE) error = altq_enable(&ifp->if_snd); /* set tokenbucket regulator */ if (error == 0 && ifp != NULL && ALTQ_IS_ENABLED(&ifp->if_snd)) { tb.rate = altq->ifbandwidth; tb.depth = altq->tbrsize; s = splnet(); #ifdef __FreeBSD__ PF_UNLOCK(); #endif error = tbr_set(&ifp->if_snd, &tb); #ifdef __FreeBSD__ PF_LOCK(); #endif splx(s); } return (error); } int pf_disable_altq(struct pf_altq *altq) { struct ifnet *ifp; struct tb_profile tb; int s, error; if ((ifp = ifunit(altq->ifname)) == NULL) return (EINVAL); /* * when the discipline is no longer referenced, it was overridden * by a new one. if so, just return. */ if (altq->altq_disc != ifp->if_snd.altq_disc) return (0); error = altq_disable(&ifp->if_snd); if (error == 0) { /* clear tokenbucket regulator */ tb.rate = 0; s = splnet(); #ifdef __FreeBSD__ PF_UNLOCK(); #endif error = tbr_set(&ifp->if_snd, &tb); #ifdef __FreeBSD__ PF_LOCK(); #endif splx(s); } return (error); } #ifdef __FreeBSD__ void pf_altq_ifnet_event(struct ifnet *ifp, int remove) { struct ifnet *ifp1; struct pf_altq *a1, *a2, *a3; u_int32_t ticket; int error = 0; /* Interrupt userland queue modifications */ #ifdef __FreeBSD__ if (V_altqs_inactive_open) pf_rollback_altq(V_ticket_altqs_inactive); #else if (altqs_inactive_open) pf_rollback_altq(ticket_altqs_inactive); #endif /* Start new altq ruleset */ if (pf_begin_altq(&ticket)) return; /* Copy the current active set */ #ifdef __FreeBSD__ TAILQ_FOREACH(a1, V_pf_altqs_active, entries) { a2 = pool_get(&V_pf_altq_pl, PR_NOWAIT); #else TAILQ_FOREACH(a1, pf_altqs_active, entries) { a2 = pool_get(&pf_altq_pl, PR_NOWAIT); #endif if (a2 == NULL) { error = ENOMEM; break; } bcopy(a1, a2, sizeof(struct pf_altq)); if (a2->qname[0] != 0) { if ((a2->qid = pf_qname2qid(a2->qname)) == 0) { error = EBUSY; #ifdef __FreeBSD__ pool_put(&V_pf_altq_pl, a2); #else pool_put(&pf_altq_pl, a2); #endif break; } a2->altq_disc = NULL; #ifdef __FreeBSD__ TAILQ_FOREACH(a3, V_pf_altqs_inactive, entries) { #else TAILQ_FOREACH(a3, pf_altqs_inactive, entries) { #endif if (strncmp(a3->ifname, a2->ifname, IFNAMSIZ) == 0 && a3->qname[0] == 0) { a2->altq_disc = a3->altq_disc; break; } } } /* Deactivate the interface in question */ a2->local_flags &= ~PFALTQ_FLAG_IF_REMOVED; if ((ifp1 = ifunit(a2->ifname)) == NULL || (remove && ifp1 == ifp)) { a2->local_flags |= PFALTQ_FLAG_IF_REMOVED; } else { PF_UNLOCK(); error = altq_add(a2); PF_LOCK(); #ifdef __FreeBSD__ if (ticket != V_ticket_altqs_inactive) #else if (ticket != ticket_altqs_inactive) #endif error = EBUSY; if (error) { #ifdef __FreeBSD__ pool_put(&V_pf_altq_pl, a2); #else pool_put(&pf_altq_pl, a2); #endif break; } } #ifdef __FreeBSD__ TAILQ_INSERT_TAIL(V_pf_altqs_inactive, a2, entries); #else TAILQ_INSERT_TAIL(pf_altqs_inactive, a2, entries); #endif } if (error != 0) pf_rollback_altq(ticket); else pf_commit_altq(ticket); } #endif #endif /* ALTQ */ int pf_begin_rules(u_int32_t *ticket, int rs_num, const char *anchor) { struct pf_ruleset *rs; struct pf_rule *rule; if (rs_num < 0 || rs_num >= PF_RULESET_MAX) return (EINVAL); rs = pf_find_or_create_ruleset(anchor); if (rs == NULL) return (EINVAL); while ((rule = TAILQ_FIRST(rs->rules[rs_num].inactive.ptr)) != NULL) { pf_rm_rule(rs->rules[rs_num].inactive.ptr, rule); rs->rules[rs_num].inactive.rcount--; } *ticket = ++rs->rules[rs_num].inactive.ticket; rs->rules[rs_num].inactive.open = 1; return (0); } int pf_rollback_rules(u_int32_t ticket, int rs_num, char *anchor) { struct pf_ruleset *rs; struct pf_rule *rule; if (rs_num < 0 || rs_num >= PF_RULESET_MAX) return (EINVAL); rs = pf_find_ruleset(anchor); if (rs == NULL || !rs->rules[rs_num].inactive.open || rs->rules[rs_num].inactive.ticket != ticket) return (0); while ((rule = TAILQ_FIRST(rs->rules[rs_num].inactive.ptr)) != NULL) { pf_rm_rule(rs->rules[rs_num].inactive.ptr, rule); rs->rules[rs_num].inactive.rcount--; } rs->rules[rs_num].inactive.open = 0; return (0); } #define PF_MD5_UPD(st, elm) \ MD5Update(ctx, (u_int8_t *) &(st)->elm, sizeof((st)->elm)) #define PF_MD5_UPD_STR(st, elm) \ MD5Update(ctx, (u_int8_t *) (st)->elm, strlen((st)->elm)) #define PF_MD5_UPD_HTONL(st, elm, stor) do { \ (stor) = htonl((st)->elm); \ MD5Update(ctx, (u_int8_t *) &(stor), sizeof(u_int32_t));\ } while (0) #define PF_MD5_UPD_HTONS(st, elm, stor) do { \ (stor) = htons((st)->elm); \ MD5Update(ctx, (u_int8_t *) &(stor), sizeof(u_int16_t));\ } while (0) void pf_hash_rule_addr(MD5_CTX *ctx, struct pf_rule_addr *pfr) { PF_MD5_UPD(pfr, addr.type); switch (pfr->addr.type) { case PF_ADDR_DYNIFTL: PF_MD5_UPD(pfr, addr.v.ifname); PF_MD5_UPD(pfr, addr.iflags); break; case PF_ADDR_TABLE: PF_MD5_UPD(pfr, addr.v.tblname); break; case PF_ADDR_ADDRMASK: /* XXX ignore af? */ PF_MD5_UPD(pfr, addr.v.a.addr.addr32); PF_MD5_UPD(pfr, addr.v.a.mask.addr32); break; case PF_ADDR_RTLABEL: PF_MD5_UPD(pfr, addr.v.rtlabelname); break; } PF_MD5_UPD(pfr, port[0]); PF_MD5_UPD(pfr, port[1]); PF_MD5_UPD(pfr, neg); PF_MD5_UPD(pfr, port_op); } void pf_hash_rule(MD5_CTX *ctx, struct pf_rule *rule) { u_int16_t x; u_int32_t y; pf_hash_rule_addr(ctx, &rule->src); pf_hash_rule_addr(ctx, &rule->dst); PF_MD5_UPD_STR(rule, label); PF_MD5_UPD_STR(rule, ifname); PF_MD5_UPD_STR(rule, match_tagname); PF_MD5_UPD_HTONS(rule, match_tag, x); /* dup? */ PF_MD5_UPD_HTONL(rule, os_fingerprint, y); PF_MD5_UPD_HTONL(rule, prob, y); PF_MD5_UPD_HTONL(rule, uid.uid[0], y); PF_MD5_UPD_HTONL(rule, uid.uid[1], y); PF_MD5_UPD(rule, uid.op); PF_MD5_UPD_HTONL(rule, gid.gid[0], y); PF_MD5_UPD_HTONL(rule, gid.gid[1], y); PF_MD5_UPD(rule, gid.op); PF_MD5_UPD_HTONL(rule, rule_flag, y); PF_MD5_UPD(rule, action); PF_MD5_UPD(rule, direction); PF_MD5_UPD(rule, af); PF_MD5_UPD(rule, quick); PF_MD5_UPD(rule, ifnot); PF_MD5_UPD(rule, match_tag_not); PF_MD5_UPD(rule, natpass); PF_MD5_UPD(rule, keep_state); PF_MD5_UPD(rule, proto); PF_MD5_UPD(rule, type); PF_MD5_UPD(rule, code); PF_MD5_UPD(rule, flags); PF_MD5_UPD(rule, flagset); PF_MD5_UPD(rule, allow_opts); PF_MD5_UPD(rule, rt); PF_MD5_UPD(rule, tos); } int pf_commit_rules(u_int32_t ticket, int rs_num, char *anchor) { struct pf_ruleset *rs; struct pf_rule *rule, **old_array; struct pf_rulequeue *old_rules; int s, error; u_int32_t old_rcount; if (rs_num < 0 || rs_num >= PF_RULESET_MAX) return (EINVAL); rs = pf_find_ruleset(anchor); if (rs == NULL || !rs->rules[rs_num].inactive.open || ticket != rs->rules[rs_num].inactive.ticket) return (EBUSY); /* Calculate checksum for the main ruleset */ if (rs == &pf_main_ruleset) { error = pf_setup_pfsync_matching(rs); if (error != 0) return (error); } /* Swap rules, keep the old. */ s = splsoftnet(); old_rules = rs->rules[rs_num].active.ptr; old_rcount = rs->rules[rs_num].active.rcount; old_array = rs->rules[rs_num].active.ptr_array; rs->rules[rs_num].active.ptr = rs->rules[rs_num].inactive.ptr; rs->rules[rs_num].active.ptr_array = rs->rules[rs_num].inactive.ptr_array; rs->rules[rs_num].active.rcount = rs->rules[rs_num].inactive.rcount; rs->rules[rs_num].inactive.ptr = old_rules; rs->rules[rs_num].inactive.ptr_array = old_array; rs->rules[rs_num].inactive.rcount = old_rcount; rs->rules[rs_num].active.ticket = rs->rules[rs_num].inactive.ticket; pf_calc_skip_steps(rs->rules[rs_num].active.ptr); /* Purge the old rule list. */ while ((rule = TAILQ_FIRST(old_rules)) != NULL) pf_rm_rule(old_rules, rule); if (rs->rules[rs_num].inactive.ptr_array) free(rs->rules[rs_num].inactive.ptr_array, M_TEMP); rs->rules[rs_num].inactive.ptr_array = NULL; rs->rules[rs_num].inactive.rcount = 0; rs->rules[rs_num].inactive.open = 0; pf_remove_if_empty_ruleset(rs); splx(s); return (0); } int pf_setup_pfsync_matching(struct pf_ruleset *rs) { MD5_CTX ctx; struct pf_rule *rule; int rs_cnt; u_int8_t digest[PF_MD5_DIGEST_LENGTH]; MD5Init(&ctx); for (rs_cnt = 0; rs_cnt < PF_RULESET_MAX; rs_cnt++) { /* XXX PF_RULESET_SCRUB as well? */ if (rs_cnt == PF_RULESET_SCRUB) continue; if (rs->rules[rs_cnt].inactive.ptr_array) free(rs->rules[rs_cnt].inactive.ptr_array, M_TEMP); rs->rules[rs_cnt].inactive.ptr_array = NULL; if (rs->rules[rs_cnt].inactive.rcount) { rs->rules[rs_cnt].inactive.ptr_array = malloc(sizeof(caddr_t) * rs->rules[rs_cnt].inactive.rcount, M_TEMP, M_NOWAIT); if (!rs->rules[rs_cnt].inactive.ptr_array) return (ENOMEM); } TAILQ_FOREACH(rule, rs->rules[rs_cnt].inactive.ptr, entries) { pf_hash_rule(&ctx, rule); (rs->rules[rs_cnt].inactive.ptr_array)[rule->nr] = rule; } } MD5Final(digest, &ctx); #ifdef __FreeBSD__ memcpy(V_pf_status.pf_chksum, digest, sizeof(V_pf_status.pf_chksum)); #else memcpy(pf_status.pf_chksum, digest, sizeof(pf_status.pf_chksum)); #endif return (0); } int pf_addr_setup(struct pf_ruleset *ruleset, struct pf_addr_wrap *addr, sa_family_t af) { if (pfi_dynaddr_setup(addr, af) || pf_tbladdr_setup(ruleset, addr)) return (EINVAL); return (0); } void pf_addr_copyout(struct pf_addr_wrap *addr) { pfi_dynaddr_copyout(addr); pf_tbladdr_copyout(addr); pf_rtlabel_copyout(addr); } int #ifdef __FreeBSD__ pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td) #else pfioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p) #endif { struct pf_pooladdr *pa = NULL; struct pf_pool *pool = NULL; #ifndef __FreeBSD__ int s; #endif int error = 0; CURVNET_SET(TD_TO_VNET(td)); /* XXX keep in sync with switch() below */ #ifdef __FreeBSD__ if (securelevel_gt(td->td_ucred, 2)) #else if (securelevel > 1) #endif switch (cmd) { case DIOCGETRULES: case DIOCGETRULE: case DIOCGETADDRS: case DIOCGETADDR: case DIOCGETSTATE: case DIOCSETSTATUSIF: case DIOCGETSTATUS: case DIOCCLRSTATUS: case DIOCNATLOOK: case DIOCSETDEBUG: case DIOCGETSTATES: case DIOCGETTIMEOUT: case DIOCCLRRULECTRS: case DIOCGETLIMIT: case DIOCGETALTQS: case DIOCGETALTQ: case DIOCGETQSTATS: case DIOCGETRULESETS: case DIOCGETRULESET: case DIOCRGETTABLES: case DIOCRGETTSTATS: case DIOCRCLRTSTATS: case DIOCRCLRADDRS: case DIOCRADDADDRS: case DIOCRDELADDRS: case DIOCRSETADDRS: case DIOCRGETADDRS: case DIOCRGETASTATS: case DIOCRCLRASTATS: case DIOCRTSTADDRS: case DIOCOSFPGET: case DIOCGETSRCNODES: case DIOCCLRSRCNODES: case DIOCIGETIFACES: #ifdef __FreeBSD__ case DIOCGIFSPEED: #endif case DIOCSETIFFLAG: case DIOCCLRIFFLAG: break; case DIOCRCLRTABLES: case DIOCRADDTABLES: case DIOCRDELTABLES: case DIOCRSETTFLAGS: if (((struct pfioc_table *)addr)->pfrio_flags & PFR_FLAG_DUMMY) break; /* dummy operation ok */ return (EPERM); default: return (EPERM); } if (!(flags & FWRITE)) switch (cmd) { case DIOCGETRULES: case DIOCGETADDRS: case DIOCGETADDR: case DIOCGETSTATE: case DIOCGETSTATUS: case DIOCGETSTATES: case DIOCGETTIMEOUT: case DIOCGETLIMIT: case DIOCGETALTQS: case DIOCGETALTQ: case DIOCGETQSTATS: case DIOCGETRULESETS: case DIOCGETRULESET: case DIOCNATLOOK: case DIOCRGETTABLES: case DIOCRGETTSTATS: case DIOCRGETADDRS: case DIOCRGETASTATS: case DIOCRTSTADDRS: case DIOCOSFPGET: case DIOCGETSRCNODES: case DIOCIGETIFACES: #ifdef __FreeBSD__ case DIOCGIFSPEED: #endif break; case DIOCRCLRTABLES: case DIOCRADDTABLES: case DIOCRDELTABLES: case DIOCRCLRTSTATS: case DIOCRCLRADDRS: case DIOCRADDADDRS: case DIOCRDELADDRS: case DIOCRSETADDRS: case DIOCRSETTFLAGS: if (((struct pfioc_table *)addr)->pfrio_flags & PFR_FLAG_DUMMY) { flags |= FWRITE; /* need write lock for dummy */ break; /* dummy operation ok */ } return (EACCES); case DIOCGETRULE: if (((struct pfioc_rule *)addr)->action == PF_GET_CLR_CNTR) return (EACCES); break; default: return (EACCES); } if (flags & FWRITE) #ifdef __FreeBSD__ sx_xlock(&V_pf_consistency_lock); else sx_slock(&V_pf_consistency_lock); #else rw_enter_write(&pf_consistency_lock); else rw_enter_read(&pf_consistency_lock); #endif #ifdef __FreeBSD__ PF_LOCK(); #else s = splsoftnet(); #endif switch (cmd) { case DIOCSTART: #ifdef __FreeBSD__ if (V_pf_status.running) #else if (pf_status.running) #endif error = EEXIST; else { #ifdef __FreeBSD__ PF_UNLOCK(); error = hook_pf(); PF_LOCK(); if (error) { DPFPRINTF(PF_DEBUG_MISC, ("pf: pfil registeration fail\n")); break; } V_pf_status.running = 1; V_pf_status.since = time_second; if (V_pf_status.stateid == 0) { V_pf_status.stateid = time_second; V_pf_status.stateid = V_pf_status.stateid << 32; } #else pf_status.running = 1; pf_status.since = time_second; if (pf_status.stateid == 0) { pf_status.stateid = time_second; pf_status.stateid = pf_status.stateid << 32; } #endif DPFPRINTF(PF_DEBUG_MISC, ("pf: started\n")); } break; case DIOCSTOP: #ifdef __FreeBSD__ if (!V_pf_status.running) error = ENOENT; else { V_pf_status.running = 0; PF_UNLOCK(); error = dehook_pf(); PF_LOCK(); if (error) { V_pf_status.running = 1; DPFPRINTF(PF_DEBUG_MISC, ("pf: pfil unregisteration failed\n")); } V_pf_status.since = time_second; #else if (!pf_status.running) error = ENOENT; else { pf_status.running = 0; pf_status.since = time_second; #endif DPFPRINTF(PF_DEBUG_MISC, ("pf: stopped\n")); } break; case DIOCADDRULE: { struct pfioc_rule *pr = (struct pfioc_rule *)addr; struct pf_ruleset *ruleset; struct pf_rule *rule, *tail; struct pf_pooladdr *pa; int rs_num; pr->anchor[sizeof(pr->anchor) - 1] = 0; ruleset = pf_find_ruleset(pr->anchor); if (ruleset == NULL) { error = EINVAL; break; } rs_num = pf_get_ruleset_number(pr->rule.action); if (rs_num >= PF_RULESET_MAX) { error = EINVAL; break; } if (pr->rule.return_icmp >> 8 > ICMP_MAXTYPE) { error = EINVAL; break; } if (pr->ticket != ruleset->rules[rs_num].inactive.ticket) { #ifdef __FreeBSD__ DPFPRINTF(PF_DEBUG_MISC, ("ticket: %d != [%d]%d\n", pr->ticket, rs_num, ruleset->rules[rs_num].inactive.ticket)); #endif error = EBUSY; break; } #ifdef __FreeBSD__ if (pr->pool_ticket != V_ticket_pabuf) { DPFPRINTF(PF_DEBUG_MISC, ("pool_ticket: %d != %d\n", pr->pool_ticket, V_ticket_pabuf)); #else if (pr->pool_ticket != ticket_pabuf) { #endif error = EBUSY; break; } #ifdef __FreeBSD__ rule = pool_get(&V_pf_rule_pl, PR_NOWAIT); #else rule = pool_get(&pf_rule_pl, PR_WAITOK|PR_LIMITFAIL); #endif if (rule == NULL) { error = ENOMEM; break; } bcopy(&pr->rule, rule, sizeof(struct pf_rule)); #ifdef __FreeBSD__ rule->cuid = td->td_ucred->cr_ruid; rule->cpid = td->td_proc ? td->td_proc->p_pid : 0; #else rule->cuid = p->p_cred->p_ruid; rule->cpid = p->p_pid; #endif rule->anchor = NULL; rule->kif = NULL; TAILQ_INIT(&rule->rpool.list); /* initialize refcounting */ rule->states_cur = 0; rule->src_nodes = 0; rule->entries.tqe_prev = NULL; #ifndef INET if (rule->af == AF_INET) { #ifdef __FreeBSD__ pool_put(&V_pf_rule_pl, rule); #else pool_put(&pf_rule_pl, rule); #endif error = EAFNOSUPPORT; break; } #endif /* INET */ #ifndef INET6 if (rule->af == AF_INET6) { #ifdef __FreeBSD__ pool_put(&V_pf_rule_pl, rule); #else pool_put(&pf_rule_pl, rule); #endif error = EAFNOSUPPORT; break; } #endif /* INET6 */ tail = TAILQ_LAST(ruleset->rules[rs_num].inactive.ptr, pf_rulequeue); if (tail) rule->nr = tail->nr + 1; else rule->nr = 0; if (rule->ifname[0]) { rule->kif = pfi_kif_get(rule->ifname); if (rule->kif == NULL) { #ifdef __FreeBSD__ pool_put(&V_pf_rule_pl, rule); #else pool_put(&pf_rule_pl, rule); #endif error = EINVAL; break; } pfi_kif_ref(rule->kif, PFI_KIF_REF_RULE); } #ifdef __FreeBSD__ /* ROUTING */ if (rule->rtableid > 0 && rule->rtableid >= rt_numfibs) #else if (rule->rtableid > 0 && !rtable_exists(rule->rtableid)) #endif error = EBUSY; #ifdef ALTQ /* set queue IDs */ if (rule->qname[0] != 0) { if ((rule->qid = pf_qname2qid(rule->qname)) == 0) error = EBUSY; else if (rule->pqname[0] != 0) { if ((rule->pqid = pf_qname2qid(rule->pqname)) == 0) error = EBUSY; } else rule->pqid = rule->qid; } #endif if (rule->tagname[0]) if ((rule->tag = pf_tagname2tag(rule->tagname)) == 0) error = EBUSY; if (rule->match_tagname[0]) if ((rule->match_tag = pf_tagname2tag(rule->match_tagname)) == 0) error = EBUSY; if (rule->rt && !rule->direction) error = EINVAL; #if NPFLOG > 0 if (!rule->log) rule->logif = 0; if (rule->logif >= PFLOGIFS_MAX) error = EINVAL; #endif if (pf_rtlabel_add(&rule->src.addr) || pf_rtlabel_add(&rule->dst.addr)) error = EBUSY; if (pf_addr_setup(ruleset, &rule->src.addr, rule->af)) error = EINVAL; if (pf_addr_setup(ruleset, &rule->dst.addr, rule->af)) error = EINVAL; if (pf_anchor_setup(rule, ruleset, pr->anchor_call)) error = EINVAL; #ifdef __FreeBSD__ TAILQ_FOREACH(pa, &V_pf_pabuf, entries) #else TAILQ_FOREACH(pa, &pf_pabuf, entries) #endif if (pf_tbladdr_setup(ruleset, &pa->addr)) error = EINVAL; if (rule->overload_tblname[0]) { if ((rule->overload_tbl = pfr_attach_table(ruleset, rule->overload_tblname, 0)) == NULL) error = EINVAL; else rule->overload_tbl->pfrkt_flags |= PFR_TFLAG_ACTIVE; } #ifdef __FreeBSD__ pf_mv_pool(&V_pf_pabuf, &rule->rpool.list); #else pf_mv_pool(&pf_pabuf, &rule->rpool.list); #endif if (((((rule->action == PF_NAT) || (rule->action == PF_RDR) || (rule->action == PF_BINAT)) && rule->anchor == NULL) || (rule->rt > PF_FASTROUTE)) && (TAILQ_FIRST(&rule->rpool.list) == NULL)) error = EINVAL; if (error) { pf_rm_rule(NULL, rule); break; } #ifdef __FreeBSD__ if (!V_debug_pfugidhack && (rule->uid.op || rule->gid.op || rule->log & PF_LOG_SOCKET_LOOKUP)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: debug.pfugidhack enabled\n")); V_debug_pfugidhack = 1; } #endif rule->rpool.cur = TAILQ_FIRST(&rule->rpool.list); rule->evaluations = rule->packets[0] = rule->packets[1] = rule->bytes[0] = rule->bytes[1] = 0; TAILQ_INSERT_TAIL(ruleset->rules[rs_num].inactive.ptr, rule, entries); ruleset->rules[rs_num].inactive.rcount++; break; } case DIOCGETRULES: { struct pfioc_rule *pr = (struct pfioc_rule *)addr; struct pf_ruleset *ruleset; struct pf_rule *tail; int rs_num; pr->anchor[sizeof(pr->anchor) - 1] = 0; ruleset = pf_find_ruleset(pr->anchor); if (ruleset == NULL) { error = EINVAL; break; } rs_num = pf_get_ruleset_number(pr->rule.action); if (rs_num >= PF_RULESET_MAX) { error = EINVAL; break; } tail = TAILQ_LAST(ruleset->rules[rs_num].active.ptr, pf_rulequeue); if (tail) pr->nr = tail->nr + 1; else pr->nr = 0; pr->ticket = ruleset->rules[rs_num].active.ticket; break; } case DIOCGETRULE: { struct pfioc_rule *pr = (struct pfioc_rule *)addr; struct pf_ruleset *ruleset; struct pf_rule *rule; int rs_num, i; pr->anchor[sizeof(pr->anchor) - 1] = 0; ruleset = pf_find_ruleset(pr->anchor); if (ruleset == NULL) { error = EINVAL; break; } rs_num = pf_get_ruleset_number(pr->rule.action); if (rs_num >= PF_RULESET_MAX) { error = EINVAL; break; } if (pr->ticket != ruleset->rules[rs_num].active.ticket) { error = EBUSY; break; } rule = TAILQ_FIRST(ruleset->rules[rs_num].active.ptr); while ((rule != NULL) && (rule->nr != pr->nr)) rule = TAILQ_NEXT(rule, entries); if (rule == NULL) { error = EBUSY; break; } bcopy(rule, &pr->rule, sizeof(struct pf_rule)); if (pf_anchor_copyout(ruleset, rule, pr)) { error = EBUSY; break; } pf_addr_copyout(&pr->rule.src.addr); pf_addr_copyout(&pr->rule.dst.addr); for (i = 0; i < PF_SKIP_COUNT; ++i) if (rule->skip[i].ptr == NULL) pr->rule.skip[i].nr = -1; else pr->rule.skip[i].nr = rule->skip[i].ptr->nr; if (pr->action == PF_GET_CLR_CNTR) { rule->evaluations = 0; rule->packets[0] = rule->packets[1] = 0; rule->bytes[0] = rule->bytes[1] = 0; rule->states_tot = 0; } break; } case DIOCCHANGERULE: { struct pfioc_rule *pcr = (struct pfioc_rule *)addr; struct pf_ruleset *ruleset; struct pf_rule *oldrule = NULL, *newrule = NULL; u_int32_t nr = 0; int rs_num; if (!(pcr->action == PF_CHANGE_REMOVE || pcr->action == PF_CHANGE_GET_TICKET) && #ifdef __FreeBSD__ pcr->pool_ticket != V_ticket_pabuf) { #else pcr->pool_ticket != ticket_pabuf) { #endif error = EBUSY; break; } if (pcr->action < PF_CHANGE_ADD_HEAD || pcr->action > PF_CHANGE_GET_TICKET) { error = EINVAL; break; } ruleset = pf_find_ruleset(pcr->anchor); if (ruleset == NULL) { error = EINVAL; break; } rs_num = pf_get_ruleset_number(pcr->rule.action); if (rs_num >= PF_RULESET_MAX) { error = EINVAL; break; } if (pcr->action == PF_CHANGE_GET_TICKET) { pcr->ticket = ++ruleset->rules[rs_num].active.ticket; break; } else { if (pcr->ticket != ruleset->rules[rs_num].active.ticket) { error = EINVAL; break; } if (pcr->rule.return_icmp >> 8 > ICMP_MAXTYPE) { error = EINVAL; break; } } if (pcr->action != PF_CHANGE_REMOVE) { #ifdef __FreeBSD__ newrule = pool_get(&V_pf_rule_pl, PR_NOWAIT); #else newrule = pool_get(&pf_rule_pl, PR_WAITOK|PR_LIMITFAIL); #endif if (newrule == NULL) { error = ENOMEM; break; } bcopy(&pcr->rule, newrule, sizeof(struct pf_rule)); #ifdef __FreeBSD__ newrule->cuid = td->td_ucred->cr_ruid; newrule->cpid = td->td_proc ? td->td_proc->p_pid : 0; #else newrule->cuid = p->p_cred->p_ruid; newrule->cpid = p->p_pid; #endif TAILQ_INIT(&newrule->rpool.list); /* initialize refcounting */ newrule->states_cur = 0; newrule->entries.tqe_prev = NULL; #ifndef INET if (newrule->af == AF_INET) { #ifdef __FreeBSD__ pool_put(&V_pf_rule_pl, newrule); #else pool_put(&pf_rule_pl, newrule); #endif error = EAFNOSUPPORT; break; } #endif /* INET */ #ifndef INET6 if (newrule->af == AF_INET6) { #ifdef __FreeBSD__ pool_put(&V_pf_rule_pl, newrule); #else pool_put(&pf_rule_pl, newrule); #endif error = EAFNOSUPPORT; break; } #endif /* INET6 */ if (newrule->ifname[0]) { newrule->kif = pfi_kif_get(newrule->ifname); if (newrule->kif == NULL) { #ifdef __FreeBSD__ pool_put(&V_pf_rule_pl, newrule); #else pool_put(&pf_rule_pl, newrule); #endif error = EINVAL; break; } pfi_kif_ref(newrule->kif, PFI_KIF_REF_RULE); } else newrule->kif = NULL; if (newrule->rtableid > 0 && #ifdef __FreeBSD__ /* ROUTING */ newrule->rtableid >= rt_numfibs) #else !rtable_exists(newrule->rtableid)) #endif error = EBUSY; #ifdef ALTQ /* set queue IDs */ if (newrule->qname[0] != 0) { if ((newrule->qid = pf_qname2qid(newrule->qname)) == 0) error = EBUSY; else if (newrule->pqname[0] != 0) { if ((newrule->pqid = pf_qname2qid(newrule->pqname)) == 0) error = EBUSY; } else newrule->pqid = newrule->qid; } #endif /* ALTQ */ if (newrule->tagname[0]) if ((newrule->tag = pf_tagname2tag(newrule->tagname)) == 0) error = EBUSY; if (newrule->match_tagname[0]) if ((newrule->match_tag = pf_tagname2tag( newrule->match_tagname)) == 0) error = EBUSY; if (newrule->rt && !newrule->direction) error = EINVAL; #if NPFLOG > 0 if (!newrule->log) newrule->logif = 0; if (newrule->logif >= PFLOGIFS_MAX) error = EINVAL; #endif if (pf_rtlabel_add(&newrule->src.addr) || pf_rtlabel_add(&newrule->dst.addr)) error = EBUSY; if (pf_addr_setup(ruleset, &newrule->src.addr, newrule->af)) error = EINVAL; if (pf_addr_setup(ruleset, &newrule->dst.addr, newrule->af)) error = EINVAL; if (pf_anchor_setup(newrule, ruleset, pcr->anchor_call)) error = EINVAL; #ifdef __FreeBSD__ TAILQ_FOREACH(pa, &V_pf_pabuf, entries) #else TAILQ_FOREACH(pa, &pf_pabuf, entries) #endif if (pf_tbladdr_setup(ruleset, &pa->addr)) error = EINVAL; if (newrule->overload_tblname[0]) { if ((newrule->overload_tbl = pfr_attach_table( ruleset, newrule->overload_tblname, 0)) == NULL) error = EINVAL; else newrule->overload_tbl->pfrkt_flags |= PFR_TFLAG_ACTIVE; } #ifdef __FreeBSD__ pf_mv_pool(&V_pf_pabuf, &newrule->rpool.list); #else pf_mv_pool(&pf_pabuf, &newrule->rpool.list); #endif if (((((newrule->action == PF_NAT) || (newrule->action == PF_RDR) || (newrule->action == PF_BINAT) || (newrule->rt > PF_FASTROUTE)) && !newrule->anchor)) && (TAILQ_FIRST(&newrule->rpool.list) == NULL)) error = EINVAL; if (error) { pf_rm_rule(NULL, newrule); break; } #ifdef __FreeBSD__ if (!V_debug_pfugidhack && (newrule->uid.op || newrule->gid.op || newrule->log & PF_LOG_SOCKET_LOOKUP)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: debug.pfugidhack enabled\n")); V_debug_pfugidhack = 1; } #endif newrule->rpool.cur = TAILQ_FIRST(&newrule->rpool.list); newrule->evaluations = 0; newrule->packets[0] = newrule->packets[1] = 0; newrule->bytes[0] = newrule->bytes[1] = 0; } #ifdef __FreeBSD__ pf_empty_pool(&V_pf_pabuf); #else pf_empty_pool(&pf_pabuf); #endif if (pcr->action == PF_CHANGE_ADD_HEAD) oldrule = TAILQ_FIRST( ruleset->rules[rs_num].active.ptr); else if (pcr->action == PF_CHANGE_ADD_TAIL) oldrule = TAILQ_LAST( ruleset->rules[rs_num].active.ptr, pf_rulequeue); else { oldrule = TAILQ_FIRST( ruleset->rules[rs_num].active.ptr); while ((oldrule != NULL) && (oldrule->nr != pcr->nr)) oldrule = TAILQ_NEXT(oldrule, entries); if (oldrule == NULL) { if (newrule != NULL) pf_rm_rule(NULL, newrule); error = EINVAL; break; } } if (pcr->action == PF_CHANGE_REMOVE) { pf_rm_rule(ruleset->rules[rs_num].active.ptr, oldrule); ruleset->rules[rs_num].active.rcount--; } else { if (oldrule == NULL) TAILQ_INSERT_TAIL( ruleset->rules[rs_num].active.ptr, newrule, entries); else if (pcr->action == PF_CHANGE_ADD_HEAD || pcr->action == PF_CHANGE_ADD_BEFORE) TAILQ_INSERT_BEFORE(oldrule, newrule, entries); else TAILQ_INSERT_AFTER( ruleset->rules[rs_num].active.ptr, oldrule, newrule, entries); ruleset->rules[rs_num].active.rcount++; } nr = 0; TAILQ_FOREACH(oldrule, ruleset->rules[rs_num].active.ptr, entries) oldrule->nr = nr++; ruleset->rules[rs_num].active.ticket++; pf_calc_skip_steps(ruleset->rules[rs_num].active.ptr); pf_remove_if_empty_ruleset(ruleset); break; } case DIOCCLRSTATES: { struct pf_state *s, *nexts; struct pfioc_state_kill *psk = (struct pfioc_state_kill *)addr; u_int killed = 0; #ifdef __FreeBSD__ for (s = RB_MIN(pf_state_tree_id, &V_tree_id); s; s = nexts) { nexts = RB_NEXT(pf_state_tree_id, &V_tree_id, s); #else for (s = RB_MIN(pf_state_tree_id, &tree_id); s; s = nexts) { nexts = RB_NEXT(pf_state_tree_id, &tree_id, s); #endif if (!psk->psk_ifname[0] || !strcmp(psk->psk_ifname, s->kif->pfik_name)) { #if NPFSYNC > 0 /* don't send out individual delete messages */ SET(s->state_flags, PFSTATE_NOSYNC); #endif pf_unlink_state(s); killed++; } } psk->psk_killed = killed; #if NPFSYNC > 0 #ifdef __FreeBSD__ if (pfsync_clear_states_ptr != NULL) pfsync_clear_states_ptr(V_pf_status.hostid, psk->psk_ifname); #else pfsync_clear_states(pf_status.hostid, psk->psk_ifname); #endif #endif break; } case DIOCKILLSTATES: { struct pf_state *s, *nexts; struct pf_state_key *sk; struct pf_addr *srcaddr, *dstaddr; u_int16_t srcport, dstport; struct pfioc_state_kill *psk = (struct pfioc_state_kill *)addr; u_int killed = 0; if (psk->psk_pfcmp.id) { if (psk->psk_pfcmp.creatorid == 0) #ifdef __FreeBSD__ psk->psk_pfcmp.creatorid = V_pf_status.hostid; #else psk->psk_pfcmp.creatorid = pf_status.hostid; #endif if ((s = pf_find_state_byid(&psk->psk_pfcmp))) { pf_unlink_state(s); psk->psk_killed = 1; } break; } #ifdef __FreeBSD__ for (s = RB_MIN(pf_state_tree_id, &V_tree_id); s; s = nexts) { nexts = RB_NEXT(pf_state_tree_id, &V_tree_id, s); #else for (s = RB_MIN(pf_state_tree_id, &tree_id); s; s = nexts) { nexts = RB_NEXT(pf_state_tree_id, &tree_id, s); #endif sk = s->key[PF_SK_WIRE]; if (s->direction == PF_OUT) { srcaddr = &sk->addr[1]; dstaddr = &sk->addr[0]; srcport = sk->port[0]; dstport = sk->port[0]; } else { srcaddr = &sk->addr[0]; dstaddr = &sk->addr[1]; srcport = sk->port[0]; dstport = sk->port[0]; } if ((!psk->psk_af || sk->af == psk->psk_af) && (!psk->psk_proto || psk->psk_proto == sk->proto) && PF_MATCHA(psk->psk_src.neg, &psk->psk_src.addr.v.a.addr, &psk->psk_src.addr.v.a.mask, srcaddr, sk->af) && PF_MATCHA(psk->psk_dst.neg, &psk->psk_dst.addr.v.a.addr, &psk->psk_dst.addr.v.a.mask, dstaddr, sk->af) && (psk->psk_src.port_op == 0 || pf_match_port(psk->psk_src.port_op, psk->psk_src.port[0], psk->psk_src.port[1], srcport)) && (psk->psk_dst.port_op == 0 || pf_match_port(psk->psk_dst.port_op, psk->psk_dst.port[0], psk->psk_dst.port[1], dstport)) && (!psk->psk_label[0] || (s->rule.ptr->label[0] && !strcmp(psk->psk_label, s->rule.ptr->label))) && (!psk->psk_ifname[0] || !strcmp(psk->psk_ifname, s->kif->pfik_name))) { pf_unlink_state(s); killed++; } } psk->psk_killed = killed; break; } case DIOCADDSTATE: { struct pfioc_state *ps = (struct pfioc_state *)addr; struct pfsync_state *sp = &ps->state; if (sp->timeout >= PFTM_MAX && sp->timeout != PFTM_UNTIL_PACKET) { error = EINVAL; break; } #ifdef __FreeBSD__ if (pfsync_state_import_ptr != NULL) error = pfsync_state_import_ptr(sp, PFSYNC_SI_IOCTL); #else error = pfsync_state_import(sp, PFSYNC_SI_IOCTL); #endif break; } case DIOCGETSTATE: { struct pfioc_state *ps = (struct pfioc_state *)addr; struct pf_state *s; struct pf_state_cmp id_key; bcopy(ps->state.id, &id_key.id, sizeof(id_key.id)); id_key.creatorid = ps->state.creatorid; s = pf_find_state_byid(&id_key); if (s == NULL) { error = ENOENT; break; } pfsync_state_export(&ps->state, s); break; } case DIOCGETSTATES: { struct pfioc_states *ps = (struct pfioc_states *)addr; struct pf_state *state; struct pfsync_state *p, *pstore; u_int32_t nr = 0; if (ps->ps_len == 0) { #ifdef __FreeBSD__ nr = V_pf_status.states; #else nr = pf_status.states; #endif ps->ps_len = sizeof(struct pfsync_state) * nr; break; } #ifdef __FreeBSD__ PF_UNLOCK(); #endif pstore = malloc(sizeof(*pstore), M_TEMP, M_WAITOK); #ifdef __FreeBSD__ PF_LOCK(); #endif p = ps->ps_states; #ifdef __FreeBSD__ state = TAILQ_FIRST(&V_state_list); #else state = TAILQ_FIRST(&state_list); #endif while (state) { if (state->timeout != PFTM_UNLINKED) { if ((nr+1) * sizeof(*p) > (unsigned)ps->ps_len) break; pfsync_state_export(pstore, state); #ifdef __FreeBSD__ PF_COPYOUT(pstore, p, sizeof(*p), error); #else error = copyout(pstore, p, sizeof(*p)); #endif if (error) { free(pstore, M_TEMP); goto fail; } p++; nr++; } state = TAILQ_NEXT(state, entry_list); } ps->ps_len = sizeof(struct pfsync_state) * nr; free(pstore, M_TEMP); break; } case DIOCGETSTATUS: { struct pf_status *s = (struct pf_status *)addr; #ifdef __FreeBSD__ bcopy(&V_pf_status, s, sizeof(struct pf_status)); #else bcopy(&pf_status, s, sizeof(struct pf_status)); #endif pfi_update_status(s->ifname, s); break; } case DIOCSETSTATUSIF: { struct pfioc_if *pi = (struct pfioc_if *)addr; if (pi->ifname[0] == 0) { #ifdef __FreeBSD__ bzero(V_pf_status.ifname, IFNAMSIZ); #else bzero(pf_status.ifname, IFNAMSIZ); #endif break; } #ifdef __FreeBSD__ strlcpy(V_pf_status.ifname, pi->ifname, IFNAMSIZ); #else strlcpy(pf_status.ifname, pi->ifname, IFNAMSIZ); #endif break; } case DIOCCLRSTATUS: { #ifdef __FreeBSD__ bzero(V_pf_status.counters, sizeof(V_pf_status.counters)); bzero(V_pf_status.fcounters, sizeof(V_pf_status.fcounters)); bzero(V_pf_status.scounters, sizeof(V_pf_status.scounters)); V_pf_status.since = time_second; if (*V_pf_status.ifname) pfi_update_status(V_pf_status.ifname, NULL); #else bzero(pf_status.counters, sizeof(pf_status.counters)); bzero(pf_status.fcounters, sizeof(pf_status.fcounters)); bzero(pf_status.scounters, sizeof(pf_status.scounters)); pf_status.since = time_second; if (*pf_status.ifname) pfi_update_status(pf_status.ifname, NULL); #endif break; } case DIOCNATLOOK: { struct pfioc_natlook *pnl = (struct pfioc_natlook *)addr; struct pf_state_key *sk; struct pf_state *state; struct pf_state_key_cmp key; int m = 0, direction = pnl->direction; int sidx, didx; /* NATLOOK src and dst are reversed, so reverse sidx/didx */ sidx = (direction == PF_IN) ? 1 : 0; didx = (direction == PF_IN) ? 0 : 1; if (!pnl->proto || PF_AZERO(&pnl->saddr, pnl->af) || PF_AZERO(&pnl->daddr, pnl->af) || ((pnl->proto == IPPROTO_TCP || pnl->proto == IPPROTO_UDP) && (!pnl->dport || !pnl->sport))) error = EINVAL; else { key.af = pnl->af; key.proto = pnl->proto; PF_ACPY(&key.addr[sidx], &pnl->saddr, pnl->af); key.port[sidx] = pnl->sport; PF_ACPY(&key.addr[didx], &pnl->daddr, pnl->af); key.port[didx] = pnl->dport; state = pf_find_state_all(&key, direction, &m); if (m > 1) error = E2BIG; /* more than one state */ else if (state != NULL) { sk = state->key[sidx]; PF_ACPY(&pnl->rsaddr, &sk->addr[sidx], sk->af); pnl->rsport = sk->port[sidx]; PF_ACPY(&pnl->rdaddr, &sk->addr[didx], sk->af); pnl->rdport = sk->port[didx]; } else error = ENOENT; } break; } case DIOCSETTIMEOUT: { struct pfioc_tm *pt = (struct pfioc_tm *)addr; int old; if (pt->timeout < 0 || pt->timeout >= PFTM_MAX || pt->seconds < 0) { error = EINVAL; goto fail; } #ifdef __FreeBSD__ old = V_pf_default_rule.timeout[pt->timeout]; #else old = pf_default_rule.timeout[pt->timeout]; #endif if (pt->timeout == PFTM_INTERVAL && pt->seconds == 0) pt->seconds = 1; #ifdef __FreeBSD__ V_pf_default_rule.timeout[pt->timeout] = pt->seconds; #else pf_default_rule.timeout[pt->timeout] = pt->seconds; #endif if (pt->timeout == PFTM_INTERVAL && pt->seconds < old) wakeup(pf_purge_thread); pt->seconds = old; break; } case DIOCGETTIMEOUT: { struct pfioc_tm *pt = (struct pfioc_tm *)addr; if (pt->timeout < 0 || pt->timeout >= PFTM_MAX) { error = EINVAL; goto fail; } #ifdef __FreeBSD__ pt->seconds = V_pf_default_rule.timeout[pt->timeout]; #else pt->seconds = pf_default_rule.timeout[pt->timeout]; #endif break; } case DIOCGETLIMIT: { struct pfioc_limit *pl = (struct pfioc_limit *)addr; if (pl->index < 0 || pl->index >= PF_LIMIT_MAX) { error = EINVAL; goto fail; } #ifdef __FreeBSD__ pl->limit = V_pf_pool_limits[pl->index].limit; #else pl->limit = pf_pool_limits[pl->index].limit; #endif break; } case DIOCSETLIMIT: { struct pfioc_limit *pl = (struct pfioc_limit *)addr; int old_limit; if (pl->index < 0 || pl->index >= PF_LIMIT_MAX || #ifdef __FreeBSD__ V_pf_pool_limits[pl->index].pp == NULL) { #else pf_pool_limits[pl->index].pp == NULL) { #endif error = EINVAL; goto fail; } #ifdef __FreeBSD__ uma_zone_set_max(V_pf_pool_limits[pl->index].pp, pl->limit); old_limit = V_pf_pool_limits[pl->index].limit; V_pf_pool_limits[pl->index].limit = pl->limit; pl->limit = old_limit; #else if (pool_sethardlimit(pf_pool_limits[pl->index].pp, pl->limit, NULL, 0) != 0) { error = EBUSY; goto fail; } old_limit = pf_pool_limits[pl->index].limit; pf_pool_limits[pl->index].limit = pl->limit; pl->limit = old_limit; #endif break; } case DIOCSETDEBUG: { u_int32_t *level = (u_int32_t *)addr; #ifdef __FreeBSD__ V_pf_status.debug = *level; #else pf_status.debug = *level; #endif break; } case DIOCCLRRULECTRS: { /* obsoleted by DIOCGETRULE with action=PF_GET_CLR_CNTR */ struct pf_ruleset *ruleset = &pf_main_ruleset; struct pf_rule *rule; TAILQ_FOREACH(rule, ruleset->rules[PF_RULESET_FILTER].active.ptr, entries) { rule->evaluations = 0; rule->packets[0] = rule->packets[1] = 0; rule->bytes[0] = rule->bytes[1] = 0; } break; } #ifdef __FreeBSD__ case DIOCGIFSPEED: { struct pf_ifspeed *psp = (struct pf_ifspeed *)addr; struct pf_ifspeed ps; struct ifnet *ifp; if (psp->ifname[0] != 0) { /* Can we completely trust user-land? */ strlcpy(ps.ifname, psp->ifname, IFNAMSIZ); ifp = ifunit(ps.ifname); if (ifp != NULL) psp->baudrate = ifp->if_baudrate; else error = EINVAL; } else error = EINVAL; break; } #endif /* __FreeBSD__ */ #ifdef ALTQ case DIOCSTARTALTQ: { struct pf_altq *altq; /* enable all altq interfaces on active list */ #ifdef __FreeBSD__ TAILQ_FOREACH(altq, V_pf_altqs_active, entries) { if (altq->qname[0] == 0 && (altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { #else TAILQ_FOREACH(altq, pf_altqs_active, entries) { if (altq->qname[0] == 0) { #endif error = pf_enable_altq(altq); if (error != 0) break; } } if (error == 0) #ifdef __FreeBSD__ V_pf_altq_running = 1; #else pf_altq_running = 1; #endif DPFPRINTF(PF_DEBUG_MISC, ("altq: started\n")); break; } case DIOCSTOPALTQ: { struct pf_altq *altq; /* disable all altq interfaces on active list */ #ifdef __FreeBSD__ TAILQ_FOREACH(altq, V_pf_altqs_active, entries) { if (altq->qname[0] == 0 && (altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { #else TAILQ_FOREACH(altq, pf_altqs_active, entries) { if (altq->qname[0] == 0) { #endif error = pf_disable_altq(altq); if (error != 0) break; } } if (error == 0) #ifdef __FreeBSD__ V_pf_altq_running = 0; #else pf_altq_running = 0; #endif DPFPRINTF(PF_DEBUG_MISC, ("altq: stopped\n")); break; } case DIOCADDALTQ: { struct pfioc_altq *pa = (struct pfioc_altq *)addr; struct pf_altq *altq, *a; #ifdef __FreeBSD__ if (pa->ticket != V_ticket_altqs_inactive) { #else if (pa->ticket != ticket_altqs_inactive) { #endif error = EBUSY; break; } #ifdef __FreeBSD__ altq = pool_get(&V_pf_altq_pl, PR_NOWAIT); #else altq = pool_get(&pf_altq_pl, PR_WAITOK|PR_LIMITFAIL); #endif if (altq == NULL) { error = ENOMEM; break; } bcopy(&pa->altq, altq, sizeof(struct pf_altq)); #ifdef __FreeBSD__ altq->local_flags = 0; #endif /* * if this is for a queue, find the discipline and * copy the necessary fields */ if (altq->qname[0] != 0) { if ((altq->qid = pf_qname2qid(altq->qname)) == 0) { error = EBUSY; #ifdef __FreeBSD__ pool_put(&V_pf_altq_pl, altq); #else pool_put(&pf_altq_pl, altq); #endif break; } altq->altq_disc = NULL; #ifdef __FreeBSD__ TAILQ_FOREACH(a, V_pf_altqs_inactive, entries) { #else TAILQ_FOREACH(a, pf_altqs_inactive, entries) { #endif if (strncmp(a->ifname, altq->ifname, IFNAMSIZ) == 0 && a->qname[0] == 0) { altq->altq_disc = a->altq_disc; break; } } } #ifdef __FreeBSD__ struct ifnet *ifp; if ((ifp = ifunit(altq->ifname)) == NULL) { altq->local_flags |= PFALTQ_FLAG_IF_REMOVED; } else { PF_UNLOCK(); #endif error = altq_add(altq); #ifdef __FreeBSD__ PF_LOCK(); } #endif if (error) { #ifdef __FreeBSD__ pool_put(&V_pf_altq_pl, altq); #else pool_put(&pf_altq_pl, altq); #endif break; } #ifdef __FreeBSD__ TAILQ_INSERT_TAIL(V_pf_altqs_inactive, altq, entries); #else TAILQ_INSERT_TAIL(pf_altqs_inactive, altq, entries); #endif bcopy(altq, &pa->altq, sizeof(struct pf_altq)); break; } case DIOCGETALTQS: { struct pfioc_altq *pa = (struct pfioc_altq *)addr; struct pf_altq *altq; pa->nr = 0; #ifdef __FreeBSD__ TAILQ_FOREACH(altq, V_pf_altqs_active, entries) pa->nr++; pa->ticket = V_ticket_altqs_active; #else TAILQ_FOREACH(altq, pf_altqs_active, entries) pa->nr++; pa->ticket = ticket_altqs_active; #endif break; } case DIOCGETALTQ: { struct pfioc_altq *pa = (struct pfioc_altq *)addr; struct pf_altq *altq; u_int32_t nr; #ifdef __FreeBSD__ if (pa->ticket != V_ticket_altqs_active) { #else if (pa->ticket != ticket_altqs_active) { #endif error = EBUSY; break; } nr = 0; #ifdef __FreeBSD__ altq = TAILQ_FIRST(V_pf_altqs_active); #else altq = TAILQ_FIRST(pf_altqs_active); #endif while ((altq != NULL) && (nr < pa->nr)) { altq = TAILQ_NEXT(altq, entries); nr++; } if (altq == NULL) { error = EBUSY; break; } bcopy(altq, &pa->altq, sizeof(struct pf_altq)); break; } case DIOCCHANGEALTQ: /* CHANGEALTQ not supported yet! */ error = ENODEV; break; case DIOCGETQSTATS: { struct pfioc_qstats *pq = (struct pfioc_qstats *)addr; struct pf_altq *altq; u_int32_t nr; int nbytes; #ifdef __FreeBSD__ if (pq->ticket != V_ticket_altqs_active) { #else if (pq->ticket != ticket_altqs_active) { #endif error = EBUSY; break; } nbytes = pq->nbytes; nr = 0; #ifdef __FreeBSD__ altq = TAILQ_FIRST(V_pf_altqs_active); #else altq = TAILQ_FIRST(pf_altqs_active); #endif while ((altq != NULL) && (nr < pq->nr)) { altq = TAILQ_NEXT(altq, entries); nr++; } if (altq == NULL) { error = EBUSY; break; } #ifdef __FreeBSD__ if ((altq->local_flags & PFALTQ_FLAG_IF_REMOVED) != 0) { error = ENXIO; break; } PF_UNLOCK(); #endif error = altq_getqstats(altq, pq->buf, &nbytes); #ifdef __FreeBSD__ PF_LOCK(); #endif if (error == 0) { pq->scheduler = altq->scheduler; pq->nbytes = nbytes; } break; } #endif /* ALTQ */ case DIOCBEGINADDRS: { struct pfioc_pooladdr *pp = (struct pfioc_pooladdr *)addr; #ifdef __FreeBSD__ pf_empty_pool(&V_pf_pabuf); pp->ticket = ++V_ticket_pabuf; #else pf_empty_pool(&pf_pabuf); pp->ticket = ++ticket_pabuf; #endif break; } case DIOCADDADDR: { struct pfioc_pooladdr *pp = (struct pfioc_pooladdr *)addr; #ifdef __FreeBSD__ if (pp->ticket != V_ticket_pabuf) { #else if (pp->ticket != ticket_pabuf) { #endif error = EBUSY; break; } #ifndef INET if (pp->af == AF_INET) { error = EAFNOSUPPORT; break; } #endif /* INET */ #ifndef INET6 if (pp->af == AF_INET6) { error = EAFNOSUPPORT; break; } #endif /* INET6 */ if (pp->addr.addr.type != PF_ADDR_ADDRMASK && pp->addr.addr.type != PF_ADDR_DYNIFTL && pp->addr.addr.type != PF_ADDR_TABLE) { error = EINVAL; break; } #ifdef __FreeBSD__ pa = pool_get(&V_pf_pooladdr_pl, PR_NOWAIT); #else pa = pool_get(&pf_pooladdr_pl, PR_WAITOK|PR_LIMITFAIL); #endif if (pa == NULL) { error = ENOMEM; break; } bcopy(&pp->addr, pa, sizeof(struct pf_pooladdr)); if (pa->ifname[0]) { pa->kif = pfi_kif_get(pa->ifname); if (pa->kif == NULL) { #ifdef __FreeBSD__ pool_put(&V_pf_pooladdr_pl, pa); #else pool_put(&pf_pooladdr_pl, pa); #endif error = EINVAL; break; } pfi_kif_ref(pa->kif, PFI_KIF_REF_RULE); } if (pfi_dynaddr_setup(&pa->addr, pp->af)) { pfi_dynaddr_remove(&pa->addr); pfi_kif_unref(pa->kif, PFI_KIF_REF_RULE); #ifdef __FreeBSD__ pool_put(&V_pf_pooladdr_pl, pa); #else pool_put(&pf_pooladdr_pl, pa); #endif error = EINVAL; break; } #ifdef __FreeBSD__ TAILQ_INSERT_TAIL(&V_pf_pabuf, pa, entries); #else TAILQ_INSERT_TAIL(&pf_pabuf, pa, entries); #endif break; } case DIOCGETADDRS: { struct pfioc_pooladdr *pp = (struct pfioc_pooladdr *)addr; pp->nr = 0; pool = pf_get_pool(pp->anchor, pp->ticket, pp->r_action, pp->r_num, 0, 1, 0); if (pool == NULL) { error = EBUSY; break; } TAILQ_FOREACH(pa, &pool->list, entries) pp->nr++; break; } case DIOCGETADDR: { struct pfioc_pooladdr *pp = (struct pfioc_pooladdr *)addr; u_int32_t nr = 0; pool = pf_get_pool(pp->anchor, pp->ticket, pp->r_action, pp->r_num, 0, 1, 1); if (pool == NULL) { error = EBUSY; break; } pa = TAILQ_FIRST(&pool->list); while ((pa != NULL) && (nr < pp->nr)) { pa = TAILQ_NEXT(pa, entries); nr++; } if (pa == NULL) { error = EBUSY; break; } bcopy(pa, &pp->addr, sizeof(struct pf_pooladdr)); pf_addr_copyout(&pp->addr.addr); break; } case DIOCCHANGEADDR: { struct pfioc_pooladdr *pca = (struct pfioc_pooladdr *)addr; struct pf_pooladdr *oldpa = NULL, *newpa = NULL; struct pf_ruleset *ruleset; if (pca->action < PF_CHANGE_ADD_HEAD || pca->action > PF_CHANGE_REMOVE) { error = EINVAL; break; } if (pca->addr.addr.type != PF_ADDR_ADDRMASK && pca->addr.addr.type != PF_ADDR_DYNIFTL && pca->addr.addr.type != PF_ADDR_TABLE) { error = EINVAL; break; } ruleset = pf_find_ruleset(pca->anchor); if (ruleset == NULL) { error = EBUSY; break; } pool = pf_get_pool(pca->anchor, pca->ticket, pca->r_action, pca->r_num, pca->r_last, 1, 1); if (pool == NULL) { error = EBUSY; break; } if (pca->action != PF_CHANGE_REMOVE) { #ifdef __FreeBSD__ newpa = pool_get(&V_pf_pooladdr_pl, PR_NOWAIT); #else newpa = pool_get(&pf_pooladdr_pl, PR_WAITOK|PR_LIMITFAIL); #endif if (newpa == NULL) { error = ENOMEM; break; } bcopy(&pca->addr, newpa, sizeof(struct pf_pooladdr)); #ifndef INET if (pca->af == AF_INET) { #ifdef __FreeBSD__ pool_put(&V_pf_pooladdr_pl, newpa); #else pool_put(&pf_pooladdr_pl, newpa); #endif error = EAFNOSUPPORT; break; } #endif /* INET */ #ifndef INET6 if (pca->af == AF_INET6) { #ifdef __FreeBSD__ pool_put(&V_pf_pooladdr_pl, newpa); #else pool_put(&pf_pooladdr_pl, newpa); #endif error = EAFNOSUPPORT; break; } #endif /* INET6 */ if (newpa->ifname[0]) { newpa->kif = pfi_kif_get(newpa->ifname); if (newpa->kif == NULL) { #ifdef __FreeBSD__ pool_put(&V_pf_pooladdr_pl, newpa); #else pool_put(&pf_pooladdr_pl, newpa); #endif error = EINVAL; break; } pfi_kif_ref(newpa->kif, PFI_KIF_REF_RULE); } else newpa->kif = NULL; if (pfi_dynaddr_setup(&newpa->addr, pca->af) || pf_tbladdr_setup(ruleset, &newpa->addr)) { pfi_dynaddr_remove(&newpa->addr); pfi_kif_unref(newpa->kif, PFI_KIF_REF_RULE); #ifdef __FreeBSD__ pool_put(&V_pf_pooladdr_pl, newpa); #else pool_put(&pf_pooladdr_pl, newpa); #endif error = EINVAL; break; } } if (pca->action == PF_CHANGE_ADD_HEAD) oldpa = TAILQ_FIRST(&pool->list); else if (pca->action == PF_CHANGE_ADD_TAIL) oldpa = TAILQ_LAST(&pool->list, pf_palist); else { int i = 0; oldpa = TAILQ_FIRST(&pool->list); while ((oldpa != NULL) && (i < pca->nr)) { oldpa = TAILQ_NEXT(oldpa, entries); i++; } if (oldpa == NULL) { error = EINVAL; break; } } if (pca->action == PF_CHANGE_REMOVE) { TAILQ_REMOVE(&pool->list, oldpa, entries); pfi_dynaddr_remove(&oldpa->addr); pf_tbladdr_remove(&oldpa->addr); pfi_kif_unref(oldpa->kif, PFI_KIF_REF_RULE); #ifdef __FreeBSD__ pool_put(&V_pf_pooladdr_pl, oldpa); #else pool_put(&pf_pooladdr_pl, oldpa); #endif } else { if (oldpa == NULL) TAILQ_INSERT_TAIL(&pool->list, newpa, entries); else if (pca->action == PF_CHANGE_ADD_HEAD || pca->action == PF_CHANGE_ADD_BEFORE) TAILQ_INSERT_BEFORE(oldpa, newpa, entries); else TAILQ_INSERT_AFTER(&pool->list, oldpa, newpa, entries); } pool->cur = TAILQ_FIRST(&pool->list); PF_ACPY(&pool->counter, &pool->cur->addr.v.a.addr, pca->af); break; } case DIOCGETRULESETS: { struct pfioc_ruleset *pr = (struct pfioc_ruleset *)addr; struct pf_ruleset *ruleset; struct pf_anchor *anchor; pr->path[sizeof(pr->path) - 1] = 0; if ((ruleset = pf_find_ruleset(pr->path)) == NULL) { error = EINVAL; break; } pr->nr = 0; if (ruleset->anchor == NULL) { /* XXX kludge for pf_main_ruleset */ #ifdef __FreeBSD__ RB_FOREACH(anchor, pf_anchor_global, &V_pf_anchors) #else RB_FOREACH(anchor, pf_anchor_global, &pf_anchors) #endif if (anchor->parent == NULL) pr->nr++; } else { RB_FOREACH(anchor, pf_anchor_node, &ruleset->anchor->children) pr->nr++; } break; } case DIOCGETRULESET: { struct pfioc_ruleset *pr = (struct pfioc_ruleset *)addr; struct pf_ruleset *ruleset; struct pf_anchor *anchor; u_int32_t nr = 0; pr->path[sizeof(pr->path) - 1] = 0; if ((ruleset = pf_find_ruleset(pr->path)) == NULL) { error = EINVAL; break; } pr->name[0] = 0; if (ruleset->anchor == NULL) { /* XXX kludge for pf_main_ruleset */ #ifdef __FreeBSD__ RB_FOREACH(anchor, pf_anchor_global, &V_pf_anchors) #else RB_FOREACH(anchor, pf_anchor_global, &pf_anchors) #endif if (anchor->parent == NULL && nr++ == pr->nr) { strlcpy(pr->name, anchor->name, sizeof(pr->name)); break; } } else { RB_FOREACH(anchor, pf_anchor_node, &ruleset->anchor->children) if (nr++ == pr->nr) { strlcpy(pr->name, anchor->name, sizeof(pr->name)); break; } } if (!pr->name[0]) error = EBUSY; break; } case DIOCRCLRTABLES: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != 0) { error = ENODEV; break; } error = pfr_clr_tables(&io->pfrio_table, &io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL); break; } case DIOCRADDTABLES: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != sizeof(struct pfr_table)) { error = ENODEV; break; } error = pfr_add_tables(io->pfrio_buffer, io->pfrio_size, &io->pfrio_nadd, io->pfrio_flags | PFR_FLAG_USERIOCTL); break; } case DIOCRDELTABLES: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != sizeof(struct pfr_table)) { error = ENODEV; break; } error = pfr_del_tables(io->pfrio_buffer, io->pfrio_size, &io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL); break; } case DIOCRGETTABLES: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != sizeof(struct pfr_table)) { error = ENODEV; break; } error = pfr_get_tables(&io->pfrio_table, io->pfrio_buffer, &io->pfrio_size, io->pfrio_flags | PFR_FLAG_USERIOCTL); break; } case DIOCRGETTSTATS: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != sizeof(struct pfr_tstats)) { error = ENODEV; break; } error = pfr_get_tstats(&io->pfrio_table, io->pfrio_buffer, &io->pfrio_size, io->pfrio_flags | PFR_FLAG_USERIOCTL); break; } case DIOCRCLRTSTATS: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != sizeof(struct pfr_table)) { error = ENODEV; break; } error = pfr_clr_tstats(io->pfrio_buffer, io->pfrio_size, &io->pfrio_nzero, io->pfrio_flags | PFR_FLAG_USERIOCTL); break; } case DIOCRSETTFLAGS: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != sizeof(struct pfr_table)) { error = ENODEV; break; } error = pfr_set_tflags(io->pfrio_buffer, io->pfrio_size, io->pfrio_setflag, io->pfrio_clrflag, &io->pfrio_nchange, &io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL); break; } case DIOCRCLRADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != 0) { error = ENODEV; break; } error = pfr_clr_addrs(&io->pfrio_table, &io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL); break; } case DIOCRADDADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; break; } error = pfr_add_addrs(&io->pfrio_table, io->pfrio_buffer, io->pfrio_size, &io->pfrio_nadd, io->pfrio_flags | PFR_FLAG_USERIOCTL); break; } case DIOCRDELADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; break; } error = pfr_del_addrs(&io->pfrio_table, io->pfrio_buffer, io->pfrio_size, &io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL); break; } case DIOCRSETADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; break; } error = pfr_set_addrs(&io->pfrio_table, io->pfrio_buffer, io->pfrio_size, &io->pfrio_size2, &io->pfrio_nadd, &io->pfrio_ndel, &io->pfrio_nchange, io->pfrio_flags | PFR_FLAG_USERIOCTL, 0); break; } case DIOCRGETADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; break; } error = pfr_get_addrs(&io->pfrio_table, io->pfrio_buffer, &io->pfrio_size, io->pfrio_flags | PFR_FLAG_USERIOCTL); break; } case DIOCRGETASTATS: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != sizeof(struct pfr_astats)) { error = ENODEV; break; } error = pfr_get_astats(&io->pfrio_table, io->pfrio_buffer, &io->pfrio_size, io->pfrio_flags | PFR_FLAG_USERIOCTL); break; } case DIOCRCLRASTATS: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; break; } error = pfr_clr_astats(&io->pfrio_table, io->pfrio_buffer, io->pfrio_size, &io->pfrio_nzero, io->pfrio_flags | PFR_FLAG_USERIOCTL); break; } case DIOCRTSTADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; break; } error = pfr_tst_addrs(&io->pfrio_table, io->pfrio_buffer, io->pfrio_size, &io->pfrio_nmatch, io->pfrio_flags | PFR_FLAG_USERIOCTL); break; } case DIOCRINADEFINE: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; break; } error = pfr_ina_define(&io->pfrio_table, io->pfrio_buffer, io->pfrio_size, &io->pfrio_nadd, &io->pfrio_naddr, io->pfrio_ticket, io->pfrio_flags | PFR_FLAG_USERIOCTL); break; } case DIOCOSFPADD: { struct pf_osfp_ioctl *io = (struct pf_osfp_ioctl *)addr; error = pf_osfp_add(io); break; } case DIOCOSFPGET: { struct pf_osfp_ioctl *io = (struct pf_osfp_ioctl *)addr; error = pf_osfp_get(io); break; } case DIOCXBEGIN: { struct pfioc_trans *io = (struct pfioc_trans *)addr; struct pfioc_trans_e *ioe; struct pfr_table *table; int i; if (io->esize != sizeof(*ioe)) { error = ENODEV; goto fail; } #ifdef __FreeBSD__ PF_UNLOCK(); #endif ioe = malloc(sizeof(*ioe), M_TEMP, M_WAITOK); table = malloc(sizeof(*table), M_TEMP, M_WAITOK); #ifdef __FreeBSD__ PF_LOCK(); #endif for (i = 0; i < io->size; i++) { #ifdef __FreeBSD__ PF_COPYIN(io->array+i, ioe, sizeof(*ioe), error); if (error) { #else if (copyin(io->array+i, ioe, sizeof(*ioe))) { #endif free(table, M_TEMP); free(ioe, M_TEMP); error = EFAULT; goto fail; } switch (ioe->rs_num) { #ifdef ALTQ case PF_RULESET_ALTQ: if (ioe->anchor[0]) { free(table, M_TEMP); free(ioe, M_TEMP); error = EINVAL; goto fail; } if ((error = pf_begin_altq(&ioe->ticket))) { free(table, M_TEMP); free(ioe, M_TEMP); goto fail; } break; #endif /* ALTQ */ case PF_RULESET_TABLE: bzero(table, sizeof(*table)); strlcpy(table->pfrt_anchor, ioe->anchor, sizeof(table->pfrt_anchor)); if ((error = pfr_ina_begin(table, &ioe->ticket, NULL, 0))) { free(table, M_TEMP); free(ioe, M_TEMP); goto fail; } break; default: if ((error = pf_begin_rules(&ioe->ticket, ioe->rs_num, ioe->anchor))) { free(table, M_TEMP); free(ioe, M_TEMP); goto fail; } break; } #ifdef __FreeBSD__ PF_COPYOUT(ioe, io->array+i, sizeof(io->array[i]), error); if (error) { #else if (copyout(ioe, io->array+i, sizeof(io->array[i]))) { #endif free(table, M_TEMP); free(ioe, M_TEMP); error = EFAULT; goto fail; } } free(table, M_TEMP); free(ioe, M_TEMP); break; } case DIOCXROLLBACK: { struct pfioc_trans *io = (struct pfioc_trans *)addr; struct pfioc_trans_e *ioe; struct pfr_table *table; int i; if (io->esize != sizeof(*ioe)) { error = ENODEV; goto fail; } #ifdef __FreeBSD__ PF_UNLOCK(); #endif ioe = malloc(sizeof(*ioe), M_TEMP, M_WAITOK); table = malloc(sizeof(*table), M_TEMP, M_WAITOK); #ifdef __FreeBSD__ PF_LOCK(); #endif for (i = 0; i < io->size; i++) { #ifdef __FreeBSD__ PF_COPYIN(io->array+i, ioe, sizeof(*ioe), error); if (error) { #else if (copyin(io->array+i, ioe, sizeof(*ioe))) { #endif free(table, M_TEMP); free(ioe, M_TEMP); error = EFAULT; goto fail; } switch (ioe->rs_num) { #ifdef ALTQ case PF_RULESET_ALTQ: if (ioe->anchor[0]) { free(table, M_TEMP); free(ioe, M_TEMP); error = EINVAL; goto fail; } if ((error = pf_rollback_altq(ioe->ticket))) { free(table, M_TEMP); free(ioe, M_TEMP); goto fail; /* really bad */ } break; #endif /* ALTQ */ case PF_RULESET_TABLE: bzero(table, sizeof(*table)); strlcpy(table->pfrt_anchor, ioe->anchor, sizeof(table->pfrt_anchor)); if ((error = pfr_ina_rollback(table, ioe->ticket, NULL, 0))) { free(table, M_TEMP); free(ioe, M_TEMP); goto fail; /* really bad */ } break; default: if ((error = pf_rollback_rules(ioe->ticket, ioe->rs_num, ioe->anchor))) { free(table, M_TEMP); free(ioe, M_TEMP); goto fail; /* really bad */ } break; } } free(table, M_TEMP); free(ioe, M_TEMP); break; } case DIOCXCOMMIT: { struct pfioc_trans *io = (struct pfioc_trans *)addr; struct pfioc_trans_e *ioe; struct pfr_table *table; struct pf_ruleset *rs; int i; if (io->esize != sizeof(*ioe)) { error = ENODEV; goto fail; } #ifdef __FreeBSD__ PF_UNLOCK(); #endif ioe = malloc(sizeof(*ioe), M_TEMP, M_WAITOK); table = malloc(sizeof(*table), M_TEMP, M_WAITOK); #ifdef __FreeBSD__ PF_LOCK(); #endif /* first makes sure everything will succeed */ for (i = 0; i < io->size; i++) { #ifdef __FreeBSD__ PF_COPYIN(io->array+i, ioe, sizeof(*ioe), error); if (error) { #else if (copyin(io->array+i, ioe, sizeof(*ioe))) { #endif free(table, M_TEMP); free(ioe, M_TEMP); error = EFAULT; goto fail; } switch (ioe->rs_num) { #ifdef ALTQ case PF_RULESET_ALTQ: if (ioe->anchor[0]) { free(table, M_TEMP); free(ioe, M_TEMP); error = EINVAL; goto fail; } #ifdef __FreeBSD__ if (!V_altqs_inactive_open || ioe->ticket != V_ticket_altqs_inactive) { #else if (!altqs_inactive_open || ioe->ticket != ticket_altqs_inactive) { #endif free(table, M_TEMP); free(ioe, M_TEMP); error = EBUSY; goto fail; } break; #endif /* ALTQ */ case PF_RULESET_TABLE: rs = pf_find_ruleset(ioe->anchor); if (rs == NULL || !rs->topen || ioe->ticket != rs->tticket) { free(table, M_TEMP); free(ioe, M_TEMP); error = EBUSY; goto fail; } break; default: if (ioe->rs_num < 0 || ioe->rs_num >= PF_RULESET_MAX) { free(table, M_TEMP); free(ioe, M_TEMP); error = EINVAL; goto fail; } rs = pf_find_ruleset(ioe->anchor); if (rs == NULL || !rs->rules[ioe->rs_num].inactive.open || rs->rules[ioe->rs_num].inactive.ticket != ioe->ticket) { free(table, M_TEMP); free(ioe, M_TEMP); error = EBUSY; goto fail; } break; } } /* now do the commit - no errors should happen here */ for (i = 0; i < io->size; i++) { #ifdef __FreeBSD__ PF_COPYIN(io->array+i, ioe, sizeof(*ioe), error); if (error) { #else if (copyin(io->array+i, ioe, sizeof(*ioe))) { #endif free(table, M_TEMP); free(ioe, M_TEMP); error = EFAULT; goto fail; } switch (ioe->rs_num) { #ifdef ALTQ case PF_RULESET_ALTQ: if ((error = pf_commit_altq(ioe->ticket))) { free(table, M_TEMP); free(ioe, M_TEMP); goto fail; /* really bad */ } break; #endif /* ALTQ */ case PF_RULESET_TABLE: bzero(table, sizeof(*table)); strlcpy(table->pfrt_anchor, ioe->anchor, sizeof(table->pfrt_anchor)); if ((error = pfr_ina_commit(table, ioe->ticket, NULL, NULL, 0))) { free(table, M_TEMP); free(ioe, M_TEMP); goto fail; /* really bad */ } break; default: if ((error = pf_commit_rules(ioe->ticket, ioe->rs_num, ioe->anchor))) { free(table, M_TEMP); free(ioe, M_TEMP); goto fail; /* really bad */ } break; } } free(table, M_TEMP); free(ioe, M_TEMP); break; } case DIOCGETSRCNODES: { struct pfioc_src_nodes *psn = (struct pfioc_src_nodes *)addr; struct pf_src_node *n, *p, *pstore; u_int32_t nr = 0; int space = psn->psn_len; if (space == 0) { #ifdef __FreeBSD__ RB_FOREACH(n, pf_src_tree, &V_tree_src_tracking) #else RB_FOREACH(n, pf_src_tree, &tree_src_tracking) #endif nr++; psn->psn_len = sizeof(struct pf_src_node) * nr; break; } #ifdef __FreeBSD__ PF_UNLOCK(); #endif pstore = malloc(sizeof(*pstore), M_TEMP, M_WAITOK); #ifdef __FreeBSD__ PF_LOCK(); #endif p = psn->psn_src_nodes; #ifdef __FreeBSD__ RB_FOREACH(n, pf_src_tree, &V_tree_src_tracking) { #else RB_FOREACH(n, pf_src_tree, &tree_src_tracking) { #endif int secs = time_second, diff; if ((nr + 1) * sizeof(*p) > (unsigned)psn->psn_len) break; bcopy(n, pstore, sizeof(*pstore)); if (n->rule.ptr != NULL) pstore->rule.nr = n->rule.ptr->nr; pstore->creation = secs - pstore->creation; if (pstore->expire > secs) pstore->expire -= secs; else pstore->expire = 0; /* adjust the connection rate estimate */ diff = secs - n->conn_rate.last; if (diff >= n->conn_rate.seconds) pstore->conn_rate.count = 0; else pstore->conn_rate.count -= n->conn_rate.count * diff / n->conn_rate.seconds; #ifdef __FreeBSD__ PF_COPYOUT(pstore, p, sizeof(*p), error); #else error = copyout(pstore, p, sizeof(*p)); #endif if (error) { free(pstore, M_TEMP); goto fail; } p++; nr++; } psn->psn_len = sizeof(struct pf_src_node) * nr; free(pstore, M_TEMP); break; } case DIOCCLRSRCNODES: { struct pf_src_node *n; struct pf_state *state; #ifdef __FreeBSD__ RB_FOREACH(state, pf_state_tree_id, &V_tree_id) { #else RB_FOREACH(state, pf_state_tree_id, &tree_id) { #endif state->src_node = NULL; state->nat_src_node = NULL; } #ifdef __FreeBSD__ RB_FOREACH(n, pf_src_tree, &V_tree_src_tracking) { #else RB_FOREACH(n, pf_src_tree, &tree_src_tracking) { #endif n->expire = 1; n->states = 0; } pf_purge_expired_src_nodes(1); #ifdef __FreeBSD__ V_pf_status.src_nodes = 0; #else pf_status.src_nodes = 0; #endif break; } case DIOCKILLSRCNODES: { struct pf_src_node *sn; struct pf_state *s; struct pfioc_src_node_kill *psnk = (struct pfioc_src_node_kill *)addr; u_int killed = 0; #ifdef __FreeBSD__ RB_FOREACH(sn, pf_src_tree, &V_tree_src_tracking) { #else RB_FOREACH(sn, pf_src_tree, &tree_src_tracking) { #endif if (PF_MATCHA(psnk->psnk_src.neg, &psnk->psnk_src.addr.v.a.addr, &psnk->psnk_src.addr.v.a.mask, &sn->addr, sn->af) && PF_MATCHA(psnk->psnk_dst.neg, &psnk->psnk_dst.addr.v.a.addr, &psnk->psnk_dst.addr.v.a.mask, &sn->raddr, sn->af)) { /* Handle state to src_node linkage */ if (sn->states != 0) { RB_FOREACH(s, pf_state_tree_id, #ifdef __FreeBSD__ &V_tree_id) { #else &tree_id) { #endif if (s->src_node == sn) s->src_node = NULL; if (s->nat_src_node == sn) s->nat_src_node = NULL; } sn->states = 0; } sn->expire = 1; killed++; } } if (killed > 0) pf_purge_expired_src_nodes(1); psnk->psnk_killed = killed; break; } case DIOCSETHOSTID: { u_int32_t *hostid = (u_int32_t *)addr; #ifdef __FreeBSD__ if (*hostid == 0) V_pf_status.hostid = arc4random(); else V_pf_status.hostid = *hostid; #else if (*hostid == 0) pf_status.hostid = arc4random(); else pf_status.hostid = *hostid; #endif break; } case DIOCOSFPFLUSH: pf_osfp_flush(); break; case DIOCIGETIFACES: { struct pfioc_iface *io = (struct pfioc_iface *)addr; if (io->pfiio_esize != sizeof(struct pfi_kif)) { error = ENODEV; break; } error = pfi_get_ifaces(io->pfiio_name, io->pfiio_buffer, &io->pfiio_size); break; } case DIOCSETIFFLAG: { struct pfioc_iface *io = (struct pfioc_iface *)addr; error = pfi_set_flags(io->pfiio_name, io->pfiio_flags); break; } case DIOCCLRIFFLAG: { struct pfioc_iface *io = (struct pfioc_iface *)addr; error = pfi_clear_flags(io->pfiio_name, io->pfiio_flags); break; } default: error = ENODEV; break; } fail: #ifdef __FreeBSD__ PF_UNLOCK(); if (flags & FWRITE) sx_xunlock(&V_pf_consistency_lock); else sx_sunlock(&V_pf_consistency_lock); #else splx(s); if (flags & FWRITE) rw_exit_write(&pf_consistency_lock); else rw_exit_read(&pf_consistency_lock); #endif CURVNET_RESTORE(); return (error); } #ifdef __FreeBSD__ void pfsync_state_export(struct pfsync_state *sp, struct pf_state *st) { bzero(sp, sizeof(struct pfsync_state)); /* copy from state key */ sp->key[PF_SK_WIRE].addr[0] = st->key[PF_SK_WIRE]->addr[0]; sp->key[PF_SK_WIRE].addr[1] = st->key[PF_SK_WIRE]->addr[1]; sp->key[PF_SK_WIRE].port[0] = st->key[PF_SK_WIRE]->port[0]; sp->key[PF_SK_WIRE].port[1] = st->key[PF_SK_WIRE]->port[1]; sp->key[PF_SK_STACK].addr[0] = st->key[PF_SK_STACK]->addr[0]; sp->key[PF_SK_STACK].addr[1] = st->key[PF_SK_STACK]->addr[1]; sp->key[PF_SK_STACK].port[0] = st->key[PF_SK_STACK]->port[0]; sp->key[PF_SK_STACK].port[1] = st->key[PF_SK_STACK]->port[1]; sp->proto = st->key[PF_SK_WIRE]->proto; sp->af = st->key[PF_SK_WIRE]->af; /* copy from state */ strlcpy(sp->ifname, st->kif->pfik_name, sizeof(sp->ifname)); bcopy(&st->rt_addr, &sp->rt_addr, sizeof(sp->rt_addr)); sp->creation = htonl(time_second - st->creation); sp->expire = pf_state_expires(st); if (sp->expire <= time_second) sp->expire = htonl(0); else sp->expire = htonl(sp->expire - time_second); sp->direction = st->direction; sp->log = st->log; sp->timeout = st->timeout; sp->state_flags = st->state_flags; if (st->src_node) sp->sync_flags |= PFSYNC_FLAG_SRCNODE; if (st->nat_src_node) sp->sync_flags |= PFSYNC_FLAG_NATSRCNODE; bcopy(&st->id, &sp->id, sizeof(sp->id)); sp->creatorid = st->creatorid; pf_state_peer_hton(&st->src, &sp->src); pf_state_peer_hton(&st->dst, &sp->dst); if (st->rule.ptr == NULL) sp->rule = htonl(-1); else sp->rule = htonl(st->rule.ptr->nr); if (st->anchor.ptr == NULL) sp->anchor = htonl(-1); else sp->anchor = htonl(st->anchor.ptr->nr); if (st->nat_rule.ptr == NULL) sp->nat_rule = htonl(-1); else sp->nat_rule = htonl(st->nat_rule.ptr->nr); pf_state_counter_hton(st->packets[0], sp->packets[0]); pf_state_counter_hton(st->packets[1], sp->packets[1]); pf_state_counter_hton(st->bytes[0], sp->bytes[0]); pf_state_counter_hton(st->bytes[1], sp->bytes[1]); } /* * XXX - Check for version missmatch!!! */ static void pf_clear_states(void) { struct pf_state *state; #ifdef __FreeBSD__ RB_FOREACH(state, pf_state_tree_id, &V_tree_id) { #else RB_FOREACH(state, pf_state_tree_id, &tree_id) { #endif state->timeout = PFTM_PURGE; #if NPFSYNC /* don't send out individual delete messages */ state->sync_state = PFSTATE_NOSYNC; #endif pf_unlink_state(state); } #if 0 /* NPFSYNC */ /* * XXX This is called on module unload, we do not want to sync that over? */ */ pfsync_clear_states(V_pf_status.hostid, psk->psk_ifname); #endif } static int pf_clear_tables(void) { struct pfioc_table io; int error; bzero(&io, sizeof(io)); error = pfr_clr_tables(&io.pfrio_table, &io.pfrio_ndel, io.pfrio_flags); return (error); } static void pf_clear_srcnodes(void) { struct pf_src_node *n; struct pf_state *state; #ifdef __FreeBSD__ RB_FOREACH(state, pf_state_tree_id, &V_tree_id) { #else RB_FOREACH(state, pf_state_tree_id, &tree_id) { #endif state->src_node = NULL; state->nat_src_node = NULL; } #ifdef __FreeBSD__ RB_FOREACH(n, pf_src_tree, &V_tree_src_tracking) { #else RB_FOREACH(n, pf_src_tree, &tree_src_tracking) { #endif n->expire = 1; n->states = 0; } } /* * XXX - Check for version missmatch!!! */ /* * Duplicate pfctl -Fa operation to get rid of as much as we can. */ static int shutdown_pf(void) { int error = 0; u_int32_t t[5]; char nn = '\0'; V_pf_status.running = 0; do { if ((error = pf_begin_rules(&t[0], PF_RULESET_SCRUB, &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: SCRUB\n")); break; } if ((error = pf_begin_rules(&t[1], PF_RULESET_FILTER, &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: FILTER\n")); break; /* XXX: rollback? */ } if ((error = pf_begin_rules(&t[2], PF_RULESET_NAT, &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: NAT\n")); break; /* XXX: rollback? */ } if ((error = pf_begin_rules(&t[3], PF_RULESET_BINAT, &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: BINAT\n")); break; /* XXX: rollback? */ } if ((error = pf_begin_rules(&t[4], PF_RULESET_RDR, &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: RDR\n")); break; /* XXX: rollback? */ } /* XXX: these should always succeed here */ pf_commit_rules(t[0], PF_RULESET_SCRUB, &nn); pf_commit_rules(t[1], PF_RULESET_FILTER, &nn); pf_commit_rules(t[2], PF_RULESET_NAT, &nn); pf_commit_rules(t[3], PF_RULESET_BINAT, &nn); pf_commit_rules(t[4], PF_RULESET_RDR, &nn); if ((error = pf_clear_tables()) != 0) break; #ifdef ALTQ if ((error = pf_begin_altq(&t[0])) != 0) { DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: ALTQ\n")); break; } pf_commit_altq(t[0]); #endif pf_clear_states(); pf_clear_srcnodes(); /* status does not use malloced mem so no need to cleanup */ /* fingerprints and interfaces have thier own cleanup code */ } while(0); return (error); } #ifdef INET static int pf_check_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, struct inpcb *inp) { /* * XXX Wed Jul 9 22:03:16 2003 UTC * OpenBSD has changed its byte ordering convention on ip_len/ip_off * in network stack. OpenBSD's network stack have converted * ip_len/ip_off to host byte order frist as FreeBSD. * Now this is not true anymore , so we should convert back to network * byte order. */ struct ip *h = NULL; int chk; if ((*m)->m_pkthdr.len >= (int)sizeof(struct ip)) { /* if m_pkthdr.len is less than ip header, pf will handle. */ h = mtod(*m, struct ip *); HTONS(h->ip_len); HTONS(h->ip_off); } CURVNET_SET(ifp->if_vnet); chk = pf_test(PF_IN, ifp, m, NULL, inp); CURVNET_RESTORE(); if (chk && *m) { m_freem(*m); *m = NULL; } if (*m != NULL) { /* pf_test can change ip header location */ h = mtod(*m, struct ip *); NTOHS(h->ip_len); NTOHS(h->ip_off); } return chk; } static int pf_check_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, struct inpcb *inp) { /* * XXX Wed Jul 9 22:03:16 2003 UTC * OpenBSD has changed its byte ordering convention on ip_len/ip_off * in network stack. OpenBSD's network stack have converted * ip_len/ip_off to host byte order frist as FreeBSD. * Now this is not true anymore , so we should convert back to network * byte order. */ struct ip *h = NULL; int chk; - /* We need a proper CSUM befor we start (s. OpenBSD ip_output) */ - if ((*m)->m_pkthdr.csum_flags & CSUM_DELAY_DATA) { - in_delayed_cksum(*m); - (*m)->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA; - } if ((*m)->m_pkthdr.len >= (int)sizeof(*h)) { /* if m_pkthdr.len is less than ip header, pf will handle. */ h = mtod(*m, struct ip *); HTONS(h->ip_len); HTONS(h->ip_off); } CURVNET_SET(ifp->if_vnet); chk = pf_test(PF_OUT, ifp, m, NULL, inp); CURVNET_RESTORE(); if (chk && *m) { m_freem(*m); *m = NULL; } if (*m != NULL) { /* pf_test can change ip header location */ h = mtod(*m, struct ip *); NTOHS(h->ip_len); NTOHS(h->ip_off); } return chk; } #endif #ifdef INET6 static int pf_check6_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, struct inpcb *inp) { /* * IPv6 is not affected by ip_len/ip_off byte order changes. */ int chk; /* * In case of loopback traffic IPv6 uses the real interface in * order to support scoped addresses. In order to support stateful * filtering we have change this to lo0 as it is the case in IPv4. */ CURVNET_SET(ifp->if_vnet); chk = pf_test6(PF_IN, (*m)->m_flags & M_LOOP ? V_loif : ifp, m, NULL, inp); CURVNET_RESTORE(); if (chk && *m) { m_freem(*m); *m = NULL; } return chk; } static int pf_check6_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, struct inpcb *inp) { /* * IPv6 does not affected ip_len/ip_off byte order changes. */ int chk; - /* We need a proper CSUM before we start (s. OpenBSD ip_output) */ - if ((*m)->m_pkthdr.csum_flags & CSUM_DELAY_DATA) { -#ifdef INET - /* XXX-BZ copy&paste error from r126261? */ - in_delayed_cksum(*m); -#endif - (*m)->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA; - } CURVNET_SET(ifp->if_vnet); chk = pf_test6(PF_OUT, ifp, m, NULL, inp); CURVNET_RESTORE(); if (chk && *m) { m_freem(*m); *m = NULL; } return chk; } #endif /* INET6 */ static int hook_pf(void) { #ifdef INET struct pfil_head *pfh_inet; #endif #ifdef INET6 struct pfil_head *pfh_inet6; #endif PF_UNLOCK_ASSERT(); if (V_pf_pfil_hooked) return (0); #ifdef INET pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); if (pfh_inet == NULL) return (ESRCH); /* XXX */ pfil_add_hook(pf_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet); pfil_add_hook(pf_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet); #endif #ifdef INET6 pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); if (pfh_inet6 == NULL) { #ifdef INET pfil_remove_hook(pf_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet); pfil_remove_hook(pf_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet); #endif return (ESRCH); /* XXX */ } pfil_add_hook(pf_check6_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet6); pfil_add_hook(pf_check6_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet6); #endif V_pf_pfil_hooked = 1; return (0); } static int dehook_pf(void) { #ifdef INET struct pfil_head *pfh_inet; #endif #ifdef INET6 struct pfil_head *pfh_inet6; #endif PF_UNLOCK_ASSERT(); if (V_pf_pfil_hooked == 0) return (0); #ifdef INET pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); if (pfh_inet == NULL) return (ESRCH); /* XXX */ pfil_remove_hook(pf_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet); pfil_remove_hook(pf_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet); #endif #ifdef INET6 pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); if (pfh_inet6 == NULL) return (ESRCH); /* XXX */ pfil_remove_hook(pf_check6_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet6); pfil_remove_hook(pf_check6_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet6); #endif V_pf_pfil_hooked = 0; return (0); } static int pf_load(void) { VNET_ITERATOR_DECL(vnet_iter); VNET_LIST_RLOCK(); VNET_FOREACH(vnet_iter) { CURVNET_SET(vnet_iter); V_pf_pfil_hooked = 0; V_pf_end_threads = 0; V_debug_pfugidhack = 0; TAILQ_INIT(&V_pf_tags); TAILQ_INIT(&V_pf_qids); CURVNET_RESTORE(); } VNET_LIST_RUNLOCK(); init_pf_mutex(); pf_dev = make_dev(&pf_cdevsw, 0, 0, 0, 0600, PF_NAME); init_zone_var(); sx_init(&V_pf_consistency_lock, "pf_statetbl_lock"); if (pfattach() < 0) return (ENOMEM); return (0); } static int pf_unload(void) { int error = 0; PF_LOCK(); V_pf_status.running = 0; PF_UNLOCK(); m_addr_chg_pf_p = NULL; error = dehook_pf(); if (error) { /* * Should not happen! * XXX Due to error code ESRCH, kldunload will show * a message like 'No such process'. */ printf("%s : pfil unregisteration fail\n", __FUNCTION__); return error; } PF_LOCK(); shutdown_pf(); V_pf_end_threads = 1; while (V_pf_end_threads < 2) { wakeup_one(pf_purge_thread); msleep(pf_purge_thread, &pf_task_mtx, 0, "pftmo", hz); } pfi_cleanup(); pf_osfp_flush(); pf_osfp_cleanup(); cleanup_pf_zone(); PF_UNLOCK(); destroy_dev(pf_dev); destroy_pf_mutex(); sx_destroy(&V_pf_consistency_lock); return error; } static int pf_modevent(module_t mod, int type, void *data) { int error = 0; switch(type) { case MOD_LOAD: error = pf_load(); break; case MOD_QUIESCE: /* * Module should not be unloaded due to race conditions. */ error = EPERM; break; case MOD_UNLOAD: error = pf_unload(); break; default: error = EINVAL; break; } return error; } static moduledata_t pf_mod = { "pf", pf_modevent, 0 }; DECLARE_MODULE(pf, pf_mod, SI_SUB_PSEUDO, SI_ORDER_FIRST); MODULE_VERSION(pf, PF_MODVER); #endif /* __FreeBSD__ */ Index: stable/9/sys/contrib/pf/net/pf_norm.c =================================================================== --- stable/9/sys/contrib/pf/net/pf_norm.c (revision 292730) +++ stable/9/sys/contrib/pf/net/pf_norm.c (revision 292731) @@ -1,2359 +1,2360 @@ /* $OpenBSD: pf_norm.c,v 1.114 2009/01/29 14:11:45 henning Exp $ */ /* * Copyright 2001 Niels Provos * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef __FreeBSD__ #include "opt_inet.h" #include "opt_inet6.h" #include "opt_pf.h" #include __FBSDID("$FreeBSD$"); #ifdef DEV_PFLOG #define NPFLOG DEV_PFLOG #else #define NPFLOG 0 #endif #else #include "pflog.h" #endif #include #include #include #include #include #include #include #include #ifndef __FreeBSD__ #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET6 #include #endif /* INET6 */ #include #ifndef __FreeBSD__ struct pf_frent { LIST_ENTRY(pf_frent) fr_next; struct ip *fr_ip; struct mbuf *fr_m; }; struct pf_frcache { LIST_ENTRY(pf_frcache) fr_next; uint16_t fr_off; uint16_t fr_end; }; #endif #define PFFRAG_SEENLAST 0x0001 /* Seen the last fragment for this */ #define PFFRAG_NOBUFFER 0x0002 /* Non-buffering fragment cache */ #define PFFRAG_DROP 0x0004 /* Drop all fragments */ #define BUFFER_FRAGMENTS(fr) (!((fr)->fr_flags & PFFRAG_NOBUFFER)) #ifndef __FreeBSD__ struct pf_fragment { RB_ENTRY(pf_fragment) fr_entry; TAILQ_ENTRY(pf_fragment) frag_next; struct in_addr fr_src; struct in_addr fr_dst; u_int8_t fr_p; /* protocol of this fragment */ u_int8_t fr_flags; /* status flags */ u_int16_t fr_id; /* fragment id for reassemble */ u_int16_t fr_max; /* fragment data max */ u_int32_t fr_timeout; #define fr_queue fr_u.fru_queue #define fr_cache fr_u.fru_cache union { LIST_HEAD(pf_fragq, pf_frent) fru_queue; /* buffering */ LIST_HEAD(pf_cacheq, pf_frcache) fru_cache; /* non-buf */ } fr_u; }; #endif #ifdef __FreeBSD__ TAILQ_HEAD(pf_fragqueue, pf_fragment); TAILQ_HEAD(pf_cachequeue, pf_fragment); VNET_DEFINE(struct pf_fragqueue, pf_fragqueue); #define V_pf_fragqueue VNET(pf_fragqueue) VNET_DEFINE(struct pf_cachequeue, pf_cachequeue); #define V_pf_cachequeue VNET(pf_cachequeue) #else TAILQ_HEAD(pf_fragqueue, pf_fragment) pf_fragqueue; TAILQ_HEAD(pf_cachequeue, pf_fragment) pf_cachequeue; #endif #ifndef __FreeBSD__ static __inline int pf_frag_compare(struct pf_fragment *, struct pf_fragment *); #else static int pf_frag_compare(struct pf_fragment *, struct pf_fragment *); #endif #ifdef __FreeBSD__ RB_HEAD(pf_frag_tree, pf_fragment); VNET_DEFINE(struct pf_frag_tree, pf_frag_tree); #define V_pf_frag_tree VNET(pf_frag_tree) VNET_DEFINE(struct pf_frag_tree, pf_cache_tree); #define V_pf_cache_tree VNET(pf_cache_tree) #else RB_HEAD(pf_frag_tree, pf_fragment) pf_frag_tree, pf_cache_tree; #endif RB_PROTOTYPE(pf_frag_tree, pf_fragment, fr_entry, pf_frag_compare); RB_GENERATE(pf_frag_tree, pf_fragment, fr_entry, pf_frag_compare); /* Private prototypes */ void pf_ip2key(struct pf_fragment *, struct ip *); void pf_remove_fragment(struct pf_fragment *); void pf_flush_fragments(void); void pf_free_fragment(struct pf_fragment *); struct pf_fragment *pf_find_fragment(struct ip *, struct pf_frag_tree *); struct mbuf *pf_reassemble(struct mbuf **, struct pf_fragment **, struct pf_frent *, int); struct mbuf *pf_fragcache(struct mbuf **, struct ip*, struct pf_fragment **, int, int, int *); int pf_normalize_tcpopt(struct pf_rule *, struct mbuf *, struct tcphdr *, int, sa_family_t); void pf_scrub_ip(struct mbuf **, u_int32_t, u_int8_t, u_int8_t); #ifdef INET6 void pf_scrub_ip6(struct mbuf **, u_int8_t); #endif #ifdef __FreeBSD__ #define DPFPRINTF(x) do { \ if (V_pf_status.debug >= PF_DEBUG_MISC) { \ printf("%s: ", __func__); \ printf x ; \ } \ } while(0) #else #define DPFPRINTF(x) do { \ if (pf_status.debug >= PF_DEBUG_MISC) { \ printf("%s: ", __func__); \ printf x ; \ } \ } while(0) #endif /* Globals */ #ifdef __FreeBSD__ VNET_DEFINE(uma_zone_t, pf_frent_pl); VNET_DEFINE(uma_zone_t, pf_frag_pl); VNET_DEFINE(uma_zone_t, pf_cache_pl); VNET_DEFINE(uma_zone_t, pf_cent_pl); VNET_DEFINE(uma_zone_t, pf_state_scrub_pl); VNET_DEFINE(int, pf_nfrents); #define V_pf_nfrents VNET(pf_nfrents) VNET_DEFINE(int, pf_ncache); #define V_pf_ncache VNET(pf_ncache) #else struct pool pf_frent_pl, pf_frag_pl, pf_cache_pl, pf_cent_pl; struct pool pf_state_scrub_pl; int pf_nfrents, pf_ncache; #endif void pf_normalize_init(void) { #ifdef __FreeBSD__ /* * XXX * No high water mark support(It's hint not hard limit). * uma_zone_set_max(pf_frag_pl, PFFRAG_FRAG_HIWAT); */ uma_zone_set_max(V_pf_frent_pl, PFFRAG_FRENT_HIWAT); uma_zone_set_max(V_pf_cache_pl, PFFRAG_FRCACHE_HIWAT); uma_zone_set_max(V_pf_cent_pl, PFFRAG_FRCENT_HIWAT); #else pool_init(&pf_frent_pl, sizeof(struct pf_frent), 0, 0, 0, "pffrent", NULL); pool_init(&pf_frag_pl, sizeof(struct pf_fragment), 0, 0, 0, "pffrag", NULL); pool_init(&pf_cache_pl, sizeof(struct pf_fragment), 0, 0, 0, "pffrcache", NULL); pool_init(&pf_cent_pl, sizeof(struct pf_frcache), 0, 0, 0, "pffrcent", NULL); pool_init(&pf_state_scrub_pl, sizeof(struct pf_state_scrub), 0, 0, 0, "pfstscr", NULL); pool_sethiwat(&pf_frag_pl, PFFRAG_FRAG_HIWAT); pool_sethardlimit(&pf_frent_pl, PFFRAG_FRENT_HIWAT, NULL, 0); pool_sethardlimit(&pf_cache_pl, PFFRAG_FRCACHE_HIWAT, NULL, 0); pool_sethardlimit(&pf_cent_pl, PFFRAG_FRCENT_HIWAT, NULL, 0); #endif #ifdef __FreeBSD__ TAILQ_INIT(&V_pf_fragqueue); TAILQ_INIT(&V_pf_cachequeue); #else TAILQ_INIT(&pf_fragqueue); TAILQ_INIT(&pf_cachequeue); #endif } #ifdef __FreeBSD__ static int #else static __inline int #endif pf_frag_compare(struct pf_fragment *a, struct pf_fragment *b) { int diff; if ((diff = a->fr_id - b->fr_id)) return (diff); else if ((diff = a->fr_p - b->fr_p)) return (diff); else if (a->fr_src.s_addr < b->fr_src.s_addr) return (-1); else if (a->fr_src.s_addr > b->fr_src.s_addr) return (1); else if (a->fr_dst.s_addr < b->fr_dst.s_addr) return (-1); else if (a->fr_dst.s_addr > b->fr_dst.s_addr) return (1); return (0); } void pf_purge_expired_fragments(void) { struct pf_fragment *frag; #ifdef __FreeBSD__ u_int32_t expire = time_second - V_pf_default_rule.timeout[PFTM_FRAG]; #else u_int32_t expire = time_second - pf_default_rule.timeout[PFTM_FRAG]; #endif #ifdef __FreeBSD__ while ((frag = TAILQ_LAST(&V_pf_fragqueue, pf_fragqueue)) != NULL) { KASSERT((BUFFER_FRAGMENTS(frag)), ("BUFFER_FRAGMENTS(frag) == 0: %s", __FUNCTION__)); #else while ((frag = TAILQ_LAST(&pf_fragqueue, pf_fragqueue)) != NULL) { KASSERT(BUFFER_FRAGMENTS(frag)); #endif if (frag->fr_timeout > expire) break; DPFPRINTF(("expiring %d(%p)\n", frag->fr_id, frag)); pf_free_fragment(frag); } #ifdef __FreeBSD__ while ((frag = TAILQ_LAST(&V_pf_cachequeue, pf_cachequeue)) != NULL) { KASSERT((!BUFFER_FRAGMENTS(frag)), ("BUFFER_FRAGMENTS(frag) != 0: %s", __FUNCTION__)); #else while ((frag = TAILQ_LAST(&pf_cachequeue, pf_cachequeue)) != NULL) { KASSERT(!BUFFER_FRAGMENTS(frag)); #endif if (frag->fr_timeout > expire) break; DPFPRINTF(("expiring %d(%p)\n", frag->fr_id, frag)); pf_free_fragment(frag); #ifdef __FreeBSD__ KASSERT((TAILQ_EMPTY(&V_pf_cachequeue) || TAILQ_LAST(&V_pf_cachequeue, pf_cachequeue) != frag), ("!(TAILQ_EMPTY() || TAILQ_LAST() == farg): %s", __FUNCTION__)); #else KASSERT(TAILQ_EMPTY(&pf_cachequeue) || TAILQ_LAST(&pf_cachequeue, pf_cachequeue) != frag); #endif } } /* * Try to flush old fragments to make space for new ones */ void pf_flush_fragments(void) { struct pf_fragment *frag; int goal; #ifdef __FreeBSD__ goal = V_pf_nfrents * 9 / 10; DPFPRINTF(("trying to free > %d frents\n", V_pf_nfrents - goal)); while (goal < V_pf_nfrents) { #else goal = pf_nfrents * 9 / 10; DPFPRINTF(("trying to free > %d frents\n", pf_nfrents - goal)); while (goal < pf_nfrents) { #endif #ifdef __FreeBSD__ frag = TAILQ_LAST(&V_pf_fragqueue, pf_fragqueue); #else frag = TAILQ_LAST(&pf_fragqueue, pf_fragqueue); #endif if (frag == NULL) break; pf_free_fragment(frag); } #ifdef __FreeBSD__ goal = V_pf_ncache * 9 / 10; DPFPRINTF(("trying to free > %d cache entries\n", V_pf_ncache - goal)); while (goal < V_pf_ncache) { #else goal = pf_ncache * 9 / 10; DPFPRINTF(("trying to free > %d cache entries\n", pf_ncache - goal)); while (goal < pf_ncache) { #endif #ifdef __FreeBSD__ frag = TAILQ_LAST(&V_pf_cachequeue, pf_cachequeue); #else frag = TAILQ_LAST(&pf_cachequeue, pf_cachequeue); #endif if (frag == NULL) break; pf_free_fragment(frag); } } /* Frees the fragments and all associated entries */ void pf_free_fragment(struct pf_fragment *frag) { struct pf_frent *frent; struct pf_frcache *frcache; /* Free all fragments */ if (BUFFER_FRAGMENTS(frag)) { for (frent = LIST_FIRST(&frag->fr_queue); frent; frent = LIST_FIRST(&frag->fr_queue)) { LIST_REMOVE(frent, fr_next); m_freem(frent->fr_m); #ifdef __FreeBSD__ pool_put(&V_pf_frent_pl, frent); V_pf_nfrents--; #else pool_put(&pf_frent_pl, frent); pf_nfrents--; #endif } } else { for (frcache = LIST_FIRST(&frag->fr_cache); frcache; frcache = LIST_FIRST(&frag->fr_cache)) { LIST_REMOVE(frcache, fr_next); #ifdef __FreeBSD__ KASSERT((LIST_EMPTY(&frag->fr_cache) || LIST_FIRST(&frag->fr_cache)->fr_off > frcache->fr_end), ("! (LIST_EMPTY() || LIST_FIRST()->fr_off >" " frcache->fr_end): %s", __FUNCTION__)); pool_put(&V_pf_cent_pl, frcache); V_pf_ncache--; #else KASSERT(LIST_EMPTY(&frag->fr_cache) || LIST_FIRST(&frag->fr_cache)->fr_off > frcache->fr_end); pool_put(&pf_cent_pl, frcache); pf_ncache--; #endif } } pf_remove_fragment(frag); } void pf_ip2key(struct pf_fragment *key, struct ip *ip) { key->fr_p = ip->ip_p; key->fr_id = ip->ip_id; key->fr_src.s_addr = ip->ip_src.s_addr; key->fr_dst.s_addr = ip->ip_dst.s_addr; } struct pf_fragment * pf_find_fragment(struct ip *ip, struct pf_frag_tree *tree) { struct pf_fragment key; struct pf_fragment *frag; pf_ip2key(&key, ip); frag = RB_FIND(pf_frag_tree, tree, &key); if (frag != NULL) { /* XXX Are we sure we want to update the timeout? */ frag->fr_timeout = time_second; if (BUFFER_FRAGMENTS(frag)) { #ifdef __FreeBSD__ TAILQ_REMOVE(&V_pf_fragqueue, frag, frag_next); TAILQ_INSERT_HEAD(&V_pf_fragqueue, frag, frag_next); #else TAILQ_REMOVE(&pf_fragqueue, frag, frag_next); TAILQ_INSERT_HEAD(&pf_fragqueue, frag, frag_next); #endif } else { #ifdef __FreeBSD__ TAILQ_REMOVE(&V_pf_cachequeue, frag, frag_next); TAILQ_INSERT_HEAD(&V_pf_cachequeue, frag, frag_next); #else TAILQ_REMOVE(&pf_cachequeue, frag, frag_next); TAILQ_INSERT_HEAD(&pf_cachequeue, frag, frag_next); #endif } } return (frag); } /* Removes a fragment from the fragment queue and frees the fragment */ void pf_remove_fragment(struct pf_fragment *frag) { if (BUFFER_FRAGMENTS(frag)) { #ifdef __FreeBSD__ RB_REMOVE(pf_frag_tree, &V_pf_frag_tree, frag); TAILQ_REMOVE(&V_pf_fragqueue, frag, frag_next); pool_put(&V_pf_frag_pl, frag); #else RB_REMOVE(pf_frag_tree, &pf_frag_tree, frag); TAILQ_REMOVE(&pf_fragqueue, frag, frag_next); pool_put(&pf_frag_pl, frag); #endif } else { #ifdef __FreeBSD__ RB_REMOVE(pf_frag_tree, &V_pf_cache_tree, frag); TAILQ_REMOVE(&V_pf_cachequeue, frag, frag_next); pool_put(&V_pf_cache_pl, frag); #else RB_REMOVE(pf_frag_tree, &pf_cache_tree, frag); TAILQ_REMOVE(&pf_cachequeue, frag, frag_next); pool_put(&pf_cache_pl, frag); #endif } } #define FR_IP_OFF(fr) ((ntohs((fr)->fr_ip->ip_off) & IP_OFFMASK) << 3) struct mbuf * pf_reassemble(struct mbuf **m0, struct pf_fragment **frag, struct pf_frent *frent, int mff) { struct mbuf *m = *m0, *m2; struct pf_frent *frea, *next; struct pf_frent *frep = NULL; struct ip *ip = frent->fr_ip; int hlen = ip->ip_hl << 2; u_int16_t off = (ntohs(ip->ip_off) & IP_OFFMASK) << 3; u_int16_t ip_len = ntohs(ip->ip_len) - ip->ip_hl * 4; u_int16_t max = ip_len + off; #ifdef __FreeBSD__ KASSERT((*frag == NULL || BUFFER_FRAGMENTS(*frag)), ("! (*frag == NULL || BUFFER_FRAGMENTS(*frag)): %s", __FUNCTION__)); #else KASSERT(*frag == NULL || BUFFER_FRAGMENTS(*frag)); #endif /* Strip off ip header */ m->m_data += hlen; m->m_len -= hlen; /* Create a new reassembly queue for this packet */ if (*frag == NULL) { #ifdef __FreeBSD__ *frag = pool_get(&V_pf_frag_pl, PR_NOWAIT); #else *frag = pool_get(&pf_frag_pl, PR_NOWAIT); #endif if (*frag == NULL) { pf_flush_fragments(); #ifdef __FreeBSD__ *frag = pool_get(&V_pf_frag_pl, PR_NOWAIT); #else *frag = pool_get(&pf_frag_pl, PR_NOWAIT); #endif if (*frag == NULL) goto drop_fragment; } (*frag)->fr_flags = 0; (*frag)->fr_max = 0; (*frag)->fr_src = frent->fr_ip->ip_src; (*frag)->fr_dst = frent->fr_ip->ip_dst; (*frag)->fr_p = frent->fr_ip->ip_p; (*frag)->fr_id = frent->fr_ip->ip_id; (*frag)->fr_timeout = time_second; LIST_INIT(&(*frag)->fr_queue); #ifdef __FreeBSD__ RB_INSERT(pf_frag_tree, &V_pf_frag_tree, *frag); TAILQ_INSERT_HEAD(&V_pf_fragqueue, *frag, frag_next); #else RB_INSERT(pf_frag_tree, &pf_frag_tree, *frag); TAILQ_INSERT_HEAD(&pf_fragqueue, *frag, frag_next); #endif /* We do not have a previous fragment */ frep = NULL; goto insert; } /* * Find a fragment after the current one: * - off contains the real shifted offset. */ LIST_FOREACH(frea, &(*frag)->fr_queue, fr_next) { if (FR_IP_OFF(frea) > off) break; frep = frea; } #ifdef __FreeBSD__ KASSERT((frep != NULL || frea != NULL), ("!(frep != NULL || frea != NULL): %s", __FUNCTION__));; #else KASSERT(frep != NULL || frea != NULL); #endif if (frep != NULL && FR_IP_OFF(frep) + ntohs(frep->fr_ip->ip_len) - frep->fr_ip->ip_hl * 4 > off) { u_int16_t precut; precut = FR_IP_OFF(frep) + ntohs(frep->fr_ip->ip_len) - frep->fr_ip->ip_hl * 4 - off; if (precut >= ip_len) goto drop_fragment; m_adj(frent->fr_m, precut); DPFPRINTF(("overlap -%d\n", precut)); /* Enforce 8 byte boundaries */ ip->ip_off = htons(ntohs(ip->ip_off) + (precut >> 3)); off = (ntohs(ip->ip_off) & IP_OFFMASK) << 3; ip_len -= precut; ip->ip_len = htons(ip_len); } for (; frea != NULL && ip_len + off > FR_IP_OFF(frea); frea = next) { u_int16_t aftercut; aftercut = ip_len + off - FR_IP_OFF(frea); DPFPRINTF(("adjust overlap %d\n", aftercut)); if (aftercut < ntohs(frea->fr_ip->ip_len) - frea->fr_ip->ip_hl * 4) { frea->fr_ip->ip_len = htons(ntohs(frea->fr_ip->ip_len) - aftercut); frea->fr_ip->ip_off = htons(ntohs(frea->fr_ip->ip_off) + (aftercut >> 3)); m_adj(frea->fr_m, aftercut); break; } /* This fragment is completely overlapped, lose it */ next = LIST_NEXT(frea, fr_next); m_freem(frea->fr_m); LIST_REMOVE(frea, fr_next); #ifdef __FreeBSD__ pool_put(&V_pf_frent_pl, frea); V_pf_nfrents--; #else pool_put(&pf_frent_pl, frea); pf_nfrents--; #endif } insert: /* Update maximum data size */ if ((*frag)->fr_max < max) (*frag)->fr_max = max; /* This is the last segment */ if (!mff) (*frag)->fr_flags |= PFFRAG_SEENLAST; if (frep == NULL) LIST_INSERT_HEAD(&(*frag)->fr_queue, frent, fr_next); else LIST_INSERT_AFTER(frep, frent, fr_next); /* Check if we are completely reassembled */ if (!((*frag)->fr_flags & PFFRAG_SEENLAST)) return (NULL); /* Check if we have all the data */ off = 0; for (frep = LIST_FIRST(&(*frag)->fr_queue); frep; frep = next) { next = LIST_NEXT(frep, fr_next); off += ntohs(frep->fr_ip->ip_len) - frep->fr_ip->ip_hl * 4; if (off < (*frag)->fr_max && (next == NULL || FR_IP_OFF(next) != off)) { DPFPRINTF(("missing fragment at %d, next %d, max %d\n", off, next == NULL ? -1 : FR_IP_OFF(next), (*frag)->fr_max)); return (NULL); } } DPFPRINTF(("%d < %d?\n", off, (*frag)->fr_max)); if (off < (*frag)->fr_max) return (NULL); /* We have all the data */ frent = LIST_FIRST(&(*frag)->fr_queue); #ifdef __FreeBSD__ KASSERT((frent != NULL), ("frent == NULL: %s", __FUNCTION__)); #else KASSERT(frent != NULL); #endif if ((frent->fr_ip->ip_hl << 2) + off > IP_MAXPACKET) { DPFPRINTF(("drop: too big: %d\n", off)); pf_free_fragment(*frag); *frag = NULL; return (NULL); } next = LIST_NEXT(frent, fr_next); /* Magic from ip_input */ ip = frent->fr_ip; m = frent->fr_m; m2 = m->m_next; m->m_next = NULL; m_cat(m, m2); #ifdef __FreeBSD__ pool_put(&V_pf_frent_pl, frent); V_pf_nfrents--; #else pool_put(&pf_frent_pl, frent); pf_nfrents--; #endif for (frent = next; frent != NULL; frent = next) { next = LIST_NEXT(frent, fr_next); m2 = frent->fr_m; #ifdef __FreeBSD__ pool_put(&V_pf_frent_pl, frent); V_pf_nfrents--; #else pool_put(&pf_frent_pl, frent); pf_nfrents--; #endif #ifdef __FreeBSD__ m->m_pkthdr.csum_flags &= m2->m_pkthdr.csum_flags; m->m_pkthdr.csum_data += m2->m_pkthdr.csum_data; #endif m_cat(m, m2); } #ifdef __FreeBSD__ while (m->m_pkthdr.csum_data & 0xffff0000) m->m_pkthdr.csum_data = (m->m_pkthdr.csum_data & 0xffff) + (m->m_pkthdr.csum_data >> 16); #endif ip->ip_src = (*frag)->fr_src; ip->ip_dst = (*frag)->fr_dst; /* Remove from fragment queue */ pf_remove_fragment(*frag); *frag = NULL; hlen = ip->ip_hl << 2; ip->ip_len = htons(off + hlen); m->m_len += hlen; m->m_data -= hlen; /* some debugging cruft by sklower, below, will go away soon */ /* XXX this should be done elsewhere */ if (m->m_flags & M_PKTHDR) { int plen = 0; for (m2 = m; m2; m2 = m2->m_next) plen += m2->m_len; m->m_pkthdr.len = plen; } DPFPRINTF(("complete: %p(%d)\n", m, ntohs(ip->ip_len))); return (m); drop_fragment: /* Oops - fail safe - drop packet */ #ifdef __FreeBSD__ pool_put(&V_pf_frent_pl, frent); V_pf_nfrents--; #else pool_put(&pf_frent_pl, frent); pf_nfrents--; #endif m_freem(m); return (NULL); } struct mbuf * pf_fragcache(struct mbuf **m0, struct ip *h, struct pf_fragment **frag, int mff, int drop, int *nomem) { struct mbuf *m = *m0; struct pf_frcache *frp, *fra, *cur = NULL; int ip_len = ntohs(h->ip_len) - (h->ip_hl << 2); u_int16_t off = ntohs(h->ip_off) << 3; u_int16_t max = ip_len + off; int hosed = 0; #ifdef __FreeBSD__ KASSERT((*frag == NULL || !BUFFER_FRAGMENTS(*frag)), ("!(*frag == NULL || !BUFFER_FRAGMENTS(*frag)): %s", __FUNCTION__)); #else KASSERT(*frag == NULL || !BUFFER_FRAGMENTS(*frag)); #endif /* Create a new range queue for this packet */ if (*frag == NULL) { #ifdef __FreeBSD__ *frag = pool_get(&V_pf_cache_pl, PR_NOWAIT); #else *frag = pool_get(&pf_cache_pl, PR_NOWAIT); #endif if (*frag == NULL) { pf_flush_fragments(); #ifdef __FreeBSD__ *frag = pool_get(&V_pf_cache_pl, PR_NOWAIT); #else *frag = pool_get(&pf_cache_pl, PR_NOWAIT); #endif if (*frag == NULL) goto no_mem; } /* Get an entry for the queue */ #ifdef __FreeBSD__ cur = pool_get(&V_pf_cent_pl, PR_NOWAIT); if (cur == NULL) { pool_put(&V_pf_cache_pl, *frag); #else cur = pool_get(&pf_cent_pl, PR_NOWAIT); if (cur == NULL) { pool_put(&pf_cache_pl, *frag); #endif *frag = NULL; goto no_mem; } #ifdef __FreeBSD__ V_pf_ncache++; #else pf_ncache++; #endif (*frag)->fr_flags = PFFRAG_NOBUFFER; (*frag)->fr_max = 0; (*frag)->fr_src = h->ip_src; (*frag)->fr_dst = h->ip_dst; (*frag)->fr_p = h->ip_p; (*frag)->fr_id = h->ip_id; (*frag)->fr_timeout = time_second; cur->fr_off = off; cur->fr_end = max; LIST_INIT(&(*frag)->fr_cache); LIST_INSERT_HEAD(&(*frag)->fr_cache, cur, fr_next); #ifdef __FreeBSD__ RB_INSERT(pf_frag_tree, &V_pf_cache_tree, *frag); TAILQ_INSERT_HEAD(&V_pf_cachequeue, *frag, frag_next); #else RB_INSERT(pf_frag_tree, &pf_cache_tree, *frag); TAILQ_INSERT_HEAD(&pf_cachequeue, *frag, frag_next); #endif DPFPRINTF(("fragcache[%d]: new %d-%d\n", h->ip_id, off, max)); goto pass; } /* * Find a fragment after the current one: * - off contains the real shifted offset. */ frp = NULL; LIST_FOREACH(fra, &(*frag)->fr_cache, fr_next) { if (fra->fr_off > off) break; frp = fra; } #ifdef __FreeBSD__ KASSERT((frp != NULL || fra != NULL), ("!(frp != NULL || fra != NULL): %s", __FUNCTION__)); #else KASSERT(frp != NULL || fra != NULL); #endif if (frp != NULL) { int precut; precut = frp->fr_end - off; if (precut >= ip_len) { /* Fragment is entirely a duplicate */ DPFPRINTF(("fragcache[%d]: dead (%d-%d) %d-%d\n", h->ip_id, frp->fr_off, frp->fr_end, off, max)); goto drop_fragment; } if (precut == 0) { /* They are adjacent. Fixup cache entry */ DPFPRINTF(("fragcache[%d]: adjacent (%d-%d) %d-%d\n", h->ip_id, frp->fr_off, frp->fr_end, off, max)); frp->fr_end = max; } else if (precut > 0) { /* The first part of this payload overlaps with a * fragment that has already been passed. * Need to trim off the first part of the payload. * But to do so easily, we need to create another * mbuf to throw the original header into. */ DPFPRINTF(("fragcache[%d]: chop %d (%d-%d) %d-%d\n", h->ip_id, precut, frp->fr_off, frp->fr_end, off, max)); off += precut; max -= precut; /* Update the previous frag to encompass this one */ frp->fr_end = max; if (!drop) { /* XXX Optimization opportunity * This is a very heavy way to trim the payload. * we could do it much faster by diddling mbuf * internals but that would be even less legible * than this mbuf magic. For my next trick, * I'll pull a rabbit out of my laptop. */ #ifdef __FreeBSD__ *m0 = m_dup(m, M_DONTWAIT); #else *m0 = m_copym2(m, 0, h->ip_hl << 2, M_NOWAIT); #endif if (*m0 == NULL) goto no_mem; #ifdef __FreeBSD__ /* From KAME Project : We have missed this! */ m_adj(*m0, (h->ip_hl << 2) - (*m0)->m_pkthdr.len); KASSERT(((*m0)->m_next == NULL), ("(*m0)->m_next != NULL: %s", __FUNCTION__)); #else KASSERT((*m0)->m_next == NULL); #endif m_adj(m, precut + (h->ip_hl << 2)); m_cat(*m0, m); m = *m0; if (m->m_flags & M_PKTHDR) { int plen = 0; struct mbuf *t; for (t = m; t; t = t->m_next) plen += t->m_len; m->m_pkthdr.len = plen; } h = mtod(m, struct ip *); #ifdef __FreeBSD__ KASSERT(((int)m->m_len == ntohs(h->ip_len) - precut), ("m->m_len != ntohs(h->ip_len) - precut: %s", __FUNCTION__)); #else KASSERT((int)m->m_len == ntohs(h->ip_len) - precut); #endif h->ip_off = htons(ntohs(h->ip_off) + (precut >> 3)); h->ip_len = htons(ntohs(h->ip_len) - precut); } else { hosed++; } } else { /* There is a gap between fragments */ DPFPRINTF(("fragcache[%d]: gap %d (%d-%d) %d-%d\n", h->ip_id, -precut, frp->fr_off, frp->fr_end, off, max)); #ifdef __FreeBSD__ cur = pool_get(&V_pf_cent_pl, PR_NOWAIT); #else cur = pool_get(&pf_cent_pl, PR_NOWAIT); #endif if (cur == NULL) goto no_mem; #ifdef __FreeBSD__ V_pf_ncache++; #else pf_ncache++; #endif cur->fr_off = off; cur->fr_end = max; LIST_INSERT_AFTER(frp, cur, fr_next); } } if (fra != NULL) { int aftercut; int merge = 0; aftercut = max - fra->fr_off; if (aftercut == 0) { /* Adjacent fragments */ DPFPRINTF(("fragcache[%d]: adjacent %d-%d (%d-%d)\n", h->ip_id, off, max, fra->fr_off, fra->fr_end)); fra->fr_off = off; merge = 1; } else if (aftercut > 0) { /* Need to chop off the tail of this fragment */ DPFPRINTF(("fragcache[%d]: chop %d %d-%d (%d-%d)\n", h->ip_id, aftercut, off, max, fra->fr_off, fra->fr_end)); fra->fr_off = off; max -= aftercut; merge = 1; if (!drop) { m_adj(m, -aftercut); if (m->m_flags & M_PKTHDR) { int plen = 0; struct mbuf *t; for (t = m; t; t = t->m_next) plen += t->m_len; m->m_pkthdr.len = plen; } h = mtod(m, struct ip *); #ifdef __FreeBSD__ KASSERT(((int)m->m_len == ntohs(h->ip_len) - aftercut), ("m->m_len != ntohs(h->ip_len) - aftercut: %s", __FUNCTION__)); #else KASSERT((int)m->m_len == ntohs(h->ip_len) - aftercut); #endif h->ip_len = htons(ntohs(h->ip_len) - aftercut); } else { hosed++; } } else if (frp == NULL) { /* There is a gap between fragments */ DPFPRINTF(("fragcache[%d]: gap %d %d-%d (%d-%d)\n", h->ip_id, -aftercut, off, max, fra->fr_off, fra->fr_end)); #ifdef __FreeBSD__ cur = pool_get(&V_pf_cent_pl, PR_NOWAIT); #else cur = pool_get(&pf_cent_pl, PR_NOWAIT); #endif if (cur == NULL) goto no_mem; #ifdef __FreeBSD__ V_pf_ncache++; #else pf_ncache++; #endif cur->fr_off = off; cur->fr_end = max; LIST_INSERT_BEFORE(fra, cur, fr_next); } /* Need to glue together two separate fragment descriptors */ if (merge) { if (cur && fra->fr_off <= cur->fr_end) { /* Need to merge in a previous 'cur' */ DPFPRINTF(("fragcache[%d]: adjacent(merge " "%d-%d) %d-%d (%d-%d)\n", h->ip_id, cur->fr_off, cur->fr_end, off, max, fra->fr_off, fra->fr_end)); fra->fr_off = cur->fr_off; LIST_REMOVE(cur, fr_next); #ifdef __FreeBSD__ pool_put(&V_pf_cent_pl, cur); V_pf_ncache--; #else pool_put(&pf_cent_pl, cur); pf_ncache--; #endif cur = NULL; } else if (frp && fra->fr_off <= frp->fr_end) { /* Need to merge in a modified 'frp' */ #ifdef __FreeBSD__ KASSERT((cur == NULL), ("cur != NULL: %s", __FUNCTION__)); #else KASSERT(cur == NULL); #endif DPFPRINTF(("fragcache[%d]: adjacent(merge " "%d-%d) %d-%d (%d-%d)\n", h->ip_id, frp->fr_off, frp->fr_end, off, max, fra->fr_off, fra->fr_end)); fra->fr_off = frp->fr_off; LIST_REMOVE(frp, fr_next); #ifdef __FreeBSD__ pool_put(&V_pf_cent_pl, frp); V_pf_ncache--; #else pool_put(&pf_cent_pl, frp); pf_ncache--; #endif frp = NULL; } } } if (hosed) { /* * We must keep tracking the overall fragment even when * we're going to drop it anyway so that we know when to * free the overall descriptor. Thus we drop the frag late. */ goto drop_fragment; } pass: /* Update maximum data size */ if ((*frag)->fr_max < max) (*frag)->fr_max = max; /* This is the last segment */ if (!mff) (*frag)->fr_flags |= PFFRAG_SEENLAST; /* Check if we are completely reassembled */ if (((*frag)->fr_flags & PFFRAG_SEENLAST) && LIST_FIRST(&(*frag)->fr_cache)->fr_off == 0 && LIST_FIRST(&(*frag)->fr_cache)->fr_end == (*frag)->fr_max) { /* Remove from fragment queue */ DPFPRINTF(("fragcache[%d]: done 0-%d\n", h->ip_id, (*frag)->fr_max)); pf_free_fragment(*frag); *frag = NULL; } return (m); no_mem: *nomem = 1; /* Still need to pay attention to !IP_MF */ if (!mff && *frag != NULL) (*frag)->fr_flags |= PFFRAG_SEENLAST; m_freem(m); return (NULL); drop_fragment: /* Still need to pay attention to !IP_MF */ if (!mff && *frag != NULL) (*frag)->fr_flags |= PFFRAG_SEENLAST; if (drop) { /* This fragment has been deemed bad. Don't reass */ if (((*frag)->fr_flags & PFFRAG_DROP) == 0) DPFPRINTF(("fragcache[%d]: dropping overall fragment\n", h->ip_id)); (*frag)->fr_flags |= PFFRAG_DROP; } m_freem(m); return (NULL); } #ifdef INET int pf_normalize_ip(struct mbuf **m0, int dir, struct pfi_kif *kif, u_short *reason, struct pf_pdesc *pd) { struct mbuf *m = *m0; struct pf_rule *r; struct pf_frent *frent; struct pf_fragment *frag = NULL; struct ip *h = mtod(m, struct ip *); int mff = (ntohs(h->ip_off) & IP_MF); int hlen = h->ip_hl << 2; u_int16_t fragoff = (ntohs(h->ip_off) & IP_OFFMASK) << 3; u_int16_t max; int ip_len; int ip_off; int tag = -1; r = TAILQ_FIRST(pf_main_ruleset.rules[PF_RULESET_SCRUB].active.ptr); while (r != NULL) { r->evaluations++; if (pfi_kif_match(r->kif, kif) == r->ifnot) r = r->skip[PF_SKIP_IFP].ptr; else if (r->direction && r->direction != dir) r = r->skip[PF_SKIP_DIR].ptr; else if (r->af && r->af != AF_INET) r = r->skip[PF_SKIP_AF].ptr; else if (r->proto && r->proto != h->ip_p) r = r->skip[PF_SKIP_PROTO].ptr; else if (PF_MISMATCHAW(&r->src.addr, (struct pf_addr *)&h->ip_src.s_addr, AF_INET, r->src.neg, kif, M_GETFIB(m))) r = r->skip[PF_SKIP_SRC_ADDR].ptr; else if (PF_MISMATCHAW(&r->dst.addr, (struct pf_addr *)&h->ip_dst.s_addr, AF_INET, r->dst.neg, NULL, M_GETFIB(m))) r = r->skip[PF_SKIP_DST_ADDR].ptr; #ifdef __FreeBSD__ else if (r->match_tag && !pf_match_tag(m, r, &tag, pd->pf_mtag)) #else else if (r->match_tag && !pf_match_tag(m, r, &tag)) #endif r = TAILQ_NEXT(r, entries); else break; } if (r == NULL || r->action == PF_NOSCRUB) return (PF_PASS); else { r->packets[dir == PF_OUT]++; r->bytes[dir == PF_OUT] += pd->tot_len; } /* Check for illegal packets */ if (hlen < (int)sizeof(struct ip)) goto drop; if (hlen > ntohs(h->ip_len)) goto drop; /* Clear IP_DF if the rule uses the no-df option */ if (r->rule_flag & PFRULE_NODF && h->ip_off & htons(IP_DF)) { u_int16_t ip_off = h->ip_off; h->ip_off &= htons(~IP_DF); h->ip_sum = pf_cksum_fixup(h->ip_sum, ip_off, h->ip_off, 0); } /* We will need other tests here */ if (!fragoff && !mff) goto no_fragment; /* We're dealing with a fragment now. Don't allow fragments * with IP_DF to enter the cache. If the flag was cleared by * no-df above, fine. Otherwise drop it. */ if (h->ip_off & htons(IP_DF)) { DPFPRINTF(("IP_DF\n")); goto bad; } ip_len = ntohs(h->ip_len) - hlen; ip_off = (ntohs(h->ip_off) & IP_OFFMASK) << 3; /* All fragments are 8 byte aligned */ if (mff && (ip_len & 0x7)) { DPFPRINTF(("mff and %d\n", ip_len)); goto bad; } /* Respect maximum length */ if (fragoff + ip_len > IP_MAXPACKET) { DPFPRINTF(("max packet %d\n", fragoff + ip_len)); goto bad; } max = fragoff + ip_len; if ((r->rule_flag & (PFRULE_FRAGCROP|PFRULE_FRAGDROP)) == 0) { /* Fully buffer all of the fragments */ #ifdef __FreeBSD__ frag = pf_find_fragment(h, &V_pf_frag_tree); #else frag = pf_find_fragment(h, &pf_frag_tree); #endif /* Check if we saw the last fragment already */ if (frag != NULL && (frag->fr_flags & PFFRAG_SEENLAST) && max > frag->fr_max) goto bad; /* Get an entry for the fragment queue */ #ifdef __FreeBSD__ frent = pool_get(&V_pf_frent_pl, PR_NOWAIT); #else frent = pool_get(&pf_frent_pl, PR_NOWAIT); #endif if (frent == NULL) { REASON_SET(reason, PFRES_MEMORY); return (PF_DROP); } #ifdef __FreeBSD__ V_pf_nfrents++; #else pf_nfrents++; #endif frent->fr_ip = h; frent->fr_m = m; /* Might return a completely reassembled mbuf, or NULL */ DPFPRINTF(("reass frag %d @ %d-%d\n", h->ip_id, fragoff, max)); *m0 = m = pf_reassemble(m0, &frag, frent, mff); if (m == NULL) return (PF_DROP); /* use mtag from concatenated mbuf chain */ pd->pf_mtag = pf_find_mtag(m); #ifdef DIAGNOSTIC if (pd->pf_mtag == NULL) { printf("%s: pf_find_mtag returned NULL(1)\n", __func__); if ((pd->pf_mtag = pf_get_mtag(m)) == NULL) { m_freem(m); *m0 = NULL; goto no_mem; } } #endif if (frag != NULL && (frag->fr_flags & PFFRAG_DROP)) goto drop; h = mtod(m, struct ip *); } else { /* non-buffering fragment cache (drops or masks overlaps) */ int nomem = 0; #ifdef __FreeBSD__ if (dir == PF_OUT && pd->pf_mtag->flags & PF_TAG_FRAGCACHE) { #else if (dir == PF_OUT && m->m_pkthdr.pf.flags & PF_TAG_FRAGCACHE) { #endif /* * Already passed the fragment cache in the * input direction. If we continued, it would * appear to be a dup and would be dropped. */ goto fragment_pass; } #ifdef __FreeBSD__ frag = pf_find_fragment(h, &V_pf_cache_tree); #else frag = pf_find_fragment(h, &pf_cache_tree); #endif /* Check if we saw the last fragment already */ if (frag != NULL && (frag->fr_flags & PFFRAG_SEENLAST) && max > frag->fr_max) { if (r->rule_flag & PFRULE_FRAGDROP) frag->fr_flags |= PFFRAG_DROP; goto bad; } *m0 = m = pf_fragcache(m0, h, &frag, mff, (r->rule_flag & PFRULE_FRAGDROP) ? 1 : 0, &nomem); if (m == NULL) { if (nomem) goto no_mem; goto drop; } /* use mtag from copied and trimmed mbuf chain */ pd->pf_mtag = pf_find_mtag(m); #ifdef DIAGNOSTIC if (pd->pf_mtag == NULL) { printf("%s: pf_find_mtag returned NULL(2)\n", __func__); if ((pd->pf_mtag = pf_get_mtag(m)) == NULL) { m_freem(m); *m0 = NULL; goto no_mem; } } #endif if (dir == PF_IN) #ifdef __FreeBSD__ pd->pf_mtag->flags |= PF_TAG_FRAGCACHE; #else m->m_pkthdr.pf.flags |= PF_TAG_FRAGCACHE; #endif if (frag != NULL && (frag->fr_flags & PFFRAG_DROP)) goto drop; goto fragment_pass; } no_fragment: /* At this point, only IP_DF is allowed in ip_off */ if (h->ip_off & ~htons(IP_DF)) { u_int16_t ip_off = h->ip_off; h->ip_off &= htons(IP_DF); h->ip_sum = pf_cksum_fixup(h->ip_sum, ip_off, h->ip_off, 0); } /* not missing a return here */ fragment_pass: pf_scrub_ip(&m, r->rule_flag, r->min_ttl, r->set_tos); if ((r->rule_flag & (PFRULE_FRAGCROP|PFRULE_FRAGDROP)) == 0) pd->flags |= PFDESC_IP_REAS; return (PF_PASS); no_mem: REASON_SET(reason, PFRES_MEMORY); if (r != NULL && r->log) PFLOG_PACKET(kif, h, m, AF_INET, dir, *reason, r, NULL, NULL, pd); return (PF_DROP); drop: REASON_SET(reason, PFRES_NORM); if (r != NULL && r->log) PFLOG_PACKET(kif, h, m, AF_INET, dir, *reason, r, NULL, NULL, pd); return (PF_DROP); bad: DPFPRINTF(("dropping bad fragment\n")); /* Free associated fragments */ if (frag != NULL) pf_free_fragment(frag); REASON_SET(reason, PFRES_FRAG); if (r != NULL && r->log) PFLOG_PACKET(kif, h, m, AF_INET, dir, *reason, r, NULL, NULL, pd); return (PF_DROP); } #endif #ifdef INET6 int pf_normalize_ip6(struct mbuf **m0, int dir, struct pfi_kif *kif, u_short *reason, struct pf_pdesc *pd) { struct mbuf *m = *m0; struct pf_rule *r; struct ip6_hdr *h = mtod(m, struct ip6_hdr *); int off; struct ip6_ext ext; struct ip6_opt opt; struct ip6_opt_jumbo jumbo; struct ip6_frag frag; u_int32_t jumbolen = 0, plen; u_int16_t fragoff = 0; int optend; int ooff; u_int8_t proto; int terminal; r = TAILQ_FIRST(pf_main_ruleset.rules[PF_RULESET_SCRUB].active.ptr); while (r != NULL) { r->evaluations++; if (pfi_kif_match(r->kif, kif) == r->ifnot) r = r->skip[PF_SKIP_IFP].ptr; else if (r->direction && r->direction != dir) r = r->skip[PF_SKIP_DIR].ptr; else if (r->af && r->af != AF_INET6) r = r->skip[PF_SKIP_AF].ptr; #if 0 /* header chain! */ else if (r->proto && r->proto != h->ip6_nxt) r = r->skip[PF_SKIP_PROTO].ptr; #endif else if (PF_MISMATCHAW(&r->src.addr, (struct pf_addr *)&h->ip6_src, AF_INET6, r->src.neg, kif, M_GETFIB(m))) r = r->skip[PF_SKIP_SRC_ADDR].ptr; else if (PF_MISMATCHAW(&r->dst.addr, (struct pf_addr *)&h->ip6_dst, AF_INET6, r->dst.neg, NULL, M_GETFIB(m))) r = r->skip[PF_SKIP_DST_ADDR].ptr; else break; } if (r == NULL || r->action == PF_NOSCRUB) return (PF_PASS); else { r->packets[dir == PF_OUT]++; r->bytes[dir == PF_OUT] += pd->tot_len; } /* Check for illegal packets */ if (sizeof(struct ip6_hdr) + IPV6_MAXPACKET < m->m_pkthdr.len) goto drop; off = sizeof(struct ip6_hdr); proto = h->ip6_nxt; terminal = 0; do { switch (proto) { case IPPROTO_FRAGMENT: goto fragment; break; case IPPROTO_AH: case IPPROTO_ROUTING: case IPPROTO_DSTOPTS: if (!pf_pull_hdr(m, off, &ext, sizeof(ext), NULL, NULL, AF_INET6)) goto shortpkt; if (proto == IPPROTO_AH) off += (ext.ip6e_len + 2) * 4; else off += (ext.ip6e_len + 1) * 8; proto = ext.ip6e_nxt; break; case IPPROTO_HOPOPTS: if (!pf_pull_hdr(m, off, &ext, sizeof(ext), NULL, NULL, AF_INET6)) goto shortpkt; optend = off + (ext.ip6e_len + 1) * 8; ooff = off + sizeof(ext); do { if (!pf_pull_hdr(m, ooff, &opt.ip6o_type, sizeof(opt.ip6o_type), NULL, NULL, AF_INET6)) goto shortpkt; if (opt.ip6o_type == IP6OPT_PAD1) { ooff++; continue; } if (!pf_pull_hdr(m, ooff, &opt, sizeof(opt), NULL, NULL, AF_INET6)) goto shortpkt; if (ooff + sizeof(opt) + opt.ip6o_len > optend) goto drop; switch (opt.ip6o_type) { case IP6OPT_JUMBO: if (h->ip6_plen != 0) goto drop; if (!pf_pull_hdr(m, ooff, &jumbo, sizeof(jumbo), NULL, NULL, AF_INET6)) goto shortpkt; memcpy(&jumbolen, jumbo.ip6oj_jumbo_len, sizeof(jumbolen)); jumbolen = ntohl(jumbolen); if (jumbolen <= IPV6_MAXPACKET) goto drop; if (sizeof(struct ip6_hdr) + jumbolen != m->m_pkthdr.len) goto drop; break; default: break; } ooff += sizeof(opt) + opt.ip6o_len; } while (ooff < optend); off = optend; proto = ext.ip6e_nxt; break; default: terminal = 1; break; } } while (!terminal); /* jumbo payload option must be present, or plen > 0 */ if (ntohs(h->ip6_plen) == 0) plen = jumbolen; else plen = ntohs(h->ip6_plen); if (plen == 0) goto drop; if (sizeof(struct ip6_hdr) + plen > m->m_pkthdr.len) goto shortpkt; pf_scrub_ip6(&m, r->min_ttl); return (PF_PASS); fragment: if (ntohs(h->ip6_plen) == 0 || jumbolen) goto drop; plen = ntohs(h->ip6_plen); if (!pf_pull_hdr(m, off, &frag, sizeof(frag), NULL, NULL, AF_INET6)) goto shortpkt; fragoff = ntohs(frag.ip6f_offlg & IP6F_OFF_MASK); if (fragoff + (plen - off - sizeof(frag)) > IPV6_MAXPACKET) goto badfrag; /* do something about it */ /* remember to set pd->flags |= PFDESC_IP_REAS */ return (PF_PASS); shortpkt: REASON_SET(reason, PFRES_SHORT); if (r != NULL && r->log) PFLOG_PACKET(kif, h, m, AF_INET6, dir, *reason, r, NULL, NULL, pd); return (PF_DROP); drop: REASON_SET(reason, PFRES_NORM); if (r != NULL && r->log) PFLOG_PACKET(kif, h, m, AF_INET6, dir, *reason, r, NULL, NULL, pd); return (PF_DROP); badfrag: REASON_SET(reason, PFRES_FRAG); if (r != NULL && r->log) PFLOG_PACKET(kif, h, m, AF_INET6, dir, *reason, r, NULL, NULL, pd); return (PF_DROP); } #endif /* INET6 */ int pf_normalize_tcp(int dir, struct pfi_kif *kif, struct mbuf *m, int ipoff, int off, void *h, struct pf_pdesc *pd) { struct pf_rule *r, *rm = NULL; struct tcphdr *th = pd->hdr.tcp; int rewrite = 0; u_short reason; u_int8_t flags; sa_family_t af = pd->af; r = TAILQ_FIRST(pf_main_ruleset.rules[PF_RULESET_SCRUB].active.ptr); while (r != NULL) { r->evaluations++; if (pfi_kif_match(r->kif, kif) == r->ifnot) r = r->skip[PF_SKIP_IFP].ptr; else if (r->direction && r->direction != dir) r = r->skip[PF_SKIP_DIR].ptr; else if (r->af && r->af != af) r = r->skip[PF_SKIP_AF].ptr; else if (r->proto && r->proto != pd->proto) r = r->skip[PF_SKIP_PROTO].ptr; else if (PF_MISMATCHAW(&r->src.addr, pd->src, af, r->src.neg, kif, M_GETFIB(m))) r = r->skip[PF_SKIP_SRC_ADDR].ptr; else if (r->src.port_op && !pf_match_port(r->src.port_op, r->src.port[0], r->src.port[1], th->th_sport)) r = r->skip[PF_SKIP_SRC_PORT].ptr; else if (PF_MISMATCHAW(&r->dst.addr, pd->dst, af, r->dst.neg, NULL, M_GETFIB(m))) r = r->skip[PF_SKIP_DST_ADDR].ptr; else if (r->dst.port_op && !pf_match_port(r->dst.port_op, r->dst.port[0], r->dst.port[1], th->th_dport)) r = r->skip[PF_SKIP_DST_PORT].ptr; else if (r->os_fingerprint != PF_OSFP_ANY && !pf_osfp_match( pf_osfp_fingerprint(pd, m, off, th), r->os_fingerprint)) r = TAILQ_NEXT(r, entries); else { rm = r; break; } } if (rm == NULL || rm->action == PF_NOSCRUB) return (PF_PASS); else { r->packets[dir == PF_OUT]++; r->bytes[dir == PF_OUT] += pd->tot_len; } if (rm->rule_flag & PFRULE_REASSEMBLE_TCP) pd->flags |= PFDESC_TCP_NORM; flags = th->th_flags; if (flags & TH_SYN) { /* Illegal packet */ if (flags & TH_RST) goto tcp_drop; if (flags & TH_FIN) flags &= ~TH_FIN; } else { /* Illegal packet */ if (!(flags & (TH_ACK|TH_RST))) goto tcp_drop; } if (!(flags & TH_ACK)) { /* These flags are only valid if ACK is set */ if ((flags & TH_FIN) || (flags & TH_PUSH) || (flags & TH_URG)) goto tcp_drop; } /* Check for illegal header length */ if (th->th_off < (sizeof(struct tcphdr) >> 2)) goto tcp_drop; /* If flags changed, or reserved data set, then adjust */ if (flags != th->th_flags || th->th_x2 != 0) { u_int16_t ov, nv; ov = *(u_int16_t *)(&th->th_ack + 1); th->th_flags = flags; th->th_x2 = 0; nv = *(u_int16_t *)(&th->th_ack + 1); - th->th_sum = pf_cksum_fixup(th->th_sum, ov, nv, 0); + th->th_sum = pf_proto_cksum_fixup(m, th->th_sum, ov, nv, 0); rewrite = 1; } /* Remove urgent pointer, if TH_URG is not set */ if (!(flags & TH_URG) && th->th_urp) { - th->th_sum = pf_cksum_fixup(th->th_sum, th->th_urp, 0, 0); + th->th_sum = pf_proto_cksum_fixup(m, th->th_sum, th->th_urp, + 0, 0); th->th_urp = 0; rewrite = 1; } /* Process options */ if (r->max_mss && pf_normalize_tcpopt(r, m, th, off, pd->af)) rewrite = 1; /* copy back packet headers if we sanitized */ if (rewrite) #ifdef __FreeBSD__ m_copyback(m, off, sizeof(*th), (caddr_t)th); #else m_copyback(m, off, sizeof(*th), th); #endif return (PF_PASS); tcp_drop: REASON_SET(&reason, PFRES_NORM); if (rm != NULL && r->log) PFLOG_PACKET(kif, h, m, AF_INET, dir, reason, r, NULL, NULL, pd); return (PF_DROP); } int pf_normalize_tcp_init(struct mbuf *m, int off, struct pf_pdesc *pd, struct tcphdr *th, struct pf_state_peer *src, struct pf_state_peer *dst) { u_int32_t tsval, tsecr; u_int8_t hdr[60]; u_int8_t *opt; #ifdef __FreeBSD__ KASSERT((src->scrub == NULL), ("pf_normalize_tcp_init: src->scrub != NULL")); src->scrub = pool_get(&V_pf_state_scrub_pl, PR_NOWAIT); #else KASSERT(src->scrub == NULL); src->scrub = pool_get(&pf_state_scrub_pl, PR_NOWAIT); #endif if (src->scrub == NULL) return (1); bzero(src->scrub, sizeof(*src->scrub)); switch (pd->af) { #ifdef INET case AF_INET: { struct ip *h = mtod(m, struct ip *); src->scrub->pfss_ttl = h->ip_ttl; break; } #endif /* INET */ #ifdef INET6 case AF_INET6: { struct ip6_hdr *h = mtod(m, struct ip6_hdr *); src->scrub->pfss_ttl = h->ip6_hlim; break; } #endif /* INET6 */ } /* * All normalizations below are only begun if we see the start of * the connections. They must all set an enabled bit in pfss_flags */ if ((th->th_flags & TH_SYN) == 0) return (0); if (th->th_off > (sizeof(struct tcphdr) >> 2) && src->scrub && pf_pull_hdr(m, off, hdr, th->th_off << 2, NULL, NULL, pd->af)) { /* Diddle with TCP options */ int hlen; opt = hdr + sizeof(struct tcphdr); hlen = (th->th_off << 2) - sizeof(struct tcphdr); while (hlen >= TCPOLEN_TIMESTAMP) { switch (*opt) { case TCPOPT_EOL: /* FALLTHROUGH */ case TCPOPT_NOP: opt++; hlen--; break; case TCPOPT_TIMESTAMP: if (opt[1] >= TCPOLEN_TIMESTAMP) { src->scrub->pfss_flags |= PFSS_TIMESTAMP; src->scrub->pfss_ts_mod = htonl(arc4random()); /* note PFSS_PAWS not set yet */ memcpy(&tsval, &opt[2], sizeof(u_int32_t)); memcpy(&tsecr, &opt[6], sizeof(u_int32_t)); src->scrub->pfss_tsval0 = ntohl(tsval); src->scrub->pfss_tsval = ntohl(tsval); src->scrub->pfss_tsecr = ntohl(tsecr); getmicrouptime(&src->scrub->pfss_last); } /* FALLTHROUGH */ default: hlen -= MAX(opt[1], 2); opt += MAX(opt[1], 2); break; } } } return (0); } void pf_normalize_tcp_cleanup(struct pf_state *state) { #ifdef __FreeBSD__ if (state->src.scrub) pool_put(&V_pf_state_scrub_pl, state->src.scrub); if (state->dst.scrub) pool_put(&V_pf_state_scrub_pl, state->dst.scrub); #else if (state->src.scrub) pool_put(&pf_state_scrub_pl, state->src.scrub); if (state->dst.scrub) pool_put(&pf_state_scrub_pl, state->dst.scrub); #endif /* Someday... flush the TCP segment reassembly descriptors. */ } int pf_normalize_tcp_stateful(struct mbuf *m, int off, struct pf_pdesc *pd, u_short *reason, struct tcphdr *th, struct pf_state *state, struct pf_state_peer *src, struct pf_state_peer *dst, int *writeback) { struct timeval uptime; u_int32_t tsval, tsecr; u_int tsval_from_last; u_int8_t hdr[60]; u_int8_t *opt; int copyback = 0; int got_ts = 0; #ifdef __FreeBSD__ KASSERT((src->scrub || dst->scrub), ("pf_normalize_tcp_statefull: src->scrub && dst->scrub!")); #else KASSERT(src->scrub || dst->scrub); #endif /* * Enforce the minimum TTL seen for this connection. Negate a common * technique to evade an intrusion detection system and confuse * firewall state code. */ switch (pd->af) { #ifdef INET case AF_INET: { if (src->scrub) { struct ip *h = mtod(m, struct ip *); if (h->ip_ttl > src->scrub->pfss_ttl) src->scrub->pfss_ttl = h->ip_ttl; h->ip_ttl = src->scrub->pfss_ttl; } break; } #endif /* INET */ #ifdef INET6 case AF_INET6: { if (src->scrub) { struct ip6_hdr *h = mtod(m, struct ip6_hdr *); if (h->ip6_hlim > src->scrub->pfss_ttl) src->scrub->pfss_ttl = h->ip6_hlim; h->ip6_hlim = src->scrub->pfss_ttl; } break; } #endif /* INET6 */ } if (th->th_off > (sizeof(struct tcphdr) >> 2) && ((src->scrub && (src->scrub->pfss_flags & PFSS_TIMESTAMP)) || (dst->scrub && (dst->scrub->pfss_flags & PFSS_TIMESTAMP))) && pf_pull_hdr(m, off, hdr, th->th_off << 2, NULL, NULL, pd->af)) { /* Diddle with TCP options */ int hlen; opt = hdr + sizeof(struct tcphdr); hlen = (th->th_off << 2) - sizeof(struct tcphdr); while (hlen >= TCPOLEN_TIMESTAMP) { switch (*opt) { case TCPOPT_EOL: /* FALLTHROUGH */ case TCPOPT_NOP: opt++; hlen--; break; case TCPOPT_TIMESTAMP: /* Modulate the timestamps. Can be used for * NAT detection, OS uptime determination or * reboot detection. */ if (got_ts) { /* Huh? Multiple timestamps!? */ #ifdef __FreeBSD__ if (V_pf_status.debug >= PF_DEBUG_MISC) { #else if (pf_status.debug >= PF_DEBUG_MISC) { #endif DPFPRINTF(("multiple TS??")); pf_print_state(state); printf("\n"); } REASON_SET(reason, PFRES_TS); return (PF_DROP); } if (opt[1] >= TCPOLEN_TIMESTAMP) { memcpy(&tsval, &opt[2], sizeof(u_int32_t)); if (tsval && src->scrub && (src->scrub->pfss_flags & PFSS_TIMESTAMP)) { tsval = ntohl(tsval); - pf_change_a(&opt[2], + pf_change_proto_a(m, &opt[2], &th->th_sum, htonl(tsval + src->scrub->pfss_ts_mod), 0); copyback = 1; } /* Modulate TS reply iff valid (!0) */ memcpy(&tsecr, &opt[6], sizeof(u_int32_t)); if (tsecr && dst->scrub && (dst->scrub->pfss_flags & PFSS_TIMESTAMP)) { tsecr = ntohl(tsecr) - dst->scrub->pfss_ts_mod; - pf_change_a(&opt[6], + pf_change_proto_a(m, &opt[6], &th->th_sum, htonl(tsecr), 0); copyback = 1; } got_ts = 1; } /* FALLTHROUGH */ default: hlen -= MAX(opt[1], 2); opt += MAX(opt[1], 2); break; } } if (copyback) { /* Copyback the options, caller copys back header */ *writeback = 1; m_copyback(m, off + sizeof(struct tcphdr), (th->th_off << 2) - sizeof(struct tcphdr), hdr + sizeof(struct tcphdr)); } } /* * Must invalidate PAWS checks on connections idle for too long. * The fastest allowed timestamp clock is 1ms. That turns out to * be about 24 days before it wraps. XXX Right now our lowerbound * TS echo check only works for the first 12 days of a connection * when the TS has exhausted half its 32bit space */ #define TS_MAX_IDLE (24*24*60*60) #define TS_MAX_CONN (12*24*60*60) /* XXX remove when better tsecr check */ getmicrouptime(&uptime); if (src->scrub && (src->scrub->pfss_flags & PFSS_PAWS) && (uptime.tv_sec - src->scrub->pfss_last.tv_sec > TS_MAX_IDLE || time_second - state->creation > TS_MAX_CONN)) { #ifdef __FreeBSD__ if (V_pf_status.debug >= PF_DEBUG_MISC) { #else if (pf_status.debug >= PF_DEBUG_MISC) { #endif DPFPRINTF(("src idled out of PAWS\n")); pf_print_state(state); printf("\n"); } src->scrub->pfss_flags = (src->scrub->pfss_flags & ~PFSS_PAWS) | PFSS_PAWS_IDLED; } if (dst->scrub && (dst->scrub->pfss_flags & PFSS_PAWS) && uptime.tv_sec - dst->scrub->pfss_last.tv_sec > TS_MAX_IDLE) { #ifdef __FreeBSD__ if (V_pf_status.debug >= PF_DEBUG_MISC) { #else if (pf_status.debug >= PF_DEBUG_MISC) { #endif DPFPRINTF(("dst idled out of PAWS\n")); pf_print_state(state); printf("\n"); } dst->scrub->pfss_flags = (dst->scrub->pfss_flags & ~PFSS_PAWS) | PFSS_PAWS_IDLED; } if (got_ts && src->scrub && dst->scrub && (src->scrub->pfss_flags & PFSS_PAWS) && (dst->scrub->pfss_flags & PFSS_PAWS)) { /* Validate that the timestamps are "in-window". * RFC1323 describes TCP Timestamp options that allow * measurement of RTT (round trip time) and PAWS * (protection against wrapped sequence numbers). PAWS * gives us a set of rules for rejecting packets on * long fat pipes (packets that were somehow delayed * in transit longer than the time it took to send the * full TCP sequence space of 4Gb). We can use these * rules and infer a few others that will let us treat * the 32bit timestamp and the 32bit echoed timestamp * as sequence numbers to prevent a blind attacker from * inserting packets into a connection. * * RFC1323 tells us: * - The timestamp on this packet must be greater than * or equal to the last value echoed by the other * endpoint. The RFC says those will be discarded * since it is a dup that has already been acked. * This gives us a lowerbound on the timestamp. * timestamp >= other last echoed timestamp * - The timestamp will be less than or equal to * the last timestamp plus the time between the * last packet and now. The RFC defines the max * clock rate as 1ms. We will allow clocks to be * up to 10% fast and will allow a total difference * or 30 seconds due to a route change. And this * gives us an upperbound on the timestamp. * timestamp <= last timestamp + max ticks * We have to be careful here. Windows will send an * initial timestamp of zero and then initialize it * to a random value after the 3whs; presumably to * avoid a DoS by having to call an expensive RNG * during a SYN flood. Proof MS has at least one * good security geek. * * - The TCP timestamp option must also echo the other * endpoints timestamp. The timestamp echoed is the * one carried on the earliest unacknowledged segment * on the left edge of the sequence window. The RFC * states that the host will reject any echoed * timestamps that were larger than any ever sent. * This gives us an upperbound on the TS echo. * tescr <= largest_tsval * - The lowerbound on the TS echo is a little more * tricky to determine. The other endpoint's echoed * values will not decrease. But there may be * network conditions that re-order packets and * cause our view of them to decrease. For now the * only lowerbound we can safely determine is that * the TS echo will never be less than the original * TS. XXX There is probably a better lowerbound. * Remove TS_MAX_CONN with better lowerbound check. * tescr >= other original TS * * It is also important to note that the fastest * timestamp clock of 1ms will wrap its 32bit space in * 24 days. So we just disable TS checking after 24 * days of idle time. We actually must use a 12d * connection limit until we can come up with a better * lowerbound to the TS echo check. */ struct timeval delta_ts; int ts_fudge; /* * PFTM_TS_DIFF is how many seconds of leeway to allow * a host's timestamp. This can happen if the previous * packet got delayed in transit for much longer than * this packet. */ if ((ts_fudge = state->rule.ptr->timeout[PFTM_TS_DIFF]) == 0) #ifdef __FreeBSD__ ts_fudge = V_pf_default_rule.timeout[PFTM_TS_DIFF]; #else ts_fudge = pf_default_rule.timeout[PFTM_TS_DIFF]; #endif /* Calculate max ticks since the last timestamp */ #define TS_MAXFREQ 1100 /* RFC max TS freq of 1Khz + 10% skew */ #define TS_MICROSECS 1000000 /* microseconds per second */ #ifdef __FreeBSD__ #ifndef timersub #define timersub(tvp, uvp, vvp) \ do { \ (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \ (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \ if ((vvp)->tv_usec < 0) { \ (vvp)->tv_sec--; \ (vvp)->tv_usec += 1000000; \ } \ } while (0) #endif #endif timersub(&uptime, &src->scrub->pfss_last, &delta_ts); tsval_from_last = (delta_ts.tv_sec + ts_fudge) * TS_MAXFREQ; tsval_from_last += delta_ts.tv_usec / (TS_MICROSECS/TS_MAXFREQ); if ((src->state >= TCPS_ESTABLISHED && dst->state >= TCPS_ESTABLISHED) && (SEQ_LT(tsval, dst->scrub->pfss_tsecr) || SEQ_GT(tsval, src->scrub->pfss_tsval + tsval_from_last) || (tsecr && (SEQ_GT(tsecr, dst->scrub->pfss_tsval) || SEQ_LT(tsecr, dst->scrub->pfss_tsval0))))) { /* Bad RFC1323 implementation or an insertion attack. * * - Solaris 2.6 and 2.7 are known to send another ACK * after the FIN,FIN|ACK,ACK closing that carries * an old timestamp. */ DPFPRINTF(("Timestamp failed %c%c%c%c\n", SEQ_LT(tsval, dst->scrub->pfss_tsecr) ? '0' : ' ', SEQ_GT(tsval, src->scrub->pfss_tsval + tsval_from_last) ? '1' : ' ', SEQ_GT(tsecr, dst->scrub->pfss_tsval) ? '2' : ' ', SEQ_LT(tsecr, dst->scrub->pfss_tsval0)? '3' : ' ')); #ifdef __FreeBSD__ DPFPRINTF((" tsval: %u tsecr: %u +ticks: %u " "idle: %jus %lums\n", tsval, tsecr, tsval_from_last, (uintmax_t)delta_ts.tv_sec, delta_ts.tv_usec / 1000)); DPFPRINTF((" src->tsval: %u tsecr: %u\n", src->scrub->pfss_tsval, src->scrub->pfss_tsecr)); DPFPRINTF((" dst->tsval: %u tsecr: %u tsval0: %u" "\n", dst->scrub->pfss_tsval, dst->scrub->pfss_tsecr, dst->scrub->pfss_tsval0)); #else DPFPRINTF((" tsval: %lu tsecr: %lu +ticks: %lu " "idle: %lus %lums\n", tsval, tsecr, tsval_from_last, delta_ts.tv_sec, delta_ts.tv_usec / 1000)); DPFPRINTF((" src->tsval: %lu tsecr: %lu\n", src->scrub->pfss_tsval, src->scrub->pfss_tsecr)); DPFPRINTF((" dst->tsval: %lu tsecr: %lu tsval0: %lu" "\n", dst->scrub->pfss_tsval, dst->scrub->pfss_tsecr, dst->scrub->pfss_tsval0)); #endif #ifdef __FreeBSD__ if (V_pf_status.debug >= PF_DEBUG_MISC) { #else if (pf_status.debug >= PF_DEBUG_MISC) { #endif pf_print_state(state); pf_print_flags(th->th_flags); printf("\n"); } REASON_SET(reason, PFRES_TS); return (PF_DROP); } /* XXX I'd really like to require tsecr but it's optional */ } else if (!got_ts && (th->th_flags & TH_RST) == 0 && ((src->state == TCPS_ESTABLISHED && dst->state == TCPS_ESTABLISHED) || pd->p_len > 0 || (th->th_flags & TH_SYN)) && src->scrub && dst->scrub && (src->scrub->pfss_flags & PFSS_PAWS) && (dst->scrub->pfss_flags & PFSS_PAWS)) { /* Didn't send a timestamp. Timestamps aren't really useful * when: * - connection opening or closing (often not even sent). * but we must not let an attacker to put a FIN on a * data packet to sneak it through our ESTABLISHED check. * - on a TCP reset. RFC suggests not even looking at TS. * - on an empty ACK. The TS will not be echoed so it will * probably not help keep the RTT calculation in sync and * there isn't as much danger when the sequence numbers * got wrapped. So some stacks don't include TS on empty * ACKs :-( * * To minimize the disruption to mostly RFC1323 conformant * stacks, we will only require timestamps on data packets. * * And what do ya know, we cannot require timestamps on data * packets. There appear to be devices that do legitimate * TCP connection hijacking. There are HTTP devices that allow * a 3whs (with timestamps) and then buffer the HTTP request. * If the intermediate device has the HTTP response cache, it * will spoof the response but not bother timestamping its * packets. So we can look for the presence of a timestamp in * the first data packet and if there, require it in all future * packets. */ if (pd->p_len > 0 && (src->scrub->pfss_flags & PFSS_DATA_TS)) { /* * Hey! Someone tried to sneak a packet in. Or the * stack changed its RFC1323 behavior?!?! */ #ifdef __FreeBSD__ if (V_pf_status.debug >= PF_DEBUG_MISC) { #else if (pf_status.debug >= PF_DEBUG_MISC) { #endif DPFPRINTF(("Did not receive expected RFC1323 " "timestamp\n")); pf_print_state(state); pf_print_flags(th->th_flags); printf("\n"); } REASON_SET(reason, PFRES_TS); return (PF_DROP); } } /* * We will note if a host sends his data packets with or without * timestamps. And require all data packets to contain a timestamp * if the first does. PAWS implicitly requires that all data packets be * timestamped. But I think there are middle-man devices that hijack * TCP streams immediately after the 3whs and don't timestamp their * packets (seen in a WWW accelerator or cache). */ if (pd->p_len > 0 && src->scrub && (src->scrub->pfss_flags & (PFSS_TIMESTAMP|PFSS_DATA_TS|PFSS_DATA_NOTS)) == PFSS_TIMESTAMP) { if (got_ts) src->scrub->pfss_flags |= PFSS_DATA_TS; else { src->scrub->pfss_flags |= PFSS_DATA_NOTS; #ifdef __FreeBSD__ if (V_pf_status.debug >= PF_DEBUG_MISC && dst->scrub && #else if (pf_status.debug >= PF_DEBUG_MISC && dst->scrub && #endif (dst->scrub->pfss_flags & PFSS_TIMESTAMP)) { /* Don't warn if other host rejected RFC1323 */ DPFPRINTF(("Broken RFC1323 stack did not " "timestamp data packet. Disabled PAWS " "security.\n")); pf_print_state(state); pf_print_flags(th->th_flags); printf("\n"); } } } /* * Update PAWS values */ if (got_ts && src->scrub && PFSS_TIMESTAMP == (src->scrub->pfss_flags & (PFSS_PAWS_IDLED|PFSS_TIMESTAMP))) { getmicrouptime(&src->scrub->pfss_last); if (SEQ_GEQ(tsval, src->scrub->pfss_tsval) || (src->scrub->pfss_flags & PFSS_PAWS) == 0) src->scrub->pfss_tsval = tsval; if (tsecr) { if (SEQ_GEQ(tsecr, src->scrub->pfss_tsecr) || (src->scrub->pfss_flags & PFSS_PAWS) == 0) src->scrub->pfss_tsecr = tsecr; if ((src->scrub->pfss_flags & PFSS_PAWS) == 0 && (SEQ_LT(tsval, src->scrub->pfss_tsval0) || src->scrub->pfss_tsval0 == 0)) { /* tsval0 MUST be the lowest timestamp */ src->scrub->pfss_tsval0 = tsval; } /* Only fully initialized after a TS gets echoed */ if ((src->scrub->pfss_flags & PFSS_PAWS) == 0) src->scrub->pfss_flags |= PFSS_PAWS; } } /* I have a dream.... TCP segment reassembly.... */ return (0); } int pf_normalize_tcpopt(struct pf_rule *r, struct mbuf *m, struct tcphdr *th, int off, sa_family_t af) { u_int16_t *mss; int thoff; int opt, cnt, optlen = 0; int rewrite = 0; #ifdef __FreeBSD__ u_char opts[TCP_MAXOLEN]; #else u_char opts[MAX_TCPOPTLEN]; #endif u_char *optp = opts; thoff = th->th_off << 2; cnt = thoff - sizeof(struct tcphdr); if (cnt > 0 && !pf_pull_hdr(m, off + sizeof(*th), opts, cnt, NULL, NULL, af)) return (rewrite); for (; cnt > 0; cnt -= optlen, optp += optlen) { opt = optp[0]; if (opt == TCPOPT_EOL) break; if (opt == TCPOPT_NOP) optlen = 1; else { if (cnt < 2) break; optlen = optp[1]; if (optlen < 2 || optlen > cnt) break; } switch (opt) { case TCPOPT_MAXSEG: mss = (u_int16_t *)(optp + 2); if ((ntohs(*mss)) > r->max_mss) { - th->th_sum = pf_cksum_fixup(th->th_sum, - *mss, htons(r->max_mss), 0); + th->th_sum = pf_proto_cksum_fixup(m, + th->th_sum, *mss, htons(r->max_mss), 0); *mss = htons(r->max_mss); rewrite = 1; } break; default: break; } } if (rewrite) m_copyback(m, off + sizeof(*th), thoff - sizeof(*th), opts); return (rewrite); } void pf_scrub_ip(struct mbuf **m0, u_int32_t flags, u_int8_t min_ttl, u_int8_t tos) { struct mbuf *m = *m0; struct ip *h = mtod(m, struct ip *); /* Clear IP_DF if no-df was requested */ if (flags & PFRULE_NODF && h->ip_off & htons(IP_DF)) { u_int16_t ip_off = h->ip_off; h->ip_off &= htons(~IP_DF); h->ip_sum = pf_cksum_fixup(h->ip_sum, ip_off, h->ip_off, 0); } /* Enforce a minimum ttl, may cause endless packet loops */ if (min_ttl && h->ip_ttl < min_ttl) { u_int16_t ip_ttl = h->ip_ttl; h->ip_ttl = min_ttl; h->ip_sum = pf_cksum_fixup(h->ip_sum, ip_ttl, h->ip_ttl, 0); } /* Enforce tos */ if (flags & PFRULE_SET_TOS) { u_int16_t ov, nv; ov = *(u_int16_t *)h; h->ip_tos = tos; nv = *(u_int16_t *)h; h->ip_sum = pf_cksum_fixup(h->ip_sum, ov, nv, 0); } /* random-id, but not for fragments */ if (flags & PFRULE_RANDOMID && !(h->ip_off & ~htons(IP_DF))) { u_int16_t ip_id = h->ip_id; h->ip_id = ip_randomid(); h->ip_sum = pf_cksum_fixup(h->ip_sum, ip_id, h->ip_id, 0); } } #ifdef INET6 void pf_scrub_ip6(struct mbuf **m0, u_int8_t min_ttl) { struct mbuf *m = *m0; struct ip6_hdr *h = mtod(m, struct ip6_hdr *); /* Enforce a minimum ttl, may cause endless packet loops */ if (min_ttl && h->ip6_hlim < min_ttl) h->ip6_hlim = min_ttl; } #endif Index: stable/9/sys/contrib/pf/net/pfvar.h =================================================================== --- stable/9/sys/contrib/pf/net/pfvar.h (revision 292730) +++ stable/9/sys/contrib/pf/net/pfvar.h (revision 292731) @@ -1,2234 +1,2239 @@ /* $OpenBSD: pfvar.h,v 1.282 2009/01/29 15:12:28 pyr Exp $ */ /* * Copyright (c) 2001 Daniel Hartmeier * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - 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 COPYRIGHT HOLDERS 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 * COPYRIGHT HOLDERS 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. * */ #ifndef _NET_PFVAR_H_ #define _NET_PFVAR_H_ #include #include #include #include #ifdef __FreeBSD__ #include #include #else #include #endif #include #include #ifdef __FreeBSD__ #include #include #include #else #include #endif #ifdef __FreeBSD__ #include #endif #include struct ip; struct ip6_hdr; #ifdef __FreeBSD__ struct inpcb; #endif #define PF_TCPS_PROXY_SRC ((TCP_NSTATES)+0) #define PF_TCPS_PROXY_DST ((TCP_NSTATES)+1) #define PF_MD5_DIGEST_LENGTH 16 #ifdef MD5_DIGEST_LENGTH #if PF_MD5_DIGEST_LENGTH != MD5_DIGEST_LENGTH #error #endif #endif enum { PF_INOUT, PF_IN, PF_OUT }; enum { PF_PASS, PF_DROP, PF_SCRUB, PF_NOSCRUB, PF_NAT, PF_NONAT, PF_BINAT, PF_NOBINAT, PF_RDR, PF_NORDR, PF_SYNPROXY_DROP, PF_DEFER }; enum { PF_RULESET_SCRUB, PF_RULESET_FILTER, PF_RULESET_NAT, PF_RULESET_BINAT, PF_RULESET_RDR, PF_RULESET_MAX }; enum { PF_OP_NONE, PF_OP_IRG, PF_OP_EQ, PF_OP_NE, PF_OP_LT, PF_OP_LE, PF_OP_GT, PF_OP_GE, PF_OP_XRG, PF_OP_RRG }; enum { PF_DEBUG_NONE, PF_DEBUG_URGENT, PF_DEBUG_MISC, PF_DEBUG_NOISY }; enum { PF_CHANGE_NONE, PF_CHANGE_ADD_HEAD, PF_CHANGE_ADD_TAIL, PF_CHANGE_ADD_BEFORE, PF_CHANGE_ADD_AFTER, PF_CHANGE_REMOVE, PF_CHANGE_GET_TICKET }; enum { PF_GET_NONE, PF_GET_CLR_CNTR }; enum { PF_SK_WIRE, PF_SK_STACK, PF_SK_BOTH }; /* * Note about PFTM_*: real indices into pf_rule.timeout[] come before * PFTM_MAX, special cases afterwards. See pf_state_expires(). */ enum { PFTM_TCP_FIRST_PACKET, PFTM_TCP_OPENING, PFTM_TCP_ESTABLISHED, PFTM_TCP_CLOSING, PFTM_TCP_FIN_WAIT, PFTM_TCP_CLOSED, PFTM_UDP_FIRST_PACKET, PFTM_UDP_SINGLE, PFTM_UDP_MULTIPLE, PFTM_ICMP_FIRST_PACKET, PFTM_ICMP_ERROR_REPLY, PFTM_OTHER_FIRST_PACKET, PFTM_OTHER_SINGLE, PFTM_OTHER_MULTIPLE, PFTM_FRAG, PFTM_INTERVAL, PFTM_ADAPTIVE_START, PFTM_ADAPTIVE_END, PFTM_SRC_NODE, PFTM_TS_DIFF, PFTM_MAX, PFTM_PURGE, PFTM_UNLINKED, PFTM_UNTIL_PACKET }; /* PFTM default values */ #define PFTM_TCP_FIRST_PACKET_VAL 120 /* First TCP packet */ #define PFTM_TCP_OPENING_VAL 30 /* No response yet */ #define PFTM_TCP_ESTABLISHED_VAL 24*60*60/* Established */ #define PFTM_TCP_CLOSING_VAL 15 * 60 /* Half closed */ #define PFTM_TCP_FIN_WAIT_VAL 45 /* Got both FINs */ #define PFTM_TCP_CLOSED_VAL 90 /* Got a RST */ #define PFTM_UDP_FIRST_PACKET_VAL 60 /* First UDP packet */ #define PFTM_UDP_SINGLE_VAL 30 /* Unidirectional */ #define PFTM_UDP_MULTIPLE_VAL 60 /* Bidirectional */ #define PFTM_ICMP_FIRST_PACKET_VAL 20 /* First ICMP packet */ #define PFTM_ICMP_ERROR_REPLY_VAL 10 /* Got error response */ #define PFTM_OTHER_FIRST_PACKET_VAL 60 /* First packet */ #define PFTM_OTHER_SINGLE_VAL 30 /* Unidirectional */ #define PFTM_OTHER_MULTIPLE_VAL 60 /* Bidirectional */ #define PFTM_FRAG_VAL 30 /* Fragment expire */ #define PFTM_INTERVAL_VAL 10 /* Expire interval */ #define PFTM_SRC_NODE_VAL 0 /* Source tracking */ #define PFTM_TS_DIFF_VAL 30 /* Allowed TS diff */ enum { PF_NOPFROUTE, PF_FASTROUTE, PF_ROUTETO, PF_DUPTO, PF_REPLYTO }; enum { PF_LIMIT_STATES, PF_LIMIT_SRC_NODES, PF_LIMIT_FRAGS, PF_LIMIT_TABLES, PF_LIMIT_TABLE_ENTRIES, PF_LIMIT_MAX }; #define PF_POOL_IDMASK 0x0f enum { PF_POOL_NONE, PF_POOL_BITMASK, PF_POOL_RANDOM, PF_POOL_SRCHASH, PF_POOL_ROUNDROBIN }; enum { PF_ADDR_ADDRMASK, PF_ADDR_NOROUTE, PF_ADDR_DYNIFTL, PF_ADDR_TABLE, PF_ADDR_RTLABEL, PF_ADDR_URPFFAILED, PF_ADDR_RANGE }; #define PF_POOL_TYPEMASK 0x0f #define PF_POOL_STICKYADDR 0x20 #define PF_WSCALE_FLAG 0x80 #define PF_WSCALE_MASK 0x0f #define PF_LOG 0x01 #define PF_LOG_ALL 0x02 #define PF_LOG_SOCKET_LOOKUP 0x04 struct pf_addr { union { struct in_addr v4; struct in6_addr v6; u_int8_t addr8[16]; u_int16_t addr16[8]; u_int32_t addr32[4]; } pfa; /* 128-bit address */ #define v4 pfa.v4 #define v6 pfa.v6 #define addr8 pfa.addr8 #define addr16 pfa.addr16 #define addr32 pfa.addr32 }; #define PF_TABLE_NAME_SIZE 32 #define PFI_AFLAG_NETWORK 0x01 #define PFI_AFLAG_BROADCAST 0x02 #define PFI_AFLAG_PEER 0x04 #define PFI_AFLAG_MODEMASK 0x07 #define PFI_AFLAG_NOALIAS 0x08 struct pf_addr_wrap { union { struct { struct pf_addr addr; struct pf_addr mask; } a; char ifname[IFNAMSIZ]; char tblname[PF_TABLE_NAME_SIZE]; #ifdef __FreeBSD__ #define RTLABEL_LEN 32 #endif char rtlabelname[RTLABEL_LEN]; u_int32_t rtlabel; } v; union { struct pfi_dynaddr *dyn; struct pfr_ktable *tbl; int dyncnt; int tblcnt; } p; u_int8_t type; /* PF_ADDR_* */ u_int8_t iflags; /* PFI_AFLAG_* */ }; #ifdef _KERNEL struct pfi_dynaddr { TAILQ_ENTRY(pfi_dynaddr) entry; struct pf_addr pfid_addr4; struct pf_addr pfid_mask4; struct pf_addr pfid_addr6; struct pf_addr pfid_mask6; struct pfr_ktable *pfid_kt; struct pfi_kif *pfid_kif; void *pfid_hook_cookie; int pfid_net; /* mask or 128 */ int pfid_acnt4; /* address count IPv4 */ int pfid_acnt6; /* address count IPv6 */ sa_family_t pfid_af; /* rule af */ u_int8_t pfid_iflags; /* PFI_AFLAG_* */ }; /* * Address manipulation macros */ #ifdef __FreeBSD__ #define splsoftnet() splnet() #define HTONL(x) (x) = htonl((__uint32_t)(x)) #define HTONS(x) (x) = htons((__uint16_t)(x)) #define NTOHL(x) (x) = ntohl((__uint32_t)(x)) #define NTOHS(x) (x) = ntohs((__uint16_t)(x)) #define PF_NAME "pf" #define PR_NOWAIT M_NOWAIT #define PR_WAITOK M_WAITOK #define PR_ZERO M_ZERO #define pool_get(p, f) uma_zalloc(*(p), (f)) #define pool_put(p, o) uma_zfree(*(p), (o)) #define UMA_CREATE(var, type, desc) \ var = uma_zcreate(desc, sizeof(type), \ NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); \ if (var == NULL) \ break #define UMA_DESTROY(var) \ if (var) \ uma_zdestroy(var) #ifdef __FreeBSD__ extern struct mtx pf_task_mtx; #define PF_LOCK_ASSERT() mtx_assert(&pf_task_mtx, MA_OWNED) #define PF_UNLOCK_ASSERT() mtx_assert(&pf_task_mtx, MA_NOTOWNED) #define PF_LOCK() mtx_lock(&pf_task_mtx) #define PF_UNLOCK() mtx_unlock(&pf_task_mtx) #else #define PF_LOCK_ASSERT() #define PF_UNLOCK_ASSERT() #define PF_LOCK() #define PF_UNLOCK() #endif /* __FreeBSD__ */ #define PF_COPYIN(uaddr, kaddr, len, r) do { \ PF_UNLOCK(); \ r = copyin((uaddr), (kaddr), (len)); \ PF_LOCK(); \ } while(0) #define PF_COPYOUT(kaddr, uaddr, len, r) do { \ PF_UNLOCK(); \ r = copyout((kaddr), (uaddr), (len)); \ PF_LOCK(); \ } while(0) #define PF_MODVER 1 #define PFLOG_MODVER 1 #define PFSYNC_MODVER 1 #define PFLOG_MINVER 1 #define PFLOG_PREFVER PFLOG_MODVER #define PFLOG_MAXVER 1 #define PFSYNC_MINVER 1 #define PFSYNC_PREFVER PFSYNC_MODVER #define PFSYNC_MAXVER 1 #endif /* __FreeBSD__ */ #ifdef INET #ifndef INET6 #define PF_INET_ONLY #endif /* ! INET6 */ #endif /* INET */ #ifdef INET6 #ifndef INET #define PF_INET6_ONLY #endif /* ! INET */ #endif /* INET6 */ #ifdef INET #ifdef INET6 #define PF_INET_INET6 #endif /* INET6 */ #endif /* INET */ #else #define PF_INET_INET6 #endif /* _KERNEL */ /* Both IPv4 and IPv6 */ #ifdef PF_INET_INET6 #define PF_AEQ(a, b, c) \ ((c == AF_INET && (a)->addr32[0] == (b)->addr32[0]) || \ ((a)->addr32[3] == (b)->addr32[3] && \ (a)->addr32[2] == (b)->addr32[2] && \ (a)->addr32[1] == (b)->addr32[1] && \ (a)->addr32[0] == (b)->addr32[0])) \ #define PF_ANEQ(a, b, c) \ ((c == AF_INET && (a)->addr32[0] != (b)->addr32[0]) || \ ((a)->addr32[3] != (b)->addr32[3] || \ (a)->addr32[2] != (b)->addr32[2] || \ (a)->addr32[1] != (b)->addr32[1] || \ (a)->addr32[0] != (b)->addr32[0])) \ #define PF_AZERO(a, c) \ ((c == AF_INET && !(a)->addr32[0]) || \ (!(a)->addr32[0] && !(a)->addr32[1] && \ !(a)->addr32[2] && !(a)->addr32[3] )) \ #define PF_MATCHA(n, a, m, b, f) \ pf_match_addr(n, a, m, b, f) #define PF_ACPY(a, b, f) \ pf_addrcpy(a, b, f) #define PF_AINC(a, f) \ pf_addr_inc(a, f) #define PF_POOLMASK(a, b, c, d, f) \ pf_poolmask(a, b, c, d, f) #else /* Just IPv6 */ #ifdef PF_INET6_ONLY #define PF_AEQ(a, b, c) \ ((a)->addr32[3] == (b)->addr32[3] && \ (a)->addr32[2] == (b)->addr32[2] && \ (a)->addr32[1] == (b)->addr32[1] && \ (a)->addr32[0] == (b)->addr32[0]) \ #define PF_ANEQ(a, b, c) \ ((a)->addr32[3] != (b)->addr32[3] || \ (a)->addr32[2] != (b)->addr32[2] || \ (a)->addr32[1] != (b)->addr32[1] || \ (a)->addr32[0] != (b)->addr32[0]) \ #define PF_AZERO(a, c) \ (!(a)->addr32[0] && \ !(a)->addr32[1] && \ !(a)->addr32[2] && \ !(a)->addr32[3] ) \ #define PF_MATCHA(n, a, m, b, f) \ pf_match_addr(n, a, m, b, f) #define PF_ACPY(a, b, f) \ pf_addrcpy(a, b, f) #define PF_AINC(a, f) \ pf_addr_inc(a, f) #define PF_POOLMASK(a, b, c, d, f) \ pf_poolmask(a, b, c, d, f) #else /* Just IPv4 */ #ifdef PF_INET_ONLY #define PF_AEQ(a, b, c) \ ((a)->addr32[0] == (b)->addr32[0]) #define PF_ANEQ(a, b, c) \ ((a)->addr32[0] != (b)->addr32[0]) #define PF_AZERO(a, c) \ (!(a)->addr32[0]) #define PF_MATCHA(n, a, m, b, f) \ pf_match_addr(n, a, m, b, f) #define PF_ACPY(a, b, f) \ (a)->v4.s_addr = (b)->v4.s_addr #define PF_AINC(a, f) \ do { \ (a)->addr32[0] = htonl(ntohl((a)->addr32[0]) + 1); \ } while (0) #define PF_POOLMASK(a, b, c, d, f) \ do { \ (a)->addr32[0] = ((b)->addr32[0] & (c)->addr32[0]) | \ (((c)->addr32[0] ^ 0xffffffff ) & (d)->addr32[0]); \ } while (0) #endif /* PF_INET_ONLY */ #endif /* PF_INET6_ONLY */ #endif /* PF_INET_INET6 */ /* * XXX callers not FIB-aware in our version of pf yet. * OpenBSD fixed it later it seems, 2010/05/07 13:33:16 claudio. */ #define PF_MISMATCHAW(aw, x, af, neg, ifp, rtid) \ ( \ (((aw)->type == PF_ADDR_NOROUTE && \ pf_routable((x), (af), NULL, (rtid))) || \ (((aw)->type == PF_ADDR_URPFFAILED && (ifp) != NULL && \ pf_routable((x), (af), (ifp), (rtid))) || \ ((aw)->type == PF_ADDR_RTLABEL && \ !pf_rtlabel_match((x), (af), (aw), (rtid))) || \ ((aw)->type == PF_ADDR_TABLE && \ !pfr_match_addr((aw)->p.tbl, (x), (af))) || \ ((aw)->type == PF_ADDR_DYNIFTL && \ !pfi_match_addr((aw)->p.dyn, (x), (af))) || \ ((aw)->type == PF_ADDR_RANGE && \ !pf_match_addr_range(&(aw)->v.a.addr, \ &(aw)->v.a.mask, (x), (af))) || \ ((aw)->type == PF_ADDR_ADDRMASK && \ !PF_AZERO(&(aw)->v.a.mask, (af)) && \ !PF_MATCHA(0, &(aw)->v.a.addr, \ &(aw)->v.a.mask, (x), (af))))) != \ (neg) \ ) struct pf_rule_uid { uid_t uid[2]; u_int8_t op; }; struct pf_rule_gid { uid_t gid[2]; u_int8_t op; }; struct pf_rule_addr { struct pf_addr_wrap addr; u_int16_t port[2]; u_int8_t neg; u_int8_t port_op; }; struct pf_pooladdr { struct pf_addr_wrap addr; TAILQ_ENTRY(pf_pooladdr) entries; char ifname[IFNAMSIZ]; struct pfi_kif *kif; }; TAILQ_HEAD(pf_palist, pf_pooladdr); struct pf_poolhashkey { union { u_int8_t key8[16]; u_int16_t key16[8]; u_int32_t key32[4]; } pfk; /* 128-bit hash key */ #define key8 pfk.key8 #define key16 pfk.key16 #define key32 pfk.key32 }; struct pf_pool { struct pf_palist list; struct pf_pooladdr *cur; struct pf_poolhashkey key; struct pf_addr counter; int tblidx; u_int16_t proxy_port[2]; u_int8_t port_op; u_int8_t opts; }; /* A packed Operating System description for fingerprinting */ typedef u_int32_t pf_osfp_t; #define PF_OSFP_ANY ((pf_osfp_t)0) #define PF_OSFP_UNKNOWN ((pf_osfp_t)-1) #define PF_OSFP_NOMATCH ((pf_osfp_t)-2) struct pf_osfp_entry { SLIST_ENTRY(pf_osfp_entry) fp_entry; pf_osfp_t fp_os; int fp_enflags; #define PF_OSFP_EXPANDED 0x001 /* expanded entry */ #define PF_OSFP_GENERIC 0x002 /* generic signature */ #define PF_OSFP_NODETAIL 0x004 /* no p0f details */ #define PF_OSFP_LEN 32 char fp_class_nm[PF_OSFP_LEN]; char fp_version_nm[PF_OSFP_LEN]; char fp_subtype_nm[PF_OSFP_LEN]; }; #define PF_OSFP_ENTRY_EQ(a, b) \ ((a)->fp_os == (b)->fp_os && \ memcmp((a)->fp_class_nm, (b)->fp_class_nm, PF_OSFP_LEN) == 0 && \ memcmp((a)->fp_version_nm, (b)->fp_version_nm, PF_OSFP_LEN) == 0 && \ memcmp((a)->fp_subtype_nm, (b)->fp_subtype_nm, PF_OSFP_LEN) == 0) /* handle pf_osfp_t packing */ #define _FP_RESERVED_BIT 1 /* For the special negative #defines */ #define _FP_UNUSED_BITS 1 #define _FP_CLASS_BITS 10 /* OS Class (Windows, Linux) */ #define _FP_VERSION_BITS 10 /* OS version (95, 98, NT, 2.4.54, 3.2) */ #define _FP_SUBTYPE_BITS 10 /* patch level (NT SP4, SP3, ECN patch) */ #define PF_OSFP_UNPACK(osfp, class, version, subtype) do { \ (class) = ((osfp) >> (_FP_VERSION_BITS+_FP_SUBTYPE_BITS)) & \ ((1 << _FP_CLASS_BITS) - 1); \ (version) = ((osfp) >> _FP_SUBTYPE_BITS) & \ ((1 << _FP_VERSION_BITS) - 1);\ (subtype) = (osfp) & ((1 << _FP_SUBTYPE_BITS) - 1); \ } while(0) #define PF_OSFP_PACK(osfp, class, version, subtype) do { \ (osfp) = ((class) & ((1 << _FP_CLASS_BITS) - 1)) << (_FP_VERSION_BITS \ + _FP_SUBTYPE_BITS); \ (osfp) |= ((version) & ((1 << _FP_VERSION_BITS) - 1)) << \ _FP_SUBTYPE_BITS; \ (osfp) |= (subtype) & ((1 << _FP_SUBTYPE_BITS) - 1); \ } while(0) /* the fingerprint of an OSes TCP SYN packet */ typedef u_int64_t pf_tcpopts_t; struct pf_os_fingerprint { SLIST_HEAD(pf_osfp_enlist, pf_osfp_entry) fp_oses; /* list of matches */ pf_tcpopts_t fp_tcpopts; /* packed TCP options */ u_int16_t fp_wsize; /* TCP window size */ u_int16_t fp_psize; /* ip->ip_len */ u_int16_t fp_mss; /* TCP MSS */ u_int16_t fp_flags; #define PF_OSFP_WSIZE_MOD 0x0001 /* Window modulus */ #define PF_OSFP_WSIZE_DC 0x0002 /* Window don't care */ #define PF_OSFP_WSIZE_MSS 0x0004 /* Window multiple of MSS */ #define PF_OSFP_WSIZE_MTU 0x0008 /* Window multiple of MTU */ #define PF_OSFP_PSIZE_MOD 0x0010 /* packet size modulus */ #define PF_OSFP_PSIZE_DC 0x0020 /* packet size don't care */ #define PF_OSFP_WSCALE 0x0040 /* TCP window scaling */ #define PF_OSFP_WSCALE_MOD 0x0080 /* TCP window scale modulus */ #define PF_OSFP_WSCALE_DC 0x0100 /* TCP window scale dont-care */ #define PF_OSFP_MSS 0x0200 /* TCP MSS */ #define PF_OSFP_MSS_MOD 0x0400 /* TCP MSS modulus */ #define PF_OSFP_MSS_DC 0x0800 /* TCP MSS dont-care */ #define PF_OSFP_DF 0x1000 /* IPv4 don't fragment bit */ #define PF_OSFP_TS0 0x2000 /* Zero timestamp */ #define PF_OSFP_INET6 0x4000 /* IPv6 */ u_int8_t fp_optcnt; /* TCP option count */ u_int8_t fp_wscale; /* TCP window scaling */ u_int8_t fp_ttl; /* IPv4 TTL */ #define PF_OSFP_MAXTTL_OFFSET 40 /* TCP options packing */ #define PF_OSFP_TCPOPT_NOP 0x0 /* TCP NOP option */ #define PF_OSFP_TCPOPT_WSCALE 0x1 /* TCP window scaling option */ #define PF_OSFP_TCPOPT_MSS 0x2 /* TCP max segment size opt */ #define PF_OSFP_TCPOPT_SACK 0x3 /* TCP SACK OK option */ #define PF_OSFP_TCPOPT_TS 0x4 /* TCP timestamp option */ #define PF_OSFP_TCPOPT_BITS 3 /* bits used by each option */ #define PF_OSFP_MAX_OPTS \ (sizeof(((struct pf_os_fingerprint *)0)->fp_tcpopts) * 8) \ / PF_OSFP_TCPOPT_BITS SLIST_ENTRY(pf_os_fingerprint) fp_next; }; struct pf_osfp_ioctl { struct pf_osfp_entry fp_os; pf_tcpopts_t fp_tcpopts; /* packed TCP options */ u_int16_t fp_wsize; /* TCP window size */ u_int16_t fp_psize; /* ip->ip_len */ u_int16_t fp_mss; /* TCP MSS */ u_int16_t fp_flags; u_int8_t fp_optcnt; /* TCP option count */ u_int8_t fp_wscale; /* TCP window scaling */ u_int8_t fp_ttl; /* IPv4 TTL */ int fp_getnum; /* DIOCOSFPGET number */ }; union pf_rule_ptr { struct pf_rule *ptr; u_int32_t nr; }; #define PF_ANCHOR_NAME_SIZE 64 struct pf_rule { struct pf_rule_addr src; struct pf_rule_addr dst; #define PF_SKIP_IFP 0 #define PF_SKIP_DIR 1 #define PF_SKIP_AF 2 #define PF_SKIP_PROTO 3 #define PF_SKIP_SRC_ADDR 4 #define PF_SKIP_SRC_PORT 5 #define PF_SKIP_DST_ADDR 6 #define PF_SKIP_DST_PORT 7 #define PF_SKIP_COUNT 8 union pf_rule_ptr skip[PF_SKIP_COUNT]; #define PF_RULE_LABEL_SIZE 64 char label[PF_RULE_LABEL_SIZE]; #define PF_QNAME_SIZE 64 char ifname[IFNAMSIZ]; char qname[PF_QNAME_SIZE]; char pqname[PF_QNAME_SIZE]; #define PF_TAG_NAME_SIZE 64 char tagname[PF_TAG_NAME_SIZE]; char match_tagname[PF_TAG_NAME_SIZE]; char overload_tblname[PF_TABLE_NAME_SIZE]; TAILQ_ENTRY(pf_rule) entries; struct pf_pool rpool; u_int64_t evaluations; u_int64_t packets[2]; u_int64_t bytes[2]; struct pfi_kif *kif; struct pf_anchor *anchor; struct pfr_ktable *overload_tbl; pf_osfp_t os_fingerprint; int rtableid; u_int32_t timeout[PFTM_MAX]; u_int32_t states_cur; u_int32_t states_tot; u_int32_t max_states; u_int32_t src_nodes; u_int32_t max_src_nodes; u_int32_t max_src_states; u_int32_t spare1; /* netgraph */ u_int32_t max_src_conn; struct { u_int32_t limit; u_int32_t seconds; } max_src_conn_rate; u_int32_t qid; u_int32_t pqid; u_int32_t rt_listid; u_int32_t nr; u_int32_t prob; uid_t cuid; pid_t cpid; u_int16_t return_icmp; u_int16_t return_icmp6; u_int16_t max_mss; u_int16_t tag; u_int16_t match_tag; u_int16_t spare2; /* netgraph */ struct pf_rule_uid uid; struct pf_rule_gid gid; u_int32_t rule_flag; u_int8_t action; u_int8_t direction; u_int8_t log; u_int8_t logif; u_int8_t quick; u_int8_t ifnot; u_int8_t match_tag_not; u_int8_t natpass; #define PF_STATE_NORMAL 0x1 #define PF_STATE_MODULATE 0x2 #define PF_STATE_SYNPROXY 0x3 u_int8_t keep_state; sa_family_t af; u_int8_t proto; u_int8_t type; u_int8_t code; u_int8_t flags; u_int8_t flagset; u_int8_t min_ttl; u_int8_t allow_opts; u_int8_t rt; u_int8_t return_ttl; u_int8_t tos; u_int8_t set_tos; u_int8_t anchor_relative; u_int8_t anchor_wildcard; #define PF_FLUSH 0x01 #define PF_FLUSH_GLOBAL 0x02 u_int8_t flush; struct { struct pf_addr addr; u_int16_t port; } divert; }; /* rule flags */ #define PFRULE_DROP 0x0000 #define PFRULE_RETURNRST 0x0001 #define PFRULE_FRAGMENT 0x0002 #define PFRULE_RETURNICMP 0x0004 #define PFRULE_RETURN 0x0008 #define PFRULE_NOSYNC 0x0010 #define PFRULE_SRCTRACK 0x0020 /* track source states */ #define PFRULE_RULESRCTRACK 0x0040 /* per rule */ /* scrub flags */ #define PFRULE_NODF 0x0100 #define PFRULE_FRAGCROP 0x0200 /* non-buffering frag cache */ #define PFRULE_FRAGDROP 0x0400 /* drop funny fragments */ #define PFRULE_RANDOMID 0x0800 #define PFRULE_REASSEMBLE_TCP 0x1000 #define PFRULE_SET_TOS 0x2000 /* rule flags again */ #define PFRULE_IFBOUND 0x00010000 /* if-bound */ #define PFRULE_STATESLOPPY 0x00020000 /* sloppy state tracking */ #define PFRULE_PFLOW 0x00040000 #define PFSTATE_HIWAT 10000 /* default state table size */ #define PFSTATE_ADAPT_START 6000 /* default adaptive timeout start */ #define PFSTATE_ADAPT_END 12000 /* default adaptive timeout end */ struct pf_threshold { u_int32_t limit; #define PF_THRESHOLD_MULT 1000 #define PF_THRESHOLD_MAX 0xffffffff / PF_THRESHOLD_MULT u_int32_t seconds; u_int32_t count; u_int32_t last; }; struct pf_src_node { RB_ENTRY(pf_src_node) entry; struct pf_addr addr; struct pf_addr raddr; union pf_rule_ptr rule; struct pfi_kif *kif; u_int64_t bytes[2]; u_int64_t packets[2]; u_int32_t states; u_int32_t conn; struct pf_threshold conn_rate; u_int32_t creation; u_int32_t expire; sa_family_t af; u_int8_t ruletype; }; #define PFSNODE_HIWAT 10000 /* default source node table size */ struct pf_state_scrub { struct timeval pfss_last; /* time received last packet */ u_int32_t pfss_tsecr; /* last echoed timestamp */ u_int32_t pfss_tsval; /* largest timestamp */ u_int32_t pfss_tsval0; /* original timestamp */ u_int16_t pfss_flags; #define PFSS_TIMESTAMP 0x0001 /* modulate timestamp */ #define PFSS_PAWS 0x0010 /* stricter PAWS checks */ #define PFSS_PAWS_IDLED 0x0020 /* was idle too long. no PAWS */ #define PFSS_DATA_TS 0x0040 /* timestamp on data packets */ #define PFSS_DATA_NOTS 0x0080 /* no timestamp on data packets */ u_int8_t pfss_ttl; /* stashed TTL */ u_int8_t pad; u_int32_t pfss_ts_mod; /* timestamp modulation */ }; struct pf_state_host { struct pf_addr addr; u_int16_t port; u_int16_t pad; }; struct pf_state_peer { struct pf_state_scrub *scrub; /* state is scrubbed */ u_int32_t seqlo; /* Max sequence number sent */ u_int32_t seqhi; /* Max the other end ACKd + win */ u_int32_t seqdiff; /* Sequence number modulator */ u_int16_t max_win; /* largest window (pre scaling) */ u_int16_t mss; /* Maximum segment size option */ u_int8_t state; /* active state level */ u_int8_t wscale; /* window scaling factor */ u_int8_t tcp_est; /* Did we reach TCPS_ESTABLISHED */ u_int8_t pad[1]; }; TAILQ_HEAD(pf_state_queue, pf_state); /* keep synced with struct pf_state_key, used in RB_FIND */ struct pf_state_key_cmp { struct pf_addr addr[2]; u_int16_t port[2]; sa_family_t af; u_int8_t proto; u_int8_t pad[2]; }; struct pf_state_item { TAILQ_ENTRY(pf_state_item) entry; struct pf_state *s; }; TAILQ_HEAD(pf_statelisthead, pf_state_item); struct pf_state_key { struct pf_addr addr[2]; u_int16_t port[2]; sa_family_t af; u_int8_t proto; u_int8_t pad[2]; RB_ENTRY(pf_state_key) entry; struct pf_statelisthead states; struct pf_state_key *reverse; struct inpcb *inp; }; /* keep synced with struct pf_state, used in RB_FIND */ struct pf_state_cmp { u_int64_t id; u_int32_t creatorid; u_int8_t direction; u_int8_t pad[3]; }; struct pf_state { u_int64_t id; u_int32_t creatorid; u_int8_t direction; #ifdef __FreeBSD__ u_int8_t pad[2]; u_int8_t local_flags; #define PFSTATE_EXPIRING 0x01 #else u_int8_t pad[3]; #endif TAILQ_ENTRY(pf_state) sync_list; TAILQ_ENTRY(pf_state) entry_list; RB_ENTRY(pf_state) entry_id; struct pf_state_peer src; struct pf_state_peer dst; union pf_rule_ptr rule; union pf_rule_ptr anchor; union pf_rule_ptr nat_rule; struct pf_addr rt_addr; struct pf_state_key *key[2]; /* addresses stack and wire */ struct pfi_kif *kif; struct pfi_kif *rt_kif; struct pf_src_node *src_node; struct pf_src_node *nat_src_node; u_int64_t packets[2]; u_int64_t bytes[2]; u_int32_t creation; u_int32_t expire; u_int32_t pfsync_time; u_int16_t tag; u_int8_t log; u_int8_t state_flags; #define PFSTATE_ALLOWOPTS 0x01 #define PFSTATE_SLOPPY 0x02 #define PFSTATE_PFLOW 0x04 #define PFSTATE_NOSYNC 0x08 #define PFSTATE_ACK 0x10 u_int8_t timeout; u_int8_t sync_state; /* PFSYNC_S_x */ /* XXX */ u_int8_t sync_updates; u_int8_t _tail[3]; }; /* * Unified state structures for pulling states out of the kernel * used by pfsync(4) and the pf(4) ioctl. */ struct pfsync_state_scrub { u_int16_t pfss_flags; u_int8_t pfss_ttl; /* stashed TTL */ #define PFSYNC_SCRUB_FLAG_VALID 0x01 u_int8_t scrub_flag; u_int32_t pfss_ts_mod; /* timestamp modulation */ } __packed; struct pfsync_state_peer { struct pfsync_state_scrub scrub; /* state is scrubbed */ u_int32_t seqlo; /* Max sequence number sent */ u_int32_t seqhi; /* Max the other end ACKd + win */ u_int32_t seqdiff; /* Sequence number modulator */ u_int16_t max_win; /* largest window (pre scaling) */ u_int16_t mss; /* Maximum segment size option */ u_int8_t state; /* active state level */ u_int8_t wscale; /* window scaling factor */ u_int8_t pad[6]; } __packed; struct pfsync_state_key { struct pf_addr addr[2]; u_int16_t port[2]; }; struct pfsync_state { u_int32_t id[2]; char ifname[IFNAMSIZ]; struct pfsync_state_key key[2]; struct pfsync_state_peer src; struct pfsync_state_peer dst; struct pf_addr rt_addr; u_int32_t rule; u_int32_t anchor; u_int32_t nat_rule; u_int32_t creation; u_int32_t expire; u_int32_t packets[2][2]; u_int32_t bytes[2][2]; u_int32_t creatorid; sa_family_t af; u_int8_t proto; u_int8_t direction; #ifdef __FreeBSD__ u_int8_t local_flags; #define PFSTATE_EXPIRING 0x01 u_int8_t pad; #endif u_int8_t log; u_int8_t state_flags; u_int8_t timeout; u_int8_t sync_flags; u_int8_t updates; } __packed; #ifdef __FreeBSD__ #ifdef _KERNEL /* pfsync */ typedef int pfsync_state_import_t(struct pfsync_state *, u_int8_t); typedef void pfsync_insert_state_t(struct pf_state *); typedef void pfsync_update_state_t(struct pf_state *); typedef void pfsync_delete_state_t(struct pf_state *); typedef void pfsync_clear_states_t(u_int32_t, const char *); typedef int pfsync_state_in_use_t(struct pf_state *); typedef int pfsync_defer_t(struct pf_state *, struct mbuf *); typedef int pfsync_up_t(void); extern pfsync_state_import_t *pfsync_state_import_ptr; extern pfsync_insert_state_t *pfsync_insert_state_ptr; extern pfsync_update_state_t *pfsync_update_state_ptr; extern pfsync_delete_state_t *pfsync_delete_state_ptr; extern pfsync_clear_states_t *pfsync_clear_states_ptr; extern pfsync_state_in_use_t *pfsync_state_in_use_ptr; extern pfsync_defer_t *pfsync_defer_ptr; extern pfsync_up_t *pfsync_up_ptr; void pfsync_state_export(struct pfsync_state *, struct pf_state *); /* pflow */ typedef int export_pflow_t(struct pf_state *); extern export_pflow_t *export_pflow_ptr; /* pflog */ struct pf_ruleset; struct pf_pdesc; typedef int pflog_packet_t(struct pfi_kif *, struct mbuf *, sa_family_t, u_int8_t, u_int8_t, struct pf_rule *, struct pf_rule *, struct pf_ruleset *, struct pf_pdesc *); extern pflog_packet_t *pflog_packet_ptr; /* pf uid hack */ VNET_DECLARE(int, debug_pfugidhack); #define V_debug_pfugidhack VNET(debug_pfugidhack) #define V_pf_end_threads VNET(pf_end_threads) #endif /* Macros to set/clear/test flags. */ #ifdef _KERNEL #define SET(t, f) ((t) |= (f)) #define CLR(t, f) ((t) &= ~(f)) #define ISSET(t, f) ((t) & (f)) #endif #endif #define PFSYNC_FLAG_SRCNODE 0x04 #define PFSYNC_FLAG_NATSRCNODE 0x08 /* for copies to/from network byte order */ /* ioctl interface also uses network byte order */ #define pf_state_peer_hton(s,d) do { \ (d)->seqlo = htonl((s)->seqlo); \ (d)->seqhi = htonl((s)->seqhi); \ (d)->seqdiff = htonl((s)->seqdiff); \ (d)->max_win = htons((s)->max_win); \ (d)->mss = htons((s)->mss); \ (d)->state = (s)->state; \ (d)->wscale = (s)->wscale; \ if ((s)->scrub) { \ (d)->scrub.pfss_flags = \ htons((s)->scrub->pfss_flags & PFSS_TIMESTAMP); \ (d)->scrub.pfss_ttl = (s)->scrub->pfss_ttl; \ (d)->scrub.pfss_ts_mod = htonl((s)->scrub->pfss_ts_mod);\ (d)->scrub.scrub_flag = PFSYNC_SCRUB_FLAG_VALID; \ } \ } while (0) #define pf_state_peer_ntoh(s,d) do { \ (d)->seqlo = ntohl((s)->seqlo); \ (d)->seqhi = ntohl((s)->seqhi); \ (d)->seqdiff = ntohl((s)->seqdiff); \ (d)->max_win = ntohs((s)->max_win); \ (d)->mss = ntohs((s)->mss); \ (d)->state = (s)->state; \ (d)->wscale = (s)->wscale; \ if ((s)->scrub.scrub_flag == PFSYNC_SCRUB_FLAG_VALID && \ (d)->scrub != NULL) { \ (d)->scrub->pfss_flags = \ ntohs((s)->scrub.pfss_flags) & PFSS_TIMESTAMP; \ (d)->scrub->pfss_ttl = (s)->scrub.pfss_ttl; \ (d)->scrub->pfss_ts_mod = ntohl((s)->scrub.pfss_ts_mod);\ } \ } while (0) #define pf_state_counter_hton(s,d) do { \ d[0] = htonl((s>>32)&0xffffffff); \ d[1] = htonl(s&0xffffffff); \ } while (0) #define pf_state_counter_from_pfsync(s) \ (((u_int64_t)(s[0])<<32) | (u_int64_t)(s[1])) #define pf_state_counter_ntoh(s,d) do { \ d = ntohl(s[0]); \ d = d<<32; \ d += ntohl(s[1]); \ } while (0) TAILQ_HEAD(pf_rulequeue, pf_rule); struct pf_anchor; struct pf_ruleset { struct { struct pf_rulequeue queues[2]; struct { struct pf_rulequeue *ptr; struct pf_rule **ptr_array; u_int32_t rcount; u_int32_t ticket; int open; } active, inactive; } rules[PF_RULESET_MAX]; struct pf_anchor *anchor; u_int32_t tticket; int tables; int topen; }; RB_HEAD(pf_anchor_global, pf_anchor); RB_HEAD(pf_anchor_node, pf_anchor); struct pf_anchor { RB_ENTRY(pf_anchor) entry_global; RB_ENTRY(pf_anchor) entry_node; struct pf_anchor *parent; struct pf_anchor_node children; char name[PF_ANCHOR_NAME_SIZE]; char path[MAXPATHLEN]; struct pf_ruleset ruleset; int refcnt; /* anchor rules */ int match; }; RB_PROTOTYPE(pf_anchor_global, pf_anchor, entry_global, pf_anchor_compare); RB_PROTOTYPE(pf_anchor_node, pf_anchor, entry_node, pf_anchor_compare); #define PF_RESERVED_ANCHOR "_pf" #define PFR_TFLAG_PERSIST 0x00000001 #define PFR_TFLAG_CONST 0x00000002 #define PFR_TFLAG_ACTIVE 0x00000004 #define PFR_TFLAG_INACTIVE 0x00000008 #define PFR_TFLAG_REFERENCED 0x00000010 #define PFR_TFLAG_REFDANCHOR 0x00000020 #define PFR_TFLAG_COUNTERS 0x00000040 /* Adjust masks below when adding flags. */ #define PFR_TFLAG_USRMASK 0x00000043 #define PFR_TFLAG_SETMASK 0x0000003C #define PFR_TFLAG_ALLMASK 0x0000007F struct pfr_table { char pfrt_anchor[MAXPATHLEN]; char pfrt_name[PF_TABLE_NAME_SIZE]; u_int32_t pfrt_flags; u_int8_t pfrt_fback; }; enum { PFR_FB_NONE, PFR_FB_MATCH, PFR_FB_ADDED, PFR_FB_DELETED, PFR_FB_CHANGED, PFR_FB_CLEARED, PFR_FB_DUPLICATE, PFR_FB_NOTMATCH, PFR_FB_CONFLICT, PFR_FB_NOCOUNT, PFR_FB_MAX }; struct pfr_addr { union { struct in_addr _pfra_ip4addr; struct in6_addr _pfra_ip6addr; } pfra_u; u_int8_t pfra_af; u_int8_t pfra_net; u_int8_t pfra_not; u_int8_t pfra_fback; }; #define pfra_ip4addr pfra_u._pfra_ip4addr #define pfra_ip6addr pfra_u._pfra_ip6addr enum { PFR_DIR_IN, PFR_DIR_OUT, PFR_DIR_MAX }; enum { PFR_OP_BLOCK, PFR_OP_PASS, PFR_OP_ADDR_MAX, PFR_OP_TABLE_MAX }; #define PFR_OP_XPASS PFR_OP_ADDR_MAX struct pfr_astats { struct pfr_addr pfras_a; u_int64_t pfras_packets[PFR_DIR_MAX][PFR_OP_ADDR_MAX]; u_int64_t pfras_bytes[PFR_DIR_MAX][PFR_OP_ADDR_MAX]; long pfras_tzero; }; enum { PFR_REFCNT_RULE, PFR_REFCNT_ANCHOR, PFR_REFCNT_MAX }; struct pfr_tstats { struct pfr_table pfrts_t; u_int64_t pfrts_packets[PFR_DIR_MAX][PFR_OP_TABLE_MAX]; u_int64_t pfrts_bytes[PFR_DIR_MAX][PFR_OP_TABLE_MAX]; u_int64_t pfrts_match; u_int64_t pfrts_nomatch; long pfrts_tzero; int pfrts_cnt; int pfrts_refcnt[PFR_REFCNT_MAX]; }; #define pfrts_name pfrts_t.pfrt_name #define pfrts_flags pfrts_t.pfrt_flags #ifndef _SOCKADDR_UNION_DEFINED #define _SOCKADDR_UNION_DEFINED union sockaddr_union { struct sockaddr sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; }; #endif /* _SOCKADDR_UNION_DEFINED */ struct pfr_kcounters { u_int64_t pfrkc_packets[PFR_DIR_MAX][PFR_OP_ADDR_MAX]; u_int64_t pfrkc_bytes[PFR_DIR_MAX][PFR_OP_ADDR_MAX]; }; SLIST_HEAD(pfr_kentryworkq, pfr_kentry); struct pfr_kentry { struct radix_node pfrke_node[2]; union sockaddr_union pfrke_sa; SLIST_ENTRY(pfr_kentry) pfrke_workq; union { struct pfr_kcounters *pfrke_counters; #if 0 struct pfr_kroute *pfrke_route; #endif } u; long pfrke_tzero; u_int8_t pfrke_af; u_int8_t pfrke_net; u_int8_t pfrke_not; u_int8_t pfrke_mark; }; #define pfrke_counters u.pfrke_counters #define pfrke_route u.pfrke_route SLIST_HEAD(pfr_ktableworkq, pfr_ktable); RB_HEAD(pfr_ktablehead, pfr_ktable); struct pfr_ktable { struct pfr_tstats pfrkt_ts; RB_ENTRY(pfr_ktable) pfrkt_tree; SLIST_ENTRY(pfr_ktable) pfrkt_workq; struct radix_node_head *pfrkt_ip4; struct radix_node_head *pfrkt_ip6; struct pfr_ktable *pfrkt_shadow; struct pfr_ktable *pfrkt_root; struct pf_ruleset *pfrkt_rs; long pfrkt_larg; int pfrkt_nflags; }; #define pfrkt_t pfrkt_ts.pfrts_t #define pfrkt_name pfrkt_t.pfrt_name #define pfrkt_anchor pfrkt_t.pfrt_anchor #define pfrkt_ruleset pfrkt_t.pfrt_ruleset #define pfrkt_flags pfrkt_t.pfrt_flags #define pfrkt_cnt pfrkt_ts.pfrts_cnt #define pfrkt_refcnt pfrkt_ts.pfrts_refcnt #define pfrkt_packets pfrkt_ts.pfrts_packets #define pfrkt_bytes pfrkt_ts.pfrts_bytes #define pfrkt_match pfrkt_ts.pfrts_match #define pfrkt_nomatch pfrkt_ts.pfrts_nomatch #define pfrkt_tzero pfrkt_ts.pfrts_tzero RB_HEAD(pf_state_tree, pf_state_key); RB_PROTOTYPE(pf_state_tree, pf_state_key, entry, pf_state_compare_key); RB_HEAD(pf_state_tree_ext_gwy, pf_state_key); RB_PROTOTYPE(pf_state_tree_ext_gwy, pf_state_key, entry_ext_gwy, pf_state_compare_ext_gwy); RB_HEAD(pfi_ifhead, pfi_kif); /* state tables */ #ifdef __FreeBSD__ #ifdef _KERNEL VNET_DECLARE(struct pf_state_tree, pf_statetbl); #define V_pf_statetbl VNET(pf_statetbl) #endif #else extern struct pf_state_tree pf_statetbl; #endif /* keep synced with pfi_kif, used in RB_FIND */ struct pfi_kif_cmp { char pfik_name[IFNAMSIZ]; }; struct pfi_kif { char pfik_name[IFNAMSIZ]; RB_ENTRY(pfi_kif) pfik_tree; u_int64_t pfik_packets[2][2][2]; u_int64_t pfik_bytes[2][2][2]; u_int32_t pfik_tzero; int pfik_flags; void *pfik_ah_cookie; struct ifnet *pfik_ifp; struct ifg_group *pfik_group; int pfik_states; int pfik_rules; TAILQ_HEAD(, pfi_dynaddr) pfik_dynaddrs; }; enum pfi_kif_refs { PFI_KIF_REF_NONE, PFI_KIF_REF_STATE, PFI_KIF_REF_RULE }; #define PFI_IFLAG_SKIP 0x0100 /* skip filtering on interface */ struct pf_pdesc { struct { int done; uid_t uid; gid_t gid; pid_t pid; } lookup; u_int64_t tot_len; /* Make Mickey money */ union { struct tcphdr *tcp; struct udphdr *udp; struct icmp *icmp; #ifdef INET6 struct icmp6_hdr *icmp6; #endif /* INET6 */ void *any; } hdr; struct pf_rule *nat_rule; /* nat/rdr rule applied to packet */ struct ether_header *eh; struct pf_addr *src; /* src address */ struct pf_addr *dst; /* dst address */ u_int16_t *sport; u_int16_t *dport; #ifdef __FreeBSD__ struct pf_mtag *pf_mtag; #endif u_int32_t p_len; /* total length of payload */ u_int16_t *ip_sum; u_int16_t *proto_sum; u_int16_t flags; /* Let SCRUB trigger behavior in * state code. Easier than tags */ #define PFDESC_TCP_NORM 0x0001 /* TCP shall be statefully scrubbed */ #define PFDESC_IP_REAS 0x0002 /* IP frags would've been reassembled */ sa_family_t af; u_int8_t proto; u_int8_t tos; u_int8_t dir; /* direction */ u_int8_t sidx; /* key index for source */ u_int8_t didx; /* key index for destination */ }; /* flags for RDR options */ #define PF_DPORT_RANGE 0x01 /* Dest port uses range */ #define PF_RPORT_RANGE 0x02 /* RDR'ed port uses range */ /* Reasons code for passing/dropping a packet */ #define PFRES_MATCH 0 /* Explicit match of a rule */ #define PFRES_BADOFF 1 /* Bad offset for pull_hdr */ #define PFRES_FRAG 2 /* Dropping following fragment */ #define PFRES_SHORT 3 /* Dropping short packet */ #define PFRES_NORM 4 /* Dropping by normalizer */ #define PFRES_MEMORY 5 /* Dropped due to lacking mem */ #define PFRES_TS 6 /* Bad TCP Timestamp (RFC1323) */ #define PFRES_CONGEST 7 /* Congestion (of ipintrq) */ #define PFRES_IPOPTIONS 8 /* IP option */ #define PFRES_PROTCKSUM 9 /* Protocol checksum invalid */ #define PFRES_BADSTATE 10 /* State mismatch */ #define PFRES_STATEINS 11 /* State insertion failure */ #define PFRES_MAXSTATES 12 /* State limit */ #define PFRES_SRCLIMIT 13 /* Source node/conn limit */ #define PFRES_SYNPROXY 14 /* SYN proxy */ #define PFRES_MAX 15 /* total+1 */ #define PFRES_NAMES { \ "match", \ "bad-offset", \ "fragment", \ "short", \ "normalize", \ "memory", \ "bad-timestamp", \ "congestion", \ "ip-option", \ "proto-cksum", \ "state-mismatch", \ "state-insert", \ "state-limit", \ "src-limit", \ "synproxy", \ NULL \ } /* Counters for other things we want to keep track of */ #define LCNT_STATES 0 /* states */ #define LCNT_SRCSTATES 1 /* max-src-states */ #define LCNT_SRCNODES 2 /* max-src-nodes */ #define LCNT_SRCCONN 3 /* max-src-conn */ #define LCNT_SRCCONNRATE 4 /* max-src-conn-rate */ #define LCNT_OVERLOAD_TABLE 5 /* entry added to overload table */ #define LCNT_OVERLOAD_FLUSH 6 /* state entries flushed */ #define LCNT_MAX 7 /* total+1 */ #define LCNT_NAMES { \ "max states per rule", \ "max-src-states", \ "max-src-nodes", \ "max-src-conn", \ "max-src-conn-rate", \ "overload table insertion", \ "overload flush states", \ NULL \ } /* UDP state enumeration */ #define PFUDPS_NO_TRAFFIC 0 #define PFUDPS_SINGLE 1 #define PFUDPS_MULTIPLE 2 #define PFUDPS_NSTATES 3 /* number of state levels */ #define PFUDPS_NAMES { \ "NO_TRAFFIC", \ "SINGLE", \ "MULTIPLE", \ NULL \ } /* Other protocol state enumeration */ #define PFOTHERS_NO_TRAFFIC 0 #define PFOTHERS_SINGLE 1 #define PFOTHERS_MULTIPLE 2 #define PFOTHERS_NSTATES 3 /* number of state levels */ #define PFOTHERS_NAMES { \ "NO_TRAFFIC", \ "SINGLE", \ "MULTIPLE", \ NULL \ } #define FCNT_STATE_SEARCH 0 #define FCNT_STATE_INSERT 1 #define FCNT_STATE_REMOVALS 2 #define FCNT_MAX 3 #define SCNT_SRC_NODE_SEARCH 0 #define SCNT_SRC_NODE_INSERT 1 #define SCNT_SRC_NODE_REMOVALS 2 #define SCNT_MAX 3 #define ACTION_SET(a, x) \ do { \ if ((a) != NULL) \ *(a) = (x); \ } while (0) #ifdef __FreeBSD__ #define REASON_SET(a, x) \ do { \ if ((a) != NULL) \ *(a) = (x); \ if (x < PFRES_MAX) \ V_pf_status.counters[x]++; \ } while (0) #else #define REASON_SET(a, x) \ do { \ if ((a) != NULL) \ *(a) = (x); \ if (x < PFRES_MAX) \ pf_status.counters[x]++; \ } while (0) #endif struct pf_status { u_int64_t counters[PFRES_MAX]; u_int64_t lcounters[LCNT_MAX]; /* limit counters */ u_int64_t fcounters[FCNT_MAX]; u_int64_t scounters[SCNT_MAX]; u_int64_t pcounters[2][2][3]; u_int64_t bcounters[2][2]; u_int64_t stateid; u_int32_t running; u_int32_t states; u_int32_t src_nodes; u_int32_t since; u_int32_t debug; u_int32_t hostid; char ifname[IFNAMSIZ]; u_int8_t pf_chksum[PF_MD5_DIGEST_LENGTH]; }; struct cbq_opts { u_int minburst; u_int maxburst; u_int pktsize; u_int maxpktsize; u_int ns_per_byte; u_int maxidle; int minidle; u_int offtime; int flags; }; struct priq_opts { int flags; }; struct hfsc_opts { /* real-time service curve */ u_int rtsc_m1; /* slope of the 1st segment in bps */ u_int rtsc_d; /* the x-projection of m1 in msec */ u_int rtsc_m2; /* slope of the 2nd segment in bps */ /* link-sharing service curve */ u_int lssc_m1; u_int lssc_d; u_int lssc_m2; /* upper-limit service curve */ u_int ulsc_m1; u_int ulsc_d; u_int ulsc_m2; int flags; }; struct pf_altq { char ifname[IFNAMSIZ]; void *altq_disc; /* discipline-specific state */ TAILQ_ENTRY(pf_altq) entries; /* scheduler spec */ u_int8_t scheduler; /* scheduler type */ u_int16_t tbrsize; /* tokenbucket regulator size */ u_int32_t ifbandwidth; /* interface bandwidth */ /* queue spec */ char qname[PF_QNAME_SIZE]; /* queue name */ char parent[PF_QNAME_SIZE]; /* parent name */ u_int32_t parent_qid; /* parent queue id */ u_int32_t bandwidth; /* queue bandwidth */ u_int8_t priority; /* priority */ #ifdef __FreeBSD__ u_int8_t local_flags; /* dynamic interface */ #define PFALTQ_FLAG_IF_REMOVED 0x01 #endif u_int16_t qlimit; /* queue size limit */ u_int16_t flags; /* misc flags */ union { struct cbq_opts cbq_opts; struct priq_opts priq_opts; struct hfsc_opts hfsc_opts; } pq_u; u_int32_t qid; /* return value */ }; struct pf_tagname { TAILQ_ENTRY(pf_tagname) entries; char name[PF_TAG_NAME_SIZE]; u_int16_t tag; int ref; }; struct pf_divert { union { struct in_addr ipv4; struct in6_addr ipv6; } addr; u_int16_t port; }; #define PFFRAG_FRENT_HIWAT 5000 /* Number of fragment entries */ #define PFFRAG_FRAG_HIWAT 1000 /* Number of fragmented packets */ #define PFFRAG_FRCENT_HIWAT 50000 /* Number of fragment cache entries */ #define PFFRAG_FRCACHE_HIWAT 10000 /* Number of fragment descriptors */ #define PFR_KTABLE_HIWAT 1000 /* Number of tables */ #define PFR_KENTRY_HIWAT 200000 /* Number of table entries */ #define PFR_KENTRY_HIWAT_SMALL 100000 /* Number of table entries (tiny hosts) */ /* * ioctl parameter structures */ struct pfioc_pooladdr { u_int32_t action; u_int32_t ticket; u_int32_t nr; u_int32_t r_num; u_int8_t r_action; u_int8_t r_last; u_int8_t af; char anchor[MAXPATHLEN]; struct pf_pooladdr addr; }; struct pfioc_rule { u_int32_t action; u_int32_t ticket; u_int32_t pool_ticket; u_int32_t nr; char anchor[MAXPATHLEN]; char anchor_call[MAXPATHLEN]; struct pf_rule rule; }; struct pfioc_natlook { struct pf_addr saddr; struct pf_addr daddr; struct pf_addr rsaddr; struct pf_addr rdaddr; u_int16_t sport; u_int16_t dport; u_int16_t rsport; u_int16_t rdport; sa_family_t af; u_int8_t proto; u_int8_t direction; }; struct pfioc_state { struct pfsync_state state; }; struct pfioc_src_node_kill { sa_family_t psnk_af; struct pf_rule_addr psnk_src; struct pf_rule_addr psnk_dst; u_int psnk_killed; }; struct pfioc_state_kill { struct pf_state_cmp psk_pfcmp; sa_family_t psk_af; int psk_proto; struct pf_rule_addr psk_src; struct pf_rule_addr psk_dst; char psk_ifname[IFNAMSIZ]; char psk_label[PF_RULE_LABEL_SIZE]; u_int psk_killed; }; struct pfioc_states { int ps_len; union { caddr_t psu_buf; struct pfsync_state *psu_states; } ps_u; #define ps_buf ps_u.psu_buf #define ps_states ps_u.psu_states }; struct pfioc_src_nodes { int psn_len; union { caddr_t psu_buf; struct pf_src_node *psu_src_nodes; } psn_u; #define psn_buf psn_u.psu_buf #define psn_src_nodes psn_u.psu_src_nodes }; struct pfioc_if { char ifname[IFNAMSIZ]; }; struct pfioc_tm { int timeout; int seconds; }; struct pfioc_limit { int index; unsigned limit; }; struct pfioc_altq { u_int32_t action; u_int32_t ticket; u_int32_t nr; struct pf_altq altq; }; struct pfioc_qstats { u_int32_t ticket; u_int32_t nr; void *buf; int nbytes; u_int8_t scheduler; }; struct pfioc_ruleset { u_int32_t nr; char path[MAXPATHLEN]; char name[PF_ANCHOR_NAME_SIZE]; }; #define PF_RULESET_ALTQ (PF_RULESET_MAX) #define PF_RULESET_TABLE (PF_RULESET_MAX+1) struct pfioc_trans { int size; /* number of elements */ int esize; /* size of each element in bytes */ struct pfioc_trans_e { int rs_num; char anchor[MAXPATHLEN]; u_int32_t ticket; } *array; }; #define PFR_FLAG_ATOMIC 0x00000001 #define PFR_FLAG_DUMMY 0x00000002 #define PFR_FLAG_FEEDBACK 0x00000004 #define PFR_FLAG_CLSTATS 0x00000008 #define PFR_FLAG_ADDRSTOO 0x00000010 #define PFR_FLAG_REPLACE 0x00000020 #define PFR_FLAG_ALLRSETS 0x00000040 #define PFR_FLAG_ALLMASK 0x0000007F #ifdef _KERNEL #define PFR_FLAG_USERIOCTL 0x10000000 #endif struct pfioc_table { struct pfr_table pfrio_table; void *pfrio_buffer; int pfrio_esize; int pfrio_size; int pfrio_size2; int pfrio_nadd; int pfrio_ndel; int pfrio_nchange; int pfrio_flags; u_int32_t pfrio_ticket; }; #define pfrio_exists pfrio_nadd #define pfrio_nzero pfrio_nadd #define pfrio_nmatch pfrio_nadd #define pfrio_naddr pfrio_size2 #define pfrio_setflag pfrio_size2 #define pfrio_clrflag pfrio_nadd struct pfioc_iface { char pfiio_name[IFNAMSIZ]; void *pfiio_buffer; int pfiio_esize; int pfiio_size; int pfiio_nzero; int pfiio_flags; }; /* * ioctl operations */ #define DIOCSTART _IO ('D', 1) #define DIOCSTOP _IO ('D', 2) #define DIOCADDRULE _IOWR('D', 4, struct pfioc_rule) #define DIOCGETRULES _IOWR('D', 6, struct pfioc_rule) #define DIOCGETRULE _IOWR('D', 7, struct pfioc_rule) /* XXX cut 8 - 17 */ #define DIOCCLRSTATES _IOWR('D', 18, struct pfioc_state_kill) #define DIOCGETSTATE _IOWR('D', 19, struct pfioc_state) #define DIOCSETSTATUSIF _IOWR('D', 20, struct pfioc_if) #define DIOCGETSTATUS _IOWR('D', 21, struct pf_status) #define DIOCCLRSTATUS _IO ('D', 22) #define DIOCNATLOOK _IOWR('D', 23, struct pfioc_natlook) #define DIOCSETDEBUG _IOWR('D', 24, u_int32_t) #define DIOCGETSTATES _IOWR('D', 25, struct pfioc_states) #define DIOCCHANGERULE _IOWR('D', 26, struct pfioc_rule) /* XXX cut 26 - 28 */ #define DIOCSETTIMEOUT _IOWR('D', 29, struct pfioc_tm) #define DIOCGETTIMEOUT _IOWR('D', 30, struct pfioc_tm) #define DIOCADDSTATE _IOWR('D', 37, struct pfioc_state) #define DIOCCLRRULECTRS _IO ('D', 38) #define DIOCGETLIMIT _IOWR('D', 39, struct pfioc_limit) #define DIOCSETLIMIT _IOWR('D', 40, struct pfioc_limit) #define DIOCKILLSTATES _IOWR('D', 41, struct pfioc_state_kill) #define DIOCSTARTALTQ _IO ('D', 42) #define DIOCSTOPALTQ _IO ('D', 43) #define DIOCADDALTQ _IOWR('D', 45, struct pfioc_altq) #define DIOCGETALTQS _IOWR('D', 47, struct pfioc_altq) #define DIOCGETALTQ _IOWR('D', 48, struct pfioc_altq) #define DIOCCHANGEALTQ _IOWR('D', 49, struct pfioc_altq) #define DIOCGETQSTATS _IOWR('D', 50, struct pfioc_qstats) #define DIOCBEGINADDRS _IOWR('D', 51, struct pfioc_pooladdr) #define DIOCADDADDR _IOWR('D', 52, struct pfioc_pooladdr) #define DIOCGETADDRS _IOWR('D', 53, struct pfioc_pooladdr) #define DIOCGETADDR _IOWR('D', 54, struct pfioc_pooladdr) #define DIOCCHANGEADDR _IOWR('D', 55, struct pfioc_pooladdr) /* XXX cut 55 - 57 */ #define DIOCGETRULESETS _IOWR('D', 58, struct pfioc_ruleset) #define DIOCGETRULESET _IOWR('D', 59, struct pfioc_ruleset) #define DIOCRCLRTABLES _IOWR('D', 60, struct pfioc_table) #define DIOCRADDTABLES _IOWR('D', 61, struct pfioc_table) #define DIOCRDELTABLES _IOWR('D', 62, struct pfioc_table) #define DIOCRGETTABLES _IOWR('D', 63, struct pfioc_table) #define DIOCRGETTSTATS _IOWR('D', 64, struct pfioc_table) #define DIOCRCLRTSTATS _IOWR('D', 65, struct pfioc_table) #define DIOCRCLRADDRS _IOWR('D', 66, struct pfioc_table) #define DIOCRADDADDRS _IOWR('D', 67, struct pfioc_table) #define DIOCRDELADDRS _IOWR('D', 68, struct pfioc_table) #define DIOCRSETADDRS _IOWR('D', 69, struct pfioc_table) #define DIOCRGETADDRS _IOWR('D', 70, struct pfioc_table) #define DIOCRGETASTATS _IOWR('D', 71, struct pfioc_table) #define DIOCRCLRASTATS _IOWR('D', 72, struct pfioc_table) #define DIOCRTSTADDRS _IOWR('D', 73, struct pfioc_table) #define DIOCRSETTFLAGS _IOWR('D', 74, struct pfioc_table) #define DIOCRINADEFINE _IOWR('D', 77, struct pfioc_table) #define DIOCOSFPFLUSH _IO('D', 78) #define DIOCOSFPADD _IOWR('D', 79, struct pf_osfp_ioctl) #define DIOCOSFPGET _IOWR('D', 80, struct pf_osfp_ioctl) #define DIOCXBEGIN _IOWR('D', 81, struct pfioc_trans) #define DIOCXCOMMIT _IOWR('D', 82, struct pfioc_trans) #define DIOCXROLLBACK _IOWR('D', 83, struct pfioc_trans) #define DIOCGETSRCNODES _IOWR('D', 84, struct pfioc_src_nodes) #define DIOCCLRSRCNODES _IO('D', 85) #define DIOCSETHOSTID _IOWR('D', 86, u_int32_t) #define DIOCIGETIFACES _IOWR('D', 87, struct pfioc_iface) #define DIOCSETIFFLAG _IOWR('D', 89, struct pfioc_iface) #define DIOCCLRIFFLAG _IOWR('D', 90, struct pfioc_iface) #define DIOCKILLSRCNODES _IOWR('D', 91, struct pfioc_src_node_kill) #ifdef __FreeBSD__ struct pf_ifspeed { char ifname[IFNAMSIZ]; u_int32_t baudrate; }; #define DIOCGIFSPEED _IOWR('D', 92, struct pf_ifspeed) #endif #ifdef _KERNEL RB_HEAD(pf_src_tree, pf_src_node); RB_PROTOTYPE(pf_src_tree, pf_src_node, entry, pf_src_compare); #ifdef __FreeBSD__ VNET_DECLARE(struct pf_src_tree, tree_src_tracking); #define V_tree_src_tracking VNET(tree_src_tracking) #else extern struct pf_src_tree tree_src_tracking; #endif RB_HEAD(pf_state_tree_id, pf_state); RB_PROTOTYPE(pf_state_tree_id, pf_state, entry_id, pf_state_compare_id); #ifdef __FreeBSD__ VNET_DECLARE(struct pf_state_tree_id, tree_id); #define V_tree_id VNET(tree_id) VNET_DECLARE(struct pf_state_queue, state_list); #define V_state_list VNET(state_list) #else extern struct pf_state_tree_id tree_id; extern struct pf_state_queue state_list; #endif TAILQ_HEAD(pf_poolqueue, pf_pool); #ifdef __FreeBSD__ VNET_DECLARE(struct pf_poolqueue, pf_pools[2]); #define V_pf_pools VNET(pf_pools) #else extern struct pf_poolqueue pf_pools[2]; #endif TAILQ_HEAD(pf_altqqueue, pf_altq); #ifdef __FreeBSD__ VNET_DECLARE(struct pf_altqqueue, pf_altqs[2]); #define V_pf_altqs VNET(pf_altqs) VNET_DECLARE(struct pf_palist, pf_pabuf); #define V_pf_pabuf VNET(pf_pabuf) #else extern struct pf_altqqueue pf_altqs[2]; extern struct pf_palist pf_pabuf; #endif #ifdef __FreeBSD__ VNET_DECLARE(u_int32_t, ticket_altqs_active); #define V_ticket_altqs_active VNET(ticket_altqs_active) VNET_DECLARE(u_int32_t, ticket_altqs_inactive); #define V_ticket_altqs_inactive VNET(ticket_altqs_inactive) VNET_DECLARE(int, altqs_inactive_open); #define V_altqs_inactive_open VNET(altqs_inactive_open) VNET_DECLARE(u_int32_t, ticket_pabuf); #define V_ticket_pabuf VNET(ticket_pabuf) VNET_DECLARE(struct pf_altqqueue *, pf_altqs_active); #define V_pf_altqs_active VNET(pf_altqs_active) VNET_DECLARE(struct pf_altqqueue *, pf_altqs_inactive); #define V_pf_altqs_inactive VNET(pf_altqs_inactive) VNET_DECLARE(struct pf_poolqueue *, pf_pools_active); #define V_pf_pools_active VNET(pf_pools_active) VNET_DECLARE(struct pf_poolqueue *, pf_pools_inactive); #define V_pf_pools_inactive VNET(pf_pools_inactive) #else extern u_int32_t ticket_altqs_active; extern u_int32_t ticket_altqs_inactive; extern int altqs_inactive_open; extern u_int32_t ticket_pabuf; extern struct pf_altqqueue *pf_altqs_active; extern struct pf_altqqueue *pf_altqs_inactive; extern struct pf_poolqueue *pf_pools_active; extern struct pf_poolqueue *pf_pools_inactive; #endif extern int pf_tbladdr_setup(struct pf_ruleset *, struct pf_addr_wrap *); extern void pf_tbladdr_remove(struct pf_addr_wrap *); extern void pf_tbladdr_copyout(struct pf_addr_wrap *); extern void pf_calc_skip_steps(struct pf_rulequeue *); #ifdef __FreeBSD__ #ifdef ALTQ extern void pf_altq_ifnet_event(struct ifnet *, int); #endif VNET_DECLARE(uma_zone_t, pf_src_tree_pl); #define V_pf_src_tree_pl VNET(pf_src_tree_pl) VNET_DECLARE(uma_zone_t, pf_rule_pl); #define V_pf_rule_pl VNET(pf_rule_pl) VNET_DECLARE(uma_zone_t, pf_state_pl); #define V_pf_state_pl VNET(pf_state_pl) VNET_DECLARE(uma_zone_t, pf_state_key_pl); #define V_pf_state_key_pl VNET(pf_state_key_pl) VNET_DECLARE(uma_zone_t, pf_state_item_pl); #define V_pf_state_item_pl VNET(pf_state_item_pl) VNET_DECLARE(uma_zone_t, pf_altq_pl); #define V_pf_altq_pl VNET(pf_altq_pl) VNET_DECLARE(uma_zone_t, pf_pooladdr_pl); #define V_pf_pooladdr_pl VNET(pf_pooladdr_pl) VNET_DECLARE(uma_zone_t, pfr_ktable_pl); #define V_pfr_ktable_pl VNET(pfr_ktable_pl) VNET_DECLARE(uma_zone_t, pfr_kentry_pl); #define V_pfr_kentry_pl VNET(pfr_kentry_pl) VNET_DECLARE(uma_zone_t, pfr_kcounters_pl); #define V_pfr_kcounters_pl VNET(pfr_kcounters_pl) VNET_DECLARE(uma_zone_t, pf_cache_pl); #define V_pf_cache_pl VNET(pf_cache_pl) VNET_DECLARE(uma_zone_t, pf_cent_pl); #define V_pf_cent_pl VNET(pf_cent_pl) VNET_DECLARE(uma_zone_t, pf_state_scrub_pl); #define V_pf_state_scrub_pl VNET(pf_state_scrub_pl) VNET_DECLARE(uma_zone_t, pfi_addr_pl); #define V_pfi_addr_pl VNET(pfi_addr_pl) #else extern struct pool pf_src_tree_pl, pf_rule_pl; extern struct pool pf_state_pl, pf_state_key_pl, pf_state_item_pl, pf_altq_pl, pf_pooladdr_pl; extern struct pool pf_state_scrub_pl; #endif extern void pf_purge_thread(void *); #ifdef __FreeBSD__ extern int pf_purge_expired_src_nodes(int); extern int pf_purge_expired_states(u_int32_t , int); #else extern void pf_purge_expired_src_nodes(int); extern void pf_purge_expired_states(u_int32_t); #endif extern void pf_unlink_state(struct pf_state *); extern void pf_free_state(struct pf_state *); extern int pf_state_insert(struct pfi_kif *, struct pf_state_key *, struct pf_state_key *, struct pf_state *); extern int pf_insert_src_node(struct pf_src_node **, struct pf_rule *, struct pf_addr *, sa_family_t); void pf_src_tree_remove_state(struct pf_state *); extern struct pf_state *pf_find_state_byid(struct pf_state_cmp *); extern struct pf_state *pf_find_state_all(struct pf_state_key_cmp *, u_int, int *); extern void pf_print_state(struct pf_state *); extern void pf_print_flags(u_int8_t); extern u_int16_t pf_cksum_fixup(u_int16_t, u_int16_t, u_int16_t, u_int8_t); +extern u_int16_t pf_proto_cksum_fixup(struct mbuf *, u_int16_t, + u_int16_t, u_int16_t, u_int8_t); #ifdef __FreeBSD__ VNET_DECLARE(struct ifnet *, sync_ifp); #define V_sync_ifp VNET(sync_ifp); VNET_DECLARE(struct pf_rule, pf_default_rule); #define V_pf_default_rule VNET(pf_default_rule) #else extern struct ifnet *sync_ifp; extern struct pf_rule pf_default_rule; #endif extern void pf_addrcpy(struct pf_addr *, struct pf_addr *, u_int8_t); void pf_rm_rule(struct pf_rulequeue *, struct pf_rule *); #ifndef __FreeBSD__ struct pf_divert *pf_find_divert(struct mbuf *); #endif #ifdef INET #ifdef __FreeBSD__ int pf_test(int, struct ifnet *, struct mbuf **, struct ether_header *, struct inpcb *); #else int pf_test(int, struct ifnet *, struct mbuf **, struct ether_header *); #endif #endif /* INET */ #ifdef INET6 #ifdef __FreeBSD__ int pf_test6(int, struct ifnet *, struct mbuf **, struct ether_header *, struct inpcb *); #else int pf_test6(int, struct ifnet *, struct mbuf **, struct ether_header *); #endif void pf_poolmask(struct pf_addr *, struct pf_addr*, struct pf_addr *, struct pf_addr *, u_int8_t); void pf_addr_inc(struct pf_addr *, sa_family_t); #endif /* INET6 */ #ifdef __FreeBSD__ u_int32_t pf_new_isn(struct pf_state *); #endif void *pf_pull_hdr(struct mbuf *, int, void *, int, u_short *, u_short *, sa_family_t); void pf_change_a(void *, u_int16_t *, u_int32_t, u_int8_t); +void pf_change_proto_a(struct mbuf *, void *, u_int16_t *, u_int32_t, + u_int8_t); +void pf_change_tcp_a(struct mbuf *, void *, u_int16_t *, u_int32_t); int pflog_packet(struct pfi_kif *, struct mbuf *, sa_family_t, u_int8_t, u_int8_t, struct pf_rule *, struct pf_rule *, struct pf_ruleset *, struct pf_pdesc *); void pf_send_deferred_syn(struct pf_state *); int pf_match_addr(u_int8_t, struct pf_addr *, struct pf_addr *, struct pf_addr *, sa_family_t); int pf_match_addr_range(struct pf_addr *, struct pf_addr *, struct pf_addr *, sa_family_t); int pf_match(u_int8_t, u_int32_t, u_int32_t, u_int32_t); int pf_match_port(u_int8_t, u_int16_t, u_int16_t, u_int16_t); int pf_match_uid(u_int8_t, uid_t, uid_t, uid_t); int pf_match_gid(u_int8_t, gid_t, gid_t, gid_t); void pf_normalize_init(void); int pf_normalize_ip(struct mbuf **, int, struct pfi_kif *, u_short *, struct pf_pdesc *); int pf_normalize_ip6(struct mbuf **, int, struct pfi_kif *, u_short *, struct pf_pdesc *); int pf_normalize_tcp(int, struct pfi_kif *, struct mbuf *, int, int, void *, struct pf_pdesc *); void pf_normalize_tcp_cleanup(struct pf_state *); int pf_normalize_tcp_init(struct mbuf *, int, struct pf_pdesc *, struct tcphdr *, struct pf_state_peer *, struct pf_state_peer *); int pf_normalize_tcp_stateful(struct mbuf *, int, struct pf_pdesc *, u_short *, struct tcphdr *, struct pf_state *, struct pf_state_peer *, struct pf_state_peer *, int *); u_int32_t pf_state_expires(const struct pf_state *); void pf_purge_expired_fragments(void); int pf_routable(struct pf_addr *addr, sa_family_t af, struct pfi_kif *, int); int pf_rtlabel_match(struct pf_addr *, sa_family_t, struct pf_addr_wrap *, int); #ifdef __FreeBSD__ int pf_socket_lookup(int, struct pf_pdesc *, struct inpcb *); #else int pf_socket_lookup(int, struct pf_pdesc *); #endif struct pf_state_key *pf_alloc_state_key(int); void pf_pkt_addr_changed(struct mbuf *); int pf_state_key_attach(struct pf_state_key *, struct pf_state *, int); void pfr_initialize(void); int pfr_match_addr(struct pfr_ktable *, struct pf_addr *, sa_family_t); void pfr_update_stats(struct pfr_ktable *, struct pf_addr *, sa_family_t, u_int64_t, int, int, int); int pfr_pool_get(struct pfr_ktable *, int *, struct pf_addr *, struct pf_addr **, struct pf_addr **, sa_family_t); void pfr_dynaddr_update(struct pfr_ktable *, struct pfi_dynaddr *); struct pfr_ktable * pfr_attach_table(struct pf_ruleset *, char *, int); void pfr_detach_table(struct pfr_ktable *); int pfr_clr_tables(struct pfr_table *, int *, int); int pfr_add_tables(struct pfr_table *, int, int *, int); int pfr_del_tables(struct pfr_table *, int, int *, int); int pfr_get_tables(struct pfr_table *, struct pfr_table *, int *, int); int pfr_get_tstats(struct pfr_table *, struct pfr_tstats *, int *, int); int pfr_clr_tstats(struct pfr_table *, int, int *, int); int pfr_set_tflags(struct pfr_table *, int, int, int, int *, int *, int); int pfr_clr_addrs(struct pfr_table *, int *, int); int pfr_insert_kentry(struct pfr_ktable *, struct pfr_addr *, long); int pfr_add_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_del_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_set_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int *, int *, int *, int, u_int32_t); int pfr_get_addrs(struct pfr_table *, struct pfr_addr *, int *, int); int pfr_get_astats(struct pfr_table *, struct pfr_astats *, int *, int); int pfr_clr_astats(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_tst_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_ina_begin(struct pfr_table *, u_int32_t *, int *, int); int pfr_ina_rollback(struct pfr_table *, u_int32_t, int *, int); int pfr_ina_commit(struct pfr_table *, u_int32_t, int *, int *, int); int pfr_ina_define(struct pfr_table *, struct pfr_addr *, int, int *, int *, u_int32_t, int); #ifdef __FreeBSD__ VNET_DECLARE(struct pfi_kif *, pfi_all); #define V_pfi_all VNET(pfi_all) #else extern struct pfi_kif *pfi_all; #endif void pfi_initialize(void); #ifdef __FreeBSD__ void pfi_cleanup(void); #endif struct pfi_kif *pfi_kif_get(const char *); void pfi_kif_ref(struct pfi_kif *, enum pfi_kif_refs); void pfi_kif_unref(struct pfi_kif *, enum pfi_kif_refs); int pfi_kif_match(struct pfi_kif *, struct pfi_kif *); void pfi_attach_ifnet(struct ifnet *); void pfi_detach_ifnet(struct ifnet *); void pfi_attach_ifgroup(struct ifg_group *); void pfi_detach_ifgroup(struct ifg_group *); void pfi_group_change(const char *); int pfi_match_addr(struct pfi_dynaddr *, struct pf_addr *, sa_family_t); int pfi_dynaddr_setup(struct pf_addr_wrap *, sa_family_t); void pfi_dynaddr_remove(struct pf_addr_wrap *); void pfi_dynaddr_copyout(struct pf_addr_wrap *); void pfi_update_status(const char *, struct pf_status *); int pfi_get_ifaces(const char *, struct pfi_kif *, int *); int pfi_set_flags(const char *, int); int pfi_clear_flags(const char *, int); #ifdef __FreeBSD__ int pf_match_tag(struct mbuf *, struct pf_rule *, int *, struct pf_mtag *); #else int pf_match_tag(struct mbuf *, struct pf_rule *, int *); #endif u_int16_t pf_tagname2tag(char *); void pf_tag2tagname(u_int16_t, char *); void pf_tag_ref(u_int16_t); void pf_tag_unref(u_int16_t); #ifdef __FreeBSD__ int pf_tag_packet(struct mbuf *, int, int, struct pf_mtag *); #else int pf_tag_packet(struct mbuf *, int, int); #endif u_int32_t pf_qname2qid(char *); void pf_qid2qname(u_int32_t, char *); void pf_qid_unref(u_int32_t); #ifdef __FreeBSD__ VNET_DECLARE(struct pf_status, pf_status); #define V_pf_status VNET(pf_status) #else extern struct pf_status pf_status; #endif #ifdef __FreeBSD__ VNET_DECLARE(uma_zone_t, pf_frent_pl); #define V_pf_frent_pl VNET(pf_frent_pl) VNET_DECLARE(uma_zone_t, pf_frag_pl); #define V_pf_frag_pl VNET(pf_frag_pl) VNET_DECLARE(struct sx, pf_consistency_lock); #define V_pf_consistency_lock VNET(pf_consistency_lock) #else extern struct pool pf_frent_pl, pf_frag_pl; extern struct rwlock pf_consistency_lock; #endif struct pf_pool_limit { void *pp; unsigned limit; }; #ifdef __FreeBSD__ VNET_DECLARE(struct pf_pool_limit, pf_pool_limits[PF_LIMIT_MAX]); #define V_pf_pool_limits VNET(pf_pool_limits) #else extern struct pf_pool_limit pf_pool_limits[PF_LIMIT_MAX]; #endif #ifdef __FreeBSD__ struct pf_frent { LIST_ENTRY(pf_frent) fr_next; struct ip *fr_ip; struct mbuf *fr_m; }; struct pf_frcache { LIST_ENTRY(pf_frcache) fr_next; uint16_t fr_off; uint16_t fr_end; }; struct pf_fragment { RB_ENTRY(pf_fragment) fr_entry; TAILQ_ENTRY(pf_fragment) frag_next; struct in_addr fr_src; struct in_addr fr_dst; u_int8_t fr_p; /* protocol of this fragment */ u_int8_t fr_flags; /* status flags */ u_int16_t fr_id; /* fragment id for reassemble */ u_int16_t fr_max; /* fragment data max */ u_int32_t fr_timeout; #define fr_queue fr_u.fru_queue #define fr_cache fr_u.fru_cache union { LIST_HEAD(pf_fragq, pf_frent) fru_queue; /* buffering */ LIST_HEAD(pf_cacheq, pf_frcache) fru_cache; /* non-buf */ } fr_u; }; #endif /* (__FreeBSD__) */ #endif /* _KERNEL */ #ifdef __FreeBSD__ #ifdef _KERNEL VNET_DECLARE(struct pf_anchor_global, pf_anchors); #define V_pf_anchors VNET(pf_anchors) VNET_DECLARE(struct pf_anchor, pf_main_anchor); #define V_pf_main_anchor VNET(pf_main_anchor) #define pf_main_ruleset V_pf_main_anchor.ruleset #endif #else extern struct pf_anchor_global pf_anchors; extern struct pf_anchor pf_main_anchor; #define pf_main_ruleset pf_main_anchor.ruleset #endif /* these ruleset functions can be linked into userland programs (pfctl) */ int pf_get_ruleset_number(u_int8_t); void pf_init_ruleset(struct pf_ruleset *); int pf_anchor_setup(struct pf_rule *, const struct pf_ruleset *, const char *); int pf_anchor_copyout(const struct pf_ruleset *, const struct pf_rule *, struct pfioc_rule *); void pf_anchor_remove(struct pf_rule *); void pf_remove_if_empty_ruleset(struct pf_ruleset *); struct pf_anchor *pf_find_anchor(const char *); struct pf_ruleset *pf_find_ruleset(const char *); struct pf_ruleset *pf_find_or_create_ruleset(const char *); void pf_rs_initialize(void); #ifndef __FreeBSD__ #ifdef _KERNEL int pf_anchor_copyout(const struct pf_ruleset *, const struct pf_rule *, struct pfioc_rule *); void pf_anchor_remove(struct pf_rule *); #endif /* _KERNEL */ #endif /* The fingerprint functions can be linked into userland programs (tcpdump) */ int pf_osfp_add(struct pf_osfp_ioctl *); #ifdef _KERNEL struct pf_osfp_enlist * pf_osfp_fingerprint(struct pf_pdesc *, struct mbuf *, int, const struct tcphdr *); #endif /* _KERNEL */ struct pf_osfp_enlist * pf_osfp_fingerprint_hdr(const struct ip *, const struct ip6_hdr *, const struct tcphdr *); void pf_osfp_flush(void); int pf_osfp_get(struct pf_osfp_ioctl *); #ifdef __FreeBSD__ int pf_osfp_initialize(void); void pf_osfp_cleanup(void); #else void pf_osfp_initialize(void); #endif int pf_osfp_match(struct pf_osfp_enlist *, pf_osfp_t); struct pf_os_fingerprint * pf_osfp_validate(void); #ifdef _KERNEL void pf_print_host(struct pf_addr *, u_int16_t, u_int8_t); void pf_step_into_anchor(int *, struct pf_ruleset **, int, struct pf_rule **, struct pf_rule **, int *); int pf_step_out_of_anchor(int *, struct pf_ruleset **, int, struct pf_rule **, struct pf_rule **, int *); int pf_map_addr(u_int8_t, struct pf_rule *, struct pf_addr *, struct pf_addr *, struct pf_addr *, struct pf_src_node **); struct pf_rule *pf_get_translation(struct pf_pdesc *, struct mbuf *, int, int, struct pfi_kif *, struct pf_src_node **, struct pf_state_key **, struct pf_state_key **, struct pf_state_key **, struct pf_state_key **, struct pf_addr *, struct pf_addr *, u_int16_t, u_int16_t); int pf_state_key_setup(struct pf_pdesc *, struct pf_rule *, struct pf_state_key **, struct pf_state_key **, struct pf_state_key **, struct pf_state_key **, struct pf_addr *, struct pf_addr *, u_int16_t, u_int16_t); #endif /* _KERNEL */ #endif /* _NET_PFVAR_H_ */