Skip to content

Commit c9af703

Browse files
committed
controller: Add support for syncing the nexthop table.
The nexthop table contains entries about nexthop and groups of them. This is utilized by EVPN in scenarios when there are remote VTEPs configured active-active multihoming. Add support for syncing the table from netlink, those data will be later on used to configure the multihoming for OVN EVPN. Reported-at: https://redhat.atlassian.net/browse/FDP-3071 Acked-by: Dumitru Ceara <dceara@redhat.com> Signed-off-by: Ales Musil <amusil@redhat.com>
1 parent 9ae6ae2 commit c9af703

11 files changed

Lines changed: 576 additions & 0 deletions

configure.ac

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ OVS_CHECK_PRAGMA_MESSAGE
174174
OVN_CHECK_VERSION_SUFFIX
175175
OVN_CHECK_OVS
176176
OVN_CHECK_VIF_PLUG_PROVIDER
177+
OVN_CHECK_LINUX_NEXTHOP_WEIGHT
177178
OVN_ENABLE_VIF_PLUG
178179
OVS_CTAGS_IDENTIFIERS
179180
AC_SUBST([OVS_CFLAGS])

controller/automake.mk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ controller_ovn_controller_SOURCES += \
7878
controller/neighbor-exchange-netlink.h \
7979
controller/neighbor-exchange-netlink.c \
8080
controller/neighbor-exchange.c \
81+
controller/nexthop-exchange.h \
82+
controller/nexthop-exchange.c \
8183
controller/route-exchange-netlink.h \
8284
controller/route-exchange-netlink.c \
8385
controller/route-exchange.c
@@ -86,6 +88,7 @@ controller_ovn_controller_SOURCES += \
8688
controller/host-if-monitor-stub.c \
8789
controller/ovn-netlink-notifier-stub.c \
8890
controller/neighbor-exchange-stub.c \
91+
controller/nexthop-exchange-stub.c \
8992
controller/route-exchange-stub.c
9093
endif
9194

controller/nexthop-exchange-stub.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/* Copyright (c) 2026, Red Hat, Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at:
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
#include <config.h>
17+
18+
#include "lib/netlink.h"
19+
#include "openvswitch/hmap.h"
20+
#include "openvswitch/ofpbuf.h"
21+
22+
#include "nexthop-exchange.h"
23+
24+
/* Populates 'nexthops' with all nexthop entries
25+
* (struct nexthop_entry) with fdb flag set that exist in the table. */
26+
void
27+
nexthops_sync(struct hmap *nexthops OVS_UNUSED)
28+
{
29+
}
30+
31+
void
32+
nexthop_entry_format(struct ds *ds OVS_UNUSED,
33+
const struct nexthop_entry *nhe OVS_UNUSED)
34+
{
35+
}
36+
37+
int
38+
nh_table_parse(struct ofpbuf *buf OVS_UNUSED,
39+
struct nh_table_msg *change OVS_UNUSED)
40+
{
41+
return 0;
42+
}

