#include "opt_ipfw.h"
#include "opt_inet.h"
#ifndef INET
#error IPFIREWALL requires INET.
#endif
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <sys/ucred.h>
#include <sys/in_cksum.h>
#include <sys/limits.h>
#include <sys/lock.h>
#include <sys/tree.h>
#include <net/if.h>
#include <net/route.h>
#include <net/pfil.h>
#include <net/dummynet/ip_dummynet.h>
#include <sys/thread2.h>
#include <net/netmsg2.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/in_pcb.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/ip_icmp.h>
#include <netinet/tcp.h>
#include <netinet/tcp_seq.h>
#include <netinet/tcp_timer.h>
#include <netinet/tcp_var.h>
#include <netinet/tcpip.h>
#include <netinet/udp.h>
#include <netinet/udp_var.h>
#include <netinet/ip_divert.h>
#include <netinet/if_ether.h>
#include <net/ipfw/ip_fw2.h>
#ifdef IPFIREWALL_DEBUG
#define DPRINTF(fmt, ...) \
do { \
if (fw_debug > 0) \
kprintf(fmt, __VA_ARGS__); \
} while (0)
#else
#define DPRINTF(fmt, ...) ((void)0)
#endif
#define IPFW_AUTOINC_STEP_MIN 1
#define IPFW_AUTOINC_STEP_MAX 1000
#define IPFW_AUTOINC_STEP_DEF 100
#define IPFW_TABLE_MAX_DEF 64
#define IPFW_DEFAULT_RULE 65535
#define IPFW_DEFAULT_SET 31
#define MATCH_REVERSE 0
#define MATCH_FORWARD 1
#define MATCH_NONE 2
#define MATCH_UNKNOWN 3
#define TIME_LEQ(a, b) ((a) - (b) <= 0)
#define IPFW_STATE_TCPFLAGS (TH_SYN | TH_FIN | TH_RST)
#define IPFW_STATE_TCPSTATES (IPFW_STATE_TCPFLAGS | \
(IPFW_STATE_TCPFLAGS << 8))
#define BOTH_SYN (TH_SYN | (TH_SYN << 8))
#define BOTH_FIN (TH_FIN | (TH_FIN << 8))
#define BOTH_RST (TH_RST | (TH_RST << 8))
#define BOTH_FINACK (TH_ACK | (TH_ACK << 8))
#define IPFW_STATE_TCPCLOSED(s) ((s)->st_proto == IPPROTO_TCP && \
(((s)->st_state & BOTH_RST) || \
((s)->st_state & BOTH_FINACK) == BOTH_FINACK))
#define O_ANCHOR O_NOP
#define IPFW_ISXLAT(type) ((type) == O_REDIRECT)
#define IPFW_XLAT_INVALID(s) (IPFW_ISXLAT((s)->st_type) && \
((struct ipfw_xlat *)(s))->xlat_invalid)
#define IPFW_MBUF_XLATINS FW_MBUF_PRIVATE1
#define IPFW_MBUF_XLATFWD FW_MBUF_PRIVATE2
#define IPFW_XLATE_INSERT 0x0001
#define IPFW_XLATE_FORWARD 0x0002
#define IPFW_XLATE_OUTPUT 0x0004
struct netmsg_ipfw {
struct netmsg_base base;
const struct ipfw_ioc_rule *ioc_rule;
struct ip_fw *next_rule;
struct ip_fw *prev_rule;
struct ip_fw *sibling;
uint32_t rule_flags;
struct ip_fw **cross_rules;
};
struct netmsg_del {
struct netmsg_base base;
struct ip_fw *start_rule;
struct ip_fw *prev_rule;
uint16_t rulenum;
uint8_t from_set;
uint8_t to_set;
};
struct netmsg_zent {
struct netmsg_base base;
struct ip_fw *start_rule;
uint16_t rulenum;
uint16_t log_only;
};
struct netmsg_cpstate {
struct netmsg_base base;
struct ipfw_ioc_state *ioc_state;
int state_cntmax;
int state_cnt;
};
struct netmsg_tblent {
struct netmsg_base base;
struct sockaddr *key;
struct sockaddr *netmask;
struct ipfw_tblent *sibling;
int tableid;
};
struct netmsg_tblflush {
struct netmsg_base base;
int tableid;
int destroy;
};
struct netmsg_tblexp {
struct netmsg_base base;
time_t expire;
int tableid;
int cnt;
int expcnt;
struct radix_node_head *rnh;
};
struct ipfw_table_cp {
struct ipfw_ioc_tblent *te;
int te_idx;
int te_cnt;
};
struct ip_fw_local {
u_short offset;
uint8_t proto;
uint16_t src_port;
uint16_t dst_port;
struct in_addr src_ip;
struct in_addr dst_ip;
uint16_t ip_len;
struct tcphdr *tcp;
};
struct ipfw_addrs {
uint32_t addr1;
uint32_t addr2;
};
struct ipfw_ports {
uint16_t port1;
uint16_t port2;
};
struct ipfw_key {
union {
struct ipfw_addrs addrs;
uint64_t value;
} addr_u;
union {
struct ipfw_ports ports;
uint32_t value;
} port_u;
uint8_t proto;
uint8_t swap;
uint16_t rsvd2;
};
#define IPFW_KEY_SWAP_ADDRS 0x1
#define IPFW_KEY_SWAP_PORTS 0x2
#define IPFW_KEY_SWAP_ALL (IPFW_KEY_SWAP_ADDRS | IPFW_KEY_SWAP_PORTS)
struct ipfw_trkcnt {
RB_ENTRY(ipfw_trkcnt) tc_rblink;
struct ipfw_key tc_key;
uintptr_t tc_ruleid;
int tc_refs;
int tc_count;
time_t tc_expire;
uint16_t tc_rulenum;
} __cachealign;
#define tc_addrs tc_key.addr_u.value
#define tc_ports tc_key.port_u.value
#define tc_proto tc_key.proto
#define tc_saddr tc_key.addr_u.addrs.addr1
#define tc_daddr tc_key.addr_u.addrs.addr2
#define tc_sport tc_key.port_u.ports.port1
#define tc_dport tc_key.port_u.ports.port2
RB_HEAD(ipfw_trkcnt_tree, ipfw_trkcnt);
struct ipfw_state;
struct ipfw_track {
RB_ENTRY(ipfw_track) t_rblink;
struct ipfw_key t_key;
struct ip_fw *t_rule;
time_t t_lastexp;
LIST_HEAD(, ipfw_state) t_state_list;
time_t t_expire;
volatile int *t_count;
struct ipfw_trkcnt *t_trkcnt;
TAILQ_ENTRY(ipfw_track) t_link;
};
#define t_addrs t_key.addr_u.value
#define t_ports t_key.port_u.value
#define t_proto t_key.proto
#define t_saddr t_key.addr_u.addrs.addr1
#define t_daddr t_key.addr_u.addrs.addr2
#define t_sport t_key.port_u.ports.port1
#define t_dport t_key.port_u.ports.port2
RB_HEAD(ipfw_track_tree, ipfw_track);
TAILQ_HEAD(ipfw_track_list, ipfw_track);
struct ipfw_state {
RB_ENTRY(ipfw_state) st_rblink;
struct ipfw_key st_key;
time_t st_expire;
struct ip_fw *st_rule;
uint64_t st_pcnt;
uint64_t st_bcnt;
uint32_t st_state;
uint32_t st_ack_fwd;
uint32_t st_seq_fwd;
uint32_t st_ack_rev;
uint32_t st_seq_rev;
uint16_t st_flags;
uint16_t st_type;
struct ipfw_track *st_track;
LIST_ENTRY(ipfw_state) st_trklink;
TAILQ_ENTRY(ipfw_state) st_link;
};
#define st_addrs st_key.addr_u.value
#define st_ports st_key.port_u.value
#define st_proto st_key.proto
#define st_swap st_key.swap
#define IPFW_STATE_F_ACKFWD 0x0001
#define IPFW_STATE_F_SEQFWD 0x0002
#define IPFW_STATE_F_ACKREV 0x0004
#define IPFW_STATE_F_SEQREV 0x0008
#define IPFW_STATE_F_XLATSRC 0x0010
#define IPFW_STATE_F_XLATSLAVE 0x0020
#define IPFW_STATE_F_LINKED 0x0040
#define IPFW_STATE_SCANSKIP(s) ((s)->st_type == O_ANCHOR || \
((s)->st_flags & IPFW_STATE_F_XLATSLAVE))
#define IPFW_STATE_ISDEAD(s) (TIME_LEQ((s)->st_expire, time_uptime) || \
IPFW_XLAT_INVALID((s)))
TAILQ_HEAD(ipfw_state_list, ipfw_state);
RB_HEAD(ipfw_state_tree, ipfw_state);
struct ipfw_xlat {
struct ipfw_state xlat_st;
uint32_t xlat_addr;
uint16_t xlat_port;
uint16_t xlat_dir;
struct ifnet *xlat_ifp;
struct ipfw_xlat *xlat_pair;
int xlat_pcpu;
volatile int xlat_invalid;
volatile uint64_t xlat_crefs;
struct netmsg_base xlat_freenm;
};
#define xlat_type xlat_st.st_type
#define xlat_flags xlat_st.st_flags
#define xlat_rule xlat_st.st_rule
#define xlat_bcnt xlat_st.st_bcnt
#define xlat_pcnt xlat_st.st_pcnt
struct ipfw_tblent {
struct radix_node te_nodes[2];
struct sockaddr_in te_key;
u_long te_use;
time_t te_lastuse;
struct ipfw_tblent *te_sibling;
volatile int te_expired;
};
struct ipfw_context {
struct ip_fw *ipfw_layer3_chain;
struct ip_fw *ipfw_default_rule;
uint64_t ipfw_norule_counter;
uint32_t ipfw_set_disable;
uint8_t ipfw_flags;
struct ip_fw *ipfw_cont_rule;
struct ipfw_xlat *ipfw_cont_xlat;
struct ipfw_state_tree ipfw_state_tree;
struct ipfw_state_list ipfw_state_list;
int ipfw_state_loosecnt;
int ipfw_state_cnt;
union {
struct ipfw_state state;
struct ipfw_track track;
struct ipfw_trkcnt trkcnt;
} ipfw_tmpkey;
struct ipfw_track_tree ipfw_track_tree;
struct ipfw_track_list ipfw_track_list;
struct ipfw_trkcnt *ipfw_trkcnt_spare;
struct callout ipfw_stateto_ch;
time_t ipfw_state_lastexp;
struct netmsg_base ipfw_stateexp_nm;
struct netmsg_base ipfw_stateexp_more;
struct ipfw_state ipfw_stateexp_anch;
struct callout ipfw_trackto_ch;
time_t ipfw_track_lastexp;
struct netmsg_base ipfw_trackexp_nm;
struct netmsg_base ipfw_trackexp_more;
struct ipfw_track ipfw_trackexp_anch;
struct callout ipfw_keepalive_ch;
struct netmsg_base ipfw_keepalive_nm;
struct netmsg_base ipfw_keepalive_more;
struct ipfw_state ipfw_keepalive_anch;
struct callout ipfw_xlatreap_ch;
struct netmsg_base ipfw_xlatreap_nm;
struct ipfw_state_list ipfw_xlatreap;
u_long ipfw_sts_reap;
u_long ipfw_sts_reapfailed;
u_long ipfw_sts_overflow;
u_long ipfw_sts_nomem;
u_long ipfw_sts_tcprecycled;
u_long ipfw_tks_nomem;
u_long ipfw_tks_reap;
u_long ipfw_tks_reapfailed;
u_long ipfw_tks_overflow;
u_long ipfw_tks_cntnomem;
u_long ipfw_frags;
u_long ipfw_defraged;
u_long ipfw_defrag_remote;
u_long ipfw_xlated;
u_long ipfw_xlate_split;
u_long ipfw_xlate_conflicts;
u_long ipfw_xlate_cresolved;
struct radix_node_head *ipfw_tables[];
};
#define IPFW_FLAG_KEEPALIVE 0x01
#define IPFW_FLAG_STATEEXP 0x02
#define IPFW_FLAG_TRACKEXP 0x04
#define IPFW_FLAG_STATEREAP 0x08
#define IPFW_FLAG_TRACKREAP 0x10
#define ipfw_state_tmpkey ipfw_tmpkey.state
#define ipfw_track_tmpkey ipfw_tmpkey.track
#define ipfw_trkcnt_tmpkey ipfw_tmpkey.trkcnt
struct ipfw_global {
int ipfw_state_loosecnt;
time_t ipfw_state_globexp __cachealign;
struct lwkt_token ipfw_trkcnt_token __cachealign;
struct ipfw_trkcnt_tree ipfw_trkcnt_tree;
int ipfw_trkcnt_cnt;
time_t ipfw_track_globexp;
struct ip_fw *ipfw_crossref_free __cachealign;
struct callout ipfw_crossref_ch;
struct netmsg_base ipfw_crossref_nm;
#ifdef KLD_MODULE
int ipfw_refcnt __cachealign;
#endif
} __cachealign;
static struct ipfw_context *ipfw_ctx[MAXCPU];
MALLOC_DEFINE(M_IPFW, "IpFw/IpAcct", "IpFw/IpAcct chain's");
static uint32_t static_count;
static uint32_t static_ioc_len;
static int ipfw_flushing;
static int fw_verbose;
static int verbose_limit;
static int fw_debug;
static int autoinc_step = IPFW_AUTOINC_STEP_DEF;
static int ipfw_table_max = IPFW_TABLE_MAX_DEF;
static int ipfw_sysctl_enable(SYSCTL_HANDLER_ARGS);
static int ipfw_sysctl_autoinc_step(SYSCTL_HANDLER_ARGS);
TUNABLE_INT("net.inet.ip.fw.table_max", &ipfw_table_max);
SYSCTL_NODE(_net_inet_ip, OID_AUTO, fw, CTLFLAG_RW, 0, "Firewall");
SYSCTL_NODE(_net_inet_ip_fw, OID_AUTO, stats, CTLFLAG_RW, 0,
"Firewall statistics");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, enable, CTLTYPE_INT | CTLFLAG_RW,
&fw_enable, 0, ipfw_sysctl_enable, "I", "Enable ipfw");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, autoinc_step, CTLTYPE_INT | CTLFLAG_RW,
&autoinc_step, 0, ipfw_sysctl_autoinc_step, "I",
"Rule number autincrement step");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO,one_pass,CTLFLAG_RW,
&fw_one_pass, 0,
"Only do a single pass through ipfw when using dummynet(4)");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, debug, CTLFLAG_RW,
&fw_debug, 0, "Enable printing of debug ip_fw statements");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, verbose, CTLFLAG_RW,
&fw_verbose, 0, "Log matches to ipfw rules");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, verbose_limit, CTLFLAG_RW,
&verbose_limit, 0, "Set upper limit of matches of ipfw rules logged");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, table_max, CTLFLAG_RD,
&ipfw_table_max, 0, "Max # of tables");
static int ipfw_sysctl_dyncnt(SYSCTL_HANDLER_ARGS);
static int ipfw_sysctl_dynmax(SYSCTL_HANDLER_ARGS);
static int ipfw_sysctl_statecnt(SYSCTL_HANDLER_ARGS);
static int ipfw_sysctl_statemax(SYSCTL_HANDLER_ARGS);
static int ipfw_sysctl_scancnt(SYSCTL_HANDLER_ARGS);
static int ipfw_sysctl_stat(SYSCTL_HANDLER_ARGS);
static uint32_t dyn_ack_lifetime = 300;
static uint32_t dyn_syn_lifetime = 20;
static uint32_t dyn_finwait_lifetime = 20;
static uint32_t dyn_fin_lifetime = 2;
static uint32_t dyn_rst_lifetime = 2;
static uint32_t dyn_udp_lifetime = 10;
static uint32_t dyn_short_lifetime = 5;
static uint32_t dyn_keepalive_interval = 20;
static uint32_t dyn_keepalive_period = 5;
static uint32_t dyn_keepalive = 1;
static struct ipfw_global ipfw_gd;
static int ipfw_state_loosecnt_updthr;
static int ipfw_state_max = 4096;
static int ipfw_track_max = 4096;
static int ipfw_state_headroom;
static int ipfw_state_reap_min = 8;
static int ipfw_state_expire_max = 32;
static int ipfw_state_scan_max = 256;
static int ipfw_keepalive_max = 8;
static int ipfw_track_reap_max = 4;
static int ipfw_track_expire_max = 16;
static int ipfw_track_scan_max = 128;
static eventhandler_tag ipfw_ifaddr_event;
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, dyn_count,
CTLTYPE_INT | CTLFLAG_RD, NULL, 0, ipfw_sysctl_dyncnt, "I",
"Number of states and tracks");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, dyn_max,
CTLTYPE_INT | CTLFLAG_RW, NULL, 0, ipfw_sysctl_dynmax, "I",
"Max number of states and tracks");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, state_cnt,
CTLTYPE_INT | CTLFLAG_RD, NULL, 0, ipfw_sysctl_statecnt, "I",
"Number of states");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, state_max,
CTLTYPE_INT | CTLFLAG_RW, NULL, 0, ipfw_sysctl_statemax, "I",
"Max number of states");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, state_headroom, CTLFLAG_RW,
&ipfw_state_headroom, 0, "headroom for state reap");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, track_cnt, CTLFLAG_RD,
&ipfw_gd.ipfw_trkcnt_cnt, 0, "Number of tracks");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, track_max, CTLFLAG_RW,
&ipfw_track_max, 0, "Max number of tracks");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, static_count, CTLFLAG_RD,
&static_count, 0, "Number of static rules");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_ack_lifetime, CTLFLAG_RW,
&dyn_ack_lifetime, 0, "Lifetime of dyn. rules for acks");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_syn_lifetime, CTLFLAG_RW,
&dyn_syn_lifetime, 0, "Lifetime of dyn. rules for syn");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_fin_lifetime, CTLFLAG_RW,
&dyn_fin_lifetime, 0, "Lifetime of dyn. rules for fin");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_finwait_lifetime, CTLFLAG_RW,
&dyn_finwait_lifetime, 0, "Lifetime of dyn. rules for fin wait");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_rst_lifetime, CTLFLAG_RW,
&dyn_rst_lifetime, 0, "Lifetime of dyn. rules for rst");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_udp_lifetime, CTLFLAG_RW,
&dyn_udp_lifetime, 0, "Lifetime of dyn. rules for UDP");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_short_lifetime, CTLFLAG_RW,
&dyn_short_lifetime, 0, "Lifetime of dyn. rules for other situations");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_keepalive, CTLFLAG_RW,
&dyn_keepalive, 0, "Enable keepalives for dyn. rules");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, state_scan_max,
CTLTYPE_INT | CTLFLAG_RW, &ipfw_state_scan_max, 0, ipfw_sysctl_scancnt,
"I", "# of states to scan for each expire iteration");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, state_expire_max,
CTLTYPE_INT | CTLFLAG_RW, &ipfw_state_expire_max, 0, ipfw_sysctl_scancnt,
"I", "# of states to expire for each expire iteration");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, keepalive_max,
CTLTYPE_INT | CTLFLAG_RW, &ipfw_keepalive_max, 0, ipfw_sysctl_scancnt,
"I", "# of states to expire for each expire iteration");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, state_reap_min,
CTLTYPE_INT | CTLFLAG_RW, &ipfw_state_reap_min, 0, ipfw_sysctl_scancnt,
"I", "# of states to reap for state shortage");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, track_scan_max,
CTLTYPE_INT | CTLFLAG_RW, &ipfw_track_scan_max, 0, ipfw_sysctl_scancnt,
"I", "# of tracks to scan for each expire iteration");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, track_expire_max,
CTLTYPE_INT | CTLFLAG_RW, &ipfw_track_expire_max, 0, ipfw_sysctl_scancnt,
"I", "# of tracks to expire for each expire iteration");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, track_reap_max,
CTLTYPE_INT | CTLFLAG_RW, &ipfw_track_reap_max, 0, ipfw_sysctl_scancnt,
"I", "# of tracks to reap for track shortage");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, state_reap,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_sts_reap), ipfw_sysctl_stat,
"LU", "# of state reaps due to states shortage");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, state_reapfailed,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_sts_reapfailed), ipfw_sysctl_stat,
"LU", "# of state reap failure");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, state_overflow,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_sts_overflow), ipfw_sysctl_stat,
"LU", "# of state overflow");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, state_nomem,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_sts_nomem), ipfw_sysctl_stat,
"LU", "# of state allocation failure");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, state_tcprecycled,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_sts_tcprecycled), ipfw_sysctl_stat,
"LU", "# of state deleted due to fast TCP port recycling");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, track_nomem,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_tks_nomem), ipfw_sysctl_stat,
"LU", "# of track allocation failure");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, track_reap,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_tks_reap), ipfw_sysctl_stat,
"LU", "# of track reap due to tracks shortage");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, track_reapfailed,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_tks_reapfailed), ipfw_sysctl_stat,
"LU", "# of track reap failure");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, track_overflow,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_tks_overflow), ipfw_sysctl_stat,
"LU", "# of track overflow");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, track_cntnomem,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_tks_cntnomem), ipfw_sysctl_stat,
"LU", "# of track counter allocation failure");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, frags,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_frags), ipfw_sysctl_stat,
"LU", "# of IP fragements defraged");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, defraged,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_defraged), ipfw_sysctl_stat,
"LU", "# of IP packets after defrag");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, defrag_remote,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_defrag_remote), ipfw_sysctl_stat,
"LU", "# of IP packets after defrag dispatched to remote cpus");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, xlated,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_xlated), ipfw_sysctl_stat,
"LU", "# address/port translations");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, xlate_split,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_xlate_split), ipfw_sysctl_stat,
"LU", "# address/port translations split between different cpus");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, xlate_conflicts,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_xlate_conflicts), ipfw_sysctl_stat,
"LU", "# address/port translations conflicts on remote cpu");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, xlate_cresolved,
CTLTYPE_ULONG | CTLFLAG_RW, NULL,
__offsetof(struct ipfw_context, ipfw_xlate_cresolved), ipfw_sysctl_stat,
"LU", "# address/port translations conflicts resolved on remote cpu");
static int ipfw_state_cmp(struct ipfw_state *,
struct ipfw_state *);
static int ipfw_trkcnt_cmp(struct ipfw_trkcnt *,
struct ipfw_trkcnt *);
static int ipfw_track_cmp(struct ipfw_track *,
struct ipfw_track *);
RB_PROTOTYPE(ipfw_state_tree, ipfw_state, st_rblink, ipfw_state_cmp);
RB_GENERATE(ipfw_state_tree, ipfw_state, st_rblink, ipfw_state_cmp);
RB_PROTOTYPE(ipfw_trkcnt_tree, ipfw_trkcnt, tc_rblink, ipfw_trkcnt_cmp);
RB_GENERATE(ipfw_trkcnt_tree, ipfw_trkcnt, tc_rblink, ipfw_trkcnt_cmp);
RB_PROTOTYPE(ipfw_track_tree, ipfw_track, t_rblink, ipfw_track_cmp);
RB_GENERATE(ipfw_track_tree, ipfw_track, t_rblink, ipfw_track_cmp);
static int ipfw_chk(struct ip_fw_args *);
static void ipfw_track_expire_ipifunc(void *);
static void ipfw_state_expire_ipifunc(void *);
static void ipfw_keepalive(void *);
static int ipfw_state_expire_start(struct ipfw_context *,
int, int);
static void ipfw_crossref_timeo(void *);
static void ipfw_state_remove(struct ipfw_context *,
struct ipfw_state *);
static void ipfw_xlat_reap_timeo(void *);
static void ipfw_defrag_redispatch(struct mbuf *, int,
struct ip_fw *);
#define IPFW_TRKCNT_TOKGET lwkt_gettoken(&ipfw_gd.ipfw_trkcnt_token)
#define IPFW_TRKCNT_TOKREL lwkt_reltoken(&ipfw_gd.ipfw_trkcnt_token)
#define IPFW_TRKCNT_TOKINIT \
lwkt_token_init(&ipfw_gd.ipfw_trkcnt_token, "ipfw_trkcnt");
static void
sa_maskedcopy(const struct sockaddr *src, struct sockaddr *dst,
const struct sockaddr *netmask)
{
const u_char *cp1 = (const u_char *)src;
u_char *cp2 = (u_char *)dst;
const u_char *cp3 = (const u_char *)netmask;
u_char *cplim = cp2 + *cp3;
u_char *cplim2 = cp2 + *cp1;
*cp2++ = *cp1++; *cp2++ = *cp1++;
cp3 += 2;
if (cplim > cplim2)
cplim = cplim2;
while (cp2 < cplim)
*cp2++ = *cp1++ & *cp3++;
if (cp2 < cplim2)
bzero(cp2, cplim2 - cp2);
}
static __inline uint16_t
pfil_cksum_fixup(uint16_t cksum, uint16_t old, uint16_t new, uint8_t udp)
{
uint32_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);
}
static __inline void
ipfw_key_build(struct ipfw_key *key, in_addr_t saddr, uint16_t sport,
in_addr_t daddr, uint16_t dport, uint8_t proto)
{
key->proto = proto;
key->swap = 0;
if (saddr < daddr) {
key->addr_u.addrs.addr1 = daddr;
key->addr_u.addrs.addr2 = saddr;
key->swap |= IPFW_KEY_SWAP_ADDRS;
} else {
key->addr_u.addrs.addr1 = saddr;
key->addr_u.addrs.addr2 = daddr;
}
if (sport < dport) {
key->port_u.ports.port1 = dport;
key->port_u.ports.port2 = sport;
key->swap |= IPFW_KEY_SWAP_PORTS;
} else {
key->port_u.ports.port1 = sport;
key->port_u.ports.port2 = dport;
}
if (sport == dport && (key->swap & IPFW_KEY_SWAP_ADDRS))
key->swap |= IPFW_KEY_SWAP_PORTS;
if (saddr == daddr && (key->swap & IPFW_KEY_SWAP_PORTS))
key->swap |= IPFW_KEY_SWAP_ADDRS;
}
static __inline void
ipfw_key_4tuple(const struct ipfw_key *key, in_addr_t *saddr, uint16_t *sport,
in_addr_t *daddr, uint16_t *dport)
{
if (key->swap & IPFW_KEY_SWAP_ADDRS) {
*saddr = key->addr_u.addrs.addr2;
*daddr = key->addr_u.addrs.addr1;
} else {
*saddr = key->addr_u.addrs.addr1;
*daddr = key->addr_u.addrs.addr2;
}
if (key->swap & IPFW_KEY_SWAP_PORTS) {
*sport = key->port_u.ports.port2;
*dport = key->port_u.ports.port1;
} else {
*sport = key->port_u.ports.port1;
*dport = key->port_u.ports.port2;
}
}
static int
ipfw_state_cmp(struct ipfw_state *s1, struct ipfw_state *s2)
{
if (s1->st_proto > s2->st_proto)
return (1);
if (s1->st_proto < s2->st_proto)
return (-1);
if (s1->st_addrs > s2->st_addrs)
return (1);
if (s1->st_addrs < s2->st_addrs)
return (-1);
if (s1->st_ports > s2->st_ports)
return (1);
if (s1->st_ports < s2->st_ports)
return (-1);
if (s1->st_swap == s2->st_swap ||
(s1->st_swap ^ s2->st_swap) == IPFW_KEY_SWAP_ALL)
return (0);
if (s1->st_swap > s2->st_swap)
return (1);
else
return (-1);
}
static int
ipfw_trkcnt_cmp(struct ipfw_trkcnt *t1, struct ipfw_trkcnt *t2)
{
if (t1->tc_proto > t2->tc_proto)
return (1);
if (t1->tc_proto < t2->tc_proto)
return (-1);
if (t1->tc_addrs > t2->tc_addrs)
return (1);
if (t1->tc_addrs < t2->tc_addrs)
return (-1);
if (t1->tc_ports > t2->tc_ports)
return (1);
if (t1->tc_ports < t2->tc_ports)
return (-1);
if (t1->tc_ruleid > t2->tc_ruleid)
return (1);
if (t1->tc_ruleid < t2->tc_ruleid)
return (-1);
return (0);
}
static int
ipfw_track_cmp(struct ipfw_track *t1, struct ipfw_track *t2)
{
if (t1->t_proto > t2->t_proto)
return (1);
if (t1->t_proto < t2->t_proto)
return (-1);
if (t1->t_addrs > t2->t_addrs)
return (1);
if (t1->t_addrs < t2->t_addrs)
return (-1);
if (t1->t_ports > t2->t_ports)
return (1);
if (t1->t_ports < t2->t_ports)
return (-1);
if ((uintptr_t)t1->t_rule > (uintptr_t)t2->t_rule)
return (1);
if ((uintptr_t)t1->t_rule < (uintptr_t)t2->t_rule)
return (-1);
return (0);
}
static __inline struct ipfw_state *
ipfw_state_link(struct ipfw_context *ctx, struct ipfw_state *s)
{
struct ipfw_state *dup;
KASSERT((s->st_flags & IPFW_STATE_F_LINKED) == 0,
("state %p was linked", s));
dup = RB_INSERT(ipfw_state_tree, &ctx->ipfw_state_tree, s);
if (dup == NULL) {
TAILQ_INSERT_TAIL(&ctx->ipfw_state_list, s, st_link);
s->st_flags |= IPFW_STATE_F_LINKED;
}
return (dup);
}
static __inline void
ipfw_state_unlink(struct ipfw_context *ctx, struct ipfw_state *s)
{
KASSERT(s->st_flags & IPFW_STATE_F_LINKED,
("state %p was not linked", s));
RB_REMOVE(ipfw_state_tree, &ctx->ipfw_state_tree, s);
TAILQ_REMOVE(&ctx->ipfw_state_list, s, st_link);
s->st_flags &= ~IPFW_STATE_F_LINKED;
}
static void
ipfw_state_max_set(int state_max)
{
ipfw_state_max = state_max;
ipfw_state_loosecnt_updthr = (state_max / 20) / netisr_ncpus;
}
static __inline int
ipfw_state_cntcoll(void)
{
int cpu, state_cnt = 0;
for (cpu = 0; cpu < netisr_ncpus; ++cpu)
state_cnt += ipfw_ctx[cpu]->ipfw_state_cnt;
return (state_cnt);
}
static __inline int
ipfw_state_cntsync(void)
{
int state_cnt;
state_cnt = ipfw_state_cntcoll();
ipfw_gd.ipfw_state_loosecnt = state_cnt;
return (state_cnt);
}
static __inline int
ipfw_free_rule(struct ip_fw *rule)
{
KASSERT(rule->cpuid == mycpuid, ("rule freed on cpu%d", mycpuid));
KASSERT(rule->refcnt > 0, ("invalid refcnt %u", rule->refcnt));
rule->refcnt--;
if (rule->refcnt == 0) {
if (rule->cross_rules != NULL)
kfree(rule->cross_rules, M_IPFW);
kfree(rule, M_IPFW);
return 1;
}
return 0;
}
static void
ipfw_unref_rule(void *priv)
{
ipfw_free_rule(priv);
#ifdef KLD_MODULE
KASSERT(ipfw_gd.ipfw_refcnt > 0,
("invalid ipfw_refcnt %d", ipfw_gd.ipfw_refcnt));
atomic_subtract_int(&ipfw_gd.ipfw_refcnt, 1);
#endif
}
static __inline void
ipfw_ref_rule(struct ip_fw *rule)
{
KASSERT(rule->cpuid == mycpuid, ("rule used on cpu%d", mycpuid));
#ifdef KLD_MODULE
atomic_add_int(&ipfw_gd.ipfw_refcnt, 1);
#endif
rule->refcnt++;
}
#define L3HDR(T, ip) ((T *)((uint32_t *)(ip) + (ip)->ip_hl))
static __inline int
icmptype_match(struct ip *ip, ipfw_insn_u32 *cmd)
{
int type = L3HDR(struct icmp,ip)->icmp_type;
int idx_max = F_LEN(&cmd->o) - F_INSN_SIZE(ipfw_insn);
int idx = type / 32;
if (idx >= idx_max)
return (0);
return (cmd->d[idx] & (1 << (type % 32)));
}
static __inline int
icmpcode_match(struct ip *ip, ipfw_insn_u32 *cmd)
{
int code = L3HDR(struct icmp,ip)->icmp_code;
int idx_max = F_LEN(&cmd->o) - F_INSN_SIZE(ipfw_insn);
int idx = code / 32;
if (idx >= idx_max)
return (0);
return (cmd->d[idx] & (1 << (code % 32)));
}
#define TT ((1 << ICMP_ECHO) | \
(1 << ICMP_ROUTERSOLICIT) | \
(1 << ICMP_TSTAMP) | \
(1 << ICMP_IREQ) | \
(1 << ICMP_MASKREQ))
static int
is_icmp_query(struct ip *ip)
{
int type = L3HDR(struct icmp, ip)->icmp_type;
return (type < 32 && (TT & (1 << type)));
}
#undef TT
static int
flags_match(ipfw_insn *cmd, uint8_t bits)
{
u_char want_clear;
bits = ~bits;
if (((cmd->arg1 & 0xff) & bits) != 0)
return 0;
want_clear = (cmd->arg1 >> 8) & 0xff;
if ((want_clear & bits) != want_clear)
return 0;
return 1;
}
static int
ipopts_match(struct ip *ip, ipfw_insn *cmd)
{
int optlen, bits = 0;
u_char *cp = (u_char *)(ip + 1);
int x = (ip->ip_hl << 2) - sizeof(struct ip);
for (; x > 0; x -= optlen, cp += optlen) {
int opt = cp[IPOPT_OPTVAL];
if (opt == IPOPT_EOL)
break;
if (opt == IPOPT_NOP) {
optlen = 1;
} else {
optlen = cp[IPOPT_OLEN];
if (optlen <= 0 || optlen > x)
return 0;
}
switch (opt) {
case IPOPT_LSRR:
bits |= IP_FW_IPOPT_LSRR;
break;
case IPOPT_SSRR:
bits |= IP_FW_IPOPT_SSRR;
break;
case IPOPT_RR:
bits |= IP_FW_IPOPT_RR;
break;
case IPOPT_TS:
bits |= IP_FW_IPOPT_TS;
break;
default:
break;
}
}
return (flags_match(cmd, bits));
}
static int
tcpopts_match(struct ip *ip, ipfw_insn *cmd)
{
int optlen, bits = 0;
struct tcphdr *tcp = L3HDR(struct tcphdr,ip);
u_char *cp = (u_char *)(tcp + 1);
int x = (tcp->th_off << 2) - sizeof(struct tcphdr);
for (; x > 0; x -= optlen, cp += optlen) {
int opt = cp[0];
if (opt == TCPOPT_EOL)
break;
if (opt == TCPOPT_NOP) {
optlen = 1;
} else {
optlen = cp[1];
if (optlen <= 0)
break;
}
switch (opt) {
case TCPOPT_MAXSEG:
bits |= IP_FW_TCPOPT_MSS;
break;
case TCPOPT_WINDOW:
bits |= IP_FW_TCPOPT_WINDOW;
break;
case TCPOPT_SACK_PERMITTED:
case TCPOPT_SACK:
bits |= IP_FW_TCPOPT_SACK;
break;
case TCPOPT_TIMESTAMP:
bits |= IP_FW_TCPOPT_TS;
break;
case TCPOPT_CC:
case TCPOPT_CCNEW:
case TCPOPT_CCECHO:
bits |= IP_FW_TCPOPT_CC;
break;
default:
break;
}
}
return (flags_match(cmd, bits));
}
static int
iface_match(struct ifnet *ifp, ipfw_insn_if *cmd)
{
if (ifp == NULL)
return 0;
if (cmd->name[0] != '\0') {
if (cmd->p.glob) {
if (kfnmatch(cmd->name, ifp->if_xname, 0) == 0)
return(1);
} else {
if (strncmp(ifp->if_xname, cmd->name, IFNAMSIZ) == 0)
return(1);
}
} else {
struct ifaddr_container *ifac;
TAILQ_FOREACH(ifac, &ifp->if_addrheads[mycpuid], ifa_link) {
struct ifaddr *ia = ifac->ifa;
if (ia->ifa_addr == NULL)
continue;
if (ia->ifa_addr->sa_family != AF_INET)
continue;
if (cmd->p.ip.s_addr == ((struct sockaddr_in *)
(ia->ifa_addr))->sin_addr.s_addr)
return(1);
}
}
return(0);
}
#define SNPARGS(buf, len) buf + len, sizeof(buf) > len ? sizeof(buf) - len : 0
static void
ipfw_log(struct ipfw_context *ctx, struct ip_fw *f, u_int hlen,
struct ether_header *eh, struct mbuf *m, struct ifnet *oif)
{
char *action;
int limit_reached = 0;
char action2[40], proto[48], fragment[28], abuf[INET_ADDRSTRLEN];
fragment[0] = '\0';
proto[0] = '\0';
if (f == NULL) {
if (verbose_limit != 0 &&
ctx->ipfw_norule_counter >= verbose_limit)
return;
ctx->ipfw_norule_counter++;
if (ctx->ipfw_norule_counter == verbose_limit)
limit_reached = verbose_limit;
action = "Refuse";
} else {
ipfw_insn *cmd = ACTION_PTR(f);
ipfw_insn_log *l = (ipfw_insn_log *)cmd;
if (l->max_log != 0 && l->log_left == 0)
return;
l->log_left--;
if (l->log_left == 0)
limit_reached = l->max_log;
cmd += F_LEN(cmd);
if (cmd->opcode == O_PROB)
cmd += F_LEN(cmd);
action = action2;
switch (cmd->opcode) {
case O_DENY:
action = "Deny";
break;
case O_REJECT:
if (cmd->arg1==ICMP_REJECT_RST) {
action = "Reset";
} else if (cmd->arg1==ICMP_UNREACH_HOST) {
action = "Reject";
} else {
ksnprintf(SNPARGS(action2, 0), "Unreach %d",
cmd->arg1);
}
break;
case O_ACCEPT:
action = "Accept";
break;
case O_COUNT:
action = "Count";
break;
case O_DIVERT:
ksnprintf(SNPARGS(action2, 0), "Divert %d", cmd->arg1);
break;
case O_TEE:
ksnprintf(SNPARGS(action2, 0), "Tee %d", cmd->arg1);
break;
case O_SKIPTO:
ksnprintf(SNPARGS(action2, 0), "SkipTo %d", cmd->arg1);
break;
case O_PIPE:
ksnprintf(SNPARGS(action2, 0), "Pipe %d", cmd->arg1);
break;
case O_QUEUE:
ksnprintf(SNPARGS(action2, 0), "Queue %d", cmd->arg1);
break;
case O_FORWARD_IP:
{
ipfw_insn_sa *sa = (ipfw_insn_sa *)cmd;
int len;
len = ksnprintf(SNPARGS(action2, 0),
"Forward to %s",
kinet_ntoa(sa->sa.sin_addr, abuf));
if (sa->sa.sin_port) {
ksnprintf(SNPARGS(action2, len), ":%d",
sa->sa.sin_port);
}
}
break;
default:
action = "UNKNOWN";
break;
}
}
if (hlen == 0) {
ksnprintf(SNPARGS(proto, 0), "MAC");
} else {
struct ip *ip = mtod(m, struct ip *);
struct icmp *const icmp = L3HDR(struct icmp, ip);
struct tcphdr *const tcp = (struct tcphdr *)icmp;
struct udphdr *const udp = (struct udphdr *)icmp;
int ip_off, offset, ip_len;
int len;
ip_off = ntohs(ip->ip_off);
ip_len = ntohs(ip->ip_len);
offset = ip_off & IP_OFFMASK;
switch (ip->ip_p) {
case IPPROTO_TCP:
len = ksnprintf(SNPARGS(proto, 0), "TCP %s",
kinet_ntoa(ip->ip_src, abuf));
if (offset == 0) {
ksnprintf(SNPARGS(proto, len), ":%d %s:%d",
ntohs(tcp->th_sport),
kinet_ntoa(ip->ip_dst, abuf),
ntohs(tcp->th_dport));
} else {
ksnprintf(SNPARGS(proto, len), " %s",
kinet_ntoa(ip->ip_dst, abuf));
}
break;
case IPPROTO_UDP:
len = ksnprintf(SNPARGS(proto, 0), "UDP %s",
kinet_ntoa(ip->ip_src, abuf));
if (offset == 0) {
ksnprintf(SNPARGS(proto, len), ":%d %s:%d",
ntohs(udp->uh_sport),
kinet_ntoa(ip->ip_dst, abuf),
ntohs(udp->uh_dport));
} else {
ksnprintf(SNPARGS(proto, len), " %s",
kinet_ntoa(ip->ip_dst, abuf));
}
break;
case IPPROTO_ICMP:
if (offset == 0) {
len = ksnprintf(SNPARGS(proto, 0),
"ICMP:%u.%u ",
icmp->icmp_type,
icmp->icmp_code);
} else {
len = ksnprintf(SNPARGS(proto, 0), "ICMP ");
}
len += ksnprintf(SNPARGS(proto, len), "%s",
kinet_ntoa(ip->ip_src, abuf));
ksnprintf(SNPARGS(proto, len), " %s",
kinet_ntoa(ip->ip_dst, abuf));
break;
default:
len = ksnprintf(SNPARGS(proto, 0), "P:%d %s", ip->ip_p,
kinet_ntoa(ip->ip_src, abuf));
ksnprintf(SNPARGS(proto, len), " %s",
kinet_ntoa(ip->ip_dst, abuf));
break;
}
if (ip_off & (IP_MF | IP_OFFMASK)) {
ksnprintf(SNPARGS(fragment, 0), " (frag %d:%d@%d%s)",
ntohs(ip->ip_id), ip_len - (ip->ip_hl << 2),
offset << 3, (ip_off & IP_MF) ? "+" : "");
}
}
if (oif || m->m_pkthdr.rcvif) {
log(LOG_SECURITY | LOG_INFO,
"ipfw: %d %s %s %s via %s%s\n",
f ? f->rulenum : -1,
action, proto, oif ? "out" : "in",
oif ? oif->if_xname : m->m_pkthdr.rcvif->if_xname,
fragment);
} else {
log(LOG_SECURITY | LOG_INFO,
"ipfw: %d %s %s [no if info]%s\n",
f ? f->rulenum : -1,
action, proto, fragment);
}
if (limit_reached) {
log(LOG_SECURITY | LOG_NOTICE,
"ipfw: limit %d reached on entry %d\n",
limit_reached, f ? f->rulenum : -1);
}
}
#undef SNPARGS
static void
ipfw_xlat_reap(struct ipfw_xlat *x, struct ipfw_xlat *slave_x)
{
struct ip_fw *rule = slave_x->xlat_rule;
KKASSERT(rule->cpuid == mycpuid);
kfree(x, M_IPFW);
kfree(slave_x, M_IPFW);
rule->cross_refs--;
}
static void
ipfw_xlat_reap_dispatch(netmsg_t nm)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ipfw_state *s, *ns;
ASSERT_NETISR_NCPUS(mycpuid);
crit_enter();
netisr_replymsg(&ctx->ipfw_xlatreap_nm, 0);
crit_exit();
TAILQ_FOREACH_MUTABLE(s, &ctx->ipfw_xlatreap, st_link, ns) {
struct ipfw_xlat *x = (struct ipfw_xlat *)s;
struct ipfw_xlat *slave_x = x->xlat_pair;
uint64_t crefs;
crefs = slave_x->xlat_crefs + x->xlat_crefs;
if (crefs == 0) {
TAILQ_REMOVE(&ctx->ipfw_xlatreap, &x->xlat_st, st_link);
ipfw_xlat_reap(x, slave_x);
}
}
if (!TAILQ_EMPTY(&ctx->ipfw_xlatreap)) {
callout_reset(&ctx->ipfw_xlatreap_ch, 2, ipfw_xlat_reap_timeo,
&ctx->ipfw_xlatreap_nm);
}
}
static void
ipfw_xlat_reap_timeo(void *xnm)
{
struct netmsg_base *nm = xnm;
KKASSERT(mycpuid < netisr_ncpus);
crit_enter();
if (nm->lmsg.ms_flags & MSGF_DONE)
netisr_sendmsg_oncpu(nm);
crit_exit();
}
static void
ipfw_xlat_free_dispatch(netmsg_t nmsg)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ipfw_xlat *x = nmsg->lmsg.u.ms_resultp;
struct ipfw_xlat *slave_x = x->xlat_pair;
uint64_t crefs;
ASSERT_NETISR_NCPUS(mycpuid);
KKASSERT(slave_x != NULL);
KKASSERT(slave_x->xlat_invalid && x->xlat_invalid);
KASSERT((x->xlat_flags & IPFW_STATE_F_LINKED) == 0,
("master xlat is still linked"));
if (slave_x->xlat_flags & IPFW_STATE_F_LINKED)
ipfw_state_unlink(ctx, &slave_x->xlat_st);
slave_x->xlat_crefs--;
crefs = slave_x->xlat_crefs + x->xlat_crefs;
if (crefs == 0) {
ipfw_xlat_reap(x, slave_x);
return;
}
if (TAILQ_EMPTY(&ctx->ipfw_xlatreap)) {
callout_reset(&ctx->ipfw_xlatreap_ch, 2, ipfw_xlat_reap_timeo,
&ctx->ipfw_xlatreap_nm);
}
TAILQ_INSERT_TAIL(&ctx->ipfw_xlatreap, &x->xlat_st, st_link);
}
static __inline void
ipfw_xlat_invalidate(struct ipfw_xlat *x)
{
x->xlat_invalid = 1;
x->xlat_pair->xlat_invalid = 1;
}
static void
ipfw_state_del(struct ipfw_context *ctx, struct ipfw_state *s)
{
struct ipfw_xlat *x, *slave_x;
struct netmsg_base *nm;
KASSERT(s->st_type == O_KEEP_STATE || s->st_type == O_LIMIT ||
IPFW_ISXLAT(s->st_type), ("invalid state type %u", s->st_type));
KASSERT((s->st_flags & IPFW_STATE_F_XLATSLAVE) == 0,
("delete slave xlat"));
KASSERT(ctx->ipfw_state_cnt > 0,
("invalid state count %d", ctx->ipfw_state_cnt));
ctx->ipfw_state_cnt--;
if (ctx->ipfw_state_loosecnt > 0)
ctx->ipfw_state_loosecnt--;
if (s->st_track != NULL) {
struct ipfw_track *t = s->st_track;
KASSERT(!LIST_EMPTY(&t->t_state_list),
("track state list is empty"));
LIST_REMOVE(s, st_trklink);
KASSERT(*t->t_count > 0,
("invalid track count %d", *t->t_count));
atomic_subtract_int(t->t_count, 1);
}
ipfw_state_unlink(ctx, s);
if (!IPFW_ISXLAT(s->st_type)) {
kfree(s, M_IPFW);
return;
}
x = (struct ipfw_xlat *)s;
if (x->xlat_pair == NULL) {
kfree(x, M_IPFW);
return;
}
slave_x = x->xlat_pair;
KKASSERT(slave_x->xlat_flags & IPFW_STATE_F_XLATSLAVE);
if (x->xlat_pcpu == mycpuid) {
KKASSERT(x->xlat_crefs == 0);
KKASSERT(slave_x->xlat_crefs == 0);
if (slave_x->xlat_flags & IPFW_STATE_F_LINKED)
ipfw_state_unlink(ctx, &slave_x->xlat_st);
kfree(x, M_IPFW);
kfree(slave_x, M_IPFW);
return;
}
ipfw_xlat_invalidate(x);
nm = &x->xlat_freenm;
netmsg_init(nm, NULL, &netisr_apanic_rport, MSGF_PRIORITY,
ipfw_xlat_free_dispatch);
nm->lmsg.u.ms_resultp = x;
x->xlat_rule->cross_refs++;
x->xlat_crefs++;
netisr_sendmsg(nm, x->xlat_pcpu);
}
static void
ipfw_state_remove(struct ipfw_context *ctx, struct ipfw_state *s)
{
if (s->st_flags & IPFW_STATE_F_XLATSLAVE) {
KKASSERT(IPFW_ISXLAT(s->st_type));
ipfw_xlat_invalidate((struct ipfw_xlat *)s);
ipfw_state_unlink(ctx, s);
return;
}
ipfw_state_del(ctx, s);
}
static int
ipfw_state_reap(struct ipfw_context *ctx, int reap_max)
{
struct ipfw_state *s, *anchor;
int expired;
if (reap_max < ipfw_state_reap_min)
reap_max = ipfw_state_reap_min;
if ((ctx->ipfw_flags & IPFW_FLAG_STATEEXP) == 0) {
ctx->ipfw_flags |= IPFW_FLAG_STATEREAP;
expired = ipfw_state_expire_start(ctx, INT_MAX, reap_max);
ctx->ipfw_flags &= ~IPFW_FLAG_STATEREAP;
return (expired);
}
if (ctx->ipfw_state_cnt == 0)
return (0);
expired = 0;
anchor = &ctx->ipfw_stateexp_anch;
while ((s = TAILQ_NEXT(anchor, st_link)) != NULL) {
TAILQ_REMOVE(&ctx->ipfw_state_list, anchor, st_link);
TAILQ_INSERT_AFTER(&ctx->ipfw_state_list, s, anchor, st_link);
if (IPFW_STATE_SCANSKIP(s))
continue;
if (IPFW_STATE_ISDEAD(s) || IPFW_STATE_TCPCLOSED(s)) {
ipfw_state_del(ctx, s);
if (++expired >= reap_max)
break;
if ((expired & 0xff) == 0 &&
ipfw_state_cntcoll() + ipfw_state_headroom <=
ipfw_state_max)
break;
}
}
return (expired);
}
static void
ipfw_state_flush(struct ipfw_context *ctx, const struct ip_fw *rule)
{
struct ipfw_state *s, *sn;
TAILQ_FOREACH_MUTABLE(s, &ctx->ipfw_state_list, st_link, sn) {
if (IPFW_STATE_SCANSKIP(s))
continue;
if (rule != NULL && s->st_rule != rule)
continue;
ipfw_state_del(ctx, s);
}
}
static void
ipfw_state_expire_done(struct ipfw_context *ctx)
{
KASSERT(ctx->ipfw_flags & IPFW_FLAG_STATEEXP,
("stateexp is not in progress"));
ctx->ipfw_flags &= ~IPFW_FLAG_STATEEXP;
callout_reset(&ctx->ipfw_stateto_ch, hz,
ipfw_state_expire_ipifunc, NULL);
}
static void
ipfw_state_expire_more(struct ipfw_context *ctx)
{
struct netmsg_base *nm = &ctx->ipfw_stateexp_more;
KASSERT(ctx->ipfw_flags & IPFW_FLAG_STATEEXP,
("stateexp is not in progress"));
KASSERT(nm->lmsg.ms_flags & MSGF_DONE,
("stateexp more did not finish"));
netisr_sendmsg_oncpu(nm);
}
static int
ipfw_state_expire_loop(struct ipfw_context *ctx, struct ipfw_state *anchor,
int scan_max, int expire_max)
{
struct ipfw_state *s;
int scanned = 0, expired = 0;
KASSERT(ctx->ipfw_flags & IPFW_FLAG_STATEEXP,
("stateexp is not in progress"));
while ((s = TAILQ_NEXT(anchor, st_link)) != NULL) {
if (scanned++ >= scan_max) {
ipfw_state_expire_more(ctx);
return (expired);
}
TAILQ_REMOVE(&ctx->ipfw_state_list, anchor, st_link);
TAILQ_INSERT_AFTER(&ctx->ipfw_state_list, s, anchor, st_link);
if (IPFW_STATE_SCANSKIP(s))
continue;
if (IPFW_STATE_ISDEAD(s) ||
((ctx->ipfw_flags & IPFW_FLAG_STATEREAP) &&
IPFW_STATE_TCPCLOSED(s))) {
ipfw_state_del(ctx, s);
if (++expired >= expire_max) {
ipfw_state_expire_more(ctx);
return (expired);
}
if ((ctx->ipfw_flags & IPFW_FLAG_STATEREAP) &&
(expired & 0xff) == 0 &&
ipfw_state_cntcoll() + ipfw_state_headroom <=
ipfw_state_max) {
ipfw_state_expire_more(ctx);
return (expired);
}
}
}
TAILQ_REMOVE(&ctx->ipfw_state_list, anchor, st_link);
ipfw_state_expire_done(ctx);
return (expired);
}
static void
ipfw_state_expire_more_dispatch(netmsg_t nm)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ipfw_state *anchor;
ASSERT_NETISR_NCPUS(mycpuid);
KASSERT(ctx->ipfw_flags & IPFW_FLAG_STATEEXP,
("statexp is not in progress"));
netisr_replymsg(&nm->base, 0);
anchor = &ctx->ipfw_stateexp_anch;
if (ctx->ipfw_state_cnt == 0) {
TAILQ_REMOVE(&ctx->ipfw_state_list, anchor, st_link);
ipfw_state_expire_done(ctx);
return;
}
ipfw_state_expire_loop(ctx, anchor,
ipfw_state_scan_max, ipfw_state_expire_max);
}
static int
ipfw_state_expire_start(struct ipfw_context *ctx, int scan_max, int expire_max)
{
struct ipfw_state *anchor;
KASSERT((ctx->ipfw_flags & IPFW_FLAG_STATEEXP) == 0,
("stateexp is in progress"));
ctx->ipfw_flags |= IPFW_FLAG_STATEEXP;
if (ctx->ipfw_state_cnt == 0) {
ipfw_state_expire_done(ctx);
return (0);
}
if ((ctx->ipfw_flags & IPFW_FLAG_STATEREAP) == 0 &&
ctx->ipfw_state_lastexp == time_uptime) {
ipfw_state_expire_done(ctx);
return (0);
}
ctx->ipfw_state_lastexp = time_uptime;
anchor = &ctx->ipfw_stateexp_anch;
TAILQ_INSERT_HEAD(&ctx->ipfw_state_list, anchor, st_link);
return (ipfw_state_expire_loop(ctx, anchor, scan_max, expire_max));
}
static void
ipfw_state_expire_dispatch(netmsg_t nm)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
ASSERT_NETISR_NCPUS(mycpuid);
crit_enter();
netisr_replymsg(&nm->base, 0);
crit_exit();
if (ctx->ipfw_flags & IPFW_FLAG_STATEEXP) {
return;
}
ipfw_state_expire_start(ctx,
ipfw_state_scan_max, ipfw_state_expire_max);
}
static void
ipfw_state_expire_ipifunc(void *dummy __unused)
{
struct netmsg_base *msg;
KKASSERT(mycpuid < netisr_ncpus);
msg = &ipfw_ctx[mycpuid]->ipfw_stateexp_nm;
crit_enter();
if (msg->lmsg.ms_flags & MSGF_DONE)
netisr_sendmsg_oncpu(msg);
crit_exit();
}
static boolean_t
ipfw_state_update_tcp(struct ipfw_state *s, int dir, const struct tcphdr *tcp)
{
uint32_t seq = ntohl(tcp->th_seq);
uint32_t ack = ntohl(tcp->th_ack);
if (tcp->th_flags & TH_RST)
return (TRUE);
if (dir == MATCH_FORWARD) {
if ((s->st_flags & IPFW_STATE_F_SEQFWD) == 0) {
s->st_flags |= IPFW_STATE_F_SEQFWD;
s->st_seq_fwd = seq;
} else if (SEQ_GEQ(seq, s->st_seq_fwd)) {
s->st_seq_fwd = seq;
} else {
return (FALSE);
}
if (tcp->th_flags & TH_ACK) {
if ((s->st_flags & IPFW_STATE_F_ACKFWD) == 0) {
s->st_flags |= IPFW_STATE_F_ACKFWD;
s->st_ack_fwd = ack;
} else if (SEQ_GEQ(ack, s->st_ack_fwd)) {
s->st_ack_fwd = ack;
} else {
return (FALSE);
}
if ((s->st_state & ((TH_FIN | TH_ACK) << 8)) ==
(TH_FIN << 8) && s->st_ack_fwd == s->st_seq_rev + 1)
s->st_state |= (TH_ACK << 8);
}
} else {
if ((s->st_flags & IPFW_STATE_F_SEQREV) == 0) {
s->st_flags |= IPFW_STATE_F_SEQREV;
s->st_seq_rev = seq;
} else if (SEQ_GEQ(seq, s->st_seq_rev)) {
s->st_seq_rev = seq;
} else {
return (FALSE);
}
if (tcp->th_flags & TH_ACK) {
if ((s->st_flags & IPFW_STATE_F_ACKREV) == 0) {
s->st_flags |= IPFW_STATE_F_ACKREV;
s->st_ack_rev= ack;
} else if (SEQ_GEQ(ack, s->st_ack_rev)) {
s->st_ack_rev = ack;
} else {
return (FALSE);
}
if ((s->st_state & (TH_FIN | TH_ACK)) == TH_FIN &&
s->st_ack_rev == s->st_seq_fwd + 1)
s->st_state |= TH_ACK;
}
}
return (TRUE);
}
static void
ipfw_state_update(const struct ipfw_flow_id *pkt, int dir,
const struct tcphdr *tcp, struct ipfw_state *s)
{
if (pkt->proto == IPPROTO_TCP) {
u_char flags = pkt->flags & IPFW_STATE_TCPFLAGS;
if (tcp != NULL && !ipfw_state_update_tcp(s, dir, tcp))
return;
s->st_state |= (dir == MATCH_FORWARD) ? flags : (flags << 8);
switch (s->st_state & IPFW_STATE_TCPSTATES) {
case TH_SYN:
s->st_expire = time_uptime + dyn_syn_lifetime;
break;
case BOTH_SYN:
case BOTH_SYN | TH_FIN:
case BOTH_SYN | (TH_FIN << 8):
s->st_expire = time_uptime + dyn_ack_lifetime;
break;
case BOTH_SYN | BOTH_FIN:
if ((s->st_state & BOTH_FINACK) == BOTH_FINACK) {
s->st_expire = time_uptime + dyn_fin_lifetime;
} else {
s->st_expire = time_uptime +
dyn_finwait_lifetime;
}
break;
default:
#if 0
if ((s->st_state & ((TH_RST << 8) | TH_RST)) == 0)
kprintf("invalid state: 0x%x\n", s->st_state);
#endif
s->st_expire = time_uptime + dyn_rst_lifetime;
break;
}
} else if (pkt->proto == IPPROTO_UDP) {
s->st_expire = time_uptime + dyn_udp_lifetime;
} else {
s->st_expire = time_uptime + dyn_short_lifetime;
}
}
static struct ipfw_state *
ipfw_state_lookup(struct ipfw_context *ctx, const struct ipfw_flow_id *pkt,
int *match_direction, const struct tcphdr *tcp)
{
struct ipfw_state *key, *s;
int dir = MATCH_NONE;
key = &ctx->ipfw_state_tmpkey;
ipfw_key_build(&key->st_key, pkt->src_ip, pkt->src_port,
pkt->dst_ip, pkt->dst_port, pkt->proto);
s = RB_FIND(ipfw_state_tree, &ctx->ipfw_state_tree, key);
if (s == NULL)
goto done;
if (IPFW_STATE_ISDEAD(s)) {
ipfw_state_remove(ctx, s);
s = NULL;
goto done;
}
if ((pkt->flags & TH_SYN) && IPFW_STATE_TCPCLOSED(s)) {
ctx->ipfw_sts_tcprecycled++;
ipfw_state_remove(ctx, s);
s = NULL;
goto done;
}
if (s->st_swap == key->st_swap) {
dir = MATCH_FORWARD;
} else {
KASSERT((s->st_swap & key->st_swap) == 0,
("found mismatch state"));
dir = MATCH_REVERSE;
}
ipfw_state_update(pkt, dir, tcp, s);
if (s->st_track != NULL) {
s->st_track->t_expire = time_uptime + dyn_short_lifetime;
}
done:
if (match_direction)
*match_direction = dir;
return (s);
}
static struct ipfw_state *
ipfw_state_alloc(struct ipfw_context *ctx, const struct ipfw_flow_id *id,
uint16_t type, struct ip_fw *rule, const struct tcphdr *tcp)
{
struct ipfw_state *s;
size_t sz;
KASSERT(type == O_KEEP_STATE || type == O_LIMIT || IPFW_ISXLAT(type),
("invalid state type %u", type));
sz = sizeof(struct ipfw_state);
if (IPFW_ISXLAT(type))
sz = sizeof(struct ipfw_xlat);
s = kmalloc(sz, M_IPFW, M_INTWAIT | M_NULLOK | M_ZERO);
if (s == NULL) {
ctx->ipfw_sts_nomem++;
return (NULL);
}
ipfw_key_build(&s->st_key, id->src_ip, id->src_port,
id->dst_ip, id->dst_port, id->proto);
s->st_rule = rule;
s->st_type = type;
if (IPFW_ISXLAT(type)) {
struct ipfw_xlat *x = (struct ipfw_xlat *)s;
x->xlat_dir = MATCH_NONE;
x->xlat_pcpu = -1;
}
ipfw_state_update(id, MATCH_FORWARD, tcp, s);
return (s);
}
static struct ipfw_state *
ipfw_state_add(struct ipfw_context *ctx, const struct ipfw_flow_id *id,
uint16_t type, struct ip_fw *rule, struct ipfw_track *t,
const struct tcphdr *tcp)
{
struct ipfw_state *s, *dup;
s = ipfw_state_alloc(ctx, id, type, rule, tcp);
if (s == NULL)
return (NULL);
ctx->ipfw_state_cnt++;
ctx->ipfw_state_loosecnt++;
if (ctx->ipfw_state_loosecnt >= ipfw_state_loosecnt_updthr) {
ipfw_gd.ipfw_state_loosecnt += ctx->ipfw_state_loosecnt;
ctx->ipfw_state_loosecnt = 0;
}
dup = ipfw_state_link(ctx, s);
if (dup != NULL)
panic("ipfw: %u state exists %p", type, dup);
if (t != NULL) {
LIST_INSERT_HEAD(&t->t_state_list, s, st_trklink);
s->st_track = t;
}
return (s);
}
static boolean_t
ipfw_track_free(struct ipfw_context *ctx, struct ipfw_track *t)
{
struct ipfw_trkcnt *trk;
boolean_t trk_freed = FALSE;
KASSERT(t->t_count != NULL, ("track anchor"));
KASSERT(LIST_EMPTY(&t->t_state_list),
("invalid track is still referenced"));
trk = t->t_trkcnt;
KASSERT(trk != NULL, ("track has no trkcnt"));
RB_REMOVE(ipfw_track_tree, &ctx->ipfw_track_tree, t);
TAILQ_REMOVE(&ctx->ipfw_track_list, t, t_link);
kfree(t, M_IPFW);
for (;;) {
int refs = trk->tc_refs;
cpu_ccfence();
KASSERT(refs > 0, ("invalid trkcnt refs %d", refs));
if (refs == 1) {
IPFW_TRKCNT_TOKGET;
if (atomic_cmpset_int(&trk->tc_refs, refs, 0)) {
KASSERT(trk->tc_count == 0,
("%d states reference this trkcnt",
trk->tc_count));
RB_REMOVE(ipfw_trkcnt_tree,
&ipfw_gd.ipfw_trkcnt_tree, trk);
KASSERT(ipfw_gd.ipfw_trkcnt_cnt > 0,
("invalid trkcnt cnt %d",
ipfw_gd.ipfw_trkcnt_cnt));
ipfw_gd.ipfw_trkcnt_cnt--;
IPFW_TRKCNT_TOKREL;
if (ctx->ipfw_trkcnt_spare == NULL)
ctx->ipfw_trkcnt_spare = trk;
else
kfree(trk, M_IPFW);
trk_freed = TRUE;
break;
}
IPFW_TRKCNT_TOKREL;
} else if (atomic_cmpset_int(&trk->tc_refs, refs, refs - 1)) {
break;
}
}
return (trk_freed);
}
static void
ipfw_track_flush(struct ipfw_context *ctx, struct ip_fw *rule)
{
struct ipfw_track *t, *tn;
TAILQ_FOREACH_MUTABLE(t, &ctx->ipfw_track_list, t_link, tn) {
if (t->t_count == NULL)
continue;
if (rule != NULL && t->t_rule != rule)
continue;
ipfw_track_free(ctx, t);
}
}
static boolean_t
ipfw_track_state_expire(struct ipfw_context *ctx, struct ipfw_track *t,
boolean_t reap)
{
struct ipfw_state *s, *sn;
boolean_t ret = FALSE;
KASSERT(t->t_count != NULL, ("track anchor"));
if (LIST_EMPTY(&t->t_state_list))
return (FALSE);
if (t->t_lastexp == time_uptime)
return (FALSE);
t->t_lastexp = time_uptime;
LIST_FOREACH_MUTABLE(s, &t->t_state_list, st_trklink, sn) {
if (IPFW_STATE_ISDEAD(s) || (reap && IPFW_STATE_TCPCLOSED(s))) {
KASSERT(s->st_track == t,
("state track %p does not match %p",
s->st_track, t));
ipfw_state_del(ctx, s);
ret = TRUE;
}
}
return (ret);
}
static __inline struct ipfw_trkcnt *
ipfw_trkcnt_alloc(struct ipfw_context *ctx)
{
struct ipfw_trkcnt *trk;
if (ctx->ipfw_trkcnt_spare != NULL) {
trk = ctx->ipfw_trkcnt_spare;
ctx->ipfw_trkcnt_spare = NULL;
} else {
trk = kmalloc(sizeof(*trk), M_IPFW,
M_INTWAIT | M_NULLOK | M_CACHEALIGN);
}
return (trk);
}
static void
ipfw_track_expire_done(struct ipfw_context *ctx)
{
KASSERT(ctx->ipfw_flags & IPFW_FLAG_TRACKEXP,
("trackexp is not in progress"));
ctx->ipfw_flags &= ~IPFW_FLAG_TRACKEXP;
callout_reset(&ctx->ipfw_trackto_ch, hz,
ipfw_track_expire_ipifunc, NULL);
}
static void
ipfw_track_expire_more(struct ipfw_context *ctx)
{
struct netmsg_base *nm = &ctx->ipfw_trackexp_more;
KASSERT(ctx->ipfw_flags & IPFW_FLAG_TRACKEXP,
("trackexp is not in progress"));
KASSERT(nm->lmsg.ms_flags & MSGF_DONE,
("trackexp more did not finish"));
netisr_sendmsg_oncpu(nm);
}
static int
ipfw_track_expire_loop(struct ipfw_context *ctx, struct ipfw_track *anchor,
int scan_max, int expire_max)
{
struct ipfw_track *t;
int scanned = 0, expired = 0;
boolean_t reap = FALSE;
KASSERT(ctx->ipfw_flags & IPFW_FLAG_TRACKEXP,
("trackexp is not in progress"));
if (ctx->ipfw_flags & IPFW_FLAG_TRACKREAP)
reap = TRUE;
while ((t = TAILQ_NEXT(anchor, t_link)) != NULL) {
if (scanned++ >= scan_max) {
ipfw_track_expire_more(ctx);
return (expired);
}
TAILQ_REMOVE(&ctx->ipfw_track_list, anchor, t_link);
TAILQ_INSERT_AFTER(&ctx->ipfw_track_list, t, anchor, t_link);
if (t->t_count == NULL)
continue;
ipfw_track_state_expire(ctx, t, reap);
if (!LIST_EMPTY(&t->t_state_list)) {
continue;
}
if (TIME_LEQ(t->t_expire, time_uptime) || reap) {
if (ipfw_track_free(ctx, t)) {
if (++expired >= expire_max) {
ipfw_track_expire_more(ctx);
return (expired);
}
}
}
}
TAILQ_REMOVE(&ctx->ipfw_track_list, anchor, t_link);
ipfw_track_expire_done(ctx);
return (expired);
}
static int
ipfw_track_expire_start(struct ipfw_context *ctx, int scan_max, int expire_max)
{
struct ipfw_track *anchor;
KASSERT((ctx->ipfw_flags & IPFW_FLAG_TRACKEXP) == 0,
("trackexp is in progress"));
ctx->ipfw_flags |= IPFW_FLAG_TRACKEXP;
if (RB_EMPTY(&ctx->ipfw_track_tree)) {
ipfw_track_expire_done(ctx);
return (0);
}
if ((ctx->ipfw_flags & IPFW_FLAG_TRACKREAP) == 0 &&
ctx->ipfw_track_lastexp == time_uptime) {
ipfw_track_expire_done(ctx);
return (0);
}
ctx->ipfw_track_lastexp = time_uptime;
anchor = &ctx->ipfw_trackexp_anch;
TAILQ_INSERT_HEAD(&ctx->ipfw_track_list, anchor, t_link);
return (ipfw_track_expire_loop(ctx, anchor, scan_max, expire_max));
}
static void
ipfw_track_expire_more_dispatch(netmsg_t nm)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ipfw_track *anchor;
ASSERT_NETISR_NCPUS(mycpuid);
KASSERT(ctx->ipfw_flags & IPFW_FLAG_TRACKEXP,
("trackexp is not in progress"));
netisr_replymsg(&nm->base, 0);
anchor = &ctx->ipfw_trackexp_anch;
if (RB_EMPTY(&ctx->ipfw_track_tree)) {
TAILQ_REMOVE(&ctx->ipfw_track_list, anchor, t_link);
ipfw_track_expire_done(ctx);
return;
}
ipfw_track_expire_loop(ctx, anchor,
ipfw_track_scan_max, ipfw_track_expire_max);
}
static void
ipfw_track_expire_dispatch(netmsg_t nm)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
ASSERT_NETISR_NCPUS(mycpuid);
crit_enter();
netisr_replymsg(&nm->base, 0);
crit_exit();
if (ctx->ipfw_flags & IPFW_FLAG_TRACKEXP) {
return;
}
ipfw_track_expire_start(ctx,
ipfw_track_scan_max, ipfw_track_expire_max);
}
static void
ipfw_track_expire_ipifunc(void *dummy __unused)
{
struct netmsg_base *msg;
KKASSERT(mycpuid < netisr_ncpus);
msg = &ipfw_ctx[mycpuid]->ipfw_trackexp_nm;
crit_enter();
if (msg->lmsg.ms_flags & MSGF_DONE)
netisr_sendmsg_oncpu(msg);
crit_exit();
}
static int
ipfw_track_reap(struct ipfw_context *ctx)
{
struct ipfw_track *t, *anchor;
int expired;
if ((ctx->ipfw_flags & IPFW_FLAG_TRACKEXP) == 0) {
ctx->ipfw_flags |= IPFW_FLAG_TRACKREAP;
expired = ipfw_track_expire_start(ctx, INT_MAX,
ipfw_track_reap_max);
ctx->ipfw_flags &= ~IPFW_FLAG_TRACKREAP;
return (expired);
}
if (RB_EMPTY(&ctx->ipfw_track_tree))
return (0);
expired = 0;
anchor = &ctx->ipfw_trackexp_anch;
while ((t = TAILQ_NEXT(anchor, t_link)) != NULL) {
TAILQ_REMOVE(&ctx->ipfw_track_list, anchor, t_link);
TAILQ_INSERT_AFTER(&ctx->ipfw_track_list, t, anchor, t_link);
if (t->t_count == NULL)
continue;
ipfw_track_state_expire(ctx, t, TRUE);
if (!LIST_EMPTY(&t->t_state_list)) {
continue;
}
if (ipfw_track_free(ctx, t)) {
if (++expired >= ipfw_track_reap_max) {
ipfw_track_expire_more(ctx);
break;
}
}
}
return (expired);
}
static struct ipfw_track *
ipfw_track_alloc(struct ipfw_context *ctx, const struct ipfw_flow_id *id,
uint16_t limit_mask, struct ip_fw *rule)
{
struct ipfw_track *key, *t, *dup;
struct ipfw_trkcnt *trk, *ret;
boolean_t do_expire = FALSE;
KASSERT(rule->track_ruleid != 0,
("rule %u has no track ruleid", rule->rulenum));
key = &ctx->ipfw_track_tmpkey;
key->t_proto = id->proto;
key->t_addrs = 0;
key->t_ports = 0;
key->t_rule = rule;
if (limit_mask & DYN_SRC_ADDR)
key->t_saddr = id->src_ip;
if (limit_mask & DYN_DST_ADDR)
key->t_daddr = id->dst_ip;
if (limit_mask & DYN_SRC_PORT)
key->t_sport = id->src_port;
if (limit_mask & DYN_DST_PORT)
key->t_dport = id->dst_port;
t = RB_FIND(ipfw_track_tree, &ctx->ipfw_track_tree, key);
if (t != NULL)
goto done;
t = kmalloc(sizeof(*t), M_IPFW, M_INTWAIT | M_NULLOK);
if (t == NULL) {
ctx->ipfw_tks_nomem++;
return (NULL);
}
t->t_key = key->t_key;
t->t_rule = rule;
t->t_lastexp = 0;
LIST_INIT(&t->t_state_list);
if (ipfw_gd.ipfw_trkcnt_cnt >= ipfw_track_max) {
time_t globexp, uptime;
trk = NULL;
do_expire = TRUE;
uptime = time_uptime;
globexp = ipfw_gd.ipfw_track_globexp;
if (globexp != uptime &&
atomic_cmpset_long(&ipfw_gd.ipfw_track_globexp,
globexp, uptime)) {
int cpu;
for (cpu = 0; cpu < netisr_ncpus; ++cpu) {
if (cpu == mycpuid)
continue;
lwkt_send_ipiq(globaldata_find(cpu),
ipfw_track_expire_ipifunc, NULL);
}
}
} else {
trk = ipfw_trkcnt_alloc(ctx);
}
if (trk == NULL) {
struct ipfw_trkcnt *tkey;
tkey = &ctx->ipfw_trkcnt_tmpkey;
key = NULL;
tkey->tc_key = t->t_key;
tkey->tc_ruleid = rule->track_ruleid;
IPFW_TRKCNT_TOKGET;
trk = RB_FIND(ipfw_trkcnt_tree, &ipfw_gd.ipfw_trkcnt_tree,
tkey);
if (trk == NULL) {
IPFW_TRKCNT_TOKREL;
if (do_expire) {
ctx->ipfw_tks_reap++;
if (ipfw_track_reap(ctx) > 0) {
if (ipfw_gd.ipfw_trkcnt_cnt <
ipfw_track_max) {
trk = ipfw_trkcnt_alloc(ctx);
if (trk != NULL)
goto install;
ctx->ipfw_tks_cntnomem++;
} else {
ctx->ipfw_tks_overflow++;
}
} else {
ctx->ipfw_tks_reapfailed++;
ctx->ipfw_tks_overflow++;
}
} else {
ctx->ipfw_tks_cntnomem++;
}
kfree(t, M_IPFW);
return (NULL);
}
KASSERT(trk->tc_refs > 0 && trk->tc_refs < netisr_ncpus,
("invalid trkcnt refs %d", trk->tc_refs));
atomic_add_int(&trk->tc_refs, 1);
IPFW_TRKCNT_TOKREL;
} else {
install:
trk->tc_key = t->t_key;
trk->tc_ruleid = rule->track_ruleid;
trk->tc_refs = 0;
trk->tc_count = 0;
trk->tc_expire = 0;
trk->tc_rulenum = rule->rulenum;
IPFW_TRKCNT_TOKGET;
ret = RB_INSERT(ipfw_trkcnt_tree, &ipfw_gd.ipfw_trkcnt_tree,
trk);
if (ret != NULL) {
KASSERT(ret->tc_refs > 0 &&
ret->tc_refs < netisr_ncpus,
("invalid trkcnt refs %d", ret->tc_refs));
KASSERT(ctx->ipfw_trkcnt_spare == NULL,
("trkcnt spare was installed"));
ctx->ipfw_trkcnt_spare = trk;
trk = ret;
} else {
ipfw_gd.ipfw_trkcnt_cnt++;
}
atomic_add_int(&trk->tc_refs, 1);
IPFW_TRKCNT_TOKREL;
}
t->t_count = &trk->tc_count;
t->t_trkcnt = trk;
dup = RB_INSERT(ipfw_track_tree, &ctx->ipfw_track_tree, t);
if (dup != NULL)
panic("ipfw: track exists");
TAILQ_INSERT_TAIL(&ctx->ipfw_track_list, t, t_link);
done:
t->t_expire = time_uptime + dyn_short_lifetime;
return (t);
}
static struct ipfw_state *
ipfw_state_install(struct ipfw_context *ctx, struct ip_fw *rule,
ipfw_insn_limit *cmd, struct ip_fw_args *args, const struct tcphdr *tcp)
{
struct ipfw_state *s;
struct ipfw_track *t;
int count, diff;
if (ipfw_gd.ipfw_state_loosecnt >= ipfw_state_max &&
(diff = (ipfw_state_cntsync() - ipfw_state_max)) >= 0) {
boolean_t overflow = TRUE;
ctx->ipfw_sts_reap++;
if (ipfw_state_reap(ctx, diff) == 0)
ctx->ipfw_sts_reapfailed++;
if (ipfw_state_cntsync() < ipfw_state_max)
overflow = FALSE;
if (overflow) {
time_t globexp, uptime;
int cpu;
uptime = time_uptime;
globexp = ipfw_gd.ipfw_state_globexp;
if (globexp == uptime ||
!atomic_cmpset_long(&ipfw_gd.ipfw_state_globexp,
globexp, uptime)) {
ctx->ipfw_sts_overflow++;
return (NULL);
}
for (cpu = 0; cpu < netisr_ncpus; ++cpu) {
if (cpu == mycpuid)
continue;
lwkt_send_ipiq(globaldata_find(cpu),
ipfw_state_expire_ipifunc, NULL);
}
ctx->ipfw_sts_overflow++;
return (NULL);
}
}
switch (cmd->o.opcode) {
case O_KEEP_STATE:
case O_REDIRECT:
s = ipfw_state_add(ctx, &args->f_id, cmd->o.opcode, rule, NULL,
tcp);
if (s == NULL)
return (NULL);
break;
case O_LIMIT:
t = ipfw_track_alloc(ctx, &args->f_id, cmd->limit_mask, rule);
if (t == NULL)
return (NULL);
if (*t->t_count >= cmd->conn_limit) {
if (!ipfw_track_state_expire(ctx, t, TRUE))
return (NULL);
}
for (;;) {
count = *t->t_count;
if (count >= cmd->conn_limit)
return (NULL);
if (atomic_cmpset_int(t->t_count, count, count + 1))
break;
}
s = ipfw_state_add(ctx, &args->f_id, O_LIMIT, rule, t, tcp);
if (s == NULL) {
atomic_subtract_int(t->t_count, 1);
return (NULL);
}
break;
default:
panic("unknown state type %u\n", cmd->o.opcode);
}
if (s->st_type == O_REDIRECT) {
struct ipfw_xlat *x = (struct ipfw_xlat *)s;
ipfw_insn_rdr *r = (ipfw_insn_rdr *)cmd;
x->xlat_addr = r->addr.s_addr;
x->xlat_port = r->port;
x->xlat_ifp = args->m->m_pkthdr.rcvif;
x->xlat_dir = MATCH_FORWARD;
KKASSERT(x->xlat_ifp != NULL);
}
return (s);
}
static int
ipfw_table_lookup(struct ipfw_context *ctx, uint16_t tableid,
const struct in_addr *in)
{
struct radix_node_head *rnh;
struct sockaddr_in sin;
struct ipfw_tblent *te;
KASSERT(tableid < ipfw_table_max, ("invalid tableid %u", tableid));
rnh = ctx->ipfw_tables[tableid];
if (rnh == NULL)
return (0);
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_len = sizeof(sin);
sin.sin_addr = *in;
te = (struct ipfw_tblent *)rnh->rnh_matchaddr(&sin, rnh);
if (te == NULL)
return (0);
te->te_use++;
te->te_lastuse = time_second;
return (1);
}
static void
send_pkt(const struct ipfw_flow_id *id, uint32_t seq, uint32_t ack, int flags)
{
struct mbuf *m;
struct ip *ip;
struct tcphdr *tcp;
struct route sro;
MGETHDR(m, M_NOWAIT, MT_HEADER);
if (m == NULL)
return;
m->m_pkthdr.rcvif = NULL;
m->m_pkthdr.len = m->m_len = sizeof(struct ip) + sizeof(struct tcphdr);
m->m_data += max_linkhdr;
ip = mtod(m, struct ip *);
bzero(ip, m->m_len);
tcp = (struct tcphdr *)(ip + 1);
ip->ip_p = IPPROTO_TCP;
tcp->th_off = 5;
ip->ip_src.s_addr = htonl(id->dst_ip);
ip->ip_dst.s_addr = htonl(id->src_ip);
tcp->th_sport = htons(id->dst_port);
tcp->th_dport = htons(id->src_port);
if (flags & TH_RST) {
if (flags & TH_ACK) {
tcp->th_seq = htonl(ack);
tcp->th_ack = htonl(0);
tcp->th_flags = TH_RST;
} else {
if (flags & TH_SYN)
seq++;
tcp->th_seq = htonl(0);
tcp->th_ack = htonl(seq);
tcp->th_flags = TH_RST | TH_ACK;
}
} else {
if (flags & TH_SYN) {
ip->ip_dst.s_addr = htonl(id->dst_ip);
ip->ip_src.s_addr = htonl(id->src_ip);
tcp->th_dport = htons(id->dst_port);
tcp->th_sport = htons(id->src_port);
}
tcp->th_seq = htonl(seq);
tcp->th_ack = htonl(ack);
tcp->th_flags = TH_ACK;
}
ip->ip_len = htons(sizeof(struct tcphdr));
tcp->th_sum = in_cksum(m, m->m_pkthdr.len);
ip->ip_ttl = ip_defttl;
ip->ip_len = htons(m->m_pkthdr.len);
bzero(&sro, sizeof(sro));
ip_rtaddr(ip->ip_dst, &sro);
m->m_pkthdr.fw_flags |= IPFW_MBUF_GENERATED;
ip_output(m, NULL, &sro, 0, NULL, NULL);
if (sro.ro_rt)
RTFREE(sro.ro_rt);
}
static void
send_reject(struct ip_fw_args *args, int code, int offset, int ip_len)
{
if (code != ICMP_REJECT_RST) {
icmp_error(args->m, ICMP_UNREACH, code, 0L, 0);
} else if (offset == 0 && args->f_id.proto == IPPROTO_TCP) {
struct tcphdr *const tcp =
L3HDR(struct tcphdr, mtod(args->m, struct ip *));
if ((tcp->th_flags & TH_RST) == 0) {
send_pkt(&args->f_id, ntohl(tcp->th_seq),
ntohl(tcp->th_ack), tcp->th_flags | TH_RST);
}
m_freem(args->m);
} else {
m_freem(args->m);
}
args->m = NULL;
}
static struct ip_fw *
lookup_next_rule(struct ip_fw *me)
{
struct ip_fw *rule = NULL;
ipfw_insn *cmd;
cmd = ACTION_PTR(me);
if (cmd->opcode == O_LOG)
cmd += F_LEN(cmd);
if (cmd->opcode == O_SKIPTO) {
for (rule = me->next; rule; rule = rule->next) {
if (rule->rulenum >= cmd->arg1)
break;
}
}
if (rule == NULL)
rule = me->next;
me->next_rule = rule;
return rule;
}
static int
ipfw_match_uid(const struct ipfw_flow_id *fid, struct ifnet *oif,
enum ipfw_opcodes opcode, uid_t uid)
{
struct in_addr src_ip, dst_ip;
struct inpcbinfo *pi;
boolean_t wildcard;
struct inpcb *pcb;
if (fid->proto == IPPROTO_TCP) {
wildcard = FALSE;
pi = &tcbinfo[mycpuid];
} else if (fid->proto == IPPROTO_UDP) {
wildcard = TRUE;
pi = &udbinfo[mycpuid];
} else {
return 0;
}
dst_ip.s_addr = htonl(fid->dst_ip);
src_ip.s_addr = htonl(fid->src_ip);
if (oif) {
pcb = in_pcblookup_hash(pi,
dst_ip, htons(fid->dst_port),
src_ip, htons(fid->src_port),
wildcard, oif);
} else {
pcb = in_pcblookup_hash(pi,
src_ip, htons(fid->src_port),
dst_ip, htons(fid->dst_port),
wildcard, NULL);
}
if (pcb == NULL || pcb->inp_socket == NULL)
return 0;
if (opcode == O_UID) {
#define socheckuid(a,b) ((a)->so_cred->cr_uid != (b))
return !socheckuid(pcb->inp_socket, uid);
#undef socheckuid
} else {
return groupmember(uid, pcb->inp_socket->so_cred);
}
}
static int
ipfw_match_ifip(ipfw_insn_ifip *cmd, const struct in_addr *ip)
{
if (__predict_false((cmd->o.arg1 & IPFW_IFIP_VALID) == 0)) {
struct ifaddr_container *ifac;
struct ifnet *ifp;
ifp = ifunit_netisr(cmd->ifname);
if (ifp == NULL)
return (0);
TAILQ_FOREACH(ifac, &ifp->if_addrheads[mycpuid], ifa_link) {
struct ifaddr *ia = ifac->ifa;
if (ia->ifa_addr == NULL)
continue;
if (ia->ifa_addr->sa_family != AF_INET)
continue;
cmd->mask.s_addr = INADDR_ANY;
if (cmd->o.arg1 & IPFW_IFIP_NET) {
cmd->mask = ((struct sockaddr_in *)
ia->ifa_netmask)->sin_addr;
}
if (cmd->mask.s_addr == INADDR_ANY)
cmd->mask.s_addr = INADDR_BROADCAST;
cmd->addr =
((struct sockaddr_in *)ia->ifa_addr)->sin_addr;
cmd->addr.s_addr &= cmd->mask.s_addr;
cmd->o.arg1 |= IPFW_IFIP_VALID;
break;
}
if ((cmd->o.arg1 & IPFW_IFIP_VALID) == 0)
return (0);
}
return ((ip->s_addr & cmd->mask.s_addr) == cmd->addr.s_addr);
}
static void
ipfw_xlate(const struct ipfw_xlat *x, struct mbuf *m,
struct in_addr *old_addr, uint16_t *old_port)
{
struct ip *ip = mtod(m, struct ip *);
struct in_addr *addr;
uint16_t *port, *csum, dlen = 0;
uint8_t udp = 0;
boolean_t pseudo = FALSE;
if (x->xlat_flags & IPFW_STATE_F_XLATSRC) {
addr = &ip->ip_src;
switch (ip->ip_p) {
case IPPROTO_TCP:
port = &L3HDR(struct tcphdr, ip)->th_sport;
csum = &L3HDR(struct tcphdr, ip)->th_sum;
break;
case IPPROTO_UDP:
port = &L3HDR(struct udphdr, ip)->uh_sport;
csum = &L3HDR(struct udphdr, ip)->uh_sum;
udp = 1;
break;
default:
panic("ipfw: unsupported src xlate proto %u", ip->ip_p);
}
} else {
addr = &ip->ip_dst;
switch (ip->ip_p) {
case IPPROTO_TCP:
port = &L3HDR(struct tcphdr, ip)->th_dport;
csum = &L3HDR(struct tcphdr, ip)->th_sum;
break;
case IPPROTO_UDP:
port = &L3HDR(struct udphdr, ip)->uh_dport;
csum = &L3HDR(struct udphdr, ip)->uh_sum;
udp = 1;
break;
default:
panic("ipfw: unsupported dst xlate proto %u", ip->ip_p);
}
}
if (old_addr != NULL)
*old_addr = *addr;
if (old_port != NULL) {
if (x->xlat_port != 0)
*old_port = *port;
else
*old_port = 0;
}
if (m->m_pkthdr.csum_flags & (CSUM_UDP | CSUM_TCP | CSUM_TSO)) {
if ((m->m_pkthdr.csum_flags & CSUM_TSO) == 0)
dlen = ntohs(ip->ip_len) - (ip->ip_hl << 2);
pseudo = TRUE;
}
if (!pseudo) {
const uint16_t *oaddr, *naddr;
oaddr = (const uint16_t *)&addr->s_addr;
naddr = (const uint16_t *)&x->xlat_addr;
ip->ip_sum = pfil_cksum_fixup(pfil_cksum_fixup(ip->ip_sum,
oaddr[0], naddr[0], 0), oaddr[1], naddr[1], 0);
*csum = pfil_cksum_fixup(pfil_cksum_fixup(*csum,
oaddr[0], naddr[0], udp), oaddr[1], naddr[1], udp);
}
addr->s_addr = x->xlat_addr;
if (x->xlat_port != 0) {
if (!pseudo) {
*csum = pfil_cksum_fixup(*csum, *port, x->xlat_port,
udp);
}
*port = x->xlat_port;
}
if (pseudo) {
*csum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr,
htons(dlen + ip->ip_p));
}
}
static void
ipfw_ip_xlate_dispatch(netmsg_t nmsg)
{
struct netmsg_genpkt *nm = (struct netmsg_genpkt *)nmsg;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct mbuf *m = nm->m;
struct ipfw_xlat *x = nm->arg1;
struct ip_fw *rule = x->xlat_rule;
ASSERT_NETISR_NCPUS(mycpuid);
KASSERT(rule->cpuid == mycpuid,
("rule does not belong to cpu%d", mycpuid));
KASSERT(m->m_pkthdr.fw_flags & IPFW_MBUF_CONTINUE,
("mbuf does not have ipfw continue rule"));
KASSERT(ctx->ipfw_cont_rule == NULL,
("pending ipfw continue rule"));
KASSERT(ctx->ipfw_cont_xlat == NULL,
("pending ipfw continue xlat"));
ctx->ipfw_cont_rule = rule;
ctx->ipfw_cont_xlat = x;
if (nm->arg2 == 0)
ip_input(m);
else
ip_output(m, NULL, NULL, IP_FORWARDING, NULL, NULL);
ctx->ipfw_cont_rule = NULL;
ctx->ipfw_cont_xlat = NULL;
x->xlat_crefs--;
rule->cross_refs--;
}
static void
ipfw_xlate_redispatch(struct mbuf *m, int cpuid, struct ipfw_xlat *x,
uint32_t flags)
{
struct netmsg_genpkt *nm;
KASSERT(x->xlat_pcpu == cpuid, ("xlat paired cpu%d, target cpu%d",
x->xlat_pcpu, cpuid));
x->xlat_rule->cross_refs++;
x->xlat_crefs++;
m->m_pkthdr.fw_flags |= IPFW_MBUF_CONTINUE;
if (flags & IPFW_XLATE_INSERT)
m->m_pkthdr.fw_flags |= IPFW_MBUF_XLATINS;
if (flags & IPFW_XLATE_FORWARD)
m->m_pkthdr.fw_flags |= IPFW_MBUF_XLATFWD;
nm = &m->m_hdr.mh_genmsg;
netmsg_init(&nm->base, NULL, &netisr_apanic_rport, 0,
ipfw_ip_xlate_dispatch);
nm->m = m;
nm->arg1 = x->xlat_pair;
nm->arg2 = 0;
if (flags & IPFW_XLATE_OUTPUT)
nm->arg2 = 1;
netisr_sendmsg(&nm->base, cpuid);
}
static struct mbuf *
ipfw_setup_local(struct mbuf *m, const int hlen, struct ip_fw_args *args,
struct ip_fw_local *local, struct ip **ip0)
{
struct ip *ip = mtod(m, struct ip *);
struct tcphdr *tcp;
struct udphdr *udp;
if (hlen == 0) {
local->proto = args->f_id.proto = 0;
goto done;
}
local->proto = args->f_id.proto = ip->ip_p;
local->src_ip = ip->ip_src;
local->dst_ip = ip->ip_dst;
local->offset = ntohs(ip->ip_off) & IP_OFFMASK;
local->ip_len = ntohs(ip->ip_len);
#define PULLUP_TO(len) \
do { \
if (m->m_len < (len)) { \
args->m = m = m_pullup(m, (len)); \
if (m == NULL) { \
ip = NULL; \
goto done; \
} \
ip = mtod(m, struct ip *); \
} \
} while (0)
if (local->offset == 0) {
switch (local->proto) {
case IPPROTO_TCP:
PULLUP_TO(hlen + sizeof(struct tcphdr));
local->tcp = tcp = L3HDR(struct tcphdr, ip);
local->dst_port = tcp->th_dport;
local->src_port = tcp->th_sport;
args->f_id.flags = tcp->th_flags;
break;
case IPPROTO_UDP:
PULLUP_TO(hlen + sizeof(struct udphdr));
udp = L3HDR(struct udphdr, ip);
local->dst_port = udp->uh_dport;
local->src_port = udp->uh_sport;
break;
case IPPROTO_ICMP:
PULLUP_TO(hlen + 4);
args->f_id.flags = L3HDR(struct icmp, ip)->icmp_type;
break;
default:
break;
}
}
#undef PULLUP_TO
args->f_id.src_ip = ntohl(local->src_ip.s_addr);
args->f_id.dst_ip = ntohl(local->dst_ip.s_addr);
args->f_id.src_port = local->src_port = ntohs(local->src_port);
args->f_id.dst_port = local->dst_port = ntohs(local->dst_port);
done:
*ip0 = ip;
return (m);
}
static struct mbuf *
ipfw_rehashm(struct mbuf *m, const int hlen, struct ip_fw_args *args,
struct ip_fw_local *local, struct ip **ip0)
{
m->m_flags &= ~M_HASH;
ip_hashfn(&m, 0);
args->m = m;
if (m == NULL) {
*ip0 = NULL;
return (NULL);
}
KASSERT(m->m_flags & M_HASH, ("no hash"));
return (ipfw_setup_local(m, hlen, args, local, ip0));
}
static int
ipfw_chk(struct ip_fw_args *args)
{
struct mbuf *m = args->m;
struct ip *ip = mtod(m, struct ip *);
struct ifnet *oif = args->oif;
struct ip_fw *f = NULL;
int retval = IP_FW_PASS;
struct m_tag *mtag;
struct divert_info *divinfo;
struct ipfw_state *s;
u_int hlen = 0;
struct ip_fw_local lc;
int dyn_dir = MATCH_UNKNOWN;
struct ip_fw *dyn_f = NULL;
int cpuid = mycpuid;
struct ipfw_context *ctx;
ASSERT_NETISR_NCPUS(cpuid);
ctx = ipfw_ctx[cpuid];
if (m->m_pkthdr.fw_flags & IPFW_MBUF_GENERATED)
return IP_FW_PASS;
if (args->eh == NULL ||
(m->m_pkthdr.len >= sizeof(struct ip) &&
ntohs(args->eh->ether_type) == ETHERTYPE_IP))
hlen = ip->ip_hl << 2;
memset(&lc, 0, sizeof(lc));
m = ipfw_setup_local(m, hlen, args, &lc, &ip);
if (m == NULL)
goto pullup_failed;
if (args->rule) {
if (fw_one_pass && (args->flags & IP_FWARG_F_CONT) == 0)
return IP_FW_PASS;
args->flags &= ~IP_FWARG_F_CONT;
if (ipfw_flushing)
return IP_FW_DENY;
KASSERT(args->rule->cpuid == cpuid,
("rule used on cpu%d", cpuid));
if (args->rule->rule_flags & IPFW_RULE_F_INVALID)
return IP_FW_DENY;
if (args->xlat != NULL) {
struct ipfw_xlat *x = args->xlat;
if (x->xlat_invalid)
return IP_FW_DENY;
f = args->rule;
dyn_f = f;
dyn_dir = (args->flags & IP_FWARG_F_XLATFWD) ?
MATCH_FORWARD : MATCH_REVERSE;
if (args->flags & IP_FWARG_F_XLATINS) {
KASSERT(x->xlat_flags & IPFW_STATE_F_XLATSLAVE,
("not slave %u state", x->xlat_type));
s = ipfw_state_link(ctx, &x->xlat_st);
if (s != NULL) {
ctx->ipfw_xlate_conflicts++;
if (IPFW_STATE_ISDEAD(s)) {
ipfw_state_remove(ctx, s);
s = ipfw_state_link(ctx,
&x->xlat_st);
}
if (s != NULL) {
if (bootverbose) {
kprintf("ipfw: "
"slave %u state "
"conflicts %u state\n",
x->xlat_type,
s->st_type);
}
ipfw_xlat_invalidate(x);
return IP_FW_DENY;
}
ctx->ipfw_xlate_cresolved++;
}
} else {
ipfw_state_update(&args->f_id, dyn_dir,
lc.tcp, &x->xlat_st);
}
} else {
f = args->rule->next_rule;
if (f == NULL)
f = lookup_next_rule(args->rule);
}
} else {
int skipto;
KKASSERT((args->flags &
(IP_FWARG_F_XLATINS | IP_FWARG_F_CONT)) == 0);
KKASSERT(args->xlat == NULL);
mtag = m_tag_find(m, PACKET_TAG_IPFW_DIVERT, NULL);
if (mtag != NULL) {
divinfo = m_tag_data(mtag);
skipto = divinfo->skipto;
} else {
skipto = 0;
}
f = ctx->ipfw_layer3_chain;
if (args->eh == NULL && skipto != 0) {
if (ipfw_flushing)
return IP_FW_DENY;
if (skipto >= IPFW_DEFAULT_RULE)
return IP_FW_DENY;
while (f && f->rulenum <= skipto)
f = f->next;
if (f == NULL)
return IP_FW_DENY;
} else if (ipfw_flushing) {
f = ctx->ipfw_default_rule;
}
}
if ((mtag = m_tag_find(m, PACKET_TAG_IPFW_DIVERT, NULL)) != NULL)
m_tag_delete(m, mtag);
for (; f; f = f->next) {
int l, cmdlen;
ipfw_insn *cmd;
int skip_or;
again:
if (ctx->ipfw_set_disable & (1 << f->set)) {
args->xlat = NULL;
continue;
}
if (args->xlat != NULL) {
args->xlat = NULL;
l = f->cmd_len - f->act_ofs;
cmd = ACTION_PTR(f);
} else {
l = f->cmd_len;
cmd = f->cmd;
}
skip_or = 0;
for (; l > 0; l -= cmdlen, cmd += cmdlen) {
int match;
check_body:
cmdlen = F_LEN(cmd);
if (skip_or) {
if ((cmd->len & F_OR) == 0)
skip_or = 0;
continue;
}
match = 0;
switch (cmd->opcode) {
case O_NOP:
match = 1;
break;
case O_FORWARD_MAC:
kprintf("ipfw: opcode %d unimplemented\n",
cmd->opcode);
break;
case O_GID:
case O_UID:
if (lc.offset!=0)
break;
match = ipfw_match_uid(&args->f_id, oif,
cmd->opcode,
(uid_t)((ipfw_insn_u32 *)cmd)->d[0]);
break;
case O_RECV:
match = iface_match(m->m_pkthdr.rcvif,
(ipfw_insn_if *)cmd);
break;
case O_XMIT:
match = iface_match(oif, (ipfw_insn_if *)cmd);
break;
case O_VIA:
match = iface_match(oif ? oif :
m->m_pkthdr.rcvif, (ipfw_insn_if *)cmd);
break;
case O_MACADDR2:
if (args->eh != NULL) {
uint32_t *want = (uint32_t *)
((ipfw_insn_mac *)cmd)->addr;
uint32_t *mask = (uint32_t *)
((ipfw_insn_mac *)cmd)->mask;
uint32_t *hdr = (uint32_t *)args->eh;
match =
(want[0] == (hdr[0] & mask[0]) &&
want[1] == (hdr[1] & mask[1]) &&
want[2] == (hdr[2] & mask[2]));
}
break;
case O_MAC_TYPE:
if (args->eh != NULL) {
uint16_t t =
ntohs(args->eh->ether_type);
uint16_t *p =
((ipfw_insn_u16 *)cmd)->ports;
int i;
if (m->m_flags & M_VLANTAG)
t = ETHERTYPE_VLAN;
for (i = cmdlen - 1; !match && i > 0;
i--, p += 2) {
match =
(t >= p[0] && t <= p[1]);
}
}
break;
case O_FRAG:
match = (hlen > 0 && lc.offset != 0);
break;
case O_IPFRAG:
if (hlen > 0) {
uint16_t off;
off = ntohs(ip->ip_off);
if (off & (IP_MF | IP_OFFMASK))
match = 1;
}
break;
case O_IN:
match = (oif == NULL);
break;
case O_LAYER2:
match = (args->eh != NULL);
break;
case O_PROTO:
match = (lc.proto == cmd->arg1);
break;
case O_IP_SRC:
match = (hlen > 0 &&
((ipfw_insn_ip *)cmd)->addr.s_addr ==
lc.src_ip.s_addr);
break;
case O_IP_SRC_MASK:
match = (hlen > 0 &&
((ipfw_insn_ip *)cmd)->addr.s_addr ==
(lc.src_ip.s_addr &
((ipfw_insn_ip *)cmd)->mask.s_addr));
break;
case O_IP_SRC_ME:
if (hlen > 0) {
struct ifnet *tif;
tif = INADDR_TO_IFP(&lc.src_ip);
match = (tif != NULL);
}
break;
case O_IP_SRC_TABLE:
match = ipfw_table_lookup(ctx, cmd->arg1,
&lc.src_ip);
break;
case O_IP_SRC_IFIP:
match = ipfw_match_ifip((ipfw_insn_ifip *)cmd,
&lc.src_ip);
break;
case O_IP_DST_SET:
case O_IP_SRC_SET:
if (hlen > 0) {
uint32_t *d = (uint32_t *)(cmd + 1);
uint32_t addr =
cmd->opcode == O_IP_DST_SET ?
args->f_id.dst_ip :
args->f_id.src_ip;
if (addr < d[0])
break;
addr -= d[0];
match =
(addr < cmd->arg1) &&
(d[1 + (addr >> 5)] &
(1 << (addr & 0x1f)));
}
break;
case O_IP_DST:
match = (hlen > 0 &&
((ipfw_insn_ip *)cmd)->addr.s_addr ==
lc.dst_ip.s_addr);
break;
case O_IP_DST_MASK:
match = (hlen > 0) &&
(((ipfw_insn_ip *)cmd)->addr.s_addr ==
(lc.dst_ip.s_addr &
((ipfw_insn_ip *)cmd)->mask.s_addr));
break;
case O_IP_DST_ME:
if (hlen > 0) {
struct ifnet *tif;
tif = INADDR_TO_IFP(&lc.dst_ip);
match = (tif != NULL);
}
break;
case O_IP_DST_TABLE:
match = ipfw_table_lookup(ctx, cmd->arg1,
&lc.dst_ip);
break;
case O_IP_DST_IFIP:
match = ipfw_match_ifip((ipfw_insn_ifip *)cmd,
&lc.dst_ip);
break;
case O_IP_SRCPORT:
case O_IP_DSTPORT:
if ((lc.proto==IPPROTO_UDP ||
lc.proto==IPPROTO_TCP)
&& lc.offset == 0) {
uint16_t x =
(cmd->opcode == O_IP_SRCPORT) ?
lc.src_port : lc.dst_port;
uint16_t *p =
((ipfw_insn_u16 *)cmd)->ports;
int i;
for (i = cmdlen - 1; !match && i > 0;
i--, p += 2) {
match =
(x >= p[0] && x <= p[1]);
}
}
break;
case O_ICMPCODE:
match = (lc.offset == 0 &&
lc.proto==IPPROTO_ICMP &&
icmpcode_match(ip, (ipfw_insn_u32 *)cmd));
break;
case O_ICMPTYPE:
match = (lc.offset == 0 &&
lc.proto==IPPROTO_ICMP &&
icmptype_match(ip, (ipfw_insn_u32 *)cmd));
break;
case O_IPOPT:
match = (hlen > 0 && ipopts_match(ip, cmd));
break;
case O_IPVER:
match = (hlen > 0 && cmd->arg1 == ip->ip_v);
break;
case O_IPTTL:
match = (hlen > 0 && cmd->arg1 == ip->ip_ttl);
break;
case O_IPID:
match = (hlen > 0 &&
cmd->arg1 == ntohs(ip->ip_id));
break;
case O_IPLEN:
match = (hlen > 0 && cmd->arg1 == lc.ip_len);
break;
case O_IPPRECEDENCE:
match = (hlen > 0 &&
(cmd->arg1 == (ip->ip_tos & 0xe0)));
break;
case O_IPTOS:
match = (hlen > 0 &&
flags_match(cmd, ip->ip_tos));
break;
case O_TCPFLAGS:
match = (lc.proto == IPPROTO_TCP &&
lc.offset == 0 &&
flags_match(cmd,
L3HDR(struct tcphdr,ip)->th_flags));
break;
case O_TCPOPTS:
match = (lc.proto == IPPROTO_TCP &&
lc.offset == 0 && tcpopts_match(ip, cmd));
break;
case O_TCPSEQ:
match = (lc.proto == IPPROTO_TCP &&
lc.offset == 0 &&
((ipfw_insn_u32 *)cmd)->d[0] ==
L3HDR(struct tcphdr,ip)->th_seq);
break;
case O_TCPACK:
match = (lc.proto == IPPROTO_TCP &&
lc.offset == 0 &&
((ipfw_insn_u32 *)cmd)->d[0] ==
L3HDR(struct tcphdr,ip)->th_ack);
break;
case O_TCPWIN:
match = (lc.proto == IPPROTO_TCP &&
lc.offset == 0 &&
cmd->arg1 ==
L3HDR(struct tcphdr,ip)->th_win);
break;
case O_ESTAB:
match = (lc.proto == IPPROTO_TCP &&
lc.offset == 0 &&
(L3HDR(struct tcphdr,ip)->th_flags &
(TH_RST | TH_ACK | TH_SYN)) != TH_SYN);
break;
case O_LOG:
if (fw_verbose) {
ipfw_log(ctx, f, hlen, args->eh, m,
oif);
}
match = 1;
break;
case O_PROB:
match = (krandom() <
((ipfw_insn_u32 *)cmd)->d[0]);
break;
case O_REDIRECT:
if (f->cross_rules == NULL) {
goto next_rule;
}
if (oif != NULL || args->eh != NULL ||
(ip->ip_off & htons(IP_MF | IP_OFFMASK)) ||
(lc.proto != IPPROTO_TCP &&
lc.proto != IPPROTO_UDP))
break;
case O_LIMIT:
case O_KEEP_STATE:
if (hlen == 0)
break;
s = ipfw_state_install(ctx, f,
(ipfw_insn_limit *)cmd, args, lc.tcp);
if (s == NULL) {
retval = IP_FW_DENY;
goto done;
}
s->st_pcnt++;
s->st_bcnt += lc.ip_len;
if (s->st_type == O_REDIRECT) {
struct in_addr oaddr;
uint16_t oport;
struct ipfw_xlat *slave_x, *x;
struct ipfw_state *dup;
x = (struct ipfw_xlat *)s;
ipfw_xlate(x, m, &oaddr, &oport);
m = ipfw_rehashm(m, hlen, args, &lc,
&ip);
if (m == NULL) {
ipfw_state_del(ctx, s);
goto pullup_failed;
}
cpuid = netisr_hashcpu(
m->m_pkthdr.hash);
slave_x = (struct ipfw_xlat *)
ipfw_state_alloc(ctx, &args->f_id,
O_REDIRECT, f->cross_rules[cpuid],
lc.tcp);
if (slave_x == NULL) {
ipfw_state_del(ctx, s);
retval = IP_FW_DENY;
goto done;
}
slave_x->xlat_addr = oaddr.s_addr;
slave_x->xlat_port = oport;
slave_x->xlat_dir = MATCH_REVERSE;
slave_x->xlat_flags |=
IPFW_STATE_F_XLATSRC |
IPFW_STATE_F_XLATSLAVE;
slave_x->xlat_pair = x;
slave_x->xlat_pcpu = mycpuid;
x->xlat_pair = slave_x;
x->xlat_pcpu = cpuid;
ctx->ipfw_xlated++;
if (cpuid != mycpuid) {
ctx->ipfw_xlate_split++;
ipfw_xlate_redispatch(
m, cpuid, x,
IPFW_XLATE_INSERT |
IPFW_XLATE_FORWARD);
args->m = NULL;
return (IP_FW_REDISPATCH);
}
dup = ipfw_state_link(ctx,
&slave_x->xlat_st);
if (dup != NULL) {
ctx->ipfw_xlate_conflicts++;
if (IPFW_STATE_ISDEAD(dup)) {
ipfw_state_remove(ctx,
dup);
dup = ipfw_state_link(
ctx, &slave_x->xlat_st);
}
if (dup != NULL) {
if (bootverbose) {
kprintf("ipfw: "
"slave %u state "
"conflicts "
"%u state\n",
x->xlat_type,
s->st_type);
}
ipfw_state_del(ctx, s);
return (IP_FW_DENY);
}
ctx->ipfw_xlate_cresolved++;
}
}
match = 1;
break;
case O_PROBE_STATE:
case O_CHECK_STATE:
s = NULL;
if (dyn_dir == MATCH_UNKNOWN) {
s = ipfw_state_lookup(ctx,
&args->f_id, &dyn_dir, lc.tcp);
}
if (s == NULL ||
(s->st_type == O_REDIRECT &&
(args->eh != NULL ||
(ip->ip_off & htons(IP_MF | IP_OFFMASK)) ||
(lc.proto != IPPROTO_TCP &&
lc.proto != IPPROTO_UDP)))) {
if (cmd->opcode == O_CHECK_STATE)
goto next_rule;
match = 1;
break;
}
s->st_pcnt++;
s->st_bcnt += lc.ip_len;
if (s->st_type == O_REDIRECT) {
struct ipfw_xlat *x =
(struct ipfw_xlat *)s;
if (oif != NULL &&
x->xlat_ifp == NULL) {
KASSERT(x->xlat_flags &
IPFW_STATE_F_XLATSLAVE,
("master rdr state "
"missing ifp"));
x->xlat_ifp = oif;
} else if (
(oif != NULL && x->xlat_ifp!=oif) ||
(oif == NULL &&
x->xlat_ifp!=m->m_pkthdr.rcvif)) {
retval = IP_FW_DENY;
goto done;
}
if (x->xlat_dir != dyn_dir)
goto skip_xlate;
ipfw_xlate(x, m, NULL, NULL);
m = ipfw_rehashm(m, hlen, args, &lc,
&ip);
if (m == NULL)
goto pullup_failed;
cpuid = netisr_hashcpu(
m->m_pkthdr.hash);
if (cpuid != mycpuid) {
uint32_t xlate = 0;
if (oif != NULL) {
xlate |=
IPFW_XLATE_OUTPUT;
}
if (dyn_dir == MATCH_FORWARD) {
xlate |=
IPFW_XLATE_FORWARD;
}
ipfw_xlate_redispatch(m, cpuid,
x, xlate);
args->m = NULL;
return (IP_FW_REDISPATCH);
}
KKASSERT(x->xlat_pcpu == mycpuid);
ipfw_state_update(&args->f_id, dyn_dir,
lc.tcp, &x->xlat_pair->xlat_st);
}
skip_xlate:
f = s->st_rule;
KKASSERT(f->cpuid == mycpuid);
cmd = ACTION_PTR(f);
l = f->cmd_len - f->act_ofs;
dyn_f = f;
goto check_body;
case O_ACCEPT:
retval = IP_FW_PASS;
goto done;
case O_DEFRAG:
if (f->cross_rules == NULL) {
goto next_rule;
}
if (oif != NULL || args->eh != NULL ||
(ip->ip_off & htons(IP_MF | IP_OFFMASK)) == 0)
goto next_rule;
ctx->ipfw_frags++;
m = ip_reass(m);
args->m = m;
if (m == NULL) {
retval = IP_FW_PASS;
goto done;
}
ctx->ipfw_defraged++;
KASSERT((m->m_flags & M_HASH) == 0,
("hash not cleared"));
f->pcnt++;
f->bcnt += lc.ip_len;
f->timestamp = time_second;
ip = mtod(m, struct ip *);
hlen = ip->ip_hl << 2;
ip->ip_len = htons(ntohs(ip->ip_len) + hlen);
ip_hashfn(&m, 0);
args->m = m;
if (m == NULL)
goto pullup_failed;
KASSERT(m->m_flags & M_HASH, ("no hash"));
cpuid = netisr_hashcpu(m->m_pkthdr.hash);
if (cpuid != mycpuid) {
ctx->ipfw_defrag_remote++;
ipfw_defrag_redispatch(m, cpuid, f);
args->m = NULL;
return (IP_FW_REDISPATCH);
}
ip = mtod(m, struct ip *);
m = ipfw_setup_local(m, hlen, args, &lc, &ip);
if (m == NULL)
goto pullup_failed;
goto next_rule;
case O_PIPE:
case O_QUEUE:
args->rule = f;
args->cookie = cmd->arg1;
retval = IP_FW_DUMMYNET;
goto done;
case O_DIVERT:
case O_TEE:
if (args->eh)
break;
mtag = m_tag_get(PACKET_TAG_IPFW_DIVERT,
sizeof(*divinfo), M_INTWAIT | M_NULLOK);
if (mtag == NULL) {
retval = IP_FW_DENY;
goto done;
}
divinfo = m_tag_data(mtag);
divinfo->skipto = f->rulenum;
divinfo->port = cmd->arg1;
divinfo->tee = (cmd->opcode == O_TEE);
m_tag_prepend(m, mtag);
args->cookie = cmd->arg1;
retval = (cmd->opcode == O_DIVERT) ?
IP_FW_DIVERT : IP_FW_TEE;
goto done;
case O_COUNT:
case O_SKIPTO:
f->pcnt++;
f->bcnt += lc.ip_len;
f->timestamp = time_second;
if (cmd->opcode == O_COUNT)
goto next_rule;
if (f->next_rule == NULL)
lookup_next_rule(f);
f = f->next_rule;
goto again;
case O_REJECT:
if (hlen > 0 &&
(lc.proto != IPPROTO_ICMP ||
is_icmp_query(ip)) &&
!(m->m_flags & (M_BCAST|M_MCAST)) &&
!IN_MULTICAST(ntohl(lc.dst_ip.s_addr))) {
send_reject(args, cmd->arg1,
lc.offset, lc.ip_len);
retval = IP_FW_DENY;
goto done;
}
case O_DENY:
retval = IP_FW_DENY;
goto done;
case O_FORWARD_IP:
if (args->eh)
break;
if (!dyn_f || dyn_dir == MATCH_FORWARD) {
struct sockaddr_in *sin;
mtag = m_tag_get(PACKET_TAG_IPFORWARD,
sizeof(*sin), M_INTWAIT | M_NULLOK);
if (mtag == NULL) {
retval = IP_FW_DENY;
goto done;
}
sin = m_tag_data(mtag);
*sin = ((ipfw_insn_sa *)cmd)->sa;
m_tag_prepend(m, mtag);
m->m_pkthdr.fw_flags |=
IPFORWARD_MBUF_TAGGED;
m->m_pkthdr.fw_flags &=
~BRIDGE_MBUF_TAGGED;
}
retval = IP_FW_PASS;
goto done;
default:
panic("-- unknown opcode %d", cmd->opcode);
}
if (cmd->len & F_NOT)
match = !match;
if (match) {
if (cmd->len & F_OR)
skip_or = 1;
} else {
if (!(cmd->len & F_OR))
break;
}
}
next_rule:;
}
kprintf("+++ ipfw: ouch!, skip past end of rules, denying packet\n");
return IP_FW_DENY;
done:
f->pcnt++;
f->bcnt += lc.ip_len;
f->timestamp = time_second;
return retval;
pullup_failed:
if (fw_verbose)
kprintf("pullup failed\n");
return IP_FW_DENY;
}
static struct mbuf *
ipfw_dummynet_io(struct mbuf *m, int pipe_nr, int dir, struct ip_fw_args *fwa)
{
struct m_tag *mtag;
struct dn_pkt *pkt;
ipfw_insn *cmd;
const struct ipfw_flow_id *id;
struct dn_flow_id *fid;
M_ASSERTPKTHDR(m);
mtag = m_tag_get(PACKET_TAG_DUMMYNET, sizeof(*pkt),
M_INTWAIT | M_NULLOK);
if (mtag == NULL) {
m_freem(m);
return (NULL);
}
m_tag_prepend(m, mtag);
pkt = m_tag_data(mtag);
bzero(pkt, sizeof(*pkt));
cmd = fwa->rule->cmd + fwa->rule->act_ofs;
if (cmd->opcode == O_LOG)
cmd += F_LEN(cmd);
KASSERT(cmd->opcode == O_PIPE || cmd->opcode == O_QUEUE,
("Rule is not PIPE or QUEUE, opcode %d", cmd->opcode));
pkt->dn_m = m;
pkt->dn_flags = (dir & DN_FLAGS_DIR_MASK);
pkt->ifp = fwa->oif;
pkt->pipe_nr = pipe_nr;
pkt->cpuid = mycpuid;
pkt->msgport = netisr_curport();
id = &fwa->f_id;
fid = &pkt->id;
fid->fid_dst_ip = id->dst_ip;
fid->fid_src_ip = id->src_ip;
fid->fid_dst_port = id->dst_port;
fid->fid_src_port = id->src_port;
fid->fid_proto = id->proto;
fid->fid_flags = id->flags;
ipfw_ref_rule(fwa->rule);
pkt->dn_priv = fwa->rule;
pkt->dn_unref_priv = ipfw_unref_rule;
if (cmd->opcode == O_PIPE)
pkt->dn_flags |= DN_FLAGS_IS_PIPE;
m->m_pkthdr.fw_flags |= DUMMYNET_MBUF_TAGGED;
return (m);
}
static void
ipfw_flush_rule_ptrs(struct ipfw_context *ctx)
{
struct ip_fw *rule;
for (rule = ctx->ipfw_layer3_chain; rule; rule = rule->next)
rule->next_rule = NULL;
}
static void
ipfw_inc_static_count(struct ip_fw *rule)
{
KKASSERT(mycpuid == 0);
static_count++;
static_ioc_len += IOC_RULESIZE(rule);
}
static void
ipfw_dec_static_count(struct ip_fw *rule)
{
int l = IOC_RULESIZE(rule);
KKASSERT(mycpuid == 0);
KASSERT(static_count > 0, ("invalid static count %u", static_count));
static_count--;
KASSERT(static_ioc_len >= l,
("invalid static len %u", static_ioc_len));
static_ioc_len -= l;
}
static void
ipfw_link_sibling(struct netmsg_ipfw *fwmsg, struct ip_fw *rule)
{
if (fwmsg->sibling != NULL) {
KKASSERT(mycpuid > 0 && fwmsg->sibling->cpuid == mycpuid - 1);
fwmsg->sibling->sibling = rule;
}
fwmsg->sibling = rule;
}
static struct ip_fw *
ipfw_create_rule(const struct ipfw_ioc_rule *ioc_rule, uint32_t rule_flags)
{
struct ip_fw *rule;
rule = kmalloc(RULESIZE(ioc_rule), M_IPFW, M_WAITOK | M_ZERO);
rule->act_ofs = ioc_rule->act_ofs;
rule->cmd_len = ioc_rule->cmd_len;
rule->rulenum = ioc_rule->rulenum;
rule->set = ioc_rule->set;
rule->usr_flags = ioc_rule->usr_flags;
bcopy(ioc_rule->cmd, rule->cmd, rule->cmd_len * 4 );
rule->refcnt = 1;
rule->cpuid = mycpuid;
rule->rule_flags = rule_flags;
return rule;
}
static void
ipfw_add_rule_dispatch(netmsg_t nmsg)
{
struct netmsg_ipfw *fwmsg = (struct netmsg_ipfw *)nmsg;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ip_fw *rule;
ASSERT_NETISR_NCPUS(mycpuid);
rule = ipfw_create_rule(fwmsg->ioc_rule, fwmsg->rule_flags);
if (fwmsg->prev_rule != NULL) {
struct ip_fw *prev, *next;
prev = fwmsg->prev_rule;
KKASSERT(prev->cpuid == mycpuid);
next = fwmsg->next_rule;
KKASSERT(next->cpuid == mycpuid);
rule->next = next;
prev->next = rule;
fwmsg->prev_rule = prev->sibling;
fwmsg->next_rule = next->sibling;
} else {
KKASSERT(fwmsg->next_rule == NULL);
rule->next = ctx->ipfw_layer3_chain;
ctx->ipfw_layer3_chain = rule;
}
ipfw_link_sibling(fwmsg, rule);
ipfw_flush_rule_ptrs(ctx);
if (mycpuid == 0) {
ipfw_inc_static_count(rule);
nmsg->lmsg.u.ms_resultp = rule;
}
if (rule->rule_flags & IPFW_RULE_F_GENTRACK)
rule->track_ruleid = (uintptr_t)nmsg->lmsg.u.ms_resultp;
if (fwmsg->cross_rules != NULL) {
fwmsg->cross_rules[mycpuid] = rule;
}
netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}
static void
ipfw_crossref_rule_dispatch(netmsg_t nmsg)
{
struct netmsg_ipfw *fwmsg = (struct netmsg_ipfw *)nmsg;
struct ip_fw *rule = fwmsg->sibling;
int sz = sizeof(struct ip_fw *) * netisr_ncpus;
ASSERT_NETISR_NCPUS(mycpuid);
KASSERT(rule->rule_flags & IPFW_RULE_F_CROSSREF,
("not crossref rule"));
rule->cross_rules = kmalloc(sz, M_IPFW, M_WAITOK);
memcpy(rule->cross_rules, fwmsg->cross_rules, sz);
fwmsg->sibling = rule->sibling;
netisr_forwardmsg(&fwmsg->base, mycpuid + 1);
}
static void
ipfw_add_rule(struct ipfw_ioc_rule *ioc_rule, uint32_t rule_flags)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct netmsg_ipfw fwmsg;
struct ip_fw *f, *prev, *rule;
ASSERT_NETISR0;
if (ioc_rule->rulenum == 0) {
int step = autoinc_step;
KKASSERT(step >= IPFW_AUTOINC_STEP_MIN &&
step <= IPFW_AUTOINC_STEP_MAX);
for (f = ctx->ipfw_layer3_chain; f; f = f->next) {
if (f->rulenum == IPFW_DEFAULT_RULE)
break;
ioc_rule->rulenum = f->rulenum;
}
if (ioc_rule->rulenum < IPFW_DEFAULT_RULE - step)
ioc_rule->rulenum += step;
}
KASSERT(ioc_rule->rulenum != IPFW_DEFAULT_RULE &&
ioc_rule->rulenum != 0,
("invalid rule num %d", ioc_rule->rulenum));
for (prev = NULL, f = ctx->ipfw_layer3_chain; f;
prev = f, f = f->next) {
if (f->rulenum > ioc_rule->rulenum) {
break;
}
}
KASSERT(f != NULL, ("no default rule?!"));
bzero(&fwmsg, sizeof(fwmsg));
netmsg_init(&fwmsg.base, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_add_rule_dispatch);
fwmsg.ioc_rule = ioc_rule;
fwmsg.prev_rule = prev;
fwmsg.next_rule = prev == NULL ? NULL : f;
fwmsg.rule_flags = rule_flags;
if (rule_flags & IPFW_RULE_F_CROSSREF) {
fwmsg.cross_rules = kmalloc(
sizeof(struct ip_fw *) * netisr_ncpus, M_TEMP,
M_WAITOK | M_ZERO);
}
netisr_domsg_global(&fwmsg.base);
KKASSERT(fwmsg.prev_rule == NULL && fwmsg.next_rule == NULL);
rule = fwmsg.base.lmsg.u.ms_resultp;
KKASSERT(rule != NULL && rule->cpuid == mycpuid);
if (fwmsg.cross_rules != NULL) {
netmsg_init(&fwmsg.base, NULL, &curthread->td_msgport,
MSGF_PRIORITY, ipfw_crossref_rule_dispatch);
fwmsg.sibling = rule;
netisr_domsg_global(&fwmsg.base);
KKASSERT(fwmsg.sibling == NULL);
kfree(fwmsg.cross_rules, M_TEMP);
#ifdef KLD_MODULE
atomic_add_int(&ipfw_gd.ipfw_refcnt, 1);
#endif
}
DPRINTF("++ installed rule %d, static count now %d\n",
rule->rulenum, static_count);
}
static struct ip_fw *
ipfw_delete_rule(struct ipfw_context *ctx,
struct ip_fw *prev, struct ip_fw *rule)
{
struct ip_fw *n;
n = rule->next;
if (prev == NULL)
ctx->ipfw_layer3_chain = n;
else
prev->next = n;
rule->rule_flags |= IPFW_RULE_F_INVALID;
rule->next_rule = NULL;
rule->sibling = NULL;
#ifdef foo
rule->cpuid = -1;
#endif
if (mycpuid == 0)
ipfw_dec_static_count(rule);
if ((rule->rule_flags & IPFW_RULE_F_CROSSREF) == 0) {
ipfw_free_rule(rule);
} else {
if (mycpuid == 0) {
rule->next = ipfw_gd.ipfw_crossref_free;
ipfw_gd.ipfw_crossref_free = rule;
}
}
return n;
}
static void
ipfw_flush_dispatch(netmsg_t nmsg)
{
int kill_default = nmsg->lmsg.u.ms_result;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ip_fw *rule;
ASSERT_NETISR_NCPUS(mycpuid);
ipfw_state_flush(ctx, NULL);
KASSERT(ctx->ipfw_state_cnt == 0,
("%d pcpu states remain", ctx->ipfw_state_cnt));
ctx->ipfw_state_loosecnt = 0;
ctx->ipfw_state_lastexp = 0;
ipfw_track_flush(ctx, NULL);
ctx->ipfw_track_lastexp = 0;
if (ctx->ipfw_trkcnt_spare != NULL) {
kfree(ctx->ipfw_trkcnt_spare, M_IPFW);
ctx->ipfw_trkcnt_spare = NULL;
}
ipfw_flush_rule_ptrs(ctx);
while ((rule = ctx->ipfw_layer3_chain) != NULL &&
(kill_default || rule->rulenum != IPFW_DEFAULT_RULE))
ipfw_delete_rule(ctx, NULL, rule);
netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}
static void
ipfw_flush(int kill_default)
{
struct netmsg_base nmsg;
#ifdef INVARIANTS
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
int state_cnt;
#endif
ASSERT_NETISR0;
if (!kill_default) {
ipfw_flushing = 1;
netmsg_service_sync();
}
bzero(&nmsg, sizeof(nmsg));
netmsg_init(&nmsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_flush_dispatch);
nmsg.lmsg.u.ms_result = kill_default;
netisr_domsg_global(&nmsg);
ipfw_gd.ipfw_state_loosecnt = 0;
ipfw_gd.ipfw_state_globexp = 0;
ipfw_gd.ipfw_track_globexp = 0;
#ifdef INVARIANTS
state_cnt = ipfw_state_cntcoll();
KASSERT(state_cnt == 0, ("%d states remain", state_cnt));
KASSERT(ipfw_gd.ipfw_trkcnt_cnt == 0,
("%d trkcnts remain", ipfw_gd.ipfw_trkcnt_cnt));
if (kill_default) {
KASSERT(static_count == 0,
("%u static rules remain", static_count));
KASSERT(static_ioc_len == 0,
("%u bytes of static rules remain", static_ioc_len));
} else {
KASSERT(static_count == 1,
("%u static rules remain", static_count));
KASSERT(static_ioc_len == IOC_RULESIZE(ctx->ipfw_default_rule),
("%u bytes of static rules remain, should be %lu",
static_ioc_len,
(u_long)IOC_RULESIZE(ctx->ipfw_default_rule)));
}
#endif
ipfw_flushing = 0;
}
static void
ipfw_alt_delete_rule_dispatch(netmsg_t nmsg)
{
struct netmsg_del *dmsg = (struct netmsg_del *)nmsg;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ip_fw *rule, *prev;
ASSERT_NETISR_NCPUS(mycpuid);
rule = dmsg->start_rule;
KKASSERT(rule->cpuid == mycpuid);
dmsg->start_rule = rule->sibling;
prev = dmsg->prev_rule;
if (prev != NULL) {
KKASSERT(prev->cpuid == mycpuid);
dmsg->prev_rule = prev->sibling;
}
ipfw_flush_rule_ptrs(ctx);
while (rule && rule->rulenum == dmsg->rulenum) {
if (rule->rule_flags & IPFW_RULE_F_GENSTATE) {
ipfw_state_flush(ctx, rule);
}
if (rule->rule_flags & IPFW_RULE_F_GENTRACK) {
ipfw_track_flush(ctx, rule);
}
rule = ipfw_delete_rule(ctx, prev, rule);
}
netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}
static int
ipfw_alt_delete_rule(uint16_t rulenum)
{
struct ip_fw *prev, *rule;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct netmsg_del dmsg;
ASSERT_NETISR0;
for (prev = NULL, rule = ctx->ipfw_layer3_chain;
rule && rule->rulenum < rulenum;
prev = rule, rule = rule->next)
;
if (rule->rulenum != rulenum)
return EINVAL;
bzero(&dmsg, sizeof(dmsg));
netmsg_init(&dmsg.base, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_alt_delete_rule_dispatch);
dmsg.prev_rule = prev;
dmsg.start_rule = rule;
dmsg.rulenum = rulenum;
netisr_domsg_global(&dmsg.base);
KKASSERT(dmsg.prev_rule == NULL && dmsg.start_rule == NULL);
return 0;
}
static void
ipfw_alt_delete_ruleset_dispatch(netmsg_t nmsg)
{
struct netmsg_del *dmsg = (struct netmsg_del *)nmsg;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ip_fw *prev, *rule;
#ifdef INVARIANTS
int del = 0;
#endif
ASSERT_NETISR_NCPUS(mycpuid);
ipfw_flush_rule_ptrs(ctx);
prev = NULL;
rule = ctx->ipfw_layer3_chain;
while (rule != NULL) {
if (rule->set == dmsg->from_set) {
if (rule->rule_flags & IPFW_RULE_F_GENSTATE) {
ipfw_state_flush(ctx, rule);
}
if (rule->rule_flags & IPFW_RULE_F_GENTRACK) {
ipfw_track_flush(ctx, rule);
}
rule = ipfw_delete_rule(ctx, prev, rule);
#ifdef INVARIANTS
del = 1;
#endif
} else {
prev = rule;
rule = rule->next;
}
}
KASSERT(del, ("no match set?!"));
netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}
static int
ipfw_alt_delete_ruleset(uint8_t set)
{
struct netmsg_del dmsg;
int del;
struct ip_fw *rule;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
ASSERT_NETISR0;
del = 0;
for (rule = ctx->ipfw_layer3_chain; rule; rule = rule->next) {
if (rule->set == set)
del = 1;
}
if (!del)
return 0;
bzero(&dmsg, sizeof(dmsg));
netmsg_init(&dmsg.base, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_alt_delete_ruleset_dispatch);
dmsg.from_set = set;
netisr_domsg_global(&dmsg.base);
return 0;
}
static void
ipfw_alt_move_rule_dispatch(netmsg_t nmsg)
{
struct netmsg_del *dmsg = (struct netmsg_del *)nmsg;
struct ip_fw *rule;
ASSERT_NETISR_NCPUS(mycpuid);
rule = dmsg->start_rule;
KKASSERT(rule->cpuid == mycpuid);
dmsg->start_rule = rule->sibling;
while (rule && rule->rulenum <= dmsg->rulenum) {
if (rule->rulenum == dmsg->rulenum)
rule->set = dmsg->to_set;
rule = rule->next;
}
netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}
static int
ipfw_alt_move_rule(uint16_t rulenum, uint8_t set)
{
struct netmsg_del dmsg;
struct netmsg_base *nmsg;
struct ip_fw *rule;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
ASSERT_NETISR0;
for (rule = ctx->ipfw_layer3_chain; rule && rule->rulenum <= rulenum;
rule = rule->next) {
if (rule->rulenum == rulenum && rule->set != set)
break;
}
if (rule == NULL || rule->rulenum > rulenum)
return 0;
bzero(&dmsg, sizeof(dmsg));
nmsg = &dmsg.base;
netmsg_init(nmsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_alt_move_rule_dispatch);
dmsg.start_rule = rule;
dmsg.rulenum = rulenum;
dmsg.to_set = set;
netisr_domsg_global(nmsg);
KKASSERT(dmsg.start_rule == NULL);
return 0;
}
static void
ipfw_alt_move_ruleset_dispatch(netmsg_t nmsg)
{
struct netmsg_del *dmsg = (struct netmsg_del *)nmsg;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ip_fw *rule;
ASSERT_NETISR_NCPUS(mycpuid);
for (rule = ctx->ipfw_layer3_chain; rule; rule = rule->next) {
if (rule->set == dmsg->from_set)
rule->set = dmsg->to_set;
}
netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}
static int
ipfw_alt_move_ruleset(uint8_t from_set, uint8_t to_set)
{
struct netmsg_del dmsg;
struct netmsg_base *nmsg;
ASSERT_NETISR0;
bzero(&dmsg, sizeof(dmsg));
nmsg = &dmsg.base;
netmsg_init(nmsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_alt_move_ruleset_dispatch);
dmsg.from_set = from_set;
dmsg.to_set = to_set;
netisr_domsg_global(nmsg);
return 0;
}
static void
ipfw_alt_swap_ruleset_dispatch(netmsg_t nmsg)
{
struct netmsg_del *dmsg = (struct netmsg_del *)nmsg;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ip_fw *rule;
ASSERT_NETISR_NCPUS(mycpuid);
for (rule = ctx->ipfw_layer3_chain; rule; rule = rule->next) {
if (rule->set == dmsg->from_set)
rule->set = dmsg->to_set;
else if (rule->set == dmsg->to_set)
rule->set = dmsg->from_set;
}
netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}
static int
ipfw_alt_swap_ruleset(uint8_t set1, uint8_t set2)
{
struct netmsg_del dmsg;
struct netmsg_base *nmsg;
ASSERT_NETISR0;
bzero(&dmsg, sizeof(dmsg));
nmsg = &dmsg.base;
netmsg_init(nmsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_alt_swap_ruleset_dispatch);
dmsg.from_set = set1;
dmsg.to_set = set2;
netisr_domsg_global(nmsg);
return 0;
}
static int
ipfw_ctl_alter(uint32_t arg)
{
uint16_t rulenum;
uint8_t cmd, new_set;
int error = 0;
ASSERT_NETISR0;
rulenum = arg & 0xffff;
cmd = (arg >> 24) & 0xff;
new_set = (arg >> 16) & 0xff;
if (cmd > 4)
return EINVAL;
if (new_set >= IPFW_DEFAULT_SET)
return EINVAL;
if (cmd == 0 || cmd == 2) {
if (rulenum == IPFW_DEFAULT_RULE)
return EINVAL;
} else {
if (rulenum >= IPFW_DEFAULT_SET)
return EINVAL;
}
switch (cmd) {
case 0:
error = ipfw_alt_delete_rule(rulenum);
break;
case 1:
error = ipfw_alt_delete_ruleset(rulenum);
break;
case 2:
error = ipfw_alt_move_rule(rulenum, new_set);
break;
case 3:
error = ipfw_alt_move_ruleset(rulenum, new_set);
break;
case 4:
error = ipfw_alt_swap_ruleset(rulenum, new_set);
break;
}
return error;
}
static void
clear_counters(struct ip_fw *rule, int log_only)
{
ipfw_insn_log *l = (ipfw_insn_log *)ACTION_PTR(rule);
if (log_only == 0) {
rule->bcnt = rule->pcnt = 0;
rule->timestamp = 0;
}
if (l->o.opcode == O_LOG)
l->log_left = l->max_log;
}
static void
ipfw_zero_entry_dispatch(netmsg_t nmsg)
{
struct netmsg_zent *zmsg = (struct netmsg_zent *)nmsg;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ip_fw *rule;
ASSERT_NETISR_NCPUS(mycpuid);
if (zmsg->rulenum == 0) {
KKASSERT(zmsg->start_rule == NULL);
ctx->ipfw_norule_counter = 0;
for (rule = ctx->ipfw_layer3_chain; rule; rule = rule->next)
clear_counters(rule, zmsg->log_only);
} else {
struct ip_fw *start = zmsg->start_rule;
KKASSERT(start->cpuid == mycpuid);
KKASSERT(start->rulenum == zmsg->rulenum);
for (rule = start; rule && rule->rulenum == zmsg->rulenum;
rule = rule->next)
clear_counters(rule, zmsg->log_only);
zmsg->start_rule = start->sibling;
}
netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}
static int
ipfw_ctl_zero_entry(int rulenum, int log_only)
{
struct netmsg_zent zmsg;
struct netmsg_base *nmsg;
const char *msg;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
ASSERT_NETISR0;
bzero(&zmsg, sizeof(zmsg));
nmsg = &zmsg.base;
netmsg_init(nmsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_zero_entry_dispatch);
zmsg.log_only = log_only;
if (rulenum == 0) {
msg = log_only ? "ipfw: All logging counts reset.\n"
: "ipfw: Accounting cleared.\n";
} else {
struct ip_fw *rule;
for (rule = ctx->ipfw_layer3_chain; rule; rule = rule->next) {
if (rule->rulenum == rulenum)
break;
}
if (rule == NULL)
return (EINVAL);
zmsg.start_rule = rule;
zmsg.rulenum = rulenum;
msg = log_only ? "ipfw: Entry %d logging count reset.\n"
: "ipfw: Entry %d cleared.\n";
}
netisr_domsg_global(nmsg);
KKASSERT(zmsg.start_rule == NULL);
if (fw_verbose)
log(LOG_SECURITY | LOG_NOTICE, msg, rulenum);
return (0);
}
static int
ipfw_check_ioc_rule(struct ipfw_ioc_rule *rule, int size, uint32_t *rule_flags)
{
int l, cmdlen = 0;
int have_action = 0;
ipfw_insn *cmd;
*rule_flags = 0;
if (size < sizeof(*rule)) {
kprintf("ipfw: rule too short\n");
return EINVAL;
}
l = IOC_RULESIZE(rule);
if (l != size) {
kprintf("ipfw: size mismatch (have %d want %d)\n", size, l);
return EINVAL;
}
if (rule->rulenum == IPFW_DEFAULT_RULE) {
kprintf("ipfw: invalid rule number\n");
return EINVAL;
}
for (l = rule->cmd_len, cmd = rule->cmd; l > 0;
l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
if (cmdlen > l) {
kprintf("ipfw: opcode %d size truncated\n",
cmd->opcode);
return EINVAL;
}
DPRINTF("ipfw: opcode %d\n", cmd->opcode);
if (cmd->opcode == O_KEEP_STATE || cmd->opcode == O_LIMIT ||
IPFW_ISXLAT(cmd->opcode)) {
*rule_flags |= IPFW_RULE_F_GENSTATE;
if (cmd->opcode == O_LIMIT)
*rule_flags |= IPFW_RULE_F_GENTRACK;
}
if (cmd->opcode == O_DEFRAG || IPFW_ISXLAT(cmd->opcode))
*rule_flags |= IPFW_RULE_F_CROSSREF;
if (cmd->opcode == O_IP_SRC_IFIP ||
cmd->opcode == O_IP_DST_IFIP) {
*rule_flags |= IPFW_RULE_F_DYNIFADDR;
cmd->arg1 &= IPFW_IFIP_SETTINGS;
}
switch (cmd->opcode) {
case O_NOP:
case O_PROBE_STATE:
case O_KEEP_STATE:
case O_PROTO:
case O_IP_SRC_ME:
case O_IP_DST_ME:
case O_LAYER2:
case O_IN:
case O_FRAG:
case O_IPFRAG:
case O_IPOPT:
case O_IPLEN:
case O_IPID:
case O_IPTOS:
case O_IPPRECEDENCE:
case O_IPTTL:
case O_IPVER:
case O_TCPWIN:
case O_TCPFLAGS:
case O_TCPOPTS:
case O_ESTAB:
if (cmdlen != F_INSN_SIZE(ipfw_insn))
goto bad_size;
break;
case O_IP_SRC_TABLE:
case O_IP_DST_TABLE:
if (cmdlen != F_INSN_SIZE(ipfw_insn))
goto bad_size;
if (cmd->arg1 >= ipfw_table_max) {
kprintf("ipfw: invalid table id %u, max %d\n",
cmd->arg1, ipfw_table_max);
return EINVAL;
}
break;
case O_IP_SRC_IFIP:
case O_IP_DST_IFIP:
if (cmdlen != F_INSN_SIZE(ipfw_insn_ifip))
goto bad_size;
break;
case O_ICMPCODE:
case O_ICMPTYPE:
if (cmdlen < F_INSN_SIZE(ipfw_insn_u32))
goto bad_size;
break;
case O_UID:
case O_GID:
case O_IP_SRC:
case O_IP_DST:
case O_TCPSEQ:
case O_TCPACK:
case O_PROB:
if (cmdlen != F_INSN_SIZE(ipfw_insn_u32))
goto bad_size;
break;
case O_LIMIT:
if (cmdlen != F_INSN_SIZE(ipfw_insn_limit))
goto bad_size;
break;
case O_REDIRECT:
if (cmdlen != F_INSN_SIZE(ipfw_insn_rdr))
goto bad_size;
break;
case O_LOG:
if (cmdlen != F_INSN_SIZE(ipfw_insn_log))
goto bad_size;
((ipfw_insn_log *)cmd)->log_left =
((ipfw_insn_log *)cmd)->max_log;
break;
case O_IP_SRC_MASK:
case O_IP_DST_MASK:
if (cmdlen != F_INSN_SIZE(ipfw_insn_ip))
goto bad_size;
if (((ipfw_insn_ip *)cmd)->mask.s_addr == 0) {
kprintf("ipfw: opcode %d, useless rule\n",
cmd->opcode);
return EINVAL;
}
break;
case O_IP_SRC_SET:
case O_IP_DST_SET:
if (cmd->arg1 == 0 || cmd->arg1 > 256) {
kprintf("ipfw: invalid set size %d\n",
cmd->arg1);
return EINVAL;
}
if (cmdlen != F_INSN_SIZE(ipfw_insn_u32) +
(cmd->arg1+31)/32 )
goto bad_size;
break;
case O_MACADDR2:
if (cmdlen != F_INSN_SIZE(ipfw_insn_mac))
goto bad_size;
break;
case O_MAC_TYPE:
case O_IP_SRCPORT:
case O_IP_DSTPORT:
if (cmdlen < 2 || cmdlen > 31)
goto bad_size;
break;
case O_RECV:
case O_XMIT:
case O_VIA:
if (cmdlen != F_INSN_SIZE(ipfw_insn_if))
goto bad_size;
break;
case O_PIPE:
case O_QUEUE:
if (cmdlen != F_INSN_SIZE(ipfw_insn_pipe))
goto bad_size;
goto check_action;
case O_FORWARD_IP:
if (cmdlen != F_INSN_SIZE(ipfw_insn_sa)) {
goto bad_size;
} else {
in_addr_t fwd_addr;
fwd_addr = ((ipfw_insn_sa *)cmd)->
sa.sin_addr.s_addr;
if (IN_MULTICAST(ntohl(fwd_addr))) {
kprintf("ipfw: try forwarding to "
"multicast address\n");
return EINVAL;
}
}
goto check_action;
case O_FORWARD_MAC:
case O_CHECK_STATE:
case O_COUNT:
case O_ACCEPT:
case O_DENY:
case O_REJECT:
case O_SKIPTO:
case O_DIVERT:
case O_TEE:
case O_DEFRAG:
if (cmdlen != F_INSN_SIZE(ipfw_insn))
goto bad_size;
check_action:
if (have_action) {
kprintf("ipfw: opcode %d, multiple actions"
" not allowed\n",
cmd->opcode);
return EINVAL;
}
have_action = 1;
if (l != cmdlen) {
kprintf("ipfw: opcode %d, action must be"
" last opcode\n",
cmd->opcode);
return EINVAL;
}
break;
default:
kprintf("ipfw: opcode %d, unknown opcode\n",
cmd->opcode);
return EINVAL;
}
}
if (have_action == 0) {
kprintf("ipfw: missing action\n");
return EINVAL;
}
return 0;
bad_size:
kprintf("ipfw: opcode %d size %d wrong\n",
cmd->opcode, cmdlen);
return EINVAL;
}
static int
ipfw_ctl_add_rule(struct sockopt *sopt)
{
struct ipfw_ioc_rule *ioc_rule;
size_t size;
uint32_t rule_flags;
int error;
ASSERT_NETISR0;
size = sopt->sopt_valsize;
if (size > (sizeof(uint32_t) * IPFW_RULE_SIZE_MAX) ||
size < sizeof(*ioc_rule)) {
return EINVAL;
}
if (size != (sizeof(uint32_t) * IPFW_RULE_SIZE_MAX)) {
sopt->sopt_val = krealloc(sopt->sopt_val, sizeof(uint32_t) *
IPFW_RULE_SIZE_MAX, M_TEMP, M_WAITOK);
}
ioc_rule = sopt->sopt_val;
error = ipfw_check_ioc_rule(ioc_rule, size, &rule_flags);
if (error)
return error;
ipfw_add_rule(ioc_rule, rule_flags);
if (sopt->sopt_dir == SOPT_GET)
sopt->sopt_valsize = IOC_RULESIZE(ioc_rule);
return 0;
}
static void *
ipfw_copy_rule(const struct ipfw_context *ctx, const struct ip_fw *rule,
struct ipfw_ioc_rule *ioc_rule)
{
const struct ip_fw *sibling;
#ifdef INVARIANTS
int i;
#endif
ASSERT_NETISR0;
KASSERT(rule->cpuid == 0, ("rule does not belong to cpu0"));
ioc_rule->act_ofs = rule->act_ofs;
ioc_rule->cmd_len = rule->cmd_len;
ioc_rule->rulenum = rule->rulenum;
ioc_rule->set = rule->set;
ioc_rule->usr_flags = rule->usr_flags;
ioc_rule->set_disable = ctx->ipfw_set_disable;
ioc_rule->static_count = static_count;
ioc_rule->static_len = static_ioc_len;
#ifdef INVARIANTS
i = 0;
#endif
ioc_rule->pcnt = 0;
ioc_rule->bcnt = 0;
ioc_rule->timestamp = 0;
for (sibling = rule; sibling != NULL; sibling = sibling->sibling) {
ioc_rule->pcnt += sibling->pcnt;
ioc_rule->bcnt += sibling->bcnt;
if (sibling->timestamp > ioc_rule->timestamp)
ioc_rule->timestamp = sibling->timestamp;
#ifdef INVARIANTS
++i;
#endif
}
KASSERT(i == netisr_ncpus,
("static rule is not duplicated on netisr_ncpus %d", netisr_ncpus));
bcopy(rule->cmd, ioc_rule->cmd, ioc_rule->cmd_len * 4 );
return ((uint8_t *)ioc_rule + IOC_RULESIZE(ioc_rule));
}
static boolean_t
ipfw_track_copy(const struct ipfw_trkcnt *trk, struct ipfw_ioc_state *ioc_state)
{
struct ipfw_ioc_flowid *ioc_id;
if (trk->tc_expire == 0) {
return (FALSE);
}
ioc_state->expire = TIME_LEQ(trk->tc_expire, time_uptime) ?
0 : trk->tc_expire - time_uptime;
ioc_state->pcnt = 0;
ioc_state->bcnt = 0;
ioc_state->dyn_type = O_LIMIT_PARENT;
ioc_state->count = trk->tc_count;
ioc_state->rulenum = trk->tc_rulenum;
ioc_id = &ioc_state->id;
ioc_id->type = ETHERTYPE_IP;
ioc_id->u.ip.proto = trk->tc_proto;
ioc_id->u.ip.src_ip = trk->tc_saddr;
ioc_id->u.ip.dst_ip = trk->tc_daddr;
ioc_id->u.ip.src_port = trk->tc_sport;
ioc_id->u.ip.dst_port = trk->tc_dport;
return (TRUE);
}
static boolean_t
ipfw_state_copy(const struct ipfw_state *s, struct ipfw_ioc_state *ioc_state)
{
struct ipfw_ioc_flowid *ioc_id;
if (IPFW_STATE_SCANSKIP(s))
return (FALSE);
ioc_state->expire = TIME_LEQ(s->st_expire, time_uptime) ?
0 : s->st_expire - time_uptime;
ioc_state->pcnt = s->st_pcnt;
ioc_state->bcnt = s->st_bcnt;
ioc_state->dyn_type = s->st_type;
ioc_state->count = 0;
ioc_state->rulenum = s->st_rule->rulenum;
ioc_id = &ioc_state->id;
ioc_id->type = ETHERTYPE_IP;
ioc_id->u.ip.proto = s->st_proto;
ipfw_key_4tuple(&s->st_key,
&ioc_id->u.ip.src_ip, &ioc_id->u.ip.src_port,
&ioc_id->u.ip.dst_ip, &ioc_id->u.ip.dst_port);
if (IPFW_ISXLAT(s->st_type)) {
const struct ipfw_xlat *x = (const struct ipfw_xlat *)s;
if (x->xlat_port == 0)
ioc_state->xlat_port = ioc_id->u.ip.dst_port;
else
ioc_state->xlat_port = ntohs(x->xlat_port);
ioc_state->xlat_addr = ntohl(x->xlat_addr);
ioc_state->pcnt += x->xlat_pair->xlat_pcnt;
ioc_state->bcnt += x->xlat_pair->xlat_bcnt;
}
return (TRUE);
}
static void
ipfw_state_copy_dispatch(netmsg_t nmsg)
{
struct netmsg_cpstate *nm = (struct netmsg_cpstate *)nmsg;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
const struct ipfw_state *s;
const struct ipfw_track *t;
ASSERT_NETISR_NCPUS(mycpuid);
KASSERT(nm->state_cnt < nm->state_cntmax,
("invalid state count %d, max %d",
nm->state_cnt, nm->state_cntmax));
TAILQ_FOREACH(s, &ctx->ipfw_state_list, st_link) {
if (ipfw_state_copy(s, nm->ioc_state)) {
nm->ioc_state++;
nm->state_cnt++;
if (nm->state_cnt == nm->state_cntmax)
goto done;
}
}
TAILQ_FOREACH(t, &ctx->ipfw_track_list, t_link) {
struct ipfw_trkcnt *trk;
if (t->t_count == NULL)
continue;
trk = t->t_trkcnt;
if (trk->tc_expire > t->t_expire)
continue;
trk->tc_expire = t->t_expire;
}
if (mycpuid == netisr_ncpus - 1) {
struct ipfw_trkcnt *trk;
KASSERT(nm->state_cnt < nm->state_cntmax,
("invalid state count %d, max %d",
nm->state_cnt, nm->state_cntmax));
IPFW_TRKCNT_TOKGET;
RB_FOREACH(trk, ipfw_trkcnt_tree, &ipfw_gd.ipfw_trkcnt_tree) {
if (ipfw_track_copy(trk, nm->ioc_state)) {
nm->ioc_state++;
nm->state_cnt++;
if (nm->state_cnt == nm->state_cntmax) {
IPFW_TRKCNT_TOKREL;
goto done;
}
}
}
IPFW_TRKCNT_TOKREL;
}
done:
if (nm->state_cnt == nm->state_cntmax) {
netisr_replymsg(&nm->base, 0);
} else {
netisr_forwardmsg(&nm->base, mycpuid + 1);
}
}
static int
ipfw_ctl_get_rules(struct sockopt *sopt)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ip_fw *rule;
void *bp;
size_t size;
int state_cnt;
ASSERT_NETISR0;
size = static_ioc_len;
state_cnt = ipfw_state_cntcoll() + ipfw_gd.ipfw_trkcnt_cnt;
state_cnt = (state_cnt * 5) / 4;
size += state_cnt * sizeof(struct ipfw_ioc_state);
if (sopt->sopt_valsize < size) {
bzero(sopt->sopt_val, sopt->sopt_valsize);
return 0;
}
bp = sopt->sopt_val;
for (rule = ctx->ipfw_layer3_chain; rule; rule = rule->next)
bp = ipfw_copy_rule(ctx, rule, bp);
if (state_cnt) {
struct netmsg_cpstate nm;
#ifdef INVARIANTS
size_t old_size = size;
#endif
netmsg_init(&nm.base, NULL, &curthread->td_msgport,
MSGF_PRIORITY, ipfw_state_copy_dispatch);
nm.ioc_state = bp;
nm.state_cntmax = state_cnt;
nm.state_cnt = 0;
netisr_domsg_global(&nm.base);
size = static_ioc_len +
(nm.state_cnt * sizeof(struct ipfw_ioc_state));
KKASSERT(size <= old_size);
}
sopt->sopt_valsize = size;
return 0;
}
static void
ipfw_set_disable_dispatch(netmsg_t nmsg)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
ASSERT_NETISR_NCPUS(mycpuid);
ctx->ipfw_set_disable = nmsg->lmsg.u.ms_result32;
netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}
static void
ipfw_ctl_set_disable(uint32_t disable, uint32_t enable)
{
struct netmsg_base nmsg;
uint32_t set_disable;
ASSERT_NETISR0;
enable |= (1 << IPFW_DEFAULT_SET);
set_disable = (ipfw_ctx[mycpuid]->ipfw_set_disable | disable) & ~enable;
bzero(&nmsg, sizeof(nmsg));
netmsg_init(&nmsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_set_disable_dispatch);
nmsg.lmsg.u.ms_result32 = set_disable;
netisr_domsg_global(&nmsg);
}
static void
ipfw_table_create_dispatch(netmsg_t nm)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
int tblid = nm->lmsg.u.ms_result;
ASSERT_NETISR_NCPUS(mycpuid);
if (!rn_inithead(&ctx->ipfw_tables[tblid], rn_cpumaskhead(mycpuid),
offsetof(struct sockaddr_in, sin_addr)))
panic("ipfw: create table%d failed", tblid);
netisr_forwardmsg(&nm->base, mycpuid + 1);
}
static int
ipfw_table_create(struct sockopt *sopt)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ipfw_ioc_table *tbl;
struct netmsg_base nm;
ASSERT_NETISR0;
if (sopt->sopt_valsize != sizeof(*tbl))
return (EINVAL);
tbl = sopt->sopt_val;
if (tbl->tableid < 0 || tbl->tableid >= ipfw_table_max)
return (EINVAL);
if (ctx->ipfw_tables[tbl->tableid] != NULL)
return (EEXIST);
netmsg_init(&nm, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_table_create_dispatch);
nm.lmsg.u.ms_result = tbl->tableid;
netisr_domsg_global(&nm);
return (0);
}
static void
ipfw_table_killent(struct radix_node *rn)
{
struct ipfw_tblent *te;
te = (struct ipfw_tblent *)rn;
kfree(te, M_IPFW);
}
static void
ipfw_table_flush_oncpu(struct ipfw_context *ctx, int tableid,
int destroy)
{
struct radix_node_head *rnh;
ASSERT_NETISR_NCPUS(mycpuid);
rnh = ctx->ipfw_tables[tableid];
rn_flush(rnh, ipfw_table_killent);
if (destroy) {
rn_freehead(rnh);
ctx->ipfw_tables[tableid] = NULL;
}
}
static void
ipfw_table_flush_dispatch(netmsg_t nmsg)
{
struct netmsg_tblflush *nm = (struct netmsg_tblflush *)nmsg;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
ASSERT_NETISR_NCPUS(mycpuid);
ipfw_table_flush_oncpu(ctx, nm->tableid, nm->destroy);
netisr_forwardmsg(&nm->base, mycpuid + 1);
}
static void
ipfw_table_flushall_oncpu(struct ipfw_context *ctx, int destroy)
{
int i;
ASSERT_NETISR_NCPUS(mycpuid);
for (i = 0; i < ipfw_table_max; ++i) {
if (ctx->ipfw_tables[i] != NULL)
ipfw_table_flush_oncpu(ctx, i, destroy);
}
}
static void
ipfw_table_flushall_dispatch(netmsg_t nmsg)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
ASSERT_NETISR_NCPUS(mycpuid);
ipfw_table_flushall_oncpu(ctx, 0);
netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}
static int
ipfw_table_flush(struct sockopt *sopt)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ipfw_ioc_table *tbl;
struct netmsg_tblflush nm;
ASSERT_NETISR0;
if (sopt->sopt_valsize != sizeof(*tbl))
return (EINVAL);
tbl = sopt->sopt_val;
if (sopt->sopt_name == IP_FW_TBL_FLUSH && tbl->tableid < 0) {
netmsg_init(&nm.base, NULL, &curthread->td_msgport,
MSGF_PRIORITY, ipfw_table_flushall_dispatch);
netisr_domsg_global(&nm.base);
return (0);
}
if (tbl->tableid < 0 || tbl->tableid >= ipfw_table_max)
return (EINVAL);
if (ctx->ipfw_tables[tbl->tableid] == NULL)
return (ENOENT);
netmsg_init(&nm.base, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_table_flush_dispatch);
nm.tableid = tbl->tableid;
nm.destroy = 0;
if (sopt->sopt_name == IP_FW_TBL_DESTROY)
nm.destroy = 1;
netisr_domsg_global(&nm.base);
return (0);
}
static int
ipfw_table_cntent(struct radix_node *rn __unused, void *xcnt)
{
int *cnt = xcnt;
(*cnt)++;
return (0);
}
static int
ipfw_table_cpent(struct radix_node *rn, void *xcp)
{
struct ipfw_table_cp *cp = xcp;
struct ipfw_tblent *te = (struct ipfw_tblent *)rn;
struct ipfw_ioc_tblent *ioc_te;
#ifdef INVARIANTS
int cnt;
#endif
KASSERT(cp->te_idx < cp->te_cnt, ("invalid table cp idx %d, cnt %d",
cp->te_idx, cp->te_cnt));
ioc_te = &cp->te[cp->te_idx];
if (te->te_nodes->rn_mask != NULL) {
memcpy(&ioc_te->netmask, te->te_nodes->rn_mask,
*te->te_nodes->rn_mask);
} else {
ioc_te->netmask.sin_len = 0;
}
memcpy(&ioc_te->key, &te->te_key, sizeof(ioc_te->key));
ioc_te->use = te->te_use;
ioc_te->last_used = te->te_lastuse;
#ifdef INVARIANTS
cnt = 1;
#endif
while ((te = te->te_sibling) != NULL) {
#ifdef INVARIANTS
++cnt;
#endif
ioc_te->use += te->te_use;
if (te->te_lastuse > ioc_te->last_used)
ioc_te->last_used = te->te_lastuse;
}
KASSERT(cnt == netisr_ncpus,
("invalid # of tblent %d, should be %d", cnt, netisr_ncpus));
cp->te_idx++;
return (0);
}
static int
ipfw_table_get(struct sockopt *sopt)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct radix_node_head *rnh;
struct ipfw_ioc_table *tbl;
struct ipfw_ioc_tblcont *cont;
struct ipfw_table_cp cp;
int cnt = 0, sz;
ASSERT_NETISR0;
if (sopt->sopt_valsize < sizeof(*tbl))
return (EINVAL);
tbl = sopt->sopt_val;
if (tbl->tableid < 0) {
struct ipfw_ioc_tbllist *list;
int i;
for (i = 0; i < ipfw_table_max; ++i) {
if (ctx->ipfw_tables[i] != NULL)
++cnt;
}
sz = __offsetof(struct ipfw_ioc_tbllist, tables[cnt]);
if (sopt->sopt_valsize < sz) {
bzero(sopt->sopt_val, sopt->sopt_valsize);
return (E2BIG);
}
list = sopt->sopt_val;
list->tablecnt = cnt;
cnt = 0;
for (i = 0; i < ipfw_table_max; ++i) {
if (ctx->ipfw_tables[i] != NULL) {
KASSERT(cnt < list->tablecnt,
("invalid idx %d, cnt %d",
cnt, list->tablecnt));
list->tables[cnt++] = i;
}
}
sopt->sopt_valsize = sz;
return (0);
} else if (tbl->tableid >= ipfw_table_max) {
return (EINVAL);
}
rnh = ctx->ipfw_tables[tbl->tableid];
if (rnh == NULL)
return (ENOENT);
rnh->rnh_walktree(rnh, ipfw_table_cntent, &cnt);
sz = __offsetof(struct ipfw_ioc_tblcont, ent[cnt]);
if (sopt->sopt_valsize < sz) {
bzero(sopt->sopt_val, sopt->sopt_valsize);
return (E2BIG);
}
cont = sopt->sopt_val;
cont->entcnt = cnt;
cp.te = cont->ent;
cp.te_idx = 0;
cp.te_cnt = cnt;
rnh->rnh_walktree(rnh, ipfw_table_cpent, &cp);
sopt->sopt_valsize = sz;
return (0);
}
static void
ipfw_table_add_dispatch(netmsg_t nmsg)
{
struct netmsg_tblent *nm = (struct netmsg_tblent *)nmsg;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct radix_node_head *rnh;
struct ipfw_tblent *te;
ASSERT_NETISR_NCPUS(mycpuid);
rnh = ctx->ipfw_tables[nm->tableid];
te = kmalloc(sizeof(*te), M_IPFW, M_WAITOK | M_ZERO);
te->te_nodes->rn_key = (char *)&te->te_key;
memcpy(&te->te_key, nm->key, sizeof(te->te_key));
if (rnh->rnh_addaddr(&te->te_key, nm->netmask, rnh, te->te_nodes)
== NULL) {
if (mycpuid == 0) {
kfree(te, M_IPFW);
netisr_replymsg(&nm->base, EEXIST);
return;
}
panic("rnh_addaddr failed");
}
if (nm->sibling != NULL)
nm->sibling->te_sibling = te;
nm->sibling = te;
netisr_forwardmsg(&nm->base, mycpuid + 1);
}
static void
ipfw_table_del_dispatch(netmsg_t nmsg)
{
struct netmsg_tblent *nm = (struct netmsg_tblent *)nmsg;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct radix_node_head *rnh;
struct radix_node *rn;
ASSERT_NETISR_NCPUS(mycpuid);
rnh = ctx->ipfw_tables[nm->tableid];
rn = rnh->rnh_deladdr(nm->key, nm->netmask, rnh);
if (rn == NULL) {
if (mycpuid == 0) {
netisr_replymsg(&nm->base, ESRCH);
return;
}
panic("rnh_deladdr failed");
}
kfree(rn, M_IPFW);
netisr_forwardmsg(&nm->base, mycpuid + 1);
}
static int
ipfw_table_alt(struct sockopt *sopt)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ipfw_ioc_tblcont *tbl;
struct ipfw_ioc_tblent *te;
struct sockaddr_in key0;
struct sockaddr *netmask = NULL, *key;
struct netmsg_tblent nm;
ASSERT_NETISR0;
if (sopt->sopt_valsize != sizeof(*tbl))
return (EINVAL);
tbl = sopt->sopt_val;
if (tbl->tableid < 0 || tbl->tableid >= ipfw_table_max)
return (EINVAL);
if (tbl->entcnt != 1)
return (EINVAL);
if (ctx->ipfw_tables[tbl->tableid] == NULL)
return (ENOENT);
te = &tbl->ent[0];
if (te->key.sin_family != AF_INET ||
te->key.sin_port != 0 ||
te->key.sin_len != sizeof(struct sockaddr_in))
return (EINVAL);
key = (struct sockaddr *)&te->key;
if (te->netmask.sin_len != 0) {
if (te->netmask.sin_port != 0 ||
te->netmask.sin_len > sizeof(struct sockaddr_in))
return (EINVAL);
netmask = (struct sockaddr *)&te->netmask;
sa_maskedcopy(key, (struct sockaddr *)&key0, netmask);
key = (struct sockaddr *)&key0;
}
if (sopt->sopt_name == IP_FW_TBL_ADD) {
netmsg_init(&nm.base, NULL, &curthread->td_msgport,
MSGF_PRIORITY, ipfw_table_add_dispatch);
} else {
netmsg_init(&nm.base, NULL, &curthread->td_msgport,
MSGF_PRIORITY, ipfw_table_del_dispatch);
}
nm.key = key;
nm.netmask = netmask;
nm.tableid = tbl->tableid;
nm.sibling = NULL;
return (netisr_domsg_global(&nm.base));
}
static int
ipfw_table_zeroent(struct radix_node *rn, void *arg __unused)
{
struct ipfw_tblent *te = (struct ipfw_tblent *)rn;
te->te_use = 0;
te->te_lastuse = 0;
return (0);
}
static void
ipfw_table_zero_dispatch(netmsg_t nmsg)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct radix_node_head *rnh;
ASSERT_NETISR_NCPUS(mycpuid);
rnh = ctx->ipfw_tables[nmsg->lmsg.u.ms_result];
rnh->rnh_walktree(rnh, ipfw_table_zeroent, NULL);
netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}
static void
ipfw_table_zeroall_dispatch(netmsg_t nmsg)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
int i;
ASSERT_NETISR_NCPUS(mycpuid);
for (i = 0; i < ipfw_table_max; ++i) {
struct radix_node_head *rnh = ctx->ipfw_tables[i];
if (rnh != NULL)
rnh->rnh_walktree(rnh, ipfw_table_zeroent, NULL);
}
netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}
static int
ipfw_table_zero(struct sockopt *sopt)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct netmsg_base nm;
struct ipfw_ioc_table *tbl;
ASSERT_NETISR0;
if (sopt->sopt_valsize != sizeof(*tbl))
return (EINVAL);
tbl = sopt->sopt_val;
if (tbl->tableid < 0) {
netmsg_init(&nm, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_table_zeroall_dispatch);
netisr_domsg_global(&nm);
return (0);
} else if (tbl->tableid >= ipfw_table_max) {
return (EINVAL);
} else if (ctx->ipfw_tables[tbl->tableid] == NULL) {
return (ENOENT);
}
netmsg_init(&nm, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_table_zero_dispatch);
nm.lmsg.u.ms_result = tbl->tableid;
netisr_domsg_global(&nm);
return (0);
}
static int
ipfw_table_killexp(struct radix_node *rn, void *xnm)
{
struct netmsg_tblexp *nm = xnm;
struct ipfw_tblent *te = (struct ipfw_tblent *)rn;
struct radix_node *ret;
if (te->te_expired) {
ret = nm->rnh->rnh_deladdr(rn->rn_key, rn->rn_mask, nm->rnh);
if (ret != rn)
panic("deleted other table entry");
kfree(ret, M_IPFW);
nm->expcnt++;
}
return (0);
}
static void
ipfw_table_expire_dispatch(netmsg_t nmsg)
{
struct netmsg_tblexp *nm = (struct netmsg_tblexp *)nmsg;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct radix_node_head *rnh;
ASSERT_NETISR_NCPUS(mycpuid);
rnh = ctx->ipfw_tables[nm->tableid];
nm->rnh = rnh;
rnh->rnh_walktree(rnh, ipfw_table_killexp, nm);
KASSERT(nm->expcnt == nm->cnt * (mycpuid + 1),
("not all expired addresses (%d) were deleted (%d)",
nm->cnt * (mycpuid + 1), nm->expcnt));
netisr_forwardmsg(&nm->base, mycpuid + 1);
}
static void
ipfw_table_expireall_dispatch(netmsg_t nmsg)
{
struct netmsg_tblexp *nm = (struct netmsg_tblexp *)nmsg;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
int i;
ASSERT_NETISR_NCPUS(mycpuid);
for (i = 0; i < ipfw_table_max; ++i) {
struct radix_node_head *rnh = ctx->ipfw_tables[i];
if (rnh == NULL)
continue;
nm->rnh = rnh;
rnh->rnh_walktree(rnh, ipfw_table_killexp, nm);
}
KASSERT(nm->expcnt == nm->cnt * (mycpuid + 1),
("not all expired addresses (%d) were deleted (%d)",
nm->cnt * (mycpuid + 1), nm->expcnt));
netisr_forwardmsg(&nm->base, mycpuid + 1);
}
static int
ipfw_table_markexp(struct radix_node *rn, void *xnm)
{
struct netmsg_tblexp *nm = xnm;
struct ipfw_tblent *te;
time_t lastuse;
te = (struct ipfw_tblent *)rn;
lastuse = te->te_lastuse;
while ((te = te->te_sibling) != NULL) {
if (te->te_lastuse > lastuse)
lastuse = te->te_lastuse;
}
if (!TIME_LEQ(lastuse + nm->expire, time_second)) {
return (0);
}
te = (struct ipfw_tblent *)rn;
te->te_expired = 1;
while ((te = te->te_sibling) != NULL)
te->te_expired = 1;
nm->cnt++;
return (0);
}
static int
ipfw_table_expire(struct sockopt *sopt)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct netmsg_tblexp nm;
struct ipfw_ioc_tblexp *tbl;
struct radix_node_head *rnh;
ASSERT_NETISR0;
if (sopt->sopt_valsize != sizeof(*tbl))
return (EINVAL);
tbl = sopt->sopt_val;
tbl->expcnt = 0;
nm.expcnt = 0;
nm.cnt = 0;
nm.expire = tbl->expire;
if (tbl->tableid < 0) {
int i;
for (i = 0; i < ipfw_table_max; ++i) {
rnh = ctx->ipfw_tables[i];
if (rnh == NULL)
continue;
rnh->rnh_walktree(rnh, ipfw_table_markexp, &nm);
}
if (nm.cnt == 0) {
return (0);
}
tbl->expcnt = nm.cnt;
netmsg_init(&nm.base, NULL, &curthread->td_msgport,
MSGF_PRIORITY, ipfw_table_expireall_dispatch);
nm.tableid = -1;
netisr_domsg_global(&nm.base);
KASSERT(nm.expcnt == nm.cnt * netisr_ncpus,
("not all expired addresses (%d) were deleted (%d)",
nm.cnt * netisr_ncpus, nm.expcnt));
return (0);
} else if (tbl->tableid >= ipfw_table_max) {
return (EINVAL);
}
rnh = ctx->ipfw_tables[tbl->tableid];
if (rnh == NULL)
return (ENOENT);
rnh->rnh_walktree(rnh, ipfw_table_markexp, &nm);
if (nm.cnt == 0) {
return (0);
}
tbl->expcnt = nm.cnt;
netmsg_init(&nm.base, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_table_expire_dispatch);
nm.tableid = tbl->tableid;
netisr_domsg_global(&nm.base);
KASSERT(nm.expcnt == nm.cnt * netisr_ncpus,
("not all expired addresses (%d) were deleted (%d)",
nm.cnt * netisr_ncpus, nm.expcnt));
return (0);
}
static void
ipfw_crossref_free_dispatch(netmsg_t nmsg)
{
struct ip_fw *rule = nmsg->lmsg.u.ms_resultp;
KKASSERT((rule->rule_flags &
(IPFW_RULE_F_CROSSREF | IPFW_RULE_F_INVALID)) ==
(IPFW_RULE_F_CROSSREF | IPFW_RULE_F_INVALID));
ipfw_free_rule(rule);
netisr_replymsg(&nmsg->base, 0);
}
static void
ipfw_crossref_reap(void)
{
struct ip_fw *rule, *prev = NULL;
ASSERT_NETISR0;
rule = ipfw_gd.ipfw_crossref_free;
while (rule != NULL) {
uint64_t inflight = 0;
int i;
for (i = 0; i < netisr_ncpus; ++i)
inflight += rule->cross_rules[i]->cross_refs;
if (inflight == 0) {
struct ip_fw *f = rule;
rule = rule->next;
if (prev != NULL)
prev->next = rule;
else
ipfw_gd.ipfw_crossref_free = rule;
for (i = 1; i < netisr_ncpus; ++i) {
struct netmsg_base nm;
netmsg_init(&nm, NULL, &curthread->td_msgport,
MSGF_PRIORITY, ipfw_crossref_free_dispatch);
nm.lmsg.u.ms_resultp = f->cross_rules[i];
netisr_domsg(&nm, i);
}
KKASSERT((f->rule_flags &
(IPFW_RULE_F_CROSSREF | IPFW_RULE_F_INVALID)) ==
(IPFW_RULE_F_CROSSREF | IPFW_RULE_F_INVALID));
ipfw_unref_rule(f);
} else {
prev = rule;
rule = rule->next;
}
}
if (ipfw_gd.ipfw_crossref_free != NULL) {
callout_reset(&ipfw_gd.ipfw_crossref_ch, hz,
ipfw_crossref_timeo, NULL);
}
}
static int
ipfw_ctl(struct sockopt *sopt)
{
int error, rulenum;
uint32_t *masks;
size_t size;
ASSERT_NETISR0;
error = 0;
switch (sopt->sopt_name) {
case IP_FW_GET:
error = ipfw_ctl_get_rules(sopt);
break;
case IP_FW_FLUSH:
ipfw_flush(0 );
break;
case IP_FW_ADD:
error = ipfw_ctl_add_rule(sopt);
break;
case IP_FW_DEL:
masks = sopt->sopt_val;
size = sopt->sopt_valsize;
if (size == sizeof(*masks)) {
error = ipfw_ctl_alter(masks[0]);
} else if (size == (2 * sizeof(*masks))) {
ipfw_ctl_set_disable(masks[0], masks[1]);
} else {
error = EINVAL;
}
break;
case IP_FW_ZERO:
case IP_FW_RESETLOG:
rulenum = 0;
if (sopt->sopt_val != 0) {
error = soopt_to_kbuf(sopt, &rulenum,
sizeof(int), sizeof(int));
if (error)
break;
}
error = ipfw_ctl_zero_entry(rulenum,
sopt->sopt_name == IP_FW_RESETLOG);
break;
case IP_FW_TBL_CREATE:
error = ipfw_table_create(sopt);
break;
case IP_FW_TBL_ADD:
case IP_FW_TBL_DEL:
error = ipfw_table_alt(sopt);
break;
case IP_FW_TBL_FLUSH:
case IP_FW_TBL_DESTROY:
error = ipfw_table_flush(sopt);
break;
case IP_FW_TBL_GET:
error = ipfw_table_get(sopt);
break;
case IP_FW_TBL_ZERO:
error = ipfw_table_zero(sopt);
break;
case IP_FW_TBL_EXPIRE:
error = ipfw_table_expire(sopt);
break;
default:
kprintf("ipfw_ctl invalid option %d\n", sopt->sopt_name);
error = EINVAL;
}
ipfw_crossref_reap();
return error;
}
static void
ipfw_keepalive_done(struct ipfw_context *ctx)
{
KASSERT(ctx->ipfw_flags & IPFW_FLAG_KEEPALIVE,
("keepalive is not in progress"));
ctx->ipfw_flags &= ~IPFW_FLAG_KEEPALIVE;
callout_reset(&ctx->ipfw_keepalive_ch, dyn_keepalive_period * hz,
ipfw_keepalive, NULL);
}
static void
ipfw_keepalive_more(struct ipfw_context *ctx)
{
struct netmsg_base *nm = &ctx->ipfw_keepalive_more;
KASSERT(ctx->ipfw_flags & IPFW_FLAG_KEEPALIVE,
("keepalive is not in progress"));
KASSERT(nm->lmsg.ms_flags & MSGF_DONE,
("keepalive more did not finish"));
netisr_sendmsg_oncpu(nm);
}
static void
ipfw_keepalive_loop(struct ipfw_context *ctx, struct ipfw_state *anchor)
{
struct ipfw_state *s;
int scanned = 0, expired = 0, kept = 0;
KASSERT(ctx->ipfw_flags & IPFW_FLAG_KEEPALIVE,
("keepalive is not in progress"));
while ((s = TAILQ_NEXT(anchor, st_link)) != NULL) {
uint32_t ack_rev, ack_fwd;
struct ipfw_flow_id id;
uint8_t send_dir;
if (scanned++ >= ipfw_state_scan_max) {
ipfw_keepalive_more(ctx);
return;
}
TAILQ_REMOVE(&ctx->ipfw_state_list, anchor, st_link);
TAILQ_INSERT_AFTER(&ctx->ipfw_state_list, s, anchor, st_link);
if (s->st_type == O_ANCHOR)
continue;
if (IPFW_STATE_ISDEAD(s)) {
ipfw_state_remove(ctx, s);
if (++expired >= ipfw_state_expire_max) {
ipfw_keepalive_more(ctx);
return;
}
continue;
}
if (s->st_proto != IPPROTO_TCP)
continue;
if ((s->st_state & IPFW_STATE_TCPSTATES) != BOTH_SYN)
continue;
if (TIME_LEQ(time_uptime + dyn_keepalive_interval,
s->st_expire))
continue;
ipfw_key_4tuple(&s->st_key, &id.src_ip, &id.src_port,
&id.dst_ip, &id.dst_port);
ack_rev = s->st_ack_rev;
ack_fwd = s->st_ack_fwd;
#define SEND_FWD 0x1
#define SEND_REV 0x2
if (IPFW_ISXLAT(s->st_type)) {
const struct ipfw_xlat *x = (const struct ipfw_xlat *)s;
if (x->xlat_dir == MATCH_FORWARD)
send_dir = SEND_FWD;
else
send_dir = SEND_REV;
} else {
send_dir = SEND_FWD | SEND_REV;
}
if (send_dir & SEND_REV)
send_pkt(&id, ack_rev - 1, ack_fwd, TH_SYN);
if (send_dir & SEND_FWD)
send_pkt(&id, ack_fwd - 1, ack_rev, 0);
#undef SEND_FWD
#undef SEND_REV
if (++kept >= ipfw_keepalive_max) {
ipfw_keepalive_more(ctx);
return;
}
}
TAILQ_REMOVE(&ctx->ipfw_state_list, anchor, st_link);
ipfw_keepalive_done(ctx);
}
static void
ipfw_keepalive_more_dispatch(netmsg_t nm)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ipfw_state *anchor;
ASSERT_NETISR_NCPUS(mycpuid);
KASSERT(ctx->ipfw_flags & IPFW_FLAG_KEEPALIVE,
("keepalive is not in progress"));
netisr_replymsg(&nm->base, 0);
anchor = &ctx->ipfw_keepalive_anch;
if (!dyn_keepalive || ctx->ipfw_state_cnt == 0) {
TAILQ_REMOVE(&ctx->ipfw_state_list, anchor, st_link);
ipfw_keepalive_done(ctx);
return;
}
ipfw_keepalive_loop(ctx, anchor);
}
static void
ipfw_keepalive_dispatch(netmsg_t nm)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ipfw_state *anchor;
ASSERT_NETISR_NCPUS(mycpuid);
KASSERT((ctx->ipfw_flags & IPFW_FLAG_KEEPALIVE) == 0,
("keepalive is in progress"));
ctx->ipfw_flags |= IPFW_FLAG_KEEPALIVE;
crit_enter();
netisr_replymsg(&nm->base, 0);
crit_exit();
if (!dyn_keepalive || ctx->ipfw_state_cnt == 0) {
ipfw_keepalive_done(ctx);
return;
}
anchor = &ctx->ipfw_keepalive_anch;
TAILQ_INSERT_HEAD(&ctx->ipfw_state_list, anchor, st_link);
ipfw_keepalive_loop(ctx, anchor);
}
static void
ipfw_keepalive(void *dummy __unused)
{
struct netmsg_base *msg;
KKASSERT(mycpuid < netisr_ncpus);
msg = &ipfw_ctx[mycpuid]->ipfw_keepalive_nm;
crit_enter();
if (msg->lmsg.ms_flags & MSGF_DONE)
netisr_sendmsg_oncpu(msg);
crit_exit();
}
static void
ipfw_ip_input_dispatch(netmsg_t nmsg)
{
struct netmsg_genpkt *nm = (struct netmsg_genpkt *)nmsg;
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct mbuf *m = nm->m;
struct ip_fw *rule = nm->arg1;
ASSERT_NETISR_NCPUS(mycpuid);
KASSERT(rule->cpuid == mycpuid,
("rule does not belong to cpu%d", mycpuid));
KASSERT(m->m_pkthdr.fw_flags & IPFW_MBUF_CONTINUE,
("mbuf does not have ipfw continue rule"));
KASSERT(ctx->ipfw_cont_rule == NULL,
("pending ipfw continue rule"));
ctx->ipfw_cont_rule = rule;
ip_input(m);
ctx->ipfw_cont_rule = NULL;
rule->cross_refs--;
}
static void
ipfw_defrag_redispatch(struct mbuf *m, int cpuid, struct ip_fw *rule)
{
struct netmsg_genpkt *nm;
KASSERT(cpuid != mycpuid, ("continue on the same cpu%d", cpuid));
rule->cross_refs++;
m->m_pkthdr.fw_flags |= IPFW_MBUF_CONTINUE;
nm = &m->m_hdr.mh_genmsg;
netmsg_init(&nm->base, NULL, &netisr_apanic_rport, 0,
ipfw_ip_input_dispatch);
nm->m = m;
nm->arg1 = rule->cross_rules[cpuid];
netisr_sendmsg(&nm->base, cpuid);
}
static void
ipfw_init_args(struct ip_fw_args *args, struct mbuf *m, struct ifnet *oif)
{
args->flags = 0;
args->rule = NULL;
args->xlat = NULL;
if (m->m_pkthdr.fw_flags & DUMMYNET_MBUF_TAGGED) {
struct m_tag *mtag;
mtag = m_tag_find(m, PACKET_TAG_DUMMYNET, NULL);
KKASSERT(mtag != NULL);
args->rule = ((struct dn_pkt *)m_tag_data(mtag))->dn_priv;
KKASSERT(args->rule != NULL);
m_tag_delete(m, mtag);
m->m_pkthdr.fw_flags &= ~DUMMYNET_MBUF_TAGGED;
} else if (m->m_pkthdr.fw_flags & IPFW_MBUF_CONTINUE) {
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
KKASSERT(ctx->ipfw_cont_rule != NULL);
args->rule = ctx->ipfw_cont_rule;
ctx->ipfw_cont_rule = NULL;
if (ctx->ipfw_cont_xlat != NULL) {
args->xlat = ctx->ipfw_cont_xlat;
ctx->ipfw_cont_xlat = NULL;
if (m->m_pkthdr.fw_flags & IPFW_MBUF_XLATINS) {
args->flags |= IP_FWARG_F_XLATINS;
m->m_pkthdr.fw_flags &= ~IPFW_MBUF_XLATINS;
}
if (m->m_pkthdr.fw_flags & IPFW_MBUF_XLATFWD) {
args->flags |= IP_FWARG_F_XLATFWD;
m->m_pkthdr.fw_flags &= ~IPFW_MBUF_XLATFWD;
}
}
KKASSERT((m->m_pkthdr.fw_flags &
(IPFW_MBUF_XLATINS | IPFW_MBUF_XLATFWD)) == 0);
args->flags |= IP_FWARG_F_CONT;
m->m_pkthdr.fw_flags &= ~IPFW_MBUF_CONTINUE;
}
args->eh = NULL;
args->oif = oif;
args->m = m;
}
static int
ipfw_check_in(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir)
{
struct ip_fw_args args;
struct mbuf *m = *m0;
int tee = 0, error = 0, ret;
ipfw_init_args(&args, m, NULL);
ret = ipfw_chk(&args);
m = args.m;
if (m == NULL) {
if (ret != IP_FW_REDISPATCH)
error = EACCES;
goto back;
}
switch (ret) {
case IP_FW_PASS:
break;
case IP_FW_DENY:
m_freem(m);
m = NULL;
error = EACCES;
break;
case IP_FW_DUMMYNET:
m = ipfw_dummynet_io(m, args.cookie, DN_TO_IP_IN, &args);
break;
case IP_FW_TEE:
tee = 1;
case IP_FW_DIVERT:
m->m_pkthdr.fw_flags &= ~BRIDGE_MBUF_TAGGED;
if (ip_divert_p != NULL) {
m = ip_divert_p(m, tee, 1);
} else {
m_freem(m);
m = NULL;
error = EACCES;
}
break;
default:
panic("unknown ipfw return value: %d", ret);
}
back:
*m0 = m;
return error;
}
static int
ipfw_check_out(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir)
{
struct ip_fw_args args;
struct mbuf *m = *m0;
int tee = 0, error = 0, ret;
ipfw_init_args(&args, m, ifp);
ret = ipfw_chk(&args);
m = args.m;
if (m == NULL) {
if (ret != IP_FW_REDISPATCH)
error = EACCES;
goto back;
}
switch (ret) {
case IP_FW_PASS:
break;
case IP_FW_DENY:
m_freem(m);
m = NULL;
error = EACCES;
break;
case IP_FW_DUMMYNET:
m = ipfw_dummynet_io(m, args.cookie, DN_TO_IP_OUT, &args);
break;
case IP_FW_TEE:
tee = 1;
case IP_FW_DIVERT:
if (ip_divert_p != NULL) {
m = ip_divert_p(m, tee, 0);
} else {
m_freem(m);
m = NULL;
error = EACCES;
}
break;
default:
panic("unknown ipfw return value: %d", ret);
}
back:
*m0 = m;
return error;
}
static void
ipfw_hook(void)
{
struct pfil_head *pfh;
ASSERT_NETISR0;
pfh = pfil_head_get(PFIL_TYPE_AF, AF_INET);
if (pfh == NULL)
return;
pfil_add_hook(ipfw_check_in, NULL, PFIL_IN, pfh);
pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT, pfh);
}
static void
ipfw_dehook(void)
{
struct pfil_head *pfh;
ASSERT_NETISR0;
pfh = pfil_head_get(PFIL_TYPE_AF, AF_INET);
if (pfh == NULL)
return;
pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN, pfh);
pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT, pfh);
}
static int
ipfw_sysctl_dyncnt(SYSCTL_HANDLER_ARGS)
{
int dyn_cnt;
dyn_cnt = ipfw_state_cntcoll();
dyn_cnt += ipfw_gd.ipfw_trkcnt_cnt;
return (sysctl_handle_int(oidp, &dyn_cnt, 0, req));
}
static int
ipfw_sysctl_statecnt(SYSCTL_HANDLER_ARGS)
{
int state_cnt;
state_cnt = ipfw_state_cntcoll();
return (sysctl_handle_int(oidp, &state_cnt, 0, req));
}
static int
ipfw_sysctl_statemax(SYSCTL_HANDLER_ARGS)
{
int state_max, error;
state_max = ipfw_state_max;
error = sysctl_handle_int(oidp, &state_max, 0, req);
if (error || req->newptr == NULL)
return (error);
if (state_max < 1)
return (EINVAL);
ipfw_state_max_set(state_max);
return (0);
}
static int
ipfw_sysctl_dynmax(SYSCTL_HANDLER_ARGS)
{
int dyn_max, error;
dyn_max = ipfw_state_max + ipfw_track_max;
error = sysctl_handle_int(oidp, &dyn_max, 0, req);
if (error || req->newptr == NULL)
return (error);
if (dyn_max < 2)
return (EINVAL);
ipfw_state_max_set(dyn_max / 2);
ipfw_track_max = dyn_max / 2;
return (0);
}
static void
ipfw_sysctl_enable_dispatch(netmsg_t nmsg)
{
int enable = nmsg->lmsg.u.ms_result;
ASSERT_NETISR0;
if (fw_enable == enable)
goto reply;
fw_enable = enable;
if (fw_enable)
ipfw_hook();
else
ipfw_dehook();
reply:
netisr_replymsg(&nmsg->base, 0);
}
static int
ipfw_sysctl_enable(SYSCTL_HANDLER_ARGS)
{
struct netmsg_base nmsg;
int enable, error;
enable = fw_enable;
error = sysctl_handle_int(oidp, &enable, 0, req);
if (error || req->newptr == NULL)
return error;
netmsg_init(&nmsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_sysctl_enable_dispatch);
nmsg.lmsg.u.ms_result = enable;
return netisr_domsg(&nmsg, 0);
}
static int
ipfw_sysctl_autoinc_step(SYSCTL_HANDLER_ARGS)
{
return sysctl_int_range(oidp, arg1, arg2, req,
IPFW_AUTOINC_STEP_MIN, IPFW_AUTOINC_STEP_MAX);
}
static int
ipfw_sysctl_scancnt(SYSCTL_HANDLER_ARGS)
{
return sysctl_int_range(oidp, arg1, arg2, req, 1, INT_MAX);
}
static int
ipfw_sysctl_stat(SYSCTL_HANDLER_ARGS)
{
u_long stat = 0;
int cpu, error;
for (cpu = 0; cpu < netisr_ncpus; ++cpu)
stat += *((u_long *)((uint8_t *)ipfw_ctx[cpu] + arg2));
error = sysctl_handle_long(oidp, &stat, 0, req);
if (error || req->newptr == NULL)
return (error);
for (cpu = 0; cpu < netisr_ncpus; ++cpu)
*((u_long *)((uint8_t *)ipfw_ctx[cpu] + arg2)) = 0;
return (0);
}
static void
ipfw_ctx_init_dispatch(netmsg_t nmsg)
{
struct netmsg_ipfw *fwmsg = (struct netmsg_ipfw *)nmsg;
struct ipfw_context *ctx;
struct ip_fw *def_rule;
ASSERT_NETISR_NCPUS(mycpuid);
ctx = kmalloc(__offsetof(struct ipfw_context,
ipfw_tables[ipfw_table_max]), M_IPFW, M_WAITOK | M_ZERO);
RB_INIT(&ctx->ipfw_state_tree);
TAILQ_INIT(&ctx->ipfw_state_list);
RB_INIT(&ctx->ipfw_track_tree);
TAILQ_INIT(&ctx->ipfw_track_list);
callout_init_mp(&ctx->ipfw_stateto_ch);
netmsg_init(&ctx->ipfw_stateexp_nm, NULL, &netisr_adone_rport,
MSGF_DROPABLE | MSGF_PRIORITY, ipfw_state_expire_dispatch);
ctx->ipfw_stateexp_anch.st_type = O_ANCHOR;
netmsg_init(&ctx->ipfw_stateexp_more, NULL, &netisr_adone_rport,
MSGF_DROPABLE, ipfw_state_expire_more_dispatch);
callout_init_mp(&ctx->ipfw_trackto_ch);
netmsg_init(&ctx->ipfw_trackexp_nm, NULL, &netisr_adone_rport,
MSGF_DROPABLE | MSGF_PRIORITY, ipfw_track_expire_dispatch);
netmsg_init(&ctx->ipfw_trackexp_more, NULL, &netisr_adone_rport,
MSGF_DROPABLE, ipfw_track_expire_more_dispatch);
callout_init_mp(&ctx->ipfw_keepalive_ch);
netmsg_init(&ctx->ipfw_keepalive_nm, NULL, &netisr_adone_rport,
MSGF_DROPABLE | MSGF_PRIORITY, ipfw_keepalive_dispatch);
ctx->ipfw_keepalive_anch.st_type = O_ANCHOR;
netmsg_init(&ctx->ipfw_keepalive_more, NULL, &netisr_adone_rport,
MSGF_DROPABLE, ipfw_keepalive_more_dispatch);
callout_init_mp(&ctx->ipfw_xlatreap_ch);
netmsg_init(&ctx->ipfw_xlatreap_nm, NULL, &netisr_adone_rport,
MSGF_DROPABLE | MSGF_PRIORITY, ipfw_xlat_reap_dispatch);
TAILQ_INIT(&ctx->ipfw_xlatreap);
ipfw_ctx[mycpuid] = ctx;
def_rule = kmalloc(sizeof(*def_rule), M_IPFW, M_WAITOK | M_ZERO);
def_rule->act_ofs = 0;
def_rule->rulenum = IPFW_DEFAULT_RULE;
def_rule->cmd_len = 1;
def_rule->set = IPFW_DEFAULT_SET;
def_rule->cmd[0].len = 1;
#ifdef IPFIREWALL_DEFAULT_TO_ACCEPT
def_rule->cmd[0].opcode = O_ACCEPT;
#else
if (filters_default_to_accept)
def_rule->cmd[0].opcode = O_ACCEPT;
else
def_rule->cmd[0].opcode = O_DENY;
#endif
def_rule->refcnt = 1;
def_rule->cpuid = mycpuid;
ctx->ipfw_default_rule = def_rule;
ctx->ipfw_layer3_chain = def_rule;
ipfw_link_sibling(fwmsg, def_rule);
if (mycpuid == 0)
ipfw_inc_static_count(def_rule);
netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}
static void
ipfw_crossref_reap_dispatch(netmsg_t nmsg)
{
crit_enter();
netisr_replymsg(&nmsg->base, 0);
crit_exit();
ipfw_crossref_reap();
}
static void
ipfw_crossref_timeo(void *dummy __unused)
{
struct netmsg_base *msg = &ipfw_gd.ipfw_crossref_nm;
KKASSERT(mycpuid == 0);
crit_enter();
if (msg->lmsg.ms_flags & MSGF_DONE)
netisr_sendmsg_oncpu(msg);
crit_exit();
}
static void
ipfw_ifaddr_dispatch(netmsg_t nmsg)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
struct ifnet *ifp = nmsg->lmsg.u.ms_resultp;
struct ip_fw *f;
ASSERT_NETISR_NCPUS(mycpuid);
for (f = ctx->ipfw_layer3_chain; f != NULL; f = f->next) {
int l, cmdlen;
ipfw_insn *cmd;
if ((f->rule_flags & IPFW_RULE_F_DYNIFADDR) == 0)
continue;
for (l = f->cmd_len, cmd = f->cmd; l > 0;
l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
if (cmd->opcode == O_IP_SRC_IFIP ||
cmd->opcode == O_IP_DST_IFIP) {
if (strncmp(ifp->if_xname,
((ipfw_insn_ifip *)cmd)->ifname,
IFNAMSIZ) == 0)
cmd->arg1 &= ~IPFW_IFIP_VALID;
}
}
}
netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}
static void
ipfw_ifaddr(void *arg __unused, struct ifnet *ifp,
enum ifaddr_event event __unused, struct ifaddr *ifa __unused)
{
struct netmsg_base nm;
netmsg_init(&nm, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_ifaddr_dispatch);
nm.lmsg.u.ms_resultp = ifp;
netisr_domsg_global(&nm);
}
static void
ipfw_init_dispatch(netmsg_t nmsg)
{
struct netmsg_ipfw fwmsg;
int error = 0, cpu;
ASSERT_NETISR0;
if (IPFW_LOADED) {
kprintf("IP firewall already loaded\n");
error = EEXIST;
goto reply;
}
if (ipfw_table_max > UINT16_MAX || ipfw_table_max <= 0)
ipfw_table_max = UINT16_MAX;
RB_INIT(&ipfw_gd.ipfw_trkcnt_tree);
IPFW_TRKCNT_TOKINIT;
callout_init_mp(&ipfw_gd.ipfw_crossref_ch);
netmsg_init(&ipfw_gd.ipfw_crossref_nm, NULL, &netisr_adone_rport,
MSGF_PRIORITY | MSGF_DROPABLE, ipfw_crossref_reap_dispatch);
ipfw_state_max_set(ipfw_state_max);
ipfw_state_headroom = 8 * netisr_ncpus;
bzero(&fwmsg, sizeof(fwmsg));
netmsg_init(&fwmsg.base, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_ctx_init_dispatch);
netisr_domsg_global(&fwmsg.base);
ip_fw_chk_ptr = ipfw_chk;
ip_fw_ctl_ptr = ipfw_ctl;
ip_fw_dn_io_ptr = ipfw_dummynet_io;
kprintf("ipfw2 initialized, default to %s, logging ",
ipfw_ctx[mycpuid]->ipfw_default_rule->cmd[0].opcode ==
O_ACCEPT ? "accept" : "deny");
#ifdef IPFIREWALL_VERBOSE
fw_verbose = 1;
#endif
#ifdef IPFIREWALL_VERBOSE_LIMIT
verbose_limit = IPFIREWALL_VERBOSE_LIMIT;
#endif
if (fw_verbose == 0) {
kprintf("disabled\n");
} else if (verbose_limit == 0) {
kprintf("unlimited\n");
} else {
kprintf("limited to %d packets/entry by default\n",
verbose_limit);
}
ip_fw_loaded = 1;
for (cpu = 0; cpu < netisr_ncpus; ++cpu) {
callout_reset_bycpu(&ipfw_ctx[cpu]->ipfw_stateto_ch, hz,
ipfw_state_expire_ipifunc, NULL, cpu);
callout_reset_bycpu(&ipfw_ctx[cpu]->ipfw_trackto_ch, hz,
ipfw_track_expire_ipifunc, NULL, cpu);
callout_reset_bycpu(&ipfw_ctx[cpu]->ipfw_keepalive_ch, hz,
ipfw_keepalive, NULL, cpu);
}
if (fw_enable)
ipfw_hook();
ipfw_ifaddr_event = EVENTHANDLER_REGISTER(ifaddr_event, ipfw_ifaddr,
NULL, EVENTHANDLER_PRI_ANY);
if (ipfw_ifaddr_event == NULL)
kprintf("ipfw: ifaddr_event register failed\n");
reply:
netisr_replymsg(&nmsg->base, error);
}
static int
ipfw_init(void)
{
struct netmsg_base smsg;
netmsg_init(&smsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_init_dispatch);
return netisr_domsg(&smsg, 0);
}
#ifdef KLD_MODULE
static void
ipfw_ctx_fini_dispatch(netmsg_t nmsg)
{
struct ipfw_context *ctx = ipfw_ctx[mycpuid];
ASSERT_NETISR_NCPUS(mycpuid);
callout_cancel(&ctx->ipfw_stateto_ch);
callout_cancel(&ctx->ipfw_trackto_ch);
callout_cancel(&ctx->ipfw_keepalive_ch);
callout_cancel(&ctx->ipfw_xlatreap_ch);
crit_enter();
netisr_dropmsg(&ctx->ipfw_stateexp_more);
netisr_dropmsg(&ctx->ipfw_stateexp_nm);
netisr_dropmsg(&ctx->ipfw_trackexp_more);
netisr_dropmsg(&ctx->ipfw_trackexp_nm);
netisr_dropmsg(&ctx->ipfw_keepalive_more);
netisr_dropmsg(&ctx->ipfw_keepalive_nm);
netisr_dropmsg(&ctx->ipfw_xlatreap_nm);
crit_exit();
ipfw_table_flushall_oncpu(ctx, 1);
netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}
static void
ipfw_fini_dispatch(netmsg_t nmsg)
{
struct netmsg_base nm;
int error = 0, cpu;
ASSERT_NETISR0;
ipfw_crossref_reap();
if (ipfw_gd.ipfw_refcnt != 0) {
error = EBUSY;
goto reply;
}
ip_fw_loaded = 0;
ipfw_dehook();
lwkt_synchronize_ipiqs("ipfwfini");
netmsg_init(&nm, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_ctx_fini_dispatch);
netisr_domsg_global(&nm);
callout_cancel(&ipfw_gd.ipfw_crossref_ch);
crit_enter();
netisr_dropmsg(&ipfw_gd.ipfw_crossref_nm);
crit_exit();
if (ipfw_ifaddr_event != NULL)
EVENTHANDLER_DEREGISTER(ifaddr_event, ipfw_ifaddr_event);
ip_fw_chk_ptr = NULL;
ip_fw_ctl_ptr = NULL;
ip_fw_dn_io_ptr = NULL;
ipfw_flush(1 );
for (cpu = 0; cpu < netisr_ncpus; ++cpu)
kfree(ipfw_ctx[cpu], M_IPFW);
kprintf("IP firewall unloaded\n");
reply:
netisr_replymsg(&nmsg->base, error);
}
static void
ipfw_fflush_dispatch(netmsg_t nmsg)
{
ipfw_flush(0 );
ipfw_crossref_reap();
netisr_replymsg(&nmsg->base, 0);
}
static int
ipfw_fini(void)
{
struct netmsg_base smsg;
int i = 0;
for (;;) {
netmsg_init(&smsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_fflush_dispatch);
netisr_domsg(&smsg, 0);
if (ipfw_gd.ipfw_refcnt == 0)
break;
kprintf("ipfw: flush pending %d\n", ++i);
tsleep(&smsg, 0, "ipfwff", (3 * hz) / 2);
}
netmsg_init(&smsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
ipfw_fini_dispatch);
return netisr_domsg(&smsg, 0);
}
#endif
static int
ipfw_modevent(module_t mod, int type, void *unused)
{
int err = 0;
switch (type) {
case MOD_LOAD:
err = ipfw_init();
break;
case MOD_UNLOAD:
#ifndef KLD_MODULE
kprintf("ipfw statically compiled, cannot unload\n");
err = EBUSY;
#else
err = ipfw_fini();
#endif
break;
default:
break;
}
return err;
}
static moduledata_t ipfwmod = {
"ipfw",
ipfw_modevent,
0
};
DECLARE_MODULE(ipfw, ipfwmod, SI_SUB_PROTO_END, SI_ORDER_ANY);
MODULE_VERSION(ipfw, 1);