X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/b0d623f7f2ae71ed96e60569f61f9a9a27016e80..4d15aeb193b2c68f1d38666c317f8d3734f5f083:/bsd/netinet6/ip6_forward.c diff --git a/bsd/netinet6/ip6_forward.c b/bsd/netinet6/ip6_forward.c index c9f7075f4..e6beab89a 100644 --- a/bsd/netinet6/ip6_forward.c +++ b/bsd/netinet6/ip6_forward.c @@ -1,8 +1,8 @@ /* - * Copyright (c) 2008 Apple Inc. All rights reserved. + * Copyright (c) 2009-2016 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 @@ -11,10 +11,10 @@ * 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, @@ -22,7 +22,7 @@ * 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@ */ @@ -84,6 +84,7 @@ #include #include #include +#include #include @@ -95,12 +96,14 @@ #include extern int ipsec_bypass; #endif /* IPSEC */ -extern lck_mtx_t *ip6_mutex; - -#include #include +#if DUMMYNET +#include +#include +#endif /* DUMMYNET */ + #if PF #include #endif /* PF */ @@ -118,24 +121,67 @@ extern lck_mtx_t *ip6_mutex; * */ -void +struct mbuf * ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, - int srcrt, int locked) + int srcrt) { struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); struct sockaddr_in6 *dst; struct rtentry *rt; int error, type = 0, code = 0; + boolean_t proxy = FALSE; struct mbuf *mcopy = NULL; - struct ifnet *ifp, *origifp; /* maybe unnecessary */ + 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 - struct timeval timenow; - int tunneledv4 = 0; + 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; + } - getmicrotime(&timenow); +#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 /* @@ -149,14 +195,13 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, if (ipsec6_in_reject(m, NULL)) { IPSEC_STAT_INCREMENT(ipsec6stat.in_polvio); m_freem(m); - return; + return (NULL); } } #endif /*IPSEC*/ /* - * Do not forward packets to multicast destination (should be handled - * by ip6_mforward(). + * Do not forward packets to multicast destination. * Do not forward packets with unspecified source. It was discussed * in July 2000, on ipngwg mailing list. */ @@ -165,8 +210,8 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, 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 < timenow.tv_sec) { - ip6_log_time = timenow.tv_sec; + 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", @@ -176,19 +221,34 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, 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) */ - if (locked) - lck_mtx_unlock(ip6_mutex); - icmp6_error(m, ICMP6_TIME_EXCEEDED, - ICMP6_TIME_EXCEED_TRANSIT, 0); - if (locked) - lck_mtx_lock(ip6_mutex); - return; + icmp6_error_flag(m, ICMP6_TIME_EXCEEDED, + ICMP6_TIME_EXCEED_TRANSIT, 0, 0); + 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; /* @@ -219,7 +279,7 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, #endif } m_freem(m); - return; + return (NULL); } error = 0; @@ -242,7 +302,7 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, #endif } m_freem(m); - return; + return (NULL); case IPSEC_POLICY_BYPASS: case IPSEC_POLICY_NONE: @@ -264,7 +324,7 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, #endif } m_freem(m); - return; + return (NULL); } /* do IPsec */ break; @@ -290,19 +350,18 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, */ bzero(&state, sizeof(state)); state.m = m; - state.ro = NULL; /* update at ipsec6_output_tunnel() */ state.dst = NULL; /* update at ipsec6_output_tunnel() */ - if (locked) - lck_mtx_unlock(ip6_mutex); - error = ipsec6_output_tunnel(&state, sp, 0, &tunneledv4); - if (locked) - lck_mtx_lock(ip6_mutex); + error = ipsec6_output_tunnel(&state, sp, 0); key_freesp(sp, KEY_SADB_UNLOCKED); - if (tunneledv4) - return; /* packet is gone - sent over IPv4 */ - + if (state.tunneled == 4) { + ROUTE_RELEASE(&state.ro); + return (NULL); /* packet is gone - sent over IPv4 */ + } + m = state.m; + ROUTE_RELEASE(&state.ro); + if (error) { /* mbuf is already reclaimed in ipsec6_output_tunnel. */ switch (error) { @@ -328,21 +387,12 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, #endif } m_freem(m); - return; + return (NULL); } } - skip_ipsec: #endif /* IPSEC */ + skip_ipsec: - /* - * If "locked", ip6forward_rt points to the globally defined - * struct route cache which requires ip6_mutex, e.g. when this - * is called from ip6_input(). Else the caller is responsible - * for the struct route and its serialization (if needed), e.g. - * when this is called from ip6_rthdr0(). - */ - if (locked) - lck_mtx_assert(ip6_mutex, LCK_MTX_ASSERT_OWNED); dst = (struct sockaddr_in6 *)&ip6forward_rt->ro_dst; if ((rt = ip6forward_rt->ro_rt) != NULL) { RT_LOCK(rt); @@ -350,22 +400,22 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, RT_ADDREF_LOCKED(rt); } + VERIFY(rt == NULL || rt == ip6forward_rt->ro_rt); if (!srcrt) { /* * ip6forward_rt->ro_dst.sin6_addr is equal to ip6->ip6_dst */ - if (rt == NULL || !(rt->rt_flags & RTF_UP) || - rt->generation_id != route_generation) { + if (ROUTE_UNUSABLE(ip6forward_rt)) { if (rt != NULL) { /* Release extra ref */ RT_REMREF_LOCKED(rt); RT_UNLOCK(rt); - rtfree(rt); - ip6forward_rt->ro_rt = NULL; } + ROUTE_RELEASE(ip6forward_rt); + /* this probably fails but give it a try again */ - rtalloc_ign((struct route *)ip6forward_rt, - RTF_PRCLONING); + 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 */ @@ -376,47 +426,37 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, if (rt == NULL) { ip6stat.ip6s_noroute++; in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_noroute); - if (mcopy) { - if (locked) - lck_mtx_unlock(ip6_mutex); + if (mcopy) icmp6_error(mcopy, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE, 0); - if (locked) - lck_mtx_lock(ip6_mutex); - } m_freem(m); - return; + return (NULL); } RT_LOCK_ASSERT_HELD(rt); - } else if (rt == NULL || !(rt->rt_flags & RTF_UP) || - !IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &dst->sin6_addr) || - rt->generation_id != route_generation) { + } 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); - rtfree(rt); - ip6forward_rt->ro_rt = NULL; } + 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; - rtalloc_ign((struct route *)ip6forward_rt, RTF_PRCLONING); + rtalloc_scoped_ign((struct route *)ip6forward_rt, + RTF_PRCLONING, ifscope); if ((rt = ip6forward_rt->ro_rt) == NULL) { ip6stat.ip6s_noroute++; in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_noroute); - if (mcopy) { - if (locked) - lck_mtx_unlock(ip6_mutex); + if (mcopy) icmp6_error(mcopy, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE, 0); - if (locked) - lck_mtx_lock(ip6_mutex); - } m_freem(m); - return; + return (NULL); } RT_LOCK(rt); /* Take an extra ref for ourselves */ @@ -424,20 +464,41 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_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-02.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)) { + RT_REMREF_LOCKED(rt); + RT_UNLOCK(rt); + /* 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)) { + RT_REMREF_LOCKED(rt); + RT_UNLOCK(rt); + 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 < timenow.tv_sec) { - ip6_log_time = timenow.tv_sec; + 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", @@ -450,15 +511,30 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, RT_REMREF_LOCKED(rt); RT_UNLOCK(rt); if (mcopy) { - if (locked) - lck_mtx_unlock(ip6_mutex); icmp6_error(mcopy, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_BEYONDSCOPE, 0); - if (locked) - lck_mtx_lock(ip6_mutex); } m_freem(m); - return; + 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) { + RT_REMREF_LOCKED(rt); + RT_UNLOCK(rt); + ip6stat.ip6s_cantforward++; + ip6stat.ip6s_badscope++; + m_freem(m); + return (NULL); } if (m->m_pkthdr.len > rt->rt_ifp->if_mtu) { @@ -475,7 +551,7 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, #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". @@ -499,22 +575,18 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, /* Release extra ref */ RT_REMREF_LOCKED(rt); RT_UNLOCK(rt); - if (locked) - lck_mtx_unlock(ip6_mutex); icmp6_error(mcopy, ICMP6_PACKET_TOO_BIG, 0, mtu); - if (locked) - lck_mtx_lock(ip6_mutex); } 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 @@ -525,7 +597,8 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, * Also, don't send redirect if forwarding using a route * modified by a redirect. */ - if (rt->rt_ifp == m->m_pkthdr.rcvif && !srcrt && + 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) { /* @@ -540,38 +613,13 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, */ RT_REMREF_LOCKED(rt); /* Release extra ref */ RT_UNLOCK(rt); - if (locked) - lck_mtx_unlock(ip6_mutex); icmp6_error(mcopy, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR, 0); - if (locked) - lck_mtx_lock(ip6_mutex); m_freem(m); - return; + return (NULL); } type = ND_REDIRECT; } - - /* - * 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); - } - /* * Fake scoped addresses. Note that even link-local source or * destinaion can appear, if the originating node just sends the @@ -591,7 +639,7 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, * rthdr. (itojun) */ #if 1 - if (0) + if ((0)) #else if ((rt->rt_flags & (RTF_BLACKHOLE|RTF_REJECT)) == 0) #endif @@ -605,49 +653,96 @@ ip6_forward(struct mbuf *m, struct route_in6 *ip6forward_rt, } /* we can just use rcvif in forwarding. */ - origifp = m->m_pkthdr.rcvif; - } - else + 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; -#ifndef SCOPEDROUTING + } /* * 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); -#endif ifp = rt->rt_ifp; /* Drop the lock but retain the extra ref */ RT_UNLOCK(rt); -#if PF - if (locked) - lck_mtx_unlock(ip6_mutex); - - /* Invoke outbound packet filter */ - error = pf_af_hook(ifp, NULL, &m, AF_INET6, FALSE); + /* + * 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 (locked) - lck_mtx_lock(ip6_mutex); + /* Mark this packet as being forwarded from another interface */ + m->m_pkthdr.pkt_flags |= PKTF_FORWARDED; - if (error) { - if (m != NULL) { - panic("%s: unexpected packet %p\n", __func__, m); - /* NOTREACHED */ +#if PF + if (PF_IS_ENABLED) { +#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; } - /* 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 */ - error = nd6_output(ifp, origifp, m, dst, rt, locked); + len = m_pktlen(m); + error = nd6_output(ifp, origifp, m, dst, rt, NULL); if (error) { 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(ifp, ifs6_out_forward); if (type) @@ -664,7 +759,7 @@ senderr: if (mcopy == NULL) { /* Release extra ref */ RT_REMREF(rt); - return; + return (NULL); } switch (error) { case 0: @@ -673,7 +768,7 @@ senderr: icmp6_redirect_output(mcopy, rt); /* Release extra ref */ RT_REMREF(rt); - return; + return (NULL); } #endif goto freecopy; @@ -695,18 +790,14 @@ senderr: code = ICMP6_DST_UNREACH_ADDR; break; } - if (locked) - lck_mtx_unlock(ip6_mutex); icmp6_error(mcopy, type, code, 0); - if (locked) - lck_mtx_lock(ip6_mutex); /* Release extra ref */ RT_REMREF(rt); - return; + return (NULL); freecopy: m_freem(mcopy); /* Release extra ref */ RT_REMREF(rt); - return; + return (NULL); }