// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2022 Felix Fietkau */ #include #include #include #include #include #include #include #include #include #include #include #include "unetd.h" int network_get_endpoint(union network_endpoint *dest, int af, const char *str, int default_port, int idx) { struct addrinfo hints = { .ai_flags = AI_ADDRCONFIG, .ai_family = af, }; char *buf = strdup(str); char *host = buf, *port; struct addrinfo *ai, *ai_cur; int n_res; int ret = -1; memset(dest, 0, sizeof(*dest)); if (*host == '[') { if (af == AF_INET) goto out; host++; port = strchr(host, ']'); if (!port) goto out; *(port++) = 0; if (!*port) port = NULL; else if (*port == ':') port++; else goto out; hints.ai_family = AF_INET6; hints.ai_flags |= AI_NUMERICHOST; } else { host = buf; port = strchr(host, ':'); if (port) *(port++) = 0; } if (getaddrinfo(host, port, &hints, &ai) || !ai) goto out; while (1) { ai_cur = ai; for (n_res = 0; ai_cur; ai_cur = ai_cur->ai_next, n_res++) if (!idx--) goto found; idx %= n_res; } found: if (ai_cur->ai_addrlen > sizeof(*dest)) goto free_ai; memcpy(dest, ai_cur->ai_addr, ai_cur->ai_addrlen); if (!port) dest->in.sin_port = htons(default_port); ret = 0; free_ai: freeaddrinfo(ai); out: free(buf); return ret; } int network_get_subnet(int af, union network_addr *addr, int *mask, const char *str) { char *buf = strdup(str); char *sep, *end; int ret = -1; if (af == AF_INET6) *mask = 128; else *mask = 32; sep = strchr(buf, '/'); if (sep) { unsigned long val; *(sep++) = 0; val = strtoul(sep, &end, 0); if ((end && *end) || val > *mask) goto out; *mask = val; } if (inet_pton(af, buf, addr) == 1) ret = 0; out: free(buf); return ret; } int network_get_local_addr(void *local, const union network_endpoint *target) { union network_endpoint ep = {}; socklen_t len; int ret = -1; int fd; memset(local, 0, sizeof(union network_addr)); if (target->sa.sa_family == AF_INET6) len = sizeof(ep.in6); else len = sizeof(ep.in); fd = socket(target->sa.sa_family, SOCK_DGRAM, IPPROTO_UDP); if (fd < 0) return -1; if (connect(fd, (const struct sockaddr *)target, len)) goto out; len = sizeof(ep); if (getsockname(fd, &ep.sa, &len)) goto out; if (ep.sa.sa_family == AF_INET6) memcpy(local, &ep.in6.sin6_addr, sizeof(ep.in6.sin6_addr)); else memcpy(local, &ep.in.sin_addr, sizeof(ep.in.sin_addr)); ret = 0; out: close(fd); return ret; } void *unet_read_file(const char *name, size_t *len) { struct stat st; void *data; FILE *f; f = fopen(name, "r"); if (!f) goto error; if (fstat(fileno(f), &st) < 0) goto close; if (*len && st.st_size > *len) goto close; data = malloc(st.st_size); if (!data) goto close; if (fread(data, 1, st.st_size, f) != st.st_size) { free(data); goto close; } fclose(f); *len = st.st_size; return data; close: fclose(f); error: *len = 0; return NULL; } uint64_t unet_gettime(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec; } static inline uint32_t csum_tcpudp_nofold(uint32_t saddr, uint32_t daddr, uint8_t proto, uint32_t len) { uint64_t sum = 0; sum += saddr; sum += daddr; #if __BYTE_ORDER == __LITTLE_ENDIAN sum += (proto + len) << 8; #else sum += proto + len; #endif sum = (sum & 0xffffffff) + (sum >> 32); sum = (sum & 0xffffffff) + (sum >> 32); return (uint32_t)sum; } static inline uint32_t csum_add(uint32_t sum, uint32_t addend) { sum += addend; return sum + (sum < addend); } static inline uint16_t csum_fold(uint32_t sum) { sum = (sum & 0xffff) + (sum >> 16); sum = (sum & 0xffff) + (sum >> 16); return (uint16_t)~sum; } static uint32_t csum_partial(const void *buf, int len) { const uint16_t *data = buf; uint32_t sum = 0; while (len > 1) { sum += *data++; len -= 2; } if (len == 1) #if __BYTE_ORDER == __LITTLE_ENDIAN sum += *(uint8_t *)data; #else sum += *(uint8_t *)data << 8; #endif sum = (sum & 0xffff) + (sum >> 16); sum = (sum & 0xffff) + (sum >> 16); return sum; } static void fixup_udpv4(void *hdr, size_t hdrlen, const void *data, size_t len) { struct ip *ip = hdr; struct udphdr *udp = hdr + ip->ip_hl * 4; uint16_t udp_len = sizeof(*udp) + len; uint32_t sum; if ((void *)&udp[1] > hdr + hdrlen) return; udp->uh_sum = 0; udp->uh_ulen = htons(udp_len); sum = csum_tcpudp_nofold(*(uint32_t *)&ip->ip_src, *(uint32_t *)&ip->ip_dst, ip->ip_p, udp_len); sum = csum_add(sum, csum_partial(udp, sizeof(*udp))); sum = csum_add(sum, csum_partial(data, len)); udp->uh_sum = csum_fold(sum); ip->ip_len = htons(hdrlen + len); ip->ip_sum = 0; ip->ip_sum = csum_fold(csum_partial(ip, sizeof(*ip))); #ifdef __APPLE__ ip->ip_len = hdrlen + len; #endif } static void fixup_udpv6(void *hdr, size_t hdrlen, const void *data, size_t len) { struct ip6_hdr *ip = hdr; struct udphdr *udp = hdr + sizeof(*ip); uint16_t udp_len = htons(sizeof(*udp) + len); if ((void *)&udp[1] > hdr + hdrlen) return; ip->ip6_plen = htons(sizeof(*udp) + len); udp->uh_sum = 0; udp->uh_ulen = udp_len; udp->uh_sum = csum_fold(csum_partial(hdr, sizeof(*ip) + sizeof(*udp))); #ifdef __APPLE__ ip->ip6_plen = sizeof(*udp) + len; #endif } static void fixup_ip_udp_header(void *hdr, size_t hdrlen, const void *data, size_t len) { if (hdrlen >= sizeof(struct ip6_hdr) + sizeof(struct udphdr)) fixup_udpv6(hdr, hdrlen, data, len); else if (hdrlen >= sizeof(struct ip) + sizeof(struct udphdr)) fixup_udpv4(hdr, hdrlen, data, len); } int sendto_rawudp(int fd, const void *addr, void *ip_hdr, size_t ip_hdrlen, const void *data, size_t len) { const struct sockaddr *sa = addr; struct iovec iov[2] = { { .iov_base = ip_hdr, .iov_len = ip_hdrlen }, { .iov_base = (void *)data, .iov_len = len } }; struct msghdr msg = { .msg_name = (void *)addr, .msg_iov = iov, .msg_iovlen = ARRAY_SIZE(iov), }; if (sa->sa_family == AF_INET6) msg.msg_namelen = sizeof(struct sockaddr_in6); else msg.msg_namelen = sizeof(struct sockaddr_in); fixup_ip_udp_header(ip_hdr, ip_hdrlen, data, len); return sendmsg(fd, &msg, 0); }