Beispiel #1
0
/*
 * Given a source IPv6 address (and route, if available), determine the best
 * interface to send the packet from.  Checking for (and updating) the
 * ROF_SRCIF_SELECTED flag in the pcb-supplied route placeholder is done
 * without any locks, based on the assumption that in the event this is
 * called from ip6_output(), the output operation is single-threaded per-pcb,
 * i.e. for any given pcb there can only be one thread performing output at
 * the IPv6 layer.
 *
 * This routine is analogous to in_selectsrcif() for IPv4.
 *
 * clone - meaningful only for bsdi and freebsd
 */
static int
selectroute(struct sockaddr_in6 *srcsock, struct sockaddr_in6 *dstsock,
    struct ip6_pktopts *opts, struct ip6_moptions *mopts, struct route_in6 *ro,
    struct ifnet **retifp, struct rtentry **retrt, int clone,
    int norouteok, unsigned int ifscope, unsigned int nocell)
{
	int error = 0;
	struct ifnet *ifp = NULL;
	struct route_in6 *route = NULL;
	struct sockaddr_in6 *sin6_next;
	struct in6_pktinfo *pi = NULL;
	struct in6_addr *dst = &dstsock->sin6_addr;
	struct ifaddr *ifa = NULL;
	char s_src[MAX_IPv6_STR_LEN], s_dst[MAX_IPv6_STR_LEN];
	boolean_t select_srcif;

#if 0
	char ip6buf[INET6_ADDRSTRLEN];

	if (dstsock->sin6_addr.s6_addr32[0] == 0 &&
	    dstsock->sin6_addr.s6_addr32[1] == 0 &&
	    !IN6_IS_ADDR_LOOPBACK(&dstsock->sin6_addr)) {
		printf("in6_selectroute: strange destination %s\n",
		       ip6_sprintf(ip6buf, &dstsock->sin6_addr));
	} else {
		printf("in6_selectroute: destination = %s%%%d\n",
		       ip6_sprintf(ip6buf, &dstsock->sin6_addr),
		       dstsock->sin6_scope_id); /* for debug */
	}
#endif

	if (retifp != NULL)
		*retifp = NULL;

	if (retrt != NULL)
		*retrt = NULL;

	if (ip6_select_srcif_debug) {
		struct in6_addr src;
		src = (srcsock != NULL) ? srcsock->sin6_addr : in6addr_any;
		(void) inet_ntop(AF_INET6, &src, s_src, sizeof (s_src));
		(void) inet_ntop(AF_INET6, dst, s_dst, sizeof (s_dst));
	}

	/*
	 * If the destination address is UNSPECIFIED addr, bail out.
	 */
	if (IN6_IS_ADDR_UNSPECIFIED(dst)) {
		error = EHOSTUNREACH;
		goto done;
	}

	/*
	 * Perform source interface selection only if Scoped Routing
	 * is enabled and a source address that isn't unspecified.
	 */
	select_srcif = (ip6_doscopedroute && srcsock != NULL &&
	    !IN6_IS_ADDR_UNSPECIFIED(&srcsock->sin6_addr));

	/*
	 * If Scoped Routing is disabled, ignore the given ifscope.
	 * Otherwise even if source selection won't be performed,
	 * we still obey IPV6_BOUND_IF.
	 */
	if (!ip6_doscopedroute && ifscope != IFSCOPE_NONE)
		ifscope = IFSCOPE_NONE;

	/* If the caller specified the outgoing interface explicitly, use it */
	if (opts != NULL && (pi = opts->ip6po_pktinfo) != NULL &&
	    pi->ipi6_ifindex != 0) {
		/*
		 * If IPV6_PKTINFO takes precedence over IPV6_BOUND_IF.
		 */
		ifscope = pi->ipi6_ifindex;
		ifnet_head_lock_shared();
		/* ifp may be NULL if detached or out of range */
		ifp = (ifscope <= if_index) ? ifindex2ifnet[ifscope] : NULL;
		ifnet_head_done();
		if (norouteok || retrt == NULL || IN6_IS_ADDR_MULTICAST(dst)) {
			/*
			 * We do not have to check or get the route for
			 * multicast.  If the caller didn't ask/care for
			 * the route and we have no interface to use,
			 * it's an error.
			 */
			if (ifp == NULL)
				error = EHOSTUNREACH;
			goto done;
		} else {
			goto getsrcif;
		}
	}

	/*
	 * If the destination address is a multicast address and the outgoing
	 * interface for the address is specified by the caller, use it.
	 */
	if (IN6_IS_ADDR_MULTICAST(dst) && mopts != NULL) {
		IM6O_LOCK(mopts);
		if ((ifp = mopts->im6o_multicast_ifp) != NULL) {
			IM6O_UNLOCK(mopts);
			goto done; /* we do not need a route for multicast. */
		}
		IM6O_UNLOCK(mopts);
	}

getsrcif:
	/*
	 * If the outgoing interface was not set via IPV6_BOUND_IF or
	 * IPV6_PKTINFO, use the scope ID in the destination address.
	 */
	if (ip6_doscopedroute && ifscope == IFSCOPE_NONE)
		ifscope = dstsock->sin6_scope_id;

	/*
	 * Perform source interface selection; the source IPv6 address
	 * must belong to one of the addresses of the interface used
	 * by the route.  For performance reasons, do this only if
	 * there is no route, or if the routing table has changed,
	 * or if we haven't done source interface selection on this
	 * route (for this PCB instance) before.
	 */
	if (!select_srcif || (ro != NULL && ro->ro_rt != NULL &&
	    (ro->ro_rt->rt_flags & RTF_UP) &&
	    ro->ro_rt->generation_id == route_generation &&
	    (ro->ro_flags & ROF_SRCIF_SELECTED))) {
		if (ro != NULL && ro->ro_rt != NULL) {
			ifa = ro->ro_rt->rt_ifa;
			IFA_ADDREF(ifa);
		}
		goto getroute;
	}

	/*
	 * Given the source IPv6 address, find a suitable source interface
	 * to use for transmission; if a scope ID has been specified,
	 * optimize the search by looking at the addresses only for that
	 * interface.  This is still suboptimal, however, as we need to
	 * traverse the per-interface list.
	 */
	if (ifscope != IFSCOPE_NONE || (ro != NULL && ro->ro_rt != NULL)) {
		unsigned int scope = ifscope;
		struct ifnet *rt_ifp;

		rt_ifp = (ro->ro_rt != NULL) ? ro->ro_rt->rt_ifp : NULL;

		/*
		 * If no scope is specified and the route is stale (pointing
		 * to a defunct interface) use the current primary interface;
		 * this happens when switching between interfaces configured
		 * with the same IPv6 address.  Otherwise pick up the scope
		 * information from the route; the ULP may have looked up a
		 * correct route and we just need to verify it here and mark
		 * it with the ROF_SRCIF_SELECTED flag below.
		 */
		if (scope == IFSCOPE_NONE) {
			scope = rt_ifp->if_index;
			if (scope != get_primary_ifscope(AF_INET6) &&
			    ro->ro_rt->generation_id != route_generation)
				scope = get_primary_ifscope(AF_INET6);
		}

		ifa = (struct ifaddr *)
		    ifa_foraddr6_scoped(&srcsock->sin6_addr, scope);

		if (ip6_select_srcif_debug && ifa != NULL) {
			if (ro->ro_rt != NULL) {
				printf("%s->%s ifscope %d->%d ifa_if %s "
				    "ro_if %s\n", s_src, s_dst, ifscope,
				    scope, if_name(ifa->ifa_ifp),
				    if_name(rt_ifp));
			} else {
				printf("%s->%s ifscope %d->%d ifa_if %s\n",
				    s_src, s_dst, ifscope, scope,
				    if_name(ifa->ifa_ifp));
			}
		}
	}

	/*
	 * Slow path; search for an interface having the corresponding source
	 * IPv6 address if the scope was not specified by the caller, and:
	 *
	 *   1) There currently isn't any route, or,
	 *   2) The interface used by the route does not own that source
	 *	IPv6 address; in this case, the route will get blown away
	 *	and we'll do a more specific scoped search using the newly
	 *	found interface.
	 */
	if (ifa == NULL && ifscope == IFSCOPE_NONE) {
		ifa = (struct ifaddr *)ifa_foraddr6(&srcsock->sin6_addr);

		if (ip6_select_srcif_debug && ifa != NULL) {
			printf("%s->%s ifscope %d ifa_if %s\n",
			    s_src, s_dst, ifscope, if_name(ifa->ifa_ifp));
		}

	}

getroute:
	if (ifa != NULL)
		ifscope = ifa->ifa_ifp->if_index;

	/*
	 * If the next hop address for the packet is specified by the caller,
	 * use it as the gateway.
	 */
	if (opts != NULL && opts->ip6po_nexthop != NULL) {
		struct route_in6 *ron;

		sin6_next = satosin6(opts->ip6po_nexthop);

		/* at this moment, we only support AF_INET6 next hops */
		if (sin6_next->sin6_family != AF_INET6) {
			error = EAFNOSUPPORT; /* or should we proceed? */
			goto done;
		}

		/*
		 * If the next hop is an IPv6 address, then the node identified
		 * by that address must be a neighbor of the sending host.
		 */
		ron = &opts->ip6po_nextroute;
		if (ron->ro_rt != NULL)
			RT_LOCK(ron->ro_rt);
		if ((ron->ro_rt != NULL &&
		    ((ron->ro_rt->rt_flags & (RTF_UP | RTF_LLINFO)) !=
		    (RTF_UP | RTF_LLINFO) ||
		    ron->ro_rt->generation_id != route_generation ||
		    (select_srcif && (ifa == NULL ||
		    ifa->ifa_ifp != ron->ro_rt->rt_ifp)))) ||
		    !IN6_ARE_ADDR_EQUAL(&satosin6(&ron->ro_dst)->sin6_addr,
		    &sin6_next->sin6_addr)) {
			if (ron->ro_rt != NULL) {
				RT_UNLOCK(ron->ro_rt);
				rtfree(ron->ro_rt);
				ron->ro_rt = NULL;
			}
			*satosin6(&ron->ro_dst) = *sin6_next;
		}
		if (ron->ro_rt == NULL) {
			rtalloc_scoped((struct route *)ron, ifscope);
			if (ron->ro_rt != NULL)
				RT_LOCK(ron->ro_rt);
			if (ron->ro_rt == NULL ||
			    !(ron->ro_rt->rt_flags & RTF_LLINFO) ||
			    !IN6_ARE_ADDR_EQUAL(&satosin6(rt_key(ron->ro_rt))->
			    sin6_addr, &sin6_next->sin6_addr)) {
				if (ron->ro_rt != NULL) {
					RT_UNLOCK(ron->ro_rt);
					rtfree(ron->ro_rt);
					ron->ro_rt = NULL;
				}
				error = EHOSTUNREACH;
				goto done;
			}
		}
		route = ron;
		ifp = ron->ro_rt->rt_ifp;

		/*
		 * When cloning is required, try to allocate a route to the
		 * destination so that the caller can store path MTU
		 * information.
		 */
		if (!clone) {
			if (select_srcif) {
				/* Keep the route locked */
				goto validateroute;
			}
			RT_UNLOCK(ron->ro_rt);
			goto done;
		}
		RT_UNLOCK(ron->ro_rt);
	}

	/*
	 * Use a cached route if it exists and is valid, else try to allocate
	 * a new one.  Note that we should check the address family of the
	 * cached destination, in case of sharing the cache with IPv4.
	 */
	if (ro == NULL)
		goto done;
	if (ro->ro_rt != NULL)
		RT_LOCK(ro->ro_rt);
	if (ro->ro_rt != NULL && (!(ro->ro_rt->rt_flags & RTF_UP) ||
	    satosin6(&ro->ro_dst)->sin6_family != AF_INET6 ||
	    ro->ro_rt->generation_id != route_generation ||
	    !IN6_ARE_ADDR_EQUAL(&satosin6(&ro->ro_dst)->sin6_addr, dst) ||
	    (select_srcif && (ifa == NULL ||
	    ifa->ifa_ifp != ro->ro_rt->rt_ifp)))) {
		RT_UNLOCK(ro->ro_rt);
		rtfree(ro->ro_rt);
		ro->ro_rt = NULL;
	}
	if (ro->ro_rt == NULL) {
		struct sockaddr_in6 *sa6;

		if (ro->ro_rt != NULL)
			RT_UNLOCK(ro->ro_rt);
		/* No route yet, so try to acquire one */
		bzero(&ro->ro_dst, sizeof(struct sockaddr_in6));
		sa6 = (struct sockaddr_in6 *)&ro->ro_dst;
		sa6->sin6_family = AF_INET6;
		sa6->sin6_len = sizeof(struct sockaddr_in6);
		sa6->sin6_addr = *dst;
		if (IN6_IS_ADDR_MULTICAST(dst)) {
			ro->ro_rt = rtalloc1_scoped(
			    &((struct route *)ro)->ro_dst, 0, 0, ifscope);
		} else {
			rtalloc_scoped((struct route *)ro, ifscope);
		}
		if (ro->ro_rt != NULL)
			RT_LOCK(ro->ro_rt);
	}

	/*
	 * Do not care about the result if we have the nexthop
	 * explicitly specified (in case we're asked to clone.)
	 */
	if (opts != NULL && opts->ip6po_nexthop != NULL) {
		if (ro->ro_rt != NULL)
			RT_UNLOCK(ro->ro_rt);
		goto done;
	}

	if (ro->ro_rt != NULL) {
		RT_LOCK_ASSERT_HELD(ro->ro_rt);
		ifp = ro->ro_rt->rt_ifp;
	} else {
		error = EHOSTUNREACH;
	}
	route = ro;

validateroute:
	if (select_srcif) {
		boolean_t has_route = (route != NULL && route->ro_rt != NULL);

		if (has_route)
			RT_LOCK_ASSERT_HELD(route->ro_rt);
		/*
		 * If there is a non-loopback route with the wrong interface,
		 * or if there is no interface configured with such an address,
		 * blow it away.  Except for local/loopback, we look for one
		 * with a matching interface scope/index.
		 */
		if (has_route && (ifa == NULL ||
		    (ifa->ifa_ifp != ifp && ifp != lo_ifp) ||
		    !(route->ro_rt->rt_flags & RTF_UP))) {
			if (ip6_select_srcif_debug) {
				if (ifa != NULL) {
					printf("%s->%s ifscope %d ro_if %s "
					    "!= ifa_if %s (cached route "
					    "cleared)\n", s_src, s_dst,
					    ifscope, if_name(ifp),
					    if_name(ifa->ifa_ifp));
				} else {
					printf("%s->%s ifscope %d ro_if %s "
					    "(no ifa_if found)\n", s_src,
					    s_dst, ifscope, if_name(ifp));
				}
			}
			RT_UNLOCK(route->ro_rt);
			rtfree(route->ro_rt);
			route->ro_rt = NULL;
			route->ro_flags &= ~ROF_SRCIF_SELECTED;
			error = EHOSTUNREACH;
			/* Undo the settings done above */
			route = NULL;
			ifp = NULL;
		} else if (has_route) {
			route->ro_flags |= ROF_SRCIF_SELECTED;
			route->ro_rt->generation_id = route_generation;
			RT_UNLOCK(route->ro_rt);
		}
	} else {
		if (ro->ro_rt != NULL)
			RT_UNLOCK(ro->ro_rt);
		if (ifp != NULL && opts != NULL &&
		    opts->ip6po_pktinfo != NULL &&
		    opts->ip6po_pktinfo->ipi6_ifindex != 0) {
			/*
			 * Check if the outgoing interface conflicts with the
			 * interface specified by ipi6_ifindex (if specified).
			 * Note that loopback interface is always okay.
			 * (this may happen when we are sending a packet to
			 * one of our own addresses.)
			 */
			if (!(ifp->if_flags & IFF_LOOPBACK) && ifp->if_index !=
			    opts->ip6po_pktinfo->ipi6_ifindex) {
				error = EHOSTUNREACH;
				goto done;
			}
		}
	}

done:
	if (nocell && error == 0) {
		if ((ifp != NULL && ifp->if_type == IFT_CELLULAR) ||
		    (route != NULL && route->ro_rt != NULL &&
		    route->ro_rt->rt_ifp->if_type == IFT_CELLULAR)) {
			if (route != NULL && route->ro_rt != NULL) {
				rtfree(route->ro_rt);
				route->ro_rt = NULL;
				route->ro_flags &= ~ROF_SRCIF_SELECTED;
				route = NULL;
			}
			ifp = NULL;
			error = EHOSTUNREACH;
		}
	}

	if (ifp == NULL && (route == NULL || route->ro_rt == NULL)) {
		/*
		 * This can happen if the caller did not pass a cached route
		 * nor any other hints.  We treat this case an error.
		 */
		error = EHOSTUNREACH;
	}
	if (error == EHOSTUNREACH)
		ip6stat.ip6s_noroute++;

	if (error == 0) {
		if (retifp != NULL) {
			if (ifp != NULL)
				ifnet_reference(ifp);	/* for caller */
			*retifp = ifp;
		}
		if (retrt != NULL && route != NULL)
			*retrt = route->ro_rt;	/* ro_rt may be NULL */
	} else if (select_srcif && ip6_select_srcif_debug) {
		printf("%s->%s ifscope %d ifa_if %s ro_if %s (error=%d)\n",
		    s_src, s_dst, ifscope,
		    (ifa != NULL) ? if_name(ifa->ifa_ifp) : "NONE",
		    (ifp != NULL) ? if_name(ifp) : "NONE", error);
	}

	if (ifa != NULL)
		IFA_REMREF(ifa);

	return (error);
}
Beispiel #2
0
/*
 * Fragment input
 * NOTE: this function is called with the inet6_domain_mutex held from ip6_input.
 * 	 inet6_domain_mutex is protecting he frag6 queue manipulation.
 */
