hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

ip.ha (8887B)


      1 // License: MPL-2.0
      2 // (c) 2022 Alexey Yerin <yyp@disroot.org>
      3 // (c) 2021 Bor Grošelj Simić <bor.groseljsimic@telemach.net>
      4 // (c) 2021 Byron Torres <b@torresjrjr.com>
      5 // (c) 2021 Drew DeVault <sir@cmpwn.com>
      6 // (c) 2021 Eyal Sawady <ecs@d2evs.net>
      7 // (c) 2021 Miccah Castorina <contact@miccah.io>
      8 // (c) 2021 Mykyta Holubakha <hilobakho@gmail.com>
      9 // (c) 2021 Thomas Bracht Laumann Jespersen <t@laumann.xyz>
     10 use io;
     11 use strio;
     12 use fmt;
     13 use bytes;
     14 use rt;
     15 use strconv;
     16 use strings;
     17 
     18 // An IPv4 address.
     19 export type addr4 = [4]u8;
     20 
     21 // An IPv6 address.
     22 export type addr6 = [16]u8;
     23 
     24 // An IP address.
     25 export type addr = (addr4 | addr6);
     26 
     27 // An IP subnet.
     28 export type subnet = struct {
     29 	addr: addr,
     30 	mask: addr,
     31 };
     32 
     33 // An IPv4 address which represents "any" address, i.e. "0.0.0.0". Binding to
     34 // this address will listen on all available IPv4 interfaces on most systems.
     35 export const ANY_V4: addr4 = [0, 0, 0, 0];
     36 
     37 // An IPv6 address which represents "any" address, i.e. "::". Binding to this
     38 // address will listen on all available IPv6 interfaces on most systems.
     39 export const ANY_V6: addr6 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
     40 
     41 // An IPv4 address which represents the loopback address, i.e. "127.0.0.1".
     42 export const LOCAL_V4: addr4 = [127, 0, 0, 1];
     43 
     44 // An IPv6 address which represents the loopback address, i.e. "::1".
     45 export const LOCAL_V6: addr6 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
     46 
     47 // Invalid parse result.
     48 export type invalid = !void;
     49 
     50 // Test if two [[addr]]s are equal.
     51 export fn equal(l: addr, r: addr) bool = {
     52 	match (l) {
     53 	case let l: addr4 =>
     54 		if (!(r is addr4)) {
     55 			return false;
     56 		};
     57 		let r = r as addr4;
     58 		return bytes::equal(l, r);
     59 	case let l: addr6 =>
     60 		if (!(r is addr6)) {
     61 			return false;
     62 		};
     63 		let r = r as addr6;
     64 		return bytes::equal(l, r);
     65 	};
     66 };
     67 
     68 fn parsev4(st: str) (addr4 | invalid) = {
     69 	let ret: addr4 = [0...];
     70 	let tok = strings::tokenize(st, ".");
     71 	let i = 0z;
     72 	for (i < 4; i += 1) {
     73 		let s = wanttoken(&tok)?;
     74 		if (len(s) != 1 && strings::hasprefix(s, "0")) {
     75 			return invalid;
     76 		};
     77 		ret[i] = match (strconv::stou8(s)) {
     78 		case let term: u8 =>
     79 			yield term;
     80 		case =>
     81 			return invalid;
     82 		};
     83 	};
     84 	if (i < 4 || !(strings::next_token(&tok) is void)) {
     85 		return invalid;
     86 	};
     87 	return ret;
     88 };
     89 
     90 fn parsev6(st: str) (addr6 | invalid) = {
     91 	let ret: addr6 = [0...];
     92 	let tok = strings::tokenize(st, ":");
     93 	if (st == "::") {
     94 		return ret;
     95 	};
     96 	let ells = -1;
     97 	if (strings::hasprefix(st, "::")) {
     98 		wanttoken(&tok)?;
     99 		wanttoken(&tok)?;
    100 		ells = 0;
    101 	} else if (strings::hasprefix(st, ":")) {
    102 		return invalid;
    103 	};
    104 	let i = 0;
    105 	for (i < 16) {
    106 		let s = match (strings::next_token(&tok)) {
    107 		case let s: str =>
    108 			yield s;
    109 		case void =>
    110 			break;
    111 		};
    112 		if (s == "") {
    113 			if (ells != -1) {
    114 				return invalid;
    115 			};
    116 			ells = i;
    117 			continue;
    118 		};
    119 		let val = strconv::stou16b(s, 16);
    120 		if (val is u16) {
    121 			let v = val as u16;
    122 			ret[i] = (v >> 8): u8;
    123 			i += 1;
    124 			ret[i] = v: u8;
    125 			i += 1;
    126 			continue;
    127 		} else {
    128 			let v4 = parsev4(s)?;
    129 			rt::memcpy(&ret[i], &v4, 4);
    130 			i += 4;
    131 			break;
    132 		};
    133 	};
    134 	if (!(strings::next_token(&tok) is void)) {
    135 		return invalid;
    136 	};
    137 	if (ells >= 0) {
    138 		if (i >= 15) {
    139 			return invalid;
    140 		};
    141 		rt::memcpy(
    142 			&ret[16 - (i - ells)],
    143 			&ret[ells], (i - ells): size);
    144 		rt::memset(&ret[ells], 0, (i - ells): size);
    145 	} else {
    146 		if (i != 16)
    147 			return invalid;
    148 	};
    149 
    150 	return ret;
    151 };
    152 
    153 
    154 // Parses an IP address.
    155 export fn parse(s: str) (addr | invalid) = {
    156 	match (parsev4(s)) {
    157 	case let v4: addr4 =>
    158 		return v4;
    159 	case invalid => void;
    160 	};
    161 	match (parsev6(s)) {
    162 	case let v6: addr6 =>
    163 		return v6;
    164 	case invalid => void;
    165 	};
    166 	return invalid;
    167 };
    168 
    169 fn fmtv4(s: io::handle, a: addr4) (io::error | size) = {
    170 	let ret = 0z;
    171 	for (let i = 0; i < 4; i += 1) {
    172 		if (i > 0) {
    173 			ret += fmt::fprintf(s, ".")?;
    174 		};
    175 		ret += fmt::fprintf(s, "{}", a[i])?;
    176 	};
    177 	return ret;
    178 };
    179 
    180 fn fmtv6(s: io::handle, a: addr6) (io::error | size) = {
    181 	let ret = 0z;
    182 	let zstart: int = -1;
    183 	let zend: int = -1;
    184 	for (let i = 0; i < 16; i += 2) {
    185 		let j = i;
    186 		for (j < 16 && a[j] == 0 && a[j + 1] == 0) {
    187 			j += 2;
    188 		};
    189 
    190 		if (j > i && j - i > zend - zstart) {
    191 			zstart = i;
    192 			zend = j;
    193 			i = j;
    194 		};
    195 	};
    196 
    197 	if (zend - zstart <= 2) {
    198 		zstart = -1;
    199 		zend = -1;
    200 	};
    201 
    202 	for (let i = 0; i < 16; i += 2) {
    203 		if (i == zstart) {
    204 			ret += fmt::fprintf(s, "::")?;
    205 			i = zend;
    206 			if (i >= 16)
    207 				break;
    208 		} else if (i > 0) {
    209 			ret += fmt::fprintf(s, ":")?;
    210 		};
    211 		let term = (a[i]: u16) << 8 | a[i + 1];
    212 		ret += fmt::fprintf(s, "{:x}", term)?;
    213 	};
    214 	return ret;
    215 };
    216 
    217 // Fills a netmask according to the CIDR value
    218 // e.g. 23 -> [0xFF, 0xFF, 0xFD, 0x00]
    219 fn fillmask(mask: []u8, val: u8) void = {
    220 	rt::memset(&mask[0], 0xFF, len(mask));
    221 	let i: int = len(mask): int - 1;
    222 	val = len(mask): u8 * 8 - val;
    223 	for (val >= 8) {
    224 		mask[i] = 0x00;
    225 		val -= 8;
    226 		i -= 1;
    227 	};
    228 	if (i >= 0) {
    229 		mask[i] = ~((1 << val) - 1);
    230 	};
    231 };
    232 
    233 // Returns an addr representing a netmask
    234 fn cidrmask(addr: addr, val: u8) (addr | invalid) = {
    235 	let a_len: u8 = match (addr) {
    236 	case addr4 =>
    237 		yield 4;
    238 	case addr6 =>
    239 		yield 16;
    240 	};
    241 
    242 	if (val > 8 * a_len)
    243 		return invalid;
    244 	if (a_len == 4) {
    245 		let ret: addr4 = [0...];
    246 		fillmask(ret[..], val);
    247 		return ret;
    248 	};
    249 	if (a_len == 16) {
    250 		let ret: addr6 = [0...];
    251 		fillmask(ret[..], val);
    252 		return ret;
    253 	};
    254 	return invalid;
    255 };
    256 
    257 // Parse an IP subnet in CIDR notation e.g. 192.168.1.0/24
    258 export fn parsecidr(st: str) (subnet | invalid) = {
    259 	let tok = strings::tokenize(st, "/");
    260 	let ips = wanttoken(&tok)?;
    261 	let addr = parse(ips)?;
    262 	let masks = wanttoken(&tok)?;
    263 	let val = match (strconv::stou8(masks)) {
    264 	case let x: u8 =>
    265 		yield x;
    266 	case =>
    267 		return invalid;
    268 	};
    269 	if (!(strings::next_token(&tok) is void)) {
    270 		return invalid;
    271 	};
    272 	return subnet {
    273 		addr = addr,
    274 		mask = cidrmask(addr, val)?
    275 	};
    276 };
    277 
    278 fn masklen(addr: []u8) (void | size) = {
    279 	let n = 0z;
    280 	for (let i = 0z; i < len(addr); i += 1) {
    281 		if (addr[i] == 0xff) {
    282 			n += 8;
    283 			continue;
    284 		};
    285 		let val = addr[i];
    286 		for (val & 0x80 != 0) {
    287 			n += 1;
    288 			val <<= 1;
    289 		};
    290 		if (val != 0)
    291 			return;
    292 		for (let j = i + 1; j < len(addr); j += 1) {
    293 			if (addr[j] != 0)
    294 				return;
    295 		};
    296 		break;
    297 	};
    298 	return n;
    299 };
    300 
    301 fn fmtmask(s: io::handle, mask: addr) (io::error | size) = {
    302 	let ret = 0z;
    303 	let slice = match (mask) {
    304 	case let v4: addr4 =>
    305 		yield v4[..];
    306 	case let v6: addr6 =>
    307 		yield v6[..];
    308 	};
    309 	match (masklen(slice)) {
    310 	case void =>
    311 		// Format as hex, if zero runs are not contiguous
    312 		// (like golang does)
    313 		for (let i = 0z; i < len(slice); i += 1) {
    314 			ret += fmt::fprintf(s, "{:x}", slice[i])?;
    315 		};
    316 	case let n: size =>
    317 		// Standard CIDR integer
    318 		ret += fmt::fprintf(s, "{}", n)?;
    319 	};
    320 	return ret;
    321 };
    322 
    323 fn fmtsubnet(s: io::handle, subnet: subnet) (io::error | size) = {
    324 	let ret = 0z;
    325 	ret += fmt(s, subnet.addr)?;
    326 	ret += fmt::fprintf(s, "/")?;
    327 	ret += fmtmask(s, subnet.mask)?;
    328 	return ret;
    329 };
    330 
    331 // Formats an [[addr]] or [[subnet]] and prints it to a stream.
    332 export fn fmt(s: io::handle, item: (...addr | subnet)) (io::error | size) = {
    333 	match (item) {
    334 	case let v4: addr4 =>
    335 		return fmtv4(s, v4)?;
    336 	case let v6: addr6 =>
    337 		return fmtv6(s, v6)?;
    338 	case let sub: subnet =>
    339 		return fmtsubnet(s, sub);
    340 	};
    341 };
    342 
    343 // Formats an [[addr]] or [[subnet]] as a string. The return value is statically
    344 // allocated and will be overwritten on subsequent calls; see [[strings::dup]] to
    345 // extend its lifetime.
    346 export fn string(item: (...addr | subnet)) str = {
    347 	// Maximum length of an IPv6 address plus its netmask in hexadecimal
    348 	static let buf: [64]u8 = [0...];
    349 	let stream = strio::fixed(buf);
    350 	fmt(&stream, item) as size;
    351 	return strio::string(&stream);
    352 };
    353 
    354 fn wanttoken(tok: *strings::tokenizer) (str | invalid) = {
    355 	match (strings::next_token(tok)) {
    356 	case let s: str =>
    357 		return s;
    358 	case void =>
    359 		return invalid;
    360 	};
    361 };
    362 
    363 // Returns whether an [[addr]] (or another [[subnet]]) is contained
    364 // within a [[subnet]].
    365 export fn subnet_contains(sub: subnet, item: (addr | subnet)) bool = {
    366 	let a: subnet = match (item) {
    367 	case let a: addr =>
    368 		yield subnet {
    369 			addr = a,
    370 			mask = sub.mask,
    371 		};
    372 	case let sub: subnet =>
    373 		yield sub;
    374 	};
    375 	// Get byte slices for both addresses and masks.
    376 	let ipa = match (sub.addr) {
    377 		case let v4: addr4 => yield v4[..];
    378 		case let v6: addr6 => yield v6[..];
    379 	};
    380 	let maska = match (sub.mask) {
    381 		case let v4: addr4 => yield v4[..];
    382 		case let v6: addr6 => yield v6[..];
    383 	};
    384 	let ipb = match (a.addr) {
    385 		case let v4: addr4 => yield v4[..];
    386 		case let v6: addr6 => yield v6[..];
    387 	};
    388 	let maskb = match (a.mask) {
    389 		case let v4: addr4 => yield v4[..];
    390 		case let v6: addr6 => yield v6[..];
    391 	};
    392 	if (len(ipa) != len(ipb) || len(maska) != len(maskb) || len(ipa) != len(maska)) {
    393 		// Mismatched addr4 and addr6 addresses / masks.
    394 		return false;
    395 	};
    396 	for (let i = 0z; i < len(ipa); i += 1) {
    397 		if (ipa[i] & maska[i] != ipb[i] & maska[i] || maska[i] > maskb[i]) {
    398 			return false;
    399 		};
    400 	};
    401 	return true;
    402 };