X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/1c79356b52d46aa6b508fb032f5ae709b1f2897b..143464d58d2bd6378e74eec636961ceb0d32fb91:/bsd/netinet6/ip6_forward.c?ds=sidebyside diff --git a/bsd/netinet6/ip6_forward.c b/bsd/netinet6/ip6_forward.c index 95838ed3c..6fdaa1069 100644 --- a/bsd/netinet6/ip6_forward.c +++ b/bsd/netinet6/ip6_forward.c @@ -1,4 +1,33 @@ -/* $KAME: ip6_forward.c,v 1.29 2000/02/26 18:08:38 itojun Exp $ */ +/* + * Copyright (c) 2009-2013 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 + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * 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, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * 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@ + */ + +/* $FreeBSD: src/sys/netinet6/ip6_forward.c,v 1.16 2002/10/16 02:25:05 sam Exp $ */ +/* $KAME: ip6_forward.c,v 1.69 2001/05/17 03:48:30 itojun Exp $ */ /* * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. @@ -29,10 +58,6 @@ * SUCH DAMAGE. */ -#if (defined(__FreeBSD__) && __FreeBSD__ >= 3) -#include "opt_ip6fw.h" -#include "opt_inet.h" -#endif #include #include @@ -51,29 +76,34 @@ #include #include +#include +#include #include +#include #include #include #include #include +#include + +#include -#if IPSEC_IPV6FWD +#if IPSEC #include +#if INET6 +#include +#endif #include -#include -#endif /* IPSEC_IPV6FWD */ +extern int ipsec_bypass; +#endif /* IPSEC */ -#if IPV6FIREWALL #include -#endif - -#if MIP6 -#include -#endif #include -struct route_in6 ip6_forward_rt; +#if PF +#include +#endif /* PF */ /* * Forward a packet. If some error occurs return the sender @@ -88,24 +118,69 @@ struct route_in6 ip6_forward_rt; * */ -void -ip6_forward(m, srcrt) - struct mbuf *m; - int srcrt; +struct mbuf * +ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, + int srcrt) { struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); - register struct sockaddr_in6 *dst; - register struct rtentry *rt; + struct sockaddr_in6 *dst; + struct rtentry *rt; int error, type = 0, code = 0; + boolean_t proxy = FALSE; struct mbuf *mcopy = NULL; -#if IPSEC_IPV6FWD + struct ifnet *ifp, *rcvifp, *origifp; /* maybe unnecessary */ + u_int32_t inzone, outzone, len; + struct in6_addr src_in6, dst_in6; + uint64_t curtime = net_uptime(); +#if IPSEC struct secpolicy *sp = NULL; #endif -#if !(defined(__FreeBSD__) && __FreeBSD__ >= 3) && !defined(__APPLE__) - long time_second = time.tv_sec; -#endif + unsigned int ifscope = IFSCOPE_NONE; +#if PF + struct pf_mtag *pf_mtag; +#endif /* PF */ + + /* + * In the prefix proxying case, the route to the proxied node normally + * gets created by nd6_prproxy_ns_output(), as part of forwarding a + * NS (NUD/AR) packet to the proxied node. In the event that such + * packet did not arrive in time before the correct route gets created, + * ip6_input() would have performed a rtalloc() which most likely will + * create the wrong cloned route; this route points back to the same + * interface as the inbound interface, since the parent non-scoped + * prefix route points there. Therefore we check if that is the case + * and perform the necessary fixup to get the correct route installed. + */ + 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) + ifscope = rt->rt_ifp->if_index; + } -#if IPSEC_IPV6FWD +#if PF + pf_mtag = pf_find_mtag(m); + if (pf_mtag != NULL && pf_mtag->pftag_rtableid != IFSCOPE_NONE) + ifscope = pf_mtag->pftag_rtableid; + + /* + * If the caller provides a route which is on a different interface + * than the one specified for scoped forwarding, discard the route + * and do a lookup below. + */ + if (ifscope != IFSCOPE_NONE && (rt = ip6forward_rt->ro_rt) != NULL) { + RT_LOCK(rt); + if (rt->rt_ifp->if_index != ifscope) { + RT_UNLOCK(rt); + ROUTE_RELEASE(ip6forward_rt); + rt = NULL; + } else { + RT_UNLOCK(rt); + } + } +#endif /* PF */ + +#if IPSEC /* * Check AH/ESP integrity. */ @@ -113,19 +188,28 @@ ip6_forward(m, srcrt) * Don't increment ip6s_cantforward because this is the check * before forwarding packet actually. */ - if (ipsec6_in_reject(m, NULL)) { - ipsec6stat.in_polvio++; - m_freem(m); - return; + if (ipsec_bypass == 0) { + if (ipsec6_in_reject(m, NULL)) { + IPSEC_STAT_INCREMENT(ipsec6stat.in_polvio); + m_freem(m); + return (NULL); + } } -#endif /*IPSEC_IPV6FWD*/ +#endif /*IPSEC*/ + /* + * Do not forward packets to multicast destination (should be handled + * by ip6_mforward(). + * 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 || - IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { + IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) || + IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src)) { ip6stat.ip6s_cantforward++; /* XXX in6_ifstat_inc(rt->rt_ifp, ifs6_in_discard) */ - if (ip6_log_time + ip6_log_interval < time_second) { - ip6_log_time = time_second; + if (ip6_log_time + ip6_log_interval < curtime) { + ip6_log_time = curtime; log(LOG_DEBUG, "cannot forward " "from %s to %s nxt %d received on %s\n", @@ -135,15 +219,34 @@ ip6_forward(m, srcrt) if_name(m->m_pkthdr.rcvif)); } m_freem(m); - return; + return (NULL); } if (ip6->ip6_hlim <= IPV6_HLIMDEC) { /* XXX in6_ifstat_inc(rt->rt_ifp, ifs6_in_discard) */ icmp6_error(m, ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT, 0); - return; + return (NULL); } + + /* + * See if the destination is a proxied address, and if so pretend + * that it's for us. This is mostly to handle NUD probes against + * the proxied addresses. We filter for ICMPv6 here and will let + * icmp6_input handle the rest. + */ + if (!srcrt && nd6_prproxy) { + VERIFY(!IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)); + proxy = nd6_prproxy_isours(m, ip6, ip6forward_rt, ifscope); + /* + * Don't update hop limit while proxying; RFC 4389 4.1. + * Also skip IPsec forwarding path processing as this + * packet is not to be forwarded. + */ + if (proxy) + goto skip_ipsec; + } + ip6->ip6_hlim -= IPV6_HLIMDEC; /* @@ -157,12 +260,14 @@ ip6_forward(m, srcrt) */ mcopy = m_copy(m, 0, imin(m->m_pkthdr.len, ICMPV6_PLD_MAXLEN)); - -#if IPSEC_IPV6FWD +#if IPSEC + if (ipsec_bypass != 0) + goto skip_ipsec; /* get a security policy for this packet */ - sp = ipsec6_getpolicybyaddr(m, IPSEC_DIR_OUTBOUND, 0, &error); + sp = ipsec6_getpolicybyaddr(m, IPSEC_DIR_OUTBOUND, IP_FORWARDING, + &error); if (sp == NULL) { - ipsec6stat.out_inval++; + IPSEC_STAT_INCREMENT(ipsec6stat.out_inval); ip6stat.ip6s_cantforward++; if (mcopy) { #if 0 @@ -172,7 +277,7 @@ ip6_forward(m, srcrt) #endif } m_freem(m); - return; + return (NULL); } error = 0; @@ -180,12 +285,13 @@ ip6_forward(m, srcrt) /* check policy */ switch (sp->policy) { case IPSEC_POLICY_DISCARD: + case IPSEC_POLICY_GENERATE: /* * This packet is just discarded. */ - ipsec6stat.out_polvio++; + IPSEC_STAT_INCREMENT(ipsec6stat.out_polvio); ip6stat.ip6s_cantforward++; - key_freesp(sp); + key_freesp(sp, KEY_SADB_UNLOCKED); if (mcopy) { #if 0 /* XXX: what icmp ? */ @@ -194,20 +300,20 @@ ip6_forward(m, srcrt) #endif } m_freem(m); - return; + return (NULL); case IPSEC_POLICY_BYPASS: case IPSEC_POLICY_NONE: /* no need to do IPsec. */ - key_freesp(sp); + key_freesp(sp, KEY_SADB_UNLOCKED); goto skip_ipsec; - + case IPSEC_POLICY_IPSEC: if (sp->req == NULL) { /* XXX should be panic ? */ printf("ip6_forward: No IPsec request specified.\n"); ip6stat.ip6s_cantforward++; - key_freesp(sp); + key_freesp(sp, KEY_SADB_UNLOCKED); if (mcopy) { #if 0 /* XXX: what icmp ? */ @@ -216,7 +322,7 @@ ip6_forward(m, srcrt) #endif } m_freem(m); - return; + return (NULL); } /* do IPsec */ break; @@ -225,7 +331,7 @@ ip6_forward(m, srcrt) default: /* should be panic ?? */ printf("ip6_forward: Invalid policy found. %d\n", sp->policy); - key_freesp(sp); + key_freesp(sp, KEY_SADB_UNLOCKED); goto skip_ipsec; } @@ -242,17 +348,17 @@ ip6_forward(m, srcrt) */ bzero(&state, sizeof(state)); state.m = m; - state.ro = NULL; /* update at ipsec6_output_tunnel() */ 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 */ + } m = state.m; -#if 0 /* XXX allocate a route (ro, dst) again later */ - ro = (struct route_in6 *)state.ro; - dst = (struct sockaddr_in6 *)state.dst; -#endif - key_freesp(sp); + ROUTE_RELEASE(&state.ro); if (error) { /* mbuf is already reclaimed in ipsec6_output_tunnel. */ @@ -265,7 +371,7 @@ ip6_forward(m, srcrt) break; default: printf("ip6_output (ipsec): error code %d\n", error); - /*fall through*/ + /* fall through */ case ENOENT: /* don't show these error codes to the user */ break; @@ -279,104 +385,114 @@ ip6_forward(m, srcrt) #endif } m_freem(m); - return; + return (NULL); } } +#endif /* IPSEC */ skip_ipsec: -#endif /* IPSEC_IPV6FWD */ - -#if MIP6 - { - struct mip6_bc *bc; - - bc = mip6_bc_find(&ip6->ip6_dst); - if ((bc != NULL) && (bc->hr_flag)) { - if (mip6_tunnel_output(&m, bc) != 0) { - ip6stat.ip6s_cantforward++; - if (mcopy) - m_freem(mcopy); - m_freem(m); - return; - } - } - ip6 = mtod(m, struct ip6_hdr *); /* m has changed */ + + dst = (struct sockaddr_in6 *)&ip6forward_rt->ro_dst; + if ((rt = ip6forward_rt->ro_rt) != NULL) { + RT_LOCK(rt); + /* Take an extra ref for ourselves */ + RT_ADDREF_LOCKED(rt); } -#endif - - dst = &ip6_forward_rt.ro_dst; + + VERIFY(rt == NULL || rt == ip6forward_rt->ro_rt); if (!srcrt) { /* - * ip6_forward_rt.ro_dst.sin6_addr is equal to ip6->ip6_dst + * ip6forward_rt->ro_dst.sin6_addr is equal to ip6->ip6_dst */ - if (ip6_forward_rt.ro_rt == 0 || - (ip6_forward_rt.ro_rt->rt_flags & RTF_UP) == 0) { - if (ip6_forward_rt.ro_rt) { - RTFREE(ip6_forward_rt.ro_rt); - ip6_forward_rt.ro_rt = 0; + if (ROUTE_UNUSABLE(ip6forward_rt)) { + if (rt != NULL) { + /* Release extra ref */ + RT_REMREF_LOCKED(rt); + RT_UNLOCK(rt); } + ROUTE_RELEASE(ip6forward_rt); + /* this probably fails but give it a try again */ -#if __FreeBSD__ || defined(__APPLE__) - rtalloc_ign((struct route *)&ip6_forward_rt, - RTF_PRCLONING); -#else - rtalloc((struct route *)&ip6_forward_rt); -#endif + rtalloc_scoped_ign((struct route *)ip6forward_rt, + RTF_PRCLONING, ifscope); + if ((rt = ip6forward_rt->ro_rt) != NULL) { + RT_LOCK(rt); + /* Take an extra ref for ourselves */ + RT_ADDREF_LOCKED(rt); + } } - - if (ip6_forward_rt.ro_rt == 0) { + + if (rt == NULL) { ip6stat.ip6s_noroute++; - /* XXX in6_ifstat_inc(rt->rt_ifp, ifs6_in_noroute) */ - if (mcopy) { + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_noroute); + if (mcopy) icmp6_error(mcopy, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE, 0); - } m_freem(m); - return; + return (NULL); } - } else if ((rt = ip6_forward_rt.ro_rt) == 0 || - !IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &dst->sin6_addr)) { - if (ip6_forward_rt.ro_rt) { - RTFREE(ip6_forward_rt.ro_rt); - ip6_forward_rt.ro_rt = 0; + RT_LOCK_ASSERT_HELD(rt); + } else if (ROUTE_UNUSABLE(ip6forward_rt) || + !IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &dst->sin6_addr)) { + if (rt != NULL) { + /* Release extra ref */ + RT_REMREF_LOCKED(rt); + RT_UNLOCK(rt); } + ROUTE_RELEASE(ip6forward_rt); + bzero(dst, sizeof(*dst)); dst->sin6_len = sizeof(struct sockaddr_in6); dst->sin6_family = AF_INET6; dst->sin6_addr = ip6->ip6_dst; -#if __FreeBSD__ || defined(__APPLE__) - rtalloc_ign((struct route *)&ip6_forward_rt, RTF_PRCLONING); -#else - rtalloc((struct route *)&ip6_forward_rt); -#endif - if (ip6_forward_rt.ro_rt == 0) { + rtalloc_scoped_ign((struct route *)ip6forward_rt, + RTF_PRCLONING, ifscope); + if ((rt = ip6forward_rt->ro_rt) == NULL) { ip6stat.ip6s_noroute++; - /* XXX in6_ifstat_inc(rt->rt_ifp, ifs6_in_noroute) */ - if (mcopy) { + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_noroute); + if (mcopy) icmp6_error(mcopy, ICMP6_DST_UNREACH, - ICMP6_DST_UNREACH_NOROUTE, 0); - } + ICMP6_DST_UNREACH_NOROUTE, 0); m_freem(m); - return; + return (NULL); } + RT_LOCK(rt); + /* Take an extra ref for ourselves */ + RT_ADDREF_LOCKED(rt); } - rt = ip6_forward_rt.ro_rt; /* - * Scope check: if a packet can't be delivered to its destination - * for the reason that the destination is beyond the scope of the - * source address, discard the packet and return an icmp6 destination - * unreachable error with Code 2 (beyond scope of source address). - * [draft-ietf-ipngwg-icmp-v3-00.txt, Section 3.1] + * Source scope check: if a packet can't be delivered to its + * destination for the reason that the destination is beyond the scope + * of the source address, discard the packet and return an icmp6 + * destination unreachable error with Code 2 (beyond scope of source + * address) unless we are proxying (source address is link local + * for NUDs.) We use a local copy of ip6_src, since in6_setscope() + * will possibly modify its first argument. + * [draft-ietf-ipngwg-icmp-v3-04.txt, Section 3.1] */ - if (in6_addr2scopeid(m->m_pkthdr.rcvif, &ip6->ip6_src) != - in6_addr2scopeid(rt->rt_ifp, &ip6->ip6_src)) { + src_in6 = ip6->ip6_src; + if (in6_setscope(&src_in6, rt->rt_ifp, &outzone)) { + /* XXX: this should not happen */ + ip6stat.ip6s_cantforward++; + ip6stat.ip6s_badscope++; + m_freem(m); + return (NULL); + } + if (in6_setscope(&src_in6, m->m_pkthdr.rcvif, &inzone)) { + ip6stat.ip6s_cantforward++; + ip6stat.ip6s_badscope++; + m_freem(m); + return (NULL); + } + + if (inzone != outzone && !proxy) { ip6stat.ip6s_cantforward++; ip6stat.ip6s_badscope++; in6_ifstat_inc(rt->rt_ifp, ifs6_in_discard); - if (ip6_log_time + ip6_log_interval < time_second) { - ip6_log_time = time_second; + if (ip6_log_time + ip6_log_interval < curtime) { + ip6_log_time = curtime; log(LOG_DEBUG, "cannot forward " "src %s, dst %s, nxt %d, rcvif %s, outif %s\n", @@ -385,41 +501,62 @@ ip6_forward(m, srcrt) ip6->ip6_nxt, if_name(m->m_pkthdr.rcvif), if_name(rt->rt_ifp)); } - if (mcopy) + /* Release extra ref */ + RT_REMREF_LOCKED(rt); + RT_UNLOCK(rt); + if (mcopy) { icmp6_error(mcopy, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_BEYONDSCOPE, 0); + } + m_freem(m); + return (NULL); + } + + /* + * Destination scope check: if a packet is going to break the scope + * zone of packet's destination address, discard it. This case should + * usually be prevented by appropriately-configured routing table, but + * we need an explicit check because we may mistakenly forward the + * packet to a different zone by (e.g.) a default route. + */ + dst_in6 = ip6->ip6_dst; + if (in6_setscope(&dst_in6, m->m_pkthdr.rcvif, &inzone) != 0 || + in6_setscope(&dst_in6, rt->rt_ifp, &outzone) != 0 || + inzone != outzone) { + ip6stat.ip6s_cantforward++; + ip6stat.ip6s_badscope++; m_freem(m); - return; + return (NULL); } if (m->m_pkthdr.len > rt->rt_ifp->if_mtu) { in6_ifstat_inc(rt->rt_ifp, ifs6_in_toobig); if (mcopy) { - u_long mtu; -#if IPSEC_IPV6FWD - struct secpolicy *sp; + uint32_t mtu; +#if IPSEC + struct secpolicy *sp2; int ipsecerror; size_t ipsechdrsiz; #endif mtu = rt->rt_ifp->if_mtu; -#if IPSEC_IPV6FWD +#if IPSEC /* * When we do IPsec tunnel ingress, we need to play - * with if_mtu value (decrement IPsec header size + * with the link value (decrement IPsec header size * from mtu value). The code is much simpler than v4 * case, as we have the outgoing interface for * encapsulated packet as "rt->rt_ifp". */ - sp = ipsec6_getpolicybyaddr(mcopy, IPSEC_DIR_OUTBOUND, + sp2 = ipsec6_getpolicybyaddr(mcopy, IPSEC_DIR_OUTBOUND, IP_FORWARDING, &ipsecerror); - if (sp) { + if (sp2) { ipsechdrsiz = ipsec6_hdrsiz(mcopy, 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). @@ -427,14 +564,21 @@ ip6_forward(m, srcrt) if (mtu < IPV6_MMTU) mtu = IPV6_MMTU; #endif + /* Release extra ref */ + RT_REMREF_LOCKED(rt); + RT_UNLOCK(rt); icmp6_error(mcopy, ICMP6_PACKET_TOO_BIG, 0, mtu); + } else { + /* Release extra ref */ + RT_REMREF_LOCKED(rt); + RT_UNLOCK(rt); } m_freem(m); - return; + return (NULL); } if (rt->rt_flags & RTF_GATEWAY) - dst = (struct sockaddr_in6 *)rt->rt_gateway; + dst = (struct sockaddr_in6 *)(void *)rt->rt_gateway; /* * If we are to forward the packet using the same interface @@ -445,55 +589,179 @@ ip6_forward(m, srcrt) * Also, don't send redirect if forwarding using a route * modified by a redirect. */ - if (rt->rt_ifp == m->m_pkthdr.rcvif && !srcrt && - (rt->rt_flags & (RTF_DYNAMIC|RTF_MODIFIED)) == 0) + if (!proxy && + ip6_sendredirects && rt->rt_ifp == m->m_pkthdr.rcvif && !srcrt && + (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 + * one, and the link attached to the interface is + * point-to-point, then it will be highly probable + * that a routing loop occurs. Thus, we immediately + * drop the packet and send an ICMPv6 error message. + * + * 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_UNLOCK(rt); + icmp6_error(mcopy, ICMP6_DST_UNREACH, + ICMP6_DST_UNREACH_ADDR, 0); + m_freem(m); + return (NULL); + } type = ND_REDIRECT; + } -#if IPV6FIREWALL +#if IPFW2 /* * Check with the firewall... */ - if (ip6_fw_chk_ptr) { + 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, rt->rt_ifp, &port, &m)) { + if (ip6_fw_chk_ptr(&ip6, ifp, &port, &m)) { m_freem(m); goto freecopy; } - if (!m) + if (!m) { goto freecopy; + } + /* We still have the extra ref on rt */ + RT_LOCK(rt); } #endif -#if OLDIP6OUTPUT - error = (*rt->rt_ifp->if_output)(rt->rt_ifp, m, - (struct sockaddr *)dst, - ip6_forward_rt.ro_rt); + /* + * Fake scoped addresses. Note that even link-local source or + * destinaion can appear, if the originating node just sends the + * packet to us (without address resolution for the destination). + * Since both icmp6_error and icmp6_redirect_output fill the embedded + * link identifiers, we can do this stuff after making a copy for + * returning an error. + */ + if ((rt->rt_ifp->if_flags & IFF_LOOPBACK) != 0) { + /* + * See corresponding comments in ip6_output. + * XXX: but is it possible that ip6_forward() sends a packet + * to a loopback interface? I don't think so, and thus + * I bark here. (jinmei@kame.net) + * XXX: it is common to route invalid packets to loopback. + * also, the codepath will be visited on use of ::1 in + * rthdr. (itojun) + */ +#if 1 + if (0) #else - error = nd6_output(rt->rt_ifp, m, dst, rt); -#endif + 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)); + } + + /* we can just use rcvif in forwarding. */ + origifp = rcvifp = m->m_pkthdr.rcvif; + } else if (nd6_prproxy) { + /* + * In the prefix proxying case, we need to inform nd6_output() + * about the inbound interface, so that any subsequent NS + * packets generated by nd6_prproxy_ns_output() will not be + * sent back to that same interface. + */ + origifp = rcvifp = m->m_pkthdr.rcvif; + } else { + rcvifp = m->m_pkthdr.rcvif; + origifp = rt->rt_ifp; + } + /* + * clear embedded scope identifiers if necessary. + * in6_clearscope will touch the addresses only when necessary. + */ + in6_clearscope(&ip6->ip6_src); + in6_clearscope(&ip6->ip6_dst); + + ifp = rt->rt_ifp; + /* Drop the lock but retain the extra ref */ + RT_UNLOCK(rt); + + /* + * If this is to be processed locally, let ip6_input have it. + */ + if (proxy) { + VERIFY(m->m_pkthdr.pkt_flags & PKTF_PROXY_DST); + /* Release extra ref */ + RT_REMREF(rt); + if (mcopy != NULL) + m_freem(mcopy); + return (m); + } + +#if PF + /* Invoke outbound packet filter */ + error = pf_af_hook(ifp, NULL, &m, AF_INET6, FALSE, NULL); + + if (error != 0 || m == NULL) { + if (m != NULL) { + panic("%s: unexpected packet %p\n", __func__, m); + /* NOTREACHED */ + } + /* Already freed by callee */ + goto senderr; + } + 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); + + error = nd6_output(ifp, origifp, m, dst, rt, NULL); if (error) { - in6_ifstat_inc(rt->rt_ifp, ifs6_out_discard); + in6_ifstat_inc(ifp, ifs6_out_discard); ip6stat.ip6s_cantforward++; } 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_fbytes += len; + ip6stat.ip6s_forward++; - in6_ifstat_inc(rt->rt_ifp, ifs6_out_forward); + in6_ifstat_inc(ifp, ifs6_out_forward); if (type) ip6stat.ip6s_redirectsent++; else { - if (mcopy) + if (mcopy) { goto freecopy; + } } } - if (mcopy == NULL) - return; - +#if PF +senderr: +#endif /* PF */ + if (mcopy == NULL) { + /* Release extra ref */ + RT_REMREF(rt); + return (NULL); + } switch (error) { case 0: #if 1 if (type == ND_REDIRECT) { icmp6_redirect_output(mcopy, rt); - return; + /* Release extra ref */ + RT_REMREF(rt); + return (NULL); } #endif goto freecopy; @@ -516,9 +784,13 @@ ip6_forward(m, srcrt) break; } icmp6_error(mcopy, type, code, 0); - return; + /* Release extra ref */ + RT_REMREF(rt); + return (NULL); freecopy: m_freem(mcopy); - return; + /* Release extra ref */ + RT_REMREF(rt); + return (NULL); }