summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhagbard <vyosdev@derith.de>2018-11-09 10:50:00 -0800
committerhagbard <vyosdev@derith.de>2018-11-09 12:27:21 -0800
commit55ee90e486a500868c7fd1b523d76179522f1f56 (patch)
treef76d3bbee12276cab490cdbd818d5970ce3820c3
parent60d961765217381108926c51521e90b222d60649 (diff)
downloadvyos-1x-55ee90e486a500868c7fd1b523d76179522f1f56.tar.gz
vyos-1x-55ee90e486a500868c7fd1b523d76179522f1f56.zip
T835: accel-ppp pppoe implementation
-rw-r--r--debian/changelog7
-rw-r--r--interface-definitions/pppoe-server.xml198
-rw-r--r--op-mode-definitions/pppoe-server.xml32
-rwxr-xr-xsrc/conf_mode/accel_pppoe.py363
4 files changed, 600 insertions, 0 deletions
diff --git a/debian/changelog b/debian/changelog
index d60c36316..1db603fe5 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,15 @@
+vyos-1x (1.2.0-5) unstable; urgency=medium
+
+ * T835: accel-ppp: pppoe implementation
+
+ -- hagbard <vyosdev@derith.de> Fri, 09 Nov 2018 10:49:48 -0800
+
vyos-1x (1.2.0-4) unstable; urgency=medium
* T240 adds feature system integrity check
-- hagbard <vyosdev@derith.de> Mon, 29 Oct 2018 11:10:18 -0700
+
vyos-1x (1.2.0-3) unstable; urgency=medium
* T933: adding vmac_xmit_base if use_vmac has been chosen
diff --git a/interface-definitions/pppoe-server.xml b/interface-definitions/pppoe-server.xml
new file mode 100644
index 000000000..ffefff1cb
--- /dev/null
+++ b/interface-definitions/pppoe-server.xml
@@ -0,0 +1,198 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="pppoe-server" owner="${vyos_conf_scripts_dir}/accel_pppoe.py">
+ <properties>
+ <help>Point to Point over Ethernet (PPPoE) Server</help>
+ <priority>900</priority>
+ </properties>
+ <children>
+ <leafNode name="access-concentrator">
+ <properties>
+ <help>Access concentrator name</help>
+ <constraint>
+ <regex>^[a-zA-Z0-9]{1,100}</regex>
+ </constraint>
+ <constraintErrorMessage>access-concentrator name limited to aplhanumerical characters only (max. 100)</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <node name="authentication">
+ <properties>
+ <help>Authentication for remote access PPPoE Server</help>
+ </properties>
+ <children>
+ <node name="local-users">
+ <properties>
+ <help>Local user authentication for PPPoE server</help>
+ </properties>
+ <children>
+ <tagNode name="username">
+ <properties>
+ <help>User name for authentication</help>
+ </properties>
+ <children>
+ <leafNode name="disable">
+ <properties>
+ <help>Option to disable a PPPoE Server user</help>
+ </properties>
+ </leafNode>
+ <leafNode name="password">
+ <properties>
+ <help>Password for authentication</help>
+ </properties>
+ </leafNode>
+ <leafNode name="static-ip">
+ <properties>
+ <help>Static client IP address</help>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ <leafNode name="mode">
+ <properties>
+ <help>Authentication mode for PPPoE Server</help>
+ <valueHelp>
+ <format>local</format>
+ <description>Use local username/password configuration</description>
+ </valueHelp>
+ <valueHelp>
+ <format>radius</format>
+ <description>Use Radius server to autenticate users</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(local|radius)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="client-ip-pool">
+ <properties>
+ <help>Pool of client IP address (must be within a /24)</help>
+ </properties>
+ <children>
+ <leafNode name="start">
+ <properties>
+ <help>First IP address in the pool</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="stop">
+ <properties>
+ <help>Last IP address in the pool</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="dns-servers">
+ <properties>
+ <help>Domain Name Service (DNS) server</help>
+ </properties>
+ <children>
+ <leafNode name="server-1">
+ <properties>
+ <help>Primary DNS server</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="server-2">
+ <properties>
+ <help>Secondary DNS server</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="interface">
+ <properties>
+ <help>interface(s) to listen on</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="local-ip">
+ <properties>
+ <help>local gateway address</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="mtu">
+ <properties>
+ <help>Maximum Transmission Unit (MTU) - default 1440</help>
+ <constraint>
+ <validator name="numeric" argument="--range 128-16384"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="radius">
+ <properties>
+ <help>RADIUS settings</help>
+ </properties>
+ <children>
+ <leafNode name="default-interim-interval">
+ <properties>
+ <help>Default interim accounting interval</help>
+ <valueHelp>
+ <format>text</format>
+ <description>Use local username/password configuration</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 60-10000000"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="service-name">
+ <properties>
+ <help>Service name</help>
+ <constraint>
+ <regex>^[a-zA-Z0-9\-]{1,100}</regex>
+ </constraint>
+ <constraintErrorMessage>servicename can contain aplhanumerical characters and dash only (max. 100)</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <node name="wins-servers">
+ <properties>
+ <help>Windows Internet Name Service (WINS) server settings</help>
+ </properties>
+ <children>
+ <leafNode name="server-1">
+ <properties>
+ <help>Primary WINS server</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="server-2">
+ <properties>
+ <help>Secondary WINS server</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/pppoe-server.xml b/op-mode-definitions/pppoe-server.xml
new file mode 100644
index 000000000..7595d6ecf
--- /dev/null
+++ b/op-mode-definitions/pppoe-server.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="pppoe-server">
+ <properties>
+ <help>show pppoe-server status</help>
+ </properties>
+ <children>
+ <leafNode name="sessions">
+ <properties>
+ <help>Show active PPPoE server sessions</help>
+ </properties>
+ <command>/usr/bin/accel-cmd 'show sessions'</command>
+ </leafNode>
+ <leafNode name="statistics">
+ <properties>
+ <help>Show PPPoE server statistics</help>
+ </properties>
+ <command>/usr/bin/accel-cmd 'show stat'</command>
+ </leafNode>
+ <leafNode name="interfaces">
+ <properties>
+ <help>Show interfaces where pppoe-server listens on</help>
+ </properties>
+ <command>/usr/bin/accel-cmd 'pppoe interface show'</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/src/conf_mode/accel_pppoe.py b/src/conf_mode/accel_pppoe.py
new file mode 100755
index 000000000..4aea84c44
--- /dev/null
+++ b/src/conf_mode/accel_pppoe.py
@@ -0,0 +1,363 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 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 sys
+import os
+import re
+import subprocess
+import jinja2
+import socket
+import time
+import syslog as sl
+
+from vyos.config import Config
+from vyos import ConfigError
+
+pidfile = r'/var/run/accel_pppoe.pid'
+pppoe_cnf_dir = r'/etc/accel-ppp/pppoe'
+chap_secrets = pppoe_cnf_dir + '/chap-secrets'
+pppoe_conf = pppoe_cnf_dir + '/pppoe.config'
+# accel-pppd -d -c /etc/accel-ppp/pppoe/pppoe.config -p /var/run/accel_pppoe.pid
+
+### config path creation
+if not os.path.exists(pppoe_cnf_dir):
+ os.makedirs(pppoe_cnf_dir)
+ sl.syslog(sl.LOG_NOTICE, pppoe_cnf_dir + " created")
+
+pppoe_config = '''
+### generated by accel_pppoe.py ###
+[modules]
+log_syslog
+pppoe
+ippool
+chap-secrets
+auth_pap
+auth_chap_md5
+auth_mschap_v1
+auth_mschap_v2
+pppd_compat
+shaper
+net-snmp
+connlimit
+{% if authentication['mode'] == 'radius' %}
+radius
+{% endif %}
+
+[core]
+thread-count={{thread_cnt}}
+
+[log]
+syslog=accel-pppoe,daemon
+copy=1
+level=5
+
+[snmp]
+master=1
+
+[client-ip-range]
+disable
+
+[ip-pool]
+{% if client_ip_pool %}
+{{client_ip_pool}}
+{% endif %}
+gw-ip-address={{ppp_gw}}
+
+{% if dns %}
+[dns]
+{% if dns[0] %}
+dns1={{dns[0]}}
+{% endif %}
+{% if dns[1] %}
+dns2={{dns[1]}}
+{% endif %}
+{% endif %}
+
+{% if wins %}
+[wins]
+{% if wins[0] %}
+wins1={{wins[0]}}
+{% endif %}
+{% if wins[1] %}
+wins2={{wins[1]}}
+{% endif %}
+{% endif %}
+
+{% if authentication['mode'] == 'local' %}
+[chap-secrets]
+chap-secrets=/etc/accel-ppp/pppoe/chap-secrets
+{% endif %}
+
+{% if authentication['mode'] == 'radius' %}
+[radius]
+{% for rsrv in authentication['radiussrv']: %}
+server={{rsrv}},{{authentication['radiussrv'][rsrv]}}
+{% endfor %}
+timeout=10
+acct-timeout=3
+gw-ip-address={{ppp_gw}}
+verbose=1
+{% endif %}
+
+[ppp]
+verbose=1
+min-mtu={{mtu}}
+mtu={{mtu}}
+mru=1400
+check-ip=1
+mppe=prefer
+ipv4=require
+check-ip=1
+single-session=replace
+mppe=prefer
+lcp-echo-interval=30
+lcp-echo-failure=3
+
+[pppoe]
+verbose=1
+{% if concentrator %}
+ac-name={{concentrator}}
+{% endif %}
+{% if interface %}
+{% for int in interface %}
+interface={{int}}
+{% endfor %}
+{% endif %}
+{% if svc_name %}
+service-name={{svc_name}}
+{% endif %}
+
+
+[connlimit]
+limit=10/min
+burst=3
+timeout=60
+
+[cli]
+tcp=127.0.0.1:2001
+'''
+
+### pppoe chap secrets
+chap_secrets_conf = '''
+# username server password acceptable local IP addresses
+{% for user in authentication['local-users'] %}
+{% if authentication['local-users'][user]['state'] == 'enabled' %}
+{{user}}\t*\t{{authentication['local-users'][user]['passwd']}}\t{{authentication['local-users'][user]['ip']}}
+{% endif %}
+{% endfor %}
+'''
+###
+# inline helper functions
+###
+# depending on hw and threads, daemon needs a little to start
+# if it takes longer than 100 * 0.5 secs, exception is being raised
+# not sure if that's the best way to check it, but it worked so far quite well
+###
+def chk_con():
+ cnt = 0
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ while True:
+ try:
+ s.connect(("127.0.0.1", 2001))
+ break
+ except ConnectionRefusedError:
+ time.sleep(0.5)
+ cnt +=1
+ if cnt == 100:
+ raise("failed to start pppoe server")
+ break
+
+### chap_secrets file if auth mode local
+def write_chap_secrets(c):
+ tmpl = jinja2.Template(chap_secrets_conf, trim_blocks=True)
+ chap_secrets_txt = tmpl.render(c)
+ old_umask = os.umask(0o077)
+ open(chap_secrets,'w').write(chap_secrets_txt)
+ os.umask(old_umask)
+ sl.syslog(sl.LOG_NOTICE, chap_secrets + ' written')
+
+def accel_cmd(cmd=''):
+ if not cmd:
+ return None
+ try:
+ ret = subprocess.check_output(['/usr/bin/accel-cmd',cmd]).decode().strip()
+ return ret
+ except:
+ return 1
+
+###
+# inline helper functions end
+###
+
+def get_config():
+ c = Config()
+ if not c.exists('service pppoe-server'):
+ return None
+
+ config_data = {
+ 'concentrator' : 'vyos-ac',
+ 'authentication' : {
+ 'local-users' : {
+ },
+ 'mode' : 'local',
+ 'radiussrv' : {}
+ },
+ 'client_ip_pool' : '',
+ 'interface' : [],
+ 'ppp_gw' : '',
+ 'svc_name' : '',
+ 'dns' : [],
+ 'wins' : [],
+ 'mtu' : '1492'
+ }
+
+ c.set_level('service pppoe-server')
+
+ if c.exists('access-concentrator'):
+ config_data['concentrator'] = c.return_value('access-concentrator')
+ if c.exists('service-name'):
+ config_data['svc_name'] = c.return_value('service-name')
+ if c.exists('interface'):
+ config_data['interface'] = c.return_values('interface')
+ if c.exists('local-ip'):
+ config_data['ppp_gw'] = c.return_value('local-ip')
+ if c.exists('dns-servers'):
+ if c.return_value('dns-servers server-1'):
+ config_data['dns'].append(c.return_value('dns-servers server-1'))
+ if c.return_value('dns-servers server-2'):
+ config_data['dns'].append(c.return_value('dns-servers server-2'))
+ if c.exists('wins-servers'):
+ if c.return_value('wins-servers server-1'):
+ config_data['wins'].append(c.return_value('wins-servers server-1'))
+ if c.return_value('wins-servers server-2'):
+ config_data['wins'].append(c.return_value('wins-servers server-2'))
+ if c.exists('client-ip-pool'):
+ if c.exists('client-ip-pool start'):
+ config_data['client_ip_pool'] = c.return_value('client-ip-pool start')
+ if c.exists('client-ip-pool stop'):
+ config_data['client_ip_pool'] += '-' + re.search('[0-9]+$', c.return_value('client-ip-pool stop')).group(0)
+ else:
+ raise ConfigError('client ip pool stop required')
+
+ #### authentication mode local
+ if c.exists('authentication'):
+ if c.return_value('authentication mode') == 'local':
+ if c.exists('authentication local-users username'):
+ for usr in c.list_nodes('authentication local-users username'):
+ config_data['authentication']['local-users'].update(
+ {
+ usr : {
+ 'passwd' : '',
+ 'state' : 'enabled',
+ 'ip' : '*'
+ }
+ }
+ )
+ if c.exists('authentication local-users username ' + usr + ' password'):
+ config_data['authentication']['local-users'][usr]['passwd'] = c.return_value('authentication local-users username ' + usr + ' password')
+ if c.exists('authentication local-users username ' + usr + ' disable'):
+ config_data['authentication']['local-users'][usr]['state'] = 'disable'
+ if c.exists('authentication local-users username ' + usr + ' static-ip'):
+ config_data['authentication']['local-users'][usr]['ip'] = c.return_value('authentication local-users username ' + usr + ' static-ip')
+
+ ### authentication mode radius
+ if c.return_value('authentication mode') == 'radius':
+ config_data['authentication']['mode'] = 'radius'
+ rsrvs = c.list_nodes('authentication radius-server')
+ for rsrv in rsrvs:
+ config_data['authentication']['radiussrv'].update(
+ {
+ rsrv : str(c.return_value('authentication radius-server ' + rsrv + ' key'))
+ }
+ )
+
+ if c.exists('mtu'):
+ config_data['mtu'] = c.return_value('mtu')
+
+ return config_data
+
+def verify(c):
+ if c == None:
+ return None
+
+ for usr in c['authentication']['local-users']:
+ if not c['authentication']['local-users'][usr]:
+ raise ConfigError('user ' + usr + ' has no password set')
+
+ if not c['ppp_gw']:
+ raise ConfigError('pppoe gateway-ip required')
+
+ if c['authentication']['mode'] == 'radius':
+ if len(c['authentication']['radiussrv']) == 0:
+ raise ConfigError('radius server required')
+
+def generate(c):
+ if c == None:
+ return None
+
+ ### accel-cmd reload doesn't work so any change results in a restart of the daemon
+ try:
+ if os.cpu_count() == 1:
+ c['thread_cnt'] = 1
+ else:
+ c['thread_cnt'] = int(os.cpu_count()/2)
+ except KeyError:
+ if os.cpu_count() == 1:
+ c['thread_cnt'] = 1
+ else:
+ c['thread_cnt'] = int(os.cpu_count()/2)
+
+ tmpl = jinja2.Template(pppoe_config, trim_blocks=True)
+ config_text = tmpl.render(c)
+ open(pppoe_conf,'w').write(config_text)
+ sl.syslog(sl.LOG_NOTICE, pppoe_config + ' written')
+
+ return c
+
+def apply(c):
+ if c == None:
+ if os.path.exists(pidfile):
+ accel_cmd('shutdown hard')
+ if os.path.exists(pidfile):
+ os.remove(pidfile)
+ return None
+
+ if not os.path.exists(pidfile):
+ ret = subprocess.call(['/usr/sbin/accel-pppd','-c',pppoe_conf,'-p',pidfile,'-d'])
+ chk_con()
+ if ret !=0 and os.path.exists(pidfile):
+ os.remove(pidfile)
+ raise ConfigError('accel-pppd failed to start')
+ else:
+ accel_cmd('restart')
+ sl.syslog(sl.LOG_NOTICE, "reloading config via daemon restart")
+
+ #if c['state'] == 'update':
+ # accel_cmd('restart')
+ # sl.syslog(sl.LOG_NOTICE, "reloading config via daemon restart")
+ # ## check that config reload actually works
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)