Skip to content

Commit 6abdf9a

Browse files
committed
[nh-encap] Allow adding a nh_encap to a rtnl_nh
A regular "nexthop object" à la `ip nexthop` can have an associated encapsulation. So, add the code to support that. Signed-off-by: Christoph Paasch <[email protected]>
1 parent acb0156 commit 6abdf9a

File tree

4 files changed

+236
-0
lines changed

4 files changed

+236
-0
lines changed

include/netlink/route/nh.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ extern int rtnl_nh_set_res_group_unbalanced_timer(struct rtnl_nh *,
7272
extern int rtnl_nh_get_res_group_unbalanced_timer(struct rtnl_nh *,
7373
uint32_t *out_value);
7474

75+
/* lwtunnel encapsulation */
76+
struct rtnl_nh_encap;
77+
extern int rtnl_nh_set_encap(struct rtnl_nh *, struct rtnl_nh_encap *);
78+
extern struct rtnl_nh_encap *rtnl_nh_get_encap(struct rtnl_nh *);
79+
7580
#ifdef __cplusplus
7681
}
7782
#endif

lib/route/nh.c

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#include "nl-default.h"
77

88
#include <linux/nexthop.h>
9+
#include <linux/lwtunnel.h>
10+
#include <linux/mpls_iptunnel.h>
911