controller/nexthop-exchange.c

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
/* Copyright (c) 2026, Red Hat, Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at:
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
#include <config.h>
17+
#include <linux/rtnetlink.h>
18+
#include <linux/nexthop.h>
19+
20+
#include "lib/netlink.h"
21+
#include "lib/netlink-socket.h"
22+
#include "openvswitch/ofpbuf.h"
23+
#include "openvswitch/vlog.h"
24+
#include "packets.h"
25+
26+
#include "nexthop-exchange.h"
27+
28+
VLOG_DEFINE_THIS_MODULE(nexthop_exchange);
29+
30+
#define NETNL_REQ_BUFFER_SIZE 128
31+
32+
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
33+
34+
static int nh_table_parse__(struct ofpbuf *, size_t ofs,
35+
const struct nlmsghdr *,
36+
struct nh_table_msg *);
37+
static void nh_populate_grp_pointers(struct nexthop_entry *, struct hmap *);
38+
static uint32_t nexthop_entry_hash(uint32_t id);
39+
40+
/* The following definition should be available in Linux 6.12 and might be
41+
* missing if we have older headers. */
42+
#ifndef HAVE_NH_GRP_WEIGHT
43+
static uint16_t
44+
nexthop_grp_weight(const struct nexthop_grp *entry)
45+
{
46+
return entry->weight + 1;
47+
}
48+
#endif
49+
50+
/* Populates 'nexthops' with all nexthop entries
51+
* (struct nexthop_entry) with fdb flag set that exist in the table. */
52+
void
53+
nexthops_sync(struct hmap *nexthops)
54+
{
55+
uint64_t reply_stub[NL_DUMP_BUFSIZE / 8];
56+
struct ofpbuf request, reply, buf;
57+
struct nl_dump dump;
58+
59+
uint8_t request_stub[NETNL_REQ_BUFFER_SIZE];
60+
ofpbuf_use_stub(&request, request_stub, sizeof request_stub);
61+
62+
nl_msg_put_nlmsghdr(&request, sizeof(struct nhmsg),
63+
RTM_GETNEXTHOP, NLM_F_REQUEST);
64+
ofpbuf_put_zeros(&request, sizeof(struct nhmsg));
65+
nl_dump_start(&dump, NETLINK_ROUTE, &request);
66+
ofpbuf_uninit(&request);
67+
68+
ofpbuf_use_stub(&buf, reply_stub, sizeof reply_stub);
69+
while (nl_dump_next(&dump, &reply, &buf)) {
70+
struct nh_table_msg msg;
71+
72+
if (!nh_table_parse(&reply, &msg)) {
73+
continue;
74+
}
75+
76+
hmap_insert(nexthops, &msg.nhe->hmap_node,
77+
nexthop_entry_hash(msg.nhe->id));
78+
}
79+
ofpbuf_uninit(&buf);
80+
nl_dump_done(&dump);
81+
82+
struct nexthop_entry *nhe;
83+
HMAP_FOR_EACH (nhe, hmap_node, nexthops) {
84+
nh_populate_grp_pointers(nhe, nexthops);
85+
}
86+
}
87+
88+
void
89+
nexthop_entry_format(struct ds *ds, const struct nexthop_entry *nhe)
90+
{
91+
ds_put_format(ds, "id=%"PRIu32", ", nhe->id);
92+
if (!nhe->n_grps) {
93+
ds_put_cstr(ds, "address=");
94+
ipv6_format_mapped(&nhe->addr, ds);
95+
} else {
96+
ds_put_cstr(ds, "group=[");
97+
for (size_t i = 0; i < nhe->n_grps; i++) {
98+
const struct nexthop_grp_entry *grp = &nhe->grps[i];
99+
ds_put_format(ds, "%"PRIu32";", grp->id);
100+
if (grp->gateway) {
101+
ipv6_format_mapped(&grp->gateway->addr, ds);
102+
ds_put_char(ds, ';');
103+
}
104+
ds_put_format(ds, "%"PRIu16", ", grp->weight);
105+
}
106+
107+
ds_truncate(ds, ds->length - 2);
108+
ds_put_char(ds, ']');
109+
}
110+
}
111+
112+
/* Parse Netlink message in buf, which is expected to contain a UAPI nhmsg
113+
* header and associated nexthop attributes. This will allocate
114+
* 'struct nexthop_entry' which needs to be freed by the caller.
115+
*
116+
* Return RTNLGRP_NEXTHOP on success, and 0 on a parse error. */
117+
int
118+
nh_table_parse(struct ofpbuf *buf, struct nh_table_msg *change)
119+
{
120+
struct nlmsghdr *nlmsg = ofpbuf_at(buf, 0, NLMSG_HDRLEN);
121+
struct nhmsg *nh = ofpbuf_at(buf, NLMSG_HDRLEN, sizeof *nh);
122+
123+
if (!nlmsg || !nh) {
124+
return 0;
125+
}
126+
127+
return nh_table_parse__(buf, NLMSG_HDRLEN + sizeof *nh,
128+
nlmsg, change);
129+
}
130+
131+
static int
132+
nh_table_parse__(struct ofpbuf *buf, size_t ofs, const struct nlmsghdr *nlmsg,
133+
struct nh_table_msg *change)
134+
{
135+
bool parsed;
136+
137+
static const struct nl_policy policy[] = {
138+
[NHA_ID] = { .type = NL_A_U32 },
139+
[NHA_FDB] = { .type = NL_A_FLAG, .optional = true },
140+
[NHA_GROUP] = { .type = NL_A_UNSPEC, .optional = true,
141+
.min_len = sizeof(struct nexthop_grp) },
142+
[NHA_GATEWAY] = { .type = NL_A_UNSPEC, .optional = true,
143+
.min_len = sizeof(struct in_addr),
144+
.max_len = sizeof(struct in6_addr) },
145+
};
146+
147+
struct nlattr *attrs[ARRAY_SIZE(policy)];
148+
parsed = nl_policy_parse(buf, ofs, policy, attrs, ARRAY_SIZE(policy));
149+
150+
if (!parsed) {
151+
VLOG_DBG_RL(&rl, "received unparseable rtnetlink nexthop message");
152+
return 0;
153+
}
154+
155+
if (!nl_attr_get_flag(attrs[NHA_FDB])) {
156+
return 0;
157+
}
158+
159+
const struct nexthop_grp *grps = NULL;
160+
struct in6_addr addr = in6addr_any;
161+
size_t n_grps = 0;
162+
163+
if (attrs[NHA_GATEWAY]) {
164+
size_t nda_dst_size = nl_attr_get_size(attrs[NHA_GATEWAY]);
165+
166+
switch (nda_dst_size) {
167+
case sizeof(uint32_t):
168+
in6_addr_set_mapped_ipv4(&addr,
169+
nl_attr_get_be32(attrs[NHA_GATEWAY]));
170+
break;
171+
case sizeof(struct in6_addr):
172+
addr = nl_attr_get_in6_addr(attrs[NHA_GATEWAY]);
173+
break;
174+
default:
175+
VLOG_DBG_RL(&rl,
176+
"nexthop message contains non-IPv4/IPv6 NHA_GATEWAY");
177+
return 0;
178+
}
179+
} else if (attrs[NHA_GROUP]) {
180+
n_grps = nl_attr_get_size(attrs[NHA_GROUP]) / sizeof *grps;
181+
grps = nl_attr_get(attrs[NHA_GROUP]);
182+
} else {
183+
VLOG_DBG_RL(&rl, "missing group or gateway nexthop attribute");
184+
return 0;
185+
}
186+
187+
size_t grp_size = n_grps * sizeof(struct nexthop_grp_entry);
188+
change->nlmsg_type = nlmsg->nlmsg_type;
189+
change->nhe = xmalloc(sizeof *change->nhe + grp_size);
190+
*change->nhe = (struct nexthop_entry) {
191+
.id = nl_attr_get_u32(attrs[NHA_ID]),
192+
.addr = addr,
193+
.n_grps = n_grps,
194+
};
195+
196+
for (size_t i = 0; i < n_grps; i++) {
197+
const struct nexthop_grp *grp = &grps[i];
198+
change->nhe->grps[i] = (struct nexthop_grp_entry) {
199+
.id = grp->id,
200+
.weight = nexthop_grp_weight(grp),
201+
/* We need to parse all entries first before adjusting
202+
* the references in 'nh_populate_grp_pointers()' */
203+
.gateway = NULL,
204+
};
205+
}
206+
207+
/* Success. */
208+
return RTNLGRP_NEXTHOP;
209+
}
210+
211+
static uint32_t
212+
nexthop_entry_hash(uint32_t id)
213+
{
214+
return hash_int(id, 0);
215+
}
216+
217+
static struct nexthop_entry *
218+
nexthop_find(struct hmap *nexthops, uint32_t id)
219+
{
220+
uint32_t hash = nexthop_entry_hash(id);
221+
struct nexthop_entry *nhe;
222+
HMAP_FOR_EACH_WITH_HASH (nhe, hmap_node, hash, nexthops) {
223+
if (nhe->id == id) {
224+
return nhe;
225+
}
226+
}
227+
228+
return NULL;
229+
}
230+
231+
static void
232+
nh_populate_grp_pointers(struct nexthop_entry *nhe, struct hmap *nexthops)
233+
{
234+
for (size_t i = 0; i < nhe->n_grps; i++) {
235+
struct nexthop_grp_entry *grp = &nhe->grps[i];
236+
grp->gateway = nexthop_find(nexthops, grp->id);
237+
}
238+
}

