Repository navigation
Expand file tree
/
Copy pathmodel_network_syscalls.py
More file actions
2177 lines (1545 loc) · 92 KB
/
Copy pathmodel_network_syscalls.py
File metadata and controls
2177 lines (1545 loc) · 92 KB
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
"""
<Program Name>
model_network_syscalls.py
<Author>
Eleni Gessiou
<Start Date>
20 June 2012
<Purpose>
Modeling network behavior of the POSIX API system calls.
It contains one function for every POSIX system call,
which simulates the behavior of the particular call.
It is also stateful: it keeps necessary state for every syscall
has been called so far.
I've implemented it based on Linux behavior.
<Notes>
I don't handle readv for now
Broad/Multicasting are not handled correctly. There are only some effort done.
"""
import sys
import socket
# source: http://code.google.com/p/ipaddr-py/
from ipaddr import IPAddress
import ip_matching
from trace_ordering import SyscallException
from trace_ordering import SyscallError
from trace_ordering import SyscallWarning
from trace_ordering import SyscallNotice
from trace_ordering import SyscallDontCare
##### GLOBALS
AF_INET = PF_INET = 2
PF_INET6 = AF_INET6 = 30
SOCK_STREAM = 1
SOCK_DGRAM = 2
IPPROTO_TCP = 6
IPPROTO_UDP = 17
SOL_IP = IPPROTO_IP = 0
SOL_UDP = 17
SOL_TCP = 6
SOL_SOCKET = 0xffff
SOL_IP = 1
IP_MULTICAST_IF = 32
IP_MULTICAST_TTL = 33
IP_MULTICAST_LOOP = 34
IP_ADD_MEMBERSHIP = 35
IP_DROP_MEMBERSHIP = 36
# More setsockopt / getsockopt
SO_DEBUG = 0x0001 # turn on debugging info recording
SO_ACCEPTCONN = 0x0002 # socket has had listen()
SO_REUSEADDR = 0x0004 # allow local address reuse
SO_KEEPALIVE = 0x0008 # keep connections alive
SO_DONTROUTE = 0x0010 # just use interface addresses
SO_BROADCAST = 0x0020 # permit sending of broadcast msgs
SO_USELOOPBACK = 0x0040 # bypass hardware when possible
SO_LINGER = 0x0080 # linger on close if data present (in ticks)
SO_OOBINLINE = 0x0100 # leave received OOB data in line
SO_REUSEPORT = 0x0200 # allow local address & port reuse
SO_TIMESTAMP = 0x0400 # timestamp received dgram traffic
SO_ACCEPTFILTER = 0x1000 # there is an accept filter
SO_DONTTRUNC = 0x2000 # APPLE: Retain unread data
SO_WANTMORE = 0x4000 # APPLE: Give hint when more data ready
SO_WANTOOBFLAG = 0x8000 # APPLE: Want OOB in MSG_FLAG on receive
SO_SNDBUF = 0x1001 # send buffer size
SO_RCVBUF = 0x1002 # receive buffer size
SO_SNDLOWAT = 0x1003 # send low-water mark
SO_RCVLOWAT = 0x1004 # receive low-water mark
SO_SNDTIMEO = 0x1005 # send timeout
SO_RCVTIMEO = 0x1006 # receive timeout
SO_ERROR = 0x1007 # get error status and clear
SO_TYPE = 0x1008 # get socket type
SO_NREAD = 0x1020 # APPLE: get 1st-packet byte count
SO_NKE = 0x1021 # APPLE: Install socket-level NKE
SO_NOSIGPIPE = 0x1022 # APPLE: No SIGPIPE on EPIPE
SO_NOADDRERR = 0x1023 # APPLE: Returns EADDRNOTAVAIL when src is not available anymore
SO_NWRITE = 0x1024 # APPLE: Get number of bytes currently in send socket buffer
SO_REUSESHAREUID = 0x1025 # APPLE: Allow reuse of port/socket by different userids
SO_NOTIFYCONFLICT = 0x1026 # APPLE: send notification if there is a bind on a port which is already in use
SO_UPCALLCLOSEWAIT = 0x1027 # APPLE: block on close until an upcall returns
SO_LINGER_SEC = 0x1080 # linger on close if data present (in seconds)
SO_RESTRICTIONS = 0x1081 # APPLE: deny inbound/outbound/both/flag set
SO_RESTRICT_DENYIN = 0x00000001 # flag for SO_RESTRICTIONS - deny inbound
SO_RESTRICT_DENYOUT = 0x00000002 # flag for SO_RESTRICTIONS - deny outbound
SO_RESTRICT_DENYSET = 0x80000000 # flag for SO_RESTRICTIONS - deny has been set
SO_RANDOMPORT = 0x1082 # APPLE: request local port randomization
SO_NP_EXTENSIONS = 0x1083 # To turn off some POSIX behavior
SO_LABEL = 0x1010 # socket's MAC label
SO_PEERLABEL = 0x1011 # socket's peer MAC label
SO_PRIORITY = 12
TCP_NODELAY = 0x01 # don't delay send to coalesce packets
TCP_MAXSEG = 0x02 # set maximum segment size
TCP_NOPUSH = 0x04 # don't push last block of write
TCP_NOOPT = 0x08 # don't use TCP options
TCP_KEEPALIVE = 0x10 # idle time used when SO_KEEPALIVE is enabled
TCP_CONNECTIONTIMEOUT = 0x20 # connection timeout
PERSIST_TIMEOUT = 0x40 # time after which a connection in persist timeout
# will terminate.
# see draft-ananth-tcpm-persist-02.txt
TCP_RXT_CONNDROPTIME = 0x80 # time after which tcp retransmissions will be
# stopped and the connection will be dropped
TCP_RXT_FINDROP = 0x100 # When set, a connection is dropped after 3 FINs
O_NONBLOCK = 04000
# recv flags
MSG_OOB = 0x1 # process out-of-band data
# The MSG_PEEK flag causes the receive operation to return data
# from the beginning of the receive queue without removing that data from the queue.
# Thus, a subsequent receive call will return the same data.
MSG_PEEK = 0x2 # peek at incoming message
MSG_WAITALL = 0x40 # wait for full request or error
CLONE_FILES = 0x00000400
# FCNTL
F_DUPFD = 0
F_SETFD = 2
F_SETFL = 4
# SHUTDOWN
SHUT_RD = 0
SHUT_WR = 1
SHUT_RDWR = 2
# broadcast ip addrs
broadcast_ip = ['255.255.255.255']
# TODO: even if MSG_OOB flag is set, if SO_OOBINLINE option is also set for the socket
# data are delivered in-line.
# Out-of-band data (called "urgent data" in TCP)
##### Global Structures keeping the necessary state for the model
# keep general info about sockets:
# sockets[(node_name, sockfd)] = {'domain': dom, 'type': typ, 'protocol': prot,
# 'local_ip': None, 'local_port': None, 'peer_ip': None, 'peer_port': None,
# 'state': state, 'nonblock': nonblock, 'sndbuf': (131070, 0), 'rcvbuf': (262140, 0)}
# Valid values are the following:
# domain/address family = AF_INET = PF_INET = 2 or PF_INET6 = AF_INET6 = 10
# type = SOCK_STREAM = 1 or SOCK_DGRAM = 2 or SOCK_RAW = 3
# if protocol != 0 then it shall specify a protocol that is supported by the address family
# if protocol == 0, the default protocol for this address family and type shall be used.
# for the 'state' field there are the following options : 'CREATED', 'BINDED', 'CONNECTED', 'LISTEN'
# the sndbuf and rcvbuf size is initiated with the default value Linix uses
sockets = dict()
# socket fds that are in use
active_sockets = set([])
# The following list of tuples differs on how it handles messages:
# - for TCP, we add the message sent in the buffer and in a receive call
# we pop the corresponding part of the buffer
# - for UDP, we add all the messages in a list during the send calls
# and we just mark them as read in receive calls. This way we eliminate
# false positives for multicasting applications where there are many receivers
tcp_tuples = []
udp_tuples = []
# keep a list of non-AF_INET sockets, because we don't care about them
non_internet_sockets = []
# connected sockets waiting to get accepted
pending_connections = dict()
# keeps information about possible network misbehavior, if poll/select returns timeout
poll_timeout = set([])
# http://linux.die.net/man/2/socket
def socket_syscall(node_name, args, ret):
# unpack the arguments and the return value
dom, typ, prot = args
sockfd, impl_errno = ret
# On error, -1 is returned, and errno is set appropriately
# in this case model doesn't update it's internal memory and let the caller
# know that from now on we won't care about this fd
if sockfd < 0:
raise SyscallDontCare("socket_syscall", 'MINUS_FD', "Socket returned a negative file descriptor.")
# AF_INET = PF_INET = 2, I care only for internet domain sockets
# PF_INET6 = AF_INET6 = 10
# handle both IPv4 and IPv6 implementations
if dom == PF_INET or dom == PF_INET6:
# SOCK_STREAM
if typ == SOCK_STREAM:
# if protocol == 0, the default protocol for this address family and type shall be used.
if prot == 0:
prot = IPPROTO_TCP
if prot != IPPROTO_TCP:
raise SyscallWarning("socket_syscall", 'EPROTOTYPE',
"[Application Error] The socket type is not supported by the protocol.")
nonblock = (0, 0)
# 2050 = it's SOCK_DGRAM with flag SOCK_NONBLOCK = O_NONBLOCK
elif typ == 2050 or typ == SOCK_DGRAM:
if typ == 2050:
nonblock = (1, 0)
else:
nonblock = (0, 0)
typ = SOCK_DGRAM
# if protocol == 0, the default protocol for this address family and type shall be used.
if prot == 0:
prot = IPPROTO_UDP
if prot != IPPROTO_UDP and prot != IPPROTO_IP:
raise SyscallWarning("socket_syscall", 'EPROTOTYPE',
"[Application Error] The socket type is not supported by the protocol.")
# the socket syscall creates new active sockets (and accept - see below)
active_sockets.add((node_name, sockfd))
# add an entry about this socket in the appropriate structure for future references
sockets[(node_name, sockfd)] = {'domain': dom, 'type': typ, 'protocol': prot,
'local_ip': None, 'local_port': None, 'peer_ip': None, 'peer_port': None,
'state': 'CREATED', 'nonblock': nonblock, 'sndbuf': (131070, 0), 'rcvbuf': (262140, 0)}
return (sockfd, None)
# TODO: make use of this list!!
# if socket is not AF_INET just add them in a list for elimination of future false positives
else:
non_internet_sockets.append((node_name, sockfd))
raise SyscallDontCare ("socket_syscall", 'DONT_CARE', "Socket is not of the AF_INET family. I'll pass..")
# http://linux.die.net/man/2/bind
def bind_syscall(node_name, args, ret):
# unpack the arguments and the return value
sock, addr, port = args
impl_ret, err = ret
# we only care about active sockets created
if (node_name, sock) not in active_sockets:
raise SyscallDontCare("bind_syscall", 'DONT_CARE', "The socket argument is not a valid file descriptor.")
# there are situations where the ip addr is different from the one stated in the CONFIG_FILE
# and we don't care about them
if addr not in broadcast_ip and ip_matching.addr_dont_care(addr, port):
active_sockets.discard((node_name, sock))
raise SyscallDontCare ("bind_syscall", 'DONT_CARE', "The local address is not one we care about.")
try:
ip = IPAddress(addr)
except Exception:
raise SyscallWarning("bind_syscall", 'NOT_AN_IP', addr + " is not an IP address.")
if err == 'EADDRINUSE':
raise SyscallNotice("bind_syscall", 'EADDRINUSE', "The address is already in use.")
already_bound = sockets[(node_name, sock)]['local_ip'] != None or sockets[(node_name, sock)]['peer_ip'] != None
if err is None:
if addr not in broadcast_ip and ip_matching.addr_dont_care(addr, 0):
print "[Warning] Trace", node_name, "is binding to", repr(addr),
print "but that IP is not listed in its host's configuration."
# Update state if implementation succeeded even if it was unexpected
# since there sin't really anything else we can do about it.
sockets[(node_name, sock)]['local_ip'] = addr
sockets[(node_name, sock)]['local_port'] = port
sockets[(node_name, sock)]['state'] = 'BINDED'
if already_bound:
raise SyscallWarning("bind_syscall", 'EINVAL', "[Application Error] The socket is already bound to an address")
# check if the socket shares some same features with other active sockets
# since the real execution hasn't return an error, we can throw a warning.
for socket in active_sockets:
if socket != (node_name, sock) and port != 0 and sockets[socket]['local_port'] == port:
if sockets[socket]['local_ip'] == addr:
message = "Another socket is already bound to that address and port"
elif addr == '0.0.0.0' or addr == '::':
message = "Binding to unspecified address when port is already in use"
elif sockets[socket]['local_ip'] == '0.0.0.0' or sockets[socket]['local_ip'] == '::':
message = "Another socket is already bound to an unspecified address and that port"
else:
continue
# TODO: Check inheritance and implementation result
# if another socket is using the same local_port but the reuseport option in both sockets
# is set, it's ok!
if sockets[socket].get('reuseport', (0, 0))[0] == 1 and \
sockets[(node_name, sock)].get('reuseport', (0, 0))[0] == 1:
continue
if sockets[socket].get('reuseaddr', (0, 0))[0] == 1:
raise SyscallWarning("bind_syscall", 'EADDRINUSE', "[Portability Issue] " + message +
", and the conflicting socket is using SO_REUSEADDR to allow rebinding, which is not portable")
raise SyscallWarning("bind_syscall", 'EADDRINUSE', "[Application Error] " + message + ".")
if err != None:
raise SyscallWarning("bind_syscall", 'UNEXPECTED_FAILURE', "Bind failed unexpectedly.")
return (0, None)
# http://linux.die.net/man/2/listen
def listen_syscall(node_name, args, ret):
# unpack the arguments and the return value
sock, log = args
impl_ret, err = ret
# we only care about active sockets created
if (node_name, sock) not in active_sockets:
raise SyscallDontCare("listen_syscall", 'DONT_CARE', "The socket argument is not a valid file descriptor.")
# check for every possible sistuation of error
if sockets[(node_name, sock)]['local_ip'] == None and sockets[(node_name, sock)]['state'] != 'BINDED':
raise SyscallWarning("listen_syscall", 'EDESTADDRREQ', \
"[Application Error] The socket is not bound to a local address, \
and the protocol does not support listening on an unbound socket.")
if sockets[(node_name, sock)]['state'] == 'CONNECTED':
raise SyscallWarning("listen_syscall", 'EINVAL',
"[Application Error] The socket is already connected.")
if sockets[(node_name, sock)]['protocol'] == IPPROTO_UDP or sockets[(node_name, sock)]['protocol'] == IPPROTO_IP:
raise SyscallWarning("listen_syscall", 'EOPNOTSUPP',
"[Application Error] The socket protocol does not support listen().")
if err is not None:
raise SyscallWarning("listen_syscall", 'UNEXPECTED_FAILURE', "Listen failed unexpectedly.")
# if everything is successful update the state of the socket and return success!
sockets[(node_name, sock)]['state'] = 'LISTEN'
return (0, None)
# http://linux.die.net/man/2/accept
def accept_syscall(node_name, args, ret):
# unpack the arguments and the return value
sock, peer_addr, peer_port = args
connected_socket, err = ret
if not isinstance(connected_socket, int):
raise SyscallDontCare("accept_syscall", 'DONT_CARE', "The connected socket is not an integer..")
if connected_socket == -1:
# handlind non or blocking sockets...
if err == 'EAGAIN' or err == 'EWOULDBLOCK':
if sockets[(node_name, sock)]['nonblock'] == (1, 1):
raise SyscallNotice("accept_syscall", 'EAGAIN/EWOULDBLOCK', "Resource temporarily unavailable.")
elif sockets[(node_name, sock)]['nonblock'] == (1, 0):
raise SyscallWarning("accept_syscall", 'COULD_HAVE_BLOCKED',
"[Portability Issue] The socket inherited the nonblocking flag from the server socket that returned it, \
which does not consistently happen across operating systems.")
else:
raise SyscallWarning("accept_syscall", 'EXPECTED_BLOCKING',
"[Network Misbehavior] Socket does not have the nonblocking flag set.")
else:
raise SyscallWarning("accept_syscall", 'UNEXPECTED_FAILURE', "Accept failed unexpectedly.")
if (node_name, sock) not in active_sockets:
raise SyscallDontCare("accept_syscall", 'DONT_CARE', "The socket argument is not a valid file descriptor.")
if peer_addr not in broadcast_ip and ip_matching.addr_dont_care(peer_addr, peer_port):
raise SyscallDontCare("accept_syscall", 'DONT_CARE', "The remote address is not one we care about.")
if (node_name, sock) not in pending_connections or not pending_connections[(node_name, sock)]:
raise SyscallError("accept_syscall", 'NO_PENDING_CONN', "There are no pending connections.")
for require_data_matches in [True, False]:
for require_established in [True, False]:
if (require_data_matches or require_established) and not ip_matching.ENABLE_TCP_DATA_MATCHING:
continue
# Search through the available connections until we find one that matches the one we want to accept
for index in xrange(len(pending_connections[(node_name, sock)])):
peer_name, peer_fd = pending_connections[(node_name, sock)][index]
if require_established and sockets[(peer_name, peer_fd)]['state'] != 'CONNECTED':
continue
if require_data_matches and not ip_matching.is_socket_match(
(peer_name, peer_fd), (node_name, connected_socket)):
continue
private_addr = (sockets[(peer_name, peer_fd)]['local_ip'], sockets[(peer_name, peer_fd)]['local_port'])
is_match, warnings = ip_matching.is_addr_match(peer_name, private_addr, node_name, (peer_addr, peer_port), False)
if is_match:
print "TCP server socket %s%d" % (node_name, sock),
if sockets[(node_name, sock)]['local_ip']:
print "(private address " + ip_matching.format_addr(sockets[(node_name, sock)]['local_ip'],
sockets[(node_name, sock)]['local_port']) + ")",
print "returning socket %s%d," % (node_name, connected_socket),
print "which is connected to socket %s%d" % (peer_name, peer_fd),
if peer_addr:
print "(public address " + ip_matching.format_addr(peer_addr, peer_port) + ")",
print
for warning in warnings:
print " * [Warning]", warning
if not require_data_matches and ip_matching.ENABLE_TCP_DATA_MATCHING:
print " * [Warning] The data sent/received by this pair of sockets doesn't match"
pending_connections[(node_name, sock)].pop(index)
# create a new socket with the same socket type protocol and address family as the specified socket,
# and allocate a new file descriptor for that socket.
active_sockets.add((node_name, connected_socket))
# the child fd inherits the parent
s_info = sockets[(node_name, sock)]
sockets[(node_name, connected_socket)] = s_info.copy()
# indicate that flag is inhereted from parent and not set explicitly
for k in sockets[(node_name, connected_socket)].iterkeys():
if k != 'domain' and k != 'type' and k != 'protocol' and k != 'local_ip' and k != 'local_port' and \
k != 'state' and k != 'flags' and k != 'peer_ip' and k != 'peer_port':
sockets[(node_name, connected_socket)][k] = (sockets[(node_name, connected_socket)][k][0], 0)
sockets[(node_name, connected_socket)]['peer_ip'] = peer_addr
sockets[(node_name, connected_socket)]['peer_port'] = peer_port
sockets[(node_name, connected_socket)]['state'] = 'CONNECTED'
for t in tcp_tuples:
if t['connected_fd'] == (peer_name, peer_fd):
t['accepting_fd'] = (node_name, connected_socket)
return (connected_socket, None)
raise SyscallError("accept_syscall", 'NO_PENDING_CONN', "There are no pending connections from that peer address.")
# TODO: handle getsockopt(49, SOL_SOCKET, SO_ERROR, [111], [4]) = 0
# http://linux.die.net/man/2/connect
def connect_syscall(node_name, args, ret):
# unpack the arguments and the return value
sock, addr, port = args
impl_ret, err = ret
if (node_name, sock) not in active_sockets:
raise SyscallDontCare ("connect_syscall", 'DONT_CARE', "The socket argument is not a valid file descriptor.")
try:
ip = IPAddress(addr)
except Exception:
raise SyscallWarning("connect_syscall", 'NOT_AN_IP', addr + " is not an IP address.")
# Check if socket is connection-mode(IPPROTO_TCP) or not (IPPROTO_UDP)
# if IPPROTO_UDP then set the socket's peer address, and no connection is made
if sockets[(node_name, sock)]['protocol'] == IPPROTO_UDP:
if err is None:
sockets[(node_name, sock)]['peer_ip'] = addr
sockets[(node_name, sock)]['peer_port'] = port
if addr not in broadcast_ip and ip_matching.addr_dont_care(addr, port):
raise SyscallDontCare ("connect_syscall", 'DONT_CARE', "The remote address is not one we care about.")
# If the socket protocol supports broadcast (eg UDP) and the specified address is a broadcast address
# for the socket protocol, sendto() shall fail if the SO_BROADCAST option is not set for the socket.
if addr in broadcast_ip and ('broadcast' not in sockets[(node_name, sock)] or sockets[(node_name, sock)]['broadcast'][0] != 1):
if 'broadcast' in sockets[(node_name, sock)] and sockets[(node_name, sock)]['broadcast'][0] != 1 and sockets[(node_name, sock)]['broadcast'][1] == 0:
print "[Warning] Socket has inherited SO_BROADCAST flag from parent and cannot send data. I will raise an error.."
raise SyscallWarning("connect_syscall", "EACCES", "[Application Error] Sockets's trying to connect to broadcast address without using the broadcast flag.")
if not IPAddress(addr).is_loopback and sockets[(node_name, sock)]['local_ip'] is not None and \
IPAddress(sockets[(node_name, sock)]['local_ip']).is_loopback:
raise SyscallWarning("connect_syscall", 'EINVAL', "UDP connect on socket bound to loopback to non-loopback IP.")
if err is not None:
raise SyscallWarning("connect_syscall", 'UNEXPECTED_FAILURE', "UDP connect failed unexpectedly.")
return (0, None)
# if IPPROTO_TCP then attempt to establish a connection to the peer address
elif sockets[(node_name, sock)]['protocol'] == IPPROTO_TCP:
# stream sockets may successfully connect() only once
if sockets[(node_name, sock)]['state'] == 'CONNECTED':
raise SyscallWarning("connect_syscall", 'EISCONN', "[Application Error] Socket is already connected.")
if addr not in broadcast_ip and ip_matching.addr_dont_care(addr, port):
active_sockets.discard((node_name, sock))
raise SyscallDontCare ("connect_syscall", 'DONT_CARE', "The remote address is not one we care about.")
# Don't process nonblocking connects, but make a note of the connection in progress
if err == 'EINPROGRESS' or err == 'EALREADY' or err == "EWOULDBLOCK":
if 'PENDING' in sockets[(node_name, sock)]['state']:
raise SyscallNotice("connect_syscall", 'EALREADY', "The socket is nonblocking and a previous \
connection attempt has not yet been completed. ")
else:
sockets[(node_name, sock)]['peer_ip'] = addr
sockets[(node_name, sock)]['peer_port'] = port
sockets[(node_name, sock)]['state'] = 'PENDING'
# This allows us to consider the socket if we are matching up data send/received.
if ip_matching.ENABLE_TCP_DATA_MATCHING and ip_matching.is_connected_socket((node_name, sock)):
try:
connect_syscall(node_name, args, (0, None))
sockets[(node_name, sock)]['state'] = 'PENDING/CONNECTED'
except SyscallException:
pass
# check if there is already a socket that shares common features and return the corresponding exception
for node, fd in active_sockets:
if node == node_name and fd != sock and sockets[(node, fd)]['state'] == 'PENDING':
raise SyscallNotice("connect_syscall", "OVERLAPPING_CONNECTS", \
"This trace already has one or more nonblocking connects pending, \
which may mean that the corresponding accepts are improperly matched.")
if not IPAddress(addr).is_loopback and sockets[(node_name, sock)]['local_ip'] is not None and \
IPAddress(sockets[(node_name, sock)]['local_ip']).is_loopback:
raise SyscallWarning("connect_syscall", 'EINVAL', "TCP connect on socket bound to loopback to non-loopback IP.")
if sockets[(node_name, sock)]['nonblock'] == (1, 1):
raise SyscallNotice("connect_syscall", 'EINPROGRESS',
"The socket is nonblocking and the connection cannot be completed immediately.")
elif sockets[(node_name, sock)]['nonblock'] == (1, 0):
raise SyscallWarning("connect_syscall", 'COULD_HAVE_BLOCKED',
"[Portability Issue] The socket inherited the nonblocking flag from the server socket that returned it, \
which does not consistently happen across operating systems.")
else:
raise SyscallWarning("connect_syscall", 'EXPECTED_BLOCKING',
"[Network Misbehavior] Socket does not have the nonblocking flag set.")
if sockets[(node_name, sock)]['state'] == 'PENDING/CONNECTED' and err is None:
sockets[(node_name, sock)]['state'] = 'CONNECTED'
if not IPAddress(addr).is_loopback and sockets[(node_name, sock)]['local_ip'] is not None and \
IPAddress(sockets[(node_name, sock)]['local_ip']).is_loopback:
raise SyscallWarning("connect_syscall", 'EINVAL', "TCP connect on socket bound to loopback to non-loopback IP.")
return (0, None)
if err is None:
# for every socket that matches to the criteria and is listening we add this pending connection.
# We have no way of knowing which is the correct peer if more than one have the same
# local_ip/local_port and they are listening
for peer_socket in active_sockets:
if sockets[peer_socket]['state'] != 'LISTEN':
continue
peer_addr = (sockets[peer_socket]['local_ip'], sockets[peer_socket]['local_port'])
is_match, warnings = ip_matching.is_addr_match(peer_socket[0], peer_addr, node_name, (addr, port), True)
if is_match:
sockets[(node_name, sock)]['peer_ip'] = addr
sockets[(node_name, sock)]['peer_port'] = port
# notify the user with important info for the connection
print "TCP socket %s%d" % (node_name, sock),
if sockets[(node_name, sock)]['local_ip']:
print "(private address " + ip_matching.format_addr(sockets[(node_name, sock)]['local_ip'],
sockets[(node_name, sock)]['local_port']) + ")",
print "connecting to socket %s%d" % peer_socket,
if addr:
print "(public address " + ip_matching.format_addr(addr, port) + ")",
print
for warning in warnings:
print " * [Warning]", warning
sockets[(node_name, sock)]['state'] = 'CONNECTED'
# create an entry for the connection with all the necessary info
connection_dict = {'accepting_fd': None, 'a_buffer': ("", 0, 0), 'a_shutdown': False,
'connected_fd': (node_name, sock), 'c_buffer': ("", 0, 0), 'c_shutdown': False}
tcp_tuples.append(connection_dict)
if peer_socket not in pending_connections:
pending_connections[peer_socket] = []
pending_connections[peer_socket].append((node_name, sock))
if not IPAddress(addr).is_loopback and sockets[(node_name, sock)]['local_ip'] is not None and \
IPAddress(sockets[(node_name, sock)]['local_ip']).is_loopback:
raise SyscallWarning("connect_syscall", 'EINVAL', "TCP connect on socket bound to loopback to non-loopback IP.")
return (0, None)
if err == None:
raise SyscallError("connect_syscall", 'ECONNREFUSED', "[Ambiguous Misbehavior] \
The target address was not listening for connections or refused the connection request.")
elif not IPAddress(addr).is_loopback and sockets[(node_name, sock)]['local_ip'] is not None and \
IPAddress(sockets[(node_name, sock)]['local_ip']).is_loopback:
raise SyscallWarning("connect_syscall", 'EINVAL', "TCP connect on socket bound to loopback to non-loopback IP.")
elif err == 'ECONNREFUSED' or err == 'EINVAL':
raise SyscallWarning("connect_syscall", 'ECONNREFUSED', "[Ambiguous Misbehavior] \
The target address was not listening for connections.")
else:
raise SyscallWarning("connect_syscall", 'UNEXPECTED_FAILURE', "TCP connect failed with an unexpected error.")
# write_syscall is the same with flags = 0
# and writev_syscall
# may be used only when the socket is in a connected state
# it should only be used for IPPROTO_TCP protocol
# http://linux.die.net/man/2/send
def send_syscall(node_name, args, ret):
sock, msg, flags = args
msg_len, err = ret
if (node_name, sock) not in active_sockets:
raise SyscallDontCare ("send_syscall", 'DONT_CARE', "The socket argument is not a valid file descriptor.")
# if it's a UDP socket, call sendto instead
if sockets[(node_name, sock)]['protocol'] != IPPROTO_TCP:
return sendto_syscall(node_name, (sock, msg, flags, '', 0), ret)
if 'PENDING' in sockets[(node_name, sock)]['state']:
if err == 'EAGAIN' or err == 'EWOULDBLOCK':
raise SyscallNotice("recv_syscall", 'EAGAIN/EWOULDBLOCK', "No data was sent.")
# Node didn't actually call connect again to see that the nonblocking connect succeeded
connect_syscall(node_name, (sock, sockets[(node_name, sock)]['peer_ip'],
sockets[(node_name, sock)]['peer_port']), ret)
if sockets[(node_name, sock)]['state'] != 'CONNECTED':
raise SyscallWarning("send_syscall", 'ENOTCONN', "[Application Error] The descriptor is not connected.")
# MSG_OOB with datagram socket is not allowed
if flags == MSG_OOB and (sockets[(node_name, sock)]['type'] != SOCK_STREAM or
'oobdata' not in sockets[(node_name, sock)] or sockets[(node_name, sock)]['oobdata'] != 1):
raise SyscallWarning("send_syscall", 'EOPNOTSUPP', "[Application Error] \
Some bit in the flags argument is inappropriate for the socket type.")
if flags == MSG_OOB and ('oobdata' not in sockets[(node_name, sock)] or sockets[(node_name, sock)]['oobdata'] != 1):
raise SyscallNotice("send_syscall", 'OOB_DATA', 'Sending out-of-band data..')
if msg_len == 0:
return (0, None)
elif msg_len > 0:
msg = msg.decode('string_escape')
if len(msg) > msg_len:
msg = msg[:msg_len]
elif err != 'EPIPE' and err != 'ECONNRESET':
if err == 'EAGAIN' or err == 'EWOULDBLOCK':
# handle all the non and blocking cases
if sockets[(node_name, sock)]['nonblock'] == (1, 1):
raise SyscallNotice("send_syscall", 'EAGAIN/EWOULDBLOCK', "No data was sent.")
elif sockets[(node_name, sock)]['nonblock'] == (1, 0):
raise SyscallWarning("send_syscall", 'COULD_HAVE_BLOCKED',
"[Portability Issue] The socket inherited the nonblocking flag from the server socket that returned it, \
which does not consistently happen across operating systems.")
else:
raise SyscallWarning("send_syscall", 'EXPECTED_BLOCKING',
"[Network Misbehavior] Socket does not have the nonblocking flag set.")
else:
raise SyscallWarning("send_syscall", 'UNEXPECTED_FAILURE', "Send failed unexpectedly.")
# among all the socket tuples, find the correct socket either in the connected of the accepting sockets.
for t in tcp_tuples:
# the socket can be found either in the 'accepting' or the 'connected' part of the tuple
# the only difference is who initiated the connection; whatever socket called connect will be in the
# 'connected' part while the socket that accepted the connection will be in the 'accepting' part
if (node_name, sock) == t['accepting_fd']:
if msg_len < 0:
if t['a_shutdown'] or t['connected_fd'] not in active_sockets:
raise SyscallWarning("send_syscall", 'EPIPE/ECONNRESET', "The connection has been closed.")
else:
raise SyscallError("send_syscall", 'CONN_NOT_CLOSED', "The connection has not been closed yet.")
# simulate sending the msg by adding the msg in the string buffer of the socket
if len(t['a_buffer'][0]) == t['a_buffer'][1]:
t['a_buffer'] = (t['a_buffer'][0] + msg, t['a_buffer'][1] + msg_len, t['a_buffer'][2] + msg_len)
else:
t['a_buffer'] = (t['a_buffer'][0], t['a_buffer'][1] + msg_len, t['a_buffer'][2] + msg_len)
if t['a_shutdown'] or t['connected_fd'] not in active_sockets:
raise SyscallWarning ("send_syscall", 'EPIPE/ECONNRESET', "The connection has been closed.")
if t['a_buffer'][1] > sockets[(node_name, sock)]['sndbuf'][0]:
raise SyscallWarning ("send_syscall", 'MSG_>_BUFSIZE', "[Application Error] \
Sending this message has caused data in the buffer to exceed the buffer size.")
# check network error due to poll/select seeing nothing
if t['connected_fd'] in poll_timeout:
poll_timeout.remove(t['connected_fd'])
raise SyscallWarning ("send_syscall", 'NETWORK_ERROR_%s%d' % t['connected_fd'],
"[Possible Network Misbehavior] Poll/Select returned nothing although there is 'data in the air'!!")
return (msg_len, None)
if (node_name, sock) == t['connected_fd']:
if msg_len < 0:
if t['c_shutdown'] or (t['accepting_fd'] is not None and t['accepting_fd'] not in active_sockets):
raise SyscallWarning("send_syscall", 'EPIPE/ECONNRESET', "The connection has been closed.")
else:
raise SyscallError("send_syscall", 'CONN_NOT_CLOSED', "The connection has not been closed yet.")
if len(t['c_buffer'][0]) == t['c_buffer'][1]:
t['c_buffer'] = (t['c_buffer'][0] + msg, t['c_buffer'][1] + msg_len, t['c_buffer'][2] + msg_len)
else:
t['c_buffer'] = (t['c_buffer'][0], t['c_buffer'][1] + msg_len, t['c_buffer'][2] + msg_len)
if t['c_shutdown'] or (t['accepting_fd'] is not None and t['accepting_fd'] not in active_sockets):
raise SyscallWarning ("send_syscall", 'EPIPE/ECONNRESET', "The connection has been closed.")
if t['c_buffer'][1] > sockets[(node_name, sock)]['sndbuf'][0]:
raise SyscallWarning ("send_syscall", 'MSG_>_BUFSIZE', "[Application Error] Sending this \
message has caused data in the buffer to exceed the buffer size.")
# check network error due to poll/select seeing nothing
if t['accepting_fd'] in poll_timeout:
poll_timeout.remove(t['accepting_fd'])
raise SyscallWarning ("send_syscall", 'NETWORK_ERROR_%s%d' % t['accepting_fd'], \
"[Possible Network Misbehavior] Poll/Select returned nothing although there is 'data in the air'!!")
return (msg_len, None)
raise SyscallError("send_syscall", 'MSGNOTSENT', "[Possible Network Misbehavior] The message was not sent, \
because no established connection found.")
# same: sendmsg_syscall with msg_len = len(msg)
# http://linux.die.net/man/2/sendto
def sendto_syscall(node_name, args, ret):
# unpack the arguments and the return value
sock, msg, flags, dest_addr, dest_port = args
msg_len, err = ret
if (node_name, sock) not in active_sockets:
raise SyscallDontCare ("sendto_syscall", 'DONT_CARE', "The socket argument is not a valid file descriptor.")
# if IP addr/port is not defined, use send instead
if sockets[(node_name, sock)]['protocol'] == IPPROTO_TCP:
send_syscall(node_name, (sock, msg, flags), ret)
if dest_addr:
raise SyscallWarning("sendto_syscall", "EISCONN", "[Application Error] \
The connection-mode socket was connected already but a recipient was specified.")
return (msg_len, None)
# If no IP and port are given, then try using the peer address we are connected to
if (dest_addr == '' and dest_port == 0) or (dest_addr == None and dest_port == None):
if sockets[(node_name, sock)]['peer_ip'] != None:
dest_addr = sockets[(node_name, sock)]['peer_ip']
dest_port = sockets[(node_name, sock)]['peer_port']
else:
raise SyscallWarning("sendto_syscall", "EDESTADDRREQ", "[Application Error] \
The socket is not connection-mode, and no peer address is set.")
try:
ip = IPAddress(dest_addr)
except Exception:
raise SyscallWarning("sendto_syscall", 'NOT_AN_IP', dest_addr + " is not an IP address.")
if dest_addr not in broadcast_ip and ip_matching.addr_dont_care(dest_addr, dest_port):
raise SyscallDontCare("sendto_syscall", 'DONT_CARE', "The remote address is not one we care about.")
warning = None
if msg_len > sockets[(node_name, sock)]['sndbuf'][0]:
warning = SyscallWarning("sendto_syscall", "MSG_>_BUFSIZE", "[Portability Issue] \
The socket is trying to send a message larger than its send buffer size, which is not portable.")
# If the socket protocol supports broadcast (eg UDP) and the specified address is a broadcast address
# for the socket protocol, sendto() shall fail if the SO_BROADCAST option is not set for the socket.
if dest_addr in broadcast_ip and ('broadcast' not in sockets[(node_name, sock)] or
sockets[(node_name, sock)]['broadcast'][0] != 1):
if 'broadcast' in sockets[(node_name, sock)] and sockets[(node_name, sock)]['broadcast'][0] != 1 \
and sockets[(node_name, sock)]['broadcast'][1] == 0:
print "[Warning] Socket has inherited SO_BROADCAST flag from parent and cannot send data. I will raise an error.."
warning = SyscallWarning("sendto_syscall", "EACCES", "[Application Error] Sockets's trying to broadcast a \
message without using the broadcast flag.")
if not IPAddress(dest_addr).is_loopback and sockets[(node_name, sock)]['local_ip'] is not None and \
IPAddress(sockets[(node_name, sock)]['local_ip']).is_loopback:
warning = SyscallWarning("sendto_syscall", 'EINVAL', "UDP send on socket bound to loopback to non-loopback IP.")
if msg_len == -1:
if warning:
raise warning
# handling non and blocking behavior
if err == 'EAGAIN' or err == 'EWOULDBLOCK':
if sockets[(node_name, sock)]['nonblock'] == (1, 1):
raise SyscallNotice("sendto_syscall", 'EAGAIN/EWOULDBLOCK', "No data was sent.")
elif sockets[(node_name, sock)]['nonblock'] == (1, 0):
raise SyscallWarning("sendto_syscall", 'COULD_HAVE_BLOCKED',
"[Portability Issue] The socket inherited the nonblocking flag from the server socket that returned it, \
which does not consistently happen across operating systems.")
else:
raise SyscallWarning("sendto_syscall", 'EXPECTED_BLOCKING',
"[Network Misbehavior] Socket does not have the nonblocking flag set.")
else:
raise SyscallWarning("sendto_syscall", 'UNEXPECTED_FAILURE', "Sendto failed unexpectedly.")
msg = msg.decode('string_escape')
# especially for sendmsg
if len(msg) > msg_len:
msg = msg[:msg_len]
# look for existing mappings we can use
for t in udp_tuples:
# the socket can be found either in the 'accepting' or the 'connected' part of the tuple
# the only difference is who initiated the communication; whatever socket tried to send first will be in the
# 'connected' part while the socket that received will be in the 'accepting' part
if (node_name, sock) in t['accepting_fd_list']:
if (t['connected_ip'] == dest_addr and t['connected_port'] == dest_port):
# simulate the behavior by adding the datagram sent in the list of datagrams
# also keep a counter with each datagram which shows how many times is received
# (this helps in case of the multicasting where the same datagram is being recwived
# multiple times)
if msg in t['a_dtgrams']:
t['a_dtgrams'][msg] = (t['a_dtgrams'][msg][0] + 1, t['a_dtgrams'][msg][1])
else:
t['a_dtgrams'][msg] = (1, 0)
# check network error due to poll/select seeing nothing
for p in t['connected_fd_list']:
if p in poll_timeout:
poll_timeout.remove(p)
raise SyscallWarning ("sendto_syscall", 'NETWORK_ERROR_%s%d' % p, "[Possible Network Misbehavior] \
Poll/Select returned nothing although there is 'data in the air'!!")
if warning:
raise warning
return (msg_len, None)
if (node_name, sock) in t['connected_fd_list']:
# simulate behavior.
if t['accepting_ip'] == dest_addr and t['accepting_port'] == dest_port:
if msg in t['c_dtgrams']:
t['c_dtgrams'][msg] = (t['c_dtgrams'][msg][0] + 1, t['c_dtgrams'][msg][1])
else:
t['c_dtgrams'][msg] = (1, 0)
# check network error due to poll/select seeing nothing
for p in t['accepting_fd_list']:
if p in poll_timeout:
poll_timeout.remove(p)
raise SyscallWarning ("sendto_syscall", 'NETWORK_ERROR_%s%d' % p,
"[Possible Network Misbehavior] Poll/Select returned nothing although there is 'data in the air'!!")
if warning:
raise warning
return (msg_len, None)
# notify the user with important info for the syscall
print "UDP socket %s%d" % (node_name, sock),
if sockets[(node_name, sock)]['local_ip']:
print "(private address " + ip_matching.format_addr(sockets[(node_name, sock)]['local_ip'],
sockets[(node_name, sock)]['local_port']) + ")",
print "is sending to public address " + ip_matching.format_addr(dest_addr, dest_port)
# if no data sent before this, we will have no record about this connection, so we have to create it
udp_tuples.append({'connected_fd_list': set([(node_name, sock)]),
'connected_ip': None, 'connected_port': None, 'c_dtgrams': {msg: (1, 0)},
'accepting_fd_list': set([]), 'accepting_ip': dest_addr, 'accepting_port': dest_port, 'a_dtgrams': dict()})
if warning:
raise warning
return (msg_len, None)
# http://linux.die.net/man/2/sendmsg
def sendmsg_syscall(node_name, args, ret):
sock, msg, dest_addr, dest_port, flags = args
sendto_args = sock, msg, flags, dest_addr, dest_port
# just call sendto instead
return sendto_syscall(node_name, sendto_args, ret)
# http://linux.die.net/man/2/write
def write_syscall(node_name, args, ret):
sock, msg = args
send_args = sock, msg, 0
# just call send instead
return send_syscall(node_name, send_args, ret)
# http://linux.die.net/man/2/writev
def writev_syscall(node_name, args, ret):
sock, msg, count = args
send_args = sock, msg, 0
# just call send instead
return send_syscall(node_name, send_args, ret)
# Implementation is not full.. I only model this for apache..
# I assume that it sends data to TCP sockets only. Otherwise I throw an error.