diff --git a/sys/conf/options b/sys/conf/options index 0c7e460c755..12bd8740546 100644 --- a/sys/conf/options +++ b/sys/conf/options @@ -982,6 +982,9 @@ PAX_ASLR_COMPAT_DELTA_STACK_DEF_LEN opt_pax.h PAX_ASLR_COMPAT_DELTA_EXEC_DEF_LEN opt_pax.h PAX_ASLR_COMPAT_DELTA_VDSO_DEF_LEN opt_pax.h +# OPNsense additions +PF_SHARE_FORWARD opt_pf.h + # Random number generator(s) # Which CSPRNG hash we get. # If Yarrow is not chosen, Fortuna is selected. diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c index 3cd8ef1db82..5e04b4ed932 100644 --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -288,6 +288,9 @@ static void pf_mtag_free(struct m_tag *); static void pf_route(struct mbuf **, struct pf_rule *, int, struct ifnet *, struct pf_state *, struct pf_pdesc *); +static void pf_route_shared(struct mbuf **, struct pf_rule *, int, + struct ifnet *, struct pf_state *, + struct pf_pdesc *); #endif /* INET */ #ifdef INET6 static void pf_change_a6(struct pf_addr *, u_int16_t *, @@ -366,6 +369,17 @@ SYSCTL_ULONG(_net_pf, OID_AUTO, states_hashsize, CTLFLAG_RDTUN, SYSCTL_ULONG(_net_pf, OID_AUTO, source_nodes_hashsize, CTLFLAG_RDTUN, &pf_srchashsize, 0, "Size of pf(4) source nodes hashtable"); +#ifdef PF_SHARE_FORWARD +static VNET_DEFINE(int, pf_share_forward) = 1; +#else +static VNET_DEFINE(int, pf_share_forward) = 0; +#endif +#define V_pf_share_forward VNET(pf_share_forward) + +SYSCTL_INT(_net_pf, OID_AUTO, share_forward, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(pf_share_forward), 0, + "If set pf(4) will share IPv4 forwarding decisions with ipfw(4)."); + VNET_DEFINE(void *, pf_swi_cookie); VNET_DEFINE(uint32_t, pf_hashseed); @@ -5393,17 +5407,19 @@ pf_routable(struct pf_addr *addr, sa_family_t af, struct pfi_kif *kif, #ifdef INET static void -pf_route(struct mbuf **m, struct pf_rule *r, int dir, struct ifnet *ifp, +pf_route(struct mbuf **m, struct pf_rule *r, int dir, struct ifnet *oifp, struct pf_state *s, struct pf_pdesc *pd) { - struct mbuf *m0; + struct mbuf *m0, *m1; struct sockaddr_in dst; struct ip *ip; + struct ifnet *ifp = NULL; struct pf_addr naddr; struct pf_src_node *sn = NULL; int error = 0; + uint16_t ip_len, ip_off; - KASSERT(m && *m && r && ifp, ("%s: invalid parameters", __func__)); + KASSERT(m && *m && r && oifp, ("%s: invalid parameters", __func__)); KASSERT(dir == PF_IN || dir == PF_OUT, ("%s: invalid direction", __func__)); @@ -5476,9 +5492,189 @@ pf_route(struct mbuf **m, struct pf_rule *r, int dir, struct ifnet *ifp, if (ifp == NULL) goto bad; - if (ip_set_fwdtag(m0, &dst, ifp->if_index)) + if (oifp != ifp) { + if (pf_test(PF_OUT, ifp, &m0, NULL) != PF_PASS) + goto bad; + else if (m0 == NULL) + goto done; + if (m0->m_len < sizeof(struct ip)) { + DPFPRINTF(PF_DEBUG_URGENT, + ("%s: m0->m_len < sizeof(struct ip)\n", __func__)); + goto bad; + } + ip = mtod(m0, struct ip *); + } + + if (ifp->if_flags & IFF_LOOPBACK) + m0->m_flags |= M_SKIP_FIREWALL; + + ip_len = ntohs(ip->ip_len); + ip_off = ntohs(ip->ip_off); + + /* Copied from FreeBSD 10.0-CURRENT ip_output. */ + m0->m_pkthdr.csum_flags |= CSUM_IP; + if (m0->m_pkthdr.csum_flags & CSUM_DELAY_DATA & ~ifp->if_hwassist) { + in_delayed_cksum(m0); + m0->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA; + } +#ifdef SCTP + if (m0->m_pkthdr.csum_flags & CSUM_SCTP & ~ifp->if_hwassist) { + sctp_delayed_cksum(m, (uint32_t)(ip->ip_hl << 2)); + m0->m_pkthdr.csum_flags &= ~CSUM_SCTP; + } +#endif + + /* + * If small enough for interface, or the interface will take + * care of the fragmentation for us, we can just send directly. + */ + if (ip_len <= ifp->if_mtu || + (m0->m_pkthdr.csum_flags & ifp->if_hwassist & CSUM_TSO) != 0) { + ip->ip_sum = 0; + if (m0->m_pkthdr.csum_flags & CSUM_IP & ~ifp->if_hwassist) { + ip->ip_sum = in_cksum(m0, ip->ip_hl << 2); + m0->m_pkthdr.csum_flags &= ~CSUM_IP; + } + m_clrprotoflags(m0); /* Avoid confusing lower layers. */ + error = (*ifp->if_output)(ifp, m0, sintosa(&dst), NULL); + goto done; + } + + /* Balk when DF bit is set or the interface didn't support TSO. */ + if ((ip_off & IP_DF) || (m0->m_pkthdr.csum_flags & CSUM_TSO)) { + error = EMSGSIZE; + KMOD_IPSTAT_INC(ips_cantfrag); + if (r->rt != PF_DUPTO) { + icmp_error(m0, ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG, 0, + ifp->if_mtu); + goto done; + } else + goto bad; + } + + error = ip_fragment(ip, &m0, ifp->if_mtu, ifp->if_hwassist); + if (error) goto bad; + for (; m0; m0 = m1) { + m1 = m0->m_nextpkt; + m0->m_nextpkt = NULL; + if (error == 0) { + m_clrprotoflags(m0); + error = (*ifp->if_output)(ifp, m0, sintosa(&dst), NULL); + } else + m_freem(m0); + } + + if (error == 0) + KMOD_IPSTAT_INC(ips_fragmented); + +done: + if (r->rt != PF_DUPTO) + *m = NULL; + return; + +bad_locked: + if (s) + PF_STATE_UNLOCK(s); +bad: + m_freem(m0); + goto done; +} + +static void +pf_route_shared(struct mbuf **m, struct pf_rule *r, int dir, struct ifnet *ifp, + struct pf_state *s, struct pf_pdesc *pd) +{ + struct mbuf *m0; + struct sockaddr_in dst; + struct ip *ip; + struct pf_addr naddr; + struct pf_src_node *sn = NULL; + int error = 0; + + KASSERT(m && *m && r && ifp, ("%s: invalid parameters", __func__)); + KASSERT(dir == PF_IN || dir == PF_OUT, ("%s: invalid direction", + __func__)); + + if ((pd->pf_mtag == NULL && + ((pd->pf_mtag = pf_get_mtag(*m)) == NULL)) || + pd->pf_mtag->routed++ > 3) { + m0 = *m; + *m = NULL; + goto bad_locked; + } + + if (r->rt == PF_DUPTO) { + if ((m0 = m_dup(*m, M_NOWAIT)) == NULL) { + if (s) + PF_STATE_UNLOCK(s); + return; + } + } else { + if ((r->rt == PF_REPLYTO) == (r->direction == dir)) { + if (s) + PF_STATE_UNLOCK(s); + return; + } + m0 = *m; + } + + /* retain old behaviour by avoiding a rewrite */ + if (IP_HAS_NEXTHOP(m0)) { + if (s) + PF_STATE_UNLOCK(s); + return; + } + + ip = mtod(m0, struct ip *); + + bzero(&dst, sizeof(dst)); + dst.sin_family = AF_INET; + dst.sin_len = sizeof(dst); + dst.sin_addr = ip->ip_dst; + + if (r->rt == PF_FASTROUTE) { + struct nhop4_basic nh4; + + if (s) + PF_STATE_UNLOCK(s); + + if (fib4_lookup_nh_basic(M_GETFIB(m0), ip->ip_dst, 0, + m0->m_pkthdr.flowid, &nh4) != 0) { + KMOD_IPSTAT_INC(ips_noroute); + error = EHOSTUNREACH; + goto bad; + } + + ifp = nh4.nh_ifp; + dst.sin_addr = nh4.nh_addr; + } else { + if (TAILQ_EMPTY(&r->rpool.list)) { + DPFPRINTF(PF_DEBUG_URGENT, + ("%s: TAILQ_EMPTY(&r->rpool.list)\n", __func__)); + goto bad_locked; + } + if (s == NULL) { + pf_map_addr(AF_INET, r, (struct pf_addr *)&ip->ip_src, + &naddr, NULL, &sn); + if (!PF_AZERO(&naddr, AF_INET)) + dst.sin_addr.s_addr = naddr.v4.s_addr; + ifp = r->rpool.cur->kif ? + r->rpool.cur->kif->pfik_ifp : NULL; + } else { + if (!PF_AZERO(&s->rt_addr, AF_INET)) + dst.sin_addr.s_addr = + s->rt_addr.v4.s_addr; + ifp = s->rt_kif ? s->rt_kif->pfik_ifp : NULL; + PF_STATE_UNLOCK(s); + } + } + if (ifp == NULL) + goto bad; + + if (ip_set_fwdtag(m0, &dst, ifp->if_index)) + goto bad; done: if (r->rt == PF_DUPTO && IP_HAS_NEXTHOP(m0)) @@ -5778,12 +5974,25 @@ pf_test(int dir, struct ifnet *ifp, struct mbuf **m0, struct inpcb *inp) struct pf_ruleset *ruleset = NULL; struct pf_pdesc pd; int off, dirndx, pqid = 0; + int share_forward = V_pf_share_forward; + u_short ifidx; M_ASSERTPKTHDR(m); if (!V_pf_status.running) return (PF_PASS); + /* restore the correct forwarding interface */ + if (share_forward && dir == PF_OUT && IP_HAS_NEXTHOP(*m0) && + !ip_get_fwdtag(*m0, NULL, &ifidx)) { + if (ifidx != 0) { + struct ifnet *nifp = ifnet_byindex(ifidx); + if (nifp != NULL) { + ifp = nifp; + } + } + } + memset(&pd, 0, sizeof(pd)); kif = (struct pfi_kif *)ifp->if_pf_kif; @@ -6139,7 +6348,11 @@ done: default: /* pf_route() returns unlocked. */ if (r->rt) { - pf_route(m0, r, dir, kif->pfik_ifp, s, &pd); + if (!share_forward) + pf_route(m0, r, dir, kif->pfik_ifp, s, &pd); + else + pf_route_shared(m0, r, dir, kif->pfik_ifp, s, + &pd); return (action); } break;