From 60f55f34eeec70842b21accdd7fc0dc5d89493a2 Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Sat, 24 Oct 2026 17:21:47 +0200 Subject: Add an ip address parser and formatter for all formats opentracker needs --- ot_ip.c | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 ot_ip.c (limited to 'ot_ip.c') diff --git a/ot_ip.c b/ot_ip.c new file mode 100644 index 0000000..8cde7d2 --- /dev/null +++ b/ot_ip.c @@ -0,0 +1,201 @@ +/* This software was written by Dirk Engling + It is considered beerware. Prost. Skol. Cheers or whatever. + + $id$ */ + +/* System */ +#include +#include + +/* Opentracker */ +#include "ot_hex.h" +#include "ot_ip.h" + +const char ip_v4mapped_prefix[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}; + +/* The dotted quad is only ever reached through the v4 mapped form, so both + halves of it stay in here */ + +static size_t ip4_format(char *dest, const uint8_t *ip) { + char *d = dest; + int i; + + for (i = 0; i < 4; ++i) { + unsigned int octet = ip[i]; + + if (i) + *d++ = '.'; + if (octet >= 100) + *d++ = (char)('0' + octet / 100); + if (octet >= 10) + *d++ = (char)('0' + (octet / 10) % 10); + *d++ = (char)('0' + octet % 10); + } + + return (size_t)(d - dest); +} + +static size_t ip4_scan(const char *src, uint8_t ip[4]) { + uint8_t octets[4]; + const char *s = src; + int i; + + for (i = 0; i < 4; ++i) { + unsigned int value = 0; + int digits = 0; + + if (i && *s++ != '.') + return 0; + + /* Leading zeros are tolerated, a value beyond one octet is not */ + while (*s >= '0' && *s <= '9' && digits < 4) { + value = value * 10 + (unsigned int)(*s++ - '0'); + ++digits; + } + if (!digits || value > 255) + return 0; + + octets[i] = (uint8_t)value; + } + + memcpy(ip, octets, sizeof(octets)); + return (size_t)(s - src); +} + +static unsigned int group_at(const char *ip, int n) { return ((unsigned int)(uint8_t)ip[2 * n] << 8) | (uint8_t)ip[2 * n + 1]; } + +size_t ip_format(char *dest, const ot_ip6 ip) { + static const char hexdigit[] = "0123456789abcdef"; + char *d = dest; + int best_start = 0, best_len = 0, i = 0; + + if (ip_is_v4mapped(ip)) + return ip4_format(dest, (const uint8_t *)ip + sizeof(ip_v4mapped_prefix)); + + /* RFC 5952 4.2: the longest run of zero groups becomes "::", never a run + of one, and the first of two equal runs wins */ + while (i < 8) { + int j = i; + + if (group_at(ip, i)) { + ++i; + continue; + } + while (j < 8 && !group_at(ip, j)) + ++j; + if (j - i > best_len) { + best_len = j - i; + best_start = i; + } + i = j; + } + if (best_len < 2) + best_len = 0; + + for (i = 0; i < 8; ++i) { + unsigned int group; + int shift = 12; + + if (best_len && i == best_start) { + *d++ = ':'; + if (i + best_len == 8) + *d++ = ':'; /* nothing follows, so close the run */ + i += best_len - 1; + continue; + } + + if (i) + *d++ = ':'; + + group = group_at(ip, i); + while (shift && !(group >> shift)) + shift -= 4; /* RFC 5952 4.1: no leading zeros */ + for (;; shift -= 4) { + *d++ = hexdigit[(group >> shift) & 15]; + if (!shift) + break; + } + } + + return (size_t)(d - dest); +} + +size_t ip_scan(const char *src, ot_ip6 ip) { + uint8_t prefix[16], suffix[16]; + size_t prefixlen = 0, suffixlen = 0; + const char *s = src; + int have_gap = 0; + int need_colon = 0; + + /* A plain IPv4 address is an address too, stored v4 mapped */ + { + size_t consumed = ip4_scan(src, prefix); + if (consumed) { + memcpy(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix)); + memcpy(ip + sizeof(ip_v4mapped_prefix), prefix, 4); + return consumed; + } + } + + for (;;) { + unsigned int value = 0; + int digits = 0, nibble; + uint8_t *into = have_gap ? suffix : prefix; + size_t *fill = have_gap ? &suffixlen : &prefixlen; + + /* RFC 4291 2.2: one run of zero groups may be written "::" */ + if (s[0] == ':' && s[1] == ':') { + if (have_gap) + return 0; + have_gap = 1; + need_colon = 0; + s += 2; + continue; + } + + if (need_colon) { + if (*s != ':') + break; + ++s; + } + + /* RFC 4291 2.2 form 3: the final two groups may be a dotted quad */ + if (prefixlen + suffixlen + 4 <= 16) { + size_t consumed = ip4_scan(s, into + *fill); + if (consumed) { + *fill += 4; + s += consumed; + break; + } + } + + while (digits < 4 && (nibble = hex_value(*s)) >= 0) { + value = value * 16 + (unsigned int)nibble; + ++digits; + ++s; + } + if (!digits) { + if (need_colon) + --s; /* leave the separator we ate for the caller */ + break; + } + + if (prefixlen + suffixlen + 2 > 16) + return 0; + + into[(*fill)++] = (uint8_t)(value >> 8); + into[(*fill)++] = (uint8_t)value; + need_colon = 1; + } + + if (!prefixlen && !suffixlen && !have_gap) + return 0; + if (!have_gap && prefixlen + suffixlen != 16) + return 0; /* without "::" every group has to be spelled out */ + + memset(ip, 0, 16); + memcpy(ip, prefix, prefixlen); + memcpy(ip + 16 - suffixlen, suffix, suffixlen); + + return (size_t)(s - src); +} -- cgit v1.3