const AF_INET = 2 const SOCK_STREAM = 1 const SOCK_DGRAM = 2 const SOL_SOCKET = 1 const SO_REUSEADDR = 2 const SOMAXCONN = 128 const IP_MAXPACKET = 65535 const DNS_TYPE_A = 1 const DNS_CLASS_IN = 1 const DNS_RECURSION_DESIRED = 256 func net.listen[host: str, port: i64] : i64, bool s := _builtin_syscall(SYS_socket, AF_INET, SOCK_STREAM, 0) if s < 0 return -1, false optval := 1 if _builtin_syscall(SYS_setsockopt, s, SOL_SOCKET, SO_REUSEADDR, ^optval, 8) < 0 _builtin_syscall(SYS_close, s) return -1, false ~packed_host, ok := net.resolve(host) if !ok return -1, false sa := _stackalloc(16) net._build_sa(sa, packed_host, port) if _builtin_syscall(SYS_bind, s, sa, 16) < 0 _builtin_syscall(SYS_close, s) return -1, false if _builtin_syscall(SYS_listen, s, SOMAXCONN) < 0 _builtin_syscall(SYS_close, s) return -1, false return s, true func net.connect[host: str, port: i64] : i64, bool s := _builtin_syscall(SYS_socket, AF_INET, SOCK_STREAM, 0) if s < 0 return -1, false ~packed_host, ok := net.resolve(host) if !ok return -1, false sa := _stackalloc(16) net._build_sa(sa, packed_host, port) if _builtin_syscall(SYS_connect, s, sa, 16) < 0 _builtin_syscall(SYS_close, s) return -1, false return s, true func net.accept[s: i64, addr: ptr] : i64 addrlen := 16 return _builtin_syscall(SYS_accept, s, addr, ^addrlen) func net.send[s: i64, data: ptr, size: i64] : void _builtin_syscall(SYS_sendto, s, data, size, 0, 0, 0) func net.read[s: i64, buffer: ptr, size: i64] : i64 return _builtin_syscall(SYS_read, s, buffer, size) func net.close[s: i64] : void _builtin_syscall(SYS_close, s) func net.pack_addr[a: i64, b: i64, c: i64, d: i64] : i64 return (a << 24) | (b << 16) | (c << 8) | d func net._build_sa[sa: ptr, packed_host: i64, port: i64] : void mem.zero(sa, 16) sa[0] = AF_INET sa[1] = 0 sa[2] = (port >> 8) & 255 sa[3] = port & 255 sa[4] = (packed_host >> 24) & 255 sa[5] = (packed_host >> 16) & 255 sa[6] = (packed_host >> 8) & 255 sa[7] = packed_host & 255 struct net.UDPSocket fd: i64 addr: ptr func net.UDPSocket.receive[s: net.UDPSocket, size: i64] : net.UDPPacket pkt := new* net.UDPPacket pkt->data = mem.alloc(size) pkt->source_addr = mem.alloc(16) mem.zero(pkt->source_addr, 16) addrlen := 16 pkt->size = _builtin_syscall(SYS_recvfrom, s->fd, pkt->data, size, 0, pkt->source_addr, ^addrlen) return pkt func net.UDPSocket.close_and_free[s: net.UDPSocket] : void _builtin_syscall(SYS_close, s->fd) s->addr->free() mem.free(s) func net.create_udp_client[host: str, port: i64] : net.UDPSocket, bool s := new* net.UDPSocket ~packed_host, ok := net.resolve(host) if !ok return 0 as net.UDPSocket, false s->fd = _builtin_syscall(SYS_socket, AF_INET, SOCK_DGRAM, 0) if s->fd < 0 mem.free(s) return 0 as net.UDPSocket, false s->addr = mem.alloc(16) net._build_sa(s->addr, packed_host, port) return s, true func net.create_udp_server[host: str, port: i64] : net.UDPSocket, bool ~s, ok := net.create_udp_client(host, port) if !ok return 0 as net.UDPSocket, false if _builtin_syscall(SYS_bind, s->fd, s->addr, 16) < 0 s->close_and_free() return 0 as net.UDPSocket, false return s, true func net.UDPSocket.send_to[s: net.UDPSocket, addr: ptr, data: ptr, size: i64] : void _builtin_syscall(SYS_sendto, s->fd, data, size, 0, addr, 16) struct net.UDPPacket data: ptr source_addr: ptr size: i64 func net.UDPPacket.free[pkt: net.UDPPacket] : void pkt->data->free() pkt->source_addr->free() mem.free(pkt) func net.resolve[domain: str] : i64, bool // if domain is already an ip, skip dns resolution parts := domain->split(".") if parts->size == 4 valid := true for i in 0..4 p := parts->nth(i) as str if p->len() < 1 || p->len() > 3 valid = false break for j in 0..p->len() if !p[j]->is_digit() valid = false break if !valid break if valid a := (parts->nth(0) as str)->parse_i64() b := (parts->nth(1) as str)->parse_i64() c := (parts->nth(2) as str)->parse_i64() d := (parts->nth(3) as str)->parse_i64() if a >= 0 && a <= 255 && b >= 0 && b <= 255 && c >= 0 && c <= 255 && d >= 0 && d <= 255 parts->free_with_items() return net.pack_addr(a, b, c, d), true parts->free_with_items() query := net.build_dns_query(domain, DNS_TYPE_A) // TODO: this probably shouldnt be hardcoded ~s, ok := net.create_udp_client("1.1.1.1", 53) if !ok query->free() return -1, false s->send_to(s->addr, query->data, query->size) query->free() pkt := s->receive(1024) s->close_and_free() // TODO: do actual parsing pos := 12 // skip header (12 bytes) while pkt->data[pos] != 0 pos += pkt->data[pos] + 1 // skip question pos += 5 // skip null byte, type(2), class(2) pos += 12 // skip name(2), type(2), class(2), ttl(4), rdlength(2) out := net.pack_addr(pkt->data[pos] as i64, pkt->data[pos+1] as i64, pkt->data[pos+2] as i64, pkt->data[pos+3] as i64) pkt->free() return out, true func net.encode_dns_name[domain: str] : Blob domain_len := domain->len() buf := Blob.alloc(domain_len + 2) out_pos := 0 part_start := 0 i := 0 while i <= domain_len if i == domain_len || domain[i] == '.' part_len := i - part_start buf->data[out_pos] = part_len & 0xff out_pos += 1 mem.copy(domain as ptr + part_start, buf->data + out_pos, part_len) out_pos += part_len part_start = i + 1 i += 1 buf->data[out_pos] = 0 return buf func net.build_dns_query[domain_name: str, record_type: i64] : Blob name := net.encode_dns_name(domain_name) out := Blob.alloc(12 + name->size + 4) // header id := (os.urandom_i64() % 65536)->abs() mem.write16be(out->data + 0, id) mem.write16be(out->data + 2, DNS_RECURSION_DESIRED) mem.write16be(out->data + 4, 1) // num_questions mem.write16be(out->data + 6, 0) // num_answers mem.write16be(out->data + 8, 0) // num_authorities mem.write16be(out->data + 10, 0) // num_additionals // question mem.copy(name->data, out->data + 12, name->size) mem.write16be(out->data + 12 + name->size, record_type) mem.write16be(out->data + 12 + name->size + 2, DNS_CLASS_IN) name->free() return out