nopasaran 0.2.99__tar.gz → 0.2.101__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.
- {nopasaran-0.2.99 → nopasaran-0.2.101}/PKG-INFO +1 -1
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/definitions/events.py +2 -1
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/action_primitives.py +5 -1
- nopasaran-0.2.101/nopasaran/primitives/action_primitives/udp_dns_request_primitives.py +29 -0
- nopasaran-0.2.101/nopasaran/primitives/action_primitives/udp_dns_response_primitives.py +40 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/tools/tcp_dns_socket_server.py +29 -32
- nopasaran-0.2.101/nopasaran/tools/udp_dns_socket_server.py +120 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/utils.py +67 -16
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran.egg-info/PKG-INFO +1 -1
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran.egg-info/SOURCES.txt +3 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/setup.py +1 -1
- {nopasaran-0.2.99 → nopasaran-0.2.101}/LICENSE +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/README.md +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/__init__.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/__main__.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/controllers/__init__.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/controllers/controller.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/controllers/factory.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/controllers/protocol.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/decorators.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/definitions/__init__.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/definitions/commands.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/definitions/control_channel.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/definitions/transitions.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/errors/__init__.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/errors/parsing_error.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/http_2_utils.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/interpreters/__init__.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/interpreters/action_interpreter.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/interpreters/condition_interpreter.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/interpreters/interpreter.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/interpreters/transition_interpreter.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/machines/__init__.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/machines/action_queue.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/machines/state_machine.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/parsers/__init__.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/parsers/interpreter_parser.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/parsers/state_machine_parser.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/__init__.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/__init__.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/certificate_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/client_echo_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/control_channel_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/data_channel_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/data_manipulation.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/dns_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/event_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/http_1_request_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/http_1_response_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/http_2_client_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/http_2_server_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/http_simple_client_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/https_1_request_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/https_1_response_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/icmp_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/io_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/ip_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/nested_machine_utils.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/probing_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/replay_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/server_echo_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/signaling_primitive.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/tcp_dns_request_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/tcp_dns_response_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/tcp_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/timing_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/tls_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/udp_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/condition_primitives/__init__.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/condition_primitives/condition_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/condition_primitives/variable_comparisons.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/transition_primitives/__init__.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/transition_primitives/assignment_transitions.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/transition_primitives/transition_primitives.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/sniffers/__init__.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/sniffers/sniffer.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/tools/__init__.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/tools/checks.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/tools/echo_socket_server.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/tools/http_1_socket_server.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/tools/http_2_overwrite.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/tools/http_2_socket_base.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/tools/http_2_socket_client.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/tools/http_2_socket_server.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/tools/https_1_socket_server.py +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran.egg-info/dependency_links.txt +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran.egg-info/entry_points.txt +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran.egg-info/requires.txt +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran.egg-info/top_level.txt +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/setup.cfg +0 -0
- {nopasaran-0.2.99 → nopasaran-0.2.101}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: nopasaran
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.101
|
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
|
{nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/action_primitives.py
RENAMED
@@ -27,6 +27,8 @@ from nopasaran.primitives.action_primitives.replay_primitives import ReplayPrimi
|
|
27
27
|
from nopasaran.primitives.action_primitives.http_simple_client_primitives import HTTPSimpleClientPrimitives
|
28
28
|
from nopasaran.primitives.action_primitives.tcp_dns_request_primitives import TCPDNSRequestPrimitives
|
29
29
|
from nopasaran.primitives.action_primitives.tcp_dns_response_primitives import TCPDNSResponsePrimitives
|
30
|
+
from nopasaran.primitives.action_primitives.udp_dns_request_primitives import UDPDNSRequestPrimitives
|
31
|
+
from nopasaran.primitives.action_primitives.udp_dns_response_primitives import UDPDNSResponsePrimitives
|
30
32
|
|
31
33
|
|
32
34
|
class ActionPrimitives(Primitives):
|
@@ -60,6 +62,8 @@ class ActionPrimitives(Primitives):
|
|
60
62
|
PortProbingPrimitives,
|
61
63
|
ReplayPrimitives,
|
62
64
|
HTTPSimpleClientPrimitives,
|
65
|
+
TCPDNSRequestPrimitives,
|
63
66
|
TCPDNSResponsePrimitives,
|
64
|
-
|
67
|
+
UDPDNSRequestPrimitives,
|
68
|
+
UDPDNSResponsePrimitives
|
65
69
|
]
|
@@ -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 UDPDNSRequestPrimitives:
|
6
|
+
@staticmethod
|
7
|
+
@parsing_decorator(input_args=4, output_args=1)
|
8
|
+
def make_udp_dns_query(inputs, outputs, state_machine):
|
9
|
+
"""
|
10
|
+
Make a DNS query over UDP 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_udp_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.udp_dns_socket_server import UDPDNSSocketServer
|
3
|
+
|
4
|
+
class UDPDNSResponsePrimitives:
|
5
|
+
|
6
|
+
@staticmethod
|
7
|
+
@parsing_decorator(input_args=0, output_args=1)
|
8
|
+
def create_udp_dns_server(inputs, outputs, state_machine):
|
9
|
+
server = UDPDNSSocketServer()
|
10
|
+
state_machine.set_variable_value(outputs[0], server)
|
11
|
+
|
12
|
+
@staticmethod
|
13
|
+
@parsing_decorator(input_args=2, output_args=0)
|
14
|
+
def start_udp_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_udp_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_udp_dns_server(inputs, outputs, state_machine):
|
39
|
+
server = state_machine.get_variable_value(inputs[0])
|
40
|
+
server.close()
|
@@ -4,6 +4,7 @@ import select
|
|
4
4
|
import time
|
5
5
|
from dnslib import DNSRecord, RR, QTYPE, A, CNAME, MX, TXT, NS, SOA, PTR, AAAA, SRV, DS, RRSIG, NSEC, DNSKEY
|
6
6
|
from nopasaran.definitions.events import EventNames
|
7
|
+
import logging
|
7
8
|
|
8
9
|
class TCPDNSSocketServer:
|
9
10
|
def __init__(self):
|
@@ -14,6 +15,7 @@ class TCPDNSSocketServer:
|
|
14
15
|
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
15
16
|
self.sock.bind(('', port))
|
16
17
|
self.sock.listen(5)
|
18
|
+
logging.info(f"TCP DNS server started on port {port}")
|
17
19
|
return EventNames.SERVER_STARTED.name, f"TCP DNS server started on port {port}"
|
18
20
|
|
19
21
|
def wait_for_query(self, timeout, response_spec=None):
|
@@ -21,69 +23,66 @@ class TCPDNSSocketServer:
|
|
21
23
|
start_time = time.time()
|
22
24
|
self.sock.setblocking(False)
|
23
25
|
|
24
|
-
|
26
|
+
logging.info(f"Waiting for connections on port {self.sock.getsockname()[1]} with timeout {timeout} seconds")
|
25
27
|
|
26
28
|
while True:
|
27
29
|
remaining_time = timeout - (time.time() - start_time)
|
28
|
-
|
30
|
+
logging.debug(f"Remaining time: {remaining_time:.2f} seconds")
|
29
31
|
if remaining_time <= 0:
|
30
|
-
|
32
|
+
logging.warning("Timeout reached with no connection.")
|
31
33
|
return {"received": None}, EventNames.TIMEOUT.name
|
32
34
|
|
33
35
|
ready, _, _ = select.select([self.sock], [], [], remaining_time)
|
34
36
|
if ready:
|
35
|
-
print("[Server] Socket is ready, accepting...")
|
36
37
|
client_sock, client_addr = self.sock.accept()
|
37
|
-
|
38
|
+
logging.info(f"Accepted connection from {client_addr}")
|
38
39
|
|
39
40
|
try:
|
40
|
-
# Set client socket read timeout
|
41
41
|
client_sock.settimeout(5)
|
42
42
|
length_data = client_sock.recv(2)
|
43
|
-
|
43
|
+
logging.debug(f"Received length_data: {length_data}")
|
44
44
|
|
45
45
|
if len(length_data) < 2:
|
46
|
-
|
46
|
+
logging.error("Incomplete length_data")
|
47
47
|
return {"received": None}, EventNames.ERROR.name
|
48
48
|
|
49
49
|
expected_length = struct.unpack("!H", length_data)[0]
|
50
|
-
|
50
|
+
logging.debug(f"Expecting {expected_length} bytes of query data")
|
51
51
|
|
52
52
|
request_data = b""
|
53
53
|
receive_start_time = time.time()
|
54
54
|
receive_timeout = 5 # seconds
|
55
55
|
|
56
56
|
while len(request_data) < expected_length:
|
57
|
-
# Check elapsed time to prevent infinite waiting
|
58
57
|
if time.time() - receive_start_time > receive_timeout:
|
59
|
-
|
58
|
+
logging.warning("Timeout while receiving DNS query data")
|
60
59
|
return {"received": None}, EventNames.TIMEOUT.name
|
61
60
|
|
62
61
|
try:
|
63
62
|
chunk = client_sock.recv(expected_length - len(request_data))
|
64
63
|
if not chunk:
|
65
|
-
|
64
|
+
logging.error("Connection closed before full query received")
|
66
65
|
return {"received": None}, EventNames.ERROR.name
|
67
66
|
request_data += chunk
|
68
|
-
|
67
|
+
logging.debug(f"Received {len(request_data)}/{expected_length} bytes")
|
69
68
|
except socket.timeout:
|
70
|
-
|
69
|
+
logging.warning("Socket recv() timed out")
|
71
70
|
return {"received": None}, EventNames.TIMEOUT.name
|
72
71
|
|
73
72
|
if not request_data:
|
74
|
-
|
73
|
+
logging.error("No request data received")
|
75
74
|
return {"received": None}, EventNames.ERROR.name
|
76
75
|
|
77
|
-
|
76
|
+
logging.debug("Parsing DNS query...")
|
78
77
|
parsed_query = DNSRecord.parse(request_data)
|
79
|
-
|
78
|
+
logging.info(f"Parsed query:\n{parsed_query.toZone()}")
|
80
79
|
|
81
|
-
|
80
|
+
logging.debug("Building DNS response...")
|
82
81
|
response = self.build_response(parsed_query, response_spec)
|
83
82
|
|
84
|
-
|
83
|
+
logging.debug("Sending DNS response...")
|
85
84
|
self.send_dns_response(client_sock, response)
|
86
|
-
|
85
|
+
logging.info("DNS response sent successfully.")
|
87
86
|
|
88
87
|
return {
|
89
88
|
"received": parsed_query.toZone(),
|
@@ -91,11 +90,9 @@ class TCPDNSSocketServer:
|
|
91
90
|
}, EventNames.REQUEST_RECEIVED.name
|
92
91
|
|
93
92
|
finally:
|
94
|
-
|
93
|
+
logging.debug("Closing client socket")
|
95
94
|
client_sock.close()
|
96
95
|
|
97
|
-
|
98
|
-
|
99
96
|
def build_response(self, query_record, response_spec=None):
|
100
97
|
qname = str(query_record.q.qname)
|
101
98
|
qtype = query_record.q.qtype
|
@@ -124,33 +121,32 @@ class TCPDNSSocketServer:
|
|
124
121
|
|
125
122
|
handler = handlers.get(response_type)
|
126
123
|
if not handler:
|
127
|
-
|
124
|
+
logging.warning(f"No handler for response_type: {response_type}")
|
128
125
|
return query_record.reply()
|
129
126
|
|
130
127
|
reverse_qtype = {QTYPE[k]: k for k in QTYPE if isinstance(k, int)}
|
131
128
|
rtype = reverse_qtype.get(response_type)
|
132
129
|
if rtype is None:
|
133
|
-
|
130
|
+
logging.warning(f"Unsupported response_type: {response_type}")
|
134
131
|
return query_record.reply()
|
135
132
|
|
136
133
|
try:
|
137
|
-
|
134
|
+
logging.debug(f"Calling handler for type {response_type}")
|
138
135
|
rdata = handler()
|
139
|
-
|
136
|
+
logging.debug(f"Handler produced rdata: {rdata}")
|
140
137
|
except Exception as e:
|
141
|
-
|
138
|
+
logging.error(f"Handler for {response_type} failed: {e}", exc_info=True)
|
142
139
|
return query_record.reply()
|
143
140
|
|
144
141
|
response.add_answer(RR(rname=response_qname, rtype=rtype, rclass=1, ttl=60, rdata=rdata))
|
145
142
|
return response
|
146
143
|
|
147
|
-
|
148
|
-
|
149
144
|
def _parse_srv(self, value):
|
150
145
|
try:
|
151
146
|
target, port, priority, weight = value.split(",")
|
152
147
|
return SRV(int(priority), int(weight), int(port), target)
|
153
148
|
except Exception:
|
149
|
+
logging.warning(f"Failed to parse SRV value: '{value}', using default fallback")
|
154
150
|
return SRV(0, 0, 80, "service.example.com")
|
155
151
|
|
156
152
|
def send_dns_response(self, client_sock, dns_record):
|
@@ -160,11 +156,12 @@ class TCPDNSSocketServer:
|
|
160
156
|
client_sock.sendall(length_prefix + response_bytes)
|
161
157
|
return EventNames.RESPONSE_SENT.name
|
162
158
|
except Exception as e:
|
163
|
-
|
159
|
+
logging.error(f"Failed to send DNS response: {e}", exc_info=True)
|
164
160
|
return EventNames.ERROR.name
|
165
161
|
|
166
162
|
def close(self):
|
167
163
|
if self.sock:
|
168
164
|
self.sock.close()
|
169
165
|
self.sock = None
|
170
|
-
|
166
|
+
logging.info("TCP socket closed")
|
167
|
+
return EventNames.CONNECTION_ENDING.name
|
@@ -0,0 +1,120 @@
|
|
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
|
+
import logging
|
8
|
+
|
9
|
+
class UDPDNSSocketServer:
|
10
|
+
def __init__(self):
|
11
|
+
self.sock = None
|
12
|
+
|
13
|
+
def start(self, port):
|
14
|
+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
15
|
+
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
16
|
+
self.sock.bind(('', port))
|
17
|
+
logging.info(f"UDP DNS server started on port {port}")
|
18
|
+
return EventNames.SERVER_STARTED.name, f"UDP DNS server started on port {port}"
|
19
|
+
|
20
|
+
def wait_for_query(self, timeout, response_spec=None):
|
21
|
+
self.sock.settimeout(timeout)
|
22
|
+
logging.info(f"Waiting for UDP packets on port {self.sock.getsockname()[1]} with timeout {timeout} seconds")
|
23
|
+
|
24
|
+
try:
|
25
|
+
data, client_addr = self.sock.recvfrom(512) # max size of a DNS UDP packet
|
26
|
+
logging.debug(f"Received {len(data)} bytes from {client_addr}")
|
27
|
+
|
28
|
+
logging.debug("Parsing DNS query...")
|
29
|
+
parsed_query = DNSRecord.parse(data)
|
30
|
+
logging.info(f"Parsed query:\n{parsed_query.toZone()}")
|
31
|
+
|
32
|
+
logging.debug("Building DNS response...")
|
33
|
+
response = self.build_response(parsed_query, response_spec)
|
34
|
+
|
35
|
+
logging.debug("Sending DNS response...")
|
36
|
+
self.send_dns_response(client_addr, response)
|
37
|
+
logging.info("DNS response sent successfully.")
|
38
|
+
|
39
|
+
return {
|
40
|
+
"received": parsed_query.toZone(),
|
41
|
+
"client_address": client_addr
|
42
|
+
}, EventNames.REQUEST_RECEIVED.name
|
43
|
+
|
44
|
+
except socket.timeout:
|
45
|
+
logging.warning("Timeout reached with no packet received.")
|
46
|
+
return {"received": None}, EventNames.TIMEOUT.name
|
47
|
+
except Exception as e:
|
48
|
+
logging.error(f"Error while handling DNS query: {e}", exc_info=True)
|
49
|
+
return {"received": None}, EventNames.ERROR.name
|
50
|
+
|
51
|
+
def build_response(self, query_record, response_spec=None):
|
52
|
+
qname = str(query_record.q.qname)
|
53
|
+
qtype = query_record.q.qtype
|
54
|
+
response_qname = response_spec.get("qname") if response_spec and response_spec.get("qname") else qname
|
55
|
+
response_type = response_spec.get("type").upper() if response_spec and response_spec.get("type") else QTYPE[qtype].name
|
56
|
+
response_value = response_spec.get("value") if response_spec else None
|
57
|
+
|
58
|
+
response = query_record.reply()
|
59
|
+
|
60
|
+
handlers = {
|
61
|
+
"A": lambda: A(response_value or "127.0.0.1"),
|
62
|
+
"CNAME": lambda: CNAME(response_value or response_qname),
|
63
|
+
"MX": lambda: MX(response_value or f"mail.{response_qname}", preference=10),
|
64
|
+
"TXT": lambda: TXT(response_value or f"dummy record for {response_qname}"),
|
65
|
+
"NS": lambda: NS(response_value or f"ns1.{response_qname}"),
|
66
|
+
"SOA": lambda: SOA(response_value or f"ns1.{response_qname}", f"admin.{response_qname}", (2024051801, 3600, 3600, 3600, 3600)),
|
67
|
+
"PTR": lambda: PTR(response_value or f"ptr.{response_qname}"),
|
68
|
+
"AAAA": lambda: AAAA(response_value or "::1"),
|
69
|
+
"SRV": lambda: self._parse_srv(response_value or f"service.{response_qname},80,0,0"),
|
70
|
+
"DS": lambda: DS(12345, 1, 1, bytes(response_value or f"abcdef{response_qname}", 'utf-8')),
|
71
|
+
"RRSIG": lambda: RRSIG(1, 1, 0, 3600, 0, 0, 0, response_value or f"signer.{response_qname}", b"signature"),
|
72
|
+
"NSEC": lambda: NSEC(response_value or f"next.{response_qname}", []),
|
73
|
+
"DNSKEY": lambda: DNSKEY(256, 3, 8, bytes(response_value or f"publickey{response_qname}", 'utf-8')),
|
74
|
+
"ANY": lambda: A(response_value or "127.0.0.1")
|
75
|
+
}
|
76
|
+
|
77
|
+
handler = handlers.get(response_type)
|
78
|
+
if not handler:
|
79
|
+
logging.warning(f"No handler for response_type: {response_type}")
|
80
|
+
return query_record.reply()
|
81
|
+
|
82
|
+
reverse_qtype = {QTYPE[k]: k for k in QTYPE if isinstance(k, int)}
|
83
|
+
rtype = reverse_qtype.get(response_type)
|
84
|
+
if rtype is None:
|
85
|
+
logging.warning(f"Unsupported response_type: {response_type}")
|
86
|
+
return query_record.reply()
|
87
|
+
|
88
|
+
try:
|
89
|
+
logging.debug(f"Calling handler for type {response_type}")
|
90
|
+
rdata = handler()
|
91
|
+
logging.debug(f"Handler produced rdata: {rdata}")
|
92
|
+
except Exception as e:
|
93
|
+
logging.error(f"Handler for {response_type} failed: {e}", exc_info=True)
|
94
|
+
return query_record.reply()
|
95
|
+
|
96
|
+
response.add_answer(RR(rname=response_qname, rtype=rtype, rclass=1, ttl=60, rdata=rdata))
|
97
|
+
return response
|
98
|
+
|
99
|
+
def _parse_srv(self, value):
|
100
|
+
try:
|
101
|
+
target, port, priority, weight = value.split(",")
|
102
|
+
return SRV(int(priority), int(weight), int(port), target)
|
103
|
+
except Exception:
|
104
|
+
logging.warning(f"Failed to parse SRV value: '{value}', using default fallback")
|
105
|
+
return SRV(0, 0, 80, "service.example.com")
|
106
|
+
|
107
|
+
def send_dns_response(self, client_addr, dns_record):
|
108
|
+
try:
|
109
|
+
self.sock.sendto(dns_record.pack(), client_addr)
|
110
|
+
return EventNames.RESPONSE_SENT.name
|
111
|
+
except Exception as e:
|
112
|
+
logging.error(f"Failed to send DNS response to {client_addr}: {e}", exc_info=True)
|
113
|
+
return EventNames.ERROR.name
|
114
|
+
|
115
|
+
def close(self):
|
116
|
+
if self.sock:
|
117
|
+
self.sock.close()
|
118
|
+
self.sock = None
|
119
|
+
logging.info("UDP socket closed")
|
120
|
+
return EventNames.CONNECTION_ENDING.name
|
@@ -5,6 +5,7 @@ import socket
|
|
5
5
|
import select
|
6
6
|
import ssl
|
7
7
|
import struct
|
8
|
+
import logging
|
8
9
|
from dnslib import DNSRecord, QTYPE
|
9
10
|
|
10
11
|
|
@@ -399,7 +400,7 @@ def group_ports(ports):
|
|
399
400
|
def send_tcp_dns_query(server_ip, server_port, domain, query_type="A"):
|
400
401
|
dnsatypes = {
|
401
402
|
1: "A", 2: "NS", 5: "CNAME", 6: "SOA", 12: "PTR", 15: "MX",
|
402
|
-
16: "TXT", 28: "AAAA", 33: "SRV",
|
403
|
+
16: "TXT", 28: "AAAA", 33: "SRV", 43: "DS",
|
403
404
|
46: "RRSIG", 47: "NSEC", 48: "DNSKEY", 255: "ANY"
|
404
405
|
}
|
405
406
|
|
@@ -408,46 +409,43 @@ def send_tcp_dns_query(server_ip, server_port, domain, query_type="A"):
|
|
408
409
|
lookup_by_name = {v.upper(): k for k, v in dnsatypes.items()}
|
409
410
|
query_type_str = str(query_type).strip().upper()
|
410
411
|
|
411
|
-
|
412
|
+
logging.debug(f"Received query_type: {query_type_str}")
|
412
413
|
|
413
|
-
# Validate query type
|
414
414
|
if query_type_str.isdigit():
|
415
415
|
qtype = int(query_type_str)
|
416
416
|
if qtype not in dnsatypes:
|
417
417
|
result["error"] = f"Unsupported numeric query type: {query_type}.\nSupported types are: {supported_types}"
|
418
|
-
|
418
|
+
logging.error(result["error"])
|
419
419
|
return result
|
420
420
|
else:
|
421
421
|
qtype = lookup_by_name.get(query_type_str)
|
422
422
|
if qtype is None:
|
423
423
|
result["error"] = f"Unsupported string query type: {query_type}.\nSupported types are: {supported_types}"
|
424
|
-
|
424
|
+
logging.error(result["error"])
|
425
425
|
return result
|
426
426
|
|
427
427
|
try:
|
428
|
-
|
429
|
-
dns_query = DNSRecord.question(domain, qtype=
|
428
|
+
logging.debug(f"Building DNS query for domain {domain} with type {query_type_str} ({qtype})")
|
429
|
+
dns_query = DNSRecord.question(domain, qtype=dnsatypes[qtype])
|
430
430
|
dns_query.header.id = random.randint(0, 65535)
|
431
431
|
query_packet = dns_query.pack()
|
432
432
|
result["query"] = dns_query.toZone()
|
433
433
|
|
434
|
-
|
434
|
+
logging.debug("Opening TCP socket")
|
435
435
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
436
|
-
sock.bind(('', 0))
|
437
436
|
sock.settimeout(2)
|
438
|
-
|
439
|
-
print(f"[Debug] Connecting to {server_ip}:{server_port}")
|
437
|
+
logging.debug(f"Connecting to {server_ip}:{server_port}")
|
440
438
|
sock.connect((server_ip, server_port))
|
441
439
|
|
442
440
|
length_prefix = struct.pack("!H", len(query_packet))
|
443
|
-
|
441
|
+
logging.debug("Sending DNS query")
|
444
442
|
sock.sendall(length_prefix + query_packet)
|
445
443
|
|
446
|
-
|
444
|
+
logging.debug("Waiting for response")
|
447
445
|
length_data = sock.recv(2)
|
448
446
|
if len(length_data) < 2:
|
449
447
|
result["error"] = "Incomplete length prefix"
|
450
|
-
|
448
|
+
logging.error(result["error"])
|
451
449
|
return result
|
452
450
|
|
453
451
|
expected_length = struct.unpack("!H", length_data)[0]
|
@@ -458,12 +456,65 @@ def send_tcp_dns_query(server_ip, server_port, domain, query_type="A"):
|
|
458
456
|
break
|
459
457
|
response_data += chunk
|
460
458
|
|
461
|
-
|
459
|
+
logging.debug("Received response data")
|
460
|
+
parsed_response = DNSRecord.parse(response_data)
|
461
|
+
result["response"] = parsed_response.to_dict()
|
462
|
+
return result
|
463
|
+
|
464
|
+
except Exception as e:
|
465
|
+
result["error"] = f"Exception occurred: {str(e)}"
|
466
|
+
logging.error("An error occurred while sending the TCP DNS query")
|
467
|
+
return result
|
468
|
+
|
469
|
+
def send_udp_dns_query(server_ip, server_port, domain, query_type="A"):
|
470
|
+
dnsatypes = {
|
471
|
+
1: "A", 2: "NS", 5: "CNAME", 6: "SOA", 12: "PTR", 15: "MX",
|
472
|
+
16: "TXT", 28: "AAAA", 33: "SRV", 43: "DS",
|
473
|
+
46: "RRSIG", 47: "NSEC", 48: "DNSKEY", 255: "ANY"
|
474
|
+
}
|
475
|
+
|
476
|
+
result = {"query": None, "response": None, "error": None}
|
477
|
+
supported_types = ', '.join([f"{v}({k})" for k, v in dnsatypes.items()])
|
478
|
+
lookup_by_name = {v.upper(): k for k, v in dnsatypes.items()}
|
479
|
+
query_type_str = str(query_type).strip().upper()
|
480
|
+
|
481
|
+
logging.debug(f"Received query_type: {query_type_str}")
|
482
|
+
|
483
|
+
if query_type_str.isdigit():
|
484
|
+
qtype = int(query_type_str)
|
485
|
+
if qtype not in dnsatypes:
|
486
|
+
result["error"] = f"Unsupported numeric query type: {query_type}.\nSupported types are: {supported_types}"
|
487
|
+
logging.error(result["error"])
|
488
|
+
return result
|
489
|
+
else:
|
490
|
+
qtype = lookup_by_name.get(query_type_str)
|
491
|
+
if qtype is None:
|
492
|
+
result["error"] = f"Unsupported string query type: {query_type}.\nSupported types are: {supported_types}"
|
493
|
+
logging.error(result["error"])
|
494
|
+
return result
|
495
|
+
|
496
|
+
try:
|
497
|
+
logging.debug(f"Building DNS query for domain {domain} with type {query_type_str} ({qtype})")
|
498
|
+
dns_query = DNSRecord.question(domain, qtype=dnsatypes[qtype])
|
499
|
+
dns_query.header.id = random.randint(0, 65535)
|
500
|
+
query_packet = dns_query.pack()
|
501
|
+
result["query"] = dns_query.toZone()
|
502
|
+
|
503
|
+
logging.debug("Opening UDP socket")
|
504
|
+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
|
505
|
+
sock.settimeout(2)
|
506
|
+
logging.debug(f"Sending UDP DNS query to {server_ip}:{server_port}")
|
507
|
+
sock.sendto(query_packet, (server_ip, server_port))
|
508
|
+
|
509
|
+
logging.debug("Waiting for UDP response")
|
510
|
+
response_data, _ = sock.recvfrom(4096)
|
511
|
+
|
512
|
+
logging.debug("Received UDP response")
|
462
513
|
parsed_response = DNSRecord.parse(response_data)
|
463
514
|
result["response"] = parsed_response.to_dict()
|
464
515
|
return result
|
465
516
|
|
466
517
|
except Exception as e:
|
467
518
|
result["error"] = f"Exception occurred: {str(e)}"
|
468
|
-
|
519
|
+
logging.error("An error occurred while sending the UDP DNS query")
|
469
520
|
return result
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: nopasaran
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.101
|
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
|
@@ -65,6 +65,8 @@ nopasaran/primitives/action_primitives/tcp_dns_response_primitives.py
|
|
65
65
|
nopasaran/primitives/action_primitives/tcp_primitives.py
|
66
66
|
nopasaran/primitives/action_primitives/timing_primitives.py
|
67
67
|
nopasaran/primitives/action_primitives/tls_primitives.py
|
68
|
+
nopasaran/primitives/action_primitives/udp_dns_request_primitives.py
|
69
|
+
nopasaran/primitives/action_primitives/udp_dns_response_primitives.py
|
68
70
|
nopasaran/primitives/action_primitives/udp_primitives.py
|
69
71
|
nopasaran/primitives/condition_primitives/__init__.py
|
70
72
|
nopasaran/primitives/condition_primitives/condition_primitives.py
|
@@ -84,4 +86,5 @@ nopasaran/tools/http_2_socket_client.py
|
|
84
86
|
nopasaran/tools/http_2_socket_server.py
|
85
87
|
nopasaran/tools/https_1_socket_server.py
|
86
88
|
nopasaran/tools/tcp_dns_socket_server.py
|
89
|
+
nopasaran/tools/udp_dns_socket_server.py
|
87
90
|
tests/__init__.py
|
@@ -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.
|
16
|
+
version='0.2.101',
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/data_manipulation.py
RENAMED
File without changes
|
{nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/dns_primitives.py
RENAMED
File without changes
|
{nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/event_primitives.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/icmp_primitives.py
RENAMED
File without changes
|
{nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/io_primitives.py
RENAMED
File without changes
|
{nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/ip_primitives.py
RENAMED
File without changes
|
File without changes
|
{nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/probing_primitives.py
RENAMED
File without changes
|
{nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/replay_primitives.py
RENAMED
File without changes
|
File without changes
|
{nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/signaling_primitive.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/tcp_primitives.py
RENAMED
File without changes
|
{nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/timing_primitives.py
RENAMED
File without changes
|
{nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/tls_primitives.py
RENAMED
File without changes
|
{nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/action_primitives/udp_primitives.py
RENAMED
File without changes
|
{nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/condition_primitives/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{nopasaran-0.2.99 → nopasaran-0.2.101}/nopasaran/primitives/transition_primitives/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|