hare

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

commit 3ccbce7163d62e5c59a5c0c01f4b4588de689a31
parent 0473b9b0ecfca63f7b1ed4d8e1956a97a8f17ea3
Author: Drew DeVault <sir@cmpwn.com>
Date:   Wed, 31 Mar 2021 11:40:12 -0400

net: generalize listener interface

Adds TODO for Unix sockets in +linux

Diffstat:
Dnet/+linux.ha | 138-------------------------------------------------------------------------------
Anet/+linux/socket.ha | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anet/+linux/util.ha | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mnet/socket.ha | 23++++++++++++++++++++++-
Mscripts/gen-stdlib | 3++-
Mstdlib.mk | 6++++--
6 files changed, 203 insertions(+), 142 deletions(-)

diff --git a/net/+linux.ha b/net/+linux.ha @@ -1,138 +0,0 @@ -use io; -use net::ip; -use os; -use rt; -use strings; - -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; -}; - -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); -}; diff --git a/net/+linux/socket.ha b/net/+linux/socket.ha @@ -0,0 +1,116 @@ +use io; +use net::ip; +use os; +use rt; +use strings; + +// 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); +}; + +export fn connect_unix( + path: str, + options: connect_option... +) (*io::stream | io::error) = { + abort(); // TODO +}; + +type tcp_listener = struct { + l: listener, + fd: int, +}; + +// 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 alloc(tcp_listener { + l = listener { + accept = &tcp_accept, + shutdown = &tcp_shutdown, + }, + fd = sockfd, + }): *listener; +}; + +fn tcp_accept(l: *listener) (*io::stream | io::error) = { + assert(l.accept == &tcp_accept); + let l = l: *tcp_listener; + let sn = rt::sockaddr {...}; + const sz = size(rt::sockaddr): u32; + const fd = wrap(rt::accept(l.fd, &sn, &sz))?; + const addr = ip::from_native(sn).0; + return os::fdopen(fd, ip::string(addr), + io::mode::READ | io::mode::WRITE); +}; + +fn tcp_shutdown(l: *listener) void = { + assert(l.shutdown == &tcp_shutdown); + let l = l: *tcp_listener; + rt::close(l.fd); + free(l); +}; + +// Binds a Unix socket at the given path. +export fn listen_unix( + path: str, + options: listen_option... +) (*listener | io::error) = { + abort(); // TODO +}; diff --git a/net/+linux/util.ha b/net/+linux/util.ha @@ -0,0 +1,59 @@ +use io; +use net::ip; +use os; +use rt; + +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; +}; + +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))?; +}; + +// 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); +}; diff --git a/net/socket.ha b/net/socket.ha @@ -1,3 +1,5 @@ +use io; + // Enables keep-alive for a socket. export type keepalive = void; @@ -18,7 +20,10 @@ 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; +export type listener = struct { + accept: nullable *fn(l: *listener) (*io::stream | io::error), + shutdown: nullable *fn(l: *listener) void, +}; // Options for [listen]. export type listen_option = ( @@ -31,3 +36,19 @@ export type listen_option = ( // Options for [connect]. export type connect_option = keepalive; +// Accepts the next connection from a listener. Blocks until a new connection is +// available. +export fn accept(l: *listener) (*io::stream | io::error) = { + return match (l.accept) { + f: *fn(l: *listener) (*io::stream | io::error) => f(l), + null => io::unsupported, + }; +}; + +// Shuts down a [listener] and frees resources associated with it. +export fn shutdown(l: *listener) void = { + match (l.shutdown) { + f: *fn(l: *listener) void => f(l), + null => void, + }; +}; diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -376,7 +376,8 @@ linux_vdso() { net() { printf '# net\n' gen_srcs net \ - '$(PLATFORM).ha' \ + '$(PLATFORM)/socket.ha' \ + '$(PLATFORM)/util.ha' \ socket.ha gen_ssa net io os strings net::ip } diff --git a/stdlib.mk b/stdlib.mk @@ -521,7 +521,8 @@ $(HARECACHE)/linux/vdso/linux_vdso.ssa: $(stdlib_linux_vdso_srcs) $(stdlib_rt) $ # net # net stdlib_net_srcs= \ - $(STDLIB)/net/$(PLATFORM).ha \ + $(STDLIB)/net/$(PLATFORM)/socket.ha \ + $(STDLIB)/net/$(PLATFORM)/util.ha \ $(STDLIB)/net/socket.ha $(HARECACHE)/net/net.ssa: $(stdlib_net_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_os) $(stdlib_strings) $(stdlib_net_ip) @@ -1261,7 +1262,8 @@ $(TESTCACHE)/linux/vdso/linux_vdso.ssa: $(testlib_linux_vdso_srcs) $(testlib_rt) # net # net testlib_net_srcs= \ - $(STDLIB)/net/$(PLATFORM).ha \ + $(STDLIB)/net/$(PLATFORM)/socket.ha \ + $(STDLIB)/net/$(PLATFORM)/util.ha \ $(STDLIB)/net/socket.ha $(TESTCACHE)/net/net.ssa: $(testlib_net_srcs) $(testlib_rt) $(testlib_io) $(testlib_os) $(testlib_strings) $(testlib_net_ip)