6 from atf_python.sys.net.tools import ToolsHelper
7 from atf_python.sys.net.vnet import VnetTestTemplate
11 SCTP_UNORDERED = 0x0400
13 SCTP_NODELAY = 0x00000004
14 SCTP_SET_PEER_PRIMARY_ADDR = 0x00000006
15 SCTP_PRIMARY_ADDR = 0x00000007
17 SCTP_BINDX_ADD_ADDR = 0x00008001
18 SCTP_BINDX_REM_ADDR = 0x00008002
20 class sockaddr_in(ctypes.Structure):
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)
29 class sockaddr_in6(ctypes.Structure):
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)
39 class sockaddr_storage(ctypes.Union):
45 class sctp_sndrcvinfo(ctypes.Structure):
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),
58 class sctp_setprim(ctypes.Structure):
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)
66 def to_sockaddr(ip, port):
67 ip = ipaddress.ip_address(ip)
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'))
76 assert ip.version == 6
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]
88 def __init__(self, family, port=1234):
89 self._libc = ctypes.CDLL("libc.so.7", use_errno=True)
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")
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
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
108 ret = self._libc.bind(self._listen_fd, ctypes.pointer(srvaddr),
109 ctypes.sizeof(srvaddr))
111 raise Exception("Failed to bind: %d" % ctypes.get_errno())
113 ret = self._libc.listen(self._listen_fd, 2)
115 raise Exception("Failed to listen")
117 def _to_string(self, buf):
118 return ''.join([chr(int.from_bytes(i, byteorder='big')) for i in buf]).rstrip('\x00')
120 def accept(self, vnet):
121 fd = self._libc.accept(self._listen_fd, 0, 0)
123 raise Exception("Failed to accept")
125 print("SCTPServer: connection opened")
127 rcvinfo = sctp_sndrcvinfo()
128 flags = ctypes.c_int()
129 buf = ctypes.create_string_buffer(128)
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))
135 print("SCTPServer: connection closed")
141 rcvd['ppid'] = socket.ntohl(rcvinfo.sinfo_ppid)
142 rcvd['data'] = self._to_string(buf)
148 def __init__(self, ip, port=1234, fromaddr=None):
149 self._libc = ctypes.CDLL("libc.so.7", use_errno=True)
151 if ipaddress.ip_address(ip).version == 4:
152 family = socket.AF_INET
154 family = socket.AF_INET6
156 self._fd = self._libc.socket(family, socket.SOCK_STREAM,
159 raise Exception("Failed to open socket")
161 if fromaddr is not None:
162 addr = to_sockaddr(fromaddr, 0)
164 ret = self._libc.bind(self._fd, ctypes.pointer(addr), ctypes.sizeof(addr))
166 print("bind() => %d", ctypes.get_errno())
169 addr = to_sockaddr(ip, port)
170 ret = self._libc.connect(self._fd, ctypes.pointer(addr), ctypes.sizeof(addr))
172 raise Exception("Failed to connect")
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)
180 def newpeer(self, addr):
181 print("newpeer(%s)" % (addr))
183 setp = sctp_setprim()
184 a = to_sockaddr(addr, 0)
185 if type(a) is sockaddr_in:
188 assert type(a) is sockaddr_in6
191 ret = self._libc.setsockopt(self._fd, socket.IPPROTO_SCTP,
192 SCTP_PRIMARY_ADDR, ctypes.pointer(setp), ctypes.sizeof(setp))
194 print("errno %d" % ctypes.get_errno())
195 raise Exception(ctypes.get_errno())
197 def newprimary(self, addr):
198 print("newprimary(%s)" % (addr))
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:
207 assert type(a) is sockaddr_in6
210 ret = self._libc.setsockopt(self._fd, socket.IPPROTO_SCTP,
211 SCTP_SET_PEER_PRIMARY_ADDR, ctypes.pointer(setp), ctypes.sizeof(setp))
213 print("errno %d" % ctypes.get_errno())
216 def bindx(self, addr, add):
217 print("bindx(%s, %s)" % (addr, add))
219 addr = to_sockaddr(addr, 0)
222 flag = SCTP_BINDX_ADD_ADDR
224 flag = SCTP_BINDX_REM_ADDR
225 ret = self._libc.sctp_bindx(self._fd, ctypes.pointer(addr), 1, flag)
227 print("sctp_bindx() errno %d" % ctypes.get_errno())
230 def send(self, buf, ppid, ordered=False):
234 flags = SCTP_UNORDERED
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)
240 raise Exception("Failed to send message")
243 self._libc.close(self._fd)
246 class TestSCTP(VnetTestTemplate):
247 REQUIRED_MODULES = ["sctp", "pf"]
249 "vnet1": {"ifaces": ["if1"]},
250 "vnet2": {"ifaces": ["if1"]},
251 "if1": {"prefixes4": [("192.0.2.1/24", "192.0.2.2/24")]},
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)
259 # Start an SCTP server process, pipe the ppid + data back to the other vnet?
260 srv = SCTPServer(socket.AF_INET, port=1234)
264 @pytest.mark.require_user("root")
265 def test_multihome(self):
266 srv_vnet = self.vnet_map["vnet2"]
268 ToolsHelper.print_output("/sbin/pfctl -e")
269 ToolsHelper.pf_rules([
271 "pass inet proto sctp to 192.0.2.0/24"])
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)
278 assert rcvd['ppid'] == 0
279 assert rcvd['data'] == "hello"
282 client.newpeer("192.0.2.2")
283 client.send(b"world", 0)
284 rcvd = self.wait_object(srv_vnet.pipe)
286 assert rcvd['ppid'] == 0
287 assert rcvd['data'] == "world"
290 ToolsHelper.print_output("/sbin/pfctl -ss")
291 ToolsHelper.print_output("/sbin/pfctl -sr -vv")
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)
298 @pytest.mark.require_user("root")
299 def test_multihome_asconf(self):
300 srv_vnet = self.vnet_map["vnet2"]
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([
308 "pass inet proto sctp from 192.0.2.0/24"])
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)
315 assert rcvd['ppid'] == 0
316 assert rcvd['data'] == "hello"
318 # Now add our second address to the connection
319 client.bindx("192.0.2.10", True)
321 # We can still communicate
322 client.send(b"world", 0)
323 rcvd = self.wait_object(srv_vnet.pipe)
325 assert rcvd['ppid'] == 0
326 assert rcvd['data'] == "world"
328 # Now change to a different peer address
330 client.newprimary("192.0.2.10")
332 rcvd = self.wait_object(srv_vnet.pipe, 5)
334 assert rcvd['ppid'] == 0
335 assert rcvd['data'] == "!"
338 ToolsHelper.print_output("/sbin/pfctl -ss -vv")
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)
345 # Now remove 192.0.2.1 as an address
346 client.bindx("192.0.2.1", False)
348 # We can still communicate
350 client.send(b"More data", 0)
351 rcvd = self.wait_object(srv_vnet.pipe, 5)
353 assert rcvd['ppid'] == 0
354 assert rcvd['data'] =="More data"
357 ToolsHelper.print_output("/sbin/pfctl -ss -vv")
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)
363 class TestSCTPv6(VnetTestTemplate):
364 REQUIRED_MODULES = ["sctp", "pf"]
366 "vnet1": {"ifaces": ["if1"]},
367 "vnet2": {"ifaces": ["if1"]},
368 "if1": {"prefixes6": [("2001:db8::1/64", "2001:db8::2/64")]},
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)
376 # Start an SCTP server process, pipe the ppid + data back to the other vnet?
377 srv = SCTPServer(socket.AF_INET6, port=1234)
381 @pytest.mark.require_user("root")
382 def test_multihome(self):
383 srv_vnet = self.vnet_map["vnet2"]
385 ToolsHelper.print_output("/sbin/pfctl -e")
386 ToolsHelper.pf_rules([
388 "pass inet6 proto sctp to 2001:db8::0/64"])
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)
395 assert rcvd['ppid'] == 0
396 assert rcvd['data'] == "hello"
398 # Now change to a different peer address
400 client.newpeer("2001:db8::2")
401 client.send(b"world", 0)
402 rcvd = self.wait_object(srv_vnet.pipe)
404 assert rcvd['ppid'] == 0
405 assert rcvd['data'] == "world"
408 ToolsHelper.print_output("/sbin/pfctl -ss -vv")
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)
415 @pytest.mark.require_user("root")
416 def test_multihome_asconf(self):
417 srv_vnet = self.vnet_map["vnet2"]
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([
425 "pass inet6 proto sctp from 2001:db8::/64"])
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)
432 assert rcvd['ppid'] == 0
433 assert rcvd['data'] == "hello"
435 # Now add our second address to the connection
436 client.bindx("2001:db8::10", True)
438 # We can still communicate
439 client.send(b"world", 0)
440 rcvd = self.wait_object(srv_vnet.pipe)
442 assert rcvd['ppid'] == 0
443 assert rcvd['data'] == "world"
445 # Now change to a different peer address
447 client.newprimary("2001:db8::10")
449 rcvd = self.wait_object(srv_vnet.pipe, 5)
451 assert rcvd['ppid'] == 0
452 assert rcvd['data'] == "!"
455 ToolsHelper.print_output("/sbin/pfctl -ss -vv")
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)
462 # Now remove 2001:db8::1 as an address
463 client.bindx("2001:db8::1", False)
465 # Wecan still communicate
467 client.send(b"More data", 0)
468 rcvd = self.wait_object(srv_vnet.pipe, 5)
470 assert rcvd['ppid'] == 0
471 assert rcvd['data'] == "More data"
474 ToolsHelper.print_output("/sbin/pfctl -ss -vv")
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)