Skip to content

Commit

Permalink
Replace ping for internet checking
Browse files Browse the repository at this point in the history
Signed-off-by: Marius Sincovici <marius.sincovici@nordsec.com>
  • Loading branch information
mariusSincovici committed Jan 16, 2025
1 parent 0cdd0c7 commit ba8c9f9
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 85 deletions.
6 changes: 3 additions & 3 deletions test/qa/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,17 @@ def _check_connection_to_ip(ip_address, stop_event):
print("Start _check_connection_to_ip")
while not stop_event.is_set():
try:
"icmp_seq=" in sh.ping("-c", "3", "-W", "3", ip_address) # noqa: B015
network.is_internet_reachable(ip_address=ip_address, retry=1)
except Exception as e: # noqa: BLE001
print(f"_check_connection_to_ip: IN-PING {ip_address} FAILURE: {e}.")
print(f"_check_connection_to_ip: FAILURE for {ip_address}: {e}.")
stop_event.wait(_CHECK_FREQUENCY)


def _check_connection_to_ip_outside_vpn(ip_address, stop_event):
print("Start _check_connection_to_ip_outside_vpn")
while not stop_event.is_set():
try:
"icmp_seq=" in sh.sudo.ping("-c", "3", "-W", "3", "-m", "57841", ip_address) # noqa: B015
network.is_internet_reachable_outside_vpn(ip_address=ip_address, retry=1)
except Exception as e: # noqa: BLE001
print(f"~~~_check_connection_to_ip_outside_vpn: {ip_address} FAILURE: {e}.")
stop_event.wait(_CHECK_FREQUENCY)
Expand Down
133 changes: 57 additions & 76 deletions test/qa/lib/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from threading import Thread

import dns.resolver
import pytest
import requests
import sh
import urllib

from . import daemon, firewall, info, logging, settings

Expand All @@ -24,89 +24,95 @@
FWMARK = 57841

class PacketCaptureThread(Thread):
def __init__(self, connection_settings):
def __init__(self, connection_settings, duration: int):
Thread.__init__(self)
self.packets_captured: int = -1
self.connection_settings = connection_settings
self.packets = ""
self.duration=duration

def run(self):
self.packets_captured = _capture_packets(self.connection_settings)
self.packets_captured = self._capture_packets()


def _capture_packets(connection_settings: (str, str, str)) -> int:
technology = connection_settings[0]
protocol = connection_settings[1]
obfuscated = connection_settings[2]
def _capture_packets(self) -> str:
technology = self.connection_settings[0]
protocol = self.connection_settings[1]
obfuscated = self.connection_settings[2]

# Collect information needed for tshark filter
server_ip = settings.get_server_ip()
# Collect information needed for tshark filter
server_ip = settings.get_server_ip()

# Choose traffic filter according to information collected above
if technology == "nordlynx" and protocol == "" and obfuscated == "":
traffic_filter = TSHARK_FILTER_NORDLYNX % server_ip
elif technology == "openvpn" and protocol == "udp" and obfuscated == "off":
traffic_filter = TSHARK_FILTER_UDP % server_ip
elif technology == "openvpn" and protocol == "tcp" and obfuscated == "off":
traffic_filter = TSHARK_FILTER_TCP % server_ip
elif technology == "openvpn" and protocol == "udp" and obfuscated == "on":
traffic_filter = TSHARK_FILTER_UDP_OBFUSCATED % server_ip
elif technology == "openvpn" and protocol == "tcp" and obfuscated == "on":
traffic_filter = TSHARK_FILTER_TCP_OBFUSCATED % server_ip
# Choose traffic filter according to information collected above
if technology == "nordlynx" and protocol == "" and obfuscated == "":
traffic_filter = TSHARK_FILTER_NORDLYNX % server_ip
elif technology == "openvpn" and protocol == "udp" and obfuscated == "off":
traffic_filter = TSHARK_FILTER_UDP % server_ip
elif technology == "openvpn" and protocol == "tcp" and obfuscated == "off":
traffic_filter = TSHARK_FILTER_TCP % server_ip
elif technology == "openvpn" and protocol == "udp" and obfuscated == "on":
traffic_filter = TSHARK_FILTER_UDP_OBFUSCATED % server_ip
elif technology == "openvpn" and protocol == "tcp" and obfuscated == "on":
traffic_filter = TSHARK_FILTER_TCP_OBFUSCATED % server_ip

# If enough packets are captured, do not wait the duration time, exit early, show compact output
tshark_result: str = sh.tshark("-i", "any", "-T", "fields", "-e", "ip.src", "-e", "ip.dst", "-a", "duration:3", "-a", "packets:1", "-f", traffic_filter)
#tshark_result: str = os.popen(f"sudo tshark -i any -T fields -e ip.src -e ip.dst -a duration:3 -a packets:1 -f {traffic_filter}").read()
command = ["-i", "any", "-a", f"duration:{self.duration}"]
if technology != "" :
command += "-f", traffic_filter

packets = tshark_result.strip().splitlines()
tshark_result: str = sh.tshark(command)

return len(packets)
return tshark_result.strip()


def capture_traffic(connection_settings) -> int:
def capture_traffic(connection_settings, duration: int=5) -> str:
"""Returns count of captured packets."""

# We try to capture packets using other thread
t_connect = PacketCaptureThread(connection_settings)
t_connect = PacketCaptureThread(connection_settings, duration)
t_connect.start()

sh.ping("-c", "2", "-w", "2", "1.1.1.1")
try:
# generate some traffic
generate_traffic()
except Exception as e: # noqa: BLE001
logging.log(f"capture_traffic exception: {e}")
logging.log(t_connect.packets)

t_connect.join()

return t_connect.packets_captured
return t_connect.packets


