diff options
| author | Dirk Engling <erdgeist@erdgeist.org> | 2026-10-24 17:21:47 +0200 |
|---|---|---|
| committer | Dirk Engling <erdgeist@erdgeist.org> | 2026-10-24 17:21:47 +0200 |
| commit | 60f55f34eeec70842b21accdd7fc0dc5d89493a2 (patch) | |
| tree | 21a50b9d936df6b4348d6d1c95681a4269165804 /ot_ip.c | |
| parent | ea9c9a1766f1ce716c359fc04628855293fcffbd (diff) | |
Add an ip address parser and formatter for all formats opentracker needs
Diffstat (limited to 'ot_ip.c')
| -rw-r--r-- | ot_ip.c | 201 |
1 files changed, 201 insertions, 0 deletions
| @@ -0,0 +1,201 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. | ||
| 3 | |||
| 4 | $id$ */ | ||
| 5 | |||
| 6 | /* System */ | ||
| 7 | #include <stdint.h> | ||
| 8 | #include <string.h> | ||
| 9 | |||
| 10 | /* Opentracker */ | ||
| 11 | #include "ot_hex.h" | ||
| 12 | #include "ot_ip.h" | ||
| 13 | |||
| 14 | const char ip_v4mapped_prefix[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}; | ||
| 15 | |||
| 16 | /* The dotted quad is only ever reached through the v4 mapped form, so both | ||
| 17 | halves of it stay in here */ | ||
| 18 | |||
| 19 | static size_t ip4_format(char *dest, const uint8_t *ip) { | ||
| 20 | char *d = dest; | ||
| 21 | int i; | ||
| 22 | |||
| 23 | for (i = 0; i < 4; ++i) { | ||
| 24 | unsigned int octet = ip[i]; | ||
| 25 | |||
| 26 | if (i) | ||
| 27 | *d++ = '.'; | ||
| 28 | if (octet >= 100) | ||
| 29 | *d++ = (char)('0' + octet / 100); | ||
| 30 | if (octet >= 10) | ||
| 31 | *d++ = (char)('0' + (octet / 10) % 10); | ||
| 32 | *d++ = (char)('0' + octet % 10); | ||
| 33 | } | ||
| 34 | |||
| 35 | return (size_t)(d - dest); | ||
| 36 | } | ||
| 37 | |||
| 38 | static size_t ip4_scan(const char *src, uint8_t ip[4]) { | ||
| 39 | uint8_t octets[4]; | ||
| 40 | const char *s = src; | ||
| 41 | int i; | ||
| 42 | |||
| 43 | for (i = 0; i < 4; ++i) { | ||
| 44 | unsigned int value = 0; | ||
| 45 | int digits = 0; | ||
| 46 | |||
| 47 | if (i && *s++ != '.') | ||
| 48 | return 0; | ||
| 49 | |||
| 50 | /* Leading zeros are tolerated, a value beyond one octet is not */ | ||
| 51 | while (*s >= '0' && *s <= '9' && digits < 4) { | ||
| 52 | value = value * 10 + (unsigned int)(*s++ - '0'); | ||
| 53 | ++digits; | ||
| 54 | } | ||
| 55 | if (!digits || value > 255) | ||
| 56 | return 0; | ||
| 57 | |||
| 58 | octets[i] = (uint8_t)value; | ||
| 59 | } | ||
| 60 | |||
| 61 | memcpy(ip, octets, sizeof(octets)); | ||
| 62 | return (size_t)(s - src); | ||
| 63 | } | ||
| 64 | |||
| 65 | 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]; } | ||
| 66 | |||
| 67 | size_t ip_format(char *dest, const ot_ip6 ip) { | ||
| 68 | static const char hexdigit[] = "0123456789abcdef"; | ||
| 69 | char *d = dest; | ||
| 70 | int best_start = 0, best_len = 0, i = 0; | ||
| 71 | |||
| 72 | if (ip_is_v4mapped(ip)) | ||
| 73 | return ip4_format(dest, (const uint8_t *)ip + sizeof(ip_v4mapped_prefix)); | ||
| 74 | |||
| 75 | /* RFC 5952 4.2: the longest run of zero groups becomes "::", never a run | ||
| 76 | of one, and the first of two equal runs wins */ | ||
| 77 | while (i < 8) { | ||
| 78 | int j = i; | ||
| 79 | |||
| 80 | if (group_at(ip, i)) { | ||
| 81 | ++i; | ||
| 82 | continue; | ||
| 83 | } | ||
| 84 | while (j < 8 && !group_at(ip, j)) | ||
| 85 | ++j; | ||
| 86 | if (j - i > best_len) { | ||
| 87 | best_len = j - i; | ||
| 88 | best_start = i; | ||
| 89 | } | ||
| 90 | i = j; | ||
| 91 | } | ||
| 92 | if (best_len < 2) | ||
| 93 | best_len = 0; | ||
| 94 | |||
| 95 | for (i = 0; i < 8; ++i) { | ||
| 96 | unsigned int group; | ||
| 97 | int shift = 12; | ||
| 98 | |||
| 99 | if (best_len && i == best_start) { | ||
| 100 | *d++ = ':'; | ||
| 101 | if (i + best_len == 8) | ||
| 102 | *d++ = ':'; /* nothing follows, so close the run */ | ||
| 103 | i += best_len - 1; | ||
| 104 | continue; | ||
| 105 | } | ||
| 106 | |||
| 107 | if (i) | ||
| 108 | *d++ = ':'; | ||
| 109 | |||
| 110 | group = group_at(ip, i); | ||
| 111 | while (shift && !(group >> shift)) | ||
| 112 | shift -= 4; /* RFC 5952 4.1: no leading zeros */ | ||
| 113 | for (;; shift -= 4) { | ||
| 114 | *d++ = hexdigit[(group >> shift) & 15]; | ||
| 115 | if (!shift) | ||
| 116 | break; | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | return (size_t)(d - dest); | ||
| 121 | } | ||
| 122 | |||
| 123 | size_t ip_scan(const char *src, ot_ip6 ip) { | ||
| 124 | uint8_t prefix[16], suffix[16]; | ||
| 125 | size_t prefixlen = 0, suffixlen = 0; | ||
| 126 | const char *s = src; | ||
| 127 | int have_gap = 0; | ||
| 128 | int need_colon = 0; | ||
| 129 | |||
| 130 | /* A plain IPv4 address is an address too, stored v4 mapped */ | ||
| 131 | { | ||
| 132 | size_t consumed = ip4_scan(src, prefix); | ||
| 133 | if (consumed) { | ||
| 134 | memcpy(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix)); | ||
| 135 | memcpy(ip + sizeof(ip_v4mapped_prefix), prefix, 4); | ||
| 136 | return consumed; | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | for (;;) { | ||
| 141 | unsigned int value = 0; | ||
| 142 | int digits = 0, nibble; | ||
| 143 | uint8_t *into = have_gap ? suffix : prefix; | ||
| 144 | size_t *fill = have_gap ? &suffixlen : &prefixlen; | ||
| 145 | |||
| 146 | /* RFC 4291 2.2: one run of zero groups may be written "::" */ | ||
| 147 | if (s[0] == ':' && s[1] == ':') { | ||
| 148 | if (have_gap) | ||
| 149 | return 0; | ||
| 150 | have_gap = 1; | ||
| 151 | need_colon = 0; | ||
| 152 | s += 2; | ||
| 153 | continue; | ||
| 154 | } | ||
| 155 | |||
| 156 | if (need_colon) { | ||
| 157 | if (*s != ':') | ||
| 158 | break; | ||
| 159 | ++s; | ||
| 160 | } | ||
| 161 | |||
| 162 | /* RFC 4291 2.2 form 3: the final two groups may be a dotted quad */ | ||
| 163 | if (prefixlen + suffixlen + 4 <= 16) { | ||
| 164 | size_t consumed = ip4_scan(s, into + *fill); | ||
| 165 | if (consumed) { | ||
| 166 | *fill += 4; | ||
| 167 | s += consumed; | ||
| 168 | break; | ||
| 169 | } | ||
| 170 | } | ||
| 171 | |||
| 172 | while (digits < 4 && (nibble = hex_value(*s)) >= 0) { | ||
| 173 | value = value * 16 + (unsigned int)nibble; | ||
| 174 | ++digits; | ||
| 175 | ++s; | ||
| 176 | } | ||
| 177 | if (!digits) { | ||
| 178 | if (need_colon) | ||
| 179 | --s; /* leave the separator we ate for the caller */ | ||
| 180 | break; | ||
| 181 | } | ||
| 182 | |||
| 183 | if (prefixlen + suffixlen + 2 > 16) | ||
| 184 | return 0; | ||
| 185 | |||
| 186 | into[(*fill)++] = (uint8_t)(value >> 8); | ||
| 187 | into[(*fill)++] = (uint8_t)value; | ||
| 188 | need_colon = 1; | ||
| 189 | } | ||
| 190 | |||
| 191 | if (!prefixlen && !suffixlen && !have_gap) | ||
| 192 | return 0; | ||
| 193 | if (!have_gap && prefixlen + suffixlen != 16) | ||
| 194 | return 0; /* without "::" every group has to be spelled out */ | ||
| 195 | |||
| 196 | memset(ip, 0, 16); | ||
| 197 | memcpy(ip, prefix, prefixlen); | ||
| 198 | memcpy(ip + 16 - suffixlen, suffix, suffixlen); | ||
| 199 | |||
| 200 | return (size_t)(s - src); | ||
| 201 | } | ||
