#include <sys/cdefs.h>
#include "opt_ipfw.h"
#include "opt_ipdivert.h"
#include "opt_inet.h"
#ifndef INET
#error "IPFIREWALL requires INET"
#endif
#include "opt_inet6.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/condvar.h>
#include <sys/counter.h>
#include <sys/eventhandler.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/jail.h>
#include <sys/module.h>
#include <sys/priv.h>
#include <sys/proc.h>
#include <sys/rwlock.h>
#include <sys/rmlock.h>
#include <sys/sdt.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <sys/ucred.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_private.h>
#include <net/route.h>
#include <net/route/nhop.h>
#include <net/pfil.h>
#include <net/vnet.h>
#include <net/if_gif.h>
#include <net/if_pfsync.h>
#include <netpfil/pf/pf_mtag.h>
#include <netinet/in.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/ip_fw.h>
#include <netinet/ip_carp.h>
#include <netinet/pim.h>
#include <netinet/tcp_var.h>
#include <netinet/udp.h>
#include <netinet/udp_var.h>
#include <netinet/sctp.h>
#include <netinet/sctp_crc32.h>
#include <netinet/sctp_header.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <netinet/in_fib.h>
#ifdef INET6
#include <netinet6/in6_fib.h>
#include <netinet6/in6_pcb.h>
#include <netinet6/scope6_var.h>
#include <netinet6/ip6_var.h>
#endif
#include <net/if_gre.h>
#include <netpfil/ipfw/ip_fw_private.h>
#include <machine/in_cksum.h>
#ifdef MAC
#include <security/mac/mac_framework.h>
#endif
#define IPFW_PROBE(probe, arg0, arg1, arg2, arg3, arg4, arg5) \
SDT_PROBE6(ipfw, , , probe, arg0, arg1, arg2, arg3, arg4, arg5)
SDT_PROVIDER_DEFINE(ipfw);
SDT_PROBE_DEFINE6(ipfw, , , rule__matched,
"int",
"int",
"void *",
"void *",
"struct ip_fw_args *",
"struct ip_fw *" );
VNET_DEFINE_STATIC(int, fw_deny_unknown_exthdrs);
#define V_fw_deny_unknown_exthdrs VNET(fw_deny_unknown_exthdrs)
VNET_DEFINE_STATIC(int, fw_permit_single_frag6) = 1;
#define V_fw_permit_single_frag6 VNET(fw_permit_single_frag6)
#ifdef IPFIREWALL_DEFAULT_TO_ACCEPT
static int default_to_accept = 1;
#else
static int default_to_accept;
#endif
VNET_DEFINE(int, autoinc_step);
VNET_DEFINE(int, fw_one_pass) = 1;
VNET_DEFINE(unsigned int, fw_tables_max);
VNET_DEFINE(unsigned int, fw_tables_sets) = 0;
static unsigned int default_fw_tables = IPFW_TABLES_DEFAULT;
#ifndef IPFIREWALL_LINEAR_SKIPTO
VNET_DEFINE(int, skipto_cache) = 0;
#else
VNET_DEFINE(int, skipto_cache) = 1;
#endif
static uint32_t jump(struct ip_fw_chain *chain, struct ip_fw *f,
uint32_t num, int tablearg, bool jump_backwards);
VNET_DEFINE(u_int32_t, set_disable);
#define V_set_disable VNET(set_disable)
VNET_DEFINE(int, fw_verbose);
VNET_DEFINE(u_int64_t, norule_counter);
VNET_DEFINE(int, verbose_limit);
VNET_DEFINE(struct ip_fw_chain, layer3_chain);
VNET_DEFINE(int, ipfw_vnet_ready) = 0;
VNET_DEFINE(int, ipfw_nat_ready) = 0;
ipfw_nat_t *ipfw_nat_ptr = NULL;
struct cfg_nat *(*lookup_nat_ptr)(struct nat_list *, int);
ipfw_nat_cfg_t *ipfw_nat_cfg_ptr;
ipfw_nat_cfg_t *ipfw_nat_del_ptr;
ipfw_nat_cfg_t *ipfw_nat_get_cfg_ptr;
ipfw_nat_cfg_t *ipfw_nat_get_log_ptr;
#ifdef SYSCTL_NODE
uint32_t dummy_def = IPFW_DEFAULT_RULE;
static int sysctl_ipfw_table_num(SYSCTL_HANDLER_ARGS);
static int sysctl_ipfw_tables_sets(SYSCTL_HANDLER_ARGS);
SYSBEGIN(f3)
SYSCTL_NODE(_net_inet_ip, OID_AUTO, fw, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
"Firewall");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, one_pass,
CTLFLAG_VNET | CTLFLAG_RW | CTLFLAG_SECURE3, &VNET_NAME(fw_one_pass), 0,
"Only do a single pass through ipfw when using dummynet(4), ipfw_nat or other divert(4)-like interfaces");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, autoinc_step,
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(autoinc_step), 0,
"Rule number auto-increment step");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, verbose,
CTLFLAG_VNET | CTLFLAG_RW | CTLFLAG_SECURE3, &VNET_NAME(fw_verbose), 0,
"Log matches to ipfw rules");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, verbose_limit,
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(verbose_limit), 0,
"Set upper limit of matches of ipfw rules logged");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, skipto_cache,
CTLFLAG_VNET | CTLFLAG_RD, &VNET_NAME(skipto_cache), 0,
"Status of linear skipto cache: 1 - enabled, 0 - disabled.");
SYSCTL_UINT(_net_inet_ip_fw, OID_AUTO, default_rule, CTLFLAG_RD,
&dummy_def, 0,
"The default/max possible rule number.");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, tables_max,
CTLFLAG_VNET | CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE,
0, 0, sysctl_ipfw_table_num, "IU",
"Maximum number of concurrently used tables");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, tables_sets,
CTLFLAG_VNET | CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE,
0, 0, sysctl_ipfw_tables_sets, "IU",
"Use per-set namespace for tables");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, default_to_accept, CTLFLAG_RDTUN,
&default_to_accept, 0,
"Make the default rule accept all packets.");
TUNABLE_INT("net.inet.ip.fw.tables_max", (int *)&default_fw_tables);
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, static_count,
CTLFLAG_VNET | CTLFLAG_RD, &VNET_NAME(layer3_chain.n_rules), 0,
"Number of static rules");
#ifdef INET6
SYSCTL_DECL(_net_inet6_ip6);
SYSCTL_NODE(_net_inet6_ip6, OID_AUTO, fw, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
"Firewall");
SYSCTL_INT(_net_inet6_ip6_fw, OID_AUTO, deny_unknown_exthdrs,
CTLFLAG_VNET | CTLFLAG_RW | CTLFLAG_SECURE,
&VNET_NAME(fw_deny_unknown_exthdrs), 0,
"Deny packets with unknown IPv6 Extension Headers");
SYSCTL_INT(_net_inet6_ip6_fw, OID_AUTO, permit_single_frag6,
CTLFLAG_VNET | CTLFLAG_RW | CTLFLAG_SECURE,
&VNET_NAME(fw_permit_single_frag6), 0,
"Permit single packet IPv6 fragments");
#endif
SYSEND
#endif
#define L3HDR(T, ip) ((T *)((u_int32_t *)(ip) + (ip)->ip_hl))
#define TCP(p) ((struct tcphdr *)(p))
#define SCTP(p) ((struct sctphdr *)(p))
#define UDP(p) ((struct udphdr *)(p))
#define ICMP(p) ((struct icmphdr *)(p))
#define ICMP6(p) ((struct icmp6_hdr *)(p))
static __inline int
icmptype_match(struct icmphdr *icmp, ipfw_insn_u32 *cmd)
{
int type = icmp->icmp_type;
return (type <= ICMP_MAXTYPE && (cmd->d[0] & (1<<type)) );
}
#define TT ( (1 << ICMP_ECHO) | (1 << ICMP_ROUTERSOLICIT) | \
(1 << ICMP_TSTAMP) | (1 << ICMP_IREQ) | (1 << ICMP_MASKREQ) )
static int
is_icmp_query(struct icmphdr *icmp)
{
int type = icmp->icmp_type;
return (type <= ICMP_MAXTYPE && (TT & (1<<type)) );
}
#undef TT
static int
flags_match(ipfw_insn *cmd, u_int8_t bits)
{
u_char want_clear;
bits = ~bits;
if ( ((cmd->arg1 & 0xff) & bits) != 0)
return 0;
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) {
default:
break;
case IPOPT_LSRR:
bits |= IP_FW_IPOPT_LSRR;
break;
case IPOPT_SSRR:
bits |= IP_FW_IPOPT_SSRR;
break;
case IPOPT_RR:
bits |= IP_FW_IPOPT_RR;
break;
case IPOPT_TS:
bits |= IP_FW_IPOPT_TS;
break;
}
}
return (flags_match(cmd, bits));
}
static int
tcpopts_parse(const struct tcphdr *tcp, uint16_t *mss)
{
const u_char *cp = (const u_char *)(tcp + 1);
int optlen, bits = 0;
int cnt = (tcp->th_off << 2) - sizeof(struct tcphdr);
for (; cnt > 0; cnt -= optlen, cp += optlen) {
int opt = cp[0];
if (opt == TCPOPT_EOL)
break;
if (opt == TCPOPT_NOP)
optlen = 1;
else {
if (cnt < 2)
break;
optlen = cp[1];
if (optlen < 2 || optlen > cnt)
break;
}
switch (opt) {
default:
break;
case TCPOPT_MAXSEG:
if (optlen != TCPOLEN_MAXSEG)
break;
bits |= IP_FW_TCPOPT_MSS;
if (mss != NULL)
*mss = be16dec(cp + 2);
break;
case TCPOPT_WINDOW:
if (optlen == TCPOLEN_WINDOW)
bits |= IP_FW_TCPOPT_WINDOW;
break;
case TCPOPT_SACK_PERMITTED:
if (optlen == TCPOLEN_SACK_PERMITTED)
bits |= IP_FW_TCPOPT_SACK;
break;
case TCPOPT_SACK:
if (optlen > 2 && (optlen - 2) % TCPOLEN_SACK == 0)
bits |= IP_FW_TCPOPT_SACK;
break;
case TCPOPT_TIMESTAMP:
if (optlen == TCPOLEN_TIMESTAMP)
bits |= IP_FW_TCPOPT_TS;
break;
}
}
return (bits);
}
static int
tcpopts_match(struct tcphdr *tcp, ipfw_insn *cmd)
{
return (flags_match(cmd, tcpopts_parse(tcp, NULL)));
}
static int
iface_match(struct ifnet *ifp, ipfw_insn_if *cmd, struct ip_fw_chain *chain,
uint32_t *tablearg)
{
if (ifp == NULL)
return (0);
if (cmd->name[0] != '\0') {
if (cmd->name[0] == '\1')
return ipfw_lookup_table(chain, cmd->p.kidx, 0,
&ifp->if_index, tablearg);
if (cmd->p.glob) {
if (fnmatch(cmd->name, ifp->if_xname, 0) == 0)
return(1);
} else {
if (strncmp(ifp->if_xname, cmd->name, IFNAMSIZ) == 0)
return(1);
}
} else {
#if !defined(USERSPACE) && defined(__FreeBSD__)
struct ifaddr *ia;
NET_EPOCH_ASSERT();
CK_STAILQ_FOREACH(ia, &ifp->if_addrhead, ifa_link) {
if (ia->ifa_addr->sa_family != AF_INET)
continue;
if (cmd->p.ip.s_addr == ((struct sockaddr_in *)
(ia->ifa_addr))->sin_addr.s_addr)
return (1);
}
#endif
}
return(0);
}
static int
verify_path(struct in_addr src, struct ifnet *ifp, u_int fib)
{
#if defined(USERSPACE) || !defined(__FreeBSD__)
return 0;
#else
struct nhop_object *nh;
nh = fib4_lookup(fib, src, 0, NHR_NONE, 0);
if (nh == NULL)
return (0);
if (ifp != NULL && ifp != nh->nh_aifp)
return (0);
if (ifp == NULL && (nh->nh_flags & NHF_DEFAULT) != 0)
return (0);
if (ifp == NULL && (nh->nh_flags & (NHF_REJECT|NHF_BLACKHOLE)) != 0)
return (0);
return 1;
#endif
}
static struct mbuf *
ipfw_send_abort(struct mbuf *replyto, struct ipfw_flow_id *id, u_int32_t vtag,
int reflected)
{
struct mbuf *m;
struct ip *ip;
#ifdef INET6
struct ip6_hdr *ip6;
#endif
struct sctphdr *sctp;
struct sctp_chunkhdr *chunk;
u_int16_t hlen, plen, tlen;
MGETHDR(m, M_NOWAIT, MT_DATA);
if (m == NULL)
return (NULL);
M_SETFIB(m, id->fib);
#ifdef MAC
if (replyto != NULL)
mac_netinet_firewall_reply(replyto, m);
else
mac_netinet_firewall_send(m);
#else
(void)replyto;
#endif
switch (id->addr_type) {
case 4:
hlen = sizeof(struct ip);
break;
#ifdef INET6
case 6:
hlen = sizeof(struct ip6_hdr);
break;
#endif
default:
FREE_PKT(m);
return (NULL);
}
plen = sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr);
tlen = hlen + plen;
m->m_data += max_linkhdr;
m->m_flags |= M_SKIP_FIREWALL;
m->m_pkthdr.len = m->m_len = tlen;
m->m_pkthdr.rcvif = NULL;
bzero(m->m_data, tlen);
switch (id->addr_type) {
case 4:
ip = mtod(m, struct ip *);
ip->ip_v = 4;
ip->ip_hl = sizeof(struct ip) >> 2;
ip->ip_tos = IPTOS_LOWDELAY;
ip->ip_len = htons(tlen);
ip->ip_id = htons(0);
ip->ip_off = htons(0);
ip->ip_ttl = V_ip_defttl;
ip->ip_p = IPPROTO_SCTP;
ip->ip_sum = 0;
ip->ip_src.s_addr = htonl(id->dst_ip);
ip->ip_dst.s_addr = htonl(id->src_ip);
sctp = (struct sctphdr *)(ip + 1);
break;
#ifdef INET6
case 6:
ip6 = mtod(m, struct ip6_hdr *);
ip6->ip6_vfc = IPV6_VERSION;
ip6->ip6_plen = htons(plen);
ip6->ip6_nxt = IPPROTO_SCTP;
ip6->ip6_hlim = IPV6_DEFHLIM;
ip6->ip6_src = id->dst_ip6;
ip6->ip6_dst = id->src_ip6;
sctp = (struct sctphdr *)(ip6 + 1);
break;
#endif
}
sctp->src_port = htons(id->dst_port);
sctp->dest_port = htons(id->src_port);
sctp->v_tag = htonl(vtag);
sctp->checksum = htonl(0);
chunk = (struct sctp_chunkhdr *)(sctp + 1);
chunk->chunk_type = SCTP_ABORT_ASSOCIATION;
chunk->chunk_flags = 0;
if (reflected != 0) {
chunk->chunk_flags |= SCTP_HAD_NO_TCB;
}
chunk->chunk_length = htons(sizeof(struct sctp_chunkhdr));
sctp->checksum = sctp_calculate_cksum(m, hlen);
return (m);
}
struct mbuf *
ipfw_send_pkt(struct mbuf *replyto, struct ipfw_flow_id *id, u_int32_t seq,
u_int32_t ack, int flags)
{
struct mbuf *m = NULL;
struct ip *h = NULL;
#ifdef INET6
struct ip6_hdr *h6 = NULL;
#endif
struct tcphdr *th = NULL;
int len, dir;
MGETHDR(m, M_NOWAIT, MT_DATA);
if (m == NULL)
return (NULL);
M_SETFIB(m, id->fib);
#ifdef MAC
if (replyto != NULL)
mac_netinet_firewall_reply(replyto, m);
else
mac_netinet_firewall_send(m);
#else
(void)replyto;
#endif
switch (id->addr_type) {
case 4:
len = sizeof(struct ip) + sizeof(struct tcphdr);
break;
#ifdef INET6
case 6:
len = sizeof(struct ip6_hdr) + sizeof(struct tcphdr);
break;
#endif
default:
FREE_PKT(m);
return (NULL);
}
dir = ((flags & (TH_SYN | TH_RST)) == TH_SYN);
m->m_data += max_linkhdr;
m->m_flags |= M_SKIP_FIREWALL;
m->m_pkthdr.len = m->m_len = len;
m->m_pkthdr.rcvif = NULL;
bzero(m->m_data, len);
switch (id->addr_type) {
case 4:
h = mtod(m, struct ip *);
h->ip_p = IPPROTO_TCP;
h->ip_len = htons(sizeof(struct tcphdr));
if (dir) {
h->ip_src.s_addr = htonl(id->src_ip);
h->ip_dst.s_addr = htonl(id->dst_ip);
} else {
h->ip_src.s_addr = htonl(id->dst_ip);
h->ip_dst.s_addr = htonl(id->src_ip);
}
th = (struct tcphdr *)(h + 1);
break;
#ifdef INET6
case 6:
h6 = mtod(m, struct ip6_hdr *);
h6->ip6_nxt = IPPROTO_TCP;
h6->ip6_plen = htons(sizeof(struct tcphdr));
if (dir) {
h6->ip6_src = id->src_ip6;
h6->ip6_dst = id->dst_ip6;
} else {
h6->ip6_src = id->dst_ip6;
h6->ip6_dst = id->src_ip6;
}
th = (struct tcphdr *)(h6 + 1);
break;
#endif
}
if (dir) {
th->th_sport = htons(id->src_port);
th->th_dport = htons(id->dst_port);
} else {
th->th_sport = htons(id->dst_port);
th->th_dport = htons(id->src_port);
}
th->th_off = sizeof(struct tcphdr) >> 2;
if (flags & TH_RST) {
if (flags & TH_ACK) {
th->th_seq = htonl(ack);
tcp_set_flags(th, TH_RST);
} else {
if (flags & TH_SYN)
seq++;
th->th_ack = htonl(seq);
tcp_set_flags(th, TH_RST | TH_ACK);
}
} else {
th->th_seq = htonl(seq);
th->th_ack = htonl(ack);
tcp_set_flags(th, TH_ACK);
}
switch (id->addr_type) {
case 4:
th->th_sum = in_cksum(m, len);
h->ip_v = 4;
h->ip_hl = sizeof(*h) >> 2;
h->ip_tos = IPTOS_LOWDELAY;
h->ip_off = htons(0);
h->ip_len = htons(len);
h->ip_ttl = V_ip_defttl;
h->ip_sum = 0;
break;
#ifdef INET6
case 6:
th->th_sum = in6_cksum(m, IPPROTO_TCP, sizeof(*h6),
sizeof(struct tcphdr));
h6->ip6_vfc |= IPV6_VERSION;
h6->ip6_hlim = IPV6_DEFHLIM;
break;
#endif
}
return (m);
}
#ifdef INET6
static __inline int
icmp6type_match(int type, ipfw_insn_u32 *cmd)
{
return (type <= ICMP6_MAXTYPE && (cmd->d[type/32] & (1<<(type%32)) ) );
}
static int
flow6id_match(int curr_flow, ipfw_insn_u32 *cmd)
{
int i;
for (i=0; i <= cmd->o.arg1; ++i)
if (curr_flow == cmd->d[i])
return 1;
return 0;
}
static const struct in6_addr lla_mask = {{{
0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
}}};
static int
ipfw_localip6(struct in6_addr *in6)
{
struct rm_priotracker in6_ifa_tracker;
struct in6_ifaddr *ia;
if (IN6_IS_ADDR_MULTICAST(in6))
return (0);
if (!IN6_IS_ADDR_LINKLOCAL(in6))
return (in6_localip(in6));
IN6_IFADDR_RLOCK(&in6_ifa_tracker);
CK_STAILQ_FOREACH(ia, &V_in6_ifaddrhead, ia_link) {
if (!IN6_IS_ADDR_LINKLOCAL(&ia->ia_addr.sin6_addr))
continue;
if (IN6_ARE_MASKED_ADDR_EQUAL(&ia->ia_addr.sin6_addr,
in6, &lla_mask)) {
IN6_IFADDR_RUNLOCK(&in6_ifa_tracker);
return (1);
}
}
IN6_IFADDR_RUNLOCK(&in6_ifa_tracker);
return (0);
}
static int
verify_path6(struct in6_addr *src, struct ifnet *ifp, u_int fib)
{
struct nhop_object *nh;
if (IN6_IS_SCOPE_LINKLOCAL(src))
return (1);
nh = fib6_lookup(fib, src, 0, NHR_NONE, 0);
if (nh == NULL)
return (0);
if (ifp != NULL && ifp != nh->nh_aifp)
return (0);
if (ifp == NULL && (nh->nh_flags & NHF_DEFAULT) != 0)
return (0);
if (ifp == NULL && (nh->nh_flags & (NHF_REJECT|NHF_BLACKHOLE)) != 0)
return (0);
return 1;
}
static int
is_icmp6_query(int icmp6_type)
{
if ((icmp6_type <= ICMP6_MAXTYPE) &&
(icmp6_type == ICMP6_ECHO_REQUEST ||
icmp6_type == ICMP6_MEMBERSHIP_QUERY ||
icmp6_type == ICMP6_WRUREQUEST ||
icmp6_type == ICMP6_FQDN_QUERY ||
icmp6_type == ICMP6_NI_QUERY))
return (1);
return (0);
}
static int
map_icmp_unreach(int code)
{
switch (code) {
case ICMP_UNREACH_NET:
case ICMP_UNREACH_HOST:
case ICMP_UNREACH_SRCFAIL:
case ICMP_UNREACH_NET_UNKNOWN:
case ICMP_UNREACH_HOST_UNKNOWN:
case ICMP_UNREACH_TOSNET:
case ICMP_UNREACH_TOSHOST:
return (ICMP6_DST_UNREACH_NOROUTE);
case ICMP_UNREACH_PORT:
return (ICMP6_DST_UNREACH_NOPORT);
default:
return (ICMP6_DST_UNREACH_ADMIN);
}
}
static void
send_reject6(struct ip_fw_args *args, int code, u_int hlen,
const struct ip6_hdr *ip6)
{
struct mbuf *m;
m = args->m;
if (code == ICMP6_UNREACH_RST && args->f_id.proto == IPPROTO_TCP) {
const struct tcphdr * tcp;
tcp = (const struct tcphdr *)((const char *)ip6 + hlen);
if ((tcp_get_flags(tcp) & TH_RST) == 0) {
struct mbuf *m0;
m0 = ipfw_send_pkt(args->m, &(args->f_id),
ntohl(tcp->th_seq), ntohl(tcp->th_ack),
tcp_get_flags(tcp) | TH_RST);
if (m0 != NULL)
ip6_output(m0, NULL, NULL, 0, NULL, NULL,
NULL);
}
FREE_PKT(m);
} else if (code == ICMP6_UNREACH_ABORT &&
args->f_id.proto == IPPROTO_SCTP) {
struct mbuf *m0;
const struct sctphdr *sctp;
u_int32_t v_tag;
int reflected;
sctp = (const struct sctphdr *)((const char *)ip6 + hlen);
reflected = 1;
v_tag = ntohl(sctp->v_tag);
if (m->m_len >= hlen + sizeof(struct sctphdr) +
sizeof(struct sctp_chunkhdr)) {
const struct sctp_chunkhdr *chunk;
chunk = (const struct sctp_chunkhdr *)(sctp + 1);
switch (chunk->chunk_type) {
case SCTP_INITIATION:
if (v_tag != 0) {
v_tag = 0;
break;
}
if (m->m_pkthdr.len >
hlen + sizeof(struct sctphdr) +
ntohs(chunk->chunk_length) + 3) {
break;
}
if ((m->m_len >= hlen + sizeof(struct sctphdr) +
sizeof(struct sctp_chunkhdr) +
offsetof(struct sctp_init, a_rwnd))) {
const struct sctp_init *init;
init = (const struct sctp_init *)(chunk + 1);
v_tag = ntohl(init->initiate_tag);
reflected = 0;
}
break;
case SCTP_ABORT_ASSOCIATION:
v_tag = 0;
break;
}
}
if (v_tag == 0) {
m0 = NULL;
} else {
m0 = ipfw_send_abort(args->m, &(args->f_id), v_tag,
reflected);
}
if (m0 != NULL)
ip6_output(m0, NULL, NULL, 0, NULL, NULL, NULL);
FREE_PKT(m);
} else if (code != ICMP6_UNREACH_RST && code != ICMP6_UNREACH_ABORT) {
#if 0
if (args->L3offset)
m_adj(m, args->L3offset);
#endif
icmp6_error(m, ICMP6_DST_UNREACH, code, 0);
} else
FREE_PKT(m);
args->m = NULL;
}
#endif
static void
send_reject(struct ip_fw_args *args, int code, uint16_t mtu, int iplen,
const struct ip *ip)
{
#if 0
* The mbuf will however be thrown away so we can adjust it.
* Remember we did an m_pullup on it already so we
* can make some assumptions about contiguousness.
*/
if (args->L3offset)
m_adj(m, args->L3offset);
#endif
if (code != ICMP_REJECT_RST && code != ICMP_REJECT_ABORT) {
icmp_error(args->m, ICMP_UNREACH, code, 0L, mtu);
} else if (code == ICMP_REJECT_RST && args->f_id.proto == IPPROTO_TCP) {
struct tcphdr *const tcp =
L3HDR(struct tcphdr, mtod(args->m, struct ip *));
if ( (tcp_get_flags(tcp) & TH_RST) == 0) {
struct mbuf *m;
m = ipfw_send_pkt(args->m, &(args->f_id),
ntohl(tcp->th_seq), ntohl(tcp->th_ack),
tcp_get_flags(tcp) | TH_RST);
if (m != NULL)
ip_output(m, NULL, NULL, 0, NULL, NULL);
}
FREE_PKT(args->m);
} else if (code == ICMP_REJECT_ABORT &&
args->f_id.proto == IPPROTO_SCTP) {
struct mbuf *m;
struct sctphdr *sctp;
struct sctp_chunkhdr *chunk;
struct sctp_init *init;
u_int32_t v_tag;
int reflected;
sctp = L3HDR(struct sctphdr, mtod(args->m, struct ip *));
reflected = 1;
v_tag = ntohl(sctp->v_tag);
if (iplen >= (ip->ip_hl << 2) + sizeof(struct sctphdr) +
sizeof(struct sctp_chunkhdr)) {
chunk = (struct sctp_chunkhdr *)(sctp + 1);
switch (chunk->chunk_type) {
case SCTP_INITIATION:
if (v_tag != 0) {
v_tag = 0;
break;
}
if (iplen >
(ip->ip_hl << 2) + sizeof(struct sctphdr) +
ntohs(chunk->chunk_length) + 3) {
break;
}
if ((iplen >= (ip->ip_hl << 2) +
sizeof(struct sctphdr) +
sizeof(struct sctp_chunkhdr) +
offsetof(struct sctp_init, a_rwnd))) {
init = (struct sctp_init *)(chunk + 1);
v_tag = ntohl(init->initiate_tag);
reflected = 0;
}
break;
case SCTP_ABORT_ASSOCIATION:
v_tag = 0;
break;
}
}
if (v_tag == 0) {
m = NULL;
} else {
m = ipfw_send_abort(args->m, &(args->f_id), v_tag,
reflected);
}
if (m != NULL)
ip_output(m, NULL, NULL, 0, NULL, NULL);
FREE_PKT(args->m);
} else
FREE_PKT(args->m);
args->m = NULL;
}
static int
check_uidgid(ipfw_insn_u32 *insn, struct ip_fw_args *args, int *ugid_lookupp,
struct ucred **uc)
{
#if defined(USERSPACE)
return 0;
#else
#ifndef __FreeBSD__
return cred_check(insn, proto, oif,
dst_ip, dst_port, src_ip, src_port,
(struct bsd_ucred *)uc, ugid_lookupp, ((struct mbuf *)inp)->m_skb);
#else
struct in_addr src_ip, dst_ip;
struct inpcbinfo *pi;
struct ipfw_flow_id *id;
struct inpcb *pcb, *inp;
int lookupflags;
int match;
id = &args->f_id;
inp = args->inp;
if (inp && *ugid_lookupp == 0) {
INP_LOCK_ASSERT(inp);
if (inp->inp_socket != NULL) {
*uc = crhold(inp->inp_cred);
*ugid_lookupp = 1;
} else
*ugid_lookupp = -1;
}
if (*ugid_lookupp == -1)
return (0);
if (id->proto == IPPROTO_TCP) {
lookupflags = 0;
pi = &V_tcbinfo;
} else if (id->proto == IPPROTO_UDP) {
lookupflags = INPLOOKUP_WILDCARD;
pi = &V_udbinfo;
} else if (id->proto == IPPROTO_UDPLITE) {
lookupflags = INPLOOKUP_WILDCARD;
pi = &V_ulitecbinfo;
} else
return 0;
lookupflags |= INPLOOKUP_RLOCKPCB;
match = 0;
if (*ugid_lookupp == 0) {
if (id->addr_type == 6) {
#ifdef INET6
if (args->flags & IPFW_ARGS_IN)
pcb = in6_pcblookup_mbuf(pi,
&id->src_ip6, htons(id->src_port),
&id->dst_ip6, htons(id->dst_port),
lookupflags, NULL, args->m);
else
pcb = in6_pcblookup_mbuf(pi,
&id->dst_ip6, htons(id->dst_port),
&id->src_ip6, htons(id->src_port),
lookupflags, args->ifp, args->m);
#else
*ugid_lookupp = -1;
return (0);
#endif
} else {
src_ip.s_addr = htonl(id->src_ip);
dst_ip.s_addr = htonl(id->dst_ip);
if (args->flags & IPFW_ARGS_IN)
pcb = in_pcblookup_mbuf(pi,
src_ip, htons(id->src_port),
dst_ip, htons(id->dst_port),
lookupflags, NULL, args->m);
else
pcb = in_pcblookup_mbuf(pi,
dst_ip, htons(id->dst_port),
src_ip, htons(id->src_port),
lookupflags, args->ifp, args->m);
}
if (pcb != NULL) {
INP_RLOCK_ASSERT(pcb);
*uc = crhold(pcb->inp_cred);
*ugid_lookupp = 1;
INP_RUNLOCK(pcb);
}
if (*ugid_lookupp == 0) {
*ugid_lookupp = -1;
return (0);
}
}
if (insn->o.opcode == O_UID)
match = ((*uc)->cr_uid == (uid_t)insn->d[0]);
else if (insn->o.opcode == O_GID)
match = groupmember((gid_t)insn->d[0], *uc);
else if (insn->o.opcode == O_JAIL)
match = ((*uc)->cr_prison->pr_id == (int)insn->d[0]);
return (match);
#endif
#endif
}
static inline void
set_match(struct ip_fw_args *args, int slot,
struct ip_fw_chain *chain)
{
args->rule.chain_id = chain->id;
args->rule.slot = slot + 1;
args->rule.rule_id = 1 + chain->map[slot]->id;
args->rule.rulenum = chain->map[slot]->rulenum;
args->flags |= IPFW_ARGS_REF;
}
static uint32_t
jump_lookup_pos(struct ip_fw_chain *chain, struct ip_fw *f, uint32_t num,
int tablearg, bool jump_backwards)
{
int f_pos, i;
i = IP_FW_ARG_TABLEARG(chain, num, skipto);
if (!jump_backwards && i <= f->rulenum)
i = f->rulenum + 1;
if (V_skipto_cache == 0)
f_pos = ipfw_find_rule(chain, i, 0);
else {
if (i >= IPFW_DEFAULT_RULE)
i = IPFW_DEFAULT_RULE - 1;
f_pos = chain->idxmap[i];
}
return (f_pos);
}
static uint32_t
jump(struct ip_fw_chain *chain, struct ip_fw *f, uint32_t num,
int tablearg, bool jump_backwards)
{
int f_pos;
if (num == IP_FW_TARG)
return jump_lookup_pos(chain, f, num, tablearg, jump_backwards);
#ifdef __LP64__
struct ip_fw_jump_cache cache;
cache.raw_value = f->cache.raw_value;
if (cache.id == chain->id)
return (cache.pos);
f_pos = jump_lookup_pos(chain, f, num, tablearg, jump_backwards);
cache.pos = f_pos;
cache.id = chain->id;
f->cache.raw_value = cache.raw_value;
#else
if (f->cache.id == chain->id) {
atomic_thread_fence_acq();
return (f->cache.pos);
}
f_pos = jump_lookup_pos(chain, f, num, tablearg, jump_backwards);
f->cache.pos = f_pos;
atomic_thread_fence_rel();
f->cache.id = chain->id;
#endif
return (f_pos);
}
#define TARG(k, f) IP_FW_ARG_TABLEARG(chain, k, f)
static inline int
tvalue_match(struct ip_fw_chain *ch, const ipfw_insn_table *cmd,
uint32_t tablearg)
{
uint32_t tvalue;
switch (IPFW_TVALUE_TYPE(&cmd->o)) {
case TVALUE_PIPE:
tvalue = TARG_VAL(ch, tablearg, pipe);
break;
case TVALUE_DIVERT:
tvalue = TARG_VAL(ch, tablearg, divert);
break;
case TVALUE_SKIPTO:
tvalue = TARG_VAL(ch, tablearg, skipto);
break;
case TVALUE_NETGRAPH:
tvalue = TARG_VAL(ch, tablearg, netgraph);
break;
case TVALUE_FIB:
tvalue = TARG_VAL(ch, tablearg, fib);
break;
case TVALUE_NAT:
tvalue = TARG_VAL(ch, tablearg, nat);
break;
case TVALUE_NH4:
tvalue = TARG_VAL(ch, tablearg, nh4);
break;
case TVALUE_DSCP:
tvalue = TARG_VAL(ch, tablearg, dscp);
break;
case TVALUE_LIMIT:
tvalue = TARG_VAL(ch, tablearg, limit);
break;
case TVALUE_MARK:
tvalue = TARG_VAL(ch, tablearg, mark);
break;
case TVALUE_TAG:
default:
tvalue = TARG_VAL(ch, tablearg, tag);
break;
}
return (tvalue == cmd->value);
}
int
ipfw_chk(struct ip_fw_args *args)
{
struct mbuf *m;
struct ip *ip;
struct ether_header *eh;
#ifndef __FreeBSD__
struct bsd_ucred ucred_cache;
#else
struct ucred *ucred_cache = NULL;
#endif
uint32_t f_pos = 0;
int ucred_lookup = 0;
int retval = 0;
struct ifnet *oif, *iif;
u_int hlen = 0;
u_short offset = 0;
u_short ip6f_mf = 0;
uint8_t proto;
uint16_t src_port, dst_port;
struct in_addr src_ip, dst_ip;
int iplen = 0;
int pktlen;
struct ipfw_dyn_info dyn_info;
struct ip_fw *q = NULL;
struct ip_fw_chain *chain = &V_layer3_chain;
void *ulp = NULL;
int is_ipv6 = 0;
#ifdef INET6
uint8_t icmp6_type = 0;
#endif
uint16_t ext_hd = 0;
int is_ipv4 = 0;
int done = 0;
IPFW_RLOCK_TRACKER;
bool mem;
bool need_send_reject = false;
int reject_code;
uint16_t reject_mtu;
if ((mem = (args->flags & IPFW_ARGS_LENMASK))) {
if (args->flags & IPFW_ARGS_ETHER) {
eh = (struct ether_header *)args->mem;
if (eh->ether_type == htons(ETHERTYPE_VLAN))
ip = (struct ip *)
((struct ether_vlan_header *)eh + 1);
else
ip = (struct ip *)(eh + 1);
} else {
eh = NULL;
ip = (struct ip *)args->mem;
}
pktlen = IPFW_ARGS_LENGTH(args->flags);
args->f_id.fib = args->ifp->if_fib;
} else {
m = args->m;
if (m->m_flags & M_SKIP_FIREWALL || (! V_ipfw_vnet_ready))
return (IP_FW_PASS);
if (args->flags & IPFW_ARGS_ETHER) {
if (m->m_len < min(m->m_pkthdr.len, max_protohdr) &&
(args->m = m = m_pullup(m, min(m->m_pkthdr.len,
max_protohdr))) == NULL)
goto pullup_failed;
eh = mtod(m, struct ether_header *);
ip = (struct ip *)(eh + 1);
} else {
eh = NULL;
ip = mtod(m, struct ip *);
}
pktlen = m->m_pkthdr.len;
args->f_id.fib = M_GETFIB(m);
}
dst_ip.s_addr = 0;
src_ip.s_addr = 0;
src_port = dst_port = 0;
DYN_INFO_INIT(&dyn_info);
#define PULLUP_TO(_len, p, T) PULLUP_LEN(_len, p, sizeof(T))
#define EHLEN (eh != NULL ? ((char *)ip - (char *)eh) : 0)
#define _PULLUP_LOCKED(_len, p, T, unlock) \
do { \
int x = (_len) + T + EHLEN; \
if (mem) { \
if (__predict_false(pktlen < x)) { \
unlock; \
goto pullup_failed; \
} \
p = (char *)args->mem + (_len) + EHLEN; \
} else { \
if (__predict_false((m)->m_len < x)) { \
args->m = m = m_pullup(m, x); \
if (m == NULL) { \
unlock; \
goto pullup_failed; \
} \
} \
p = mtod(m, char *) + (_len) + EHLEN; \
} \
} while (0)
#define PULLUP_LEN(_len, p, T) _PULLUP_LOCKED(_len, p, T, )
#define PULLUP_LEN_LOCKED(_len, p, T) \
_PULLUP_LOCKED(_len, p, T, IPFW_PF_RUNLOCK(chain)); \
UPDATE_POINTERS()
#define UPDATE_POINTERS() \
do { \
if (!mem) { \
if (eh != NULL) { \
eh = mtod(m, struct ether_header *); \
ip = (struct ip *)(eh + 1); \
} else \
ip = mtod(m, struct ip *); \
args->m = m; \
} \
} while (0)
if (pktlen >= sizeof(struct ip6_hdr) &&
(eh == NULL || eh->ether_type == htons(ETHERTYPE_IPV6)) &&
ip->ip_v == 6) {
struct ip6_hdr *ip6 = (struct ip6_hdr *)ip;
is_ipv6 = 1;
args->flags |= IPFW_ARGS_IP6;
hlen = sizeof(struct ip6_hdr);
proto = ip6->ip6_nxt;
while (ulp == NULL && offset == 0) {
switch (proto) {
case IPPROTO_ICMPV6:
PULLUP_TO(hlen, ulp, struct icmp6_hdr);
#ifdef INET6
icmp6_type = ICMP6(ulp)->icmp6_type;
#endif
break;
case IPPROTO_TCP:
PULLUP_TO(hlen, ulp, struct tcphdr);
dst_port = TCP(ulp)->th_dport;
src_port = TCP(ulp)->th_sport;
args->f_id._flags = tcp_get_flags(TCP(ulp));
break;
case IPPROTO_SCTP:
if (pktlen >= hlen + sizeof(struct sctphdr) +
sizeof(struct sctp_chunkhdr) +
offsetof(struct sctp_init, a_rwnd))
PULLUP_LEN(hlen, ulp,
sizeof(struct sctphdr) +
sizeof(struct sctp_chunkhdr) +
offsetof(struct sctp_init, a_rwnd));
else if (pktlen >= hlen + sizeof(struct sctphdr))
PULLUP_LEN(hlen, ulp, pktlen - hlen);
else
PULLUP_LEN(hlen, ulp,
sizeof(struct sctphdr));
src_port = SCTP(ulp)->src_port;
dst_port = SCTP(ulp)->dest_port;
break;
case IPPROTO_UDP:
case IPPROTO_UDPLITE:
PULLUP_TO(hlen, ulp, struct udphdr);
dst_port = UDP(ulp)->uh_dport;
src_port = UDP(ulp)->uh_sport;
break;
case IPPROTO_HOPOPTS:
PULLUP_TO(hlen, ulp, struct ip6_hbh);
ext_hd |= EXT_HOPOPTS;
hlen += (((struct ip6_hbh *)ulp)->ip6h_len + 1) << 3;
proto = ((struct ip6_hbh *)ulp)->ip6h_nxt;
ulp = NULL;
break;
case IPPROTO_ROUTING:
PULLUP_TO(hlen, ulp, struct ip6_rthdr);
switch (((struct ip6_rthdr *)ulp)->ip6r_type) {
case 0:
ext_hd |= EXT_RTHDR0;
break;
case 2:
ext_hd |= EXT_RTHDR2;
break;
default:
if (V_fw_verbose)
printf("IPFW2: IPV6 - Unknown "
"Routing Header type(%d)\n",
((struct ip6_rthdr *)
ulp)->ip6r_type);
if (V_fw_deny_unknown_exthdrs)
return (IP_FW_DENY);
break;
}
ext_hd |= EXT_ROUTING;
hlen += (((struct ip6_rthdr *)ulp)->ip6r_len + 1) << 3;
proto = ((struct ip6_rthdr *)ulp)->ip6r_nxt;
ulp = NULL;
break;
case IPPROTO_FRAGMENT:
PULLUP_TO(hlen, ulp, struct ip6_frag);
ext_hd |= EXT_FRAGMENT;
hlen += sizeof (struct ip6_frag);
proto = ((struct ip6_frag *)ulp)->ip6f_nxt;
offset = ((struct ip6_frag *)ulp)->ip6f_offlg &
IP6F_OFF_MASK;
ip6f_mf = ((struct ip6_frag *)ulp)->ip6f_offlg &
IP6F_MORE_FRAG;
if (V_fw_permit_single_frag6 == 0 &&
offset == 0 && ip6f_mf == 0) {
if (V_fw_verbose)
printf("IPFW2: IPV6 - Invalid "
"Fragment Header\n");
if (V_fw_deny_unknown_exthdrs)
return (IP_FW_DENY);
break;
}
args->f_id.extra =
ntohl(((struct ip6_frag *)ulp)->ip6f_ident);
ulp = NULL;
break;
case IPPROTO_DSTOPTS:
PULLUP_TO(hlen, ulp, struct ip6_hbh);
ext_hd |= EXT_DSTOPTS;
hlen += (((struct ip6_hbh *)ulp)->ip6h_len + 1) << 3;
proto = ((struct ip6_hbh *)ulp)->ip6h_nxt;
ulp = NULL;
break;
case IPPROTO_AH:
PULLUP_TO(hlen, ulp, struct ip6_ext);
ext_hd |= EXT_AH;
hlen += (((struct ip6_ext *)ulp)->ip6e_len + 2) << 2;
proto = ((struct ip6_ext *)ulp)->ip6e_nxt;
ulp = NULL;
break;
case IPPROTO_ESP:
PULLUP_TO(hlen, ulp, uint32_t);
ext_hd |= EXT_ESP;
break;
case IPPROTO_NONE:
ulp = ip;
break;
case IPPROTO_OSPFIGP:
PULLUP_TO(hlen, ulp, struct ip6_ext);
break;
case IPPROTO_PIM:
PULLUP_TO(hlen, ulp, struct pim);
break;
case IPPROTO_GRE:
PULLUP_TO(hlen, ulp, struct grehdr);
break;
case IPPROTO_CARP:
PULLUP_TO(hlen, ulp, offsetof(
struct carp_header, carp_counter));
if (CARP_ADVERTISEMENT !=
((struct carp_header *)ulp)->carp_type)
return (IP_FW_DENY);
break;
case IPPROTO_IPV6:
PULLUP_TO(hlen, ulp, struct ip6_hdr);
break;
case IPPROTO_IPV4:
PULLUP_TO(hlen, ulp, struct ip);
break;
case IPPROTO_ETHERIP:
PULLUP_LEN(hlen, ulp,
sizeof(struct etherip_header) +
sizeof(struct ether_header));
break;
case IPPROTO_PFSYNC:
PULLUP_TO(hlen, ulp, struct pfsync_header);
break;
default:
if (V_fw_verbose)
printf("IPFW2: IPV6 - Unknown "
"Extension Header(%d), ext_hd=%x\n",
proto, ext_hd);
if (V_fw_deny_unknown_exthdrs)
return (IP_FW_DENY);
PULLUP_TO(hlen, ulp, struct ip6_ext);
break;
}
}
UPDATE_POINTERS();
ip6 = (struct ip6_hdr *)ip;
args->f_id.addr_type = 6;
args->f_id.src_ip6 = ip6->ip6_src;
args->f_id.dst_ip6 = ip6->ip6_dst;
args->f_id.flow_id6 = ntohl(ip6->ip6_flow);
iplen = ntohs(ip6->ip6_plen) + sizeof(*ip6);
} else if (pktlen >= sizeof(struct ip) &&
(eh == NULL || eh->ether_type == htons(ETHERTYPE_IP)) &&
ip->ip_v == 4) {
is_ipv4 = 1;
args->flags |= IPFW_ARGS_IP4;
hlen = ip->ip_hl << 2;
proto = ip->ip_p;
src_ip = ip->ip_src;
dst_ip = ip->ip_dst;
offset = ntohs(ip->ip_off) & IP_OFFMASK;
iplen = ntohs(ip->ip_len);
if (offset == 0) {
switch (proto) {
case IPPROTO_TCP:
PULLUP_TO(hlen, ulp, struct tcphdr);
dst_port = TCP(ulp)->th_dport;
src_port = TCP(ulp)->th_sport;
args->f_id._flags = tcp_get_flags(TCP(ulp));
break;
case IPPROTO_SCTP:
if (pktlen >= hlen + sizeof(struct sctphdr) +
sizeof(struct sctp_chunkhdr) +
offsetof(struct sctp_init, a_rwnd))
PULLUP_LEN(hlen, ulp,
sizeof(struct sctphdr) +
sizeof(struct sctp_chunkhdr) +
offsetof(struct sctp_init, a_rwnd));
else if (pktlen >= hlen + sizeof(struct sctphdr))
PULLUP_LEN(hlen, ulp, pktlen - hlen);
else
PULLUP_LEN(hlen, ulp,
sizeof(struct sctphdr));
src_port = SCTP(ulp)->src_port;
dst_port = SCTP(ulp)->dest_port;
break;
case IPPROTO_UDP:
case IPPROTO_UDPLITE:
PULLUP_TO(hlen, ulp, struct udphdr);
dst_port = UDP(ulp)->uh_dport;
src_port = UDP(ulp)->uh_sport;
break;
case IPPROTO_ICMP:
PULLUP_TO(hlen, ulp, struct icmphdr);
break;
default:
break;
}
} else {
if (offset == 1 && proto == IPPROTO_TCP) {
goto pullup_failed;
}
}
UPDATE_POINTERS();
args->f_id.addr_type = 4;
args->f_id.src_ip = ntohl(src_ip.s_addr);
args->f_id.dst_ip = ntohl(dst_ip.s_addr);
} else {
proto = 0;
dst_ip.s_addr = src_ip.s_addr = 0;
args->f_id.addr_type = 1;
}
#undef PULLUP_TO
pktlen = iplen < pktlen ? iplen: pktlen;
args->f_id.proto = proto;
args->f_id.src_port = src_port = ntohs(src_port);
args->f_id.dst_port = dst_port = ntohs(dst_port);
IPFW_PF_RLOCK(chain);
if (! V_ipfw_vnet_ready) {
IPFW_PF_RUNLOCK(chain);
return (IP_FW_PASS);
}
if (args->flags & IPFW_ARGS_REF) {
f_pos = (args->rule.chain_id == chain->id) ?
args->rule.slot :
ipfw_find_rule(chain, args->rule.rulenum,
args->rule.rule_id);
} else {
f_pos = 0;
}
if (args->flags & IPFW_ARGS_IN) {
iif = args->ifp;
oif = NULL;
} else {
MPASS(args->flags & IPFW_ARGS_OUT);
iif = mem ? NULL : m_rcvif(m);
oif = args->ifp;
}
for (; f_pos < chain->n_rules; f_pos++) {
ipfw_insn *cmd;
uint32_t tablearg = 0;
int l, cmdlen, skip_or;
struct ip_fw *f;
f = chain->map[f_pos];
if (V_set_disable & (1 << f->set) )
continue;
skip_or = 0;
for (l = f->cmd_len, cmd = f->cmd ; l > 0 ;
l -= cmdlen, cmd += cmdlen) {
int match;
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:
printf("ipfw: opcode %d unimplemented\n",
cmd->opcode);
break;
case O_GID:
case O_UID:
case O_JAIL:
if (offset != 0)
break;
if (proto == IPPROTO_TCP ||
proto == IPPROTO_UDP ||
proto == IPPROTO_UDPLITE)
match = check_uidgid(
(ipfw_insn_u32 *)cmd,
args, &ucred_lookup,
#ifdef __FreeBSD__
&ucred_cache);
#else
(void *)&ucred_cache);
#endif
break;
case O_RECV:
match = iface_match(iif, (ipfw_insn_if *)cmd,
chain, &tablearg);
break;
case O_XMIT:
match = iface_match(oif, (ipfw_insn_if *)cmd,
chain, &tablearg);
break;
case O_VIA:
match = iface_match(args->ifp,
(ipfw_insn_if *)cmd, chain, &tablearg);
break;
case O_MACADDR2:
if (args->flags & IPFW_ARGS_ETHER) {
u_int32_t *want = (u_int32_t *)
((ipfw_insn_mac *)cmd)->addr;
u_int32_t *mask = (u_int32_t *)
((ipfw_insn_mac *)cmd)->mask;
u_int32_t *hdr = (u_int32_t *)eh;
match =
( want[0] == (hdr[0] & mask[0]) &&
want[1] == (hdr[1] & mask[1]) &&
want[2] == (hdr[2] & mask[2]) );
}
break;
case O_MAC_TYPE:
if (args->flags & IPFW_ARGS_ETHER) {
u_int16_t *p =
((ipfw_insn_u16 *)cmd)->ports;
int i;
for (i = cmdlen - 1; !match && i>0;
i--, p += 2)
match =
(ntohs(eh->ether_type) >=
p[0] &&
ntohs(eh->ether_type) <=
p[1]);
}
break;
case O_FRAG:
if (is_ipv4) {
match = flags_match(cmd,
((ntohs(ip->ip_off) & ~IP_OFFMASK)
>> 8) | (offset != 0));
} else {
match = (cmd->arg1 == 0x1 &&
(offset != 0));
}
break;
case O_IN:
match = (oif == NULL);
break;
case O_LAYER2:
match = (args->flags & IPFW_ARGS_ETHER);
break;
case O_DIVERTED:
if ((args->flags & IPFW_ARGS_REF) == 0)
break;
match = ((args->rule.info & IPFW_IS_MASK) ==
IPFW_IS_DIVERT) && (
((args->rule.info & IPFW_INFO_IN) ?
1: 2) & cmd->arg1);
break;
case O_PROTO:
match = (proto == cmd->arg1);
break;
case O_IP_SRC:
match = is_ipv4 &&
(((ipfw_insn_ip *)cmd)->addr.s_addr ==
src_ip.s_addr);
break;
case O_IP_DST_LOOKUP:
if (IPFW_LOOKUP_TYPE(cmd) != LOOKUP_NONE) {
void *pkey = NULL;
uint32_t key, vidx;
uint16_t keylen = 0;
uint8_t lookup_type;
lookup_type = IPFW_LOOKUP_TYPE(cmd);
switch (lookup_type) {
case LOOKUP_DST_IP:
case LOOKUP_SRC_IP:
if (is_ipv4) {
keylen = sizeof(in_addr_t);
if (lookup_type == LOOKUP_DST_IP)
pkey = &dst_ip;
else
pkey = &src_ip;
} else if (is_ipv6) {
keylen = sizeof(struct in6_addr);
if (lookup_type == LOOKUP_DST_IP)
pkey = &args->f_id.dst_ip6;
else
pkey = &args->f_id.src_ip6;
} else
break;
case LOOKUP_DSCP:
if (is_ipv4)
key = ip->ip_tos >> 2;
else if (is_ipv6)
key = IPV6_DSCP(
(struct ip6_hdr *)ip) >> 2;
else
break;
key &= 0x3f;
if (cmdlen == F_INSN_SIZE(ipfw_insn_table))
key &= insntod(cmd, table)->value;
pkey = &key;
keylen = sizeof(key);
break;
case LOOKUP_DST_PORT:
case LOOKUP_SRC_PORT:
if (is_ipv6 == 0 && is_ipv4 == 0) {
break;
}
if (offset != 0) {
break;
}
if (proto != IPPROTO_TCP &&
proto != IPPROTO_UDP &&
proto != IPPROTO_UDPLITE &&
proto != IPPROTO_SCTP)
break;
if (lookup_type == LOOKUP_DST_PORT)
key = dst_port;
else
key = src_port;
pkey = &key;
if (cmdlen == F_INSN_SIZE(ipfw_insn_table))
key &= insntod(cmd, table)->value;
keylen = sizeof(key);
break;
case LOOKUP_DST_MAC:
case LOOKUP_SRC_MAC:
if ((args->flags & IPFW_ARGS_ETHER) == 0)
break;
pkey = lookup_type == LOOKUP_DST_MAC ?
eh->ether_dhost : eh->ether_shost;
keylen = ETHER_ADDR_LEN;
break;
#ifndef USERSPACE
case LOOKUP_UID:
case LOOKUP_JAIL:
check_uidgid(insntod(cmd, u32),
args, &ucred_lookup,
#ifdef __FreeBSD__
&ucred_cache);
if (lookup_type == LOOKUP_UID)
key = ucred_cache->cr_uid;
else if (lookup_type == LOOKUP_JAIL)
key = ucred_cache->cr_prison->pr_id;
#else
(void *)&ucred_cache);
if (lookup_type == LOOKUP_UID)
key = ucred_cache.uid;
else if (lookup_type == LOOKUP_JAIL)
key = ucred_cache.xid;
#endif
pkey = &key;
if (cmdlen == F_INSN_SIZE(ipfw_insn_table))
key &= insntod(cmd, table)->value;
keylen = sizeof(key);
break;
#endif
case LOOKUP_MARK:
key = args->rule.pkt_mark;
if (cmdlen == F_INSN_SIZE(ipfw_insn_table))
key &= insntod(cmd, table)->value;
pkey = &key;
keylen = sizeof(key);
break;
case LOOKUP_RULENUM:
key = f->rulenum;
if (cmdlen == F_INSN_SIZE(ipfw_insn_table))
key &= insntod(cmd, table)->value;
pkey = &key;
keylen = sizeof(key);
break;
}
if (keylen == 0)
break;
match = ipfw_lookup_table(chain,
insntod(cmd, kidx)->kidx, keylen,
pkey, &vidx);
if (match)
tablearg = vidx;
break;
}
case O_IP_SRC_LOOKUP:
{
void *pkey;
uint32_t vidx;
uint16_t keylen;
if (is_ipv4) {
keylen = sizeof(in_addr_t);
if (cmd->opcode == O_IP_DST_LOOKUP)
pkey = &dst_ip;
else
pkey = &src_ip;
} else if (is_ipv6) {
keylen = sizeof(struct in6_addr);
if (cmd->opcode == O_IP_DST_LOOKUP)
pkey = &args->f_id.dst_ip6;
else
pkey = &args->f_id.src_ip6;
} else
break;
match = ipfw_lookup_table(chain,
insntod(cmd, kidx)->kidx,
keylen, pkey, &vidx);
if (!match)
break;
if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) {
match = tvalue_match(chain,
insntod(cmd, table), vidx);
if (!match)
break;
}
tablearg = vidx;
break;
}
case O_MAC_SRC_LOOKUP:
case O_MAC_DST_LOOKUP:
{
void *pkey;
uint32_t vidx;
uint16_t keylen = ETHER_ADDR_LEN;
if ((args->flags & IPFW_ARGS_ETHER) == 0)
break;
if (cmd->opcode == O_MAC_DST_LOOKUP)
pkey = eh->ether_dhost;
else
pkey = eh->ether_shost;
match = ipfw_lookup_table(chain,
insntod(cmd, kidx)->kidx,
keylen, pkey, &vidx);
if (!match)
break;
if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) {
match = tvalue_match(chain,
insntod(cmd, table), vidx);
if (!match)
break;
}
tablearg = vidx;
break;
}
case O_IP_FLOW_LOOKUP:
{
uint32_t vidx = 0;
match = ipfw_lookup_table(chain,
insntod(cmd, kidx)->kidx, 0,
&args->f_id, &vidx);
if (!match)
break;
if (cmdlen == F_INSN_SIZE(ipfw_insn_table))
match = tvalue_match(chain,
insntod(cmd, table), vidx);
if (match)
tablearg = vidx;
break;
}
case O_IP_SRC_MASK:
case O_IP_DST_MASK:
if (is_ipv4) {
uint32_t a =
(cmd->opcode == O_IP_DST_MASK) ?
dst_ip.s_addr : src_ip.s_addr;
uint32_t *p = ((ipfw_insn_u32 *)cmd)->d;
int i = cmdlen-1;
for (; !match && i>0; i-= 2, p+= 2)
match = (p[0] == (a & p[1]));
}
break;
case O_IP_SRC_ME:
if (is_ipv4) {
match = in_localip(src_ip);
break;
}
#ifdef INET6
case O_IP6_SRC_ME:
match = is_ipv6 &&
ipfw_localip6(&args->f_id.src_ip6);
#endif
break;
case O_IP_DST_SET:
case O_IP_SRC_SET:
if (is_ipv4) {
u_int32_t *d = (u_int32_t *)(cmd+1);
u_int32_t addr =
cmd->opcode == O_IP_DST_SET ?
args->f_id.dst_ip :
args->f_id.src_ip;
if (addr < d[0])
break;
addr -= d[0];
match = (addr < cmd->arg1) &&
( d[ 1 + (addr>>5)] &
(1<<(addr & 0x1f)) );
}
break;
case O_IP_DST:
match = is_ipv4 &&
(((ipfw_insn_ip *)cmd)->addr.s_addr ==
dst_ip.s_addr);
break;
case O_IP_DST_ME:
if (is_ipv4) {
match = in_localip(dst_ip);
break;
}
#ifdef INET6
case O_IP6_DST_ME:
match = is_ipv6 &&
ipfw_localip6(&args->f_id.dst_ip6);
#endif
break;
case O_IP_SRCPORT:
case O_IP_DSTPORT:
if ((proto == IPPROTO_UDP ||
proto == IPPROTO_UDPLITE ||
proto == IPPROTO_TCP ||
proto == IPPROTO_SCTP) && offset == 0) {
u_int16_t x =
(cmd->opcode == O_IP_SRCPORT) ?
src_port : dst_port ;
u_int16_t *p =
((ipfw_insn_u16 *)cmd)->ports;
int i;
for (i = cmdlen - 1; !match && i>0;
i--, p += 2)
match = (x>=p[0] && x<=p[1]);
}
break;
case O_ICMPTYPE:
match = (offset == 0 && proto==IPPROTO_ICMP &&
icmptype_match(ICMP(ulp), (ipfw_insn_u32 *)cmd) );
break;
#ifdef INET6
case O_ICMP6TYPE:
match = is_ipv6 && offset == 0 &&
proto==IPPROTO_ICMPV6 &&
icmp6type_match(
ICMP6(ulp)->icmp6_type,
(ipfw_insn_u32 *)cmd);
break;
#endif
case O_IPOPT:
match = (is_ipv4 &&
ipopts_match(ip, cmd) );
break;
case O_IPVER:
match = ((is_ipv4 || is_ipv6) &&
cmd->arg1 == ip->ip_v);
break;
case O_IPID:
case O_IPTTL:
if (!is_ipv4)
break;
case O_IPLEN:
{
uint16_t x;
uint16_t *p;
int i;
if (cmd->opcode == O_IPLEN)
x = iplen;
else if (cmd->opcode == O_IPTTL)
x = ip->ip_ttl;
else
x = ntohs(ip->ip_id);
if (cmdlen == 1) {
match = (cmd->arg1 == x);
break;
}
p = ((ipfw_insn_u16 *)cmd)->ports;
i = cmdlen - 1;
for (; !match && i>0; i--, p += 2)
match = (x >= p[0] && x <= p[1]);
}
break;
case O_IPPRECEDENCE:
match = (is_ipv4 &&
(cmd->arg1 == (ip->ip_tos & 0xe0)) );
break;
case O_IPTOS:
match = (is_ipv4 &&
flags_match(cmd, ip->ip_tos));
break;
case O_DSCP:
{
uint32_t *p;
uint16_t x;
p = ((ipfw_insn_u32 *)cmd)->d;
if (is_ipv4)
x = ip->ip_tos >> 2;
else if (is_ipv6) {
x = IPV6_DSCP(
(struct ip6_hdr *)ip) >> 2;
x &= 0x3f;
} else
break;
if (x >= 32)
match = *(p + 1) & (1 << (x - 32));
else
match = *p & (1 << x);
}
break;
case O_TCPDATALEN:
if (proto == IPPROTO_TCP && offset == 0) {
struct tcphdr *tcp;
uint16_t x;
uint16_t *p;
int i;
#ifdef INET6
if (is_ipv6) {
struct ip6_hdr *ip6;
ip6 = (struct ip6_hdr *)ip;
if (ip6->ip6_plen == 0) {
break;
}
x = iplen - hlen;
} else
#endif
x = iplen - (ip->ip_hl << 2);
tcp = TCP(ulp);
x -= tcp->th_off << 2;
if (cmdlen == 1) {
match = (cmd->arg1 == x);
break;
}
p = ((ipfw_insn_u16 *)cmd)->ports;
i = cmdlen - 1;
for (; !match && i>0; i--, p += 2)
match = (x >= p[0] && x <= p[1]);
}
break;
case O_TCPFLAGS:
match = (proto == IPPROTO_TCP && offset == 0 &&
flags_match(cmd, tcp_get_flags(TCP(ulp))));
break;
case O_TCPOPTS:
if (proto == IPPROTO_TCP && offset == 0 && ulp){
PULLUP_LEN_LOCKED(hlen, ulp,
(TCP(ulp)->th_off << 2));
match = tcpopts_match(TCP(ulp), cmd);
}
break;
case O_TCPSEQ:
match = (proto == IPPROTO_TCP && offset == 0 &&
((ipfw_insn_u32 *)cmd)->d[0] ==
TCP(ulp)->th_seq);
break;
case O_TCPACK:
match = (proto == IPPROTO_TCP && offset == 0 &&
((ipfw_insn_u32 *)cmd)->d[0] ==
TCP(ulp)->th_ack);
break;
case O_TCPMSS:
if (proto == IPPROTO_TCP &&
(args->f_id._flags & TH_SYN) != 0 &&
ulp != NULL) {
uint16_t mss, *p;
int i;
PULLUP_LEN_LOCKED(hlen, ulp,
(TCP(ulp)->th_off << 2));
if ((tcpopts_parse(TCP(ulp), &mss) &
IP_FW_TCPOPT_MSS) == 0)
break;
if (cmdlen == 1) {
match = (cmd->arg1 == mss);
break;
}
p = ((ipfw_insn_u16 *)cmd)->ports;
i = cmdlen - 1;
for (; !match && i > 0; i--, p += 2)
match = (mss >= p[0] &&
mss <= p[1]);
}
break;
case O_TCPWIN:
if (proto == IPPROTO_TCP && offset == 0) {
uint16_t x;
uint16_t *p;
int i;
x = ntohs(TCP(ulp)->th_win);
if (cmdlen == 1) {
match = (cmd->arg1 == x);
break;
}
p = ((ipfw_insn_u16 *)cmd)->ports;
i = cmdlen - 1;
for (; !match && i > 0; i--, p += 2)
match = (x >= p[0] && x <= p[1]);
}
break;
case O_ESTAB:
match = (proto == IPPROTO_TCP && offset == 0 &&
(tcp_get_flags(TCP(ulp)) &
(TH_RST | TH_ACK | TH_SYN)) != TH_SYN);
break;
case O_ALTQ: {
struct pf_mtag *at;
struct m_tag *mtag;
ipfw_insn_altq *altq = (ipfw_insn_altq *)cmd;
match = 1;
at = pf_find_mtag(m);
if (at != NULL && at->qid != 0)
break;
mtag = m_tag_get(PACKET_TAG_PF,
sizeof(struct pf_mtag), M_NOWAIT | M_ZERO);
if (mtag == NULL) {
break;
}
m_tag_prepend(m, mtag);
at = (struct pf_mtag *)(mtag + 1);
at->qid = altq->qid;
at->hdr = ip;
break;
}
case O_LOG:
ipfw_log(chain, f, hlen, args,
offset | ip6f_mf, tablearg, ip, eh);
match = 1;
break;
case O_PROB:
match = (random()<((ipfw_insn_u32 *)cmd)->d[0]);
break;
case O_VERREVPATH:
match = (args->flags & IPFW_ARGS_OUT ||
(
#ifdef INET6
is_ipv6 ?
verify_path6(&(args->f_id.src_ip6),
iif, args->f_id.fib) :
#endif
verify_path(src_ip, iif, args->f_id.fib)));
break;
case O_VERSRCREACH:
match = (hlen > 0 && ((oif != NULL) || (
#ifdef INET6
is_ipv6 ?
verify_path6(&(args->f_id.src_ip6),
NULL, args->f_id.fib) :
#endif
verify_path(src_ip, NULL, args->f_id.fib))));
break;
case O_ANTISPOOF:
if (oif == NULL && hlen > 0 &&
( (is_ipv4 && in_localaddr(src_ip))
#ifdef INET6
|| (is_ipv6 &&
in6_localaddr(&(args->f_id.src_ip6)))
#endif
))
match =
#ifdef INET6
is_ipv6 ? verify_path6(
&(args->f_id.src_ip6), iif,
args->f_id.fib) :
#endif
verify_path(src_ip, iif,
args->f_id.fib);
else
match = 1;
break;
case O_IPSEC:
match = (m_tag_find(m,
PACKET_TAG_IPSEC_IN_DONE, NULL) != NULL);
break;
#ifdef INET6
case O_IP6_SRC:
match = is_ipv6 &&
IN6_ARE_ADDR_EQUAL(&args->f_id.src_ip6,
&((ipfw_insn_ip6 *)cmd)->addr6);
break;
case O_IP6_DST:
match = is_ipv6 &&
IN6_ARE_ADDR_EQUAL(&args->f_id.dst_ip6,
&((ipfw_insn_ip6 *)cmd)->addr6);
break;
case O_IP6_SRC_MASK:
case O_IP6_DST_MASK:
if (is_ipv6) {
int i = cmdlen - 1;
struct in6_addr p;
struct in6_addr *d =
&((ipfw_insn_ip6 *)cmd)->addr6;
for (; !match && i > 0; d += 2,
i -= F_INSN_SIZE(struct in6_addr)
* 2) {
p = (cmd->opcode ==
O_IP6_SRC_MASK) ?
args->f_id.src_ip6:
args->f_id.dst_ip6;
APPLY_MASK(&p, &d[1]);
match =
IN6_ARE_ADDR_EQUAL(&d[0],
&p);
}
}
break;
case O_FLOW6ID:
match = is_ipv6 &&
flow6id_match(args->f_id.flow_id6,
(ipfw_insn_u32 *) cmd);
break;
case O_EXT_HDR:
match = is_ipv6 &&
(ext_hd & ((ipfw_insn *) cmd)->arg1);
break;
case O_IP6:
match = is_ipv6;
break;
#endif
case O_IP4:
match = is_ipv4;
break;
case O_TAG: {
struct m_tag *mtag;
uint32_t tag = TARG(cmd->arg1, tag);
mtag = m_tag_locate(m, MTAG_IPFW, tag, NULL);
if (cmd->len & F_NOT) {
if (mtag != NULL)
m_tag_delete(m, mtag);
match = 0;
} else {
if (mtag == NULL) {
mtag = m_tag_alloc( MTAG_IPFW,
tag, 0, M_NOWAIT);
if (mtag != NULL)
m_tag_prepend(m, mtag);
}
match = 1;
}
break;
}
case O_FIB:
if (args->f_id.fib == cmd->arg1)
match = 1;
break;
case O_SOCKARG: {
#ifndef USERSPACE
struct inpcb *inp = args->inp;
struct inpcbinfo *pi;
bool inp_locked = false;
if (proto == IPPROTO_TCP)
pi = &V_tcbinfo;
else if (proto == IPPROTO_UDP)
pi = &V_udbinfo;
else if (proto == IPPROTO_UDPLITE)
pi = &V_ulitecbinfo;
else
break;
if (is_ipv4 && inp == NULL) {
inp = in_pcblookup(pi,
src_ip, htons(src_port),
dst_ip, htons(dst_port),
INPLOOKUP_RLOCKPCB, NULL);
inp_locked = true;
}
#ifdef INET6
if (is_ipv6 && inp == NULL) {
inp = in6_pcblookup(pi,
&args->f_id.src_ip6,
htons(src_port),
&args->f_id.dst_ip6,
htons(dst_port),
INPLOOKUP_RLOCKPCB, NULL);
inp_locked = true;
}
#endif
if (inp != NULL) {
if (inp->inp_socket) {
tablearg =
inp->inp_socket->so_user_cookie;
if (tablearg)
match = 1;
}
if (inp_locked)
INP_RUNLOCK(inp);
}
#endif
break;
}
case O_TAGGED: {
struct m_tag *mtag;
uint32_t tag = TARG(cmd->arg1, tag);
if (cmdlen == 1) {
match = m_tag_locate(m, MTAG_IPFW,
tag, NULL) != NULL;
break;
}
for (mtag = m_tag_first(m);
mtag != NULL && !match;
mtag = m_tag_next(m, mtag)) {
uint16_t *p;
int i;
if (mtag->m_tag_cookie != MTAG_IPFW)
continue;
p = ((ipfw_insn_u16 *)cmd)->ports;
i = cmdlen - 1;
for(; !match && i > 0; i--, p += 2)
match =
mtag->m_tag_id >= p[0] &&
mtag->m_tag_id <= p[1];
}
break;
}
case O_MARK: {
uint32_t mark;
if (cmd->arg1 == IP_FW_TARG)
mark = TARG_VAL(chain, tablearg, mark);
else
mark = insntoc(cmd, u32)->d[0];
match =
(args->rule.pkt_mark &
insntoc(cmd, u32)->d[1]) ==
(mark & insntoc(cmd, u32)->d[1]);
break;
}
case O_LIMIT:
case O_KEEP_STATE:
if (ipfw_dyn_install_state(chain, f,
(ipfw_insn_limit *)cmd, args, ulp,
pktlen, &dyn_info, tablearg)) {
retval = IP_FW_DENY;
l = 0;
done = 1;
}
match = 1;
break;
case O_PROBE_STATE:
case O_CHECK_STATE:
if (DYN_LOOKUP_NEEDED(&dyn_info, cmd) &&
(q = ipfw_dyn_lookup_state(args, ulp,
pktlen, cmd, &dyn_info)) != NULL) {
f = q;
f_pos = dyn_info.f_pos;
cmd = ACTION_PTR(f);
l = f->cmd_len - f->act_ofs;
cmdlen = 0;
continue;
}
if (cmd->opcode == O_CHECK_STATE)
l = 0;
match = 1;
break;
case O_SKIP_ACTION:
match = 0;
l = 0;
break;
case O_ACCEPT:
retval = 0;
l = 0;
done = 1;
break;
case O_PIPE:
case O_QUEUE:
set_match(args, f_pos, chain);
args->rule.info = TARG(cmd->arg1, pipe);
if (cmd->opcode == O_PIPE)
args->rule.info |= IPFW_IS_PIPE;
if (V_fw_one_pass)
args->rule.info |= IPFW_ONEPASS;
retval = IP_FW_DUMMYNET;
l = 0;
done = 1;
break;
case O_DIVERT:
case O_TEE:
if (args->flags & IPFW_ARGS_ETHER)
break;
l = 0;
done = 1;
retval = (cmd->opcode == O_DIVERT) ?
IP_FW_DIVERT : IP_FW_TEE;
set_match(args, f_pos, chain);
args->rule.info = TARG(cmd->arg1, divert);
break;
case O_COUNT:
IPFW_INC_RULE_COUNTER(f, pktlen);
l = 0;
break;
case O_SKIPTO:
IPFW_INC_RULE_COUNTER(f, pktlen);
f_pos = jump(chain, f,
insntod(cmd, u32)->d[0], tablearg, false);
for (; f_pos < chain->n_rules - 1 &&
(V_set_disable &
(1 << chain->map[f_pos]->set));
f_pos++)
;
f = chain->map[f_pos];
l = f->cmd_len;
cmd = f->cmd;
match = 1;
cmdlen = 0;
skip_or = 0;
continue;
break;
case O_CALLRETURN: {
struct m_tag *mtag;
uint32_t jmpto, *stack;
#define IS_CALL ((cmd->len & F_NOT) == 0)
#define IS_RETURN ((cmd->len & F_NOT) != 0)
mtag = m_tag_first(m);
while (mtag != NULL) {
if (mtag->m_tag_cookie ==
MTAG_IPFW_CALL)
break;
mtag = m_tag_next(m, mtag);
}
if (mtag != NULL) {
stack = (uint32_t *)(mtag + 1);
if (stack[0] != chain->id) {
stack[0] = chain->id;
mtag->m_tag_id = 0;
}
}
if (IS_RETURN && (mtag == NULL ||
mtag->m_tag_id == 0)) {
l = 0;
break;
}
if (mtag == NULL) {
MPASS(IS_CALL);
mtag = m_tag_alloc(MTAG_IPFW_CALL, 0,
IPFW_CALLSTACK_SIZE *
sizeof(uint32_t), M_NOWAIT);
if (mtag != NULL) {
m_tag_prepend(m, mtag);
stack = (uint32_t *)(mtag + 1);
stack[0] = chain->id;
}
}
if (mtag == NULL) {
printf("ipfw: rule %u: failed to "
"allocate call stack. "
"Denying packet.\n",
f->rulenum);
l = 0;
done = 1;
retval = IP_FW_DENY;
break;
}
if (IS_CALL && mtag->m_tag_id >=
IPFW_CALLSTACK_SIZE - 1) {
printf("ipfw: rule %u: call stack "
"overflow. Denying packet.\n",
f->rulenum);
l = 0;
done = 1;
retval = IP_FW_DENY;
break;
}
MPASS(stack == (uint32_t *)(mtag + 1));
IPFW_INC_RULE_COUNTER(f, pktlen);
if (IS_CALL) {
stack[++mtag->m_tag_id] = f_pos;
f_pos = jump(chain, f,
insntod(cmd, u32)->d[0],
tablearg, true);
} else {
jmpto = stack[mtag->m_tag_id--];
if (cmd->arg1 == RETURN_NEXT_RULE)
f_pos = jmpto + 1;
else
f_pos = ipfw_find_rule(chain,
chain->map[
jmpto]->rulenum + 1, 0);
}
MPASS(f_pos < chain->n_rules - 1);
for (; f_pos < chain->n_rules - 1 &&
(V_set_disable &
(1 << chain->map[f_pos]->set)); f_pos++)
;
f = chain->map[f_pos];
l = f->cmd_len;
cmd = f->cmd;
cmdlen = 0;
skip_or = 0;
continue;
break;
}
#undef IS_CALL
#undef IS_RETURN
case O_REJECT:
if (hlen > 0 && is_ipv4 && offset == 0 &&
(proto != IPPROTO_ICMP ||
is_icmp_query(ICMP(ulp))) &&
!(m->m_flags & (M_BCAST|M_MCAST)) &&
!IN_MULTICAST(ntohl(dst_ip.s_addr))) {
KASSERT(!need_send_reject,
("o_reject - need_send_reject was set previously"));
if ((reject_code = cmd->arg1) == ICMP_UNREACH_NEEDFRAG &&
cmd->len == F_INSN_SIZE(ipfw_insn_u16)) {
reject_mtu =
((ipfw_insn_u16 *)cmd)->ports[0];
} else {
reject_mtu = 0;
}
need_send_reject = true;
}
#ifdef INET6
case O_UNREACH6:
if (hlen > 0 && is_ipv6 &&
((offset & IP6F_OFF_MASK) == 0) &&
(proto != IPPROTO_ICMPV6 ||
(is_icmp6_query(icmp6_type) == 1)) &&
!(m->m_flags & (M_BCAST|M_MCAST)) &&
!IN6_IS_ADDR_MULTICAST(
&args->f_id.dst_ip6)) {
KASSERT(!need_send_reject,
("o_unreach6 - need_send_reject was set previously"));
reject_code = cmd->arg1;
if (cmd->opcode == O_REJECT) {
reject_code =
map_icmp_unreach(reject_code);
}
need_send_reject = true;
}
#endif
case O_DENY:
retval = IP_FW_DENY;
l = 0;
done = 1;
break;
case O_FORWARD_IP:
if (args->flags & IPFW_ARGS_ETHER)
break;
if (q != f ||
dyn_info.direction == MATCH_FORWARD) {
struct sockaddr_in *sa;
sa = &(((ipfw_insn_sa *)cmd)->sa);
if (sa->sin_addr.s_addr == INADDR_ANY) {
#ifdef INET6
if (is_ipv6) {
struct ip_fw_nh6 *nh6;
args->flags |= IPFW_ARGS_NH6;
nh6 = &args->hopstore6;
nh6->sin6_addr = TARG_VAL(
chain, tablearg, nh6);
nh6->sin6_port = sa->sin_port;
nh6->sin6_scope_id = TARG_VAL(
chain, tablearg, zoneid);
} else
#endif
{
args->flags |= IPFW_ARGS_NH4;
args->hopstore.sin_port =
sa->sin_port;
sa = &args->hopstore;
sa->sin_family = AF_INET;
sa->sin_len = sizeof(*sa);
sa->sin_addr.s_addr = htonl(
TARG_VAL(chain, tablearg,
nh4));
}
} else {
args->flags |= IPFW_ARGS_NH4PTR;
args->next_hop = sa;
}
}
retval = IP_FW_PASS;
l = 0;
done = 1;
break;
#ifdef INET6
case O_FORWARD_IP6:
if (args->flags & IPFW_ARGS_ETHER)
break;
if (q != f ||
dyn_info.direction == MATCH_FORWARD) {
struct sockaddr_in6 *sin6;
sin6 = &(((ipfw_insn_sa6 *)cmd)->sa);
args->flags |= IPFW_ARGS_NH6PTR;
args->next_hop6 = sin6;
}
retval = IP_FW_PASS;
l = 0;
done = 1;
break;
#endif
case O_NETGRAPH:
case O_NGTEE:
set_match(args, f_pos, chain);
args->rule.info = TARG(cmd->arg1, netgraph);
if (V_fw_one_pass)
args->rule.info |= IPFW_ONEPASS;
retval = (cmd->opcode == O_NETGRAPH) ?
IP_FW_NETGRAPH : IP_FW_NGTEE;
l = 0;
done = 1;
break;
case O_SETFIB: {
uint32_t fib;
IPFW_INC_RULE_COUNTER(f, pktlen);
fib = TARG(cmd->arg1, fib) & 0x7FFF;
if (fib >= rt_numfibs)
fib = 0;
M_SETFIB(m, fib);
args->f_id.fib = fib;
l = 0;
break;
}
case O_SETDSCP: {
uint16_t code;
code = TARG(cmd->arg1, dscp) & 0x3F;
l = 0;
if (is_ipv4) {
uint16_t old;
old = *(uint16_t *)ip;
ip->ip_tos = (code << 2) |
(ip->ip_tos & 0x03);
ip->ip_sum = cksum_adjust(ip->ip_sum,
old, *(uint16_t *)ip);
} else if (is_ipv6) {
args->f_id.flow_id6 =
ntohl(*(uint32_t *)ip) & ~0x0FC00000;
args->f_id.flow_id6 |= code << 22;
*((uint32_t *)ip) =
htonl(args->f_id.flow_id6);
} else
break;
IPFW_INC_RULE_COUNTER(f, pktlen);
break;
}
case O_NAT:
l = 0;
done = 1;
if (!is_ipv4 || !IPFW_NAT_LOADED) {
retval = IP_FW_DENY;
break;
}
struct cfg_nat *t;
int nat_id;
args->rule.info = 0;
set_match(args, f_pos, chain);
if (cmd->arg1 == IP_FW_NAT44_GLOBAL) {
retval = ipfw_nat_ptr(args, NULL, m);
break;
}
t = ((ipfw_insn_nat *)cmd)->nat;
if (t == NULL) {
nat_id = TARG(cmd->arg1, nat);
t = (*lookup_nat_ptr)(&chain->nat, nat_id);
if (t == NULL) {
retval = IP_FW_DENY;
break;
}
if (cmd->arg1 != IP_FW_TARG)
((ipfw_insn_nat *)cmd)->nat = t;
}
retval = ipfw_nat_ptr(args, t, m);
break;
case O_REASS: {
int ip_off;
l = 0;
if (is_ipv6)
break;
IPFW_INC_RULE_COUNTER(f, pktlen);
ip_off = ntohs(ip->ip_off);
if ((ip_off & (IP_MF | IP_OFFMASK)) == 0)
break;
args->m = m = ip_reass(m);
if (m == NULL) {
retval = IP_FW_DENY;
} else {
int hlen;
ip = mtod(m, struct ip *);
hlen = ip->ip_hl << 2;
ip->ip_sum = 0;
if (hlen == sizeof(struct ip))
ip->ip_sum = in_cksum_hdr(ip);
else
ip->ip_sum = in_cksum(m, hlen);
retval = IP_FW_REASS;
args->rule.info = 0;
set_match(args, f_pos, chain);
}
done = 1;
break;
}
case O_SETMARK: {
l = 0;
args->rule.pkt_mark = (
(cmd->arg1 == IP_FW_TARG) ?
TARG_VAL(chain, tablearg, mark) :
insntoc(cmd, u32)->d[0]);
IPFW_INC_RULE_COUNTER(f, pktlen);
break;
}
case O_EXTERNAL_ACTION:
l = 0;
retval = ipfw_run_eaction(chain, args,
cmd, &done);
if (retval == 0 && done == 0) {
IPFW_INC_RULE_COUNTER(f, pktlen);
DYN_INFO_INIT(&dyn_info);
}
break;
default:
panic("ipfw: rule %u: unknown opcode %d\n",
f->rulenum, 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;
}
}
#undef PULLUP_LEN
#undef PULLUP_LEN_LOCKED
if (done)
break;
}
if (done) {
struct ip_fw *rule = chain->map[f_pos];
IPFW_INC_RULE_COUNTER(rule, pktlen);
IPFW_PROBE(rule__matched, retval,
is_ipv4 ? AF_INET : AF_INET6,
is_ipv4 ? (uintptr_t)&src_ip :
(uintptr_t)&args->f_id.src_ip6,
is_ipv4 ? (uintptr_t)&dst_ip :
(uintptr_t)&args->f_id.dst_ip6,
args, rule);
} else {
retval = IP_FW_DENY;
printf("ipfw: ouch!, skip past end of rules, denying packet\n");
}
IPFW_PF_RUNLOCK(chain);
if (need_send_reject) {
#ifdef INET6
if (is_ipv6)
send_reject6(args, reject_code, hlen,
(struct ip6_hdr *)ip);
else
#endif
send_reject(args, reject_code, reject_mtu,
iplen, ip);
}
#ifdef __FreeBSD__
if (ucred_cache != NULL)
crfree(ucred_cache);
#endif
return (retval);
pullup_failed:
if (V_fw_verbose)
printf("ipfw: pullup failed\n");
return (IP_FW_DENY);
}
#ifdef SYSCTL_NODE
static int
sysctl_ipfw_table_num(SYSCTL_HANDLER_ARGS)
{
int error;
unsigned int ntables;
ntables = V_fw_tables_max;
error = sysctl_handle_int(oidp, &ntables, 0, req);
if ((error != 0) || (req->newptr == NULL))
return (error);
return (ipfw_resize_tables(&V_layer3_chain, ntables));
}
static int
sysctl_ipfw_tables_sets(SYSCTL_HANDLER_ARGS)
{
int error;
unsigned int sets;
sets = V_fw_tables_sets;
error = sysctl_handle_int(oidp, &sets, 0, req);
if ((error != 0) || (req->newptr == NULL))
return (error);
return (ipfw_switch_tables_namespace(&V_layer3_chain, sets));
}
#endif
static int
ipfw_init(void)
{
int error = 0;
printf("ipfw2 "
#ifdef INET6
"(+ipv6) "
#endif
"initialized, divert %s, nat %s, "
"default to %s, logging ",
#ifdef IPDIVERT
"enabled",
#else
"loadable",
#endif
#ifdef IPFIREWALL_NAT
"enabled",
#else
"loadable",
#endif
default_to_accept ? "accept" : "deny");
if (V_fw_verbose == 0)
printf("disabled\n");
else if (V_verbose_limit == 0)
printf("unlimited\n");
else
printf("limited to %d packets/entry by default\n",
V_verbose_limit);
if (default_fw_tables > IPFW_TABLES_MAX)
default_fw_tables = IPFW_TABLES_MAX;
ipfw_init_sopt_handler();
ipfw_init_obj_rewriter();
ipfw_iface_init();
return (error);
}
static void
ipfw_destroy(void)
{
ipfw_iface_destroy();
ipfw_destroy_sopt_handler();
ipfw_destroy_obj_rewriter();
printf("IP firewall unloaded\n");
}
static int
vnet_ipfw_init(const void *unused)
{
int error, first;
struct ip_fw *rule = NULL;
struct ip_fw_chain *chain;
chain = &V_layer3_chain;
first = IS_DEFAULT_VNET(curvnet) ? 1 : 0;
V_autoinc_step = 100;
V_fw_deny_unknown_exthdrs = 1;
#ifdef IPFIREWALL_VERBOSE
V_fw_verbose = 1;
#endif
#ifdef IPFIREWALL_VERBOSE_LIMIT
V_verbose_limit = IPFIREWALL_VERBOSE_LIMIT;
#endif
#ifdef IPFIREWALL_NAT
LIST_INIT(&chain->nat);
#endif
ipfw_init_srv(chain);
ipfw_init_counters();
V_fw_tables_max = default_fw_tables;
error = ipfw_init_tables(chain, first);
if (error) {
printf("ipfw2: setting up tables failed\n");
free(chain->map, M_IPFW);
free(rule, M_IPFW);
return (ENOSPC);
}
IPFW_LOCK_INIT(chain);
ipfw_dyn_init(chain);
rule = ipfw_alloc_rule(chain, sizeof(struct ip_fw));
rule->flags |= IPFW_RULE_NOOPT;
rule->cmd_len = 1;
rule->cmd[0].len = 1;
rule->cmd[0].opcode = default_to_accept ? O_ACCEPT : O_DENY;
chain->default_rule = rule;
ipfw_add_protected_rule(chain, rule, 0);
ipfw_eaction_init(chain, first);
ipfw_init_skipto_cache(chain);
ipfw_bpf_init(first);
V_ipfw_vnet_ready = 1;
V_ip_fw_ctl_ptr = ipfw_ctl3;
error = ipfw_attach_hooks();
return (error);
}
static int
vnet_ipfw_uninit(const void *unused)
{
struct ip_fw *reap;
struct ip_fw_chain *chain = &V_layer3_chain;
int i, last;
V_ipfw_vnet_ready = 0;
ipfw_detach_hooks();
V_ip_fw_ctl_ptr = NULL;
last = IS_DEFAULT_VNET(curvnet) ? 1 : 0;
IPFW_UH_WLOCK(chain);
IPFW_UH_WUNLOCK(chain);
ipfw_dyn_uninit(0);
IPFW_UH_WLOCK(chain);
reap = NULL;
IPFW_WLOCK(chain);
for (i = 0; i < chain->n_rules; i++)
ipfw_reap_add(chain, &reap, chain->map[i]);
free(chain->map, M_IPFW);
ipfw_destroy_skipto_cache(chain);
IPFW_WUNLOCK(chain);
IPFW_UH_WUNLOCK(chain);
ipfw_destroy_tables(chain, last);
ipfw_eaction_uninit(chain, last);
if (reap != NULL)
ipfw_reap_rules(reap);
vnet_ipfw_iface_destroy(chain);
ipfw_destroy_srv(chain);
IPFW_LOCK_DESTROY(chain);
ipfw_dyn_uninit(1);
ipfw_destroy_counters();
ipfw_bpf_uninit(last);
return (0);
}
static int
ipfw_modevent(module_t mod, int type, void *unused)
{
int err = 0;
switch (type) {
case MOD_LOAD:
break;
case MOD_QUIESCE:
break;
case MOD_UNLOAD:
break;
case MOD_SHUTDOWN:
break;
default:
err = EOPNOTSUPP;
break;
}
return err;
}
static moduledata_t ipfwmod = {
"ipfw",
ipfw_modevent,
0
};
#define IPFW_SI_SUB_FIREWALL SI_SUB_PROTO_FIREWALL
#define IPFW_MODEVENT_ORDER (SI_ORDER_ANY - 255)
#define IPFW_MODULE_ORDER (IPFW_MODEVENT_ORDER + 1)
#define IPFW_VNET_ORDER (IPFW_MODEVENT_ORDER + 2)
DECLARE_MODULE(ipfw, ipfwmod, IPFW_SI_SUB_FIREWALL, IPFW_MODEVENT_ORDER);
FEATURE(ipfw_ctl3, "ipfw new sockopt calls");
MODULE_VERSION(ipfw, 3);
SYSINIT(ipfw_init, IPFW_SI_SUB_FIREWALL, IPFW_MODULE_ORDER,
ipfw_init, NULL);
VNET_SYSINIT(vnet_ipfw_init, IPFW_SI_SUB_FIREWALL, IPFW_VNET_ORDER,
vnet_ipfw_init, NULL);
SYSUNINIT(ipfw_destroy, IPFW_SI_SUB_FIREWALL, IPFW_MODULE_ORDER,
ipfw_destroy, NULL);
VNET_SYSUNINIT(vnet_ipfw_uninit, IPFW_SI_SUB_FIREWALL, IPFW_VNET_ORDER,
vnet_ipfw_uninit, NULL);