authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-26 18:16:22-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-02 16:30:59-07:00
log79b807bf1c328af7bd7d27ef27dfd3938113ea40
treec87e37e976d5595406883176f49bf0ce10aaa313
parent3e8cc9c4960043e87cd7fe3a12958d7b1e6a16e8

Io.net: implement more networking

the next task is now implementing Io.Group

5 files changed, 460 insertions(+), 97 deletions(-)

lib/std/Io.zig+10-5
...@@ -663,13 +663,14 @@ pub const VTable = struct {...@@ -663,13 +663,14 @@ pub const VTable = struct {
663 now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp,663 now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp,
664 sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void,664 sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void,
665665
666 listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.ListenOptions) net.ListenError!net.Server,666 listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,
667 bind: *const fn (?*anyopaque, address: net.IpAddress, options: net.BindOptions) net.BindError!net.Socket,667 accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Stream,
668 accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Server.Connection,668 ipBind: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket,
669 netSend: *const fn (?*anyopaque, address: net.IpAddress, data: []const []const u8) net.SendError!void,669 netSend: *const fn (?*anyopaque, handle: net.Socket.Handle, address: net.IpAddress, data: []const u8) net.Socket.SendError!void,
670 netReceive: *const fn (?*anyopaque, handle: net.Socket.Handle, address: net.IpAddress, buffer: []u8) net.Socket.ReceiveError!void,
670 netRead: *const fn (?*anyopaque, src: net.Stream, data: [][]u8) net.Stream.Reader.Error!usize,671 netRead: *const fn (?*anyopaque, src: net.Stream, data: [][]u8) net.Stream.Reader.Error!usize,
671 netWrite: *const fn (?*anyopaque, dest: net.Stream, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize,672 netWrite: *const fn (?*anyopaque, dest: net.Stream, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize,
672 netClose: *const fn (?*anyopaque, socket: net.Socket) void,673 netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void,
673 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,674 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,
674 netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,675 netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,
675};676};
...@@ -711,6 +712,10 @@ pub const Duration = struct {...@@ -711,6 +712,10 @@ pub const Duration = struct {
711 pub fn ms(x: u64) Duration {712 pub fn ms(x: u64) Duration {
712 return .{ .nanoseconds = @as(i96, x) * std.time.ns_per_ms };713 return .{ .nanoseconds = @as(i96, x) * std.time.ns_per_ms };
713 }714 }
715
716 pub fn seconds(x: u64) Duration {
717 return .{ .nanoseconds = @as(i96, x) * std.time.ns_per_s };
718 }
714};719};
715pub const Deadline = union(enum) {720pub const Deadline = union(enum) {
716 duration: Duration,721 duration: Duration,
lib/std/Io/Threaded.zig+283-52
...@@ -124,8 +124,19 @@ pub fn io(pool: *Pool) Io {...@@ -124,8 +124,19 @@ pub fn io(pool: *Pool) Io {
124 .now = now,124 .now = now,
125 .sleep = sleep,125 .sleep = sleep,
126126
127 .listen = listen,127 .listen = switch (builtin.os.tag) {
128 .accept = accept,128 .windows => @panic("TODO"),
129 else => listenPosix,
130 },
131 .accept = switch (builtin.os.tag) {
132 .windows => @panic("TODO"),
133 else => acceptPosix,
134 },
135 .ipBind = switch (builtin.os.tag) {
136 .windows => @panic("TODO"),
137 else => ipBindPosix,
138 },
139 .netClose = netClose,
129 .netRead = switch (builtin.os.tag) {140 .netRead = switch (builtin.os.tag) {
130 .windows => @panic("TODO"),141 .windows => @panic("TODO"),
131 else => netReadPosix,142 else => netReadPosix,
...@@ -134,7 +145,8 @@ pub fn io(pool: *Pool) Io {...@@ -134,7 +145,8 @@ pub fn io(pool: *Pool) Io {
134 .windows => @panic("TODO"),145 .windows => @panic("TODO"),
135 else => netWritePosix,146 else => netWritePosix,
136 },147 },
137 .netClose = netClose,148 .netSend = netSend,
149 .netReceive = netReceive,
138 .netInterfaceNameResolve = netInterfaceNameResolve,150 .netInterfaceNameResolve = netInterfaceNameResolve,
139 .netInterfaceName = netInterfaceName,151 .netInterfaceName = netInterfaceName,
140 },152 },
...@@ -460,7 +472,7 @@ fn asyncDetached(...@@ -460,7 +472,7 @@ fn asyncDetached(
460472
461fn await(473fn await(
462 userdata: ?*anyopaque,474 userdata: ?*anyopaque,
463 any_future: *std.Io.AnyFuture,475 any_future: *Io.AnyFuture,
464 result: []u8,476 result: []u8,
465 result_alignment: std.mem.Alignment,477 result_alignment: std.mem.Alignment,
466) void {478) void {
...@@ -984,59 +996,228 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {...@@ -984,59 +996,228 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
984 return result.?;996 return result.?;
985}997}
986998
987fn listen(userdata: ?*anyopaque, address: Io.net.IpAddress, options: Io.net.ListenOptions) Io.net.ListenError!Io.net.Server {999fn listenPosix(
1000 userdata: ?*anyopaque,
1001 address: Io.net.IpAddress,
1002 options: Io.net.IpAddress.ListenOptions,
1003) Io.net.IpAddress.ListenError!Io.net.Server {
988 const pool: *Pool = @ptrCast(@alignCast(userdata));1004 const pool: *Pool = @ptrCast(@alignCast(userdata));
989 try pool.checkCancel();1005 const family = posixAddressFamily(&address);
9901006 const protocol: u32 = posix.IPPROTO.TCP;
991 const nonblock: u32 = if (options.force_nonblocking) posix.SOCK.NONBLOCK else 0;1007 const socket_fd = while (true) {
992 const sock_flags = posix.SOCK.STREAM | posix.SOCK.CLOEXEC | nonblock;1008 try pool.checkCancel();
993 const proto: u32 = posix.IPPROTO.TCP;1009 const flags: u32 = posix.SOCK.STREAM | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC;
994 const family = posixAddressFamily(address);1010 const socket_rc = posix.system.socket(family, flags, protocol);
995 const sockfd = try posix.socket(family, sock_flags, proto);1011 switch (posix.errno(socket_rc)) {
996 const stream: std.net.Stream = .{ .handle = sockfd };1012 .SUCCESS => {
997 errdefer stream.close();1013 const fd: posix.fd_t = @intCast(socket_rc);
1014 errdefer posix.close(fd);
1015 if (socket_flags_unsupported) while (true) {
1016 try pool.checkCancel();
1017 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, posix.FD_CLOEXEC))) {
1018 .SUCCESS => break,
1019 .INTR => continue,
1020 else => |err| return posix.unexpectedErrno(err),
1021 }
1022 };
1023 break fd;
1024 },
1025 .INTR => continue,
1026 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
1027 .MFILE => return error.ProcessFdQuotaExceeded,
1028 .NFILE => return error.SystemFdQuotaExceeded,
1029 .NOBUFS => return error.SystemResources,
1030 .NOMEM => return error.SystemResources,
1031 else => |err| return posix.unexpectedErrno(err),
1032 }
1033 };
1034 errdefer posix.close(socket_fd);
9981035
999 if (options.reuse_address) {1036 if (options.reuse_address) {
1000 try posix.setsockopt(1037 try setSocketOption(pool, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1);
1001 sockfd,1038 if (@hasDecl(posix.SO, "REUSEPORT"))
1002 posix.SOL.SOCKET,1039 try setSocketOption(pool, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, 1);
1003 posix.SO.REUSEADDR,
1004 &std.mem.toBytes(@as(c_int, 1)),
1005 );
1006 if (@hasDecl(posix.SO, "REUSEPORT") and family != posix.AF.UNIX) {
1007 try posix.setsockopt(
1008 sockfd,
1009 posix.SOL.SOCKET,
1010 posix.SO.REUSEPORT,
1011 &std.mem.toBytes(@as(c_int, 1)),
1012 );
1013 }
1014 }1040 }
10151041
1016 var storage: PosixAddress = undefined;1042 var storage: PosixAddress = undefined;
1017 var socklen = addressToPosix(address, &storage);1043 var socklen = addressToPosix(address, &storage);
1018 try posix.bind(sockfd, &storage.any, socklen);1044 try posixBind(pool, socket_fd, &storage.any, socklen);
1019 try posix.listen(sockfd, options.kernel_backlog);1045
1020 try posix.getsockname(sockfd, &storage.any, &socklen);1046 while (true) {
1047 try pool.checkCancel();
1048 switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) {
1049 .SUCCESS => break,
1050 .ADDRINUSE => return error.AddressInUse,
1051 .BADF => |err| return errnoBug(err),
1052 else => |err| return posix.unexpectedErrno(err),
1053 }
1054 }
1055
1056 try posixGetSockName(pool, socket_fd, &storage.any, &socklen);
1021 return .{1057 return .{
1022 .listen_address = addressFromPosix(&storage),1058 .socket = .{
1023 .stream = .{ .handle = stream.handle },1059 .handle = socket_fd,
1060 .address = addressFromPosix(&storage),
1061 },
1024 };1062 };
1025}1063}
10261064
1027fn accept(userdata: ?*anyopaque, server: *Io.net.Server) Io.net.Server.AcceptError!Io.net.Server.Connection {1065fn posixBind(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void {
1066 while (true) {
1067 try pool.checkCancel();
1068 switch (posix.errno(posix.system.bind(socket_fd, addr, addr_len))) {
1069 .SUCCESS => break,
1070 .INTR => continue,
1071 .ADDRINUSE => return error.AddressInUse,
1072 .BADF => |err| return errnoBug(err), // always a race condition if this error is returned
1073 .INVAL => |err| return errnoBug(err), // invalid parameters
1074 .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd`
1075 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
1076 .ADDRNOTAVAIL => return error.AddressUnavailable,
1077 .FAULT => |err| return errnoBug(err), // invalid `addr` pointer
1078 .NOMEM => return error.SystemResources,
1079 else => |err| return posix.unexpectedErrno(err),
1080 }
1081 }
1082}
1083
1084fn posixGetSockName(pool: *Pool, socket_fd: posix.fd_t, addr: *posix.sockaddr, addr_len: *posix.socklen_t) !void {
1085 while (true) {
1086 try pool.checkCancel();
1087 switch (posix.errno(posix.system.getsockname(socket_fd, addr, addr_len))) {
1088 .SUCCESS => break,
1089 .INTR => continue,
1090 .BADF => |err| return errnoBug(err), // always a race condition
1091 .FAULT => |err| return errnoBug(err),
1092 .INVAL => |err| return errnoBug(err), // invalid parameters
1093 .NOTSOCK => |err| return errnoBug(err), // always a race condition
1094 .NOBUFS => return error.SystemResources,
1095 else => |err| return posix.unexpectedErrno(err),
1096 }
1097 }
1098}
1099
1100fn setSocketOption(pool: *Pool, fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void {
1101 const o: []const u8 = @ptrCast(&option);
1102 while (true) {
1103 try pool.checkCancel();
1104 switch (posix.errno(posix.system.setsockopt(fd, level, opt_name, o.ptr, @intCast(o.len)))) {
1105 .SUCCESS => return,
1106 .INTR => continue,
1107 .BADF => |err| return errnoBug(err), // always a race condition
1108 .NOTSOCK => |err| return errnoBug(err), // always a race condition
1109 .INVAL => |err| return errnoBug(err),
1110 .FAULT => |err| return errnoBug(err),
1111 else => |err| return posix.unexpectedErrno(err),
1112 }
1113 }
1114}
1115
1116fn ipBindPosix(
1117 userdata: ?*anyopaque,
1118 address: Io.net.IpAddress,
1119 options: Io.net.IpAddress.BindOptions,
1120) Io.net.IpAddress.BindError!Io.net.Socket {
1028 const pool: *Pool = @ptrCast(@alignCast(userdata));1121 const pool: *Pool = @ptrCast(@alignCast(userdata));
1029 try pool.checkCancel();1122 const mode = posixSocketMode(options.mode);
1123 const family = posixAddressFamily(&address);
1124 const protocol = posixProtocol(options.protocol);
1125 const socket_fd = while (true) {
1126 try pool.checkCancel();
1127 const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC;
1128 const socket_rc = posix.system.socket(family, flags, protocol);
1129 switch (posix.errno(socket_rc)) {
1130 .SUCCESS => {
1131 const fd: posix.fd_t = @intCast(socket_rc);
1132 errdefer posix.close(fd);
1133 if (socket_flags_unsupported) while (true) {
1134 try pool.checkCancel();
1135 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, posix.FD_CLOEXEC))) {
1136 .SUCCESS => break,
1137 .INTR => continue,
1138 else => |err| return posix.unexpectedErrno(err),
1139 }
1140 };
1141 break fd;
1142 },
1143 .INTR => continue,
1144 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
1145 .INVAL => return error.ProtocolUnsupportedBySystem,
1146 .MFILE => return error.ProcessFdQuotaExceeded,
1147 .NFILE => return error.SystemFdQuotaExceeded,
1148 .NOBUFS => return error.SystemResources,
1149 .NOMEM => return error.SystemResources,
1150 .PROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily,
1151 .PROTOTYPE => return error.SocketModeUnsupported,
1152 else => |err| return posix.unexpectedErrno(err),
1153 }
1154 };
1155
1156 if (options.ip6_only) {
1157 try setSocketOption(pool, socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, 0);
1158 }
10301159
1031 var storage: PosixAddress = undefined;1160 var storage: PosixAddress = undefined;
1032 var addr_len: posix.socklen_t = @sizeOf(PosixAddress);1161 var socklen = addressToPosix(address, &storage);
1033 const fd = try posix.accept(server.stream.handle, &storage.any, &addr_len, posix.SOCK.CLOEXEC);1162 try posixBind(pool, socket_fd, &storage.any, socklen);
1163 try posixGetSockName(pool, socket_fd, &storage.any, &socklen);
1034 return .{1164 return .{
1035 .stream = .{ .handle = fd },1165 .handle = socket_fd,
1036 .address = addressFromPosix(&storage),1166 .address = addressFromPosix(&storage),
1037 };1167 };
1038}1168}
10391169
1170const socket_flags_unsupported = builtin.os.tag.isDarwin() or native_os == .haiku; // 💩💩
1171const have_accept4 = !socket_flags_unsupported;
1172
1173fn acceptPosix(userdata: ?*anyopaque, server: *Io.net.Server) Io.net.Server.AcceptError!Io.net.Stream {
1174 const pool: *Pool = @ptrCast(@alignCast(userdata));
1175 const listen_fd = server.socket.handle;
1176 var storage: PosixAddress = undefined;
1177 var addr_len: posix.socklen_t = @sizeOf(PosixAddress);
1178 const fd = while (true) {
1179 try pool.checkCancel();
1180 const rc = if (have_accept4)
1181 posix.system.accept4(listen_fd, &storage.any, &addr_len, posix.SOCK.CLOEXEC)
1182 else
1183 posix.system.accept(listen_fd, &storage.any, &addr_len);
1184 switch (posix.errno(rc)) {
1185 .SUCCESS => {
1186 const fd: posix.fd_t = @intCast(rc);
1187 errdefer posix.close(fd);
1188 if (!have_accept4) while (true) {
1189 try pool.checkCancel();
1190 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, posix.FD_CLOEXEC))) {
1191 .SUCCESS => break,
1192 .INTR => continue,
1193 else => |err| return posix.unexpectedErrno(err),
1194 }
1195 };
1196 break fd;
1197 },
1198 .INTR => continue,
1199 .AGAIN => |err| return errnoBug(err),
1200 .BADF => |err| return errnoBug(err), // always a race condition
1201 .CONNABORTED => return error.ConnectionAborted,
1202 .FAULT => |err| return errnoBug(err),
1203 .INVAL => return error.SocketNotListening,
1204 .NOTSOCK => |err| return errnoBug(err),
1205 .MFILE => return error.ProcessFdQuotaExceeded,
1206 .NFILE => return error.SystemFdQuotaExceeded,
1207 .NOBUFS => return error.SystemResources,
1208 .NOMEM => return error.SystemResources,
1209 .OPNOTSUPP => |err| return errnoBug(err),
1210 .PROTO => return error.ProtocolFailure,
1211 .PERM => return error.BlockedByFirewall,
1212 else => |err| return posix.unexpectedErrno(err),
1213 }
1214 };
1215 return .{ .socket = .{
1216 .handle = fd,
1217 .address = addressFromPosix(&storage),
1218 } };
1219}
1220
1040fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.net.Stream.Reader.Error!usize {1221fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.net.Stream.Reader.Error!usize {
1041 const pool: *Pool = @ptrCast(@alignCast(userdata));1222 const pool: *Pool = @ptrCast(@alignCast(userdata));
1042 try pool.checkCancel();1223 try pool.checkCancel();
...@@ -1052,11 +1233,41 @@ fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.n...@@ -1052,11 +1233,41 @@ fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.n
1052 }1233 }
1053 const dest = iovecs_buffer[0..i];1234 const dest = iovecs_buffer[0..i];
1054 assert(dest[0].len > 0);1235 assert(dest[0].len > 0);
1055 const n = try posix.readv(stream.handle, dest);1236 const n = try posix.readv(stream.socket.handle, dest);
1056 if (n == 0) return error.EndOfStream;1237 if (n == 0) return error.EndOfStream;
1057 return n;1238 return n;
1058}1239}
10591240
1241fn netSend(
1242 userdata: ?*anyopaque,
1243 handle: Io.net.Socket.Handle,
1244 address: Io.net.IpAddress,
1245 data: []const u8,
1246) Io.net.Socket.SendError!void {
1247 const pool: *Pool = @ptrCast(@alignCast(userdata));
1248 try pool.checkCancel();
1249
1250 _ = handle;
1251 _ = address;
1252 _ = data;
1253 @panic("TODO");
1254}
1255
1256fn netReceive(
1257 userdata: ?*anyopaque,
1258 handle: Io.net.Socket.Handle,
1259 address: Io.net.IpAddress,
1260 buffer: []u8,
1261) Io.net.Socket.ReceiveError!void {
1262 const pool: *Pool = @ptrCast(@alignCast(userdata));
1263 try pool.checkCancel();
1264
1265 _ = handle;
1266 _ = address;
1267 _ = buffer;
1268 @panic("TODO");
1269}
1270
1060fn netWritePosix(1271fn netWritePosix(
1061 userdata: ?*anyopaque,1272 userdata: ?*anyopaque,
1062 stream: Io.net.Stream,1273 stream: Io.net.Stream,
...@@ -1106,7 +1317,7 @@ fn netWritePosix(...@@ -1106,7 +1317,7 @@ fn netWritePosix(
1106 },1317 },
1107 };1318 };
1108 const flags = posix.MSG.NOSIGNAL;1319 const flags = posix.MSG.NOSIGNAL;
1109 return posix.sendmsg(stream.handle, &msg, flags);1320 return posix.sendmsg(stream.socket.handle, &msg, flags);
1110}1321}
11111322
1112fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), bytes: []const u8) void {1323fn 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"),...@@ -1117,11 +1328,13 @@ fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"),
1117 i.* += 1;1328 i.* += 1;
1118}1329}
11191330
1120fn netClose(userdata: ?*anyopaque, stream: Io.net.Stream) void {1331fn netClose(userdata: ?*anyopaque, handle: Io.net.Socket.Handle) void {
1121 const pool: *Pool = @ptrCast(@alignCast(userdata));1332 const pool: *Pool = @ptrCast(@alignCast(userdata));
1122 _ = pool;1333 _ = pool;
1123 const net_stream: std.net.Stream = .{ .handle = stream.handle };1334 switch (native_os) {
1124 return net_stream.close();1335 .windows => windows.closesocket(handle) catch recoverableOsBugDetected(),
1336 else => posix.close(handle),
1337 }
1125}1338}
11261339
1127fn netInterfaceNameResolve(1340fn netInterfaceNameResolve(
...@@ -1153,13 +1366,13 @@ fn netInterfaceNameResolve(...@@ -1153,13 +1366,13 @@ fn netInterfaceNameResolve(
1153 try pool.checkCancel();1366 try pool.checkCancel();
1154 switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) {1367 switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) {
1155 .SUCCESS => return .{ .index = @bitCast(ifr.ifru.ivalue) },1368 .SUCCESS => return .{ .index = @bitCast(ifr.ifru.ivalue) },
1156 .INVAL => |err| return badErrno(err), // Bad parameters.1369 .INVAL => |err| return errnoBug(err), // Bad parameters.
1157 .NOTTY => |err| return badErrno(err),1370 .NOTTY => |err| return errnoBug(err),
1158 .NXIO => |err| return badErrno(err),1371 .NXIO => |err| return errnoBug(err),
1159 .BADF => |err| return badErrno(err), // Always a race condition.1372 .BADF => |err| return errnoBug(err), // Always a race condition.
1160 .FAULT => |err| return badErrno(err), // Bad pointer parameter.1373 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
1161 .INTR => continue,1374 .INTR => continue,
1162 .IO => |err| return badErrno(err), // sock_fd is not a file descriptor1375 .IO => |err| return errnoBug(err), // sock_fd is not a file descriptor
1163 .NODEV => return error.InterfaceNotFound,1376 .NODEV => return error.InterfaceNotFound,
1164 else => |err| return posix.unexpectedErrno(err),1377 else => |err| return posix.unexpectedErrno(err),
1165 }1378 }
...@@ -1207,8 +1420,8 @@ const PosixAddress = extern union {...@@ -1207,8 +1420,8 @@ const PosixAddress = extern union {
1207 in6: posix.sockaddr.in6,1420 in6: posix.sockaddr.in6,
1208};1421};
12091422
1210fn posixAddressFamily(a: Io.net.IpAddress) posix.sa_family_t {1423fn posixAddressFamily(a: *const Io.net.IpAddress) posix.sa_family_t {
1211 return switch (a) {1424 return switch (a.*) {
1212 .ip4 => posix.AF.INET,1425 .ip4 => posix.AF.INET,
1213 .ip6 => posix.AF.INET6,1426 .ip6 => posix.AF.INET6,
1214 };1427 };
...@@ -1267,9 +1480,27 @@ fn address6ToPosix(a: Io.net.Ip6Address) posix.sockaddr.in6 {...@@ -1267,9 +1480,27 @@ fn address6ToPosix(a: Io.net.Ip6Address) posix.sockaddr.in6 {
1267 };1480 };
1268}1481}
12691482
1270fn badErrno(err: posix.E) Io.UnexpectedError {1483fn errnoBug(err: posix.E) Io.UnexpectedError {
1271 switch (builtin.mode) {1484 switch (builtin.mode) {
1272 .Debug => std.debug.panic("programmer bug caused syscall error: {t}", .{err}),1485 .Debug => std.debug.panic("programmer bug caused syscall error: {t}", .{err}),
1273 else => return error.Unexpected,1486 else => return error.Unexpected,
1274 }1487 }
1275}1488}
1489
1490fn posixSocketMode(mode: Io.net.Socket.Mode) u32 {
1491 return switch (mode) {
1492 .stream => posix.SOCK.STREAM,
1493 .dgram => posix.SOCK.DGRAM,
1494 .seqpacket => posix.SOCK.SEQPACKET,
1495 .raw => posix.SOCK.RAW,
1496 .rdm => posix.SOCK.RDM,
1497 };
1498}
1499
1500fn posixProtocol(protocol: ?Io.net.Protocol) u32 {
1501 return @intFromEnum(protocol orelse return 0);
1502}
1503
1504fn recoverableOsBugDetected() void {
1505 if (builtin.mode == .Debug) unreachable;
1506}
lib/std/Io/net.zig+160-35
...@@ -6,26 +6,41 @@ const assert = std.debug.assert;...@@ -6,26 +6,41 @@ const assert = std.debug.assert;
66
7pub const HostName = @import("net/HostName.zig");7pub const HostName = @import("net/HostName.zig");
88
9pub const ListenError = std.net.Address.ListenError || Io.Cancelable;9/// Source of truth: Internet Assigned Numbers Authority (IANA)
1010pub const Protocol = enum(u32) {
11pub const BindError = std.net.Address.BindError || Io.Cancelable;11 hopopts = 0,
1212 icmp = 1,
13pub const ListenOptions = struct {13 igmp = 2,
14 /// How many connections the kernel will accept on the application's behalf.14 ipip = 4,
15 /// If more than this many connections pool in the kernel, clients will start15 tcp = 6,
16 /// seeing "Connection refused".16 egp = 8,
17 kernel_backlog: u31 = 128,17 pup = 12,
18 /// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX.18 udp = 17,
19 /// Sets SO_REUSEADDR on Windows, which is roughly equivalent.19 idp = 22,
20 reuse_address: bool = false,20 tp = 29,
21 force_nonblocking: bool = false,21 dccp = 33,
22};22 ipv6 = 41,
2323 routing = 43,
24pub const BindOptions = struct {24 fragment = 44,
25 /// The socket is restricted to sending and receiving IPv6 packets only.25 rsvp = 46,
26 /// In this case, an IPv4 and an IPv6 application can bind to a single port26 gre = 47,
27 /// at the same time.27 esp = 50,
28 ip6_only: bool = false,28 ah = 51,
29 icmpv6 = 58,
30 none = 59,
31 dstopts = 60,
32 mtp = 92,
33 beetph = 94,
34 encap = 98,
35 pim = 103,
36 comp = 108,
37 sctp = 132,
38 mh = 135,
39 udplite = 136,
40 mpls = 137,
41 ethernet = 143,
42 raw = 255,
43 mptcp = 262,
29};44};
3045
31pub const IpAddress = union(enum) {46pub const IpAddress = union(enum) {
...@@ -132,11 +147,69 @@ pub const IpAddress = union(enum) {...@@ -132,11 +147,69 @@ pub const IpAddress = union(enum) {
132 };147 };
133 }148 }
134149
150 pub const ListenError = error{
151 /// The address is already taken. Can occur when bound port is 0 but
152 /// all ephemeral ports are already in use.
153 AddressInUse,
154 /// A nonexistent interface was requested or the requested address was not local.
155 AddressUnavailable,
156 /// The local network interface used to reach the destination is offline.
157 NetworkSubsystemDown,
158 /// Insufficient memory or other resource internal to the operating system.
159 SystemResources,
160 /// Per-process limit on the number of open file descriptors has been reached.
161 ProcessFdQuotaExceeded,
162 /// System-wide limit on the total number of open files has been reached.
163 SystemFdQuotaExceeded,
164 /// The requested address family (IPv4 or IPv6) is not supported by the operating system.
165 AddressFamilyUnsupported,
166 } || Io.UnexpectedError || Io.Cancelable;
167
168 pub const ListenOptions = struct {
169 /// How many connections the kernel will accept on the application's behalf.
170 /// If more than this many connections pool in the kernel, clients will start
171 /// seeing "Connection refused".
172 kernel_backlog: u31 = 128,
173 /// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX.
174 /// Sets SO_REUSEADDR on Windows, which is roughly equivalent.
175 reuse_address: bool = false,
176 };
177
135 /// Waits for a TCP connection. When using this API, `bind` does not need178 /// Waits for a TCP connection. When using this API, `bind` does not need
136 /// to be called. The returned `Server` has an open `stream`.179 /// to be called. The returned `Server` has an open `stream`.
137 pub fn listen(address: IpAddress, io: Io, options: ListenOptions) ListenError!Server {180 pub fn listen(address: IpAddress, io: Io, options: ListenOptions) ListenError!Server {
138 return io.vtable.listen(io.userdata, address, options);181 return io.vtable.tcpListen(io.userdata, address, options);
139 }182 }
183
184 pub const BindError = error{
185 /// The address is already taken. Can occur when bound port is 0 but
186 /// all ephemeral ports are already in use.
187 AddressInUse,
188 /// A nonexistent interface was requested or the requested address was not local.
189 AddressUnavailable,
190 /// The address is not valid for the address family of socket.
191 AddressFamilyUnsupported,
192 /// Insufficient memory or other resource internal to the operating system.
193 SystemResources,
194 /// The local network interface used to reach the destination is offline.
195 NetworkSubsystemDown,
196 ProtocolUnsupportedBySystem,
197 ProtocolUnsupportedByAddressFamily,
198 /// Per-process limit on the number of open file descriptors has been reached.
199 ProcessFdQuotaExceeded,
200 /// System-wide limit on the total number of open files has been reached.
201 SystemFdQuotaExceeded,
202 SocketModeUnsupported,
203 } || Io.UnexpectedError || Io.Cancelable;
204
205 pub const BindOptions = struct {
206 /// The socket is restricted to sending and receiving IPv6 packets only.
207 /// In this case, an IPv4 and an IPv6 application can bind to a single port
208 /// at the same time.
209 ip6_only: bool = false,
210 mode: Socket.Mode,
211 protocol: ?Protocol = null,
212 };
140213
141 /// Associates an address with a `Socket` which can be used to receive UDP214 /// Associates an address with a `Socket` which can be used to receive UDP
142 /// packets and other kinds of non-streaming messages. See `listen` for a215 /// packets and other kinds of non-streaming messages. See `listen` for a
...@@ -145,7 +218,7 @@ pub const IpAddress = union(enum) {...@@ -145,7 +218,7 @@ pub const IpAddress = union(enum) {
145 /// One bound `Socket` can be used to receive messages from multiple218 /// One bound `Socket` can be used to receive messages from multiple
146 /// different addresses.219 /// different addresses.
147 pub fn bind(address: IpAddress, io: Io, options: BindOptions) BindError!Socket {220 pub fn bind(address: IpAddress, io: Io, options: BindOptions) BindError!Socket {
148 return io.vtable.bind(io.userdata, address, options);221 return io.vtable.ipBind(io.userdata, address, options);
149 }222 }
150};223};
151224
...@@ -255,7 +328,7 @@ pub const Ip6Address = struct {...@@ -255,7 +328,7 @@ pub const Ip6Address = struct {
255 pub fn fromIp4(ip4: Ip4Address) Ip6Address {328 pub fn fromIp4(ip4: Ip4Address) Ip6Address {
256 const b = &ip4.bytes;329 const b = &ip4.bytes;
257 return .{330 return .{
258 .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, b[0], b[1], b[2], b[3] },331 .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, b[0], b[1], b[2], b[3] },
259 .port = ip4.port,332 .port = ip4.port,
260 };333 };
261 }334 }
...@@ -682,7 +755,25 @@ pub const Interface = struct {...@@ -682,7 +755,25 @@ pub const Interface = struct {
682pub const Socket = struct {755pub const Socket = struct {
683 handle: Handle,756 handle: Handle,
684 /// Contains the resolved ephemeral port number if requested.757 /// Contains the resolved ephemeral port number if requested.
685 bind_address: IpAddress,758 address: IpAddress,
759
760 pub const Mode = enum {
761 /// Provides sequenced, reliable, two-way, connection-based byte
762 /// streams. An out-of-band data transmission mechanism may be
763 /// supported.
764 stream,
765 /// Supports datagrams (connectionless, unreliable messages of a fixed
766 /// maximum length).
767 dgram,
768 /// Provides a sequenced, reliable, two-way connection-based data
769 /// transmission path for datagrams of fixed maximum length; a consumer
770 /// is required to read an entire packet with each input system call.
771 seqpacket,
772 /// Provides raw network protocol access.
773 raw,
774 /// Provides a reliable datagram layer that does not guarantee ordering.
775 rdm,
776 };
686777
687 /// Underlying platform-defined type which may or may not be778 /// Underlying platform-defined type which may or may not be
688 /// interchangeable with a file system file descriptor.779 /// interchangeable with a file system file descriptor.
...@@ -691,8 +782,48 @@ pub const Socket = struct {...@@ -691,8 +782,48 @@ pub const Socket = struct {
691 else => std.posix.fd_t,782 else => std.posix.fd_t,
692 };783 };
693784
694 pub fn close(s: Socket, io: Io) void {785 pub fn close(s: *Socket, io: Io) void {
695 return io.vtable.netClose(io.userdata, s);786 io.vtable.netClose(io.userdata, s.handle);
787 s.handle = undefined;
788 }
789
790 pub const SendError = error{
791 /// The socket type requires that message be sent atomically, and the size of the message
792 /// to be sent made this impossible. The message is not transmitted.
793 MessageTooBig,
794 /// The output queue for a network interface was full. This generally indicates that the
795 /// interface has stopped sending, but may be caused by transient congestion. (Normally,
796 /// this does not occur in Linux. Packets are just silently dropped when a device queue
797 /// overflows.)
798 ///
799 /// This is also caused when there is not enough kernel memory available.
800 SystemResources,
801 /// No route to network.
802 NetworkUnreachable,
803 /// Network reached but no route to host.
804 HostUnreachable,
805 /// The local network interface used to reach the destination is offline.
806 NetworkSubsystemDown,
807 /// The destination address is not listening. Can still occur for
808 /// connectionless messages.
809 ConnectionRefused,
810 /// Operating system or protocol does not support the address family.
811 AddressFamilyUnsupported,
812 } || Io.UnexpectedError || Io.Cancelable;
813
814 /// Transfers `data` to `dest`, connectionless.
815 pub fn send(s: *const Socket, io: Io, dest: *const IpAddress, data: []const u8) SendError!void {
816 return io.vtable.netSend(io.userdata, s.handle, dest, data);
817 }
818
819 pub const ReceiveError = error{} || Io.Cancelable;
820
821 /// Transfers `data` from `source`, connectionless.
822 ///
823 /// Returned slice has same pointer as `buffer` with possibly shorter length.
824 pub fn receive(s: *const Socket, io: Io, source: *const IpAddress, buffer: []u8) ReceiveError![]u8 {
825 const n = try io.vtable.netReceive(io.userdata, s.handle, source, buffer);
826 return buffer[0..n];
696 }827 }
697};828};
698829
...@@ -784,11 +915,6 @@ pub const Stream = struct {...@@ -784,11 +915,6 @@ pub const Stream = struct {
784pub const Server = struct {915pub const Server = struct {
785 socket: Socket,916 socket: Socket,
786917
787 pub const Connection = struct {
788 stream: Stream,
789 address: IpAddress,
790 };
791
792 pub fn deinit(s: *Server, io: Io) void {918 pub fn deinit(s: *Server, io: Io) void {
793 s.socket.close(io);919 s.socket.close(io);
794 s.* = undefined;920 s.* = undefined;
...@@ -796,9 +922,8 @@ pub const Server = struct {...@@ -796,9 +922,8 @@ pub const Server = struct {
796922
797 pub const AcceptError = std.posix.AcceptError || Io.Cancelable;923 pub const AcceptError = std.posix.AcceptError || Io.Cancelable;
798924
799 /// Blocks until a client connects to the server. The returned `Connection` has925 /// Blocks until a client connects to the server.
800 /// an open stream.926 pub fn accept(s: *Server, io: Io) AcceptError!Stream {
801 pub fn accept(s: *Server, io: Io) AcceptError!Connection {
802 return io.vtable.accept(io, s);927 return io.vtable.accept(io, s);
803 }928 }
804};929};
lib/std/Io/net/HostName.zig+6-5
...@@ -539,7 +539,7 @@ pub const ResolvConf = struct {...@@ -539,7 +539,7 @@ pub const ResolvConf = struct {
539 .search_buffer = undefined,539 .search_buffer = undefined,
540 .search_len = 0,540 .search_len = 0,
541 .ndots = 1,541 .ndots = 1,
542 .timeout = 5,542 .timeout = .seconds(5),
543 .attempts = 2,543 .attempts = 2,
544 };544 };
545545
...@@ -589,7 +589,7 @@ pub const ResolvConf = struct {...@@ -589,7 +589,7 @@ pub const ResolvConf = struct {
589 switch (std.meta.stringToEnum(Option, name) orelse continue) {589 switch (std.meta.stringToEnum(Option, name) orelse continue) {
590 .ndots => rc.ndots = @min(value, 15),590 .ndots => rc.ndots = @min(value, 15),
591 .attempts => rc.attempts = @min(value, 10),591 .attempts => rc.attempts = @min(value, 10),
592 .timeout => rc.timeout = @min(value, 60),592 .timeout => rc.timeout = .seconds(@min(value, 60)),
593 }593 }
594 },594 },
595 .nameserver => {595 .nameserver => {
...@@ -638,14 +638,15 @@ pub const ResolvConf = struct {...@@ -638,14 +638,15 @@ pub const ResolvConf = struct {
638 const socket = s: {638 const socket = s: {
639 if (any_ip6) ip6: {639 if (any_ip6) ip6: {
640 const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) };640 const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) };
641 const socket = ip6_addr.bind(io, .{ .ip6_only = true }) catch |err| switch (err) {641 const socket = ip6_addr.bind(io, .{ .ip6_only = true, .mode = .dgram }) catch |err| switch (err) {
642 error.AddressFamilyNotSupported => break :ip6,642 error.AddressFamilyUnsupported => break :ip6,
643 else => |e| return e,
643 };644 };
644 break :s socket;645 break :s socket;
645 }646 }
646 any_ip6 = false;647 any_ip6 = false;
647 const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) };648 const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) };
648 const socket = try ip4_addr.bind(io, .{});649 const socket = try ip4_addr.bind(io, .{ .mode = .dgram });
649 break :s socket;650 break :s socket;
650 };651 };
651 defer socket.close();652 defer socket.close();
lib/std/net.zig+1
...@@ -2383,6 +2383,7 @@ pub const Stream = struct {...@@ -2383,6 +2383,7 @@ pub const Stream = struct {
2383 }2383 }
2384};2384};
23852385
2386/// A bound, listening TCP socket, ready to accept new connections.
2386pub const Server = struct {2387pub const Server = struct {
2387 listen_address: Address,2388 listen_address: Address,
2388 stream: Stream,2389 stream: Stream,