exabgp.reactor.network.tcp   F
last analyzed

Complexity

Total Complexity 62

Size/Duplication

Total Lines 267
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 174
dl 0
loc 267
rs 3.44
c 0
b 0
f 0
wmc 62

10 Functions

Rating   Name   Duplication   Size   Complexity  
A TTL() 0 7 3
A nagle() 0 6 2
A asynchronous() 0 5 2
F MD5() 0 77 19
A MIN_TTL() 0 15 5
A bind() 0 8 4
B create() 0 17 6
A TTLv6() 0 6 3
B connect() 0 14 6
D ready() 0 30 12

How to fix   Complexity   

Complexity

Complex classes like exabgp.reactor.network.tcp often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# encoding: utf-8
2
"""
3
tcp.py
4
5
Created by Thomas Mangin on 2013-07-13.
6
Copyright (c) 2013-2017 Exa Networks. All rights reserved.
7
License: 3-clause BSD. (See the COPYRIGHT file)
8
"""
9
10
import re
11
import base64
12
import socket
13
import select
14
import platform
15
16
from struct import pack, calcsize
17
18
from exabgp.util.errstr import errstr
19
20
from exabgp.protocol.family import AFI
21
from exabgp.protocol.ip import IP
22
from exabgp.reactor.network.error import errno
23
from exabgp.reactor.network.error import error
24
25
from exabgp.reactor.network.error import NotConnected
26
from exabgp.reactor.network.error import BindingError
27
from exabgp.reactor.network.error import MD5Error
28
from exabgp.reactor.network.error import NagleError
29
from exabgp.reactor.network.error import TTLError
30
from exabgp.reactor.network.error import AsyncError
31
32
from exabgp.logger import log
33
34
35
def create(afi):
36
    try:
37
        if afi == AFI.ipv4:
38
            io = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
39
        if afi == AFI.ipv6:
40
            io = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP)
41
        try:
42
            io.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
0 ignored issues
show
introduced by
The variable io does not seem to be defined in case afi == AFI.ipv4 on line 37 is False. Are you sure this can never be the case?
Loading history...
43
        except (socket.error, AttributeError):
44
            pass
45
        try:
46
            io.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)  # pylint: disable=E1101
47
        except (socket.error, AttributeError):
48
            pass
49
    except socket.error:
50
        raise NotConnected('Could not create socket')
51
    return io
52
53
54
def bind(io, ip, afi):
55
    try:
56
        if afi == AFI.ipv4:
57
            io.bind((ip, 0))
58
        if afi == AFI.ipv6:
59
            io.bind((ip, 0, 0, 0))
60
    except socket.error as exc:
61
        raise BindingError('Could not bind to local ip %s - %s' % (ip, str(exc)))
62
63
64
def connect(io, ip, port, afi, md5):
65
    try:
66
        if afi == AFI.ipv4:
67
            io.connect((ip, port))
68
        if afi == AFI.ipv6:
69
            io.connect((ip, port, 0, 0))
70
    except socket.error as exc:
71
        if exc.errno == errno.EINPROGRESS:
72
            return
73
        if md5:
74
            raise NotConnected(
75
                'Could not connect to peer %s:%d, check your MD5 password (%s)' % (ip, port, errstr(exc))
76
            )
77
        raise NotConnected('Could not connect to peer %s:%d (%s)' % (ip, port, errstr(exc)))
