diff --git a/lib/std/c.zig b/lib/std/c.zig index aed2453298..7d2d200e7e 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -150,8 +150,8 @@ pub extern "c" fn socketpair(domain: c_uint, sock_type: c_uint, protocol: c_uint pub extern "c" fn listen(sockfd: fd_t, backlog: c_uint) c_int; pub extern "c" fn getsockname(sockfd: fd_t, noalias addr: *sockaddr, noalias addrlen: *socklen_t) c_int; pub extern "c" fn connect(sockfd: fd_t, sock_addr: *const sockaddr, addrlen: socklen_t) c_int; -pub extern "c" fn accept(sockfd: fd_t, addr: *sockaddr, addrlen: *socklen_t) c_int; -pub extern "c" fn accept4(sockfd: fd_t, addr: *sockaddr, addrlen: *socklen_t, flags: c_uint) c_int; +pub extern "c" fn accept(sockfd: fd_t, addr: ?*sockaddr, addrlen: ?*socklen_t) c_int; +pub extern "c" fn accept4(sockfd: fd_t, addr: ?*sockaddr, addrlen: ?*socklen_t, flags: c_uint) c_int; pub extern "c" fn getsockopt(sockfd: fd_t, level: u32, optname: u32, optval: ?*c_void, optlen: *socklen_t) c_int; pub extern "c" fn setsockopt(sockfd: fd_t, level: u32, optname: u32, optval: ?*const c_void, optlen: socklen_t) c_int; pub extern "c" fn send(sockfd: fd_t, buf: *const c_void, len: usize, flags: u32) isize; diff --git a/lib/std/net.zig b/lib/std/net.zig index d107fb36ae..c041c55e19 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -11,11 +11,7 @@ const mem = std.mem; const os = std.os; const fs = std.fs; -test "" { - _ = @import("net/test.zig"); -} - -const has_unix_sockets = @hasDecl(os, "sockaddr_un"); +pub const has_unix_sockets = @hasDecl(os, "sockaddr_un"); pub const Address = extern union { any: os.sockaddr, @@ -610,7 +606,7 @@ pub fn connectUnixSocket(path: []const u8) !fs.File { os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block, 0, ); - errdefer os.close(sockfd); + errdefer os.closeSocket(sockfd); var addr = try std.net.Address.initUnix(path); @@ -629,7 +625,7 @@ pub fn connectUnixSocket(path: []const u8) !fs.File { fn if_nametoindex(name: []const u8) !u32 { var ifr: os.ifreq = undefined; var sockfd = try os.socket(os.AF_UNIX, os.SOCK_DGRAM | os.SOCK_CLOEXEC, 0); - defer os.close(sockfd); + defer os.closeSocket(sockfd); std.mem.copy(u8, &ifr.ifrn.name, name); ifr.ifrn.name[name.len] = 0; @@ -677,7 +673,7 @@ pub fn tcpConnectToAddress(address: Address) !fs.File { const sock_flags = os.SOCK_STREAM | nonblock | (if (builtin.os.tag == .windows) 0 else os.SOCK_CLOEXEC); const sockfd = try os.socket(address.any.family, sock_flags, os.IPPROTO_TCP); - errdefer os.close(sockfd); + errdefer os.closeSocket(sockfd); if (std.io.is_async) { const loop = std.event.Loop.instance orelse return error.WouldBlock; @@ -912,7 +908,7 @@ fn linuxLookupName( var prefixlen: i32 = 0; const sock_flags = os.SOCK_DGRAM | os.SOCK_CLOEXEC; if (os.socket(addr.addr.any.family, sock_flags, os.IPPROTO_UDP)) |fd| syscalls: { - defer os.close(fd); + defer os.closeSocket(fd); os.connect(fd, da, dalen) catch break :syscalls; key |= DAS_USABLE; os.getsockname(fd, sa, &salen) catch break :syscalls; @@ -1392,7 +1388,7 @@ fn resMSendRc( }, else => |e| return e, }; - defer os.close(fd); + defer os.closeSocket(fd); try os.bind(fd, &sa.any, sl); // Past this point, there are no errors. Each individual query will @@ -1546,16 +1542,14 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) if (data.len != 4) return error.InvalidDnsARecord; const new_addr = try ctx.addrs.addOne(); new_addr.* = LookupAddr{ - // TODO slice [0..4] to make this *[4]u8 without @ptrCast - .addr = Address.initIp4(@ptrCast(*const [4]u8, data.ptr).*, ctx.port), + .addr = Address.initIp4(data[0..4].*, ctx.port), }; }, os.RR_AAAA => { if (data.len != 16) return error.InvalidDnsAAAARecord; const new_addr = try ctx.addrs.addOne(); new_addr.* = LookupAddr{ - // TODO slice [0..16] to make this *[16]u8 without @ptrCast - .addr = Address.initIp6(@ptrCast(*const [16]u8, data.ptr).*, ctx.port, 0, 0), + .addr = Address.initIp6(data[0..16].*, ctx.port, 0, 0), }; }, os.RR_CNAME => { @@ -1579,7 +1573,7 @@ pub const StreamServer = struct { /// `undefined` until `listen` returns successfully. listen_address: Address, - sockfd: ?os.fd_t, + sockfd: ?os.socket_t, pub const Options = struct { /// How many connections the kernel will accept on the application's behalf. @@ -1616,13 +1610,13 @@ pub const StreamServer = struct { const sockfd = try os.socket(address.any.family, sock_flags, proto); self.sockfd = sockfd; errdefer { - os.close(sockfd); + os.closeSocket(sockfd); self.sockfd = null; } if (self.reuse_address) { try os.setsockopt( - self.sockfd.?, + sockfd, os.SOL_SOCKET, os.SO_REUSEADDR, &mem.toBytes(@as(c_int, 1)), @@ -1640,7 +1634,7 @@ pub const StreamServer = struct { /// not listening. pub fn close(self: *StreamServer) void { if (self.sockfd) |fd| { - os.close(fd); + os.closeSocket(fd); self.sockfd = null; self.listen_address = undefined; } @@ -1670,6 +1664,14 @@ pub const StreamServer = struct { /// Permission to create a socket of the specified type and/or /// protocol is denied. PermissionDenied, + + FileDescriptorNotASocket, + + ConnectionResetByPeer, + + NetworkSubsystemFailed, + + OperationNotSupported, } || os.UnexpectedError; pub const Connection = struct { @@ -1701,3 +1703,7 @@ pub const StreamServer = struct { } } }; + +test "" { + _ = @import("net/test.zig"); +} diff --git a/lib/std/net/test.zig b/lib/std/net/test.zig index 815ee81d7f..9f40bb5a3b 100644 --- a/lib/std/net/test.zig +++ b/lib/std/net/test.zig @@ -95,22 +95,81 @@ test "parse and render IPv4 addresses" { } test "resolve DNS" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (std.builtin.os.tag == .windows) { _ = try std.os.windows.WSAStartup(2, 2); } - if (builtin.os.tag == .wasi) { - // DNS resolution not implemented on Windows yet. - return error.SkipZigTest; + defer { + if (std.builtin.os.tag == .windows) { + std.os.windows.WSACleanup() catch unreachable; + } } - const address_list = net.getAddressList(testing.allocator, "example.com", 80) catch |err| switch (err) { + // Resolve localhost, this should not fail. + { + const localhost_v4 = try net.Address.parseIp("127.0.0.1", 80); + const localhost_v6 = try net.Address.parseIp("::2", 80); + + const result = try net.getAddressList(testing.allocator, "localhost", 80); + defer result.deinit(); + for (result.addrs) |addr| { + if (addr.eql(localhost_v4) or addr.eql(localhost_v6)) break; + } else @panic("unexpected address for localhost"); + } + + { // The tests are required to work even when there is no Internet connection, // so some of these errors we must accept and skip the test. - error.UnknownHostName => return error.SkipZigTest, - error.TemporaryNameServerFailure => return error.SkipZigTest, - else => return err, + const result = net.getAddressList(testing.allocator, "example.com", 80) catch |err| switch (err) { + error.UnknownHostName => return error.SkipZigTest, + error.TemporaryNameServerFailure => return error.SkipZigTest, + else => return err, + }; + result.deinit(); + } +} + +test "listen on a port, send bytes, receive bytes" { + if (builtin.single_threaded) return error.SkipZigTest; + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + if (std.builtin.os.tag == .windows) { + _ = try std.os.windows.WSAStartup(2, 2); + } + defer { + if (std.builtin.os.tag == .windows) { + std.os.windows.WSACleanup() catch unreachable; + } + } + + // Try only the IPv4 variant as some CI builders have no IPv6 localhost + // configured. + const localhost = try net.Address.parseIp("127.0.0.1", 8080); + + var server = net.StreamServer.init(.{}); + defer server.deinit(); + + try server.listen(localhost); + + const S = struct { + fn clientFn(server_address: net.Address) !void { + const socket = try net.tcpConnectToAddress(server_address); + defer socket.close(); + + _ = try socket.writer().writeAll("Hello world!"); + } }; - address_list.deinit(); + + const t = try std.Thread.spawn(server.listen_address, S.clientFn); + defer t.wait(); + + var client = try server.accept(); + var buf: [16]u8 = undefined; + const n = try client.file.reader().read(&buf); + + testing.expectEqual(@as(usize, 12), n); + testing.expectEqualSlices(u8, "Hello world!", buf[0..n]); } test "listen on a port, send bytes, receive bytes" { diff --git a/lib/std/os.zig b/lib/std/os.zig index be1fe3e5f4..5aa81ac54b 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2687,6 +2687,14 @@ pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t } } +pub fn closeSocket(sock: socket_t) void { + if (builtin.os.tag == .windows) { + windows.closesocket(sock) catch unreachable; + } else { + close(sock); + } +} + pub const BindError = error{ /// The address is protected, and the user is not the superuser. /// For UNIX domain sockets: Search permission is denied on a component @@ -2731,8 +2739,8 @@ pub const BindError = error{ /// addr is `*const T` where T is one of the sockaddr pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!void { - const rc = system.bind(sock, addr, len); if (builtin.os.tag == .windows) { + const rc = windows.bind(sock, addr, len); if (rc == windows.ws2_32.SOCKET_ERROR) { switch (windows.ws2_32.WSAGetLastError()) { .WSANOTINITIALISED => unreachable, // not initialized WSA @@ -2750,6 +2758,7 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi } return; } else { + const rc = system.bind(sock, addr, len); switch (errno(rc)) { 0 => return, EACCES => return error.AccessDenied, @@ -2800,8 +2809,8 @@ const ListenError = error{ } || UnexpectedError; pub fn listen(sock: socket_t, backlog: u31) ListenError!void { - const rc = system.listen(sock, backlog); if (builtin.os.tag == .windows) { + const rc = windows.listen(sock, backlog); if (rc == windows.ws2_32.SOCKET_ERROR) { switch (windows.ws2_32.WSAGetLastError()) { .WSANOTINITIALISED => unreachable, // not initialized WSA @@ -2818,6 +2827,7 @@ pub fn listen(sock: socket_t, backlog: u31) ListenError!void { } return; } else { + const rc = system.listen(sock, backlog); switch (errno(rc)) { 0 => return, EADDRINUSE => return error.AddressInUse, @@ -2905,6 +2915,8 @@ pub fn accept( const accepted_sock = while (true) { const rc = if (have_accept4) system.accept4(sock, addr, addr_size, flags) + else if (builtin.os.tag == .windows) + windows.accept(sock, addr, addr_size) else system.accept(sock, addr, addr_size); @@ -3077,8 +3089,8 @@ pub const GetSockNameError = error{ } || UnexpectedError; pub fn getsockname(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSockNameError!void { - const rc = system.getsockname(sock, addr, addrlen); if (builtin.os.tag == .windows) { + const rc = windows.getsockname(sock, addr, addrlen); if (rc == windows.ws2_32.SOCKET_ERROR) { switch (windows.ws2_32.WSAGetLastError()) { .WSANOTINITIALISED => unreachable, @@ -3091,6 +3103,7 @@ pub fn getsockname(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock } return; } else { + const rc = system.getsockname(sock, addr, addrlen); switch (errno(rc)) { 0 => return, else => |err| return unexpectedErrno(err), @@ -5378,22 +5391,41 @@ pub const SetSockOptError = error{ /// Insufficient resources are available in the system to complete the call. SystemResources, + + NetworkSubsystemFailed, + FileDescriptorNotASocket, + SocketNotBound, } || UnexpectedError; /// Set a socket's options. -pub fn setsockopt(fd: fd_t, level: u32, optname: u32, opt: []const u8) SetSockOptError!void { - switch (errno(system.setsockopt(fd, level, optname, opt.ptr, @intCast(socklen_t, opt.len)))) { - 0 => {}, - EBADF => unreachable, // always a race condition - ENOTSOCK => unreachable, // always a race condition - EINVAL => unreachable, - EFAULT => unreachable, - EDOM => return error.TimeoutTooBig, - EISCONN => return error.AlreadyConnected, - ENOPROTOOPT => return error.InvalidProtocolOption, - ENOMEM => return error.SystemResources, - ENOBUFS => return error.SystemResources, - else => |err| return unexpectedErrno(err), +pub fn setsockopt(fd: socket_t, level: u32, optname: u32, opt: []const u8) SetSockOptError!void { + if (builtin.os.tag == .windows) { + const rc = windows.ws2_32.setsockopt(fd, level, optname, opt.ptr, @intCast(socklen_t, opt.len)); + if (rc == windows.ws2_32.SOCKET_ERROR) { + switch (windows.ws2_32.WSAGetLastError()) { + .WSANOTINITIALISED => unreachable, + .WSAENETDOWN => return error.NetworkSubsystemFailed, + .WSAEFAULT => unreachable, + .WSAENOTSOCK => return error.FileDescriptorNotASocket, + .WSAEINVAL => return error.SocketNotBound, + else => |err| return windows.unexpectedWSAError(err), + } + } + return; + } else { + switch (errno(system.setsockopt(fd, level, optname, opt.ptr, @intCast(socklen_t, opt.len)))) { + 0 => {}, + EBADF => unreachable, // always a race condition + ENOTSOCK => unreachable, // always a race condition + EINVAL => unreachable, + EFAULT => unreachable, + EDOM => return error.TimeoutTooBig, + EISCONN => return error.AlreadyConnected, + ENOPROTOOPT => return error.InvalidProtocolOption, + ENOMEM => return error.SystemResources, + ENOBUFS => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } } } diff --git a/lib/std/os/bits/windows.zig b/lib/std/os/bits/windows.zig index a92961881e..dda57208f8 100644 --- a/lib/std/os/bits/windows.zig +++ b/lib/std/os/bits/windows.zig @@ -256,6 +256,41 @@ pub const POLLERR = ws2_32.POLLERR; pub const POLLHUP = ws2_32.POLLHUP; pub const POLLNVAL = ws2_32.POLLNVAL; +pub const SOL_SOCKET = ws2_32.SOL_SOCKET; + +pub const SO_DEBUG = ws2_32.SO_DEBUG; +pub const SO_ACCEPTCONN = ws2_32.SO_ACCEPTCONN; +pub const SO_REUSEADDR = ws2_32.SO_REUSEADDR; +pub const SO_KEEPALIVE = ws2_32.SO_KEEPALIVE; +pub const SO_DONTROUTE = ws2_32.SO_DONTROUTE; +pub const SO_BROADCAST = ws2_32.SO_BROADCAST; +pub const SO_USELOOPBACK = ws2_32.SO_USELOOPBACK; +pub const SO_LINGER = ws2_32.SO_LINGER; +pub const SO_OOBINLINE = ws2_32.SO_OOBINLINE; + +pub const SO_DONTLINGER = ws2_32.SO_DONTLINGER; +pub const SO_EXCLUSIVEADDRUSE = ws2_32.SO_EXCLUSIVEADDRUSE; + +pub const SO_SNDBUF = ws2_32.SO_SNDBUF; +pub const SO_RCVBUF = ws2_32.SO_RCVBUF; +pub const SO_SNDLOWAT = ws2_32.SO_SNDLOWAT; +pub const SO_RCVLOWAT = ws2_32.SO_RCVLOWAT; +pub const SO_SNDTIMEO = ws2_32.SO_SNDTIMEO; +pub const SO_RCVTIMEO = ws2_32.SO_RCVTIMEO; +pub const SO_ERROR = ws2_32.SO_ERROR; +pub const SO_TYPE = ws2_32.SO_TYPE; + +pub const SO_GROUP_ID = ws2_32.SO_GROUP_ID; +pub const SO_GROUP_PRIORITY = ws2_32.SO_GROUP_PRIORITY; +pub const SO_MAX_MSG_SIZE = ws2_32.SO_MAX_MSG_SIZE; +pub const SO_PROTOCOL_INFOA = ws2_32.SO_PROTOCOL_INFOA; +pub const SO_PROTOCOL_INFOW = ws2_32.SO_PROTOCOL_INFOW; + +pub const PVD_CONFIG = ws2_32.PVD_CONFIG; +pub const SO_CONDITIONAL_ACCEPT = ws2_32.SO_CONDITIONAL_ACCEPT; + +pub const TCP_NODELAY = ws2_32.TCP_NODELAY; + pub const O_RDONLY = 0o0; pub const O_WRONLY = 0o1; pub const O_RDWR = 0o2; diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index e38d9bc10d..6d05d3b920 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1003,14 +1003,14 @@ pub fn socketpair(domain: i32, socket_type: i32, protocol: i32, fd: [2]i32) usiz return syscall4(.socketpair, @intCast(usize, domain), @intCast(usize, socket_type), @intCast(usize, protocol), @ptrToInt(&fd[0])); } -pub fn accept(fd: i32, noalias addr: *sockaddr, noalias len: *socklen_t) usize { +pub fn accept(fd: i32, noalias addr: ?*sockaddr, noalias len: ?*socklen_t) usize { if (builtin.arch == .i386) { return socketcall(SC_accept, &[4]usize{ fd, addr, len, 0 }); } return accept4(fd, addr, len, 0); } -pub fn accept4(fd: i32, noalias addr: *sockaddr, noalias len: *socklen_t, flags: u32) usize { +pub fn accept4(fd: i32, noalias addr: ?*sockaddr, noalias len: ?*socklen_t, flags: u32) usize { if (builtin.arch == .i386) { return socketcall(SC_accept4, &[4]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), @ptrToInt(len), flags }); } @@ -1279,7 +1279,7 @@ pub fn prlimit(pid: pid_t, resource: rlimit_resource, new_limit: ?*const rlimit, @bitCast(usize, @as(isize, pid)), @bitCast(usize, @as(isize, @enumToInt(resource))), @ptrToInt(new_limit), - @ptrToInt(old_limit) + @ptrToInt(old_limit), ); } diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 58c2b311b1..7df05df2cc 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -594,7 +594,7 @@ test "fsync" { test "getrlimit and setrlimit" { // TODO enable for other systems when implemented - if(builtin.os.tag != .linux){ + if (builtin.os.tag != .linux) { return error.SkipZigTest; } diff --git a/lib/std/os/windows/ws2_32.zig b/lib/std/os/windows/ws2_32.zig index cfd48e4ff5..f0bbf75854 100644 --- a/lib/std/os/windows/ws2_32.zig +++ b/lib/std/os/windows/ws2_32.zig @@ -718,6 +718,41 @@ const IOC_WS2 = 0x08000000; pub const SIO_BASE_HANDLE = IOC_OUT | IOC_WS2 | 34; +pub const SOL_SOCKET = 0xffff; + +pub const SO_DEBUG = 0x0001; +pub const SO_ACCEPTCONN = 0x0002; +pub const SO_REUSEADDR = 0x0004; +pub const SO_KEEPALIVE = 0x0008; +pub const SO_DONTROUTE = 0x0010; +pub const SO_BROADCAST = 0x0020; +pub const SO_USELOOPBACK = 0x0040; +pub const SO_LINGER = 0x0080; +pub const SO_OOBINLINE = 0x0100; + +pub const SO_DONTLINGER = ~@as(u32, SO_LINGER); +pub const SO_EXCLUSIVEADDRUSE = ~@as(u32, SO_REUSEADDR); + +pub const SO_SNDBUF = 0x1001; +pub const SO_RCVBUF = 0x1002; +pub const SO_SNDLOWAT = 0x1003; +pub const SO_RCVLOWAT = 0x1004; +pub const SO_SNDTIMEO = 0x1005; +pub const SO_RCVTIMEO = 0x1006; +pub const SO_ERROR = 0x1007; +pub const SO_TYPE = 0x1008; + +pub const SO_GROUP_ID = 0x2001; +pub const SO_GROUP_PRIORITY = 0x2002; +pub const SO_MAX_MSG_SIZE = 0x2003; +pub const SO_PROTOCOL_INFOA = 0x2004; +pub const SO_PROTOCOL_INFOW = 0x2005; + +pub const PVD_CONFIG = 0x3001; +pub const SO_CONDITIONAL_ACCEPT = 0x3002; + +pub const TCP_NODELAY = 0x0001; + pub extern "ws2_32" fn WSAStartup( wVersionRequired: WORD, lpWSAData: *WSADATA, @@ -835,3 +870,10 @@ pub extern "ws2_32" fn getsockname( name: *sockaddr, namelen: *c_int, ) callconv(.Stdcall) c_int; +pub extern "ws2_32" fn setsockopt( + s: SOCKET, + level: u32, + optname: u32, + optval: ?*const c_void, + optlen: socklen_t, +) callconv(.Stdcall) c_int;