Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F177512473
D61250.1794593279.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
9 KB
Referenced Files
None
Subscribers
None
D61250.1794593279.diff
View Options
diff --git a/sys/net/route/nhgrp_ctl.c b/sys/net/route/nhgrp_ctl.c
--- a/sys/net/route/nhgrp_ctl.c
+++ b/sys/net/route/nhgrp_ctl.c
@@ -74,6 +74,7 @@
CTASSERT(RIB_MAX_MPATH_WIDTH <= 64);
static int wn_cmp_idx(const void *a, const void *b);
+static int wn_cmp(const void *a, const void *b);
static void sort_weightened_nhops(struct weightened_nhop *wn, int num_nhops);
static struct nhgrp_priv *get_nhgrp(struct nh_control *ctl,
@@ -98,15 +99,39 @@
return (0);
}
+/*
+ * Order of the nexthops in a group: by neighbor family, then by index.
+ * Nexthops with a gateway of another family (IPv4 over IPv6) are indexed
+ * in that family's nexthop table, so the index alone is not unique.
+ */
+int
+nhgrp_wn_cmp(const struct weightened_nhop *w_a,
+ const struct weightened_nhop *w_b)
+{
+ uint8_t a_family = w_a->nh->nh_priv->nh_neigh_family;
+ uint8_t b_family = w_b->nh->nh_priv->nh_neigh_family;
+
+ if (a_family != b_family)
+ return (a_family < b_family ? -1 : 1);
+ return (wn_cmp_idx(w_a, w_b));
+}
+
+static int
+wn_cmp(const void *a, const void *b)
+{
+
+ return (nhgrp_wn_cmp(a, b));
+}
+
/*
* Perform in-place sorting for array of nexthops in @wn.
- * Sort by nexthop index ascending.
+ * Sort by neighbor family, then by nexthop index, see nhgrp_wn_cmp().
*/
static void
sort_weightened_nhops(struct weightened_nhop *wn, int num_nhops)
{
- qsort(wn, num_nhops, sizeof(struct weightened_nhop), wn_cmp_idx);
+ qsort(wn, num_nhops, sizeof(struct weightened_nhop), wn_cmp);
}
/*
@@ -534,18 +559,19 @@
/* Sort nexthops & check there are no duplicates */
sort_weightened_nhops(wn, num_nhops);
- uint32_t last_id = 0;
uint32_t min_metric = nhop_get_metric(wn[0].nh);
for (int i = 0; i < num_nhops; i++) {
- if (wn[i].nh->nh_priv->nh_control != ctl) {
+ const struct nhop_priv *nh_priv = wn[i].nh->nh_priv;
+
+ if (nh_priv->nh_fibnum != fibnum ||
+ nh_priv->nh_upper_family != family) {
*perror = EINVAL;
return (NULL);
}
- if (wn[i].nh->nh_priv->nh_idx == last_id) {
+ if (i > 0 && wn[i].nh == wn[i - 1].nh) {
*perror = EEXIST;
return (NULL);
}
- last_id = wn[i].nh->nh_priv->nh_idx;
if (nhop_get_metric(wn[i].nh) < min_metric)
min_metric = nhop_get_metric(wn[i].nh);
@@ -1062,11 +1088,10 @@
* Returns the address family of the nexthop index space @nhg was
* allocated from.
*
- * XXX: we get group indexes from the nh_control, which is keyed by
- * the neighbor family of its members. For now, all members are required
- * to share that nh_control. The ideal is to support nexthop groups spanning
- * multiple address families. Such a group will no longer belong to a
- * single neighbor family.
+ * Groups get their index from the nh_control of the rib they are
+ * allocated in. Their members can come from another family's nh_control
+ * (IPv4 routes with IPv6 gateways), so this is the index space of the
+ * group, not of its members.
*/
uint8_t
nhgrp_get_neigh_family(const struct nhgrp_object *nhg)
diff --git a/sys/net/route/route_helpers.c b/sys/net/route/route_helpers.c
--- a/sys/net/route/route_helpers.c
+++ b/sys/net/route/route_helpers.c
@@ -330,17 +330,16 @@
* of primitive (RTM_DEL, RTM_ADD) operations.
*
* Leverage the fact that each nexthop group has its nexthops sorted
- * by their indices.
+ * by nhgrp_wn_cmp() (neighbor family, then index).
* [1] -> [1, 2] = A{2}
* [1, 2] -> [1] = D{2}
* [1, 2, 4] -> [1, 3, 4] = D{2}, A{3}
* [1, 2] -> [3, 4] = D{1}, D{2}, A{3}, A{4]
*/
while ((idx_old < num_old) && (idx_new < num_new)) {
- uint32_t nh_idx_old = wn_old[idx_old].nh->nh_priv->nh_idx;
- uint32_t nh_idx_new = wn_new[idx_new].nh->nh_priv->nh_idx;
+ int cmp = nhgrp_wn_cmp(&wn_old[idx_old], &wn_new[idx_new]);
- if (nh_idx_old == nh_idx_new) {
+ if (cmp == 0) {
if (wn_old[idx_old].weight != wn_new[idx_new].weight) {
/* Update weight by providing del/add notifications */
notify_del(&rc_del, &wn_old[idx_old], cb, cbdata);
@@ -348,12 +347,11 @@
}
idx_old++;
idx_new++;
- } else if (nh_idx_old < nh_idx_new) {
+ } else if (cmp < 0) {
/* [1, ~2~, 4], [1, ~3~, 4] */
notify_del(&rc_del, &wn_old[idx_old], cb, cbdata);
idx_old++;
} else {
- /* nh_idx_old > nh_idx_new. */
notify_add(&rc_add, &wn_new[idx_new], cb, cbdata);
idx_new++;
}
diff --git a/sys/net/route/route_var.h b/sys/net/route/route_var.h
--- a/sys/net/route/route_var.h
+++ b/sys/net/route/route_var.h
@@ -314,6 +314,8 @@
void nhgrp_ref_object(struct nhgrp_object *nhg);
uint32_t nhgrp_get_idx(const struct nhgrp_object *nhg);
+int nhgrp_wn_cmp(const struct weightened_nhop *w_a,
+ const struct weightened_nhop *w_b);
void nhgrp_free(struct nhgrp_object *nhg);
/* rtsock */
diff --git a/tests/sys/net/routing/test_rtsock_multipath.py b/tests/sys/net/routing/test_rtsock_multipath.py
--- a/tests/sys/net/routing/test_rtsock_multipath.py
+++ b/tests/sys/net/routing/test_rtsock_multipath.py
@@ -1,3 +1,5 @@
+import socket
+
import pytest
from atf_python.sys.net.rtsock import RtConst
from atf_python.sys.net.rtsock import Rtsock
@@ -9,7 +11,12 @@
class TestRtmMultipath(SingleVnetTestTemplate):
def setup_method(self, method):
method_name = method.__name__
- if "multipath4" in method_name:
+ if "multipath4_v6gw" in method_name or "multipath4_mixed" in method_name:
+ # An IPv4 route needs an IPv4 address in the vnet, even via IPv6
+ self.IPV4_PREFIXES = ["192.0.2.1/24"]
+ self.IPV6_PREFIXES = ["2001:db8::1/64"]
+ self.PREFIX = "128.66.0.0/24"
+ elif "multipath4" in method_name:
self.IPV4_PREFIXES = ["192.0.2.1/24"]
self.PREFIX = "128.66.0.0/24"
elif "multipath6" in method_name:
@@ -86,6 +93,104 @@
"""Tests RTM_ADD with IPv6 dest transitioning to multipath"""
self._test_rtm_multipath(gws, "2001:db8:")
+ @pytest.mark.parametrize(
+ "gws",
+ [
+ pytest.param(["+:10=2", "+:5=3"], id="transition_multi"),
+ pytest.param(["+:10=2", "+:5=3", "-:10=2"], id="transition_single1"),
+ pytest.param(
+ ["+:10", "+:11", "+:50", "-:50", "+:145", "+:72"], id="correctness"
+ ),
+ pytest.param(["+:10=2", "+:5=3", "~:5=4"], id="change_new_weight"),
+ ],
+ )
+ @pytest.mark.require_user("root")
+ def test_rtm_multipath4_v6gw(self, gws):
+ """Tests RTM_ADD with IPv4 dest and IPv6 gateways transitioning to multipath"""
+ self._test_rtm_multipath(gws, "2001:db8:")
+
+ @pytest.mark.require_user("root")
+ def test_rtm_multipath4_mixed(self):
+ """Tests an IPv4 multipath route with both IPv4 and IPv6 gateways"""
+ gws = ["192.0.2.10", "2001:db8::10", "192.0.2.11", "2001:db8::11"]
+ for gw in gws:
+ msg = self.rtsock.new_rtm_add(self.PREFIX, gw)
+ msg.rtm_flags = RtConst.RTF_GATEWAY
+ self.rtsock.write_message(msg)
+ data = self.rtsock.read_data(msg.rtm_seq)
+ msg_in = RtsockRtMessage.from_bytes(data)
+ msg_in.print_in_message()
+ assert msg_in.rtm_errno == 0
+
+ routes = self.get_prefix_routes()
+ assert sorted(r["gateway"] for r in routes) == sorted(gws)
+
+ # Removing the IPv4 gateways leaves an IPv6 only group
+ for gw in gws[0::2]:
+ msg = self.rtsock.new_rtm_del(self.PREFIX, gw)
+ msg.rtm_flags = RtConst.RTF_GATEWAY
+ self.rtsock.write_message(msg)
+ self.rtsock.read_data(msg.rtm_seq)
+
+ routes = self.get_prefix_routes()
+ assert sorted(r["gateway"] for r in routes) == sorted(gws[1::2])
+
+ def _add_route(self, prefix: str, gw: str) -> int:
+ msg = self.rtsock.new_rtm_add(prefix, gw)
+ msg.rtm_flags = RtConst.RTF_GATEWAY
+ self.rtsock.write_message(msg)
+ msg_in = RtsockRtMessage.from_bytes(self.rtsock.read_data(msg.rtm_seq))
+ assert msg_in.rtm_errno == 0
+ return msg_in.rtm_rmx.rmx_nhidx
+
+ @staticmethod
+ def _sa_to_ip(sa: bytes) -> str:
+ if sa[1] == socket.AF_INET:
+ return socket.inet_ntop(socket.AF_INET, sa[4:8])
+ return socket.inet_ntop(socket.AF_INET6, sa[8:24])
+
+ @pytest.mark.require_user("root")
+ def test_rtm_multipath4_mixed_notify(self):
+ """Tests the rtsock notifications of paths deleted from a mixed group
+
+ The IPv4 and IPv6 nexthops of the group are indexed in different
+ nexthop tables, so their indexes can't be compared. With more IPv4
+ paths than the IPv6 nexthop index, one of them has a larger index.
+ Delete the IPv4 paths one by one and make sure only the deleted
+ path is reported each time.
+ """
+ gw6 = "2001:db8::10"
+ idx6 = self._add_route(self.PREFIX, gw6)
+ gws4 = ["192.0.2.{}".format(10 + i) for i in range(idx6 + 1)]
+ for gw in gws4:
+ self._add_route(self.PREFIX, gw)
+
+ listener = Rtsock()
+ listener.socket.settimeout(0.3)
+ dst = None
+ for gw4 in gws4:
+ msg = self.rtsock.new_rtm_del(self.PREFIX, gw4)
+ msg.rtm_flags = RtConst.RTF_GATEWAY
+ dst = msg.get_sa(RtConst.RTA_DST)
+ self.rtsock.write_message(msg)
+ self.rtsock.read_data(msg.rtm_seq)
+
+ events = []
+ while True:
+ try:
+ data = listener.read_data()
+ except socket.timeout:
+ break
+ msg_in = RtsockRtMessage.from_bytes(data)
+ if msg_in.get_sa(RtConst.RTA_DST) != dst:
+ continue
+ gw = self._sa_to_ip(msg_in.get_sa(RtConst.RTA_GATEWAY))
+ events.append((msg_in.rtm_type, gw))
+ assert events == [(RtConst.RTM_DELETE, gw4)]
+
+ routes = self.get_prefix_routes()
+ assert [r["gateway"] for r in routes] == [gw6]
+
def _test_rtm_multipath(self, gws, gw_prefix: str):
desired_map = {}
for gw_act in gws:
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Nov 13, 6:07 PM (13 h, 17 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41676452
Default Alt Text
D61250.1794593279.diff (9 KB)
Attached To
Mode
D61250: routing: Allow nexthops of another family in a nexthop group
Attached
Detach File
Event Timeline