]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/netpfil/pf/sctp.py
unbound: Import upstream 0ee44ef3 when ENOBUFS is returned
[FreeBSD/FreeBSD.git] / tests / sys / netpfil / pf / sctp.py
1 import pytest
2 import ctypes
3 import socket
4 import ipaddress
5 import re
6 from atf_python.sys.net.tools import ToolsHelper
7 from atf_python.sys.net.vnet import VnetTestTemplate
8
9 import time
10
11 SCTP_UNORDERED = 0x0400
12
13 SCTP_NODELAY                 = 0x00000004
14 SCTP_SET_PEER_PRIMARY_ADDR   = 0x00000006
15 SCTP_PRIMARY_ADDR            = 0x00000007
16
17 SCTP_BINDX_ADD_ADDR          = 0x00008001
18 SCTP_BINDX_REM_ADDR          = 0x00008002
19
20 class sockaddr_in(ctypes.Structure):
21     _fields_ = [
22         ('sin_len', ctypes.c_uint8),
23         ('sin_family', ctypes.c_uint8),
24         ('sin_port', ctypes.c_uint16),
25         ('sin_addr', ctypes.c_uint32),
26         ('sin_zero', ctypes.c_int8 * 8)
27     ]
28
29 class sockaddr_in6(ctypes.Structure):
30     _fields_ = [
31         ('sin6_len',      ctypes.c_uint8),
32         ('sin6_family',   ctypes.c_uint8),
33         ('sin6_port',     ctypes.c_uint16),
34         ('sin6_flowinfo', ctypes.c_uint32),
35         ('sin6_addr',     ctypes.c_uint8 * 16),
36         ('sin6_scope_id', ctypes.c_uint32)
37     ]
38
39 class sockaddr_storage(ctypes.Union):
40     _fields_ = [
41         ("v4",    sockaddr_in),
42         ("v6",   sockaddr_in6)
43     ]
44
45 class sctp_sndrcvinfo(ctypes.Structure):
46     _fields_ = [
47         ('sinfo_stream',        ctypes.c_uint16),
48         ('sinfo_ssn',           ctypes.c_uint16),
49         ('sinfo_flags',         ctypes.c_uint16),
50         ('sinfo_ppid',          ctypes.c_uint32),
51         ('sinfo_context',       ctypes.c_uint32),
52         ('sinfo_timetolive',    ctypes.c_uint32),
53         ('sinfo_tsn',           ctypes.c_uint32),
54         ('sinfo_cumtsn',        ctypes.c_uint32),
55         ('sinfo_assoc_id',      ctypes.c_uint32),
56     ]
57
58 class sctp_setprim(ctypes.Structure):
59     _fields_ = [
60         ('ssp_addr',        sockaddr_storage),
61         ('ssp_pad',         ctypes.c_int8 * (128 - 16)),
62         ('ssp_assoc_id',    ctypes.c_uint32),
63         ('ssp_padding',     ctypes.c_uint32)
64     ]
65
66 def to_sockaddr(ip, port):
67     ip = ipaddress.ip_address(ip)
68
69     if ip.version == 4:
70         addr = sockaddr_in()
71         addr.sin_len = ctypes.sizeof(addr)
72         addr.sin_family = socket.AF_INET
73         addr.sin_port = socket.htons(port)
74         addr.sin_addr = socket.htonl(int.from_bytes(ip.packed, byteorder='big'))
75     else:
76         assert ip.version == 6
77
78         addr = sockaddr_in6()
79         addr.sin6_len = ctypes.sizeof(addr)
80         addr.sin6_family = socket.AF_INET6
81         addr.sin6_port = socket.htons(port)
82         for i in range(0, 16):
83             addr.sin6_addr[i] = ip.packed[i]
84
85     return addr
86
87 class SCTPServer:
88     def __init__(self, family, port=1234):
89         self._libc = ctypes.CDLL("libc.so.7", use_errno=True)
90
91         self._listen_fd = self._libc.socket(family, socket.SOCK_STREAM, socket.IPPROTO_SCTP)
92         if self._listen_fd == -1:
93             raise Exception("Failed to create socket")
94
95         if family == socket.AF_INET:
96             srvaddr = sockaddr_in()
97             srvaddr.sin_len = ctypes.sizeof(srvaddr)
98             srvaddr.sin_family = socket.AF_INET
99             srvaddr.sin_port = socket.htons(port)
100             srvaddr.sin_addr = socket.INADDR_ANY
101         else:
102             srvaddr = sockaddr_in6()
103             srvaddr.sin6_len = ctypes.sizeof(srvaddr)
104             srvaddr.sin6_family = family
105             srvaddr.sin6_port = socket.htons(port)
106             # Leave sin_addr empty, because ANY is zero
107
108         ret = self._libc.bind(self._listen_fd, ctypes.pointer(srvaddr),
109             ctypes.sizeof(srvaddr))
110         if ret == -1:
111             raise Exception("Failed to bind: %d" % ctypes.get_errno())
112
113         ret = self._libc.listen(self._listen_fd, 2)
114         if ret == -1:
115             raise Exception("Failed to listen")
116
117     def _to_string(self, buf):
118         return ''.join([chr(int.from_bytes(i, byteorder='big')) for i in buf]).rstrip('\x00')
119
120     def accept(self, vnet):
121         fd = self._libc.accept(self._listen_fd, 0, 0)
122         if fd < 0:
123             raise Exception("Failed to accept")
124
125         print("SCTPServer: connection opened")
126         while True:
127             rcvinfo = sctp_sndrcvinfo()
128             flags = ctypes.c_int()
129             buf = ctypes.create_string_buffer(128)
130
131             # Receive a single message, and inform the other vnet about it.
132             ret = self._libc.sctp_recvmsg(fd, ctypes.cast(buf, ctypes.c_void_p), 128,
133                 0, 0, ctypes.pointer(rcvinfo), ctypes.pointer(flags))
134             if ret < 0:
135                 print("SCTPServer: connection closed")
136                 return
137             if ret == 0:
138                 continue
139
140             rcvd = {}
141             rcvd['ppid'] = socket.ntohl(rcvinfo.sinfo_ppid)
142             rcvd['data'] = self._to_string(buf)
143             rcvd['len'] = ret
144             print(rcvd)
145             vnet.pipe.send(rcvd)
146
147 class SCTPClient:
148     def __init__(self, ip, port=1234, fromaddr=None):
149         self._libc = ctypes.CDLL("libc.so.7", use_errno=True)
150
151         if ipaddress.ip_address(ip).version == 4:
152             family = socket.AF_INET
153         else:
154             family = socket.AF_INET6
155
156         self._fd = self._libc.socket(family, socket.SOCK_STREAM,
157             socket.IPPROTO_SCTP)
158         if self._fd == -1:
159             raise Exception("Failed to open socket")
160
161         if fromaddr is not None:
162             addr = to_sockaddr(fromaddr, 0)
163
164             ret = self._libc.bind(self._fd, ctypes.pointer(addr), ctypes.sizeof(addr))
165             if ret != 0:
166                 print("bind() => %d", ctypes.get_errno())
167                 raise
168
169         addr = to_sockaddr(ip, port)
170         ret = self._libc.connect(self._fd, ctypes.pointer(addr), ctypes.sizeof(addr))
171         if ret == -1:
172             raise Exception("Failed to connect")
173
174         # Enable NODELAY, because otherwise the sending host may wait for SACK
175         # on a data chunk we've removed
176         enable = ctypes.c_int(1)
177         ret = self._libc.setsockopt(self._fd, socket.IPPROTO_SCTP,
178                 SCTP_NODELAY, ctypes.pointer(enable), 4)
179
180     def newpeer(self, addr):
181         print("newpeer(%s)" % (addr))
182
183         setp = sctp_setprim()
184         a = to_sockaddr(addr, 0)
185         if type(a) is sockaddr_in:
186             setp.ssp_addr.v4 = a
187         else:
188             assert type(a) is sockaddr_in6
189             setp.ssp_addr.v6 = a
190
191         ret = self._libc.setsockopt(self._fd, socket.IPPROTO_SCTP,
192             SCTP_PRIMARY_ADDR, ctypes.pointer(setp), ctypes.sizeof(setp))
193         if ret != 0:
194             print("errno %d" % ctypes.get_errno())
195             raise Exception(ctypes.get_errno())
196
197     def newprimary(self, addr):
198         print("newprimary(%s)" % (addr))
199
200         # Strictly speaking needs to be struct sctp_setpeerprim, but that's
201         # identical to sctp_setprim
202         setp = sctp_setprim()
203         a = to_sockaddr(addr, 0)
204         if type(a) is sockaddr_in:
205             setp.ssp_addr.v4 = a
206         else:
207             assert type(a) is sockaddr_in6
208             setp.ssp_addr.v6 = a
209
210         ret = self._libc.setsockopt(self._fd, socket.IPPROTO_SCTP,
211             SCTP_SET_PEER_PRIMARY_ADDR, ctypes.pointer(setp), ctypes.sizeof(setp))
212         if ret != 0:
213             print("errno %d" % ctypes.get_errno())
214             raise
215
216     def bindx(self, addr, add):
217         print("bindx(%s, %s)" % (addr, add))
218
219         addr = to_sockaddr(addr, 0)
220
221         if add:
222             flag = SCTP_BINDX_ADD_ADDR
223         else:
224             flag = SCTP_BINDX_REM_ADDR
225         ret = self._libc.sctp_bindx(self._fd, ctypes.pointer(addr), 1, flag)
226         if ret != 0:
227             print("sctp_bindx() errno %d" % ctypes.get_errno())
228             raise
229
230     def send(self, buf, ppid, ordered=False):
231         flags = 0
232
233         if not ordered:
234             flags = SCTP_UNORDERED
235
236         ppid = socket.htonl(ppid)
237         ret = self._libc.sctp_sendmsg(self._fd, ctypes.c_char_p(buf), len(buf),
238             ctypes.c_void_p(0), 0, ppid, flags, 0, 0, 0)
239         if ret < 0:
240             raise Exception("Failed to send message")
241
242     def close(self):
243         self._libc.close(self._fd)
244         self._fd = -1
245
246 class TestSCTP(VnetTestTemplate):
247     REQUIRED_MODULES = ["sctp", "pf"]
248     TOPOLOGY = {
249         "vnet1": {"ifaces": ["if1"]},
250         "vnet2": {"ifaces": ["if1"]},
251         "if1": {"prefixes4": [("192.0.2.1/24", "192.0.2.2/24")]},
252     }
253
254     def vnet2_handler(self, vnet):
255         # Give ourself a second IP address, for multihome testing
256         ifname = vnet.iface_alias_map["if1"].name
257         ToolsHelper.print_output("/sbin/ifconfig %s inet alias 192.0.2.3/24" % ifname)
258
259         # Start an SCTP server process, pipe the ppid + data back to the other vnet?
260         srv = SCTPServer(socket.AF_INET, port=1234)
261         while True:
262             srv.accept(vnet)
263
264     @pytest.mark.require_user("root")
265     def test_multihome(self):
266         srv_vnet = self.vnet_map["vnet2"]
267
268         ToolsHelper.print_output("/sbin/pfctl -e")
269         ToolsHelper.pf_rules([
270             "block proto sctp",
271             "pass inet proto sctp to 192.0.2.0/24"])
272
273         # Sanity check, we can communicate with the primary address.
274         client = SCTPClient("192.0.2.3", 1234)
275         client.send(b"hello", 0)
276         rcvd = self.wait_object(srv_vnet.pipe)
277         print(rcvd)
278         assert rcvd['ppid'] == 0
279         assert rcvd['data'] == "hello"
280
281         try:
282             client.newpeer("192.0.2.2")
283             client.send(b"world", 0)
284             rcvd = self.wait_object(srv_vnet.pipe)
285             print(rcvd)
286             assert rcvd['ppid'] == 0
287             assert rcvd['data'] == "world"
288         finally:
289             # Debug output
290             ToolsHelper.print_output("/sbin/pfctl -ss")
291             ToolsHelper.print_output("/sbin/pfctl -sr -vv")
292
293         # Check that we have a state for 192.0.2.3 and 192.0.2.2 to 192.0.2.1
294         states = ToolsHelper.get_output("/sbin/pfctl -ss")
295         assert re.search(r"all sctp 192.0.2.1:.*192.0.2.3:1234", states)
296         assert re.search(r"all sctp 192.0.2.1:.*192.0.2.2:1234", states)
297
298     @pytest.mark.require_user("root")
299     def test_multihome_asconf(self):
300         srv_vnet = self.vnet_map["vnet2"]
301
302         # Assign a second IP to ourselves
303         ToolsHelper.print_output("/sbin/ifconfig %s inet alias 192.0.2.10/24"
304             % self.vnet.iface_alias_map["if1"].name)
305         ToolsHelper.print_output("/sbin/pfctl -e")
306         ToolsHelper.pf_rules([
307             "block proto sctp",
308             "pass inet proto sctp from 192.0.2.0/24"])
309
310         # Sanity check, we can communicate with the primary address.
311         client = SCTPClient("192.0.2.3", 1234, "192.0.2.1")
312         client.send(b"hello", 0)
313         rcvd = self.wait_object(srv_vnet.pipe)
314         print(rcvd)
315         assert rcvd['ppid'] == 0
316         assert rcvd['data'] == "hello"
317
318         # Now add our second address to the connection
319         client.bindx("192.0.2.10", True)
320
321         # We can still communicate
322         client.send(b"world", 0)
323         rcvd = self.wait_object(srv_vnet.pipe)
324         print(rcvd)
325         assert rcvd['ppid'] == 0
326         assert rcvd['data'] == "world"
327
328         # Now change to a different peer address
329         try:
330             client.newprimary("192.0.2.10")
331             client.send(b"!", 0)
332             rcvd = self.wait_object(srv_vnet.pipe, 5)
333             print(rcvd)
334             assert rcvd['ppid'] == 0
335             assert rcvd['data'] == "!"
336         finally:
337             # Debug output
338             ToolsHelper.print_output("/sbin/pfctl -ss -vv")
339
340         # Ensure we have the states we'd expect
341         states = ToolsHelper.get_output("/sbin/pfctl -ss")
342         assert re.search(r"all sctp 192.0.2.1:.*192.0.2.3:1234", states)
343         assert re.search(r"all sctp 192.0.2.10:.*192.0.2.3:1234", states)
344
345         # Now remove 192.0.2.1 as an address
346         client.bindx("192.0.2.1", False)
347
348         # We can still communicate
349         try:
350             client.send(b"More data", 0)
351             rcvd = self.wait_object(srv_vnet.pipe, 5)
352             print(rcvd)
353             assert rcvd['ppid'] == 0
354             assert rcvd['data'] =="More data"
355         finally:
356             # Debug output
357             ToolsHelper.print_output("/sbin/pfctl -ss -vv")
358
359         # Verify that state is closing
360         states = ToolsHelper.get_output("/sbin/pfctl -ss")
361         assert re.search(r"all sctp 192.0.2.1:.*192.0.2.3:1234.*SHUTDOWN", states)
362
363 class TestSCTPv6(VnetTestTemplate):
364     REQUIRED_MODULES = ["sctp", "pf"]
365     TOPOLOGY = {
366         "vnet1": {"ifaces": ["if1"]},
367         "vnet2": {"ifaces": ["if1"]},
368         "if1": {"prefixes6": [("2001:db8::1/64", "2001:db8::2/64")]},
369     }
370
371     def vnet2_handler(self, vnet):
372         # Give ourself a second IP address, for multihome testing
373         ifname = vnet.iface_alias_map["if1"].name
374         ToolsHelper.print_output("/sbin/ifconfig %s inet6 alias 2001:db8::3/64" % ifname)
375
376         # Start an SCTP server process, pipe the ppid + data back to the other vnet?
377         srv = SCTPServer(socket.AF_INET6, port=1234)
378         while True:
379             srv.accept(vnet)
380
381     @pytest.mark.require_user("root")
382     def test_multihome(self):
383         srv_vnet = self.vnet_map["vnet2"]
384
385         ToolsHelper.print_output("/sbin/pfctl -e")
386         ToolsHelper.pf_rules([
387             "block proto sctp",
388             "pass inet6 proto sctp to 2001:db8::0/64"])
389
390         # Sanity check, we can communicate with the primary address.
391         client = SCTPClient("2001:db8::3", 1234)
392         client.send(b"hello", 0)
393         rcvd = self.wait_object(srv_vnet.pipe)
394         print(rcvd)
395         assert rcvd['ppid'] == 0
396         assert rcvd['data'] == "hello"
397
398         # Now change to a different peer address
399         try:
400             client.newpeer("2001:db8::2")
401             client.send(b"world", 0)
402             rcvd = self.wait_object(srv_vnet.pipe)
403             print(rcvd)
404             assert rcvd['ppid'] == 0
405             assert rcvd['data'] == "world"
406         finally:
407             # Debug output
408             ToolsHelper.print_output("/sbin/pfctl -ss -vv")
409
410         # Check that we have the expected states
411         states = ToolsHelper.get_output("/sbin/pfctl -ss")
412         assert re.search(r"all sctp 2001:db8::1\[.*2001:db8::3\[1234\]", states)
413         assert re.search(r"all sctp 2001:db8::1\[.*2001:db8::2\[1234\]", states)
414
415     @pytest.mark.require_user("root")
416     def test_multihome_asconf(self):
417         srv_vnet = self.vnet_map["vnet2"]
418
419         # Assign a second IP to ourselves
420         ToolsHelper.print_output("/sbin/ifconfig %s inet6 alias 2001:db8::10/64"
421             % self.vnet.iface_alias_map["if1"].name)
422         ToolsHelper.print_output("/sbin/pfctl -e")
423         ToolsHelper.pf_rules([
424             "block proto sctp",
425             "pass inet6 proto sctp from 2001:db8::/64"])
426
427         # Sanity check, we can communicate with the primary address.
428         client = SCTPClient("2001:db8::3", 1234, "2001:db8::1")
429         client.send(b"hello", 0)
430         rcvd = self.wait_object(srv_vnet.pipe)
431         print(rcvd)
432         assert rcvd['ppid'] == 0
433         assert rcvd['data'] == "hello"
434
435         # Now add our second address to the connection
436         client.bindx("2001:db8::10", True)
437
438         # We can still communicate
439         client.send(b"world", 0)
440         rcvd = self.wait_object(srv_vnet.pipe)
441         print(rcvd)
442         assert rcvd['ppid'] == 0
443         assert rcvd['data'] == "world"
444
445         # Now change to a different peer address
446         try:
447             client.newprimary("2001:db8::10")
448             client.send(b"!", 0)
449             rcvd = self.wait_object(srv_vnet.pipe, 5)
450             print(rcvd)
451             assert rcvd['ppid'] == 0
452             assert rcvd['data'] == "!"
453         finally:
454             # Debug output
455             ToolsHelper.print_output("/sbin/pfctl -ss -vv")
456
457         # Check that we have the expected states
458         states = ToolsHelper.get_output("/sbin/pfctl -ss")
459         assert re.search(r"all sctp 2001:db8::1\[.*2001:db8::3\[1234\]", states)
460         assert re.search(r"all sctp 2001:db8::10\[.*2001:db8::3\[1234\]", states)
461
462         # Now remove 2001:db8::1 as an address
463         client.bindx("2001:db8::1", False)
464
465         # Wecan still communicate
466         try:
467             client.send(b"More data", 0)
468             rcvd = self.wait_object(srv_vnet.pipe, 5)
469             print(rcvd)
470             assert rcvd['ppid'] == 0
471             assert rcvd['data'] == "More data"
472         finally:
473             # Debug output
474             ToolsHelper.print_output("/sbin/pfctl -ss -vv")
475
476         # Verify that the state is closing
477         states = ToolsHelper.get_output("/sbin/pfctl -ss")
478         assert re.search(r"all sctp 2001:db8::1\[.*2001:db8::3\[1234\].*SHUTDOWN", states)