diff options
Diffstat (limited to 'tests')
20 files changed, 1014 insertions, 21 deletions
diff --git a/tests/README.md b/tests/README.md index d062ed46..689a16d8 100644 --- a/tests/README.md +++ b/tests/README.md @@ -6,7 +6,7 @@ These tests are done for Ubuntu and Debian distros. Please use latest stable Deb Install pytest -Using apt: `sudo apt install python3-pytest python3-pytest-dependency` or using pip: `sudo pip3 install pytest pytest-dependency`. +Using apt: `sudo apt install python3-pytest python3-pytest-dependency python3-pytest-order` or using pip: `sudo pip3 install pytest pytest-dependency pytest-order`. pytest-dependency version must be >= 0.5 (with 'scope' support) @@ -43,13 +43,13 @@ sudo insmod build/drivers/ipoe/driver/ipoe.ko ```bash # from this dir (tests) -sudo python3 -m pytest -Wall -v +sudo python3 -m pytest -Wall --order-dependencies -v ``` To skip tests related to ipoe and vlan_mon kernel modules: ```bash # from this dir (tests) -sudo python3 -m pytest -Wall -v -m "not ipoe_driver and not vlan_mon_driver" +sudo python3 -m pytest -Wall --order-dependencies -v -m "not ipoe_driver and not vlan_mon_driver" ``` ## Preparations (for coverage report) @@ -83,7 +83,7 @@ Then insert kernel modules (ipoe.ko and vlan-mon.ko) ```bash # from root dir (parent for this dir) -sudo python3 -m pytest -Wall tests -v # execute tests to collect coverage data +sudo python3 -m pytest -Wall --order-dependencies tests -v # execute tests to collect coverage data mkdir tests/report gcovr --config=tests/gcovr.conf # default report gcovr --config=tests/gcovr.conf --csv # csv report diff --git a/tests/accel-pppd/test_basic.py b/tests/accel-pppd/general/test_basic.py index 2b2c6f71..9652ee0b 100644 --- a/tests/accel-pppd/test_basic.py +++ b/tests/accel-pppd/general/test_basic.py @@ -19,9 +19,11 @@ def accel_pppd_config(): log_tcp #log_pgsql + connlimit pptp l2tp sstp + radius pppoe ipoe @@ -30,7 +32,6 @@ def accel_pppd_config(): auth_chap_md5 auth_pap - radius chap-secrets ippool @@ -39,7 +40,6 @@ def accel_pppd_config(): shaper #net-snmp logwtmp - connlimit ipv6_nd ipv6_dhcp diff --git a/tests/accel-pppd/general/test_metrics.py b/tests/accel-pppd/general/test_metrics.py new file mode 100644 index 00000000..aa90a13a --- /dev/null +++ b/tests/accel-pppd/general/test_metrics.py @@ -0,0 +1,112 @@ +import http.client +import json + +import pytest + + +PROM_PORT = 9099 + + +def _config(fmt, sessions=False): + return f""" + [modules] + metrics + + [core] + log-error=/dev/stderr + + [log] + log-emerg=/dev/stderr + level=1 + + [cli] + tcp=127.0.0.1:2001 + + [metrics] + address=127.0.0.1:{PROM_PORT} + format={fmt} + sessions={int(sessions)} + """ + + +def _request(path, method="GET"): + conn = http.client.HTTPConnection("127.0.0.1", PROM_PORT, timeout=5) + try: + conn.request(method, path) + resp = conn.getresponse() + # strict: the renderer must never emit a body that is not valid UTF-8 + body = resp.read().decode("utf-8") + headers = {k.lower(): v for k, v in resp.getheaders()} + return resp.status, headers, body + finally: + conn.close() + + +class TestPrometheus: + @pytest.fixture() + def accel_pppd_config(self): + # sessions=1 must stay a no-op here: prometheus output is aggregate only + return _config("prometheus", sessions=True) + + def test_metrics_prometheus(self, accel_pppd_instance): + assert accel_pppd_instance + + status, headers, body = _request("/metrics") + + assert status == 200 + assert "text/plain" in headers.get("content-type", "") + assert "accel_ppp_build_info{version=" in body + assert "# TYPE accel_ppp_uptime_seconds gauge" in body + assert 'accel_ppp_sessions{state="active"}' in body + assert "session_details" not in body + + def test_metrics_404_unknown_path(self, accel_pppd_instance): + assert accel_pppd_instance + + status, _, _ = _request("/nope") + + assert status == 404 + + def test_metrics_405_non_get(self, accel_pppd_instance): + assert accel_pppd_instance + + status, _, _ = _request("/metrics", method="POST") + + assert status == 405 + + +class TestJson: + @pytest.fixture() + def accel_pppd_config(self): + return _config("json", sessions=True) + + def test_metrics_json(self, accel_pppd_instance): + assert accel_pppd_instance + + status, headers, body = _request("/metrics") + + assert status == 200 + assert headers.get("content-type") == "application/json" + + assert int(headers["content-length"]) == len(body.encode("utf-8")) + + doc = json.loads(body) + assert "build" in doc and "version" in doc["build"] + assert "uptime_seconds" in doc + assert "active" in doc["sessions"] + assert "threads" in doc["core"] + assert doc["session_details"] == [] + + +class TestJsonNoSessions: + @pytest.fixture() + def accel_pppd_config(self): + return _config("json") + + def test_metrics_json_without_sessions(self, accel_pppd_instance): + assert accel_pppd_instance + + status, _, body = _request("/metrics") + + assert status == 200 + assert "session_details" not in json.loads(body) diff --git a/tests/accel-pppd/general/test_pcre_negative_cases.py b/tests/accel-pppd/general/test_pcre_negative_cases.py new file mode 100644 index 00000000..698c83ab --- /dev/null +++ b/tests/accel-pppd/general/test_pcre_negative_cases.py @@ -0,0 +1,65 @@ +import pytest +from common import process + + +@pytest.fixture() +def accel_pppd_config(): + return """ + [modules] + radius + pppoe + + [core] + log-error=/dev/stderr + + [log] + log-debug=/dev/stdout + log-file=/dev/stdout + log-emerg=/dev/stderr + level=5 + + [cli] + tcp=127.0.0.1:2001 + + [radius] + + [pppoe] + """ + + +# test pcre-related negative cases +def test_pcre_negative_cases(accel_pppd_instance, accel_cmd): + + # test that accel-pppd started successfully + assert accel_pppd_instance + + (exit_sh_sess, out_sh_sess, err_sh_sess) = process.run([accel_cmd, "show sessions match username 00("]) + # test that 'show sessions' with invalid regexp reports the issue and error position + assert ( + exit_sh_sess == 0 + and len(out_sh_sess) > 0 + and err_sh_sess == "" + and "match: " in out_sh_sess + and "at 3" in out_sh_sess + ) + + + (exit_iface_add, out_iface_add, err_iface_add) = process.run([accel_cmd, "pppoe interface add re:000("]) + # test that 'pppoe interface add' with invalid regexp reports the issue and error position + assert ( + exit_iface_add == 0 + and len(out_iface_add) > 0 + and err_iface_add == "" + and "pppoe: " in out_iface_add + and "at 4" in out_iface_add + ) + + (exit_term, out_term, err_term) = process.run([accel_cmd, "terminate match username 00("]) + # test that 'terminate' with invalid regexp reports the issue and error position + assert ( + exit_term == 0 + and len(out_term) > 0 + and err_term == "" + and "match: " in out_term + and "at 3" in out_term + ) diff --git a/tests/accel-pppd/ipoe/conftest.py b/tests/accel-pppd/ipoe/conftest.py index 3db8dd60..353ed270 100644 --- a/tests/accel-pppd/ipoe/conftest.py +++ b/tests/accel-pppd/ipoe/conftest.py @@ -1,5 +1,5 @@ import pytest -from common import dhclient_process +from common import dhclient_process, config import tempfile, os # dhclient executable file name @@ -44,3 +44,21 @@ def dhclient_instance(accel_pppd_instance, veth_pair_netns, dhclient, dhclient_a # test teardown: dhclient_process.end(dhclient_thread, dhclient_control) + +# lua script as string (should be redefined by specific test) +@pytest.fixture() +def lua_script(): + return "" + + +# lua script file name +@pytest.fixture() +def lua_script_file(lua_script): + # test setup: + filename = config.make_tmp(lua_script) + + # test execution + yield filename + + # test teardown: + config.delete_tmp(filename) diff --git a/tests/accel-pppd/ipoe/dhcpv4/test_ipoe_shared_session_chap_secrets.py b/tests/accel-pppd/ipoe/dhcpv4/test_ipoe_shared_session_chap_secrets.py new file mode 100644 index 00000000..80c559da --- /dev/null +++ b/tests/accel-pppd/ipoe/dhcpv4/test_ipoe_shared_session_chap_secrets.py @@ -0,0 +1,92 @@ +import pytest +from common import process +import time + + +@pytest.fixture() +def chap_secrets_config(veth_pair_netns): + return veth_pair_netns["veth_a"] + " * pass123 192.0.2.57" + + +@pytest.fixture() +def accel_pppd_config(veth_pair_netns, chap_secrets_config_file): + print( + "accel_pppd_config veth_pair_netns: " + + str(veth_pair_netns) + + "chap_secrets_config_file: " + + str(chap_secrets_config_file) + ) + return ( + """ + [modules] + connlimit + chap-secrets + ipoe + + [cli] + tcp=127.0.0.1:2001 + + [core] + log-error=/dev/stderr + + [log] + log-debug=/dev/stdout + log-file=/dev/stdout + log-emerg=/dev/stderr + level=5 + + [ipoe] + username=ifname + password=pass123 + verbose=5 + start=dhcpv4 + shared=1 + gw-ip-address=192.0.2.1/24 + interface=re:.""" + + veth_pair_netns["veth_a"][1:] + + """ + [chap-secrets] + chap-secrets=""" + + chap_secrets_config_file + ) + + +# test dhcpv4 shared session without auth check +@pytest.mark.dependency(depends=["ipoe_driver_loaded"], scope="session") +@pytest.mark.ipoe_driver +@pytest.mark.chap_secrets +def test_ipoe_shared_session_chap_secrets( + dhclient_instance, accel_cmd, veth_pair_netns +): + + # test that dhclient (with accel-pppd) started successfully + assert dhclient_instance["is_started"] + + # wait until session is started + max_wait_time = 10.0 + sleep_time = 0.0 + is_started = False # is session started + while sleep_time < max_wait_time: + (exit, out, err) = process.run( + [ + accel_cmd, + "show sessions called-sid,ip,state", + ] + ) + assert exit == 0 # accel-cmd fails + # print(out) + if veth_pair_netns["veth_a"] in out and "192.0.2.57" in out and "active" in out: + # session is found + print( + "test_ipoe_session_chap_secrets: session found in (sec): " + + str(sleep_time) + ) + is_started = True + break + time.sleep(0.1) + sleep_time += 0.1 + + print("test_ipoe_shared_session_chap_secrets: last accel-cmd out: " + out) + + # test that session is started + assert is_started == True diff --git a/tests/accel-pppd/ipoe/dhcpv4/test_ipoe_shared_session_lua_chap_secrets.py b/tests/accel-pppd/ipoe/dhcpv4/test_ipoe_shared_session_lua_chap_secrets.py new file mode 100644 index 00000000..fdee7c4a --- /dev/null +++ b/tests/accel-pppd/ipoe/dhcpv4/test_ipoe_shared_session_lua_chap_secrets.py @@ -0,0 +1,112 @@ +import pytest +from common import process +import time + + +# create vlan 335 on both interfaces of pair +@pytest.fixture() +def veth_pair_vlans_config(): + return {"vlans_a": [335], "vlans_b": [335]} + + +# use vlan as a username +@pytest.fixture() +def chap_secrets_config(veth_pair_netns): + return "335 * pass123 192.0.2.67" + + +# return vlan as a username +@pytest.fixture() +def lua_script(): + return """#!lua + function vlan_func(pkt) + return pkt:vlan() + end""" + + +@pytest.fixture() +def accel_pppd_config(veth_pair_netns, chap_secrets_config_file, lua_script_file): + print( + "accel_pppd_config veth_pair_netns: " + + str(veth_pair_netns) + + "chap_secrets_config_file: " + + str(chap_secrets_config_file) + ) + return ( + """ + [modules] + connlimit + chap-secrets + ipoe + + [cli] + tcp=127.0.0.1:2001 + + [core] + log-error=/dev/stderr + + [log] + log-debug=/dev/stdout + log-file=/dev/stdout + log-emerg=/dev/stderr + level=5 + + [ipoe] + lua-file=""" + + lua_script_file + + """ + username=lua:vlan_func + password=pass123 + verbose=5 + start=dhcpv4 + shared=1 + gw-ip-address=192.0.2.1/24 + interface=re:.""" + + veth_pair_netns["veth_a"][1:] + + "\\.335" + + """ + [chap-secrets] + chap-secrets=""" + + chap_secrets_config_file + ) + + +# test dhcpv4 shared session without auth check +@pytest.mark.dependency(depends=["ipoe_driver_loaded"], scope="session") +@pytest.mark.ipoe_driver +@pytest.mark.chap_secrets +def test_ipoe_shared_session_lua_chap_secrets( + dhclient_instance, accel_cmd, veth_pair_netns +): + + # test that dhclient (with accel-pppd) started successfully + assert dhclient_instance["is_started"] + + # wait until session is started + max_wait_time = 10.0 + sleep_time = 0.0 + is_started = False # is session started + while sleep_time < max_wait_time: + (exit, out, err) = process.run( + [ + accel_cmd, + "show sessions called-sid,ip,state", + ] + ) + assert exit == 0 # accel-cmd fails + # print(out) + if veth_pair_netns["veth_a"] in out and "192.0.2.67" in out and "active" in out: + # session is found + print( + "test_ipoe_session_lua_chap_secrets: session found in (sec): " + + str(sleep_time) + ) + is_started = True + break + time.sleep(0.1) + sleep_time += 0.1 + + print("test_ipoe_shared_session_lua_chap_secrets: last accel-cmd out: " + out) + + # test that session is started + assert is_started == True diff --git a/tests/accel-pppd/ipoe/dhcpv4/test_ipoe_shared_session_wo_auth.py b/tests/accel-pppd/ipoe/dhcpv4/test_ipoe_shared_session_wo_auth.py index 104e4e9b..7ebb420a 100644 --- a/tests/accel-pppd/ipoe/dhcpv4/test_ipoe_shared_session_wo_auth.py +++ b/tests/accel-pppd/ipoe/dhcpv4/test_ipoe_shared_session_wo_auth.py @@ -9,7 +9,8 @@ def accel_pppd_config(veth_pair_netns): return ( """ [modules] - pppoe + connlimit + radius ipoe ippool @@ -20,16 +21,23 @@ def accel_pppd_config(veth_pair_netns): [cli] tcp=127.0.0.1:2001 + [core] + log-error=/dev/stderr + [log] log-debug=/dev/stdout + log-file=/dev/stdout + log-emerg=/dev/stderr level=5 + [radius] + [ipoe] noauth=1 shared=1 gw-ip-address=192.0.2.1/24 - interface=""" - + veth_pair_netns["veth_a"] + interface=re:.""" + + veth_pair_netns["veth_a"][1:] ) diff --git a/tests/accel-pppd/ipoe/test_ipoe_link_ops.py b/tests/accel-pppd/ipoe/test_ipoe_link_ops.py new file mode 100644 index 00000000..00d06af4 --- /dev/null +++ b/tests/accel-pppd/ipoe/test_ipoe_link_ops.py @@ -0,0 +1,82 @@ +import pytest +from common import process, ipoe_iface + + +@pytest.fixture() +def accel_pppd_config(veth_pair_netns): + return ( + """ + [modules] + connlimit + radius + ipoe + ippool + + [ip-pool] + gw-ip-address=192.0.2.1 + 192.0.2.2-255 + + [cli] + tcp=127.0.0.1:2001 + + [core] + log-error=/dev/stderr + + [log] + log-debug=/dev/stdout + log-file=/dev/stdout + log-emerg=/dev/stderr + level=5 + + [radius] + + [ipoe] + noauth=1 + shared=1 + gw-ip-address=192.0.2.1/24 + interface=re:.""" + + veth_pair_netns["veth_a"][1:] + ) + + +# sessions carry private state set up over generic netlink, a device made by +# rtnetlink would have none of it +@pytest.mark.dependency(depends=["ipoe_driver_loaded"], scope="session") +@pytest.mark.ipoe_driver +def test_ipoe_interface_cannot_be_created_by_iproute(): + (exit_code, out, err) = process.run( + ["ip", "link", "add", "ipoetest0", "type", "ipoe"] + ) + print("ip link add: exit=%d out=%s err=%s" % (exit_code, out, err)) + + # remove it again in case it got created anyway, so that the rest of the + # suite is not affected + process.run(["ip", "link", "del", "ipoetest0"]) + + assert exit_code != 0 + + +# stale interfaces have to be removable without restarting accel-pppd, which +# would drop every remaining session +@pytest.mark.dependency(depends=["ipoe_driver_loaded"], scope="session") +@pytest.mark.ipoe_driver +def test_ipoe_interface_can_be_deleted_by_iproute( + accel_pppd_instance, dhclient_instance, accel_cmd, veth_pair_netns +): + assert accel_pppd_instance, "accel-pppd did not start" + assert dhclient_instance["is_started"] + assert ipoe_iface.wait_for_session(accel_cmd, veth_pair_netns["veth_a"]) + + before = ipoe_iface.list_ipoe() + print("ipoe interfaces: " + str(before)) + assert len(before) > 0 + + (ifindex, name) = before[0] + + (exit_code, out, err) = process.run(["ip", "link", "del", name]) + print("ip link del %s: exit=%d out=%s err=%s" % (name, exit_code, out, err)) + assert exit_code == 0 + + after = ipoe_iface.list_ipoe() + print("ipoe interfaces after delete: " + str(after)) + assert (ifindex, name) not in after diff --git a/tests/accel-pppd/ipoe/test_ipoe_stale_cleanup.py b/tests/accel-pppd/ipoe/test_ipoe_stale_cleanup.py new file mode 100644 index 00000000..b7cfd533 --- /dev/null +++ b/tests/accel-pppd/ipoe/test_ipoe_stale_cleanup.py @@ -0,0 +1,97 @@ +import pytest +from common import accel_pppd_process, ipoe_iface + + +@pytest.fixture() +def accel_pppd_config(veth_pair_netns): + return ( + """ + [modules] + connlimit + radius + ipoe + ippool + + [ip-pool] + gw-ip-address=192.0.2.1 + 192.0.2.2-255 + + [cli] + tcp=127.0.0.1:2001 + + [core] + log-error=/dev/stderr + + [log] + log-debug=/dev/stdout + log-file=/dev/stdout + log-emerg=/dev/stderr + level=5 + + [radius] + + [ipoe] + noauth=1 + shared=1 + gw-ip-address=192.0.2.1/24 + interface=re:.""" + + veth_pair_netns["veth_a"][1:] + ) + + +# accel-pppd killed with SIGKILL has no chance to remove the session +# interfaces it created, so they are left in the kernel. The next instance is +# supposed to drop them while starting up. +@pytest.mark.dependency(depends=["ipoe_driver_loaded"], scope="session") +@pytest.mark.ipoe_driver +def test_ipoe_stale_interfaces_removed_after_sigkill( + accel_pppd_instance, + dhclient_instance, + accel_cmd, + accel_pppd, + accel_pppd_config_file, + veth_pair_netns, + pytestconfig, +): + assert accel_pppd_instance, "accel-pppd did not start" + assert dhclient_instance["is_started"] + assert ipoe_iface.wait_for_session(accel_cmd, veth_pair_netns["veth_a"]) + + # the session must have created an interface, otherwise there is nothing + # for this test to check + before = ipoe_iface.list_ipoe() + print("ipoe interfaces before the crash: " + str(before)) + assert len(before) > 0 + + assert ipoe_iface.kill_accel_pppd() > 0 + + # nothing removes them while no accel-pppd is running (not asserted, the + # kernel module is free to start doing it on its own one day) + print("ipoe interfaces after the crash: " + str(ipoe_iface.list_ipoe())) + + # start again, it replies to 'show version' only once the startup flush is + # done, so there is no need to wait for anything else + (is_started, thread, control) = accel_pppd_process.start( + accel_pppd, + ["-c" + accel_pppd_config_file], + accel_cmd, + pytestconfig.getoption("accel_pppd_max_wait_time"), + ) + + try: + assert is_started + + after = ipoe_iface.list_ipoe() + print("ipoe interfaces after the restart: " + str(after)) + + # compared by ifindex: dhclient may get a new session in the meantime, + # and the fresh interface would reuse the ipoe0 name + stale = set(before) & set(after) + assert not stale, "interfaces left over from the killed instance: " + str(stale) + finally: + accel_pppd_process.end( + thread, + control, + accel_cmd, + pytestconfig.getoption("accel_pppd_max_finish_time"), + ) diff --git a/tests/accel-pppd/pppoe/conftest.py b/tests/accel-pppd/pppoe/conftest.py index 8ebaaed3..b3893200 100644 --- a/tests/accel-pppd/pppoe/conftest.py +++ b/tests/accel-pppd/pppoe/conftest.py @@ -1,5 +1,6 @@ -import pytest +import pytest, subprocess, re from common import pppd_process +from packaging.version import Version # pppd executable file name @pytest.fixture() @@ -13,11 +14,21 @@ def pppd(pytestconfig): def pppd_config(): return "" +# determines which plugin is required - pppoe.so (pppd 2.5.0+) or rp-pppoe.so (pppd <2.5.0) +def pppd_plugin_so(pppd): + command = [pppd, "--version"] + result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + pppd_version = Version(re.search(r'\d+\.\d+\.\d+', result.stdout + result.stderr).group()) + ref_version = Version("2.5.0") + if pppd_version >= ref_version: + return "pppoe.so" + else: + return "rp-pppoe.so" # pppd configuration as command line args @pytest.fixture() -def pppd_args(pppd_config): - return pppd_config.split() +def pppd_args(pppd_config, pppd): + return ("plugin " + pppd_plugin_so(pppd) + "\n" + pppd_config).split() # setup and teardown for tests that required running pppd (after accel-pppd) diff --git a/tests/accel-pppd/pppoe/test_pppoe_ccp_ipcp_race.py b/tests/accel-pppd/pppoe/test_pppoe_ccp_ipcp_race.py new file mode 100644 index 00000000..d277af63 --- /dev/null +++ b/tests/accel-pppd/pppoe/test_pppoe_ccp_ipcp_race.py @@ -0,0 +1,138 @@ +import pytest +from common import process, config +import time + + +# accel-pppd log file (separate file, because stdout of accel-pppd is piped +# by the test harness and is not readable while the test is running) +@pytest.fixture() +def accel_pppd_log_file(): + # test setup: + filename = config.make_tmp("") + + # test execution: + yield filename + + # test teardown: + config.delete_tmp(filename) + + +@pytest.fixture() +def chap_secrets_config(): + return "loginRACE * pass123 192.0.2.38" + + +# 'mppe=prefer' makes CCP non-passive, so CCP negotiation is still in progress +# when the peer sends its IPCP ConfReq +@pytest.fixture() +def accel_pppd_config(veth_pair_netns, chap_secrets_config_file, accel_pppd_log_file): + return ( + """ + [modules] + log_file + chap-secrets + pppoe + auth_mschap_v2 + + [core] + log-error=/dev/stderr + + [ppp] + verbose=1 + mppe=prefer + + [log] + log-debug=""" + + accel_pppd_log_file + + """ + log-file=/dev/stdout + log-emerg=/dev/stderr + level=5 + + [cli] + tcp=127.0.0.1:2001 + + [pppoe] + interface=""" + + veth_pair_netns["veth_a"] + + """ + [chap-secrets] + gw-ip-address=192.0.2.1 + chap-secrets=""" + + chap_secrets_config_file + ) + + +# pppd does not require MPPE, so it rejects the MPPE option offered by +# accel-pppd (which costs CCP an additional round trip) and does not delay +# IPCP until CCP is done +@pytest.fixture() +def pppd_config(veth_pair_netns): + return ( + """ + nodetach + noipdefault + noauth + persist + mtu 1492 + noaccomp + default-asyncmap + lcp-echo-interval 0 + user loginRACE + password pass123 + nic-""" + + veth_pair_netns["veth_b"] + ) + + +# IPCP ConfReq received while CCP is still negotiating must not be answered +# with a TermAck: the ack is withheld and sent when CCP is done +@pytest.mark.chap_secrets +def test_pppoe_ccp_ipcp_race(pppd_instance, accel_cmd, accel_pppd_log_file): + + # test that pppd (with accel-pppd) started successfully + assert pppd_instance["is_started"] + + # wait until session is started + max_wait_time = 10.0 + sleep_time = 0.0 + is_started = False # is session started + while sleep_time < max_wait_time: + (exit, out, err) = process.run( + [ + accel_cmd, + "show sessions match username log.nRACE username,ip,state", + ] + ) + assert exit == 0 # accel-cmd fails + if "loginRACE" in out and "192.0.2.38" in out and "active" in out: + print("test_pppoe_ccp_ipcp_race: session found in (sec): " + str(sleep_time)) + is_started = True + break + time.sleep(0.1) + sleep_time += 0.1 + + print("test_pppoe_ccp_ipcp_race: last accel-cmd out: " + out) + + # test that session is started + assert is_started == True + + with open(accel_pppd_log_file, "r") as f: + log = f.read().splitlines() + print("test_pppoe_ccp_ipcp_race: accel-pppd log:\n" + "\n".join(log)) + + ccp_started = [i for i, line in enumerate(log) if "ccp_layer_started" in line] + assert len(ccp_started) > 0 # CCP was negotiated + + # skip if the peer did not send its IPCP ConfReq before CCP was done, + # in this case there is nothing to check + conf_req = [ + i + for i, line in enumerate(log[: ccp_started[0]]) + if "recv [IPCP ConfReq" in line + ] + if len(conf_req) == 0: + pytest.skip("peer did not send IPCP ConfReq while CCP was negotiating") + + # test that IPCP ConfReq was not answered with a TermAck + assert len([line for line in log if "send [IPCP TermAck" in line]) == 0 diff --git a/tests/accel-pppd/pppoe/test_pppoe_disc.py b/tests/accel-pppd/pppoe/test_pppoe_disc.py index eb069c42..64a0d295 100644 --- a/tests/accel-pppd/pppoe/test_pppoe_disc.py +++ b/tests/accel-pppd/pppoe/test_pppoe_disc.py @@ -8,15 +8,23 @@ def accel_pppd_config(veth_pair_netns): return ( """ [modules] + radius pppoe + [core] + log-error=/dev/stderr + [log] log-debug=/dev/stdout + log-file=/dev/stdout + log-emerg=/dev/stderr level=5 [cli] tcp=127.0.0.1:2001 + [radius] + [pppoe] ac-name=test-accel interface=""" diff --git a/tests/accel-pppd/pppoe/test_pppoe_pado_delay.py b/tests/accel-pppd/pppoe/test_pppoe_pado_delay.py index 96c73bf8..3a93d920 100644 --- a/tests/accel-pppd/pppoe/test_pppoe_pado_delay.py +++ b/tests/accel-pppd/pppoe/test_pppoe_pado_delay.py @@ -30,15 +30,23 @@ def accel_pppd_config(veth_pair_netns): return ( """ [modules] + radius pppoe + [core] + log-error=/dev/stderr + [log] log-debug=/dev/stdout + log-file=/dev/stdout + log-emerg=/dev/stderr level=5 [cli] tcp=127.0.0.1:2001 + [radius] + [pppoe] ac-name=test-accel pado-delay=1500 diff --git a/tests/accel-pppd/pppoe/test_pppoe_session_chap_secrets.py b/tests/accel-pppd/pppoe/test_pppoe_session_chap_secrets.py new file mode 100644 index 00000000..05d19701 --- /dev/null +++ b/tests/accel-pppd/pppoe/test_pppoe_session_chap_secrets.py @@ -0,0 +1,104 @@ +import pytest +from common import process +import time + + +@pytest.fixture() +def chap_secrets_config(): + return "loginCSAB * pass123 192.0.2.37" + + +@pytest.fixture() +def accel_pppd_config(veth_pair_netns, chap_secrets_config_file): + print( + "accel_pppd_config veth_pair_netns: " + + str(veth_pair_netns) + + "chap_secrets_config_file" + + str(chap_secrets_config_file) + ) + return ( + """ + [modules] + chap-secrets + pppoe + auth_pap + + [core] + log-error=/dev/stderr + + [log] + log-debug=/dev/stdout + log-file=/dev/stdout + log-emerg=/dev/stderr + level=5 + + [cli] + tcp=127.0.0.1:2001 + + [pppoe] + interface=""" + + veth_pair_netns["veth_a"] + + """ + [chap-secrets] + gw-ip-address=192.0.2.1 + chap-secrets=""" + + chap_secrets_config_file + ) + + +@pytest.fixture() +def pppd_config(veth_pair_netns): + print("pppd_config veth_pair_netns: " + str(veth_pair_netns)) + return ( + """ + nodetach + noipdefault + defaultroute + connect /bin/true + noauth + persist + mtu 1492 + noaccomp + default-asyncmap + user loginCSAB + password pass123 + nic-""" + + veth_pair_netns["veth_b"] + ) + + +# test pppoe session without auth check +@pytest.mark.chap_secrets +def test_pppoe_session_chap_secrets(pppd_instance, accel_cmd): + + # test that pppd (with accel-pppd) started successfully + assert pppd_instance["is_started"] + + # wait until session is started + max_wait_time = 10.0 + sleep_time = 0.0 + is_started = False # is session started + while sleep_time < max_wait_time: + (exit, out, err) = process.run( + [ + accel_cmd, + "show sessions match username log.nCSAB username,ip,state", + ] + ) + assert exit == 0 # accel-cmd fails + # print(out) + if "loginCSAB" in out and "192.0.2.37" in out and "active" in out: + # session is found + print( + "test_pppoe_session_chap_secrets: session found in (sec): " + + str(sleep_time) + ) + is_started = True + break + time.sleep(0.1) + sleep_time += 0.1 + + print("test_pppoe_session_chap_secrets: last accel-cmd out: " + out) + + # test that session is started + assert is_started == True diff --git a/tests/accel-pppd/pppoe/test_pppoe_session_wo_auth.py b/tests/accel-pppd/pppoe/test_pppoe_session_wo_auth.py index 0c8aa2c0..b4cccc81 100644 --- a/tests/accel-pppd/pppoe/test_pppoe_session_wo_auth.py +++ b/tests/accel-pppd/pppoe/test_pppoe_session_wo_auth.py @@ -9,17 +9,26 @@ def accel_pppd_config(veth_pair_netns): return ( """ [modules] + radius pppoe auth_pap ippool + [core] + log-error=/dev/stderr + [log] log-debug=/dev/stdout + log-file=/dev/stdout + log-emerg=/dev/stderr level=5 [auth] any-login=1 + [ppp] + mppe=prefer + [ip-pool] gw-ip-address=192.0.2.1 192.0.2.2-255 @@ -27,6 +36,8 @@ def accel_pppd_config(veth_pair_netns): [cli] tcp=127.0.0.1:2001 + [radius] + [pppoe] interface=""" + veth_pair_netns["veth_a"] @@ -47,7 +58,6 @@ def pppd_config(veth_pair_netns): mtu 1492 noaccomp default-asyncmap - plugin rp-pppoe.so user loginAB password pass123 nic-""" @@ -69,7 +79,7 @@ def test_pppoe_session_wo_auth(pppd_instance, accel_cmd): (exit, out, err) = process.run( [ accel_cmd, - "show sessions match username loginAB username,ip,state", + "show sessions match username log.nAB username,ip,state", ] ) assert exit == 0 # accel-cmd fails diff --git a/tests/accel-pppd/pppoe/test_pppoe_vlan_mon.py b/tests/accel-pppd/pppoe/test_pppoe_vlan_mon.py index 670abc33..b73189c3 100644 --- a/tests/accel-pppd/pppoe/test_pppoe_vlan_mon.py +++ b/tests/accel-pppd/pppoe/test_pppoe_vlan_mon.py @@ -13,15 +13,23 @@ def accel_pppd_config(veth_pair_netns): print(veth_pair_netns) return """ [modules] + radius pppoe + [core] + log-error=/dev/stderr + [log] log-debug=/dev/stdout + log-file=/dev/stdout + log-emerg=/dev/stderr level=5 [cli] tcp=127.0.0.1:2001 + [radius] + [pppoe] ac-name=test-accel vlan-mon=%s,10-20 diff --git a/tests/common/ipoe_iface.py b/tests/common/ipoe_iface.py new file mode 100644 index 00000000..62b26355 --- /dev/null +++ b/tests/common/ipoe_iface.py @@ -0,0 +1,84 @@ +from common import process +import os +import signal +import time + + +# (ifindex, name) of every ipoe session interface currently in the kernel. +# Matched by name rather than by 'ip link show type ipoe' so that the helper +# keeps working with a module that does not register rtnl_link_ops. +def list_ipoe(): + (exit_code, out, err) = process.run(["ip", "-o", "link", "show"]) + assert exit_code == 0, "ip link show failed: " + err + + ifaces = [] + for line in out.splitlines(): + fields = line.split(":") + if len(fields) < 2: + continue + try: + ifindex = int(fields[0].strip()) + except ValueError: + continue + name = fields[1].strip().split("@")[0] + if name.startswith("ipoe"): + ifaces.append((ifindex, name)) + + return ifaces + + +# pids of the running accel-pppd processes +def accel_pppd_pids(): + pids = [] + for entry in os.listdir("/proc"): + if not entry.isdigit(): + continue + try: + with open("/proc/" + entry + "/comm") as comm: + if comm.read().strip() == "accel-pppd": + pids.append(int(entry)) + except OSError: # process is gone, or not ours to look at + pass + + return pids + + +# SIGKILL every accel-pppd and wait until they are really gone. +# Returns the number of processes that were killed. +def kill_accel_pppd(max_wait_time=10.0): + pids = accel_pppd_pids() + for pid in pids: + print("kill_accel_pppd: SIGKILL to pid " + str(pid)) + try: + os.kill(pid, signal.SIGKILL) + except OSError: + pass + + sleep_time = 0.0 + while sleep_time < max_wait_time: + if not accel_pppd_pids(): + print("kill_accel_pppd: gone in (sec): " + str(sleep_time)) + break + time.sleep(0.1) + sleep_time += 0.1 + + return len(pids) + + +# wait until accel-pppd reports an active ipoe session on the given interface +def wait_for_session(accel_cmd, called_sid, max_wait_time=10.0): + sleep_time = 0.0 + out = "" + while sleep_time < max_wait_time: + (exit_code, out, err) = process.run( + [accel_cmd, "show sessions called-sid,ip,state"] + ) + assert exit_code == 0, "accel-cmd failed: " + err + if called_sid in out and "192.0.2." in out and "active" in out: + print("wait_for_session: session found in (sec): " + str(sleep_time)) + return True + time.sleep(0.1) + sleep_time += 0.1 + + print("wait_for_session: last accel-cmd out: " + out) + return False diff --git a/tests/common/veth.py b/tests/common/veth.py index c9d3006c..9d51ab7a 100644 --- a/tests/common/veth.py +++ b/tests/common/veth.py @@ -2,11 +2,16 @@ from common import process, netns, vlan, iface import time import math -# creates veth pair. if ok returns 0 -def create_pair(name_a, name_b): - veth, out, err = process.run( - ["ip", "link", "add", name_a, "type", "veth", "peer", "name", name_b] - ) +# creates veth pair, optionally with fixed hardware addresses. if ok returns 0 +def create_pair(name_a, name_b, mac_a=None, mac_b=None): + command = ["ip", "link", "add", name_a] + if mac_a: + command += ["address", mac_a] + command += ["type", "veth", "peer", "name", name_b] + if mac_b: + command += ["address", mac_b] + + veth, out, err = process.run(command) print("veth.create: exit=%d out=%s err=%s" % (veth, out, err)) return veth @@ -32,7 +37,16 @@ def create_veth_pair_netns(veth_pair_vlans_config): veth_a = "A" + name veth_b = "B" + name - pair_status = create_pair(veth_a, veth_b) + + # fixed addresses: a veth pair created without them gets random ones, which + # systemd-udevd may replace right after creation (MACAddressPolicy in its + # .link rules). accel-pppd reads the address of the interface it serves once + # at startup, so when udev wins that race the daemon advertises an address + # the interface no longer has and the session never passes a frame. + # See https://github.com/accel-ppp/accel-ppp/issues/363 + num = int(name) + mac = "02:00:%02x:%02x:%02x:" % ((num >> 16) & 0xFF, (num >> 8) & 0xFF, num & 0xFF) + pair_status = create_pair(veth_a, veth_b, mac + "0a", mac + "0b") print("create_veth_pair_netns: pair_status=%d" % pair_status) iface.up(veth_a, None) diff --git a/tests/conftest.py b/tests/conftest.py index d3733409..da1f40f4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -26,6 +26,10 @@ def pytest_configure(config): "markers", "vlan_mon_driver: marks tests as related to ipoe kernel module (deselect with '-m \"not vlan_mon_driver\"')", ) + config.addinivalue_line( + "markers", + "chap_secrets: marks tests as related to chap-secrets module (deselect with '-m \"not chap_secrets\"')", + ) # accel-pppd executable file name @@ -97,3 +101,21 @@ def veth_pair_netns(veth_pair_vlans_config): # test teardown: veth.delete_veth_pair_netns(veth_pair_netns_instance) + +# chap-secrets configuration as string (should be redefined by specific test) +@pytest.fixture() +def chap_secrets_config(): + return "" + + +# chap-secrets configuration file name +@pytest.fixture() +def chap_secrets_config_file(chap_secrets_config): + # test setup: + filename = config.make_tmp(chap_secrets_config) + + # test execution + yield filename + + # test teardown: + config.delete_tmp(filename) |
