mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
Io.net: implement more networking
the next task is now implementing Io.Group
This commit is contained in:
parent
8771a9f082
commit
60c4bdb14c
5 changed files with 459 additions and 96 deletions
|
|
@ -663,13 +663,14 @@ pub const VTable = struct {
|
|||
now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp,
|
||||
sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void,
|
||||
|
||||
listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.ListenOptions) net.ListenError!net.Server,
|
||||
bind: *const fn (?*anyopaque, address: net.IpAddress, options: net.BindOptions) net.BindError!net.Socket,
|
||||
accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Server.Connection,
|
||||
netSend: *const fn (?*anyopaque, address: net.IpAddress, data: []const []const u8) net.SendError!void,
|
||||
listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,
|
||||
accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Stream,
|
||||
ipBind: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket,
|
||||
netSend: *const fn (?*anyopaque, handle: net.Socket.Handle, address: net.IpAddress, data: []const u8) net.Socket.SendError!void,
|
||||
netReceive: *const fn (?*anyopaque, handle: net.Socket.Handle, address: net.IpAddress, buffer: []u8) net.Socket.ReceiveError!void,
|
||||
netRead: *const fn (?*anyopaque, src: net.Stream, data: [][]u8) net.Stream.Reader.Error!usize,
|
||||
netWrite: *const fn (?*anyopaque, dest: net.Stream, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize,
|
||||
netClose: *const fn (?*anyopaque, socket: net.Socket) void,
|
||||
netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void,
|
||||
netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,
|
||||
netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,
|
||||
};
|
||||
|
|
@ -711,6 +712,10 @@ pub const Duration = struct {
|
|||
pub fn ms(x: u64) Duration {
|
||||
return .{ .nanoseconds = @as(i96, x) * std.time.ns_per_ms };
|
||||
}
|
||||
|
||||
pub fn seconds(x: u64) Duration {
|
||||
return .{ .nanoseconds = @as(i96, x) * std.time.ns_per_s };
|
||||
}
|
||||
};
|
||||
pub const Deadline = union(enum) {
|
||||
duration: Duration,
|
||||
|
|
|
|||
|
|
@ -124,8 +124,19 @@ pub fn io(pool: *Pool) Io {
|
|||
.now = now,
|
||||
.sleep = sleep,
|
||||
|
||||
.listen = listen,
|
||||
.accept = accept,
|
||||
.listen = switch (builtin.os.tag) {
|
||||
.windows => @panic("TODO"),
|
||||
else => listenPosix,
|
||||
},
|
||||
.accept = switch (builtin.os.tag) {
|
||||
.windows => @panic("TODO"),
|
||||
else => acceptPosix,
|
||||
},
|
||||
.ipBind = switch (builtin.os.tag) {
|
||||
.windows => @panic("TODO"),
|
||||
else => ipBindPosix,
|
||||
},
|
||||
.netClose = netClose,
|
||||
.netRead = switch (builtin.os.tag) {
|
||||
.windows => @panic("TODO"),
|
||||
else => netReadPosix,
|
||||
|
|
@ -134,7 +145,8 @@ pub fn io(pool: *Pool) Io {
|
|||
.windows => @panic("TODO"),
|
||||
else => netWritePosix,
|
||||
},
|
||||
.netClose = netClose,
|
||||
.netSend = netSend,
|
||||
.netReceive = netReceive,
|
||||
.netInterfaceNameResolve = netInterfaceNameResolve,
|
||||
.netInterfaceName = netInterfaceName,
|
||||
},
|
||||
|
|
@ -460,7 +472,7 @@ fn asyncDetached(
|
|||
|
||||
fn await(
|
||||
userdata: ?*anyopaque,
|
||||
any_future: *std.Io.AnyFuture,
|
||||
any_future: *Io.AnyFuture,
|
||||
result: []u8,
|
||||
result_alignment: std.mem.Alignment,
|
||||
) void {
|
||||
|
|
@ -984,59 +996,228 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
|
|||
return result.?;
|
||||
}
|
||||
|
||||
fn listen(userdata: ?*anyopaque, address: Io.net.IpAddress, options: Io.net.ListenOptions) Io.net.ListenError!Io.net.Server {
|
||||
fn listenPosix(
|
||||
userdata: ?*anyopaque,
|
||||
address: Io.net.IpAddress,
|
||||
options: Io.net.IpAddress.ListenOptions,
|
||||
) Io.net.IpAddress.ListenError!Io.net.Server {
|
||||
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||
const family = posixAddressFamily(&address);
|
||||
const protocol: u32 = posix.IPPROTO.TCP;
|
||||
const socket_fd = while (true) {
|
||||
try pool.checkCancel();
|
||||
|
||||
const nonblock: u32 = if (options.force_nonblocking) posix.SOCK.NONBLOCK else 0;
|
||||
const sock_flags = posix.SOCK.STREAM | posix.SOCK.CLOEXEC | nonblock;
|
||||
const proto: u32 = posix.IPPROTO.TCP;
|
||||
const family = posixAddressFamily(address);
|
||||
const sockfd = try posix.socket(family, sock_flags, proto);
|
||||
const stream: std.net.Stream = .{ .handle = sockfd };
|
||||
errdefer stream.close();
|
||||
const flags: u32 = posix.SOCK.STREAM | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC;
|
||||
const socket_rc = posix.system.socket(family, flags, protocol);
|
||||
switch (posix.errno(socket_rc)) {
|
||||
.SUCCESS => {
|
||||
const fd: posix.fd_t = @intCast(socket_rc);
|
||||
errdefer posix.close(fd);
|
||||
if (socket_flags_unsupported) while (true) {
|
||||
try pool.checkCancel();
|
||||
switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, posix.FD_CLOEXEC))) {
|
||||
.SUCCESS => break,
|
||||
.INTR => continue,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
};
|
||||
break fd;
|
||||
},
|
||||
.INTR => continue,
|
||||
.AFNOSUPPORT => return error.AddressFamilyUnsupported,
|
||||
.MFILE => return error.ProcessFdQuotaExceeded,
|
||||
.NFILE => return error.SystemFdQuotaExceeded,
|
||||
.NOBUFS => return error.SystemResources,
|
||||
.NOMEM => return error.SystemResources,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
};
|
||||
errdefer posix.close(socket_fd);
|
||||
|
||||
if (options.reuse_address) {
|
||||
try posix.setsockopt(
|
||||
sockfd,
|
||||
posix.SOL.SOCKET,
|
||||
posix.SO.REUSEADDR,
|
||||
&std.mem.toBytes(@as(c_int, 1)),
|
||||
);
|
||||
if (@hasDecl(posix.SO, "REUSEPORT") and family != posix.AF.UNIX) {
|
||||
try posix.setsockopt(
|
||||
sockfd,
|
||||
posix.SOL.SOCKET,
|
||||
posix.SO.REUSEPORT,
|
||||
&std.mem.toBytes(@as(c_int, 1)),
|
||||
);
|
||||
}
|
||||
try setSocketOption(pool, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1);
|
||||
if (@hasDecl(posix.SO, "REUSEPORT"))
|
||||
try setSocketOption(pool, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, 1);
|
||||
}
|
||||
|
||||
var storage: PosixAddress = undefined;
|
||||
var socklen = addressToPosix(address, &storage);
|
||||
try posix.bind(sockfd, &storage.any, socklen);
|
||||
try posix.listen(sockfd, options.kernel_backlog);
|
||||
try posix.getsockname(sockfd, &storage.any, &socklen);
|
||||
try posixBind(pool, socket_fd, &storage.any, socklen);
|
||||
|
||||
while (true) {
|
||||
try pool.checkCancel();
|
||||
switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) {
|
||||
.SUCCESS => break,
|
||||
.ADDRINUSE => return error.AddressInUse,
|
||||
.BADF => |err| return errnoBug(err),
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
try posixGetSockName(pool, socket_fd, &storage.any, &socklen);
|
||||
return .{
|
||||
.listen_address = addressFromPosix(&storage),
|
||||
.stream = .{ .handle = stream.handle },
|
||||
.socket = .{
|
||||
.handle = socket_fd,
|
||||
.address = addressFromPosix(&storage),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn accept(userdata: ?*anyopaque, server: *Io.net.Server) Io.net.Server.AcceptError!Io.net.Server.Connection {
|
||||
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||
fn posixBind(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void {
|
||||
while (true) {
|
||||
try pool.checkCancel();
|
||||
switch (posix.errno(posix.system.bind(socket_fd, addr, addr_len))) {
|
||||
.SUCCESS => break,
|
||||
.INTR => continue,
|
||||
.ADDRINUSE => return error.AddressInUse,
|
||||
.BADF => |err| return errnoBug(err), // always a race condition if this error is returned
|
||||
.INVAL => |err| return errnoBug(err), // invalid parameters
|
||||
.NOTSOCK => |err| return errnoBug(err), // invalid `sockfd`
|
||||
.AFNOSUPPORT => return error.AddressFamilyUnsupported,
|
||||
.ADDRNOTAVAIL => return error.AddressUnavailable,
|
||||
.FAULT => |err| return errnoBug(err), // invalid `addr` pointer
|
||||
.NOMEM => return error.SystemResources,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn posixGetSockName(pool: *Pool, socket_fd: posix.fd_t, addr: *posix.sockaddr, addr_len: *posix.socklen_t) !void {
|
||||
while (true) {
|
||||
try pool.checkCancel();
|
||||
switch (posix.errno(posix.system.getsockname(socket_fd, addr, addr_len))) {
|
||||
.SUCCESS => break,
|
||||
.INTR => continue,
|
||||
.BADF => |err| return errnoBug(err), // always a race condition
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => |err| return errnoBug(err), // invalid parameters
|
||||
.NOTSOCK => |err| return errnoBug(err), // always a race condition
|
||||
.NOBUFS => return error.SystemResources,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setSocketOption(pool: *Pool, fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void {
|
||||
const o: []const u8 = @ptrCast(&option);
|
||||
while (true) {
|
||||
try pool.checkCancel();
|
||||
switch (posix.errno(posix.system.setsockopt(fd, level, opt_name, o.ptr, @intCast(o.len)))) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.BADF => |err| return errnoBug(err), // always a race condition
|
||||
.NOTSOCK => |err| return errnoBug(err), // always a race condition
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ipBindPosix(
|
||||
userdata: ?*anyopaque,
|
||||
address: Io.net.IpAddress,
|
||||
options: Io.net.IpAddress.BindOptions,
|
||||
) Io.net.IpAddress.BindError!Io.net.Socket {
|
||||
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||
const mode = posixSocketMode(options.mode);
|
||||
const family = posixAddressFamily(&address);
|
||||
const protocol = posixProtocol(options.protocol);
|
||||
const socket_fd = while (true) {
|
||||
try pool.checkCancel();
|
||||
const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC;
|
||||
const socket_rc = posix.system.socket(family, flags, protocol);
|
||||
switch (posix.errno(socket_rc)) {
|
||||
.SUCCESS => {
|
||||
const fd: posix.fd_t = @intCast(socket_rc);
|
||||
errdefer posix.close(fd);
|
||||
if (socket_flags_unsupported) while (true) {
|
||||
try pool.checkCancel();
|
||||
switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, posix.FD_CLOEXEC))) {
|
||||
.SUCCESS => break,
|
||||
.INTR => continue,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
};
|
||||
break fd;
|
||||
},
|
||||
.INTR => continue,
|
||||
.AFNOSUPPORT => return error.AddressFamilyUnsupported,
|
||||
.INVAL => return error.ProtocolUnsupportedBySystem,
|
||||
.MFILE => return error.ProcessFdQuotaExceeded,
|
||||
.NFILE => return error.SystemFdQuotaExceeded,
|
||||
.NOBUFS => return error.SystemResources,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.PROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily,
|
||||
.PROTOTYPE => return error.SocketModeUnsupported,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
};
|
||||
|
||||
if (options.ip6_only) {
|
||||
try setSocketOption(pool, socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, 0);
|
||||
}
|
||||
|
||||
var storage: PosixAddress = undefined;
|
||||
var addr_len: posix.socklen_t = @sizeOf(PosixAddress);
|
||||
const fd = try posix.accept(server.stream.handle, &storage.any, &addr_len, posix.SOCK.CLOEXEC);
|
||||
var socklen = addressToPosix(address, &storage);
|
||||
try posixBind(pool, socket_fd, &storage.any, socklen);
|
||||
try posixGetSockName(pool, socket_fd, &storage.any, &socklen);
|
||||
return .{
|
||||
.stream = .{ .handle = fd },
|
||||
.handle = socket_fd,
|
||||
.address = addressFromPosix(&storage),
|
||||
};
|
||||
}
|
||||
|
||||
const socket_flags_unsupported = builtin.os.tag.isDarwin() or native_os == .haiku; // 💩💩
|
||||
const have_accept4 = !socket_flags_unsupported;
|
||||
|
||||
fn acceptPosix(userdata: ?*anyopaque, server: *Io.net.Server) Io.net.Server.AcceptError!Io.net.Stream {
|
||||
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||
const listen_fd = server.socket.handle;
|
||||
var storage: PosixAddress = undefined;
|
||||
var addr_len: posix.socklen_t = @sizeOf(PosixAddress);
|
||||
const fd = while (true) {
|
||||
try pool.checkCancel();
|
||||
const rc = if (have_accept4)
|
||||
posix.system.accept4(listen_fd, &storage.any, &addr_len, posix.SOCK.CLOEXEC)
|
||||
else
|
||||
posix.system.accept(listen_fd, &storage.any, &addr_len);
|
||||
switch (posix.errno(rc)) {
|
||||
.SUCCESS => {
|
||||
const fd: posix.fd_t = @intCast(rc);
|
||||
errdefer posix.close(fd);
|
||||
if (!have_accept4) while (true) {
|
||||
try pool.checkCancel();
|
||||
switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, posix.FD_CLOEXEC))) {
|
||||
.SUCCESS => break,
|
||||
.INTR => continue,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
};
|
||||
break fd;
|
||||
},
|
||||
.INTR => continue,
|
||||
.AGAIN => |err| return errnoBug(err),
|
||||
.BADF => |err| return errnoBug(err), // always a race condition
|
||||
.CONNABORTED => return error.ConnectionAborted,
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => return error.SocketNotListening,
|
||||
.NOTSOCK => |err| return errnoBug(err),
|
||||
.MFILE => return error.ProcessFdQuotaExceeded,
|
||||
.NFILE => return error.SystemFdQuotaExceeded,
|
||||
.NOBUFS => return error.SystemResources,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.OPNOTSUPP => |err| return errnoBug(err),
|
||||
.PROTO => return error.ProtocolFailure,
|
||||
.PERM => return error.BlockedByFirewall,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
};
|
||||
return .{ .socket = .{
|
||||
.handle = fd,
|
||||
.address = addressFromPosix(&storage),
|
||||
} };
|
||||
}
|
||||
|
||||
fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.net.Stream.Reader.Error!usize {
|
||||
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||
try pool.checkCancel();
|
||||
|
|
@ -1052,11 +1233,41 @@ fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.n
|
|||
}
|
||||
const dest = iovecs_buffer[0..i];
|
||||
assert(dest[0].len > 0);
|
||||
const n = try posix.readv(stream.handle, dest);
|
||||
const n = try posix.readv(stream.socket.handle, dest);
|
||||
if (n == 0) return error.EndOfStream;
|
||||
return n;
|
||||
}
|
||||
|
||||
fn netSend(
|
||||
userdata: ?*anyopaque,
|
||||
handle: Io.net.Socket.Handle,
|
||||
address: Io.net.IpAddress,
|
||||
data: []const u8,
|
||||
) Io.net.Socket.SendError!void {
|
||||
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||
try pool.checkCancel();
|
||||
|
||||
_ = handle;
|
||||
_ = address;
|
||||
_ = data;
|
||||
@panic("TODO");
|
||||
}
|
||||
|
||||
fn netReceive(
|
||||
userdata: ?*anyopaque,
|
||||
handle: Io.net.Socket.Handle,
|
||||
address: Io.net.IpAddress,
|
||||
buffer: []u8,
|
||||
) Io.net.Socket.ReceiveError!void {
|
||||
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||
try pool.checkCancel();
|
||||
|
||||
_ = handle;
|
||||
_ = address;
|
||||
_ = buffer;
|
||||
@panic("TODO");
|
||||
}
|
||||
|
||||
fn netWritePosix(
|
||||
userdata: ?*anyopaque,
|
||||
stream: Io.net.Stream,
|
||||
|
|
@ -1106,7 +1317,7 @@ fn netWritePosix(
|
|||
},
|
||||
};
|
||||
const flags = posix.MSG.NOSIGNAL;
|
||||
return posix.sendmsg(stream.handle, &msg, flags);
|
||||
return posix.sendmsg(stream.socket.handle, &msg, flags);
|
||||
}
|
||||
|
||||
fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), bytes: []const u8) void {
|
||||
|
|
@ -1117,11 +1328,13 @@ fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"),
|
|||
i.* += 1;
|
||||
}
|
||||
|
||||
fn netClose(userdata: ?*anyopaque, stream: Io.net.Stream) void {
|
||||
fn netClose(userdata: ?*anyopaque, handle: Io.net.Socket.Handle) void {
|
||||
const pool: *Pool = @ptrCast(@alignCast(userdata));
|
||||
_ = pool;
|
||||
const net_stream: std.net.Stream = .{ .handle = stream.handle };
|
||||
return net_stream.close();
|
||||
switch (native_os) {
|
||||
.windows => windows.closesocket(handle) catch recoverableOsBugDetected(),
|
||||
else => posix.close(handle),
|
||||
}
|
||||
}
|
||||
|
||||
fn netInterfaceNameResolve(
|
||||
|
|
@ -1153,13 +1366,13 @@ fn netInterfaceNameResolve(
|
|||
try pool.checkCancel();
|
||||
switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) {
|
||||
.SUCCESS => return .{ .index = @bitCast(ifr.ifru.ivalue) },
|
||||
.INVAL => |err| return badErrno(err), // Bad parameters.
|
||||
.NOTTY => |err| return badErrno(err),
|
||||
.NXIO => |err| return badErrno(err),
|
||||
.BADF => |err| return badErrno(err), // Always a race condition.
|
||||
.FAULT => |err| return badErrno(err), // Bad pointer parameter.
|
||||
.INVAL => |err| return errnoBug(err), // Bad parameters.
|
||||
.NOTTY => |err| return errnoBug(err),
|
||||
.NXIO => |err| return errnoBug(err),
|
||||
.BADF => |err| return errnoBug(err), // Always a race condition.
|
||||
.FAULT => |err| return errnoBug(err), // Bad pointer parameter.
|
||||
.INTR => continue,
|
||||
.IO => |err| return badErrno(err), // sock_fd is not a file descriptor
|
||||
.IO => |err| return errnoBug(err), // sock_fd is not a file descriptor
|
||||
.NODEV => return error.InterfaceNotFound,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
|
|
@ -1207,8 +1420,8 @@ const PosixAddress = extern union {
|
|||
in6: posix.sockaddr.in6,
|
||||
};
|
||||
|
||||
fn posixAddressFamily(a: Io.net.IpAddress) posix.sa_family_t {
|
||||
return switch (a) {
|
||||
fn posixAddressFamily(a: *const Io.net.IpAddress) posix.sa_family_t {
|
||||
return switch (a.*) {
|
||||
.ip4 => posix.AF.INET,
|
||||
.ip6 => posix.AF.INET6,
|
||||
};
|
||||
|
|
@ -1267,9 +1480,27 @@ fn address6ToPosix(a: Io.net.Ip6Address) posix.sockaddr.in6 {
|
|||
};
|
||||
}
|
||||
|
||||
fn badErrno(err: posix.E) Io.UnexpectedError {
|
||||
fn errnoBug(err: posix.E) Io.UnexpectedError {
|
||||
switch (builtin.mode) {
|
||||
.Debug => std.debug.panic("programmer bug caused syscall error: {t}", .{err}),
|
||||
else => return error.Unexpected,
|
||||
}
|
||||
}
|
||||
|
||||
fn posixSocketMode(mode: Io.net.Socket.Mode) u32 {
|
||||
return switch (mode) {
|
||||
.stream => posix.SOCK.STREAM,
|
||||
.dgram => posix.SOCK.DGRAM,
|
||||
.seqpacket => posix.SOCK.SEQPACKET,
|
||||
.raw => posix.SOCK.RAW,
|
||||
.rdm => posix.SOCK.RDM,
|
||||
};
|
||||
}
|
||||
|
||||
fn posixProtocol(protocol: ?Io.net.Protocol) u32 {
|
||||
return @intFromEnum(protocol orelse return 0);
|
||||
}
|
||||
|
||||
fn recoverableOsBugDetected() void {
|
||||
if (builtin.mode == .Debug) unreachable;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,26 +6,41 @@ const assert = std.debug.assert;
|
|||
|
||||
pub const HostName = @import("net/HostName.zig");
|
||||
|
||||
pub const ListenError = std.net.Address.ListenError || Io.Cancelable;
|
||||
|
||||
pub const BindError = std.net.Address.BindError || Io.Cancelable;
|
||||
|
||||
pub const ListenOptions = struct {
|
||||
/// How many connections the kernel will accept on the application's behalf.
|
||||
/// If more than this many connections pool in the kernel, clients will start
|
||||
/// seeing "Connection refused".
|
||||
kernel_backlog: u31 = 128,
|
||||
/// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX.
|
||||
/// Sets SO_REUSEADDR on Windows, which is roughly equivalent.
|
||||
reuse_address: bool = false,
|
||||
force_nonblocking: bool = false,
|
||||
};
|
||||
|
||||
pub const BindOptions = struct {
|
||||
/// The socket is restricted to sending and receiving IPv6 packets only.
|
||||
/// In this case, an IPv4 and an IPv6 application can bind to a single port
|
||||
/// at the same time.
|
||||
ip6_only: bool = false,
|
||||
/// Source of truth: Internet Assigned Numbers Authority (IANA)
|
||||
pub const Protocol = enum(u32) {
|
||||
hopopts = 0,
|
||||
icmp = 1,
|
||||
igmp = 2,
|
||||
ipip = 4,
|
||||
tcp = 6,
|
||||
egp = 8,
|
||||
pup = 12,
|
||||
udp = 17,
|
||||
idp = 22,
|
||||
tp = 29,
|
||||
dccp = 33,
|
||||
ipv6 = 41,
|
||||
routing = 43,
|
||||
fragment = 44,
|
||||
rsvp = 46,
|
||||
gre = 47,
|
||||
esp = 50,
|
||||
ah = 51,
|
||||
icmpv6 = 58,
|
||||
none = 59,
|
||||
dstopts = 60,
|
||||
mtp = 92,
|
||||
beetph = 94,
|
||||
encap = 98,
|
||||
pim = 103,
|
||||
comp = 108,
|
||||
sctp = 132,
|
||||
mh = 135,
|
||||
udplite = 136,
|
||||
mpls = 137,
|
||||
ethernet = 143,
|
||||
raw = 255,
|
||||
mptcp = 262,
|
||||
};
|
||||
|
||||
pub const IpAddress = union(enum) {
|
||||
|
|
@ -132,12 +147,70 @@ pub const IpAddress = union(enum) {
|
|||
};
|
||||
}
|
||||
|
||||
pub const ListenError = error{
|
||||
/// The address is already taken. Can occur when bound port is 0 but
|
||||
/// all ephemeral ports are already in use.
|
||||
AddressInUse,
|
||||
/// A nonexistent interface was requested or the requested address was not local.
|
||||
AddressUnavailable,
|
||||
/// The local network interface used to reach the destination is offline.
|
||||
NetworkSubsystemDown,
|
||||
/// Insufficient memory or other resource internal to the operating system.
|
||||
SystemResources,
|
||||
/// Per-process limit on the number of open file descriptors has been reached.
|
||||
ProcessFdQuotaExceeded,
|
||||
/// System-wide limit on the total number of open files has been reached.
|
||||
SystemFdQuotaExceeded,
|
||||
/// The requested address family (IPv4 or IPv6) is not supported by the operating system.
|
||||
AddressFamilyUnsupported,
|
||||
} || Io.UnexpectedError || Io.Cancelable;
|
||||
|
||||
pub const ListenOptions = struct {
|
||||
/// How many connections the kernel will accept on the application's behalf.
|
||||
/// If more than this many connections pool in the kernel, clients will start
|
||||
/// seeing "Connection refused".
|
||||
kernel_backlog: u31 = 128,
|
||||
/// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX.
|
||||
/// Sets SO_REUSEADDR on Windows, which is roughly equivalent.
|
||||
reuse_address: bool = false,
|
||||
};
|
||||
|
||||
/// Waits for a TCP connection. When using this API, `bind` does not need
|
||||
/// to be called. The returned `Server` has an open `stream`.
|
||||
pub fn listen(address: IpAddress, io: Io, options: ListenOptions) ListenError!Server {
|
||||
return io.vtable.listen(io.userdata, address, options);
|
||||
return io.vtable.tcpListen(io.userdata, address, options);
|
||||
}
|
||||
|
||||
pub const BindError = error{
|
||||
/// The address is already taken. Can occur when bound port is 0 but
|
||||
/// all ephemeral ports are already in use.
|
||||
AddressInUse,
|
||||
/// A nonexistent interface was requested or the requested address was not local.
|
||||
AddressUnavailable,
|
||||
/// The address is not valid for the address family of socket.
|
||||
AddressFamilyUnsupported,
|
||||
/// Insufficient memory or other resource internal to the operating system.
|
||||
SystemResources,
|
||||
/// The local network interface used to reach the destination is offline.
|
||||
NetworkSubsystemDown,
|
||||
ProtocolUnsupportedBySystem,
|
||||
ProtocolUnsupportedByAddressFamily,
|
||||
/// Per-process limit on the number of open file descriptors has been reached.
|
||||
ProcessFdQuotaExceeded,
|
||||
/// System-wide limit on the total number of open files has been reached.
|
||||
SystemFdQuotaExceeded,
|
||||
SocketModeUnsupported,
|
||||
} || Io.UnexpectedError || Io.Cancelable;
|
||||
|
||||
pub const BindOptions = struct {
|
||||
/// The socket is restricted to sending and receiving IPv6 packets only.
|
||||
/// In this case, an IPv4 and an IPv6 application can bind to a single port
|
||||
/// at the same time.
|
||||
ip6_only: bool = false,
|
||||
mode: Socket.Mode,
|
||||
protocol: ?Protocol = null,
|
||||
};
|
||||
|
||||
/// Associates an address with a `Socket` which can be used to receive UDP
|
||||
/// packets and other kinds of non-streaming messages. See `listen` for a
|
||||
/// streaming alternative.
|
||||
|
|
@ -145,7 +218,7 @@ pub const IpAddress = union(enum) {
|
|||
/// One bound `Socket` can be used to receive messages from multiple
|
||||
/// different addresses.
|
||||
pub fn bind(address: IpAddress, io: Io, options: BindOptions) BindError!Socket {
|
||||
return io.vtable.bind(io.userdata, address, options);
|
||||
return io.vtable.ipBind(io.userdata, address, options);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -255,7 +328,7 @@ pub const Ip6Address = struct {
|
|||
pub fn fromIp4(ip4: Ip4Address) Ip6Address {
|
||||
const b = &ip4.bytes;
|
||||
return .{
|
||||
.bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, b[0], b[1], b[2], b[3] },
|
||||
.bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, b[0], b[1], b[2], b[3] },
|
||||
.port = ip4.port,
|
||||
};
|
||||
}
|
||||
|
|
@ -682,7 +755,25 @@ pub const Interface = struct {
|
|||
pub const Socket = struct {
|
||||
handle: Handle,
|
||||
/// Contains the resolved ephemeral port number if requested.
|
||||
bind_address: IpAddress,
|
||||
address: IpAddress,
|
||||
|
||||
pub const Mode = enum {
|
||||
/// Provides sequenced, reliable, two-way, connection-based byte
|
||||
/// streams. An out-of-band data transmission mechanism may be
|
||||
/// supported.
|
||||
stream,
|
||||
/// Supports datagrams (connectionless, unreliable messages of a fixed
|
||||
/// maximum length).
|
||||
dgram,
|
||||
/// Provides a sequenced, reliable, two-way connection-based data
|
||||
/// transmission path for datagrams of fixed maximum length; a consumer
|
||||
/// is required to read an entire packet with each input system call.
|
||||
seqpacket,
|
||||
/// Provides raw network protocol access.
|
||||
raw,
|
||||
/// Provides a reliable datagram layer that does not guarantee ordering.
|
||||
rdm,
|
||||
};
|
||||
|
||||
/// Underlying platform-defined type which may or may not be
|
||||
/// interchangeable with a file system file descriptor.
|
||||
|
|
@ -691,8 +782,48 @@ pub const Socket = struct {
|
|||
else => std.posix.fd_t,
|
||||
};
|
||||
|
||||
pub fn close(s: Socket, io: Io) void {
|
||||
return io.vtable.netClose(io.userdata, s);
|
||||
pub fn close(s: *Socket, io: Io) void {
|
||||
io.vtable.netClose(io.userdata, s.handle);
|
||||
s.handle = undefined;
|
||||
}
|
||||
|
||||
pub const SendError = error{
|
||||
/// The socket type requires that message be sent atomically, and the size of the message
|
||||
/// to be sent made this impossible. The message is not transmitted.
|
||||
MessageTooBig,
|
||||
/// The output queue for a network interface was full. This generally indicates that the
|
||||
/// interface has stopped sending, but may be caused by transient congestion. (Normally,
|
||||
/// this does not occur in Linux. Packets are just silently dropped when a device queue
|
||||
/// overflows.)
|
||||
///
|
||||
/// This is also caused when there is not enough kernel memory available.
|
||||
SystemResources,
|
||||
/// No route to network.
|
||||
NetworkUnreachable,
|
||||
/// Network reached but no route to host.
|
||||
HostUnreachable,
|
||||
/// The local network interface used to reach the destination is offline.
|
||||
NetworkSubsystemDown,
|
||||
/// The destination address is not listening. Can still occur for
|
||||
/// connectionless messages.
|
||||
ConnectionRefused,
|
||||
/// Operating system or protocol does not support the address family.
|
||||
AddressFamilyUnsupported,
|
||||
} || Io.UnexpectedError || Io.Cancelable;
|
||||
|
||||
/// Transfers `data` to `dest`, connectionless.
|
||||
pub fn send(s: *const Socket, io: Io, dest: *const IpAddress, data: []const u8) SendError!void {
|
||||
return io.vtable.netSend(io.userdata, s.handle, dest, data);
|
||||
}
|
||||
|
||||
pub const ReceiveError = error{} || Io.Cancelable;
|
||||
|
||||
/// Transfers `data` from `source`, connectionless.
|
||||
///
|
||||
/// Returned slice has same pointer as `buffer` with possibly shorter length.
|
||||
pub fn receive(s: *const Socket, io: Io, source: *const IpAddress, buffer: []u8) ReceiveError![]u8 {
|
||||
const n = try io.vtable.netReceive(io.userdata, s.handle, source, buffer);
|
||||
return buffer[0..n];
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -784,11 +915,6 @@ pub const Stream = struct {
|
|||
pub const Server = struct {
|
||||
socket: Socket,
|
||||
|
||||
pub const Connection = struct {
|
||||
stream: Stream,
|
||||
address: IpAddress,
|
||||
};
|
||||
|
||||
pub fn deinit(s: *Server, io: Io) void {
|
||||
s.socket.close(io);
|
||||
s.* = undefined;
|
||||
|
|
@ -796,9 +922,8 @@ pub const Server = struct {
|
|||
|
||||
pub const AcceptError = std.posix.AcceptError || Io.Cancelable;
|
||||
|
||||
/// Blocks until a client connects to the server. The returned `Connection` has
|
||||
/// an open stream.
|
||||
pub fn accept(s: *Server, io: Io) AcceptError!Connection {
|
||||
/// Blocks until a client connects to the server.
|
||||
pub fn accept(s: *Server, io: Io) AcceptError!Stream {
|
||||
return io.vtable.accept(io, s);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -539,7 +539,7 @@ pub const ResolvConf = struct {
|
|||
.search_buffer = undefined,
|
||||
.search_len = 0,
|
||||
.ndots = 1,
|
||||
.timeout = 5,
|
||||
.timeout = .seconds(5),
|
||||
.attempts = 2,
|
||||
};
|
||||
|
||||
|
|
@ -589,7 +589,7 @@ pub const ResolvConf = struct {
|
|||
switch (std.meta.stringToEnum(Option, name) orelse continue) {
|
||||
.ndots => rc.ndots = @min(value, 15),
|
||||
.attempts => rc.attempts = @min(value, 10),
|
||||
.timeout => rc.timeout = @min(value, 60),
|
||||
.timeout => rc.timeout = .seconds(@min(value, 60)),
|
||||
}
|
||||
},
|
||||
.nameserver => {
|
||||
|
|
@ -638,14 +638,15 @@ pub const ResolvConf = struct {
|
|||
const socket = s: {
|
||||
if (any_ip6) ip6: {
|
||||
const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) };
|
||||
const socket = ip6_addr.bind(io, .{ .ip6_only = true }) catch |err| switch (err) {
|
||||
error.AddressFamilyNotSupported => break :ip6,
|
||||
const socket = ip6_addr.bind(io, .{ .ip6_only = true, .mode = .dgram }) catch |err| switch (err) {
|
||||
error.AddressFamilyUnsupported => break :ip6,
|
||||
else => |e| return e,
|
||||
};
|
||||
break :s socket;
|
||||
}
|
||||
any_ip6 = false;
|
||||
const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) };
|
||||
const socket = try ip4_addr.bind(io, .{});
|
||||
const socket = try ip4_addr.bind(io, .{ .mode = .dgram });
|
||||
break :s socket;
|
||||
};
|
||||
defer socket.close();
|
||||
|
|
|
|||
|
|
@ -2384,6 +2384,7 @@ pub const Stream = struct {
|
|||
}
|
||||
};
|
||||
|
||||
/// A bound, listening TCP socket, ready to accept new connections.
|
||||
pub const Server = struct {
|
||||
listen_address: Address,
|
||||
stream: Stream,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue