Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 96 additions & 55 deletions spec/std/socket_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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("")

Expand All @@ -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|
Expand All @@ -244,6 +270,7 @@ describe UNIXSocket do

left << "ping"
right.gets(4).should eq("ping")

right << "pong"
left.gets(4).should eq("pong")
end
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand All @@ -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)
Expand Down
3 changes: 1 addition & 2 deletions src/errno.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/lib_c/aarch64-linux-gnu/c/sys/socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions src/lib_c/amd64-unknown-openbsd/c/sys/socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/lib_c/arm-linux-gnueabihf/c/sys/socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 21 additions & 2 deletions src/lib_c/i686-linux-gnu/c/netdb.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
15 changes: 11 additions & 4 deletions src/lib_c/i686-linux-gnu/c/sys/socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
Loading