diff --git a/Makefile.am b/Makefile.am index 95a225509..8d70bb6b8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1064,9 +1064,10 @@ tests_check_all_SOURCES = \ tests/cksuite-all-attr.c \ tests/cksuite-all-ematch-tree-clone.c \ tests/cksuite-all-netns.c \ + tests/cksuite-all.h \ tests/cksuite-link-ip6tnl.c \ + tests/cksuite-route-nexthop.c \ tests/cksuite-route-nh.c \ - tests/cksuite-all.h \ $(NULL) tests_check_all_CPPFLAGS = \ diff --git a/include/netlink/route/nexthop.h b/include/netlink/route/nexthop.h index 1beb9fa9e..abbbabed7 100644 --- a/include/netlink/route/nexthop.h +++ b/include/netlink/route/nexthop.h @@ -14,6 +14,7 @@ extern "C" { #endif struct rtnl_nexthop; +struct rtnl_nh_encap; enum { NH_DUMP_FROM_ONELINE = -2, @@ -22,53 +23,56 @@ enum { /* > 0 reserved for nexthop index */ }; -extern struct rtnl_nexthop * rtnl_route_nh_alloc(void); -extern struct rtnl_nexthop * rtnl_route_nh_clone(struct rtnl_nexthop *); -extern void rtnl_route_nh_free(struct rtnl_nexthop *); +extern struct rtnl_nexthop *rtnl_route_nh_alloc(void); +extern struct rtnl_nexthop *rtnl_route_nh_clone(struct rtnl_nexthop *); +extern void rtnl_route_nh_free(struct rtnl_nexthop *); -extern int rtnl_route_nh_compare(struct rtnl_nexthop *, - struct rtnl_nexthop *, - uint32_t, int); +extern int rtnl_route_nh_compare(struct rtnl_nexthop *, struct rtnl_nexthop *, + uint32_t, int); -extern int rtnl_route_nh_identical(struct rtnl_nexthop *, - struct rtnl_nexthop *); +extern int rtnl_route_nh_identical(struct rtnl_nexthop *, + struct rtnl_nexthop *); -extern void rtnl_route_nh_dump(struct rtnl_nexthop *, - struct nl_dump_params *); +extern void rtnl_route_nh_dump(struct rtnl_nexthop *, struct nl_dump_params *); -extern void rtnl_route_nh_set_weight(struct rtnl_nexthop *, uint8_t); -extern uint8_t rtnl_route_nh_get_weight(struct rtnl_nexthop *); -extern void rtnl_route_nh_set_ifindex(struct rtnl_nexthop *, int); -extern int rtnl_route_nh_get_ifindex(struct rtnl_nexthop *); -extern void rtnl_route_nh_set_gateway(struct rtnl_nexthop *, - struct nl_addr *); -extern struct nl_addr * rtnl_route_nh_get_gateway(struct rtnl_nexthop *); -extern void rtnl_route_nh_set_flags(struct rtnl_nexthop *, - unsigned int); -extern void rtnl_route_nh_unset_flags(struct rtnl_nexthop *, - unsigned int); -extern unsigned int rtnl_route_nh_get_flags(struct rtnl_nexthop *); -extern void rtnl_route_nh_set_realms(struct rtnl_nexthop *, - uint32_t); -extern uint32_t rtnl_route_nh_get_realms(struct rtnl_nexthop *); +extern void rtnl_route_nh_set_weight(struct rtnl_nexthop *, uint8_t); +extern uint8_t rtnl_route_nh_get_weight(struct rtnl_nexthop *); +extern void rtnl_route_nh_set_ifindex(struct rtnl_nexthop *, int); +extern int rtnl_route_nh_get_ifindex(struct rtnl_nexthop *); +extern void rtnl_route_nh_set_gateway(struct rtnl_nexthop *, struct nl_addr *); +extern struct nl_addr *rtnl_route_nh_get_gateway(struct rtnl_nexthop *); +extern void rtnl_route_nh_set_flags(struct rtnl_nexthop *, unsigned int); +extern void rtnl_route_nh_unset_flags(struct rtnl_nexthop *, unsigned int); +extern unsigned int rtnl_route_nh_get_flags(struct rtnl_nexthop *); +extern void rtnl_route_nh_set_realms(struct rtnl_nexthop *, uint32_t); +extern uint32_t rtnl_route_nh_get_realms(struct rtnl_nexthop *); -extern int rtnl_route_nh_set_newdst(struct rtnl_nexthop *, - struct nl_addr *); -extern struct nl_addr * rtnl_route_nh_get_newdst(struct rtnl_nexthop *); -extern int rtnl_route_nh_set_via(struct rtnl_nexthop *, - struct nl_addr *); -extern struct nl_addr * rtnl_route_nh_get_via(struct rtnl_nexthop *); -extern char * rtnl_route_nh_flags2str(int, char *, size_t); -extern int rtnl_route_nh_str2flags(const char *); +extern int rtnl_route_nh_set_newdst(struct rtnl_nexthop *, struct nl_addr *); +extern struct nl_addr *rtnl_route_nh_get_newdst(struct rtnl_nexthop *); +extern int rtnl_route_nh_set_via(struct rtnl_nexthop *, struct nl_addr *); +extern struct nl_addr *rtnl_route_nh_get_via(struct rtnl_nexthop *); +extern int rtnl_route_nh_set_encap(struct rtnl_nexthop *, + struct rtnl_nh_encap *); +extern struct rtnl_nh_encap *rtnl_route_nh_get_encap(struct rtnl_nexthop *); +extern char *rtnl_route_nh_flags2str(int, char *, size_t); +extern int rtnl_route_nh_str2flags(const char *); /* * nexthop encapsulations */ -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 *); +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_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 *); +extern int rtnl_nh_get_encap_mpls_ttl(struct rtnl_nh_encap *); + +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 *); #ifdef __cplusplus } #endif diff --git a/include/netlink/route/nh.h b/include/netlink/route/nh.h index ea9159c78..094ff7999 100644 --- a/include/netlink/route/nh.h +++ b/include/netlink/route/nh.h @@ -72,6 +72,11 @@ extern int rtnl_nh_set_res_group_unbalanced_timer(struct rtnl_nh *, extern int rtnl_nh_get_res_group_unbalanced_timer(struct rtnl_nh *, uint32_t *out_value); +/* lwtunnel encapsulation */ +struct rtnl_nh_encap; +extern int rtnl_nh_set_encap(struct rtnl_nh *, struct rtnl_nh_encap *); +extern struct rtnl_nh_encap *rtnl_nh_get_encap(struct rtnl_nh *); + #ifdef __cplusplus } #endif diff --git a/include/nl-aux-route/nl-route.h b/include/nl-aux-route/nl-route.h index 3072db4a6..17b05a0b9 100644 --- a/include/nl-aux-route/nl-route.h +++ b/include/nl-aux-route/nl-route.h @@ -6,6 +6,7 @@ #include "base/nl-base-utils.h" #include +#include struct rtnl_link; void rtnl_link_put(struct rtnl_link *); @@ -42,6 +43,10 @@ void rtnl_nh_put(struct rtnl_nh *); #define _nl_auto_rtnl_nh _nl_auto(_nl_auto_rtnl_nh_fcn) _NL_AUTO_DEFINE_FCN_TYPED0(struct rtnl_nh *, _nl_auto_rtnl_nh_fcn, rtnl_nh_put); +#define _nl_auto_rtnl_nh_encap _nl_auto(_nl_auto_rtnl_nh_encap_fcn) +_NL_AUTO_DEFINE_FCN_TYPED0(struct rtnl_nh_encap *, _nl_auto_rtnl_nh_encap_fcn, + rtnl_nh_encap_free); + struct rtnl_link_af_ops; void rtnl_link_af_ops_put(struct rtnl_link_af_ops *); #define _nl_auto_rtnl_link_af_ops _nl_auto(_nl_auto_rtnl_link_af_ops_fcn) diff --git a/lib/route/nexthop-encap.h b/lib/route/nexthop-encap.h index dde1bfbec..12f514a4d 100644 --- a/lib/route/nexthop-encap.h +++ b/lib/route/nexthop-encap.h @@ -1,27 +1,26 @@ #ifndef NETLINK_NEXTHOP_ENCAP_H_ -#define NETLINK_NEXTHOP_ENCAP_H_ +#define NETLINK_NEXTHOP_ENCAP_H_ + +struct rtnl_nh_encap; struct nh_encap_ops { uint16_t encap_type; - int (*build_msg)(struct nl_msg *msg, void *priv); - int (*parse_msg)(struct nlattr *nla, struct rtnl_nexthop *rtnh); + int (*build_msg)(struct nl_msg *msg, void *priv); + int (*parse_msg)(struct nlattr *nla, struct rtnl_nh_encap **encap_out); - int (*compare)(void *a, void *b); + int (*compare)(void *a, void *b); + void *(*clone)(void *priv); - void (*dump)(void *priv, struct nl_dump_params *dp); - void (*destructor)(void *priv); + void (*dump)(void *priv, struct nl_dump_params *dp); + void (*destructor)(void *priv); }; -struct rtnl_nh_encap; - /* * generic nexthop encap */ -void nh_set_encap(struct rtnl_nexthop *nh, struct rtnl_nh_encap *rtnh_encap); - int nh_encap_parse_msg(struct nlattr *encap, struct nlattr *encap_type, - struct rtnl_nexthop *rtnh); + struct rtnl_nh_encap **encap_out); int nh_encap_build_msg(struct nl_msg *msg, struct rtnl_nh_encap *rtnh_encap); void nh_encap_dump(struct rtnl_nh_encap *rtnh_encap, struct nl_dump_params *dp); diff --git a/lib/route/nexthop.c b/lib/route/nexthop.c index 80ea19b6f..6412e5f4d 100644 --- a/lib/route/nexthop.c +++ b/lib/route/nexthop.c @@ -17,18 +17,19 @@ #include #include "nexthop-encap.h" -#include "nl-route.h" +#include "nl-aux-route/nl-route.h" #include "nl-priv-dynamic-core/nl-core.h" +#include "nl-route.h" /** @cond SKIP */ -#define NH_ATTR_FLAGS 0x000001 -#define NH_ATTR_WEIGHT 0x000002 +#define NH_ATTR_FLAGS 0x000001 +#define NH_ATTR_WEIGHT 0x000002 #define NH_ATTR_IFINDEX 0x000004 #define NH_ATTR_GATEWAY 0x000008 -#define NH_ATTR_REALMS 0x000010 -#define NH_ATTR_NEWDST 0x000020 -#define NH_ATTR_VIA 0x000040 -#define NH_ATTR_ENCAP 0x000080 +#define NH_ATTR_REALMS 0x000010 +#define NH_ATTR_NEWDST 0x000020 +#define NH_ATTR_VIA 0x000040 +#define NH_ATTR_ENCAP 0x000080 /** @endcond */ /** @@ -51,7 +52,7 @@ struct rtnl_nexthop *rtnl_route_nh_alloc(void) struct rtnl_nexthop *rtnl_route_nh_clone(struct rtnl_nexthop *src) { - struct rtnl_nexthop *nh; + _nl_auto_rtnl_nexthop struct rtnl_nexthop *nh = NULL; nh = rtnl_route_nh_alloc(); if (!nh) @@ -66,32 +67,30 @@ struct rtnl_nexthop *rtnl_route_nh_clone(struct rtnl_nexthop *src) if (src->rtnh_gateway) { nh->rtnh_gateway = nl_addr_clone(src->rtnh_gateway); - if (!nh->rtnh_gateway) { - free(nh); + if (!nh->rtnh_gateway) return NULL; - } } if (src->rtnh_newdst) { nh->rtnh_newdst = nl_addr_clone(src->rtnh_newdst); - if (!nh->rtnh_newdst) { - nl_addr_put(nh->rtnh_gateway); - free(nh); + if (!nh->rtnh_newdst) return NULL; - } } if (src->rtnh_via) { nh->rtnh_via = nl_addr_clone(src->rtnh_via); - if (!nh->rtnh_via) { - nl_addr_put(nh->rtnh_gateway); - nl_addr_put(nh->rtnh_newdst); - free(nh); + if (!nh->rtnh_via) return NULL; - } } - return nh; + /* Clone encapsulation information if present */ + if (src->rtnh_encap) { + nh->rtnh_encap = rtnl_nh_encap_clone(src->rtnh_encap); + if (!nh->rtnh_encap) + return NULL; + } + + return _nl_steal_pointer(&nh); } void rtnl_route_nh_free(struct rtnl_nexthop *nh) @@ -99,12 +98,7 @@ void rtnl_route_nh_free(struct rtnl_nexthop *nh) nl_addr_put(nh->rtnh_gateway); nl_addr_put(nh->rtnh_newdst); nl_addr_put(nh->rtnh_via); - if (nh->rtnh_encap) { - if (nh->rtnh_encap->ops && nh->rtnh_encap->ops->destructor) - nh->rtnh_encap->ops->destructor(nh->rtnh_encap->priv); - free(nh->rtnh_encap->priv); - free(nh->rtnh_encap); - } + rtnl_nh_encap_free(nh->rtnh_encap); free(nh); } @@ -150,8 +144,9 @@ int rtnl_route_nh_identical(struct rtnl_nexthop *a, struct rtnl_nexthop *b) { return !rtnl_route_nh_compare(a, b, NH_ATTR_IFINDEX | NH_ATTR_REALMS | - NH_ATTR_GATEWAY | NH_ATTR_NEWDST | - NH_ATTR_VIA | NH_ATTR_ENCAP, 0); + NH_ATTR_GATEWAY | NH_ATTR_NEWDST | + NH_ATTR_VIA | NH_ATTR_ENCAP, + 0); } static void nh_dump_line(struct rtnl_nexthop *nh, struct nl_dump_params *dp) @@ -171,18 +166,16 @@ static void nh_dump_line(struct rtnl_nexthop *nh, struct nl_dump_params *dp) nl_dump(dp, "via"); if (nh->ce_mask & NH_ATTR_VIA) - nl_dump(dp, " %s", - nl_addr2str(nh->rtnh_via, buf, sizeof(buf))); + nl_dump(dp, " %s", nl_addr2str(nh->rtnh_via, buf, sizeof(buf))); if (nh->ce_mask & NH_ATTR_GATEWAY) - nl_dump(dp, " %s", nl_addr2str(nh->rtnh_gateway, - buf, sizeof(buf))); + nl_dump(dp, " %s", + nl_addr2str(nh->rtnh_gateway, buf, sizeof(buf))); - if(nh->ce_mask & NH_ATTR_IFINDEX) { + if (nh->ce_mask & NH_ATTR_IFINDEX) { if (link_cache) { nl_dump(dp, " dev %s", - rtnl_link_i2name(link_cache, - nh->rtnh_ifindex, + rtnl_link_i2name(link_cache, nh->rtnh_ifindex, buf, sizeof(buf))); } else nl_dump(dp, " dev %d", nh->rtnh_ifindex); @@ -215,14 +208,13 @@ static void nh_dump_details(struct rtnl_nexthop *nh, struct nl_dump_params *dp) nl_addr2str(nh->rtnh_via, buf, sizeof(buf))); if (nh->ce_mask & NH_ATTR_GATEWAY) - nl_dump(dp, " via %s", nl_addr2str(nh->rtnh_gateway, - buf, sizeof(buf))); + nl_dump(dp, " via %s", + nl_addr2str(nh->rtnh_gateway, buf, sizeof(buf))); - if(nh->ce_mask & NH_ATTR_IFINDEX) { + if (nh->ce_mask & NH_ATTR_IFINDEX) { if (link_cache) { nl_dump(dp, " dev %s", - rtnl_link_i2name(link_cache, - nh->rtnh_ifindex, + rtnl_link_i2name(link_cache, nh->rtnh_ifindex, buf, sizeof(buf))); } else nl_dump(dp, " dev %d", nh->rtnh_ifindex); @@ -237,8 +229,9 @@ static void nh_dump_details(struct rtnl_nexthop *nh, struct nl_dump_params *dp) RTNL_REALM_TO(nh->rtnh_realms)); if (nh->ce_mask & NH_ATTR_FLAGS) - nl_dump(dp, " <%s>", rtnl_route_nh_flags2str(nh->rtnh_flags, - buf, sizeof(buf))); + nl_dump(dp, " <%s>", + rtnl_route_nh_flags2str(nh->rtnh_flags, buf, + sizeof(buf))); if (link_cache) nl_cache_put(link_cache); @@ -262,24 +255,6 @@ void rtnl_route_nh_dump(struct rtnl_nexthop *nh, struct nl_dump_params *dp) } } -void nh_set_encap(struct rtnl_nexthop *nh, struct rtnl_nh_encap *rtnh_encap) -{ - if (nh->rtnh_encap) { - if (nh->rtnh_encap->ops && nh->rtnh_encap->ops->destructor) - nh->rtnh_encap->ops->destructor(nh->rtnh_encap->priv); - free(nh->rtnh_encap->priv); - free(nh->rtnh_encap); - } - - if (rtnh_encap) { - nh->rtnh_encap = rtnh_encap; - nh->ce_mask |= NH_ATTR_ENCAP; - } else { - nh->rtnh_encap = NULL; - nh->ce_mask &= ~NH_ATTR_ENCAP; - } -} - /** * @name Attributes * @{ @@ -305,7 +280,7 @@ void rtnl_route_nh_set_ifindex(struct rtnl_nexthop *nh, int ifindex) int rtnl_route_nh_get_ifindex(struct rtnl_nexthop *nh) { return nh->rtnh_ifindex; -} +} /* FIXME: Convert to return an int */ void rtnl_route_nh_set_gateway(struct rtnl_nexthop *nh, struct nl_addr *addr) @@ -391,7 +366,7 @@ int rtnl_route_nh_set_via(struct rtnl_nexthop *nh, struct nl_addr *addr) nh->ce_mask |= NH_ATTR_VIA; } else { nh->ce_mask &= ~NH_ATTR_VIA; - nh->rtnh_via= NULL; + nh->rtnh_via = NULL; } if (old) @@ -405,6 +380,45 @@ struct nl_addr *rtnl_route_nh_get_via(struct rtnl_nexthop *nh) return nh->rtnh_via; } +/** + * Set nexthop encapsulation + * @arg nh Route nexthop object + * @arg nh_encap Encapsulation descriptor + * + * Assigns ownership of the encapsulation object to the route's nexthop. + * Any previously configured encapsulation is released. Passing a NULL + * encapsulation clears the encapsulation on the nexthop. + * + * On failure, the function consumes and frees \p nh_encap. + * + * @return 0 on success, or the appropriate error-code on failure. + */ +int rtnl_route_nh_set_encap(struct rtnl_nexthop *nh, + struct rtnl_nh_encap *nh_encap) +{ + if (!nh) { + rtnl_nh_encap_free(nh_encap); + return -NLE_INVAL; + } + + if (nh_encap && !nh_encap->ops) { + rtnl_nh_encap_free(nh_encap); + return -NLE_INVAL; + } + + rtnl_nh_encap_free(nh->rtnh_encap); + + if (nh_encap) { + nh->rtnh_encap = nh_encap; + nh->ce_mask |= NH_ATTR_ENCAP; + } else { + nh->rtnh_encap = NULL; + nh->ce_mask &= ~NH_ATTR_ENCAP; + } + + return 0; +} + /** @} */ /** diff --git a/lib/route/nexthop_encap.c b/lib/route/nexthop_encap.c index 226b901a6..4fd406543 100644 --- a/lib/route/nexthop_encap.c +++ b/lib/route/nexthop_encap.c @@ -13,10 +13,10 @@ 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_IP6] = { .name = "ip6" }, - [LWTUNNEL_ENCAP_ILA] = { .name = "ila" }, - [LWTUNNEL_ENCAP_BPF] = { .name = "bpf" }, + [LWTUNNEL_ENCAP_IP] = { .name = "ip" }, + [LWTUNNEL_ENCAP_IP6] = { .name = "ip6" }, + [LWTUNNEL_ENCAP_ILA] = { .name = "ila" }, + [LWTUNNEL_ENCAP_BPF] = { .name = "bpf" }, }; static const char *nh_encap_type2str(unsigned int type) @@ -72,26 +72,40 @@ int nh_encap_build_msg(struct nl_msg *msg, struct rtnl_nh_encap *rtnh_encap) } int nh_encap_parse_msg(struct nlattr *encap, struct nlattr *encap_type, - struct rtnl_nexthop *rtnh) + struct rtnl_nh_encap **encap_out) { uint16_t e_type = nla_get_u16(encap_type); if (e_type == LWTUNNEL_ENCAP_NONE) { NL_DBG(2, "RTA_ENCAP_TYPE should not be LWTUNNEL_ENCAP_NONE\n"); - return -NLE_INVAL; + + goto unsupported_encap; } + if (e_type > LWTUNNEL_ENCAP_MAX) { NL_DBG(2, "Unknown RTA_ENCAP_TYPE: %d\n", e_type); - return -NLE_INVAL; + + goto unsupported_encap; } if (!lwtunnel_encap_types[e_type].ops) { NL_DBG(2, "RTA_ENCAP_TYPE %s is not implemented\n", lwtunnel_encap_types[e_type].name); - return -NLE_MSGTYPE_NOSUPPORT; + + goto unsupported_encap; } - return lwtunnel_encap_types[e_type].ops->parse_msg(encap, rtnh); + return lwtunnel_encap_types[e_type].ops->parse_msg(encap, encap_out); + +unsupported_encap: + /* If we don't yet support this lwtunnel, just return 0. + * + * Force encap_out to NULL so that subsequent calls to set + * it on a nexthop/route will simply reset the encapsulation + * on that nexthop/route. + */ + *encap_out = NULL; + return 0; } int nh_encap_compare(struct rtnl_nh_encap *a, struct rtnl_nh_encap *b) diff --git a/lib/route/nh.c b/lib/route/nh.c index dbfa74a9e..81ff74f08 100644 --- a/lib/route/nh.c +++ b/lib/route/nh.c @@ -6,11 +6,14 @@ #include "nl-default.h" #include +#include +#include #include #include #include +#include "nexthop-encap.h" #include "nl-aux-route/nl-route.h" #include "nl-route.h" #include "nl-priv-dynamic-core/nl-core.h" @@ -28,6 +31,7 @@ struct rtnl_nh { nl_nh_group_t *nh_group; uint32_t nh_oif; struct nl_addr *nh_gateway; + struct rtnl_nh_encap *nh_encap; /* Resilient nexthop group parameters */ uint16_t res_grp_buckets; @@ -49,6 +53,7 @@ struct rtnl_nh { #define NH_ATTR_RES_BUCKETS (1 << 10) #define NH_ATTR_RES_IDLE_TIMER (1 << 11) #define NH_ATTR_RES_UNBALANCED_TIMER (1 << 12) +#define NH_ATTR_ENCAP (1 << 13) /** @endcond */ struct nla_policy rtnl_nh_policy[NHA_MAX + 1] = { @@ -59,6 +64,8 @@ struct nla_policy rtnl_nh_policy[NHA_MAX + 1] = { [NHA_BLACKHOLE] = { .type = NLA_UNSPEC }, [NHA_OIF] = { .type = NLA_U32 }, [NHA_RES_GROUP] = { .type = NLA_NESTED }, + [NHA_ENCAP] = { .type = NLA_NESTED }, + [NHA_ENCAP_TYPE] = { .type = NLA_U16 }, }; static struct nl_cache_ops rtnl_nh_ops; @@ -154,6 +161,13 @@ static int nh_clone(struct nl_object *_src, struct nl_object *_dst) dst->res_grp_unbalanced_timer = src->res_grp_unbalanced_timer; dst->ce_mask = src->ce_mask; + if (src->nh_encap) { + dst->nh_encap = rtnl_nh_encap_clone(src->nh_encap); + if (!dst->nh_encap) + return -NLE_NOMEM; + dst->ce_mask |= NH_ATTR_ENCAP; + } + if (src->nh_gateway) { dst->nh_gateway = nl_addr_clone(src->nh_gateway); if (!dst->nh_gateway) { @@ -173,10 +187,10 @@ static int nh_clone(struct nl_object *_src, struct nl_object *_dst) static void nh_free(struct nl_object *obj) { struct rtnl_nh *nh = nl_object_priv(obj); - nl_addr_put(nh->nh_gateway); - if (nh->nh_group) - rtnl_nh_grp_put(nh->nh_group); + nl_addr_put(nh->nh_gateway); + rtnl_nh_encap_free(nh->nh_encap); + rtnl_nh_grp_put(nh->nh_group); } void rtnl_nh_put(struct rtnl_nh *nh) @@ -236,6 +250,52 @@ struct nl_addr *rtnl_nh_get_gateway(struct rtnl_nh *nexthop) return nexthop->nh_gateway; } +/** + * Set nexthop encapsulation + * @arg nh Nexthop object + * @arg encap Encapsulation descriptor + * + * Assigns ownership of the encapsulation object to the nexthop. Any + * previously configured encapsulation is released. Passing a NULL + * encapsulation clears the encapsulation on the nexthop. + * + * On failure, the function consumes and frees encap. + * + * @return 0 on success, or the appropriate error-code on failure. + */ +int rtnl_nh_set_encap(struct rtnl_nh *nh, struct rtnl_nh_encap *encap) +{ + if (!nh) { + rtnl_nh_encap_free(encap); + return -NLE_INVAL; + } + + if (encap && !encap->ops) { + rtnl_nh_encap_free(encap); + return -NLE_INVAL; + } + + rtnl_nh_encap_free(nh->nh_encap); + + if (encap) { + nh->nh_encap = encap; + nh->ce_mask |= NH_ATTR_ENCAP; + } else { + nh->nh_encap = NULL; + nh->ce_mask &= ~NH_ATTR_ENCAP; + } + + return 0; +} + +struct rtnl_nh_encap *rtnl_nh_get_encap(struct rtnl_nh *nh) +{ + if (!nh || !(nh->ce_mask & NH_ATTR_ENCAP)) + return NULL; + + return nh->nh_encap; +} + int rtnl_nh_set_fdb(struct rtnl_nh *nexthop, int value) { if (value) @@ -546,6 +606,27 @@ static int rtnl_nh_build_msg(struct nl_msg *msg, struct rtnl_nh *nh) if (nh->ce_mask & NH_ATTR_FLAG_BLACKHOLE) NLA_PUT_FLAG(msg, NHA_BLACKHOLE); + if (nh->ce_mask & NH_ATTR_ENCAP) { + struct nlattr *encap; + + if (!nh->nh_encap || !nh->nh_encap->ops) + return -NLE_INVAL; + + NLA_PUT_U16(msg, NHA_ENCAP_TYPE, nh->nh_encap->ops->encap_type); + + encap = nla_nest_start(msg, NHA_ENCAP); + if (!encap) + goto nla_put_failure; + + if (nh->nh_encap->ops->build_msg) { + int err = nh->nh_encap->ops->build_msg( + msg, nh->nh_encap->priv); + if (err < 0) + return err; + } + nla_nest_end(msg, encap); + } + /* Nexthop group */ if (nh->ce_mask & NH_ATTR_GROUP) { struct nexthop_grp *grp; @@ -651,6 +732,55 @@ int rtnl_nh_add(struct nl_sock *sk, struct rtnl_nh *nh, int flags) return wait_for_ack(sk); } +struct rtnl_nh_encap *rtnl_nh_encap_alloc(void) +{ + return calloc(1, sizeof(struct rtnl_nh_encap)); +} + +void rtnl_nh_encap_free(struct rtnl_nh_encap *nh_encap) +{ + if (!nh_encap) + return; + + if (nh_encap->ops && nh_encap->ops->destructor) + nh_encap->ops->destructor(nh_encap->priv); + + free(nh_encap->priv); + free(nh_encap); +} + +struct rtnl_nh_encap *rtnl_nh_encap_clone(struct rtnl_nh_encap *src) +{ + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap *new_encap = NULL; + + if (!src) + return NULL; + + new_encap = rtnl_nh_encap_alloc(); + if (!new_encap) + return NULL; + + new_encap->ops = src->ops; + if (new_encap->ops) { + new_encap->priv = new_encap->ops->clone(src->priv); + if (!new_encap->priv) + return NULL; + } + + return _nl_steal_pointer(&new_encap); +} + +/* + * Retrieve the encapsulation associated with a nexthop if any. + */ +struct rtnl_nh_encap *rtnl_route_nh_get_encap(struct rtnl_nexthop *nh) +{ + if (!nh) + return NULL; + + return nh->rtnh_encap; +} + static struct nla_policy nh_res_group_policy[NHA_RES_GROUP_MAX + 1] = { [NHA_RES_GROUP_UNSPEC] = { .type = NLA_UNSPEC }, [NHA_RES_GROUP_BUCKETS] = { .type = NLA_U16 }, @@ -708,6 +838,19 @@ static int nexthop_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who, nexthop->ce_mask |= NH_ATTR_GROUP_TYPE; } + if (tb[NHA_ENCAP] && tb[NHA_ENCAP_TYPE]) { + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap *nh_encap = NULL; + + err = nh_encap_parse_msg(tb[NHA_ENCAP], tb[NHA_ENCAP_TYPE], + &nh_encap); + if (err < 0) + return err; + + err = rtnl_nh_set_encap(nexthop, _nl_steal_pointer(&nh_encap)); + if (err < 0) + return err; + } + if (tb[NHA_BLACKHOLE]) { nexthop->ce_mask |= NH_ATTR_FLAG_BLACKHOLE; } @@ -820,6 +963,9 @@ static void nh_dump_line(struct nl_object *obj, struct nl_dump_params *dp) nl_dump(dp, " via %s", nl_addr2str(nh->nh_gateway, buf, sizeof(buf))); + if (nh->ce_mask & NH_ATTR_ENCAP && nh->nh_encap) + nh_encap_dump(nh->nh_encap, dp); + if (nh->ce_mask & NH_ATTR_FLAG_BLACKHOLE) nl_dump(dp, " blackhole"); @@ -880,6 +1026,8 @@ static uint64_t nh_compare(struct nl_object *a, struct nl_object *b, diff |= _DIFF(NH_ATTR_RES_UNBALANCED_TIMER, src->res_grp_unbalanced_timer != dst->res_grp_unbalanced_timer); + diff |= _DIFF(NH_ATTR_ENCAP, + nh_encap_compare(src->nh_encap, dst->nh_encap)); #undef _DIFF return diff; diff --git a/lib/route/nh_encap_mpls.c b/lib/route/nh_encap_mpls.c index a7802917c..71ed3bee0 100644 --- a/lib/route/nh_encap_mpls.c +++ b/lib/route/nh_encap_mpls.c @@ -7,8 +7,10 @@ #include -#include "nl-route.h" #include "nexthop-encap.h" +#include "nl-aux-core/nl-core.h" +#include "nl-aux-route/nl-route.h" +#include "nl-route.h" struct mpls_iptunnel_encap { struct nl_addr *dst; @@ -47,15 +49,35 @@ static void mpls_encap_destructor(void *priv) nl_addr_put(encap_info->dst); } +static void *mpls_encap_clone(void *priv) +{ + struct mpls_iptunnel_encap *src = priv; + struct mpls_iptunnel_encap *clone; + + if (!src) + return NULL; + + clone = calloc(1, sizeof(*clone)); + if (!clone) + return NULL; + + clone->dst = nl_addr_get(src->dst); + clone->ttl = src->ttl; + + return clone; +} + static struct nla_policy mpls_encap_policy[MPLS_IPTUNNEL_MAX + 1] = { - [MPLS_IPTUNNEL_DST] = { .type = NLA_U32 }, - [MPLS_IPTUNNEL_TTL] = { .type = NLA_U8 }, + [MPLS_IPTUNNEL_DST] = { .type = NLA_U32 }, + [MPLS_IPTUNNEL_TTL] = { .type = NLA_U8 }, }; -static int mpls_encap_parse_msg(struct nlattr *nla, struct rtnl_nexthop *nh) +static int mpls_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 *labels = NULL; struct nlattr *tb[MPLS_IPTUNNEL_MAX + 1]; - struct nl_addr *labels; uint8_t ttl = 0; int err; @@ -73,11 +95,17 @@ static int mpls_encap_parse_msg(struct nlattr *nla, struct rtnl_nexthop *nh) if (tb[MPLS_IPTUNNEL_TTL]) ttl = nla_get_u8(tb[MPLS_IPTUNNEL_TTL]); - err = rtnl_route_nh_encap_mpls(nh, labels, ttl); + nh_encap = rtnl_nh_encap_alloc(); + if (!nh_encap) + return -NLE_NOMEM; - nl_addr_put(labels); + err = rtnl_nh_encap_mpls(nh_encap, labels, ttl); + if (err < 0) + return err; + + *encap_out = _nl_steal_pointer(&nh_encap); - return err; + return 0; } static int mpls_encap_compare(void *_a, void *_b) @@ -93,69 +121,95 @@ static int mpls_encap_compare(void *_a, void *_b) } struct nh_encap_ops mpls_encap_ops = { - .encap_type = LWTUNNEL_ENCAP_MPLS, - .build_msg = mpls_encap_build_msg, - .parse_msg = mpls_encap_parse_msg, - .compare = mpls_encap_compare, - .dump = mpls_encap_dump, - .destructor = mpls_encap_destructor, + .encap_type = LWTUNNEL_ENCAP_MPLS, + .build_msg = mpls_encap_build_msg, + .parse_msg = mpls_encap_parse_msg, + .compare = mpls_encap_compare, + .clone = mpls_encap_clone, + .dump = mpls_encap_dump, + .destructor = mpls_encap_destructor, }; -int rtnl_route_nh_encap_mpls(struct rtnl_nexthop *nh, - struct nl_addr *addr, - uint8_t ttl) +int rtnl_nh_encap_mpls(struct rtnl_nh_encap *nh_encap, struct nl_addr *dst, + uint8_t ttl) { struct mpls_iptunnel_encap *mpls_encap; - struct rtnl_nh_encap *rtnh_encap; - if (!addr) + if (!dst || !nh_encap) return -NLE_INVAL; - rtnh_encap = calloc(1, sizeof(*rtnh_encap)); - if (!rtnh_encap) - return -NLE_NOMEM; - mpls_encap = calloc(1, sizeof(*mpls_encap)); if (!mpls_encap) { - free(rtnh_encap); return -NLE_NOMEM; } - mpls_encap->dst = nl_addr_get(addr); + mpls_encap->dst = nl_addr_get(dst); mpls_encap->ttl = ttl; - rtnh_encap->priv = mpls_encap; - rtnh_encap->ops = &mpls_encap_ops; + nh_encap->priv = mpls_encap; + nh_encap->ops = &mpls_encap_ops; + + return 0; +} + +int rtnl_route_nh_encap_mpls(struct rtnl_nexthop *nh, struct nl_addr *addr, + uint8_t ttl) +{ + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap *rtnh_encap = NULL; + int ret; + + rtnh_encap = rtnl_nh_encap_alloc(); + if (!rtnh_encap) + return -NLE_NOMEM; + + ret = rtnl_nh_encap_mpls(rtnh_encap, addr, ttl); + if (ret < 0) + return ret; - nh_set_encap(nh, rtnh_encap); + rtnl_route_nh_set_encap(nh, _nl_steal_pointer(&rtnh_encap)); return 0; } -struct nl_addr *rtnl_route_nh_get_encap_mpls_dst(struct rtnl_nexthop *nh) +struct nl_addr *rtnl_nh_get_encap_mpls_dst(struct rtnl_nh_encap *nh_encap) { struct mpls_iptunnel_encap *mpls_encap; - if (!nh->rtnh_encap || nh->rtnh_encap->ops->encap_type != LWTUNNEL_ENCAP_MPLS) + if (!nh_encap || nh_encap->ops->encap_type != LWTUNNEL_ENCAP_MPLS) return NULL; - mpls_encap = (struct mpls_iptunnel_encap *)nh->rtnh_encap->priv; + mpls_encap = (struct mpls_iptunnel_encap *)nh_encap->priv; if (!mpls_encap) return NULL; return mpls_encap->dst; } -uint8_t rtnl_route_nh_get_encap_mpls_ttl(struct rtnl_nexthop *nh) +struct nl_addr *rtnl_route_nh_get_encap_mpls_dst(struct rtnl_nexthop *nh) +{ + return rtnl_nh_get_encap_mpls_dst(nh->rtnh_encap); +} + +int rtnl_nh_get_encap_mpls_ttl(struct rtnl_nh_encap *nh_encap) { struct mpls_iptunnel_encap *mpls_encap; - if (!nh->rtnh_encap || nh->rtnh_encap->ops->encap_type != LWTUNNEL_ENCAP_MPLS) - return 0; + if (!nh_encap || !nh_encap->ops || + nh_encap->ops->encap_type != LWTUNNEL_ENCAP_MPLS) + return -NLE_INVAL; - mpls_encap = (struct mpls_iptunnel_encap *)nh->rtnh_encap->priv; + mpls_encap = (struct mpls_iptunnel_encap *)nh_encap->priv; if (!mpls_encap) - return 0; + return -NLE_INVAL; return mpls_encap->ttl; } + +uint8_t rtnl_route_nh_get_encap_mpls_ttl(struct rtnl_nexthop *nh) +{ + int ttl = rtnl_nh_get_encap_mpls_ttl(nh->rtnh_encap); + + if (ttl < 0) + return 0; + return (uint8_t)ttl; +} diff --git a/lib/route/route_obj.c b/lib/route/route_obj.c index 094ae536c..8448e9a86 100644 --- a/lib/route/route_obj.c +++ b/lib/route/route_obj.c @@ -955,7 +955,8 @@ struct rtnl_nexthop *rtnl_route_nexthop_n(struct rtnl_route *r, int n) i = 0; nl_list_for_each_entry(nh, &r->rt_nexthops, rtnh_list) { - if (i == n) return nh; + if (i == n) + return nh; i++; } } @@ -1148,9 +1149,17 @@ static int parse_multipath(struct rtnl_route *route, struct nlattr *attr) } if (ntb[RTA_ENCAP] && ntb[RTA_ENCAP_TYPE]) { + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap + *encap = NULL; + err = nh_encap_parse_msg(ntb[RTA_ENCAP], ntb[RTA_ENCAP_TYPE], - nh); + &encap); + if (err < 0) + return err; + + err = rtnl_route_nh_set_encap( + nh, _nl_steal_pointer(&encap)); if (err < 0) return err; } @@ -1357,11 +1366,18 @@ int rtnl_route_parse(struct nlmsghdr *nlh, struct rtnl_route **result) } if (tb[RTA_ENCAP] && tb[RTA_ENCAP_TYPE]) { + _nl_auto_rtnl_nh_encap struct rtnl_nh_encap *encap = NULL; + if (!old_nh && !(old_nh = rtnl_route_nh_alloc())) return -NLE_NOMEM; - err = nh_encap_parse_msg(tb[RTA_ENCAP], - tb[RTA_ENCAP_TYPE], old_nh); + err = nh_encap_parse_msg(tb[RTA_ENCAP], tb[RTA_ENCAP_TYPE], + &encap); + if (err < 0) + return err; + + err = rtnl_route_nh_set_encap(old_nh, + _nl_steal_pointer(&encap)); if (err < 0) return err; } diff --git a/libnl-route-3.sym b/libnl-route-3.sym index f90634f60..de1eb0229 100644 --- a/libnl-route-3.sym +++ b/libnl-route-3.sym @@ -1371,12 +1371,20 @@ global: rtnl_link_ip6_tnl_set_collect_metadata; rtnl_link_is_bond; rtnl_nh_add; + rtnl_nh_encap_alloc; + rtnl_nh_encap_clone; + rtnl_nh_encap_free; + rtnl_nh_encap_mpls; + rtnl_nh_get_encap; + rtnl_nh_get_encap_mpls_dst; + rtnl_nh_get_encap_mpls_ttl; rtnl_nh_get_family; rtnl_nh_get_group_type; rtnl_nh_get_oif; rtnl_nh_get_res_group_bucket_size; rtnl_nh_get_res_group_idle_timer; rtnl_nh_get_res_group_unbalanced_timer; + rtnl_nh_set_encap; rtnl_nh_set_family; rtnl_nh_set_group; rtnl_nh_set_group_type; @@ -1385,4 +1393,6 @@ global: rtnl_nh_set_res_group_bucket_size; rtnl_nh_set_res_group_idle_timer; rtnl_nh_set_res_group_unbalanced_timer; + rtnl_route_nh_get_encap; + rtnl_route_nh_set_encap; } libnl_3_11; diff --git a/tests/check-all.c b/tests/check-all.c index 69214e31a..46e7ca3ea 100644 --- a/tests/check-all.c +++ b/tests/check-all.c @@ -29,6 +29,7 @@ int main(int argc, char *argv[]) srunner_add_suite(runner, make_nl_netns_suite()); srunner_add_suite(runner, make_nl_ip6_tnl_suite()); srunner_add_suite(runner, make_nl_route_nh_suite()); + srunner_add_suite(runner, make_nl_route_nexthop_suite()); srunner_run_all(runner, CK_ENV); diff --git a/tests/cksuite-all.h b/tests/cksuite-all.h index c1911b34b..bdbbc6401 100644 --- a/tests/cksuite-all.h +++ b/tests/cksuite-all.h @@ -11,5 +11,6 @@ Suite *make_nl_ematch_tree_clone_suite(void); Suite *make_nl_netns_suite(void); Suite *make_nl_ip6_tnl_suite(void); Suite *make_nl_route_nh_suite(void); +Suite *make_nl_route_nexthop_suite(void); #endif /* __LIBNL3_TESTS_CHECK_ALL_H__ */ diff --git a/tests/cksuite-route-nexthop.c b/tests/cksuite-route-nexthop.c new file mode 100644 index 000000000..46f060a6b --- /dev/null +++ b/tests/cksuite-route-nexthop.c @@ -0,0 +1,484 @@ +/* SPDX-License-Identifier: LGPL-2.1-only */ + +/* + * Tests for include/netlink/route/nexthop.h getters and setters. + * + * We cover two aspects: + * 1. Pure userspace logic – manipulating an allocated rtnl_nexthop object + * and validating all getters, including negative/error conditions. + * 2. Kernel round-trips – create real routes with nexthops in a private + * network namespace, query them back and verify attributes round-trip. + */ + +#include "nl-default.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "cksuite-all.h" +#include "nl-priv-dynamic-route/nl-priv-dynamic-route.h" + +/*****************************************************************************/ +/* Userspace API tests */ + +START_TEST(test_route_nexthop_api_set_get_all) +{ + _nl_auto_rtnl_nexthop struct rtnl_nexthop *nh = NULL; + _nl_auto_nl_addr struct nl_addr *gw4 = NULL; + _nl_auto_nl_addr struct nl_addr *gw6 = NULL; + _nl_auto_nl_addr struct nl_addr *newdst4 = NULL; + _nl_auto_nl_addr struct nl_addr *via6 = NULL; + _nl_auto_nl_addr struct nl_addr *mpls = NULL; + _nl_auto_rtnl_nexthop struct rtnl_nexthop *clone = NULL; + _nl_auto_rtnl_nexthop struct rtnl_nexthop *clone_with_encap = NULL; + struct rtnl_nh_encap *encap = NULL; + struct rtnl_nh_encap *got = NULL; + struct rtnl_nh_encap *encap_clone = NULL; + uint32_t realms = 0xAABBCCDDu; + char flags_buf[64]; + int flags_parsed; + + nh = rtnl_route_nh_alloc(); + ck_assert_ptr_nonnull(nh); + + /* Ifindex set/get */ + ck_assert_int_eq(rtnl_route_nh_get_ifindex(nh), 0); + rtnl_route_nh_set_ifindex(nh, 123); + ck_assert_int_eq(rtnl_route_nh_get_ifindex(nh), 123); + + /* Weight set/get */ + ck_assert_uint_eq(rtnl_route_nh_get_weight(nh), 0); + rtnl_route_nh_set_weight(nh, 7); + ck_assert_uint_eq(rtnl_route_nh_get_weight(nh), 7); + + /* Flags set/unset/get and string conversion */ + ck_assert_uint_eq(rtnl_route_nh_get_flags(nh), 0U); + rtnl_route_nh_set_flags(nh, RTNH_F_ONLINK); + ck_assert_int_eq((int)(rtnl_route_nh_get_flags(nh) & RTNH_F_ONLINK), + (int)RTNH_F_ONLINK); + ck_assert_ptr_nonnull(rtnl_route_nh_flags2str(RTNH_F_ONLINK, flags_buf, + sizeof(flags_buf))); + flags_parsed = rtnl_route_nh_str2flags("onlink"); + ck_assert_int_eq(flags_parsed, RTNH_F_ONLINK); + rtnl_route_nh_unset_flags(nh, RTNH_F_ONLINK); + ck_assert_int_eq((int)(rtnl_route_nh_get_flags(nh) & RTNH_F_ONLINK), 0); + + /* Realms set/get */ + ck_assert_uint_eq(rtnl_route_nh_get_realms(nh), 0U); + rtnl_route_nh_set_realms(nh, realms); + ck_assert_uint_eq(rtnl_route_nh_get_realms(nh), realms); + + /* Gateway get/set; start NULL, set v4 then replace with v6 */ + ck_assert_ptr_eq(rtnl_route_nh_get_gateway(nh), NULL); + ck_assert_int_eq(nl_addr_parse("192.0.2.1", AF_INET, &gw4), 0); + rtnl_route_nh_set_gateway(nh, gw4); + ck_assert_ptr_nonnull(rtnl_route_nh_get_gateway(nh)); + ck_assert_int_eq(nl_addr_get_family(rtnl_route_nh_get_gateway(nh)), + AF_INET); + ck_assert_int_eq(nl_addr_parse("2001:db8::1", AF_INET6, &gw6), 0); + rtnl_route_nh_set_gateway(nh, gw6); + ck_assert_int_eq(nl_addr_shared(gw4), 0); + ck_assert_int_eq(nl_addr_get_family(rtnl_route_nh_get_gateway(nh)), + AF_INET6); + rtnl_route_nh_set_gateway(nh, NULL); + ck_assert_ptr_eq(rtnl_route_nh_get_gateway(nh), NULL); + + /* newdst set/get */ + ck_assert_ptr_eq(rtnl_route_nh_get_newdst(nh), NULL); + ck_assert_int_eq(nl_addr_parse("198.51.100.7", AF_INET, &newdst4), 0); + ck_assert_int_eq(rtnl_route_nh_set_newdst(nh, newdst4), 0); + ck_assert_ptr_nonnull(rtnl_route_nh_get_newdst(nh)); + ck_assert_int_eq(nl_addr_get_family(rtnl_route_nh_get_newdst(nh)), + AF_INET); + ck_assert_int_eq(rtnl_route_nh_set_newdst(nh, NULL), 0); + ck_assert_ptr_eq(rtnl_route_nh_get_newdst(nh), NULL); + + /* via set/get */ + ck_assert_ptr_eq(rtnl_route_nh_get_via(nh), NULL); + ck_assert_int_eq(nl_addr_parse("2001:db8::2", AF_INET6, &via6), 0); + ck_assert_int_eq(rtnl_route_nh_set_via(nh, via6), 0); + ck_assert_ptr_nonnull(rtnl_route_nh_get_via(nh)); + ck_assert_int_eq(nl_addr_get_family(rtnl_route_nh_get_via(nh)), + AF_INET6); + ck_assert_int_eq(rtnl_route_nh_set_via(nh, NULL), 0); + ck_assert_ptr_eq(rtnl_route_nh_get_via(nh), NULL); + + /* clone, compare, identical */ + clone = rtnl_route_nh_clone(nh); + ck_assert_ptr_nonnull(clone); + ck_assert_int_eq(rtnl_route_nh_compare(nh, clone, 0xFFFFFFFFu, 0), 0); + + /* Only differ in flags/weight -> identical() should still be true */ + rtnl_route_nh_set_weight(clone, 9); + rtnl_route_nh_set_flags(clone, RTNH_F_ONLINK); + ck_assert_int_ne(rtnl_route_nh_compare(nh, clone, 0xFFFFFFFFu, 0), 0); + ck_assert_int_ne(rtnl_route_nh_identical(nh, clone), 0); + + /* MPLS encapsulation */ + encap = rtnl_nh_encap_alloc(); + ck_assert_ptr_nonnull(encap); + + ck_assert_int_eq(rtnl_nh_get_encap_mpls_ttl(NULL), -NLE_INVAL); + ck_assert_int_eq(rtnl_nh_get_encap_mpls_ttl(encap), -NLE_INVAL); + + /* Invalid: missing destination labels */ + ck_assert_int_eq(rtnl_nh_encap_mpls(encap, NULL, 0), -NLE_INVAL); + + /* Valid MPLS encap: push label 100 with TTL 64 */ + ck_assert_int_eq(nl_addr_parse("100", AF_MPLS, &mpls), 0); + ck_assert_int_eq(rtnl_nh_encap_mpls(encap, mpls, 64), 0); + + /* Attach encap to nexthop and retrieve it back */ + ck_assert_int_eq(rtnl_route_nh_set_encap(nh, encap), 0); + got = rtnl_route_nh_get_encap(nh); + ck_assert_ptr_eq(got, encap); + + /* Access MPLS encap details through the new getters */ + ck_assert_ptr_nonnull(rtnl_nh_get_encap_mpls_dst(got)); + ck_assert_int_eq(rtnl_nh_get_encap_mpls_ttl(got), 64); + + /* Exercise rtnl_nh_encap_clone() directly */ + encap_clone = rtnl_nh_encap_clone(encap); + ck_assert_ptr_nonnull(encap_clone); + ck_assert_ptr_nonnull(rtnl_nh_get_encap_mpls_dst(encap_clone)); + ck_assert_int_eq( + nl_addr_cmp(rtnl_nh_get_encap_mpls_dst(encap_clone), mpls), 0); + ck_assert_uint_eq(rtnl_nh_get_encap_mpls_ttl(encap_clone), 64); + /* Free the cloned encap explicitly */ + rtnl_nh_encap_free(encap_clone); + encap_clone = NULL; + + /* Exercise nexthop clone with encap: encap should be deep-cloned */ + clone_with_encap = rtnl_route_nh_clone(nh); + ck_assert_ptr_nonnull(clone_with_encap); + encap_clone = rtnl_route_nh_get_encap(clone_with_encap); + ck_assert_ptr_nonnull(encap_clone); + /* The encap on the clone must not be the same pointer */ + ck_assert_ptr_ne(encap_clone, encap); + ck_assert_int_eq( + nl_addr_cmp(rtnl_nh_get_encap_mpls_dst(encap_clone), mpls), 0); + ck_assert_uint_eq(rtnl_nh_get_encap_mpls_ttl(encap_clone), 64); + + /* Clear encap and verify it is removed */ + ck_assert_int_eq(rtnl_route_nh_set_encap(nh, NULL), 0); + ck_assert_ptr_eq(rtnl_route_nh_get_encap(nh), NULL); +} +END_TEST + +/*****************************************************************************/ +/* Kernel round-trip tests */ + +/* + * Helper: retrieve a configured route object by its prefix (exact match). + * This searches the kernel's route tables for a route whose destination + * prefix matches the nl_addr 'dst' (including prefix length), and returns + * it via 'out'. + */ +static int nltst_route_get_by_dst(struct nl_sock *sk, struct nl_addr *dst, + struct rtnl_route **out) +{ + _nl_auto_nl_cache struct nl_cache *cache = NULL; + _nl_auto_rtnl_route struct rtnl_route *filter = NULL; + struct nl_object *obj; + int err; + + if (!dst || !out) + return -NLE_INVAL; + + err = rtnl_route_alloc_cache(sk, nl_addr_get_family(dst), 0, &cache); + if (err < 0) + return err; + + filter = rtnl_route_alloc(); + if (!filter) + return -NLE_NOMEM; + + err = rtnl_route_set_dst(filter, dst); + if (err < 0) + return err; + + obj = nl_cache_find(cache, (struct nl_object *)filter); + if (!obj) + return -NLE_OBJ_NOTFOUND; + + *out = (struct rtnl_route *)obj; + return 0; +} + +START_TEST(test_kernel_route_roundtrip_single_v4) +{ + const char *IFNAME_DUMMY = "rnh-dummy0"; + _nltst_auto_delete_link const char *auto_del_dummy = NULL; + _nl_auto_nl_socket struct nl_sock *sk = NULL; + _nl_auto_rtnl_route struct rtnl_route *route = NULL; + _nl_auto_rtnl_route struct rtnl_route *got = NULL; + _nl_auto_nl_addr struct nl_addr *dst = NULL; + _nl_auto_nl_addr struct nl_addr *gw = NULL; + struct rtnl_nexthop *nh, *knh; + struct nl_addr *kgw; + int ifindex_dummy; + int r; + + if (_nltst_skip_no_netns()) + return; + + 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); + + /* Build a simple IPv4 route via gateway on dummy */ + route = rtnl_route_alloc(); + ck_assert_ptr_nonnull(route); + ck_assert_int_eq(nl_addr_parse("198.51.100.0/24", AF_INET, &dst), 0); + ck_assert_int_eq(rtnl_route_set_family(route, AF_INET), 0); + ck_assert_int_eq(rtnl_route_set_dst(route, dst), 0); + + nh = rtnl_route_nh_alloc(); + ck_assert_ptr_nonnull(nh); + rtnl_route_nh_set_ifindex(nh, ifindex_dummy); + ck_assert_int_eq(nl_addr_parse("192.0.2.1", AF_INET, &gw), 0); + rtnl_route_nh_set_realms(nh, 0xdeadbeef); + rtnl_route_nh_set_gateway(nh, gw); + rtnl_route_nh_set_weight(nh, 1); + rtnl_route_add_nexthop(route, nh); + + r = rtnl_route_add(sk, route, NLM_F_CREATE); + ck_assert_int_eq(r, 0); + + /* Retrieve the route back by its prefix and validate nexthop attributes */ + ck_assert_int_eq(nltst_route_get_by_dst(sk, dst, &got), 0); + ck_assert_ptr_nonnull(got); + ck_assert_int_eq(nl_addr_cmp(rtnl_route_get_dst(got), dst), 0); + ck_assert_int_eq(rtnl_route_get_nnexthops(got), 1); + + knh = rtnl_route_nexthop_n(got, 0); + kgw = rtnl_route_nh_get_gateway(knh); + ck_assert_int_eq(rtnl_route_nh_get_ifindex(knh), ifindex_dummy); + ck_assert_ptr_nonnull(kgw); + ck_assert_int_eq(nl_addr_get_family(kgw), AF_INET); + ck_assert_int_eq(nl_addr_cmp(kgw, gw), 0); + + /* Single-nexthop route weight is not propagated to kernel - thus check for 0 */ + ck_assert_uint_eq(rtnl_route_nh_get_weight(knh), 0); + ck_assert_uint_eq(rtnl_route_nh_get_flags(knh), 0U); + ck_assert_uint_eq(rtnl_route_nh_get_realms(knh), 0xdeadbeef); + ck_assert_ptr_eq(rtnl_route_nh_get_newdst(knh), NULL); + ck_assert_ptr_eq(rtnl_route_nh_get_via(knh), NULL); + /* No encap present */ + ck_assert_ptr_eq(rtnl_route_nh_get_encap(knh), NULL); +} +END_TEST + +static void nltst_assert_multipath_v4(struct rtnl_route *route, + int ifindex_dummy, struct nl_addr *gw1, + unsigned int w1, struct nl_addr *gw2, + unsigned int w2, uint32_t realms) +{ + int seen_gw1 = 0, seen_gw2 = 0; + struct nl_list_head *list = rtnl_route_get_nexthops(route); + struct rtnl_nexthop *knh; + size_t cnt = 0; + + nl_list_for_each_entry(knh, list, rtnh_list) { + struct nl_addr *kgw = rtnl_route_nh_get_gateway(knh); + + ck_assert_int_eq(rtnl_route_nh_get_ifindex(knh), ifindex_dummy); + ck_assert_ptr_nonnull(kgw); + ck_assert_int_eq(nl_addr_get_family(kgw), AF_INET); + + if (nl_addr_cmp(kgw, gw1) == 0) { + seen_gw1 = 1; + ck_assert_uint_eq(rtnl_route_nh_get_weight(knh), w1); + } + if (nl_addr_cmp(kgw, gw2) == 0) { + seen_gw2 = 1; + ck_assert_uint_eq(rtnl_route_nh_get_weight(knh), w2); + } + + ck_assert_uint_eq(rtnl_route_nh_get_flags(knh), 0U); + ck_assert_uint_eq(rtnl_route_nh_get_realms(knh), realms); + ck_assert_ptr_eq(rtnl_route_nh_get_newdst(knh), NULL); + ck_assert_ptr_eq(rtnl_route_nh_get_via(knh), NULL); + /* No encap present */ + ck_assert_ptr_eq(rtnl_route_nh_get_encap(knh), NULL); + + cnt++; + } + + ck_assert_uint_eq(cnt, 2); + ck_assert_int_ne(seen_gw1, 0); + ck_assert_int_ne(seen_gw2, 0); +} + +START_TEST(test_kernel_route_roundtrip_multipath_v4) +{ + const char *IFNAME_DUMMY = "rnh-dummy1"; + _nltst_auto_delete_link const char *auto_del_dummy = NULL; + _nl_auto_nl_socket struct nl_sock *sk = NULL; + _nl_auto_rtnl_route struct rtnl_route *route = NULL; + _nl_auto_rtnl_route struct rtnl_route *got = NULL; + _nl_auto_nl_addr struct nl_addr *dst = NULL; + _nl_auto_nl_addr struct nl_addr *gw1 = NULL; + _nl_auto_nl_addr struct nl_addr *gw2 = NULL; + struct rtnl_nexthop *nh1 = NULL; + struct rtnl_nexthop *nh2 = NULL; + int ifindex_dummy; + + if (_nltst_skip_no_netns()) + return; + + 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); + + /* Build IPv4 ECMP route with 2 nexthops differing by gateway */ + route = rtnl_route_alloc(); + ck_assert_ptr_nonnull(route); + ck_assert_int_eq(nl_addr_parse("198.51.101.0/24", AF_INET, &dst), 0); + ck_assert_int_eq(rtnl_route_set_family(route, AF_INET), 0); + ck_assert_int_eq(rtnl_route_set_dst(route, dst), 0); + + nh1 = rtnl_route_nh_alloc(); + ck_assert_ptr_nonnull(nh1); + rtnl_route_nh_set_ifindex(nh1, ifindex_dummy); + ck_assert_int_eq(nl_addr_parse("192.0.2.1", AF_INET, &gw1), 0); + rtnl_route_nh_set_gateway(nh1, gw1); + rtnl_route_nh_set_weight(nh1, 1); + rtnl_route_nh_set_realms(nh1, 0xdeadbeef); + rtnl_route_add_nexthop(route, nh1); + + nh2 = rtnl_route_nh_alloc(); + ck_assert_ptr_nonnull(nh2); + rtnl_route_nh_set_ifindex(nh2, ifindex_dummy); + ck_assert_int_eq(nl_addr_parse("192.0.2.3", AF_INET, &gw2), 0); + rtnl_route_nh_set_gateway(nh2, gw2); + rtnl_route_nh_set_weight(nh2, 2); + rtnl_route_nh_set_realms(nh2, 0xdeadbeef); + rtnl_route_add_nexthop(route, nh2); + + ck_assert_int_eq(rtnl_route_add(sk, route, NLM_F_CREATE), 0); + + /* Retrieve the route back by its prefix and validate multipath nexthops */ + ck_assert_int_eq(nltst_route_get_by_dst(sk, dst, &got), 0); + ck_assert_ptr_nonnull(got); + ck_assert_int_eq(nl_addr_cmp(rtnl_route_get_dst(got), dst), 0); + ck_assert_int_eq(rtnl_route_get_nnexthops(got), 2); + + nltst_assert_multipath_v4(got, ifindex_dummy, gw1, 1, gw2, 2, + 0xdeadbeef); +} +END_TEST + +static void nltst_assert_single_v4_mpls(struct rtnl_route *route, + int ifindex_dummy, struct nl_addr *gw, + struct nl_addr *labels, + unsigned int ttl) +{ + struct rtnl_nexthop *knh = rtnl_route_nexthop_n(route, 0); + struct nl_addr *kgw = rtnl_route_nh_get_gateway(knh); + struct rtnl_nh_encap *encap = rtnl_route_nh_get_encap(knh); + struct nl_addr *klabels; + + ck_assert_ptr_nonnull(encap); + ck_assert_int_eq(rtnl_route_nh_get_ifindex(knh), ifindex_dummy); + ck_assert_ptr_nonnull(kgw); + ck_assert_int_eq(nl_addr_get_family(kgw), AF_INET); + ck_assert_int_eq(nl_addr_cmp(kgw, gw), 0); + + klabels = rtnl_nh_get_encap_mpls_dst(encap); + ck_assert_ptr_nonnull(klabels); + ck_assert_int_eq(nl_addr_get_family(klabels), AF_MPLS); + ck_assert_int_eq(nl_addr_cmp(klabels, labels), 0); + + ck_assert_int_eq(rtnl_nh_get_encap_mpls_ttl(encap), ttl); +} + +START_TEST(test_kernel_route_roundtrip_nh_mpls_encap_v4) +{ + const char *IFNAME_DUMMY = "rnh-dummy2"; + _nltst_auto_delete_link const char *auto_del_dummy = NULL; + _nl_auto_nl_socket struct nl_sock *sk = NULL; + _nl_auto_rtnl_route struct rtnl_route *route = NULL; + _nl_auto_rtnl_route struct rtnl_route *got = NULL; + _nl_auto_nl_addr struct nl_addr *dst = NULL; + _nl_auto_nl_addr struct nl_addr *gw = NULL; + _nl_auto_nl_addr struct nl_addr *labels = NULL; + struct rtnl_nh_encap *encap2; + struct rtnl_nexthop *nh = NULL; + int ifindex_dummy; + + if (_nltst_skip_no_netns()) + return; + + 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); + + /* Build a simple IPv4 route via gateway on dummy with MPLS encap */ + route = rtnl_route_alloc(); + ck_assert_ptr_nonnull(route); + ck_assert_int_eq(nl_addr_parse("198.51.102.0/24", AF_INET, &dst), 0); + ck_assert_int_eq(rtnl_route_set_family(route, AF_INET), 0); + ck_assert_int_eq(rtnl_route_set_dst(route, dst), 0); + + nh = rtnl_route_nh_alloc(); + ck_assert_ptr_nonnull(nh); + rtnl_route_nh_set_ifindex(nh, ifindex_dummy); + ck_assert_int_eq(nl_addr_parse("192.0.2.1", AF_INET, &gw), 0); + rtnl_route_nh_set_gateway(nh, gw); + + /* Push label 100 with TTL 64 */ + encap2 = rtnl_nh_encap_alloc(); + ck_assert_ptr_nonnull(encap2); + ck_assert_int_eq(nl_addr_parse("100", AF_MPLS, &labels), 0); + ck_assert_int_eq(rtnl_nh_encap_mpls(encap2, labels, 64), 0); + ck_assert_int_eq(rtnl_route_nh_set_encap(nh, encap2), 0); + rtnl_route_add_nexthop(route, nh); + + ck_assert_int_eq(rtnl_route_add(sk, route, NLM_F_CREATE), 0); + + /* Retrieve the route back by its prefix and validate MPLS encap on nexthop */ + ck_assert_int_eq(nltst_route_get_by_dst(sk, dst, &got), 0); + ck_assert_ptr_nonnull(got); + ck_assert_int_eq(nl_addr_cmp(rtnl_route_get_dst(got), dst), 0); + ck_assert_int_eq(rtnl_route_get_nnexthops(got), 1); + nltst_assert_single_v4_mpls(got, ifindex_dummy, gw, labels, 64u); +} +END_TEST + +Suite *make_nl_route_nexthop_suite(void) +{ + Suite *suite = suite_create("route-nexthop"); + TCase *tc_api = tcase_create("Userspace-API"); + TCase *tc_kernel = tcase_create("Kernel"); + + /* Userspace only tests */ + tcase_add_test(tc_api, test_route_nexthop_api_set_get_all); + suite_add_tcase(suite, tc_api); + + /* Kernel round-trip – needs private netns */ + tcase_add_checked_fixture(tc_kernel, nltst_netns_fixture_setup, + nltst_netns_fixture_teardown); + tcase_add_test(tc_kernel, test_kernel_route_roundtrip_single_v4); + tcase_add_test(tc_kernel, test_kernel_route_roundtrip_multipath_v4); + /* Nexthop encapsulation roundtrip tests */ + tcase_add_test(tc_kernel, test_kernel_route_roundtrip_nh_mpls_encap_v4); + suite_add_tcase(suite, tc_kernel); + + return suite; +} diff --git a/tests/cksuite-route-nh.c b/tests/cksuite-route-nh.c index f011c3b27..dc54ee64b 100644 --- a/tests/cksuite-route-nh.c +++ b/tests/cksuite-route-nh.c @@ -18,40 +18,13 @@ #include #include #include +#include #include "cksuite-all.h" /*****************************************************************************/ /* Kernel round-trip tests exercising rtnl_nh_add() and message parsing */ -static void _nh_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); -} - -static void _nh_addr4_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 *local4 = NULL; - - addr = rtnl_addr_alloc(); - ck_assert_ptr_nonnull(addr); - rtnl_addr_set_ifindex(addr, ifindex); - rtnl_addr_set_family(addr, AF_INET); - ck_assert_int_eq(nl_addr_parse(ip, AF_INET, &local4), 0); - ck_assert_int_eq(rtnl_addr_set_local(addr, local4), 0); - rtnl_addr_set_prefixlen(addr, prefixlen); - ck_assert_int_eq(rtnl_addr_add(sk, addr, 0), 0); -} - START_TEST(test_kernel_roundtrip_basic_v4) { const char *IFNAME_DUMMY = "nh-dummy0"; @@ -74,8 +47,8 @@ START_TEST(test_kernel_roundtrip_basic_v4) _nltst_add_link(sk, IFNAME_DUMMY, "dummy", &ifindex_dummy); /* Bring up and add an IPv4 address via libnl */ - _nh_link_up(sk, IFNAME_DUMMY); - _nh_addr4_add(sk, ifindex_dummy, "192.0.2.2", 24); + _nltst_link_up(sk, IFNAME_DUMMY); + _nltst_addr4_add(sk, ifindex_dummy, "192.0.2.2", 24); /* Build nexthop: v4 gateway over dummy OIF with explicit id */ nh = rtnl_nh_alloc(); @@ -103,6 +76,72 @@ START_TEST(test_kernel_roundtrip_basic_v4) } END_TEST +/* Kernel round-trip tests for MPLS encap on rtnl_nh */ + +START_TEST(test_kernel_roundtrip_encap_mpls) +{ + const char *IFNAME_DUMMY = "nh-dummy-encap0"; + _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; + _nl_auto_nl_addr struct nl_addr *gw4 = NULL; + _nl_auto_nl_addr struct nl_addr *labels = NULL; + struct rtnl_nh_encap *encap = NULL; + int ifindex_dummy; + + if (_nltst_skip_no_netns()) + return; + + sk = _nltst_socket(NETLINK_ROUTE); + + /* Create underlay */ + 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); + + /* Build nexthop: v4 gw over dummy with MPLS encap */ + nh = rtnl_nh_alloc(); + ck_assert_ptr_nonnull(nh); + ck_assert_int_eq(rtnl_nh_set_id(nh, 3101), 0); + + /* Surprisingly the kernel accepts a nexthop with encap and a gw */ + 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("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); + + /* Fails - we need a family & oif*/ + ck_assert_int_eq(rtnl_nh_add(sk, nh, NLM_F_CREATE), -NLE_INVAL); + + /* Fails, we need a family */ + ck_assert_int_eq(rtnl_nh_set_oif(nh, (uint32_t)ifindex_dummy), 0); + ck_assert_int_eq(rtnl_nh_add(sk, nh, NLM_F_CREATE), -NLE_INVAL); + + 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, 3101); + ck_assert_ptr_nonnull(knh); + ck_assert_int_eq(rtnl_nh_get_id(knh), 3101); + ck_assert_int_eq(rtnl_nh_get_oif(knh), ifindex_dummy); + + kencap = rtnl_nh_get_encap(knh); + ck_assert_ptr_nonnull(kencap); + ck_assert_ptr_nonnull(rtnl_nh_get_encap_mpls_dst(kencap)); + ck_assert_uint_eq(rtnl_nh_get_encap_mpls_ttl(kencap), 64); +} +END_TEST + START_TEST(test_kernel_negative_mismatched_gw_family) { const char *IFNAME_DUMMY = "nh-dummy-neg0"; @@ -119,7 +158,7 @@ START_TEST(test_kernel_negative_mismatched_gw_family) auto_del_dummy = IFNAME_DUMMY; _nltst_add_link(sk, IFNAME_DUMMY, "dummy", &ifindex_dummy); - _nh_link_up(sk, IFNAME_DUMMY); + _nltst_link_up(sk, IFNAME_DUMMY); /* Build nexthop with AF_INET6 but an IPv4 gateway -> invalid */ nh = rtnl_nh_alloc(); @@ -172,7 +211,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); - _nh_link_up(sk, IFNAME_DUMMY); + _nltst_link_up(sk, IFNAME_DUMMY); /* Build nexthop with IPv4 gateway but no OIF -> invalid */ nh = rtnl_nh_alloc(); @@ -206,7 +245,7 @@ START_TEST(test_kernel_roundtrip_oif_only) _nltst_add_link(sk, IFNAME_DUMMY, "dummy", &ifindex_dummy); /* Bring interface up via libnl */ - _nh_link_up(sk, IFNAME_DUMMY); + _nltst_link_up(sk, IFNAME_DUMMY); /* Build nexthop: OIF only, unspecified family */ nh = rtnl_nh_alloc(); @@ -256,7 +295,7 @@ START_TEST(test_kernel_roundtrip_group_mpath) _nltst_add_link(sk, IFNAME_DUMMY, "dummy", &ifindex_dummy); /* Bring interface up via libnl */ - _nh_link_up(sk, IFNAME_DUMMY); + _nltst_link_up(sk, IFNAME_DUMMY); /* Two basic nexthops to reference in the group */ nh1 = rtnl_nh_alloc(); @@ -331,7 +370,7 @@ START_TEST(test_kernel_roundtrip_group_resilient) auto_del_dummy = IFNAME_DUMMY; _nltst_add_link(sk, IFNAME_DUMMY, "dummy", &ifindex_dummy); /* Bring interface up via libnl */ - _nh_link_up(sk, IFNAME_DUMMY); + _nltst_link_up(sk, IFNAME_DUMMY); /* Two basic nexthops to reference in the group */ nh1 = rtnl_nh_alloc(); @@ -546,6 +585,56 @@ START_TEST(test_api_set_get_all) } END_TEST +/* Userspace tests for MPLS encap on rtnl_nh */ + +START_TEST(test_api_encap_mpls_set_get) +{ + _nl_auto_rtnl_nh struct rtnl_nh *nh = NULL; + struct rtnl_nh_encap *encap = NULL; + struct rtnl_nh_encap *got = NULL; + _nl_auto_nl_addr struct nl_addr *labels = NULL; + + /* 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, encap), -NLE_INVAL); + + encap = rtnl_nh_encap_alloc(); + ck_assert_ptr_nonnull(encap); + + /* "empty" encap (no type set) cannot be assigned. */ + ck_assert_int_eq(rtnl_nh_set_encap(nh, encap), -NLE_INVAL); + ck_assert_ptr_eq(rtnl_nh_get_encap_mpls_dst(rtnl_nh_get_encap(nh)), + NULL); + ck_assert_uint_eq(rtnl_nh_get_encap_mpls_ttl(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_int_eq(nl_addr_parse("100", AF_MPLS, &labels), 0); + ck_assert_int_eq(rtnl_nh_encap_mpls(encap, labels, 64), 0); + + /* Attach and retrieve */ + ck_assert_int_eq(rtnl_nh_set_encap(nh, encap), 0); + got = rtnl_nh_get_encap(nh); + ck_assert_ptr_nonnull(got); + + /* Access MPLS-specific getters */ + ck_assert_ptr_nonnull(rtnl_nh_get_encap_mpls_dst(got)); + ck_assert_uint_eq(rtnl_nh_get_encap_mpls_ttl(got), 64); + + /* Clear encap */ + ck_assert_int_eq(rtnl_nh_set_encap(nh, NULL), 0); + ck_assert_ptr_eq(rtnl_nh_get_encap(nh), NULL); +} +END_TEST + Suite *make_nl_route_nh_suite(void) { Suite *suite = suite_create("route-nh"); @@ -554,6 +643,8 @@ Suite *make_nl_route_nh_suite(void) /* Comprehensive API setter/getter test (userspace only) */ tcase_add_test(tc_api, test_api_set_get_all); + /* Userspace encap tests */ + tcase_add_test(tc_api, test_api_encap_mpls_set_get); suite_add_tcase(suite, tc_api); /* Kernel round-trip – needs private netns */ @@ -566,6 +657,8 @@ Suite *make_nl_route_nh_suite(void) tcase_add_test(tc_kernel, test_kernel_roundtrip_oif_only); tcase_add_test(tc_kernel, test_kernel_roundtrip_group_mpath); 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); /* 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); diff --git a/tests/nl-test-util.c b/tests/nl-test-util.c index 53f2a5a06..74d5ad81c 100644 --- a/tests/nl-test-util.c +++ b/tests/nl-test-util.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include #include "lib/route/nl-route.h" @@ -570,6 +572,34 @@ 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) +{ + _nl_auto_rtnl_addr struct rtnl_addr *addr = NULL; + _nl_auto_nl_addr struct nl_addr *local4 = NULL; + + addr = rtnl_addr_alloc(); + ck_assert_ptr_nonnull(addr); + rtnl_addr_set_ifindex(addr, ifindex); + rtnl_addr_set_family(addr, AF_INET); + ck_assert_int_eq(nl_addr_parse(ip, AF_INET, &local4), 0); + ck_assert_int_eq(rtnl_addr_set_local(addr, local4), 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) { diff --git a/tests/nl-test-util.h b/tests/nl-test-util.h index 15ad35ed0..8b3b94cd8 100644 --- a/tests/nl-test-util.h +++ b/tests/nl-test-util.h @@ -542,6 +542,10 @@ 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_addr4_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); diff --git a/tools/clang-format.sh b/tools/clang-format.sh index 09fc40e9b..9165a5beb 100755 --- a/tools/clang-format.sh +++ b/tools/clang-format.sh @@ -103,7 +103,6 @@ EXCLUDE_PATHS_TOPLEVEL+=( "include/netlink/route/neighbour.h" "include/netlink/route/neightbl.h" "include/netlink/route/netconf.h" - "include/netlink/route/nexthop.h" "include/netlink/route/pktloc.h" "include/netlink/route/qdisc.h" "include/netlink/route/qdisc/cbq.h" @@ -239,10 +238,6 @@ EXCLUDE_PATHS_TOPLEVEL+=( "lib/route/mdb.c" "lib/route/neigh.c" "lib/route/netconf.c" - "lib/route/nexthop-encap.h" - "lib/route/nexthop.c" - "lib/route/nexthop_encap.c" - "lib/route/nh_encap_mpls.c" "lib/route/pktloc.c" "lib/route/qdisc.c" "lib/route/qdisc/blackhole.c"