diff --git a/spec/std/socket_spec.cr b/spec/std/socket_spec.cr index 0b0ee66e5269..c96947a38220 100644 --- a/spec/std/socket_spec.cr +++ b/spec/std/socket_spec.cr @@ -4,7 +4,7 @@ require "socket" describe Socket do # Tests from libc-test: # http://repo.or.cz/libc-test.git/blob/master:/src/functional/inet_pton.c - assert "ip?" do + it ".ip?" do # dotted-decimal notation Socket.ip?("0.0.0.0").should be_true Socket.ip?("127.0.0.1").should be_true @@ -65,29 +65,46 @@ describe Socket do end describe Socket::IPAddress do - it "transforms an IPv4 address into a C struct and back again" do - addr1 = Socket::IPAddress.new(Socket::Family::INET, "127.0.0.1", 8080.to_i16) - addr2 = Socket::IPAddress.new(addr1.sockaddr, addr1.addrlen) - - addr1.family.should eq(addr2.family) - addr1.port.should eq(addr2.port) - addr1.address.should eq(addr2.address) - addr1.to_s.should eq("127.0.0.1:8080") + it "transforms an IPv4 address into a C struct and back" do + addr1 = Socket::IPAddress.new("127.0.0.1", 8080) + addr2 = Socket::IPAddress.from(addr1.to_unsafe, addr1.size) + + addr2.family.should eq(addr1.family) + addr2.port.should eq(addr1.port) + addr2.address.should eq(addr1.address) end - it "transforms an IPv6 address into a C struct and back again" do - addr1 = Socket::IPAddress.new(Socket::Family::INET6, "2001:db8:8714:3a90::12", 8080.to_i16) - addr2 = Socket::IPAddress.new(addr1.sockaddr, addr1.addrlen) + it "transforms an IPv6 address into a C struct and back" do + addr1 = Socket::IPAddress.new("2001:db8:8714:3a90::12", 8080) + addr2 = Socket::IPAddress.from(addr1.to_unsafe, addr1.size) + + addr2.family.should eq(addr1.family) + addr2.port.should eq(addr1.port) + addr2.address.should eq(addr1.address) + end - addr1.family.should eq(addr2.family) - addr1.port.should eq(addr2.port) - addr1.address.should eq(addr2.address) - addr1.to_s.should eq("2001:db8:8714:3a90::12:8080") + it "to_s" do + Socket::IPAddress.new("127.0.0.1", 80).to_s.should eq("127.0.0.1:80") + Socket::IPAddress.new("2001:db8:8714:3a90::12", 443).to_s.should eq("[2001:db8:8714:3a90::12]:443") end end describe Socket::UNIXAddress do - it "does to_s" do + it "transforms into a C struct and back" do + addr1 = Socket::UNIXAddress.new("/tmp/service.sock") + 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("/tmp/service.sock") + end + + it "raises when path is too long" do + path = "/tmp/crystal-test-too-long-unix-socket-#{("a" * 2048)}.sock" + expect_raises(ArgumentError, "Path size exceeds the maximum size") { Socket::UNIXAddress.new(path) } + end + + it "to_s" do Socket::UNIXAddress.new("some_path").to_s.should eq("some_path") end end @@ -211,8 +228,6 @@ describe UNIXSocket do client.local_address.path.should eq(path) server.accept do |sock| - sock.sync?.should eq(server.sync?) - sock.local_address.family.should eq(Socket::Family::UNIX) sock.local_address.path.should eq("") @@ -225,8 +240,19 @@ describe UNIXSocket do client.gets(4).should eq("pong") end end + end + end + + it "sync flag after accept" do + path = "/tmp/crystal-test-unix-sock" + + UNIXServer.open(path) do |server| + UNIXSocket.open(path) do |client| + server.accept do |sock| + sock.sync?.should eq(server.sync?) + end + end - # test sync flag propagation after accept server.sync = !server.sync? UNIXSocket.open(path) do |client| @@ -244,6 +270,7 @@ describe UNIXSocket do left << "ping" right.gets(4).should eq("ping") + right << "pong" left.gets(4).should eq("pong") end @@ -283,10 +310,19 @@ end describe TCPServer do it "fails when port is in use" do + port = free_udp_socket_port + expect_raises Errno, /(already|Address) in use/ do - TCPServer.open("::", 0) do |server| - TCPServer.open("::", server.local_address.port) { } - end + sock = Socket.tcp(Socket::Family::INET6) + sock.bind(Socket::IPAddress.new("::1", port)) + + TCPServer.open("::1", port) {} + end + end + + it "allows to share the same port (SO_REUSEPORT)" do + TCPServer.open("::", 0) do |server| + TCPServer.open("::", server.local_address.port) {} end end end @@ -382,31 +418,25 @@ describe TCPSocket do end it "fails when host doesn't exist" do - expect_raises(Socket::Error, /^getaddrinfo: (.+ not known|no address .+|Non-recoverable failure in name resolution|Name does not resolve)$/i) do + expect_raises(Socket::Error, /No address found for localhostttttt:12345/) do TCPSocket.new("localhostttttt", 12345) end end end describe UDPSocket do - it "sends and receives messages by reading and writing" do + it "reads and writes data to server" do port = free_udp_socket_port server = UDPSocket.new(Socket::Family::INET6) server.bind("::", port) - - server.local_address.family.should eq(Socket::Family::INET6) - server.local_address.port.should eq(port) - server.local_address.address.should eq("::") + server.local_address.should eq(Socket::IPAddress.new("::", port)) client = UDPSocket.new(Socket::Family::INET6) client.connect("::1", port) - client.local_address.family.should eq(Socket::Family::INET6) client.local_address.address.should eq("::1") - client.remote_address.family.should eq(Socket::Family::INET6) - client.remote_address.port.should eq(port) - client.remote_address.address.should eq("::1") + client.remote_address.should eq(Socket::IPAddress.new("::1", port)) client << "message" server.gets(7).should eq("message") @@ -415,51 +445,62 @@ describe UDPSocket do server.close end - it "sends and receives messages by send and receive over IPv4" do + it "sends and receives messages over IPv4" do + buffer = uninitialized UInt8[256] + server = UDPSocket.new(Socket::Family::INET) server.bind("127.0.0.1", 0) client = UDPSocket.new(Socket::Family::INET) - - buffer = uninitialized UInt8[256] - client.send("message equal to buffer", server.local_address) - bytes_read, addr1 = server.receive(buffer.to_slice[0, 23]) - message1 = String.new(buffer.to_slice[0, bytes_read]) - message1.should eq("message equal to buffer") - addr1.family.should eq(server.local_address.family) - addr1.address.should eq(server.local_address.address) + + bytes_read, client_addr = server.receive(buffer.to_slice[0, 23]) + message = String.new(buffer.to_slice[0, bytes_read]) + message.should eq("message equal to buffer") + client_addr.should eq(Socket::IPAddress.new("127.0.0.1", client.local_address.port)) client.send("message less than buffer", server.local_address) - bytes_read, addr2 = server.receive(buffer.to_slice) - message2 = String.new(buffer.to_slice[0, bytes_read]) - message2.should eq("message less than buffer") - addr2.family.should eq(server.local_address.family) - addr2.address.should eq(server.local_address.address) + + bytes_read, client_addr = server.receive(buffer.to_slice) + message = String.new(buffer.to_slice[0, bytes_read]) + message.should eq("message less than buffer") + + client.connect server.local_address + client.send "ip4 message" + + message, client_addr = server.receive + message.should eq("ip4 message") + client_addr.should eq(Socket::IPAddress.new("127.0.0.1", client.local_address.port)) server.close client.close end - it "sends and receives messages by send and receive over IPv6" do + it "sends and receives messages over IPv6" do + buffer = uninitialized UInt8[1500] + server = UDPSocket.new(Socket::Family::INET6) server.bind("::1", 0) client = UDPSocket.new(Socket::Family::INET6) + client.send("some message", server.local_address) - buffer = uninitialized UInt8[1500] + bytes_read, client_addr = server.receive(buffer.to_slice) + String.new(buffer.to_slice[0, bytes_read]).should eq("some message") + client_addr.should eq(Socket::IPAddress.new("::1", client.local_address.port)) + + client.connect server.local_address + client.send "ip6 message" - client.send("message", server.local_address) - bytes_read, addr = server.receive(buffer.to_slice) - String.new(buffer.to_slice[0, bytes_read]).should eq("message") - addr.family.should eq(server.local_address.family) - addr.address.should eq(server.local_address.address) + message, client_addr = server.receive(20) + message.should eq("ip6 message") + client_addr.should eq(Socket::IPAddress.new("::1", client.local_address.port)) server.close client.close end - it "broadcast messages" do + it "broadcasts messages" do port = free_udp_socket_port client = UDPSocket.new(Socket::Family::INET) diff --git a/src/errno.cr b/src/errno.cr index 7046793be0ad..f035fadf5c18 100644 --- a/src/errno.cr +++ b/src/errno.cr @@ -212,8 +212,7 @@ class Errno < Exception # raise Errno.new("some_call") # end # ``` - def initialize(message) - errno = Errno.value + def initialize(message, errno = Errno.value) @errno = errno super "#{message}: #{String.new(LibC.strerror(errno))}" end diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/socket.cr b/src/lib_c/aarch64-linux-gnu/c/sys/socket.cr index 22fdf388c5b8..11785c128f52 100644 --- a/src/lib_c/aarch64-linux-gnu/c/sys/socket.cr +++ b/src/lib_c/aarch64-linux-gnu/c/sys/socket.cr @@ -11,6 +11,7 @@ lib LibC SO_LINGER = 13 SO_RCVBUF = 8 SO_REUSEADDR = 2 + SO_REUSEPORT = 15 SO_SNDBUF = 7 PF_INET = 2 PF_INET6 = 10 diff --git a/src/lib_c/amd64-unknown-openbsd/c/sys/socket.cr b/src/lib_c/amd64-unknown-openbsd/c/sys/socket.cr index 06d2108a7c89..cb2f1fa8123b 100644 --- a/src/lib_c/amd64-unknown-openbsd/c/sys/socket.cr +++ b/src/lib_c/amd64-unknown-openbsd/c/sys/socket.cr @@ -11,6 +11,7 @@ lib LibC SO_LINGER = 0x0080 SO_RCVBUF = 0x1002 SO_REUSEADDR = 0x0004 + SO_REUSEPORT = 0x0200 SO_SNDBUF = 0x1001 PF_INET = LibC::AF_INET PF_INET6 = LibC::AF_INET6 @@ -36,6 +37,14 @@ lib LibC sa_data : StaticArray(Char, 14) end + struct SockaddrStorage + ss_len : UChar + ss_family : SaFamilyT + __ss_pad1 : StaticArray(Char, 6) + __ss_pad2 : ULongLong + __ss_pad3 : StaticArray(Char, 240) + end + struct Linger l_onoff : Int l_linger : Int diff --git a/src/lib_c/arm-linux-gnueabihf/c/sys/socket.cr b/src/lib_c/arm-linux-gnueabihf/c/sys/socket.cr index 15db2cee8c23..3a61519d9baa 100644 --- a/src/lib_c/arm-linux-gnueabihf/c/sys/socket.cr +++ b/src/lib_c/arm-linux-gnueabihf/c/sys/socket.cr @@ -11,6 +11,7 @@ lib LibC SO_LINGER = 13 SO_RCVBUF = 8 SO_REUSEADDR = 2 + SO_REUSEPORT = 15 SO_SNDBUF = 7 PF_INET = 2 PF_INET6 = 10 diff --git a/src/lib_c/i686-linux-gnu/c/netdb.cr b/src/lib_c/i686-linux-gnu/c/netdb.cr index f1edb640898a..ce727b4b21ac 100644 --- a/src/lib_c/i686-linux-gnu/c/netdb.cr +++ b/src/lib_c/i686-linux-gnu/c/netdb.cr @@ -3,6 +3,24 @@ require "./sys/socket" require "./stdint" lib LibC + AI_PASSIVE = 0x0001 + AI_CANONNAME = 0x0002 + AI_NUMERICHOST = 0x0004 + AI_NUMERICSERV = 0x0400 + AI_V4MAPPED = 0x0008 + AI_ALL = 0x0010 + AI_ADDRCONFIG = 0x0020 + EAI_AGAIN = -3 + EAI_BADFLAGS = -1 + EAI_FAIL = -4 + EAI_FAMILY = -6 + EAI_MEMORY = -10 + EAI_NONAME = -2 + EAI_SERVICE = -8 + EAI_SOCKTYPE = -7 + EAI_SYSTEM = -11 + EAI_OVERFLOW = -12 + struct Addrinfo ai_flags : Int ai_family : Int @@ -14,7 +32,8 @@ lib LibC ai_next : Addrinfo* end + fun freeaddrinfo(ai : Addrinfo*) : Void fun gai_strerror(ecode : Int) : Char* - fun getaddrinfo(hostname : Char*, servname : Char*, hints : Addrinfo*, res : Addrinfo**) : Int - fun freeaddrinfo(ai : Addrinfo*) + fun getaddrinfo(name : Char*, service : Char*, req : Addrinfo*, pai : Addrinfo**) : Int + fun getnameinfo(sa : Sockaddr*, salen : SocklenT, host : Char*, hostlen : SocklenT, serv : Char*, servlen : SocklenT, flags : Int) : Int end diff --git a/src/lib_c/i686-linux-gnu/c/sys/socket.cr b/src/lib_c/i686-linux-gnu/c/sys/socket.cr index 8364aa62cf4c..b02ed75210dc 100644 --- a/src/lib_c/i686-linux-gnu/c/sys/socket.cr +++ b/src/lib_c/i686-linux-gnu/c/sys/socket.cr @@ -11,6 +11,7 @@ lib LibC SO_LINGER = 13 SO_RCVBUF = 8 SO_REUSEADDR = 2 + SO_REUSEPORT = 15 SO_SNDBUF = 7 PF_INET = 2 PF_INET6 = 10 @@ -35,6 +36,12 @@ lib LibC sa_data : StaticArray(Char, 14) end + struct SockaddrStorage + ss_family : SaFamilyT + __ss_align : ULong + __ss_padding : StaticArray(Char, 120) + end + struct Linger l_onoff : Int l_linger : Int @@ -47,10 +54,10 @@ lib LibC fun getsockname(fd : Int, addr : Sockaddr*, len : SocklenT*) : Int fun getsockopt(fd : Int, level : Int, optname : Int, optval : Void*, optlen : SocklenT*) : Int fun listen(fd : Int, n : Int) : Int - fun recv(fd : Int, buf : Void*, n : SizeT, flags : Int) : SSizeT - fun recvfrom(fd : Int, buf : Void*, n : SizeT, flags : Int, addr : Sockaddr*, addr_len : SocklenT*) : SSizeT - fun send(fd : Int, buf : Void*, n : SizeT, flags : Int) : SSizeT - fun sendto(fd : Int, buf : Void*, n : SizeT, flags : Int, addr : Sockaddr*, addr_len : SocklenT) : SSizeT + fun recv(fd : Int, buf : Void*, n : Int, flags : Int) : SSizeT + fun recvfrom(fd : Int, buf : Void*, n : Int, flags : Int, addr : Sockaddr*, addr_len : SocklenT*) : SSizeT + fun send(fd : Int, buf : Void*, n : Int, flags : Int) : SSizeT + fun sendto(fd : Int, buf : Void*, n : Int, flags : Int, addr : Sockaddr*, addr_len : SocklenT) : SSizeT fun setsockopt(fd : Int, level : Int, optname : Int, optval : Void*, optlen : SocklenT) : Int fun shutdown(fd : Int, how : Int) : Int fun socket(domain : Int, type : Int, protocol : Int) : Int diff --git a/src/lib_c/i686-linux-musl/c/netdb.cr b/src/lib_c/i686-linux-musl/c/netdb.cr index 4e06b1208dd2..a5ac97843811 100644 --- a/src/lib_c/i686-linux-musl/c/netdb.cr +++ b/src/lib_c/i686-linux-musl/c/netdb.cr @@ -3,6 +3,24 @@ require "./sys/socket" require "./stdint" lib LibC + AI_PASSIVE = 0x01 + AI_CANONNAME = 0x02 + AI_NUMERICHOST = 0x04 + AI_NUMERICSERV = 0x400 + AI_V4MAPPED = 0x08 + AI_ALL = 0x10 + AI_ADDRCONFIG = 0x20 + EAI_AGAIN = -3 + EAI_BADFLAGS = -1 + EAI_FAIL = -4 + EAI_FAMILY = -6 + EAI_MEMORY = -10 + EAI_NONAME = -2 + EAI_SERVICE = -8 + EAI_SOCKTYPE = -7 + EAI_SYSTEM = -11 + EAI_OVERFLOW = -12 + struct Addrinfo ai_flags : Int ai_family : Int @@ -14,7 +32,8 @@ lib LibC ai_next : Addrinfo* end + fun freeaddrinfo(x0 : Addrinfo*) : Void fun gai_strerror(x0 : Int) : Char* - fun getaddrinfo(hostname : Char*, servname : Char*, hints : Addrinfo*, res : Addrinfo**) : Int - fun freeaddrinfo(ai : Addrinfo*) + fun getaddrinfo(x0 : Char*, x1 : Char*, x2 : Addrinfo*, x3 : Addrinfo**) : Int + fun getnameinfo(x0 : Sockaddr*, x1 : SocklenT, x2 : Char*, x3 : SocklenT, x4 : Char*, x5 : SocklenT, x6 : Int) : Int end diff --git a/src/lib_c/i686-linux-musl/c/sys/socket.cr b/src/lib_c/i686-linux-musl/c/sys/socket.cr index 1956a5c7b988..361e5b8040d6 100644 --- a/src/lib_c/i686-linux-musl/c/sys/socket.cr +++ b/src/lib_c/i686-linux-musl/c/sys/socket.cr @@ -11,6 +11,7 @@ lib LibC SO_LINGER = 13 SO_RCVBUF = 8 SO_REUSEADDR = 2 + SO_REUSEPORT = 15 SO_SNDBUF = 7 PF_INET = 2 PF_INET6 = 10 @@ -35,6 +36,12 @@ lib LibC sa_data : StaticArray(Char, 14) end + struct SockaddrStorage + ss_family : SaFamilyT + __ss_align : ULong + __ss_padding : StaticArray(Char, 112) + end + struct Linger l_onoff : Int l_linger : Int diff --git a/src/lib_c/x86_64-linux-gnu/c/netdb.cr b/src/lib_c/x86_64-linux-gnu/c/netdb.cr index f1edb640898a..ce727b4b21ac 100644 --- a/src/lib_c/x86_64-linux-gnu/c/netdb.cr +++ b/src/lib_c/x86_64-linux-gnu/c/netdb.cr @@ -3,6 +3,24 @@ require "./sys/socket" require "./stdint" lib LibC + AI_PASSIVE = 0x0001 + AI_CANONNAME = 0x0002 + AI_NUMERICHOST = 0x0004 + AI_NUMERICSERV = 0x0400 + AI_V4MAPPED = 0x0008 + AI_ALL = 0x0010 + AI_ADDRCONFIG = 0x0020 + EAI_AGAIN = -3 + EAI_BADFLAGS = -1 + EAI_FAIL = -4 + EAI_FAMILY = -6 + EAI_MEMORY = -10 + EAI_NONAME = -2 + EAI_SERVICE = -8 + EAI_SOCKTYPE = -7 + EAI_SYSTEM = -11 + EAI_OVERFLOW = -12 + struct Addrinfo ai_flags : Int ai_family : Int @@ -14,7 +32,8 @@ lib LibC ai_next : Addrinfo* end + fun freeaddrinfo(ai : Addrinfo*) : Void fun gai_strerror(ecode : Int) : Char* - fun getaddrinfo(hostname : Char*, servname : Char*, hints : Addrinfo*, res : Addrinfo**) : Int - fun freeaddrinfo(ai : Addrinfo*) + fun getaddrinfo(name : Char*, service : Char*, req : Addrinfo*, pai : Addrinfo**) : Int + fun getnameinfo(sa : Sockaddr*, salen : SocklenT, host : Char*, hostlen : SocklenT, serv : Char*, servlen : SocklenT, flags : Int) : Int end diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/socket.cr b/src/lib_c/x86_64-linux-gnu/c/sys/socket.cr index 8364aa62cf4c..6cee17373bca 100644 --- a/src/lib_c/x86_64-linux-gnu/c/sys/socket.cr +++ b/src/lib_c/x86_64-linux-gnu/c/sys/socket.cr @@ -11,6 +11,7 @@ lib LibC SO_LINGER = 13 SO_RCVBUF = 8 SO_REUSEADDR = 2 + SO_REUSEPORT = 15 SO_SNDBUF = 7 PF_INET = 2 PF_INET6 = 10 @@ -35,6 +36,12 @@ lib LibC sa_data : StaticArray(Char, 14) end + struct SockaddrStorage + ss_family : SaFamilyT + __ss_align : ULong + __ss_padding : StaticArray(Char, 112) + end + struct Linger l_onoff : Int l_linger : Int @@ -47,10 +54,10 @@ lib LibC fun getsockname(fd : Int, addr : Sockaddr*, len : SocklenT*) : Int fun getsockopt(fd : Int, level : Int, optname : Int, optval : Void*, optlen : SocklenT*) : Int fun listen(fd : Int, n : Int) : Int - fun recv(fd : Int, buf : Void*, n : SizeT, flags : Int) : SSizeT - fun recvfrom(fd : Int, buf : Void*, n : SizeT, flags : Int, addr : Sockaddr*, addr_len : SocklenT*) : SSizeT - fun send(fd : Int, buf : Void*, n : SizeT, flags : Int) : SSizeT - fun sendto(fd : Int, buf : Void*, n : SizeT, flags : Int, addr : Sockaddr*, addr_len : SocklenT) : SSizeT + fun recv(fd : Int, buf : Void*, n : Int, flags : Int) : SSizeT + fun recvfrom(fd : Int, buf : Void*, n : Int, flags : Int, addr : Sockaddr*, addr_len : SocklenT*) : SSizeT + fun send(fd : Int, buf : Void*, n : Int, flags : Int) : SSizeT + fun sendto(fd : Int, buf : Void*, n : Int, flags : Int, addr : Sockaddr*, addr_len : SocklenT) : SSizeT fun setsockopt(fd : Int, level : Int, optname : Int, optval : Void*, optlen : SocklenT) : Int fun shutdown(fd : Int, how : Int) : Int fun socket(domain : Int, type : Int, protocol : Int) : Int diff --git a/src/lib_c/x86_64-linux-musl/c/netdb.cr b/src/lib_c/x86_64-linux-musl/c/netdb.cr index 4e06b1208dd2..a5ac97843811 100644 --- a/src/lib_c/x86_64-linux-musl/c/netdb.cr +++ b/src/lib_c/x86_64-linux-musl/c/netdb.cr @@ -3,6 +3,24 @@ require "./sys/socket" require "./stdint" lib LibC + AI_PASSIVE = 0x01 + AI_CANONNAME = 0x02 + AI_NUMERICHOST = 0x04 + AI_NUMERICSERV = 0x400 + AI_V4MAPPED = 0x08 + AI_ALL = 0x10 + AI_ADDRCONFIG = 0x20 + EAI_AGAIN = -3 + EAI_BADFLAGS = -1 + EAI_FAIL = -4 + EAI_FAMILY = -6 + EAI_MEMORY = -10 + EAI_NONAME = -2 + EAI_SERVICE = -8 + EAI_SOCKTYPE = -7 + EAI_SYSTEM = -11 + EAI_OVERFLOW = -12 + struct Addrinfo ai_flags : Int ai_family : Int @@ -14,7 +32,8 @@ lib LibC ai_next : Addrinfo* end + fun freeaddrinfo(x0 : Addrinfo*) : Void fun gai_strerror(x0 : Int) : Char* - fun getaddrinfo(hostname : Char*, servname : Char*, hints : Addrinfo*, res : Addrinfo**) : Int - fun freeaddrinfo(ai : Addrinfo*) + fun getaddrinfo(x0 : Char*, x1 : Char*, x2 : Addrinfo*, x3 : Addrinfo**) : Int + fun getnameinfo(x0 : Sockaddr*, x1 : SocklenT, x2 : Char*, x3 : SocklenT, x4 : Char*, x5 : SocklenT, x6 : Int) : Int end diff --git a/src/lib_c/x86_64-linux-musl/c/sys/socket.cr b/src/lib_c/x86_64-linux-musl/c/sys/socket.cr index 1956a5c7b988..361e5b8040d6 100644 --- a/src/lib_c/x86_64-linux-musl/c/sys/socket.cr +++ b/src/lib_c/x86_64-linux-musl/c/sys/socket.cr @@ -11,6 +11,7 @@ lib LibC SO_LINGER = 13 SO_RCVBUF = 8 SO_REUSEADDR = 2 + SO_REUSEPORT = 15 SO_SNDBUF = 7 PF_INET = 2 PF_INET6 = 10 @@ -35,6 +36,12 @@ lib LibC sa_data : StaticArray(Char, 14) end + struct SockaddrStorage + ss_family : SaFamilyT + __ss_align : ULong + __ss_padding : StaticArray(Char, 112) + end + struct Linger l_onoff : Int l_linger : Int diff --git a/src/lib_c/x86_64-macosx-darwin/c/netdb.cr b/src/lib_c/x86_64-macosx-darwin/c/netdb.cr index 2127937a02f7..1a37cdafa00c 100644 --- a/src/lib_c/x86_64-macosx-darwin/c/netdb.cr +++ b/src/lib_c/x86_64-macosx-darwin/c/netdb.cr @@ -3,6 +3,24 @@ require "./sys/socket" require "./stdint" lib LibC + AI_PASSIVE = 0x00000001 + AI_CANONNAME = 0x00000002 + AI_NUMERICHOST = 0x00000004 + AI_NUMERICSERV = 0x00001000 + AI_V4MAPPED = 0x00000800 + AI_ALL = 0x00000100 + AI_ADDRCONFIG = 0x00000400 + EAI_AGAIN = 2 + EAI_BADFLAGS = 3 + EAI_FAIL = 4 + EAI_FAMILY = 5 + EAI_MEMORY = 6 + EAI_NONAME = 8 + EAI_SERVICE = 9 + EAI_SOCKTYPE = 10 + EAI_SYSTEM = 11 + EAI_OVERFLOW = 14 + struct Addrinfo ai_flags : Int ai_family : Int @@ -14,7 +32,8 @@ lib LibC ai_next : Addrinfo* end + fun freeaddrinfo(x0 : Addrinfo*) : Void fun gai_strerror(x0 : Int) : Char* - fun getaddrinfo(hostname : Char*, servname : Char*, hints : Addrinfo*, res : Addrinfo**) : Int - fun freeaddrinfo(ai : Addrinfo*) + fun getaddrinfo(x0 : Char*, x1 : Char*, x2 : Addrinfo*, x3 : Addrinfo**) : Int + fun getnameinfo(x0 : Sockaddr*, x1 : SocklenT, x2 : Char*, x3 : SocklenT, x4 : Char*, x5 : SocklenT, x6 : Int) : Int end diff --git a/src/lib_c/x86_64-macosx-darwin/c/sys/socket.cr b/src/lib_c/x86_64-macosx-darwin/c/sys/socket.cr index ca311e2938c5..907a0460e68a 100644 --- a/src/lib_c/x86_64-macosx-darwin/c/sys/socket.cr +++ b/src/lib_c/x86_64-macosx-darwin/c/sys/socket.cr @@ -11,6 +11,7 @@ lib LibC SO_LINGER = 0x0080 SO_RCVBUF = 0x1002 SO_REUSEADDR = 0x0004 + SO_REUSEPORT = 0x0200 SO_SNDBUF = 0x1001 PF_INET = LibC::AF_INET PF_INET6 = LibC::AF_INET6 @@ -35,6 +36,14 @@ lib LibC sa_data : StaticArray(Char, 14) end + struct SockaddrStorage + ss_len : SaFamilyT + ss_family : SaFamilyT + __ss_pad1 : StaticArray(Char, 6) + __ss_align : LongLong + __ss_pad2 : StaticArray(Char, 112) + end + struct Linger l_onoff : Int l_linger : Int diff --git a/src/lib_c/x86_64-portbld-freebsd/c/netdb.cr b/src/lib_c/x86_64-portbld-freebsd/c/netdb.cr index 8be344702749..eee1390fbebe 100644 --- a/src/lib_c/x86_64-portbld-freebsd/c/netdb.cr +++ b/src/lib_c/x86_64-portbld-freebsd/c/netdb.cr @@ -3,6 +3,24 @@ require "./sys/socket" require "./stdint" lib LibC + AI_PASSIVE = 0x00000001 + AI_CANONNAME = 0x00000002 + AI_NUMERICHOST = 0x00000004 + AI_NUMERICSERV = 0x00000008 + AI_V4MAPPED = 0x00000800 + AI_ALL = 0x00000100 + AI_ADDRCONFIG = 0x00000400 + EAI_AGAIN = 2 + EAI_BADFLAGS = 3 + EAI_FAIL = 4 + EAI_FAMILY = 5 + EAI_MEMORY = 6 + EAI_NONAME = 8 + EAI_SERVICE = 9 + EAI_SOCKTYPE = 10 + EAI_SYSTEM = 11 + EAI_OVERFLOW = 14 + struct Addrinfo ai_flags : Int ai_family : Int @@ -14,7 +32,8 @@ lib LibC ai_next : Addrinfo* end + fun freeaddrinfo(x0 : Addrinfo*) : Void fun gai_strerror(x0 : Int) : Char* - fun getaddrinfo(hostname : Char*, servname : Char*, hints : Addrinfo*, res : Addrinfo**) : Int - fun freeaddrinfo(ai : Addrinfo*) + fun getaddrinfo(x0 : Char*, x1 : Char*, x2 : Addrinfo*, x3 : Addrinfo**) : Int + fun getnameinfo(x0 : Void*, x1 : SocklenT, x2 : Char*, x3 : SizeT, x4 : Char*, x5 : SizeT, x6 : Int) : Int end diff --git a/src/lib_c/x86_64-portbld-freebsd/c/sys/socket.cr b/src/lib_c/x86_64-portbld-freebsd/c/sys/socket.cr index 92f1ffd3ea36..8a731c8ee82c 100644 --- a/src/lib_c/x86_64-portbld-freebsd/c/sys/socket.cr +++ b/src/lib_c/x86_64-portbld-freebsd/c/sys/socket.cr @@ -11,6 +11,7 @@ lib LibC SO_LINGER = 0x0080 SO_RCVBUF = 0x1002 SO_REUSEADDR = 0x0004 + SO_REUSEPORT = 0x0200 SO_SNDBUF = 0x1001 PF_INET = LibC::AF_INET PF_INET6 = LibC::AF_INET6 @@ -36,6 +37,14 @@ lib LibC sa_data : StaticArray(Char, 14) end + struct SockaddrStorage + ss_len : Char + ss_family : SaFamilyT + __ss_pad1 : StaticArray(Char, 6) + __ss_align : Long + __ss_pad2 : StaticArray(Char, 112) + end + struct Linger l_onoff : Int l_linger : Int diff --git a/src/socket.cr b/src/socket.cr index 706f00e1c993..b02be5c6d574 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -31,110 +31,314 @@ class Socket < IO::FileDescriptor INET6 = LibC::AF_INET6 end - struct IPAddress - getter family : Family - getter address : String - getter port : UInt16 - - def initialize(@family : Family, @address : String, port : Int) - if family != Family::INET && family != Family::INET6 - raise ArgumentError.new("Unsupported address family") - end + # :nodoc: + SOMAXCONN = 128 + + getter family : Family + getter type : Type + getter protocol : Protocol + + # Creates a TCP socket. Consider using `TCPSocket` or `TCPServer` unless you + # need full control over the socket. + def self.tcp(family : Family, blocking = false) + new(family, Type::STREAM, Protocol::TCP, blocking) + end + + # Creates an UDP socket. Consider using `UDPSocket` unless you need full + # control over the socket. + def self.udp(family : Family, blocking = false) + new(family, Type::DGRAM, Protocol::UDP, blocking) + end + + # Creates an UNIX socket. Consider using `UNIXSocket` or `UNIXServer` unless + # you need full control over the socket. + def self.unix(type : Type = Type::Stream, blocking = false) + new(Family::UNIX, type, blocking: blocking) + end + + def initialize(@family, @type, @protocol = Protocol::IP, blocking = false) + fd = LibC.socket(family, type, protocol) + raise Errno.new("failed to create socket:") if fd == -1 + init_close_on_exec(fd) + super(fd, blocking) + self.sync = true + end + + protected def initialize(fd : Int32, @family, @type, @protocol = Protocol::IP) + init_close_on_exec(fd) + super fd, blocking: false + self.sync = true + end + + # Force opened sockets to be closed on `exec(2)`. Only for platforms that don't + # support `SOCK_CLOEXEC` (e.g., Darwin). + protected def init_close_on_exec(fd : Int32) + {% unless LibC.has_constant?(:SOCK_CLOEXEC) %} + LibC.fcntl(fd, LibC::F_SETFD, LibC::FD_CLOEXEC) + {% end %} + end - @port = port.to_u16 + # Connects the socket to a remote host:port. + # + # ``` + # sock = Socket.tcp(Socket::Family::INET) + # sock.connect "crystal-lang.org", 80 + # ``` + def connect(host : String, port : Int, connect_timeout = nil) + Addrinfo.resolve(host, port, @family, @type, @protocol) do |addrinfo| + connect(addrinfo, timeout: connect_timeout) { |error| error } end + end + + # Connects the socket to a remote address. Raises if the connection failed. + # + # ``` + # sock = Socket.unix + # sock.connect UNIXAddress.new("/tmp/service.sock") + # ``` + def connect(addr, timeout = nil) : Nil + connect(addr, timeout) { |error| raise error } + end - def initialize(sockaddr : LibC::SockaddrIn6, addrlen : LibC::SocklenT) - case addrlen - when LibC::SocklenT.new(sizeof(LibC::SockaddrIn)) - sockaddrin = pointerof(sockaddr).as(LibC::SockaddrIn*).value - addr = sockaddrin.sin_addr - @family = Family::INET - @address = inet_ntop(family.value, pointerof(addr).as(Void*), addrlen) - when LibC::SocklenT.new(sizeof(LibC::SockaddrIn6)) - addr6 = sockaddr.sin6_addr - @family = Family::INET6 - @address = inet_ntop(family.value, pointerof(addr6).as(Void*), addrlen) + # Tries to connect to a remote address. Yields an `IO::Timeout` or an + # `Errno` error if the connection failed. + def connect(addr, timeout = nil) + loop do + if LibC.connect(fd, addr, addr.size) == 0 + return + end + case Errno.value + when Errno::EISCONN + return + when Errno::EINPROGRESS, Errno::EALREADY + wait_writable(msg: "connect timed out", timeout: timeout) do |error| + return yield error + end else - raise ArgumentError.new("Unsupported address family") + return yield Errno.new("connect") end - @port = LibC.htons(sockaddr.sin6_port).to_u16 end + end - def sockaddr - sockaddrin6 = LibC::SockaddrIn6.new - sockaddrin6.sin6_family = LibC::SaFamilyT.new(family.value) - - case family - when Family::INET - sockaddrin = pointerof(sockaddrin6).as(LibC::SockaddrIn*).value - addr = sockaddrin.sin_addr - LibC.inet_pton(family.value, address, pointerof(addr).as(Void*)) - sockaddrin.sin_addr = addr - sockaddrin6 = pointerof(sockaddrin).as(LibC::SockaddrIn6*).value - when Family::INET6 - addr6 = sockaddrin6.sin6_addr - LibC.inet_pton(family.value, address, pointerof(addr6).as(Void*)) - sockaddrin6.sin6_addr = addr6 - end - - sockaddrin6.sin6_port = LibC.ntohs(port).to_i16 - sockaddrin6 + # Binds the socket to a local address. + # + # ``` + # sock = Socket.tcp(Socket::Family::INET) + # sock.bind "localhost", 1234 + # ``` + def bind(host : String, port : Int) + Addrinfo.resolve(host, port, @family, @type, @protocol) do |addrinfo| + bind(addrinfo) { |errno| errno } end + end - def addrlen - case family - when Family::INET then LibC::SocklenT.new(sizeof(LibC::SockaddrIn)) - when Family::INET6 then LibC::SocklenT.new(sizeof(LibC::SockaddrIn6)) - else LibC::SocklenT.new(0) - end + # Binds the socket on *port* to all local interfaces. + # + # ``` + # sock = Socket.tcp(Socket::Family::INET6) + # sock.bind 1234 + # ``` + def bind(port : Int) + Addrinfo.resolve("::", port, @family, @type, @protocol) do |addrinfo| + bind(addrinfo) { |errno| errno } end + end + + # Binds the socket to a local address. + # + # ``` + # sock = Socket.udp(Socket::Family::INET) + # sock.bind Socket::IPAddress.new("192.168.1.25", 80) + # ``` + def bind(addr) + bind(addr) { |errno| raise errno } + end - def to_s(io) - io << address << ":" << port + # Tries to bind the socket to a local address. Yields an `Errno` if the + # binding failed. + def bind(addr) + unless LibC.bind(fd, addr, addr.size) == 0 + yield Errno.new("bind") end + end - private def inet_ntop(af : Int, src : Void*, len : LibC::SocklenT) - dest = GC.malloc_atomic(addrlen.to_u32).as(UInt8*) - if LibC.inet_ntop(af, src, dest, len).null? - raise Errno.new("Failed to convert IP address") - end - String.new(dest) + # Tells the previously bound socket to listen for incoming connections. + def listen(backlog = SOMAXCONN) + listen(backlog) { |errno| raise errno } + end + + # Tries to listen for connections on the previously bound socket. Yields an + # `Errno` on failure. + def listen(backlog = SOMAXCONN) + unless LibC.listen(fd, backlog) == 0 + yield Errno.new("listen") end end - struct UNIXAddress - getter path : String + # Accepts an incoming connection. + # + # Returns the client socket. Raises an `IO::Error` (closed stream) exception + # if the server is closed after invoking this method. + # + # ``` + # require "socket" + # + # server = TCPServer.new(2202) + # socket = server.accept + # socket.puts Time.now + # socket.close + # ``` + def accept + accept? || raise IO::Error.new("closed stream") + end - def initialize(@path : String) + # Accepts an incoming connection. + # + # Returns the client `Socket` or `nil` if the server is closed after invoking + # this method. + # + # ``` + # require "socket" + # + # server = TCPServer.new(2202) + # if socket = server.accept? + # socket.puts Time.now + # socket.close + # end + # ``` + def accept? + if client_fd = accept_impl + sock = Socket.new(client_fd) + sock.sync = sync? + sock end + end - def family - Family::UNIX + protected def accept_impl + loop do + client_fd = LibC.accept(fd, out client_addr, out client_addrlen) + if client_fd == -1 + if closed? + return + elsif Errno.value == Errno::EAGAIN + wait_readable + else + raise Errno.new("accept") + end + else + return client_fd + end end + end + + # Sends a text message to a previously connected remote address. + # + # ``` + # sock = Socket.udp(Socket::Family::INET) + # sock.connect("example.com", 2000) + # sock.send("text message") + # ``` + def send(message) + send(message.to_slice) + end - def to_s(io) - path.to_s(io) + # Sends a binary message to a previously connected remote address. + # + # ``` + # sock = Socket.unix(Socket::Type::DGRAM) + # sock.connect("/tmp/service.sock") + # sock.send(binary_message) + # ``` + def send(message : Bytes) + bytes_sent = LibC.send(fd, message.to_unsafe.as(Void*), message.size, 0) + raise Errno.new("Error sending datagram") if bytes_sent == -1 + bytes_sent + ensure + # see IO::FileDescriptor#unbuffered_write + if (writers = @writers) && !writers.empty? + add_write_event end end - def initialize(fd, blocking = false) - super fd, blocking - self.sync = true + # Sends a text message to the specified remote address. + # + # ``` + # server = Socket::IPAddress.new("10.0.3.1", 2022) + # sock = Socket.udp(Socket::Family::INET) + # sock.connect("example.com", 2000) + # sock.send("text query", to: server) + # ``` + def send(message, to addr : Address) + send(message.to_slice, to: addr) end - protected def create_socket(family, stype, protocol = 0) - sock = LibC.socket(family, stype, protocol) - raise Errno.new("Error opening socket") if sock <= 0 - init_close_on_exec sock - sock + # Sends a binary message to the specified remote address. + # + # ``` + # server = Socket::IPAddress.new("10.0.3.1", 53) + # sock = Socket.udp(Socket::Family::INET) + # sock.connect("example.com", 2000) + # sock.send(dns_query, to: server) + # ``` + def send(message : Bytes, to addr : Address) + bytes_sent = LibC.sendto(fd, message.to_unsafe.as(Void*), message.size, 0, addr, addr.size) + raise Errno.new("Error sending datagram to #{addr}") if bytes_sent == -1 + bytes_sent end - # only used when SOCK_CLOEXEC doesn't exist on the current platform - protected def init_close_on_exec(fd : Int32) - {% unless LibC.constants.includes?("SOCK_CLOEXEC".id) %} - LibC.fcntl(fd, LibC::F_SETFD, LibC::FD_CLOEXEC) - {% end %} + # Receives a text message from the previously bound address. + # + # ``` + # server = Socket.udp(Socket::Family::INET) + # server.bind("localhost", 1234) + # + # message, client_addr = server.receive + # ``` + def receive(max_message_size = 512) : {String, Address} + address = nil + message = String.new(max_message_size) do |buffer| + bytes_read, sockaddr, addrlen = recvfrom(Slice.new(buffer, max_message_size)) + address = Address.from(sockaddr, addrlen) + {bytes_read, 0} + end + {message, address.not_nil!} + end + + # Receives a binary message from the previously bound address. + # + # ``` + # server = Socket.udp(Socket::Family::INET) + # server.bind("localhost", 1234) + # + # message = Bytes.new(32) + # bytes_read, client_addr = server.receive(message) + # ``` + def receive(message : Bytes) : {Int32, Address} + bytes_read, sockaddr, addrlen = recvfrom(message) + {bytes_read, Address.from(sockaddr, addrlen)} + end + + protected def recvfrom(message) + sockaddr = Pointer(LibC::SockaddrStorage).malloc.as(LibC::Sockaddr*) + addrlen = LibC::SocklenT.new(sizeof(LibC::SockaddrStorage)) + + loop do + bytes_read = LibC.recvfrom(fd, message.to_unsafe.as(Void*), message.size, 0, sockaddr, pointerof(addrlen)) + if bytes_read == -1 + if Errno.value == Errno::EAGAIN + wait_readable + else + raise Errno.new("Error receiving datagram") + end + else + return {bytes_read.to_i, sockaddr, addrlen} + end + end + ensure + # see IO::FileDescriptor#unbuffered_read + if (readers = @readers) && !readers.empty? + add_read_event + end end # Calls shutdown(2) with SHUT_RD @@ -183,6 +387,14 @@ class Socket < IO::FileDescriptor setsockopt_bool LibC::SO_REUSEADDR, val end + def reuse_port? + getsockopt_bool LibC::SO_REUSEPORT + end + + def reuse_port=(val : Bool) + setsockopt_bool LibC::SO_REUSEPORT, val + end + def broadcast? getsockopt_bool LibC::SO_BROADCAST end @@ -254,27 +466,6 @@ class Socket < IO::FileDescriptor optval end - private def nonblocking_connect(host, port, addrinfo, timeout = nil) - loop do - ret = - {% if flag?(:freebsd) || flag?(:openbsd) %} - LibC.connect(@fd, addrinfo.ai_addr.as(LibC::Sockaddr*), addrinfo.ai_addrlen) - {% else %} - LibC.connect(@fd, addrinfo.ai_addr, addrinfo.ai_addrlen) - {% end %} - return nil if ret == 0 # success - - case Errno.value - when Errno::EISCONN - return nil # success - when Errno::EINPROGRESS, Errno::EALREADY - wait_writable(msg: "connect timed out", timeout: timeout) { |err| return err } - else - return Errno.new("Error connecting to '#{host}:#{port}'") - end - end - end - # Returns true if the string represents a valid IPv4 or IPv6 address. def self.ip?(string : String) addr = LibC::In6Addr.new diff --git a/src/socket/address.cr b/src/socket/address.cr new file mode 100644 index 000000000000..d88fc86adf06 --- /dev/null +++ b/src/socket/address.cr @@ -0,0 +1,194 @@ +class Socket + abstract struct Address + getter family : Family + getter size : Int32 + + def self.from(sockaddr : LibC::Sockaddr*, addrlen) : Address + case family = Family.new(sockaddr.value.sa_family) + when Family::INET6 + IPAddress.new(sockaddr.as(LibC::SockaddrIn6*), addrlen.to_i) + when Family::INET + IPAddress.new(sockaddr.as(LibC::SockaddrIn*), addrlen.to_i) + when Family::UNIX + UNIXAddress.new(sockaddr.as(LibC::SockaddrUn*), addrlen.to_i) + else + raise "unsupported family type: #{family} (#{family.value})" + end + end + + def initialize(@family : Family, @size : Int32) + end + + abstract def to_unsafe : LibC::Sockaddr* + + def ==(other) + false + end + end + + struct IPAddress < Address + getter port : Int32 + + @address : String? + @addr6 : LibC::In6Addr? + @addr4 : LibC::InAddr? + + def initialize(@address : String, @port : Int32) + if @addr6 = ip6?(address) + @family = Family::INET6 + @size = sizeof(LibC::SockaddrIn6) + elsif @addr4 = ip4?(address) + @family = Family::INET + @size = sizeof(LibC::SockaddrIn) + else + raise Error.new("Invalid IP address: #{address}") + end + end + + def self.from(sockaddr : LibC::Sockaddr*, addrlen) : IPAddress + case family = Family.new(sockaddr.value.sa_family) + when Family::INET6 + new(sockaddr.as(LibC::SockaddrIn6*), addrlen.to_i) + when Family::INET + new(sockaddr.as(LibC::SockaddrIn*), addrlen.to_i) + else + raise "unsupported family type: #{family} (#{family.value})" + end + end + + protected def initialize(sockaddr : LibC::SockaddrIn6*, @size) + @family = Family::INET6 + @addr6 = sockaddr.value.sin6_addr + @port = LibC.ntohs(sockaddr.value.sin6_port).to_i + end + + protected def initialize(sockaddr : LibC::SockaddrIn*, @size) + @family = Family::INET + @addr4 = sockaddr.value.sin_addr + @port = LibC.ntohs(sockaddr.value.sin_port).to_i + end + + private def ip6?(address) + addr = uninitialized LibC::In6Addr + addr if LibC.inet_pton(LibC::AF_INET6, address, pointerof(addr)) == 1 + end + + private def ip4?(address) + addr = uninitialized LibC::InAddr + addr if LibC.inet_pton(LibC::AF_INET, address, pointerof(addr)) == 1 + end + + def address + @address ||= begin + case family + when Family::INET6 then address(@addr6) + when Family::INET then address(@addr4) + else raise "unsupported IP address family: #{family}" + end + end + end + + private def address(addr : LibC::In6Addr) + String.new(46) do |buffer| + unless LibC.inet_ntop(family, pointerof(addr).as(Void*), buffer, 46) + raise Errno.new("Failed to convert IP address") + end + {LibC.strlen(buffer), 0} + end + end + + private def address(addr : LibC::InAddr) + String.new(16) do |buffer| + unless LibC.inet_ntop(family, pointerof(addr).as(Void*), buffer, 16) + raise Errno.new("Failed to convert IP address") + end + {LibC.strlen(buffer), 0} + end + end + + private def address(addr) : Nil + # shouldn't happen + end + + def ==(other : IPAddress) + family == other.family && + port == other.port && + address == other.address + end + + def to_s(io) + if family == Family::INET6 + io << '[' << address << ']' << ':' << port + else + io << address << ':' << port + end + end + + def to_unsafe : LibC::Sockaddr* + case family + when Family::INET6 + to_sockaddr_in6 + when Family::INET + to_sockaddr_in + else + raise "unsupported IP address family: #{family}" + end + end + + private def to_sockaddr_in6 + sockaddr = Pointer(LibC::SockaddrIn6).malloc + sockaddr.value.sin6_family = family + sockaddr.value.sin6_port = LibC.htons(port) + sockaddr.value.sin6_addr = @addr6.not_nil! + sockaddr.as(LibC::Sockaddr*) + end + + private def to_sockaddr_in + sockaddr = Pointer(LibC::SockaddrIn).malloc + sockaddr.value.sin_family = family + sockaddr.value.sin_port = LibC.htons(port) + sockaddr.value.sin_addr = @addr4.not_nil! + sockaddr.as(LibC::Sockaddr*) + end + end + + struct UNIXAddress < Address + getter path : String + + # :nodoc: + MAX_PATH_SIZE = LibC::SockaddrUn.new.sun_path.size - 1 + + def initialize(@path : String) + if @path.bytesize + 1 > MAX_PATH_SIZE + raise ArgumentError.new("Path size exceeds the maximum size of #{MAX_PATH_SIZE} bytes") + end + @family = Family::UNIX + @size = sizeof(LibC::SockaddrUn) + end + + def self.from(sockaddr : LibC::Sockaddr*, addrlen) : UNIXAddress + new(sockaddr.as(LibC::SockaddrUn*), addrlen.to_i) + end + + protected def initialize(sockaddr : LibC::SockaddrUn*, size) + @family = Family::UNIX + @path = String.new(sockaddr.value.sun_path.to_unsafe) + @size = size || sizeof(LibC::SockaddrUn) + end + + def ==(other : UNIXAddress) + path == other.path + end + + def to_s(io) + io << path + end + + def to_unsafe : LibC::Sockaddr* + sockaddr = Pointer(LibC::SockaddrUn).malloc + sockaddr.value.sun_family = family + sockaddr.value.sun_path.to_unsafe.copy_from(@path.to_unsafe, @path.bytesize + 1) + sockaddr.as(LibC::Sockaddr*) + end + end +end diff --git a/src/socket/addrinfo.cr b/src/socket/addrinfo.cr new file mode 100644 index 000000000000..3cf302ab30b3 --- /dev/null +++ b/src/socket/addrinfo.cr @@ -0,0 +1,91 @@ +class Socket + struct Addrinfo + getter family : Family + getter type : Type + getter protocol : Protocol + getter size : Int32 + + @addr : LibC::SockaddrIn6 + @next : LibC::Addrinfo* + + def self.resolve(host, service, family : Family, type : Type, protocol : Protocol = Protocol::IP, timeout = nil) + hints = LibC::Addrinfo.new + hints.ai_family = (family || Family::UNSPEC).to_i32 + hints.ai_socktype = type + hints.ai_protocol = protocol + hints.ai_flags = 0 + + if service.is_a?(Int) + hints.ai_flags |= LibC::AI_NUMERICSERV + end + + case ret = LibC.getaddrinfo(host, service.to_s, pointerof(hints), out ptr) + when 0 + # success + when LibC::EAI_NONAME + raise Socket::Error.new("No address found for #{host}:#{service} over #{protocol}") + else + raise Socket::Error.new("getaddrinfo: #{String.new(LibC.gai_strerror(ret))}") + end + + begin + addrinfo = new(ptr) + error = nil + + loop do + error = yield addrinfo.not_nil! + return unless error + + unless addrinfo = addrinfo.try(&.next?) + if error.is_a?(Errno) && error.errno == Errno::ECONNREFUSED + raise Errno.new("Error connecting to '#{host}:#{service}': Connection refused", error.errno) + else + raise error if error + end + end + end + ensure + LibC.freeaddrinfo(ptr) + end + end + + def self.tcp(host, service, family = Family::UNSPEC, timeout = nil) + resolve(host, service, family, Type::STREAM, Protocol::TCP) { |addrinfo| yield addrinfo } + end + + def self.udp(host, service, family = Family::UNSPEC, timeout = nil) + resolve(host, service, family, Type::DGRAM, Protocol::UDP) { |addrinfo| yield addrinfo } + end + + protected def initialize(addrinfo : LibC::Addrinfo*) + @family = Family.from_value(addrinfo.value.ai_family) + @type = Type.from_value(addrinfo.value.ai_socktype) + @protocol = Protocol.from_value(addrinfo.value.ai_protocol) + @size = addrinfo.value.ai_addrlen.to_i + + @addr = uninitialized LibC::SockaddrIn6 + @next = addrinfo.value.ai_next + + case @family + when Family::INET6 + addrinfo.value.ai_addr.as(LibC::SockaddrIn6*).copy_to(pointerof(@addr).as(LibC::SockaddrIn6*), 1) + when Family::INET + addrinfo.value.ai_addr.as(LibC::SockaddrIn*).copy_to(pointerof(@addr).as(LibC::SockaddrIn*), 1) + end + end + + def ip_address + @ip_address = IPAddress.from(@addr, @addrlen) + end + + def to_unsafe + pointerof(@addr).as(LibC::Sockaddr*) + end + + protected def next? + if addrinfo = @next + Addrinfo.new(addrinfo) + end + end + end +end diff --git a/src/socket/ip_socket.cr b/src/socket/ip_socket.cr index c274bbf1a440..12b63b556438 100644 --- a/src/socket/ip_socket.cr +++ b/src/socket/ip_socket.cr @@ -1,139 +1,27 @@ class IPSocket < Socket # Returns the `IPAddress` for the local end of the IP socket. def local_address - sockaddr = uninitialized LibC::SockaddrIn6 + sockaddr6 = uninitialized LibC::SockaddrIn6 + sockaddr = pointerof(sockaddr6).as(LibC::Sockaddr*) addrlen = LibC::SocklenT.new(sizeof(LibC::SockaddrIn6)) - if LibC.getsockname(fd, pointerof(sockaddr).as(LibC::Sockaddr*), pointerof(addrlen)) != 0 + if LibC.getsockname(fd, sockaddr, pointerof(addrlen)) != 0 raise Errno.new("getsockname") end - IPAddress.new(sockaddr, addrlen) + IPAddress.from(sockaddr, addrlen) end # Returns the `IPAddress` for the remote end of the IP socket. def remote_address - sockaddr = uninitialized LibC::SockaddrIn6 + sockaddr6 = uninitialized LibC::SockaddrIn6 + sockaddr = pointerof(sockaddr6).as(LibC::Sockaddr*) addrlen = LibC::SocklenT.new(sizeof(LibC::SockaddrIn6)) - if LibC.getpeername(fd, pointerof(sockaddr).as(LibC::Sockaddr*), pointerof(addrlen)) != 0 + if LibC.getpeername(fd, sockaddr, pointerof(addrlen)) != 0 raise Errno.new("getpeername") end - IPAddress.new(sockaddr, addrlen) - end - - class DnsRequestCbArg - getter value : Int32 | Pointer(LibC::Addrinfo) | Nil - @fiber : Fiber - - def initialize - @fiber = Fiber.current - end - - def value=(val) - @value = val - @fiber.resume - end - end - - # Yields LibC::Addrinfo to the block while the block returns false and there are more LibC::Addrinfo results. - # - # The block must return true if it succeeded using that addressinfo - # (to connect or bind, for example), and false otherwise. If it returns false and - # the LibC::Addrinfo has a next LibC::Addrinfo, it is yielded to the block, and so on. - private def getaddrinfo(host, port, family, socktype, protocol = Protocol::IP, timeout = nil) - # Using getaddrinfo from libevent doesn't work well, - # see https://github.com/crystal-lang/crystal/issues/2660 - # - # For now it's better to have this working well but maybe a bit slow than - # having it working fast but something working bad or not seeing some networks. - IPSocket.getaddrinfo_c_call(host, port, family, socktype, protocol, timeout) { |ai| yield ai } - end - - # :nodoc: - def self.getaddrinfo_c_call(host, port, family, socktype, protocol = Protocol::IP, timeout = nil) - hints = LibC::Addrinfo.new - hints.ai_family = (family || Family::UNSPEC).to_i32 - hints.ai_socktype = socktype - hints.ai_protocol = protocol - hints.ai_flags = 0 - - ret = LibC.getaddrinfo(host, port.to_s, pointerof(hints), out addrinfo) - raise Socket::Error.new("getaddrinfo: #{String.new(LibC.gai_strerror(ret))}") if ret != 0 - - begin - current_addrinfo = addrinfo - while current_addrinfo - success = yield current_addrinfo.value - break if success - current_addrinfo = current_addrinfo.value.ai_next - end - ensure - LibC.freeaddrinfo(addrinfo) - end - end - - # :nodoc: - def self.getaddrinfo_libevent(host, port, family, socktype, protocol = Protocol::IP, timeout = nil) - hints = LibC::Addrinfo.new - hints.ai_family = (family || Family::UNSPEC).to_i32 - hints.ai_socktype = socktype - hints.ai_protocol = protocol - hints.ai_flags = 0 - - dns_req = DnsRequestCbArg.new - - # may fire immediately or on the next event loop - req = Scheduler.create_dns_request(host, port.to_s, pointerof(hints), dns_req) do |err, addr, data| - dreq = data.as(DnsRequestCbArg) - - if err == 0 - dreq.value = addr - else - dreq.value = err - end - end - - if timeout && req - spawn do - sleep timeout.not_nil! - req.not_nil!.cancel unless dns_req.value - end - end - - success = false - - value = dns_req.value - # BUG: not thread safe. change when threads are implemented - unless value - Scheduler.reschedule - value = dns_req.value - end - - if value.is_a?(LibC::Addrinfo*) - begin - cur_addr = value - while cur_addr - success = yield cur_addr.value - - break if success - cur_addr = cur_addr.value.ai_next - end - ensure - LibEvent2.evutil_freeaddrinfo value - end - elsif value.is_a?(Int) - if value == LibEvent2::EVUTIL_EAI_CANCEL - raise IO::Timeout.new("Failed to resolve #{host} in #{timeout} seconds") - end - error_message = String.new(LibC.gai_strerror(value)) - raise Socket::Error.new("getaddrinfo: #{error_message}") - else - raise "unknown type #{value.inspect}" - end - - # shouldn't raise - raise Socket::Error.new("getaddrinfo: unspecified error") unless success + IPAddress.from(sockaddr, addrlen) end end diff --git a/src/socket/server.cr b/src/socket/server.cr new file mode 100644 index 000000000000..4154187f722e --- /dev/null +++ b/src/socket/server.cr @@ -0,0 +1,51 @@ +class Socket + module Server + # Accepts an incoming connection and yields the client socket to the block. + # Eventually closes the connection when the block returns. + # + # Returns the value of the block. If the server is closed after invoking this + # method, an `IO::Error` (closed stream) exception will be raised. + # + # ``` + # require "socket" + # + # server = TCPServer.new(2202) + # server.accept do |socket| + # socket.puts Time.now + # end + # ``` + def accept + sock = accept + begin + yield sock + ensure + sock.close + end + end + + # Accepts an incoming connection and yields the client socket to the block. + # Eventualy closes the connection when the block returns. + # + # Returns the value of the block or `nil` if the server is closed after + # invoking this method. + # + # ``` + # require "socket" + # + # server = UNIXServer.new("/tmp/service.sock") + # server.accept? do |socket| + # socket.puts Time.now + # end + # ``` + def accept? + sock = accept? + return unless sock + + begin + yield sock + ensure + sock.close + end + end + end +end diff --git a/src/socket/tcp_server.cr b/src/socket/tcp_server.cr index 6e77c06daf78..a3f43222c069 100644 --- a/src/socket/tcp_server.cr +++ b/src/socket/tcp_server.cr @@ -15,32 +15,29 @@ require "./tcp_socket" # end # ``` class TCPServer < TCPSocket - def initialize(host, port, backlog = 128) - getaddrinfo(host, port, nil, Type::STREAM, Protocol::TCP) do |addrinfo| - super create_socket(addrinfo.ai_family, addrinfo.ai_socktype, addrinfo.ai_protocol) + include Socket::Server + + def initialize(host : String, port : Int, backlog = SOMAXCONN, dns_timeout = nil) + Addrinfo.tcp(host, port, timeout: dns_timeout) do |addrinfo| + super(addrinfo.family, addrinfo.type, addrinfo.protocol) self.reuse_address = true + self.reuse_port = true - if LibC.bind(@fd, addrinfo.ai_addr.as(LibC::Sockaddr*), addrinfo.ai_addrlen) != 0 - errno = Errno.new("Error binding TCP server at #{host}:#{port}") + if errno = bind(addrinfo) { |errno| errno } close - next false if addrinfo.ai_next - raise errno + next errno end - if LibC.listen(@fd, backlog) != 0 - errno = Errno.new("Error listening TCP server at #{host}:#{port}") + if errno = listen(backlog) { |errno| errno } close - next false if addrinfo.ai_next - raise errno + next errno end - - true end end # Creates a new TCP server, listening on all local interfaces (`::`). - def self.new(port : Int, backlog = 128) + def self.new(port : Int, backlog = SOMAXCONN) new("::", port, backlog) end @@ -48,7 +45,7 @@ class TCPServer < TCPSocket # server socket when the block returns. # # Returns the value of the block. - def self.open(host, port, backlog = 128) + def self.open(host, port, backlog = SOMAXCONN) server = new(host, port, backlog) begin yield server @@ -61,7 +58,7 @@ class TCPServer < TCPSocket # block. Eventually closes the server socket when the block returns. # # Returns the value of the block. - def self.open(port : Int, backlog = 128) + def self.open(port : Int, backlog = SOMAXCONN) server = new(port, backlog) begin yield server @@ -70,80 +67,15 @@ class TCPServer < TCPSocket end end - # Accepts an incoming connection and yields the client socket to the block. - # Eventually closes the connection when the block returns. - # - # Returns the value of the block. If the server is closed after invoking this - # method, an `IO::Error` (closed stream) exception will be raised. - # - # ``` - # require "socket" - # - # server = TCPServer.new(2202) - # server.accept do |socket| - # socket.puts Time.now - # end - # ``` - def accept - sock = accept - begin - yield sock - ensure - sock.close - end - end - - # Accepts an incoming connection and yields the client socket to the block. - # Eventualy closes the connection when the block returns. - # - # Returns the value of the block or `nil` if the server is closed after - # invoking this method. - # - # ``` - # require "socket" - # - # server = TCPServer.new(2202) - # server.accept? do |socket| - # socket.puts Time.now - # end - # ``` - def accept? - sock = accept? - return unless sock - - begin - yield sock - ensure - sock.close - end - end - - # Accepts an incoming connection. - # - # Returns the client socket. Raises an `IO::Error` (closed stream) exception - # if the server is closed after invoking this method. - # - # ``` - # require "socket" - # - # server = TCPServer.new(2202) - # socket = server.accept - # socket.puts Time.now - # socket.close - # ``` - def accept : TCPSocket - accept? || raise IO::Error.new("closed stream") - end - # Accepts an incoming connection. # - # Returns the client socket or `nil` if the server is closed after invoking + # Returns the client `TCPSocket` or `nil` if the server is closed after invoking # this method. # # ``` # require "socket" # - # server = TCPServer.new(2202) + # server = TCPServer.new(2022) # loop do # if socket = server.accept? # # handle the client in a fiber @@ -154,24 +86,11 @@ class TCPServer < TCPSocket # end # end # ``` - def accept? : TCPSocket? - loop do - client_addr = uninitialized LibC::SockaddrIn6 - client_addr_len = LibC::SocklenT.new(sizeof(LibC::SockaddrIn6)) - client_fd = LibC.accept(fd, pointerof(client_addr).as(LibC::Sockaddr*), pointerof(client_addr_len)) - if client_fd == -1 - return nil if closed? - - if Errno.value == Errno::EAGAIN - wait_readable - else - raise Errno.new "Error accepting socket" - end - else - sock = TCPSocket.new(client_fd) - sock.sync = sync? - return sock - end + def accept? + if client_fd = accept_impl + sock = TCPSocket.new(client_fd, family, type, protocol) + sock.sync = sync? + sock end end end diff --git a/src/socket/tcp_socket.cr b/src/socket/tcp_socket.cr index b0d5705caacf..88749a6abe13 100644 --- a/src/socket/tcp_socket.cr +++ b/src/socket/tcp_socket.cr @@ -20,21 +20,21 @@ class TCPSocket < IPSocket # # Note that `dns_timeout` is currently ignored. def initialize(host, port, dns_timeout = nil, connect_timeout = nil) - getaddrinfo(host, port, nil, Type::STREAM, Protocol::TCP, timeout: dns_timeout) do |addrinfo| - super create_socket(addrinfo.ai_family, addrinfo.ai_socktype, addrinfo.ai_protocol) - - if err = nonblocking_connect(host, port, addrinfo, timeout: connect_timeout) + Addrinfo.tcp(host, port, timeout: dns_timeout) do |addrinfo| + super(addrinfo.family, addrinfo.type, addrinfo.protocol) + connect(addrinfo, timeout: connect_timeout) do |error| close - next false if addrinfo.ai_next - raise err + error end - - true end end - protected def initialize(fd : Int32) - super fd + protected def initialize(family : Family, type : Type, protocol : Protocol) + super family, type, protocol + end + + protected def initialize(fd : Int32, family : Family, type : Type, protocol : Protocol) + super fd, family, type, protocol end # Opens a TCP socket to a remote TCP server, yields it to the block, then diff --git a/src/socket/udp_socket.cr b/src/socket/udp_socket.cr index 00324c194b46..6cab1cf44f32 100644 --- a/src/socket/udp_socket.cr +++ b/src/socket/udp_socket.cr @@ -1,6 +1,6 @@ require "./ip_socket" -# A User Datagram Protocol socket. +# A User Datagram Protocol (UDP) socket. # # UDP runs on top of the Internet Protocol (IP) and was developed for applications that do # not require reliability, acknowledgement, or flow control features at the transport layer. @@ -27,8 +27,11 @@ require "./ip_socket" # client = UDPSocket.new # client.connect "localhost", 1234 # -# client.puts "message" # send message to server -# server.gets # => "message\n" +# # Send a text message to server +# client.send "message" +# +# # Receive text message from client +# message, client_addr = server.receive # # # Close client and server # client.close @@ -48,113 +51,39 @@ require "./ip_socket" # end # ``` class UDPSocket < IPSocket - def initialize(@family : Family = Family::INET) - super create_socket(family.value, Type::DGRAM, Protocol::UDP) + def initialize(family : Family = Family::INET) + super(family, Type::DGRAM, Protocol::UDP) end - # Binds the UDP socket to a local address. + # Receives a text message from the previously bound address. # # ``` # server = UDPSocket.new - # server.bind "localhost", 1234 - # ``` - def bind(host, port, dns_timeout = nil) - getaddrinfo(host, port, @family, Type::DGRAM, Protocol::UDP, timeout: dns_timeout) do |addrinfo| - self.reuse_address = true - - ret = - {% if flag?(:freebsd) || flag?(:openbsd) %} - LibC.bind(fd, addrinfo.ai_addr.as(LibC::Sockaddr*), addrinfo.ai_addrlen) - {% else %} - LibC.bind(fd, addrinfo.ai_addr, addrinfo.ai_addrlen) - {% end %} - unless ret == 0 - next false if addrinfo.ai_next - raise Errno.new("Error binding UDP socket at #{host}:#{port}") - end - - true - end - end - - # Connects the UDP socket to a remote address to send messages to. + # server.bind("localhost", 1234) # + # message, client_addr = server.receive # ``` - # client = UDPSocket.new - # client.connect("localhost", 1234) - # client.send("a text message") - # ``` - def connect(host, port, dns_timeout = nil, connect_timeout = nil) - getaddrinfo(host, port, @family, Type::DGRAM, Protocol::UDP, timeout: dns_timeout) do |addrinfo| - if err = nonblocking_connect host, port, addrinfo, timeout: connect_timeout - next false if addrinfo.ai_next - raise err - end - - true - end - end - - # Sends a text message to the previously connected remote address. See - # `#connect`. - def send(message : String) - send(message.to_slice) - end - - # Sends a binary message to the previously connected remote address. See - # `#connect`. - def send(message : Bytes) - bytes_sent = LibC.send(fd, (message.to_unsafe.as(Void*)), message.size, 0) - raise Errno.new("Error sending datagram") if bytes_sent == -1 - bytes_sent - ensure - if (writers = @writers) && !writers.empty? - add_write_event + def receive(max_message_size = 512) : {String, IPAddress} + address = nil + message = String.new(max_message_size) do |buffer| + bytes_read, sockaddr, addrlen = recvfrom(Slice.new(buffer, max_message_size)) + address = IPAddress.from(sockaddr, addrlen) + {bytes_read, 0} end + {message, address.not_nil!} end - # Sends a text message to the specified remote address. - def send(message : String, addr : IPAddress) - send(message.to_slice, addr) - end - - # Sends a binary message to the specified remote address. - def send(message : Bytes, addr : IPAddress) - sockaddr = addr.sockaddr - bytes_sent = LibC.sendto(fd, (message.to_unsafe.as(Void*)), message.size, 0, pointerof(sockaddr).as(LibC::Sockaddr*), addr.addrlen) - raise Errno.new("Error sending datagram to #{addr}") if bytes_sent == -1 - bytes_sent - end - - # Receives a binary message on the previously bound address. + # Receives a binary message from the previously bound address. # # ``` # server = UDPSocket.new # server.bind "localhost", 1234 # # message = Bytes.new(32) - # message_size, client_addr = server.receive(message) + # bytes_read, client_addr = server.receive(message) # ``` def receive(message : Bytes) : {Int32, IPAddress} - loop do - sockaddr = uninitialized LibC::SockaddrIn6 - addrlen = LibC::SocklenT.new(sizeof(LibC::SockaddrIn6)) - bytes_read = LibC.recvfrom(fd, (message.to_unsafe.as(Void*)), message.size, 0, pointerof(sockaddr).as(LibC::Sockaddr*), pointerof(addrlen)) - - if bytes_read == -1 - if Errno.value == Errno::EAGAIN - wait_readable - else - raise Errno.new("Error receiving datagram") - end - else - return {bytes_read.to_i32, IPAddress.new(sockaddr, addrlen)} - end - end - ensure - # see IO::FileDescriptor#unbuffered_read - if (readers = @readers) && !readers.empty? - add_read_event - end + bytes_read, sockaddr, addrlen = recvfrom(message) + {bytes_read, IPAddress.from(sockaddr, addrlen)} end end diff --git a/src/socket/unix_server.cr b/src/socket/unix_server.cr index 4c3cdb14578f..15feb2768181 100644 --- a/src/socket/unix_server.cr +++ b/src/socket/unix_server.cr @@ -13,6 +13,8 @@ require "./unix_socket" # server.puts message # ``` class UNIXServer < UNIXSocket + include Socket::Server + # Creates a named UNIX socket, listening on a filesystem pathname. # # Always deletes any existing filesystam pathname first, in order to cleanup @@ -24,24 +26,16 @@ class UNIXServer < UNIXSocket # UNIXServer.new("/tmp/dgram.sock", Socket::Type::DGRAM) # ``` def initialize(@path : String, type : Type = Type::STREAM, backlog = 128) - addr = LibC::SockaddrUn.new - addr.sun_family = typeof(addr.sun_family).new(Family::UNIX) - - if path.bytesize + 1 > addr.sun_path.size - raise ArgumentError.new("Path size exceeds the maximum size of #{addr.sun_path.size - 1} bytes") - end - addr.sun_path.to_unsafe.copy_from(path.to_unsafe, path.bytesize + 1) + super(Family::UNIX, type) - super create_socket(Family::UNIX, type, 0) - - if LibC.bind(@fd, (pointerof(addr).as(LibC::Sockaddr*)), sizeof(LibC::SockaddrUn)) != 0 + bind(UNIXAddress.new(path)) do |error| close - raise Errno.new("Error binding UNIX server at #{path}") + raise error end - if LibC.listen(@fd, backlog) != 0 + listen(backlog) do |error| close - raise Errno.new("Error listening UNIX server at #{path}") + raise error end end @@ -58,68 +52,19 @@ class UNIXServer < UNIXSocket end end - # Accepts an incoming connection and yields the client socket to the block. - # Eventually closes the connection when the block returns. - # - # Returns the value of the block. If the server is closed after invoking this - # method, an `IO::Error` (closed stream) exception will be raised. - def accept - sock = accept - begin - yield sock - ensure - sock.close - end - end - - # Accepts an incoming connection and yields the client socket to the block. - # Eventualy closes the connection when the block returns. - # - # Returns the value of the block or `nil` if the server is closed after - # invoking this method. - def accept? - sock = accept? - return unless sock - - begin - yield sock - ensure - sock.close - end - end - - # Accepts an incoming connection. - # - # Returns the client socket. Raises an `IO::Error` (closed stream) exception - # if the server is closed after invoking this method. - def accept : UNIXSocket - accept? || raise IO::Error.new("closed stream") - end - # Accepts an incoming connection. # # Returns the client socket or `nil` if the server is closed after invoking # this method. def accept? : UNIXSocket? - loop do - client_fd = LibC.accept(fd, out client_addr, out client_addrlen) - if client_fd == -1 - return nil if closed? - - if Errno.value == Errno::EAGAIN - wait_readable - else - raise Errno.new("Error accepting socket at #{path}") - end - else - sock = UNIXSocket.new(client_fd) - sock.sync = sync? - return sock - end + if client_fd = accept_impl + sock = UNIXSocket.new(client_fd, type) + sock.sync = sync? + sock end end - # Closes the socket and deletes the filesystem pathname. + # Closes the socket, then deletes the filesystem pathname if it exists. def close super ensure diff --git a/src/socket/unix_socket.cr b/src/socket/unix_socket.cr index f5d75da7e77e..777941efca6f 100644 --- a/src/socket/unix_socket.cr +++ b/src/socket/unix_socket.cr @@ -16,25 +16,20 @@ class UNIXSocket < Socket # Connects a named UNIX socket, bound to a filesystem pathname. def initialize(@path : String, type : Type = Type::STREAM) - addr = LibC::SockaddrUn.new - addr.sun_family = LibC::SaFamilyT.new(Family::UNIX) + super(Family::UNIX, type, Protocol::IP) - if path.bytesize + 1 > addr.sun_path.size - raise ArgumentError.new("Path size exceeds the maximum size of #{addr.sun_path.size - 1} bytes") - end - addr.sun_path.to_unsafe.copy_from(path.to_unsafe, path.bytesize + 1) - - super create_socket(Family::UNIX, type, 0) - - if LibC.connect(@fd, (pointerof(addr).as(LibC::Sockaddr*)), sizeof(LibC::SockaddrUn)) != 0 + connect(UNIXAddress.new(path)) do |error| close - raise Errno.new("Error connecting to '#{path}'") + raise error end end - protected def initialize(fd : Int32) - init_close_on_exec fd - super fd + protected def initialize(family : Family, type : Type) + super family, type, Protocol::IP + end + + protected def initialize(fd : Int32, type : Type) + super fd, Family::UNIX, type, Protocol::IP end # Opens an UNIX socket to a filesystem pathname, yields it to the block, then @@ -65,15 +60,18 @@ class UNIXSocket < Socket # left.gets # => "message" # ``` def self.pair(type : Type = Type::STREAM) - fds = StaticArray(Int32, 2).new { 0_i32 } + fds = uninitialized Int32[2] + socktype = type.value - {% if LibC.constants.includes?("SOCK_CLOEXEC".id) %} + {% if LibC.has_constant?(:SOCK_CLOEXEC) %} socktype |= LibC::SOCK_CLOEXEC {% end %} + if LibC.socketpair(Family::UNIX, socktype, 0, fds) != 0 raise Errno.new("socketpair:") end - fds.map { |fd| UNIXSocket.new(fd) } + + {UNIXSocket.new(fds[0], type), UNIXSocket.new(fds[1], type)} end # Creates a pair of unamed UNIX sockets (see `pair`) and yields them to the @@ -97,4 +95,9 @@ class UNIXSocket < Socket def remote_address UNIXAddress.new(path.to_s) end + + def receive + bytes_read, sockaddr, addrlen = recvfrom + {bytes_read, UNIXAddress.from(sockaddr, addrlen)} + end end