Commit 2eaab1e65e

Jonathan Marler <johnnymarler@gmail.com>
2020-05-30 06:38:48
more windows network support, including dns
1 parent 65c3833
Changed files (5)
lib/std/os/bits/windows.zig
@@ -177,6 +177,8 @@ pub const sockaddr_un = ws2_32.sockaddr_un;
 pub const in6_addr = [16]u8;
 pub const in_addr = u32;
 
+pub const addrinfo = ws2_32.addrinfo;
+
 pub const AF_UNSPEC = ws2_32.AF_UNSPEC;
 pub const AF_UNIX = ws2_32.AF_UNIX;
 pub const AF_INET = ws2_32.AF_INET;
lib/std/os/windows/ws2_32.zig
@@ -163,11 +163,33 @@ pub const IPPROTO_UDP = 17;
 pub const IPPROTO_ICMPV6 = 58;
 pub const IPPROTO_RM = 113;
 
+pub const AI_PASSIVE                = 0x00001;
+pub const AI_CANONNAME              = 0x00002;
+pub const AI_NUMERICHOST            = 0x00004;
+pub const AI_NUMERICSERV            = 0x00008;
+pub const AI_ADDRCONFIG             = 0x00400;
+pub const AI_V4MAPPED               = 0x00800;
+pub const AI_NON_AUTHORITATIVE      = 0x04000;
+pub const AI_SECURE                 = 0x08000;
+pub const AI_RETURN_PREFERRED_NAMES = 0x10000;
+pub const AI_DISABLE_IDN_ENCODING   = 0x80000;
+
 pub const sockaddr = extern struct {
     family: ADDRESS_FAMILY,
     data: [14]u8,
 };
 
