aboutsummaryrefslogtreecommitdiff
path: root/src/sa.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sa.c')
-rw-r--r--src/sa.c489
1 files changed, 489 insertions, 0 deletions
diff --git a/src/sa.c b/src/sa.c
new file mode 100644
index 000000000000..c6a19d1bef7a
--- /dev/null
+++ b/src/sa.c
@@ -0,0 +1,489 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Socket Address handling for dhcpcd
+ * Copyright (c) 2015-2021 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <arpa/inet.h>
+#ifdef AF_LINK
+#include <net/if_dl.h>
+#elif defined(AF_PACKET)
+#include <linux/if_packet.h>
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "config.h"
+#include "common.h"
+#include "sa.h"
+
+#ifndef NDEBUG
+static bool sa_inprefix;
+#endif
+
+socklen_t
+sa_addroffset(const struct sockaddr *sa)
+{
+
+ assert(sa != NULL);
+ switch(sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ return offsetof(struct sockaddr_in, sin_addr) +
+ offsetof(struct in_addr, s_addr);
+#endif /* INET */
+#ifdef INET6
+ case AF_INET6:
+ return offsetof(struct sockaddr_in6, sin6_addr) +
+ offsetof(struct in6_addr, s6_addr);
+#endif /* INET6 */
+ default:
+ errno = EAFNOSUPPORT;
+ return 0;
+ }
+}
+
+socklen_t
+sa_addrlen(const struct sockaddr *sa)
+{
+#define membersize(type, member) sizeof(((type *)0)->member)
+ assert(sa != NULL);
+ switch(sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ return membersize(struct in_addr, s_addr);
+#endif /* INET */
+#ifdef INET6
+ case AF_INET6:
+ return membersize(struct in6_addr, s6_addr);
+#endif /* INET6 */
+ default:
+ errno = EAFNOSUPPORT;
+ return 0;
+ }
+}
+
+#ifndef HAVE_SA_LEN
+socklen_t
+sa_len(const struct sockaddr *sa)
+{
+
+ switch (sa->sa_family) {
+#ifdef AF_LINK
+ case AF_LINK:
+ return sizeof(struct sockaddr_dl);
+#endif
+#ifdef AF_PACKET
+ case AF_PACKET:
+ return sizeof(struct sockaddr_ll);
+#endif
+ case AF_INET:
+ return sizeof(struct sockaddr_in);
+ case AF_INET6:
+ return sizeof(struct sockaddr_in6);
+ default:
+ return sizeof(struct sockaddr);
+ }
+}
+#endif
+
+bool
+sa_is_unspecified(const struct sockaddr *sa)
+{
+
+ assert(sa != NULL);
+ switch(sa->sa_family) {
+ case AF_UNSPEC:
+ return true;
+#ifdef INET
+ case AF_INET:
+ return satocsin(sa)->sin_addr.s_addr == INADDR_ANY;
+#endif /* INET */
+#ifdef INET6
+ case AF_INET6:
+ return IN6_IS_ADDR_UNSPECIFIED(&satocsin6(sa)->sin6_addr);
+#endif /* INET6 */
+ default:
+ errno = EAFNOSUPPORT;
+ return false;
+ }
+}
+
+#ifdef INET6
+#ifndef IN6MASK128
+#define IN6MASK128 {{{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}}
+#endif
+static const struct in6_addr in6allones = IN6MASK128;
+#endif
+
+bool
+sa_is_allones(const struct sockaddr *sa)
+{
+
+ assert(sa != NULL);
+ switch(sa->sa_family) {
+ case AF_UNSPEC:
+ return false;
+#ifdef INET
+ case AF_INET:
+ {
+ const struct sockaddr_in *sin;
+
+ sin = satocsin(sa);
+ return sin->sin_addr.s_addr == INADDR_BROADCAST;
+ }
+#endif /* INET */
+#ifdef INET6
+ case AF_INET6:
+ {
+ const struct sockaddr_in6 *sin6;
+
+ sin6 = satocsin6(sa);
+ return IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, &in6allones);
+ }
+#endif /* INET6 */
+ default:
+ errno = EAFNOSUPPORT;
+ return false;
+ }
+}
+
+bool
+sa_is_loopback(const struct sockaddr *sa)
+{
+
+ assert(sa != NULL);
+ switch(sa->sa_family) {
+ case AF_UNSPEC:
+ return false;
+#ifdef INET
+ case AF_INET:
+ {
+ const struct sockaddr_in *sin;
+
+ sin = satocsin(sa);
+ return sin->sin_addr.s_addr == htonl(INADDR_LOOPBACK);
+ }
+#endif /* INET */
+#ifdef INET6
+ case AF_INET6:
+ {
+ const struct sockaddr_in6 *sin6;
+
+ sin6 = satocsin6(sa);
+ return IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr);
+ }
+#endif /* INET6 */
+ default:
+ errno = EAFNOSUPPORT;
+ return false;
+ }
+}
+
+int
+sa_toprefix(const struct sockaddr *sa)
+{
+ int prefix;
+
+ assert(sa != NULL);
+ switch(sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ const struct sockaddr_in *sin;
+ uint32_t mask;
+
+ sin = satocsin(sa);
+ if (sin->sin_addr.s_addr == INADDR_ANY) {
+ prefix = 0;
+ break;
+ }
+ mask = ntohl(sin->sin_addr.s_addr);
+ prefix = 33 - ffs((int)mask); /* 33 - (1 .. 32) -> 32 .. 1 */
+ if (prefix < 32) { /* more than 1 bit in mask */
+ /* check for non-contig netmask */
+ if ((mask^(((1U << prefix)-1) << (32 - prefix))) != 0) {
+ errno = EINVAL;
+ return -1; /* noncontig, no pfxlen */
+ }
+ }
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ const struct sockaddr_in6 *sin6;
+ int x, y;
+ const uint8_t *lim, *p;
+
+ sin6 = satocsin6(sa);
+ p = (const uint8_t *)sin6->sin6_addr.s6_addr;
+ lim = p + sizeof(sin6->sin6_addr.s6_addr);
+ for (x = 0; p < lim; x++, p++) {
+ if (*p != 0xff)
+ break;
+ }
+ y = 0;
+ if (p < lim) {
+ for (y = 0; y < NBBY; y++) {
+ if ((*p & (0x80 >> y)) == 0)
+ break;
+ }
+ }
+
+ /*
+ * when the limit pointer is given, do a stricter check on the
+ * remaining bits.
+ */
+ if (p < lim) {
+ if (y != 0 && (*p & (0x00ff >> y)) != 0)
+ return 0;
+ for (p = p + 1; p < lim; p++)
+ if (*p != 0)
+ return 0;
+ }
+
+ prefix = x * NBBY + y;
+ break;
+ }
+#endif
+ default:
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+
+#ifndef NDEBUG
+ /* Ensure the calculation is correct */
+ if (!sa_inprefix) {
+ union sa_ss ss = { .sa = { .sa_family = sa->sa_family } };
+
+ sa_inprefix = true;
+ sa_fromprefix(&ss.sa, prefix);
+ assert(sa_cmp(sa, &ss.sa) == 0);
+ sa_inprefix = false;
+ }
+#endif
+
+ return prefix;
+}
+
+int
+sa_fromprefix(struct sockaddr *sa, int prefix)
+{
+ uint8_t *ap;
+ int max_prefix, bytes, bits, i;
+
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ max_prefix = 32;
+#ifdef HAVE_SA_LEN
+ sa->sa_len = sizeof(struct sockaddr_in);
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ max_prefix = 128;
+#ifdef HAVE_SA_LEN
+ sa->sa_len = sizeof(struct sockaddr_in6);
+#endif
+ break;
+#endif
+ default:
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+
+ bytes = prefix / NBBY;
+ bits = prefix % NBBY;
+
+ ap = (uint8_t *)sa + sa_addroffset(sa);
+ for (i = 0; i < bytes; i++)
+ *ap++ = 0xff;
+ if (bits) {
+ uint8_t a;
+
+ a = 0xff;
+ a = (uint8_t)(a << (8 - bits));
+ *ap++ = a;
+ }
+ bytes = (max_prefix - prefix) / NBBY;
+ for (i = 0; i < bytes; i++)
+ *ap++ = 0x00;
+
+#ifndef NDEBUG
+ /* Ensure the calculation is correct */
+ if (!sa_inprefix) {
+ sa_inprefix = true;
+ assert(sa_toprefix(sa) == prefix);
+ sa_inprefix = false;
+ }
+#endif
+ return 0;
+}
+
+/* inet_ntop, but for sockaddr. */
+const char *
+sa_addrtop(const struct sockaddr *sa, char *buf, socklen_t len)
+{
+ const void *addr;
+
+ assert(buf != NULL);
+ assert(len > 0);
+
+ if (sa->sa_family == 0) {
+ *buf = '\0';
+ return NULL;
+ }
+
+#ifdef AF_LINK
+#ifndef CLLADDR
+#define CLLADDR(sdl) (const void *)((sdl)->sdl_data + (sdl)->sdl_nlen)
+#endif
+ if (sa->sa_family == AF_LINK) {
+ const struct sockaddr_dl *sdl;
+
+ sdl = (const void *)sa;
+ if (sdl->sdl_alen == 0) {
+ if (snprintf(buf, len, "link#%d", sdl->sdl_index) == -1)
+ return NULL;
+ return buf;
+ }
+ return hwaddr_ntoa(CLLADDR(sdl), sdl->sdl_alen, buf, len);
+ }
+#elif defined(AF_PACKET)
+ if (sa->sa_family == AF_PACKET) {
+ const struct sockaddr_ll *sll;
+
+ sll = (const void *)sa;
+ return hwaddr_ntoa(sll->sll_addr, sll->sll_halen, buf, len);
+ }
+#endif
+ addr = (const char *)sa + sa_addroffset(sa);
+ return inet_ntop(sa->sa_family, addr, buf, len);
+}
+
+int
+sa_cmp(const struct sockaddr *sa1, const struct sockaddr *sa2)
+{
+ socklen_t offset, len;
+
+ assert(sa1 != NULL);
+ assert(sa2 != NULL);
+
+ /* Treat AF_UNSPEC as the unspecified address. */
+ if ((sa1->sa_family == AF_UNSPEC || sa2->sa_family == AF_UNSPEC) &&
+ sa_is_unspecified(sa1) && sa_is_unspecified(sa2))
+ return 0;
+
+ if (sa1->sa_family != sa2->sa_family)
+ return sa1->sa_family - sa2->sa_family;
+
+#ifdef HAVE_SA_LEN
+ len = MIN(sa1->sa_len, sa2->sa_len);
+#endif
+
+ switch (sa1->sa_family) {
+#ifdef INET
+ case AF_INET:
+ offset = offsetof(struct sockaddr_in, sin_addr);
+#ifdef HAVE_SA_LEN
+ len -= offset;
+ len = MIN(len, sizeof(struct in_addr));
+#else
+ len = sizeof(struct in_addr);
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ offset = offsetof(struct sockaddr_in6, sin6_addr);
+#ifdef HAVE_SA_LEN
+ len -= offset;
+ len = MIN(len, sizeof(struct in6_addr));
+#else
+ len = sizeof(struct in6_addr);
+#endif
+ break;
+#endif
+ default:
+ offset = 0;
+#ifndef HAVE_SA_LEN
+ len = sizeof(struct sockaddr);
+#endif
+ break;
+ }
+
+ return memcmp((const char *)sa1 + offset,
+ (const char *)sa2 + offset,
+ len);
+}
+
+#ifdef INET
+void
+sa_in_init(struct sockaddr *sa, const struct in_addr *addr)
+{
+ struct sockaddr_in *sin;
+
+ assert(sa != NULL);
+ assert(addr != NULL);
+ sin = satosin(sa);
+ sin->sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+ sin->sin_len = sizeof(*sin);
+#endif
+ sin->sin_addr.s_addr = addr->s_addr;
+}
+#endif
+
+#ifdef INET6
+void
+sa_in6_init(struct sockaddr *sa, const struct in6_addr *addr)
+{
+ struct sockaddr_in6 *sin6;
+
+ assert(sa != NULL);
+ assert(addr != NULL);
+ sin6 = satosin6(sa);
+ sin6->sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+ sin6->sin6_len = sizeof(*sin6);
+#endif
+ memcpy(&sin6->sin6_addr.s6_addr, &addr->s6_addr,
+ sizeof(sin6->sin6_addr.s6_addr));
+}
+#endif