Skip to content

Commit de3f0fd

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 de3f0fd

File tree

5 files changed

+238
-4
lines changed

5 files changed

+238
-4
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/nexthop_encap.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,15 @@ int nh_encap_parse_msg(struct nlattr *encap, struct nlattr *encap_type,
8888
if (!lwtunnel_encap_types[e_type].ops) {
8989
NL_DBG(2, "RTA_ENCAP_TYPE %s is not implemented\n",
9090
lwtunnel_encap_types[e_type].name);
91-
return -NLE_MSGTYPE_NOSUPPORT;
91+
92+
/* If we don't yet support this lwtunnel, just return 0.
93+
*
94+
* Force encap_out to NULL so that subsequent calls to set
95+
* it on a nexthop/route will simply reset the encapsulation
96+
* on that nexthop/route..
97+
*/
98+
*encap_out = NULL;
99+
return 0;
92100
}
93101

94102
if (!lwtunnel_encap_types[e_type].ops->parse_msg)

lib/route/nh.c

Lines changed: 101 additions & 3 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) {
@@ -174,10 +187,10 @@ static int nh_clone(struct nl_object *_src, struct nl_object *_dst)
174187
static void nh_free(struct nl_object *obj)
175188
{
176189
struct rtnl_nh *nh = nl_object_priv(obj);
177-
nl_addr_put(nh->nh_gateway);
178190

179-
if (nh->nh_group)
180-
rtnl_nh_grp_put(nh->nh_group);
191+
nl_addr_put(nh->nh_gateway);
192+
rtnl_nh_encap_free(nh->nh_encap);
193+
rtnl_nh_grp_put(nh->nh_group);
181194
}
182195

183196
void rtnl_nh_put(struct rtnl_nh *nh)
@@ -237,6 +250,52 @@ struct nl_addr *rtnl_nh_get_gateway(struct rtnl_nh *nexthop)
237250
return nexthop->nh_gateway;
238251
}
239252

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

609+
if (nh->ce_mask & NH_ATTR_ENCAP) {
610+
struct nlattr *encap;
611+
612+
if (!nh->nh_encap || !nh->nh_encap->ops) {
613+
return -NLE_INVAL;
614+
}
615+
NLA_PUT_U16(msg, NHA_ENCAP_TYPE, nh->nh_encap->ops->encap_type);
616+
617+
encap = nla_nest_start(msg, NHA_ENCAP);
618+
if (!encap)
619+
goto nla_put_failure;
620+
621+
if (nh->nh_encap->ops->build_msg) {
622+
int err = nh->nh_encap->ops->build_msg(
623+
msg, nh->nh_encap->priv);
624+
if (err < 0)
625+
return err;
626+
}
627+
nla_nest_end(msg, encap);
628+
}
629+
550630
/* Nexthop group */
551631
if (nh->ce_mask & NH_ATTR_GROUP) {
552632
struct nexthop_grp *grp;
@@ -757,6 +837,19 @@ static int nexthop_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
757837
nexthop->ce_mask |= NH_ATTR_GROUP_TYPE;
758838
}
759839

840+
if (tb[NHA_ENCAP] && tb[NHA_ENCAP_TYPE]) {
841+
_nl_auto_rtnl_nh_encap struct rtnl_nh_encap *nh_encap = NULL;
842+
843+
err = nh_encap_parse_msg(tb[NHA_ENCAP], tb[NHA_ENCAP_TYPE],
844+
&nh_encap);
845+
if (err < 0)
846+
return err;
847+
848+
err = rtnl_nh_set_encap(nexthop, _nl_steal_pointer(&nh_encap));
849+
if (err < 0)
850+
return err;
851+
}
852+
760853
if (tb[NHA_BLACKHOLE]) {
761854
nexthop->ce_mask |= NH_ATTR_FLAG_BLACKHOLE;
762855
}
@@ -869,6 +962,9 @@ static void nh_dump_line(struct nl_object *obj, struct nl_dump_params *dp)
869962
nl_dump(dp, " via %s",
870963
nl_addr2str(nh->nh_gateway, buf, sizeof(buf)));
871964

965+
if (nh->ce_mask & NH_ATTR_ENCAP && nh->nh_encap)
966+
nh_encap_dump(nh->nh_encap, dp);
967+
872968
if (nh->ce_mask & NH_ATTR_FLAG_BLACKHOLE)
873969
nl_dump(dp, " blackhole");
874970

@@ -929,6 +1025,8 @@ static uint64_t nh_compare(struct nl_object *a, struct nl_object *b,
9291025
diff |= _DIFF(NH_ATTR_RES_UNBALANCED_TIMER,
9301026
src->res_grp_unbalanced_timer !=
9311027
dst->res_grp_unbalanced_timer);
1028+
diff |= _DIFF(NH_ATTR_ENCAP,
1029+
nh_encap_compare(src->nh_encap, dst->nh_encap));
9321030
#undef _DIFF
9331031

9341032
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)