Commit 8044ed4c66

LemonBoy <thatlemon@gmail.com>
2020-10-27 15:37:28
std: Add basic smoke test for net functionality
1 parent 194e29a
Changed files (7)
lib/std/net/test.zig
@@ -95,22 +95,79 @@ 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;
+        }
+    }
+
+    // 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");
     }
 
-    const address_list = net.getAddressList(testing.allocator, "example.com", 80) catch |err| switch (err) {
+    {
         // 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(.{});
+    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" {
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;
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;
lib/std/os/linux.zig
@@ -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),
     );
 }
 
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;
     }
 
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,
@@ -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.
@@ -1622,7 +1616,7 @@ pub const StreamServer = struct {
 
         if (self.reuse_address) {
             try os.setsockopt(
-                self.sockfd.?,
+                sockfd,
                 os.SOL_SOCKET,
                 os.SO_REUSEADDR,
                 &mem.toBytes(@as(c_int, 1)),
@@ -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");
+}
lib/std/os.zig
@@ -5378,22 +5378,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),
+        }
     }
 }