dpyproxy 2.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. dpyproxy/__init__.py +1 -0
  2. dpyproxy/__main__.py +4 -0
  3. dpyproxy-2.2.0.dist-info/METADATA +296 -0
  4. dpyproxy-2.2.0.dist-info/RECORD +59 -0
  5. dpyproxy-2.2.0.dist-info/WHEEL +4 -0
  6. dpyproxy-2.2.0.dist-info/entry_points.txt +2 -0
  7. dpyproxy-2.2.0.dist-info/licenses/LICENSE +201 -0
  8. enumerators/DnsProxyMode.py +39 -0
  9. enumerators/DnsResolvers.py +141 -0
  10. enumerators/HttpMethod.py +17 -0
  11. enumerators/Modules.py +38 -0
  12. enumerators/Port.py +11 -0
  13. enumerators/TcpProxyMode.py +17 -0
  14. enumerators/TlsVersion.py +21 -0
  15. enumerators/__init__.py +0 -0
  16. exception/DnsException.py +7 -0
  17. exception/ParserException.py +7 -0
  18. exception/__init__.py +0 -0
  19. main.py +94 -0
  20. modules/Module.py +45 -0
  21. modules/__init__.py +0 -0
  22. modules/dns/DnsModeDeterminator.py +358 -0
  23. modules/dns/DnsModule.py +113 -0
  24. modules/dns/DnsProxy.py +277 -0
  25. modules/dns/DnsResolver.py +18 -0
  26. modules/dns/__init__.py +0 -0
  27. modules/http/HttpModule.py +69 -0
  28. modules/http/HttpStrategies.py +849 -0
  29. modules/http/HttpUtils.py +94 -0
  30. modules/http/__init__.py +0 -0
  31. modules/tls/TcpProxy.py +106 -0
  32. modules/tls/TlsModule.py +173 -0
  33. modules/tls/__init__.py +0 -0
  34. network/DomainResolver.py +472 -0
  35. network/NetworkAddress.py +10 -0
  36. network/WrappedSocket.py +97 -0
  37. network/__init__.py +0 -0
  38. network/protocols/Dns.py +62 -0
  39. network/protocols/Http.py +109 -0
  40. network/protocols/Socksv4.py +70 -0
  41. network/protocols/Socksv5.py +106 -0
  42. network/protocols/Tls.py +113 -0
  43. network/protocols/__init__.py +0 -0
  44. network/tcp/Forwarder.py +203 -0
  45. network/tcp/TcpConnectionHandler.py +264 -0
  46. network/tcp/WrappedTcpSocket.py +30 -0
  47. network/tcp/__init__.py +0 -0
  48. network/udp/__init__.py +0 -0
  49. test/Sink.py +23 -0
  50. test/__init__.py +0 -0
  51. test/test_dns.py +98 -0
  52. test/test_http.py +57 -0
  53. test/test_tls.py +63 -0
  54. util/DnsAutoModeRuntimeMeasurement.py +62 -0
  55. util/DnsReachabilityCollector.py +160 -0
  56. util/DnsResolversDomainResolver.py +36 -0
  57. util/Util.py +62 -0
  58. util/__init__.py +0 -0
  59. util/constants.py +8 -0
