Index: sys/netinet6/ip6_output.c =================================================================== --- sys/netinet6/ip6_output.c +++ sys/netinet6/ip6_output.c @@ -571,52 +571,79 @@ counter_u64_add(rt->rt_pksent, 1); } - /* - * The outgoing interface must be in the zone of source and - * destination addresses. + * setup data structures for scope ID checks */ - origifp = ifp; - src0 = ip6->ip6_src; - if (in6_setscope(&src0, origifp, &zone)) - goto badscope; bzero(&src_sa, sizeof(src_sa)); src_sa.sin6_family = AF_INET6; src_sa.sin6_len = sizeof(src_sa); src_sa.sin6_addr = ip6->ip6_src; - if (sa6_recoverscope(&src_sa) || zone != src_sa.sin6_scope_id) - goto badscope; dst0 = ip6->ip6_dst; - if (in6_setscope(&dst0, origifp, &zone)) - goto badscope; /* re-initialize to be sure */ bzero(&dst_sa, sizeof(dst_sa)); dst_sa.sin6_family = AF_INET6; dst_sa.sin6_len = sizeof(dst_sa); dst_sa.sin6_addr = ip6->ip6_dst; - if (sa6_recoverscope(&dst_sa) || zone != dst_sa.sin6_scope_id) { - goto badscope; - } - /* We should use ia_ifp to support the case of - * sending packets to an address of our own. - */ - if (ia != NULL && ia->ia_ifp) - ifp = ia->ia_ifp; + /* check for valid scope ID */ + if (in6_setscope(&src0, ifp, &zone) == 0 && + sa6_recoverscope(&src_sa) == 0 && zone == src_sa.sin6_scope_id && + in6_setscope(&dst0, ifp, &zone) == 0 && + sa6_recoverscope(&dst_sa) == 0 && zone == dst_sa.sin6_scope_id) { + /* + * The outgoing interface is in the zone of the source + * and destination addresses. + */ + origifp = ifp; + + /* + * We should use ia_ifp to support the case of sending + * packets to an address of our own. + */ + if (ia != NULL && ia->ia_ifp) + ifp = ia->ia_ifp; - /* scope check is done. */ - goto routefound; + } else if ((ifp->if_flags & IFF_LOOPBACK) && + sa6_recoverscope(&src_sa) == 0 && + sa6_recoverscope(&dst_sa) == 0 && + dst_sa.sin6_scope_id != 0 && + (src_sa.sin6_scope_id == 0 || + src_sa.sin6_scope_id == dst_sa.sin6_scope_id) && + (origifp = ifnet_byindex(dst_sa.sin6_scope_id)) != NULL) { + /* + * If the destination network interface is a loopback + * interface and the destination network address has a + * scope ID, the scope check above will fail, because + * the zone is lo0, while "sin6_scope_id" is a network + * interface. + * + * Because the default loopback network interface + * knows nothing about scope ID values, which are + * cleared by ip6_input(), the trick is to pretend the + * outgoing packet was received by the real network + * interface, by setting "origifp" different from + * "ifp". This is only allowed when "ifp" is a + * loopback network interface. + * + * The code below will recover the scope ID from the + * source and destination address and verify there is + * a valid destination scope ID. If there is a source + * scope ID, verify it belongs to the same scope as + * the destination address. + * + * If all the checks succeed use the network interface + * pointed to by the scope ID. + */ + } else { + IP6STAT_INC(ip6s_badscope); + in6_ifstat_inc(ifp, ifs6_out_discard); + if (error == 0) + error = EHOSTUNREACH; /* XXX */ + goto bad; + } - badscope: - IP6STAT_INC(ip6s_badscope); - in6_ifstat_inc(origifp, ifs6_out_discard); - if (error == 0) - error = EHOSTUNREACH; /* XXX */ - goto bad; - - routefound: if (rt && !IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { if (opt && opt->ip6po_nextroute.ro_rt) { /*