An overview of the Python SCTP module¶
Description¶
This module is a wrapper for libraries implementing the SCTP protocol
and available on Linux and FreeBSD. This module strictly
implements the API described in RFC 6458. It consists
of a pure Python module sctp and three C-modules
_sctp.sctp, _sctp.sctp_info and
_sctp.sctp_event.
Note
The SCTP module does not implement everything marked as
deprecated in RFC 6458 (e.g.,
SCTP_USE_EXT_RCVINFO socket option,
sctp_sendmsg() function,…).
The core class is the sctp_socket class. It inherits
all attributes and methods from the socket.socket class in
the Python socket module (except for the method
accept() - see note below). To create such a
class:
>>> from socket import *
>>> from sctp import *
>>> sock = sctp_socket(AF_INET, SOCK_STREAM)
>>> sock.connect(('localhost', 20000)) # method inherited
>>> data, addr, info, infotype, flags = sock.sctp_recvv(1024) # new method
...
>>> sock.close() # method inherited
Note
Method accept() of class
sctp_socket behaves in the same way
as the standard method accept(),
i.e., it takes no arguments and returns a pair (conn,
address). The only difference is that conn is a
sctp_socket object and not a
socket.socket object.
RFC 6458 defines a number of socket options and, where
applicable, the corresponding C structures, the latter being available
as classes, with the same name, inheriting from dict.
The same applies to SCTP events and notifications, which are also
implemented as classes inheriting from dict.
Warning
Unlike the standard Python module socket module, IPv6
addresses are necessarily 4-tuples of the form
(host, port, flowinfo, scope_id), see
IP addresses for details.
Class diagrams¶
Module sctp¶
![digraph {
rankdir=LR
edge [arrowhead="vee", color="#5a6062"]
node [shape="box", style=filled, fillcolor="#ffe7d4", color="#5a6062"]
a [label="sctp.sctp_socket"]
b [
style=none
shape = none
label = <<table border="0" cellspacing="0" bgcolor="#e9f6f1"
align="left">
<tr><td port="m0" border="1" bgcolor="#aee3f3">Methods</td></tr>
<tr><td border="1" align="left">getsockopt()</td></tr>
<tr><td border="1" align="left">setsockopt()</td></tr>
<tr><td border="1" align="left">sctp_opt_info()</td></tr>
</table>>
]
a -> b:m0
c [label="_sctp.sctp.sctp_socket"]
d [
style=none
shape = none
label = <<table border="0" cellspacing="0" bgcolor="#e9f6f1"
align="left">
<tr><td port="m0" border="1" bgcolor="#aee3f3">Properties</td></tr>
<tr><td border="1" align="left">sctp_type</td></tr>
<tr><td border="1" align="left">sock</td></tr>
<tr><td border="1" bgcolor="#aee3f3">Methods</td></tr>
<tr><td border="1" align="left">sctp_bindx()</td></tr>
<tr><td border="1" align="left">sctp_connectx()</td></tr>
<tr><td border="1" align="left">sctp_getladdrs()</td></tr>
<tr><td border="1" align="left">sctp_getpaddrs()</td></tr>
<tr><td border="1" align="left">sctp_recvv()</td></tr>
<tr><td border="1" align="left">sctp_sendv()</td></tr>
<tr><td border="1" align="left">sctp_peeloff()</td></tr>
</table>>
]
c -> d:m0
o [label="object"]
{
edge [constraint=false]
a -> c -> o
}
}](_images/graphviz-f0d36fc92c601438efeda39d3f5b93d00dc9ecfe.png)
![digraph {
rankdir=LR
edge [arrowhead="vee", arrowtail="vee", color="#5a6062"]
node [shape="box", style=filled, fillcolor="#ffe7d4", color="#5a6062"]
g [label="sctp.sctp_*"]
h [
style=none
shape = none
label = <<table border="0" cellspacing="0" bgcolor="#ffe7d4"
align="left">
<tr><td border="1" align="left">sctp_initmsg</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_paddrparams</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_paddrinfo</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_rtoinfo</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_assocparams</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_setprim</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_setadaptation</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_assoc_value</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_authkeyid</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_sack_info</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_default_sndinfo</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_default_prinfo</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_status</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_setpeerprim</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_authchunk</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_authkey</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_event</td></tr>
</table>>
]
g -> h [dir=both]
a [label="sctp.sctp_generic"]
b [
style=none
shape = none
label = <<table border="0" cellspacing="0" bgcolor="#e9f6f1"
align="left">
<tr><td port="m0" border="1" bgcolor="#aee3f3">Properties</td></tr>
<tr><td border="1" align="left">fmt</td></tr>
<tr><td border="1" align="left">size</td></tr>
<tr><td border="1" align="left">to_bytes</td></tr>
<tr><td border="1" bgcolor="#aee3f3">Methods</td></tr>
<tr><td border="1" align="left">set_value()</td></tr>
</table>>
]
a -> b:m0
{
edge [constraint=false]
c [label="dict"]
g -> a -> c
}
}](_images/graphviz-220e8d38f9c6dde9ada1ee95ce3c4ac247d3fb18.png)
Module sctp_info¶
![digraph {
rankdir=LR
edge [arrowhead="vee", arrowtail="vee", color="#5a6062"]
node [shape="box", style=filled, fillcolor="#ffe7d4", color="#5a6062"]
g [label="_sctp.sctp_info.sctp_*"]
h [
style=none
shape = none
label = <<table border="0" cellspacing="0" bgcolor="#ffe7d4"
align="left">
<tr><td border="1" align="left">sctp_sndinfo</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_prinfo</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_authinfo</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_sendv_spa</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_rcvinfo</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_nxtinfo</td></tr>
<tr><td bgcolor="white" height="5px"></td></tr>
<tr><td border="1" align="left">sctp_recvv_rn</td></tr>
</table>>
]
g -> h [dir=both]
{
edge [constraint=false]
a [label="dict"]
g -> a
}
}](_images/graphviz-dbd675fdbcd84a8488d07fc043e816470ff47901.png)
Module sctp_event¶
![digraph {
rankdir=LR
edge [arrowhead="vee", color="#5a6062"]
node [shape="box", style=filled, fillcolor="#ffe7d4", color="#5a6062"]
a [label="_sctp.sctp_event"]
b [
style=none
shape = none
label = <<table border="0" cellspacing="0" bgcolor="#e9f6f1"
align="left">
<tr><td port="m0" border="1" bgcolor="#aee3f3">Methods</td></tr>
<tr><td border="1" align="left">parse_notification()</td></tr>
</table>>
]
a -> b:m0
}](_images/graphviz-3b5852b8df45e0f7cc174dc9a276abbb124a4f0d.png)
Some examples¶
A simple echo client/server¶
This is the echo client/server revisited. Here, both the client and the server use different streams for sending and receiving data.
from socket import *
from sctp import *
from enum import IntEnum
ADDR = '127.0.0.1'
PORT = 20000
DATA = b'Some data to be echoed'
class Streams(IntEnum):
IN = 0
OUT = 1
sock = sctp_socket(AF_INET, SOCK_STREAM)
sock.setsockopt(IPPROTO_SCTP, SCTP_RECVRCVINFO, 1)
info = sctp_sndinfo(snd_sid=Streams.OUT)
sock.sctp_sendv((DATA,), ((ADDR, PORT),), info)
data, addr, info, infotype, flags = sock.sctp_recvv(1024)
if infotype == SCTP_RECVV_RCVINFO and info['rcv_sid'] == Streams.IN:
print(f'Received from {addr}:\n {data}')
The client sends data on stream Streams.OUT (1) and
receives it on stream Streams.IN (0).
from socket import *
from sctp import *
from enum import IntEnum
ADDR = '0.0.0.0'
PORT = 20000
class Streams(IntEnum):
IN = 1
OUT = 0
sock = sctp_socket(AF_INET, SOCK_STREAM)
sock.bind((ADDR, PORT))
sock.listen(1)
while True:
s, addr = sock.accept()
s.setsockopt(IPPROTO_SCTP, SCTP_RECVRCVINFO, 1)
data, addr, info, infotype, _ = s.sctp_recvv(1024)
if infotype == SCTP_RECVV_RCVINFO and info['rcv_sid'] == Streams.IN:
sndinfo = sctp_sndinfo(snd_sid=Streams.OUT)
s.sctp_sendv((data,), (addr,), sndinfo)
s.close()
sock.close()
The server sends data on stream Streams.OUT (0) and
receives it on stream Streams.IN (1).
The Python version of the example shown in Appendix A of RFC 6458¶
from socket import *
from sctp import *
PORT = 9
ADDR = '127.0.0.1'
SIZE_OF_MESSAGE = 1000
NUMBER_OF_MESSAGES = 10
PPID = 1234
# Create a one-to-one style SCTP socket.
sd = sctp_socket(AF_INET, SOCK_STREAM)
# Prepare for requesting 2048 outgoing streams.
init = sctp_initmsg(sinit_num_ostreams=2048)
sd.setsockopt(IPPROTO_SCTP, SCTP_INITMSG, init)
ind = sctp_setadaptation(ssb_adaptation_ind=0x01020304)
sd.setsockopt(IPPROTO_SCTP, SCTP_ADAPTATION_LAYER, ind)
# Connect to the discard server.
sd.connect((ADDR, PORT))
# Get the actual number of outgoing streams.
status = sd.getsockopt(IPPROTO_SCTP, SCTP_STATUS, sctp_status())
info = sctp_sndinfo(snd_ppid=htonl(PPID), snd_flags=SCTP_UNORDERED)
buffer = b'A' + bytes(SIZE_OF_MESSAGE - 1)
for i in range(NUMBER_OF_MESSAGES):
info['snd_sid'] = i % status['sstat_outstrms']
sd.sctp_sendv((buffer,), None, info)
sd.close()
To test this script with discard server on Linux, you must first start this server. This requires two steps (as root):
Edit file
/etc/xinetd.d/discardand setdisable=nofor TCP protocol.Start discard server with command
withsctp:# withsctp xinetd
And finally, run the script as a non-privileged user:
$ python3 one_to_one.py
The Python version of the example shown in Appendix B of RFC 6458¶
from socket import *
from sctp import *
BUFFER_SIZE = (1 << 16)
PORT = 9
ADDR = '::'
TIMEOUT = 5
def print_notification(buf):
data = parse_notification(buf)
if data['sn_type'] == SCTP_ASSOC_CHANGE:
if data['sac_state'] == SCTP_COMM_UP:
print(
"Communication up (streams (in/out)="
f"({data['sac_inbound_streams']}/"
f"{data['sac_outbound_streams']}))."
)
elif data['sac_state'] == SCTP_COMM_LOST:
print(f"Communication lost (error={data['sac_error']}).")
elif data['sac_state'] == SCTP_RESTART:
print(
"Communication restarted (streams (in/out)="
f"({data['sac_inbound_streams']}/"
f"{data['sac_outbound_streams']}))."
)
elif data['sac_state'] == SCTP_SHUTDOWN_COMP:
print("Communication completed.")
elif data['sac_state'] == SCTP_CANT_STR_ASSOC:
print("Communication couldn't be started.")
elif data['sn_type'] == SCTP_PEER_ADDR_CHANGE:
print(f"^^^ Peer Address change: {data['spc_aaddr']} ", end='')
if data['spc_state'] == SCTP_ADDR_AVAILABLE:
print("is available.")
elif data['spc_state'] == SCTP_ADDR_UNREACHABLE:
print(f"is not available (error={data['spc_error']}).")
elif data['spc_state'] == SCTP_ADDR_REMOVED:
print("was removed.")
elif data['spc_state'] == SCTP_ADDR_ADDED:
print("was added.")
elif data['spc_state'] == SCTP_ADDR_MADE_PRIM:
print("is primary.")
elif data['sn_type'] == SCTP_SHUTDOWN_EVENT:
print("^^^ Shutdown received.")
elif data['sn_type'] == SCTP_ADAPTATION_INDICATION:
print(
f"^^^ Adaptation indication {data['sai_adaptation_ind']:08x}"
" received."
)
def main():
event_types = (
SCTP_ASSOC_CHANGE,
SCTP_PEER_ADDR_CHANGE,
SCTP_SHUTDOWN_EVENT,
SCTP_ADAPTATION_INDICATION
)
sd = sctp_socket(AF_INET6, SOCK_SEQPACKET)
event = sctp_event(se_assoc_id=SCTP_FUTURE_ASSOC, se_on=1)
for e in event_types:
event['se_type'] = e
sd.setsockopt(IPPROTO_SCTP, SCTP_EVENT, event)
sd.setsockopt(IPPROTO_SCTP, SCTP_AUTOCLOSE, TIMEOUT)
sd.setsockopt(IPPROTO_SCTP, SCTP_RECVRCVINFO, 1)
sd.bind((ADDR, PORT))
sd.listen(1)
while True:
data, addr, info, infotype, flags = sd.sctp_recvv(BUFFER_SIZE)
if flags & MSG_NOTIFICATION:
print_notification(data)
else:
print(
f"Message received from {addr[0]}:{addr[1]}: "
f"len={len(data)}", end=''
)
if infotype == SCTP_RECVV_RCVINFO:
print(f", sid={info['rcv_sid']}")
if (info['rcv_flags'] & SCTP_UNORDERED):
print(", unordered", end='')
else:
print(f", ssn={info['rcv_ssn']}", end='')
print(f", tsn={info['rcv_tsn']}", end='')
print(f", ppid={ntohl(info['rcv_ppid'])}.")
elif infotype in (
SCTP_RECVV_NOINFO, SCTP_RECVV_NXTINFO, SCTP_RECVV_RN):
print(".")
sd.close()
if __name__ == '__main__':
main()
To test this script, first run it as root (port=9 is a priviliged one):
# python3 one-to-many.py
In another window, run one_to_one.py as a non-privileged user:
$ python3 one_to_one.py
In root window, you will see something like that:
# python3 one-to-many.py
Communication up (streams (in/out)=(2048/10)).
^^^ Adaptation indication 01020304 received.
Message received from ::ffff:127.0.0.1:52378: len=1000, sid=0
, unordered, tsn=3093043390, ppid=1234.
Message received from ::ffff:127.0.0.1:52378: len=1000, sid=1
, unordered, tsn=3093043391, ppid=1234.
Message received from ::ffff:127.0.0.1:52378: len=1000, sid=2
, unordered, tsn=3093043392, ppid=1234.
Message received from ::ffff:127.0.0.1:52378: len=1000, sid=3
, unordered, tsn=3093043393, ppid=1234.
Message received from ::ffff:127.0.0.1:52378: len=1000, sid=4
, unordered, tsn=3093043394, ppid=1234.
Message received from ::ffff:127.0.0.1:52378: len=1000, sid=5
, unordered, tsn=3093043395, ppid=1234.
Message received from ::ffff:127.0.0.1:52378: len=1000, sid=6
, unordered, tsn=3093043396, ppid=1234.
Message received from ::ffff:127.0.0.1:52378: len=1000, sid=7
, unordered, tsn=3093043397, ppid=1234.
Message received from ::ffff:127.0.0.1:52378: len=1000, sid=8
, unordered, tsn=3093043398, ppid=1234.
Message received from ::ffff:127.0.0.1:52378: len=1000, sid=9
, unordered, tsn=3093043399, ppid=1234.
^^^ Shutdown received.
Communication completed.