commit d11b0f794ec768acf0468440dba1f74f3311c486
parent f27664d115053f4f325050e4d53bfa4c8110ddff
Author: Mykyta Holubakha <hilobakho@gmail.com>
Date: Sun, 28 Mar 2021 19:42:55 +0300
net/ip: new module
Diffstat:
A | net/ip/+test.ha | | | 83 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | net/ip/ip.ha | | | 328 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | scripts/gen-stdlib | | | 19 | +++++++++++++++++++ |
M | stdlib.mk | | | 27 | +++++++++++++++++++++++++++ |
4 files changed, 457 insertions(+), 0 deletions(-)
diff --git a/net/ip/+test.ha b/net/ip/+test.ha
@@ -0,0 +1,83 @@
+use strings;
+use fmt;
+
+fn ip_test(s: str, expected: (addr|invalid)) void = {
+ let pr = parse(s);
+ let ip = if (pr is invalid) {
+ assert(expected is invalid);
+ return;
+ } else {
+ assert(expected is addr);
+ assert(equal(pr as addr, expected as addr));
+ pr as addr;
+ };
+ let fmted = string(ip);
+ let iprp = parse(fmted);
+ assert(iprp is addr);
+ let ipr = iprp as addr;
+ assert(equal(ip, ipr));
+ if (ip is addr4) {
+ assert(fmted == s);
+ } else {
+ assert(strings::dup(fmted) == string(ipr));
+ };
+};
+
+@test fn parse_ip() void = {
+ let tests: [](str, (addr|invalid)) = [
+ ("127.0.0.1", [127, 0, 0, 1]: addr4),
+ ("192.168.18.1", [192, 168, 18, 1]: addr4),
+ ("-127.0.0.1", invalid),
+ ("127.-0.0.1", invalid),
+ ("::", [0...]: addr6),
+ ("::1", [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]: addr6),
+ ("::FFFF:FFFF", [0,0,0,0,0,0,0,0,0,0,0,0,0xFF,0xFF,0xFF,0xFF]: addr6),
+ ("::FFFF", [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0xFF,0xFF]: addr6),
+ (":FFFF", invalid),
+ ("::1:1", [0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1]: addr6),
+ ("1::1", [0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1]: addr6),
+ (":::1:1", invalid),
+ (":::", invalid),
+ ("::1::1", invalid),
+ ("1::::1", invalid),
+ ("FFFF::FFFF::1", invalid),
+ ("::127.0.0.1", [0,0,0,0,0,0,0,0,0,0,0,0,127,0,0,1]: addr6),
+ ("FFFF:FFFF", invalid),
+ ("DEAD::BEef", [0xDE, 0xAD, 0,0,0,0,0,0,0,0,0,0,0,0, 0xBE, 0xEF]: addr6),
+ ("DEAD::BEef:A12D", [0xDE, 0xAD, 0,0,0,0,0,0,0,0,0,0, 0xBE, 0xEF, 0xA1, 0x2D]: addr6),
+ ("DEAD:BEef::0102:A12D", [0xDE, 0xAD,0xBE,0xEF,0,0,0,0,0,0,0,0,0x01,0x02,0xA1,0x2D]: addr6),
+ ("DEAD:BEef:::A12D", invalid),
+ ("1980:cafe:a:babe::1", [0x19, 0x80, 0xca, 0xfe, 0x0, 0xa, 0xba, 0xbe, 0, 0, 0, 0, 0, 0, 0, 1]: addr6),
+ ("a1:a2:a3:a4::b1:b2:b3:b4", invalid),
+ ("", invalid),
+ ];
+ for (let i = 0z; i < len(tests); i += 1) {
+ ip_test(tests[i].0, tests[i].1);
+ };
+};
+
+fn subnet_test_simple(s: str) void = {
+ let net = match (parsecidr(s)) {
+ a: subnet => a,
+ * => return,
+ };
+ let fmted = stringsubnet(net);
+ assert(fmted == s);
+ let netrp = parsecidr(fmted);
+ assert(netrp is subnet);
+ let netr = netrp as subnet;
+ assert(equal(net.addr, netr.addr));
+ assert(equal(net.mask, netr.mask));
+};
+
+@test fn parse_subnet() void = {
+ let subnet_tests: []str = [
+ "192.168.1.0/0",
+ "192.168.1.0/23",
+ "192.168.1.0/24",
+ "192.168.1.0/32",
+ ];
+ for (let i = 0z; i < len(subnet_tests); i += 1) {
+ subnet_test_simple(subnet_tests[i]);
+ };
+};
diff --git a/net/ip/ip.ha b/net/ip/ip.ha
@@ -0,0 +1,328 @@
+use io;
+use strio;
+use fmt;
+use bytes;
+use rt;
+use strconv;
+use strings;
+
+// An IPv4 address.
+export type addr4 = [4]u8;
+
+// An IPv6 address.
+export type addr6 = [16]u8;
+
+// An IP address.
+export type addr = (addr4 | addr6);
+
+// An IP subnet.
+export type subnet = struct {
+ addr: addr,
+ mask: addr,
+};
+
+// Invalid parse result.
+export type invalid = void!;
+
+// Test if two [addr]s are equal.
+export fn equal(l: addr, r: addr) bool = {
+ return match (l) {
+ l: addr4 => {
+ if (!(r is addr4)) {
+ return false;
+ };
+ let r = r as addr4;
+ bytes::equal(l, r);
+ },
+ l: addr6 => {
+ if (!(r is addr6)) {
+ return false;
+ };
+ let r = r as addr6;
+ bytes::equal(l, r);
+ },
+ };
+};
+
+export fn parsev4(st: str) (addr4 | invalid) = {
+ let ret: addr4 = [0...];
+ let tok = strings::tokenize(st, ".");
+ let i = 0z;
+ for (i < 4; i += 1) {
+ let s = wanttoken(&tok)?;
+ ret[i] = match (strconv::stou8(s)) {
+ term: u8 => term,
+ * => return invalid
+ };
+ };
+ if (i < 4 || !(strings::next_token(&tok) is void)) {
+ return invalid;
+ };
+ return ret;
+};
+
+fn parsev6(st: str) (addr6 | invalid) = {
+ let ret: addr6 = [0...];
+ let tok = strings::tokenize(st, ":");
+ if (st == "::") {
+ return ret;
+ };
+ let ells = -1;
+ if (strings::has_prefix(st, "::")) {
+ wanttoken(&tok)?;
+ wanttoken(&tok)?;
+ ells = 0;
+ } else if (strings::has_prefix(st, ":")) {
+ return invalid;
+ };
+ let i = 0;
+ for (i < 16) {
+ let s = match (strings::next_token(&tok)) {
+ s: str => s,
+ void => break,
+ };
+ if (s == "") {
+ if (ells != -1) {
+ return invalid;
+ };
+ ells = i;
+ continue;
+ };
+ let val = strconv::stou16b(s, 16);
+ if (val is u16) {
+ let v = val as u16;
+ ret[i] = (v >> 8): u8;
+ i += 1;
+ ret[i] = v: u8;
+ i += 1;
+ continue;
+ } else {
+ let v4 = parsev4(s)?;
+ rt::memcpy(&ret[i], &v4, 4);
+ i += 4;
+ break;
+ };
+ return invalid;
+ };
+ if (!(strings::next_token(&tok) is void)) {
+ return invalid;
+ };
+ if (ells >= 0) {
+ if (i >= 15)
+ return invalid;
+ rt::memcpy(
+ &ret[16 - (i - ells)],
+ &ret[ells], (i - ells): size);
+ rt::memset(&ret[ells], 0, (i - ells): size);
+ } else {
+ if (i != 16)
+ return invalid;
+ };
+
+ return ret;
+};
+
+
+// Parses an IP address.
+export fn parse(s: str) (addr | invalid) = {
+ match(parsev4(s)) {
+ v4: addr4 => return v4,
+ };
+ match(parsev6(s)) {
+ v6: addr6 => return v6,
+ };
+ return invalid;
+};
+
+fn fmtv4(a: addr4, s: *io::stream) (io::error | size) = {
+ let ret = 0z;
+ for (let i = 0; i < 4; i += 1) {
+ if (i > 0) {
+ ret += fmt::fprintf(s, ".")?;
+ };
+ ret += fmt::fprintf(s, "{}", a[i])?;
+ };
+ return ret;
+};
+
+fn fmtv6(a: addr6, s: *io::stream) (io::error | size) = {
+ let ret = 0z;
+ let zstart: int = -1;
+ let zend: int = -1;
+ for (let i = 0; i < 16; i += 2) {
+ let j = i;
+ for (j < 16 && a[j] == 0 && a[j + 1] == 0) {
+ j += 2;
+ };
+
+ if (j > i && j - i > zend - zstart) {
+ zstart = i;
+ zend = j;
+ i = j;
+ };
+ };
+
+ if (zend - zstart <= 2) {
+ zstart = -1;
+ zend = -1;
+ };
+
+ for (let i = 0; i < 16; i += 2) {
+ if (i == zstart) {
+ ret += fmt::fprintf(s, "::")?;
+ i = zend;
+ if (i >= 16)
+ break;
+ } else if (i > 0) {
+ ret += fmt::fprintf(s, ":")?;
+ };
+ let term = (a[i]: u16) << 8 | a[i + 1];
+ ret += fmt::fprintf(s, "{:x}", term)?;
+ };
+ return ret;
+};
+
+// Formats an IP address
+export fn fmt(addr: addr, s: *io::stream) (io::error | size) = {
+ return match (addr) {
+ v4: addr4 => fmtv4(v4, s)?,
+ v6: addr6 => fmtv6(v6, s)?,
+ };
+};
+
+// Formats an [addr] as a string. The return value is statically
+// allocated and will be overwritten on subsequent calls; see
+// [strings::dup] to extend its lifetime.
+export fn string(addr: addr) str = {
+ // Maximum length of an IPv6 address is 45 chars
+ static let buf: [46]u8 = [0...];
+ let stream = strio::fixed(buf);
+ fmt(addr, stream) as size;
+ return strio::string(stream);
+};
+
+// Fills a netmask according to the CIDR value
+// e.g. 23 -> [0xFF, 0xFF, 0xFD, 0x00]
+fn fillmask(mask: []u8, val: u8) void = {
+ rt::memset(&mask[0], 0xFF, len(mask));
+ let i: int = len(mask): int - 1;
+ val = 32 - val;
+ for (val >= 8) {
+ mask[i] = 0x00;
+ val -= 8;
+ i -= 1;
+ };
+ if (i >= 0) {
+ mask[i] = ~((1 << val) - 1);
+ };
+};
+
+// Returns an addr representing a netmask
+fn cidrmask(addr: addr, val: u8) (addr | invalid) = {
+ let a_len: u8 = match (addr) {
+ addr4 => 4,
+ addr6 => 16,
+ };
+
+ if (val > 8 * a_len)
+ return invalid;
+ if (a_len == 4) {
+ let ret: addr4 = [0...];
+ fillmask(ret[..], val);
+ return ret;
+ };
+ if (a_len == 16) {
+ let ret: addr6 = [0...];
+ fillmask(ret[..], val);
+ return ret;
+ };
+ return invalid;
+};
+
+// Parse an IP subnet in CIDR notation e.g. 192.168.1.0/24
+export fn parsecidr(st: str) (subnet | invalid) = {
+ let tok = strings::tokenize(st, "/");
+ let ips = wanttoken(&tok)?;
+ let addr = parse(ips)?;
+ let masks = wanttoken(&tok)?;
+ let val = match (strconv::stou8(masks)) {
+ x: u8 => x,
+ * => return invalid,
+ };
+ if (!(strings::next_token(&tok) is void)) {
+ return invalid;
+ };
+ return subnet {
+ addr = addr,
+ mask = cidrmask(addr, val)?
+ };
+};
+
+fn masklen(addr: []u8) (void | size) = {
+ let n = 0z;
+ for (let i = 0z; i < len(addr); i += 1) {
+ if (addr[i] == 0xff) {
+ n += 8;
+ continue;
+ };
+ let val = addr[i];
+ for (val & 0x80 != 0) {
+ n += 1;
+ val <<= 1;
+ };
+ if (val != 0)
+ return;
+ for (let j = i + 1; j < len(addr); j += 1) {
+ if (addr[j] != 0)
+ return;
+ };
+ break;
+ };
+ return n;
+};
+
+fn fmtmask(mask: addr, s: *io::stream) (io::error | size) = {
+ let ret = 0z;
+ let slice = match (mask) {
+ v4: addr4 => v4[..],
+ v6: addr6 => v6[..],
+ };
+ match (masklen(slice)) {
+ // format as hex, if zero runs are not contiguous
+ // (like golang does)
+ void => {
+ for (let i = 0z; i < len(slice); i += 1) {
+ ret += fmt::fprintf(s, "{:x}", slice[i])?;
+ };
+ },
+ // standard CIDR integer
+ n: size => ret += fmt::fprintf(s, "{}", n)?,
+ };
+ return ret;
+};
+
+// Format an IP subnet
+export fn fmtsubnet(subnet: subnet, s: *io::stream) (io::error | size) = {
+ let ret = 0z;
+ ret += fmt(subnet.addr, s)?;
+ ret += fmt::fprintf(s, "/")?;
+ ret += fmtmask(subnet.mask, s)?;
+ return ret;
+};
+
+// Formats a [subnet] as a string. The return value is statically
+// allocated and will be overwritten on subsequent calls; see
+// [strings::dup] to extend its lifetime.
+export fn stringsubnet(subnet: subnet) str = {
+ static let buf: [66]u8 = [0...];
+ let stream = strio::fixed(buf);
+ fmtsubnet(subnet, stream) as size;
+ return strio::string(stream);
+};
+
+fn wanttoken(tok: *strings::tokenizer) (str | invalid) = {
+ return match (strings::next_token(tok)) {
+ s: str => s,
+ void => invalid
+ };
+};
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -394,6 +394,24 @@ linux_vdso() {
gen_ssa linux::vdso linux strings format::elf
}
+gensrcs_net_ip() {
+ gen_srcs net::ip \
+ ip.ha \
+ $*
+}
+
+net_ip() {
+ printf '# net::ip\n'
+ if [ $testing -eq 0 ]
+ then
+ gensrcs_net_ip
+ else
+ gensrcs_net_ip \
+ +test.ha
+ fi
+ gen_ssa net::ip bytes io strconv strings strio
+}
+
math_random() {
printf '# math::random\n'
gen_srcs math::random \
@@ -588,6 +606,7 @@ hash_fnv
io
linux
linux_vdso
+net_ip
math_random
os
os_exec
diff --git a/stdlib.mk b/stdlib.mk
@@ -147,6 +147,9 @@ hare_stdlib_deps+=$(stdlib_linux)
stdlib_linux_vdso=$(HARECACHE)/linux/vdso/linux_vdso.o
hare_stdlib_deps+=$(stdlib_linux_vdso)
+stdlib_net_ip=$(HARECACHE)/net/ip/net_ip.o
+hare_stdlib_deps+=$(stdlib_net_ip)
+
stdlib_math_random=$(HARECACHE)/math/random/math_random.o
hare_stdlib_deps+=$(stdlib_math_random)
@@ -499,6 +502,16 @@ $(HARECACHE)/linux/vdso/linux_vdso.ssa: $(stdlib_linux_vdso_srcs) $(stdlib_rt) $
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nlinux::vdso \
-t$(HARECACHE)/linux/vdso/linux_vdso.td $(stdlib_linux_vdso_srcs)
+# net::ip
+stdlib_net_ip_srcs= \
+ $(STDLIB)/net/ip/ip.ha
+
+$(HARECACHE)/net/ip/net_ip.ssa: $(stdlib_net_ip_srcs) $(stdlib_rt) $(stdlib_bytes) $(stdlib_io) $(stdlib_strconv) $(stdlib_strings) $(stdlib_strio)
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(HARECACHE)/net/ip
+ @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nnet::ip \
+ -t$(HARECACHE)/net/ip/net_ip.td $(stdlib_net_ip_srcs)
+
# math::random
stdlib_math_random_srcs= \
$(STDLIB)/math/random/random.ha
@@ -837,6 +850,9 @@ hare_testlib_deps+=$(testlib_linux)
testlib_linux_vdso=$(TESTCACHE)/linux/vdso/linux_vdso.o
hare_testlib_deps+=$(testlib_linux_vdso)
+testlib_net_ip=$(TESTCACHE)/net/ip/net_ip.o
+hare_testlib_deps+=$(testlib_net_ip)
+
testlib_math_random=$(TESTCACHE)/math/random/math_random.o
hare_testlib_deps+=$(testlib_math_random)
@@ -1197,6 +1213,17 @@ $(TESTCACHE)/linux/vdso/linux_vdso.ssa: $(testlib_linux_vdso_srcs) $(testlib_rt)
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nlinux::vdso \
-t$(TESTCACHE)/linux/vdso/linux_vdso.td $(testlib_linux_vdso_srcs)
+# net::ip
+testlib_net_ip_srcs= \
+ $(STDLIB)/net/ip/ip.ha \
+ $(STDLIB)/net/ip/+test.ha
+
+$(TESTCACHE)/net/ip/net_ip.ssa: $(testlib_net_ip_srcs) $(testlib_rt) $(testlib_bytes) $(testlib_io) $(testlib_strconv) $(testlib_strings) $(testlib_strio)
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(TESTCACHE)/net/ip
+ @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nnet::ip \
+ -t$(TESTCACHE)/net/ip/net_ip.td $(testlib_net_ip_srcs)
+
# math::random
testlib_math_random_srcs= \
$(STDLIB)/math/random/random.ha