diff options
Diffstat (limited to 'freebsd/sys/netpfil/ipfw/ip_fw_pfil.c')
-rw-r--r-- | freebsd/sys/netpfil/ipfw/ip_fw_pfil.c | 210 |
1 files changed, 167 insertions, 43 deletions
diff --git a/freebsd/sys/netpfil/ipfw/ip_fw_pfil.c b/freebsd/sys/netpfil/ipfw/ip_fw_pfil.c index d2e1b448..59c13aa5 100644 --- a/freebsd/sys/netpfil/ipfw/ip_fw_pfil.c +++ b/freebsd/sys/netpfil/ipfw/ip_fw_pfil.c @@ -49,6 +49,7 @@ __FBSDID("$FreeBSD$"); #include <net/if.h> #include <net/route.h> +#include <net/ethernet.h> #include <net/pfil.h> #include <net/vnet.h> @@ -60,6 +61,7 @@ __FBSDID("$FreeBSD$"); #ifdef INET6 #include <netinet/ip6.h> #include <netinet6/ip6_var.h> +#include <netinet6/scope6_var.h> #endif #include <netgraph/ng_ipfw.h> @@ -76,26 +78,39 @@ static VNET_DEFINE(int, fw6_enable) = 1; #define V_fw6_enable VNET(fw6_enable) #endif +static VNET_DEFINE(int, fwlink_enable) = 0; +#define V_fwlink_enable VNET(fwlink_enable) + int ipfw_chg_hook(SYSCTL_HANDLER_ARGS); /* Forward declarations. */ static int ipfw_divert(struct mbuf **, int, struct ipfw_rule_ref *, int); +int ipfw_check_packet(void *, struct mbuf **, struct ifnet *, int, + struct inpcb *); +int ipfw_check_frame(void *, struct mbuf **, struct ifnet *, int, + struct inpcb *); #ifdef SYSCTL_NODE SYSBEGIN(f1) SYSCTL_DECL(_net_inet_ip_fw); -SYSCTL_VNET_PROC(_net_inet_ip_fw, OID_AUTO, enable, - CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_SECURE3, &VNET_NAME(fw_enable), 0, - ipfw_chg_hook, "I", "Enable ipfw"); +SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, enable, + CTLFLAG_VNET | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_SECURE3, + &VNET_NAME(fw_enable), 0, ipfw_chg_hook, "I", "Enable ipfw"); #ifdef INET6 SYSCTL_DECL(_net_inet6_ip6_fw); -SYSCTL_VNET_PROC(_net_inet6_ip6_fw, OID_AUTO, enable, - CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_SECURE3, &VNET_NAME(fw6_enable), 0, - ipfw_chg_hook, "I", "Enable ipfw+6"); +SYSCTL_PROC(_net_inet6_ip6_fw, OID_AUTO, enable, + CTLFLAG_VNET | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_SECURE3, + &VNET_NAME(fw6_enable), 0, ipfw_chg_hook, "I", "Enable ipfw+6"); #endif /* INET6 */ +SYSCTL_DECL(_net_link_ether); +SYSCTL_PROC(_net_link_ether, OID_AUTO, ipfw, + CTLFLAG_VNET | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_SECURE3, + &VNET_NAME(fwlink_enable), 0, ipfw_chg_hook, "I", + "Pass ether pkts through firewall"); + SYSEND #endif /* SYSCTL_NODE */ @@ -106,7 +121,7 @@ SYSEND * The packet may be consumed. */ int -ipfw_check_hook(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, +ipfw_check_packet(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, struct inpcb *inp) { struct ip_fw_args args; @@ -114,10 +129,6 @@ ipfw_check_hook(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, int ipfw; int ret; - /* all the processing now uses ip_len in net format */ - if (mtod(*m0, struct ip *)->ip_v == 4) - SET_NET_IPLEN(mtod(*m0, struct ip *)); - /* convert dir to IPFW values */ dir = (dir == PFIL_IN) ? DIR_IN : DIR_OUT; bzero(&args, sizeof(args)); @@ -131,11 +142,8 @@ again: if (tag != NULL) { args.rule = *((struct ipfw_rule_ref *)(tag+1)); m_tag_delete(*m0, tag); - if (args.rule.info & IPFW_ONEPASS) { - if (mtod(*m0, struct ip *)->ip_v == 4) - SET_HOST_IPLEN(mtod(*m0, struct ip *)); + if (args.rule.info & IPFW_ONEPASS) return (0); - } } args.m = *m0; @@ -192,8 +200,20 @@ again: } #ifdef INET6 if (args.next_hop6 != NULL) { - bcopy(args.next_hop6, (fwd_tag+1), len); - if (in6_localip(&args.next_hop6->sin6_addr)) + struct sockaddr_in6 *sa6; + + sa6 = (struct sockaddr_in6 *)(fwd_tag + 1); + bcopy(args.next_hop6, sa6, len); + /* + * If nh6 address is link-local we should convert + * it to kernel internal form before doing any + * comparisons. + */ + if (sa6_embedscope(sa6, V_ip6_use_defzone) != 0) { + ret = EACCES; + break; + } + if (in6_localip(&sa6->sin6_addr)) (*m0)->m_flags |= M_FASTFWD_OURS; (*m0)->m_flags |= M_IP6_NEXTHOP; } @@ -279,8 +299,112 @@ again: FREE_PKT(*m0); *m0 = NULL; } - if (*m0 && mtod(*m0, struct ip *)->ip_v == 4) - SET_HOST_IPLEN(mtod(*m0, struct ip *)); + + return ret; +} + +/* + * ipfw processing for ethernet packets (in and out). + * Inteface is NULL from ether_demux, and ifp from + * ether_output_frame. + */ +int +ipfw_check_frame(void *arg, struct mbuf **m0, struct ifnet *dst, int dir, + struct inpcb *inp) +{ + struct ether_header *eh; + struct ether_header save_eh; + struct mbuf *m; + int i, ret; + struct ip_fw_args args; + struct m_tag *mtag; + + /* fetch start point from rule, if any */ + mtag = m_tag_locate(*m0, MTAG_IPFW_RULE, 0, NULL); + if (mtag == NULL) { + args.rule.slot = 0; + } else { + /* dummynet packet, already partially processed */ + struct ipfw_rule_ref *r; + + /* XXX can we free it after use ? */ + mtag->m_tag_id = PACKET_TAG_NONE; + r = (struct ipfw_rule_ref *)(mtag + 1); + if (r->info & IPFW_ONEPASS) + return (0); + args.rule = *r; + } + + /* I need some amt of data to be contiguous */ + m = *m0; + i = min(m->m_pkthdr.len, max_protohdr); + if (m->m_len < i) { + m = m_pullup(m, i); + if (m == NULL) { + *m0 = m; + return (0); + } + } + eh = mtod(m, struct ether_header *); + save_eh = *eh; /* save copy for restore below */ + m_adj(m, ETHER_HDR_LEN); /* strip ethernet header */ + + args.m = m; /* the packet we are looking at */ + args.oif = dir == PFIL_OUT ? dst: NULL; /* destination, if any */ + args.next_hop = NULL; /* we do not support forward yet */ + args.next_hop6 = NULL; /* we do not support forward yet */ + args.eh = &save_eh; /* MAC header for bridged/MAC packets */ + args.inp = NULL; /* used by ipfw uid/gid/jail rules */ + i = ipfw_chk(&args); + m = args.m; + if (m != NULL) { + /* + * Restore Ethernet header, as needed, in case the + * mbuf chain was replaced by ipfw. + */ + M_PREPEND(m, ETHER_HDR_LEN, M_NOWAIT); + if (m == NULL) { + *m0 = NULL; + return (0); + } + if (eh != mtod(m, struct ether_header *)) + bcopy(&save_eh, mtod(m, struct ether_header *), + ETHER_HDR_LEN); + } + *m0 = m; + + ret = 0; + /* Check result of ipfw_chk() */ + switch (i) { + case IP_FW_PASS: + break; + + case IP_FW_DENY: + ret = EACCES; + break; /* i.e. drop */ + + case IP_FW_DUMMYNET: + ret = EACCES; + int dir; + + if (ip_dn_io_ptr == NULL) + break; /* i.e. drop */ + + *m0 = NULL; + dir = PROTO_LAYER2 | (dst ? DIR_OUT : DIR_IN); + ip_dn_io_ptr(&m, dir, &args); + return 0; + + default: + KASSERT(0, ("%s: unknown retval", __func__)); + } + + if (ret != 0) { + if (*m0) + FREE_PKT(*m0); + *m0 = NULL; + } + return ret; } @@ -303,7 +427,7 @@ ipfw_divert(struct mbuf **m0, int incoming, struct ipfw_rule_ref *rule, clone = *m0; /* use the original mbuf */ *m0 = NULL; } else { - clone = m_dup(*m0, M_DONTWAIT); + clone = m_dup(*m0, M_NOWAIT); /* If we cannot duplicate the mbuf, we sacrifice the divert * chain and continue with the tee-ed packet. */ @@ -325,7 +449,6 @@ ipfw_divert(struct mbuf **m0, int incoming, struct ipfw_rule_ref *rule, int hlen; struct mbuf *reass; - SET_HOST_IPLEN(ip); /* ip_reass wants host order */ reass = ip_reass(clone); /* Reassemble packet. */ if (reass == NULL) return 0; /* not an error */ @@ -336,7 +459,6 @@ ipfw_divert(struct mbuf **m0, int incoming, struct ipfw_rule_ref *rule, */ ip = mtod(reass, struct ip *); hlen = ip->ip_hl << 2; - SET_NET_IPLEN(ip); ip->ip_sum = 0; if (hlen == sizeof(struct ip)) ip->ip_sum = in_cksum_hdr(ip); @@ -385,13 +507,16 @@ static int ipfw_hook(int onoff, int pf) { struct pfil_head *pfh; + pfil_func_t hook_func; pfh = pfil_head_get(PFIL_TYPE_AF, pf); if (pfh == NULL) return ENOENT; + hook_func = (pf == AF_LINK) ? ipfw_check_frame : ipfw_check_packet; + (void) (onoff ? pfil_add_hook : pfil_remove_hook) - (ipfw_check_hook, NULL, PFIL_IN | PFIL_OUT | PFIL_WAITOK, pfh); + (hook_func, NULL, PFIL_IN | PFIL_OUT | PFIL_WAITOK, pfh); return 0; } @@ -415,51 +540,50 @@ ipfw_attach_hooks(int arg) printf("ipfw6_hook() error\n"); } #endif + if (arg == 0) /* detach */ + ipfw_hook(0, AF_LINK); + else if (V_fwlink_enable && ipfw_hook(1, AF_LINK) != 0) { + error = ENOENT; + printf("ipfw_link_hook() error\n"); + } return error; } int ipfw_chg_hook(SYSCTL_HANDLER_ARGS) { - int enable; - int oldenable; + int newval; int error; int af; - if (arg1 == &VNET_NAME(fw_enable)) { - enable = V_fw_enable; + if (arg1 == &V_fw_enable) af = AF_INET; - } #ifdef INET6 - else if (arg1 == &VNET_NAME(fw6_enable)) { - enable = V_fw6_enable; + else if (arg1 == &V_fw6_enable) af = AF_INET6; - } #endif + else if (arg1 == &V_fwlink_enable) + af = AF_LINK; else return (EINVAL); - oldenable = enable; - - error = sysctl_handle_int(oidp, &enable, 0, req); + newval = *(int *)arg1; + /* Handle sysctl change */ + error = sysctl_handle_int(oidp, &newval, 0, req); if (error) return (error); - enable = (enable) ? 1 : 0; + /* Formalize new value */ + newval = (newval) ? 1 : 0; - if (enable == oldenable) + if (*(int *)arg1 == newval) return (0); - error = ipfw_hook(enable, af); + error = ipfw_hook(newval, af); if (error) return (error); - if (af == AF_INET) - V_fw_enable = enable; -#ifdef INET6 - else if (af == AF_INET6) - V_fw6_enable = enable; -#endif + *(int *)arg1 = newval; return (0); } |