78
79
80
# http://lxr.free-electrons.com/source/include/uapi/linux/tcp.h#L197
81
#
82
# #define TCP_MD5SIG_MAXKEYLEN    80
83
#
84
# struct tcp_md5sig {
85
# 	struct __kernel_sockaddr_storage tcpm_addr;     /* address associated */  128
86
# 	__u16   __tcpm_pad1;                            /* zero */                  2
87
# 	__u16   tcpm_keylen;                            /* key length */            2
88
# 	__u32   __tcpm_pad2;                            /* zero */                  4
89
# 	__u8    tcpm_key[TCP_MD5SIG_MAXKEYLEN];         /* key (binary) */         80
90
# }
91
#
92
# #define _K_SS_MAXSIZE   128
93
#
94
# #define _K_SS_ALIGNSIZE (__alignof__ (struct sockaddr *))
95
# /* Implementation specific desired alignment */
96
#
97
# typedef unsigned short __kernel_sa_family_t;
98
#
99
# struct __kernel_sockaddr_storage {
100
# 	__kernel_sa_family_t    ss_family;              /* address family */
101
# 	/* Following field(s) are implementation specific */
102
# 	char    __data[_K_SS_MAXSIZE - sizeof(unsigned short)];
103
# 	/* space to achieve desired size, */
104
# 	/* _SS_MAXSIZE value minus size of ss_family */
105
# } __attribute__ ((aligned(_K_SS_ALIGNSIZE)));   /* force desired alignment */
106
107
108
def MD5(io, ip, port, md5, md5_base64):
109
    platform_os = platform.system()
110
    if platform_os == 'FreeBSD':
111
        if md5:
112
            if md5 != 'kernel':
113
                raise MD5Error(
114
                    'FreeBSD requires that you set your MD5 key via ipsec.conf.\n'
115
                    'Something like:\n'
116
                    'flush;\n'
117
                    'add <local ip> <peer ip> tcp 0x1000 -A tcp-md5 "password";'
118
                )
119
            try:
120
                TCP_MD5SIG = 0x10
121
                io.setsockopt(socket.IPPROTO_TCP, TCP_MD5SIG, 1)
122
            except socket.error:
123
                raise MD5Error(
124
                    'FreeBSD requires that you rebuild your kernel to enable TCP MD5 Signatures:\n'
125
                    'options         IPSEC\n'
126
                    'options         TCP_SIGNATURE\n'
127
                    'device          crypto\n'
128
                )
129
    elif platform_os == 'Linux':
130
        try:
131
            md5_bytes = None
132
            if md5:
133
                if md5_base64 is True:
134
                    try:
135
                        md5_bytes = base64.b64decode(md5)
136
                    except TypeError:
137
                        raise MD5Error("Failed to decode base 64 encoded PSK")
138
                elif md5_base64 is None and not re.match('.*[^a-f0-9].*', md5):  # auto
139
                    options = [md5 + '==', md5 + '=', md5]
140
                    for md5 in options:
141
                        try:
142
                            md5_bytes = base64.b64decode(md5)
143
                            break
144
                        except TypeError:
145
                            pass
146
147
            # __kernel_sockaddr_storage
148
            n_af = IP.toaf(ip)
149
            n_addr = IP.pton(ip)
150
            n_port = socket.htons(port)
151
152
            # pack 'x' is padding, so we want the struct
153
            # Do not use '!' for the pack, the network (big) endian switch in
154
            # struct.pack is fighting against inet_pton and htons (note the n)
155
156
            if IP.toafi(ip) == AFI.ipv4:
157
                # SS_MAXSIZE is 128 but addr_family, port and ipaddr (8 bytes total) are written independently of the padding
158
                SS_MAXSIZE_PADDING = 128 - calcsize('HH4s')  # 8
159
                sockaddr = pack('HH4s%dx' % SS_MAXSIZE_PADDING, socket.AF_INET, n_port, n_addr)
160
            else:
161
                SS_MAXSIZE_PADDING = 128 - calcsize('HI16sI')  # 28
162
                SIN6_FLOWINFO = 0
163
                SIN6_SCOPE_ID = 0
164
                sockaddr = pack('HHI16sI%dx' % SS_MAXSIZE_PADDING, n_af, n_port, SIN6_FLOWINFO, n_addr, SIN6_SCOPE_ID)
165
166
            TCP_MD5SIG_MAXKEYLEN = 80
167
            TCP_MD5SIG = 14
168
169
            if md5_bytes:
170
                key = pack('2xH4x%ds' % TCP_MD5SIG_MAXKEYLEN, len(md5_bytes), md5_bytes)