1012
#include <netlink/route/nh.h>
1113
#include <netlink/hashtable.h>
@@ -29,6 +31,7 @@ struct rtnl_nh {
2931
nl_nh_group_t *nh_group;
3032
uint32_t nh_oif;
3133
struct nl_addr *nh_gateway;
34+
struct rtnl_nh_encap *nh_encap;
3235

3336
/* Resilient nexthop group parameters */
3437
uint16_t res_grp_buckets;
@@ -50,6 +53,7 @@ struct rtnl_nh {
5053
#define NH_ATTR_RES_BUCKETS (1 << 10)
5154
#define NH_ATTR_RES_IDLE_TIMER (1 << 11)
5255
#define NH_ATTR_RES_UNBALANCED_TIMER (1 << 12)
56+
#define NH_ATTR_ENCAP (1 << 13)
5357
/** @endcond */
5458

5559
struct nla_policy rtnl_nh_policy[NHA_MAX + 1] = {
@@ -60,6 +64,8 @@ struct nla_policy rtnl_nh_policy[NHA_MAX + 1] = {
6064
[NHA_BLACKHOLE] = { .type = NLA_UNSPEC },
6165
[NHA_OIF] = { .type = NLA_U32 },
6266
[NHA_RES_GROUP] = { .type = NLA_NESTED },
67+
[NHA_ENCAP] = { .type = NLA_NESTED },
68+
[NHA_ENCAP_TYPE] = { .type = NLA_U16 },
6369
};
6470

6571
static struct nl_cache_ops rtnl_nh_ops;
@@ -155,6 +161,13 @@ static int nh_clone(struct nl_object *_src, struct nl_object *_dst)
155161
dst->res_grp_unbalanced_timer = src->res_grp_unbalanced_timer;
156162
dst->ce_mask = src->ce_mask;
157163

164+
if (src->nh_encap) {
165+
dst->nh_encap = rtnl_nh_encap_clone(src->nh_encap);
166+
if (!dst->nh_encap)
167+
return -NLE_NOMEM;
168+
dst->ce_mask |= NH_ATTR_ENCAP;
169+
}
170+
158171
if (src->nh_gateway) {
159172
dst->nh_gateway = nl_addr_clone(src->nh_gateway);
160173
if (!dst->nh_gateway) {
@@ -176,6 +189,9 @@ static void nh_free(struct nl_object *obj)
176189
struct rtnl_nh *nh = nl_object_priv(obj);
177190
nl_addr_put(nh->nh_gateway);
178191

192+
if (nh->nh_encap)
193+
rtnl_nh_encap_free(nh->nh_encap);
194+
179195
if (nh->nh_group)
180196
rtnl_nh_grp_put(nh->nh_group);
181197
}
@@ -237,6 +253,52 @@ struct nl_addr *rtnl_nh_get_gateway(struct rtnl_nh *nexthop)
237253
return nexthop->nh_gateway;
238254
}
239255

256+
/**
257+
* Set nexthop encapsulation
258+
* @arg nh Nexthop object
259+
* @arg encap Encapsulation descriptor
260+
*
261+
* Assigns ownership of the encapsulation object to the nexthop. Any
262+
* previously configured encapsulation is released. Passing a NULL
263+
* encapsulation clears the encapsulation on the nexthop.
264+
*
265+
* On failure, the function consumes and frees encap.
266+
*
267+
* @return 0 on success, or the appropriate error-code on failure.
268+
*/
269+
int rtnl_nh_set_encap(struct rtnl_nh *nh, struct rtnl_nh_encap *encap)
270+
{
271+
if (!nh) {
272+
rtnl_nh_encap_free(encap);
273+
return -NLE_INVAL;
274+
}
275+
276+
if (encap && !encap->ops) {
277+
rtnl_nh_encap_free(encap);
278+
return -NLE_INVAL;
279+
}
280+
281+
rtnl_nh_encap_free(nh->nh_encap);
282+
283+
if (encap) {
284+
nh->nh_encap = encap;
285+
nh->ce_mask |= NH_ATTR_ENCAP;
286+
} else {
287+
nh->nh_encap = NULL;
288+
nh->ce_mask &= ~NH_ATTR_ENCAP;
289+
}
290+
291+
return 0;
292+
}
293+
294+
struct rtnl_nh_encap *rtnl_nh_get_encap(struct rtnl_nh *nh)
295+
{
296+
if (!nh || !(nh->ce_mask & NH_ATTR_ENCAP))
297+
return NULL;
298+
299+
return nh->nh_encap;
300+
}
301+
240302
int rtnl_nh_set_fdb(struct rtnl_nh *nexthop, int value)
241303
{
242304
if (value)
@@ -547,6 +609,27 @@ static int rtnl_nh_build_msg(struct nl_msg *msg, struct rtnl_nh *nh)
547609
if (nh->ce_mask & NH_ATTR_FLAG_BLACKHOLE)
548610
NLA_PUT_FLAG(msg, NHA_BLACKHOLE);
549611

612+
if (nh->ce_mask & NH_ATTR_ENCAP) {
613+
struct nlattr *encap;
614+
615+
if (!nh->nh_encap || !nh->nh_encap->ops) {
616+
return -NLE_INVAL;
617+
}
618+
NLA_PUT_U16(msg, NHA_ENCAP_TYPE, nh->nh_encap->ops->encap_type);
619+
620+
encap = nla_nest_start(msg, NHA_ENCAP);
621+
if (!encap)
622+
goto nla_put_failure;
623+
624+
if (nh->nh_encap->ops->build_msg) {
625+
int err = nh->nh_encap->ops->build_msg(
626+
msg, nh->nh_encap->priv);
627+
if (err < 0)
628+
return err;
629+
}
630+
nla_nest_end(msg, encap);
631+
}
632+
550633
/* Nexthop group */
551634
if (nh->ce_mask & NH_ATTR_GROUP) {
552635
struct nexthop_grp *grp;
@@ -757,6 +840,26 @@ static int nexthop_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
757840
nexthop->ce_mask |= NH_ATTR_GROUP_TYPE;
758841
}
759842

843+
if (tb[NHA_ENCAP] && tb[NHA_ENCAP_TYPE]) {
844+
int err2;
845+
struct rtnl_nh_encap *nh_encap = NULL;
846+
847+
err2 = nh_encap_parse_msg(tb[NHA_ENCAP], tb[NHA_ENCAP_TYPE],
848+
&nh_encap);
849+
if (err2 == 0) {
850+
err2 = rtnl_nh_set_encap(nexthop, nh_encap);
851+
if (err2 < 0) {
852+
rtnl_nh_encap_free(nh_encap);
853+
return err2;
854+
}
855+
nexthop->ce_mask |= NH_ATTR_ENCAP;
856+
} else if (err2 != -NLE_MSGTYPE_NOSUPPORT) {
857+
return err2;
858+
}
859+
/* If NOSUPPORT, silently ignore to keep parity with previous
860+
* behavior where only MPLS was supported. */
861+
}
862+
760863
if (tb[NHA_BLACKHOLE]) {
761864
nexthop->ce_mask |= NH_ATTR_FLAG_BLACKHOLE;
762865
}
@@ -869,6 +972,9 @@ static void nh_dump_line(struct nl_object *obj, struct nl_dump_params *dp)
869972
nl_dump(dp, " via %s",
870973
nl_addr2str(nh->nh_gateway, buf, sizeof(buf)));
871974

975+
if (nh->ce_mask & NH_ATTR_ENCAP && nh->nh_encap)
976+
nh_encap_dump(nh->nh_encap, dp);
977+
872978
if (nh->ce_mask & NH_ATTR_FLAG_BLACKHOLE)
873979
nl_dump(dp, " blackhole");
874980

@@ -929,6 +1035,8 @@ static uint64_t nh_compare(struct nl_object *a, struct nl_object *b,
9291035
diff |= _DIFF(NH_ATTR_RES_UNBALANCED_TIMER,
9301036
src->res_grp_unbalanced_timer !=
9311037
dst->res_grp_unbalanced_timer);
1038+
diff |= _DIFF(NH_ATTR_ENCAP,
1039+
nh_encap_compare(src->nh_encap, dst->nh_encap));
9321040
#undef _DIFF
9331041

9341042
return diff;

libnl-route-3.sym

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,6 +1391,8 @@ global:
13911391
rtnl_nh_set_res_group_bucket_size;
13921392
rtnl_nh_set_res_group_idle_timer;
13931393
rtnl_nh_set_res_group_unbalanced_timer;
1394+
rtnl_nh_set_encap;
1395+
rtnl_nh_get_encap;
13941396
rtnl_route_nh_set_encap;
13951397
rtnl_route_nh_get_encap;
13961398
} libnl_3_11;

tests/cksuite-route-nh.c

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <netlink/route/nh.h>
1919
#include <netlink/route/link.h>
2020
#include <netlink/route/addr.h>
21+
#include <netlink/route/nexthop.h>
2122

2223
#include "cksuite-all.h"
2324

@@ -75,6 +76,72 @@ START_TEST(test_kernel_roundtrip_basic_v4)
7576
}
7677
END_TEST
7778

79+
/* Kernel round-trip tests for MPLS encap on rtnl_nh */
80+
81+
START_TEST(test_kernel_roundtrip_encap_mpls)
82+
{
83+
const char *IFNAME_DUMMY = "nh-dummy-encap0";
84+
_nltst_auto_delete_link const char *auto_del_dummy = NULL;
85+
_nl_auto_nl_socket struct nl_sock *sk = NULL;
86+
_nl_auto_nl_cache struct nl_cache *cache = NULL;
87+
_nl_auto_rtnl_nh struct rtnl_nh *nh = NULL;
88+
struct rtnl_nh *knh;
89+
struct rtnl_nh_encap *kencap;
90+
_nl_auto_nl_addr struct nl_addr *gw4 = NULL;
91+
_nl_auto_nl_addr struct nl_addr *labels = NULL;
92+
struct rtnl_nh_encap *encap = NULL;
93+
int ifindex_dummy;
94+
95+
if (_nltst_skip_no_netns())
96+
return;
97+
98+
sk = _nltst_socket(NETLINK_ROUTE);
99+
100+
/* Create underlay */
101+
auto_del_dummy = IFNAME_DUMMY;
102+
_nltst_add_link(sk, IFNAME_DUMMY, "dummy", &ifindex_dummy);
103+
_nltst_link_up(sk, IFNAME_DUMMY);
104+
_nltst_addr4_add(sk, ifindex_dummy, "192.0.2.2", 24);
105+
106+
/* Build nexthop: v4 gw over dummy with MPLS encap */
107+
nh = rtnl_nh_alloc();
108+
ck_assert_ptr_nonnull(nh);
109+
ck_assert_int_eq(rtnl_nh_set_id(nh, 3101), 0);
110+
111+
/* Surprisingly the kernel accepts a nexthop with encap and a gw */
112+
ck_assert_int_eq(nl_addr_parse("192.0.2.1", AF_INET, &gw4), 0);
113+
ck_assert_int_eq(rtnl_nh_set_gateway(nh, gw4), 0);
114+
115+
encap = rtnl_nh_encap_alloc();
116+
ck_assert_ptr_nonnull(encap);
117+
ck_assert_int_eq(nl_addr_parse("100", AF_MPLS, &labels), 0);
118+
ck_assert_int_eq(rtnl_nh_encap_mpls(encap, labels, 64), 0);
119+
ck_assert_int_eq(rtnl_nh_set_encap(nh, encap), 0);
120+
121+
/* Fails - we need a family & oif*/
122+
ck_assert_int_eq(rtnl_nh_add(sk, nh, NLM_F_CREATE), -NLE_INVAL);
123+
124+
/* Fails, we need a family */
125+
ck_assert_int_eq(rtnl_nh_set_oif(nh, (uint32_t)ifindex_dummy), 0);
126+
ck_assert_int_eq(rtnl_nh_add(sk, nh, NLM_F_CREATE), -NLE_INVAL);
127+
128+
ck_assert_int_eq(rtnl_nh_set_family(nh, AF_INET), 0);
129+
ck_assert_int_eq(rtnl_nh_add(sk, nh, NLM_F_CREATE), 0);
130+
131+
/* Query and verify */
132+
ck_assert_int_eq(rtnl_nh_alloc_cache(sk, AF_UNSPEC, &cache), 0);
133+
knh = rtnl_nh_get(cache, 3101);
134+
ck_assert_ptr_nonnull(knh);
135+
ck_assert_int_eq(rtnl_nh_get_id(knh), 3101);
136+
ck_assert_int_eq(rtnl_nh_get_oif(knh), ifindex_dummy);
137+
138+
kencap = rtnl_nh_get_encap(knh);
139+
ck_assert_ptr_nonnull(kencap);
140+
ck_assert_ptr_nonnull(rtnl_nh_get_encap_mpls_dst(kencap));
141+
ck_assert_uint_eq(rtnl_nh_get_encap_mpls_ttl(kencap), 64);
142+
}
143+
END_TEST
144+
78145
START_TEST(test_kernel_negative_mismatched_gw_family)
79146
{
80147
const char *IFNAME_DUMMY = "nh-dummy-neg0";
@@ -518,6 +585,56 @@ START_TEST(test_api_set_get_all)
518585
}
519586
END_TEST
520587

588+
/* Userspace tests for MPLS encap on rtnl_nh */
589+
590+
START_TEST(test_api_encap_mpls_set_get)
591+
{
592+
_nl_auto_rtnl_nh struct rtnl_nh *nh = NULL;
593+
struct rtnl_nh_encap *encap = NULL;
594+
struct rtnl_nh_encap *got = NULL;
595+
_nl_auto_nl_addr struct nl_addr *labels = NULL;
596+
597+
/* Allocate nh and an encap container */
598+
nh = rtnl_nh_alloc();
599+
ck_assert_ptr_nonnull(nh);
600+
601+
/* Negative: NULL nh */
602+
encap = rtnl_nh_encap_alloc();
603+
ck_assert_ptr_nonnull(encap);
604+
/* This will free encap */
605+
ck_assert_int_eq(rtnl_nh_set_encap(NULL, encap), -NLE_INVAL);
606+
607+
encap = rtnl_nh_encap_alloc();
608+
ck_assert_ptr_nonnull(encap);
609+
610+
/* "empty" encap (no type set) cannot be assigned. */
611+
ck_assert_int_eq(rtnl_nh_set_encap(nh, encap), -NLE_INVAL);
612+
ck_assert_ptr_eq(rtnl_nh_get_encap_mpls_dst(rtnl_nh_get_encap(nh)),
613+
NULL);
614+
ck_assert_uint_eq(rtnl_nh_get_encap_mpls_ttl(rtnl_nh_get_encap(nh)),
615+
-NLE_INVAL);
616+
617+
encap = rtnl_nh_encap_alloc();
618+
ck_assert_ptr_nonnull(encap);
619+
/* Now build a valid MPLS encap: push label 100 with TTL 64 */
620+
ck_assert_int_eq(nl_addr_parse("100", AF_MPLS, &labels), 0);
621+
ck_assert_int_eq(rtnl_nh_encap_mpls(encap, labels, 64), 0);
622+
623+
/* Attach and retrieve */
624+
ck_assert_int_eq(rtnl_nh_set_encap(nh, encap), 0);
625+
got = rtnl_nh_get_encap(nh);
626+
ck_assert_ptr_nonnull(got);
627+
628+
/* Access MPLS-specific getters */
629+
ck_assert_ptr_nonnull(rtnl_nh_get_encap_mpls_dst(got));
630+
ck_assert_uint_eq(rtnl_nh_get_encap_mpls_ttl(got), 64);
631+
632+
/* Clear encap */
633+
ck_assert_int_eq(rtnl_nh_set_encap(nh, NULL), 0);
634+
ck_assert_ptr_eq(rtnl_nh_get_encap(nh), NULL);
635+
}
636+
END_TEST
637+
521638
Suite *make_nl_route_nh_suite(void)
522639
{
523640
Suite *suite = suite_create("route-nh");
@@ -526,6 +643,8 @@ Suite *make_nl_route_nh_suite(void)
526643

527644
/* Comprehensive API setter/getter test (userspace only) */
528645
tcase_add_test(tc_api, test_api_set_get_all);
646+
/* Userspace encap tests */
647+
tcase_add_test(tc_api, test_api_encap_mpls_set_get);
529648
suite_add_tcase(suite, tc_api);
530649

531650
/* Kernel round-trip – needs private netns */
@@ -538,6 +657,8 @@ Suite *make_nl_route_nh_suite(void)
538657
tcase_add_test(tc_kernel, test_kernel_roundtrip_oif_only);
539658
tcase_add_test(tc_kernel, test_kernel_roundtrip_group_mpath);
540659
tcase_add_test(tc_kernel, test_kernel_roundtrip_group_resilient);
660+
/* Encap (MPLS) on rtnl_nh */
661+
tcase_add_test(tc_kernel, test_kernel_roundtrip_encap_mpls);
541662
/* Negative tests: kernel should reject invalid nexthops */
542663
tcase_add_test(tc_kernel, test_kernel_negative_mismatched_gw_family);
543664
tcase_add_test(tc_kernel, test_kernel_negative_group_without_entries);

0 commit comments

Comments
 (0)