summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/README.md8
-rw-r--r--tests/accel-pppd/general/test_basic.py (renamed from tests/accel-pppd/test_basic.py)4
-rw-r--r--tests/accel-pppd/general/test_metrics.py112
-rw-r--r--tests/accel-pppd/general/test_pcre_negative_cases.py65
-rw-r--r--tests/accel-pppd/ipoe/conftest.py20
-rw-r--r--tests/accel-pppd/ipoe/dhcpv4/test_ipoe_shared_session_chap_secrets.py92
-rw-r--r--tests/accel-pppd/ipoe/dhcpv4/test_ipoe_shared_session_lua_chap_secrets.py112
-rw-r--r--tests/accel-pppd/ipoe/dhcpv4/test_ipoe_shared_session_wo_auth.py14
-rw-r--r--tests/accel-pppd/ipoe/test_ipoe_link_ops.py82
-rw-r--r--tests/accel-pppd/ipoe/test_ipoe_stale_cleanup.py97
-rw-r--r--tests/accel-pppd/pppoe/conftest.py17
-rw-r--r--tests/accel-pppd/pppoe/test_pppoe_ccp_ipcp_race.py138
-rw-r--r--tests/accel-pppd/pppoe/test_pppoe_disc.py8
-rw-r--r--tests/accel-pppd/pppoe/test_pppoe_pado_delay.py8
-rw-r--r--tests/accel-pppd/pppoe/test_pppoe_session_chap_secrets.py104
-rw-r--r--tests/accel-pppd/pppoe/test_pppoe_session_wo_auth.py14
-rw-r--r--tests/accel-pppd/pppoe/test_pppoe_vlan_mon.py8
-rw-r--r--tests/common/ipoe_iface.py84
-rw-r--r--tests/common/veth.py26
-rw-r--r--tests/conftest.py22
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)