controller/nexthop-exchange.h

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* Copyright (c) 2026, Red Hat, Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at:
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
#ifndef NEXTHOP_EXCHANGE_H
17+
#define NEXTHOP_EXCHANGE_H 1
18+
19+
#include <netinet/in.h>
20+
#include <stdint.h>
21+
22+
#include "openvswitch/hmap.h"
23+
24+
struct ds;
25+
struct ofpbuf;
26+
27+
struct nexthop_grp_entry {
28+
/* The id of the nexthop gateway. */
29+
uint32_t id;
30+
/* The weight of the entry. */
31+
uint16_t weight;
32+
/* The pointer to the gateway entry. */
33+
struct nexthop_entry *gateway;
34+
};
35+
36+
struct nexthop_entry {
37+
struct hmap_node hmap_node;
38+
/* The id of the nexthop. */
39+
uint32_t id;
40+
/* Nexthop IP address, zeroed in case of group entry. */
41+
struct in6_addr addr;
42+
/* Number of group entries, "0" in case of gateway entry. */
43+
size_t n_grps;
44+
/* Array of group entries. */
45+
struct nexthop_grp_entry grps[];
46+
};
47+
48+
/* A digested version of a nexthop message sent down by the kernel to indicate
49+
* that a nexthop entry has changed. */
50+
struct nh_table_msg {
51+
/* E.g. RTM_NEWNEXTHOP, RTM_DELNEXTHOP. */
52+
uint16_t nlmsg_type;
53+
/* The inner entry. */
54+
struct nexthop_entry *nhe;
55+
};
56+
57+
void nexthops_sync(struct hmap *nexthops);
58+
void nexthop_entry_format(struct ds *ds, const struct nexthop_entry *nhe);
59+
int nh_table_parse(struct ofpbuf *, struct nh_table_msg *change);
60+
61+
#endif /* NEXTHOP_EXCHANGE_H */

0 commit comments

Comments
 (0)