mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 03:51:18 +00:00
cross platform socket interface (#857)
* lib/std/net: add Network, Socket and Listener Signed-off-by: Pierre Curto <pierre.curto@gmail.com> * lib/std/net: add SocketOption Signed-off-by: Pierre Curto <pierre.curto@gmail.com> * lib/std/net: fixes for win32 and wasm Signed-off-by: Pierre Curto <pierre.curto@gmail.com> --------- Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
This commit is contained in:
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
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;
|
||||
@@ -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;
|
||||
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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
170
lib/std/net/socket.c3
Normal file
170
lib/std/net/socket.c3
Normal file
@@ -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()!;
|
||||
}
|
||||
Reference in New Issue
Block a user