nopasaran 0.2.95__tar.gz → 0.2.97__tar.gz

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 (89) hide show
  1. {nopasaran-0.2.95 → nopasaran-0.2.97}/PKG-INFO +1 -1
  2. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/action_primitives.py +6 -1
  3. nopasaran-0.2.97/nopasaran/primitives/action_primitives/tcp_dns_request_primitives.py +29 -0
  4. nopasaran-0.2.97/nopasaran/primitives/action_primitives/tcp_dns_response_primitives.py +40 -0
  5. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/tools/http_1_socket_server.py +2 -75
  6. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/tools/https_1_socket_server.py +41 -75
  7. nopasaran-0.2.97/nopasaran/tools/tcp_dns_socket_server.py +170 -0
  8. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/utils.py +76 -0
  9. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran.egg-info/PKG-INFO +1 -1
  10. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran.egg-info/SOURCES.txt +3 -0
  11. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran.egg-info/requires.txt +1 -0
  12. {nopasaran-0.2.95 → nopasaran-0.2.97}/setup.py +1 -1
  13. {nopasaran-0.2.95 → nopasaran-0.2.97}/LICENSE +0 -0
  14. {nopasaran-0.2.95 → nopasaran-0.2.97}/README.md +0 -0
  15. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/__init__.py +0 -0
  16. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/__main__.py +0 -0
  17. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/controllers/__init__.py +0 -0
  18. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/controllers/controller.py +0 -0
  19. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/controllers/factory.py +0 -0
  20. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/controllers/protocol.py +0 -0
  21. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/decorators.py +0 -0
  22. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/definitions/__init__.py +0 -0
  23. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/definitions/commands.py +0 -0
  24. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/definitions/control_channel.py +0 -0
  25. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/definitions/events.py +0 -0
  26. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/definitions/transitions.py +0 -0
  27. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/errors/__init__.py +0 -0
  28. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/errors/parsing_error.py +0 -0
  29. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/http_2_utils.py +0 -0
  30. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/interpreters/__init__.py +0 -0
  31. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/interpreters/action_interpreter.py +0 -0
  32. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/interpreters/condition_interpreter.py +0 -0
  33. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/interpreters/interpreter.py +0 -0
  34. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/interpreters/transition_interpreter.py +0 -0
  35. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/machines/__init__.py +0 -0
  36. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/machines/action_queue.py +0 -0
  37. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/machines/state_machine.py +0 -0
  38. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/parsers/__init__.py +0 -0
  39. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/parsers/interpreter_parser.py +0 -0
  40. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/parsers/state_machine_parser.py +0 -0
  41. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/__init__.py +0 -0
  42. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/__init__.py +0 -0
  43. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/certificate_primitives.py +0 -0
  44. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/client_echo_primitives.py +0 -0
  45. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/control_channel_primitives.py +0 -0
  46. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/data_channel_primitives.py +0 -0
  47. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/data_manipulation.py +0 -0
  48. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/dns_primitives.py +0 -0
  49. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/event_primitives.py +0 -0
  50. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/http_1_request_primitives.py +0 -0
  51. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/http_1_response_primitives.py +0 -0
  52. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/http_2_client_primitives.py +0 -0
  53. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/http_2_server_primitives.py +0 -0
  54. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/http_simple_client_primitives.py +0 -0
  55. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/https_1_request_primitives.py +0 -0
  56. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/https_1_response_primitives.py +0 -0
  57. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/icmp_primitives.py +0 -0
  58. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/io_primitives.py +0 -0
  59. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/ip_primitives.py +0 -0
  60. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/nested_machine_utils.py +0 -0
  61. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/probing_primitives.py +0 -0
  62. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/replay_primitives.py +0 -0
  63. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/server_echo_primitives.py +0 -0
  64. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/signaling_primitive.py +0 -0
  65. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/tcp_primitives.py +0 -0
  66. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/timing_primitives.py +0 -0
  67. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/tls_primitives.py +0 -0
  68. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/action_primitives/udp_primitives.py +0 -0
  69. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/condition_primitives/__init__.py +0 -0
  70. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/condition_primitives/condition_primitives.py +0 -0
  71. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/condition_primitives/variable_comparisons.py +0 -0
  72. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/primitives.py +0 -0
  73. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/transition_primitives/__init__.py +0 -0
  74. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/transition_primitives/assignment_transitions.py +0 -0
  75. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/primitives/transition_primitives/transition_primitives.py +0 -0
  76. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/sniffers/__init__.py +0 -0
  77. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/sniffers/sniffer.py +0 -0
  78. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/tools/__init__.py +0 -0
  79. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/tools/checks.py +0 -0
  80. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/tools/echo_socket_server.py +0 -0
  81. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/tools/http_2_overwrite.py +0 -0
  82. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/tools/http_2_socket_base.py +0 -0
  83. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/tools/http_2_socket_client.py +0 -0
  84. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran/tools/http_2_socket_server.py +0 -0
  85. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran.egg-info/dependency_links.txt +0 -0
  86. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran.egg-info/entry_points.txt +0 -0
  87. {nopasaran-0.2.95 → nopasaran-0.2.97}/nopasaran.egg-info/top_level.txt +0 -0
  88. {nopasaran-0.2.95 → nopasaran-0.2.97}/setup.cfg +0 -0
  89. {nopasaran-0.2.95 → nopasaran-0.2.97}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nopasaran
