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_hex.c | 20 ++++--- ot_hex.h | 4 ++ ot_ip.c | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ot_ip.h | 33 +++++++++++ 4 files changed, 249 insertions(+), 9 deletions(-) create mode 100644 ot_ip.c create mode 100644 ot_ip.h diff --git a/ot_hex.c b/ot_hex.c index 17dd1ef..9581951 100644 --- a/ot_hex.c +++ b/ot_hex.c @@ -10,16 +10,18 @@ /* Opentracker */ #include "ot_hex.h" -static int fromhex(unsigned char c) { - if (c >= '0' && c <= '9') - return c - '0'; - c |= 0x20; /* fold upper case onto lower, digits are already handled */ - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; +int hex_value(int c) { + unsigned char u = (unsigned char)c; + + if (u >= '0' && u <= '9') + return u - '0'; + u |= 0x20; /* fold upper case onto lower, digits are already handled */ + if (u >= 'a' && u <= 'f') + return u - 'a' + 10; return -1; } -int hex_isdigit(int c) { return fromhex((unsigned char)c) >= 0; } +int hex_isdigit(int c) { return hex_value(c) >= 0; } char *hex_fromhash(char *dest, const uint8_t *hash) { static const char digits[] = "0123456789ABCDEF"; @@ -43,9 +45,9 @@ int hex_tohash(ot_hash hash, const char *src) { /* Tested one character at a time so that a string ending early stops us at its terminator rather than one byte past it */ - if ((high = fromhex((unsigned char)src[2 * i])) < 0) + if ((high = hex_value((unsigned char)src[2 * i])) < 0) return 0; - if ((low = fromhex((unsigned char)src[2 * i + 1])) < 0) + if ((low = hex_value((unsigned char)src[2 * i + 1])) < 0) return 0; decoded[i] = (uint8_t)((high << 4) | low); diff --git a/ot_hex.h b/ot_hex.h index cb2e86f..3b45b61 100644 --- a/ot_hex.h +++ b/ot_hex.h @@ -22,4 +22,8 @@ int hex_tohash(ot_hash hash, const char *src); /* Non-zero if c is a hexadecimal digit, in either case. */ int hex_isdigit(int c); +/* The value of a hexadecimal digit, or -1 if c is not one. Shared with the + address scanner so that both agree on what a digit is. */ +int hex_value(int c); + #endif 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); +} diff --git a/ot_ip.h b/ot_ip.h new file mode 100644 index 0000000..ac8964d --- /dev/null +++ b/ot_ip.h @@ -0,0 +1,33 @@ +/* This software was written by Dirk Engling + It is considered beerware. Prost. Skol. Cheers or whatever. + + $id$ */ + +#ifndef OT_IP_H__ +#define OT_IP_H__ + +#include +#include + +#include "trackerlogic.h" + +/* opentracker holds every address as sixteen bytes, writing IPv4 as the v4 + mapped form of RFC 4291 section 2.5.5.2. */ +extern const char ip_v4mapped_prefix[12]; + +static inline int ip_is_v4mapped(const void *ip) { return !memcmp(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix)); } + +/* Read an address and return how many characters it took, or zero if src + does not start with one. A plain IPv4 address is accepted and stored in + its v4 mapped form. + + The count lets the caller carry on where the address ended, which is how + "address:port" and "address/bits" are read. */ +size_t ip_scan(const char *src, ot_ip6 ip); + +/* Write an address and return how many characters it took. Nothing is + terminated, so a caller may keep appending. A v4 mapped address is written + as the plain dotted quad rather than "::ffff:1.2.3.4". */ +size_t ip_format(char *dest, const ot_ip6 ip); + +#endif -- cgit v1.3