From a7544b5f0f3e23d05cb4622ff4db3cbb7f1b996a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 24 Apr 2021 19:31:08 +0200 Subject: [PATCH] Port `Socket::Address` to win32 (#10610) --- .github/workflows/win.yml | 2 +- spec/std/socket/address_spec.cr | 117 +++++++++++--------- spec/win32_std_spec.cr | 2 +- src/lib_c/x86_64-windows-msvc/c/afunix.cr | 8 ++ src/lib_c/x86_64-windows-msvc/c/errno.cr | 86 +++++++------- src/lib_c/x86_64-windows-msvc/c/in6addr.cr | 10 ++ src/lib_c/x86_64-windows-msvc/c/winsock2.cr | 21 ++++ src/lib_c/x86_64-windows-msvc/c/ws2def.cr | 14 +++ src/lib_c/x86_64-windows-msvc/c/ws2ipdef.cr | 18 +++ src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr | 7 ++ src/socket.cr | 43 ------- src/socket/address.cr | 11 +- src/socket/common.cr | 51 +++++++++ 13 files changed, 250 insertions(+), 140 deletions(-) create mode 100644 src/lib_c/x86_64-windows-msvc/c/afunix.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/in6addr.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/winsock2.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/ws2def.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/ws2ipdef.cr create mode 100644 src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr create mode 100644 src/socket/common.cr diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 7175ffeabd59..58cde03df3b3 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -168,7 +168,7 @@ jobs: cl /MT /c src\llvm\ext\llvm_ext.cc -I llvm\include /Fosrc\llvm\ext\llvm_ext.obj - name: Link Crystal executable run: | - Invoke-Expression "cl crystal.obj /Fecrystal-cross src\llvm\ext\llvm_ext.obj $(llvm\bin\llvm-config.exe --libs) libs\pcre.lib libs\gc.lib advapi32.lib libcmt.lib legacy_stdio_definitions.lib /F10000000" + Invoke-Expression "cl crystal.obj /Fecrystal-cross src\llvm\ext\llvm_ext.obj $(llvm\bin\llvm-config.exe --libs) libs\pcre.lib libs\gc.lib WS2_32.lib advapi32.lib libcmt.lib legacy_stdio_definitions.lib /F10000000" - name: Re-build Crystal run: | diff --git a/spec/std/socket/address_spec.cr b/spec/std/socket/address_spec.cr index 500df202160a..571c21d5d38b 100644 --- a/spec/std/socket/address_spec.cr +++ b/spec/std/socket/address_spec.cr @@ -1,5 +1,6 @@ require "spec" -require "socket" +require "socket/address" +require "../../support/win32" describe Socket::Address do describe ".parse" do @@ -18,7 +19,7 @@ describe Socket::Address do address.should eq Socket::IPAddress.new("192.168.0.1", 8081) end - it "parses UNIX" do + pending_win32 "parses UNIX" do address = Socket::Address.parse "unix://socket.sock" address.should eq Socket::UNIXAddress.new("socket.sock") end @@ -152,76 +153,78 @@ describe Socket::IPAddress do end end -describe Socket::UNIXAddress do - it "transforms into a C struct and back" do - path = "unix_address.sock" +{% unless flag?(:win32) %} + describe Socket::UNIXAddress do + it "transforms into a C struct and back" do + path = "unix_address.sock" - addr1 = Socket::UNIXAddress.new(path) - addr2 = Socket::UNIXAddress.from(addr1.to_unsafe, addr1.size) + addr1 = Socket::UNIXAddress.new(path) + addr2 = Socket::UNIXAddress.from(addr1.to_unsafe, addr1.size) - addr2.family.should eq(addr1.family) - addr2.path.should eq(addr1.path) - addr2.to_s.should eq(path) - end + addr2.family.should eq(addr1.family) + addr2.path.should eq(addr1.path) + addr2.to_s.should eq(path) + end - it "raises when path is too long" do - path = "unix_address-too-long-#{("a" * 2048)}.sock" + it "raises when path is too long" do + path = "unix_address-too-long-#{("a" * 2048)}.sock" - expect_raises(ArgumentError, "Path size exceeds the maximum size") do - Socket::UNIXAddress.new(path) + expect_raises(ArgumentError, "Path size exceeds the maximum size") do + Socket::UNIXAddress.new(path) + end end - end - it "to_s" do - Socket::UNIXAddress.new("some_path").to_s.should eq("some_path") - end - - it "#==" do - Socket::UNIXAddress.new("some_path").should eq Socket::UNIXAddress.new("some_path") - Socket::UNIXAddress.new("some_path").hash.should eq Socket::UNIXAddress.new("some_path").hash + it "to_s" do + Socket::UNIXAddress.new("some_path").to_s.should eq("some_path") + end - Socket::UNIXAddress.new("some_path").should_not eq Socket::UNIXAddress.new("other_path") - Socket::UNIXAddress.new("some_path").hash.should_not eq Socket::UNIXAddress.new("other_path").hash - end + it "#==" do + Socket::UNIXAddress.new("some_path").should eq Socket::UNIXAddress.new("some_path") + Socket::UNIXAddress.new("some_path").hash.should eq Socket::UNIXAddress.new("some_path").hash - describe ".parse" do - it "parses relative" do - address = Socket::UNIXAddress.parse "unix://foo.sock" - address.should eq Socket::UNIXAddress.new("foo.sock") + Socket::UNIXAddress.new("some_path").should_not eq Socket::UNIXAddress.new("other_path") + Socket::UNIXAddress.new("some_path").hash.should_not eq Socket::UNIXAddress.new("other_path").hash end - it "parses relative subpath" do - address = Socket::UNIXAddress.parse "unix://foo/bar.sock" - address.should eq Socket::UNIXAddress.new("foo/bar.sock") - end + describe ".parse" do + it "parses relative" do + address = Socket::UNIXAddress.parse "unix://foo.sock" + address.should eq Socket::UNIXAddress.new("foo.sock") + end - it "parses relative dot" do - address = Socket::UNIXAddress.parse "unix://./bar.sock" - address.should eq Socket::UNIXAddress.new("./bar.sock") - end + it "parses relative subpath" do + address = Socket::UNIXAddress.parse "unix://foo/bar.sock" + address.should eq Socket::UNIXAddress.new("foo/bar.sock") + end - it "relative with" do - address = Socket::UNIXAddress.parse "unix://foo:21/bar.sock" - address.should eq Socket::UNIXAddress.new("foo:21/bar.sock") - end + it "parses relative dot" do + address = Socket::UNIXAddress.parse "unix://./bar.sock" + address.should eq Socket::UNIXAddress.new("./bar.sock") + end - it "parses absolute" do - address = Socket::UNIXAddress.parse "unix:///foo.sock" - address.should eq Socket::UNIXAddress.new("/foo.sock") - end + it "relative with" do + address = Socket::UNIXAddress.parse "unix://foo:21/bar.sock" + address.should eq Socket::UNIXAddress.new("foo:21/bar.sock") + end - it "ignores params" do - address = Socket::UNIXAddress.parse "unix:///foo.sock?bar=baz" - address.should eq Socket::UNIXAddress.new("/foo.sock") - end + it "parses absolute" do + address = Socket::UNIXAddress.parse "unix:///foo.sock" + address.should eq Socket::UNIXAddress.new("/foo.sock") + end - it "fails with missing path" do - expect_raises(Socket::Error, "Invalid UNIX address: missing path") do - Socket::UNIXAddress.parse "unix://?foo=bar" + it "ignores params" do + address = Socket::UNIXAddress.parse "unix:///foo.sock?bar=baz" + address.should eq Socket::UNIXAddress.new("/foo.sock") + end + + it "fails with missing path" do + expect_raises(Socket::Error, "Invalid UNIX address: missing path") do + Socket::UNIXAddress.parse "unix://?foo=bar" + end end end end -end +{% end %} describe Socket do # Tests from libc-test: @@ -273,7 +276,11 @@ describe Socket do Socket.ip?("1:2:3:4:5:6:7:8::").should be_false Socket.ip?("1:2:3:4:5:6:7::9").should be_false Socket.ip?("::1:2:3:4:5:6").should be_true - Socket.ip?("::1:2:3:4:5:6:7").should be_true + {% if flag?(:win32) %} + Socket.ip?("::1:2:3:4:5:6:7").should be_false + {% else %} + Socket.ip?("::1:2:3:4:5:6:7").should be_true + {% end %} Socket.ip?("::1:2:3:4:5:6:7:8").should be_false Socket.ip?("a:b::c:d:e:f").should be_true Socket.ip?("ffff:c0a8:5e4").should be_false diff --git a/spec/win32_std_spec.cr b/spec/win32_std_spec.cr index a14da4e167ee..8afb7bf70435 100644 --- a/spec/win32_std_spec.cr +++ b/spec/win32_std_spec.cr @@ -181,7 +181,7 @@ require "./std/semantic_version_spec.cr" require "./std/set_spec.cr" # require "./std/signal_spec.cr" (failed codegen) require "./std/slice_spec.cr" -# require "./std/socket/address_spec.cr" (failed codegen) +require "./std/socket/address_spec.cr" # require "./std/socket/addrinfo_spec.cr" (failed codegen) # require "./std/socket/socket_spec.cr" (failed codegen) # require "./std/socket/tcp_server_spec.cr" (failed codegen) diff --git a/src/lib_c/x86_64-windows-msvc/c/afunix.cr b/src/lib_c/x86_64-windows-msvc/c/afunix.cr new file mode 100644 index 000000000000..974a474de33c --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/afunix.cr @@ -0,0 +1,8 @@ +lib LibC + alias AddressFamily = UShort + + struct SockaddrUn + sun_family : AddressFamily + sun_path : Char[108] + end +end diff --git a/src/lib_c/x86_64-windows-msvc/c/errno.cr b/src/lib_c/x86_64-windows-msvc/c/errno.cr index 5b38e31200fd..f5c795889f55 100644 --- a/src/lib_c/x86_64-windows-msvc/c/errno.cr +++ b/src/lib_c/x86_64-windows-msvc/c/errno.cr @@ -1,44 +1,52 @@ lib LibC # source https://docs.microsoft.com/en-us/cpp/c-runtime-library/errno-doserrno-sys-errlist-and-sys-nerr - EPERM = 1 - ENOENT = 2 - ESRCH = 3 - EINTR = 4 - EIO = 5 - ENXIO = 6 - E2BIG = 7 - ENOEXEC = 8 - EBADF = 9 - ECHILD = 10 - EAGAIN = 11 - ENOMEM = 12 - EACCES = 13 - EFAULT = 14 - EBUSY = 16 - EEXIST = 17 - EXDEV = 18 - ENODEV = 19 - ENOTDIR = 20 - EISDIR = 21 - EINVAL = 22 - ENFILE = 23 - EMFILE = 24 - ENOTTY = 25 - EFBIG = 27 - ENOSPC = 28 - ESPIPE = 29 - EROFS = 30 - EMLINK = 31 - EPIPE = 32 - EDOM = 33 - ERANGE = 34 - EDEADLK = 36 - ENAMETOOLONG = 38 - ENOLCK = 39 - ENOSYS = 40 - ENOTEMPTY = 41 - EILSEQ = 42 - STRUNCATE = 80 + EPERM = 1 + ENOENT = 2 + ESRCH = 3 + EINTR = 4 + EIO = 5 + ENXIO = 6 + E2BIG = 7 + ENOEXEC = 8 + EBADF = 9 + ECHILD = 10 + EAGAIN = 11 + ENOMEM = 12 + EACCES = 13 + EFAULT = 14 + EBUSY = 16 + EEXIST = 17 + EXDEV = 18 + ENODEV = 19 + ENOTDIR = 20 + EISDIR = 21 + EINVAL = 22 + ENFILE = 23 + EMFILE = 24 + ENOTTY = 25 + EFBIG = 27 + ENOSPC = 28 + ESPIPE = 29 + EROFS = 30 + EMLINK = 31 + EPIPE = 32 + EDOM = 33 + ERANGE = 34 + EDEADLK = 36 + ENAMETOOLONG = 38 + ENOLCK = 39 + ENOSYS = 40 + ENOTEMPTY = 41 + EILSEQ = 42 + STRUNCATE = 80 + EADDRINUSE = 100 + EALREADY = 103 + ECONNABORTED = 106 + ECONNREFUSED = 107 + ECONNRESET = 108 + EINPROGRESS = 112 + EISCONN = 113 + ENOPROTOOPT = 123 alias ErrnoT = Int end diff --git a/src/lib_c/x86_64-windows-msvc/c/in6addr.cr b/src/lib_c/x86_64-windows-msvc/c/in6addr.cr new file mode 100644 index 000000000000..2f5e657b6ccc --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/in6addr.cr @@ -0,0 +1,10 @@ +lib LibC + struct In6Addr + u : In6AddrU + end + + union In6AddrU + byte : Char[16] + word : WORD[8] + end +end diff --git a/src/lib_c/x86_64-windows-msvc/c/winsock2.cr b/src/lib_c/x86_64-windows-msvc/c/winsock2.cr new file mode 100644 index 000000000000..7d40c83d719f --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/winsock2.cr @@ -0,0 +1,21 @@ +require "./ws2def" + +@[Link("WS2_32")] +lib LibC + AF_UNSPEC = 0 + AF_UNIX = 1 + AF_INET = 2 + AF_IPX = 6 + AF_APPLETALK = 16 + AF_NETBIOS = 17 + AF_INET6 = 23 + AF_IRDA = 26 + AF_BTH = 32 + + struct InAddr + s_addr : UInt32 + end + + fun htons(hostshort : UShort) : UShort + fun ntohs(netshort : UShort) : UShort +end diff --git a/src/lib_c/x86_64-windows-msvc/c/ws2def.cr b/src/lib_c/x86_64-windows-msvc/c/ws2def.cr new file mode 100644 index 000000000000..30021ae63909 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/ws2def.cr @@ -0,0 +1,14 @@ +lib LibC + IPPROTO_IP = 0 + IPPROTO_ICMP = 1 + IPPROTO_IGMP = 2 + IPPROTO_TCP = 6 + IPPROTO_UDP = 17 + IPPROTO_ICMPV6 = 58 + IPPROTO_RAW = 255 + + struct Sockaddr + sa_family : UInt8 + sa_data : Char[14] + end +end diff --git a/src/lib_c/x86_64-windows-msvc/c/ws2ipdef.cr b/src/lib_c/x86_64-windows-msvc/c/ws2ipdef.cr new file mode 100644 index 000000000000..8298054ff9b6 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/ws2ipdef.cr @@ -0,0 +1,18 @@ +require "./in6addr" + +lib LibC + struct SockaddrIn6 + sin6_family : Short + sin6_port : UShort + sin6_flowinfo : ULong + sin6_addr : In6Addr + sin6_scope_id : ULong + end + + struct SockaddrIn + sin_family : Short + sin_port : UShort + sin_addr : InAddr + sin_zero : StaticArray(CHAR, 8) + end +end diff --git a/src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr b/src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr new file mode 100644 index 000000000000..663fb70f2454 --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr @@ -0,0 +1,7 @@ +require "./winsock2" +require "./ws2ipdef" + +lib LibC + fun inet_ntop(family : Int, pAddr : Void*, pStringBuf : Char*, stringBufSize : SizeT) : Char* + fun inet_pton(family : Int, pszAddrString : Char*, pAddrBuf : Void*) : Int +end diff --git a/src/socket.cr b/src/socket.cr index 7781515f7e6e..3bbb38910cda 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -1,34 +1,13 @@ -require "c/arpa/inet" require "c/netdb" require "c/netinet/in" require "c/netinet/tcp" require "c/sys/socket" -require "c/sys/un" require "io/evented" class Socket < IO include IO::Buffered include IO::Evented - class Error < IO::Error - private def self.new_from_errno(message, errno, **opts) - case errno - when Errno::ECONNREFUSED - Socket::ConnectError.new(message, **opts) - when Errno::EADDRINUSE - Socket::BindError.new(message, **opts) - else - super message, errno, **opts - end - end - end - - class ConnectError < Error - end - - class BindError < Error - end - enum Type STREAM = LibC::SOCK_STREAM DGRAM = LibC::SOCK_DGRAM @@ -36,21 +15,6 @@ class Socket < IO SEQPACKET = LibC::SOCK_SEQPACKET end - enum Protocol - IP = LibC::IPPROTO_IP - TCP = LibC::IPPROTO_TCP - UDP = LibC::IPPROTO_UDP - RAW = LibC::IPPROTO_RAW - ICMP = LibC::IPPROTO_ICMP - end - - enum Family : LibC::SaFamilyT - UNSPEC = LibC::AF_UNSPEC - UNIX = LibC::AF_UNIX - INET = LibC::AF_INET - INET6 = LibC::AF_INET6 - end - # :nodoc: SOMAXCONN = 128 @@ -516,13 +480,6 @@ class Socket < IO optval end - # Returns `true` if the string represents a valid IPv4 or IPv6 address. - def self.ip?(string : String) - addr = LibC::In6Addr.new - ptr = pointerof(addr).as(Void*) - LibC.inet_pton(LibC::AF_INET, string, ptr) > 0 || LibC.inet_pton(LibC::AF_INET6, string, ptr) > 0 - end - def blocking fcntl(LibC::F_GETFL) & LibC::O_NONBLOCK == 0 end diff --git a/src/socket/address.cr b/src/socket/address.cr index 298ca77e89bb..b91baa210ec9 100644 --- a/src/socket/address.cr +++ b/src/socket/address.cr @@ -1,4 +1,4 @@ -require "socket" +require "./common" require "uri" class Socket @@ -246,6 +246,8 @@ class Socket addr.__in6_union.__s6_addr {% elsif flag?(:linux) %} addr.__in6_u.__u6_addr8 + {% elsif flag?(:win32) %} + addr.u.byte {% else %} {% raise "Unsupported platform" %} {% end %} @@ -400,4 +402,11 @@ class Socket sockaddr.as(LibC::Sockaddr*) end end + + # Returns `true` if the string represents a valid IPv4 or IPv6 address. + def self.ip?(string : String) + addr = LibC::In6Addr.new + ptr = pointerof(addr).as(Void*) + LibC.inet_pton(LibC::AF_INET, string, ptr) > 0 || LibC.inet_pton(LibC::AF_INET6, string, ptr) > 0 + end end diff --git a/src/socket/common.cr b/src/socket/common.cr new file mode 100644 index 000000000000..2bf555a897d9 --- /dev/null +++ b/src/socket/common.cr @@ -0,0 +1,51 @@ +{% if flag?(:win32) %} + require "c/ws2tcpip" + require "c/afunix" +{% else %} + require "c/arpa/inet" + require "c/sys/un" + require "c/netinet/in" +{% end %} + +class Socket + enum Protocol + IP = LibC::IPPROTO_IP + TCP = LibC::IPPROTO_TCP + UDP = LibC::IPPROTO_UDP + RAW = LibC::IPPROTO_RAW + ICMP = LibC::IPPROTO_ICMP + end + + # :nodoc: + {% if flag?(:win32) %} + alias FamilyT = UInt8 + {% else %} + alias FamilyT = LibC::SaFamilyT + {% end %} + + enum Family : FamilyT + UNSPEC = LibC::AF_UNSPEC + UNIX = LibC::AF_UNIX + INET = LibC::AF_INET + INET6 = LibC::AF_INET6 + end + + class Error < IO::Error + private def self.new_from_errno(message, errno, **opts) + case errno + when Errno::ECONNREFUSED + Socket::ConnectError.new(message, **opts) + when Errno::EADDRINUSE + Socket::BindError.new(message, **opts) + else + super message, errno, **opts + end + end + end + + class ConnectError < Error + end + + class BindError < Error + end +end