From 27bd0a33d895ee55ed91b93d8bcc43bb98bd1bf3 Mon Sep 17 00:00:00 2001 From: Christoph Paasch Date: Thu, 21 Aug 2025 15:18:22 -0700 Subject: [PATCH 1/6] nh_encap: Add rtnl_nh_encap_get_type() As we are adding more encap types, it is useful to have a function that can actually query the type of a nh-encap object. Signed-off-by: Christoph Paasch --- include/netlink/route/nexthop.h | 1 + lib/route/nh.c | 10 ++++++++++ lib/route/nh_encap_mpls.c | 9 ++++++--- libnl-route-3.sym | 1 + tests/cksuite-route-nh.c | 13 ++++++++++++- 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/include/netlink/route/nexthop.h b/include/netlink/route/nexthop.h index abbbabed7..3fcb2f2ed 100644 --- a/include/netlink/route/nexthop.h +++ b/include/netlink/route/nexthop.h @@ -63,6 +63,7 @@ extern int rtnl_route_nh_str2flags(const char *); extern struct rtnl_nh_encap *rtnl_nh_encap_alloc(void); extern void rtnl_nh_encap_free(struct rtnl_nh_encap *nh_encap); extern struct rtnl_nh_encap *rtnl_nh_encap_clone(struct rtnl_nh_encap *src); +extern int rtnl_nh_encap_get_type(struct rtnl_nh_encap *nh_encap); extern int rtnl_nh_encap_mpls(struct rtnl_nh_encap *nh_encap, struct nl_addr *dst, uint8_t ttl); diff --git a/lib/route/nh.c b/lib/route/nh.c index 81ff74f08..13389da79 100644 --- a/lib/route/nh.c +++ b/lib/route/nh.c @@ -737,6 +737,16 @@ struct rtnl_nh_encap *rtnl_nh_encap_alloc(void) return calloc(1, sizeof(struct rtnl_nh_encap)); } +int rtnl_nh_encap_get_type(struct rtnl_nh_encap *nh_encap) +{ + if (!nh_encap) + return -NLE_INVAL; + if (!nh_encap->ops) + return -NLE_INVAL; + + return nh_encap->ops->encap_type; +} + void rtnl_nh_encap_free(struct rtnl_nh_encap *nh_encap) { if (!nh_encap) diff --git a/lib/route/nh_encap_mpls.c b/lib/route/nh_encap_mpls.c index 71ed3bee0..ca72fd90d 100644 --- a/lib/route/nh_encap_mpls.c +++ b/lib/route/nh_encap_mpls.c @@ -174,8 +174,10 @@ int rtnl_route_nh_encap_mpls(struct rtnl_nexthop *nh, struct nl_addr *addr, struct nl_addr *rtnl_nh_get_encap_mpls_dst(struct rtnl_nh_encap *nh_encap) { struct mpls_iptunnel_encap *mpls_encap; + int encap_type; - if (!nh_encap || nh_encap->ops->encap_type != LWTUNNEL_ENCAP_MPLS) + encap_type = rtnl_nh_encap_get_type(nh_encap); + if (encap_type != LWTUNNEL_ENCAP_MPLS) return NULL; mpls_encap = (struct mpls_iptunnel_encap *)nh_encap->priv; @@ -193,9 +195,10 @@ struct nl_addr *rtnl_route_nh_get_encap_mpls_dst(struct rtnl_nexthop *nh) int rtnl_nh_get_encap_mpls_ttl(struct rtnl_nh_encap *nh_encap) { struct mpls_iptunnel_encap *mpls_encap; + int encap_type; - if (!nh_encap || !nh_encap->ops || - nh_encap->ops->encap_type != LWTUNNEL_ENCAP_MPLS) + encap_type = rtnl_nh_encap_get_type(nh_encap); + if (encap_type != LWTUNNEL_ENCAP_MPLS) return -NLE_INVAL; mpls_encap = (struct mpls_iptunnel_encap *)nh_encap->priv; diff --git a/libnl-route-3.sym b/libnl-route-3.sym index de1eb0229..660afe19b 100644 --- a/libnl-route-3.sym +++ b/libnl-route-3.sym @@ -1374,6 +1374,7 @@ global: rtnl_nh_encap_alloc; rtnl_nh_encap_clone; rtnl_nh_encap_free; + rtnl_nh_encap_get_type; rtnl_nh_encap_mpls; rtnl_nh_get_encap; rtnl_nh_get_encap_mpls_dst; diff --git a/tests/cksuite-route-nh.c b/tests/cksuite-route-nh.c index dc54ee64b..9c39f9508 100644 --- a/tests/cksuite-route-nh.c +++ b/tests/cksuite-route-nh.c @@ -12,8 +12,9 @@ #include "nl-default.h" -#include #include +#include +#include #include #include @@ -114,6 +115,8 @@ START_TEST(test_kernel_roundtrip_encap_mpls) encap = rtnl_nh_encap_alloc(); ck_assert_ptr_nonnull(encap); + ck_assert_ptr_null(rtnl_nh_get_encap_mpls_dst(encap)); + ck_assert_int_eq(rtnl_nh_get_encap_mpls_ttl(encap), -NLE_INVAL); ck_assert_int_eq(nl_addr_parse("100", AF_MPLS, &labels), 0); ck_assert_int_eq(rtnl_nh_encap_mpls(encap, labels, 64), 0); ck_assert_int_eq(rtnl_nh_set_encap(nh, encap), 0); @@ -137,6 +140,7 @@ START_TEST(test_kernel_roundtrip_encap_mpls) kencap = rtnl_nh_get_encap(knh); ck_assert_ptr_nonnull(kencap); + ck_assert_int_eq(rtnl_nh_encap_get_type(kencap), LWTUNNEL_ENCAP_MPLS); ck_assert_ptr_nonnull(rtnl_nh_get_encap_mpls_dst(kencap)); ck_assert_uint_eq(rtnl_nh_get_encap_mpls_ttl(kencap), 64); } @@ -613,10 +617,16 @@ START_TEST(test_api_encap_mpls_set_get) NULL); ck_assert_uint_eq(rtnl_nh_get_encap_mpls_ttl(rtnl_nh_get_encap(nh)), -NLE_INVAL); + /* Type getter negatives */ + ck_assert_int_eq(rtnl_nh_encap_get_type(NULL), -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_encap_get_type(rtnl_nh_get_encap(nh)), + -NLE_INVAL); encap = rtnl_nh_encap_alloc(); ck_assert_ptr_nonnull(encap); /* Now build a valid MPLS encap: push label 100 with TTL 64 */ + ck_assert_ptr_null(rtnl_nh_get_encap_mpls_dst(encap)); + ck_assert_int_eq(rtnl_nh_get_encap_mpls_ttl(encap), -NLE_INVAL); ck_assert_int_eq(nl_addr_parse("100", AF_MPLS, &labels), 0); ck_assert_int_eq(rtnl_nh_encap_mpls(encap, labels, 64), 0); @@ -624,6 +634,7 @@ START_TEST(test_api_encap_mpls_set_get) ck_assert_int_eq(rtnl_nh_set_encap(nh, encap), 0); got = rtnl_nh_get_encap(nh); ck_assert_ptr_nonnull(got); + ck_assert_int_eq(rtnl_nh_encap_get_type(got), LWTUNNEL_ENCAP_MPLS); /* Access MPLS-specific getters */ ck_assert_ptr_nonnull(rtnl_nh_get_encap_mpls_dst(got)); From 46485280e94a17d5078ffb0710e7617bd0425278 Mon Sep 17 00:00:00 2001 From: Christoph Paasch Date: Thu, 21 Aug 2025 15:19:29 -0700 Subject: [PATCH 2/6] nh_encap: Add helper to easily check for a valid nh-encap With more nexthop encapsulation types coming, having a helper will simplify the code. Signed-off-by: Christoph Paasch --- lib/route/nexthop-encap.h | 3 +++ lib/route/nexthop_encap.c | 11 +++++++++++ lib/route/nh_encap_mpls.c | 21 +++++++++------------ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/lib/route/nexthop-encap.h b/lib/route/nexthop-encap.h index 12f514a4d..5d296aab5 100644 --- a/lib/route/nexthop-encap.h +++ b/lib/route/nexthop-encap.h @@ -27,6 +27,9 @@ void nh_encap_dump(struct rtnl_nh_encap *rtnh_encap, struct nl_dump_params *dp); int nh_encap_compare(struct rtnl_nh_encap *a, struct rtnl_nh_encap *b); +void *nh_encap_check_and_get_priv(struct rtnl_nh_encap *nh_encap, + uint16_t encap_type); + /* * MPLS encap */ diff --git a/lib/route/nexthop_encap.c b/lib/route/nexthop_encap.c index 4fd406543..3fe7d8e61 100644 --- a/lib/route/nexthop_encap.c +++ b/lib/route/nexthop_encap.c @@ -121,3 +121,14 @@ int nh_encap_compare(struct rtnl_nh_encap *a, struct rtnl_nh_encap *b) return a->ops->compare(a->priv, b->priv); } + +void *nh_encap_check_and_get_priv(struct rtnl_nh_encap *nh_encap, + uint16_t encap_type) +{ + if (!nh_encap || !nh_encap->ops || + nh_encap->ops->encap_type != encap_type) { + return NULL; + } + + return nh_encap->priv; +} diff --git a/lib/route/nh_encap_mpls.c b/lib/route/nh_encap_mpls.c index ca72fd90d..7705375e0 100644 --- a/lib/route/nh_encap_mpls.c +++ b/lib/route/nh_encap_mpls.c @@ -171,16 +171,18 @@ int rtnl_route_nh_encap_mpls(struct rtnl_nexthop *nh, struct nl_addr *addr, return 0; } +static struct mpls_iptunnel_encap * +nh_encap_get_mpls(struct rtnl_nh_encap *nh_encap) +{ + return (struct mpls_iptunnel_encap *)nh_encap_check_and_get_priv( + nh_encap, LWTUNNEL_ENCAP_MPLS); +} + struct nl_addr *rtnl_nh_get_encap_mpls_dst(struct rtnl_nh_encap *nh_encap) { struct mpls_iptunnel_encap *mpls_encap; - int encap_type; - encap_type = rtnl_nh_encap_get_type(nh_encap); - if (encap_type != LWTUNNEL_ENCAP_MPLS) - return NULL; - - mpls_encap = (struct mpls_iptunnel_encap *)nh_encap->priv; + mpls_encap = nh_encap_get_mpls(nh_encap); if (!mpls_encap) return NULL; @@ -195,13 +197,8 @@ struct nl_addr *rtnl_route_nh_get_encap_mpls_dst(struct rtnl_nexthop *nh) int rtnl_nh_get_encap_mpls_ttl(struct rtnl_nh_encap *nh_encap) { struct mpls_iptunnel_encap *mpls_encap; - int encap_type; - - encap_type = rtnl_nh_encap_get_type(nh_encap); - if (encap_type != LWTUNNEL_ENCAP_MPLS) - return -NLE_INVAL; - mpls_encap = (struct mpls_iptunnel_encap *)nh_encap->priv; + mpls_encap = nh_encap_get_mpls(nh_encap); if (!mpls_encap) return -NLE_INVAL; From 9d6b619239e8d7538c4ba430bb134be90566cfa9 Mon Sep 17 00:00:00 2001 From: Christoph Paasch Date: Tue, 23 Sep 2025 08:23:04 -0700 Subject: [PATCH 3/6] tests: Add some more helpers to reduce code duplication Group link-creation and adding an address in a helper. _nltst_add_dummy_v6_with_addr() is not yet used but will be by the next change. Signed-off-by: Christoph Paasch --- tests/cksuite-route-nexthop.c | 15 +++----- tests/cksuite-route-nh.c | 36 ++++++------------ tests/nl-test-util.c | 71 +++++++++++++++++++++++++++++------ tests/nl-test-util.h | 12 +++++- 4 files changed, 87 insertions(+), 47 deletions(-) diff --git a/tests/cksuite-route-nexthop.c b/tests/cksuite-route-nexthop.c index 46f060a6b..34fc1d3b3 100644 --- a/tests/cksuite-route-nexthop.c +++ b/tests/cksuite-route-nexthop.c @@ -231,9 +231,8 @@ START_TEST(test_kernel_route_roundtrip_single_v4) sk = _nltst_socket(NETLINK_ROUTE); auto_del_dummy = IFNAME_DUMMY; - _nltst_add_link(sk, IFNAME_DUMMY, "dummy", &ifindex_dummy); - _nltst_link_up(sk, IFNAME_DUMMY); - _nltst_addr4_add(sk, ifindex_dummy, "192.0.2.2", 24); + _nltst_add_dummy_v4_with_addr(sk, IFNAME_DUMMY, &ifindex_dummy, + "192.0.2.2", 24); /* Build a simple IPv4 route via gateway on dummy */ route = rtnl_route_alloc(); @@ -339,9 +338,8 @@ START_TEST(test_kernel_route_roundtrip_multipath_v4) sk = _nltst_socket(NETLINK_ROUTE); auto_del_dummy = IFNAME_DUMMY; - _nltst_add_link(sk, IFNAME_DUMMY, "dummy", &ifindex_dummy); - _nltst_link_up(sk, IFNAME_DUMMY); - _nltst_addr4_add(sk, ifindex_dummy, "192.0.2.2", 24); + _nltst_add_dummy_v4_with_addr(sk, IFNAME_DUMMY, &ifindex_dummy, + "192.0.2.2", 24); /* Build IPv4 ECMP route with 2 nexthops differing by gateway */ route = rtnl_route_alloc(); @@ -425,9 +423,8 @@ START_TEST(test_kernel_route_roundtrip_nh_mpls_encap_v4) sk = _nltst_socket(NETLINK_ROUTE); auto_del_dummy = IFNAME_DUMMY; - _nltst_add_link(sk, IFNAME_DUMMY, "dummy", &ifindex_dummy); - _nltst_link_up(sk, IFNAME_DUMMY); - _nltst_addr4_add(sk, ifindex_dummy, "192.0.2.2", 24); + _nltst_add_dummy_v4_with_addr(sk, IFNAME_DUMMY, &ifindex_dummy, + "192.0.2.2", 24); /* Build a simple IPv4 route via gateway on dummy with MPLS encap */ route = rtnl_route_alloc(); diff --git a/tests/cksuite-route-nh.c b/tests/cksuite-route-nh.c index 9c39f9508..3025d7658 100644 --- a/tests/cksuite-route-nh.c +++ b/tests/cksuite-route-nh.c @@ -43,13 +43,10 @@ START_TEST(test_kernel_roundtrip_basic_v4) sk = _nltst_socket(NETLINK_ROUTE); - /* Create dummy underlay */ + /* Create dummy underlay, bring up and add an IPv4 address */ auto_del_dummy = IFNAME_DUMMY; - _nltst_add_link(sk, IFNAME_DUMMY, "dummy", &ifindex_dummy); - - /* Bring up and add an IPv4 address via libnl */ - _nltst_link_up(sk, IFNAME_DUMMY); - _nltst_addr4_add(sk, ifindex_dummy, "192.0.2.2", 24); + _nltst_add_dummy_v4_with_addr(sk, IFNAME_DUMMY, &ifindex_dummy, + "192.0.2.2", 24); /* Build nexthop: v4 gateway over dummy OIF with explicit id */ nh = rtnl_nh_alloc(); @@ -98,11 +95,10 @@ START_TEST(test_kernel_roundtrip_encap_mpls) sk = _nltst_socket(NETLINK_ROUTE); - /* Create underlay */ + /* Create underlay with IPv4 address */ auto_del_dummy = IFNAME_DUMMY; - _nltst_add_link(sk, IFNAME_DUMMY, "dummy", &ifindex_dummy); - _nltst_link_up(sk, IFNAME_DUMMY); - _nltst_addr4_add(sk, ifindex_dummy, "192.0.2.2", 24); + _nltst_add_dummy_v4_with_addr(sk, IFNAME_DUMMY, &ifindex_dummy, + "192.0.2.2", 24); /* Build nexthop: v4 gw over dummy with MPLS encap */ nh = rtnl_nh_alloc(); @@ -161,8 +157,7 @@ START_TEST(test_kernel_negative_mismatched_gw_family) sk = _nltst_socket(NETLINK_ROUTE); auto_del_dummy = IFNAME_DUMMY; - _nltst_add_link(sk, IFNAME_DUMMY, "dummy", &ifindex_dummy); - _nltst_link_up(sk, IFNAME_DUMMY); + _nltst_add_dummy_and_up(sk, IFNAME_DUMMY, &ifindex_dummy); /* Build nexthop with AF_INET6 but an IPv4 gateway -> invalid */ nh = rtnl_nh_alloc(); @@ -214,8 +209,7 @@ START_TEST(test_kernel_negative_gateway_without_oif) /* Create a dummy device to avoid dependency on system state */ auto_del_dummy = IFNAME_DUMMY; - _nltst_add_link(sk, IFNAME_DUMMY, "dummy", &ifindex_dummy); - _nltst_link_up(sk, IFNAME_DUMMY); + _nltst_add_dummy_and_up(sk, IFNAME_DUMMY, &ifindex_dummy); /* Build nexthop with IPv4 gateway but no OIF -> invalid */ nh = rtnl_nh_alloc(); @@ -246,10 +240,7 @@ START_TEST(test_kernel_roundtrip_oif_only) sk = _nltst_socket(NETLINK_ROUTE); auto_del_dummy = IFNAME_DUMMY; - _nltst_add_link(sk, IFNAME_DUMMY, "dummy", &ifindex_dummy); - - /* Bring interface up via libnl */ - _nltst_link_up(sk, IFNAME_DUMMY); + _nltst_add_dummy_and_up(sk, IFNAME_DUMMY, &ifindex_dummy); /* Build nexthop: OIF only, unspecified family */ nh = rtnl_nh_alloc(); @@ -296,10 +287,7 @@ START_TEST(test_kernel_roundtrip_group_mpath) sk = _nltst_socket(NETLINK_ROUTE); auto_del_dummy = IFNAME_DUMMY; - _nltst_add_link(sk, IFNAME_DUMMY, "dummy", &ifindex_dummy); - - /* Bring interface up via libnl */ - _nltst_link_up(sk, IFNAME_DUMMY); + _nltst_add_dummy_and_up(sk, IFNAME_DUMMY, &ifindex_dummy); /* Two basic nexthops to reference in the group */ nh1 = rtnl_nh_alloc(); @@ -372,9 +360,7 @@ START_TEST(test_kernel_roundtrip_group_resilient) sk = _nltst_socket(NETLINK_ROUTE); auto_del_dummy = IFNAME_DUMMY; - _nltst_add_link(sk, IFNAME_DUMMY, "dummy", &ifindex_dummy); - /* Bring interface up via libnl */ - _nltst_link_up(sk, IFNAME_DUMMY); + _nltst_add_dummy_and_up(sk, IFNAME_DUMMY, &ifindex_dummy); /* Two basic nexthops to reference in the group */ nh1 = rtnl_nh_alloc(); diff --git a/tests/nl-test-util.c b/tests/nl-test-util.c index 74d5ad81c..51c04c69f 100644 --- a/tests/nl-test-util.c +++ b/tests/nl-test-util.c @@ -572,18 +572,6 @@ void _nltst_get_link(struct nl_sock *sk, const char *ifname, int *out_ifindex, } } -void _nltst_link_up(struct nl_sock *sk, const char *ifname) -{ - _nl_auto_rtnl_link struct rtnl_link *link_obj = NULL; - _nl_auto_rtnl_link struct rtnl_link *change = NULL; - - _nltst_get_link(sk, ifname, NULL, &link_obj); - change = rtnl_link_alloc(); - ck_assert_ptr_nonnull(change); - rtnl_link_set_flags(change, IFF_UP); - ck_assert_int_eq(rtnl_link_change(sk, link_obj, change, 0), 0); -} - void _nltst_addr4_add(struct nl_sock *sk, int ifindex, const char *ip, int prefixlen) { @@ -600,6 +588,22 @@ void _nltst_addr4_add(struct nl_sock *sk, int ifindex, const char *ip, ck_assert_int_eq(rtnl_addr_add(sk, addr, 0), 0); } +void _nltst_addr6_add(struct nl_sock *sk, int ifindex, const char *ip, + int prefixlen) +{ + _nl_auto_rtnl_addr struct rtnl_addr *addr = NULL; + _nl_auto_nl_addr struct nl_addr *local6 = NULL; + + addr = rtnl_addr_alloc(); + ck_assert_ptr_nonnull(addr); + rtnl_addr_set_ifindex(addr, ifindex); + rtnl_addr_set_family(addr, AF_INET6); + ck_assert_int_eq(nl_addr_parse(ip, AF_INET6, &local6), 0); + ck_assert_int_eq(rtnl_addr_set_local(addr, local6), 0); + rtnl_addr_set_prefixlen(addr, prefixlen); + ck_assert_int_eq(rtnl_addr_add(sk, addr, 0), 0); +} + struct nl_cache *_nltst_rtnl_link_alloc_cache(struct nl_sock *sk, int addr_family, unsigned flags) { @@ -834,6 +838,49 @@ bool _nltst_skip_no_iproute2(const char *msg) /*****************************************************************************/ +void _nltst_add_dummy_and_up(struct nl_sock *sk, const char *ifname, + int *out_ifindex) +{ + _nl_auto_rtnl_link struct rtnl_link *link_obj = NULL; + _nl_auto_rtnl_link struct rtnl_link *change = NULL; + + /* Add the link */ + _nltst_add_link(sk, ifname, "dummy", out_ifindex); + + /* Bring the link up */ + _nltst_get_link(sk, ifname, NULL, &link_obj); + change = rtnl_link_alloc(); + ck_assert_ptr_nonnull(change); + rtnl_link_set_flags(change, IFF_UP); + ck_assert_int_eq(rtnl_link_change(sk, link_obj, change, 0), 0); +} + +void _nltst_add_dummy_v4_with_addr(struct nl_sock *sk, const char *ifname, + int *out_ifindex, const char *ip, + int prefixlen) +{ + int ifindex_local; + + _nltst_add_dummy_and_up(sk, ifname, &ifindex_local); + _nltst_addr4_add(sk, ifindex_local, ip, prefixlen); + if (out_ifindex) + *out_ifindex = ifindex_local; +} + +void _nltst_add_dummy_v6_with_addr(struct nl_sock *sk, const char *ifname, + int *out_ifindex, const char *ip, + int prefixlen) +{ + int ifindex_local; + + _nltst_add_dummy_and_up(sk, ifname, &ifindex_local); + _nltst_addr6_add(sk, ifindex_local, ip, prefixlen); + if (out_ifindex) + *out_ifindex = ifindex_local; +} + +/*****************************************************************************/ + void _nltst_select_route_clear(NLTstSelectRoute *select_route) { _nltst_assert_select_route(select_route); diff --git a/tests/nl-test-util.h b/tests/nl-test-util.h index 8b3b94cd8..ecae8856a 100644 --- a/tests/nl-test-util.h +++ b/tests/nl-test-util.h @@ -542,9 +542,19 @@ void _nltst_delete_link(struct nl_sock *sk, const char *ifname); void _nltst_get_link(struct nl_sock *sk, const char *ifname, int *out_ifindex, struct rtnl_link **out_link); -void _nltst_link_up(struct nl_sock *sk, const char *ifname); +void _nltst_add_dummy_and_up(struct nl_sock *sk, const char *ifname, + int *out_ifindex); +void _nltst_add_dummy_v4_with_addr(struct nl_sock *sk, const char *ifname, + int *out_ifindex, const char *ip, + int prefixlen); +void _nltst_add_dummy_v6_with_addr(struct nl_sock *sk, const char *ifname, + int *out_ifindex, const char *ip, + int prefixlen); + void _nltst_addr4_add(struct nl_sock *sk, int ifindex, const char *ip, int prefixlen); +void _nltst_addr6_add(struct nl_sock *sk, int ifindex, const char *ip, + int prefixlen); void _nltst_assert_route_list(struct nl_object *const *objs, ssize_t len, const char *const *expected_routes); From 83dcde20d07bfef48592f01bd38bc37222fd8c15 Mon Sep 17 00:00:00 2001 From: Christoph Paasch Date: Tue, 23 Sep 2025 08:26:05 -0700 Subject: [PATCH 4/6] ip6-encap: Add support for an IP6 nexthop encapsulation and tests Signed-off-by: Christoph Paasch --- Makefile.am | 1 + include/netlink/route/nexthop.h | 16 ++ lib/route/nexthop-encap.h | 5 + lib/route/nexthop_encap.c | 2 +- lib/route/nh_encap_ip6.c | 391 ++++++++++++++++++++++++++++++++ libnl-route-3.sym | 12 + tests/cksuite-route-nh.c | 204 +++++++++++++++++ 7 files changed, 630 insertions(+), 1 deletion(-) create mode 100644 lib/route/nh_encap_ip6.c diff --git a/Makefile.am b/Makefile.am index 8d70bb6b8..2e8b03973 100644 --- a/Makefile.am +++ b/Makefile.am @@ -528,6 +528,7 @@ lib_libnl_route_3_la_SOURCES = \ lib/route/nexthop.c \ lib/route/nexthop_encap.c \ lib/route/nh.c \ + lib/route/nh_encap_ip6.c \ lib/route/nh_encap_mpls.c \ lib/route/nl-route.h \ lib/route/pktloc.c \ diff --git a/include/netlink/route/nexthop.h b/include/netlink/route/nexthop.h index 3fcb2f2ed..48359d4ba 100644 --- a/include/netlink/route/nexthop.h +++ b/include/netlink/route/nexthop.h @@ -65,6 +65,7 @@ extern void rtnl_nh_encap_free(struct rtnl_nh_encap *nh_encap); extern struct rtnl_nh_encap *rtnl_nh_encap_clone(struct rtnl_nh_encap *src); extern int rtnl_nh_encap_get_type(struct rtnl_nh_encap *nh_encap); +/* MPLS encap */ extern int rtnl_nh_encap_mpls(struct rtnl_nh_encap *nh_encap, struct nl_addr *dst, uint8_t ttl); struct nl_addr *rtnl_nh_get_encap_mpls_dst(struct rtnl_nh_encap *); @@ -74,6 +75,21 @@ extern int rtnl_route_nh_encap_mpls(struct rtnl_nexthop *nh, struct nl_addr *addr, uint8_t ttl); extern struct nl_addr *rtnl_route_nh_get_encap_mpls_dst(struct rtnl_nexthop *); extern uint8_t rtnl_route_nh_get_encap_mpls_ttl(struct rtnl_nexthop *); + +/* IPv6 encap */ +extern int rtnl_nh_encap_ip6(struct rtnl_nh_encap *nh_encap, + struct nl_addr *dst); +struct nl_addr *rtnl_nh_get_encap_ip6_dst(struct rtnl_nh_encap *); +extern int rtnl_nh_set_encap_ip6_id(struct rtnl_nh_encap *, uint64_t); +extern uint64_t rtnl_nh_get_encap_ip6_id(struct rtnl_nh_encap *); +extern int rtnl_nh_set_encap_ip6_hoplimit(struct rtnl_nh_encap *, uint8_t); +extern int rtnl_nh_get_encap_ip6_hoplimit(struct rtnl_nh_encap *); +extern int rtnl_nh_set_encap_ip6_tc(struct rtnl_nh_encap *, uint8_t); +extern int rtnl_nh_get_encap_ip6_tc(struct rtnl_nh_encap *); +extern int rtnl_nh_set_encap_ip6_src(struct rtnl_nh_encap *, struct nl_addr *); +struct nl_addr *rtnl_nh_get_encap_ip6_src(struct rtnl_nh_encap *); +extern int rtnl_nh_set_encap_ip6_flags(struct rtnl_nh_encap *, uint16_t); +extern int rtnl_nh_get_encap_ip6_flags(struct rtnl_nh_encap *); #ifdef __cplusplus } #endif diff --git a/lib/route/nexthop-encap.h b/lib/route/nexthop-encap.h index 5d296aab5..f4b607d2e 100644 --- a/lib/route/nexthop-encap.h +++ b/lib/route/nexthop-encap.h @@ -34,4 +34,9 @@ void *nh_encap_check_and_get_priv(struct rtnl_nh_encap *nh_encap, * MPLS encap */ extern struct nh_encap_ops mpls_encap_ops; + +/* + * IPv6 encap + */ +extern struct nh_encap_ops ip6_encap_ops; #endif diff --git a/lib/route/nexthop_encap.c b/lib/route/nexthop_encap.c index 3fe7d8e61..09dacae4b 100644 --- a/lib/route/nexthop_encap.c +++ b/lib/route/nexthop_encap.c @@ -14,7 +14,7 @@ static struct lwtunnel_encap_type { [LWTUNNEL_ENCAP_NONE] = { .name = "none" }, [LWTUNNEL_ENCAP_MPLS] = { .name = "mpls", .ops = &mpls_encap_ops }, [LWTUNNEL_ENCAP_IP] = { .name = "ip" }, - [LWTUNNEL_ENCAP_IP6] = { .name = "ip6" }, + [LWTUNNEL_ENCAP_IP6] = { .name = "ip6", .ops = &ip6_encap_ops }, [LWTUNNEL_ENCAP_ILA] = { .name = "ila" }, [LWTUNNEL_ENCAP_BPF] = { .name = "bpf" }, }; diff --git a/lib/route/nh_encap_ip6.c b/lib/route/nh_encap_ip6.c new file mode 100644 index 000000000..3262dae59 --- /dev/null +++ b/lib/route/nh_encap_ip6.c @@ -0,0 +1,391 @@ +/* SPDX-License-Identifier: LGPL-2.1-only */ + +#include "nl-default.h" + +#include +#include + +#include + +#include "nexthop-encap.h" +#include "nl-aux-core/nl-core.h" +#include "nl-aux-route/nl-route.h" +#include "nl-route.h" + +struct ip6_tunnel_encap { + struct nl_addr *dst; + struct nl_addr *src; + uint64_t id; + uint8_t tc; + uint8_t hoplimit; + uint16_t flags; +}; + +static void ip6_encap_dump(void *priv, struct nl_dump_params *dp) +{ + struct ip6_tunnel_encap *encap_info = priv; + char buf[256]; + + if (encap_info->id) + nl_dump(dp, "id %llu ", (unsigned long long)encap_info->id); + + nl_dump(dp, "%s ", nl_addr2str(encap_info->dst, buf, sizeof(buf))); + + if (encap_info->src) + nl_dump(dp, "src %s ", + nl_addr2str(encap_info->src, buf, sizeof(buf))); + + if (encap_info->tc) + nl_dump(dp, "tc %u ", encap_info->tc); + + if (encap_info->hoplimit) + nl_dump(dp, "hoplimit %u ", encap_info->hoplimit); + + if (encap_info->flags) { + if (encap_info->flags & TUNNEL_KEY) + nl_dump(dp, "key "); + if (encap_info->flags & TUNNEL_CSUM) + nl_dump(dp, "csum "); + if (encap_info->flags & TUNNEL_SEQ) + nl_dump(dp, "seq "); + } +} + +static int ip6_encap_build_msg(struct nl_msg *msg, void *priv) +{ + struct ip6_tunnel_encap *encap_info = priv; + + if (encap_info->id) + NLA_PUT_U64(msg, LWTUNNEL_IP6_ID, htonll(encap_info->id)); + NLA_PUT_ADDR(msg, LWTUNNEL_IP6_DST, encap_info->dst); + if (encap_info->src) + NLA_PUT_ADDR(msg, LWTUNNEL_IP6_SRC, encap_info->src); + if (encap_info->tc) + NLA_PUT_U8(msg, LWTUNNEL_IP6_TC, encap_info->tc); + if (encap_info->hoplimit) + NLA_PUT_U8(msg, LWTUNNEL_IP6_HOPLIMIT, encap_info->hoplimit); + if (encap_info->flags) + NLA_PUT_U16(msg, LWTUNNEL_IP6_FLAGS, encap_info->flags); + + return 0; + +nla_put_failure: + return -NLE_MSGSIZE; +} + +static void ip6_encap_destructor(void *priv) +{ + struct ip6_tunnel_encap *encap_info = priv; + + nl_addr_put(encap_info->dst); + nl_addr_put(encap_info->src); +} + +static void *ip6_encap_clone(void *priv) +{ + struct ip6_tunnel_encap *src = priv; + struct ip6_tunnel_encap *clone; + + if (!src) + return NULL; + + clone = calloc(1, sizeof(*clone)); + if (!clone) + return NULL; + + clone->dst = nl_addr_get(src->dst); + if (src->src) + clone->src = nl_addr_get(src->src); + clone->id = src->id; + clone->tc = src->tc; + clone->hoplimit = src->hoplimit; + clone->flags = src->flags; + + return clone; +} + +static struct nla_policy ip6_encap_policy[LWTUNNEL_IP6_MAX + 1] = { + [LWTUNNEL_IP6_ID] = { .type = NLA_U64 }, + [LWTUNNEL_IP6_DST] = { .type = NLA_BINARY, .minlen = 16, .maxlen = 16 }, + [LWTUNNEL_IP6_SRC] = { .type = NLA_BINARY, .minlen = 16, .maxlen = 16 }, + [LWTUNNEL_IP6_HOPLIMIT] = { .type = NLA_U8 }, + [LWTUNNEL_IP6_TC] = { .type = NLA_U8 }, + [LWTUNNEL_IP6_FLAGS] = { .type = NLA_U16 }, +}; + +static int ip6_encap_parse_msg(struct nlattr *nla, + struct rtnl_nh_encap **encap_out) +{ + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap *nh_encap = NULL; + _nl_auto_nl_addr struct nl_addr *dst = NULL; + _nl_auto_nl_addr struct nl_addr *src = NULL; + struct nlattr *tb[LWTUNNEL_IP6_MAX + 1]; + uint16_t flags = 0; + uint64_t id = 0; + uint8_t hoplimit = 0; + uint8_t tc = 0; + int err; + + err = nla_parse_nested(tb, LWTUNNEL_IP6_MAX, nla, ip6_encap_policy); + if (err < 0) + return err; + + if (!tb[LWTUNNEL_IP6_DST]) + return -NLE_INVAL; + + dst = nl_addr_alloc_attr(tb[LWTUNNEL_IP6_DST], AF_INET6); + if (!dst) + return -NLE_NOMEM; + + if (tb[LWTUNNEL_IP6_SRC]) { + src = nl_addr_alloc_attr(tb[LWTUNNEL_IP6_SRC], AF_INET6); + if (!src) + return -NLE_NOMEM; + } + + if (tb[LWTUNNEL_IP6_HOPLIMIT]) + hoplimit = nla_get_u8(tb[LWTUNNEL_IP6_HOPLIMIT]); + + if (tb[LWTUNNEL_IP6_TC]) + tc = nla_get_u8(tb[LWTUNNEL_IP6_TC]); + + if (tb[LWTUNNEL_IP6_ID]) + id = ntohll(nla_get_u64(tb[LWTUNNEL_IP6_ID])); + + if (tb[LWTUNNEL_IP6_FLAGS]) + flags = nla_get_u16(tb[LWTUNNEL_IP6_FLAGS]); + + nh_encap = rtnl_nh_encap_alloc(); + if (!nh_encap) + return -NLE_NOMEM; + + err = rtnl_nh_encap_ip6(nh_encap, dst); + if (err < 0) + return err; + + if (src) { + err = rtnl_nh_set_encap_ip6_src(nh_encap, src); + if (err < 0) + return err; + } + + if (id) { + err = rtnl_nh_set_encap_ip6_id(nh_encap, id); + if (err < 0) + return err; + } + + if (hoplimit) { + err = rtnl_nh_set_encap_ip6_hoplimit(nh_encap, hoplimit); + if (err < 0) + return err; + } + + if (tc) { + err = rtnl_nh_set_encap_ip6_tc(nh_encap, tc); + if (err < 0) + return err; + } + + if (flags) { + err = rtnl_nh_set_encap_ip6_flags(nh_encap, flags); + if (err < 0) + return err; + } + + *encap_out = _nl_steal_pointer(&nh_encap); + + return 0; +} + +static int ip6_encap_compare(void *_a, void *_b) +{ + struct ip6_tunnel_encap *a = _a; + struct ip6_tunnel_encap *b = _b; + int diff = 0; + + diff |= (a->tc != b->tc); + diff |= (a->hoplimit != b->hoplimit); + diff |= (a->flags != b->flags); + diff |= (a->id != b->id); + diff |= nl_addr_cmp(a->src, b->src); + diff |= nl_addr_cmp(a->dst, b->dst); + + return diff; +} + +struct nh_encap_ops ip6_encap_ops = { + .encap_type = LWTUNNEL_ENCAP_IP6, + .build_msg = ip6_encap_build_msg, + .parse_msg = ip6_encap_parse_msg, + .compare = ip6_encap_compare, + .clone = ip6_encap_clone, + .dump = ip6_encap_dump, + .destructor = ip6_encap_destructor, +}; + +int rtnl_nh_encap_ip6(struct rtnl_nh_encap *nh_encap, struct nl_addr *dst) +{ + struct ip6_tunnel_encap *ip6_encap; + + if (!dst || !nh_encap) + return -NLE_INVAL; + if (nl_addr_get_family(dst) != AF_INET6) + return -NLE_INVAL; + + ip6_encap = calloc(1, sizeof(*ip6_encap)); + if (!ip6_encap) + return -NLE_NOMEM; + + ip6_encap->dst = nl_addr_get(dst); + + nh_encap->priv = ip6_encap; + nh_encap->ops = &ip6_encap_ops; + + return 0; +} + +static struct ip6_tunnel_encap *nh_encap_get_ip6(struct rtnl_nh_encap *nh_encap) +{ + return (struct ip6_tunnel_encap *)nh_encap_check_and_get_priv( + nh_encap, LWTUNNEL_ENCAP_IP6); +} + +struct nl_addr *rtnl_nh_get_encap_ip6_dst(struct rtnl_nh_encap *nh_encap) +{ + struct ip6_tunnel_encap *ip6_encap; + + ip6_encap = nh_encap_get_ip6(nh_encap); + if (!ip6_encap) + return NULL; + + return ip6_encap->dst; +} + +int rtnl_nh_set_encap_ip6_src(struct rtnl_nh_encap *nh_encap, + struct nl_addr *src) +{ + struct ip6_tunnel_encap *ip6_encap; + struct nl_addr *old_src; + + ip6_encap = nh_encap_get_ip6(nh_encap); + if (!ip6_encap) + return -NLE_INVAL; + if (src && nl_addr_get_family(src) != AF_INET6) + return -NLE_INVAL; + + old_src = ip6_encap->src; + if (src) + ip6_encap->src = nl_addr_get(src); + else + ip6_encap->src = NULL; + + nl_addr_put(old_src); + + return 0; +} + +struct nl_addr *rtnl_nh_get_encap_ip6_src(struct rtnl_nh_encap *nh_encap) +{ + struct ip6_tunnel_encap *ip6_encap; + + ip6_encap = nh_encap_get_ip6(nh_encap); + if (!ip6_encap) + return NULL; + + return ip6_encap->src; +} + +int rtnl_nh_set_encap_ip6_tc(struct rtnl_nh_encap *nh_encap, uint8_t tc) +{ + struct ip6_tunnel_encap *ip6_encap; + + ip6_encap = nh_encap_get_ip6(nh_encap); + if (!ip6_encap) + return -NLE_INVAL; + + ip6_encap->tc = tc; + return 0; +} + +int rtnl_nh_get_encap_ip6_tc(struct rtnl_nh_encap *nh_encap) +{ + struct ip6_tunnel_encap *ip6_encap; + + ip6_encap = nh_encap_get_ip6(nh_encap); + if (!ip6_encap) + return -NLE_INVAL; + + return ip6_encap->tc; +} + +int rtnl_nh_set_encap_ip6_hoplimit(struct rtnl_nh_encap *nh_encap, + uint8_t hoplimit) +{ + struct ip6_tunnel_encap *ip6_encap; + + ip6_encap = nh_encap_get_ip6(nh_encap); + if (!ip6_encap) + return -NLE_INVAL; + + ip6_encap->hoplimit = hoplimit; + return 0; +} + +int rtnl_nh_get_encap_ip6_hoplimit(struct rtnl_nh_encap *nh_encap) +{ + struct ip6_tunnel_encap *ip6_encap; + + ip6_encap = nh_encap_get_ip6(nh_encap); + if (!ip6_encap) + return -NLE_INVAL; + + return ip6_encap->hoplimit; +} + +int rtnl_nh_set_encap_ip6_flags(struct rtnl_nh_encap *nh_encap, uint16_t flags) +{ + struct ip6_tunnel_encap *ip6_encap; + + ip6_encap = nh_encap_get_ip6(nh_encap); + if (!ip6_encap) + return -NLE_INVAL; + + ip6_encap->flags = flags; + return 0; +} + +int rtnl_nh_get_encap_ip6_flags(struct rtnl_nh_encap *nh_encap) +{ + struct ip6_tunnel_encap *ip6_encap; + + ip6_encap = nh_encap_get_ip6(nh_encap); + if (!ip6_encap) + return -NLE_INVAL; + + return ip6_encap->flags; +} + +int rtnl_nh_set_encap_ip6_id(struct rtnl_nh_encap *nh_encap, uint64_t id) +{ + struct ip6_tunnel_encap *ip6_encap; + + ip6_encap = nh_encap_get_ip6(nh_encap); + if (!ip6_encap) + return -NLE_INVAL; + + ip6_encap->id = id; + + return 0; +} + +uint64_t rtnl_nh_get_encap_ip6_id(struct rtnl_nh_encap *nh_encap) +{ + struct ip6_tunnel_encap *ip6_encap; + + ip6_encap = nh_encap_get_ip6(nh_encap); + if (!ip6_encap) + return 0; + + return ip6_encap->id; +} diff --git a/libnl-route-3.sym b/libnl-route-3.sym index 660afe19b..a6c762b42 100644 --- a/libnl-route-3.sym +++ b/libnl-route-3.sym @@ -1375,8 +1375,15 @@ global: rtnl_nh_encap_clone; rtnl_nh_encap_free; rtnl_nh_encap_get_type; + rtnl_nh_encap_ip6; rtnl_nh_encap_mpls; rtnl_nh_get_encap; + rtnl_nh_get_encap_ip6_dst; + rtnl_nh_get_encap_ip6_flags; + rtnl_nh_get_encap_ip6_hoplimit; + rtnl_nh_get_encap_ip6_id; + rtnl_nh_get_encap_ip6_src; + rtnl_nh_get_encap_ip6_tc; rtnl_nh_get_encap_mpls_dst; rtnl_nh_get_encap_mpls_ttl; rtnl_nh_get_family; @@ -1386,6 +1393,11 @@ global: rtnl_nh_get_res_group_idle_timer; rtnl_nh_get_res_group_unbalanced_timer; rtnl_nh_set_encap; + rtnl_nh_set_encap_ip6_flags; + rtnl_nh_set_encap_ip6_hoplimit; + rtnl_nh_set_encap_ip6_id; + rtnl_nh_set_encap_ip6_src; + rtnl_nh_set_encap_ip6_tc; rtnl_nh_set_family; rtnl_nh_set_group; rtnl_nh_set_group_type; diff --git a/tests/cksuite-route-nh.c b/tests/cksuite-route-nh.c index 3025d7658..e569868f0 100644 --- a/tests/cksuite-route-nh.c +++ b/tests/cksuite-route-nh.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -632,6 +633,205 @@ START_TEST(test_api_encap_mpls_set_get) } END_TEST +/* Userspace tests for IPv6 encap on rtnl_nh (set/get + wrong-type negatives) */ + +START_TEST(test_api_encap_ip6) +{ + _nl_auto_rtnl_nh struct rtnl_nh *nh = NULL; + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap *encap = NULL; + struct rtnl_nh_encap *got = NULL; + struct nl_addr *got_dst = NULL; + struct nl_addr *got_src = NULL; + _nl_auto_nl_addr struct nl_addr *dst6 = NULL; + _nl_auto_nl_addr struct nl_addr *src6 = NULL; + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap *encap_mpls = NULL; + _nl_auto_nl_addr struct nl_addr *label = NULL; + uint64_t id_val; + uint16_t flags; + + /* Allocate nh and an encap container */ + nh = rtnl_nh_alloc(); + ck_assert_ptr_nonnull(nh); + + /* Negative: NULL nh */ + encap = rtnl_nh_encap_alloc(); + ck_assert_ptr_nonnull(encap); + + /* This will free encap */ + ck_assert_int_eq(rtnl_nh_set_encap(NULL, _nl_steal_pointer(&encap)), + -NLE_INVAL); + + /* Allocate a fresh encap after negative test freed the previous one */ + encap = rtnl_nh_encap_alloc(); + ck_assert_ptr_nonnull(encap); + + /* Now build a valid IPv6 encap */ + ck_assert_int_eq(nl_addr_parse("2001:db8:1::1", AF_INET6, &dst6), 0); + ck_assert_int_eq(rtnl_nh_encap_ip6(encap, dst6), 0); + ck_assert_int_eq(nl_addr_parse("2001:db8:1::2", AF_INET6, &src6), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_src(encap, src6), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_hoplimit(encap, 32), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_tc(encap, 0x2e), 0); + flags = TUNNEL_KEY | TUNNEL_CSUM | TUNNEL_SEQ; + ck_assert_int_eq(rtnl_nh_set_encap_ip6_flags(encap, flags), 0); + + ck_assert_uint_eq(rtnl_nh_get_encap_ip6_id(encap), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_id(encap, 0x1122334455667788ULL), + 0); + ck_assert_uint_eq(rtnl_nh_get_encap_ip6_id(encap), + 0x1122334455667788ULL); + + /* Attach and retrieve */ + ck_assert_int_eq(rtnl_nh_set_encap(nh, _nl_steal_pointer(&encap)), 0); + got = rtnl_nh_get_encap(nh); + ck_assert_ptr_nonnull(got); + ck_assert_int_eq(rtnl_nh_encap_get_type(got), LWTUNNEL_ENCAP_IP6); + + /* Access IPv6-specific getters */ + got_dst = rtnl_nh_get_encap_ip6_dst(got); + ck_assert_ptr_nonnull(got_dst); + ck_assert_int_eq(nl_addr_cmp(got_dst, dst6), 0); + got_src = rtnl_nh_get_encap_ip6_src(got); + ck_assert_ptr_nonnull(got_src); + ck_assert_int_eq(nl_addr_cmp(got_src, src6), 0); + ck_assert_int_eq(rtnl_nh_get_encap_ip6_hoplimit(got), 32); + ck_assert_int_eq(rtnl_nh_get_encap_ip6_tc(got), 0x2e); + ck_assert_int_eq(rtnl_nh_get_encap_ip6_flags(got), flags); + ck_assert_uint_eq(rtnl_nh_get_encap_ip6_id(NULL), 0); + ck_assert_uint_eq(rtnl_nh_get_encap_ip6_id(got), 0x1122334455667788ULL); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_id(got, 0x8877665544332211ULL), + 0); + id_val = rtnl_nh_get_encap_ip6_id(got); + ck_assert(id_val == 0x8877665544332211ULL); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_id(got, 0), 0); + ck_assert_uint_eq(rtnl_nh_get_encap_ip6_id(got), 0); + + /* Clear/zero optional fields and verify getters */ + ck_assert_int_eq(rtnl_nh_set_encap_ip6_hoplimit(got, 0), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_tc(got, 0), 0); + ck_assert_int_eq(rtnl_nh_get_encap_ip6_hoplimit(got), 0); + ck_assert_int_eq(rtnl_nh_get_encap_ip6_tc(got), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_flags(got, 0), 0); + ck_assert_int_eq(rtnl_nh_get_encap_ip6_flags(got), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_src(got, NULL), 0); + ck_assert_ptr_eq(rtnl_nh_get_encap_ip6_src(got), NULL); + + /* Clear encap */ + ck_assert_int_eq(rtnl_nh_set_encap(nh, NULL), 0); + ck_assert_ptr_eq(rtnl_nh_get_encap(nh), NULL); + /* Type getter negative on NULL */ + ck_assert_int_eq(rtnl_nh_encap_get_type(NULL), -NLE_INVAL); + + /* Negative tests for IPv6 setters on a non-IPv6 encap (wrong type) */ + encap_mpls = rtnl_nh_encap_alloc(); + ck_assert_ptr_nonnull(encap_mpls); + ck_assert_int_eq(nl_addr_parse("100", AF_MPLS, &label), 0); + ck_assert_int_eq(rtnl_nh_encap_mpls(encap_mpls, label, 64), 0); + + /* Now try IPv6-specific setters/getters on MPLS encap */ + ck_assert_int_eq(rtnl_nh_set_encap_ip6_hoplimit(encap_mpls, 16), + -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_tc(encap_mpls, 1), -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_src(encap_mpls, NULL), + -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_flags(encap_mpls, 1), + -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_id(encap_mpls, 1), -NLE_INVAL); + ck_assert_uint_eq(rtnl_nh_get_encap_ip6_id(encap_mpls), 0); + /* And verify its type is MPLS */ + ck_assert_int_eq(rtnl_nh_encap_get_type(encap_mpls), + LWTUNNEL_ENCAP_MPLS); + ck_assert_ptr_eq(rtnl_nh_get_encap_ip6_dst(encap_mpls), NULL); + ck_assert_ptr_eq(rtnl_nh_get_encap_ip6_src(encap_mpls), NULL); + ck_assert_int_eq(rtnl_nh_get_encap_ip6_hoplimit(encap_mpls), + -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_get_encap_ip6_tc(encap_mpls), -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_get_encap_ip6_flags(encap_mpls), -NLE_INVAL); + ck_assert_uint_eq(rtnl_nh_get_encap_ip6_id(encap_mpls), 0); +} +END_TEST + +/* Kernel round-trip tests for IPv6 encap on rtnl_nh */ + +START_TEST(test_kernel_roundtrip_encap_ip6) +{ + const char *IFNAME_DUMMY = "nh-dummy-encap6"; + _nltst_auto_delete_link const char *auto_del_dummy = NULL; + _nl_auto_nl_socket struct nl_sock *sk = NULL; + _nl_auto_nl_cache struct nl_cache *cache = NULL; + _nl_auto_rtnl_nh struct rtnl_nh *nh = NULL; + struct rtnl_nh *knh; + struct rtnl_nh_encap *kencap; + struct nl_addr *kdst = NULL; + struct nl_addr *ksrc = NULL; + _nl_auto_nl_addr struct nl_addr *gw6 = NULL; + _nl_auto_nl_addr struct nl_addr *dst6 = NULL; + _nl_auto_nl_addr struct nl_addr *src6 = NULL; + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap *encap = NULL; + uint16_t flags; + int ifindex_dummy; + + if (_nltst_skip_no_netns()) + return; + + sk = _nltst_socket(NETLINK_ROUTE); + + /* Create underlay, bring it up and assign an IPv6 address */ + auto_del_dummy = IFNAME_DUMMY; + _nltst_add_dummy_v6_with_addr(sk, IFNAME_DUMMY, &ifindex_dummy, + "2001:db8::2", 64); + + /* Build nexthop: IPv6 encap with dst/src/hoplimit/tc/flags and IPv6 gw */ + nh = rtnl_nh_alloc(); + ck_assert_ptr_nonnull(nh); + ck_assert_int_eq(rtnl_nh_set_id(nh, 3201), 0); + + ck_assert_int_eq(nl_addr_parse("2001:db8::1", AF_INET6, &gw6), 0); + ck_assert_int_eq(rtnl_nh_set_gateway(nh, gw6), 0); + + encap = rtnl_nh_encap_alloc(); + ck_assert_ptr_nonnull(encap); + ck_assert_int_eq(nl_addr_parse("2001:db8:1::1", AF_INET6, &dst6), 0); + ck_assert_int_eq(rtnl_nh_encap_ip6(encap, dst6), 0); + ck_assert_int_eq(nl_addr_parse("2001:db8:1::2", AF_INET6, &src6), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_src(encap, src6), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_hoplimit(encap, 32), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_tc(encap, 0x2e), 0); + flags = TUNNEL_KEY | TUNNEL_CSUM | TUNNEL_SEQ; + ck_assert_int_eq(rtnl_nh_set_encap_ip6_flags(encap, flags), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip6_id(encap, 0x1122334455667788ULL), + 0); + ck_assert_int_eq(rtnl_nh_set_encap(nh, _nl_steal_pointer(&encap)), 0); + + /* Set required attributes and add */ + ck_assert_int_eq(rtnl_nh_set_oif(nh, (uint32_t)ifindex_dummy), 0); + ck_assert_int_eq(rtnl_nh_set_family(nh, AF_INET6), 0); + ck_assert_int_eq(rtnl_nh_add(sk, nh, NLM_F_CREATE), 0); + + /* Query and verify */ + ck_assert_int_eq(rtnl_nh_alloc_cache(sk, AF_UNSPEC, &cache), 0); + knh = rtnl_nh_get(cache, 3201); + ck_assert_ptr_nonnull(knh); + ck_assert_int_eq(rtnl_nh_get_id(knh), 3201); + ck_assert_int_eq(rtnl_nh_get_oif(knh), ifindex_dummy); + + kencap = rtnl_nh_get_encap(knh); + ck_assert_ptr_nonnull(kencap); + ck_assert_int_eq(rtnl_nh_encap_get_type(kencap), LWTUNNEL_ENCAP_IP6); + kdst = rtnl_nh_get_encap_ip6_dst(kencap); + ck_assert_ptr_nonnull(kdst); + ck_assert_int_eq(nl_addr_cmp(kdst, dst6), 0); + ck_assert_int_eq(rtnl_nh_get_encap_ip6_hoplimit(kencap), 32); + ck_assert_int_eq(rtnl_nh_get_encap_ip6_tc(kencap), 0x2e); + ksrc = rtnl_nh_get_encap_ip6_src(kencap); + ck_assert_ptr_nonnull(ksrc); + ck_assert_int_eq(nl_addr_cmp(ksrc, src6), 0); + ck_assert_int_eq(rtnl_nh_get_encap_ip6_flags(kencap), flags); + ck_assert_uint_eq(rtnl_nh_get_encap_ip6_id(kencap), + 0x1122334455667788ULL); +} +END_TEST + Suite *make_nl_route_nh_suite(void) { Suite *suite = suite_create("route-nh"); @@ -642,6 +842,8 @@ Suite *make_nl_route_nh_suite(void) tcase_add_test(tc_api, test_api_set_get_all); /* Userspace encap tests */ tcase_add_test(tc_api, test_api_encap_mpls_set_get); + /* Userspace IPv6 encap tests */ + tcase_add_test(tc_api, test_api_encap_ip6); suite_add_tcase(suite, tc_api); /* Kernel round-trip – needs private netns */ @@ -656,6 +858,8 @@ Suite *make_nl_route_nh_suite(void) tcase_add_test(tc_kernel, test_kernel_roundtrip_group_resilient); /* Encap (MPLS) on rtnl_nh */ tcase_add_test(tc_kernel, test_kernel_roundtrip_encap_mpls); + /* Encap (IPv6) on rtnl_nh */ + tcase_add_test(tc_kernel, test_kernel_roundtrip_encap_ip6); /* Negative tests: kernel should reject invalid nexthops */ tcase_add_test(tc_kernel, test_kernel_negative_mismatched_gw_family); tcase_add_test(tc_kernel, test_kernel_negative_group_without_entries); From e378f0ea86590e80dd8998c24b708fb871b987e9 Mon Sep 17 00:00:00 2001 From: Christoph Paasch Date: Thu, 21 Aug 2025 16:10:41 -0700 Subject: [PATCH 5/6] ip-encap: Add support for an IPv4 nexthop encapsulation Signed-off-by: Christoph Paasch --- Makefile.am | 1 + include/netlink/route/nexthop.h | 15 ++ lib/route/nexthop-encap.h | 5 + lib/route/nexthop_encap.c | 2 +- lib/route/nh_encap_ip.c | 386 ++++++++++++++++++++++++++++++++ libnl-route-3.sym | 12 + tests/cksuite-route-nh.c | 193 ++++++++++++++++ 7 files changed, 613 insertions(+), 1 deletion(-) create mode 100644 lib/route/nh_encap_ip.c diff --git a/Makefile.am b/Makefile.am index 2e8b03973..fae6b526c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -528,6 +528,7 @@ lib_libnl_route_3_la_SOURCES = \ lib/route/nexthop.c \ lib/route/nexthop_encap.c \ lib/route/nh.c \ + lib/route/nh_encap_ip.c \ lib/route/nh_encap_ip6.c \ lib/route/nh_encap_mpls.c \ lib/route/nl-route.h \ diff --git a/include/netlink/route/nexthop.h b/include/netlink/route/nexthop.h index 48359d4ba..9613f0a88 100644 --- a/include/netlink/route/nexthop.h +++ b/include/netlink/route/nexthop.h @@ -90,6 +90,21 @@ extern int rtnl_nh_set_encap_ip6_src(struct rtnl_nh_encap *, struct nl_addr *); struct nl_addr *rtnl_nh_get_encap_ip6_src(struct rtnl_nh_encap *); extern int rtnl_nh_set_encap_ip6_flags(struct rtnl_nh_encap *, uint16_t); extern int rtnl_nh_get_encap_ip6_flags(struct rtnl_nh_encap *); + +/* IPv4 encap */ +extern int rtnl_nh_encap_ip(struct rtnl_nh_encap *nh_encap, + struct nl_addr *dst); +struct nl_addr *rtnl_nh_get_encap_ip_dst(struct rtnl_nh_encap *); +extern int rtnl_nh_set_encap_ip_src(struct rtnl_nh_encap *, struct nl_addr *); +struct nl_addr *rtnl_nh_get_encap_ip_src(struct rtnl_nh_encap *); +extern int rtnl_nh_set_encap_ip_ttl(struct rtnl_nh_encap *, uint8_t); +extern int rtnl_nh_get_encap_ip_ttl(struct rtnl_nh_encap *); +extern int rtnl_nh_set_encap_ip_tos(struct rtnl_nh_encap *, uint8_t); +extern int rtnl_nh_get_encap_ip_tos(struct rtnl_nh_encap *); +extern int rtnl_nh_set_encap_ip_id(struct rtnl_nh_encap *, uint64_t); +extern uint64_t rtnl_nh_get_encap_ip_id(struct rtnl_nh_encap *); +extern int rtnl_nh_set_encap_ip_flags(struct rtnl_nh_encap *, uint16_t); +extern int rtnl_nh_get_encap_ip_flags(struct rtnl_nh_encap *); #ifdef __cplusplus } #endif diff --git a/lib/route/nexthop-encap.h b/lib/route/nexthop-encap.h index f4b607d2e..2f3024d30 100644 --- a/lib/route/nexthop-encap.h +++ b/lib/route/nexthop-encap.h @@ -39,4 +39,9 @@ extern struct nh_encap_ops mpls_encap_ops; * IPv6 encap */ extern struct nh_encap_ops ip6_encap_ops; + +/* + * IPv4 encap + */ +extern struct nh_encap_ops ip_encap_ops; #endif diff --git a/lib/route/nexthop_encap.c b/lib/route/nexthop_encap.c index 09dacae4b..3737dd911 100644 --- a/lib/route/nexthop_encap.c +++ b/lib/route/nexthop_encap.c @@ -13,7 +13,7 @@ static struct lwtunnel_encap_type { } lwtunnel_encap_types[__LWTUNNEL_ENCAP_MAX] = { [LWTUNNEL_ENCAP_NONE] = { .name = "none" }, [LWTUNNEL_ENCAP_MPLS] = { .name = "mpls", .ops = &mpls_encap_ops }, - [LWTUNNEL_ENCAP_IP] = { .name = "ip" }, + [LWTUNNEL_ENCAP_IP] = { .name = "ip", .ops = &ip_encap_ops }, [LWTUNNEL_ENCAP_IP6] = { .name = "ip6", .ops = &ip6_encap_ops }, [LWTUNNEL_ENCAP_ILA] = { .name = "ila" }, [LWTUNNEL_ENCAP_BPF] = { .name = "bpf" }, diff --git a/lib/route/nh_encap_ip.c b/lib/route/nh_encap_ip.c new file mode 100644 index 000000000..f11e962bc --- /dev/null +++ b/lib/route/nh_encap_ip.c @@ -0,0 +1,386 @@ +/* SPDX-License-Identifier: LGPL-2.1-only */ + +#include "nl-default.h" + +#include +#include + +#include + +#include "nexthop-encap.h" +#include "nl-aux-core/nl-core.h" +#include "nl-aux-route/nl-route.h" +#include "nl-route.h" + +struct ip_tunnel_encap { + struct nl_addr *dst; + struct nl_addr *src; + uint64_t id; + uint16_t flags; + uint8_t tos; + uint8_t ttl; +}; + +static void ip_encap_dump(void *priv, struct nl_dump_params *dp) +{ + struct ip_tunnel_encap *encap_info = priv; + char buf[256]; + + nl_dump(dp, "%s ", nl_addr2str(encap_info->dst, buf, sizeof(buf))); + + if (encap_info->src) + nl_dump(dp, "src %s ", + nl_addr2str(encap_info->src, buf, sizeof(buf))); + + if (encap_info->id) + nl_dump(dp, "id %llu ", (unsigned long long)encap_info->id); + + if (encap_info->ttl) + nl_dump(dp, "ttl %u ", encap_info->ttl); + + if (encap_info->tos) + nl_dump(dp, "tos %u ", encap_info->tos); + + if (encap_info->flags) { + if (encap_info->flags & TUNNEL_KEY) + nl_dump(dp, "key "); + if (encap_info->flags & TUNNEL_CSUM) + nl_dump(dp, "csum "); + if (encap_info->flags & TUNNEL_SEQ) + nl_dump(dp, "seq "); + } +} + +static int ip_encap_build_msg(struct nl_msg *msg, void *priv) +{ + struct ip_tunnel_encap *encap_info = priv; + + NLA_PUT_ADDR(msg, LWTUNNEL_IP_DST, encap_info->dst); + if (encap_info->src) + NLA_PUT_ADDR(msg, LWTUNNEL_IP_SRC, encap_info->src); + if (encap_info->id) + NLA_PUT_U64(msg, LWTUNNEL_IP_ID, htonll(encap_info->id)); + if (encap_info->ttl) + NLA_PUT_U8(msg, LWTUNNEL_IP_TTL, encap_info->ttl); + if (encap_info->tos) + NLA_PUT_U8(msg, LWTUNNEL_IP_TOS, encap_info->tos); + if (encap_info->flags) + NLA_PUT_U16(msg, LWTUNNEL_IP_FLAGS, encap_info->flags); + + return 0; + +nla_put_failure: + return -NLE_MSGSIZE; +} + +static void ip_encap_destructor(void *priv) +{ + struct ip_tunnel_encap *encap_info = priv; + + nl_addr_put(encap_info->dst); + nl_addr_put(encap_info->src); +} + +static void *ip_encap_clone(void *priv) +{ + struct ip_tunnel_encap *src = priv; + struct ip_tunnel_encap *clone; + + if (!src) + return NULL; + + clone = calloc(1, sizeof(*clone)); + if (!clone) + return NULL; + + clone->dst = nl_addr_get(src->dst); + if (src->src) + clone->src = nl_addr_get(src->src); + clone->id = src->id; + clone->flags = src->flags; + clone->tos = src->tos; + clone->ttl = src->ttl; + + return clone; +} + +static struct nla_policy ip_encap_policy[LWTUNNEL_IP_MAX + 1] = { + [LWTUNNEL_IP_DST] = { .type = NLA_BINARY, .minlen = 4, .maxlen = 4 }, + [LWTUNNEL_IP_SRC] = { .type = NLA_BINARY, .minlen = 4, .maxlen = 4 }, + [LWTUNNEL_IP_ID] = { .type = NLA_U64 }, + [LWTUNNEL_IP_TTL] = { .type = NLA_U8 }, + [LWTUNNEL_IP_TOS] = { .type = NLA_U8 }, + [LWTUNNEL_IP_FLAGS] = { .type = NLA_U16 }, +}; + +static int ip_encap_parse_msg(struct nlattr *nla, + struct rtnl_nh_encap **encap_out) +{ + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap *nh_encap = NULL; + _nl_auto_nl_addr struct nl_addr *dst = NULL; + _nl_auto_nl_addr struct nl_addr *src = NULL; + struct nlattr *tb[LWTUNNEL_IP_MAX + 1]; + uint16_t flags = 0; + uint64_t id = 0; + uint8_t ttl = 0; + uint8_t tos = 0; + int err; + + err = nla_parse_nested(tb, LWTUNNEL_IP_MAX, nla, ip_encap_policy); + if (err < 0) + return err; + + if (!tb[LWTUNNEL_IP_DST]) + return -NLE_INVAL; + + dst = nl_addr_alloc_attr(tb[LWTUNNEL_IP_DST], AF_INET); + if (!dst) + return -NLE_NOMEM; + + if (tb[LWTUNNEL_IP_SRC]) { + src = nl_addr_alloc_attr(tb[LWTUNNEL_IP_SRC], AF_INET); + if (!src) + return -NLE_NOMEM; + } + + if (tb[LWTUNNEL_IP_ID]) + id = ntohll(nla_get_u64(tb[LWTUNNEL_IP_ID])); + if (tb[LWTUNNEL_IP_TTL]) + ttl = nla_get_u8(tb[LWTUNNEL_IP_TTL]); + if (tb[LWTUNNEL_IP_TOS]) + tos = nla_get_u8(tb[LWTUNNEL_IP_TOS]); + if (tb[LWTUNNEL_IP_FLAGS]) + flags = nla_get_u16(tb[LWTUNNEL_IP_FLAGS]); + + nh_encap = rtnl_nh_encap_alloc(); + if (!nh_encap) + return -NLE_NOMEM; + + err = rtnl_nh_encap_ip(nh_encap, dst); + if (err < 0) + return err; + + if (src) { + err = rtnl_nh_set_encap_ip_src(nh_encap, src); + if (err < 0) + return err; + } + + if (id) { + err = rtnl_nh_set_encap_ip_id(nh_encap, id); + if (err < 0) + return err; + } + + if (ttl) { + err = rtnl_nh_set_encap_ip_ttl(nh_encap, ttl); + if (err < 0) + return err; + } + + if (tos) { + err = rtnl_nh_set_encap_ip_tos(nh_encap, tos); + if (err < 0) + return err; + } + + if (tb[LWTUNNEL_IP_FLAGS]) { + err = rtnl_nh_set_encap_ip_flags(nh_encap, flags); + if (err < 0) + return err; + } + + *encap_out = _nl_steal_pointer(&nh_encap); + + return 0; +} + +static int ip_encap_compare(void *_a, void *_b) +{ + struct ip_tunnel_encap *a = _a; + struct ip_tunnel_encap *b = _b; + int diff = 0; + + diff |= (a->id != b->id); + diff |= (a->flags != b->flags); + diff |= (a->tos != b->tos); + diff |= (a->ttl != b->ttl); + diff |= nl_addr_cmp(a->src, b->src); + diff |= nl_addr_cmp(a->dst, b->dst); + + return diff; +} + +struct nh_encap_ops ip_encap_ops = { + .encap_type = LWTUNNEL_ENCAP_IP, + .build_msg = ip_encap_build_msg, + .parse_msg = ip_encap_parse_msg, + .compare = ip_encap_compare, + .clone = ip_encap_clone, + .dump = ip_encap_dump, + .destructor = ip_encap_destructor, +}; + +int rtnl_nh_encap_ip(struct rtnl_nh_encap *nh_encap, struct nl_addr *dst) +{ + struct ip_tunnel_encap *ip_encap; + + if (!dst || !nh_encap) + return -NLE_INVAL; + if (nl_addr_get_family(dst) != AF_INET) + return -NLE_INVAL; + + ip_encap = calloc(1, sizeof(*ip_encap)); + if (!ip_encap) + return -NLE_NOMEM; + + ip_encap->dst = nl_addr_get(dst); + + nh_encap->priv = ip_encap; + nh_encap->ops = &ip_encap_ops; + + return 0; +} + +static struct ip_tunnel_encap *nh_encap_get_ip(struct rtnl_nh_encap *nh_encap) +{ + return (struct ip_tunnel_encap *)nh_encap_check_and_get_priv( + nh_encap, LWTUNNEL_ENCAP_IP); +} + +struct nl_addr *rtnl_nh_get_encap_ip_dst(struct rtnl_nh_encap *nh_encap) +{ + struct ip_tunnel_encap *ip_encap; + + ip_encap = nh_encap_get_ip(nh_encap); + if (!ip_encap) + return NULL; + + return ip_encap->dst; +} + +int rtnl_nh_set_encap_ip_src(struct rtnl_nh_encap *nh_encap, + struct nl_addr *src) +{ + struct ip_tunnel_encap *ip_encap; + struct nl_addr *old_src; + + ip_encap = nh_encap_get_ip(nh_encap); + if (!ip_encap) + return -NLE_INVAL; + if (src && nl_addr_get_family(src) != AF_INET) + return -NLE_INVAL; + + old_src = ip_encap->src; + if (src) + ip_encap->src = nl_addr_get(src); + else + ip_encap->src = NULL; + + nl_addr_put(old_src); + + return 0; +} + +struct nl_addr *rtnl_nh_get_encap_ip_src(struct rtnl_nh_encap *nh_encap) +{ + struct ip_tunnel_encap *ip_encap; + + ip_encap = nh_encap_get_ip(nh_encap); + if (!ip_encap) + return NULL; + + return ip_encap->src; +} + +int rtnl_nh_set_encap_ip_ttl(struct rtnl_nh_encap *nh_encap, uint8_t ttl) +{ + struct ip_tunnel_encap *ip_encap; + + ip_encap = nh_encap_get_ip(nh_encap); + if (!ip_encap) + return -NLE_INVAL; + + ip_encap->ttl = ttl; + return 0; +} + +int rtnl_nh_get_encap_ip_ttl(struct rtnl_nh_encap *nh_encap) +{ + struct ip_tunnel_encap *ip_encap; + + ip_encap = nh_encap_get_ip(nh_encap); + if (!ip_encap) + return -NLE_INVAL; + + return ip_encap->ttl; +} + +int rtnl_nh_set_encap_ip_tos(struct rtnl_nh_encap *nh_encap, uint8_t tos) +{ + struct ip_tunnel_encap *ip_encap; + + ip_encap = nh_encap_get_ip(nh_encap); + if (!ip_encap) + return -NLE_INVAL; + + ip_encap->tos = tos; + return 0; +} + +int rtnl_nh_get_encap_ip_tos(struct rtnl_nh_encap *nh_encap) +{ + struct ip_tunnel_encap *ip_encap; + + ip_encap = nh_encap_get_ip(nh_encap); + if (!ip_encap) + return -NLE_INVAL; + + return ip_encap->tos; +} + +int rtnl_nh_set_encap_ip_id(struct rtnl_nh_encap *nh_encap, uint64_t id) +{ + struct ip_tunnel_encap *ip_encap; + + ip_encap = nh_encap_get_ip(nh_encap); + if (!ip_encap) + return -NLE_INVAL; + + ip_encap->id = id; + return 0; +} + +uint64_t rtnl_nh_get_encap_ip_id(struct rtnl_nh_encap *nh_encap) +{ + struct ip_tunnel_encap *ip_encap; + + ip_encap = nh_encap_get_ip(nh_encap); + if (!ip_encap) + return 0; + + return ip_encap->id; +} + +int rtnl_nh_set_encap_ip_flags(struct rtnl_nh_encap *nh_encap, uint16_t flags) +{ + struct ip_tunnel_encap *ip_encap; + + ip_encap = nh_encap_get_ip(nh_encap); + if (!ip_encap) + return -NLE_INVAL; + + ip_encap->flags = flags; + return 0; +} + +int rtnl_nh_get_encap_ip_flags(struct rtnl_nh_encap *nh_encap) +{ + struct ip_tunnel_encap *ip_encap; + + ip_encap = nh_encap_get_ip(nh_encap); + if (!ip_encap) + return -NLE_INVAL; + + return ip_encap->flags; +} diff --git a/libnl-route-3.sym b/libnl-route-3.sym index a6c762b42..8ded48597 100644 --- a/libnl-route-3.sym +++ b/libnl-route-3.sym @@ -1376,6 +1376,7 @@ global: rtnl_nh_encap_free; rtnl_nh_encap_get_type; rtnl_nh_encap_ip6; + rtnl_nh_encap_ip; rtnl_nh_encap_mpls; rtnl_nh_get_encap; rtnl_nh_get_encap_ip6_dst; @@ -1384,6 +1385,12 @@ global: rtnl_nh_get_encap_ip6_id; rtnl_nh_get_encap_ip6_src; rtnl_nh_get_encap_ip6_tc; + rtnl_nh_get_encap_ip_dst; + rtnl_nh_get_encap_ip_flags; + rtnl_nh_get_encap_ip_id; + rtnl_nh_get_encap_ip_src; + rtnl_nh_get_encap_ip_tos; + rtnl_nh_get_encap_ip_ttl; rtnl_nh_get_encap_mpls_dst; rtnl_nh_get_encap_mpls_ttl; rtnl_nh_get_family; @@ -1398,6 +1405,11 @@ global: rtnl_nh_set_encap_ip6_id; rtnl_nh_set_encap_ip6_src; rtnl_nh_set_encap_ip6_tc; + rtnl_nh_set_encap_ip_id; + rtnl_nh_set_encap_ip_flags; + rtnl_nh_set_encap_ip_src; + rtnl_nh_set_encap_ip_tos; + rtnl_nh_set_encap_ip_ttl; rtnl_nh_set_family; rtnl_nh_set_group; rtnl_nh_set_group_type; diff --git a/tests/cksuite-route-nh.c b/tests/cksuite-route-nh.c index e569868f0..dbef00c71 100644 --- a/tests/cksuite-route-nh.c +++ b/tests/cksuite-route-nh.c @@ -751,6 +751,115 @@ START_TEST(test_api_encap_ip6) } END_TEST +/* Userspace tests for IPv4 encap on rtnl_nh (set/get + wrong-type negatives) */ + +START_TEST(test_api_encap_ip) +{ + _nl_auto_rtnl_nh struct rtnl_nh *nh = NULL; + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap *encap = NULL; + struct rtnl_nh_encap *got = NULL; + struct nl_addr *got_dst = NULL; + struct nl_addr *got_src = NULL; + _nl_auto_nl_addr struct nl_addr *dst6 = NULL; + _nl_auto_nl_addr struct nl_addr *src6 = NULL; + _nl_auto_nl_addr struct nl_addr *dst4 = NULL; + _nl_auto_nl_addr struct nl_addr *src4 = NULL; + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap *encap_mpls = NULL; + _nl_auto_nl_addr struct nl_addr *label = NULL; + uint64_t id = 0; + + nh = rtnl_nh_alloc(); + ck_assert_ptr_nonnull(nh); + + encap = rtnl_nh_encap_alloc(); + ck_assert_ptr_nonnull(encap); + /* This will free encap */ + ck_assert_int_eq(rtnl_nh_set_encap(NULL, _nl_steal_pointer(&encap)), + -NLE_INVAL); + + encap = rtnl_nh_encap_alloc(); + ck_assert_ptr_nonnull(encap); + + /* Family mismatch should be rejected */ + ck_assert_int_eq(nl_addr_parse("2001:db8::1", AF_INET6, &dst6), 0); + ck_assert_int_eq(rtnl_nh_encap_ip(encap, dst6), -NLE_INVAL); + + /* Build a valid IPv4 encap */ + ck_assert_int_eq(nl_addr_parse("198.51.100.1", AF_INET, &dst4), 0); + ck_assert_int_eq(rtnl_nh_encap_ip(encap, dst4), 0); + ck_assert_int_eq(nl_addr_parse("2001:db8::2", AF_INET6, &src6), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip_src(encap, src6), -NLE_INVAL); + ck_assert_int_eq(nl_addr_parse("198.51.100.2", AF_INET, &src4), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip_src(encap, src4), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip_id(encap, 0x123456789ULL), 0); + ck_assert_uint_eq(rtnl_nh_get_encap_ip_id(encap), 0x123456789ULL); + ck_assert_int_eq(rtnl_nh_set_encap_ip_ttl(encap, 32), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip_tos(encap, 0x2e), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip_flags(encap, 0x3), 0); + + /* Attach and retrieve */ + ck_assert_int_eq(rtnl_nh_set_encap(nh, _nl_steal_pointer(&encap)), 0); + got = rtnl_nh_get_encap(nh); + ck_assert_ptr_nonnull(got); + ck_assert_int_eq(rtnl_nh_encap_get_type(got), LWTUNNEL_ENCAP_IP); + + /* Access IPv4-specific getters */ + got_dst = rtnl_nh_get_encap_ip_dst(got); + ck_assert_ptr_nonnull(got_dst); + ck_assert_int_eq(nl_addr_cmp(got_dst, dst4), 0); + got_src = rtnl_nh_get_encap_ip_src(got); + ck_assert_ptr_nonnull(got_src); + ck_assert_int_eq(nl_addr_cmp(got_src, src4), 0); + ck_assert_int_eq(rtnl_nh_get_encap_ip_ttl(got), 32); + ck_assert_int_eq(rtnl_nh_get_encap_ip_tos(got), 0x2e); + ck_assert_uint_eq(rtnl_nh_get_encap_ip_id(NULL), 0); + ck_assert_uint_eq(rtnl_nh_get_encap_ip_id(got), 0x123456789ULL); + ck_assert_int_eq(rtnl_nh_set_encap_ip_id(got, 0x987654321ULL), 0); + id = rtnl_nh_get_encap_ip_id(got); + ck_assert(id == 0x987654321ULL); + ck_assert_int_eq(rtnl_nh_get_encap_ip_flags(got), 0x3); + + /* Clear/zero optional fields and verify getters */ + ck_assert_int_eq(rtnl_nh_set_encap_ip_ttl(got, 0), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip_tos(got, 0), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip_id(got, 0), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip_flags(got, 0), 0); + ck_assert_int_eq(rtnl_nh_get_encap_ip_ttl(got), 0); + ck_assert_int_eq(rtnl_nh_get_encap_ip_tos(got), 0); + ck_assert_uint_eq(rtnl_nh_get_encap_ip_id(got), 0); + ck_assert_int_eq(rtnl_nh_get_encap_ip_flags(got), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip_src(got, NULL), 0); + ck_assert_ptr_eq(rtnl_nh_get_encap_ip_src(got), NULL); + + /* Clear encap */ + ck_assert_int_eq(rtnl_nh_set_encap(nh, NULL), 0); + ck_assert_ptr_eq(rtnl_nh_get_encap(nh), NULL); + /* Type getter negative on NULL */ + ck_assert_int_eq(rtnl_nh_encap_get_type(NULL), -NLE_INVAL); + + /* Negative tests for IPv4 setters on a non-IPv4 encap (wrong type) */ + encap_mpls = rtnl_nh_encap_alloc(); + ck_assert_ptr_nonnull(encap_mpls); + ck_assert_int_eq(nl_addr_parse("100", AF_MPLS, &label), 0); + ck_assert_int_eq(rtnl_nh_encap_mpls(encap_mpls, label, 64), 0); + + ck_assert_int_eq(rtnl_nh_set_encap_ip_ttl(encap_mpls, 16), -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_set_encap_ip_tos(encap_mpls, 1), -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_set_encap_ip_id(encap_mpls, 1), -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_set_encap_ip_flags(encap_mpls, 1), -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_set_encap_ip_src(encap_mpls, NULL), + -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_encap_get_type(encap_mpls), + LWTUNNEL_ENCAP_MPLS); + ck_assert_ptr_eq(rtnl_nh_get_encap_ip_dst(encap_mpls), NULL); + ck_assert_ptr_eq(rtnl_nh_get_encap_ip_src(encap_mpls), NULL); + ck_assert_int_eq(rtnl_nh_get_encap_ip_ttl(encap_mpls), -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_get_encap_ip_tos(encap_mpls), -NLE_INVAL); + ck_assert_uint_eq(rtnl_nh_get_encap_ip_id(encap_mpls), 0); + ck_assert_int_eq(rtnl_nh_get_encap_ip_flags(encap_mpls), -NLE_INVAL); +} +END_TEST + /* Kernel round-trip tests for IPv6 encap on rtnl_nh */ START_TEST(test_kernel_roundtrip_encap_ip6) @@ -832,6 +941,86 @@ START_TEST(test_kernel_roundtrip_encap_ip6) } END_TEST +/* Kernel round-trip tests for IPv4 encap on rtnl_nh */ + +START_TEST(test_kernel_roundtrip_encap_ip) +{ + const char *IFNAME_DUMMY = "nh-dummy-encap4"; + _nltst_auto_delete_link const char *auto_del_dummy = NULL; + _nl_auto_nl_socket struct nl_sock *sk = NULL; + _nl_auto_nl_cache struct nl_cache *cache = NULL; + _nl_auto_rtnl_nh struct rtnl_nh *nh = NULL; + struct rtnl_nh *knh; + struct rtnl_nh_encap *kencap; + struct nl_addr *kdst = NULL; + struct nl_addr *ksrc = NULL; + _nl_auto_nl_addr struct nl_addr *gw4 = NULL; + _nl_auto_nl_addr struct nl_addr *dst4 = NULL; + _nl_auto_nl_addr struct nl_addr *src4 = NULL; + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap *encap = NULL; + int ifindex_dummy; + uint64_t id = 0; + + if (_nltst_skip_no_netns()) + return; + + sk = _nltst_socket(NETLINK_ROUTE); + + /* Create underlay and assign an IPv4 address */ + auto_del_dummy = IFNAME_DUMMY; + _nltst_add_dummy_v4_with_addr(sk, IFNAME_DUMMY, &ifindex_dummy, + "192.0.2.2", 24); + + /* Build nexthop: IPv4 encap with dst/src/ttl/tos and IPv4 gw */ + nh = rtnl_nh_alloc(); + ck_assert_ptr_nonnull(nh); + ck_assert_int_eq(rtnl_nh_set_id(nh, 3301), 0); + + ck_assert_int_eq(nl_addr_parse("192.0.2.1", AF_INET, &gw4), 0); + ck_assert_int_eq(rtnl_nh_set_gateway(nh, gw4), 0); + + encap = rtnl_nh_encap_alloc(); + ck_assert_ptr_nonnull(encap); + ck_assert_int_eq(nl_addr_parse("198.51.100.1", AF_INET, &dst4), 0); + ck_assert_int_eq(rtnl_nh_encap_ip(encap, dst4), 0); + ck_assert_int_eq(nl_addr_parse("198.51.100.2", AF_INET, &src4), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip_src(encap, src4), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip_id(encap, 0xABCDEFULL), 0); + ck_assert_uint_eq(rtnl_nh_get_encap_ip_id(encap), 0xABCDEFULL); + ck_assert_int_eq(rtnl_nh_set_encap_ip_ttl(encap, 32), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip_tos(encap, 0x2e), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ip_flags(encap, 0x5), 0); + ck_assert_int_eq(rtnl_nh_set_encap(nh, _nl_steal_pointer(&encap)), 0); + + /* Set required attributes and add */ + ck_assert_int_eq(rtnl_nh_set_oif(nh, (uint32_t)ifindex_dummy), 0); + ck_assert_int_eq(rtnl_nh_set_family(nh, AF_INET), 0); + ck_assert_int_eq(rtnl_nh_add(sk, nh, NLM_F_CREATE), 0); + + /* Query and verify */ + ck_assert_int_eq(rtnl_nh_alloc_cache(sk, AF_UNSPEC, &cache), 0); + knh = rtnl_nh_get(cache, 3301); + ck_assert_ptr_nonnull(knh); + ck_assert_int_eq(rtnl_nh_get_id(knh), 3301); + ck_assert_int_eq(rtnl_nh_get_oif(knh), ifindex_dummy); + + kencap = rtnl_nh_get_encap(knh); + ck_assert_ptr_nonnull(kencap); + ck_assert_int_eq(rtnl_nh_encap_get_type(kencap), LWTUNNEL_ENCAP_IP); + kdst = rtnl_nh_get_encap_ip_dst(kencap); + ck_assert_ptr_nonnull(kdst); + ck_assert_int_eq(nl_addr_cmp(kdst, dst4), 0); + ck_assert_int_eq(rtnl_nh_get_encap_ip_ttl(kencap), 32); + ck_assert_int_eq(rtnl_nh_get_encap_ip_tos(kencap), 0x2e); + id = rtnl_nh_get_encap_ip_id(kencap); + ck_assert(id == 0xABCDEFULL); + ck_assert_int_eq(rtnl_nh_get_encap_ip_flags(kencap), 0x5); + ksrc = rtnl_nh_get_encap_ip_src(kencap); + ck_assert_ptr_nonnull(ksrc); + ck_assert_int_eq(nl_addr_cmp(ksrc, src4), 0); +} +END_TEST + Suite *make_nl_route_nh_suite(void) { Suite *suite = suite_create("route-nh"); @@ -844,6 +1033,8 @@ Suite *make_nl_route_nh_suite(void) tcase_add_test(tc_api, test_api_encap_mpls_set_get); /* Userspace IPv6 encap tests */ tcase_add_test(tc_api, test_api_encap_ip6); + /* Userspace IPv4 encap tests */ + tcase_add_test(tc_api, test_api_encap_ip); suite_add_tcase(suite, tc_api); /* Kernel round-trip – needs private netns */ @@ -860,6 +1051,8 @@ Suite *make_nl_route_nh_suite(void) tcase_add_test(tc_kernel, test_kernel_roundtrip_encap_mpls); /* Encap (IPv6) on rtnl_nh */ tcase_add_test(tc_kernel, test_kernel_roundtrip_encap_ip6); + /* Encap (IPv4) on rtnl_nh */ + tcase_add_test(tc_kernel, test_kernel_roundtrip_encap_ip); /* Negative tests: kernel should reject invalid nexthops */ tcase_add_test(tc_kernel, test_kernel_negative_mismatched_gw_family); tcase_add_test(tc_kernel, test_kernel_negative_group_without_entries); From 9f8945251da404fb59778edc74be5775091e1f44 Mon Sep 17 00:00:00 2001 From: Christoph Paasch Date: Tue, 9 Sep 2025 16:57:12 -0700 Subject: [PATCH 6/6] ila-encap: Add support for ILA nexthop encapsulation Full support as well as tests. Added rtnl_nh_clear_encap_ila_* functions to clear certain fields. This is needed because 0 is a valid value for those fields. Signed-off-by: Christoph Paasch --- Makefile.am | 1 + include/netlink/route/nexthop.h | 17 ++ lib/route/nexthop-encap.h | 5 + lib/route/nexthop_encap.c | 2 +- lib/route/nh_encap_ila.c | 333 ++++++++++++++++++++++++++++++++ libnl-route-3.sym | 11 ++ tests/cksuite-route-nh.c | 143 ++++++++++++++ 7 files changed, 511 insertions(+), 1 deletion(-) create mode 100644 lib/route/nh_encap_ila.c diff --git a/Makefile.am b/Makefile.am index fae6b526c..2bd0c06e7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -528,6 +528,7 @@ lib_libnl_route_3_la_SOURCES = \ lib/route/nexthop.c \ lib/route/nexthop_encap.c \ lib/route/nh.c \ + lib/route/nh_encap_ila.c \ lib/route/nh_encap_ip.c \ lib/route/nh_encap_ip6.c \ lib/route/nh_encap_mpls.c \ diff --git a/include/netlink/route/nexthop.h b/include/netlink/route/nexthop.h index 9613f0a88..f0781ba8e 100644 --- a/include/netlink/route/nexthop.h +++ b/include/netlink/route/nexthop.h @@ -91,6 +91,23 @@ struct nl_addr *rtnl_nh_get_encap_ip6_src(struct rtnl_nh_encap *); extern int rtnl_nh_set_encap_ip6_flags(struct rtnl_nh_encap *, uint16_t); extern int rtnl_nh_get_encap_ip6_flags(struct rtnl_nh_encap *); +/* ILA encap */ +extern int rtnl_nh_encap_ila(struct rtnl_nh_encap *nh_encap, uint64_t locator); +extern int rtnl_nh_get_encap_ila_locator(struct rtnl_nh_encap *nh_encap, + uint64_t *locator); +extern int rtnl_nh_set_encap_ila_csum_mode(struct rtnl_nh_encap *nh_encap, + uint8_t csum_mode); +extern int rtnl_nh_clear_encap_ila_csum_mode(struct rtnl_nh_encap *nh_encap); +extern int rtnl_nh_get_encap_ila_csum_mode(struct rtnl_nh_encap *nh_encap); +extern int rtnl_nh_set_encap_ila_ident_type(struct rtnl_nh_encap *nh_encap, + uint8_t ident_type); +extern int rtnl_nh_clear_encap_ila_ident_type(struct rtnl_nh_encap *nh_encap); +extern int rtnl_nh_get_encap_ila_ident_type(struct rtnl_nh_encap *nh_encap); +extern int rtnl_nh_set_encap_ila_hook_type(struct rtnl_nh_encap *nh_encap, + uint8_t hook_type); +extern int rtnl_nh_clear_encap_ila_hook_type(struct rtnl_nh_encap *nh_encap); +extern int rtnl_nh_get_encap_ila_hook_type(struct rtnl_nh_encap *nh_encap); + /* IPv4 encap */ extern int rtnl_nh_encap_ip(struct rtnl_nh_encap *nh_encap, struct nl_addr *dst); diff --git a/lib/route/nexthop-encap.h b/lib/route/nexthop-encap.h index 2f3024d30..6d98d7324 100644 --- a/lib/route/nexthop-encap.h +++ b/lib/route/nexthop-encap.h @@ -44,4 +44,9 @@ extern struct nh_encap_ops ip6_encap_ops; * IPv4 encap */ extern struct nh_encap_ops ip_encap_ops; + +/* + * ILA encap + */ +extern struct nh_encap_ops ila_encap_ops; #endif diff --git a/lib/route/nexthop_encap.c b/lib/route/nexthop_encap.c index 3737dd911..6f31ffc08 100644 --- a/lib/route/nexthop_encap.c +++ b/lib/route/nexthop_encap.c @@ -15,7 +15,7 @@ static struct lwtunnel_encap_type { [LWTUNNEL_ENCAP_MPLS] = { .name = "mpls", .ops = &mpls_encap_ops }, [LWTUNNEL_ENCAP_IP] = { .name = "ip", .ops = &ip_encap_ops }, [LWTUNNEL_ENCAP_IP6] = { .name = "ip6", .ops = &ip6_encap_ops }, - [LWTUNNEL_ENCAP_ILA] = { .name = "ila" }, + [LWTUNNEL_ENCAP_ILA] = { .name = "ila", .ops = &ila_encap_ops }, [LWTUNNEL_ENCAP_BPF] = { .name = "bpf" }, }; diff --git a/lib/route/nh_encap_ila.c b/lib/route/nh_encap_ila.c new file mode 100644 index 000000000..a582430d8 --- /dev/null +++ b/lib/route/nh_encap_ila.c @@ -0,0 +1,333 @@ +/* SPDX-License-Identifier: LGPL-2.1-only */ + +#include "nl-default.h" + +#include +#include + +#include + +#include "nexthop-encap.h" +#include "nl-aux-route/nl-route.h" +#include "nl-route.h" + +struct ila_tunnel_encap { + uint64_t locator; + uint8_t csum_mode; + uint8_t ident_type; + uint8_t hook_type; + bool has_csum_mode : 1; + bool has_ident_type : 1; + bool has_hook_type : 1; +}; + +static void ila_encap_dump(void *priv, struct nl_dump_params *dp) +{ + struct ila_tunnel_encap *ila_encap = priv; + + nl_dump(dp, " locator 0x%llx ", (unsigned long long)ila_encap->locator); + + if (ila_encap->has_csum_mode) + nl_dump(dp, " csum-mode %u ", ila_encap->csum_mode); + + if (ila_encap->has_ident_type) + nl_dump(dp, " ident-type %u ", ila_encap->ident_type); + + if (ila_encap->has_hook_type) + nl_dump(dp, " hook-type %u ", ila_encap->hook_type); +} + +static int ila_encap_build_msg(struct nl_msg *msg, void *priv) +{ + struct ila_tunnel_encap *ila_encap = priv; + + NLA_PUT_U64(msg, ILA_ATTR_LOCATOR, ila_encap->locator); + + if (ila_encap->has_csum_mode) + NLA_PUT_U8(msg, ILA_ATTR_CSUM_MODE, ila_encap->csum_mode); + + if (ila_encap->has_ident_type) + NLA_PUT_U8(msg, ILA_ATTR_IDENT_TYPE, ila_encap->ident_type); + + if (ila_encap->has_hook_type) + NLA_PUT_U8(msg, ILA_ATTR_HOOK_TYPE, ila_encap->hook_type); + + return 0; + +nla_put_failure: + return -NLE_MSGSIZE; +} + +static void *ila_encap_clone(void *priv) +{ + return priv ? _nl_memdup(priv, sizeof(struct ila_tunnel_encap)) : NULL; +} + +static int ila_encap_compare(void *_a, void *_b) +{ + struct ila_tunnel_encap *a = _a; + struct ila_tunnel_encap *b = _b; + + if (!a || !b) + return a != b; + + if (a->locator != b->locator) + return 1; + + if (a->has_csum_mode != b->has_csum_mode) + return 1; + + if (a->has_csum_mode && a->csum_mode != b->csum_mode) + return 1; + + if (a->has_ident_type != b->has_ident_type) + return 1; + + if (a->has_ident_type && a->ident_type != b->ident_type) + return 1; + + if (a->has_hook_type != b->has_hook_type) + return 1; + + if (a->has_hook_type && a->hook_type != b->hook_type) + return 1; + + return 0; +} + +static struct nla_policy ila_encap_policy[ILA_ATTR_MAX + 1] = { + [ILA_ATTR_LOCATOR] = { .type = NLA_U64 }, + [ILA_ATTR_CSUM_MODE] = { .type = NLA_U8 }, + [ILA_ATTR_IDENT_TYPE] = { .type = NLA_U8 }, + [ILA_ATTR_HOOK_TYPE] = { .type = NLA_U8 }, +}; + +static int ila_encap_parse_msg(struct nlattr *nla, + struct rtnl_nh_encap **encap_out) +{ + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap *nh_encap = NULL; + struct nlattr *tb[ILA_ATTR_MAX + 1]; + uint64_t locator; + int err; + + err = nla_parse_nested(tb, ILA_ATTR_MAX, nla, ila_encap_policy); + if (err < 0) + return err; + + if (!tb[ILA_ATTR_LOCATOR]) + return -NLE_INVAL; + + locator = nla_get_u64(tb[ILA_ATTR_LOCATOR]); + + nh_encap = rtnl_nh_encap_alloc(); + if (!nh_encap) + return -NLE_NOMEM; + + err = rtnl_nh_encap_ila(nh_encap, locator); + if (err < 0) + return err; + + if (tb[ILA_ATTR_CSUM_MODE]) { + err = rtnl_nh_set_encap_ila_csum_mode( + nh_encap, nla_get_u8(tb[ILA_ATTR_CSUM_MODE])); + if (err < 0) + return err; + } + + if (tb[ILA_ATTR_IDENT_TYPE]) { + err = rtnl_nh_set_encap_ila_ident_type( + nh_encap, nla_get_u8(tb[ILA_ATTR_IDENT_TYPE])); + if (err < 0) + return err; + } + + if (tb[ILA_ATTR_HOOK_TYPE]) { + err = rtnl_nh_set_encap_ila_hook_type( + nh_encap, nla_get_u8(tb[ILA_ATTR_HOOK_TYPE])); + if (err < 0) + return err; + } + + *encap_out = _nl_steal_pointer(&nh_encap); + + return 0; +} + +struct nh_encap_ops ila_encap_ops = { + .encap_type = LWTUNNEL_ENCAP_ILA, + .build_msg = ila_encap_build_msg, + .parse_msg = ila_encap_parse_msg, + .compare = ila_encap_compare, + .clone = ila_encap_clone, + .dump = ila_encap_dump, +}; + +static struct ila_tunnel_encap *nh_encap_get_ila(struct rtnl_nh_encap *nh_encap) +{ + return nh_encap_check_and_get_priv(nh_encap, LWTUNNEL_ENCAP_ILA); +} + +int rtnl_nh_encap_ila(struct rtnl_nh_encap *nh_encap, uint64_t locator) +{ + struct ila_tunnel_encap *ila_encap; + + if (!nh_encap) + return -NLE_INVAL; + + ila_encap = calloc(1, sizeof(*ila_encap)); + if (!ila_encap) + return -NLE_NOMEM; + + ila_encap->locator = locator; + + nh_encap->priv = ila_encap; + nh_encap->ops = &ila_encap_ops; + + return 0; +} + +int rtnl_nh_get_encap_ila_locator(struct rtnl_nh_encap *nh_encap, + uint64_t *locator) +{ + struct ila_tunnel_encap *ila_encap; + + if (!locator) + return -NLE_INVAL; + + ila_encap = nh_encap_get_ila(nh_encap); + if (!ila_encap) + return -NLE_INVAL; + + *locator = ila_encap->locator; + + return 0; +} + +int rtnl_nh_set_encap_ila_csum_mode(struct rtnl_nh_encap *nh_encap, + uint8_t csum_mode) +{ + struct ila_tunnel_encap *ila_encap; + + ila_encap = nh_encap_get_ila(nh_encap); + if (!ila_encap) + return -NLE_INVAL; + + ila_encap->csum_mode = csum_mode; + ila_encap->has_csum_mode = true; + + return 0; +} + +int rtnl_nh_clear_encap_ila_csum_mode(struct rtnl_nh_encap *nh_encap) +{ + struct ila_tunnel_encap *ila_encap; + + ila_encap = nh_encap_get_ila(nh_encap); + if (!ila_encap) + return -NLE_INVAL; + + ila_encap->has_csum_mode = false; + ila_encap->csum_mode = 0; + + return 0; +} + +int rtnl_nh_get_encap_ila_csum_mode(struct rtnl_nh_encap *nh_encap) +{ + struct ila_tunnel_encap *ila_encap; + + ila_encap = nh_encap_get_ila(nh_encap); + if (!ila_encap) + return -NLE_INVAL; + + if (!ila_encap->has_csum_mode) + return -NLE_MISSING_ATTR; + + return ila_encap->csum_mode; +} + +int rtnl_nh_set_encap_ila_ident_type(struct rtnl_nh_encap *nh_encap, + uint8_t ident_type) +{ + struct ila_tunnel_encap *ila_encap; + + ila_encap = nh_encap_get_ila(nh_encap); + if (!ila_encap) + return -NLE_INVAL; + + ila_encap->ident_type = ident_type; + ila_encap->has_ident_type = true; + + return 0; +} + +int rtnl_nh_clear_encap_ila_ident_type(struct rtnl_nh_encap *nh_encap) +{ + struct ila_tunnel_encap *ila_encap; + + ila_encap = nh_encap_get_ila(nh_encap); + if (!ila_encap) + return -NLE_INVAL; + + ila_encap->has_ident_type = false; + ila_encap->ident_type = 0; + + return 0; +} + +int rtnl_nh_get_encap_ila_ident_type(struct rtnl_nh_encap *nh_encap) +{ + struct ila_tunnel_encap *ila_encap; + + ila_encap = nh_encap_get_ila(nh_encap); + if (!ila_encap) + return -NLE_INVAL; + + if (!ila_encap->has_ident_type) + return -NLE_MISSING_ATTR; + + return ila_encap->ident_type; +} + +int rtnl_nh_set_encap_ila_hook_type(struct rtnl_nh_encap *nh_encap, + uint8_t hook_type) +{ + struct ila_tunnel_encap *ila_encap; + + ila_encap = nh_encap_get_ila(nh_encap); + if (!ila_encap) + return -NLE_INVAL; + + ila_encap->hook_type = hook_type; + ila_encap->has_hook_type = true; + + return 0; +} + +int rtnl_nh_clear_encap_ila_hook_type(struct rtnl_nh_encap *nh_encap) +{ + struct ila_tunnel_encap *ila_encap; + + ila_encap = nh_encap_get_ila(nh_encap); + if (!ila_encap) + return -NLE_INVAL; + + ila_encap->has_hook_type = false; + ila_encap->hook_type = 0; + + return 0; +} + +int rtnl_nh_get_encap_ila_hook_type(struct rtnl_nh_encap *nh_encap) +{ + struct ila_tunnel_encap *ila_encap; + + ila_encap = nh_encap_get_ila(nh_encap); + if (!ila_encap) + return -NLE_INVAL; + + if (!ila_encap->has_hook_type) + return -NLE_MISSING_ATTR; + + return ila_encap->hook_type; +} diff --git a/libnl-route-3.sym b/libnl-route-3.sym index 8ded48597..9e6f1c893 100644 --- a/libnl-route-3.sym +++ b/libnl-route-3.sym @@ -1371,14 +1371,22 @@ global: rtnl_link_ip6_tnl_set_collect_metadata; rtnl_link_is_bond; rtnl_nh_add; + rtnl_nh_clear_encap_ila_csum_mode; + rtnl_nh_clear_encap_ila_hook_type; + rtnl_nh_clear_encap_ila_ident_type; rtnl_nh_encap_alloc; rtnl_nh_encap_clone; rtnl_nh_encap_free; rtnl_nh_encap_get_type; + rtnl_nh_encap_ila; rtnl_nh_encap_ip6; rtnl_nh_encap_ip; rtnl_nh_encap_mpls; rtnl_nh_get_encap; + rtnl_nh_get_encap_ila_csum_mode; + rtnl_nh_get_encap_ila_hook_type; + rtnl_nh_get_encap_ila_ident_type; + rtnl_nh_get_encap_ila_locator; rtnl_nh_get_encap_ip6_dst; rtnl_nh_get_encap_ip6_flags; rtnl_nh_get_encap_ip6_hoplimit; @@ -1400,6 +1408,9 @@ global: rtnl_nh_get_res_group_idle_timer; rtnl_nh_get_res_group_unbalanced_timer; rtnl_nh_set_encap; + rtnl_nh_set_encap_ila_csum_mode; + rtnl_nh_set_encap_ila_hook_type; + rtnl_nh_set_encap_ila_ident_type; rtnl_nh_set_encap_ip6_flags; rtnl_nh_set_encap_ip6_hoplimit; rtnl_nh_set_encap_ip6_id; diff --git a/tests/cksuite-route-nh.c b/tests/cksuite-route-nh.c index dbef00c71..07a7a839b 100644 --- a/tests/cksuite-route-nh.c +++ b/tests/cksuite-route-nh.c @@ -13,6 +13,7 @@ #include "nl-default.h" #include +#include #include #include #include @@ -751,6 +752,70 @@ START_TEST(test_api_encap_ip6) } END_TEST +START_TEST(test_api_encap_ila) +{ + _nl_auto_rtnl_nh struct rtnl_nh *nh = NULL; + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap *encap = NULL; + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap *encap_mpls = NULL; + struct rtnl_nh_encap *got = NULL; + _nl_auto_nl_addr struct nl_addr *label = NULL; + uint64_t locator = 0; + + nh = rtnl_nh_alloc(); + ck_assert_ptr_nonnull(nh); + + encap = rtnl_nh_encap_alloc(); + ck_assert_ptr_nonnull(encap); + ck_assert_int_eq(rtnl_nh_encap_ila(encap, 0x1122334455667788ULL), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ila_csum_mode(encap, + ILA_CSUM_NEUTRAL_MAP), + 0); + ck_assert_int_eq( + rtnl_nh_set_encap_ila_ident_type(encap, ILA_ATYPE_LUID), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ila_hook_type(encap, + ILA_HOOK_ROUTE_OUTPUT), + 0); + + ck_assert_int_eq(rtnl_nh_set_encap(nh, _nl_steal_pointer(&encap)), 0); + got = rtnl_nh_get_encap(nh); + ck_assert_ptr_nonnull(got); + ck_assert_int_eq(rtnl_nh_encap_get_type(got), LWTUNNEL_ENCAP_ILA); + ck_assert_int_eq(rtnl_nh_get_encap_ila_locator(got, &locator), 0); + ck_assert_uint_eq(locator, 0x1122334455667788ULL); + ck_assert_int_eq(rtnl_nh_get_encap_ila_locator(got, NULL), -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_get_encap_ila_csum_mode(got), + ILA_CSUM_NEUTRAL_MAP); + ck_assert_int_eq(rtnl_nh_get_encap_ila_ident_type(got), ILA_ATYPE_LUID); + ck_assert_int_eq(rtnl_nh_get_encap_ila_hook_type(got), + ILA_HOOK_ROUTE_OUTPUT); + + ck_assert_int_eq(rtnl_nh_set_encap_ila_csum_mode(got, 0), 0); + ck_assert_int_eq(rtnl_nh_get_encap_ila_csum_mode(got), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ila_ident_type(got, 0), 0); + ck_assert_int_eq(rtnl_nh_get_encap_ila_ident_type(got), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ila_hook_type(got, 0), 0); + ck_assert_int_eq(rtnl_nh_get_encap_ila_hook_type(got), 0); + + ck_assert_int_eq(rtnl_nh_set_encap(nh, NULL), 0); + ck_assert_ptr_eq(rtnl_nh_get_encap(nh), NULL); + + encap_mpls = rtnl_nh_encap_alloc(); + ck_assert_ptr_nonnull(encap_mpls); + ck_assert_int_eq(nl_addr_parse("100", AF_MPLS, &label), 0); + ck_assert_int_eq(rtnl_nh_encap_mpls(encap_mpls, label, 64), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ila_csum_mode(encap_mpls, 1), + -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_get_encap_ila_locator(encap_mpls, &locator), + -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_get_encap_ila_csum_mode(encap_mpls), + -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_get_encap_ila_ident_type(encap_mpls), + -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_get_encap_ila_hook_type(encap_mpls), + -NLE_INVAL); +} +END_TEST + /* Userspace tests for IPv4 encap on rtnl_nh (set/get + wrong-type negatives) */ START_TEST(test_api_encap_ip) @@ -941,6 +1006,80 @@ START_TEST(test_kernel_roundtrip_encap_ip6) } END_TEST +START_TEST(test_kernel_roundtrip_encap_ila) +{ + const char *IFNAME_DUMMY = "encap-ila"; + _nltst_auto_delete_link const char *auto_del_dummy = NULL; + _nl_auto_nl_socket struct nl_sock *sk = NULL; + _nl_auto_nl_cache struct nl_cache *cache = NULL; + _nl_auto_rtnl_nh struct rtnl_nh *nh = NULL; + _nl_auto_nl_addr struct nl_addr *gw6 = NULL; + struct rtnl_nh *knh; + struct rtnl_nh_encap *kencap; + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap *encap = NULL; + uint64_t locator = 0; + int ifindex_dummy; + int ret; + + if (_nltst_skip_no_netns()) + return; + + sk = _nltst_socket(NETLINK_ROUTE); + + /* Create underlay, bring it up and assign an IPv6 address */ + auto_del_dummy = IFNAME_DUMMY; + _nltst_add_dummy_v6_with_addr(sk, IFNAME_DUMMY, &ifindex_dummy, + "2001:db8:2::2", 64); + + nh = rtnl_nh_alloc(); + ck_assert_ptr_nonnull(nh); + ck_assert_int_eq(rtnl_nh_set_id(nh, 3401), 0); + + ck_assert_int_eq(nl_addr_parse("2001:db8:2::1", AF_INET6, &gw6), 0); + ck_assert_int_eq(rtnl_nh_set_gateway(nh, gw6), 0); + + encap = rtnl_nh_encap_alloc(); + ck_assert_ptr_nonnull(encap); + ck_assert_int_eq(rtnl_nh_encap_ila(encap, 0x1122334455667788ULL), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ila_csum_mode(encap, + ILA_CSUM_NEUTRAL_MAP), + 0); + ck_assert_int_eq( + rtnl_nh_set_encap_ila_ident_type(encap, ILA_ATYPE_LUID), 0); + ck_assert_int_eq(rtnl_nh_set_encap_ila_hook_type(encap, + ILA_HOOK_ROUTE_OUTPUT), + 0); + ck_assert_int_eq(rtnl_nh_set_encap(nh, _nl_steal_pointer(&encap)), 0); + + ck_assert_int_eq(rtnl_nh_set_oif(nh, (uint32_t)ifindex_dummy), 0); + ck_assert_int_eq(rtnl_nh_set_family(nh, AF_INET6), 0); + ret = rtnl_nh_add(sk, nh, NLM_F_CREATE); + if (ret == -NLE_OPNOTSUPP) { + /* ila module is not loaded - skipping */ + return; + } + ck_assert_int_eq(ret, 0); + + ck_assert_int_eq(rtnl_nh_alloc_cache(sk, AF_UNSPEC, &cache), 0); + knh = rtnl_nh_get(cache, 3401); + ck_assert_ptr_nonnull(knh); + ck_assert_int_eq(rtnl_nh_get_id(knh), 3401); + ck_assert_int_eq(rtnl_nh_get_oif(knh), ifindex_dummy); + + kencap = rtnl_nh_get_encap(knh); + ck_assert_ptr_nonnull(kencap); + ck_assert_int_eq(rtnl_nh_encap_get_type(kencap), LWTUNNEL_ENCAP_ILA); + ck_assert_int_eq(rtnl_nh_get_encap_ila_locator(kencap, &locator), 0); + ck_assert_uint_eq(locator, 0x1122334455667788ULL); + ck_assert_int_eq(rtnl_nh_get_encap_ila_csum_mode(kencap), + ILA_CSUM_NEUTRAL_MAP); + ck_assert_int_eq(rtnl_nh_get_encap_ila_ident_type(kencap), + ILA_ATYPE_LUID); + ck_assert_int_eq(rtnl_nh_get_encap_ila_hook_type(kencap), + ILA_HOOK_ROUTE_OUTPUT); +} +END_TEST + /* Kernel round-trip tests for IPv4 encap on rtnl_nh */ START_TEST(test_kernel_roundtrip_encap_ip) @@ -1033,6 +1172,8 @@ Suite *make_nl_route_nh_suite(void) tcase_add_test(tc_api, test_api_encap_mpls_set_get); /* Userspace IPv6 encap tests */ tcase_add_test(tc_api, test_api_encap_ip6); + /* Userspace ILA encap tests */ + tcase_add_test(tc_api, test_api_encap_ila); /* Userspace IPv4 encap tests */ tcase_add_test(tc_api, test_api_encap_ip); suite_add_tcase(suite, tc_api); @@ -1051,6 +1192,8 @@ Suite *make_nl_route_nh_suite(void) tcase_add_test(tc_kernel, test_kernel_roundtrip_encap_mpls); /* Encap (IPv6) on rtnl_nh */ tcase_add_test(tc_kernel, test_kernel_roundtrip_encap_ip6); + /* Encap (ILA) on rtnl_nh */ + tcase_add_test(tc_kernel, test_kernel_roundtrip_encap_ila); /* Encap (IPv4) on rtnl_nh */ tcase_add_test(tc_kernel, test_kernel_roundtrip_encap_ip); /* Negative tests: kernel should reject invalid nexthops */