3
- Version: 0.2.95
3
+ Version: 0.2.97
4
4
  Summary: NoPASARAN is an advanced network tool designed to detect, fingerprint, and locate network middleboxes in a unified framework.
5
5
  Home-page: https://github.com/BenIlies/NoPASARAN
6
6
  Author: Ilies Benhabbour
@@ -25,6 +25,9 @@ from nopasaran.primitives.action_primitives.client_echo_primitives import Client
25
25
  from nopasaran.primitives.action_primitives.probing_primitives import PortProbingPrimitives
26
26
  from nopasaran.primitives.action_primitives.replay_primitives import ReplayPrimitives
27
27
  from nopasaran.primitives.action_primitives.http_simple_client_primitives import HTTPSimpleClientPrimitives
28
+ from nopasaran.primitives.action_primitives.tcp_dns_request_primitives import TCPDNSRequestPrimitives
29
+ from nopasaran.primitives.action_primitives.tcp_dns_response_primitives import TCPDNSResponsePrimitives
30
+
28
31
 
29
32
  class ActionPrimitives(Primitives):
30
33
  """
@@ -56,5 +59,7 @@ class ActionPrimitives(Primitives):
56
59
  ClientEchoPrimitives,
57
60
  PortProbingPrimitives,
58
61
  ReplayPrimitives,
59
- HTTPSimpleClientPrimitives
62
+ HTTPSimpleClientPrimitives,
63
+ TCPDNSResponsePrimitives,
64
+ TCPDNSRequestPrimitives
60
65
  ]
@@ -0,0 +1,29 @@
1
+ from nopasaran.decorators import parsing_decorator
2
+ from nopasaran.definitions.events import EventNames
3
+ import nopasaran.utils as utils
4
+
5
+ class TCPDNSRequestPrimitives:
6
+ @staticmethod
7
+ @parsing_decorator(input_args=4, output_args=1)
8
+ def make_tcp_dns_query(inputs, outputs, state_machine):
9
+ """
10
+ Make a DNS query over TCP with user-defined domain and query type.
11
+ inputs: [domain, query_type, server_ip, server_port]
12
+ outputs: [dns_response_dict]
13
+ """
14
+ domain = state_machine.get_variable_value(inputs[0])
15
+ query_type = state_machine.get_variable_value(inputs[1])
16
+ server_ip = state_machine.get_variable_value(inputs[2])
17
+ server_port = int(state_machine.get_variable_value(inputs[3]))
18
+
19
+ # Call utility function with provided parameters
20
+ result = utils.send_tcp_dns_query(server_ip, server_port, domain, query_type)
21
+
22
+ # Store result and trigger events accordingly
23
+ if not result or result.get("response") is None:
24
+ state_machine.set_variable_value(outputs[0], {"received": None})
25
+ state_machine.trigger_event(EventNames.REQUEST_ERROR.name)
26
+ else:
27
+ state_machine.set_variable_value(outputs[0], {"received": result})
28
+ state_machine.trigger_event(EventNames.RESPONSE_RECEIVED.name)
29
+
@@ -0,0 +1,40 @@
1
+ from nopasaran.decorators import parsing_decorator
2
+ from nopasaran.tools.tcp_dns_socket_server import TCPDNSSocketServer
3
+
4
+ class TCPDNSResponsePrimitives:
5
+
6
+ @staticmethod
7
+ @parsing_decorator(input_args=0, output_args=1)
8
+ def create_tcp_dns_server(inputs, outputs, state_machine):
9
+ server = TCPDNSSocketServer()
10
+ state_machine.set_variable_value(outputs[0], server)
11
+
12
+ @staticmethod
13
+ @parsing_decorator(input_args=2, output_args=0)
14
+ def start_tcp_dns_server(inputs, outputs, state_machine):
15
+ server = state_machine.get_variable_value(inputs[0])
16
+ port = int(state_machine.get_variable_value(inputs[1]))
17
+ server.start(port)
18
+
19
+ @staticmethod
20
+ @parsing_decorator(input_args=4, output_args=1)
21
+ def wait_and_respond_tcp_dns_query(inputs, outputs, state_machine):
22
+ """
23
+ Wait for a DNS query and respond based on provided spec.
24
+ Inputs: [server_instance, port, timeout, response_spec]
25
+ Outputs: [result_dict]
26
+ Example response_spec:
27
+ {"type": "CNAME", "value": "safe.com", "qname": "blocked.com."}
28
+ """
29
+ server = state_machine.get_variable_value(inputs[0])
30
+ timeout = int(state_machine.get_variable_value(inputs[2]))
31
+ response_spec = state_machine.get_variable_value(inputs[3]) # can be None
32
+
33
+ result, _ = server.wait_for_query(timeout, response_spec)
34
+ state_machine.set_variable_value(outputs[0], result)
35
+
36
+ @staticmethod
37
+ @parsing_decorator(input_args=1, output_args=0)
38
+ def close_tcp_dns_server(inputs, outputs, state_machine):
39
+ server = state_machine.get_variable_value(inputs[0])
40
+ server.close()
@@ -124,79 +124,6 @@ class HTTP1SocketServer:
124
124
 
125
125
  return EventNames.SERVER_STARTED.name, f"Server successfully started at {host}:{port}."
126
126
 
127
- def receive_test_frames(self):
128
- """
129
- Listen for HTTP/1.1 requests until a timeout occurs, then process all received requests.
130
-
131
- Returns:
132
- Tuple[str, str, str]: (event_name, message, received_data)
133
- - event_name: The type of event that occurred (from EventNames)
134
- - TIMEOUT: Timeout occurred before all requests were received
135
- - REJECTED: Received a 4xx or 5xx status code
136
- - RECEIVED_REQUESTS: All requests have been received
137
- - ERROR: Error occurred while receiving requests
138
- - message: A descriptive message about what happened
139
- - received_data: String representation of the received requests or None
140
- """
141
- if not self.sock:
142
- return EventNames.ERROR.name, "Server not started", None
143
-
144
- requests_received = []
145
- start_time = time.time()
146
-
147
- # Make the server socket non-blocking for accepting connections
148
- self.sock.setblocking(False)
149
-
150
- # Keep accepting connections until we hit a timeout
151
- while True:
152
- # Check for overall timeout
153
- elapsed_time = time.time() - start_time
154
- if elapsed_time > self.TIMEOUT:
155
- if len(requests_received) == 0:
156
- return EventNames.TIMEOUT.name, f"Timeout after {self.TIMEOUT}s and received no requests.", None
157
- else:
158
- break # We've received some requests, so consider it done
159
-
160
- try:
161
- # Try to accept a new connection
162
- ready_to_read, _, _ = select.select([self.sock], [], [], 0.5)
163
-
164
- if ready_to_read:
165
- client_socket, _ = self.sock.accept()
166
- client_socket.settimeout(0.5)
167
-
168
- try:
169
- data = client_socket.recv(4096)
170
-
171
- if data:
172
- # Parse the HTTP/1.1 request
173
- request_str = data.decode('utf-8', errors='ignore')
174
- requests_received.append(request_str)
175
-
176
- # Check for error status codes
177
- if "HTTP/1.1 5" in request_str or "HTTP/1.1 4" in request_str:
178
- try:
179
- status_line = request_str.split("\r\n")[0]
180
- status_code = status_line.split(" ")[1]
181
- client_socket.close()
182
- return EventNames.REJECTED.name, f"Received {status_code} status code.", request_str
183
- except (IndexError, ValueError):
184
- client_socket.close()
185
- return EventNames.REJECTED.name, "Received 4xx or 5xx status code.", request_str
186
- finally:
187
- client_socket.close()
188
- except socket.timeout:
189
- continue
190
- except Exception as e:
191
- return EventNames.ERROR.name, f"Error while receiving requests: {str(e)}", \
192
- str(requests_received) if requests_received else None
193
-
194
- # If we get here, we've either collected all requests or hit the timeout
195
- if len(requests_received) == 0:
196
- return EventNames.TIMEOUT.name, "No requests received before timeout.", None
197
-
198
- return EventNames.RECEIVED_REQUESTS.name, f"Received {len(requests_received)} HTTP/1.1 requests.", str(requests_received)
199
-
200
127
  def close(self):
201
128
  """Close the HTTP/1.1 connection and clean up resources"""
202
129
  try:
@@ -209,6 +136,6 @@ class HTTP1SocketServer:
209
136
  self.client_socket = None
210
137
  self.sock = None
211
138
 
212
- return EventNames.CONNECTION_CLOSED.name
139
+ return EventNames.CONNECTION_ENDING.name
213
140
  except Exception as e:
214
- return EventNames.CONNECTION_CLOSED.name
141
+ return EventNames.CONNECTION_ENDING.name
@@ -30,13 +30,10 @@ class HTTPS1SocketServer:
30
30
  def generate_and_load_cert(self, identifier):
31
31
  cert_path, key_path = self.generate_self_signed_cert(identifier)
32
32
 
33
- # Check if paths are valid
34
33
  if not cert_path or not os.path.exists(cert_path):
35
34
  raise FileNotFoundError(f"[HTTPS1SocketServer] Certificate file not found at {cert_path}")
36
35
  if not key_path or not os.path.exists(key_path):
37
36
  raise FileNotFoundError(f"[HTTPS1SocketServer] Key file not found at {key_path}")
38
-
39
- # Optional: Ensure they're not empty (some systems fail silently on write)
40
37
  if os.path.getsize(cert_path) == 0:
41
38
  raise ValueError(f"[HTTPS1SocketServer] Certificate file is empty at {cert_path}")
42
39
  if os.path.getsize(key_path) == 0:
@@ -50,7 +47,6 @@ class HTTPS1SocketServer:
50
47
  self._cert_path = cert_path
51
48
  self._key_path = key_path
52
49
 
53
-
54
50
  def generate_self_signed_cert(self, identifier):
55
51
  key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
56
52
 
@@ -60,11 +56,10 @@ class HTTPS1SocketServer:
60
56
 
61
57
  alt_names = [x509.DNSName(identifier)]
62
58
  try:
63
- # Try to interpret as IP address
64
59
  import ipaddress
65
60
  alt_names.append(x509.IPAddress(ipaddress.ip_address(identifier)))
66
61
  except ValueError:
67
- pass # it's not an IP, skip adding IPAddress
62
+ pass
68
63
 
69
64
  cert = (
70
65
  x509.CertificateBuilder()
@@ -102,16 +97,18 @@ class HTTPS1SocketServer:
102
97
  self.sock.bind((host, port))
103
98
  self.sock.listen(5)
104
99
  return EventNames.SERVER_STARTED.name, f"HTTPS server started at {host}:{port}"
105
-
106
-
107
-
108
100
 
109
101
  def handle_client_connection(self, tls_socket):
110
- request = tls_socket.recv(4096)
102
+ try:
103
+ request = tls_socket.recv(4096)
104
+ if not request:
105
+ return # Client closed connection or sent nothing
106
+ except socket.timeout:
107
+ return # Timed out waiting for client data
108
+
111
109
  request_str = request.decode("utf-8", errors="ignore")
112
110
  self.received_request_data = request
113
111
 
114
- # Parse request line
115
112
  headers_end_index = request_str.find("\r\n\r\n")
116
113
  headers_part = request_str[:headers_end_index] if headers_end_index != -1 else request_str
117
114
  request_line = headers_part.split("\r\n")[0]
@@ -147,51 +144,12 @@ class HTTPS1SocketServer:
147
144
  with self.request_received:
148
145
  self.request_received.notify_all()
149
146
 
150
- def receive_test_frames(self):
151
- if not self.sock:
152
- return EventNames.ERROR.name, "Server not started", None
153
-
154
- requests_received = []
155
- start_time = time.time()
156
- self.sock.setblocking(False)
157
-
158
- while True:
159
- if time.time() - start_time > self.TIMEOUT:
160
- if not requests_received:
161
- return EventNames.TIMEOUT.name, "Timeout with no request", None
162
- break
163
-
164
- try:
165
- ready_to_read, _, _ = select.select([self.sock], [], [], 0.5)
166
- if ready_to_read:
167
- client_sock, _ = self.sock.accept()
168
- tls_sock = self.context.wrap_socket(client_sock, server_side=True)
169
- tls_sock.settimeout(1.0)
170
- try:
171
- self.handle_client_connection(tls_sock)
172
- req_str = self.received_request_data.decode("utf-8", errors="ignore")
173
- requests_received.append(req_str)
174
- except Exception as e:
175
- return EventNames.ERROR.name, f"TLS error: {e}", None
176
- except Exception as e:
177
- return EventNames.ERROR.name, str(e), None
178
-
179
- return EventNames.RECEIVED_REQUESTS.name, f"Received {len(requests_received)} HTTPS requests.", str(requests_received)
180
-
181
147
  def wait_for_request(self, port, timeout):
182
148
  """
