/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nm-inet-utils.h" #include #include #include /*****************************************************************************/ const NMIPAddr nm_ip_addr_zero = {}; /* We use _nm_alignas(NMIPAddr) to ensure that fields for in_addr_t and * struct in6_addr have all the same alignment. Ensure that this is suitable. */ G_STATIC_ASSERT(_nm_alignof(in_addr_t) <= _nm_alignof(NMIPAddr)); G_STATIC_ASSERT(_nm_alignof(struct in_addr) <= _nm_alignof(NMIPAddr)); G_STATIC_ASSERT(_nm_alignof(struct in6_addr) <= _nm_alignof(NMIPAddr)); G_STATIC_ASSERT(_nm_alignof(NMEtherAddr) <= _nm_alignof(NMIPAddr)); int nm_ip_addr_cmp_for_sort(gconstpointer a, gconstpointer b, gpointer user_data) { /* This is a compare function that can be used for sorting IP addresses. * Essentially, it calls memcmp(). @user_data must be GINT_TO_POINTER(addr_family). * @a and @b must be either pointers to in_addr_t, struct in6_addr or NMIPAddr. */ return nm_ip_addr_cmp(GPOINTER_TO_INT(user_data), a, b); } /* this initializes a struct in_addr/in6_addr and allows for untrusted * arguments (like unsuitable @addr_family or @src_len). It's almost safe * in the sense that it verifies input arguments strictly. Also, it * uses memcpy() to access @src, so alignment is not an issue. * * Only potential pitfalls: * * - it allows for @addr_family to be AF_UNSPEC. If that is the case (and the * caller allows for that), the caller MUST provide @out_addr_family. * - when setting @dst to an IPv4 address, the trailing bytes are not touched. * Meaning, if @dst is an NMIPAddr union, only the first bytes will be set. * If that matter to you, clear @dst before. */ gboolean nm_ip_addr_set_from_untrusted(int addr_family, gpointer dst, gconstpointer src, gsize src_len, int *out_addr_family) { nm_assert(dst); switch (addr_family) { case AF_UNSPEC: if (!out_addr_family) { /* when the callers allow undefined @addr_family, they must provide * an @out_addr_family argument. */ nm_assert_not_reached(); return FALSE; } switch (src_len) { case sizeof(struct in_addr): addr_family = AF_INET; break; case sizeof(struct in6_addr): addr_family = AF_INET6; break; default: return FALSE; } break; case AF_INET: if (src_len != sizeof(struct in_addr)) return FALSE; break; case AF_INET6: if (src_len != sizeof(struct in6_addr)) return FALSE; break; default: /* when the callers allow undefined @addr_family, they must provide * an @out_addr_family argument. */ nm_assert(out_addr_family); return FALSE; } nm_assert(src); memcpy(dst, src, src_len); NM_SET_OUT(out_addr_family, addr_family); return TRUE; } gboolean nm_ip_addr_set_from_variant(int addr_family, gpointer dst, GVariant *variant, int *out_addr_family) { gconstpointer bytes; gsize len; g_return_val_if_fail(dst, FALSE); g_return_val_if_fail(variant, FALSE); /* This function always expects IP addresses as byte arrays ("ay"). Note that * several NetworkManager API uses "u" (32 bit unsigned intergers) for IPv4 addresses. * So this function won't work in those cases. * * Btw, using "u" for IPv4 address messes badly with the endianness (host * vs network byte order). Don't do that. */ g_return_val_if_fail(g_variant_is_of_type(variant, G_VARIANT_TYPE("ay")), FALSE); bytes = g_variant_get_fixed_array(variant, &len, sizeof(guint8)); return nm_ip_addr_set_from_untrusted(addr_family, dst, bytes, len, out_addr_family); } /*****************************************************************************/ guint32 nm_ip4_addr_get_default_prefix0(in_addr_t ip) { /* The function is originally from ipcalc.c of Red Hat's initscripts. */ switch (ntohl(ip) >> 24) { case 0 ... 127: return 8; /* Class A */ case 128 ... 191: return 16; /* Class B */ case 192 ... 223: return 24; /* Class C */ } return 0; } guint32 nm_ip4_addr_get_default_prefix(in_addr_t ip) { return nm_ip4_addr_get_default_prefix0(ip) ?: 24; } gboolean nm_ip_addr_is_site_local(int addr_family, const void *address) { NMIPAddr a; nm_ip_addr_set(addr_family, &a, address); switch (addr_family) { case AF_INET: /* RFC1918 private addresses * 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 */ return (a.addr4 & htonl(0xff000000)) == htonl(0x0a000000) || (a.addr4 & htonl(0xfff00000)) == htonl(0xac100000) || (a.addr4 & htonl(0xffff0000)) == htonl(0xc0a80000); case AF_INET6: /* IN6_IS_ADDR_SITELOCAL() is for deprecated fec0::/10 addresses (see rfc3879, 4.). * Note that for unique local IPv6 addresses (ULA, fc00::/7) this returns false. * This may or may not be a bug of this function. */ return IN6_IS_ADDR_SITELOCAL(&a.addr6); default: g_return_val_if_reached(FALSE); } } gboolean nm_ip6_addr_is_ula(const struct in6_addr *address) { /* Unique local IPv6 address (ULA) fc00::/7 */ return (address->s6_addr32[0] & htonl(0xfe000000u)) == htonl(0xfc000000u); } /*****************************************************************************/ gconstpointer nm_ip_addr_clear_host_address(int family, gpointer dst, gconstpointer src, guint32 plen) { NMIPAddr a; NMIPAddr a2; g_return_val_if_fail(dst, NULL); if (!src) { /* allow "self-assignment", by specifying %NULL as source. */ src = dst; } nm_ip_addr_set(family, &a, src); switch (family) { case AF_INET: g_return_val_if_fail(plen <= 32, NULL); a2.addr4 = nm_ip4_addr_clear_host_address(a.addr4, plen); break; case AF_INET6: nm_ip6_addr_clear_host_address(&a2.addr6, &a.addr6, plen); break; default: g_return_val_if_reached(NULL); } nm_ip_addr_set(family, dst, &a2); return dst; } /* nm_ip6_addr_clear_host_address: * @dst: destination output buffer, will contain the network part of the @src address * @src: source ip6 address. If NULL, this does an in-place update of @dst. * Also, @src and @dst are allowed to be the same pointers. * @plen: prefix length of network * * Note: this function is self assignment safe, to update @src inplace, set both * @dst and @src to the same destination or set @src NULL. */ const struct in6_addr * nm_ip6_addr_clear_host_address(struct in6_addr *dst, const struct in6_addr *src, guint32 plen) { g_return_val_if_fail(plen <= 128, NULL); g_return_val_if_fail(dst, NULL); if (!src) src = dst; if (plen < 128) { guint nbytes = plen / 8; guint nbits = plen % 8; if (nbytes && dst != src) memcpy(dst, src, nbytes); if (nbits) { dst->s6_addr[nbytes] = (src->s6_addr[nbytes] & (0xFF << (8 - nbits))); nbytes++; } if (nbytes <= 15) memset(&dst->s6_addr[nbytes], 0, 16 - nbytes); } else if (src != dst) *dst = *src; return dst; } int nm_ip6_addr_same_prefix_cmp(const struct in6_addr *addr_a, const struct in6_addr *addr_b, guint32 plen) { int nbytes; guint8 va, vb, m; if (plen >= 128) { nm_assert(plen == 128); NM_CMP_DIRECT_MEMCMP(addr_a, addr_b, sizeof(struct in6_addr)); } else { nbytes = plen / 8; if (nbytes) NM_CMP_DIRECT_MEMCMP(addr_a, addr_b, nbytes); plen = plen % 8; if (plen != 0) { m = ~((1 << (8 - plen)) - 1); va = ((((const guint8 *) addr_a))[nbytes]) & m; vb = ((((const guint8 *) addr_b))[nbytes]) & m; NM_CMP_DIRECT(va, vb); } } return 0; } /*****************************************************************************/ static int _inet_aton(const char *text, in_addr_t *out_addr) { /* Call inet_aton() via dlopen. * * The inet_aton() API is discouraged, and ABI checkers warn when we call * it. * * We want to use this function, but only for testing/asserting. To avoid * the ABI checker's complain, dlopen() the symbol. This is not used for * production. */ static gpointer fcn_sym = NULL; static gsize initialized = 0; int (*fcn)(const char *text, struct in_addr *out_addr); int r; in_addr_t a; if (g_once_init_enter(&initialized)) { GModule *module; module = g_module_open(NULL, G_MODULE_BIND_LAZY); if (module) { if (!g_module_symbol(module, "inet_aton", &fcn_sym)) g_module_close(module); else g_module_make_resident(module); } g_once_init_leave(&initialized, 1); } if (!fcn_sym) return -ENOSYS; fcn = fcn_sym; r = fcn(text, (gpointer) &a); if (r != 1) return -EINVAL; NM_SET_OUT(out_addr, a); return 0; } int nmtst_inet_aton(const char *text, in_addr_t *out_addr) { return _inet_aton(text, out_addr); } static void _nm_assert_legacy_addr4(const char *text, in_addr_t addr) { #if NM_MORE_ASSERTS > 20 char buf1[NM_INET_ADDRSTRLEN]; char buf2[NM_INET_ADDRSTRLEN]; int r; in_addr_t a; /* Our legacy parser accepted "text" as "addr". * * However, we want to ensure that whatever we parse is also parsed by old * inet_aton(). So we want to be strictly more strict than inet_aton() in * what we accept. */ r = _inet_aton(text, &a); if (r != 0) { if (r == -ENOSYS) return; g_error("inet_aton(\"%s\") failed with \"%s\", but we expected %s", text, nm_strerror_native(-r), nm_inet4_ntop(addr, buf2)); } if (a != addr) { g_error("inet_aton(\"%s\") parsed %s, but we expected %s", text, nm_inet4_ntop(a, buf1), nm_inet4_ntop(addr, buf2)); } #endif } static gboolean _parse_legacy_addr4(const char *text, in_addr_t *out_addr, GError **error) { gs_free char *s_free = NULL; union { guint8 b[sizeof(in_addr_t)]; in_addr_t a; } addr; char *s; int i; /* inet_pton() does strict parsing of IPv4 address. Good. * * However, inet_aton() used to accept much more relaxed forms (e.g. octal * and hex numbers, not having 4 components but fewer, ignore any trailing * garbage). * * Some places where we accept input, we want to be slightly more forgiving * than inet_pton() and accept some (not all!) forms of what inet_aton() * would accept. For example, we want to accept 255.000.000.000. * * We reimplement that below. * * Note that we still should (of course) accept everything that inet_pton() * accepts. This is ensured because the caller only calls this function * after inet_pton() failed. */ if (NM_STRCHAR_ANY(text, ch, (!(ch >= '0' && ch <= '9') && !NM_IN_SET(ch, '.', 'x')))) { /* We only accepts '.', digits, and 'x' for "0x". */ g_set_error_literal(error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, "contains an invalid character"); return FALSE; } s = nm_memdup_maybe_a(300, text, strlen(text) + 1, &s_free); for (i = 0; i < G_N_ELEMENTS(addr.b); i++) { char *current_token = s; gint32 v; s = strchr(s, '.'); if (s) { s[0] = '\0'; s++; } if ((i == G_N_ELEMENTS(addr.b) - 1) != (s == NULL)) { /* Exactly for the last digit, we expect to have no more following token. * But this isn't the case. Abort. */ g_set_error(error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, "wrong number of tokens (index %d, token '%s')", i, s); return FALSE; } v = _nm_utils_ascii_str_to_int64(current_token, 0, 0, 0xFF, -1); if (v == -1) { int errsv = errno; /* we do accept octal and hex (even with leading "0x"). But something * about this token is wrong. */ g_set_error(error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, "invalid token '%s': %s (%d)", current_token, nm_strerror_native(errsv), errsv); return FALSE; } addr.b[i] = v; } _nm_assert_legacy_addr4(text, addr.a); *out_addr = addr.a; return TRUE; } gboolean nm_inet_parse_bin_full(int addr_family, gboolean accept_legacy, const char *text, int *out_addr_family, gpointer out_addr) { NMIPAddr addrbin; g_return_val_if_fail(text, FALSE); if (addr_family == AF_UNSPEC) { g_return_val_if_fail(!out_addr || out_addr_family, FALSE); addr_family = strchr(text, ':') ? AF_INET6 : AF_INET; } else g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), FALSE); if (inet_pton(addr_family, text, &addrbin) != 1) { if (accept_legacy && addr_family == AF_INET && _parse_legacy_addr4(text, &addrbin.addr4, NULL)) { /* The address is in some legacy format which inet_aton() accepts, but not inet_pton(). * Most likely octal digits (leading zeros). We accept the address. */ } else return FALSE; } #if NM_MORE_ASSERTS > 10 if (addr_family == AF_INET) { NM_PRAGMA_WARNING_DISABLE_DANGLING_POINTER gs_free_error GError *error = NULL; in_addr_t a; /* The legacy parser should accept everything that inet_pton() accepts too. Meaning, * it should strictly parse *more* formats. And of course, parse it the same way. */ if (!_parse_legacy_addr4(text, &a, &error)) { char buf[INET_ADDRSTRLEN]; g_error("unexpected assertion failure: could parse \"%s\" as %s, but not accepted by " "legacy parser: %s", text, nm_inet4_ntop(addrbin.addr4, buf), error->message); } nm_assert(addrbin.addr4 == a); NM_PRAGMA_WARNING_REENABLE } #endif NM_SET_OUT(out_addr_family, addr_family); if (out_addr) nm_ip_addr_set(addr_family, out_addr, &addrbin); return TRUE; } gboolean nm_inet_parse_str(int addr_family, const char *text, char **out_addr) { NMIPAddr addrbin; if (!nm_inet_parse_bin(addr_family, text, &addr_family, &addrbin)) return FALSE; NM_SET_OUT(out_addr, nm_inet_ntop_dup(addr_family, &addrbin)); return TRUE; } gboolean nm_inet_parse_with_prefix_bin(int addr_family, const char *text, int *out_addr_family, gpointer out_addr, int *out_prefix) { gs_free char *addrstr_free = NULL; int prefix = -1; const char *slash; const char *addrstr; NMIPAddr addrbin; g_return_val_if_fail(text, FALSE); if (addr_family == AF_UNSPEC) { g_return_val_if_fail(!out_addr || out_addr_family, FALSE); addr_family = strchr(text, ':') ? AF_INET6 : AF_INET; } else g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), FALSE); slash = strchr(text, '/'); if (slash) addrstr = nm_strndup_a(300, text, slash - text, &addrstr_free); else addrstr = text; if (inet_pton(addr_family, addrstr, &addrbin) != 1) return FALSE; if (slash) { /* For IPv4, `ip addr add` supports the prefix-length as a netmask. We don't * do that. */ prefix = _nm_utils_ascii_str_to_int64(&slash[1], 10, 0, addr_family == AF_INET ? 32 : 128, -1); if (prefix == -1) return FALSE; } NM_SET_OUT(out_addr_family, addr_family); if (out_addr) nm_ip_addr_set(addr_family, out_addr, &addrbin); NM_SET_OUT(out_prefix, prefix); return TRUE; } gboolean nm_inet_parse_with_prefix_str(int addr_family, const char *text, char **out_addr, int *out_prefix) { NMIPAddr addrbin; if (!nm_inet_parse_with_prefix_bin(addr_family, text, &addr_family, &addrbin, out_prefix)) return FALSE; NM_SET_OUT(out_addr, nm_inet_ntop_dup(addr_family, &addrbin)); return TRUE; } /*****************************************************************************/ gboolean nm_inet_is_valid(int addr_family, const char *str_addr) { nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); return str_addr && nm_inet_parse_bin(addr_family, str_addr, NULL, NULL); } gboolean nm_inet_is_normalized(int addr_family, const char *str_addr) { NMIPAddr addr; char sbuf[NM_INET_ADDRSTRLEN]; nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); if (!str_addr) return FALSE; if (!nm_inet_parse_bin(addr_family, str_addr, &addr_family, &addr)) return FALSE; nm_inet_ntop(addr_family, &addr, sbuf); return nm_streq(sbuf, str_addr); } /*****************************************************************************/ NM_UTILS_ENUM2STR_DEFINE(nm_icmpv6_router_pref_to_string, NMIcmpv6RouterPref, NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_LOW, "low"), NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_MEDIUM, "medium"), NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_HIGH, "high"), NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_INVALID, "invalid"), );