@@ -0,0 +1,264 @@
1
+ import logging
2
+ import socket
3
+
4
+ from enumerators.TcpProxyMode import TcpProxyMode
5
+ from exception.ParserException import ParserException
6
+ from network.DomainResolver import DomainResolver
7
+ from network.NetworkAddress import NetworkAddress
8
+ from network.protocols.Dns import Dns
9
+ from network.protocols.Http import Http
10
+ from network.protocols.Socksv4 import Socksv4
11
+ from network.protocols.Socksv5 import Socksv5
12
+ from network.protocols.Tls import Tls
13
+ from network.tcp.Forwarder import Forwarder
14
+ from network.tcp.WrappedTcpSocket import WrappedTcpSocket
15
+ from util.constants import (
16
+ STANDARD_SOCKET_RECEIVE_SIZE,
17
+ TLS_1_0_HEADER,
18
+ TLS_1_1_HEADER,
19
+ TLS_1_2_HEADER,
20
+ SOCKSv4_HEADER,
21
+ SOCKSv5_HEADER,
22
+ )
23
+ from util.Util import is_valid_ipv4_address
24
+
25
+
26
+ class TcpConnectionHandler:
27
+ """
28
+ Handles a single client connection of the proxy server.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ connection_socket: WrappedTcpSocket,
34
+ address: NetworkAddress,
35
+ timeout: int,
36
+ record_version: str,
37
+ record_frag: bool,
38
+ tcp_frag: bool,
39
+ frag_size: int,
40
+ http_strategy: int,
41
+ http_smuggling_uncensored_url: str,
42
+ dns_server: NetworkAddress,
43
+ disabled_modes: list[TcpProxyMode],
44
+ forward_proxy: NetworkAddress,
45
+ forward_proxy_mode: TcpProxyMode,
46
+ forward_proxy_resolve_address: bool,
47
+ ):
48
+ self.connection_socket = connection_socket
49
+ self.address = address
50
+ self.proxy_mode = None
51
+ self.timeout = timeout
52
+ self.record_frag = record_frag
53
+ self.record_version = record_version
54
+ self.tcp_frag = tcp_frag
55
+ self.frag_size = frag_size
56
+ self.http_strategy = http_strategy
57
+ self.http_smuggling_uncensored_url = http_smuggling_uncensored_url
58
+ self.dns_server = dns_server
59
+ self.disabled_modes = disabled_modes
60
+ self.forward_proxy = forward_proxy
61
+ self.forward_proxy_mode = forward_proxy_mode
62
+ self.forward_proxy_resolve_address = forward_proxy_resolve_address
63
+
64
+ def handle(self):
65
+ """
66
+ Handles the connection to a single client.
67
+ :return: None
68
+ """
69
+ # determine proxy mode / message type
70
+ self.proxy_mode = self.get_proxy_mode()
71
+ if self.proxy_mode in self.disabled_modes:
72
+ self.info(f"Proxy mode {self.proxy_mode} is disabled. Stopping!")
73
+ return
74
+
75
+ # determine destination address
76
+ try:
77
+ final_server_address, http_version = self.get_destination_address()
78
+ except ParserException as e:
79
+ logging.warning(f"Could not parse initial proxy message with {e}. Stopping!")
80
+ return
81
+
82
+ # resolve domain if no forward proxy or the forward proxy needs a resolved address
83
+ if (not is_valid_ipv4_address(final_server_address.host)) and (
84
+ self.forward_proxy_resolve_address or self.forward_proxy is None
85
+ ):
86
+ if self.dns_server:
87
+ host = DomainResolver.resolve_udp_static_to_ip(
88
+ final_server_address.host, resolver=self.dns_server, timeout=self.timeout
89
+ )
90
+ else:
91
+ host = DomainResolver.resolve_local(final_server_address.host)
92
+ self.debug(f"Resolved {host} from {final_server_address.host}")
93
+ final_server_address.host = host
94
+
95
+ # set correct target
96
+ if self.forward_proxy is None:
97
+ target_address = (final_server_address.host, final_server_address.port)
98
+ else:
99
+ target_address = (self.forward_proxy.host, self.forward_proxy.port)
100
+ self.debug(f"Using forward proxy {target_address}")
101
+
102
+ # open socket to server
103
+ try:
104
+ server_socket = socket.create_connection(target_address)
105
+ except Exception as e:
106
+ self.info(f"Could not connect to server due to {e}.")
107
+ self.connection_socket.try_close()
108
+ return
109
+ if self.tcp_frag and self.record_frag:
110
+ # align record and tcp fragment size
111
+ server_socket = WrappedTcpSocket(self.timeout, server_socket, self.frag_size + 5)
112
+ elif self.tcp_frag:
113
+ server_socket = WrappedTcpSocket(self.timeout, server_socket, self.frag_size)
114
+ else:
115
+ server_socket = WrappedTcpSocket(self.timeout, server_socket)
116
+ logging.info(
117
+ f"Connected {final_server_address.host}:{final_server_address.port} "
118
+ f"to {target_address[0]}:{target_address[1]}"
119
+ )
120
+
121
+ self.send_proxy_answer(http_version, server_socket)
122
+
123
+ if self.forward_proxy is not None:
124
+ self.connect_forward_proxy(server_socket, final_server_address, http_version)
125
+
126
+ # start proxying
127
+ Forwarder(
128
+ self.connection_socket,
129
+ self.address.__str__(),
130
+ server_socket,
131
+ f"{target_address[0]}:{target_address[1]}",
132
+ self.record_frag,
133
+ self.record_version,
134
+ self.frag_size,
135
+ self.http_strategy,
136
+ self.http_smuggling_uncensored_url,
137
+ ).start()
138
+
139
+ def get_proxy_mode(self) -> TcpProxyMode:
140
+ """
141
+ Determines the mode of the proxy based on the first client message
142
+ """
143
+ header = self.connection_socket.peek(3)
144
+
145
+ if header.startswith(TLS_1_0_HEADER) or header.startswith(TLS_1_1_HEADER) or header.startswith(TLS_1_2_HEADER):
146
+ self.debug("Determined SNI Proxy Request")
147
+ return TcpProxyMode.SNI
148
+
149
+ if header.startswith(SOCKSv4_HEADER):
150
+ self.debug("Determined SOCKSv4 Proxy Request")
151
+ return TcpProxyMode.SOCKSv4
152
+
153
+ if header.startswith(SOCKSv5_HEADER):
154
+ self.debug("Determined SOCKSv5 Proxy Request")
155
+ return TcpProxyMode.SOCKSv5
156
+
157
+ try:
158
+ ascii_decoded_header = header.decode("ASCII")
159
+ except UnicodeDecodeError:
160
+ raise ParserException(f"Could not determine message type of message {header}")
161
+ else:
162
+ if ascii_decoded_header.upper().startswith("CON"):
163
+ self.debug("Determined HTTPS Proxy Request")
164
+ return TcpProxyMode.HTTPS
165
+ else:
166
+ # assume we have http
167
+ self.debug("Determined HTTP Proxy Request")
168
+ return TcpProxyMode.HTTP
169
+
170
+ def get_destination_address(self) -> (NetworkAddress, str):
171
+ """
172
+ Reads a proxy destination address and returns the host and port of the destination.
173
+ :return: Host, port, and optional http version of the destination server
174
+ """
175
+ http_version = None
176
+ if self.proxy_mode == TcpProxyMode.HTTP:
177
+ host, port, http_version = Http.read_http_get(self.connection_socket)
178
+ self.debug(f"Read host {host} and port {port} from HTTP GET")
179
+ elif self.proxy_mode == TcpProxyMode.HTTPS:
180
+ host, port, http_version = Http.read_http_connect(self.connection_socket)
181
+ self.debug(f"Read host {host} and port {port} from HTTP CONNECT")
182
+ elif self.proxy_mode == TcpProxyMode.SNI:
183
+ host, port = Tls.read_sni(self.connection_socket, self.timeout), 443
184
+ self.debug(f"Read host {host} and port {port} from SNI")
185
+ elif self.proxy_mode == TcpProxyMode.SOCKSv4:
186
+ host, port = Socksv4.read_socks4(self.connection_socket)
187
+ self.debug(f"Read host {host} and port {port} from SOCKSv4")
188
+ elif self.proxy_mode == TcpProxyMode.SOCKSv5:
189
+ host, port = Socksv5.read_socks5(self.connection_socket)
190
+ self.debug(f"Read host {host} and port {port} from SOCKSv5")
191
+ elif self.proxy_mode == TcpProxyMode.DNS:
192
+ host, port = Dns.read_dns(self.connection_socket)
193
+ self.debug(f"Read host {host} and port {port} from DNS")
194
+ else:
195
+ raise ParserException("Unknown proxy type")
196
+ return NetworkAddress(host, port), http_version
197
+
198
+ def send_proxy_answer(self, http_version: str, server_socket: WrappedTcpSocket):
199
+ if self.proxy_mode == TcpProxyMode.HTTPS:
200
+ # answer with 200 OK
201
+ self.connection_socket.send(Http.http_200_ok(http_version))
202
+ elif self.proxy_mode == TcpProxyMode.SOCKSv4:
203
+ # answer with Socksv4 okay
204
+ self.connection_socket.send(Socksv4.socks4_ok())
205
+ elif self.proxy_mode == TcpProxyMode.SOCKSv5:
206
+ # answer with Socksv5 okay
207
+ self.connection_socket.send(Socksv5.socks5_ok(server_socket))
208
+
209
+ def connect_forward_proxy(
210
+ self, server_socket: WrappedTcpSocket, final_server_address: NetworkAddress, http_version: str
211
+ ):
212
+ try:
213
+ # send proxy messages if necessary
214
+ if self.forward_proxy_mode == TcpProxyMode.HTTPS:
215
+ server_socket.send(Http.connect_message(final_server_address, http_version))
216
+ self.debug("Sent HTTP CONNECT to forward proxy")
217
+ # receive HTTP 200 OK
218
+ answer = server_socket.recv(STANDARD_SOCKET_RECEIVE_SIZE)
219
+ if not answer.upper().startswith(Http.http_200_ok(http_version)):
220
+ raise ParserException(f"Forward proxy rejected the connection with {answer}")
221
+
222
+ elif self.forward_proxy == TcpProxyMode.SOCKSv4:
223
+ server_socket.send(Socksv4.socks4_request(final_server_address))
224
+ self.debug("Sent SOCKSv4 to forward proxy")
225
+ # receive SOCKSv4 OK
226
+ answer = server_socket.recv(STANDARD_SOCKET_RECEIVE_SIZE)
227
+ if not answer.upper().startswith(Socksv4.socks4_ok()) and len(answer) != 8:
228
+ raise ParserException(f"Forward proxy rejected the connection with {answer}")
229
+
230
+ elif self.forward_proxy == TcpProxyMode.SOCKSv5:
231
+ server_socket.send(Socksv5.socks5_auth_methods())
232
+ self.debug("Sent SOCKSv5 auth methods")
233
+ answer = server_socket.recv(2)
234
+ if answer == b"\x05\xff":
235
+ raise ParserException("Forward proxy does not support no auth")
236
+ if answer != b"\x05\x00":
237
+ raise ParserException(f"Forward proxy rejected the connection with {answer}")
238
+ server_socket.send(Socksv5.socks5_request(final_server_address))
239
+ self.debug("Sent SOCKSv5 to forward proxy")
240
+ # receive SOCKSv5 OK
241
+ answer = server_socket.recv(STANDARD_SOCKET_RECEIVE_SIZE)
242
+ if not answer.upper().startswith(Socksv5.socks5_ok(server_socket)):
243
+ self.debug(f"Forward proxy rejected the connection with {answer}")
244
+ except Exception:
245
+ self.debug("Could not send proxy message")
246
+ self.connection_socket.try_close()
247
+ logging.info("Closed connections")
248
+ return
249
+
250
+ # LOGGER utility functions
251
+ def _logger_string(self, message: str) -> str:
252
+ return f"{self.address.host}:{self.address.port}: {message}"
253
+
254
+ def debug(self, message: str):
255
+ logging.debug(self._logger_string(message))
256
+
257
+ def warn(self, message: str):
258
+ logging.warning(self._logger_string(message))
259
+
260
+ def error(self, message: str):
261
+ logging.error(self._logger_string(message))
262
+
263
+ def info(self, message: str):
264
+ logging.info(self._logger_string(message))
@@ -0,0 +1,30 @@
1
+ import socket
2
+
3
+ from network.WrappedSocket import WrappedSocket
4
+
5
+
6
+ class WrappedTcpSocket(WrappedSocket):
7
+ """
8
+ Wraps a socket with useful utility functions.
9
+ """
10
+
11
+ def __init__(self, timeout: int, _socket: socket.socket, tcp_frag_size=0):
12
+ self.tcp_frag_size = tcp_frag_size
13
+ super().__init__(timeout, _socket)
14
+
15
+ def send(self, data: bytes, *args, **kwargs) -> int:
16
+ """
17
+ Wraps send() of the wrapped socket. Split into tcp fragments if given as value.
18
+ :return: Return value of the wrapped socket's send method
19
+ """
20
+ if self.tcp_frag_size <= 0:
21
+ return self.socket.send(data, *args, **kwargs)
22
+ else:
23
+ # split into fragments and send each separately
24
+ fragments = (data[i : i + self.tcp_frag_size] for i in range(0, len(data), self.tcp_frag_size))
25
+ total_sent = 0
26
+ for fragment in fragments:
27
+ total_sent += self.socket.send(fragment, *args, **kwargs)
28
+ self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 0)
29
+ self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
30
+ return total_sent
File without changes
File without changes
test/Sink.py ADDED
@@ -0,0 +1,23 @@
1
+ import socket
2
+
3
+
4
+ class Sink:
5
+ def __init__(self, ip: str, port: int):
6
+ self.socket = None
7
+ self.ip = ip
8
+ self.port = port
9
+
10
+ def start(self):
11
+ server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
12
+ server.bind((self.ip, self.port))
13
+ server.settimeout(5)
14
+ server.listen()
15
+ self.socket = server
16
+
17
+ def close(self):
18
+ self.socket.close()
19
+
20
+ def receive_message(self):
21
+ conn, addr = self.socket.accept()
22
+ with conn:
23
+ return conn.recv(65535)
test/__init__.py ADDED
File without changes
test/test_dns.py ADDED
@@ -0,0 +1,98 @@
1
+ from modules.dns.DnsModeDeterminator import DnsModeDeterminator
2
+
3
+ IP_RANGES = [
4
+ "8.8.4.0/24",
5
+ "8.8.8.0/24",
6
+ "8.34.208.0/20",
7
+ "8.35.192.0/20",
8
+ "23.236.48.0/20",
9
+ "23.251.128.0/19",
10
+ "34.0.0.0/15",
11
+ "34.2.0.0/16",
12
+ "34.3.0.0/23",
13
+ "34.3.3.0/24",
14
+ "34.3.4.0/24",
15
+ "34.3.8.0/21",
16
+ "34.3.16.0/20",
17
+ "34.3.32.0/19",
18
+ "34.3.64.0/18",
19
+ "34.4.0.0/14",
20
+ "34.8.0.0/13",
21
+ "34.16.0.0/12",
22
+ "34.32.0.0/11",
23
+ "34.64.0.0/10",
24
+ "34.128.0.0/10",
25
+ "35.184.0.0/13",
26
+ "35.192.0.0/14",
27
+ "35.196.0.0/15",
28
+ "35.198.0.0/16",
29
+ "35.199.0.0/17",
30
+ "35.199.128.0/18",
31
+ "35.200.0.0/13",
32
+ "35.208.0.0/12",
33
+ "35.224.0.0/12",
34
+ "35.240.0.0/13",
35
+ "57.140.192.0/18",
36
+ "64.15.112.0/20",
37
+ "64.233.160.0/19",
38
+ "66.22.228.0/23",
39
+ "66.102.0.0/20",
40
+ "66.249.64.0/19",
41
+ "70.32.128.0/19",
42
+ "72.14.192.0/18",
43
+ "74.114.24.0/21",
44
+ "74.125.0.0/16",
45
+ "104.154.0.0/15",
46
+ "104.196.0.0/14",
47
+ "104.237.160.0/19",
48
+ "107.167.160.0/19",
49
+ "107.178.192.0/18",
50
+ "108.59.80.0/20",
51
+ "108.170.192.0/18",
52
+ "108.177.0.0/17",
53
+ "130.211.0.0/16",
54
+ "136.22.160.0/20",
55
+ "136.22.176.0/21",
56
+ "136.22.184.0/23",
57
+ "136.22.186.0/24",
58
+ "136.124.0.0/15",
59
+ "142.250.0.0/15",
60
+ "146.148.0.0/17",
61
+ "152.65.208.0/22",
62
+ "152.65.214.0/23",
63
+ "152.65.218.0/23",
64
+ "152.65.222.0/23",
65
+ "152.65.224.0/19",
66
+ "162.120.128.0/17",
67
+ "162.216.148.0/22",
68
+ "162.222.176.0/21",
69
+ "172.110.32.0/21",
70
+ "172.217.0.0/16",
71
+ "172.253.0.0/16",
72
+ "173.194.0.0/16",
73
+ "173.255.112.0/20",
74
+ "192.104.160.0/23",
75
+ "192.158.28.0/22",
76
+ "192.178.0.0/15",
77
+ "193.186.4.0/24",
78
+ "199.36.154.0/23",
79
+ "199.36.156.0/24",
80
+ "199.192.112.0/22",
81
+ "199.223.232.0/21",
82
+ "207.223.160.0/20",
83
+ "208.65.152.0/22",
84
+ "208.68.108.0/22",
85
+ "208.81.188.0/22",
86
+ "208.117.224.0/19",
87
+ "209.85.128.0/17",
88
+ "216.58.192.0/19",
89
+ "216.73.80.0/20",
90
+ "216.239.32.0/19",
91
+ "216.252.220.0/22",
92
+ ]
93
+ DOMAIN = "www.google.dj"
94
+
95
+
96
+ def test_auto_mode_works():
97
+ resolver = DnsModeDeterminator(5, DOMAIN, IP_RANGES, False).generate_working_resolver().__next__()
98
+ assert resolver is not None
test/test_http.py ADDED
@@ -0,0 +1,57 @@
1
+ import socket
2
+ import threading
3
+ import time
4
+
5
+ import pytest
6
+
7
+ from modules.tls.TcpProxy import TcpProxy
8
+ from network.NetworkAddress import NetworkAddress
9
+ from test.Sink import Sink
10
+
11
+ NETWORK_ADDRESS = NetworkAddress("127.0.0.1", 8090)
12
+ CONNECT_DATA = "CONNECT 127.0.0.1:8091 HTTP/1.1\r\n\r\n"
13
+ HTTP_REQUEST = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
14
+
15
+
16
+ @pytest.fixture(scope="module")
17
+ def setup_sink():
18
+ sink = Sink("127.0.0.1", 8091)
19
+ sink.start()
20
+ yield sink
21
+ sink.close()
22
+
23
+
24
+ def test_basic_manipulation(setup_sink):
25
+ data = run_server(TcpProxy(address=NETWORK_ADDRESS, http_strategy=2), setup_sink)
26
+ assert data.decode() == "GET / OPTIONS\r\nHost: example.com\r\n\r\n"
27
+
28
+
29
+ def test_smuggling_manipulation(setup_sink):
30
+ data = run_server(
31
+ TcpProxy(address=NETWORK_ADDRESS, http_strategy=101, http_smuggling_uncensored_url="https://www.gov.cn/"),
32
+ setup_sink,
33
+ )
34
+ assert (
35
+ data.decode()
36
+ == "GET / HTTP/1.1\r\nHost: www.gov.cn\r\nContent-Length:: 42\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n"
37
+ "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
38
+ )
39
+
40
+
41
+ def run_server(proxy: TcpProxy, sink: Sink) -> bytes:
42
+ """
43
+ Runs the test and return the bytes sent from the proxy to the sink server.
44
+ """
45
+ thread = threading.Thread(target=proxy.start)
46
+ thread.start()
47
+ time.sleep(1)
48
+
49
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
50
+ s.connect(("127.0.0.1", 8090))
51
+ s.sendall(CONNECT_DATA.encode())
52
+ time.sleep(1)
53
+ s.sendall(HTTP_REQUEST.encode())
54
+ ret = sink.receive_message()
55
+ proxy.server.close()
56
+ thread.join()
57
+ return ret
test/test_tls.py ADDED
@@ -0,0 +1,63 @@
1
+ import socket
2
+ import threading
3
+ import time
4
+
5
+ import pytest
6
+
7
+ from enumerators.TlsVersion import TlsVersion
8
+ from modules.tls.TcpProxy import TcpProxy
9
+ from network.NetworkAddress import NetworkAddress
10
+ from test.Sink import Sink
11
+
12
+ NETWORK_ADDRESS = NetworkAddress("127.0.0.1", 8080)
13
+ CONNECT_DATA = "CONNECT 127.0.0.1:8081 HTTP/1.1\r\n\r\n"
14
+ TLS_DATA = "16030106210100061d0303b3d78ad49b3066c9b41f466f2b4226bc872ce9b9a3cfc2bb3f58532f9dc2034320bd27d7e698e64187429fb27c25d956020efb22139d2a580f20d84bb19c9648a500461302130313011304c02cc030cca9cca8c0adc02bc02fc0acc023c027c00ac014c009c013009dc09d009cc09c003d003c0035002f009fccaac09f009ec09e006b0067003900330100058eff010001000000000f000d00000a676f6f676c652e636f6d000b000403000102000a001c001a11ec11eb11ed001d0017001e00190018010001010102010301040010000e000c02683208687474702f312e31001600000017000000310000000d00280026090409050906040305030603080708080809080a080b08040805080604010501060103030301002b00050403040303002d00020101003304ea04e811ec04c09f52732ca78d99c9a447fc7ae8e47113f6b886d23b0fe61a0b77680ff30dda340ce80733b159a30a731726ca18a793677c30acf865b60dc669e260366de44be3c14fed8a176980ba5f0b00dda0634f8b8ea387a2b8b514a5b613c8e81289993c750a97a07996b858aef8540178156ba8822a7aa273e81366cce35cfd6313543b4565bba053566494379749e91fa981339a96a043f72402e0403f497aa3d66a382c318677acda56874d72020982227d9822f0e70fe6450798f407aca14ea168331a6199ed49aca49602bfaa785c2787270248beb176dbe046f6f86ca0c9ad42b31e32fc8f091c9217808f160991ae158f7a3a9c02b7938e902ab4c1ab355cb9b803873815a9e86b43c944a3d6b593b83346aff7504622bd617a648da5500c3a85a6076d0bd4272d3767ee5cb3749c08d75a6542f45834ec80bee40277671225e02900fdb6571459d8d7498d8b0e852738a07a3508146066793f87f707115112f74a1c33610f070842f2bbbe3fd3b3c7f089d4249f48883d20c29faa6c852ab3801082614309720df314827c534e66b091302d63319c6f3b91d999627d3c8e56247148103f4f2b19dab1422b192bc594217f634cc5497edf80ab05674cba251f4d0b73565320ad111765727f2223891925a94731afb8bb6c6ad4931df3732a93abb227328557c01d6996858995df8194a6f67a34515446c54439d945c4fb11133601b6d7ae84d70ae83684d7568a0ddb5e0b884b26e9c9fda8c15e9284182a5291c61c1fc0a2afe2accf8429fdc2b077c771ab6b330e915103646b5b152bc97a37350c1367e7827618a4f8569fad8712666babc50543296550b0a54ea6aac713a5c2b310690ab9c212a777d0317bd7c9b05049bd335266d9b2c3d66b2523671d6805aef31c851987b9131bb8bd5ac563a16c0ba6a359d79e19378e5da1bb8d9c7d17001417d69dc8f2667a193af92569ae645b66639076d526b3345c237b2dad74221309617482658de51b3af47ed71c9f92fb4ab5ab001d243c940b186455cefad54b57e8369f0661842a857c97433690351b041605130dd459486e5a2e48aa45e5dc032ad78e839645247342d869a834125ff20329dc079fbfe3bb5b617ddda30faad7a90106098c35c613355ad54cc1ad7a9214091752412380b3c0d9104b0efb72ee48cddd62bb20ba42db808fdab20e525141e73b3ff8a412858349f4da415dc847de0caf08e2800afb328e16adb3e99d911702500c4547f128a61c63173167362bce3ec8b22b82514d3632ad4a40feb95f54882a72907676298f5f482de54a90ff00968d2a41d86c9e353307ec73ae647902ca1837e82644687a1ebab9199e6c3a4282276f4106da4a01daac4726c7225de93414dc8d18e5661dac1dac8856922b678056ceebe7bf9fccb6c295136a43973d00b560a52b0570527249ca4bf6b70ec0306823b3695b213e4b0b3dc9a196f1c8e3e86f53c30d16858ce80180a33c5dfff25b9130636221bd5a682cdfd195eecc36cf236b51f0892ba62edb4b720b51ca3218abe0733cf72317eaa3839d02034b218ffda39b06e4be4a54aafb698060a78e0222933458397d3221d183245b7c3c908351a4a0b4420b73d1bb282d3004fe9aeb55669dc7a2efbd6ff0402598065ac66b24cede6d3dd2111f8e2a976b7a4bd38d9be2e826a1cb8cab7d95fe5fc6d77fbefa1ccf8d000be4d8d13c001d00207d2d6fb8382a8a063ee5aadd099095e13d15edcad631402dfcff0d41da401315001b0003020001" # noqa: E501
15
+
16
+
17
+ @pytest.fixture(scope="module")
18
+ def setup_sink():
19
+ sink = Sink("127.0.0.1", 8081)
20
+ sink.start()
21
+ yield sink
22
+ sink.close()
23
+
24
+
25
+ def test_record_frag(setup_sink):
26
+ data = run_server(TcpProxy(address=NETWORK_ADDRESS, record_frag=True, frag_size=1), setup_sink)
27
+ assert data.hex().startswith("160301000101160301000100")
28
+
29
+
30
+ def test_record_version(setup_sink):
31
+ data = run_server(TcpProxy(address=NETWORK_ADDRESS, record_version=TlsVersion.TLS13.value), setup_sink)
32
+ assert data.hex().startswith("1603040621")
33
+
34
+
35
+ def run_server(proxy: TcpProxy, sink: Sink) -> bytes:
36
+ """
37
+ Runs the test and return the bytes sent from the proxy to the sink server.
38
+ """
39
+ thread = threading.Thread(target=proxy.start)
40
+ thread.start()
41
+ time.sleep(1)
42
+
43
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
44
+ s.connect(("127.0.0.1", 8080))
45
+ s.sendall(CONNECT_DATA.encode())
46
+ time.sleep(1)
47
+ s.sendall(bytes.fromhex(TLS_DATA))
48
+ ret = sink.receive_message()
49
+ proxy.server.close()
50
+ thread.join()
51
+ return ret
52
+
53
+
54
+ ## TODO: cant really test this from python without running tcpdump or sth.
55
+ # def test_tcp_frag():
56
+ # data = run_server(
57
+ # TcpProxy(
58
+ # address=NETWORK_ADDRESS,
59
+ # tcp_frag=True,
60
+ # frag_size=20
61
+ # ),
62
+ # SINK
63
+ # )
@@ -0,0 +1,62 @@
1
+ import logging
2
+ import os
3
+ import statistics
4
+
5
+ # hack to add parent to pythonpath
6
+ import sys
7
+
8
+ sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
9
+
10
+ from enumerators.DnsProxyMode import DnsProxyMode
11
+ from modules.dns.DnsProxy import DnsProxy
12
+ from network.NetworkAddress import NetworkAddress
13
+
14
+ WIKIMEDIA_RANGES = [
15
+ "185.15.56.0/22",
16
+ "91.198.174.0/24",
17
+ "195.200.68.0/24",
18
+ "193.46.90.0/24",
19
+ "198.35.26.0/23",
20
+ "208.80.152.0/22",
21
+ "103.102.166.0/24",
22
+ ]
23
+
24
+ IRAN_BLOCK_PAGES = ["10.10.34.34", "10.10.34.35", "10.10.34.36"]
25
+
26
+ test_amount = 100
27
+ startup_times = []
28
+ for i in range(test_amount):
29
+ server_address = NetworkAddress("localhost", 4433)
30
+ resolver_address = NetworkAddress(None, 4433)
31
+
32
+ logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
33
+
34
+ proxy = DnsProxy(
35
+ proxy_mode=DnsProxyMode.AUTO,
36
+ address=server_address,
37
+ timeout=3,
38
+ dns_resolver_address=resolver_address,
39
+ censored_domain="twitter.com",
40
+ compare_ip_ranges=IRAN_BLOCK_PAGES,
41
+ block_page_ips=True,
42
+ add_sni=True,
43
+ )
44
+
45
+ startup_time = proxy.start(time_measurement_only=True)
46
+ startup_times.append(startup_time)
47
+
48
+ # Basic statistics
49
+ average = statistics.mean(startup_times)
50
+ median = statistics.median(startup_times)
51
+ minimum = min(startup_times)
52
+ maximum = max(startup_times)
53
+ stdev = statistics.stdev(startup_times) # Sample standard deviation
54
+
55
+ # Print summary
56
+ print("========= Timing Statistics =========")
57
+ print(f"Count: {len(startup_times)}")
58
+ print(f"Average: {average:.2f} seconds")
59
+ print(f"Median: {median:.2f} seconds")
60
+ print(f"Min: {minimum:.2f} seconds")
61
+ print(f"Max: {maximum:.2f} seconds")
62
+ print(f"Stdev: {stdev:.2f} seconds")