Page MenuHomeFreeBSD

D61250.1794593279.diff
No OneTemporary

Authored By
Unknown
Size
9 KB
Referenced Files
None
Subscribers
None

D61250.1794593279.diff

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

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)

Event Timeline