-
Notifications
You must be signed in to change notification settings - Fork 185
/
test-udp.lua
347 lines (303 loc) · 11.7 KB
/
test-udp.lua
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
local TEST_PORT = 9123
return require('lib/tap')(function (test)
test("basic udp server and client (ipv4)", function (print, p, expect, uv)
local server = uv.new_udp()
assert(uv.udp_bind(server, "0.0.0.0", TEST_PORT))
assert(uv.udp_recv_start(server, expect(function (err, data, addr, flags)
p("server on recv", server, data, addr, flags)
assert(not err, err)
assert(data == "PING")
uv.close(server, expect(function()
p("server on close", server)
end))
end)))
p{server=server}
local client = uv.new_udp()
local req = assert(uv.udp_send(client, "PING", "127.0.0.1", TEST_PORT, expect(function (err)
p("client on send", client, err)
assert(not err, err)
uv.close(client, expect(function()
p("client on close", client)
end))
end)))
p{client=client,req=req}
end)
test("basic udp send from table", function (print, p, expect, uv)
local sendData = {"P", "I", "NG"}
local server = uv.new_udp()
assert(uv.udp_bind(server, "0.0.0.0", TEST_PORT))
assert(uv.udp_recv_start(server, expect(function (err, data, addr, flags)
p("server on recv", server, data, addr, flags)
assert(not err, err)
assert(data == table.concat(sendData))
uv.close(server, expect(function()
p("server on close", server)
end))
end)))
p{server=server}
local client = uv.new_udp()
local req = assert(uv.udp_send(client, sendData, "127.0.0.1", TEST_PORT, expect(function (err)
p("client on send", client, err)
assert(not err, err)
uv.close(client, expect(function()
p("client on close", client)
end))
end)))
p{client=client,req=req}
end)
test("basic udp server and client (ipv6)", function (print, p, expect, uv)
local server = uv.new_udp()
local _, err = uv.udp_bind(server, "::1", TEST_PORT)
if err then
p("ipv6 unavailable", err)
uv.close(server)
return
end
assert(uv.udp_recv_start(server, expect(function (err, data, addr, flags)
p("server on recv", server, data, addr, flags)
assert(not err, err)
assert(data == "PING")
uv.close(server, expect(function()
p("server on close", server)
end))
end)))
p{server=server}
local client = uv.new_udp()
local req = assert(uv.udp_send(client, "PING", "::1", TEST_PORT, expect(function (err)
p("client on send", client, err)
assert(not err, err)
uv.close(client, expect(function()
p("client on close", client)
end))
end)))
p{client=client,req=req}
end)
test("udp send args", function(print, p, expect, uv)
local udp = uv.new_udp()
local _, err = pcall(function() uv.udp_send(udp, "PING", 5, 5) end)
print(assert(err))
_, err = pcall(function() uv.udp_send(udp, "PING", "host", "port") end)
print(assert(err))
_, err = pcall(function() uv.udp_send(udp, "PING", "host", nil) end)
print(assert(err))
_, err = pcall(function() uv.udp_send(udp, "PING", nil, 5) end)
print(assert(err))
uv.close(udp)
end, "1.27.0")
test("udp connect", function(print, p, expect, uv)
local server = uv.new_udp()
local client = uv.new_udp()
assert(uv.udp_bind(server, "0.0.0.0", TEST_PORT))
local numRecvs = 0
assert(uv.udp_recv_start(server, function (err, data, addr, flags)
p("server on recv", server, data, addr, flags)
assert(not err, err)
-- nil data signifies nothing more to read
if data ~= nil then
assert(data == "PING", data)
numRecvs = numRecvs + 1
if numRecvs == 4 then
uv.close(server, expect(function()
p("server on close", server)
end))
uv.close(client, expect(function()
p("client on close", client)
end))
end
end
end))
p{server=server}
assert(uv.udp_connect(client, "127.0.0.1", TEST_PORT))
local _, err = uv.udp_connect(client, "8.8.8.8", TEST_PORT)
assert(err and err:sub(1,7) == "EISCONN", err)
local addr = assert(uv.udp_getpeername(client))
p(addr)
assert(addr.ip == "127.0.0.1")
assert(addr.port == TEST_PORT)
-- To send messages in connected UDP sockets addr must be NULL
_, err = uv.udp_try_send(client, "PING", "127.0.0.1", TEST_PORT)
assert(err and err:sub(1,7) == "EISCONN", err)
local r = assert(uv.udp_try_send(client, "PING", nil, nil))
assert(r == 4)
_, err = uv.udp_try_send(client, "PING", "8.8.8.8", TEST_PORT)
assert(err and err:sub(1,7) == "EISCONN", err)
assert(uv.udp_connect(client, nil, nil))
_, err = uv.udp_connect(client, nil, nil)
assert(err and err:sub(1,8) == "ENOTCONN", err)
_, err = uv.udp_getpeername(client)
assert(err and err:sub(1,8) == "ENOTCONN", err)
r = uv.udp_try_send(client, "PING", "127.0.0.1", TEST_PORT)
assert(r == 4)
_, err = uv.udp_try_send(client, "PING", nil, nil)
assert(err and err:sub(1,12) == "EDESTADDRREQ", err)
assert(uv.udp_connect(client, "127.0.0.1", TEST_PORT))
_, err = uv.udp_send(client, "PING", "127.0.0.1", TEST_PORT, function()
error("this send should fail")
end)
assert(err and err:sub(1,7) == "EISCONN", err)
assert(uv.udp_send(client, "PING", nil, nil, expect(function(err)
assert(not err, err)
uv.udp_connect(client, nil, nil)
_, err = uv.udp_send(client, "PING", nil, nil, function()
error("this send should fail")
end)
assert(err and err:sub(1,12) == "EDESTADDRREQ", err)
uv.udp_send(client, "PING", "127.0.0.1", TEST_PORT, expect(function(err)
assert(not err, err)
end))
end)))
end, "1.27.0")
-- return a test function reusable for ipv4 and ipv6
local function multicast_join_test(bind_addr, multicast_addr, interface_addr)
return function(print, p, expect, uv)
local uvVersionGEQ = require('lib/utils').uvVersionGEQ
local timeout = uv.new_timer()
local TIMEOUT_TIME = 1000
local server = assert(uv.new_udp())
assert(uv.udp_bind(server, bind_addr, TEST_PORT))
local _, err, errname = uv.udp_set_membership(server, multicast_addr, interface_addr, "join")
if errname == "ENODEV" then
print("no multicast route, skipping")
server:close()
timeout:close()
return
elseif errname == "EADDRNOTAVAIL" and multicast_addr == "ff02::1" then
-- OSX, BSDs, and some other platforms need %lo in their multicast/interface addr
-- so try that instead
multicast_addr = "ff02::1%lo0"
interface_addr = "::1%lo0"
assert(uv.udp_set_membership(server, multicast_addr, interface_addr, "join"))
else
assert(not err, err)
end
local client = assert(uv.new_udp())
local recv_cb_called = 0
local function recv_cb(err, data, addr, flags)
assert(not err, err)
p(data, addr)
-- empty callback can happen, just return early
if data == nil and addr == nil then
return
end
assert(addr)
assert(data == "PING")
recv_cb_called = recv_cb_called + 1
if recv_cb_called == 2 then
-- note: because of this conditional close, the test will fail with an unclosed handle if recv_cb_called
-- doesn't hit 2, so we don't need to expect(recv_cb) or assert recv_cb_called == 2
server:close()
timeout:close()
else
-- udp_set_source_membership added in 1.32.0
if uvVersionGEQ("1.32.0") then
local source_addr = addr.ip
assert(server:set_membership(multicast_addr, interface_addr, "leave"))
_, err, errname = server:set_source_membership(multicast_addr, interface_addr, source_addr, "join")
-- handle 'EBUSY' error accidentally on macOS macOS CI, see https://github.com/luvit/luv/issues/704
while errname == 'EBUSY' do
_, err, errname = server:set_source_membership(multicast_addr, interface_addr, source_addr, "join")
end
if errname == "ENOSYS" then
-- not all systems support set_source_membership, so rejoin the previous group and continue on
assert(server:set_membership(multicast_addr, interface_addr, "join"))
else
assert(not err, err)
end
end
assert(client:send("PING", multicast_addr, TEST_PORT, expect(function(err)
assert(not err, err)
client:close()
end)))
end
end
server:recv_start(recv_cb)
assert(client:send("PING", multicast_addr, TEST_PORT, expect(function(err)
-- EPERM here likely means that a firewall has denied the send, which
-- can happen in some build/CI environments, e.g. the Fedora build system.
-- Reproducible on Linux with iptables by doing:
-- iptables --policy OUTPUT DROP
-- iptables -A OUTPUT -s 127.0.0.1 -j ACCEPT
-- and for ipv6:
-- ip6tables --policy OUTPUT DROP
-- ip6tables -A OUTPUT -s ::1 -j ACCEPT
if err == "EPERM" then
print("send to multicast ip was likely denied by firewall, skipping")
client:close()
server:close()
timeout:close()
return
end
assert(not err, err)
end)))
-- some firewalls might reject incoming messages from multicast IPs,
-- so we need a timeout to avoid hanging forever in that scenario
timeout:start(TIMEOUT_TIME, 0, expect(function()
print("timeout (could be caused by firewall settings)")
client:close()
server:close()
timeout:close()
end, 0))
end
end
-- TODO This might be overkill, but the multicast
-- tests seem to rely on external interfaces being
-- available on some platforms. So, we use this to skip
-- the tests when there are no relevant exernal interfaces
-- available. Note: The Libuv multicast join test does use this
-- same check for skipping the ipv6 test; we just expanded it to
-- the ipv4 test as well.
local function has_external_interface(uv, family)
local addresses = assert(uv.interface_addresses())
for _, vals in pairs(addresses) do
for _, info in ipairs(vals) do
if (not family or info.family == family) and not info.internal then
return true
end
end
end
return false
end
test("udp multicast join ipv4", function(print, p, expect, uv)
if not has_external_interface(uv, "inet") then
print("no external ipv4 interface, skipping")
return
end
local testfn = multicast_join_test("0.0.0.0", "239.255.0.1", nil)
return testfn(print, p, expect, uv)
end)
test("udp multicast join ipv6", function(print, p, expect, uv)
if not has_external_interface(uv, "inet6") then
print("no external ipv6 interface, skipping")
return
end
local testfn = multicast_join_test("::", "ff02::1", nil)
return testfn(print, p, expect, uv)
end)
test("udp recvmmsg", function(print, p, expect, uv)
local NUM_SENDS = 8
local NUM_MSGS_PER_ALLOC = 4
local recver = uv.new_udp({mmsgs = NUM_MSGS_PER_ALLOC})
assert(recver:bind("0.0.0.0", TEST_PORT))
local sender = uv.new_udp()
local msgs_recved = 0
local recv_cb = function(err, data, addr, flags)
assert(not err, err)
p(data, addr)
-- empty callback can happen, just return early
if data == nil and addr == nil then
return
end
assert(addr)
assert(data == "PING")
msgs_recved = msgs_recved + 1
if msgs_recved == NUM_SENDS then
sender:close()
recver:close()
end
end
assert(recver:recv_start(recv_cb))
for i=1,NUM_SENDS do
assert(sender:try_send("PING", "127.0.0.1", TEST_PORT))
end
end, "1.39.0")
end)