/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright Red Hat * Author: David Gibson * * inany.h - Types and helpers for handling addresses which could be * IPv6 or IPv4 (encoded as IPv4-mapped IPv6 addresses) */ #ifndef INANY_H #define INANY_H struct siphash_state; /** union inany_addr - Represents either an IPv4 or IPv6 address * @a6: Address as an IPv6 address, may be IPv4-mapped * @v4mapped.zero: All zero-bits for an IPv4 address * @v4mapped.one: All one-bits for an IPv4 address * @v4mapped.a4: If @a6 is an IPv4 mapped address, the IPv4 address * @u64: As an array of u64s (solely for hashing) * * @v4mapped and @u64 shouldn't be accessed except via helpers. */ union inany_addr { struct in6_addr a6; struct { uint8_t zero[10]; uint8_t one[2]; struct in_addr a4; } v4mapped; uint32_t u32[4]; }; static_assert(sizeof(union inany_addr) == sizeof(struct in6_addr), "union inany_addr is larger than an IPv6 address"); static_assert(_Alignof(union inany_addr) == _Alignof(uint32_t), "union inany_addr has unexpected alignment"); #define inany_loopback6 (*(const union inany_addr *)(&in6addr_loopback)) extern const union inany_addr inany_loopback4; #define inany_any6 (*(const union inany_addr *)(&in6addr_any)) extern const union inany_addr inany_any4; #define in4addr_loopback (inany_loopback4.v4mapped.a4) #define in4addr_any (inany_any4.v4mapped.a4) #define INANY_INIT4(a4init) { \ .v4mapped = { \ .zero = { 0 }, \ .one = { 0xff, 0xff }, \ .a4 = a4init, \ }, \ } #define inany_from_v4(a4) \ ((union inany_addr)INANY_INIT4((a4))) /** union sockaddr_inany - Either a sockaddr_in or a sockaddr_in6 * @sa_family: Address family, AF_INET or AF_INET6 * @sa: Plain struct sockaddr (useful to avoid casts) * @sa4: IPv4 socket address, valid if sa_family == AF_INET * @sa6: IPv6 socket address, valid if sa_family == AF_INET6 */ union sockaddr_inany { sa_family_t sa_family; struct sockaddr sa; struct sockaddr_in sa4; struct sockaddr_in6 sa6; }; /** inany_v4 - Extract IPv4 address, if present, from IPv[46] address * @addr: IPv4 or IPv6 address * * Return: IPv4 address if @addr is IPv4, NULL otherwise */ static inline struct in_addr *inany_v4(const union inany_addr *addr) { if (!IN6_IS_ADDR_V4MAPPED(&addr->a6)) return NULL; return (struct in_addr *)&addr->v4mapped.a4; } /** inany_equals - Compare two IPv[46] addresses * @a, @b: IPv[46] addresses * * Return: true if @a and @b are the same address */ static inline bool inany_equals(const union inany_addr *a, const union inany_addr *b) { return IN6_ARE_ADDR_EQUAL(&a->a6, &b->a6); } /** inany_equals4 - Compare an IPv[46] address to an IPv4 address * @a: IPv[46] addresses * @b: IPv4 address * * Return: true if @a and @b are the same address */ static inline bool inany_equals4(const union inany_addr *a, const struct in_addr *b) { const struct in_addr *a4 = inany_v4(a); return a4 && IN4_ARE_ADDR_EQUAL(a4, b); } /** inany_equals6 - Compare an IPv[46] address to an IPv6 address * @a: IPv[46] addresses * @b: IPv6 address * * Return: true if @a and @b are the same address */ static inline bool inany_equals6(const union inany_addr *a, const struct in6_addr *b) { return IN6_ARE_ADDR_EQUAL(&a->a6, b); } /** inany_is_loopback4() - Check if address is IPv4 loopback * @a: IPv[46] address * * Return: true if @a is in 127.0.0.1/8 */ static inline bool inany_is_loopback4(const union inany_addr *a) { const struct in_addr *v4 = inany_v4(a); return v4 && IN4_IS_ADDR_LOOPBACK(v4); } /** inany_is_loopback6() - Check if address is IPv6 loopback * @a: IPv[46] address * * Return: true if @a is in ::1 */ static inline bool inany_is_loopback6(const union inany_addr *a) { return IN6_IS_ADDR_LOOPBACK(&a->a6); } /** inany_is_loopback() - Check if address is loopback * @a: IPv[46] address * * Return: true if @a is either ::1 or in 127.0.0.1/8 */ static inline bool inany_is_loopback(const union inany_addr *a) { return inany_is_loopback4(a) || inany_is_loopback6(a); } /** inany_is_unspecified4() - Check if address is unspecified IPv4 * @a: IPv[46] address * * Return: true if @a is 0.0.0.0 */ static inline bool inany_is_unspecified4(const union inany_addr *a) { const struct in_addr *v4 = inany_v4(a); return v4 && IN4_IS_ADDR_UNSPECIFIED(v4); } /** inany_is_unspecified6() - Check if address is unspecified IPv6 * @a: IPv[46] address * * Return: true if @a is :: */ static inline bool inany_is_unspecified6(const union inany_addr *a) { return IN6_IS_ADDR_UNSPECIFIED(&a->a6); } /** inany_is_unspecified() - Check if address is unspecified * @a: IPv[46] address * * Return: true if @a is either :: or 0.0.0.0 */ static inline bool inany_is_unspecified(const union inany_addr *a) { return inany_is_unspecified4(a) || inany_is_unspecified6(a); } /* FIXME: consider handling of IPv4 link-local addresses */ /** inany_is_linklocal6() - Check if address is link-local IPv6 * @a: IPv[46] address * * Return: true if @a is in fe80::/10 (IPv6 link local unicast) */ static inline bool inany_is_linklocal6(const union inany_addr *a) { return IN6_IS_ADDR_LINKLOCAL(&a->a6); } /** inany_is_multicast() - Check if address is multicast or broadcast * @a: IPv[46] address * * Return: true if @a is IPv6 multicast or IPv4 multicast or broadcast */ static inline bool inany_is_multicast(const union inany_addr *a) { const struct in_addr *v4 = inany_v4(a); return IN6_IS_ADDR_MULTICAST(&a->a6) || (v4 && (IN4_IS_ADDR_MULTICAST(v4) || IN4_IS_ADDR_BROADCAST(v4))); } /** inany_is_unicast() - Check if address is specified and unicast * @a: IPv[46] address * * Return: true if @a is specified and a unicast address */ static inline bool inany_is_unicast(const union inany_addr *a) { return !inany_is_unspecified(a) && !inany_is_multicast(a); } /** inany_from_af - Set IPv[46] address from IPv4 or IPv6 address * @aa: Pointer to store IPv[46] address * @af: Address family of @addr * @addr: struct in_addr (IPv4) or struct in6_addr (IPv6) */ static inline void inany_from_af(union inany_addr *aa, sa_family_t af, const void *addr) { if (af == AF_INET6) { aa->a6 = *((struct in6_addr *)addr); } else if (af == AF_INET) { memset(&aa->v4mapped.zero, 0, sizeof(aa->v4mapped.zero)); memset(&aa->v4mapped.one, 0xff, sizeof(aa->v4mapped.one)); aa->v4mapped.a4 = *((struct in_addr *)addr); } else { /* Not valid to call with other address families */ ASSERT(0); } } /** inany_from_sockaddr - Extract IPv[46] address and port number from sockaddr * @aa: Pointer to store IPv[46] address * @port: Pointer to store port number, host order * @addr: AF_INET or AF_INET6 socket address */ static inline void inany_from_sockaddr(union inany_addr *aa, in_port_t *port, const union sockaddr_inany *sa) { if (sa->sa_family == AF_INET6) { inany_from_af(aa, AF_INET6, &sa->sa6.sin6_addr); *port = ntohs(sa->sa6.sin6_port); } else if (sa->sa_family == AF_INET) { inany_from_af(aa, AF_INET, &sa->sa4.sin_addr); *port = ntohs(sa->sa4.sin_port); } else { /* Not valid to call with other address families */ ASSERT(0); } } /** inany_siphash_feed- Fold IPv[46] address into an in-progress siphash * @state: siphash state * @aa: inany to hash */ static inline void inany_siphash_feed(struct siphash_state *state, const union inany_addr *aa) { siphash_feed(state, (uint64_t)aa->u32[0] << 32 | aa->u32[1]); siphash_feed(state, (uint64_t)aa->u32[2] << 32 | aa->u32[3]); } #define INANY_ADDRSTRLEN MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) const char *inany_ntop(const union inany_addr *src, char *dst, socklen_t size); int inany_pton(const char *src, union inany_addr *dst); #endif /* INANY_H */