183
149
  Wait for a single HTTPS request with TLS handshake and user-defined timeout.
184
-
185
- Args:
186
- port (int): The port to bind and listen on.
187
- timeout (int): Timeout duration in seconds.
188
-
189
- Returns:
190
- Tuple[bytes or None, str]: The raw request data (or None if timed out) and an event name.
191
150
  """
192
151
  self.received_request_data = None
193
- self.request_received = None
194
-
152
+ self.request_received = None
195
153
  context = self.context
196
154
  start_time = time.time()
197
155
 
@@ -207,36 +165,44 @@ class HTTPS1SocketServer:
207
165
  return None, EventNames.TIMEOUT.name
208
166
 
209
167
  try:
210
- ready, _, _ = select.select([server_socket], [], [], timeout - elapsed)
168
+ remaining = timeout - elapsed
169
+ ready, _, _ = select.select([server_socket], [], [], remaining)
211
170
  if ready:
212
171
  client_sock, _ = server_socket.accept()
213
- with context.wrap_socket(client_sock, server_side=True) as tls_sock:
214
- tls_sock.settimeout(1.0)
215
- try:
172
+ client_sock.settimeout(2.0) # Important: Timeout for TLS handshake and recv
173
+ try:
174
+ with context.wrap_socket(client_sock, server_side=True) as tls_sock:
175
+ tls_sock.settimeout(2.0) # Also set after TLS handshake
216
176
  self.handle_client_connection(tls_sock)
217
- return self.received_request_data, EventNames.REQUEST_RECEIVED.name
218
- except Exception as e:
219
- return None, EventNames.ERROR.name
220
- except Exception as e:
221
- return None, EventNames.ERROR.name
177
+ if self.received_request_data:
178
+ return self.received_request_data, EventNames.REQUEST_RECEIVED.name
179
+ else:
180
+ return None, EventNames.TIMEOUT.name
181
+ except (ssl.SSLError, socket.timeout):
182
+ return None, EventNames.TIMEOUT.name
183
+ except Exception:
184
+ return None, EventNames.ERROR.name
185
+ except Exception:
186
+ return None, EventNames.ERROR.name
222
187
 
223
188
  def close(self):
224
- if self.client_socket:
225
- self.client_socket.close()
226
- if self.sock:
227
- self.sock.close()
228
- self.client_socket = None
229
- self.sock = None
230
-
231
189
  try:
232
- if self._cert_path and os.path.exists(self._cert_path):
233
- os.remove(self._cert_path)
234
- if self._key_path and os.path.exists(self._key_path):
235
- os.remove(self._key_path)
236
- except Exception as e:
237
- return EventNames.ERROR.name, f"Certificate cleanup error: {str(e)}"
190
+ if self.client_socket:
191
+ self.client_socket.close()
192
+ if self.sock:
193
+ self.sock.close()
238
194
 
239
- return EventNames.CONNECTION_CLOSED.name
195
+ self.client_socket = None
196
+ self.sock = None
240
197
 
241
-
198
+ try:
199
+ if self._cert_path and os.path.exists(self._cert_path):
200
+ os.remove(self._cert_path)
201
+ if self._key_path and os.path.exists(self._key_path):
202
+ os.remove(self._key_path)
203
+ except Exception as e:
204
+ return EventNames.ERROR.name, f"Certificate cleanup error: {str(e)}"
242
205
 
206
+ return EventNames.CONNECTION_ENDING.name
207
+ except Exception:
208
+ return EventNames.CONNECTION_ENDING.name
@@ -0,0 +1,170 @@
1
+ import socket
2
+ import struct
3
+ import select
4
+ import time
5
+ from dnslib import DNSRecord, RR, QTYPE, A, CNAME, MX, TXT, NS, SOA, PTR, AAAA, SRV, DS, RRSIG, NSEC, DNSKEY
6
+ from nopasaran.definitions.events import EventNames
7
+
8
+ class TCPDNSSocketServer:
9
+ def __init__(self):
10
+ self.sock = None
11
+
12
+ def start(self, port):
13
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
14
+ self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
15
+ self.sock.bind(('', port))
16
+ self.sock.listen(5)
17
+ return EventNames.SERVER_STARTED.name, f"TCP DNS server started on port {port}"
18
+
19
+ def wait_for_query(self, timeout, response_spec=None):
20
+ timeout = float(timeout)
21
+ start_time = time.time()
22
+ self.sock.setblocking(False)
23
+
24
+ print(f"[Server] Waiting for connections on port {self.sock.getsockname()[1]} with timeout {timeout} seconds")
25
+
26
+ while True:
27
+ remaining_time = timeout - (time.time() - start_time)
28
+ print(f"[Server] Remaining time: {remaining_time:.2f} seconds")
29
+ if remaining_time <= 0:
30
+ print("[Server] Timeout reached with no connection.")
31
+ return {"received": None}, EventNames.TIMEOUT.name
32
+
33
+ ready, _, _ = select.select([self.sock], [], [], remaining_time)
34
+ if ready:
35
+ print("[Server] Socket is ready, accepting...")
36
+ client_sock, client_addr = self.sock.accept()
37
+ print(f"[Server] Accepted connection from {client_addr}")
38
+
39
+ try:
40
+ # Set client socket read timeout
41
+ client_sock.settimeout(5)
42
+ length_data = client_sock.recv(2)
43
+ print(f"[Server] Received length_data: {length_data}")
44
+
45
+ if len(length_data) < 2:
46
+ print("[Server] Incomplete length_data")
47
+ return {"received": None}, EventNames.ERROR.name
48
+
49
+ expected_length = struct.unpack("!H", length_data)[0]
50
+ print(f"[Server] Expecting {expected_length} bytes of query data")
51
+
52
+ request_data = b""
53
+ receive_start_time = time.time()
54
+ receive_timeout = 5 # seconds
55
+
56
+ while len(request_data) < expected_length:
57
+ # Check elapsed time to prevent infinite waiting
58
+ if time.time() - receive_start_time > receive_timeout:
59
+ print("[Server] Timeout while receiving DNS query data")
60
+ return {"received": None}, EventNames.TIMEOUT.name
61
+
62
+ try:
63
+ chunk = client_sock.recv(expected_length - len(request_data))
64
+ if not chunk:
65
+ print("[Server] Connection closed before full query received")
66
+ return {"received": None}, EventNames.ERROR.name
67
+ request_data += chunk
68
+ print(f"[Server] Received {len(request_data)}/{expected_length} bytes")
69
+ except socket.timeout:
70
+ print("[Server] Socket recv() timed out")
71
+ return {"received": None}, EventNames.TIMEOUT.name
72
+
73
+ if not request_data:
74
+ print("[Server] No request data received")
75
+ return {"received": None}, EventNames.ERROR.name
76
+
77
+ print("[Server] Parsing DNS query...")
78
+ parsed_query = DNSRecord.parse(request_data)
79
+ print(f"[Server] Parsed query: {parsed_query.toZone()}")
80
+
81
+ print("[Server] Building DNS response...")
82
+ response = self.build_response(parsed_query, response_spec)
83
+
84
+ print("[Server] Sending DNS response...")
85
+ self.send_dns_response(client_sock, response)
86
+ print("[Server] DNS response sent successfully.")
87
+
88
+ return {
89
+ "received": parsed_query.toZone(),
90
+ "client_address": client_addr
91
+ }, EventNames.REQUEST_RECEIVED.name
92
+
93
+ finally:
94
+ print("[Server] Closing client socket")
95
+ client_sock.close()
96
+
97
+
98
+
99
+ def build_response(self, query_record, response_spec=None):
100
+ qname = str(query_record.q.qname)
101
+ qtype = query_record.q.qtype
102
+ response_qname = response_spec.get("qname") if response_spec and response_spec.get("qname") else qname
103
+ response_type = response_spec.get("type").upper() if response_spec and response_spec.get("type") else QTYPE[qtype].name
104
+ response_value = response_spec.get("value") if response_spec else None
105
+
106
+ response = query_record.reply()
107
+
108
+ handlers = {
109
+ "A": lambda: A(response_value or "127.0.0.1"),
110
+ "CNAME": lambda: CNAME(response_value or response_qname),
111
+ "MX": lambda: MX(response_value or f"mail.{response_qname}", preference=10),
112
+ "TXT": lambda: TXT(response_value or f"dummy record for {response_qname}"),
113
+ "NS": lambda: NS(response_value or f"ns1.{response_qname}"),
114
+ "SOA": lambda: SOA(response_value or f"ns1.{response_qname}", f"admin.{response_qname}", (2024051801, 3600, 3600, 3600, 3600)),
115
+ "PTR": lambda: PTR(response_value or f"ptr.{response_qname}"),
116
+ "AAAA": lambda: AAAA(response_value or "::1"),
117
+ "SRV": lambda: self._parse_srv(response_value or f"service.{response_qname},80,0,0"),
118
+ "DS": lambda: DS(12345, 1, 1, bytes(response_value or f"abcdef{response_qname}", 'utf-8')),
119
+ "RRSIG": lambda: RRSIG(1, 1, 0, 3600, 0, 0, 0, response_value or f"signer.{response_qname}", b"signature"),
120
+ "NSEC": lambda: NSEC(response_value or f"next.{response_qname}", []),
121
+ "DNSKEY": lambda: DNSKEY(256, 3, 8, bytes(response_value or f"publickey{response_qname}", 'utf-8')),
122
+ "ANY": lambda: A(response_value or "127.0.0.1")
123
+ }
124
+
125
+ handler = handlers.get(response_type)
126
+ if not handler:
127
+ print(f"[Server Error] No handler found for response_type: {response_type}")
128
+ return query_record.reply()
129
+
130
+ reverse_qtype = {QTYPE[k]: k for k in QTYPE if isinstance(k, int)}
131
+ rtype = reverse_qtype.get(response_type)
132
+ if rtype is None:
133
+ print(f"[Server Error] Unsupported response_type: {response_type}")
134
+ return query_record.reply()
135
+
136
+ try:
137
+ print(f"[Server] Calling handler for type {response_type}")
138
+ rdata = handler()
139
+ print(f"[Server] Handler produced rdata: {rdata}")
140
+ except Exception as e:
141
+ print(f"[Server Error] Handler for {response_type} failed: {e}")
142
+ return query_record.reply()
143
+
144
+ response.add_answer(RR(rname=response_qname, rtype=rtype, rclass=1, ttl=60, rdata=rdata))
145
+ return response
146
+
147
+
148
+
149
+ def _parse_srv(self, value):
150
+ try:
151
+ target, port, priority, weight = value.split(",")
152
+ return SRV(int(priority), int(weight), int(port), target)
153
+ except Exception:
154
+ return SRV(0, 0, 80, "service.example.com")
155
+
156
+ def send_dns_response(self, client_sock, dns_record):
157
+ response_bytes = dns_record.pack()
158
+ length_prefix = struct.pack("!H", len(response_bytes))
159
+ try:
160
+ client_sock.sendall(length_prefix + response_bytes)
161
+ return EventNames.RESPONSE_SENT.name
162
+ except Exception as e:
163
+ print(f"Failed to send DNS response: {e}")
164
+ return EventNames.ERROR.name
165
+
166
+ def close(self):
167
+ if self.sock:
168
+ self.sock.close()
169
+ self.sock = None
170
+ return EventNames.CONNECTION_CLOSED.name
@@ -4,6 +4,9 @@ import random
4
4
  import socket
5
5
  import select
6
6
  import ssl
7
+ import struct
8
+ from dnslib import DNSRecord, QTYPE
9
+
7
10
 
8
11
 
9
12
  from scapy.all import IP, TCP, UDP, ICMP, Raw
@@ -391,3 +394,76 @@ def group_ports(ports):
391
394
  return result
392
395
 
393
396
 
397
+
398
+
399
+ def send_tcp_dns_query(server_ip, server_port, domain, query_type="A"):
400
+ dnsatypes = {
401
+ 1: "A", 2: "NS", 5: "CNAME", 6: "SOA", 12: "PTR", 15: "MX",
402
+ 16: "TXT", 28: "AAAA", 33: "SRV", 43: "DS",
403
+ 46: "RRSIG", 47: "NSEC", 48: "DNSKEY", 255: "ANY"
404
+ }
405
+
406
+ result = {"query": None, "response": None, "error": None}
407
+ supported_types = ', '.join([f"{v}({k})" for k, v in dnsatypes.items()])
408
+ lookup_by_name = {v.upper(): k for k, v in dnsatypes.items()}
409
+ query_type_str = str(query_type).strip().upper()
410
+
411
+ print(f"[Debug] Received query_type: {query_type_str}")
412
+
413
+ # Validate query type
414
+ if query_type_str.isdigit():
415
+ qtype = int(query_type_str)
416
+ if qtype not in dnsatypes:
417
+ result["error"] = f"Unsupported numeric query type: {query_type}.\nSupported types are: {supported_types}"
418
+ print("[Error]", result["error"])
419
+ return result
420
+ else:
421
+ qtype = lookup_by_name.get(query_type_str)
422
+ if qtype is None:
423
+ result["error"] = f"Unsupported string query type: {query_type}.\nSupported types are: {supported_types}"
424
+ print("[Error]", result["error"])
425
+ return result
426
+
427
+ try:
428
+ print(f"[Debug] Building DNS query for domain {domain} with type {query_type_str} ({qtype})")
429
+ dns_query = DNSRecord.question(domain, qtype=query_type_str)
430
+ dns_query.header.id = random.randint(0, 65535)
431
+ query_packet = dns_query.pack()
432
+ result["query"] = dns_query.toZone()
433
+
434
+ print("[Debug] Opening socket")
435
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
436
+ sock.bind(('', 0))
437
+ sock.settimeout(2)
438
+
439
+ print(f"[Debug] Connecting to {server_ip}:{server_port}")
440
+ sock.connect((server_ip, server_port))
441
+
442
+ length_prefix = struct.pack("!H", len(query_packet))
443
+ print("[Debug] Sending DNS query")
444
+ sock.sendall(length_prefix + query_packet)
445
+
446
+ print("[Debug] Waiting for response")
447
+ length_data = sock.recv(2)
448
+ if len(length_data) < 2:
449
+ result["error"] = "Incomplete length prefix"
450
+ print("[Error]", result["error"])
451
+ return result
452
+
453
+ expected_length = struct.unpack("!H", length_data)[0]
454
+ response_data = b""
455
+ while len(response_data) < expected_length:
456
+ chunk = sock.recv(expected_length - len(response_data))
457
+ if not chunk:
458
+ break
459
+ response_data += chunk
460
+
461
+ print("[Debug] Received response data")
462
+ parsed_response = DNSRecord.parse(response_data)
463
+ result["response"] = parsed_response.to_dict()
464
+ return result
465
+
466
+ except Exception as e:
467
+ result["error"] = f"Exception occurred: {str(e)}"
468
+ print("[Error]", result["error"])
469
+ return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nopasaran
3
- Version: 0.2.95
3
+ Version: 0.2.97
4
4
  Summary: NoPASARAN is an advanced network tool designed to detect, fingerprint, and locate network middleboxes in a unified framework.
5
5
  Home-page: https://github.com/BenIlies/NoPASARAN
6
6
  Author: Ilies Benhabbour
@@ -60,6 +60,8 @@ nopasaran/primitives/action_primitives/probing_primitives.py
60
60
  nopasaran/primitives/action_primitives/replay_primitives.py
61
61
  nopasaran/primitives/action_primitives/server_echo_primitives.py
62
62
  nopasaran/primitives/action_primitives/signaling_primitive.py
63
+ nopasaran/primitives/action_primitives/tcp_dns_request_primitives.py
64
+ nopasaran/primitives/action_primitives/tcp_dns_response_primitives.py
63
65
  nopasaran/primitives/action_primitives/tcp_primitives.py
64
66
  nopasaran/primitives/action_primitives/timing_primitives.py
65
67
  nopasaran/primitives/action_primitives/tls_primitives.py
@@ -81,4 +83,5 @@ nopasaran/tools/http_2_socket_base.py
81
83
  nopasaran/tools/http_2_socket_client.py
82
84
  nopasaran/tools/http_2_socket_server.py
83
85
  nopasaran/tools/https_1_socket_server.py
86
+ nopasaran/tools/tcp_dns_socket_server.py
84
87
  tests/__init__.py
@@ -6,3 +6,4 @@ scapy
6
6
  h2
7
7
  overload
8
8
  dnspython
9
+ dnslib
@@ -13,7 +13,7 @@ with open(requirements_file, "r") as f:
13
13
  # Version will automatically be updated when pushed on the main branch
14
14
  setup(
15
15
  name="nopasaran",
16
- version='0.2.95',
16
+ version='0.2.97',
17
17
  author="Ilies Benhabbour",
18
18
  author_email="ilies.benhabbour@kaust.edu.sa",
19
19
  description="NoPASARAN is an advanced network tool designed to detect, fingerprint, and locate network middleboxes in a unified framework.",
File without changes
File without changes
File without changes
File without changes