/** @file * @brief Misc network utility functions * */ /* * Copyright (c) 2016 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(net_utils, CONFIG_NET_UTILS_LOG_LEVEL); #include #include #include #include #include #include #include #include #include #include #include #include #include char *net_sprint_addr(sa_family_t af, const void *addr) { #define NBUFS 3 static char buf[NBUFS][NET_IPV6_ADDR_LEN]; static int i; char *s = buf[++i % NBUFS]; return net_addr_ntop(af, addr, s, NET_IPV6_ADDR_LEN); } const char *net_verdict2str(enum net_verdict verdict) { if (verdict == NET_OK) { return "NET_OK"; } else if (verdict == NET_CONTINUE) { return "NET_CONTINUE"; } else if (verdict == NET_DROP) { return "NET_DROP"; } return ""; } const char *net_proto2str(int family, int proto) { if (family == AF_INET || family == AF_INET6) { switch (proto) { case IPPROTO_ICMP: return "ICMPv4"; case IPPROTO_TCP: return "TCP"; case IPPROTO_UDP: return "UDP"; case IPPROTO_ICMPV6: return "ICMPv6"; default: break; } } else if (family == AF_CAN) { switch (proto) { case CAN_RAW: return "CAN_RAW"; default: break; } } return "UNK_PROTO"; } char *net_byte_to_hex(char *ptr, uint8_t byte, char base, bool pad) { uint8_t high = (byte >> 4) & 0x0f; uint8_t low = byte & 0x0f; if (pad || (high > 0)) { *ptr++ = (high < 10) ? (char) (high + '0') : (char) (high - 10 + base); } *ptr++ = (low < 10) ? (char) (low + '0') : (char) (low - 10 + base); *ptr = '\0'; return ptr; } char *net_sprint_ll_addr_buf(const uint8_t *ll, uint8_t ll_len, char *buf, int buflen) { uint8_t i, len, blen; char *ptr = buf; if (ll == NULL) { return ""; } switch (ll_len) { case 8: len = 8U; break; case 6: len = 6U; break; case 2: len = 2U; break; default: len = 6U; break; } for (i = 0U, blen = buflen; i < len && blen > 0; i++) { ptr = net_byte_to_hex(ptr, (char)ll[i], 'A', true); *ptr++ = ':'; blen -= 3U; } if (!(ptr - buf)) { return NULL; } *(ptr - 1) = '\0'; return buf; } static int net_value_to_udec(char *buf, uint32_t value, int precision) { uint32_t divisor; int i; int temp; char *start = buf; divisor = 1000000000U; if (precision < 0) { precision = 1; } for (i = 9; i >= 0; i--, divisor /= 10U) { temp = value / divisor; value = value % divisor; if ((precision > i) || (temp != 0)) { precision = i; *buf++ = (char) (temp + '0'); } } *buf = 0; return buf - start; } char *z_impl_net_addr_ntop(sa_family_t family, const void *src, char *dst, size_t size) { struct in_addr *addr = NULL; struct in6_addr *addr6 = NULL; uint16_t *w = NULL; int i; uint8_t longest = 1U; int pos = -1; char delim = ':'; uint8_t zeros[8] = { 0 }; char *ptr = dst; int len = -1; uint16_t value; bool needcolon = false; bool mapped = false; if (family == AF_INET6) { addr6 = (struct in6_addr *)src; w = (uint16_t *)addr6->s6_addr16; len = 8; if (net_ipv6_addr_is_v4_mapped(addr6)) { mapped = true; } for (i = 0; i < 8; i++) { for (int j = i; j < 8; j++) { if (UNALIGNED_GET(&w[j]) != 0) { break; } zeros[i]++; } } for (i = 0; i < 8; i++) { if (zeros[i] > longest) { longest = zeros[i]; pos = i; } } if (longest == 1U) { pos = -1; } } else if (family == AF_INET) { addr = (struct in_addr *)src; len = 4; delim = '.'; } else { return NULL; } print_mapped: for (i = 0; i < len; i++) { /* IPv4 address a.b.c.d */ if (len == 4) { uint8_t l; value = (uint16_t)addr->s4_addr[i]; /* net_byte_to_udec() eats 0 */ if (value == 0U) { *ptr++ = '0'; *ptr++ = delim; continue; } l = net_value_to_udec(ptr, value, 0); ptr += l; *ptr++ = delim; continue; } if (mapped && (i > 5)) { delim = '.'; len = 4; addr = (struct in_addr *)(&addr6->s6_addr32[3]); *ptr++ = ':'; family = AF_INET; goto print_mapped; } /* IPv6 address */ if (i == pos) { if (needcolon || i == 0U) { *ptr++ = ':'; } *ptr++ = ':'; needcolon = false; i += (int)longest - 1; continue; } if (needcolon) { *ptr++ = ':'; } value = sys_be16_to_cpu(UNALIGNED_GET(&w[i])); uint8_t bh = value >> 8; uint8_t bl = value & 0xff; if (bh) { /* Convert high byte to hex without padding */ ptr = net_byte_to_hex(ptr, bh, 'a', false); /* Always pad the low byte, since high byte is non - zero */ ptr = net_byte_to_hex(ptr, bl, 'a', true); } else { /* For the case where the high byte is zero, only process the low byte * Do not pad the low byte, since high byte is zero */ ptr = net_byte_to_hex(ptr, bl, 'a', false); } needcolon = true; } if (!(ptr - dst)) { return NULL; } if (family == AF_INET) { *(ptr - 1) = '\0'; } else { *ptr = '\0'; } return dst; } #if defined(CONFIG_USERSPACE) char *z_vrfy_net_addr_ntop(sa_family_t family, const void *src, char *dst, size_t size) { char str[INET6_ADDRSTRLEN]; struct in6_addr addr6; struct in_addr addr4; char *out; const void *addr; K_OOPS(K_SYSCALL_MEMORY_WRITE(dst, size)); if (family == AF_INET) { K_OOPS(k_usermode_from_copy(&addr4, (const void *)src, sizeof(addr4))); addr = &addr4; } else if (family == AF_INET6) { K_OOPS(k_usermode_from_copy(&addr6, (const void *)src, sizeof(addr6))); addr = &addr6; } else { return 0; } out = z_impl_net_addr_ntop(family, addr, str, sizeof(str)); if (!out) { return 0; } K_OOPS(k_usermode_to_copy((void *)dst, str, MIN(size, sizeof(str)))); return dst; } #include #endif /* CONFIG_USERSPACE */ int z_impl_net_addr_pton(sa_family_t family, const char *src, void *dst) { if (family == AF_INET) { struct in_addr *addr = (struct in_addr *)dst; size_t i, len; len = strlen(src); for (i = 0; i < len; i++) { if (!(src[i] >= '0' && src[i] <= '9') && src[i] != '.') { return -EINVAL; } } (void)memset(addr, 0, sizeof(struct in_addr)); for (i = 0; i < sizeof(struct in_addr); i++) { char *endptr; addr->s4_addr[i] = strtol(src, &endptr, 10); src = ++endptr; } } else if (family == AF_INET6) { /* If the string contains a '.', it means it's of the form * X:X:X:X:X:X:x.x.x.x, and contains only 6 16-bit pieces */ int expected_groups = strchr(src, '.') ? 6 : 8; struct in6_addr *addr = (struct in6_addr *)dst; int i, len; if (*src == ':') { /* Ignore a leading colon, makes parsing neater */ src++; } len = strlen(src); for (i = 0; i < len; i++) { if (!(src[i] >= '0' && src[i] <= '9') && !(src[i] >= 'A' && src[i] <= 'F') && !(src[i] >= 'a' && src[i] <= 'f') && src[i] != '.' && src[i] != ':') { return -EINVAL; } } for (i = 0; i < expected_groups; i++) { char *tmp; if (!src || *src == '\0') { return -EINVAL; } if (*src != ':') { /* Normal IPv6 16-bit piece */ UNALIGNED_PUT(htons(strtol(src, NULL, 16)), &addr->s6_addr16[i]); src = strchr(src, ':'); if (src) { src++; } else { if (i < expected_groups - 1) { return -EINVAL; } } continue; } /* Two colons in a row */ for (; i < expected_groups; i++) { UNALIGNED_PUT(0, &addr->s6_addr16[i]); } tmp = strrchr(src, ':'); if (src == tmp && (expected_groups == 6 || !src[1])) { src++; break; } if (expected_groups == 6) { /* we need to drop the trailing * colon since it's between the * ipv6 and ipv4 addresses, rather than being * a part of the ipv6 address */ tmp--; } /* Calculate the amount of skipped zeros */ i = expected_groups - 1; do { if (*tmp == ':') { i--; } if (i < 0) { return -EINVAL; } } while (tmp-- != src); src++; } if (expected_groups == 6) { /* Parse the IPv4 part */ for (i = 0; i < 4; i++) { if (!src || !*src) { return -EINVAL; } addr->s6_addr[12 + i] = strtol(src, NULL, 10); src = strchr(src, '.'); if (src) { src++; } else { if (i < 3) { return -EINVAL; } } } } } else { return -EINVAL; } return 0; } #if defined(CONFIG_USERSPACE) int z_vrfy_net_addr_pton(sa_family_t family, const char *src, void *dst) { char str[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)] = {}; struct in6_addr addr6; struct in_addr addr4; void *addr; size_t size; int err; if (family == AF_INET) { size = sizeof(struct in_addr); addr = &addr4; } else if (family == AF_INET6) { size = sizeof(struct in6_addr); addr = &addr6; } else { return -EINVAL; } if (k_usermode_string_copy(str, (char *)src, sizeof(str)) != 0) { return -EINVAL; } K_OOPS(K_SYSCALL_MEMORY_WRITE(dst, size)); err = z_impl_net_addr_pton(family, str, addr); if (err) { return err; } K_OOPS(k_usermode_to_copy((void *)dst, addr, size)); return 0; } #include #endif /* CONFIG_USERSPACE */ #ifdef CONFIG_LITTLE_ENDIAN #define CHECKSUM_BIG_ENDIAN 0 #else #define CHECKSUM_BIG_ENDIAN 1 #endif static uint16_t offset_based_swap8(const uint8_t *data) { uint16_t data16 = (uint16_t)*data; if (((uintptr_t)(data) & 1) == CHECKSUM_BIG_ENDIAN) { return data16; } else { return data16 << 8; } } /* Word based checksum calculation based on: * https://blogs.igalia.com/dpino/2018/06/14/fast-checksum-computation/ * It’s not necessary to add octets as 16-bit words. Due to the associative property of addition, * it is possible to do parallel addition using larger word sizes such as 32-bit or 64-bit words. * In those cases the variable that stores the accumulative sum has to be bigger too. * Once the sum is computed a final step folds the sum to a 16-bit word (adding carry if any). */ uint16_t calc_chksum(uint16_t sum_in, const uint8_t *data, size_t len) { uint64_t sum; uint32_t *p; size_t i = 0; size_t pending = len; int odd_start = ((uintptr_t)data & 0x01); /* Sum in is in host endianness, working order endianness is both dependent on endianness * and the offset of starting */ if (odd_start == CHECKSUM_BIG_ENDIAN) { sum = BSWAP_16(sum_in); } else { sum = sum_in; } /* Process up to 3 data elements up front, so the data is aligned further down the line */ if ((((uintptr_t)data & 0x01) != 0) && (pending >= 1)) { sum += offset_based_swap8(data); data++; pending--; } if ((((uintptr_t)data & 0x02) != 0) && (pending >= sizeof(uint16_t))) { pending -= sizeof(uint16_t); sum = sum + *((uint16_t *)data); data += sizeof(uint16_t); } p = (uint32_t *)data; /* Do loop unrolling for the very large data sets */ while (pending >= sizeof(uint32_t) * 4) { uint64_t sum_a = p[i]; uint64_t sum_b = p[i + 1]; pending -= sizeof(uint32_t) * 4; sum_a += p[i + 2]; sum_b += p[i + 3]; i += 4; sum += sum_a + sum_b; } while (pending >= sizeof(uint32_t)) { pending -= sizeof(uint32_t); sum = sum + p[i++]; } data = (uint8_t *)(p + i); if (pending >= 2) { pending -= sizeof(uint16_t); sum = sum + *((uint16_t *)data); data += sizeof(uint16_t); } if (pending == 1) { sum += offset_based_swap8(data); } /* Fold sum into 16-bit word. */ while (sum >> 16) { sum = (sum & 0xffff) + (sum >> 16); } /* Sum in is in host endianness, working order endianness is both dependent on endianness * and the offset of starting */ if (odd_start == CHECKSUM_BIG_ENDIAN) { return BSWAP_16((uint16_t)sum); } else { return sum; } } static inline uint16_t pkt_calc_chksum(struct net_pkt *pkt, uint16_t sum) { struct net_pkt_cursor *cur = &pkt->cursor; size_t len; if (!cur->buf || !cur->pos) { return sum; } len = cur->buf->len - (cur->pos - cur->buf->data); while (cur->buf) { sum = calc_chksum(sum, cur->pos, len); cur->buf = cur->buf->frags; if (!cur->buf || !cur->buf->len) { break; } cur->pos = cur->buf->data; if (len % 2) { sum += *cur->pos; if (sum < *cur->pos) { sum++; } cur->pos++; len = cur->buf->len - 1; } else { len = cur->buf->len; } } return sum; } #if defined(CONFIG_NET_NATIVE_IP) uint16_t net_calc_chksum(struct net_pkt *pkt, uint8_t proto) { size_t len = 0U; uint16_t sum = 0U; struct net_pkt_cursor backup; bool ow; if (IS_ENABLED(CONFIG_NET_IPV4) && net_pkt_family(pkt) == AF_INET) { if (proto != IPPROTO_ICMP && proto != IPPROTO_IGMP) { len = 2 * sizeof(struct in_addr); sum = net_pkt_get_len(pkt) - net_pkt_ip_hdr_len(pkt) - net_pkt_ipv4_opts_len(pkt) + proto; } } else if (IS_ENABLED(CONFIG_NET_IPV6) && net_pkt_family(pkt) == AF_INET6) { len = 2 * sizeof(struct in6_addr); sum = net_pkt_get_len(pkt) - net_pkt_ip_hdr_len(pkt) - net_pkt_ipv6_ext_len(pkt) + proto; } else { NET_DBG("Unknown protocol family %d", net_pkt_family(pkt)); return 0; } net_pkt_cursor_backup(pkt, &backup); net_pkt_cursor_init(pkt); ow = net_pkt_is_being_overwritten(pkt); net_pkt_set_overwrite(pkt, true); net_pkt_skip(pkt, net_pkt_ip_hdr_len(pkt) - len); sum = calc_chksum(sum, pkt->cursor.pos, len); net_pkt_skip(pkt, len + net_pkt_ip_opts_len(pkt)); sum = pkt_calc_chksum(pkt, sum); sum = (sum == 0U) ? 0xffff : htons(sum); net_pkt_cursor_restore(pkt, &backup); net_pkt_set_overwrite(pkt, ow); return ~sum; } #endif #if defined(CONFIG_NET_NATIVE_IPV4) uint16_t net_calc_chksum_ipv4(struct net_pkt *pkt) { uint16_t sum; sum = calc_chksum(0, pkt->buffer->data, net_pkt_ip_hdr_len(pkt) + net_pkt_ipv4_opts_len(pkt)); sum = (sum == 0U) ? 0xffff : htons(sum); return ~sum; } #endif /* CONFIG_NET_NATIVE_IPV4 */ #if defined(CONFIG_NET_IPV4_IGMP) uint16_t net_calc_chksum_igmp(struct net_pkt *pkt) { return net_calc_chksum(pkt, IPPROTO_IGMP); } #endif /* CONFIG_NET_IPV4_IGMP */ #if defined(CONFIG_NET_IP) static bool convert_port(const char *buf, uint16_t *port) { unsigned long tmp; char *endptr; tmp = strtoul(buf, &endptr, 10); if ((endptr == buf && tmp == 0) || !(*buf != '\0' && *endptr == '\0') || ((unsigned long)(unsigned short)tmp != tmp)) { return false; } *port = tmp; return true; } #endif /* CONFIG_NET_IP */ #if defined(CONFIG_NET_IPV6) static bool parse_ipv6(const char *str, size_t str_len, struct sockaddr *addr, bool has_port) { char *ptr = NULL; struct in6_addr *addr6; char ipaddr[INET6_ADDRSTRLEN + 1]; int end, len, ret, i; uint16_t port; len = MIN(INET6_ADDRSTRLEN, str_len); for (i = 0; i < len; i++) { if (!str[i]) { len = i; break; } } if (has_port) { /* IPv6 address with port number */ ptr = memchr(str, ']', len); if (!ptr) { return false; } end = MIN(len, ptr - (str + 1)); memcpy(ipaddr, str + 1, end); } else { end = len; memcpy(ipaddr, str, end); } ipaddr[end] = '\0'; addr6 = &net_sin6(addr)->sin6_addr; ret = net_addr_pton(AF_INET6, ipaddr, addr6); if (ret < 0) { return false; } net_sin6(addr)->sin6_family = AF_INET6; if (!has_port) { return true; } if ((ptr + 1) < (str + str_len) && *(ptr + 1) == ':') { /* -1 as end does not contain first [ * -2 as pointer is advanced by 2, skipping ]: */ len = str_len - end - 1 - 2; ptr += 2; for (i = 0; i < len; i++) { if (!ptr[i]) { len = i; break; } } /* Re-use the ipaddr buf for port conversion */ memcpy(ipaddr, ptr, len); ipaddr[len] = '\0'; ret = convert_port(ipaddr, &port); if (!ret) { return false; } net_sin6(addr)->sin6_port = htons(port); NET_DBG("IPv6 host %s port %d", net_addr_ntop(AF_INET6, addr6, ipaddr, sizeof(ipaddr) - 1), port); } else { NET_DBG("IPv6 host %s", net_addr_ntop(AF_INET6, addr6, ipaddr, sizeof(ipaddr) - 1)); } return true; } #else static inline bool parse_ipv6(const char *str, size_t str_len, struct sockaddr *addr, bool has_port) { return false; } #endif /* CONFIG_NET_IPV6 */ #if defined(CONFIG_NET_IPV4) static bool parse_ipv4(const char *str, size_t str_len, struct sockaddr *addr, bool has_port) { char *ptr = NULL; char ipaddr[NET_IPV4_ADDR_LEN + 1]; struct in_addr *addr4; int end, len, ret, i; uint16_t port; len = MIN(NET_IPV4_ADDR_LEN, str_len); for (i = 0; i < len; i++) { if (!str[i]) { len = i; break; } } if (has_port) { /* IPv4 address with port number */ ptr = memchr(str, ':', len); if (!ptr) { return false; } end = MIN(len, ptr - str); } else { end = len; } memcpy(ipaddr, str, end); ipaddr[end] = '\0'; addr4 = &net_sin(addr)->sin_addr; ret = net_addr_pton(AF_INET, ipaddr, addr4); if (ret < 0) { return false; } net_sin(addr)->sin_family = AF_INET; if (!has_port) { return true; } memcpy(ipaddr, ptr + 1, str_len - end - 1); ipaddr[str_len - end - 1] = '\0'; ret = convert_port(ipaddr, &port); if (!ret) { return false; } net_sin(addr)->sin_port = htons(port); NET_DBG("IPv4 host %s port %d", net_addr_ntop(AF_INET, addr4, ipaddr, sizeof(ipaddr) - 1), port); return true; } #else static inline bool parse_ipv4(const char *str, size_t str_len, struct sockaddr *addr, bool has_port) { return false; } #endif /* CONFIG_NET_IPV4 */ bool net_ipaddr_parse(const char *str, size_t str_len, struct sockaddr *addr) { int i, count; if (!str || str_len == 0) { return false; } /* We cannot accept empty string here */ if (*str == '\0') { return false; } if (*str == '[') { return parse_ipv6(str, str_len, addr, true); } for (count = i = 0; i < str_len && str[i]; i++) { if (str[i] == ':') { count++; } } if (count == 1) { return parse_ipv4(str, str_len, addr, true); } #if defined(CONFIG_NET_IPV4) && defined(CONFIG_NET_IPV6) if (!parse_ipv4(str, str_len, addr, false)) { return parse_ipv6(str, str_len, addr, false); } return true; #endif #if defined(CONFIG_NET_IPV4) && !defined(CONFIG_NET_IPV6) return parse_ipv4(str, str_len, addr, false); #endif #if defined(CONFIG_NET_IPV6) && !defined(CONFIG_NET_IPV4) return parse_ipv6(str, str_len, addr, false); #endif return false; } int net_port_set_default(struct sockaddr *addr, uint16_t default_port) { if (IS_ENABLED(CONFIG_NET_IPV4) && addr->sa_family == AF_INET && net_sin(addr)->sin_port == 0) { net_sin(addr)->sin_port = htons(default_port); } else if (IS_ENABLED(CONFIG_NET_IPV6) && addr->sa_family == AF_INET6 && net_sin6(addr)->sin6_port == 0) { net_sin6(addr)->sin6_port = htons(default_port); } else if ((IS_ENABLED(CONFIG_NET_IPV4) && addr->sa_family == AF_INET) || (IS_ENABLED(CONFIG_NET_IPV6) && addr->sa_family == AF_INET6)) { ; /* Port is already set */ } else { LOG_ERR("Unknown address family"); return -EINVAL; } return 0; } int net_bytes_from_str(uint8_t *buf, int buf_len, const char *src) { size_t i; size_t src_len = strlen(src); char *endptr; for (i = 0U; i < src_len; i++) { if (!isxdigit((unsigned char)src[i]) && src[i] != ':') { return -EINVAL; } } (void)memset(buf, 0, buf_len); for (i = 0U; i < (size_t)buf_len; i++) { buf[i] = (uint8_t)strtol(src, &endptr, 16); src = ++endptr; } return 0; } const char *net_family2str(sa_family_t family) { switch (family) { case AF_UNSPEC: return "AF_UNSPEC"; case AF_INET: return "AF_INET"; case AF_INET6: return "AF_INET6"; case AF_PACKET: return "AF_PACKET"; case AF_CAN: return "AF_CAN"; } return NULL; } const struct in_addr *net_ipv4_unspecified_address(void) { static const struct in_addr addr; return &addr; } const struct in_addr *net_ipv4_broadcast_address(void) { static const struct in_addr addr = { { { 255, 255, 255, 255 } } }; return &addr; } /* IPv6 wildcard and loopback address defined by RFC2553 */ const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT; const struct in6_addr *net_ipv6_unspecified_address(void) { return &in6addr_any; }