From a32bebd80639b0cd080a953221ee863c9e79e526 Mon Sep 17 00:00:00 2001 From: Oliver Smith Date: Fri, 23 Feb 2024 14:01:06 +0100 Subject: [PATCH] Import in46_addr from OsmoGGSN Import ip_addr and tests from I54c8a4b1d3a02b71d5983badcd923fa39ce7dd84. Apply minor fixes so it passes the linter. I'm in the process of extending the public API of OsmoGGSN's libgtp, so a GSN can be created with either IPv4 or IPv6. The in46_addr code from osmo-ggsn.git provides a good abstraction for that. I've considered making it public in libgtp, but given that it is generic code that could be used outside of libgtp in other Osmocom projects, put it in libosmocore instead. Related: OS#1953, OS#6096 Related: osmo-ggsn I8f4d9d78689909a149683583cfe36bcdc3f7cbc7 Change-Id: I31078f130ec42aeaa5ead1dde82fdd1eb44d992b --- include/osmocom/core/Makefile.am | 1 + include/osmocom/core/in46_addr.h | 43 +++ src/core/Makefile.am | 1 + src/core/in46_addr.c | 377 ++++++++++++++++++++++++ src/core/libosmocore.map | 11 + tests/Makefile.am | 3 + tests/in46_addr/in46a_test.c | 473 +++++++++++++++++++++++++++++++ tests/in46_addr/in46a_test.ok | 17 ++ tests/in46_addr/in46a_v6_test.ok | 10 + tests/testsuite.at | 12 + 10 files changed, 948 insertions(+) create mode 100644 include/osmocom/core/in46_addr.h create mode 100644 src/core/in46_addr.c create mode 100644 tests/in46_addr/in46a_test.c create mode 100644 tests/in46_addr/in46a_test.ok create mode 100644 tests/in46_addr/in46a_v6_test.ok diff --git a/include/osmocom/core/Makefile.am b/include/osmocom/core/Makefile.am index 7c29ca105..b60104f1e 100644 --- a/include/osmocom/core/Makefile.am +++ b/include/osmocom/core/Makefile.am @@ -25,6 +25,7 @@ osmocore_HEADERS = \ gsmtap_util.h \ hash.h \ hashtable.h \ + in46_addr.h \ isdnhdlc.h \ it_q.h \ linuxlist.h \ diff --git a/include/osmocom/core/in46_addr.h b/include/osmocom/core/in46_addr.h new file mode 100644 index 000000000..c59f3e31b --- /dev/null +++ b/include/osmocom/core/in46_addr.h @@ -0,0 +1,43 @@ +#pragma once +#include +#include + +#include + +/* a simple wrapper around an in6_addr to also contain the length of the address, + * thereby implicitly indicating the address family of the address */ +struct in46_addr { + uint8_t len; + union { + struct in_addr v4; + struct in6_addr v6; + }; +}; + +struct in46_prefix { + struct in46_addr addr; + uint8_t prefixlen; +}; + +extern int in46a_to_af(const struct in46_addr *in); +extern int in46a_to_sas(struct sockaddr_storage *out, const struct in46_addr *in); +extern const char *in46a_ntop(const struct in46_addr *in, char *dst, socklen_t dst_size); +extern const char *in46a_ntoa(const struct in46_addr *in46); +extern const char *in46p_ntoa(const struct in46_prefix *in46p); +extern int in46a_equal(const struct in46_addr *a, const struct in46_addr *b); +extern int in46a_prefix_equal(const struct in46_addr *a, const struct in46_addr *b); +extern int in46a_within_mask(const struct in46_addr *addr, const struct in46_addr *net, size_t prefixlen); +unsigned int in46a_netmasklen(const struct in46_addr *netmask); + +int in46a_to_eua(const struct in46_addr *src, unsigned int size, struct ul66_t *eua); +int in46a_from_eua(const struct ul66_t *eua, struct in46_addr *dst); + +static inline bool in46a_is_v6(const struct in46_addr *addr) +{ + return addr->len == 8 || addr->len == 16; +} + +static inline bool in46a_is_v4(const struct in46_addr *addr) +{ + return addr->len == sizeof(struct in_addr); +} diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 2efebd8d4..c018683f0 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -41,6 +41,7 @@ libosmocore_la_SOURCES = \ exec.c \ fsm.c \ gsmtap_util.c \ + in46_addr.c \ isdnhdlc.c \ it_q.c \ logging.c \ diff --git a/src/core/in46_addr.c b/src/core/in46_addr.c new file mode 100644 index 000000000..be5306742 --- /dev/null +++ b/src/core/in46_addr.c @@ -0,0 +1,377 @@ +/*! \file in46_addr.c + * IPv4/v6 address functions. + * + * Copyright (C) 2017 by Harald Welte + * + * SPDX-License-Identifier: GPL-2.0+ + * + * The contents of this file may be used under the terms of the GNU + * General Public License Version 2, provided that the above copyright + * notice and this permission notice is included in all copies or + * substantial portions of the software. + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/*! Return the address family of given \reff in46_addr argument */ +int in46a_to_af(const struct in46_addr *in) +{ + switch (in->len) { + case 4: + return AF_INET; + case 8: + case 16: + return AF_INET6; + default: + OSMO_ASSERT(0); + return -1; + } +} + +/*! Convert \ref in46_addr to sockaddr_storage */ +int in46a_to_sas(struct sockaddr_storage *out, const struct in46_addr *in) +{ + struct sockaddr_in *sin = (struct sockaddr_in *)out; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)out; + + switch (in->len) { + case 4: + sin->sin_family = AF_INET; + sin->sin_addr = in->v4; + break; + case 16: + sin6->sin6_family = AF_INET6; + sin6->sin6_addr = in->v6; + break; + default: + OSMO_ASSERT(0); + return -1; + } + + return 0; +} + +/*! Convenience wrapper around inet_ntop() for in46_addr. + * \param[in] in the in46_addr to print + * \param[out] dst destination buffer where string representation of the address is stored + * \param[out] dst_size size dst. Usually it should be at least INET6_ADDRSTRLEN. + * \return address of dst on success, NULL on error */ +const char *in46a_ntop(const struct in46_addr *in, char *dst, socklen_t dst_size) +{ + int af; + + if (!in || in->len == 0) { + osmo_strlcpy(dst, "UNDEFINED", dst_size); + return dst; + } + + af = in46a_to_af(in); + if (af < 0) + return NULL; + + return inet_ntop(af, (const void *) &in->v4, dst, dst_size); +} + +/* like inet_ntoa() */ +const char *in46a_ntoa(const struct in46_addr *in46) +{ + static char addrstr_buf[256]; + if (in46a_ntop(in46, addrstr_buf, sizeof(addrstr_buf)) < 0) + return "INVALID"; + else + return addrstr_buf; +} + +const char *in46p_ntoa(const struct in46_prefix *in46p) +{ + static char addrstr_buf[256]; + snprintf(addrstr_buf, sizeof(addrstr_buf), "%s/%u", in46a_ntoa(&in46p->addr), in46p->prefixlen); + return addrstr_buf; +} + +/*! Determine if two in46_addr are equal or not + * \returns 1 in case they are equal; 0 otherwise */ +int in46a_equal(const struct in46_addr *a, const struct in46_addr *b) +{ + if (a->len == b->len && !memcmp(&a->v6, &b->v6, a->len)) + return 1; + else + return 0; +} + +/*! Determine if two in46_addr prefix are equal or not + * The prefix length is determined by the shortest of the prefixes of a and b + * \returns 1 in case the common prefix are equal; 0 otherwise */ +int in46a_prefix_equal(const struct in46_addr *a, const struct in46_addr *b) +{ + unsigned int len; + if (a->len > b->len) + len = b->len; + else + len = a->len; + + if (!memcmp(&a->v6, &b->v6, len)) + return 1; + else + return 0; +} + +/*! Match if IPv6 addr1 + addr2 are within same \a mask */ +static int ipv6_within_mask(const struct in6_addr *addr1, const struct in6_addr *addr2, + const struct in6_addr *mask) +{ + struct in6_addr masked = *addr2; +#if defined(__linux__) + masked.s6_addr32[0] &= mask->s6_addr32[0]; + masked.s6_addr32[1] &= mask->s6_addr32[1]; + masked.s6_addr32[2] &= mask->s6_addr32[2]; + masked.s6_addr32[3] &= mask->s6_addr32[3]; +#else + masked.__u6_addr.__u6_addr32[0] &= mask->__u6_addr.__u6_addr32[0]; + masked.__u6_addr.__u6_addr32[1] &= mask->__u6_addr.__u6_addr32[1]; + masked.__u6_addr.__u6_addr32[2] &= mask->__u6_addr.__u6_addr32[2]; + masked.__u6_addr.__u6_addr32[3] &= mask->__u6_addr.__u6_addr32[3]; +#endif + if (!memcmp(addr1, &masked, sizeof(struct in6_addr))) + return 1; + else + return 0; +} + +/*! Create an IPv6 netmask from the given prefix length */ +static void create_ipv6_netmask(struct in6_addr *netmask, int prefixlen) +{ + uint32_t *p_netmask; + memset(netmask, 0, sizeof(struct in6_addr)); + if (prefixlen < 0) + prefixlen = 0; + else if (128 < prefixlen) + prefixlen = 128; + +#if defined(__linux__) + p_netmask = &netmask->s6_addr32[0]; +#else + p_netmask = &netmask->__u6_addr.__u6_addr32[0]; +#endif + while (32 < prefixlen) { + *p_netmask = 0xffffffff; + p_netmask++; + prefixlen -= 32; + } + if (prefixlen != 0) + *p_netmask = htonl(0xFFFFFFFF << (32 - prefixlen)); +} + +/*! Determine if given \a addr is within given \a net + \a prefixlen + * Builds the netmask from \a net + \a prefixlen and matches it to \a addr + * \returns 1 in case of a match, 0 otherwise */ +int in46a_within_mask(const struct in46_addr *addr, const struct in46_addr *net, size_t prefixlen) +{ + struct in_addr netmask; + struct in6_addr netmask6; + + if (addr->len != net->len) + return 0; + + switch (addr->len) { + case 4: + netmask.s_addr = htonl(0xFFFFFFFF << (32 - prefixlen)); + if ((addr->v4.s_addr & netmask.s_addr) == net->v4.s_addr) + return 1; + else + return 0; + case 16: + create_ipv6_netmask(&netmask6, prefixlen); + return ipv6_within_mask(&addr->v6, &net->v6, &netmask6); + default: + OSMO_ASSERT(0); + return 0; + } +} + +static unsigned int ipv4_netmasklen(const struct in_addr *netmask) +{ + uint32_t bits = netmask->s_addr; + uint8_t *b = (uint8_t *) &bits; + unsigned int i, prefix = 0; + + for (i = 0; i < 4; i++) { + while (b[i] & 0x80) { + prefix++; + b[i] = b[i] << 1; + } + } + return prefix; +} + +static unsigned int ipv6_netmasklen(const struct in6_addr *netmask) +{ + #if defined(__linux__) + #define ADDRFIELD(i) s6_addr32[i] + #else + #define ADDRFIELD(i) __u6_addr.__u6_addr32[i] + #endif + + unsigned int i, j, prefix = 0; + + for (j = 0; j < 4; j++) { + uint32_t bits = netmask->ADDRFIELD(j); + uint8_t *b = (uint8_t *) &bits; + for (i = 0; i < 4; i++) { + while (b[i] & 0x80) { + prefix++; + b[i] = b[i] << 1; + } + } + } + + #undef ADDRFIELD + + return prefix; +} + +/*! Convert netmask to prefix length representation + * \param[in] netmask in46_addr containing a netmask (consecutive list of 1-bit followed by consecutive list of 0-bit) + * \returns prefix length representation of the netmask (count of 1-bit from the start of the netmask) + */ +unsigned int in46a_netmasklen(const struct in46_addr *netmask) +{ + switch (netmask->len) { + case 4: + return ipv4_netmasklen(&netmask->v4); + case 16: + return ipv6_netmasklen(&netmask->v6); + default: + OSMO_ASSERT(0); + return 0; + } +} + +/*! Convert given array of in46_addr to PDP End User Address + * \param[in] src Array containing 1 or 2 in46_addr + * \param[out] eua End User Address structure to fill + * \returns 0 on success; negative on error + * + * In case size is 2, this function expects to find exactly one IPv4 and one + * IPv6 addresses in src. */ +int in46a_to_eua(const struct in46_addr *src, unsigned int size, struct ul66_t *eua) +{ + const struct in46_addr *src_v4, *src_v6; + if (size == 1) { + switch (src->len) { + case 4: + eua->l = 6; + eua->v[0] = PDP_EUA_ORG_IETF; + eua->v[1] = PDP_EUA_TYPE_v4; + memcpy(&eua->v[2], &src->v4, 4); /* Copy a 4 byte address */ + break; + case 8: + case 16: + eua->l = 18; + eua->v[0] = PDP_EUA_ORG_IETF; + eua->v[1] = PDP_EUA_TYPE_v6; + memcpy(&eua->v[2], &src->v6, 16); /* Copy a 16 byte address */ + break; + default: + OSMO_ASSERT(0); + return -1; + } + return 0; + } + + if (src[0].len == src[1].len) + return -1; /* we should have a v4 and a v6 address */ + + src_v4 = (src[0].len == 4) ? &src[0] : &src[1]; + src_v6 = (src[0].len == 4) ? &src[1] : &src[0]; + + eua->l = 22; + eua->v[0] = PDP_EUA_ORG_IETF; + eua->v[1] = PDP_EUA_TYPE_v4v6; + memcpy(&eua->v[2], &src_v4->v4, 4); + memcpy(&eua->v[6], &src_v6->v6, 16); + + return 0; +} + +/*! Convert given PDP End User Address to an array of in46_addr + * \param[in] eua End User Address structure to parse + * \param[out] dst Array containing 2 in46_addr + * \returns number of parsed addresses (1 or 2) on success; negative on error + * + * This function expects to receive an End User Address struct together with an + * array of 2 zeroed in46_addr structs. The in46_addr structs are filled in + * order, hence if the function returns 1 the parsed address will be stored in + * the first struct and the second one will be left intact. If 2 is returned, it + * is guaranteed that one of them is an IPv4 and the other one is an IPv6, but + * the order in which they are presented is not specified and must be + * discovered for instance by checking the len field of each address. + */ +int in46a_from_eua(const struct ul66_t *eua, struct in46_addr *dst) +{ + if (eua->l < 2) + goto default_to_dyn_v4; + + if (eua->v[0] != 0xf1) + return -1; + + switch (eua->v[1]) { + case PDP_EUA_TYPE_v4: + dst->len = 4; + if (eua->l >= 6) + memcpy(&dst->v4, &eua->v[2], 4); /* Copy a 4 byte address */ + else + dst->v4.s_addr = 0; + return 1; + case PDP_EUA_TYPE_v6: + dst->len = 16; + if (eua->l >= 18) + memcpy(&dst->v6, &eua->v[2], 16); /* Copy a 16 byte address */ + else + memset(&dst->v6, 0, 16); + return 1; + case PDP_EUA_TYPE_v4v6: + /* 3GPP TS 29.060, section 7.7.27 */ + switch (eua->l) { + case 2: /* v4 & v6 dynamic */ + dst[0].v4.s_addr = 0; + memset(&dst[1].v6, 0, 16); + break; + case 6: /* v4 static, v6 dynamic */ + memcpy(&dst[0].v4, &eua->v[2], 4); + memset(&dst[1].v6, 0, 16); + break; + case 18: /* v4 dynamic, v6 static */ + dst[0].v4.s_addr = 0; + memcpy(&dst[1].v6, &eua->v[2], 16); + break; + case 22: /* v4 & v6 static */ + memcpy(&dst[0].v4, &eua->v[2], 4); + memcpy(&dst[1].v6, &eua->v[6], 16); + break; + default: + return -1; + } + dst[0].len = 4; + dst[1].len = 16; + return 2; + default: + return -1; + } + +default_to_dyn_v4: + /* assume dynamic IPv4 by default */ + dst->len = 4; + dst->v4.s_addr = 0; + return 1; +} diff --git a/src/core/libosmocore.map b/src/core/libosmocore.map index b66e37d65..e58e538e1 100644 --- a/src/core/libosmocore.map +++ b/src/core/libosmocore.map @@ -55,6 +55,17 @@ gsmtap_source_init2; gsmtap_source_init_fd; gsmtap_source_init_fd2; gsmtap_type_names; +in46a_equal; +in46a_from_eua; +in46a_netmasklen; +in46a_ntoa; +in46a_ntop; +in46a_prefix_equal; +in46a_to_af; +in46a_to_eua; +in46a_to_sas; +in46a_within_mask; +in46p_ntoa; log_add_target; log_category_name; log_check_level; diff --git a/tests/Makefile.am b/tests/Makefile.am index 7c109a109..d450ed4e2 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -59,6 +59,7 @@ check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \ osmo_io/osmo_io_test \ soft_uart/soft_uart_test \ rlp/rlp_test \ + in46_addr/in46a_test \ $(NULL) if ENABLE_MSGFILE @@ -505,6 +506,8 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \ soft_uart/soft_uart_test.ok \ rlp/rlp_test.ok \ socket/socket_sctp_test.ok socket/socket_sctp_test.err \ + in46_addr/in46a_test.ok \ + in46_addr/in46a_v6_test.ok \ $(NULL) if ENABLE_LIBSCTP diff --git a/tests/in46_addr/in46a_test.c b/tests/in46_addr/in46a_test.c new file mode 100644 index 000000000..73fdd8793 --- /dev/null +++ b/tests/in46_addr/in46a_test.c @@ -0,0 +1,473 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +static struct log_info log_info = {}; + +static const struct in46_addr g_ia4 = { + .len = 4, + .v4.s_addr = 0x0d0c0b0a, +}; + +static void test_in46a_to_af(void) +{ + printf("Testing in46a_to_af() with IPv4 addresses\n"); + + OSMO_ASSERT(in46a_to_af(&g_ia4) == AF_INET); +} + +static void test_in46a_to_sas(void) +{ + struct sockaddr_storage ss; + struct sockaddr_in *sin = (struct sockaddr_in *) &ss; + + printf("Testing in46a_to_sas() with IPv4 addresses\n"); + + memset(&ss, 0, sizeof(ss)); + OSMO_ASSERT(in46a_to_sas(&ss, &g_ia4) == 0); + OSMO_ASSERT(sin->sin_family == AF_INET); + OSMO_ASSERT(sin->sin_addr.s_addr == g_ia4.v4.s_addr); +} + +static void test_in46a_ntop(void) +{ + struct in46_addr ia; + char buf[256]; + const char *res; + + printf("Testing in46a_ntop() with IPv4 addresses\n"); + + res = in46a_ntop(NULL, buf, sizeof(buf)); + OSMO_ASSERT(res && !strcmp(res, "UNDEFINED")); + printf("res = %s\n", res); + + ia.len = 0; + res = in46a_ntop(&ia, buf, sizeof(buf)); + printf("res = %s\n", res); + OSMO_ASSERT(res && !strcmp(res, "UNDEFINED")); + + ia.len = 4; + ia.v4.s_addr = htonl(0x01020304); + res = in46a_ntop(&ia, buf, sizeof(buf)); + OSMO_ASSERT(res && !strcmp(res, "1.2.3.4")); + printf("res = %s\n", res); +} + +static void test_in46p_ntoa(void) +{ + const struct in46_prefix ip46 = { + .prefixlen = 24, + .addr = { + .len = 4, + .v4.s_addr = htonl(0x10203000), + }, + }; + printf("in46p_ntoa() returns %s\n", in46p_ntoa(&ip46)); +} + +static void test_in46a_equal(void) +{ + struct in46_addr b; + + printf("Testing in46a_equal() with IPv4 addresses\n"); + + memset(&b, 0xff, sizeof(b)); + b.len = g_ia4.len; + b.v4.s_addr = g_ia4.v4.s_addr; + OSMO_ASSERT(in46a_equal(&g_ia4, &b)); +} + +static int log_in46a_within_mask(const struct in46_addr *addr, const struct in46_addr *net, + size_t prefixlen) +{ + int rc; + + printf("in46a_within_mask(%s, ", in46a_ntoa(addr)); + printf("%s, %lu) = ", in46a_ntoa(net), prefixlen); + + rc = in46a_within_mask(addr, net, prefixlen); + printf("%d\n", rc); + + return rc; +} + +static void test_in46a_within_mask(void) +{ + struct in46_addr addr, mask; + + printf("Testing in46a_within_mask() with IPv4 addresses\n"); + + addr = g_ia4; + mask = g_ia4; + OSMO_ASSERT(log_in46a_within_mask(&addr, &mask, 32)); + + mask.v4.s_addr = htonl(ntohl(mask.v4.s_addr) & 0xfffffffC); + OSMO_ASSERT(log_in46a_within_mask(&addr, &mask, 30)); + + mask.v4.s_addr = htonl(ntohl(mask.v4.s_addr) & 0xfff80000); + OSMO_ASSERT(log_in46a_within_mask(&addr, &mask, 13)); + + addr.v4.s_addr = htonl(ntohl(addr.v4.s_addr) + 1); + mask = g_ia4; + OSMO_ASSERT(!log_in46a_within_mask(&addr, &mask, 32)); + mask.v4.s_addr = htonl(ntohl(mask.v4.s_addr) & 0xfffffffC); + OSMO_ASSERT(log_in46a_within_mask(&addr, &mask, 30)); +} + +static void test_in46a_to_eua(void) +{ + struct ul66_t eua; + + printf("testing in46a_to_eua() with IPv4 addresses\n"); + +#if 0 /* triggers assert in current implementation */ + const struct in46_addr ia_invalid = { .len = 3, }; + OSMO_ASSERT(in46a_to_eua(&ia_invalid, &eua) < 0); +#endif + + /* IPv4 address */ + OSMO_ASSERT(in46a_to_eua(&g_ia4, 1, &eua) == 0); + OSMO_ASSERT(eua.v[0] == PDP_EUA_ORG_IETF); + OSMO_ASSERT(eua.v[1] == PDP_EUA_TYPE_v4); + OSMO_ASSERT(osmo_load32le(&eua.v[2]) == g_ia4.v4.s_addr); +} + +static void test_in46a_from_eua(void) +{ + struct in46_addr ia[2]; + struct ul66_t eua; + const uint8_t v4_unspec[] = { PDP_EUA_ORG_IETF, PDP_EUA_TYPE_v4 }; + const uint8_t v4_spec[] = { PDP_EUA_ORG_IETF, PDP_EUA_TYPE_v4, 1, 2, 3, 4 }; + memset(&eua, 0, sizeof(eua)); + + printf("Testing in46a_from_eua() with IPv4 addresses\n"); + + /* default: v4 unspec */ + OSMO_ASSERT(in46a_from_eua(&eua, ia) == 1); + OSMO_ASSERT(ia[0].len == 4); + OSMO_ASSERT(ia[0].v4.s_addr == 0); + + /* invalid */ + eua.v[0] = 0x23; + eua.v[1] = PDP_EUA_TYPE_v4; + eua.l = 6; + OSMO_ASSERT(in46a_from_eua(&eua, ia) < 0); + + /* invalid */ + eua.v[0] = PDP_EUA_ORG_IETF; + eua.v[1] = 0x23; + eua.l = 6; + OSMO_ASSERT(in46a_from_eua(&eua, ia) < 0); + + /* unspecified V4 */ + memcpy(eua.v, v4_unspec, sizeof(v4_unspec)); + eua.l = sizeof(v4_unspec); + OSMO_ASSERT(in46a_from_eua(&eua, ia) == 1); + OSMO_ASSERT(ia[0].len == 4); + OSMO_ASSERT(ia[0].v4.s_addr == 0); + + /* specified V4 */ + memcpy(eua.v, v4_spec, sizeof(v4_spec)); + eua.l = sizeof(v4_spec); + OSMO_ASSERT(in46a_from_eua(&eua, ia) == 1); + OSMO_ASSERT(ia[0].len == 4); + OSMO_ASSERT(ia[0].v4.s_addr == htonl(0x01020304)); +} + +static void test_in46a_netmasklen(void) +{ + struct in46_addr netmask; + unsigned int len; + + printf("Testing in46a_netmasklen() with IPv4 addresses\n"); + netmask.len = 4; + + netmask.v4.s_addr = 0xffffffff; + len = in46a_netmasklen(&netmask); + OSMO_ASSERT(len == 32); + + netmask.v4.s_addr = 0x00ffffff; + len = in46a_netmasklen(&netmask); + OSMO_ASSERT(len == 24); + + netmask.v4.s_addr = 0x00f0ffff; + len = in46a_netmasklen(&netmask); + OSMO_ASSERT(len == 20); + + netmask.v4.s_addr = 0x000000fe; + len = in46a_netmasklen(&netmask); + OSMO_ASSERT(len == 7); + + netmask.v4.s_addr = 0x00000000; + len = in46a_netmasklen(&netmask); + OSMO_ASSERT(len == 0); +} + +/* IPv6 specific tests */ + +static const struct in46_addr g_ia6 = { + .len = 16, + .v6.s6_addr = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, +}; + +static void test_in46a_to_af_v6(void) +{ + struct in46_addr ia; + + printf("Testing in46a_to_af() with IPv6 addresses\n"); + + OSMO_ASSERT(in46a_to_af(&g_ia6) == AF_INET6); + + ia.len = 8; + OSMO_ASSERT(in46a_to_af(&ia) == AF_INET6); +} + +static void test_in46a_to_sas_v6(void) +{ + struct sockaddr_storage ss; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) &ss; + + printf("Testing in46a_to_sas() with IPv6 addresses\n"); + + memset(&ss, 0, sizeof(ss)); + OSMO_ASSERT(in46a_to_sas(&ss, &g_ia6) == 0); + OSMO_ASSERT(sin6->sin6_family == AF_INET6); + OSMO_ASSERT(!memcmp(&sin6->sin6_addr, &g_ia6.v6, sizeof(sin6->sin6_addr))); +} + +static void test_in46a_ntop_v6(void) +{ + char buf[256]; + const char *res; + + printf("Testing in46a_ntop() with IPv6 addresses\n"); + + res = in46a_ntop(&g_ia6, buf, sizeof(buf)); + OSMO_ASSERT(res && !strcmp(res, "102:304:506:708:90a:b0c:d0e:f10")); + printf("res = %s\n", res); +} + +static void test_in46a_equal_v6(void) +{ + struct in46_addr b; + + printf("Testing in46a_equal() with IPv6 addresses\n"); + + memset(&b, 0xff, sizeof(b)); + b.len = g_ia6.len; + b.v6 = g_ia6.v6; + OSMO_ASSERT(in46a_equal(&g_ia6, &b)); +} + +static void test_in46a_to_eua_v6(void) +{ + const struct in46_addr ia_v6_8 = { + .len = 8, + .v6.s6_addr = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, + }; + struct ul66_t eua; + + printf("Testing in46a_to_eua() with IPv6 addresses\n"); + + /* IPv6 address */ + OSMO_ASSERT(in46a_to_eua(&g_ia6, 1, &eua) == 0); + OSMO_ASSERT(eua.v[0] == PDP_EUA_ORG_IETF); + OSMO_ASSERT(eua.v[1] == PDP_EUA_TYPE_v6); + OSMO_ASSERT(!memcmp(&eua.v[2], &g_ia6.v6, 16)); + + /* IPv6 address with prefix / length 8 */ + OSMO_ASSERT(in46a_to_eua(&ia_v6_8, 1, &eua) == 0); + OSMO_ASSERT(eua.v[0] == PDP_EUA_ORG_IETF); + OSMO_ASSERT(eua.v[1] == PDP_EUA_TYPE_v6); + OSMO_ASSERT(!memcmp(&eua.v[2], &ia_v6_8.v6, 16)); +} + +static void test_in46a_to_eua_v4v6(void) +{ + const struct in46_addr ia_v4v6[2] = { + { + .len = 16, + .v6.s6_addr = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, + }, + { + .len = 4, + .v4.s_addr = 0x0d0c0b0a, + } + }; + struct ul66_t eua; + printf("Testing in46a_to_eua() with IPv4v6 addresses\n"); + + /* IPv4 address */ + OSMO_ASSERT(in46a_to_eua(ia_v4v6, 2, &eua) == 0); + OSMO_ASSERT(eua.v[0] == PDP_EUA_ORG_IETF); + OSMO_ASSERT(eua.v[1] == PDP_EUA_TYPE_v4v6); + OSMO_ASSERT(osmo_load32le(&eua.v[2]) == g_ia4.v4.s_addr); + OSMO_ASSERT(!memcmp(&eua.v[6], &g_ia6.v6, 16)); +} + +static void test_in46a_from_eua_v6(void) +{ + struct in46_addr ia[2]; + struct ul66_t eua; + const uint8_t v6_unspec[] = { PDP_EUA_ORG_IETF, PDP_EUA_TYPE_v6 }; + const uint8_t v6_spec[] = { PDP_EUA_ORG_IETF, PDP_EUA_TYPE_v6, + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10 }; + + memset(&eua, 0, sizeof(eua)); + + printf("Testing in46a_from_eua() with IPv6 addresses\n"); + + /* unspecified V6 */ + memcpy(eua.v, v6_unspec, sizeof(v6_unspec)); + eua.l = sizeof(v6_unspec); + OSMO_ASSERT(in46a_from_eua(&eua, ia) == 1); + OSMO_ASSERT(ia[0].len == 16); + OSMO_ASSERT(IN6_IS_ADDR_UNSPECIFIED(&ia[0].v6)); + + /* specified V6 */ + memcpy(eua.v, v6_spec, sizeof(v6_spec)); + eua.l = sizeof(v6_spec); + OSMO_ASSERT(in46a_from_eua(&eua, ia) == 1); + OSMO_ASSERT(ia[0].len == 16); + OSMO_ASSERT(!memcmp(&ia[0].v6, v6_spec+2, ia[0].len)); +} + +static void test_in46a_from_eua_v4v6(void) +{ + struct in46_addr ia[2]; + struct ul66_t eua; + const uint8_t v4_unspec_v6_unspec[] = { PDP_EUA_ORG_IETF, PDP_EUA_TYPE_v4v6 }; + const uint8_t v4_spec_v6_unspec[] = { PDP_EUA_ORG_IETF, PDP_EUA_TYPE_v4v6, + 1, 2, 3, 4 }; + const uint8_t v4_unspec_v6_spec[] = { PDP_EUA_ORG_IETF, PDP_EUA_TYPE_v4v6, + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10 }; + const uint8_t v4_spec_v6_spec[] = { PDP_EUA_ORG_IETF, PDP_EUA_TYPE_v4v6, + 1, 2, 3, 4, + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10 }; + + memset(&eua, 0, sizeof(eua)); + + printf("Testing in46a_from_eua() with IPv4v6 addresses\n"); + + /* unspecified V4 & V6 */ + memcpy(eua.v, v4_unspec_v6_unspec, sizeof(v4_unspec_v6_unspec)); + eua.l = sizeof(v4_unspec_v6_unspec); + OSMO_ASSERT(in46a_from_eua(&eua, ia) == 2); + OSMO_ASSERT(ia[0].len == 4); + OSMO_ASSERT(ia[1].len == 16); + OSMO_ASSERT(ia[0].v4.s_addr == 0); + OSMO_ASSERT(IN6_IS_ADDR_UNSPECIFIED(&ia[1].v6)); + + /* specified V4, unspecified V6 */ + memcpy(eua.v, v4_spec_v6_unspec, sizeof(v4_spec_v6_unspec)); + eua.l = sizeof(v4_spec_v6_unspec); + OSMO_ASSERT(in46a_from_eua(&eua, ia) == 2); + OSMO_ASSERT(ia[0].len == 4); + OSMO_ASSERT(ia[1].len == 16); + OSMO_ASSERT(ia[0].v4.s_addr == htonl(0x01020304)); + OSMO_ASSERT(IN6_IS_ADDR_UNSPECIFIED(&ia[1].v6)); + + /* unspecified V4, specified V6 */ + memcpy(eua.v, v4_unspec_v6_spec, sizeof(v4_unspec_v6_spec)); + eua.l = sizeof(v4_unspec_v6_spec); + OSMO_ASSERT(in46a_from_eua(&eua, ia) == 2); + OSMO_ASSERT(ia[0].len == 4); + OSMO_ASSERT(ia[1].len == 16); + OSMO_ASSERT(ia[0].v4.s_addr == 0); + OSMO_ASSERT(!memcmp(&ia[1].v6, v4_unspec_v6_spec+2, ia[1].len)); + + /* specified V4, specified V6 */ + memcpy(eua.v, v4_spec_v6_spec, sizeof(v4_spec_v6_spec)); + eua.l = sizeof(v4_spec_v6_spec); + OSMO_ASSERT(in46a_from_eua(&eua, ia) == 2); + OSMO_ASSERT(ia[0].len == 4); + OSMO_ASSERT(ia[1].len == 16); + OSMO_ASSERT(ia[0].v4.s_addr == htonl(0x01020304)); + OSMO_ASSERT(!memcmp(&ia[1].v6, v4_spec_v6_spec+6, ia[1].len)); +} + +static void test_in46a_netmasklen_v6(void) +{ + unsigned int len; + printf("Testing in46a_netmasklen() with IPv6 addresses\n"); + const struct in46_addr netmaskA = { + .len = 16, + .v6.s6_addr = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + }; + len = in46a_netmasklen(&netmaskA); + OSMO_ASSERT(len == 128); + + const struct in46_addr netmaskB = { + .len = 16, + .v6.s6_addr = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00}, + }; + len = in46a_netmasklen(&netmaskB); + OSMO_ASSERT(len == 104); + + const struct in46_addr netmaskC = { + .len = 16, + .v6.s6_addr = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00}, + }; + len = in46a_netmasklen(&netmaskC); + OSMO_ASSERT(len == 103); + + const struct in46_addr netmaskD = { + .len = 16, + .v6.s6_addr = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + }; + len = in46a_netmasklen(&netmaskD); + OSMO_ASSERT(len == 0); +} + +int main(int argc, char **argv) +{ + void *tall_ctx = talloc_named_const(NULL, 1, "Root context"); + msgb_talloc_ctx_init(tall_ctx, 0); + osmo_init_logging2(tall_ctx, &log_info); + log_set_use_color(osmo_stderr_target, 0); + log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE); + + srand(time(NULL)); + + if (argc < 2 || strcmp(argv[1], "-v6")) { + test_in46a_to_af(); + test_in46a_to_sas(); + test_in46a_ntop(); + test_in46p_ntoa(); + test_in46a_equal(); + test_in46a_within_mask(); + test_in46a_to_eua(); + test_in46a_from_eua(); + test_in46a_netmasklen(); + } else { + test_in46a_to_af_v6(); + test_in46a_to_sas_v6(); + test_in46a_ntop_v6(); + test_in46a_equal_v6(); + test_in46a_to_eua_v6(); + test_in46a_from_eua_v6(); + test_in46a_to_eua_v4v6(); + test_in46a_from_eua_v4v6(); + test_in46a_netmasklen_v6(); + } + return 0; +} diff --git a/tests/in46_addr/in46a_test.ok b/tests/in46_addr/in46a_test.ok new file mode 100644 index 000000000..b497ad171 --- /dev/null +++ b/tests/in46_addr/in46a_test.ok @@ -0,0 +1,17 @@ +Testing in46a_to_af() with IPv4 addresses +Testing in46a_to_sas() with IPv4 addresses +Testing in46a_ntop() with IPv4 addresses +res = UNDEFINED +res = UNDEFINED +res = 1.2.3.4 +in46p_ntoa() returns 16.32.48.0/24 +Testing in46a_equal() with IPv4 addresses +Testing in46a_within_mask() with IPv4 addresses +in46a_within_mask(10.11.12.13, 10.11.12.13, 32) = 1 +in46a_within_mask(10.11.12.13, 10.11.12.12, 30) = 1 +in46a_within_mask(10.11.12.13, 10.8.0.0, 13) = 1 +in46a_within_mask(10.11.12.14, 10.11.12.13, 32) = 0 +in46a_within_mask(10.11.12.14, 10.11.12.12, 30) = 1 +testing in46a_to_eua() with IPv4 addresses +Testing in46a_from_eua() with IPv4 addresses +Testing in46a_netmasklen() with IPv4 addresses diff --git a/tests/in46_addr/in46a_v6_test.ok b/tests/in46_addr/in46a_v6_test.ok new file mode 100644 index 000000000..10dc7f488 --- /dev/null +++ b/tests/in46_addr/in46a_v6_test.ok @@ -0,0 +1,10 @@ +Testing in46a_to_af() with IPv6 addresses +Testing in46a_to_sas() with IPv6 addresses +Testing in46a_ntop() with IPv6 addresses +res = 102:304:506:708:90a:b0c:d0e:f10 +Testing in46a_equal() with IPv6 addresses +Testing in46a_to_eua() with IPv6 addresses +Testing in46a_from_eua() with IPv6 addresses +Testing in46a_to_eua() with IPv4v6 addresses +Testing in46a_from_eua() with IPv4v6 addresses +Testing in46a_netmasklen() with IPv6 addresses diff --git a/tests/testsuite.at b/tests/testsuite.at index 4a0af0d29..ca3e43edb 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -541,3 +541,15 @@ AT_KEYWORDS([rlp]) cat $abs_srcdir/rlp/rlp_test.ok > expout AT_CHECK([$abs_top_builddir/tests/rlp/rlp_test], [0], [expout], [ignore]) AT_CLEANUP + +AT_SETUP([in46a]) +AT_KEYWORDS([in46a]) +cat $abs_srcdir/in46_addr/in46a_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/in46_addr/in46a_test], [], [expout], []) +AT_CLEANUP + +AT_SETUP([in46a_v6]) +AT_KEYWORDS([in46a_v6]) +cat $abs_srcdir/in46_addr/in46a_v6_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/in46_addr/in46a_test -v6], [], [expout], []) +AT_CLEANUP