Index: sys/netinet/in.c =================================================================== --- sys/netinet/in.c +++ sys/netinet/in.c @@ -78,6 +78,8 @@ static void in_socktrim(struct sockaddr_in *); static void in_purgemaddrs(struct ifnet *); +static void in_rtrequest(int req, struct rtentry *rt, struct rt_addrinfo *info); + static VNET_DEFINE(int, nosameprefix); #define V_nosameprefix VNET(nosameprefix) SYSCTL_INT(_net_inet_ip, OID_AUTO, no_same_prefix, CTLFLAG_VNET | CTLFLAG_RW, @@ -401,6 +403,7 @@ ifa->ifa_netmask = (struct sockaddr *)&ia->ia_sockmask; callout_init_rw(&ia->ia_garp_timer, &ifp->if_addr_lock, CALLOUT_RETURNUNLOCKED); + ifa->ifa_rtrequest = in_rtrequest; ia->ia_ifp = ifp; ia->ia_addr = *addr; @@ -1497,3 +1500,56 @@ lltable_free(ii->ii_llt); free(ii, M_IFADDR); } + +/* + * Return true if the route described by info is the local subnet route for + * ifa, or false otherwise. + */ +static int +in_is_subnet_route(struct ifaddr *ifa, struct rt_addrinfo *info) +{ + struct sockaddr *tmp_sa; + struct sockaddr_storage tmp_storage; + + if (info->rti_info[RTAX_DST] == NULL || info->rti_info[RTAX_NETMASK] == NULL) + return (0); + + /* First test that the ifaddr falls into the subnet described by the route. */ + tmp_sa = (struct sockaddr*)&tmp_storage; + rt_maskedcopy(ifa->ifa_addr, tmp_sa, ifa->ifa_netmask); + + if (!sa_equal(tmp_sa, info->rti_info[RTAX_DST])) + return (0); + + /* + * Now test that the subnet mask is the same for the address and + * the route. If they are different then the route has a different + * prefix size and ifa just happens to fall within that route. + * + * For some reason ifa->ifa_netmask sa_len field is not + * sizeof(struct sockaddr_in). This causes attempts to directly + * compare against info->rti_info[RTAX_NETMASK] to incorrectly + * fail even if the netmasks are the same. Fix this by making a + * copy of ifa->ifa_netmask with the length field correct. + */ + bzero(tmp_sa, sizeof(struct sockaddr_in)); + memcpy(tmp_sa, ifa->ifa_netmask, ifa->ifa_netmask->sa_len); + tmp_sa->sa_len = sizeof(struct sockaddr_in); + + return (sa_equal(info->rti_info[RTAX_NETMASK], tmp_sa)); +} + +static void +in_rtrequest(int req, struct rtentry *rt, struct rt_addrinfo *info) +{ + + /* + * If we are modifying a route to a subnet, we need to migrate the IFA_ROUTE + * flag to the new source ifa for the route. + */ + if (req == RTM_DELETE && rt->rt_ifa->ifa_flags & IFA_ROUTE && + info->rti_ifa != NULL && in_is_subnet_route(rt->rt_ifa, info)) { + rt->rt_ifa->ifa_flags &= ~IFA_ROUTE; + info->rti_ifa->ifa_flags |= IFA_ROUTE; + } +} Index: tests/sys/netinet/fibs_test.sh =================================================================== --- tests/sys/netinet/fibs_test.sh +++ tests/sys/netinet/fibs_test.sh @@ -720,6 +720,83 @@ cleanup_ifaces } +atf_test_case ipv4_move_subnet_route cleanup +ipv4_move_subnet_route_head() +{ + atf_set "descr" "moving a subnet route to different ifa should be possible" + atf_set "require.user" "root" + atf_set "require.config" "fibs" +} + +ipv4_move_subnet_route_body() +{ + # Configure the TAP interfaces to use a RFC5737 nonrouteable addresses + # and a non-default fib + SUBNET_PREFIX="192.0.2" + SUBNET="${SUBNET_PREFIX}.0" + ADDR0="${SUBNET_PREFIX}.1" + ADDR1="${SUBNET_PREFIX}.2" + + MASK="24" + + # Check system configuration + if [ 0 != `sysctl -n net.add_addr_allfibs` ]; then + atf_skip "This test requires net.add_addr_allfibs=0" + fi + get_fibs 1 + + get_epair + setup_iface "$EPAIRA" "$FIB0" inet ${ADDR0} $MASK + setup_iface "$EPAIRB" "$FIB0" inet ${ADDR1} $MASK + + setfib $FIB0 route change ${SUBNET}/${MASK} -ifp "$EPAIRB" + ifconfig "$EPAIRA" inet ${ADDR0}/${MASK} fib "$FIB0" -alias + atf_check -s exit:0 ifconfig "$EPAIRB" inet ${ADDR0}/$MASK alias fib "$FIB0" +} + +ipv4_move_subnet_route_cleanup() +{ + cleanup_ifaces +} + +atf_test_case ipv6_move_subnet_route cleanup +ipv6_move_subnet_route_head() +{ + atf_set "descr" "moving a subnet route to different ifa should be possible" + atf_set "require.user" "root" + atf_set "require.config" "fibs" +} + +ipv6_move_subnet_route_body() +{ + # Configure the TAP interfaces to use a RFC5737 nonrouteable addresses + # and a non-default fib + SUBNET_PREFIX="2001:db8:" + SUBNET="${SUBNET_PREFIX}:0" + ADDR0="${SUBNET_PREFIX}:1" + ADDR1="${SUBNET_PREFIX}:2" + + MASK="64" + + # Check system configuration + if [ 0 != `sysctl -n net.add_addr_allfibs` ]; then + atf_skip "This test requires net.add_addr_allfibs=0" + fi + get_fibs 1 + + get_epair + setup_iface "$EPAIRA" "$FIB0" inet6 ${ADDR0} $MASK + setup_iface "$EPAIRB" "$FIB0" inet6 ${ADDR1} $MASK + + setfib $FIB0 route -6 change ${SUBNET}/${MASK} -ifp "$EPAIRB" + ifconfig "$EPAIRA" inet6 ${ADDR0}/${MASK} fib "$FIB0" -alias + atf_check -s exit:0 setfib 1 ifconfig "$EPAIRB" inet6 ${ADDR0}/$MASK fib "$FIB0" alias +} + +ipv6_move_subnet_route_cleanup() +{ + cleanup_ifaces +} atf_init_test_cases() { @@ -736,6 +813,8 @@ atf_add_test_case subnet_route_with_multiple_fibs_on_same_subnet_inet6 atf_add_test_case udp_dontroute atf_add_test_case udp_dontroute6 + atf_add_test_case ipv4_move_subnet_route + atf_add_test_case ipv6_move_subnet_route } # Looks up one or more fibs from the configuration data and validates them.