def _check_connectivity(host, port=80, timeout=2) -> bool:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((host, port))
sock.close()
return result == 0
finally:
sock.close()

return False

def _is_internet_reachable(retry=5) -> bool:
def is_internet_reachable(ip_address="1.1.1.1", port=443, retry=5) -> bool:
"""Returns True when remote host is reachable by its public IP."""
i = 0
while i < retry:
try:
#return "icmp_seq=" in sh.ping("-c", "1", "-w", "1", "1.1.1.1")
return _check_connectivity("1.1.1.1", 53)
if _check_connectivity(ip_address, port):
return True
except sh.ErrorReturnCode:
time.sleep(1)
i += 1
return False


def _is_internet_reachable_outside_vpn(retry=5) -> bool:
def is_internet_reachable_outside_vpn(ip_address="1.1.1.1", retry=5) -> bool:
"""Returns True when remote host is reachable by its public IP outside VPN tunnel."""
i = 0
while i < retry:
try:
return "icmp_seq=" in sh.sudo.ping("-c", "1", "-m", f"{FWMARK}", "-w", "1", "1.1.1.1")
# ping can remain since it is executed with FWMARK
return "icmp_seq=" in sh.sudo.ping("-c", "1", "-m", f"{FWMARK}", "-w", "1", ip_address)
except sh.ErrorReturnCode:
time.sleep(1)
i += 1
Expand All @@ -127,47 +133,17 @@ def _is_ipv6_internet_reachable(retry=5) -> bool:
raise last


def _is_dns_resolvable(retry=5) -> bool:
def _is_dns_resolvable(domain = "nordvpn.com", retry=5) -> bool:
"""Returns True when domain resolution is working."""
i = 0
while i < retry:
try:
# @TODO gitlab docker runner has public ipv6, but no connectivity. remove -4 once fixed
#return "icmp_seq=" in sh.ping("-4", "-c", "1", "-w", "1", "nordvpn.com")
return _check_connectivity("nordvpn.com", 80)
except sh.ErrorReturnCode:
time.sleep(1)
i += 1
return False


def _is_dns_resolvable_outside_vpn(retry: int = 5) -> bool:
"""Returns True when domain resolution outside vpn is not working."""
i = 0
while i < retry:
try:
# @TODO gitlab docker runner has public ipv6, but no connectivity. remove -4 once fixed
# @TODO need dns query to go arround vpn tunnel, here with regular ping it does not
return "icmp_seq=" in sh.ping("-4", "-c", "1", "-m", f"{FWMARK}", "-w", "1", "nordvpn.com")
except sh.ErrorReturnCode:
time.sleep(1)
i += 1
return False


def _is_dns_not_resolvable(retry: int = 5) -> bool:
"""Returns True when domain resolution is not working."""
i = 0
while i < retry:
try:
with pytest.raises((dns.resolver.NoNameservers, dns.resolver.LifetimeTimeout)):
resolver = dns.resolver.Resolver()
resolver.lifetime = 1
resolver.resolve("nordvpn.com")
resolver = dns.resolver.Resolver()
resolver.resolve(domain, 'A', lifetime=5)
return True
except: # noqa: E722
except Exception as e: # noqa: BLE001
print(f"_is_dns_resolvable: DNS {domain} FAILURE. Error: {e}")
time.sleep(1)
i += 1
return False


Expand All @@ -179,14 +155,13 @@ def is_not_available(retry=5) -> bool:

# If assert below fails, and you are running Kill Switch tests on your machine, inside of Docker,
# set DNS in resolv.conf of your system to anything else but 127.0.0.53
return not _is_internet_reachable(retry) and _is_dns_not_resolvable(retry)
return not is_internet_reachable(retry=retry) and not _is_dns_resolvable(retry=retry)


def is_available(retry=5) -> bool:
"""Returns True when network access is available or throws AssertionError otherwise."""
assert _is_internet_reachable_outside_vpn(retry)
assert _is_internet_reachable(retry)
#assert _is_dns_resolvable_outside_vpn(retry)
assert is_internet_reachable_outside_vpn(retry)
assert is_internet_reachable(retry=retry)
assert _is_dns_resolvable(retry)
return True

Expand Down Expand Up @@ -253,7 +228,7 @@ def stop() -> str:
else:
sh.sudo.ip.link.set.dev.eth0.down()

logging.log("stopping network")
logging.log(f"stopping network {default_gateway}")
assert is_not_available()
logging.log(info.collect())
return default_gateway
Expand All @@ -279,3 +254,9 @@ def unblock():
def get_external_device_ip() -> str:
"""Returns external device IP."""
return requests.get(API_EXTERNAL_IP, timeout=5).json().get("ip")


def generate_traffic(address = "https://1.1.1.1", timeout=1, repeat=1):
for _ in range(0, max(repeat, 1)):
req = urllib.request.Request(address, method="HEAD")
urllib.request.urlopen(req, timeout=timeout)
11 changes: 5 additions & 6 deletions test/qa/test_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ def connect_base_test(connection_settings, group=(), name="", hostname=""):
print(output)

assert lib.is_connect_successful(output, name, hostname)

packets_captured = network.capture_traffic(connection_settings)

assert network.is_connected()
assert packets_captured > 0

print(connection_settings)
packets_captured = network.capture_traffic(connection_settings).splitlines()
assert len(packets_captured) > 0


def disconnect_base_test():
Expand Down Expand Up @@ -377,8 +377,7 @@ def test_status_connected(tech, proto, obfuscated):

connect_time = time.monotonic()

time.sleep(5)
sh.ping("-c", "1", "-w", "1", "1.1.1.1")
network.generate_traffic(repeat=5)

status_info = daemon.get_status_data()
status_time = time.monotonic()
Expand Down

0 comments on commit ba8c9f9

Please sign in to comment.