+pub const addrinfo = extern struct {
+    flags: i32,
+    family: i32,
+    socktype: i32,
+    protocol: i32,
+    addrlen: usize,
+    canonname: ?[*:0]u8,
+    addr: ?*sockaddr,
+    next: ?*addrinfo,
+};
+
 /// IPv4 socket address
 pub const sockaddr_in = extern struct {
     family: ADDRESS_FAMILY = AF_INET,
@@ -702,6 +724,11 @@ pub extern "ws2_32" fn WSAIoctl(
     lpOverlapped: ?*WSAOVERLAPPED,
     lpCompletionRoutine: ?WSAOVERLAPPED_COMPLETION_ROUTINE,
 ) callconv(.Stdcall) c_int;
+pub extern "ws2_32" fn socket(
+    af: c_int,
+    type: c_int,
+    protocol: c_int,
+) callconv(.Stdcall) SOCKET;
 pub extern "ws2_32" fn accept(
     s: SOCKET,
     addr: ?*sockaddr,
@@ -752,3 +779,12 @@ pub extern "ws2_32" fn WSASendTo(
     lpOverlapped: ?*WSAOVERLAPPED,
     lpCompletionRoutine: ?WSAOVERLAPPED_COMPLETION_ROUTINE,
 ) callconv(.Stdcall) c_int;
+pub extern "ws2_32" fn getaddrinfo(
+    pNodeName: [*:0]const u8,
+    pServiceName: [*:0]const u8,
+    pHints: *const addrinfo,
+    ppResult: **addrinfo,
+) callconv(.Stdcall) i32;
+pub extern "ws2_32" fn freeaddrinfo(
+    pAddrInfo: *addrinfo,
+) callconv(.Stdcall) void;
lib/std/c.zig
@@ -194,6 +194,20 @@ pub usingnamespace switch (builtin.os.tag) {
         pub extern "c" fn socket(domain: c_uint, sock_type: c_uint, protocol: c_uint) c_int;
         pub extern "c" fn stat(noalias path: [*:0]const u8, noalias buf: *Stat) c_int;
     },
+    .windows => struct {
+        // TODO: copied the else case and removed the socket function (because its in ws2_32)
+        //       need to verify which of these is actually supported on windows
+        pub extern "c" fn clock_getres(clk_id: c_int, tp: *timespec) c_int;
+        pub extern "c" fn clock_gettime(clk_id: c_int, tp: *timespec) c_int;
+        pub extern "c" fn fstat(fd: fd_t, buf: *Stat) c_int;
+        pub extern "c" fn getrusage(who: c_int, usage: *rusage) c_int;
+        pub extern "c" fn gettimeofday(noalias tv: ?*timeval, noalias tz: ?*timezone) c_int;
+        pub extern "c" fn nanosleep(rqtp: *const timespec, rmtp: ?*timespec) c_int;
+        pub extern "c" fn sched_yield() c_int;
+        pub extern "c" fn sigaction(sig: c_int, noalias act: *const Sigaction, noalias oact: ?*Sigaction) c_int;
+        pub extern "c" fn sigprocmask(how: c_int, noalias set: ?*const sigset_t, noalias oset: ?*sigset_t) c_int;
+        pub extern "c" fn stat(noalias path: [*:0]const u8, noalias buf: *Stat) c_int;
+    },
     else => struct {
         pub extern "c" fn clock_getres(clk_id: c_int, tp: *timespec) c_int;
         pub extern "c" fn clock_gettime(clk_id: c_int, tp: *timespec) c_int;
lib/std/net.zig
@@ -406,7 +406,8 @@ pub fn tcpConnectToHost(allocator: *mem.Allocator, name: []const u8, port: u16)
 
 pub fn tcpConnectToAddress(address: Address) !fs.File {
     const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0;
-    const sock_flags = os.SOCK_STREAM | os.SOCK_CLOEXEC | nonblock;
+    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);
     try os.connect(sockfd, &address.any, address.getOsSockLen());
@@ -431,16 +432,16 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !*
     const arena = &result.arena.allocator;
     errdefer result.arena.deinit();
 
-    if (builtin.link_libc) {
-        const c = std.c;
+    if (builtin.os.tag == .windows or builtin.link_libc) {
         const name_c = try std.cstr.addNullByte(allocator, name);
         defer allocator.free(name_c);
 
         const port_c = try std.fmt.allocPrint(allocator, "{}\x00", .{port});
         defer allocator.free(port_c);
 
+        const sys = if (builtin.os.tag == .windows) os.windows.ws2_32 else os.system;
         const hints = os.addrinfo{
-            .flags = c.AI_NUMERICSERV,
+            .flags = sys.AI_NUMERICSERV,
             .family = os.AF_UNSPEC,
             .socktype = os.SOCK_STREAM,
             .protocol = os.IPPROTO_TCP,
@@ -450,24 +451,42 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !*
             .next = null,
         };
         var res: *os.addrinfo = undefined;
-        switch (os.system.getaddrinfo(name_c.ptr, @ptrCast([*:0]const u8, port_c.ptr), &hints, &res)) {
-            @intToEnum(os.system.EAI, 0) => {},
-            .ADDRFAMILY => return error.HostLacksNetworkAddresses,
-            .AGAIN => return error.TemporaryNameServerFailure,
-            .BADFLAGS => unreachable, // Invalid hints
-            .FAIL => return error.NameServerFailure,
-            .FAMILY => return error.AddressFamilyNotSupported,
-            .MEMORY => return error.OutOfMemory,
-            .NODATA => return error.HostLacksNetworkAddresses,
-            .NONAME => return error.UnknownHostName,
-            .SERVICE => return error.ServiceUnavailable,
-            .SOCKTYPE => unreachable, // Invalid socket type requested in hints
-            .SYSTEM => switch (os.errno(-1)) {
-                else => |e| return os.unexpectedErrno(e),
-            },
-            else => unreachable,
+        const rc = sys.getaddrinfo(name_c.ptr, @ptrCast([*:0]const u8, port_c.ptr), &hints, &res);
+        if (builtin.os.tag == .windows) {
+            const ws2_32 = os.windows.ws2_32;
+            switch (rc) {
+                0 => {},
+                @enumToInt(ws2_32.WinsockError.WSATRY_AGAIN) => return error.TemporaryNameServerFailure,
+                @enumToInt(ws2_32.WinsockError.WSANO_RECOVERY) => return error.NameServerFailure,
+                @enumToInt(ws2_32.WinsockError.WSAEAFNOSUPPORT) => return error.AddressFamilyNotSupported,
+                @enumToInt(ws2_32.WinsockError.WSA_NOT_ENOUGH_MEMORY) => return error.OutOfMemory,
+                @enumToInt(ws2_32.WinsockError.WSAHOST_NOT_FOUND) => return error.UnknownHostName,
+                @enumToInt(ws2_32.WinsockError.WSATYPE_NOT_FOUND) => return error.ServiceUnavailable,
+                @enumToInt(ws2_32.WinsockError.WSAEINVAL) => unreachable,
+                @enumToInt(ws2_32.WinsockError.WSAESOCKTNOSUPPORT) => unreachable,
+                else => |err| return os.windows.unexpectedWSAError(
+                    std.meta.intToEnum(ws2_32.WinsockError, err) catch unreachable),
+            }
+        } else {
+            switch (rc) {
+                @intToEnum(sys.EAI, 0) => {},
+                .ADDRFAMILY => return error.HostLacksNetworkAddresses,
+                .AGAIN => return error.TemporaryNameServerFailure,
+                .BADFLAGS => unreachable, // Invalid hints
+                .FAIL => return error.NameServerFailure,
+                .FAMILY => return error.AddressFamilyNotSupported,
+                .MEMORY => return error.OutOfMemory,
+                .NODATA => return error.HostLacksNetworkAddresses,
+                .NONAME => return error.UnknownHostName,
+                .SERVICE => return error.ServiceUnavailable,
+                .SOCKTYPE => unreachable, // Invalid socket type requested in hints
+                .SYSTEM => switch (os.errno(-1)) {
+                    else => |e| return os.unexpectedErrno(e),
+                },
+                else => unreachable,
+            }
         }
-        defer os.system.freeaddrinfo(res);
+        defer sys.freeaddrinfo(res);
 
         const addr_count = blk: {
             var count: usize = 0;
lib/std/os.zig
@@ -69,6 +69,8 @@ else switch (builtin.os.tag) {
 
 pub usingnamespace @import("os/bits.zig");
 
+pub const socket_t = if (builtin.os.tag == .windows) windows.ws2_32.SOCKET else fd_t;
+
 /// See also `getenv`. Populated by startup code before main().
 /// TODO this is a footgun because the value will be undefined when using `zig build-lib`.
 /// https://github.com/ziglang/zig/issues/4524
@@ -2443,7 +2445,21 @@ pub const SocketError = error{
     SocketTypeNotSupported,
 } || UnexpectedError;
 
-pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!fd_t {
+pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t {
+    if (builtin.os.tag == .windows) {
+        // NOTE: cannot remove SOCK_NONBLOCK and SOCK_CLOEXEC from socket_type because
+        //       windows does not define this flags yet
+        const rc = windows.ws2_32.socket(@intCast(c_int, domain), @intCast(c_int, socket_type), @intCast(c_int, protocol));
+        if (rc != windows.ws2_32.INVALID_SOCKET) return rc;
+        switch (windows.ws2_32.WSAGetLastError()) {
+            .WSAEMFILE => return error.ProcessFdQuotaExceeded,
+            .WSAENOBUFS => return error.SystemResources,
+            .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,
+            .WSAEPROTONOSUPPORT => return error.ProtocolNotSupported,
+            else => |err| return windows.unexpectedWSAError(err),
+        }
+    }
+
     const have_sock_flags = comptime !std.Target.current.isDarwin();
     const filtered_sock_type = if (!have_sock_flags)
         socket_type & ~@as(u32, SOCK_NONBLOCK | SOCK_CLOEXEC)
@@ -2818,7 +2834,29 @@ pub const ConnectError = error{
 } || UnexpectedError;
 
 /// Initiate a connection on a socket.
-pub fn connect(sockfd: fd_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void {
+pub fn connect(sockfd: socket_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void {
+    if (builtin.os.tag == .windows) {
+        const rc = windows.ws2_32.connect(sockfd, sock_addr, len);
+        if (rc == 0) return;
+        switch (windows.ws2_32.WSAGetLastError()) {
+            .WSAEADDRINUSE => return error.AddressInUse,
+            .WSAEADDRNOTAVAIL => return error.AddressNotAvailable,
+            .WSAECONNREFUSED => return error.ConnectionRefused,
+            .WSAETIMEDOUT => return error.ConnectionTimedOut,
+            .WSAEHOSTUNREACH // TODO: should we return NetworkUnreachable in this case as well?
+            ,.WSAENETUNREACH => return error.NetworkUnreachable,
+            .WSAEFAULT => unreachable,
+            .WSAEINVAL => unreachable,
+            .WSAEISCONN => unreachable,
+            .WSAENOTSOCK => unreachable,
+            .WSAEWOULDBLOCK => unreachable,
+            .WSAEACCES => unreachable,
+            .WSAENOBUFS => return error.SystemResources,
+            .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,
+            else => |err| return windows.unexpectedWSAError(err),
+        }
+    }
+
     while (true) {
         switch (errno(system.connect(sockfd, sock_addr, len))) {
             0 => return,