hare

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

commit bd4dee296d01f6e72c510d9c2c30754d4a9a3742
parent e6fb45c618f97f032e0316ab2f8bdfe71f885118
Author: Mykyta Holubakha <hilobakho@gmail.com>
Date:   Tue, 30 Mar 2021 17:33:29 +0300

net: add a high-level TCP server & client API

Diffstat:
Mnet/ip/ip.ha | 8++++++++
Anet/socket.ha | 172+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mscripts/gen-stdlib | 8++++++++
Mstdlib.mk | 28++++++++++++++++++++++++++++
4 files changed, 216 insertions(+), 0 deletions(-)

diff --git a/net/ip/ip.ha b/net/ip/ip.ha @@ -21,6 +21,14 @@ export type subnet = struct { mask: addr, }; +// An IPv4 address which represents "any" address, i.e. "0.0.0.0". Binding to +// this address will listen on all available IPv4 interfaces on most systems. +export const ANY_V4: addr4 = [0, 0, 0, 0]; + +// An IPv6 address which represents "any" address, i.e. "::". Binding to this +// address will listen on all available IPv6 interfaces on most systems. +export const ANY_V6: addr6 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + // Invalid parse result. export type invalid = void!; diff --git a/net/socket.ha b/net/socket.ha @@ -0,0 +1,172 @@ +use io; +use rt; +use os; +use strings; +use net::ip; + +// Enables keep-alive for a socket. +export type keepalive = void; + +// Enables port re-use for a TCP listener. +export type reuseport = void; + +// Enables address re-use for a TCP listener. +export type reuseaddr = void; + +// Configures the backlog size for a listener. If not specified, a sensible +// default (10) is used. +export type backlog = u32; + +// To have the system select an arbitrary unused TCP port for [listen], set port +// to zero. To retrieve the assigned port, provide this as one of the options +// and the addressed u16 will be filled in with the port. +export type portassignment = *u16; + +// A listener binds a socket on the system and listens for incoming traffic for +// some specific protocol. +export type listener = int; + +// Options for [listen]. +export type listen_option = ( + keepalive | + reuseport | + reuseaddr | + backlog | + portassignment); + +// Options for [connect]. +export type connect_option = keepalive; + +fn setsockopt(sockfd: int, option: int, value: bool) (void | rt::errno) = { + let val: int = if (value) 1 else 0; + rt::setsockopt(sockfd, rt::SOL_SOCKET, option, + &val: *void, size(int): u32)?; +}; + +fn setfcntl(sockfd: int, flag: int) (void | rt::errno) = { + let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)?; + rt::fcntl(sockfd, rt::F_SETFL, flags | flag); // ? always detects io::error + return; +}; + +fn wrap(ie: (int | rt::errno)) (int | io::error) = { + match (ie) { + i: int => i, + er: rt::errno => errno_to_io(er) + }; +}; + +fn mksockfd(addr: ip::addr) (int | io::error) = { + const af = match (addr) { + ip::addr4 => rt::AF_INET: int, + ip::addr6 => rt::AF_INET6: int, + }; + return wrap(rt::socket(af, rt::SOCK_STREAM, 0))?; +}; + +// Binds a TCP listener to the given address. +export fn listen( + addr: ip::addr, + port: u16, + options: listen_option... +) (listener | io::error) = { + const sockfd = mksockfd(addr)?; + + let bk: u32 = 10; + let portout: nullable *u16 = null; + + for (let i = 0z; i < len(options); i += 1) { + match (options[i]) { + reuseaddr => setsockopt(sockfd, rt::SO_REUSEADDR, true), + reuseport => setsockopt(sockfd, rt::SO_REUSEPORT, true), + keepalive => setsockopt(sockfd, rt::SO_KEEPALIVE, true), + b: backlog => bk = b, + p: portassignment => portout = p, + }; + }; + setfcntl(sockfd, rt::O_CLOEXEC); + + let sockaddr = ip::to_native(addr, port); + const asize = match (addr) { + v4: ip::addr4 => size(rt::sockaddr_in): u32, + v6: ip::addr6 => size(rt::sockaddr_in6): u32, + }; + wrap(rt::bind(sockfd, &sockaddr, asize))?; + wrap(rt::listen(sockfd, bk))?; + + match (portout) { + p: *u16 => { + let sn = rt::sockaddr {...}; + let al = asize; + wrap(rt::getsockname(sockfd, &sn, &al))?; + const addr = ip::from_native(sn); + *p = addr.1; + }, + null => void, + }; + return sockfd; +}; + + +// Returns the remote address for a given connection, or void if none is +// available. +export fn peeraddr(stream: *io::stream) ((ip::addr, u16) | void) = { + let fd = match (os::streamfd(stream)) { + fd: int => fd, + void => return, + }; + let sn = rt::sockaddr {...}; + let sz = size(rt::sockaddr): u32; + if (rt::getpeername(fd, &sn, &sz) is rt::errno) { + return; + }; + return ip::from_native(sn); +}; + +// Accepts the next connection from a listener, returning an [io::stream]. +// Blocks until a new connection is available. +export fn accept(l: listener) (*io::stream | io::error) = { + let sn = rt::sockaddr {...}; + const sz = size(rt::sockaddr): u32; + const fd = wrap(rt::accept(l, &sn, &sz))?; + const addr = ip::from_native(sn).0; + return os::fdopen(fd, ip::string(addr), + io::mode::READ | io::mode::WRITE); +}; + +// Opens a TCP connection to the given host and port. Blocks until the +// connection is established. +export fn connect( + addr: ip::addr, + port: u16, + options: connect_option... +) (*io::stream | io::error) = { + const sockfd = mksockfd(addr)?; + for (let i = 0z; i < len(options); i += 1) { + // The only option is keepalive right now + setsockopt(sockfd, rt::SO_KEEPALIVE, true); + }; + + const sockaddr = ip::to_native(addr, port); + const asize = match (addr) { + v4: ip::addr4 => size(rt::sockaddr_in): u32, + v6: ip::addr6 => size(rt::sockaddr_in6): u32, + }; + wrap(rt::connect(sockfd, &sockaddr, asize))?; + + return os::fdopen(sockfd, ip::string(addr), + io::mode::READ | io::mode::WRITE); +}; + +fn io_errstr(data: *void) str = { + const errno = data: uintptr: int: rt::errno; + return rt::errstr(errno); +}; + +fn errno_to_io(err: rt::errno) io::error = { + let err = io::os_error { + string = &io_errstr, + data = err: uintptr: *void, + }; + return err: io::error; +}; diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -373,6 +373,13 @@ linux_vdso() { gen_ssa linux::vdso linux strings format::elf } +net() { + printf '# net\n' + gen_srcs net \ + socket.ha + gen_ssa net io os strings net::ip +} + gensrcs_net_ip() { gen_srcs net::ip \ ip.ha \ @@ -571,6 +578,7 @@ hash_fnv io linux linux_vdso +net net_ip math_random os diff --git a/stdlib.mk b/stdlib.mk @@ -150,6 +150,9 @@ hare_stdlib_deps+=$(stdlib_linux) stdlib_linux_vdso=$(HARECACHE)/linux/vdso/linux_vdso.o hare_stdlib_deps+=$(stdlib_linux_vdso) +stdlib_net=$(HARECACHE)/net/net.o +hare_stdlib_deps+=$(stdlib_net) + stdlib_net_ip=$(HARECACHE)/net/ip/net_ip.o hare_stdlib_deps+=$(stdlib_net_ip) @@ -515,6 +518,17 @@ $(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 +# net +stdlib_net_srcs= \ + $(STDLIB)/net/socket.ha + +$(HARECACHE)/net/net.ssa: $(stdlib_net_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_os) $(stdlib_strings) $(stdlib_net_ip) + @printf 'HAREC \t$@\n' + @mkdir -p $(HARECACHE)/net + @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nnet \ + -t$(HARECACHE)/net/net.td $(stdlib_net_srcs) + # net::ip stdlib_net_ip_srcs= \ $(STDLIB)/net/ip/ip.ha \ @@ -867,6 +881,9 @@ hare_testlib_deps+=$(testlib_linux) testlib_linux_vdso=$(TESTCACHE)/linux/vdso/linux_vdso.o hare_testlib_deps+=$(testlib_linux_vdso) +testlib_net=$(TESTCACHE)/net/net.o +hare_testlib_deps+=$(testlib_net) + testlib_net_ip=$(TESTCACHE)/net/ip/net_ip.o hare_testlib_deps+=$(testlib_net_ip) @@ -1240,6 +1257,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 +# net +testlib_net_srcs= \ + $(STDLIB)/net/socket.ha + +$(TESTCACHE)/net/net.ssa: $(testlib_net_srcs) $(testlib_rt) $(testlib_io) $(testlib_os) $(testlib_strings) $(testlib_net_ip) + @printf 'HAREC \t$@\n' + @mkdir -p $(TESTCACHE)/net + @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nnet \ + -t$(TESTCACHE)/net/net.td $(testlib_net_srcs) + # net::ip testlib_net_ip_srcs= \ $(STDLIB)/net/ip/ip.ha \