-
Notifications
You must be signed in to change notification settings - Fork 67
/
sctp.py
1813 lines (1408 loc) · 61.3 KB
/
sctp.py
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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# SCTP bindings for Python
# -*- coding: utf-8 -*-
#
# Python-side bindings
#
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library; If not, see <http://www.gnu.org/licenses/>.
#
# Elvis Pfützenreuter (elvis.pfutzenreuter@{gmail.com,indt.org.br})
# Copyright (c) 2005 Instituto Nokia de Tecnologia
"""
Python bindings for SCTP. It is compatible with kernel-level SCTP
implementations with BSD/Sockets API.
For the users, the relevant classes and functions are:
features()
sctpsocket(): base class, ought not used directly, althrough it can be
sctpsocket_tcp(): TCP-style subclass
sctpsocket_udp(): UDP-style subclass
SCTP sockets do NOT inherit from socket._socketobject, instead they
CONTAIN a standard Python socket, and DELEGATE unknown calls to it.
So it should fit in most places where a "real" socket is expected.
When receiving data, it is also possible to receive metadata in the
form of events. An event will be one of the following classes:
sndrcvinfo():
notification():
assoc_change():
paddr_change():
send_failed():
remote_error():
shutdown_event():
pdapi_event():
adaptation_event():
Every SCTP socket has a number of properties. Two "complex" properties,
that contain a number of sub-properties, are:
sctpsocket.events = event_subscribe()
sctpsocket.initparams = initparams()
Please take a look in the classes' documentation to learn more about
respective sub-properties.
Most SCTP-specific macros are available as constants in Python, so
an experienced SCTP/C API programmer will feel at home at pysctp.
The only difference is that constants used only inside a particular
event, are defined as class constants and not as module constants,
to avoid excessive namespace pollution.
"""
from __future__ import print_function
import socket
import _sctp
import datetime
import time
import os
import sys
####################################### CONSTANTS
# bindx() constants
BINDX_ADD = _sctp.getconstant("BINDX_ADD")
BINDX_REMOVE = _sctp.getconstant("BINDX_REMOVE")
BINDX_REM = BINDX_REMOVE
# high-level (sndrcvinfo) message flags
MSG_UNORDERED = _sctp.getconstant("MSG_UNORDERED")
MSG_ADDR_OVER = _sctp.getconstant("MSG_ADDR_OVER")
MSG_ABORT = _sctp.getconstant("MSG_ABORT")
MSG_EOF = _sctp.getconstant("MSG_EOF")
MSG_FIN = _sctp.getconstant("MSG_FIN")
MSG_SENDALL = _sctp.getconstant("MSG_SENDALL")
MSG_ADDR_OVERRIDE = MSG_ADDR_OVER
# low-level (sendto/sendmsg) flags
FLAG_NOTIFICATION = _sctp.getconstant("MSG_NOTIFICATION")
FLAG_EOR = _sctp.getconstant("MSG_EOR")
FLAG_DONTROUTE = _sctp.getconstant("MSG_DONTROUTE")
(HAVE_SCTP, HAVE_KERNEL_SCTP, HAVE_SCTP_MULTIBUF, HAVE_SCTP_NOCONNECT, \
HAVE_SCTP_PRSCTP, HAVE_SCTP_ADDIP, HAVE_SCTP_CANSET_PRIMARY, HAVE_SCTP_SAT_NETWORK_CAPABILITY) = \
(1,2,4,8,16,32,64,128)
# socket constants
SOL_SCTP = _sctp.getconstant("SOL_SCTP")
IPPROTO_SCTP = _sctp.getconstant("IPPROTO_SCTP")
SOCK_SEQPACKET = _sctp.getconstant("SOCK_SEQPACKET")
SOCK_STREAM = _sctp.getconstant("SOCK_STREAM")
(STYLE_TCP, STYLE_UDP) = (SOCK_STREAM, SOCK_SEQPACKET)
(TCP_STYLE, UDP_STYLE) = (STYLE_TCP, STYLE_UDP)
####################################### STRUCTURES FOR SCTP MESSAGES AND EVENTS
class initmsg(object):
"""
Object used in explicit opening of SCTP associations in
UDP-style sockets without passing any user-data message
(UNIMPLEMENTED in pysctp, probably needs direct sendmsg() use)
User will be better off it instantiate this class via
initparams.initmsg() constructor, because it fills initmsg with
socket's defaults.
"""
def __init__(self):
self.num_ostreams = 0
self.max_instreams = 0
self.max_attempts = 0
self.max_init_timeo = 0
class initparams(object):
"""
SCTP default initialization parameters. New associations are opened
with the properties specified in this object.
Properties are:
num_ostreams
max_instreams
max_attempts
max_init_timeo (in seconds)
Setting of properties will automatically call flush() (that will
setsockopt the socket) unless "autoflush" is set to False.
User should never need to instantiate this class directly. For every
property, there is a pair of get_P and set_P methods, that user can,
but should not need, to call directly.
"""
def __init__(self, container):
"""
The container object passed as parameter will (I hope) be a
sctpsocket() descendant. It should offer the _get_initparams()
and _set_initparams() for the socket it represents.
"""
self.autoflush = True
self.container = container
self._num_ostreams = self._max_instreams = 0
self._max_attempts = self._max_init_timeo = 0
self.__dict__.update(self.container._get_initparams())
def flush(self):
"""
Flushes the initialization properties to the socket
(setsockopt). If autoflush equals True (the default),
any change to properties will call this automatically.
"""
self.container._set_initparams(self.__dict__)
def initmsg(self):
"""
Builds a initmsg() object containing the same properties
as the socket initalization parameters. The initmsg()
can then be changed and used to open a particular SCTP
association.
"""
r = initmsg()
r.num_ostreams = self._num_ostreams;
r.max_instreams = self._max_instreams;
r.max_attempts = self._max_attempts;
r.max_init_timeo = self._max_init_timeo;
return r
def get_num_ostreams(self):
return self._num_ostreams;
def set_num_ostreams(self, v):
self._num_ostreams = v;
if self.autoflush:
self.flush()
def get_max_instreams(self):
return self._max_instreams;
def set_max_instreams(self, v):
self._max_instreams = v;
if self.autoflush:
self.flush()
def get_max_attempts(self):
return self._max_attempts;
def set_max_attempts(self, v):
self._max_attempts = v;
if self.autoflush:
self.flush()
def get_max_init_timeo(self):
return self._max_init_timeo;
def set_max_init_timeo(self, v):
self._max_init_timeo = v;
if self.autoflush:
self.flush()
num_ostreams = property(get_num_ostreams, set_num_ostreams)
max_instreams = property(get_max_instreams, set_max_instreams)
max_attempts = property(get_max_attempts, set_max_attempts)
max_init_timeo = property(get_max_init_timeo, set_max_init_timeo)
class sndrcvinfo(object):
"""
Send/receive ancilliary data. In PySCTP, this object is only used
to *receive* ancilliary information about a message. The user
should never need to instantiate this class.
The "flag" attribute is a bitmap of the MSG_* class constants.
"""
def __init__(self, values=None):
self.stream = 0
self.ssn = 0
self.flags = 0
self.context = 0
self.timetolive = 0
self.tsn = 0
self.cumtsn = 0
self.assoc_id = 0
if values:
self.__dict__.update(values)
class notification(object):
"""
Base class for all notification objects. Objects of this particular
type should never appear in message receiving. If it does, it means
that PySCTP received a notification it does not understand (probably
a "new" event type), and author must be warned about this.
The "type" and "flags" fields belong to this class because they
appear in every notification. This class contains "type_*" constants
that identify every notification type. On the other hand, "flags"
meanings are particular for every notification type.
The user should never need to instantiate this class.
"""
def __init__(self, values):
self.type = 0
self.flags = 0
if values:
self.__dict__.update(values)
type_SN_TYPE_BASE = _sctp.getconstant("SCTP_SN_TYPE_BASE")
type_ASSOC_CHANGE = _sctp.getconstant("SCTP_ASSOC_CHANGE")
type_PEER_ADDR_CHANGE = _sctp.getconstant("SCTP_PEER_ADDR_CHANGE")
type_SEND_FAILED = _sctp.getconstant("SCTP_SEND_FAILED")
type_REMOTE_ERROR = _sctp.getconstant("SCTP_REMOTE_ERROR")
type_SHUTDOWN_EVENT = _sctp.getconstant("SCTP_SHUTDOWN_EVENT")
type_PARTIAL_DELIVERY_EVENT = _sctp.getconstant("SCTP_PARTIAL_DELIVERY_EVENT")
type_ADAPTATION_INDICATION = _sctp.getconstant("SCTP_ADAPTATION_INDICATION")
class assoc_change(notification):
"""
Association change notification. The relevant values are:
state
error
outbound_streams
inbound_streams
assoc_id
It seems to lack remote address/port pair, but remember that recvmsg() and
sctp_recv() return the address for every (either data or event) message, so
the application can have full knowledge about the new connection.
Most UDP-style socket users will want to save this event, in particular "assoc_id"
because that value will be needed to further refer to the association. Address/pair
port can also be used to identify the remote peer, but it can be ambiguous since
SCTP can make multihomed associations.
The "state" can be tested against "state_*" class constants. If state is
state_CANT_START_ASSOCIATION, "error" will be one of the "error_*" constants.
The user should never need to instantiate this directly. This notification will
be received only if user subscribed to receive that (see event_subscribe class
for details).
"""
def __init__(self, values=None):
self.state = 0
self.error = 0
self.outbound_streams = 0
self.inbound_streams = 0
self.assoc_id = 0
notification.__init__(self, values)
state_COMM_UP = _sctp.getconstant("SCTP_COMM_UP")
state_COMM_LOST = _sctp.getconstant("SCTP_COMM_LOST")
state_RESTART = _sctp.getconstant("SCTP_RESTART")
state_SHUTDOWN_COMP = _sctp.getconstant("SCTP_SHUTDOWN_COMP")
state_CANT_STR_ASSOC = _sctp.getconstant("SCTP_CANT_STR_ASSOC")
state_CANT_START_ASSOCIATION = _sctp.getconstant("SCTP_CANT_STR_ASSOC")
error_FAILED_THRESHOLD = _sctp.getconstant("SCTP_FAILED_THRESHOLD")
error_RECEIVED_SACK = _sctp.getconstant("SCTP_RECEIVED_SACK")
error_HEARTBEAT_SUCCESS = _sctp.getconstant("SCTP_HEARTBEAT_SUCCESS")
error_RESPONSE_TO_USER_REQ = _sctp.getconstant("SCTP_RESPONSE_TO_USER_REQ")
error_INTERNAL_ERROR = _sctp.getconstant("SCTP_INTERNAL_ERROR")
error_SHUTDOWN_GUARD_EXPIRES = _sctp.getconstant("SCTP_SHUTDOWN_GUARD_EXPIRES")
error_PEER_FAULTY = _sctp.getconstant("SCTP_PEER_FAULTY")
class paddr_change(notification):
"""
Peer address change notification. This event is received when a multihomed remote
peer changes the network interface in use.
The user should never need to instantiate this directly. This
notification will be received only if user subscribed to receive
that (see event_subscribe class for details).
"""
def __init__(self, values=None):
self.addr = ("",0)
self.state = 0
self.error = 0
self.assoc_id = 0
notification.__init__(self, values)
state_ADDR_AVAILABLE = _sctp.getconstant("SCTP_ADDR_AVAILABLE")
state_ADDR_UNREACHABLE = _sctp.getconstant("SCTP_ADDR_UNREACHABLE")
state_ADDR_REMOVED = _sctp.getconstant("SCTP_ADDR_REMOVED")
state_ADDR_ADDED = _sctp.getconstant("SCTP_ADDR_ADDED")
state_ADDR_MADE_PRIM = _sctp.getconstant("SCTP_ADDR_MADE_PRIM")
error_FAILED_THRESHOLD = assoc_change.error_FAILED_THRESHOLD
error_RECEIVED_SACK = assoc_change.error_RECEIVED_SACK
error_HEARTBEAT_SUCCESS = assoc_change.error_HEARTBEAT_SUCCESS
error_RESPONSE_TO_USER_REQ = assoc_change.error_RESPONSE_TO_USER_REQ
error_INTERNAL_ERROR = assoc_change.error_INTERNAL_ERROR
error_SHUTDOWN_GUARD_EXPIRES = assoc_change.error_SHUTDOWN_GUARD_EXPIRES
error_PEER_FAULTY = assoc_change.error_PEER_FAULTY
class remote_error(notification):
"""
Remote error notification. This is received when the remote application
explicitely sends a SCTP_REMOTE_ERROR notification. If the application
protocol does not use this feature, such messages don't need to be
handled.
It will NOT be received when an "actual", or low-level, error occurs.
For such errors, assoc_change() objects are passed instead.
The user should never need to instantiate this directly. This
notification will be received only if user subscribed to receive
that (see event_subscribe class for details).
"""
def __init__(self, values=None):
self.error = 0
self.assoc_id = 0
self.data = ""
notification.__init__(self, values)
class send_failed(notification):
"""
Send error notification. This is received when a particular message could not
be sent.
When an association fails, all pending messages will be returned as send_failed()s
*after* assoc_change() notification is reveived. Also, send_failed() is returned
when the message time-to-live expires (which is not a fatal error).
The "flag" attribute can have one of the flag_* class constants. More details about
the message, including the association ID, can be obtained inside the "info"
attribute, that is a sndrcvinfo() object. See sndrcvinfo class documentation for
more details.
The user should never need to instantiate this directly. This
notification will be received only if user subscribed to receive
that (see event_subscribe class for details).
"""
def __init__(self, values=None):
self.error = 0
self.assoc_id = 0
self.data = ""
notification.__init__(self, values)
self.info = sndrcvinfo(self._info)
del self._info
flag_DATA_UNSENT = _sctp.getconstant("SCTP_DATA_UNSENT")
flag_DATA_SENT = _sctp.getconstant("SCTP_DATA_SENT")
class shutdown_event(notification):
"""
Shutdown event. This event is received when an association goes
down. The only relevant attribute is assoc_id.
If user has subscribed to receive assoc_change notifications, it
will receive at least 2 messages (assoc_change and shutdown_event)
for an association shutdown. In addition, if the association was
down by some problem, more error notifications will be received
before this event.
The user should never need to instantiate this directly. This
notification will be received only if user subscribed to receive
that (see event_subscribe class for details).
"""
def __init__(self, values=None):
self.assoc_id = 0
notification.__init__(self, values)
class adaptation_event(notification):
"""
Adaption indication event. It signals that the remote peer requests
an specific adaptation layer.
In SCTP, you can pass an optional 32-bit metadata identifier
along each message, called "ppid". For protocols that actively use
this feature, it is like a subprotocol identifier, or "adaptation
layer". The message interpreter is chosen by this value. This is
mostly for telephony signaling-related protocols.
When receiving this event, the new messages should be sent with
the new requested ppid.
For protocols that use multiple ppids, or do not take advantage of
ppid at all, this notification is useless and can be ignored, or
even better, unsubscribed.
The user should never need to instantiate this directly. This
notification will be received only if user subscribed to receive
that (see event_subscribe class for details).
"""
def __init__(self, values=None):
self.adaptation_ind = 0
self.assoc_id = 0
notification.__init__(self, values)
class pdapi_event(notification):
"""
Partial delivery event. This event is received when a partial
delivery event occurs.
The user should never need to instantiate this directly. This
notification will be received only if user subscribed to receive
that (see event_subscribe class for details).
The indication can be one of the indication_* values.
"""
def __init__(self, values=None):
self.indication = 0
self.assoc_id = 0
notification.__init__(self, values)
indication_PD_ABORTED = _sctp.getconstant("SCTP_PARTIAL_DELIVERY_ABORTED")
indication_PARTIAL_DELIVERY_ABORTED = indication_PD_ABORTED
#################################################### NOTIFICATION FACTORY
notification_table = {
notification.type_ASSOC_CHANGE: assoc_change,
notification.type_PEER_ADDR_CHANGE: paddr_change,
notification.type_SEND_FAILED: send_failed,
notification.type_REMOTE_ERROR: remote_error,
notification.type_SHUTDOWN_EVENT: shutdown_event,
notification.type_PARTIAL_DELIVERY_EVENT: pdapi_event,
notification.type_ADAPTATION_INDICATION: adaptation_event,
}
def notification_factory(raw_notification):
"""
This function builds a notification based on a raw dictionary
received from the bottom-half (C language) of pysctp. The user
should never need to call this directly.
If the raw notification if of an unknown type, a "notification"
superclass object is returned instead, so the user has a chance
to test "type" and deal with that. A warning is sent to the
stderr. It can well happen if SCTP is extended and the user is
using an old version of PySCTP...
If sctpsocket.unexpected_event_raises_exception (default=False)
is set to True, an unknown even will raise an exception (it is
not raised by this function, but it seemed important to note it
here too).
"""
try:
num_type = raw_notification["type"]
except:
raise ValueError("Dictionary passed as parameter has no 'type' attribute")
if num_type not in notification_table:
# raw object since we do not know the type of notification
o = notification(raw_notification)
print("Warning: an unknown notification event (value %d) has arrived" % \
num_type, file=sys.stderr)
else:
o = notification_table[num_type](raw_notification)
return o
########### EVENT SUBSCRIBING CLASS
class event_subscribe(object):
"""
This class implements subproperties for sctpsocket.events "property".
Variables and methods:
autoflush: Boolean value that indicates if properties settings should be
immediately transmitted to kernel. Default = True.
flush(): Flushes the properties to the kernel. If autoflush is True, it will
be called every time a property is set.
clear(): Sets all properties to False, except data_io that is always set to
True. Guarantees that sctp_recv() will only return when an actual
data message is received.
When the class is insantiated, it gets the kernel default values. So, if the
application just want to change one or two properties from the implementation
default, it should not need to use clear().
Properties: Every property sets whether a event class will or not be received
via recvmsg() or sctp_recv().
data_io: refers to sndrcvinfo() (*)
association: refers to assoc_change() event
address: refers to paddr_change() event
send_failure: refers to send_failed() event
peer_error: refers to remote_error() event
shutdown: refers to shutdon_event()
partial_delivery: refers to pdapi_event()
adaptation_layer: refers to adaptation_event()
(*) sndrcvinfo is ALWAYS returned by sctp_recv() along with message data. The
data_io property just controls whether sndrcvinfo() contains useful data.
Convenience aliases for properties:
dataio = data_io
sndrcvinfo = data_io
sendfailure = send_failure
peererror = peer_error
partialdelivery = partial_delivery
adaptationlayer = adaptation_layer
"""
def flush(self):
"""
Flushes all event properties to the kernel.
"""
self.container._set_events(self.__dict__)
def __set_property(self, key, value):
self.__dict__[key] = value
if self.autoflush:
self.flush()
def __get_property(self, key):
return self.__dict__[key]
def get_adaptation_layer(self):
return self.__get_property("_adaptation_layer")
def get_partial_delivery(self):
return self.__get_property("_partial_delivery")
def get_shutdown(self):
return self.__get_property("_shutdown")
def get_peer_error(self):
return self.__get_property("_peer_error")
def get_send_failure(self):
return self.__get_property("_send_failure")
def get_address(self):
return self.__get_property("_address")
def get_association(self):
return self.__get_property("_association")
def get_data_io(self):
return self.__get_property("_data_io")
def set_adaptation_layer(self, value):
self.__set_property("_adaptation_layer", value)
def set_partial_delivery(self, value):
self.__set_property("_partial_delivery", value)
def set_shutdown(self, value):
self.__set_property("_shutdown", value)
def set_peer_error(self, value):
self.__set_property("_peer_error", value)
def set_send_failure(self, value):
self.__set_property("_send_failure", value)
def set_address(self, value):
self.__set_property("_address", value)
def set_association(self, value):
self.__set_property("_association", value)
def set_data_io(self, value):
self.__set_property("_data_io", value)
def clear(self):
"""
Sets all event properties do False, except data_io what is set to True.
Will NOT autoflush to kernel if autoflush variable is False.
"""
self._data_io = 1
self._association = 0
self._address = 0
self._send_failure = 0
self._peer_error = 0
self._shutdown = 0
self._partial_delivery = 0
self._adaptation_layer = 0
if self.autoflush:
self.flush()
def __init__(self, container):
self.autoflush = False
self.clear()
self.autoflush = True
self.container = container
self.__dict__.update(self.container._get_events())
# synonyms :)
data_io = property(get_data_io, set_data_io)
dataio = data_io
sndrcvinfo = data_io
association = property(get_association, set_association)
address = property(get_address, set_address)
send_failure = property(get_send_failure, set_send_failure)
sendfailure = send_failure
peer_error = property(get_peer_error, set_peer_error)
peererror = peer_error
shutdown = property(get_shutdown, set_shutdown)
partial_delivery = property(get_partial_delivery, set_partial_delivery)
partialdelivery = partial_delivery
adaptation_layer = property(get_adaptation_layer, set_adaptation_layer)
adaptationlayer = adaptation_layer
########## STRUCTURES EXCHANGED VIA set/getsockopt()
class rtoinfo(object):
"""
Retransmission Timeout object class. This object can be read from a SCTP socket
using the get_rtoinfo() method, and *written* to the socket using set_rtoinfo()
socket method.
Althrough the user could create a new rtoinfo() and fill it, it is better to get
the current RTO info, change whatever is necessary and send it back.
Relevant attributes:
assoc_id: the association ID where this info came from, or where this information
is going to be applied to. Ignored/unreliable for TCP-style sockets
because they hold only one association.
initial: Initial RTO in milisseconds
max: Maximum appliable RTO in milisseconds
min: Minimum appliable RTO in milisseconds.
"""
def __init__(self):
self.assoc_id = 0
self.initial = 0
self.max = 0
self.min = 0
class assocparams(object):
"""
Association Parameters object class. This object can be read from a SCTP socket
using the get_assocparams() socket method, and *written* to the socket using the
set_assocparams() socket method.
However, not all association properties are read-write. In the following list,
read-write parameters are marked (rw), and read-only are marked (ro). The read-only
behaviour is NOT enforced i.e. you can change an attribute in Python and it
will not "stick" at socket level :)
If a read-write attribute is passed as 0, it means "do not change it".
FROM TSVWG document: "The maximum number of retransmissions before an address is considered
unreachable is also tunable, but is address-specific, so it is covered in a separate option.
If an application attempts to set the value of the association maximum retransmission
parameter to more than the sum of all maximum retransmission parameters, setsockopt()
shall return an error."
Althrough the user could create a new object and fill it, it is better to get
the current parameters object, change whatever is necessary and send it back.
assoc_id: the association ID where this info came from, or where this information
is going to be applied to. Ignored/unreliable for TCP-style sockets
because they hold only one association.
assocmaxrxt(rw): Maximum retransmission attempts to create an association
number_peer_destinations(ro): Number of possible destinations a peer has
peer_rwnd(ro): Peer's current receiving window, subtracted by in-flight data.
local_rwnd(ro): Current local receiving window
cookie_life(rw): Cookie life in miliseconds (cookies are used while creating an association)
"""
def __init__(self):
self.assoc_id = 0
self.assocmaxrxt = 0
self.number_peer_destinations = 0
self.peer_rwnd = 0
self.local_rwnd = 0
self.cookie_life = 0
class paddrparams(object):
"""
Peer Address Parameters object class. This object can be read from a SCTP socket
using the get_paddrparms() method, and *written* to the socket using the
set_paddrparams() socket method.
When passing this object to set peer address parameters, attributes passed as
zero mean "do not change this one". This makes easier for the user to create a
new object, fill and sent it, but it is easier, and better, to get the current
parameters object, change whatever is necessary and send it back.
assoc_id: the association ID where this info came from, or where this information
is going to be applied to. Ignored/unreliable for TCP-style sockets
because they hold only one association.
sockaddr: the address this info came from, or where this information is going to
be applied to. It *is* important even for TCP-style sockets.
hbinterval: heartbeat interval in milisseconds
pathmaxrxt: maximum number of retransmissions, after that this peer address
is considered unreachable and another one will be tried.
WARNING: THE FOLLOWING ATTRIBUTES WILL BE READ/WRITABLE ONLY IF YOUR
UNDERLYING SCTP IMPLEMENTATION IS AT LEAST DRAFT 10-LEVEL.
pathmtu: Path MTU while using this peer address as path
sackdelay: when delayed Selective ACK is enabled, this value specifies the
delay in milisseconds, for this peer address.
flags: a bitmap with any subset of paddrparams.flags_* values active
test with Logical AND (& operand) instead of =
flags_HB_DISABLED: disable/disabled heartbeats for that peer
flags_HB_ENABLED: enable/enabled heartbeats
flags_SACKDELAY_*: the same song, for Selective ACK delay features
flags_PMTUD_*: the same song, but for the PMTU discovery feature.
For every feature, there are two different flags for ENABLE and DISABLE
because this allows to pass a zeroed flag which means "do not toggle
any feature". Note that *simultaneous* use of *_ENABLED and *_DISABLED
for the same feature will have undefined results!
"""
def __init__(self):
self.assoc_id = 0
self.sockaddr = ("", 0)
self.hbinterval = 0
self.pathmaxrxt = 0
self.pathmtu = 0;
self.sackdelay = 0;
self.flags = 0;
flags_HB_DISABLED = _sctp.getconstant("SPP_HB_DISABLED")
flags_HB_ENABLED = _sctp.getconstant("SPP_HB_ENABLED")
flags_PMTUD_DISABLED = _sctp.getconstant("SPP_PMTUD_DISABLED")
flags_PMTUD_ENABLED = _sctp.getconstant("SPP_PMTUD_ENABLED")
flags_SACKDELAY_DISABLED = _sctp.getconstant("SPP_SACKDELAY_DISABLED")
flags_SACKDELAY_ENABLED = _sctp.getconstant("SPP_SACKDELAY_ENABLED")
# synthatical sugar
flags_HB_DISABLE = flags_HB_DISABLED
flags_HB_ENABLE = flags_HB_ENABLED
flags_PMTUD_DISABLE = flags_PMTUD_DISABLED
flags_PMTUD_ENABLE = flags_PMTUD_ENABLED
flags_SACKDELAY_DISABLE = flags_SACKDELAY_DISABLED
flags_SACKDELAY_ENABLE = flags_SACKDELAY_ENABLED
class paddrinfo(object):
"""
Peer Address information object class. The user should never need to
instantiate this directly. It is received via get_paddrinfo() method call. It
is read-only, you cannot set anything by passing this object.
Relevant attributes:
assoc_id: the association ID that this object refers to. May not have an useful value
if the object came from a TCP-style socket that holds only 1 association.
sockaddr: address/port pair tuple that is the address being reported about
state: either state_INACTIVE or state_ACTIVE
cwnd: current congestion window size
srtt: current estimate of round-trip time
rto: current retransmission timeout
mtu: current MTU
"""
def __init__(self):
self.assoc_id = 0 # passed to getsockopt() (UDP)
self.sockaddr = ("", 0) # passed to getsockopt()
self.state = 0
self.cwnd = 0
self.srtt = 0
self.rto = 0
self.mtu = 0
state_INACTIVE = _sctp.getconstant("SCTP_INACTIVE")
state_ACTIVE = _sctp.getconstant("SCTP_ACTIVE")
class status(object):
"""
SCTP Association Status Report object class. The user should never need to
instantiate this directly. It is received via get_status() socket method call.
It is read-only, you cannot set anything using this object.
Relevant attributes:
assoc_id: the Association ID the information refers to. Should be ignored if
the object came from a TCP-style socket, which can hold only one association.
state: The state of association. May be one of the status.state_* constants.
WARNING: BOUND and LISTEN states are supported only on draft 10-level
implementations.
rwnd: Peer's current receiving window size.
unackdata: unACK'ed data chuncks (not bytes)
penddata: number of data chunks pending receipt
primary: paddrinfo() object containing information about primary peer address.
See paddrinfo docstring for specific details.
instrms: number of inbound streams. (The funnyish name was copied directly from
the C structure, we try to keep object attributes' names as near as possible
to the C API...)
outstrms: number of outbound streams
fragmentation_point: "The size at which SCTP fragmentation will occur." (tsvwg)
"""
def __init__(self):
self.assoc_id = 0 # passed by caller via getsockopt() [UDP]
self.state = 0
self.rwnd = 0
self.unackdata = 0
self.penddata = 0
self.instrms = 0
self.outstrms = 0
self.fragmentation_point = 0
self.primary = paddrinfo()
state_EMPTY = _sctp.getconstant("SCTP_EMPTY")
state_CLOSED = _sctp.getconstant("SCTP_CLOSED")
state_COOKIE_WAIT = _sctp.getconstant("SCTP_COOKIE_WAIT")
state_COOKIE_ECHOED = _sctp.getconstant("SCTP_COOKIE_ECHOED")
state_ESTABLISHED = _sctp.getconstant("SCTP_ESTABLISHED")
state_SHUTDOWN_PENDING = _sctp.getconstant("SCTP_SHUTDOWN_PENDING")
state_SHUTDOWN_SENT = _sctp.getconstant("SCTP_SHUTDOWN_SENT")
state_SHUTDOWN_RECEIVED = _sctp.getconstant("SCTP_SHUTDOWN_RECEIVED")
state_SHUTDOWN_ACK_SENT = _sctp.getconstant("SCTP_SHUTDOWN_ACK_SENT")
state_BOUND = _sctp.getconstant("SCTP_BOUND")
state_LISTEN = _sctp.getconstant("SCTP_LISTEN")
######### IMPLEMENTATION FEATURE LIST BITMAP
def features():
"""
Returns a bitmap of features possessed by the SCTP underlying
implementation. Individual bits can be tested against
HAVE_* constants.
These flags may not be completely trustable, even in the sense that
some feature may be available despite absence of flag. LK-SCTP 1.0.3 has
no HAVE_SCTP_PRSCTP macro, but Linux implements PR-SCTP extension
since kernel 2.6.10...
"""
flags = HAVE_SCTP | HAVE_KERNEL_SCTP
if _sctp.have_sctp_multibuf():
flags |= HAVE_SCTP_MULTIBUF
if _sctp.have_sctp_noconnect():
flags |= HAVE_SCTP_NOCONNECT
if _sctp.have_sctp_prsctp():
flags |= HAVE_SCTP_PRSCTP
if _sctp.have_sctp_addip():
flags |= HAVE_SCTP_ADDIP
if _sctp.have_sctp_setprimary():
flags |= HAVE_SCTP_CANSET_PRIMARY
if _sctp.have_sctp_sat_network():
flags |= HAVE_SCTP_SAT_NETWORK_CAPABILITY
return flags
#################### THE REAL THING :)
class sctpsocket(object):
"""
This is the base class for SCTP sockets. In general, the user will use sctpsocket_tcp()
and sctpsocket_udp(), althrough it can use directly this class because all required
functionality is, by design, implemented *here*. The subclasses are offered just for
user convenience.
This class does NOT inherit from python standard sockets. It CONTAINS a python socket
and DELEGATES unknown method calls to that socket. So, we expect that sctpsocket
objects can be used in most places where a regular socket is expected.
Main methods:
bindx: allows to bind to a set of network interfaces (standard bind() allows
bind to exactly one or then to all).
connectx: allows to connect by specifying a list of remote addresses.
(For those that do not know SCTP: SCTP allows "multihomed" associations, i.e.
connections over a set of peer addresses. If the primary address fails to
respond (e.g. due to a network failure) it falls back to a secondary address.
It is intended for automatic network redundancy.)
getpaddrs: Gets the list of remote peer addresses for an association
getladdrs: Gets the list of local addresses for an association
sctp_send: Sends a SCTP message. Allows to pass some SCTP-specific parameters.
If the specific parameters are not relevant, send() or sendto()
can also be used for SCTP.
sctp_recv: Receives a SCTP messages. Returns SCTP-specific metadata along
with the data. If the metadata is not relevant for the
application, recv()/recvfrom() and read() will also work.
peeloff: Detaches ("peels off") an association from an UDP-style socket.
accept: Overrides socket standard accept(), works the same way.
set_peer_primary: Sets the peer primary address
set_primary: Set the local primary address
In addition, socket methods like bind(), connect() close(), set/getsockopt()
should work as expected. We tried to implement or override just the socket
methods that have special behaviour in SCTP.
Delegation methods (use with care):
sock(): returns the contained Python standard socket.
__getattr__(): this method does the delegation magic, forwarding all
unknown attribute inquiries to the python socket.
Properties:
nodelay: If True, disables Nagle algorithm, which causes any message to be immediately
sent, potentially creating too much "tinygrams". Because of message-oriented
nature of SCTP, some implementations have it turned on by default, while TCP
is mandated to have it off by default.
adaptation: 32-bit value related to the "ppid" metadata field sent along each data
message. If this property is different than zero, the configured value
is sent via ADAPTION INDICATION event to the remote peer when a new
association is opened. This is intended to be used by telephony-related
protocols.
disable_fragments: if True, a message will never be fragmented in several datagrams.
It means that message must fit in the PMTU datagram. Default is
False for most implementations.
mappedv4: If True, all IPv4 addresses will be received as IPv6-mapped IPv4 addresses
(::ffff:0:0/96). The default is True. Otherwise, the application can receive
either pure IPv6 or IPv4 addresses.
maxseg: Maximum segment size i.e. the size of a message chunk inside a datagram,