diff --git a/ci/docker/tester/Dockerfile b/ci/docker/tester/Dockerfile index fc4e7436..8fdfb258 100644 --- a/ci/docker/tester/Dockerfile +++ b/ci/docker/tester/Dockerfile @@ -15,6 +15,10 @@ RUN apt-get update \ && apt-get install -y python3 python3-pip \ # install python packages for tests && python3 -m pip install -r /tmp/requirements.txt \ + # install thsark + DEBIAN_FRONTEND=noninteractive apt-get install -y tshark \ + # make sure, that Docker does not hang during installation, when we get TUI screen + yes yes | DEBIAN_FRONTEND=teletype dpkg-reconfigure wireshark-common \ # cleanup && apt-get clean @@ -22,6 +26,7 @@ ARG USER_ID=1000 ARG GROUP_ID=1000 RUN groupadd --system nordvpn && groupadd -g ${GROUP_ID} qa && useradd -l -m -u ${USER_ID} -g qa -G nordvpn qa && echo "qa ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers +RUN usermod -a -G wireshark qa USER qa diff --git a/ci/jobs/test.yml b/ci/jobs/test.yml index 716127a1..7b907b2e 100644 --- a/ci/jobs/test.yml +++ b/ci/jobs/test.yml @@ -5,7 +5,7 @@ include: /ci/jobs/.cond.yml - linux - infra-docker stage: test - image: ghcr.io/nordsecurity/nordvpn-linux/tester:1.0.4 + image: ghcr.io/nordsecurity/nordvpn-linux/tester:1.1.0 rules: - !reference [.cond/on-main, rules] - !reference [.cond/on-version-tag, rules] @@ -61,10 +61,10 @@ test/deb: - TEST: [connect6, dns, dns6, killswitch, login, misc, routing, settings, allowlist] test/deb-connect1: extends: .test_job_template - script: $CI_PROJECT_DIR/ci/test_deb.sh connect 'test_quick_connect or test_double_quick_connect_only or test_connect_to_absent_server or test_mistype_connect or test_connect_to_invalid_group or test_connect_to_group_flag_standard or test_connect_to_group_flag_additional or test_connect_without_internet_access' + script: $CI_PROJECT_DIR/ci/test_deb.sh connect 'test_quick_connect or test_double_quick_connect_only or test_connect_to_absent_server or test_mistype_connect or test_connect_to_invalid_group or test_connect_to_group_flag_standard or test_connect_to_group_flag_additional or test_connect_without_internet_access or test_connect_to_city or test_connect_to_code_country' test/deb-connect2: extends: .test_job_template - script: $CI_PROJECT_DIR/ci/test_deb.sh connect 'test_connect_to_random_server_by_name or test_connection_recovers_from_network_restart or test_double_quick_connect_disconnect or test_connect_to_city or test_connect_to_country or test_connect_to_code_country or test_connect_to_group_standard or test_connect_to_group_additional' + script: $CI_PROJECT_DIR/ci/test_deb.sh connect 'test_connect_to_random_server_by_name or test_connection_recovers_from_network_restart or test_double_quick_connect_disconnect or test_connect_to_country or test_connect_to_group_standard or test_connect_to_group_additional' test/deb-combinations: extends: .test_job_template script: $CI_PROJECT_DIR/ci/test_deb.sh combinations $PATTERN @@ -90,7 +90,7 @@ test/deb-firewall6: - $CI_PROJECT_DIR/ci/test_deb.sh firewall6 test/deb-manual: stage: test - image: ghcr.io/nordsecurity/nordvpn-linux/tester:1.0.4 + image: ghcr.io/nordsecurity/nordvpn-linux/tester:1.1.0 rules: # TODO: run automatically after meshnet release - !reference [.cond/on-click, rules] @@ -115,7 +115,7 @@ test/deb-meshnet: script: $CI_PROJECT_DIR/ci/test_deb.sh meshnet test/deb-fileshare: stage: test - image: ghcr.io/nordsecurity/nordvpn-linux/tester:1.0.4 + image: ghcr.io/nordsecurity/nordvpn-linux/tester:1.1.0 rules: - !reference [.cond/on-main, rules] - !reference [.cond/on-version-tag, rules] diff --git a/magefiles/mage.go b/magefiles/mage.go index 691042e4..1b4d1867 100644 --- a/magefiles/mage.go +++ b/magefiles/mage.go @@ -21,7 +21,7 @@ const ( imagePackager = registryPrefix + "packager:1.0.1" imageProtobufGenerator = registryPrefix + "generator:1.0.1" imageScanner = registryPrefix + "scanner:1.0.0" - imageTester = registryPrefix + "tester:1.0.4" + imageTester = registryPrefix + "tester:1.1.0" imageQAPeer = registryPrefix + "qa-peer:1.0.2" imageLinter = registryPrefix + "linter:1.0.0" imageRuster = registryPrefix + "ruster:1.0.1" diff --git a/test/qa/lib/daemon.py b/test/qa/lib/daemon.py index 718c2dfc..2299d783 100644 --- a/test/qa/lib/daemon.py +++ b/test/qa/lib/daemon.py @@ -151,3 +151,21 @@ def is_peer_running(ssh_client: ssh.Ssh) -> bool: return False else: return True + + +def get_server_ip(): + return sh.nordvpn.status().split('\n')[2].replace('IP: ', '') + + +def get_current_connection_protocol(): + current_protocol = sh.nordvpn("settings").split('\n')[1] + + if "UDP" in current_protocol: + return "udp" + elif "TCP" in current_protocol: + return "tcp" + else: + return "nordlynx" + +def get_is_obfuscated(): + return "enabled" in sh.nordvpn("settings").split('\n')[8] \ No newline at end of file diff --git a/test/qa/lib/info.py b/test/qa/lib/info.py index f53b72fb..70fe7644 100644 --- a/test/qa/lib/info.py +++ b/test/qa/lib/info.py @@ -8,7 +8,9 @@ def collect(): rounting_info = sh.sudo.ip.route() firewall_info = sh.sudo.iptables("-S") nameserver_info = sh.sudo.cat("/etc/resolv.conf") - processes = sh.ps("-ef") + + # without `ww` we cannot see full process lines, as it is cut off early + processes = sh.ps("-ef", "ww") return "\n".join( [ diff --git a/test/qa/test_connect.py b/test/qa/test_connect.py index 5e441c81..9d51d6a2 100644 --- a/test/qa/test_connect.py +++ b/test/qa/test_connect.py @@ -8,8 +8,11 @@ ) import lib import pytest +import queue import sh import socket +import threading +import time import timeout_decorator @@ -32,12 +35,66 @@ def teardown_function(function): logging.log() +def capture_traffic() -> int: + """ + Captures traffic that goes to VPN server + :return: int - returns count of captured packets + """ + + # Collect information needed for tshark filter + server_ip = daemon.get_server_ip() + protocol = daemon.get_current_connection_protocol() + obfuscated = daemon.get_is_obfuscated() + + # Choose traffic filter according to information collected above + if protocol == "nordlynx": + traffic_filter = "(udp port 51820) and (ip dst {})".format(server_ip) + elif protocol == "udp" and not obfuscated: + traffic_filter = "(udp port 1194) and (ip dst {})".format(server_ip) + elif protocol == "tcp" and not obfuscated: + traffic_filter = "(tcp port 443) and (ip dst {})".format(server_ip) + elif protocol == "udp" and obfuscated: + traffic_filter = "udp and (port not 1194) and (ip dst {})".format(server_ip) + elif protocol == "tcp" and obfuscated: + traffic_filter = "tcp and (port not 443) and (ip dst {})".format(server_ip) + + # Actual capture + # If 2 packets were already captured, do not wait for 3 seconds + # Show compact output about packets + tshark_result = sh.tshark("-i", "any", "-T", "fields", "-e", "ip.src", "-e", "ip.dst", "-a", "duration:3", "-a", "packets:2", "-f", traffic_filter) + + packets = tshark_result.replace("\t", " -> ") + packets = tshark_result.split("\n") + + logging.log("PACKETS_CAPTURED: " + str(packets)) + + # If no packets were captured, `packets` value should be 0 + return len(packets) - 1 + + def connect_base_test(group=[], name="", hostname=""): output = sh.nordvpn.connect(group) print(output) + + # Start capturing packets + packet_capture_thread_queue = queue.Queue() + packet_capture_thread_lambda = lambda: packet_capture_thread_queue.put(capture_traffic()) + packet_capture_thread = threading.Thread(target=packet_capture_thread_lambda) + packet_capture_thread.start() + + # We need to make sure, that packets are being sent out only after + # tshark starts, and not earlier, so we wait for one second. + time.sleep(1) assert lib.is_connect_successful(output, name, hostname) + + # Following function creates atleast two ICMP packets assert network.is_connected() + packet_capture_thread.join() + packet_capture_thread_result = packet_capture_thread_queue.get() + + assert packet_capture_thread_result >= 2 + def disconnect_base_test(): output = sh.nordvpn.disconnect() @@ -225,14 +282,14 @@ def test_connect_to_country(tech, proto, obfuscated, country): disconnect_base_test() -@pytest.mark.parametrize("country", lib.COUNTRY_CODES) +@pytest.mark.parametrize("country_code", lib.COUNTRY_CODES) @pytest.mark.parametrize("tech,proto,obfuscated", lib.TECHNOLOGIES) @pytest.mark.flaky(reruns=2, reruns_delay=90) @timeout_decorator.timeout(40) -def test_connect_to_code_country(tech, proto, obfuscated, country): +def test_connect_to_code_country(tech, proto, obfuscated, country_code): lib.set_technology_and_protocol(tech, proto, obfuscated) - connect_base_test(country) + connect_base_test(country_code) disconnect_base_test()