/*
- * Copyright (c) 2009-2013 Apple Inc. All rights reserved.
+ * Copyright (c) 2009-2020 Apple Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
- *
+ *
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* unlawful or unlicensed copies of an Apple operating system, or to
* circumvent, violate, or enable the circumvention or violation of, any
* terms of an Apple operating system software license agreement.
- *
+ *
* Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this file.
- *
+ *
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
- *
+ *
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
*/
#if IPSEC
#include <netinet6/ipsec.h>
-#if INET6
#include <netinet6/ipsec6.h>
-#endif
#include <netkey/key.h>
extern int ipsec_bypass;
#endif /* IPSEC */
-#include <netinet6/ip6_fw.h>
-
#include <net/net_osdep.h>
+#if DUMMYNET
+#include <netinet/ip_dummynet.h>
+#endif /* DUMMYNET */
+
#if PF
#include <net/pfvar.h>
#endif /* PF */
int error, type = 0, code = 0;
boolean_t proxy = FALSE;
struct mbuf *mcopy = NULL;
- struct ifnet *ifp, *rcvifp, *origifp; /* maybe unnecessary */
- u_int32_t inzone, outzone, len;
+ struct ifnet *ifp, *rcvifp, *origifp; /* maybe unnecessary */
+ u_int32_t inzone, outzone, len = 0, pktcnt = 0;
struct in6_addr src_in6, dst_in6;
uint64_t curtime = net_uptime();
#if IPSEC
unsigned int ifscope = IFSCOPE_NONE;
#if PF
struct pf_mtag *pf_mtag;
+ struct pf_fragment_tag *pf_ftagp, pf_ftag;
+ boolean_t pf_ftag_valid = FALSE;
#endif /* PF */
+ uint32_t mpktlen = 0;
/*
* In the prefix proxying case, the route to the proxied node normally
if (!srcrt && nd6_prproxy &&
(rt = ip6forward_rt->ro_rt) != NULL && (rt->rt_flags & RTF_PROXY)) {
nd6_proxy_find_fwdroute(m->m_pkthdr.rcvif, ip6forward_rt);
- if ((rt = ip6forward_rt->ro_rt) != NULL)
+ if ((rt = ip6forward_rt->ro_rt) != NULL) {
ifscope = rt->rt_ifp->if_index;
+ }
}
#if PF
pf_mtag = pf_find_mtag(m);
- if (pf_mtag != NULL && pf_mtag->pftag_rtableid != IFSCOPE_NONE)
+ /*
+ * save the PF fragmentation metadata as m_copy() removes the
+ * mbufs tags from the original mbuf.
+ */
+ pf_ftagp = pf_find_fragment_tag(m);
+ if (pf_ftagp != NULL) {
+ ASSERT(pf_mtag->pftag_flags & PF_TAG_REASSEMBLED);
+ pf_ftag = *pf_ftagp;
+ pf_ftag_valid = TRUE;
+ mpktlen = pf_ftag.ft_maxlen;
+ ASSERT(mpktlen);
+ }
+ if (pf_mtag != NULL && pf_mtag->pftag_rtableid != IFSCOPE_NONE) {
ifscope = pf_mtag->pftag_rtableid;
-
+ }
+ pf_mtag = NULL;
+ pf_ftagp = NULL;
/*
* If the caller provides a route which is on a different interface
* than the one specified for scoped forwarding, discard the route
if (ipsec6_in_reject(m, NULL)) {
IPSEC_STAT_INCREMENT(ipsec6stat.in_polvio);
m_freem(m);
- return (NULL);
+ return NULL;
}
}
#endif /*IPSEC*/
* Do not forward packets with unspecified source. It was discussed
* in July 2000, on ipngwg mailing list.
*/
- if ((m->m_flags & (M_BCAST|M_MCAST)) != 0 ||
+ if ((m->m_flags & (M_BCAST | M_MCAST)) != 0 ||
IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) ||
IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src)) {
ip6stat.ip6s_cantforward++;
if_name(m->m_pkthdr.rcvif));
}
m_freem(m);
- return (NULL);
+ return NULL;
}
if (ip6->ip6_hlim <= IPV6_HLIMDEC) {
/* XXX in6_ifstat_inc(rt->rt_ifp, ifs6_in_discard) */
icmp6_error_flag(m, ICMP6_TIME_EXCEEDED,
- ICMP6_TIME_EXCEED_TRANSIT, 0, 0);
- return (NULL);
+ ICMP6_TIME_EXCEED_TRANSIT, 0, 0);
+ return NULL;
}
/*
* Also skip IPsec forwarding path processing as this
* packet is not to be forwarded.
*/
- if (proxy)
+ if (proxy) {
goto skip_ipsec;
+ }
}
ip6->ip6_hlim -= IPV6_HLIMDEC;
mcopy = m_copy(m, 0, imin(m->m_pkthdr.len, ICMPV6_PLD_MAXLEN));
#if IPSEC
- if (ipsec_bypass != 0)
+ if (ipsec_bypass != 0) {
goto skip_ipsec;
+ }
/* get a security policy for this packet */
sp = ipsec6_getpolicybyaddr(m, IPSEC_DIR_OUTBOUND, IP_FORWARDING,
&error);
#endif
}
m_freem(m);
- return (NULL);
+ return NULL;
}
error = 0;
/* check policy */
switch (sp->policy) {
case IPSEC_POLICY_DISCARD:
- case IPSEC_POLICY_GENERATE:
+ case IPSEC_POLICY_GENERATE:
/*
* This packet is just discarded.
*/
#endif
}
m_freem(m);
- return (NULL);
+ return NULL;
case IPSEC_POLICY_BYPASS:
case IPSEC_POLICY_NONE:
#endif
}
m_freem(m);
- return (NULL);
+ return NULL;
}
/* do IPsec */
break;
goto skip_ipsec;
}
- {
- struct ipsec_output_state state;
+ {
+ struct ipsec_output_state state;
- /*
- * All the extension headers will become inaccessible
- * (since they can be encrypted).
- * Don't panic, we need no more updates to extension headers
- * on inner IPv6 packet (since they are now encapsulated).
- *
- * IPv6 [ESP|AH] IPv6 [extension headers] payload
- */
- bzero(&state, sizeof(state));
- state.m = m;
- state.dst = NULL; /* update at ipsec6_output_tunnel() */
+ /*
+ * All the extension headers will become inaccessible
+ * (since they can be encrypted).
+ * Don't panic, we need no more updates to extension headers
+ * on inner IPv6 packet (since they are now encapsulated).
+ *
+ * IPv6 [ESP|AH] IPv6 [extension headers] payload
+ */
+ bzero(&state, sizeof(state));
+ state.m = m;
+ state.dst = NULL; /* update at ipsec6_output_tunnel() */
- error = ipsec6_output_tunnel(&state, sp, 0);
- key_freesp(sp, KEY_SADB_UNLOCKED);
- if (state.tunneled == 4) {
- ROUTE_RELEASE(&state.ro);
- return (NULL); /* packet is gone - sent over IPv4 */
- }
+ error = ipsec6_output_tunnel(&state, sp, 0);
+ key_freesp(sp, KEY_SADB_UNLOCKED);
+ if (state.tunneled == 4) {
+ ROUTE_RELEASE(&state.ro);
+ return NULL; /* packet is gone - sent over IPv4 */
+ }
- m = state.m;
- ROUTE_RELEASE(&state.ro);
+ m = state.m;
+ ROUTE_RELEASE(&state.ro);
- if (error) {
- /* mbuf is already reclaimed in ipsec6_output_tunnel. */
- switch (error) {
- case EHOSTUNREACH:
- case ENETUNREACH:
- case EMSGSIZE:
- case ENOBUFS:
- case ENOMEM:
- break;
- default:
- printf("ip6_output (ipsec): error code %d\n", error);
- /* fall through */
- case ENOENT:
- /* don't show these error codes to the user */
- break;
- }
- ip6stat.ip6s_cantforward++;
- if (mcopy) {
+ if (error) {
+ /* mbuf is already reclaimed in ipsec6_output_tunnel. */
+ switch (error) {
+ case EHOSTUNREACH:
+ case ENETUNREACH:
+ case EMSGSIZE:
+ case ENOBUFS:
+ case ENOMEM:
+ break;
+ default:
+ printf("ip6_output (ipsec): error code %d\n", error);
+ OS_FALLTHROUGH;
+ case ENOENT:
+ /* don't show these error codes to the user */
+ break;
+ }
+ ip6stat.ip6s_cantforward++;
+ if (mcopy) {
#if 0
- /* XXX: what icmp ? */
+ /* XXX: what icmp ? */
#else
- m_freem(mcopy);
+ m_freem(mcopy);
#endif
+ }
+ m_freem(m);
+ return NULL;
}
- m_freem(m);
- return (NULL);
}
- }
#endif /* IPSEC */
- skip_ipsec:
+skip_ipsec:
dst = (struct sockaddr_in6 *)&ip6forward_rt->ro_dst;
if ((rt = ip6forward_rt->ro_rt) != NULL) {
if (rt == NULL) {
ip6stat.ip6s_noroute++;
in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_noroute);
- if (mcopy)
+ if (mcopy) {
icmp6_error(mcopy, ICMP6_DST_UNREACH,
- ICMP6_DST_UNREACH_NOROUTE, 0);
+ ICMP6_DST_UNREACH_NOROUTE, 0);
+ }
m_freem(m);
- return (NULL);
+ return NULL;
}
RT_LOCK_ASSERT_HELD(rt);
} else if (ROUTE_UNUSABLE(ip6forward_rt) ||
if ((rt = ip6forward_rt->ro_rt) == NULL) {
ip6stat.ip6s_noroute++;
in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_noroute);
- if (mcopy)
+ if (mcopy) {
icmp6_error(mcopy, ICMP6_DST_UNREACH,
ICMP6_DST_UNREACH_NOROUTE, 0);
+ }
m_freem(m);
- return (NULL);
+ return NULL;
}
RT_LOCK(rt);
/* Take an extra ref for ourselves */
ip6stat.ip6s_cantforward++;
ip6stat.ip6s_badscope++;
m_freem(m);
- return (NULL);
+ return NULL;
}
if (in6_setscope(&src_in6, m->m_pkthdr.rcvif, &inzone)) {
RT_REMREF_LOCKED(rt);
ip6stat.ip6s_cantforward++;
ip6stat.ip6s_badscope++;
m_freem(m);
- return (NULL);
+ return NULL;
}
if (inzone != outzone && !proxy) {
RT_UNLOCK(rt);
if (mcopy) {
icmp6_error(mcopy, ICMP6_DST_UNREACH,
- ICMP6_DST_UNREACH_BEYONDSCOPE, 0);
+ ICMP6_DST_UNREACH_BEYONDSCOPE, 0);
}
m_freem(m);
- return (NULL);
+ return NULL;
}
/*
ip6stat.ip6s_cantforward++;
ip6stat.ip6s_badscope++;
m_freem(m);
- return (NULL);
+ return NULL;
+ }
+
+ if (mpktlen == 0) {
+ mpktlen = m->m_pkthdr.len;
}
- if (m->m_pkthdr.len > rt->rt_ifp->if_mtu) {
+ if (mpktlen > rt->rt_ifp->if_mtu) {
in6_ifstat_inc(rt->rt_ifp, ifs6_in_toobig);
if (mcopy) {
uint32_t mtu;
* encapsulated packet as "rt->rt_ifp".
*/
sp2 = ipsec6_getpolicybyaddr(mcopy, IPSEC_DIR_OUTBOUND,
- IP_FORWARDING, &ipsecerror);
+ IP_FORWARDING, &ipsecerror);
if (sp2) {
ipsechdrsiz = ipsec6_hdrsiz(mcopy,
- IPSEC_DIR_OUTBOUND, NULL);
- if (ipsechdrsiz < mtu)
+ IPSEC_DIR_OUTBOUND, NULL);
+ if (ipsechdrsiz < mtu) {
mtu -= ipsechdrsiz;
+ }
key_freesp(sp2, KEY_SADB_UNLOCKED);
}
/*
* if mtu becomes less than minimum MTU,
* tell minimum MTU (and I'll need to fragment it).
*/
- if (mtu < IPV6_MMTU)
+ if (mtu < IPV6_MMTU) {
mtu = IPV6_MMTU;
+ }
#endif
/* Release extra ref */
RT_REMREF_LOCKED(rt);
RT_UNLOCK(rt);
}
m_freem(m);
- return (NULL);
- }
+ return NULL;
+ }
- if (rt->rt_flags & RTF_GATEWAY)
+ if (rt->rt_flags & RTF_GATEWAY) {
dst = (struct sockaddr_in6 *)(void *)rt->rt_gateway;
+ }
/*
* If we are to forward the packet using the same interface
*/
if (!proxy &&
ip6_sendredirects && rt->rt_ifp == m->m_pkthdr.rcvif && !srcrt &&
- (rt->rt_flags & (RTF_DYNAMIC|RTF_MODIFIED)) == 0) {
+ (rt->rt_flags & (RTF_DYNAMIC | RTF_MODIFIED)) == 0) {
if ((rt->rt_ifp->if_flags & IFF_POINTOPOINT) != 0) {
/*
* If the incoming interface is equal to the outgoing
* type/code is based on suggestion by Rich Draves.
* not sure if it is the best pick.
*/
- RT_REMREF_LOCKED(rt); /* Release extra ref */
+ RT_REMREF_LOCKED(rt); /* Release extra ref */
RT_UNLOCK(rt);
icmp6_error(mcopy, ICMP6_DST_UNREACH,
- ICMP6_DST_UNREACH_ADDR, 0);
+ ICMP6_DST_UNREACH_ADDR, 0);
m_freem(m);
- return (NULL);
+ return NULL;
}
type = ND_REDIRECT;
}
-
-#if IPFW2
- /*
- * Check with the firewall...
- */
- if (ip6_fw_enable && ip6_fw_chk_ptr) {
- u_short port = 0;
- ifp = rt->rt_ifp;
- /* Drop the lock but retain the extra ref */
- RT_UNLOCK(rt);
- /* If ipfw says divert, we have to just drop packet */
- if (ip6_fw_chk_ptr(&ip6, ifp, &port, &m)) {
- m_freem(m);
- goto freecopy;
- }
- if (!m) {
- goto freecopy;
- }
- /* We still have the extra ref on rt */
- RT_LOCK(rt);
- }
-#endif
-
/*
* Fake scoped addresses. Note that even link-local source or
* destinaion can appear, if the originating node just sends the
#if 1
if ((0))
#else
- if ((rt->rt_flags & (RTF_BLACKHOLE|RTF_REJECT)) == 0)
+ if ((rt->rt_flags & (RTF_BLACKHOLE | RTF_REJECT)) == 0)
#endif
{
printf("ip6_forward: outgoing interface is loopback. "
- "src %s, dst %s, nxt %d, rcvif %s, outif %s\n",
- ip6_sprintf(&ip6->ip6_src),
- ip6_sprintf(&ip6->ip6_dst),
- ip6->ip6_nxt, if_name(m->m_pkthdr.rcvif),
- if_name(rt->rt_ifp));
+ "src %s, dst %s, nxt %d, rcvif %s, outif %s\n",
+ ip6_sprintf(&ip6->ip6_src),
+ ip6_sprintf(&ip6->ip6_dst),
+ ip6->ip6_nxt, if_name(m->m_pkthdr.rcvif),
+ if_name(rt->rt_ifp));
}
/* we can just use rcvif in forwarding. */
VERIFY(m->m_pkthdr.pkt_flags & PKTF_PROXY_DST);
/* Release extra ref */
RT_REMREF(rt);
- if (mcopy != NULL)
+ if (mcopy != NULL) {
m_freem(mcopy);
- return (m);
+ }
+ return m;
}
+ /* Mark this packet as being forwarded from another interface */
+ m->m_pkthdr.pkt_flags |= PKTF_FORWARDED;
+
#if PF
- /* Invoke outbound packet filter */
- error = pf_af_hook(ifp, NULL, &m, AF_INET6, FALSE, NULL);
+ if (PF_IS_ENABLED) {
+ /*
+ * PF refragments any packet which it reassembled due to scrub
+ * rules, in which case it will set the PF_TAG_REFRAGMENTED
+ * flag in PF mbuf tag.
+ */
+ if (pf_ftag_valid) {
+ pf_copy_fragment_tag(m, &pf_ftag, M_DONTWAIT);
+ }
+#if DUMMYNET
+ struct ip_fw_args args;
+ bzero(&args, sizeof(args));
+
+ args.fwa_m = m;
+ args.fwa_oif = ifp;
+ args.fwa_oflags = 0;
+ args.fwa_ro6 = ip6forward_rt;
+ args.fwa_ro6_pmtu = ip6forward_rt;
+ args.fwa_mtu = rt->rt_ifp->if_mtu;
+ args.fwa_dst6 = dst;
+ args.fwa_origifp = origifp;
+ /* Invoke outbound packet filter */
+ error = pf_af_hook(ifp, NULL, &m, AF_INET6, FALSE, &args);
+#else /* !DUMMYNET */
+ error = pf_af_hook(ifp, NULL, &m, AF_INET6, FALSE, NULL);
+#endif /* !DUMMYNET */
+ if (error != 0 || m == NULL) {
+ if (m != NULL) {
+ panic("%s: unexpected packet %p\n", __func__, m);
+ /* NOTREACHED */
+ }
+ /* Already freed by callee */
+ goto senderr;
+ }
- if (error != 0 || m == NULL) {
- if (m != NULL) {
- panic("%s: unexpected packet %p\n", __func__, m);
- /* NOTREACHED */
+ pf_mtag = pf_find_mtag(m);
+ /*
+ * refragmented packets from PF.
+ */
+ if ((pf_mtag->pftag_flags & PF_TAG_REFRAGMENTED) != 0) {
+ struct mbuf *t;
+
+ pf_mtag->pftag_flags &= ~PF_TAG_REFRAGMENTED;
+ /* for statistics */
+ t = m;
+ while (t != NULL) {
+ pktcnt++;
+ len += m_pktlen(t);
+ t = t->m_nextpkt;
+ }
+
+ /*
+ * nd6_output() frees packetchain in both success and
+ * failure cases.
+ */
+ error = nd6_output(ifp, origifp, m, dst, rt, NULL);
+ m = NULL;
+ goto sent;
}
- /* Already freed by callee */
- goto senderr;
+ /*
+ * We do not use ip6 header again in the code below,
+ * however still adding the bit here so that any new
+ * code in future doesn't end up working with the
+ * wrong pointer
+ */
+ ip6 = mtod(m, struct ip6_hdr *);
}
- ip6 = mtod(m, struct ip6_hdr *);
#endif /* PF */
- /* Mark this packet as being forwarded from another interface */
- m->m_pkthdr.pkt_flags |= PKTF_FORWARDED;
len = m_pktlen(m);
-
+ pktcnt = 1;
error = nd6_output(ifp, origifp, m, dst, rt, NULL);
+sent:
if (error) {
- in6_ifstat_inc(ifp, ifs6_out_discard);
- ip6stat.ip6s_cantforward++;
+ in6_ifstat_add(ifp, ifs6_out_discard, pktcnt);
+ ip6stat.ip6s_cantforward += pktcnt;
} else {
/*
* Increment stats on the source interface; the ones
* for destination interface has been taken care of
* during output above by virtue of PKTF_FORWARDED.
*/
- rcvifp->if_fpackets++;
+ rcvifp->if_fpackets += pktcnt;
rcvifp->if_fbytes += len;
- ip6stat.ip6s_forward++;
- in6_ifstat_inc(ifp, ifs6_out_forward);
- if (type)
+ ip6stat.ip6s_forward += pktcnt;
+ in6_ifstat_add(ifp, ifs6_out_forward, pktcnt);
+ if (type) {
ip6stat.ip6s_redirectsent++;
- else {
+ } else {
if (mcopy) {
goto freecopy;
}
if (mcopy == NULL) {
/* Release extra ref */
RT_REMREF(rt);
- return (NULL);
+ return NULL;
}
switch (error) {
case 0:
icmp6_redirect_output(mcopy, rt);
/* Release extra ref */
RT_REMREF(rt);
- return (NULL);
+ return NULL;
}
#endif
goto freecopy;
/* Tell source to slow down like source quench in IP? */
goto freecopy;
- case ENETUNREACH: /* shouldn't happen, checked above */
+ case ENETUNREACH: /* shouldn't happen, checked above */
case EHOSTUNREACH:
case ENETDOWN:
case EHOSTDOWN:
icmp6_error(mcopy, type, code, 0);
/* Release extra ref */
RT_REMREF(rt);
- return (NULL);
+ return NULL;
- freecopy:
+freecopy:
m_freem(mcopy);
/* Release extra ref */
RT_REMREF(rt);
- return (NULL);
+ return NULL;
}