File: udp_socket_spec.cr

package info (click to toggle)
crystal 1.14.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 24,384 kB
  • sloc: javascript: 6,400; sh: 695; makefile: 269; ansic: 121; python: 105; cpp: 77; xml: 32
file content (187 lines) | stat: -rw-r--r-- 6,305 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
require "./spec_helper"
require "socket"

describe UDPSocket, tags: "network" do
  # Note: This spec fails with a IPv6 address. See pending below.
  it "#remote_address resets after connect" do
    socket = UDPSocket.new
    socket.connect("127.0.0.1", 1)
    socket.remote_address.port.should eq 1
    socket.connect("127.0.0.1", 2)
    socket.remote_address.port.should eq 2
    socket.close
  end

  pending "#connect with a IPv6 address" do
    socket = UDPSocket.new
    socket.connect("::1", 1)
    socket.close
  end

  each_ip_family do |family, address, unspecified_address|
    it "#bind" do
      port = unused_local_port
      socket = UDPSocket.new(family)
      socket.bind(address, port)
      socket.local_address.should eq(Socket::IPAddress.new(address, port))
      socket.close
      socket = UDPSocket.new(family)
      socket.bind(address, 0)
      socket.local_address.address.should eq address
    end

    it "sends and receives messages" do
      port = unused_local_port

      server = UDPSocket.new(family)
      server.bind(address, port)
      server.local_address.should eq(Socket::IPAddress.new(address, port))

      client = UDPSocket.new(family)
      client.bind(address, 0)

      client.send "message", to: server.local_address
      server.receive.should eq({"message", client.local_address})

      client.connect(address, port)
      client.local_address.family.should eq(family)
      client.local_address.address.should eq(address)
      client.remote_address.should eq(Socket::IPAddress.new(address, port))

      client.send "message"
      server.receive.should eq({"message", client.local_address})

      client.send("laus deo semper")

      buffer = uninitialized UInt8[256]

      bytes_read, client_addr = server.receive(buffer.to_slice)
      message = String.new(buffer.to_slice[0, bytes_read])
      message.should eq("laus deo semper")

      client.send("laus deo semper")

      # WSA errors with WSAEMSGSIZE if the buffer is not large enough to receive the message
      {% unless flag?(:win32) %}
        bytes_read, client_addr = server.receive(buffer.to_slice[0, 4])
        message = String.new(buffer.to_slice[0, bytes_read])
        message.should eq("laus")
      {% end %}

      client.close
      server.close
    end

    if {{ flag?(:darwin) }} && family == Socket::Family::INET6
      # Darwin is failing to join IPv6 multicast groups on older versions.
      # However this is known to work on macOS Mojave with Darwin 18.2.0.
      # Darwin also has a bug that prevents selecting the "default" interface.
      # https://lists.apple.com/archives/darwin-kernel/2014/Mar/msg00012.html
      pending "joins and transmits to multicast groups"
    elsif {{ flag?(:solaris) }} && family == Socket::Family::INET
      # TODO: figure out why updating `multicast_loopback` produces a
      # `setsockopt 18: Invalid argument` error
      pending "joins and transmits to multicast groups"
    else
      pending "joins and transmits to multicast groups" do
        udp = UDPSocket.new(family)
        port = unused_local_port
        udp.bind(unspecified_address, port)

        udp.multicast_loopback = false
        udp.multicast_loopback?.should eq(false)

        udp.multicast_hops = 4
        udp.multicast_hops.should eq(4)
        udp.multicast_hops = 0
        udp.multicast_hops.should eq(0)

        addr = case family
               when Socket::Family::INET
                 expect_raises(Socket::Error, "Unsupported IP address family: INET. For use with IPv6 only") do
                   udp.multicast_interface 0
                 end

                 begin
                   udp.multicast_interface Socket::IPAddress.new(unspecified_address, 0)
                 rescue e : Socket::Error
                   if e.os_error == Errno::ENOPROTOOPT
                     pending!("Multicast device selection not available on this host")
                   else
                     raise e
                   end
                 end

                 Socket::IPAddress.new("224.0.0.254", port)
               when Socket::Family::INET6
                 expect_raises(Socket::Error, "Unsupported IP address family: INET6. For use with IPv4 only") do
                   udp.multicast_interface(Socket::IPAddress.new(unspecified_address, 0))
                 end

                 begin
                   udp.multicast_interface(0)
                 rescue e : Socket::Error
                   if e.os_error == Errno::ENOPROTOOPT
                     pending!("Multicast device selection not available on this host")
                   else
                     raise e
                   end
                 end

                 Socket::IPAddress.new("ff02::102", port)
               else
                 raise "Unsupported IP address family: #{family}"
               end

        begin
          udp.join_group(addr)
        rescue e : Socket::Error
          if e.os_error == Errno::ENODEV
            pending!("Multicast device selection not available on this host")
          else
            raise e
          end
        end

        udp.multicast_loopback = true
        udp.multicast_loopback?.should eq(true)

        udp.send("testing", addr)
        udp.read_timeout = 1.second
        begin
          udp.receive[0].should eq("testing")
        rescue IO::TimeoutError
          # Since this test doesn't run over the loopback interface, this test
          # fails when there is a firewall in use. Don't fail in that case.
        end

        udp.leave_group(addr)
        udp.send("testing", addr)

        # Test that nothing was received after leaving the multicast group
        spawn do
          sleep 100.milliseconds
          udp.close
        end
        expect_raises(IO::Error) { udp.receive }
        udp.closed?.should be_true
      end
    end
  end

  {% if flag?(:linux) || flag?(:win32) %}
    it "sends broadcast message" do
      port = unused_local_port

      client = UDPSocket.new(Socket::Family::INET)
      client.bind("localhost", 0)
      client.broadcast = true
      client.broadcast?.should be_true
      client.connect("255.255.255.255", port)
      client.send("broadcast").should eq(9)
      client.close
    end
  {% else %}
    pending "sends broadcast message"
  {% end %}
end