summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/configd-include.json1
-rw-r--r--data/templates/sstp-client/peer.j246
-rw-r--r--interface-definitions/interfaces-sstpc.xml.in47
-rw-r--r--op-mode-definitions/connect.xml.in1
-rw-r--r--op-mode-definitions/disconnect.xml.in1
-rw-r--r--op-mode-definitions/monitor-log.xml.in17
-rw-r--r--op-mode-definitions/show-interfaces-sstpc.xml.in51
-rw-r--r--op-mode-definitions/show-log.xml.in17
-rw-r--r--python/vyos/ifconfig/__init__.py3
-rw-r--r--python/vyos/ifconfig/sstpc.py40
-rwxr-xr-xsrc/conf_mode/interfaces-sstpc.py142
-rwxr-xr-xsrc/etc/ppp/ip-up.d/96-vyos-sstpc-callback49
-rwxr-xr-xsrc/op_mode/connect_disconnect.py4
13 files changed, 416 insertions, 3 deletions
diff --git a/data/configd-include.json b/data/configd-include.json
index 5a4912e30..648655a8b 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -28,6 +28,7 @@
"interfaces-openvpn.py",
"interfaces-pppoe.py",
"interfaces-pseudo-ethernet.py",
+"interfaces-sstpc.py",
"interfaces-tunnel.py",
"interfaces-vti.py",
"interfaces-vxlan.py",
diff --git a/data/templates/sstp-client/peer.j2 b/data/templates/sstp-client/peer.j2
new file mode 100644
index 000000000..1127d0564
--- /dev/null
+++ b/data/templates/sstp-client/peer.j2
@@ -0,0 +1,46 @@
+### Autogenerated by interfaces-sstpc.py ###
+{{ '# ' ~ description if description is vyos_defined else '' }}
+
+# Require peer to provide the local IP address if it is not
+# specified explicitly in the config file.
+noipdefault
+
+# Don't show the password in logfiles:
+hide-password
+
+remotename {{ ifname }}
+linkname {{ ifname }}
+ipparam {{ ifname }}
+ifname {{ ifname }}
+pty "sstpc --ipparam {{ ifname }} --nolaunchpppd {{ server }}:{{ port }} --ca-cert {{ ca_file_path }}"
+
+# Override any connect script that may have been set in /etc/ppp/options.
+connect /bin/true
+
+# Don't try to authenticate the remote node
+noauth
+
+# We won't want EAP
+refuse-eap
+
+# Don't try to proxy ARP for the remote endpoint. User can set proxy
+# arp entries up manually if they wish. More importantly, having
+# the "proxyarp" parameter set disables the "defaultroute" option.
+noproxyarp
+
+# Unlimited connection attempts
+maxfail 0
+
+plugin sstp-pppd-plugin.so
+sstp-sock /var/run/sstpc/sstpc-{{ ifname }}
+
+persist
+debug
+
+{% if authentication is vyos_defined %}
+{{ 'user "' + authentication.user + '"' if authentication.user is vyos_defined }}
+{{ 'password "' + authentication.password + '"' if authentication.password is vyos_defined }}
+{% endif %}
+
+{{ "usepeerdns" if no_peer_dns is not vyos_defined }}
+
diff --git a/interface-definitions/interfaces-sstpc.xml.in b/interface-definitions/interfaces-sstpc.xml.in
new file mode 100644
index 000000000..30b55a9fa
--- /dev/null
+++ b/interface-definitions/interfaces-sstpc.xml.in
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="interfaces">
+ <children>
+ <tagNode name="sstpc" owner="${vyos_conf_scripts_dir}/interfaces-sstpc.py">
+ <properties>
+ <help>Secure Socket Tunneling Protocol (SSTP) client Interface</help>
+ <priority>460</priority>
+ <constraint>
+ <regex>sstpc[0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>Secure Socket Tunneling Protocol interface must be named sstpcN</constraintErrorMessage>
+ <valueHelp>
+ <format>sstpcN</format>
+ <description>Secure Socket Tunneling Protocol interface name</description>
+ </valueHelp>
+ </properties>
+ <children>
+ #include <include/interface/description.xml.i>
+ #include <include/interface/disable.xml.i>
+ #include <include/interface/authentication.xml.i>
+ #include <include/interface/no-default-route.xml.i>
+ #include <include/interface/default-route-distance.xml.i>
+ #include <include/interface/no-peer-dns.xml.i>
+ #include <include/interface/mtu-68-1500.xml.i>
+ <leafNode name="mtu">
+ <defaultValue>1452</defaultValue>
+ </leafNode>
+ #include <include/server-ipv4-fqdn.xml.i>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>443</defaultValue>
+ </leafNode>
+ <node name="ssl">
+ <properties>
+ <help>Secure Sockets Layer (SSL) configuration</help>
+ </properties>
+ <children>
+ #include <include/pki/ca-certificate.xml.i>
+ </children>
+ </node>
+ #include <include/interface/vrf.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/connect.xml.in b/op-mode-definitions/connect.xml.in
index d0c93195c..116cd6231 100644
--- a/op-mode-definitions/connect.xml.in
+++ b/op-mode-definitions/connect.xml.in
@@ -20,6 +20,7 @@
<help>Bring up a connection-oriented network interface</help>
<completionHelp>
<path>interfaces pppoe</path>
+ <path>interfaces sstpc</path>
<path>interfaces wwan</path>
</completionHelp>
</properties>
diff --git a/op-mode-definitions/disconnect.xml.in b/op-mode-definitions/disconnect.xml.in
index 4415c0ed2..843998c4f 100644
--- a/op-mode-definitions/disconnect.xml.in
+++ b/op-mode-definitions/disconnect.xml.in
@@ -10,6 +10,7 @@
<help>Take down a connection-oriented network interface</help>
<completionHelp>
<path>interfaces pppoe</path>
+ <path>interfaces sstpc</path>
<path>interfaces wwan</path>
</completionHelp>
</properties>
diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in
index dccdfaf9a..1b1f53dc2 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -224,6 +224,23 @@
</properties>
<command>journalctl --no-hostname --boot --follow --unit ssh.service</command>
</leafNode>
+ <node name="sstpc">
+ <properties>
+ <help>Monitor last lines of SSTP client log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit "ppp@sstpc*.service"</command>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Monitor last lines of SSTP client log for specific interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py -t sstpc</script>
+ </completionHelp>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit "ppp@$5.service"</command>
+ </tagNode>
+ </children>
+ </node>
<node name="vpn">
<properties>
<help>Show log for Virtual Private Network (VPN)</help>
diff --git a/op-mode-definitions/show-interfaces-sstpc.xml.in b/op-mode-definitions/show-interfaces-sstpc.xml.in
new file mode 100644
index 000000000..e66d3a0ac
--- /dev/null
+++ b/op-mode-definitions/show-interfaces-sstpc.xml.in
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="interfaces">
+ <children>
+ <tagNode name="sstpc">
+ <properties>
+ <help>Show specified SSTP client interface information</help>
+ <completionHelp>
+ <path>interfaces sstpc</path>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <children>
+ <leafNode name="log">
+ <properties>
+ <help>Show specified SSTP client interface log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit "ppp@$4".service</command>
+ </leafNode>
+ <leafNode name="statistics">
+ <properties>
+ <help>Show specified SSTP client interface statistics</help>
+ <completionHelp>
+ <path>interfaces sstpc</path>
+ </completionHelp>
+ </properties>
+ <command>if [ -d "/sys/class/net/$4" ]; then /usr/sbin/pppstats "$4"; fi</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="sstpc">
+ <properties>
+ <help>Show SSTP client interface information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=sstpc --action=show-brief</command>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show detailed SSTP client interface information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=sstpc --action=show</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index 404de1913..64a54015b 100644
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -356,6 +356,23 @@
</properties>
<command>journalctl --no-hostname --boot --unit ssh.service</command>
</leafNode>
+ <node name="sstpc">
+ <properties>
+ <help>Show log for SSTP client</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit "ppp@sstpc*.service"</command>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Show SSTP client log on specific interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py -t sstpc</script>
+ </completionHelp>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit "ppp@$5.service"</command>
+ </tagNode>
+ </children>
+ </node>
<tagNode name="tail">
<properties>
<help>Show last n changes to messages</help>
diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py
index d1ddaa13e..206b2bba1 100644
--- a/python/vyos/ifconfig/__init__.py
+++ b/python/vyos/ifconfig/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -38,3 +38,4 @@ from vyos.ifconfig.l2tpv3 import L2TPv3If
from vyos.ifconfig.macsec import MACsecIf
from vyos.ifconfig.veth import VethIf
from vyos.ifconfig.wwan import WWANIf
+from vyos.ifconfig.sstpc import SSTPCIf
diff --git a/python/vyos/ifconfig/sstpc.py b/python/vyos/ifconfig/sstpc.py
new file mode 100644
index 000000000..50fc6ee6b
--- /dev/null
+++ b/python/vyos/ifconfig/sstpc.py
@@ -0,0 +1,40 @@
+# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+from vyos.ifconfig.interface import Interface
+
+@Interface.register
+class SSTPCIf(Interface):
+ iftype = 'sstpc'
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'sstpc',
+ 'prefixes': ['sstpc', ],
+ 'eternal': 'sstpc[0-9]+$',
+ },
+ }
+
+ def _create(self):
+ # we can not create this interface as it is managed outside
+ pass
+
+ def _delete(self):
+ # we can not create this interface as it is managed outside
+ pass
+
+ def get_mac(self):
+ """ Get a synthetic MAC address. """
+ return self.get_mac_synthetic()
diff --git a/src/conf_mode/interfaces-sstpc.py b/src/conf_mode/interfaces-sstpc.py
new file mode 100755
index 000000000..6b8094c51
--- /dev/null
+++ b/src/conf_mode/interfaces-sstpc.py
@@ -0,0 +1,142 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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 os
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import get_interface_dict
+from vyos.configdict import is_node_changed
+from vyos.configverify import verify_authentication
+from vyos.configverify import verify_vrf
+from vyos.ifconfig import SSTPCIf
+from vyos.pki import encode_certificate
+from vyos.pki import find_chain
+from vyos.pki import load_certificate
+from vyos.template import render
+from vyos.util import call
+from vyos.util import dict_search
+from vyos.util import is_systemd_service_running
+from vyos.util import write_file
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+def get_config(config=None):
+ """
+ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the
+ interface name will be added or a deleted flag
+ """
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['interfaces', 'sstpc']
+ ifname, sstpc = get_interface_dict(conf, base)
+
+ # We should only terminate the SSTP client session if critical parameters
+ # change. All parameters that can be changed on-the-fly (like interface
+ # description) should not lead to a reconnect!
+ for options in ['authentication', 'no_peer_dns', 'no_default_route',
+ 'server', 'ssl']:
+ if is_node_changed(conf, base + [ifname, options]):
+ sstpc.update({'shutdown_required': {}})
+ # bail out early - no need to further process other nodes
+ break
+
+ # Load PKI certificates for later processing
+ sstpc['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+ return sstpc
+
+def verify(sstpc):
+ if 'deleted' in sstpc:
+ return None
+
+ verify_authentication(sstpc)
+ verify_vrf(sstpc)
+
+ if dict_search('ssl.ca_certificate', sstpc) == None:
+ raise ConfigError('Missing mandatory CA certificate!')
+
+ return None
+
+def generate(sstpc):
+ ifname = sstpc['ifname']
+ config_sstpc = f'/etc/ppp/peers/{ifname}'
+
+ sstpc['ca_file_path'] = f'/run/sstpc/{ifname}_ca-cert.pem'
+
+ if 'deleted' in sstpc:
+ for file in [sstpc['ca_file_path'], config_sstpc]:
+ if os.path.exists(file):
+ os.unlink(file)
+ return None
+
+ ca_name = sstpc['ssl']['ca_certificate']
+ pki_ca_cert = sstpc['pki']['ca'][ca_name]
+
+ loaded_ca_cert = load_certificate(pki_ca_cert['certificate'])
+ loaded_ca_certs = {load_certificate(c['certificate'])
+ for c in sstpc['pki']['ca'].values()} if 'ca' in sstpc['pki'] else {}
+
+ ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
+
+ write_file(sstpc['ca_file_path'], '\n'.join(encode_certificate(c) for c in ca_full_chain))
+ render(config_sstpc, 'sstp-client/peer.j2', sstpc, permission=0o640)
+
+ return None
+
+def apply(sstpc):
+ ifname = sstpc['ifname']
+ if 'deleted' in sstpc or 'disable' in sstpc:
+ if os.path.isdir(f'/sys/class/net/{ifname}'):
+ p = SSTPCIf(ifname)
+ p.remove()
+ call(f'systemctl stop ppp@{ifname}.service')
+ return None
+
+ # reconnect should only be necessary when specific options change,
+ # like server, authentication ... (see get_config() for details)
+ if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or
+ 'shutdown_required' in sstpc):
+
+ # cleanup system (e.g. FRR routes first)
+ if os.path.isdir(f'/sys/class/net/{ifname}'):
+ p = SSTPCIf(ifname)
+ p.remove()
+
+ call(f'systemctl restart ppp@{ifname}.service')
+ # When interface comes "live" a hook is called:
+ # /etc/ppp/ip-up.d/96-vyos-sstpc-callback
+ # which triggers SSTPCIf.update()
+ else:
+ if os.path.isdir(f'/sys/class/net/{ifname}'):
+ p = SSTPCIf(ifname)
+ p.update(sstpc)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/etc/ppp/ip-up.d/96-vyos-sstpc-callback b/src/etc/ppp/ip-up.d/96-vyos-sstpc-callback
new file mode 100755
index 000000000..4e8804f29
--- /dev/null
+++ b/src/etc/ppp/ip-up.d/96-vyos-sstpc-callback
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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/>.
+
+# This is a Python hook script which is invoked whenever a SSTP client session
+# goes "ip-up". It will call into our vyos.ifconfig library and will then
+# execute common tasks for the SSTP interface. The reason we have to "hook" this
+# is that we can not create a sstpcX interface in advance in linux and then
+# connect pppd to this already existing interface.
+
+from sys import argv
+from sys import exit
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.configdict import get_interface_dict
+from vyos.ifconfig import SSTPCIf
+
+# When the ppp link comes up, this script is called with the following
+# parameters
+# $1 the interface name used by pppd (e.g. ppp3)
+# $2 the tty device name
+# $3 the tty device speed
+# $4 the local IP address for the interface
+# $5 the remote IP address
+# $6 the parameter specified by the 'ipparam' option to pppd
+
+if (len(argv) < 7):
+ exit(1)
+
+interface = argv[6]
+
+conf = ConfigTreeQuery()
+_, sstpc = get_interface_dict(conf.config, ['interfaces', 'sstpc'], interface)
+
+# Update the config
+p = SSTPCIf(interface)
+p.update(sstpc)
diff --git a/src/op_mode/connect_disconnect.py b/src/op_mode/connect_disconnect.py
index 936c20bcb..d39e88bf3 100755
--- a/src/op_mode/connect_disconnect.py
+++ b/src/op_mode/connect_disconnect.py
@@ -41,7 +41,7 @@ def check_ppp_running(interface):
def connect(interface):
""" Connect dialer interface """
- if interface.startswith('ppp'):
+ if interface.startswith('pppoe') or interface.startswith('sstpc'):
check_ppp_interface(interface)
# Check if interface is already dialed
if os.path.isdir(f'/sys/class/net/{interface}'):
@@ -62,7 +62,7 @@ def connect(interface):
def disconnect(interface):
""" Disconnect dialer interface """
- if interface.startswith('ppp'):
+ if interface.startswith('pppoe') or interface.startswith('sstpc'):
check_ppp_interface(interface)
# Check if interface is already down