diff --git a/data/templates/accel-ppp/pppoe.config.j2 b/data/templates/accel-ppp/pppoe.config.j2 index beab46936..cf952c687 100644 --- a/data/templates/accel-ppp/pppoe.config.j2 +++ b/data/templates/accel-ppp/pppoe.config.j2 @@ -1,130 +1,136 @@ ### generated by accel_pppoe.py ### [modules] log_syslog pppoe shaper {# Common authentication backend definitions #} {% include 'accel-ppp/config_modules_auth_mode.j2' %} ippool {# Common IPv6 definitions #} {% include 'accel-ppp/config_modules_ipv6.j2' %} {# Common authentication protocols (pap, chap ...) #} {% include 'accel-ppp/config_modules_auth_protocols.j2' %} {% if snmp is vyos_defined %} net-snmp {% endif %} {% if limits is vyos_defined %} connlimit {% endif %} {% if extended_scripts is vyos_defined %} sigchld pppd_compat {% endif %} [core] thread-count={{ thread_count }} [log] syslog=accel-pppoe,daemon copy=1 {% if log.level is vyos_defined %} level={{ log.level }} {% endif %} [auth] {% if authentication.mode is vyos_defined("noauth") %} noauth=1 {% endif %} {% if authentication.any_login is vyos_defined %} any-login=1 {% endif %} [client-ip-range] 0.0.0.0/0 [common] {% if session_control is vyos_defined and session_control is not vyos_defined('disable') %} single-session={{ session_control }} {% endif %} {% if max_concurrent_sessions is vyos_defined %} max-starting={{ max_concurrent_sessions }} {% endif %} [pppoe] verbose=1 ac-name={{ access_concentrator }} {% if interface is vyos_defined %} {% for iface, iface_config in interface.items() %} {% if iface_config.vlan is not vyos_defined %} interface={{ iface }} {% else %} {% for vlan in iface_config.vlan %} interface=re:^{{ iface }}\.{{ vlan | range_to_regex }}$ {% endfor %} {% if iface_config.vlan_mon is vyos_defined %} vlan-mon={{ iface }},{{ iface_config.vlan | join(',') }} {% endif %} {% endif %} {% endfor %} {% endif %} {% if service_name %} service-name={{ service_name | join(',') }} {% endif %} +{% if accept_any_service is vyos_defined %} +accept-any-service=1 +{% endif %} +{% if accept_blank_service is vyos_defined %} +accept-blank-service=1 +{% endif %} {% if pado_delay %} {% set delay_without_sessions = pado_delay.delays_without_sessions[0] | default('0') %} {% set pado_delay_param = namespace(value=delay_without_sessions) %} {% for delay, sessions in pado_delay.delays_with_sessions | sort(attribute='1') %} {% if not delay == 'disable' %} {% set pado_delay_param.value = pado_delay_param.value + ',' + delay + ':' + sessions | string %} {% else %} {% set pado_delay_param.value = pado_delay_param.value + ',-1:' + sessions | string %} {% endif %} {% endfor %} pado-delay={{ pado_delay_param.value }} {% endif %} {% if authentication.radius.called_sid_format is vyos_defined %} called-sid={{ authentication.radius.called_sid_format }} {% endif %} {% if authentication.mode is vyos_defined("noauth") %} noauth=1 {% endif %} {% if default_pool is vyos_defined %} ip-pool={{ default_pool }} {% endif %} {% if default_ipv6_pool is vyos_defined %} ipv6-pool={{ default_ipv6_pool }} ipv6-pool-delegate={{ default_ipv6_pool }} {% endif %} {# Common IP pool definitions #} {% include 'accel-ppp/config_ip_pool.j2' %} {# Common IPv6 pool definitions #} {% include 'accel-ppp/config_ipv6_pool.j2' %} {# Common DNS name-server definition #} {% include 'accel-ppp/config_name_server.j2' %} {# Common wins-server definition #} {% include 'accel-ppp/config_wins_server.j2' %} {# Common chap-secrets and RADIUS server/option definitions #} {% include 'accel-ppp/config_chap_secrets_radius.j2' %} {# Common ppp-options definitions #} {% include 'accel-ppp/ppp-options.j2' %} {# Common RADIUS shaper configuration #} {% include 'accel-ppp/config_shaper_radius.j2' %} {# Common Extended scripts configuration #} {% include 'accel-ppp/config_extended_scripts.j2' %} {# Common Limits configuration #} {% include 'accel-ppp/config_limits.j2' %} {# Common SNMP definitions #} {% include 'accel-ppp/config_snmp.j2' %} [cli] tcp=127.0.0.1:2001 diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service_pppoe-server.xml.in index 93ec7ade9..0c99fd261 100644 --- a/interface-definitions/service_pppoe-server.xml.in +++ b/interface-definitions/service_pppoe-server.xml.in @@ -1,168 +1,180 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="service"> <children> <node name="pppoe-server" owner="${vyos_conf_scripts_dir}/service_pppoe-server.py"> <properties> <help>Point to Point over Ethernet (PPPoE) Server</help> <priority>900</priority> </properties> <children> #include <include/pppoe-access-concentrator.xml.i> <leafNode name="access-concentrator"> <defaultValue>vyos-ac</defaultValue> </leafNode> <node name="authentication"> <properties> <help>Authentication for remote access PPPoE Server</help> </properties> <children> #include <include/accel-ppp/auth-local-users.xml.i> #include <include/accel-ppp/auth-mode.xml.i> #include <include/accel-ppp/auth-protocols.xml.i> #include <include/radius-auth-server-ipv4.xml.i> #include <include/accel-ppp/radius-additions.xml.i> <node name="radius"> <children> #include <include/accel-ppp/radius-additions-rate-limit.xml.i> <leafNode name="called-sid-format"> <properties> <help>Format of Called-Station-Id attribute</help> <completionHelp> <list>ifname ifname:mac</list> </completionHelp> <constraint> <regex>(ifname|ifname:mac)</regex> </constraint> <constraintErrorMessage>Invalid Called-Station-Id format</constraintErrorMessage> <valueHelp> <format>ifname</format> <description>NAS-Port-Id - should contain root interface name (NAS-Port-Id=eth1)</description> </valueHelp> <valueHelp> <format>ifname:mac</format> <description>NAS-Port-Id - should contain root interface name and mac address (NAS-Port-Id=eth1:00:00:00:00:00:00)</description> </valueHelp> </properties> </leafNode> </children> </node> <leafNode name="any-login"> <properties> <help>Authentication with any login</help> <valueless/> </properties> </leafNode> </children> </node> <tagNode name="interface"> <properties> <help>interface(s) to listen on</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces</script> </completionHelp> </properties> <children> #include <include/accel-ppp/vlan.xml.i> #include <include/accel-ppp/vlan-mon.xml.i> </children> </tagNode> <leafNode name="service-name"> <properties> <help>Service name</help> <constraint> <regex>[a-zA-Z0-9\-]{1,100}</regex> </constraint> <constraintErrorMessage>Service-name can contain aplhanumerical characters and dashes only (max. 100)</constraintErrorMessage> <multi/> </properties> </leafNode> + <leafNode name="accept-any-service"> + <properties> + <help>Accept any service name in PPPoE Active Discovery Request (PADR)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="accept-blank-service"> + <properties> + <help>Accept blank service name in PADR</help> + <valueless/> + </properties> + </leafNode> <tagNode name="pado-delay"> <properties> <help>PADO delays</help> <valueHelp> <format>disable</format> <description>Disable new connections</description> </valueHelp> <completionHelp> <list>disable</list> </completionHelp> <valueHelp> <format>u32:1-999999</format> <description>Number in ms</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-999999"/> <regex>disable</regex> </constraint> <constraintErrorMessage>Invalid PADO delay</constraintErrorMessage> </properties> <children> <leafNode name="sessions"> <properties> <help>Number of sessions</help> <valueHelp> <format>u32:1-999999</format> <description>Number of sessions</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-999999"/> </constraint> <constraintErrorMessage>Invalid number of delayed sessions</constraintErrorMessage> </properties> </leafNode> </children> </tagNode> <leafNode name="session-control"> <properties> <help>control sessions count</help> <constraint> <regex>(deny|disable|replace)</regex> </constraint> <constraintErrorMessage>Invalid value</constraintErrorMessage> <valueHelp> <format>disable</format> <description>Disables session control</description> </valueHelp> <valueHelp> <format>deny</format> <description>Deny second session authorization</description> </valueHelp> <valueHelp> <format>replace</format> <description>Terminate first session when second is authorized</description> </valueHelp> <completionHelp> <list>deny disable replace</list> </completionHelp> </properties> <defaultValue>replace</defaultValue> </leafNode> #include <include/accel-ppp/client-ip-pool.xml.i> #include <include/accel-ppp/client-ipv6-pool.xml.i> #include <include/accel-ppp/default-pool.xml.i> #include <include/accel-ppp/default-ipv6-pool.xml.i> #include <include/accel-ppp/extended-scripts.xml.i> #include <include/accel-ppp/gateway-address.xml.i> #include <include/accel-ppp/limits.xml.i> #include <include/accel-ppp/max-concurrent-sessions.xml.i> #include <include/accel-ppp/mtu-128-16384.xml.i> #include <include/accel-ppp/ppp-options.xml.i> <node name="ppp-options"> <children> <leafNode name="min-mtu"> <defaultValue>1280</defaultValue> </leafNode> </children> </node> #include <include/accel-ppp/shaper.xml.i> #include <include/accel-ppp/snmp.xml.i> #include <include/accel-ppp/wins-server.xml.i> #include <include/generic-description.xml.i> #include <include/name-server-ipv4-ipv6.xml.i> #include <include/accel-ppp/log.xml.i> </children> </node> </children> </node> </interfaceDefinition> diff --git a/smoketest/scripts/cli/test_service_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py index 8add5ee6c..8cd87e0f2 100755 --- a/smoketest/scripts/cli/test_service_pppoe-server.py +++ b/smoketest/scripts/cli/test_service_pppoe-server.py @@ -1,200 +1,216 @@ #!/usr/bin/env python3 # # Copyright (C) 2022-2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import unittest from base_accel_ppp_test import BasicAccelPPPTest from configparser import ConfigParser from vyos.utils.file import read_file from vyos.template import range_to_regex from vyos.configsession import ConfigSessionError local_if = ['interfaces', 'dummy', 'dum667'] ac_name = 'ACN' interface = 'eth0' class TestServicePPPoEServer(BasicAccelPPPTest.TestCase): @classmethod def setUpClass(cls): cls._base_path = ['service', 'pppoe-server'] cls._config_file = '/run/accel-pppd/pppoe.conf' cls._chap_secrets = '/run/accel-pppd/pppoe.chap-secrets' cls._protocol_section = 'pppoe' # call base-classes classmethod super(TestServicePPPoEServer, cls).setUpClass() def tearDown(self): self.cli_delete(local_if) super().tearDown() def verify(self, conf): mtu = '1492' # validate some common values in the configuration for tmp in ['log_syslog', 'pppoe', 'ippool', 'auth_mschap_v2', 'auth_mschap_v1', 'auth_chap_md5', 'auth_pap', 'shaper']: # Settings without values provide None self.assertEqual(conf['modules'][tmp], None) # check Access Concentrator setting self.assertTrue(conf['pppoe']['ac-name'] == ac_name) self.assertTrue(conf['pppoe'].getboolean('verbose')) self.assertTrue(conf['pppoe']['interface'], interface) # check ppp self.assertTrue(conf['ppp'].getboolean('verbose')) self.assertTrue(conf['ppp'].getboolean('check-ip')) self.assertEqual(conf['ppp']['mtu'], mtu) super().verify(conf) def basic_protocol_specific_config(self): self.cli_set(local_if + ['address', '192.0.2.1/32']) self.set(['access-concentrator', ac_name]) self.set(['interface', interface]) def test_pppoe_limits(self): self.basic_config() self.set(['limits', 'connection-limit', '20/min']) self.cli_commit() conf = ConfigParser(allow_no_value=True, delimiters='=') conf.read(self._config_file) self.assertEqual(conf['connlimit']['limit'], '20/min') def test_pppoe_server_authentication_protocols(self): # Test configuration of local authentication for PPPoE server self.basic_config() # explicitly test mschap-v2 - no special reason self.set( ['authentication', 'protocols', 'mschap-v2']) # commit changes self.cli_commit() # Validate configuration values conf = ConfigParser(allow_no_value=True) conf.read(self._config_file) self.assertEqual(conf['modules']['auth_mschap_v2'], None) def test_pppoe_server_shaper(self): fwmark = '223' limiter = 'tbf' self.basic_config() self.set(['shaper', 'fwmark', fwmark]) # commit changes self.cli_commit() # Validate configuration values conf = ConfigParser(allow_no_value=True, delimiters='=') conf.read(self._config_file) # basic verification self.verify(conf) self.assertEqual(conf['shaper']['fwmark'], fwmark) self.assertEqual(conf['shaper']['down-limiter'], limiter) def test_accel_radius_authentication(self): radius_called_sid = 'ifname:mac' self.set(['authentication', 'radius', 'called-sid-format', radius_called_sid]) # run common tests super().test_accel_radius_authentication() # Validate configuration values conf = ConfigParser(allow_no_value=True, delimiters='=') conf.read(self._config_file) # Validate configuration self.assertEqual(conf['pppoe']['called-sid'], radius_called_sid) def test_pppoe_server_vlan(self): vlans = ['100', '200', '300-310'] # Test configuration of local authentication for PPPoE server self.basic_config() self.set(['interface', interface, 'vlan-mon']) # cannot use option "vlan-mon" if no "vlan" set with self.assertRaises(ConfigSessionError): self.cli_commit() for vlan in vlans: self.set(['interface', interface, 'vlan', vlan]) # commit changes self.cli_commit() # Validate configuration values config = read_file(self._config_file) for vlan in vlans: tmp = range_to_regex(vlan) self.assertIn(f'interface=re:^{interface}\.{tmp}$', config) tmp = ','.join(vlans) self.assertIn(f'vlan-mon={interface},{tmp}', config) def test_pppoe_server_pado_delay(self): delay_without_sessions = '10' delays = {'20': '200', '30': '300'} self.basic_config() self.set(['pado-delay', delay_without_sessions]) self.cli_commit() conf = ConfigParser(allow_no_value=True, delimiters='=') conf.read(self._config_file) self.assertEqual(conf['pppoe']['pado-delay'], delay_without_sessions) for delay, sessions in delays.items(): self.set(['pado-delay', delay, 'sessions', sessions]) self.cli_commit() conf = ConfigParser(allow_no_value=True, delimiters='=') conf.read(self._config_file) self.assertEqual(conf['pppoe']['pado-delay'], '10,20:200,30:300') self.set(['pado-delay', 'disable', 'sessions', '400']) self.cli_commit() conf = ConfigParser(allow_no_value=True, delimiters='=') conf.read(self._config_file) self.assertEqual(conf['pppoe']['pado-delay'], '10,20:200,30:300,-1:400') def test_pppoe_server_any_login(self): # Test configuration of local authentication for PPPoE server self.basic_config() self.set(['authentication', 'any-login']) self.cli_commit() # Validate configuration values config = read_file(self._config_file) self.assertIn('any-login=1', config) + def test_pppoe_server_accept_service(self): + services = ['user1-service', 'user2-service'] + self.basic_config() + + for service in services: + self.set(['service-name', service]) + self.set(['accept-any-service']) + self.set(['accept-blank-service']) + self.cli_commit() + + # Validate configuration values + config = read_file(self._config_file) + self.assertIn(f'service-name={",".join(services)}', config) + self.assertIn('accept-any-service=1', config) + self.assertIn('accept-blank-service=1', config) + if __name__ == '__main__': unittest.main(verbosity=2)