diff --git a/lib/std/libc/os/win32.c3 b/lib/std/libc/os/win32.c3 index b66169fa0..a358efa59 100644 --- a/lib/std/libc/os/win32.c3 +++ b/lib/std/libc/os/win32.c3 @@ -25,7 +25,5 @@ extern fn CInt _wremove(WString); macro Tm* localtime_r(Time_t* timer, Tm* buf) => _localtime64_s(buf, timer); macro CInt setjmp(JmpBuf* buffer) => _setjmp($$frameaddress(), buffer); macro Tm* gmtime_r(Time_t* timer, Tm* buf) => _gmtime64_s(buf, timer); -macro isz read(Fd fd, void* buffer, usz buffer_size) => _read(fd, buffer, buffer_size); -macro isz write(Fd fd, void* buffer, usz count) => _write(fd, buffer, count); - - +macro isz read(Fd fd, void* buffer, usz buffer_size) => _read(fd, buffer, (CUInt)buffer_size); +macro isz write(Fd fd, void* buffer, usz count) => _write(fd, buffer, (CUInt)count); \ No newline at end of file diff --git a/lib/std/net/net.c3 b/lib/std/net/net.c3 index 225d88c62..34586f904 100644 --- a/lib/std/net/net.c3 +++ b/lib/std/net/net.c3 @@ -1,4 +1,5 @@ module std::net; +import std::net::os; fault NetError { @@ -7,9 +8,15 @@ fault NetError INVALID_SOCKET, GENERAL_ERROR, INVALID_IP_STRING, + ADDRINFO_FAILED, + CONNECT_FAILED, + LISTEN_FAILED, + ACCEPT_FAILED, + WRITE_FAILED, + READ_FAILED, + SOCKOPT_FAILED, } - fn uint! ipv4toint(String s) { uint out; diff --git a/lib/std/net/os/common.c3 b/lib/std/net/os/common.c3 index dad14cd67..49b2f3026 100644 --- a/lib/std/net/os/common.c3 +++ b/lib/std/net/os/common.c3 @@ -48,3 +48,4 @@ const O_NONBLOCK = PLATFORM_O_NONBLOCK; extern fn CInt getaddrinfo(ZString nodename, ZString servname, AddrInfo* hints, AddrInfo** res) @if(SUPPORTS_INET); extern fn void freeaddrinfo(AddrInfo* res) @if(SUPPORTS_INET); +extern fn CInt setsockopt(NativeSocket socket, CInt level, CInt optname, void* optval, Socklen_t optlen) @if(SUPPORTS_INET); diff --git a/lib/std/net/os/darwin.c3 b/lib/std/net/os/darwin.c3 index 48f406f09..5b51bab3c 100644 --- a/lib/std/net/os/darwin.c3 +++ b/lib/std/net/os/darwin.c3 @@ -44,4 +44,11 @@ const int PLATFORM_AF_IEEE80211 = 37; const int PLATFORM_AF_UTUN = 38; const int PLATFORM_AF_VSOCK = 40; const int PLATFORM_AF_MAX = 41; -const int PLATFORM_O_NONBLOCK = 0x30; \ No newline at end of file +const int PLATFORM_O_NONBLOCK = 0x30; + +// https://opensource.apple.com/source/xnu/xnu-4570.41.2/bsd/sys/socket.h.auto.html +const int SOL_SOCKET = 0xffff; +const int SO_REUSEADDR = 0x0004; +const int SO_KEEPALIVE = 0x0008; +const int SO_BROADCAST = 0x0020; +const int SO_REUSEPORT = 0x0200; \ No newline at end of file diff --git a/lib/std/net/os/linux.c3 b/lib/std/net/os/linux.c3 index 57560f767..6c1e99e4a 100644 --- a/lib/std/net/os/linux.c3 +++ b/lib/std/net/os/linux.c3 @@ -9,4 +9,11 @@ const int PLATFORM_AF_BRIDGE = 7; const int PLATFORM_AF_AAL5 = 8; const int PLATFORM_AF_X25 = 9; -const PLATFORM_O_NONBLOCK = 0o4000; \ No newline at end of file +const PLATFORM_O_NONBLOCK = 0o4000; + +// https://git.sr.ht/~sircmpwn/hare/tree/master/item/rt/+linux/socket.ha +const int SOL_SOCKET = 1; +const int SO_REUSEADDR = 2; +const int SO_BROADCAST = 6; +const int SO_KEEPALIVE = 9; +const int SO_REUSEPORT = 15; \ No newline at end of file diff --git a/lib/std/net/os/posix.c3 b/lib/std/net/os/posix.c3 index fe4d9890e..15453b753 100644 --- a/lib/std/net/os/posix.c3 +++ b/lib/std/net/os/posix.c3 @@ -13,26 +13,26 @@ extern fn CInt bind(NativeSocket socket, SockAddrPtr address, Socklen_t address_ extern fn CInt listen(NativeSocket socket, CInt backlog) @extern("listen"); extern fn NativeSocket accept(NativeSocket socket, SockAddrPtr address, Socklen_t* address_len) @extern("accept"); -macro void! NativeSocket.close(NativeSocket this) +macro void! NativeSocket.close(self) { - if (libc::close(this)) + if (libc::close(self)) { if (libc::errno() == errno::EBADF) return NetError.INVALID_SOCKET?; return NetError.GENERAL_ERROR?; } } -macro void! NativeSocket.set_non_blocking(NativeSocket this) +macro void! NativeSocket.set_non_blocking(self) { - int flags = fcntl(this, F_GETFL, 0); - if (fcntl(this, F_SETFL, flags | O_NONBLOCK) == -1) + int flags = fcntl(self, F_GETFL, 0); + if (fcntl(self, F_SETFL, flags | O_NONBLOCK) == -1) { if (libc::errno() == errno::EBADF) return NetError.INVALID_SOCKET?; return NetError.GENERAL_ERROR?; } } -macro bool NativeSocket.is_non_blocking(NativeSocket this) +macro bool NativeSocket.is_non_blocking(self) { - return fcntl(this, F_GETFL, 0) & O_NONBLOCK == O_NONBLOCK; + return fcntl(self, F_GETFL, 0) & O_NONBLOCK == O_NONBLOCK; } diff --git a/lib/std/net/os/win32.c3 b/lib/std/net/os/win32.c3 index 9bf6183f1..415692046 100644 --- a/lib/std/net/os/win32.c3 +++ b/lib/std/net/os/win32.c3 @@ -11,5 +11,25 @@ def NativeSocket = distinct uptr; extern fn int wsa_startup(int, void*) @extern("WSAStartup"); extern fn int ioctlsocket(NativeSocket, long cmd, ulong *argp); extern fn int closesocket(NativeSocket); +extern fn NativeSocket socket(int af, int type, int protocol); +extern fn int connect(NativeSocket, SockAddrPtr address, Socklen_t address_len); +extern fn int bind(NativeSocket, SockAddrPtr address, Socklen_t address_len); +extern fn int listen(NativeSocket, int backlog); +extern fn NativeSocket accept(NativeSocket, SockAddrPtr address, Socklen_t* address_len); -macro NativeSocket.close(NativeSocket this) => closesocket(this); +macro NativeSocket.close(self) +{ + if (int err = closesocket(self)) + { + if (err == WSAENOTSOCK) return NetError.INVALID_SOCKET?; + return NetError.GENERAL_ERROR?; + } +} + +const int WSAENOTSOCK = 10038; + +// https://github.com/wine-mirror/wine/blob/master/include/winsock.h +const int SOL_SOCKET = 0xffff; +const int SO_REUSEADDR = 0x0004; +const int SO_KEEPALIVE = 0x0008; +const int SO_BROADCAST = 0x0200; \ No newline at end of file diff --git a/lib/std/net/sock.c3 b/lib/std/net/sock.c3 index f1effb678..142afa31a 100644 --- a/lib/std/net/sock.c3 +++ b/lib/std/net/sock.c3 @@ -1,7 +1,9 @@ module std::net; +import std::io; +import libc; const int SOCK_STREAM = 1; // Stream const int SOCK_DGRAM = 2; // Datagram const int SOCK_RAW = 3; // Raw const int SOCK_RDM = 4; // Reliably delivered -const int SOCK_SEQPACKET = 5; // Sequential packet +const int SOCK_SEQPACKET = 5; // Sequential packet \ No newline at end of file diff --git a/lib/std/net/socket.c3 b/lib/std/net/socket.c3 new file mode 100644 index 000000000..5c49a0f9a --- /dev/null +++ b/lib/std/net/socket.c3 @@ -0,0 +1,170 @@ +module std::net @if(os::SUPPORTS_INET); +import std::io; +import libc; + +enum Network : char (int domain, int type) +{ + TCP (os::AF_INET, SOCK_STREAM), + TCP6 (os::AF_INET6, SOCK_STREAM), + UDP (os::AF_INET, SOCK_DGRAM), + UDP6 (os::AF_INET6, SOCK_DGRAM), +} + +fn Socket! Network.connect(&self, String host, String port, SocketOption... options) +{ + @network_loop_over_ai(self, host, port; NativeSocket sockfd, AddrInfo* ai) + { + apply_sockoptions(sockfd, options)!; + int errcode = os::connect(sockfd, ai.ai_addr, ai.ai_addrlen); + // Keep the first successful connection. + if (errcode == 0) return network_socket(sockfd, ai); + }!; + return NetError.CONNECT_FAILED?; +} + +fn Socket! Network.listen(&self, String host, String port, int backlog, SocketOption... options) +{ + @network_loop_over_ai(self, host, port; NativeSocket sockfd, AddrInfo* ai) + { + apply_sockoptions(sockfd, options)!; + int errcode = os::bind(sockfd, ai.ai_addr, ai.ai_addrlen); + if (errcode == 0) + { + errcode = os::listen(sockfd, backlog); + // Keep the first successful connection. + if (errcode == 0) return network_socket(sockfd, ai); + } + }!; + return NetError.LISTEN_FAILED?; +} + +fn AddrInfo*! Network.addrinfo(&self, String host, String port) @private +{ + ZString zhost = host.zstr_tcopy(); + ZString zport = port.zstr_tcopy(); + AddrInfo hints = { .ai_family = self.domain, .ai_socktype = self.type }; + AddrInfo* ai; + int errcode = os::getaddrinfo(zhost, zport, &hints, &ai); + if (errcode != 0) return NetError.ADDRINFO_FAILED?; + return ai; +} + +macro apply_sockoptions(sockfd, options) @private +{ + Socket sock = { .sock = sockfd }; + foreach (o : options) sock.set_option(o)!; +} + +macro @network_loop_over_ai(network, host, port; @body(fd, ai)) @private +{ + AddrInfo* ai = network.addrinfo(host, port)!; + AddrInfo* first = ai; + defer os::freeaddrinfo(first); + while (ai) + { + NativeSocket sockfd = os::socket(ai.ai_family, ai.ai_socktype, ai.ai_protocol); + if (sockfd > 0) + { + @body(sockfd, ai); + } + ai = ai.ai_next; + } +} + +macro Socket network_socket(fd, ai) @private +{ + Socket sock = { .sock = fd, .ai_addrlen = ai.ai_addrlen }; + assert(sock.ai_addr_storage.len >= ai.ai_addrlen, "storage %d < addrlen %d", sock.ai_addr_storage.len, ai.ai_addrlen); + mem::copy(&sock.ai_addr_storage, (void*)ai.ai_addr, ai.ai_addrlen); + return sock; +} + +enum SocketOption : char (CInt value) @if(!env::WIN32) +{ + REUSEADDR (os::SO_REUSEADDR), + REUSEPORT (os::SO_REUSEPORT), + KEEPALIVE (os::SO_KEEPALIVE), + BROADCAST (os::SO_BROADCAST), +} +enum SocketOption : char (CInt value) @if(env::WIN32) +{ + REUSEADDR (os::SO_REUSEADDR), + KEEPALIVE (os::SO_KEEPALIVE), + BROADCAST (os::SO_BROADCAST), +} + +struct Socket +{ + NativeSocket sock; + Socklen_t ai_addrlen; + // TODO proper way to get the size of sockaddr_storage + // https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms740504(v=vs.85) + char[128] ai_addr_storage; +} + +fn Stream Socket.as_stream(&self) +{ + return { .fns = &socketstream_interface, .data = self }; +} + +StreamInterface socketstream_interface = { + .read_fn = fn(s, char[] bytes) => ((Socket*)s.data).read(bytes) @inline, + .write_fn = fn(s, char[] bytes) => ((Socket*)s.data).write(bytes) @inline, + .close_fn = fn(s) => ((Socket*)s.data).close(), +}; + +fn void! Socket.set_option(&self, SocketOption option) +{ + CInt flag = 1; + int errcode = os::setsockopt(self.sock, os::SOL_SOCKET, option.value, &flag, CInt.sizeof); + if (errcode != 0) return NetError.SOCKOPT_FAILED?; +} + +fn void! Socket.unset_option(&self, SocketOption option) +{ + CInt flag = 0; + int errcode = os::setsockopt(self.sock, os::SOL_SOCKET, option.value, &flag, CInt.sizeof); + if (errcode != 0) return NetError.SOCKOPT_FAILED?; +} + +fn usz! Socket.read(&self, char[] bytes) +{ + isz n = libc::read((Fd)self.sock, bytes.ptr, bytes.len); + if (n < 0) return NetError.READ_FAILED?; + return (usz)n; +} + +fn usz! Socket.write(&self, char[] bytes) +{ + isz n = libc::write((Fd)self.sock, bytes.ptr, bytes.len); + if (n < 0) return NetError.WRITE_FAILED?; + return (usz)n; +} + +fn void! Socket.close(&self) @inline +{ + self.sock.close()!; +} + +struct Listener +{ + Socket socket; +} + +fn void! Listener.init(&self, Network network, String host, String port, int backlog = 10, SocketOption... options) +{ + *self = { .socket = network.listen(host, port, backlog, ...options)! }; +} + +fn Socket! Listener.accept(&self) +{ + Socket sock = self.socket; + sock.sock = os::accept(sock.sock, (SockAddrPtr)&sock.ai_addr_storage, &sock.ai_addrlen); + if (sock.sock < 0) return NetError.ACCEPT_FAILED?; + return sock; +} + +fn void! Listener.close(&self) @inline +{ + self.socket.close()!; +} \ No newline at end of file