171
                io.setsockopt(socket.IPPROTO_TCP, TCP_MD5SIG, sockaddr + key)
172
            elif md5:
173
                md5_bytes = bytes(md5, 'ascii')
174
                key = pack('2xH4x%ds' % TCP_MD5SIG_MAXKEYLEN, len(md5_bytes), md5_bytes)
175
                io.setsockopt(socket.IPPROTO_TCP, TCP_MD5SIG, sockaddr + key)
176
            # else:
177
            # 	key = pack('2xH4x%ds' % TCP_MD5SIG_MAXKEYLEN, 0, b'')
178
            # 	io.setsockopt(socket.IPPROTO_TCP, TCP_MD5SIG, sockaddr + key)
179
180
        except socket.error as exc:
181
            if exc.errno != errno.ENOENT:
182
                raise MD5Error('This linux machine does not support TCP_MD5SIG, you can not use MD5 (%s)' % errstr(exc))
183
    elif md5:
184
        raise MD5Error('ExaBGP has no MD5 support for %s' % platform_os)
185
186
187
def nagle(io, ip):
188
    try:
189
        # diable Nagle's algorithm (no grouping of packets)
190
        io.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
191
    except (socket.error, AttributeError):
192
        raise NagleError("Could not disable nagle's algorithm for %s" % ip)
193
194
195
def TTL(io, ip, ttl):
196
    # None (ttl-security unset) or zero (maximum TTL) is the same thing
197
    if ttl:
198
        try:
199
            io.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, ttl)
200
        except socket.error as exc:
201
            raise TTLError('This OS does not support IP_TTL (ttl-security) for %s (%s)' % (ip, errstr(exc)))
202
203
204
def TTLv6(io, ip, ttl):
205
    if ttl:
206
        try:
207
            io.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_UNICAST_HOPS, ttl)
208
        except socket.error as exc:
209
            raise TTLError('This OS does not support unicast_hops (ttl-security) for %s (%s)' % (ip, errstr(exc)))
210
211
212
def MIN_TTL(io, ip, ttl):
213
    # None (ttl-security unset) or zero (maximum TTL) is the same thing
214
    if ttl:
215
        try:
216
            io.setsockopt(socket.IPPROTO_IP, socket.IP_MINTTL, ttl)
217
        except socket.error as exc:
218
            raise TTLError('This OS does not support IP_MINTTL (ttl-security) for %s (%s)' % (ip, errstr(exc)))
219
        except AttributeError:
220
            pass
221
222
        try:
223
            io.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, ttl)
224
        except socket.error as exc:
225
            raise TTLError(
226
                'This OS does not support IP_MINTTL or IP_TTL (ttl-security) for %s (%s)' % (ip, errstr(exc))
227
            )
228
229
230
def asynchronous(io, ip):
231
    try:
232
        io.setblocking(0)
233
    except socket.error as exc:
234
        raise AsyncError('could not set socket non-blocking for %s (%s)' % (ip, errstr(exc)))
235
236
237
def ready(io):
238
    poller = select.poll()
239
    poller.register(io, select.POLLOUT | select.POLLNVAL | select.POLLERR)
240
241
    found = False
242
243
    while True:
244
        try:
245
            for _, event in poller.poll(0):
246
                if event & select.POLLOUT or event & select.POLLIN:
247
                    found = True
248
                elif event & select.POLLHUP:
249
                    yield False, 'could not connect, retrying'
250
                    return
251
                elif event & select.POLLERR or event & select.POLLNVAL:
252
                    yield False, 'connect attempt failed, issue with reading on the network, retrying'
253
                    return
254
255
            if found:
256
                err = io.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
257
                if not err:
258
                    yield True, 'connection established'
259
                    return
260
                elif err in error.block:
261
                    yield False, 'connect attempt failed, retrying, reason %s' % errno.errorcode[err]
262
                    return
263
            yield False, 'waiting for socket to become ready'
264
        except select.error as err:
265
            yield False, 'error, retrying %s' % str(err)
266
            return
267