From f7e139caf89c8deb719ee223c26894dcbf26d212 Mon Sep 17 00:00:00 2001 From: Jeremie Leska Date: Fri, 10 Apr 2026 10:08:55 +0200 Subject: [PATCH] plugins types BUGFIX print same IPv6 format as stored The stored IPv6 address is sometimes not the same as the printed one. For instance, if a compressed IPv6 address ::2222:1111 is stored, then a dual stack IPv6 address ::34.34.17.17 will be printed. This is due to the inet_ntop function implementation. The aim of this patch is to store the IPv6 format and print the same format. Store the IPv6 format with the value. Introduce ipv6address_ip2str that converts back the IPv4 part if necessary. Signed-off-by: Jeremie Leska --- CMakeLists.txt | 1 + src/plugins_types/ipv6_address.c | 22 ++++---- src/plugins_types/ipv6_address_no_zone.c | 24 +++++---- src/plugins_types/ipv6_address_prefix.c | 23 +++++---- src/plugins_types/ipv6_common.c | 64 ++++++++++++++++++++++++ src/plugins_types/ipv6_common.h | 22 ++++++++ src/tree_data.h | 7 ++- tests/utests/types/inet_types.c | 6 +++ 8 files changed, 136 insertions(+), 33 deletions(-) create mode 100644 src/plugins_types/ipv6_common.c create mode 100644 src/plugins_types/ipv6_common.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c24a33551..de08d242f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,7 @@ set(type_plugins src/plugins_types/union.c src/plugins_types/ipv4_address.c src/plugins_types/ipv4_address_no_zone.c + src/plugins_types/ipv6_common.c src/plugins_types/ipv6_address.c src/plugins_types/ipv6_address_no_zone.c src/plugins_types/ipv4_address_prefix.c diff --git a/src/plugins_types/ipv6_address.c b/src/plugins_types/ipv6_address.c index 1822a3933..ec85ccc1b 100644 --- a/src/plugins_types/ipv6_address.c +++ b/src/plugins_types/ipv6_address.c @@ -14,6 +14,7 @@ #define _GNU_SOURCE /* strndup */ +#include "ipv6_common.h" #include "plugins_internal.h" #include "plugins_types.h" @@ -60,12 +61,13 @@ static void lyplg_type_free_ipv6_address(const struct ly_ctx *ctx, struct lyd_va * @param[in] ctx libyang context with dictionary. * @param[in,out] addr Allocated value for the address. * @param[out] zone Ipv6 address zone in dictionary. + * @param[out] dual stack format. * @param[out] err Error information on error. * @return LY_ERR value. */ static LY_ERR ipv6address_str2ip(const char *value, uint32_t value_len, uint32_t options, const struct ly_ctx *ctx, - struct in6_addr *addr, const char **zone, struct ly_err_item **err) + struct in6_addr *addr, const char **zone, uint8_t *dual_stack_format, struct ly_err_item **err) { LY_ERR ret = LY_SUCCESS; const char *addr_no_zone; @@ -107,6 +109,9 @@ ipv6address_str2ip(const char *value, uint32_t value_len, uint32_t options, cons goto cleanup; } + /* store dual stack IPv6 format or compressed format */ + *dual_stack_format = ly_strnchr(value, '.', value_len) != NULL; + /* restore the value */ if ((options & LYPLG_TYPE_STORE_DYNAMIC) && zone_ptr) { *zone_ptr = '%'; @@ -174,7 +179,8 @@ lyplg_type_store_ipv6_address(const struct ly_ctx *ctx, const struct lysc_type * LY_CHECK_GOTO(ret, cleanup); /* get the network-byte order address */ - ret = ipv6address_str2ip(value, value_size, options, ctx, &val->addr, &val->zone, err); + ret = ipv6address_str2ip(value, value_size, options, ctx, &val->addr, &val->zone, + &val->dual_stack_format, err); LY_CHECK_GOTO(ret, cleanup); if (format == LY_VALUE_CANON) { @@ -258,6 +264,7 @@ lyplg_type_print_ipv6_address(const struct ly_ctx *ctx, const struct lyd_value * struct lyd_value_ipv6_address *val; uint32_t zone_len; char *ret; + LY_ERR rc; LYD_VALUE_GET(value, val); @@ -290,15 +297,10 @@ lyplg_type_print_ipv6_address(const struct ly_ctx *ctx, const struct lyd_value * if (!value->_canonical) { /* '%' + zone */ zone_len = val->zone ? strlen(val->zone) + 1 : 0; - ret = malloc(INET6_ADDRSTRLEN + zone_len); - LY_CHECK_RET(!ret, NULL); - /* get the address in string */ - if (!inet_ntop(AF_INET6, &val->addr, ret, INET6_ADDRSTRLEN)) { - free(ret); - LOGERR(ctx, LY_ESYS, "Failed to get IPv6 address in string (%s).", strerror(errno)); - return NULL; - } + rc = ipv6address_ip2str(&val->addr, val->dual_stack_format, + &ret, INET6_ADDRSTRLEN + zone_len); + LY_CHECK_RET(!ret || rc != LY_SUCCESS, NULL); /* add zone */ if (zone_len) { diff --git a/src/plugins_types/ipv6_address_no_zone.c b/src/plugins_types/ipv6_address_no_zone.c index d375ea59d..25ad5d105 100644 --- a/src/plugins_types/ipv6_address_no_zone.c +++ b/src/plugins_types/ipv6_address_no_zone.c @@ -14,6 +14,7 @@ #define _GNU_SOURCE /* strndup */ +#include "ipv6_common.h" #include "plugins_internal.h" #include "plugins_types.h" @@ -56,11 +57,13 @@ static void lyplg_type_free_ipv6_address_no_zone(const struct ly_ctx *ctx, struc * @param[in] value_len Length of @p value. * @param[in] options Type store callback options. * @param[in,out] addr Allocated value for the address. + * @param[out] dual stack format. * @param[out] err Error information on error. * @return LY_ERR value. */ static LY_ERR -ipv6addressnozone_str2ip(const char *value, uint32_t value_len, uint32_t options, struct in6_addr *addr, struct ly_err_item **err) +ipv6addressnozone_str2ip(const char *value, uint32_t value_len, uint32_t options, + struct in6_addr *addr, uint8_t *dual_stack_format, struct ly_err_item **err) { LY_ERR ret = LY_SUCCESS; const char *addr_str; @@ -81,6 +84,9 @@ ipv6addressnozone_str2ip(const char *value, uint32_t value_len, uint32_t options goto cleanup; } + /* store dual stack IPv6 format or compressed format */ + *dual_stack_format = ly_strnchr(value, '.', value_len) != NULL; + cleanup: free(addr_dyn); return ret; @@ -143,7 +149,8 @@ lyplg_type_store_ipv6_address_no_zone(const struct ly_ctx *ctx, const struct lys LY_CHECK_GOTO(ret, cleanup); /* get the network-byte order address, validates the value */ - ret = ipv6addressnozone_str2ip(value, value_size, options, &val->addr, err); + ret = ipv6addressnozone_str2ip(value, value_size, options, &val->addr, + &val->dual_stack_format, err); LY_CHECK_GOTO(ret, cleanup); if (format == LY_VALUE_CANON) { @@ -211,6 +218,7 @@ lyplg_type_print_ipv6_address_no_zone(const struct ly_ctx *ctx, const struct lyd { struct lyd_value_ipv6_address_no_zone *val; char *ret; + LY_ERR rc; LYD_VALUE_GET(value, val); @@ -225,15 +233,9 @@ lyplg_type_print_ipv6_address_no_zone(const struct ly_ctx *ctx, const struct lyd /* generate canonical value if not already */ if (!value->_canonical) { /* '%' + zone */ - ret = malloc(INET6_ADDRSTRLEN); - LY_CHECK_RET(!ret, NULL); - - /* get the address in string */ - if (!inet_ntop(AF_INET6, &val->addr, ret, INET6_ADDRSTRLEN)) { - free(ret); - LOGERR(ctx, LY_ESYS, "Failed to get IPv6 address in string (%s).", strerror(errno)); - return NULL; - } + rc = ipv6address_ip2str(&val->addr, val->dual_stack_format, + &ret, INET6_ADDRSTRLEN); + LY_CHECK_RET(!ret || rc != LY_SUCCESS, NULL); /* store it */ if (lydict_insert_zc(ctx, ret, (const char **)&value->_canonical)) { diff --git a/src/plugins_types/ipv6_address_prefix.c b/src/plugins_types/ipv6_address_prefix.c index 201a53469..0281684a4 100644 --- a/src/plugins_types/ipv6_address_prefix.c +++ b/src/plugins_types/ipv6_address_prefix.c @@ -14,6 +14,7 @@ #define _GNU_SOURCE /* strndup */ +#include "ipv6_common.h" #include "plugins_internal.h" #include "plugins_types.h" @@ -58,11 +59,13 @@ static void lyplg_type_free_ipv6_address_prefix(const struct ly_ctx *ctx, struct * @param[in] value_len Length of @p value. * @param[in,out] addr Allocated address value to fill. * @param[out] prefix Prefix length. + * @param[out] dual stack format. * @param[out] err Error information on error. * @return LY_ERR value. */ static LY_ERR -ipv6prefix_str2ip(const char *value, uint32_t value_len, struct in6_addr *addr, uint8_t *prefix, struct ly_err_item **err) +ipv6prefix_str2ip(const char *value, uint32_t value_len, struct in6_addr *addr, uint8_t *prefix, + uint8_t *dual_stack_format, struct ly_err_item **err) { LY_ERR ret = LY_SUCCESS; const char *pref_str; @@ -82,6 +85,9 @@ ipv6prefix_str2ip(const char *value, uint32_t value_len, struct in6_addr *addr, goto cleanup; } + /* store dual stack IPv6 format or compressed format */ + *dual_stack_format = ly_strnchr(value, '.', value_len) != NULL; + cleanup: free(mask_str); return ret; @@ -194,7 +200,8 @@ lyplg_type_store_ipv6_address_prefix(const struct ly_ctx *ctx, const struct lysc } /* get the mask in network-byte order */ - ret = ipv6prefix_str2ip(value, value_size, &val->addr, &val->prefix, err); + ret = ipv6prefix_str2ip(value, value_size, &val->addr, &val->prefix, + &val->dual_stack_format, err); LY_CHECK_GOTO(ret, cleanup); if (!strcmp(type->name, "ipv6-prefix")) { @@ -267,6 +274,7 @@ lyplg_type_print_ipv6_address_prefix(const struct ly_ctx *ctx, const struct lyd_ { struct lyd_value_ipv6_prefix *val; char *ret; + LY_ERR rc; LYD_VALUE_GET(value, val); @@ -281,14 +289,9 @@ lyplg_type_print_ipv6_address_prefix(const struct ly_ctx *ctx, const struct lyd_ /* generate canonical value if not already */ if (!value->_canonical) { /* IPv6 mask + '/' + prefix */ - ret = malloc(INET6_ADDRSTRLEN + 4); - LY_CHECK_RET(!ret, NULL); - - /* convert back to string */ - if (!inet_ntop(AF_INET6, &val->addr, ret, INET6_ADDRSTRLEN)) { - free(ret); - return NULL; - } + rc = ipv6address_ip2str(&val->addr, val->dual_stack_format, + &ret, INET6_ADDRSTRLEN + 4); + LY_CHECK_RET(!ret || rc != LY_SUCCESS, NULL); /* add the prefix */ sprintf(ret + strlen(ret), "/%" PRIu8, val->prefix); diff --git a/src/plugins_types/ipv6_common.c b/src/plugins_types/ipv6_common.c new file mode 100644 index 000000000..4dcbfca2a --- /dev/null +++ b/src/plugins_types/ipv6_common.c @@ -0,0 +1,64 @@ +/* + * Copyright 2026 6WIND S.A. + */ + +#include +#include +#include +#include + +#include "compat.h" +#include "ipv6_common.h" +#include "ly_common.h" + +static int +ipv6_dual_to_compressed(char *ipv6_str) +{ + char *ipv4_str = NULL; + uint16_t parts[2]; + uint8_t bytes[4]; + + ipv4_str = strrchr(ipv6_str, ':'); + if (ipv4_str == NULL) { + return -1; + } + + /* Parse the IPv4 part */ + if (sscanf(ipv4_str + 1, "%hhu.%hhu.%hhu.%hhu", + &bytes[0], &bytes[1], &bytes[2], &bytes[3]) != 4) { + return -1; + } + + parts[0] = (bytes[0] << 8) | bytes[1]; + parts[1] = (bytes[2] << 8) | bytes[3]; + + sprintf(ipv4_str + 1, "%x:%x", parts[0], parts[1]); + + return 0; +} + +LY_ERR +ipv6address_ip2str(struct in6_addr *addr, uint8_t dual_stack_format, + char **str_addr, int str_addr_len) +{ + *str_addr = malloc(str_addr_len); + if (!*str_addr) { + return LY_EMEM; + } + + /* convert back to string */ + if (!inet_ntop(AF_INET6, addr, *str_addr, INET6_ADDRSTRLEN)) { + free(*str_addr); + return LY_EINVAL; + } + + /* if compress format is expected convert from dual to compress format */ + if (!dual_stack_format && strchr(*str_addr, '.')) { + if (ipv6_dual_to_compressed(*str_addr)) { + free(*str_addr); + return LY_EINVAL; + } + } + + return LY_SUCCESS; +} diff --git a/src/plugins_types/ipv6_common.h b/src/plugins_types/ipv6_common.h new file mode 100644 index 000000000..9dfac436e --- /dev/null +++ b/src/plugins_types/ipv6_common.h @@ -0,0 +1,22 @@ +/* + * Copyright 2026 6WIND S.A. + */ + +#ifdef _WIN32 +# include +# include +#else +# include +# if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) +# include +# include +# endif +#endif + +#include "libyang.h" + +/** + * @brief convert dual format ipv6 address inside to compressed ipv6 address + */ +LY_ERR ipv6address_ip2str(struct in6_addr *addr, uint8_t dual_stack_format, + char **str_addr, int str_addr_len); diff --git a/src/tree_data.h b/src/tree_data.h index 6dfa0c330..5e91a54b4 100644 --- a/src/tree_data.h +++ b/src/tree_data.h @@ -635,14 +635,16 @@ struct lyd_value_ipv4_prefix { */ struct lyd_value_ipv6_address_no_zone { struct in6_addr addr; /**< IPv6 address in binary */ + uint8_t dual_stack_format; /**< Dual stack IPv6 format or compressed format */ }; /** * @brief Special lyd_value structure for ietf-inet-types ipv6-address values. */ struct lyd_value_ipv6_address { - struct in6_addr addr; /**< IPv6 address in binary */ - const char *zone; /**< Optional address zone */ + struct in6_addr addr; /**< IPv6 address in binary */ + const char *zone; /**< Optional address zone */ + uint8_t dual_stack_format; /**< Dual stack IPv6 format or compressed format */ }; /** @@ -651,6 +653,7 @@ struct lyd_value_ipv6_address { struct lyd_value_ipv6_prefix { struct in6_addr addr; /**< IPv6 host address in binary */ uint8_t prefix; /**< prefix length (0 - 128) */ + uint8_t dual_stack_format; /**< Dual stack IPv6 format or compressed format */ }; /** diff --git a/tests/utests/types/inet_types.c b/tests/utests/types/inet_types.c index 54544565f..d8ac1e56e 100644 --- a/tests/utests/types/inet_types.c +++ b/tests/utests/types/inet_types.c @@ -115,6 +115,8 @@ test_data_xml(void **state) /* ipv6-address */ TEST_SUCCESS_XML("a", "l2", "FAAC:21:011:Da85::87:daaF%1", STRING, "faac:21:11:da85::87:daaf%1"); + TEST_SUCCESS_XML("a", "l2", "::2121:3737%2", STRING, "::2121:3737%2"); + TEST_SUCCESS_XML("a", "l2", "::21.21.37.37%3", STRING, "::21.21.37.37%3"); /* ip-address-no-zone */ TEST_SUCCESS_XML("a", "l3", "127.0.0.1", UNION, "127.0.0.1", STRING, "127.0.0.1"); @@ -122,6 +124,8 @@ test_data_xml(void **state) /* ipv6-address-no-zone */ TEST_SUCCESS_XML("a", "l4", "A:B:c:D:e:f:1:0", STRING, "a:b:c:d:e:f:1:0"); + TEST_SUCCESS_XML("a", "l4", "::2121:3737", STRING, "::2121:3737"); + TEST_SUCCESS_XML("a", "l4", "::21.21.37.37", STRING, "::21.21.37.37"); /* ip-prefix */ TEST_SUCCESS_XML("a", "l5", "158.1.58.4/1", UNION, "128.0.0.0/1", STRING, "128.0.0.0/1"); @@ -137,6 +141,8 @@ test_data_xml(void **state) TEST_SUCCESS_XML("a", "l7", "::C:D:E:f:a/110", STRING, "::c:d:e:c:0/110"); TEST_SUCCESS_XML("a", "l7", "::C:D:E:f:a/96", STRING, "::c:d:e:0:0/96"); TEST_SUCCESS_XML("a", "l7", "::C:D:E:f:a/55", STRING, "::/55"); + TEST_SUCCESS_XML("a", "l7", "::2121:3737/128", STRING, "::2121:3737/128"); + TEST_SUCCESS_XML("a", "l7", "::21.21.37.37/127", STRING, "::21.21.37.36/127"); } static void