int
frag6_input(struct mbuf **mp, int *offp, int proto)
{
#pragma unused(proto)
    struct mbuf *m = *mp, *t;
    struct ip6_hdr *ip6;
    struct ip6_frag *ip6f;
    struct ip6q *q6;
    struct ip6asfrag *af6, *ip6af, *af6dwn;
    int offset = *offp, nxt, i, next;
    int first_frag = 0;
    int fragoff, frgpartlen;	/* must be larger than u_int16_t */
    struct ifnet *dstifp;
    struct ifaddr *ifa = NULL;
    u_int8_t ecn, ecn0;

#ifdef IN6_IFSTAT_STRICT
    struct route_in6 ro;
    struct sockaddr_in6 *dst;
#endif

    /* Expect 32-bit aligned data pointer on strict-align platforms */
    MBUF_STRICT_DATA_ALIGNMENT_CHECK_32(m);

    ip6 = mtod(m, struct ip6_hdr *);
#ifndef PULLDOWN_TEST
    IP6_EXTHDR_CHECK(m, offset, sizeof(struct ip6_frag), return IPPROTO_DONE);
    ip6f = (struct ip6_frag *)((caddr_t)ip6 + offset);
#else
    IP6_EXTHDR_GET(ip6f, struct ip6_frag *, m, offset, sizeof(*ip6f));
    if (ip6f == NULL)
        return IPPROTO_DONE;
#endif

    dstifp = NULL;
#ifdef IN6_IFSTAT_STRICT
    /* find the destination interface of the packet. */
    bzero(&ro, sizeof (ro));
    dst = (struct sockaddr_in6 *)&ro.ro_dst;
    dst->sin6_family = AF_INET6;
    dst->sin6_len = sizeof (struct sockaddr_in6);
    dst->sin6_addr = ip6->ip6_dst;

    rtalloc((struct route *)&ro);
    if (ro.ro_rt != NULL) {
        RT_LOCK(ro.ro_rt);
        if ((ifa = ro.ro_rt->rt_ifa) != NULL) {
            IFA_ADDREF(ifa);
            dstifp = ((struct in6_ifaddr *)ro.ro_rt->rt_ifa)->ia_ifp;
        }
        RT_UNLOCK(ro.ro_rt);
        rtfree(ro.ro_rt);
        ro.ro_rt = NULL;
    }
#else
    /* we are violating the spec, this is not the destination interface */
    if ((m->m_flags & M_PKTHDR) != 0)
        dstifp = m->m_pkthdr.rcvif;
#endif

    /* jumbo payload can't contain a fragment header */
    if (ip6->ip6_plen == 0) {
        icmp6_error(m, ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER, offset);
        in6_ifstat_inc(dstifp, ifs6_reass_fail);
        if (ifa != NULL)
            IFA_REMREF(ifa);
        return IPPROTO_DONE;
    }

    /*
     * check whether fragment packet's fragment length is
     * multiple of 8 octets.
     * sizeof(struct ip6_frag) == 8
     * sizeof(struct ip6_hdr) = 40
     */
    if ((ip6f->ip6f_offlg & IP6F_MORE_FRAG) &&
            (((ntohs(ip6->ip6_plen) - offset) & 0x7) != 0)) {
        icmp6_error(m, ICMP6_PARAM_PROB,
                    ICMP6_PARAMPROB_HEADER,
                    offsetof(struct ip6_hdr, ip6_plen));
        in6_ifstat_inc(dstifp, ifs6_reass_fail);
        if (ifa != NULL)
            IFA_REMREF(ifa);
        return IPPROTO_DONE;
    }

    ip6stat.ip6s_fragments++;
    in6_ifstat_inc(dstifp, ifs6_reass_reqd);

    /* offset now points to data portion */
    offset += sizeof(struct ip6_frag);

    frag6_doing_reass = 1;

    /*
     * Enforce upper bound on number of fragments.
     * If maxfrag is 0, never accept fragments.
     * If maxfrag is -1, accept all fragments without limitation.
     */
    if (ip6_maxfrags < 0)
        ;
    else if (frag6_nfrags >= (u_int)ip6_maxfrags)
        goto dropfrag;

    for (q6 = ip6q.ip6q_next; q6 != &ip6q; q6 = q6->ip6q_next)
        if (ip6f->ip6f_ident == q6->ip6q_ident &&
                IN6_ARE_ADDR_EQUAL(&ip6->ip6_src, &q6->ip6q_src) &&
                IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &q6->ip6q_dst))
            break;

    if (q6 == &ip6q) {
        /*
         * the first fragment to arrive, create a reassembly queue.
         */
        first_frag = 1;

        /*
         * Enforce upper bound on number of fragmented packets
         * for which we attempt reassembly;
         * If maxfrag is 0, never accept fragments.
         * If maxfrag is -1, accept all fragments without limitation.
         */
        if (ip6_maxfragpackets < 0)
            ;
        else if (frag6_nfragpackets >= (u_int)ip6_maxfragpackets)
            goto dropfrag;
        frag6_nfragpackets++;
        q6 = (struct ip6q *)_MALLOC(sizeof(struct ip6q), M_FTABLE,
                                    M_DONTWAIT);
        if (q6 == NULL)
            goto dropfrag;
        bzero(q6, sizeof(*q6));

        frag6_insque(q6, &ip6q);

        /* ip6q_nxt will be filled afterwards, from 1st fragment */
        q6->ip6q_down	= q6->ip6q_up = (struct ip6asfrag *)q6;
#ifdef notyet
        q6->ip6q_nxtp	= (u_char *)nxtp;
#endif
        q6->ip6q_ident	= ip6f->ip6f_ident;
        q6->ip6q_ttl 	= IPV6_FRAGTTL;
        q6->ip6q_src	= ip6->ip6_src;
        q6->ip6q_dst	= ip6->ip6_dst;
        q6->ip6q_ecn	=
            (ntohl(ip6->ip6_flow) >> 20) & IPTOS_ECN_MASK;
        q6->ip6q_unfrglen = -1;	/* The 1st fragment has not arrived. */

        q6->ip6q_nfrag	= 0;
    }