Page MenuHomeVyOS Platform

No OneTemporary

Size
220 KB
Referenced Files
None
Subscribers
None
diff --git a/Makefile b/Makefile
index 2e49a204c..89b83d4f4 100644
--- a/Makefile
+++ b/Makefile
@@ -1,60 +1,62 @@
TMPL_DIR := templates-cfg
OP_TMPL_DIR := templates-op
.PHONY: interface_definitions
.ONESHELL:
interface_definitions:
mkdir -p $(TMPL_DIR)
find $(CURDIR)/interface-definitions/ -type f -name "*.xml" | xargs -I {} $(CURDIR)/scripts/build-command-templates {} $(CURDIR)/schema/interface_definition.rng $(TMPL_DIR) || exit 1
# XXX: delete top level node.def's that now live in other packages
rm -f $(TMPL_DIR)/firewall/node.def
rm -f $(TMPL_DIR)/interfaces/node.def
+ rm -f $(TMPL_DIR)/interfaces/bridge/node.tag/ip/node.def
rm -f $(TMPL_DIR)/protocols/node.def
rm -f $(TMPL_DIR)/protocols/static/node.def
rm -f $(TMPL_DIR)/system/node.def
rm -f $(TMPL_DIR)/system/options/node.def
rm -f $(TMPL_DIR)/vpn/node.def
rm -f $(TMPL_DIR)/vpn/ipsec/node.def
.PHONY: op_mode_definitions
.ONESHELL:
op_mode_definitions:
mkdir -p $(OP_TMPL_DIR)
find $(CURDIR)/op-mode-definitions/ -type f -name "*.xml" | xargs -I {} $(CURDIR)/scripts/build-command-op-templates {} $(CURDIR)/schema/op-mode-definition.rng $(OP_TMPL_DIR) || exit 1
# XXX: delete top level op mode node.def's that now live in other packages
rm -f $(OP_TMPL_DIR)/clear/node.def
rm -f $(OP_TMPL_DIR)/set/node.def
rm -f $(OP_TMPL_DIR)/show/node.def
rm -f $(OP_TMPL_DIR)/show/interfaces/node.def
rm -f $(OP_TMPL_DIR)/show/ip/node.def
rm -f $(OP_TMPL_DIR)/reset/node.def
rm -f $(OP_TMPL_DIR)/restart/node.def
rm -f $(OP_TMPL_DIR)/monitor/node.def
rm -f $(OP_TMPL_DIR)/generate/node.def
+ rm -f $(OP_TMPL_DIR)/show/vpn/node.def
.PHONY: all
all: clean interface_definitions op_mode_definitions
.PHONY: clean
clean:
rm -rf $(TMPL_DIR)/*
rm -rf $(OP_TMPL_DIR)/*
.PHONY: test
test:
PYTHONPATH=python/ python3 -m "nose" --with-xunit src --with-coverage --cover-erase --cover-xml --cover-package src/conf_mode,src/op_mode,src/completion,src/helpers,src/validators,src/tests --verbose
.PHONY: sonar
sonar:
sonar-scanner -X -Dsonar.login=${SONAR_TOKEN}
.PHONY: docs
.ONESHELL:
docs:
sphinx-apidoc -o sphinx/source/ python/
cd sphinx/
PYTHONPATH=../python make html
diff --git a/interface-definitions/interfaces-bridge.xml b/interface-definitions/interfaces-bridge.xml
new file mode 100644
index 000000000..d20582849
--- /dev/null
+++ b/interface-definitions/interfaces-bridge.xml
@@ -0,0 +1,269 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="interfaces">
+ <children>
+ <tagNode name="bridge" owner="${vyos_conf_scripts_dir}/interface-bridge.py">
+ <properties>
+ <help>Bridge interface name</help>
+ <priority>310</priority>
+ <constraint>
+ <regex>^br[0-9]+$</regex>
+ </constraint>
+ <constraintErrorMessage>Bridge interface must be named brN</constraintErrorMessage>
+ <valueHelp>
+ <format>brN</format>
+ <description>Bridge interface name</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="address">
+ <properties>
+ <help>IP address</help>
+ <completionHelp>
+ <list>dhcp dhcpv6</list>
+ </completionHelp>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 address and prefix length</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 address and prefix length</description>
+ </valueHelp>
+ <valueHelp>
+ <format>dhcp</format>
+ <description>Dynamic Host Configuration Protocol</description>
+ </valueHelp>
+ <valueHelp>
+ <format>dhcpv6</format>
+ <description>Dynamic Host Configuration Protocol for IPv6</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-cidr"/>
+ <regex>(dhcp|dhcpv6)</regex>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="aging">
+ <properties>
+ <help>Interval addresses are retained</help>
+ <valueHelp>
+ <format>0</format>
+ <description>Disable retaining address in bridge (always flood)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>10-1000000</format>
+ <description>Address aging time for bridge seconds (default 300)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-0"/>
+ <validator name="numeric" argument="--range 10-1000000"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="description">
+ <properties>
+ <help>Interface description</help>
+ <constraint>
+ <regex>^.{1,256}$</regex>
+ </constraint>
+ <constraintErrorMessage>Interface description too long (limit 256 characters)</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <node name="dhcp-options">
+ <properties>
+ <help>DHCP options</help>
+ </properties>
+ <children>
+ <leafNode name="client-id">
+ <properties>
+ <help>DHCP client identifier</help>
+ </properties>
+ </leafNode>
+ <leafNode name="host-name">
+ <properties>
+ <help>DHCP client host name (overrides the system host name)</help>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="dhcpv6-options">
+ <properties>
+ <help>DHCPv6 options</help>
+ <priority>319</priority>
+ </properties>
+ <children>
+ <leafNode name="parameters-only">
+ <properties>
+ <help>Acquire only config parameters, no address</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="temporary">
+ <properties>
+ <help>IPv6 "temporary" address</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="disable-link-detect">
+ <properties>
+ <help>Ignore link state changes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="disable">
+ <properties>
+ <help>Disable this bridge interface</help>
+ </properties>
+ </leafNode>
+ <leafNode name="forwarding-delay">
+ <properties>
+ <help>Forwarding delay</help>
+ <valueHelp>
+ <format>0-200</format>
+ <description>Spanning Tree Protocol forwarding delay in seconds (default 15)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-200"/>
+ </constraint>
+ <constraintErrorMessage>Forwarding delay must be between 0 and 200 seconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="hello-time">
+ <properties>
+ <help>Hello packet advertisment interval</help>
+ <valueHelp>
+ <format>1-10</format>
+ <description>Spanning Tree Protocol hello advertisement interval in seconds (default 2)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-10"/>
+ </constraint>
+ <constraintErrorMessage>Bridge Hello interval must be between 1 and 10 seconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <node name="igmp">
+ <properties>
+ <help>Internet Group Management Protocol (IGMP) settings</help>
+ </properties>
+ <children>
+ <leafNode name="querier">
+ <properties>
+ <help>Enable IGMP querier</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="ip">
+ <children>
+ <leafNode name="arp-cache-timeout">
+ <properties>
+ <help>ARP cache entry timeout in seconds</help>
+ <valueHelp>
+ <format>1-86400</format>
+ <description>ARP cache entry timout in seconds (default 30)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-86400"/>
+ </constraint>
+ <constraintErrorMessage>Bridge max aging value must be between 6 and 86400 seconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="mac">
+ <properties>
+ <help>Media Access Control (MAC) address</help>
+ <valueHelp>
+ <format>h:h:h:h:h:h</format>
+ <description>Hardware (MAC) address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="mac-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="max-age">
+ <properties>
+ <help>Interval at which neighbor bridges are removed</help>
+ <valueHelp>
+ <format>1-40</format>
+ <description>Bridge maximum aging time in seconds (default 20)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-40"/>
+ </constraint>
+ <constraintErrorMessage>Bridge max aging value must be between 1 and 40 seconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <node name="member">
+ <properties>
+ <help>Bridge member interfaces</help>
+ </properties>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Member interface name</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py --bridgeable</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="cost">
+ <properties>
+ <help>Bridge port cost</help>
+ <valueHelp>
+ <format>1-65535</format>
+ <description>Path cost value for Spanning Tree Protocol</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ <constraintErrorMessage>Path cost value must be between 1 and 65535</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="priority">
+ <properties>
+ <help>Bridge port priority</help>
+ <valueHelp>
+ <format>0-63</format>
+ <description>Bridge port priority</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-63"/>
+ </constraint>
+ <constraintErrorMessage>Port priority value must be between 0 and 63</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ <leafNode name="priority">
+ <properties>
+ <help>Priority for this bridge</help>
+ <valueHelp>
+ <format>0-65535</format>
+ <description>Bridge priority (default 32768)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-65535"/>
+ </constraint>
+ <constraintErrorMessage>Bridge priority must be between 0 and 65535 (multiples of 4096)</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="stp">
+ <properties>
+ <help>Enable spanning tree protocol</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/wireguard.xml b/interface-definitions/interfaces-wireguard.xml
similarity index 93%
rename from interface-definitions/wireguard.xml
rename to interface-definitions/interfaces-wireguard.xml
index 9cfcd32ee..6e2622018 100644
--- a/interface-definitions/wireguard.xml
+++ b/interface-definitions/interfaces-wireguard.xml
@@ -1,141 +1,142 @@
<?xml version="1.0"?>
<interfaceDefinition>
<node name="interfaces">
<children>
- <tagNode name="wireguard" owner="${vyos_conf_scripts_dir}/wireguard.py">
+ <tagNode name="wireguard" owner="${vyos_conf_scripts_dir}/interface-wireguard.py">
<properties>
<help>WireGuard interface name</help>
- <priority>459</priority> <!-- subsequent ones may be removed, just make sure ethernet ifs are present -->
+ <priority>459</priority>
+ <!-- subsequent ones may be removed, just make sure ethernet ifs are present -->
<constraint>
<regex>^wg[0-9]{1,4}</regex>
</constraint>
<constraintErrorMessage>illegal interface name</constraintErrorMessage>
<valueHelp>
<format>wgN</format>
<description>WireGuard interface name</description>
</valueHelp>
</properties>
<children>
<leafNode name="address">
<properties>
- <help>IP address</help>
+ <help>IP address</help>
<constraint>
- <validator name="cidr"/>
+ <validator name="ip-cidr"/>
</constraint>
<valueHelp>
<format>ipv4net</format>
<description>IPv4 address and prefix length</description>
</valueHelp>
<valueHelp>
<format>ipv6net</format>
<description>IPv6 address and prefix length</description>
</valueHelp>
<multi/>
</properties>
</leafNode>
<leafNode name="description">
<properties>
<help>description</help>
<constraint>
<regex>^.{1,100}$</regex>
</constraint>
<constraintErrorMessage>interface description is too long (limit 100 characters)</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="disable">
<properties>
<help>disables interface</help>
- <valueless />
+ <valueless/>
</properties>
</leafNode>
<leafNode name="port">
<properties>
<help>Local port number to accept connections</help>
<constraint>
<validator name="numeric" argument="--range 1024-65535"/>
</constraint>
</properties>
</leafNode>
<leafNode name="mtu">
<properties>
<help>interface mtu size(default: 1420)</help>
<constraint>
<validator name="numeric" argument="--range 68-9000"/>
</constraint>
</properties>
</leafNode>
<leafNode name="fwmark">
<properties>
<help>A 32-bit fwmark value set on all outgoing packets</help>
<valueHelp>
<format>number</format>
<description>value which marks the packet for QoS/shaper</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-255"/>
</constraint>
</properties>
</leafNode>
<tagNode name="peer">
<properties>
<help>peer alias</help>
<constraint>
<regex>[^ ]{1,100}$</regex>
</constraint>
<constraintErrorMessage>peer alias too long (limit 100 characters)</constraintErrorMessage>
</properties>
<children>
<leafNode name="disable">
<properties>
<help>disables peer</help>
- <valueless />
+ <valueless/>
</properties>
</leafNode>
<leafNode name="pubkey">
<properties>
<help>base64 encoded public key</help>
<constraint>
<regex>^[0-9a-zA-Z\+/]{43}=$</regex>
</constraint>
<constraintErrorMessage>Key is not valid 44-character (32-bytes) base64</constraintErrorMessage>
</properties>
</leafNode>
- <leafNode name="preshared-key">
+ <leafNode name="preshared-key">
<properties>
<help>base64 encoded preshared key</help>
<constraint>
<regex>^[0-9a-zA-Z\+/]{43}=$</regex>
</constraint>
<constraintErrorMessage>Key is not valid 44-character (32-bytes) base64</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="allowed-ips">
<properties>
<help>IP addresses allowed to traverse the peer</help>
<constraint>
<validator name="ip-prefix"/>
</constraint>
- <multi/>
+ <multi/>
</properties>
</leafNode>
<!-- eventually check format IP:port -->
<leafNode name="endpoint">
<properties>
<help>Remote endpoint (IP:port)</help>
</properties>
</leafNode>
<leafNode name="persistent-keepalive">
<properties>
<help>how often send keep alives in seconds</help>
<constraint>
- <validator name="numeric" argument="--range 1-65535"/>
+ <validator name="numeric" argument="--range 1-65535"/>
</constraint>
</properties>
</leafNode>
</children>
</tagNode>
</children>
</tagNode>
</children>
</node>
</interfaceDefinition>
diff --git a/interface-definitions/l2tp-server.xml b/interface-definitions/l2tp-server.xml
new file mode 100644
index 000000000..721913dfe
--- /dev/null
+++ b/interface-definitions/l2tp-server.xml
@@ -0,0 +1,562 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="vpn">
+ <children>
+ <node name="l2tp" owner="${vyos_conf_scripts_dir}/accel_l2tp.py">
+ <properties>
+ <help>L2TP Virtual Private Network (VPN)</help>
+ </properties>
+ <children>
+ <node name="remote-access">
+ <properties>
+ <help>Remote access L2TP VPN</help>
+ </properties>
+ <children>
+ <leafNode name="mtu">
+ <properties>
+ <help>Maximum Transmission Unit (MTU)</help>
+ <constraint>
+ <validator name="numeric" argument="--range 128-16384"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="outside-address">
+ <properties>
+ <help>External IP address to which VPN clients will connect</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="outside-nexthop">
+ <properties>
+ <help>Nexthop IP address for reaching the VPN clients</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="dns-servers">
+ <properties>
+ <help>IPv4 Domain Name Service (DNS) server</help>
+ </properties>
+ <children>
+ <leafNode name="server-1">
+ <properties>
+ <help>Primary DNS server</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="server-2">
+ <properties>
+ <help>Secondary DNS server</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="dnsv6-servers">
+ <properties>
+ <help>IPv6 Domain Name Service (DNS) server</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 DNS address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ <multi />
+ </properties>
+ </leafNode>
+ <node name="lns">
+ <properties>
+ <help>L2TP Network Server (LNS)</help>
+ </properties>
+ <children>
+ <leafNode name="shared-secret">
+ <properties>
+ <help>Tunnel password used to authenticate the client (LAC)</help>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="ccp-disable">
+ <properties>
+ <help>Disable Compression Control Protocol (CCP)</help>
+ <valueless />
+ </properties>
+ </leafNode>
+ <node name="ipsec-settings">
+ <properties>
+ <help>Internet Protocol Security (IPsec) for remote access L2TP VPN</help>
+ </properties>
+ <children>
+ <node name="authentication">
+ <properties>
+ <help>IPsec authentication settings</help>
+ </properties>
+ <children>
+ <leafNode name="mode">
+ <properties>
+ <help>Authentication mode for IPsec</help>
+ <valueHelp>
+ <format>pre-shared-secret</format>
+ <description>Use pre-shared secret for IPsec authentication</description>
+ </valueHelp>
+ <valueHelp>
+ <format>x509</format>
+ <description>Use X.509 certificate for IPsec authentication</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(pre-shared-secret|x509)</regex>
+ </constraint>
+ <completionHelp>
+ <list>pre-shared-secret x509</list>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="pre-shared-secret">
+ <properties>
+ <help>Pre-shared secret for IPsec</help>
+ </properties>
+ </leafNode>
+ <node name="x509">
+ <properties>
+ <help>X.509 certificate</help>
+ </properties>
+ <children>
+ <leafNode name="ca-cert-file">
+ <properties>
+ <help>File containing the X.509 certificate for the Certificate Authority (CA)</help>
+ <valueHelp>
+ <format>&lt;text&gt;</format>
+ <description>File in /config/auth</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="crl-file">
+ <properties>
+ <help>File containing the X.509 Certificate Revocation List (CRL)</help>
+ <valueHelp>
+ <format>&lt;text&gt;</format>
+ <description>File in /config/auth</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="server-cert-file">
+ <properties>
+ <help>File containing the X.509 certificate for the remote access VPN server (this host)</help>
+ <valueHelp>
+ <format>&lt;text&gt;</format>
+ <description>File in /config/auth</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="server-key-file">
+ <properties>
+ <help>File containing the private key for the X.509 certificate for the remote access VPN server (this host)</help>
+ <valueHelp>
+ <format>&lt;text&gt;</format>
+ <description>File in /config/auth</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="server-key-password">
+ <properties>
+ <help>Password that protects the private key</help>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ <leafNode name="ike-lifetime">
+ <properties>
+ <help>IKE lifetime</help>
+ <valueHelp>
+ <format>&lt;30-86400&gt;</format>
+ <description>IKE lifetime in seconds (default 3600)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 30-86400"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="lifetime">
+ <properties>
+ <help>ESP lifetime</help>
+ <valueHelp>
+ <format>&lt;30-86400&gt;</format>
+ <description>IKE lifetime in seconds (default 3600)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 30-86400"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <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>
+ <node name="client-ip-pool">
+ <properties>
+ <help>Pool of client IP addresses (must be within a /24)</help>
+ </properties>
+ <children>
+ <leafNode name="start">
+ <properties>
+ <help>First IP address in the pool (will be used as gateway address)</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>
+ <leafNode name="subnet">
+ <properties>
+ <help>Client IP subnet (CIDR notation)</help>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ </constraint>
+ <constraintErrorMessage>Not a valid CIDR formatted prefix</constraintErrorMessage>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 subnet address</description>
+ </valueHelp>
+ <multi />
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="client-ipv6-pool">
+ <properties>
+ <help>Pool of client IPv6 addresses</help>
+ </properties>
+ <children>
+ <leafNode name="prefix">
+ <properties>
+ <help>IPV6 prefix delegation</help>
+ <valueHelp>
+ <format>ipv6prefix/mask,prefix_len</format>
+ <description>e.g.: fc00:0:1::/48,64 - divides prefix into /64 subnets for clients</description>
+ </valueHelp>
+ <multi />
+ </properties>
+ </leafNode>
+ <leafNode name="delegate-prefix">
+ <properties>
+ <help>DHCPv6 prefix delegation - rfc3633</help>
+ <valueHelp>
+ <format>ipv6prefix/mask,prefix_len</format>
+ <description>Delegate to clients through DHCPv6 prefix delegation - rfc3633</description>
+ </valueHelp>
+ <multi />
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="description">
+ <properties>
+ <help>Description for L2TP remote-access settings</help>
+ </properties>
+ </leafNode>
+ <leafNode name="dhcp-interface">
+ <properties>
+ <help>DHCP interface to listen on</help>
+ </properties>
+ </leafNode>
+ <leafNode name="idle">
+ <properties>
+ <help>PPP idle timeout</help>
+ <valueHelp>
+ <format>&lt;30-86400&gt;</format>
+ <description>PPP idle timeout in seconds (default 1800)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 30-86400"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="authentication">
+ <properties>
+ <help>Authentication for remote access L2TP VPN</help>
+ </properties>
+ <children>
+ <leafNode name="require">
+ <properties>
+ <help>Authentication protocol for remote access peer L2TP VPN</help>
+ <valueHelp>
+ <format>pap</format>
+ <description>Require the peer to authenticate itself using PAP [Password Authentication Protocol].</description>
+ </valueHelp>
+ <valueHelp>
+ <format>chap</format>
+ <description>Require the peer to authenticate itself using CHAP [Challenge Handshake Authentication Protocol].</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mschap</format>
+ <description>Require the peer to authenticate itself using CHAP [Challenge Handshake Authentication Protocol].</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mschap-v2</format>
+ <description>Require the peer to authenticate itself using MS-CHAPv2 [Microsoft Challenge Handshake Authentication Protocol, Version 2].</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(pap|chap|mschap|mschap-v2)</regex>
+ </constraint>
+ <completionHelp>
+ <list>pap chap mschap mschap-v2</list>
+ </completionHelp>
+ <multi />
+ </properties>
+ </leafNode>
+ <leafNode name="mppe">
+ <properties>
+ <help>Specifies mppe negotioation preference. (default require mppe 128-bit stateless</help>
+ <valueHelp>
+ <format>deny</format>
+ <description>deny mppe</description>
+ </valueHelp>
+ <valueHelp>
+ <format>prefer</format>
+ <description>ask client for mppe, if it rejects don't fail</description>
+ </valueHelp>
+ <valueHelp>
+ <format>require</format>
+ <description>ask client for mppe, if it rejects drop connection</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(deny|prefer|require)</regex>
+ </constraint>
+ <completionHelp>
+ <list>deny prefer require</list>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="mode">
+ <properties>
+ <help>Authentication mode for remote access L2TP VPN</help>
+ <valueHelp>
+ <format>local</format>
+ <description>Use local username/password configuration</description>
+ </valueHelp>
+ <valueHelp>
+ <format>radius</format>
+ <description>Use a RADIUS server to autenticate users</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(local|radius)</regex>
+ </constraint>
+ <completionHelp>
+ <list>local radius</list>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <node name="local-users">
+ <properties>
+ <help>Local user authentication for remote access L2TP VPN</help>
+ </properties>
+ <children>
+ <tagNode name="username">
+ <properties>
+ <help>User name for authentication</help>
+ </properties>
+ <children>
+ <leafNode name="disable">
+ <properties>
+ <help>Option to disable a L2TP 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>
+ <node name="rate-limit">
+ <properties>
+ <help>Upload/Download speed limits</help>
+ </properties>
+ <children>
+ <leafNode name="upload">
+ <properties>
+ <help>Upload bandwidth limit in kbits/sec</help>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="download">
+ <properties>
+ <help>Download bandwidth limit in kbits/sec</help>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ <node name="radius">
+ <properties>
+ <help>RADIUS specific configuration</help>
+ </properties>
+ <children>
+ <tagNode name="server">
+ <properties>
+ <help>IP address of radius server</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IP address of RADIUS server</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="key">
+ <properties>
+ <help>Key for accessing the specified server</help>
+ </properties>
+ </leafNode>
+ <leafNode name="req-limit">
+ <properties>
+ <help>Maximum number of simultaneous requests to server (default: unlimited)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="fail-time">
+ <properties>
+ <help>If server doesn't responds mark it as unavailable for this amount of time in seconds</help>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="source-address">
+ <properties>
+ <help>Local RADIUS client address from which packets are sent.</help>
+ <valueHelp>
+ <format>&lt;x.x.x.x&gt;</format>
+ <description>Local RADIUS client address from which packets are sent</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="timeout">
+ <properties>
+ <help>Timeout to wait response from server (seconds)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="acct-timeout">
+ <properties>
+ <help>Timeout to wait reply for Interim-Update packets. (default 3 seconds)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="max-try">
+ <properties>
+ <help>Maximum number of tries to send Access-Request/Accounting-Request queries</help>
+ </properties>
+ </leafNode>
+ <leafNode name="nas-identifier">
+ <properties>
+ <help>Value to send to RADIUS server in NAS-Identifier attribute and to be matched in DM/CoA requests.</help>
+ </properties>
+ </leafNode>
+ <node name="dae-server">
+ <properties>
+ <help>IPv4 address and port to bind Dynamic Authorization Extension server (DM/CoA)</help>
+ </properties>
+ <children>
+ <leafNode name="ip-address">
+ <properties>
+ <help>IP address for Dynamic Authorization Extension server (DM/CoA)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="port">
+ <properties>
+ <help>Port for Dynamic Authorization Extension server (DM/CoA)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="secret">
+ <properties>
+ <help>Secret for Dynamic Authorization Extension server (DM/CoA)</help>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="rate-limit">
+ <properties>
+ <help>Upload/Download speed limits</help>
+ </properties>
+ <children>
+ <leafNode name="attribute">
+ <properties>
+ <help>Specifies which radius attribute contains rate information. (default is Filter-Id)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="vendor">
+ <properties>
+ <help>Specifies the vendor dictionary. (dictionary needs to be in /usr/share/accel-ppp/radius)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="enable">
+ <properties>
+ <help>Enables Bandwidth shaping via RADIUS</help>
+ <valueless />
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/protocols-bfd.xml b/interface-definitions/protocols-bfd.xml
index 47d5bf97d..f2d7d7d2f 100644
--- a/interface-definitions/protocols-bfd.xml
+++ b/interface-definitions/protocols-bfd.xml
@@ -1,114 +1,132 @@
<?xml version="1.0"?>
<!-- Bidirectional Forwarding Detection (BFD) configuration -->
<interfaceDefinition>
<node name="protocols">
<children>
<node name="bfd" owner="${vyos_conf_scripts_dir}/protocols_bfd.py">
<properties>
<help>Bidirectional Forwarding Detection (BFD)</help>
<priority>820</priority>
</properties>
<children>
<tagNode name="peer">
<properties>
<help>Configures a new BFD peer to listen and talk to</help>
<valueHelp>
<format>ipv4</format>
<description>BFD peer IPv4 address</description>
</valueHelp>
<valueHelp>
<format>ipv6</format>
<description>BFD peer IPv6 address</description>
</valueHelp>
</properties>
<children>
<node name="source">
<properties>
<help>Bind listener to specifid interface/address, mandatory for IPv6</help>
</properties>
<children>
<leafNode name="interface">
<properties>
<help>Local interface to bind our peer listener to</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces.py</script>
</completionHelp>
</properties>
</leafNode>
<leafNode name="address">
<properties>
<help>Local address to bind our peer listener to</help>
<valueHelp>
<format>ipv4</format>
<description>Local IPv4 address used to connect to the peer</description>
</valueHelp>
<valueHelp>
<format>ipv6</format>
<description>Local IPv6 address used to connect to the peer</description>
</valueHelp>
</properties>
</leafNode>
</children>
</node>
<node name="interval">
<properties>
<help>Configure timer intervals</help>
</properties>
<children>
<leafNode name="receive">
<properties>
<help>Minimum interval of receiving control packets</help>
<valueHelp>
<format>10-60000</format>
<description>Interval in milliseconds</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 10-60000"/>
</constraint>
</properties>
</leafNode>
<leafNode name="transmit">
<properties>
<help>Minimum interval of transmitting control packets</help>
<valueHelp>
<format>10-60000</format>
<description>Interval in milliseconds</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 10-60000"/>
</constraint>
</properties>
</leafNode>
<leafNode name="multiplier">
<properties>
<help>Multiplier to determine packet loss</help>
<valueHelp>
<format>2-255</format>
<description>Remote transmission interval will be multiplied by this value</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 2-255"/>
</constraint>
</properties>
</leafNode>
+ <leafNode name="echo-interval">
+ <properties>
+ <help>Echo receive transmission interval</help>
+ <valueHelp>
+ <format>10-60000</format>
+ <description>The minimal echo receive transmission interval that this system is capable of handling</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 10-60000"/>
+ </constraint>
+ </properties>
+ </leafNode>
</children>
</node>
<leafNode name="shutdown">
<properties>
<help>Disable this peer</help>
<valueless/>
</properties>
</leafNode>
<leafNode name="multihop">
<properties>
<help>Allow this BFD peer to not be directly connected</help>
<valueless/>
</properties>
</leafNode>
+ <leafNode name="echo-mode">
+ <properties>
+ <help>Enables the echo transmission mode</help>
+ <valueless/>
+ </properties>
+ </leafNode>
</children>
</tagNode>
</children>
</node>
</children>
</node>
</interfaceDefinition>
diff --git a/interface-definitions/sstp.xml b/interface-definitions/sstp.xml
new file mode 100644
index 000000000..d944baaad
--- /dev/null
+++ b/interface-definitions/sstp.xml
@@ -0,0 +1,416 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="sstp-server" owner="${vyos_conf_scripts_dir}/accel_sstp.py">
+ <properties>
+ <help>Secure Socket Tunneling Protocol (SSTP) Server</help>
+ <priority>900</priority>
+ </properties>
+ <children>
+ <node name="authentication">
+ <properties>
+ <help>Authentication for remote access SSTP Server</help>
+ </properties>
+ <children>
+ <node name="local-users">
+ <properties>
+ <help>Local user authentication for SSTP 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 SSTP Server user</help>
+ <valueless />
+ </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>
+ <node name="rate-limit">
+ <properties>
+ <help>Upload/Download speed limits</help>
+ </properties>
+ <children>
+ <leafNode name="upload">
+ <properties>
+ <help>Upload bandwidth limit in kbits/sec</help>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="download">
+ <properties>
+ <help>Download bandwidth limit in kbits/sec</help>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ <leafNode name="mode">
+ <properties>
+ <help>Authentication mode for SSTP Server</help>
+ <valueHelp>
+ <format>local</format>
+ <description>Use local username/password configuration</description>
+ </valueHelp>
+ <valueHelp>
+ <format>radius</format>
+ <description>Use a RADIUS server to autenticate users</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(local|radius)</regex>
+ </constraint>
+ <completionHelp>
+ <list>local radius</list>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="protocols">
+ <properties>
+ <help>Authentication protocol for remote access peer SSTP VPN</help>
+ <valueHelp>
+ <format>pap</format>
+ <description>Require the peer to authenticate itself using PAP [Password Authentication Protocol].</description>
+ </valueHelp>
+ <valueHelp>
+ <format>chap</format>
+ <description>Require the peer to authenticate itself using CHAP [Challenge Handshake Authentication Protocol].</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mschap</format>
+ <description>Require the peer to authenticate itself using CHAP [Challenge Handshake Authentication Protocol].</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mschap-v2</format>
+ <description>Require the peer to authenticate itself using MS-CHAPv2 [Microsoft Challenge Handshake Authentication Protocol, Version 2].</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(pap|chap|mschap|mschap-v2)</regex>
+ </constraint>
+ <completionHelp>
+ <list>pap chap mschap mschap-v2</list>
+ </completionHelp>
+ <multi />
+ </properties>
+ </leafNode>
+ <tagNode name="radius-server">
+ <properties>
+ <help>IP address of RADIUS server</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IP address of RADIUS server</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="secret">
+ <properties>
+ <help>Key for accessing the specified server</help>
+ </properties>
+ </leafNode>
+ <leafNode name="req-limit">
+ <properties>
+ <help>Maximum number of simultaneous requests to server (default: unlimited)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="fail-time">
+ <properties>
+ <help>If server doesn't responds mark it as unavailable for this amount of time in seconds</help>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="radius-settings">
+ <properties>
+ <help>RADIUS settings</help>
+ </properties>
+ <children>
+ <leafNode name="timeout">
+ <properties>
+ <help>Timeout to wait response from server (seconds)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="acct-timeout">
+ <properties>
+ <help>Timeout to wait reply for Interim-Update packets. (default 3 seconds)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="max-try">
+ <properties>
+ <help>Maximum number of tries to send Access-Request/Accounting-Request queries</help>
+ </properties>
+ </leafNode>
+ <leafNode name="nas-identifier">
+ <properties>
+ <help>Value to send to RADIUS server in NAS-Identifier attribute and to be matched in DM/CoA requests.</help>
+ </properties>
+ </leafNode>
+ <leafNode name="nas-ip-address">
+ <properties>
+ <help>Value to send to RADIUS server in NAS-IP-Address attribute and to be matched in DM/CoA requests. Also DM/CoA server will bind to that address.</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ <constraintErrorMessage>invalid IPv4 address</constraintErrorMessage>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>NAS-IP-Address Attribute Value</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <node name="dae-server">
+ <properties>
+ <help>IPv4 address and port to bind Dynamic Authorization Extension server (DM/CoA)</help>
+ </properties>
+ <children>
+ <leafNode name="ip-address">
+ <properties>
+ <help>IP address for Dynamic Authorization Extension server (DM/CoA)</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ <constraintErrorMessage>invalid IPv4 address</constraintErrorMessage>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Specifies IP address for Dynamic Authorization Extension server (DM/CoA)</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="port">
+ <properties>
+ <help>Port for Dynamic Authorization Extension server (DM/CoA)</help>
+ <valueHelp>
+ <format>number</format>
+ <description>TCP port</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="secret">
+ <properties>
+ <help>Secret for Dynamic Authorization Extension server (DM/CoA)</help>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="rate-limit">
+ <properties>
+ <help>Upload/Download speed limits</help>
+ </properties>
+ <children>
+ <leafNode name="attribute">
+ <properties>
+ <help>Specifies which radius attribute contains rate information. (default is Filter-Id)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="vendor">
+ <properties>
+ <help>Specifies the vendor dictionary. (dictionary needs to be in /usr/share/accel-ppp/radius)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="enable">
+ <properties>
+ <help>Enables Bandwidth shaping via RADIUS</help>
+ <valueless />
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="sstp-settings">
+ <properties>
+ <help>SSTP settings</help>
+ </properties>
+ <children>
+ <node name="ssl-certs">
+ <properties>
+ <help>SSL Certificate, SSL Key and CA (/config/user-data/sstp)</help>
+ </properties>
+ <children>
+ <leafNode name="ca">
+ <properties>
+ <help>Certificate Authority certificate</help>
+ <completionHelp>
+ <script>if [ -e /config/user-data/sstp ]; then ls /config/user-data/sstp; fi</script>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="server-cert">
+ <properties>
+ <help>Server Certificate</help>
+ <completionHelp>
+ <script>if [ -e /config/user-data/sstp ]; then ls /config/user-data/sstp; fi</script>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="server-key">
+ <properties>
+ <help>Privat Key of the Server Certificate</help>
+ <completionHelp>
+ <script>if [ -e /config/user-data/sstp ]; then ls /config/user-data/sstp; fi</script>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="network-settings">
+ <properties>
+ <help>Network settings</help>
+ </properties>
+ <children>
+ <node name="client-ip-settings">
+ <properties>
+ <help>Client IP pools and gateway setting</help>
+ </properties>
+ <children>
+ <leafNode name="subnet">
+ <properties>
+ <help>Client IP subnet (CIDR notation)</help>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ </constraint>
+ <constraintErrorMessage>Not a valid CIDR formatted prefix</constraintErrorMessage>
+ <multi />
+ </properties>
+ </leafNode>
+ <leafNode name="gateway-address">
+ <properties>
+ <help>Gateway IP address</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ <constraintErrorMessage>invalid IPv4 address</constraintErrorMessage>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Default Gateway send to the client</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="dns-server">
+ <properties>
+ <help>DNS servers propagated to clients</help>
+ </properties>
+ <children>
+ <leafNode name="primary-dns">
+ <properties>
+ <help>Primary DNS Server</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="secondary-dns">
+ <properties>
+ <help>Secondary DNS Server</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="mtu">
+ <properties>
+ <help>Maximum Transmission Unit (MTU)</help>
+ <constraint>
+ <validator name="numeric" argument="--range 128-16384"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="ppp-settings">
+ <properties>
+ <help>PPP (Point-to-Point Protocol) settings</help>
+ </properties>
+ <children>
+ <leafNode name="mppe">
+ <properties>
+ <help>Specifies mppe negotiation preferences</help>
+ <completionHelp>
+ <list>require prefer deny</list>
+ </completionHelp>
+ <constraint>
+ <regex>(^require|prefer|deny)</regex>
+ </constraint>
+ <valueHelp>
+ <format>require</format>
+ <description>send mppe request, if client rejects, drop the connection</description>
+ </valueHelp>
+ <valueHelp>
+ <format>prefer</format>
+ <description>send mppe request, if client rejects continue</description>
+ </valueHelp>
+ <valueHelp>
+ <format>deny</format>
+ <description>drop all mppe</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="lcp-echo-interval">
+ <properties>
+ <help>LCP echo-requests/sec</help>
+ <constraint>
+ <validator name="numeric" argument="--positive"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="lcp-echo-failure">
+ <properties>
+ <help>Maximum number of Echo-Requests may be sent without valid reply</help>
+ <constraint>
+ <validator name="numeric" argument="--positive"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="lcp-echo-timeout">
+ <properties>
+ <help>Timeout in seconds to wait for any peer activity. If this option specified it turns on adaptive lcp echo functionality and "lcp-echo-failure" is not used.</help>
+ <constraint>
+ <validator name="numeric" argument="--positive"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+</node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/l2tp-server.xml b/op-mode-definitions/l2tp-server.xml
new file mode 100644
index 000000000..fb1b85ce4
--- /dev/null
+++ b/op-mode-definitions/l2tp-server.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="l2tp-server">
+ <properties>
+ <help>show l2tp-server status</help>
+ </properties>
+ <children>
+ <leafNode name="sessions">
+ <properties>
+ <help>Show active L2TP server sessions</help>
+ </properties>
+ <command>/usr/bin/accel-cmd -p 2004 'show sessions'</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/show-protocols-bfd.xml b/op-mode-definitions/show-protocols-bfd.xml
index 3c682d6f7..2a94d0497 100644
--- a/op-mode-definitions/show-protocols-bfd.xml
+++ b/op-mode-definitions/show-protocols-bfd.xml
@@ -1,30 +1,46 @@
<?xml version="1.0"?>
<interfaceDefinition>
<node name="show">
<children>
<node name="protocols">
<children>
<node name="bfd">
<children>
<node name="peer">
<properties>
<help>Show all Bidirectional Forwarding Detection (BFD) peer status</help>
</properties>
<command>/usr/bin/vtysh -c "show bfd peers"</command>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Show Bidirectional Forwarding Detection (BFD) peer counters</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show bfd peers counters"</command>
+ </leafNode>
+ </children>
</node>
<tagNode name="peer">
<properties>
<help>Show Bidirectional Forwarding Detection (BFD) peer status</help>
<completionHelp>
<script>/usr/bin/vtysh -c "show bfd peer" | grep peer | awk '{print $2}'</script>
</completionHelp>
</properties>
<command>/usr/bin/vtysh -c "show bfd peer $5"</command>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Show Bidirectional Forwarding Detection (BFD) peer counters</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show bfd peer $5 counters"</command>
+ </leafNode>
+ </children>
</tagNode>
</children>
</node>
</children>
</node>
</children>
</node>
</interfaceDefinition>
diff --git a/op-mode-definitions/show-vpn.xml b/op-mode-definitions/show-vpn.xml
new file mode 100644
index 000000000..0e7fc38e9
--- /dev/null
+++ b/op-mode-definitions/show-vpn.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="vpn">
+ <properties>
+ <help>Show active remote access Virtual Private Network (VPN) sessions</help>
+ </properties>
+ <children>
+ <leafNode name="remote-access">
+ <properties>
+ <help>Show active VPN server sessions</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_vpn_ra.py</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/sstp-server.xml b/op-mode-definitions/sstp-server.xml
new file mode 100644
index 000000000..36d0b9985
--- /dev/null
+++ b/op-mode-definitions/sstp-server.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="sstp-server">
+ <properties>
+ <help>show sstp-server status</help>
+ </properties>
+ <children>
+ <leafNode name="sessions">
+ <properties>
+ <help>Show active SSTP server sessions</help>
+ </properties>
+ <command>/usr/bin/accel-cmd -p 2005 'show sessions ifname,username,ip,ip6,ip6-dp,calling-sid,rate-limit,state,uptime,rx-bytes,tx-bytes'</command>
+ </leafNode>
+ <leafNode name="statistics">
+ <properties>
+ <help>Show SSTP server statistics</help>
+ </properties>
+ <command>/usr/bin/accel-cmd -p 2005 'show stat'</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/python/vyos/configinterface.py b/python/vyos/configinterface.py
new file mode 100644
index 000000000..0f5b0842c
--- /dev/null
+++ b/python/vyos/configinterface.py
@@ -0,0 +1,153 @@
+# Copyright 2019 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/>.
+
+import os
+import vyos.validate
+
+def validate_mac_address(addr):
+ # a mac address consits out of 6 octets
+ octets = len(addr.split(':'))
+ if octets != 6:
+ raise ValueError('wrong number of MAC octets: {} '.format(octets))
+
+ # validate against the first mac address byte if it's a multicast address
+ if int(addr.split(':')[0]) & 1:
+ raise ValueError('{} is a multicast MAC address'.format(addr))
+
+ # overall mac address is not allowed to be 00:00:00:00:00:00
+ if sum(int(i, 16) for i in addr.split(':')) == 0:
+ raise ValueError('00:00:00:00:00:00 is not a valid MAC address')
+
+ # check for VRRP mac address
+ if addr.split(':')[0] == '0' and addr.split(':')[1] == '0' and addr.split(':')[2] == '94' and addr.split(':')[3] == '0' and addr.split(':')[4] == '1':
+ raise ValueError('{} is a VRRP MAC address')
+
+ pass
+
+def set_mac_address(intf, addr):
+ """
+ Configure interface mac address using iproute2 command
+ """
+ validate_mac_address(addr)
+
+ os.system('ip link set {} address {}'.format(intf, addr))
+ pass
+
+def set_description(intf, desc):
+ """
+ Sets the interface secription reported usually by SNMP
+ """
+ with open('/sys/class/net/' + intf + '/ifalias', 'w') as f:
+ f.write(desc)
+
+ pass
+
+def set_arp_cache_timeout(intf, tmoMS):
+ """
+ Configure the ARP cache entry timeout in milliseconds
+ """
+ with open('/proc/sys/net/ipv4/neigh/' + intf + '/base_reachable_time_ms', 'w') as f:
+ f.write(tmoMS)
+
+ pass
+
+def set_multicast_querier(intf, enable):
+ """
+ Sets whether the bridge actively runs a multicast querier or not. When a
+ bridge receives a 'multicast host membership' query from another network host,
+ that host is tracked based on the time that the query was received plus the
+ multicast query interval time.
+
+ use enable=1 to enable or enable=0 to disable
+ """
+
+ if int(enable) >= 0 and int(enable) <= 1:
+ with open('/sys/devices/virtual/net/' + intf + '/bridge/multicast_querier', 'w') as f:
+ f.write(str(enable))
+ else:
+ raise ValueError("malformed configuration string on interface {}: enable={}".format(intf, enable))
+
+ pass
+
+def set_link_detect(intf, enable):
+ """
+ 0 - Allow packets to be received for the address on this interface
+ even if interface is disabled or no carrier.
+
+ 1 - Ignore packets received if interface associated with the incoming
+ address is down.
+
+ 2 - Ignore packets received if interface associated with the incoming
+ address is down or has no carrier.
+
+ Kernel Source: Documentation/networking/ip-sysctl.txt
+ """
+
+ # Note can't use sysctl it is broken for vif name because of dots
+ # link_filter values:
+ # 0 - always receive
+ # 1 - ignore receive if admin_down
+ # 2 - ignore receive if admin_down or link down
+
+ with open('/proc/sys/net/ipv4/conf/' + intf + '/link_filter', 'w') as f:
+ if enable == True or enable == 1:
+ f.write('2')
+ if os.path.isfile('/usr/bin/vtysh'):
+ os.system('/usr/bin/vtysh -c "configure terminal" -c "interface {}" -c "link-detect"'.format(intf))
+ else:
+ f.write('1')
+ if os.path.isfile('/usr/bin/vtysh'):
+ os.system('/usr/bin/vtysh -c "configure terminal" -c "interface {}" -c "no link-detect"'.format(intf))
+
+ pass
+
+def add_interface_address(intf, addr):
+ """
+ Configure an interface IPv4/IPv6 address
+ """
+ if addr == "dhcp":
+ os.system('/opt/vyatta/sbin/vyatta-interfaces.pl --dev="{}" --dhcp=start'.format(intf))
+ elif addr == "dhcpv6":
+ os.system('/opt/vyatta/sbin/vyatta-dhcpv6-client.pl --start -ifname "{}"'.format(intf))
+ elif vyos.validate.is_ipv4(addr):
+ if not vyos.validate.is_intf_addr_assigned(intf, addr):
+ print("Assigning {} to {}".format(addr, intf))
+ os.system('sudo ip -4 addr add "{}" broadcast + dev "{}"'.format(addr, intf))
+ elif vyos.validate.is_ipv6(addr):
+ if not vyos.validate.is_intf_addr_assigned(intf, addr):
+ print("Assigning {} to {}".format(addr, intf))
+ os.system('sudo ip -6 addr add "{}" dev "{}"'.format(addr, intf))
+ else:
+ raise ConfigError('{} is not a valid interface address'.format(addr))
+
+ pass
+
+def remove_interface_address(intf, addr):
+ """
+ Remove IPv4/IPv6 address from given interface
+ """
+
+ if addr == "dhcp":
+ os.system('/opt/vyatta/sbin/vyatta-interfaces.pl --dev="{}" --dhcp=stop'.format(intf))
+ elif addr == "dhcpv6":
+ os.system('/opt/vyatta/sbin/vyatta-dhcpv6-client.pl --stop -ifname "{}"'.format(intf))
+ elif vyos.validate.is_ipv4(addr):
+ os.system('ip -4 addr del "{}" dev "{}"'.format(addr, intf))
+ elif vyos.validate.is_ipv6(addr):
+ os.system('ip -6 addr del "{}" dev "{}"'.format(addr, intf))
+ else:
+ raise ConfigError('{} is not a valid interface address'.format(addr))
+
+ pass
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index 78f332d66..8626839f2 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -1,148 +1,157 @@
# configsession -- the write API for the VyOS running config
# Copyright (C) 2019 VyOS maintainers and contributors
#
# 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import os
import re
import sys
import subprocess
CLI_SHELL_API = '/bin/cli-shell-api'
SET = '/opt/vyatta/sbin/my_set'
DELETE = '/opt/vyatta/sbin/my_delete'
COMMENT = '/opt/vyatta/sbin/my_comment'
COMMIT = '/opt/vyatta/sbin/my_commit'
DISCARD = '/opt/vyatta/sbin/my_discard'
+SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig']
# Default "commit via" string
APP = "vyos-http-api"
# When started as a service rather than from a user shell,
# the process lacks the VyOS-specific environment that comes
# from bash configs, so we have to inject it
# XXX: maybe it's better to do via a systemd environment file
def inject_vyos_env(env):
env['VYATTA_CFG_GROUP_NAME'] = 'vyattacfg'
env['VYATTA_USER_LEVEL_DIR'] = '/opt/vyatta/etc/shell/level/admin'
env['vyatta_bindir']= '/opt/vyatta/bin'
env['vyatta_cfg_templates'] = '/opt/vyatta/share/vyatta-cfg/templates'
env['vyatta_configdir'] = '/opt/vyatta/config'
env['vyatta_datadir'] = '/opt/vyatta/share'
env['vyatta_datarootdir'] = '/opt/vyatta/share'
env['vyatta_libdir'] = '/opt/vyatta/lib'
env['vyatta_libexecdir'] = '/opt/vyatta/libexec'
env['vyatta_op_templates'] = '/opt/vyatta/share/vyatta-op/templates'
env['vyatta_prefix'] = '/opt/vyatta'
env['vyatta_sbindir'] = '/opt/vyatta/sbin'
env['vyatta_sysconfdir'] = '/opt/vyatta/etc'
env['vyos_bin_dir'] = '/usr/bin'
env['vyos_cfg_templates'] = '/opt/vyatta/share/vyatta-cfg/templates'
env['vyos_completion_dir'] = '/usr/libexec/vyos/completion'
env['vyos_configdir'] = '/opt/vyatta/config'
env['vyos_conf_scripts_dir'] = '/usr/libexec/vyos/conf_mode'
env['vyos_datadir'] = '/opt/vyatta/share'
env['vyos_datarootdir']= '/opt/vyatta/share'
env['vyos_libdir'] = '/opt/vyatta/lib'
env['vyos_libexec_dir'] = '/usr/libexec/vyos'
env['vyos_op_scripts_dir'] = '/usr/libexec/vyos/op_mode'
env['vyos_op_templates'] = '/opt/vyatta/share/vyatta-op/templates'
env['vyos_prefix'] = '/opt/vyatta'
env['vyos_sbin_dir'] = '/usr/sbin'
env['vyos_validators_dir'] = '/usr/libexec/vyos/validators'
return env
class ConfigSessionError(Exception):
pass
class ConfigSession(object):
"""
The write API of VyOS.
"""
def __init__(self, session_id, app=APP):
"""
Creates a new config session.
Args:
session_id (str): Session identifier
app (str): Application name, purely informational
Note:
The session identifier MUST be globally unique within the system.
The best practice is to only have one ConfigSession object per process
and used the PID for the session identifier.
"""
env_str = subprocess.check_output([CLI_SHELL_API, 'getSessionEnv', str(session_id)])
self.__session_id = session_id
# Extract actual variables from the chunk of shell it outputs
# XXX: it's better to extend cli-shell-api to provide easily readable output
env_list = re.findall(r'([A-Z_]+)=([^;\s]+)', env_str.decode())
session_env = os.environ
session_env = inject_vyos_env(session_env)
for k, v in env_list:
session_env[k] = v
self.__session_env = session_env
self.__session_env["COMMIT_VIA"] = app
self.__run_command([CLI_SHELL_API, 'setupSession'])
def __del__(self):
try:
output = subprocess.check_output([CLI_SHELL_API, 'teardownSession'], env=self.__session_env).decode().strip()
if output:
print("cli-shell-api teardownSession output for sesion {0}: {1}".format(self.__session_id, output), file=sys.stderr)
except Exception as e:
print("Could not tear down session {0}: {1}".format(self.__session_id, e), file=sys.stderr)
def __run_command(self, cmd_list):
p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=self.__session_env)
result = p.wait()
output = p.stdout.read().decode()
if result != 0:
raise ConfigSessionError(output)
+ return output
def get_session_env(self):
return self.__session_env
def set(self, path, value=None):
if not value:
value = []
else:
value = [value]
self.__run_command([SET] + path + value)
def delete(self, path, value=None):
if not value:
value = []
else:
value = [value]
self.__run_command([DELETE] + path + value)
def comment(self, path, value=None):
if not value:
value = [""]
else:
value = [value]
self.__run_command([COMMENT] + path + value)
def commit(self):
self.__run_command([COMMIT])
def discard(self):
self.__run_command([DISCARD])
+
+ def show_config(self, path, format='raw'):
+ config_data = self.__run_command(SHOW_CONFIG + path)
+
+ if format == 'raw':
+ return config_data
+
diff --git a/python/vyos/interfaceconfig.py b/python/vyos/interfaceconfig.py
new file mode 100644
index 000000000..b8bfb707e
--- /dev/null
+++ b/python/vyos/interfaceconfig.py
@@ -0,0 +1,376 @@
+#!/usr/bin/python3
+
+# Copyright 2019 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/>.
+
+import sys
+import os
+import re
+import json
+import socket
+import subprocess
+
+dhclient_conf_dir = r'/var/lib/dhcp/dhclient_'
+
+class Interface:
+ def __init__(self, ifname=None, type=None):
+ if not ifname:
+ raise Exception("interface name required")
+ if not os.path.exists('/sys/class/net/{0}'.format(ifname)) and not type:
+ raise Exception("interface {0} not found".format(str(ifname)))
+ else:
+ if not os.path.exists('/sys/class/net/{0}'.format(ifname)):
+ try:
+ ret = subprocess.check_output(['ip link add dev ' + str(ifname) + ' type ' + type], stderr=subprocess.STDOUT, shell=True).decode()
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ if "Operation not supported" in str(e.output.decode()):
+ print(str(e.output.decode()))
+ sys.exit(0)
+
+ self._ifname = str(ifname)
+
+
+ @property
+ def mtu(self):
+ return self._mtu
+
+ @mtu.setter
+ def mtu(self, mtu=None):
+ if mtu < 68 or mtu > 9000:
+ raise ValueError("mtu size invalid value")
+ self._mtu = mtu
+ try:
+ ret = subprocess.check_output(['ip link set mtu ' + str(mtu) + ' dev ' + self._ifname], shell=True).decode()
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+
+
+ @property
+ def macaddr(self):
+ return self._macaddr
+
+ @macaddr.setter
+ def macaddr(self, mac=None):
+ if not re.search('^[a-f0-9:]{17}$', str(mac)):
+ raise ValueError("mac address invalid")
+ self._macaddr = str(mac)
+ try:
+ ret = subprocess.check_output(['ip link set address ' + mac + ' ' + self._ifname], shell=True).decode()
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+
+ @property
+ def ifalias(self):
+ return self._ifalias
+
+ @ifalias.setter
+ def ifalias(self, ifalias=None):
+ if not ifalias:
+ self._ifalias = self._ifname
+ else:
+ self._ifalias = str(ifalias)
+ open('/sys/class/net/{0}/ifalias'.format(self._ifname),'w').write(self._ifalias)
+
+ @property
+ def linkstate(self):
+ return self._linkstate
+
+ @linkstate.setter
+ def linkstate(self, state='up'):
+ if str(state).lower() == 'up' or str(state).lower() == 'down':
+ self._linkstate = str(state).lower()
+ else:
+ self._linkstate = 'up'
+ try:
+ ret = subprocess.check_output(['ip link set dev ' + self._ifname + ' ' + state], shell=True).decode()
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+
+
+
+ def _debug(self, e=None):
+ """
+ export DEBUG=1 to see debug messages
+ """
+ if os.getenv('DEBUG') == '1':
+ if e:
+ print ("Exception raised:\ncommand: {0}\nerror code: {1}\nsubprocess output: {2}".format(e.cmd, e.returncode, e.output.decode()) )
+ return True
+ return False
+
+ def get_mtu(self):
+ try:
+ ret = subprocess.check_output(['ip -j link list dev ' + self._ifname], shell=True).decode()
+ a = json.loads(ret)[0]
+ return a['mtu']
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ return None
+
+ def get_macaddr(self):
+ try:
+ ret = subprocess.check_output(['ip -j -4 link show dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode()
+ j = json.loads(ret)
+ return j[0]['address']
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ return None
+
+ def get_alias(self):
+ return open('/sys/class/net/{0}/ifalias'.format(self._ifname),'r').read()
+
+ def del_alias(self):
+ open('/sys/class/net/{0}/ifalias'.format(self._ifname),'w').write()
+
+ def get_link_state(self):
+ """
+ returns either up/down or None if it can't find the state
+ """
+ try:
+ ret = subprocess.check_output(['ip -j link show ' + self._ifname], shell=True).decode()
+ s = json.loads(ret)
+ return s[0]['operstate'].lower()
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ return None
+
+ def remove_interface(self):
+ try:
+ ret = subprocess.check_output(['ip link del dev ' + self._ifname], shell=True).decode()
+ return 0
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ return None
+
+ def get_ipv4_addr(self):
+ """
+ reads all IPs assigned to an interface and returns it in a list,
+ or None if no IP address is assigned to the interface
+ """
+ ips = []
+ try:
+ ret = subprocess.check_output(['ip -j -4 addr show dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode()
+ j = json.loads(ret)
+ for i in j:
+ if len(i) != 0:
+ for addr in i['addr_info']:
+ ips.append(addr['local'])
+ return ips
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ return None
+
+
+ def get_ipv6_addr(self):
+ """
+ reads all IPs assigned to an interface and returns it in a list,
+ or None if no IP address is assigned to the interface
+ """
+ ips = []
+ try:
+ ret = subprocess.check_output(['ip -j -6 addr show dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode()
+ j = json.loads(ret)
+ for i in j:
+ if len(i) != 0:
+ for addr in i['addr_info']:
+ ips.append(addr['local'])
+ return ips
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ return None
+
+
+ def add_ipv4_addr(self, ipaddr=[]):
+ """
+ add addresses on the interface
+ """
+ for ip in ipaddr:
+ try:
+ ret = subprocess.check_output(['ip -4 address add ' + ip + ' dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode()
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ return None
+ return True
+
+
+ def del_ipv4_addr(self, ipaddr=[]):
+ """
+ delete addresses on the interface
+ """
+ for ip in ipaddr:
+ try:
+ ret = subprocess.check_output(['ip -4 address del ' + ip + ' dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode()
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ return None
+ return True
+
+
+ def add_ipv6_addr(self, ipaddr=[]):
+ """
+ add addresses on the interface
+ """
+ for ip in ipaddr:
+ try:
+ ret = subprocess.check_output(['ip -6 address add ' + ip + ' dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode()
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ return None
+ return True
+
+
+ def del_ipv6_addr(self, ipaddr=[]):
+ """
+ delete addresses on the interface
+ """
+ for ip in ipaddr:
+ try:
+ ret = subprocess.check_output(['ip -6 address del ' + ip + ' dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode()
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ return None
+ return True
+
+
+ #### replace dhcpv4/v6 with systemd.networkd?
+ def set_dhcpv4(self):
+ conf_file = dhclient_conf_dir + self._ifname + '.conf'
+ pidfile = dhclient_conf_dir + self._ifname + '.pid'
+ leasefile = dhclient_conf_dir + self._ifname + '.leases'
+
+ a = [
+ '# generated by interface_config.py',
+ 'option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;',
+ 'interface \"' + self._ifname + '\" {',
+ '\tsend host-name \"' + socket.gethostname() +'\";',
+ '\trequest subnet-mask, broadcast-address, routers, domain-name-servers, rfc3442-classless-static-routes, domain-name, interface-mtu;',
+ '}'
+ ]
+
+ cnf = ""
+ for ln in a:
+ cnf +=str(ln + "\n")
+ open(conf_file, 'w').write(cnf)
+ if os.path.exists(dhclient_conf_dir + self._ifname + '.pid'):
+ try:
+ ret = subprocess.check_output(['/sbin/dhclient -4 -r -pf ' + pidfile], shell=True).decode()
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ try:
+ ret = subprocess.check_output(['/sbin/dhclient -4 -q -nw -cf ' + conf_file + ' -pf ' + pidfile + ' -lf ' + leasefile + ' ' + self._ifname], shell=True).decode()
+ return True
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ return None
+
+ def del_dhcpv4(self):
+ conf_file = dhclient_conf_dir + self._ifname + '.conf'
+ pidfile = dhclient_conf_dir + self._ifname + '.pid'
+ leasefile = dhclient_conf_dir + self._ifname + '.leases'
+ if not os.path.exists(pidfile):
+ return 1
+ try:
+ ret = subprocess.check_output(['/sbin/dhclient -4 -r -pf ' + pidfile], shell=True).decode()
+ return True
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ return None
+
+ def get_dhcpv4(self):
+ pidfile = dhclient_conf_dir + self._ifname + '.pid'
+ if not os.path.exists(pidfile):
+ print ("no dhcp client running on interface {0}".format(self._ifname))
+ return False
+ else:
+ pid = open(pidfile, 'r').read()
+ print("dhclient running on {0} with pid {1}".format(self._ifname, pid))
+ return True
+
+
+ def set_dhcpv6(self):
+ conf_file = dhclient_conf_dir + self._ifname + '.v6conf'
+ pidfile = dhclient_conf_dir + self._ifname + '.v6pid'
+ leasefile = dhclient_conf_dir + self._ifname + '.v6leases'
+ a = [
+ '# generated by interface_config.py',
+ 'interface \"' + self._ifname + '\" {',
+ '\trequest routers, domain-name-servers, domain-name;',
+ '}'
+ ]
+ cnf = ""
+ for ln in a:
+ cnf +=str(ln + "\n")
+ open(conf_file, 'w').write(cnf)
+ subprocess.call(['sysctl', '-q', '-w', 'net.ipv6.conf.' + self._ifname + '.accept_ra=0'])
+ if os.path.exists(pidfile):
+ try:
+ ret = subprocess.check_output(['/sbin/dhclient -6 -q -x -pf ' + pidfile], shell=True).decode()
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ try:
+ ret = subprocess.check_output(['/sbin/dhclient -6 -q -nw -cf ' + conf_file + ' -pf ' + pidfile + ' -lf ' + leasefile + ' ' + self._ifname], shell=True).decode()
+ return True
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ return None
+
+ def del_dhcpv6(self):
+ conf_file = dhclient_conf_dir + self._ifname + '.v6conf'
+ pidfile = dhclient_conf_dir + self._ifname + '.v6pid'
+ leasefile = dhclient_conf_dir + self._ifname + '.v6leases'
+ if not os.path.exists(pidfile):
+ return 1
+ try:
+ ret = subprocess.check_output(['/sbin/dhclient -6 -q -x -pf ' + pidfile], shell=True).decode()
+ subprocess.call(['sysctl', '-q', '-w', 'net.ipv6.conf.' + self._ifname + '.accept_ra=1'])
+ return True
+ except subprocess.CalledProcessError as e:
+ if self._debug():
+ self._debug(e)
+ return None
+
+ def get_dhcpv6(self):
+ pidfile = dhclient_conf_dir + self._ifname + '.v6pid'
+ if not os.path.exists(pidfile):
+ print ("no dhcpv6 client running on interface {0}".format(self._ifname))
+ return False
+ else:
+ pid = open(pidfile, 'r').read()
+ print("dhclientv6 running on {0} with pid {1}".format(self._ifname, pid))
+ return True
+
+
+#### TODO: dhcpv6-pd via dhclient
+
diff --git a/python/vyos/validate.py b/python/vyos/validate.py
index 8def0a510..97a401423 100644
--- a/python/vyos/validate.py
+++ b/python/vyos/validate.py
@@ -1,102 +1,140 @@
# Copyright 2018 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/>.
import netifaces
import ipaddress
def is_ipv4(addr):
"""
- Check addr if it is an IPv4 address/network.
-
- Return True/False
+ Check addr if it is an IPv4 address/network. Returns True/False
"""
- if ipaddress.ip_network(addr).version == 4:
+
+ # With the below statement we can check for IPv4 networks and host
+ # addresses at the same time
+ if ipaddress.ip_address(addr.split(r'/')[0]).version == 4:
return True
else:
return False
def is_ipv6(addr):
"""
- Check addr if it is an IPv6 address/network.
-
- Return True/False
+ Check addr if it is an IPv6 address/network. Returns True/False
"""
- if ipaddress.ip_network(addr).version == 6:
+
+ # With the below statement we can check for IPv4 networks and host
+ # addresses at the same time
+ if ipaddress.ip_network(addr.split(r'/')[0]).version == 6:
return True
else:
return False
-def is_addr_assigned(addr):
+def is_intf_addr_assigned(intf, addr):
"""
- Verify if the given IPv4/IPv6 address is assigned to any interface on this
- system.
-
- Return True/False
+ Verify if the given IPv4/IPv6 address is assigned to specific interface.
+ It can check both a single IP address (e.g. 192.0.2.1 or a assigned CIDR
+ address 192.0.2.1/24.
"""
# determine IP version (AF_INET or AF_INET6) depending on passed address
addr_type = netifaces.AF_INET
if is_ipv6(addr):
addr_type = netifaces.AF_INET6
- for interface in netifaces.interfaces():
- # check if the requested address type is configured at all
- if addr_type in netifaces.ifaddresses(interface).keys():
- # Check every IP address on this interface for a match
- for ip in netifaces.ifaddresses(interface)[addr_type]:
- # Check if it matches to the address requested
- if ip['addr'] == addr:
+ # check if the requested address type is configured at all
+ try:
+ netifaces.ifaddresses(intf)
+ except ValueError as e:
+ print(e)
+ return False
+
+ if addr_type in netifaces.ifaddresses(intf).keys():
+ # Check every IP address on this interface for a match
+ for ip in netifaces.ifaddresses(intf)[addr_type]:
+ # Check if it matches to the address requested
+ # If passed address contains a '/' indicating a normalized IP
+ # address we have to take this into account, too
+ if r'/' in addr:
+ prefixlen = ''
+ if is_ipv6(addr):
+ # Note that currently expanded netmasks are not supported. That means
+ # 2001:db00::0/24 is a valid argument while 2001:db00::0/ffff:ff00:: not.
+ # see https://docs.python.org/3/library/ipaddress.html
+ bits = bin( int(ip['netmask'].replace(':',''), 16) ).count('1')
+ prefixlen = '/' + str(bits)
+
+ else:
+ prefixlen = '/' + str(ipaddress.IPv4Network('0.0.0.0/' + ip['netmask']).prefixlen)
+
+ # construct temporary variable holding IPv6 address and netmask
+ # in CIDR notation
+ tmp = ip['addr'] + prefixlen
+ if addr == tmp:
return True
+ elif ip['addr'] == addr:
+ return True
+
+ return False
+
+def is_addr_assigned(addr):
+ """
+ Verify if the given IPv4/IPv6 address is assigned to any interface
+ """
+
+ for intf in netifaces.interfaces():
+ tmp = is_intf_addr_assigned(intf, addr)
+ if tmp == True:
+ return True
+
return False
def is_subnet_connected(subnet, primary=False):
"""
Verify is the given IPv4/IPv6 subnet is connected to any interface on this
system.
primary check if the subnet is reachable via the primary IP address of this
interface, or in other words has a broadcast address configured. ISC DHCP
for instance will complain if it should listen on non broadcast interfaces.
Return True/False
"""
# determine IP version (AF_INET or AF_INET6) depending on passed address
addr_type = netifaces.AF_INET
if is_ipv6(subnet):
addr_type = netifaces.AF_INET6
for interface in netifaces.interfaces():
# check if the requested address type is configured at all
if addr_type not in netifaces.ifaddresses(interface).keys():
continue
# An interface can have multiple addresses, but some software components
# only support the primary address :(
if primary:
ip = netifaces.ifaddresses(interface)[addr_type][0]['addr']
if ipaddress.ip_address(ip) in ipaddress.ip_network(subnet):
return True
else:
# Check every assigned IP address if it is connected to the subnet
# in question
for ip in netifaces.ifaddresses(interface)[addr_type]:
# remove interface extension (e.g. %eth0) that gets thrown on the end of _some_ addrs
addr = ip['addr'].split('%')[0]
if ipaddress.ip_address(addr) in ipaddress.ip_network(subnet):
return True
return False
diff --git a/scripts/build-command-templates b/scripts/build-command-templates
index a7312f77b..ba80eadb2 100755
--- a/scripts/build-command-templates
+++ b/scripts/build-command-templates
@@ -1,298 +1,298 @@
#!/usr/bin/env python3
#
# build-command-template: converts new style command definitions in XML
# to the old style (bunch of dirs and node.def's) command templates
#
# Copyright (C) 2017 VyOS maintainers <maintainers@vyos.net>
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
# USA
import sys
import os
import argparse
import copy
import functools
from lxml import etree as ET
# Defaults
#validator_dir = "/usr/libexec/vyos/validators"
validator_dir = "${vyos_validators_dir}"
default_constraint_err_msg = "Invalid value"
## Get arguments
parser = argparse.ArgumentParser(description='Converts new-style XML interface definitions to old-style command templates')
parser.add_argument('--debug', help='Enable debug information output', action='store_true')
parser.add_argument('INPUT_FILE', type=str, help="XML interface definition file")
parser.add_argument('SCHEMA_FILE', type=str, help="RelaxNG schema file")
parser.add_argument('OUTPUT_DIR', type=str, help="Output directory")
args = parser.parse_args()
input_file = args.INPUT_FILE
schema_file = args.SCHEMA_FILE
output_dir = args.OUTPUT_DIR
debug = args.debug
#debug = True
## Load and validate the inputs
try:
xml = ET.parse(input_file)
except Exception as e:
print("Failed to load interface definition file {0}".format(input_file))
print(e)
sys.exit(1)
try:
relaxng_xml = ET.parse(schema_file)
validator = ET.RelaxNG(relaxng_xml)
if not validator.validate(xml):
print(validator.error_log)
print("Interface definition file {0} does not match the schema!".format(input_file))
sys.exit(1)
except Exception as e:
print("Failed to load the XML schema {0}".format(schema_file))
print(e)
sys.exit(1)
if not os.access(output_dir, os.W_OK):
print("The output directory {0} is not writeable".format(output_dir))
sys.exit(1)
## If we got this far, everything must be ok and we can convert the file
def make_path(l):
path = functools.reduce(os.path.join, l)
if debug:
print(path)
return path
def get_properties(p):
props = {}
if p is None:
return props
# Get the help string
try:
props["help"] = p.find("help").text
except:
pass
# Get value help strings
try:
vhe = p.findall("valueHelp")
vh = []
for v in vhe:
vh.append( (v.find("format").text, v.find("description").text) )
props["val_help"] = vh
except:
props["val_help"] = []
# Get the constraint statements
error_msg = default_constraint_err_msg
# Get the error message if it's there
try:
error_msg = p.find("constraintErrorMessage").text
except:
pass
vce = p.find("constraint")
vc = []
if vce is not None:
# The old backend doesn't support multiple validators in OR mode
# so we emulate it
regexes = []
regex_elements = vce.findall("regex")
if regex_elements is not None:
regexes = list(map(lambda e: e.text.strip(), regex_elements))
if "" in regexes:
print("Warning: empty regex, node will be accepting any value")
validator_elements = vce.findall("validator")
validators = []
if validator_elements is not None:
for v in validator_elements:
v_name = os.path.join(validator_dir, v.get("name"))
# XXX: lxml returns None for empty arguments
v_argument = None
try:
v_argument = v.get("argument")
except:
pass
if v_argument is None:
v_argument = ""
validators.append("{0} {1}".format(v_name, v_argument))
regex_args = " ".join(map(lambda s: "--regex \\\'{0}\\\'".format(s), regexes))
validator_args = " ".join(map(lambda s: "--exec \\\"{0}\\\"".format(s), validators))
validator_script = '${vyos_libexec_dir}/validate-value.py'
validator_string = "exec \"{0} {1} {2} --value \\\'$VAR(@)\\\'\"; \"{3}\"".format(validator_script, regex_args, validator_args, error_msg)
props["constraint"] = validator_string
# Get the completion help strings
try:
che = p.findall("completionHelp")
ch = ""
for c in che:
scripts = c.findall("script")
paths = c.findall("path")
lists = c.findall("list")
# Current backend doesn't support multiple allowed: tags
# so we get to emulate it
comp_exprs = []
for i in lists:
comp_exprs.append("echo \"{0}\"".format(i.text))
for i in paths:
comp_exprs.append("/bin/cli-shell-api listNodes {0}".format(i.text))
for i in scripts:
comp_exprs.append("sh -c \"{0}\"".format(i.text))
comp_help = " && ".join(comp_exprs)
props["comp_help"] = comp_help
except:
props["comp_help"] = []
# Get priority
try:
props["priority"] = p.find("priority").text
except:
pass
# Get "multi"
if p.find("multi") is not None:
props["multi"] = True
# Get "valueless"
if p.find("valueless") is not None:
props["valueless"] = True
return props
def make_node_def(props):
# XXX: replace with a template processor if it grows
# out of control
node_def = ""
if "tag" in props:
node_def += "tag:\n"
if "multi" in props:
node_def += "multi:\n"
if "type" in props:
# Will always be txt in practice if it's set
node_def += "type: {0}\n".format(props["type"])
if "priority" in props:
node_def += "priority: {0}\n".format(props["priority"])
if "help" in props:
node_def += "help: {0}\n".format(props["help"])
if "val_help" in props:
for v in props["val_help"]:
node_def += "val_help: {0}; {1}\n".format(v[0], v[1])
if "comp_help" in props:
node_def += "allowed: {0}\n".format(props["comp_help"])
if "constraint" in props:
node_def += "syntax:expression: {0}\n".format(props["constraint"])
if "owner" in props:
if "tag" in props:
- node_def += "end: sudo sh -c \"VALUE='$VAR(@)' {0}\"\n".format(props["owner"])
+ node_def += "end: sudo sh -c \"VYOS_TAGNODE_VALUE='$VAR(@)' {0}\"\n".format(props["owner"])
else:
node_def += "end: sudo sh -c \"{0}\"\n".format(props["owner"])
if debug:
print("The contents of the node.def file:\n", node_def)
return node_def
def process_node(n, tmpl_dir):
# Avoid mangling the path from the outer call
my_tmpl_dir = copy.copy(tmpl_dir)
props_elem = n.find("properties")
children = n.find("children")
name = n.get("name")
owner = n.get("owner")
node_type = n.tag
my_tmpl_dir.append(name)
print("Name of the node: {0}. Created directory: {1}\n".format(name, "/".join(my_tmpl_dir)), end="")
os.makedirs(make_path(my_tmpl_dir), exist_ok=True)
props = get_properties(props_elem)
if owner:
props["owner"] = owner
# Type should not be set for non-tag, non-leaf nodes
# For non-valueless leaf nodes, set the type to txt: to make them have some type,
# actual value validation is handled by constraints translated to syntax:expression:
if node_type != "node":
if "valueless" not in props.keys():
props["type"] = "txt"
if node_type == "tagNode":
props["tag"] = "True"
nodedef_path = os.path.join(make_path(my_tmpl_dir), "node.def")
if not os.path.exists(nodedef_path):
with open(nodedef_path, "w") as f:
f.write(make_node_def(props))
else:
# Something has already generated that file
pass
if node_type == "node":
inner_nodes = children.iterfind("*")
for inner_n in inner_nodes:
process_node(inner_n, my_tmpl_dir)
if node_type == "tagNode":
my_tmpl_dir.append("node.tag")
if debug:
print("Created path for the tagNode:", end="")
os.makedirs(make_path(my_tmpl_dir), exist_ok=True)
inner_nodes = children.iterfind("*")
for inner_n in inner_nodes:
process_node(inner_n, my_tmpl_dir)
else:
# This is a leaf node
pass
root = xml.getroot()
nodes = root.iterfind("*")
for n in nodes:
process_node(n, [output_dir])
diff --git a/src/completion/list_interfaces.py b/src/completion/list_interfaces.py
index a4968c52f..66432af19 100755
--- a/src/completion/list_interfaces.py
+++ b/src/completion/list_interfaces.py
@@ -1,31 +1,40 @@
#!/usr/bin/env python3
import sys
import argparse
import vyos.interfaces
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-t", "--type", type=str, help="List interfaces of specific type")
group.add_argument("-b", "--broadcast", action="store_true", help="List all broadcast interfaces")
+group.add_argument("-br", "--bridgeable", action="store_true", help="List all bridgeable interfaces")
args = parser.parse_args()
if args.type:
try:
interfaces = vyos.interfaces.list_interfaces_of_type(args.type)
except ValueError as e:
print(e, file=sys.stderr)
print("")
elif args.broadcast:
eth = vyos.interfaces.list_interfaces_of_type("ethernet")
bridge = vyos.interfaces.list_interfaces_of_type("bridge")
bond = vyos.interfaces.list_interfaces_of_type("bonding")
interfaces = eth + bridge + bond
+elif args.bridgeable:
+ eth = vyos.interfaces.list_interfaces_of_type("ethernet")
+ bond = vyos.interfaces.list_interfaces_of_type("bonding")
+ l2tpv3 = vyos.interfaces.list_interfaces_of_type("l2tpv3")
+ openvpn = vyos.interfaces.list_interfaces_of_type("openvpn")
+ vxlan = vyos.interfaces.list_interfaces_of_type("vxlan")
+ wireless = vyos.interfaces.list_interfaces_of_type("wireless")
+ interfaces = eth + bond + l2tpv3 + openvpn + vxlan + wireless
else:
interfaces = vyos.interfaces.list_interfaces()
print(" ".join(interfaces))
diff --git a/src/conf_mode/accel_l2tp.py b/src/conf_mode/accel_l2tp.py
new file mode 100755
index 000000000..3af8b7958
--- /dev/null
+++ b/src/conf_mode/accel_l2tp.py
@@ -0,0 +1,529 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 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_l2tp.pid'
+l2tp_cnf_dir = r'/etc/accel-ppp/l2tp'
+chap_secrets = l2tp_cnf_dir + '/chap-secrets'
+l2tp_conf = l2tp_cnf_dir + '/l2tp.config'
+# accel-pppd -d -c /etc/accel-ppp/l2tp/l2tp.config -p /var/run/accel_l2tp.pid
+
+### config path creation
+if not os.path.exists(l2tp_cnf_dir):
+ os.makedirs(l2tp_cnf_dir)
+ sl.syslog(sl.LOG_NOTICE, l2tp_cnf_dir + " created")
+
+l2tp_config = '''
+### generated by accel_l2tp.py ###
+[modules]
+log_syslog
+l2tp
+chap-secrets
+{% for proto in authentication['auth_proto']: %}
+{{proto}}
+{% endfor%}
+{% if authentication['mode'] == 'radius' %}
+radius
+{% endif -%}
+ippool
+shaper
+ipv6pool
+ipv6_nd
+ipv6_dhcp
+
+[core]
+thread-count={{thread_cnt}}
+
+[log]
+syslog=accel-l2tp,daemon
+copy=1
+level=5
+
+{% if dns %}
+[dns]
+{% if dns[0] %}
+dns1={{dns[0]}}
+{% endif %}
+{% if dns[1] %}
+dns2={{dns[1]}}
+{% endif %}
+{% endif -%}
+
+{% if dnsv6 %}
+[ipv6-dns]
+{% for srv in dnsv6: %}
+{{srv}}
+{% endfor %}
+{% endif %}
+
+{% if wins %}
+[wins]
+{% if wins[0] %}
+wins1={{wins[0]}}
+{% endif %}
+{% if wins[1] %}
+wins2={{wins[1]}}
+{% endif %}
+{% endif -%}
+
+[l2tp]
+verbose=1
+ppp-max-mtu={{mtu}}
+mppe={{authentication['mppe']}}
+{% if outside_addr %}
+bind={{outside_addr}}
+{% endif %}
+{% if lns_shared_secret %}
+secret={{lns_shared_secret}}
+{% endif %}
+
+[client-ip-range]
+0.0.0.0/0
+
+{% if (client_ip_pool) or (client_ip_subnets) %}
+[ip-pool]
+{% if client_ip_pool %}
+{{client_ip_pool}}
+{% endif -%}
+{% if client_ip_subnets %}
+{% for sn in client_ip_subnets %}
+{{sn}}
+{% endfor -%}
+{% endif %}
+{% endif %}
+{% if outside_nexthop %}
+gw-ip-address={{outside_nexthop}}
+{% endif %}
+
+{% if authentication['mode'] == 'local' %}
+[chap-secrets]
+chap-secrets=/etc/accel-ppp/l2tp/chap-secrets
+{% endif %}
+
+[ppp]
+verbose=1
+check-ip=1
+single-session=replace
+{% if idle_timeout %}
+lcp-echo-timeout={{idle_timeout}}
+{% endif %}
+lcp-echo-interval=30
+{% if ccp_disable %}
+ccp=0
+{% endif %}
+{% if client_ipv6_pool %}
+ipv6=allow
+{% endif %}
+
+{% if authentication['mode'] == 'radius' %}
+[radius]
+{% for rsrv in authentication['radiussrv']: %}
+server={{rsrv}},{{authentication['radiussrv'][rsrv]['secret']}},\
+req-limit={{authentication['radiussrv'][rsrv]['req-limit']}},\
+fail-time={{authentication['radiussrv'][rsrv]['fail-time']}}
+{% endfor %}
+{% if authentication['radiusopt']['timeout'] %}
+timeout={{authentication['radiusopt']['timeout']}}
+{% endif %}
+{% if authentication['radiusopt']['acct-timeout'] %}
+acct-timeout={{authentication['radiusopt']['acct-timeout']}}
+{% endif %}
+{% if authentication['radiusopt']['max-try'] %}
+max-try={{authentication['radiusopt']['max-try']}}
+{% endif %}
+{% if authentication['radiusopt']['nas-id'] %}
+nas-identifier={{authentication['radiusopt']['nas-id']}}
+{% endif %}
+{% if authentication['radius_source_address'] %}
+nas-ip-address={{authentication['radius_source_address']}}
+{% endif -%}
+{% if authentication['radiusopt']['dae-srv'] %}
+dae-server={{authentication['radiusopt']['dae-srv']['ip-addr']}}:\
+{{authentication['radiusopt']['dae-srv']['port']}},\
+{{authentication['radiusopt']['dae-srv']['secret']}}
+{% endif -%}
+gw-ip-address={{outside_nexthop}}
+verbose=1
+{% endif -%}
+
+{% if client_ipv6_pool %}
+[ipv6-pool]
+{% for prfx in client_ipv6_pool.prefix: %}
+{{prfx}}
+{% endfor %}
+{% for prfx in client_ipv6_pool.delegate_prefix: %}
+delegate={{prfx}}
+{% endfor %}
+{% endif %}
+
+{% if client_ipv6_pool['delegate_prefix'] %}
+[ipv6-dhcp]
+verbose=1
+{% endif %}
+
+{% if authentication['radiusopt']['shaper'] %}
+[shaper]
+verbose=1
+attr={{authentication['radiusopt']['shaper']['attr']}}
+{% if authentication['radiusopt']['shaper']['vendor'] %}
+vendor={{authentication['radiusopt']['shaper']['vendor']}}
+{% endif -%}
+{% endif %}
+
+[cli]
+tcp=127.0.0.1:2004
+sessions-columns=ifname,username,calling-sid,ip,{{ip6_column}}{{ip6_dp_column}}rate-limit,type,comp,state,rx-bytes,tx-bytes,uptime
+
+'''
+
+### l2tp chap secrets
+chap_secrets_conf = '''
+# username server password acceptable local IP addresses shaper
+{% for user in authentication['local-users'] %}
+{% if authentication['local-users'][user]['state'] == 'enabled' %}
+{% if (authentication['local-users'][user]['upload']) and (authentication['local-users'][user]['download']) %}
+{{user}}\t*\t{{authentication['local-users'][user]['passwd']}}\t{{authentication['local-users'][user]['ip']}}\t\
+{{authentication['local-users'][user]['download']}}/{{authentication['local-users'][user]['upload']}}
+{% else %}
+{{user}}\t*\t{{authentication['local-users'][user]['passwd']}}\t{{authentication['local-users'][user]['ip']}}
+{% endif %}
+{% 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", 2004))
+ break
+ except ConnectionRefusedError:
+ time.sleep(0.5)
+ cnt +=1
+ if cnt == 100:
+ raise("failed to start l2tp 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','-p','2004',cmd]).decode().strip()
+ return ret
+ except:
+ return 1
+
+###
+# inline helper functions end
+###
+
+def get_config():
+ c = Config()
+ if not c.exists('vpn l2tp remote-access '):
+ return None
+
+ c.set_level('vpn l2tp remote-access')
+ config_data = {
+ 'authentication' : {
+ 'mode' : 'local',
+ 'local-users' : {
+ },
+ 'radiussrv' : {},
+ 'radiusopt' : {},
+ 'auth_proto' : [],
+ 'mppe' : 'prefer'
+ },
+ 'outside_addr' : '',
+ 'outside_nexthop' : '',
+ 'dns' : [],
+ 'dnsv6' : [],
+ 'wins' : [],
+ 'client_ip_pool' : None,
+ 'client_ip_subnets' : [],
+ 'client_ipv6_pool' : {},
+ 'mtu' : '1436',
+ 'ip6_column' : '',
+ 'ip6_dp_column' : '',
+ }
+
+ ### general options ###
+
+ if c.exists('dns-servers server-1'):
+ config_data['dns'].append( c.return_value('dns-servers server-1'))
+ if c.exists('dns-servers server-2'):
+ config_data['dns'].append( c.return_value('dns-servers server-2'))
+ if c.exists('dnsv6-servers'):
+ for dns6_server in c.return_values('dnsv6-servers'):
+ config_data['dnsv6'].append(dns6_server)
+ if c.exists('wins-servers server-1'):
+ config_data['wins'].append( c.return_value('wins-servers server-1'))
+ if c.exists('wins-servers server-2'):
+ config_data['wins'].append( c.return_value('wins-servers server-2'))
+ if c.exists('outside-address'):
+ config_data['outside_addr'] = c.return_value('outside-address')
+
+ ### auth local
+ if c.exists('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' : '*',
+ 'upload' : None,
+ 'download' : None
+ }
+ }
+ )
+
+ 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')
+ if c.exists('authentication local-users username ' + usr + ' rate-limit download'):
+ config_data['authentication']['local-users'][usr]['download'] = c.return_value('authentication local-users username ' + usr + ' rate-limit download')
+ if c.exists('authentication local-users username ' + usr + ' rate-limit upload'):
+ config_data['authentication']['local-users'][usr]['upload'] = c.return_value('authentication local-users username ' + usr + ' rate-limit upload')
+
+ ### authentication mode radius servers and settings
+
+ if c.exists('authentication mode radius'):
+ config_data['authentication']['mode'] = 'radius'
+ rsrvs = c.list_nodes('authentication radius server')
+ for rsrv in rsrvs:
+ if c.return_value('authentication radius server ' + rsrv + ' fail-time') == None:
+ ftime = '0'
+ else:
+ ftime = str(c.return_value('authentication radius server ' + rsrv + ' fail-time'))
+ if c.return_value('authentication radius-server ' + rsrv + ' req-limit') == None:
+ reql = '0'
+ else:
+ reql = str(c.return_value('authentication radius server ' + rsrv + ' req-limit'))
+
+ config_data['authentication']['radiussrv'].update(
+ {
+ rsrv : {
+ 'secret' : c.return_value('authentication radius server ' + rsrv + ' key'),
+ 'fail-time' : ftime,
+ 'req-limit' : reql
+ }
+ }
+ )
+ ### Source ip address feature
+ if c.exists('authentication radius source-address'):
+ config_data['authentication']['radius_source_address'] = c.return_value('authentication radius source-address')
+
+ #### advanced radius-setting
+ if c.exists('authentication radius acct-timeout'):
+ config_data['authentication']['radiusopt']['acct-timeout'] = c.return_value('authentication radius acct-timeout')
+ if c.exists('authentication radius max-try'):
+ config_data['authentication']['radiusopt']['max-try'] = c.return_value('authentication radius max-try')
+ if c.exists('authentication radius timeout'):
+ config_data['authentication']['radiusopt']['timeout'] = c.return_value('authentication radius timeout')
+ if c.exists('authentication radius nas-identifier'):
+ config_data['authentication']['radiusopt']['nas-id'] = c.return_value('authentication radius nas-identifier')
+ if c.exists('authentication radius dae-server'):
+ # Set default dae-server port if not defined
+ if c.exists('authentication radius dae-server port'):
+ dae_server_port = c.return_value('authentication radius dae-server port')
+ else:
+ dae_server_port = "3799"
+ config_data['authentication']['radiusopt'].update(
+ {
+ 'dae-srv' : {
+ 'ip-addr' : c.return_value('authentication radius dae-server ip-address'),
+ 'port' : dae_server_port,
+ 'secret' : str(c.return_value('authentication radius dae-server secret'))
+ }
+ }
+ )
+ #### filter-id is the internal accel default if attribute is empty
+ #### set here as default for visibility which may change in the future
+ if c.exists('authentication radius rate-limit enable'):
+ if not c.exists('authentication radius rate-limit attribute'):
+ config_data['authentication']['radiusopt']['shaper'] = {
+ 'attr' : 'Filter-Id'
+ }
+ else:
+ config_data['authentication']['radiusopt']['shaper'] = {
+ 'attr' : c.return_value('authentication radius rate-limit attribute')
+ }
+ if c.exists('authentication radius rate-limit vendor'):
+ config_data['authentication']['radiusopt']['shaper']['vendor'] = c.return_value('authentication radius rate-limit vendor')
+
+ if c.exists('client-ip-pool'):
+ if c.exists('client-ip-pool start') and c.exists('client-ip-pool stop'):
+ config_data['client_ip_pool'] = c.return_value('client-ip-pool start') + '-' + re.search('[0-9]+$', c.return_value('client-ip-pool stop')).group(0)
+
+ if c.exists('client-ip-pool subnet'):
+ config_data['client_ip_subnets'] = c.return_values('client-ip-pool subnet')
+
+ if c.exists('client-ipv6-pool prefix'):
+ config_data['client_ipv6_pool']['prefix'] = c.return_values('client-ipv6-pool prefix')
+ config_data['ip6_column'] = 'ip6,'
+ if c.exists('client-ipv6-pool delegate-prefix'):
+ config_data['client_ipv6_pool']['delegate_prefix'] = c.return_values('client-ipv6-pool delegate-prefix')
+ config_data['ip6_dp_column'] = 'ip6-dp,'
+
+ if c.exists('mtu'):
+ config_data['mtu'] = c.return_value('mtu')
+
+ ### gateway address
+ if c.exists('outside-nexthop'):
+ config_data['outside_nexthop'] = c.return_value('outside-nexthop')
+
+ if c.exists('authentication require'):
+ auth_mods = {'pap' : 'pap','chap' : 'auth_chap_md5', 'mschap' : 'auth_mschap_v1', 'mschap-v2' : 'auth_mschap_v2'}
+ for proto in c.return_values('authentication require'):
+ config_data['authentication']['auth_proto'].append(auth_mods[proto])
+ else:
+ config_data['authentication']['auth_proto'] = ['auth_mschap_v2']
+
+ if c.exists('authentication mppe'):
+ config_data['authentication']['mppe'] = c.return_value('authentication mppe')
+
+ if c.exists('idle'):
+ config_data['idle_timeout'] = c.return_value('idle')
+
+ ### LNS secret
+ if c.exists('lns shared-secret'):
+ config_data['lns_shared_secret'] = c.return_value('lns shared-secret')
+
+ if c.exists('ccp-disable'):
+ config_data['ccp_disable'] = True
+
+ return config_data
+
+def verify(c):
+ if c == None:
+ return None
+
+ if c['authentication']['mode'] == 'local':
+ if not c['authentication']['local-users']:
+ raise ConfigError('l2tp-server authentication local-users required')
+ for usr in c['authentication']['local-users']:
+ if not c['authentication']['local-users'][usr]['passwd']:
+ raise ConfigError('user ' + usr + ' requires a password')
+
+ if c['authentication']['mode'] == 'radius':
+ if len(c['authentication']['radiussrv']) == 0:
+ raise ConfigError('radius server required')
+ for rsrv in c['authentication']['radiussrv']:
+ if c['authentication']['radiussrv'][rsrv]['secret'] == None:
+ raise ConfigError('radius server ' + rsrv + ' needs a secret configured')
+
+ ### check for the existence of a client ip pool
+ if not c['client_ip_pool'] and not c['client_ip_subnets']:
+ raise ConfigError("set vpn l2tp remote-access client-ip-pool requires subnet or start/stop IP pool")
+
+ if not c['outside_nexthop']:
+ #raise ConfigError('set vpn l2tp remote-access outside-nexthop required')
+ print ("WARMING: set vpn l2tp remote-access outside-nexthop required")
+
+ ## check ipv6
+ if 'delegate_prefix' in c['client_ipv6_pool'] and not 'prefix' in c['client_ipv6_pool']:
+ raise ConfigError("\"set vpn l2tp remote-access client-ipv6-pool prefix\" required for delegate-prefix ")
+
+ if len(c['dnsv6']) > 3:
+ raise ConfigError("Maximum allowed dnsv6-servers addresses is 3")
+
+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(l2tp_config, trim_blocks=True)
+ config_text = tmpl.render(c)
+ open(l2tp_conf,'w').write(config_text)
+
+ if c['authentication']['local-users']:
+ write_chap_secrets(c)
+
+ 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',l2tp_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:
+ ### if gw ip changes, only restart doesn't work
+ accel_cmd('restart')
+ sl.syslog(sl.LOG_NOTICE, "reloading config via daemon restart")
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/conf_mode/accel_sstp.py b/src/conf_mode/accel_sstp.py
new file mode 100755
index 000000000..1317a32db
--- /dev/null
+++ b/src/conf_mode/accel_sstp.py
@@ -0,0 +1,469 @@
+#!/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_sstp.pid'
+sstp_cnf_dir = r'/etc/accel-ppp/sstp'
+chap_secrets = sstp_cnf_dir + '/chap-secrets'
+sstp_conf = sstp_cnf_dir + '/sstp.config'
+ssl_cert_dir = r'/config/user-data/sstp'
+
+### config path creation
+if not os.path.exists(sstp_cnf_dir):
+ os.makedirs(sstp_cnf_dir)
+ sl.syslog(sl.LOG_NOTICE, sstp_cnf_dir + " created")
+
+if not os.path.exists(ssl_cert_dir):
+ os.makedirs(ssl_cert_dir)
+ sl.syslog(sl.LOG_NOTICE, ssl_cert_dir + " created")
+
+sstp_config = '''
+### generated by accel_sstp.py ###
+[modules]
+log_syslog
+sstp
+ippool
+shaper
+{% if authentication['mode'] == 'local' %}
+chap-secrets
+{% endif -%}
+{% for proto in authentication['auth_proto'] %}
+{{proto}}
+{% endfor %}
+{% if authentication['mode'] == 'radius' %}
+radius
+{% endif %}
+
+[core]
+thread-count={{thread_cnt}}
+
+[common]
+single-session=replace
+
+[log]
+syslog=accel-sstp,daemon
+copy=1
+level=5
+
+[client-ip-range]
+disable
+
+[sstp]
+verbose=1
+accept=ssl
+{% if certs %}
+ssl-ca-file=/config/user-data/sstp/{{certs['ca']}}
+ssl-pemfile=/config/user-data/sstp/{{certs['server-cert']}}
+ssl-keyfile=/config/user-data/sstp/{{certs['server-key']}}
+{% endif %}
+
+{%if ip_pool %}
+[ip-pool]
+gw-ip-address={{gw}}
+{% for sn in ip_pool %}
+{{sn}}
+{% endfor %}
+{% endif %}
+
+{% if dnsv4 %}
+[dns]
+{% if dnsv4['primary'] %}
+dns1={{dnsv4['primary']}}
+{% endif -%}
+{% if dnsv4['secondary'] %}
+dns2={{dnsv4['secondary']}}
+{% endif -%}
+{% endif %}
+
+{% if authentication['mode'] == 'local' %}
+[chap-secrets]
+chap-secrets=/etc/accel-ppp/sstp/chap-secrets
+{% endif %}
+
+{%- if authentication['mode'] == 'radius' %}
+[radius]
+verbose=1
+{% for rsrv in authentication['radius-srv']: %}
+server={{rsrv}},{{authentication['radius-srv'][rsrv]['secret']}},\
+req-limit={{authentication['radius-srv'][rsrv]['req-limit']}},\
+fail-time={{authentication['radius-srv'][rsrv]['fail-time']}}
+{% endfor -%}
+{% if authentication['radiusopt']['acct-timeout'] %}
+acct-timeout={{authentication['radiusopt']['acct-timeout']}}
+{% endif -%}
+{% if authentication['radiusopt']['timeout'] %}
+timeout={{authentication['radiusopt']['timeout']}}
+{% endif -%}
+{% if authentication['radiusopt']['max-try'] %}
+max-try={{authentication['radiusopt']['max-try']}}
+{% endif -%}
+{% if authentication['radiusopt']['nas-id'] %}
+nas-identifier={{authentication['radiusopt']['nas-id']}}
+{% endif -%}
+{% if authentication['radiusopt']['nas-ip'] %}
+nas-ip-address={{authentication['radiusopt']['nas-ip']}}
+{% endif -%}
+{% if authentication['radiusopt']['dae-srv'] %}
+dae-server={{authentication['radiusopt']['dae-srv']['ip-addr']}}:\
+{{authentication['radiusopt']['dae-srv']['port']}},\
+{{authentication['radiusopt']['dae-srv']['secret']}}
+{% endif -%}
+{% endif %}
+
+[ppp]
+verbose=1
+check-ip=1
+{% if mtu %}
+mtu={{mtu}}
+{% endif -%}
+{% if ppp['mppe'] %}
+mppe={{ppp['mppe']}}
+{% endif -%}
+{% if ppp['lcp-echo-interval'] %}
+lcp-echo-interval={{ppp['lcp-echo-interval']}}
+{% endif -%}
+{% if ppp['lcp-echo-failure'] %}
+lcp-echo-failure={{ppp['lcp-echo-failure']}}
+{% endif -%}
+{% if ppp['lcp-echo-timeout'] %}
+lcp-echo-timeout={{ppp['lcp-echo-timeout']}}
+{% endif %}
+
+{% if authentication['radiusopt']['shaper'] %}
+[shaper]
+verbose=1
+attr={{authentication['radiusopt']['shaper']['attr']}}
+{% if authentication['radiusopt']['shaper']['vendor'] %}
+vendor={{authentication['radiusopt']['shaper']['vendor']}}
+{% endif -%}
+{% endif %}
+
+[cli]
+tcp=127.0.0.1:2005
+'''
+
+### sstp chap secrets
+chap_secrets_conf = '''
+# username server password acceptable local IP addresses shaper
+{% for user in authentication['local-users'] %}
+{% if authentication['local-users'][user]['state'] == 'enabled' %}
+{% if (authentication['local-users'][user]['upload']) and (authentication['local-users'][user]['download']) %}
+{{user}}\t*\t{{authentication['local-users'][user]['passwd']}}\t{{authentication['local-users'][user]['ip']}}\t\
+{{authentication['local-users'][user]['download']}}/{{authentication['local-users'][user]['upload']}}
+{% else %}
+{{user}}\t*\t{{authentication['local-users'][user]['passwd']}}\t{{authentication['local-users'][user]['ip']}}
+{% endif %}
+{% 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", 2005))
+ s.close()
+ break
+ except ConnectionRefusedError:
+ time.sleep(0.5)
+ cnt +=1
+ if cnt == 100:
+ raise("failed to start sstp 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','-p','2005',cmd]).decode().strip()
+ return ret
+ except:
+ return 1
+
+#### check ig local-ip is in client pool subnet
+
+
+###
+# inline helper functions end
+###
+
+def get_config():
+ c = Config()
+ if not c.exists('service sstp-server'):
+ return None
+
+ c.set_level('service sstp-server')
+
+ config_data = {
+ 'authentication' : {
+ 'local-users' : {
+ },
+ 'mode' : 'local',
+ 'auth_proto' : [],
+ 'radius-srv' : {},
+ 'radiusopt' : {},
+ 'dae-srv' : {}
+ },
+ 'certs' : {
+ 'ca' : None,
+ 'server-key' : None,
+ 'server-cert' : None
+ },
+ 'ip_pool' : [],
+ 'gw' : None,
+ 'dnsv4' : {},
+ 'mtu' : None,
+ 'ppp' : {},
+ }
+
+ ### local auth
+ if c.exists('authentication mode local'):
+ if c.exists('authentication local-users'):
+ for usr in c.list_nodes('authentication local-users username'):
+ config_data['authentication']['local-users'].update(
+ {
+ usr : {
+ 'passwd' : None,
+ 'state' : 'enabled',
+ 'ip' : '*',
+ 'upload' : None,
+ 'download' : None
+ }
+ }
+ )
+ 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')
+ if c.exists('authentication local-users username ' + usr + ' rate-limit download'):
+ config_data['authentication']['local-users'][usr]['download'] = c.return_value('authentication local-users username ' + usr + ' rate-limit download')
+ if c.exists('authentication local-users username ' + usr + ' rate-limit upload'):
+ config_data['authentication']['local-users'][usr]['upload'] = c.return_value('authentication local-users username ' + usr + ' rate-limit upload')
+
+ if c.exists('authentication protocols'):
+ auth_mods = {'pap' : 'pap','chap' : 'auth_chap_md5', 'mschap' : 'auth_mschap_v1', 'mschap-v2' : 'auth_mschap_v2'}
+ for proto in c.return_values('authentication protocols'):
+ config_data['authentication']['auth_proto'].append(auth_mods[proto])
+ else:
+ config_data['authentication']['auth_proto'] = ['auth_mschap_v2']
+
+ #### RADIUS auth and settings
+ if c.exists('authentication mode radius'):
+ config_data['authentication']['mode'] = c.return_value('authentication mode')
+ if c.exists('authentication radius-server'):
+ for rsrv in c.list_nodes('authentication radius-server'):
+ config_data['authentication']['radius-srv'][rsrv] = {}
+ if c.exists('authentication radius-server ' + rsrv + ' secret'):
+ config_data['authentication']['radius-srv'][rsrv]['secret'] = c.return_value('authentication radius-server ' + rsrv + ' secret')
+ else:
+ config_data['authentication']['radius-srv'][rsrv]['secret'] = None
+ if c.exists('authentication radius-server ' + rsrv + ' fail-time'):
+ config_data['authentication']['radius-srv'][rsrv]['fail-time'] = c.return_value('authentication radius-server ' + rsrv + ' fail-time')
+ else:
+ config_data['authentication']['radius-srv'][rsrv]['fail-time'] = 0
+ if c.exists('authentication radius-server ' + rsrv + ' req-limit'):
+ config_data['authentication']['radius-srv'][rsrv]['req-limit'] = c.return_value('authentication radius-server ' + rsrv + ' req-limit')
+ else:
+ config_data['authentication']['radius-srv'][rsrv]['req-limit'] = 0
+
+ #### advanced radius-setting
+ if c.exists('authentication radius-settings'):
+ if c.exists('authentication radius-settings acct-timeout'):
+ config_data['authentication']['radiusopt']['acct-timeout'] = c.return_value('authentication radius-settings acct-timeout')
+ if c.exists('authentication radius-settings max-try'):
+ config_data['authentication']['radiusopt']['max-try'] = c.return_value('authentication radius-settings max-try')
+ if c.exists('authentication radius-settings timeout'):
+ config_data['authentication']['radiusopt']['timeout'] = c.return_value('authentication radius-settings timeout')
+ if c.exists('authentication radius-settings nas-identifier'):
+ config_data['authentication']['radiusopt']['nas-id'] = c.return_value('authentication radius-settings nas-identifier')
+ if c.exists('authentication radius-settings nas-ip-address'):
+ config_data['authentication']['radiusopt']['nas-ip'] = c.return_value('authentication radius-settings nas-ip-address')
+ if c.exists('authentication radius-settings dae-server'):
+ config_data['authentication']['radiusopt'].update(
+ {
+ 'dae-srv' : {
+ 'ip-addr' : c.return_value('authentication radius-settings dae-server ip-address'),
+ 'port' : c.return_value('authentication radius-settings dae-server port'),
+ 'secret' : str(c.return_value('authentication radius-settings dae-server secret'))
+ }
+ }
+ )
+ if c.exists('authentication radius-settings rate-limit enable'):
+ if not c.exists('authentication radius-settings rate-limit attribute'):
+ config_data['authentication']['radiusopt']['shaper'] = { 'attr' : 'Filter-Id' }
+ else:
+ config_data['authentication']['radiusopt']['shaper'] = {
+ 'attr' : c.return_value('authentication radius-settings rate-limit attribute')
+ }
+ if c.exists('authentication radius-settings rate-limit vendor'):
+ config_data['authentication']['radiusopt']['shaper']['vendor'] = c.return_value('authentication radius-settings rate-limit vendor')
+
+ if c.exists('sstp-settings ssl-certs ca'):
+ config_data['certs']['ca'] = c.return_value('sstp-settings ssl-certs ca')
+ if c.exists('sstp-settings ssl-certs server-cert'):
+ config_data['certs']['server-cert'] = c.return_value('sstp-settings ssl-certs server-cert')
+ if c.exists('sstp-settings ssl-certs server-key'):
+ config_data['certs']['server-key'] = c.return_value('sstp-settings ssl-certs server-key')
+
+ if c.exists('network-settings client-ip-settings subnet'):
+ config_data['ip_pool'] = c.return_values('network-settings client-ip-settings subnet')
+ if c.exists('network-settings client-ip-settings gateway-address'):
+ config_data['gw'] = c.return_value('network-settings client-ip-settings gateway-address')
+ if c.exists('network-settings dns-server primary-dns'):
+ config_data['dnsv4']['primary'] = c.return_value('network-settings dns-server primary-dns')
+ if c.exists('network-settings dns-server secondary-dns'):
+ config_data['dnsv4']['secondary'] = c.return_value('network-settings dns-server secondary-dns')
+ if c.exists('network-settings mtu'):
+ config_data['mtu'] = c.return_value('network-settings mtu')
+
+ #### ppp
+ if c.exists('ppp-settings mppe'):
+ config_data['ppp']['mppe'] = c.return_value('ppp-settings mppe')
+ if c.exists('ppp-settings lcp-echo-failure'):
+ config_data['ppp']['lcp-echo-failure'] = c.return_value('ppp-settings lcp-echo-failure')
+ if c.exists('ppp-settings lcp-echo-interval'):
+ config_data['ppp']['lcp-echo-interval'] = c.return_value('ppp-settings lcp-echo-interval')
+ if c.exists('ppp-settings lcp-echo-timeout'):
+ config_data['ppp']['lcp-echo-timeout'] = c.return_value('ppp-settings lcp-echo-timeout')
+
+ return config_data
+
+def verify(c):
+ if c == None:
+ return None
+ ### vertify auth settings
+ if c['authentication']['mode'] == 'local':
+ if not c['authentication']['local-users']:
+ raise ConfigError('sstp-server authentication local-users required')
+
+ for usr in c['authentication']['local-users']:
+ if not c['authentication']['local-users'][usr]['passwd']:
+ raise ConfigError('user ' + usr + ' requires a password')
+ ### if up/download is set, check that both have a value
+ if c['authentication']['local-users'][usr]['upload']:
+ if not c['authentication']['local-users'][usr]['download']:
+ raise ConfigError('user ' + usr + ' requires download speed value')
+ if c['authentication']['local-users'][usr]['download']:
+ if not c['authentication']['local-users'][usr]['upload']:
+ raise ConfigError('user ' + usr + ' requires upload speed value')
+
+ if not c['certs']['ca'] or not c['certs']['server-key'] or not c['certs']['server-cert']:
+ raise ConfigError('service sstp-server sstp-settings ssl-certs needs the ssl certificates set up')
+ else:
+ ssl_path = ssl_cert_dir + '/'
+ if not os.path.exists(ssl_path + c['certs']['ca']):
+ raise ConfigError('CA {0} doesn\'t exist'.format(ssl_path + c['certs']['ca']))
+ if not os.path.exists(ssl_path + c['certs']['server-cert']):
+ raise ConfigError('SSL Cert {0} doesn\'t exist'.format(ssl_path + c['certs']['server-cert']))
+ if not os.path.exists(ssl_path + c['certs']['server-cert']):
+ raise ConfigError('SSL Key {0} doesn\'t exist'.format(ssl_path + c['certs']['server-key']))
+
+ if c['authentication']['mode'] == 'radius':
+ if len(c['authentication']['radius-srv']) == 0:
+ raise ConfigError('service sstp-server authentication radius-server needs a value')
+ for rsrv in c['authentication']['radius-srv']:
+ if c['authentication']['radius-srv'][rsrv]['secret'] == None:
+ raise ConfigError('service sstp-server authentication radius-server {0} secret requires a value'.format(rsrv))
+
+ if c['authentication']['mode'] == 'local':
+ if not c['ip_pool']:
+ print ("WARNING: service sstp-server network-settings client-ip-settings subnet requires a value")
+ if not c['gw']:
+ print ("WARNING: service sstp-server network-settings client-ip-settings gateway-address requires a value")
+
+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(sstp_config, trim_blocks=True)
+ config_text = tmpl.render(c)
+ open(sstp_conf,'w').write(config_text)
+
+ if c['authentication']['local-users']:
+ write_chap_secrets(c)
+
+ 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',sstp_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 __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/conf_mode/bridge_has_members.py b/src/conf_mode/bridge_has_members.py
deleted file mode 100755
index 712a9cc46..000000000
--- a/src/conf_mode/bridge_has_members.py
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/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 vyos.config
-
-if len(sys.argv) < 2:
- print("Argument (bridge interface name) is required")
- sys.exit(1)
-else:
- bridge = sys.argv[1]
-
-c = vyos.config.Config()
-
-members = []
-
-
-# Check in ethernet and bonding interfaces
-for p in ["interfaces ethernet", "interfaces bonding"]:
- intfs = c.list_nodes(p)
- for i in intfs:
- intf_bridge_path = "{0} {1} bridge-group bridge".format(p, i)
- if c.exists(intf_bridge_path):
- intf_bridge = c.return_value(intf_bridge_path)
- if intf_bridge == bridge:
- members.append(i)
- # Walk VLANs
- for v in c.list_nodes("{0} {1} vif".format(p, i)):
- vif_bridge_path = "{0} {1} vif {2} bridge-group bridge".format(p, i, v)
- if c.exists(vif_bridge_path):
- vif_bridge = c.return_value(vif_bridge_path)
- if vif_bridge == bridge:
- members.append("{0}.{1}".format(i, v))
- # Walk QinQ interfaces
- for vs in c.list_nodes("{0} {1} vif-s".format(p, i)):
- vifs_bridge_path = "{0} {1} vif-s {2} bridge-group bridge".format(p, i, vs)
- if c.exists(vifs_bridge_path):
- vifs_bridge = c.return_value(vifs_bridge_path)
- if vifs_bridge == bridge:
- members.append("{0}.{1}".format(i, vs))
- for vc in c.list_nodes("{0} {1} vif-s {2} vif-c".format(p, i, vs)):
- vifc_bridge_path = "{0} {1} vif-s {2} vif-c {3} bridge-group bridge".format(p, i, vs, vc)
- if c.exists(vifc_bridge_path):
- vifc_bridge = c.return_value(vifc_bridge_path)
- if vifc_bridge == bridge:
- members.append("{0}.{1}.{2}".format(i, vs, vc))
-
-# Check tunnel interfaces
-for t in c.list_nodes("interfaces tunnel"):
- tunnel_bridge_path = "interfaces tunnel {0} parameters ip bridge-group bridge".format(t)
- if c.exists(tunnel_bridge_path):
- intf_bridge = c.return_value(tunnel_bridge_path)
- if intf_bridge == bridge:
- members.append(t)
-
-# Check OpenVPN interfaces
-for o in c.list_nodes("interfaces openvpn"):
- ovpn_bridge_path = "interfaces openvpn {0} bridge-group bridge".format(o)
- if c.exists(ovpn_bridge_path):
- intf_bridge = c.return_value(ovpn_bridge_path)
- if intf_bridge == bridge:
- members.append(o)
-
-if members:
- print("Bridge {0} cannot be deleted because some interfaces are configured as its members".format(bridge))
- print("The following interfaces are members of {0}: {1}".format(bridge, " ".join(members)))
- sys.exit(1)
-else:
- sys.exit(0)
diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py
index 5868abe8a..ccabc901d 100755
--- a/src/conf_mode/dhcpv6_relay.py
+++ b/src/conf_mode/dhcpv6_relay.py
@@ -1,119 +1,120 @@
#!/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 jinja2
from vyos.config import Config
from vyos import ConfigError
config_file = r'/etc/default/isc-dhcpv6-relay'
# Please be careful if you edit the template.
config_tmpl = """
### Autogenerated by dhcpv6_relay.py ###
# Defaults for isc-dhcpv6-relay initscript sourced by /etc/init.d/isc-dhcpv6-relay
OPTIONS="-6 -l {{ listen_addr | join(' -l ') }} -u {{ upstream_addr | join(' -u ') }} {{ options | join(' ') }}"
"""
default_config_data = {
'listen_addr': [],
'upstream_addr': [],
'options': [],
}
def get_config():
relay = default_config_data
conf = Config()
if not conf.exists('service dhcpv6-relay'):
return None
else:
conf.set_level('service dhcpv6-relay')
# Network interfaces/address to listen on for DHCPv6 query(s)
if conf.exists('listen-interface'):
interfaces = conf.list_nodes('listen-interface')
for intf in interfaces:
- addr = conf.return_value('listen-interface {0} address'.format(intf))
- listen = addr + '%' + intf
- relay['listen_addr'].append(listen)
+ if conf.exists('listen-interface {0} address'.format(intf)):
+ addr = conf.return_value('listen-interface {0} address'.format(intf))
+ listen = addr + '%' + intf
+ relay['listen_addr'].append(listen)
# Upstream interface/address for remote DHCPv6 server
if conf.exists('upstream-interface'):
interfaces = conf.list_nodes('upstream-interface')
for intf in interfaces:
addresses = conf.return_values('upstream-interface {0} address'.format(intf))
for addr in addresses:
server = addr + '%' + intf
relay['upstream_addr'].append(server)
# Maximum hop count. When forwarding packets, dhcrelay discards packets
# which have reached a hop count of COUNT. Default is 10. Maximum is 255.
if conf.exists('max-hop-count'):
count = '-c ' + conf.return_value('max-hop-count')
relay['options'].append(count)
if conf.exists('use-interface-id-option'):
relay['options'].append('-I')
return relay
def verify(relay):
# bail out early - looks like removal from running config
if relay is None:
return None
if len(relay['listen_addr']) == 0 or len(relay['upstream_addr']) == 0:
- raise ConfigError('Must set at least one listen and upstream interface.')
+ raise ConfigError('Must set at least one listen and upstream interface addresses.')
return None
def generate(relay):
# bail out early - looks like removal from running config
if relay is None:
return None
tmpl = jinja2.Template(config_tmpl)
config_text = tmpl.render(relay)
with open(config_file, 'w') as f:
f.write(config_text)
return None
def apply(relay):
if relay is not None:
os.system('sudo systemctl restart isc-dhcpv6-relay.service')
else:
# DHCPv6 relay support is removed in the commit
os.system('sudo systemctl stop isc-dhcpv6-relay.service')
os.unlink(config_file)
return None
if __name__ == '__main__':
try:
c = get_config()
verify(c)
generate(c)
apply(c)
except ConfigError as e:
print(e)
sys.exit(1)
diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py
index 7d618dded..c1d596ea3 100755
--- a/src/conf_mode/http-api.py
+++ b/src/conf_mode/http-api.py
@@ -1,104 +1,105 @@
#!/usr/bin/env python3
#
# Copyright (C) 2019 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 subprocess
import json
import vyos.defaults
from vyos.config import Config
from vyos import ConfigError
config_file = '/etc/vyos/http-api.conf'
default_config_data = {
'listen_address' : '127.0.0.1',
'port' : '8080',
'strict' : 'false',
'debug' : 'false',
'api_keys' : [ {"id": "testapp", "key": "qwerty"} ]
}
vyos_conf_scripts_dir=vyos.defaults.directories['conf_mode']
# XXX: this model will need to be extended for tag nodes
dependencies = [
'https.py',
]
def get_config():
http_api = default_config_data
conf = Config()
if not conf.exists('service https api'):
return None
else:
conf.set_level('service https api')
if conf.exists('strict'):
http_api['strict'] = 'true'
if conf.exists('debug'):
http_api['debug'] = 'true'
if conf.exists('port'):
port = conf.return_value('port')
http_api['port'] = port
if conf.exists('keys'):
for name in conf.list_nodes('keys id'):
if conf.exists('keys id {0} key'.format(name)):
key = conf.return_value('keys id {0} key'.format(name))
new_key = { 'id': name, 'key': key }
http_api['api_keys'].append(new_key)
return http_api
def verify(http_api):
return None
def generate(http_api):
if http_api is None:
return None
with open(config_file, 'w') as f:
json.dump(http_api, f, indent=2)
return None
def apply(http_api):
if http_api is not None:
os.system('sudo systemctl restart vyos-http-api.service')
- for dep in dependencies:
- cmd = '{0}/{1}'.format(vyos_conf_scripts_dir, dep)
- try:
- subprocess.check_call(cmd, shell=True)
- except subprocess.CalledProcessError as err:
- raise ConfigError("{}.".format(err))
else:
os.system('sudo systemctl stop vyos-http-api.service')
+ for dep in dependencies:
+ cmd = '{0}/{1}'.format(vyos_conf_scripts_dir, dep)
+ try:
+ subprocess.check_call(cmd, shell=True)
+ except subprocess.CalledProcessError as err:
+ raise ConfigError("{}.".format(err))
+
if __name__ == '__main__':
try:
c = get_config()
verify(c)
generate(c)
apply(c)
except ConfigError as e:
print(e)
sys.exit(1)
diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py
index dae51dd7d..e1e81eef1 100755
--- a/src/conf_mode/https.py
+++ b/src/conf_mode/https.py
@@ -1,132 +1,135 @@
#!/usr/bin/env python3
#
# Copyright (C) 2019 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 jinja2
from vyos.config import Config
from vyos import ConfigError
config_file = '/etc/nginx/sites-available/default'
# Please be careful if you edit the template.
config_tmpl = """
### Autogenerated by http-api.py ###
# Default server configuration
#
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 302 https://$server_name$request_uri;
}
server {
# SSL configuration
#
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
#
# Self signed certs generated by the ssl-cert package
# Don't use them in a production server!
#
include snippets/snakeoil.conf;
{% for l_addr in listen_address %}
server_name {{ l_addr }};
{% endfor %}
- location / {
+ # proxy settings for HTTP API, if enabled; 503, if not
+ location ~ /(retrieve|configure) {
{% if api %}
proxy_pass http://localhost:{{ api.port }};
proxy_buffering off;
+{% else %}
+ return 503;
{% endif %}
}
error_page 501 502 503 =200 @50*_json;
location @50*_json {
default_type application/json;
return 200 '{"error": "Start service in configuration mode: set service https api"}';
}
}
"""
default_config_data = {
'listen_address' : [ '127.0.0.1' ]
}
default_api_config_data = {
'port' : '8080',
}
def get_config():
https = default_config_data
conf = Config()
if not conf.exists('service https'):
return None
else:
conf.set_level('service https')
if conf.exists('listen-address'):
addrs = conf.return_values('listen-address')
https['listen_address'] = addrs[:]
if conf.exists('api'):
https['api'] = default_api_config_data
if conf.exists('api port'):
port = conf.return_value('api port')
https['api']['port'] = port
return https
def verify(https):
return None
def generate(https):
if https is None:
return None
tmpl = jinja2.Template(config_tmpl, trim_blocks=True)
config_text = tmpl.render(https)
with open(config_file, 'w') as f:
f.write(config_text)
return None
def apply(https):
if https is not None:
os.system('sudo systemctl restart nginx.service')
else:
os.system('sudo systemctl stop nginx.service')
if __name__ == '__main__':
try:
c = get_config()
verify(c)
generate(c)
apply(c)
except ConfigError as e:
print(e)
sys.exit(1)
diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py
new file mode 100755
index 000000000..543349e7b
--- /dev/null
+++ b/src/conf_mode/interface-bridge.py
@@ -0,0 +1,306 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 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
+import sys
+import copy
+import subprocess
+
+import vyos.configinterface as VyIfconfig
+
+from vyos.config import Config
+from vyos import ConfigError
+
+default_config_data = {
+ 'address': [],
+ 'address_remove': [],
+ 'aging': '300',
+ 'arp_cache_timeout_ms': '30000',
+ 'description': '',
+ 'deleted': False,
+ 'dhcp_client_id': '',
+ 'dhcp_hostname': '',
+ 'dhcpv6_parameters_only': False,
+ 'dhcpv6_temporary': False,
+ 'disable': False,
+ 'disable_link_detect': False,
+ 'forwarding_delay': '15',
+ 'hello_time': '2',
+ 'igmp_querier': 0,
+ 'intf': '',
+ 'mac' : '',
+ 'max_age': '20',
+ 'member': [],
+ 'member_remove': [],
+ 'priority': '32768',
+ 'stp': 'off'
+}
+
+def subprocess_cmd(command):
+ process = subprocess.Popen(command,stdout=subprocess.PIPE, shell=True)
+ proc_stdout = process.communicate()[0].strip()
+ pass
+
+def diff(first, second):
+ second = set(second)
+ return [item for item in first if item not in second]
+
+def get_config():
+ bridge = copy.deepcopy(default_config_data)
+ conf = Config()
+
+ # determine tagNode instance
+ try:
+ bridge['intf'] = os.environ['VYOS_TAGNODE_VALUE']
+ except KeyError as E:
+ print("Interface not specified")
+
+ # Check if bridge has been removed
+ if not conf.exists('interfaces bridge ' + bridge['intf']):
+ bridge['deleted'] = True
+ return bridge
+
+ # set new configuration level
+ conf.set_level('interfaces bridge ' + bridge['intf'])
+
+ # retrieve configured interface addresses
+ if conf.exists('address'):
+ bridge['address'] = conf.return_values('address')
+
+ # retrieve aging - how long addresses are retained
+ if conf.exists('aging'):
+ bridge['aging'] = conf.return_value('aging')
+
+ # retrieve interface description
+ if conf.exists('description'):
+ bridge['description'] = conf.return_value('description')
+
+ # DHCP client identifier
+ if conf.exists('dhcp-options client-id'):
+ bridge['dhcp_client_id'] = conf.return_value('dhcp-options client-id')
+
+ # DHCP client hostname
+ if conf.exists('dhcp-options host-name'):
+ bridge['dhcp_hostname'] = conf.return_value('dhcp-options host-name')
+
+ # DHCPv6 acquire only config parameters, no address
+ if conf.exists('dhcpv6-options parameters-only'):
+ bridge['dhcpv6_parameters_only'] = True
+
+ # DHCPv6 IPv6 "temporary" address
+ if conf.exists('dhcpv6-options temporary'):
+ bridge['dhcpv6_temporary'] = True
+
+ # Disable this bridge interface
+ if conf.exists('disable'):
+ bridge['disable'] = True
+
+ # Ignore link state changes
+ if conf.exists('disable-link-detect'):
+ bridge['disable_link_detect'] = True
+
+ # Forwarding delay
+ if conf.exists('forwarding-delay'):
+ bridge['forwarding_delay'] = conf.return_value('forwarding-delay')
+
+ # Hello packet advertisment interval
+ if conf.exists('hello-time'):
+ bridge['hello_time'] = conf.return_value('hello-time')
+
+ # Enable Internet Group Management Protocol (IGMP) querier
+ if conf.exists('igmp querier'):
+ bridge['igmp_querier'] = 1
+
+ # ARP cache entry timeout in seconds
+ if conf.exists('ip arp-cache-timeout'):
+ tmp = 1000 * int(conf.return_value('ip arp-cache-timeout'))
+ bridge['arp_cache_timeout_ms'] = str(tmp)
+
+ # Media Access Control (MAC) address
+ if conf.exists('mac'):
+ bridge['mac'] = conf.return_value('mac')
+
+ # Interval at which neighbor bridges are removed
+ if conf.exists('max-age'):
+ bridge['max_age'] = conf.return_value('max-age')
+
+ # Determine bridge member interface (currently configured)
+ for intf in conf.list_nodes('member interface'):
+ iface = {
+ 'name': intf,
+ 'cost': '',
+ 'priority': ''
+ }
+
+ if conf.exists('member interface {} cost'.format(intf)):
+ iface['cost'] = conf.return_value('member interface {} cost'.format(intf))
+
+ if conf.exists('member interface {} priority'.format(intf)):
+ iface['priority'] = conf.return_value('member interface {} priority'.format(intf))
+
+ bridge['member'].append(iface)
+
+ # Determine bridge member interface (currently effective) - to determine which
+ # interfaces is no longer assigend to the bridge and thus can be removed
+ eff_intf = conf.list_effective_nodes('member interface')
+ act_intf = conf.list_nodes('member interface')
+ bridge['member_remove'] = diff(eff_intf, act_intf)
+
+ # Determine interface addresses (currently effective) - to determine which
+ # address is no longer valid and needs to be removed from the bridge
+ eff_addr = conf.return_effective_values('address')
+ act_addr = conf.return_values('address')
+ bridge['address_remove'] = diff(eff_addr, act_addr)
+
+ # Priority for this bridge
+ if conf.exists('priority'):
+ bridge['priority'] = conf.return_value('priority')
+
+ # Enable spanning tree protocol
+ if conf.exists('stp'):
+ bridge['stp'] = 'on'
+
+ return bridge
+
+def verify(bridge):
+ if bridge is None:
+ return None
+
+ conf = Config()
+ for br in conf.list_nodes('interfaces bridge'):
+ # it makes no sense to verify ourself in this case
+ if br == bridge['intf']:
+ continue
+
+ for intf in bridge['member']:
+ tmp = conf.list_nodes('interfaces bridge {} member interface'.format(br))
+ if intf['name'] in tmp:
+ raise ConfigError('{} can be assigned to any one bridge only'.format(intf['name']))
+
+ return None
+
+def generate(bridge):
+ if bridge is None:
+ return None
+
+ return None
+
+def apply(bridge):
+ if bridge is None:
+ return None
+
+ cmd = ''
+ if bridge['deleted']:
+ # bridges need to be shutdown first
+ cmd += 'ip link set dev "{}" down'.format(bridge['intf'])
+ cmd += ' && '
+ # delete bridge
+ cmd += 'brctl delbr "{}"'.format(bridge['intf'])
+ subprocess_cmd(cmd)
+
+ else:
+ # create bridge if it does not exist
+ if not os.path.exists("/sys/class/net/" + bridge['intf']):
+ # create bridge interface
+ cmd += 'brctl addbr "{}"'.format(bridge['intf'])
+ cmd += ' && '
+ # activate "UP" the interface
+ cmd += 'ip link set dev "{}" up'.format(bridge['intf'])
+ cmd += ' && '
+
+ # set ageing time
+ cmd += 'brctl setageing "{}" "{}"'.format(bridge['intf'], bridge['aging'])
+ cmd += ' && '
+
+ # set bridge forward delay
+ cmd += 'brctl setfd "{}" "{}"'.format(bridge['intf'], bridge['forwarding_delay'])
+ cmd += ' && '
+
+ # set hello time
+ cmd += 'brctl sethello "{}" "{}"'.format(bridge['intf'], bridge['hello_time'])
+ cmd += ' && '
+
+ # set max message age
+ cmd += 'brctl setmaxage "{}" "{}"'.format(bridge['intf'], bridge['max_age'])
+ cmd += ' && '
+
+ # set bridge priority
+ cmd += 'brctl setbridgeprio "{}" "{}"'.format(bridge['intf'], bridge['priority'])
+ cmd += ' && '
+
+ # turn stp on/off
+ cmd += 'brctl stp "{}" "{}"'.format(bridge['intf'], bridge['stp'])
+
+ for intf in bridge['member_remove']:
+ # remove interface from bridge
+ cmd += ' && '
+ cmd += 'brctl delif "{}" "{}"'.format(bridge['intf'], intf)
+
+ for intf in bridge['member']:
+ # add interface to bridge
+ # but only if it is not yet member of this bridge
+ if not os.path.exists('/sys/devices/virtual/net/' + bridge['intf'] + '/brif/' + intf['name']):
+ cmd += ' && '
+ cmd += 'brctl addif "{}" "{}"'.format(bridge['intf'], intf['name'])
+
+ # set bridge port cost
+ if intf['cost']:
+ cmd += ' && '
+ cmd += 'brctl setpathcost "{}" "{}" "{}"'.format(bridge['intf'], intf['name'], intf['cost'])
+
+ # set bridge port priority
+ if intf['priority']:
+ cmd += ' && '
+ cmd += 'brctl setportprio "{}" "{}" "{}"'.format(bridge['intf'], intf['name'], intf['priority'])
+
+ subprocess_cmd(cmd)
+
+ # Change interface MAC address
+ if bridge['mac']:
+ VyIfconfig.set_mac_address(bridge['intf'], bridge['mac'])
+
+ # update interface description used e.g. within SNMP
+ VyIfconfig.set_description(bridge['intf'], bridge['description'])
+
+ # Ignore link state changes?
+ VyIfconfig.set_link_detect(bridge['intf'], bridge['disable_link_detect'])
+
+ # enable or disable IGMP querier
+ VyIfconfig.set_multicast_querier(bridge['intf'], bridge['igmp_querier'])
+
+ # ARP cache entry timeout in seconds
+ VyIfconfig.set_arp_cache_timeout(bridge['intf'], bridge['arp_cache_timeout_ms'])
+
+ # Configure interface address(es)
+ for addr in bridge['address_remove']:
+ VyIfconfig.remove_interface_address(bridge['intf'], addr)
+
+ for addr in bridge['address']:
+ VyIfconfig.add_interface_address(bridge['intf'], addr)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/conf_mode/wireguard.py b/src/conf_mode/interface-wireguard.py
similarity index 100%
rename from src/conf_mode/wireguard.py
rename to src/conf_mode/interface-wireguard.py
diff --git a/src/conf_mode/ipsec-settings.py b/src/conf_mode/ipsec-settings.py
index 921f20491..8d25e7abd 100755
--- a/src/conf_mode/ipsec-settings.py
+++ b/src/conf_mode/ipsec-settings.py
@@ -1,67 +1,260 @@
#!/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 re
import os
import jinja2
+import syslog as sl
import vyos.config
import vyos.defaults
from vyos import ConfigError
+
+ra_conn_name = "remote-access"
charon_conf_file = "/etc/strongswan.d/charon.conf"
+ipsec_secrets_flie = "/etc/ipsec.secrets"
+ipsec_ra_conn_file = "/etc/ipsec.d/tunnels/"+ra_conn_name
+ipsec_conf_flie = "/etc/ipsec.conf"
+ca_cert_path = '/etc/ipsec.d/cacerts'
+server_cert_path = '/etc/ipsec.d/certs'
+server_key_path = '/etc/ipsec.d/private'
+delim_ipsec_l2tp_begin = "### VyOS L2TP VPN Begin ###"
+delim_ipsec_l2tp_end = "### VyOS L2TP VPN End ###"
+
+l2pt_ipsec_conf = '''
+{{delim_ipsec_l2tp_begin}}
+include {{ipsec_ra_conn_file}}
+{{delim_ipsec_l2tp_end}}
+'''
+
+l2pt_ipsec_secrets_conf = '''
+{{delim_ipsec_l2tp_begin}}
+{% if ipsec_l2tp_auth_mode == 'pre-shared-secret' %}
+{{outside_addr}} %any : PSK "{{ipsec_l2tp_secret}}"
+{% elif ipsec_l2tp_auth_mode == 'x509' %}
+: RSA {{server_key_file_copied}}
+{% endif%}
+{{delim_ipsec_l2tp_end}}
+'''
+l2tp_ipsec_ra_conn_conf = '''
+{{delim_ipsec_l2tp_begin}}
+conn {{ra_conn_name}}
+ type=transport
+ left={{outside_addr}}
+ leftsubnet=%dynamic[/1701]
+ rightsubnet=%dynamic
+ mark=%unique
+ auto=add
+ ike=aes256-sha1-modp1024,3des-sha1-modp1024,3des-sha1-modp1024!
+ dpddelay=15
+ dpdtimeout=45
+ dpdaction=clear
+ esp=aes256-sha1,3des-sha1!
+ rekey=no
+{% if ipsec_l2tp_auth_mode == 'pre-shared-secret' %}
+ authby=secret
+ leftauth=psk
+ rightauth=psk
+{% elif ipsec_l2tp_auth_mode == 'x509' %}
+ authby=rsasig
+ leftrsasigkey=%cert
+ rightrsasigkey=%cert
+ rightca=%same
+ leftcert={{server_cert_file_copied}}
+{% endif %}
+ ikelifetime={{ipsec_l2tp_ike_lifetime}}
+ keylife={{ipsec_l2tp_lifetime}}
+{{delim_ipsec_l2tp_end}}
+'''
def get_config():
config = vyos.config.Config()
data = {"install_routes": "yes"}
if config.exists("vpn ipsec options disable-route-autoinstall"):
data["install_routes"] = "no"
+ if config.exists("vpn ipsec ipsec-interfaces interface"):
+ data["ipsec_interfaces"] = config.return_values("vpn ipsec ipsec-interfaces interface")
+
+ # Init config variables
+ data["delim_ipsec_l2tp_begin"] = delim_ipsec_l2tp_begin
+ data["delim_ipsec_l2tp_end"] = delim_ipsec_l2tp_end
+ data["ipsec_ra_conn_file"] = ipsec_ra_conn_file
+ data["ra_conn_name"] = ra_conn_name
+ # Get l2tp ipsec settings
+ data["ipsec_l2tp"] = False
+ conf_ipsec_command = "vpn l2tp remote-access ipsec-settings " #last space is useful
+ if config.exists(conf_ipsec_command):
+ data["ipsec_l2tp"] = True
+
+ # Authentication params
+ if config.exists(conf_ipsec_command + "authentication mode"):
+ data["ipsec_l2tp_auth_mode"] = config.return_value(conf_ipsec_command + "authentication mode")
+ if config.exists(conf_ipsec_command + "authentication pre-shared-secret"):
+ data["ipsec_l2tp_secret"] = config.return_value(conf_ipsec_command + "authentication pre-shared-secret")
+
+ # mode x509
+ if config.exists(conf_ipsec_command + "authentication x509 ca-cert-file"):
+ data["ipsec_l2tp_x509_ca_cert_file"] = config.return_value(conf_ipsec_command + "authentication x509 ca-cert-file")
+ if config.exists(conf_ipsec_command + "authentication x509 crl-file"):
+ data["ipsec_l2tp_x509_crl_file"] = config.return_value(conf_ipsec_command + "authentication x509 crl-file")
+ if config.exists(conf_ipsec_command + "authentication x509 server-cert-file"):
+ data["ipsec_l2tp_x509_server_cert_file"] = config.return_value(conf_ipsec_command + "authentication x509 server-cert-file")
+ data["server_cert_file_copied"] = server_cert_path+"/"+re.search('\w+(?:\.\w+)*$', config.return_value(conf_ipsec_command + "authentication x509 server-cert-file")).group(0)
+ if config.exists(conf_ipsec_command + "authentication x509 server-key-file"):
+ data["ipsec_l2tp_x509_server_key_file"] = config.return_value(conf_ipsec_command + "authentication x509 server-key-file")
+ data["server_key_file_copied"] = server_key_path+"/"+re.search('\w+(?:\.\w+)*$', config.return_value(conf_ipsec_command + "authentication x509 server-key-file")).group(0)
+ if config.exists(conf_ipsec_command + "authentication x509 server-key-password"):
+ data["ipsec_l2tp_x509_server_key_password"] = config.return_value(conf_ipsec_command + "authentication x509 server-key-password")
+
+ # Common l2tp ipsec params
+ if config.exists(conf_ipsec_command + "ike-lifetime"):
+ data["ipsec_l2tp_ike_lifetime"] = config.return_value(conf_ipsec_command + "ike-lifetime")
+ else:
+ data["ipsec_l2tp_ike_lifetime"] = "3600"
+
+ if config.exists(conf_ipsec_command + "lifetime"):
+ data["ipsec_l2tp_lifetime"] = config.return_value(conf_ipsec_command + "lifetime")
+ else:
+ data["ipsec_l2tp_lifetime"] = "3600"
+
+ if config.exists("vpn l2tp remote-access outside-address"):
+ data['outside_addr'] = config.return_value('vpn l2tp remote-access outside-address')
+
return data
+### ipsec secret l2tp
+def write_ipsec_secrets(c):
+ tmpl = jinja2.Template(l2pt_ipsec_secrets_conf, trim_blocks=True)
+ l2pt_ipsec_secrets_txt = tmpl.render(c)
+ old_umask = os.umask(0o077)
+ open(ipsec_secrets_flie,'w').write(l2pt_ipsec_secrets_txt)
+ os.umask(old_umask)
+ sl.syslog(sl.LOG_NOTICE, ipsec_secrets_flie + ' written')
+
+### ipsec remote access connection config
+def write_ipsec_ra_conn(c):
+ tmpl = jinja2.Template(l2tp_ipsec_ra_conn_conf, trim_blocks=True)
+ ipsec_ra_conn_txt = tmpl.render(c)
+ old_umask = os.umask(0o077)
+ open(ipsec_ra_conn_file,'w').write(ipsec_ra_conn_txt)
+ os.umask(old_umask)
+ sl.syslog(sl.LOG_NOTICE, ipsec_ra_conn_file + ' written')
+
+### Remove config from file by delimiter
+def remove_confs(delim_begin, delim_end, conf_file):
+ os.system("sed -i '/"+delim_begin+"/,/"+delim_end+"/d' "+conf_file)
+
+
+### Append "include /path/to/ra_conn" to ipsec conf file
+def append_ipsec_conf(c):
+ tmpl = jinja2.Template(l2pt_ipsec_conf, trim_blocks=True)
+ l2pt_ipsec_conf_txt = tmpl.render(c)
+ old_umask = os.umask(0o077)
+ open(ipsec_conf_flie,'a').write(l2pt_ipsec_conf_txt)
+ os.umask(old_umask)
+ sl.syslog(sl.LOG_NOTICE, ipsec_conf_flie + ' written')
+
+### Checking certificate storage and notice if certificate not in /config directory
+def check_cert_file_store(cert_name, file_path, dts_path):
+ if not re.search('^\/config\/.+', file_path):
+ print("Warning: \"" + file_path + "\" lies outside of /config/auth directory. It will not get preserved during image upgrade.")
+ #Checking file existence
+ if not os.path.isfile(file_path):
+ raise ConfigError("L2TP VPN configuration error: Invalid "+cert_name+" \""+file_path+"\"")
+ else:
+ ### Cpy file to /etc/ipsec.d/certs/ /etc/ipsec.d/cacerts/
+ # todo make check
+ ret = os.system('cp -f '+file_path+' '+dts_path)
+ if ret:
+ raise ConfigError("L2TP VPN configuration error: Cannot copy "+file_path)
+ else:
+ sl.syslog(sl.LOG_NOTICE, file_path + ' copied to '+dts_path)
+
def verify(data):
- pass
+ # l2tp ipsec check
+ if data["ipsec_l2tp"]:
+ # Checking dependecies for "authentication mode pre-shared-secret"
+ if data.get("ipsec_l2tp_auth_mode") == "pre-shared-secret":
+ if not data.get("ipsec_l2tp_secret"):
+ raise ConfigError("pre-shared-secret required")
+ if not data.get("outside_addr"):
+ raise ConfigError("outside-address not defined")
+
+ # Checking dependecies for "authentication mode x509"
+ if data.get("ipsec_l2tp_auth_mode") == "x509":
+ if not data.get("ipsec_l2tp_x509_server_key_file"):
+ raise ConfigError("L2TP VPN configuration error: \"server-key-file\" not defined.")
+ else:
+ check_cert_file_store("server-key-file", data['ipsec_l2tp_x509_server_key_file'], server_key_path)
+
+ if not data.get("ipsec_l2tp_x509_server_cert_file"):
+ raise ConfigError("L2TP VPN configuration error: \"server-cert-file\" not defined.")
+ else:
+ check_cert_file_store("server-cert-file", data['ipsec_l2tp_x509_server_cert_file'], server_cert_path)
+
+ if not data.get("ipsec_l2tp_x509_ca_cert_file"):
+ raise ConfigError("L2TP VPN configuration error: \"ca-cert-file\" must be defined for X.509")
+ else:
+ check_cert_file_store("ca-cert-file", data['ipsec_l2tp_x509_ca_cert_file'], ca_cert_path)
+
+ if not data.get('ipsec_interfaces'):
+ raise ConfigError("L2TP VPN configuration error: \"vpn ipsec ipsec-interfaces\" must be specified.")
def generate(data):
tmpl_path = os.path.join(vyos.defaults.directories["data"], "templates", "ipsec")
fs_loader = jinja2.FileSystemLoader(tmpl_path)
env = jinja2.Environment(loader=fs_loader)
charon_conf_tmpl = env.get_template("charon.tmpl")
charon_conf = charon_conf_tmpl.render(data)
with open(charon_conf_file, 'w') as f:
f.write(charon_conf)
+ if data["ipsec_l2tp"]:
+ remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_conf_flie)
+ write_ipsec_secrets(data)
+ write_ipsec_ra_conn(data)
+ append_ipsec_conf(data)
+ else:
+ remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_ra_conn_file)
+ remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_secrets_flie)
+ remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_conf_flie)
+
def apply(data):
# Do nothing
# StrongSWAN should only be restarted when actual tunnels are configured
- pass
+ # Restart ipsec for l2tp
+ os.system("ipsec restart >&/dev/null")
if __name__ == '__main__':
try:
c = get_config()
verify(c)
generate(c)
apply(c)
except ConfigError as e:
print(e)
sys.exit(1)
diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py
index 04549f4b4..98f38035a 100755
--- a/src/conf_mode/protocols_bfd.py
+++ b/src/conf_mode/protocols_bfd.py
@@ -1,166 +1,227 @@
#!/usr/bin/env python3
#
# Copyright (C) 2019 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 jinja2
import copy
import os
import vyos.validate
from vyos import ConfigError
from vyos.config import Config
config_file = r'/tmp/bfd.frr'
# Please be careful if you edit the template.
config_tmpl = """
!
bfd
{% for peer in old_peers -%}
- no peer {{ peer }}
+ no peer {{ peer.remote }}{% if peer.multihop %} multihop{% endif %}{% if peer.src_addr %} local-address {{ peer.src_addr }}{% endif %}{% if peer.src_if %} interface {{ peer.src_if }}{% endif %}
{% endfor -%}
!
{% for peer in new_peers -%}
peer {{ peer.remote }}{% if peer.multihop %} multihop{% endif %}{% if peer.src_addr %} local-address {{ peer.src_addr }}{% endif %}{% if peer.src_if %} interface {{ peer.src_if }}{% endif %}
detect-multiplier {{ peer.multiplier }}
receive-interval {{ peer.rx_interval }}
transmit-interval {{ peer.tx_interval }}
+ {% if peer.echo_mode %}echo-mode{% endif %}
+ {% if peer.echo_interval != '' %}echo-interval {{ peer.echo_interval }}{% endif %}
{% if not peer.shutdown %}no {% endif %}shutdown
{% endfor -%}
!
"""
default_config_data = {
'new_peers': [],
'old_peers' : []
}
+# get configuration for BFD peer from proposed or effective configuration
+def get_bfd_peer_config(peer, conf_mode="proposed"):
+ conf = Config()
+ conf.set_level('protocols bfd peer {0}'.format(peer))
+
+ bfd_peer = {
+ 'remote': peer,
+ 'shutdown': False,
+ 'src_if': '',
+ 'src_addr': '',
+ 'multiplier': '3',
+ 'rx_interval': '300',
+ 'tx_interval': '300',
+ 'multihop': False,
+ 'echo_interval': '',
+ 'echo_mode': False,
+ }
+
+ # Check if individual peer is disabled
+ if conf_mode == "effective" and conf.exists_effective('shutdown'):
+ bfd_peer['shutdown'] = True
+ if conf_mode == "proposed" and conf.exists('shutdown'):
+ bfd_peer['shutdown'] = True
+
+ # Check if peer has a local source interface configured
+ if conf_mode == "effective" and conf.exists_effective('source interface'):
+ bfd_peer['src_if'] = conf.return_effective_value('source interface')
+ if conf_mode == "proposed" and conf.exists('source interface'):
+ bfd_peer['src_if'] = conf.return_value('source interface')
+
+ # Check if peer has a local source address configured - this is mandatory for IPv6
+ if conf_mode == "effective" and conf.exists_effective('source address'):
+ bfd_peer['src_addr'] = conf.return_effective_value('source address')
+ if conf_mode == "proposed" and conf.exists('source address'):
+ bfd_peer['src_addr'] = conf.return_value('source address')
+
+ # Tell BFD daemon that we should expect packets with TTL less than 254
+ # (because it will take more than one hop) and to listen on the multihop
+ # port (4784)
+ if conf_mode == "effective" and conf.exists_effective('multihop'):
+ bfd_peer['multihop'] = True
+ if conf_mode == "proposed" and conf.exists('multihop'):
+ bfd_peer['multihop'] = True
+
+ # Configures the minimum interval that this system is capable of receiving
+ # control packets. The default value is 300 milliseconds.
+ if conf_mode == "effective" and conf.exists_effective('interval receive'):
+ bfd_peer['rx_interval'] = conf.return_effective_value('interval receive')
+ if conf_mode == "proposed" and conf.exists('interval receive'):
+ bfd_peer['rx_interval'] = conf.return_value('interval receive')
+
+ # The minimum transmission interval (less jitter) that this system wants
+ # to use to send BFD control packets.
+ if conf_mode == "effective" and conf.exists_effective('interval transmit'):
+ bfd_peer['tx_interval'] = conf.return_effective_value('interval transmit')
+ if conf_mode == "proposed" and conf.exists('interval transmit'):
+ bfd_peer['tx_interval'] = conf.return_value('interval transmit')
+
+ # Configures the detection multiplier to determine packet loss. The remote
+ # transmission interval will be multiplied by this value to determine the
+ # connection loss detection timer. The default value is 3.
+ if conf_mode == "effective" and conf.exists_effective('interval multiplier'):
+ bfd_peer['multiplier'] = conf.return_effective_value('interval multiplier')
+ if conf_mode == "proposed" and conf.exists('interval multiplier'):
+ bfd_peer['multiplier'] = conf.return_value('interval multiplier')
+
+ # Configures the minimal echo receive transmission interval that this system is capable of handling
+ if conf_mode == "effective" and conf.exists_effective('interval echo-interval'):
+ bfd_peer['echo_interval'] = conf.return_effective_value('interval echo-interval')
+ if conf_mode == "proposed" and conf.exists('interval echo-interval'):
+ bfd_peer['echo_interval'] = conf.return_value('interval echo-interval')
+
+ # Enables or disables the echo transmission mode
+ if conf_mode == "effective" and conf.exists_effective('echo-mode'):
+ bfd_peer['echo_mode'] = True
+ if conf_mode == "proposed" and conf.exists('echo-mode'):
+ bfd_peer['echo_mode'] = True
+
+ return bfd_peer
+
def get_config():
bfd = copy.deepcopy(default_config_data)
conf = Config()
if not (conf.exists('protocols bfd') or conf.exists_effective('protocols bfd')):
return None
else:
conf.set_level('protocols bfd')
# as we have to use vtysh to talk to FRR we also need to know
# which peers are gone due to a config removal - thus we read in
# all peers (active or to delete)
- bfd['old_peers'] = conf.list_effective_nodes('peer')
+ for peer in conf.list_effective_nodes('peer'):
+ bfd['old_peers'].append(get_bfd_peer_config(peer, "effective"))
for peer in conf.list_nodes('peer'):
- conf.set_level('protocols bfd peer {0}'.format(peer))
- bfd_peer = {
- 'remote': peer,
- 'shutdown': False,
- 'src_if': '',
- 'src_addr': '',
- 'multiplier': '3',
- 'rx_interval': '300',
- 'tx_interval': '300',
- 'multihop': False
- }
-
- # Check if individual peer is disabled
- if conf.exists('shutdown'):
- bfd_peer['shutdown'] = True
-
- # Check if peer has a local source interface configured
- if conf.exists('source interface'):
- bfd_peer['src_if'] = conf.return_value('source interface')
-
- # Check if peer has a local source address configured - this is mandatory for IPv6
- if conf.exists('source address'):
- bfd_peer['src_addr'] = conf.return_value('source address')
-
- # Tell BFD daemon that we should expect packets with TTL less than 254
- # (because it will take more than one hop) and to listen on the multihop
- # port (4784)
- if conf.exists('multihop'):
- bfd_peer['multihop'] = True
-
- # Configures the minimum interval that this system is capable of receiving
- # control packets. The default value is 300 milliseconds.
- if conf.exists('interval receive'):
- bfd_peer['rx_interval'] = conf.return_value('interval receive')
-
- # The minimum transmission interval (less jitter) that this system wants
- # to use to send BFD control packets.
- if conf.exists('interval transmit'):
- bfd_peer['tx_interval'] = conf.return_value('interval transmit')
-
- # Configures the detection multiplier to determine packet loss. The remote
- # transmission interval will be multiplied by this value to determine the
- # connection loss detection timer. The default value is 3.
- if conf.exists('interval multiplier'):
- bfd_peer['multiplier'] = conf.return_value('interval multiplier')
-
- bfd['new_peers'].append(bfd_peer)
+ bfd['new_peers'].append(get_bfd_peer_config(peer))
+
+ # find deleted peers
+ set_new_peers = set(conf.list_nodes('peer'))
+ set_old_peers = set(conf.list_effective_nodes('peer'))
+ bfd['deleted_peers'] = set_old_peers - set_new_peers
return bfd
def verify(bfd):
if bfd is None:
return None
- for peer in bfd['new_peers']:
- # Bail out early if peer is shutdown
- if peer['shutdown']:
- continue
+ # some variables to use later
+ conf = Config()
+ for peer in bfd['new_peers']:
# IPv6 peers require an explicit local address/interface combination
if vyos.validate.is_ipv6(peer['remote']):
if not (peer['src_if'] and peer['src_addr']):
- raise ConfigError('BFD IPv6 peers require explicit local address/interface setting')
-
- # multihop doesn't accept interface names
- if peer['multihop'] and peer['src_if']:
- raise ConfigError('multihop does not accept interface names')
-
+ raise ConfigError('BFD IPv6 peers require explicit local address and interface setting')
+
+ # multihop require source address
+ if peer['multihop'] and not peer['src_addr']:
+ raise ConfigError('Multihop require source address')
+
+ # multihop and echo-mode cannot be used together
+ if peer['multihop'] and peer['echo_mode']:
+ raise ConfigError('Multihop and echo-mode cannot be used together')
+
+ # echo interval can be configured only with enabled echo-mode
+ if peer['echo_interval'] != '' and not peer['echo_mode']:
+ raise ConfigError('echo-interval can be configured only with enabled echo-mode')
+
+ # check if we deleted peers are not used in configuration
+ if conf.exists('protocols bgp'):
+ bgp_as = conf.list_nodes('protocols bgp')[0]
+
+ # check BGP neighbors
+ for peer in bfd['deleted_peers']:
+ if conf.exists('protocols bgp {0} neighbor {1} bfd'.format(bgp_as, peer)):
+ raise ConfigError('Cannot delete BFD peer {0}: it is used in BGP configuration'.format(peer))
+ if conf.exists('protocols bgp {0} neighbor {1} peer-group'.format(bgp_as, peer)):
+ peer_group = conf.return_value('protocols bgp {0} neighbor {1} peer-group'.format(bgp_as, peer))
+ if conf.exists('protocols bgp {0} peer-group {1} bfd'.format(bgp_as, peer_group)):
+ raise ConfigError('Cannot delete BFD peer {0}: it belongs to BGP peer-group {1} with enabled BFD'.format(peer, peer_group))
return None
def generate(bfd):
if bfd is None:
return None
return None
def apply(bfd):
if bfd is None:
return None
tmpl = jinja2.Template(config_tmpl)
config_text = tmpl.render(bfd)
with open(config_file, 'w') as f:
f.write(config_text)
os.system("sudo vtysh -d bfdd -f " + config_file)
if os.path.exists(config_file):
os.remove(config_file)
return None
if __name__ == '__main__':
try:
c = get_config()
verify(c)
generate(c)
apply(c)
except ConfigError as e:
print(e)
sys.exit(1)
diff --git a/src/migration-scripts/interfaces/0-to-1 b/src/migration-scripts/interfaces/0-to-1
new file mode 100755
index 000000000..38f2bd8f5
--- /dev/null
+++ b/src/migration-scripts/interfaces/0-to-1
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+
+# Change syntax of bridge interface
+# - move interface based bridge-group to actual bridge (de-nest)
+# - make stp and igmp-snooping nodes valueless
+# https://phabricator.vyos.net/T1556
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['interfaces', 'bridge']
+
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ #
+ # make stp and igmp-snooping nodes valueless
+ #
+ for br in config.list_nodes(base):
+ # STP: check if enabled
+ stp_val = config.return_value(base + [br, 'stp'])
+ # STP: delete node with old syntax
+ config.delete(base + [br, 'stp'])
+ # STP: set new node - if enabled
+ if stp_val == "true":
+ config.set(base + [br, 'stp'], value=None)
+
+ # igmp-snooping: check if enabled
+ igmp_val = config.return_value(base + [br, 'igmp-snooping', 'querier'])
+ # igmp-snooping: delete node with old syntax
+ config.delete(base + [br, 'igmp-snooping', 'querier'])
+ # igmp-snooping: set new node - if enabled
+ if igmp_val == "enable":
+ config.set(base + [br, 'igmp', 'querier'], value=None)
+
+ #
+ # move interface based bridge-group to actual bridge (de-nest)
+ #
+ bridge_types = ['bonding', 'ethernet', 'l2tpv3', 'openvpn', 'vxlan', 'wireless']
+ for type in bridge_types:
+ if not config.exists(['interfaces', type]):
+ continue
+
+ for intf in config.list_nodes(['interfaces', type]):
+ # check if bridge-group exists
+ if config.exists(['interfaces', type, intf, 'bridge-group']):
+ bridge = config.return_value(['interfaces', type, intf, 'bridge-group', 'bridge'])
+
+ # create new bridge member interface
+ config.set(base + [bridge, 'member', 'interface', intf])
+ # format as tag node to avoid loading problems
+ config.set_tag(base + [bridge, 'member', 'interface'])
+
+ # cost: migrate if configured
+ if config.exists(['interfaces', type, intf, 'bridge-group', 'cost']):
+ cost = config.return_value(['interfaces', type, intf, 'bridge-group', 'cost'])
+ # set new node
+ config.set(base + [bridge, 'member', 'interface', intf, 'cost'], value=cost)
+
+ if config.exists(['interfaces', type, intf, 'bridge-group', 'priority']):
+ priority = config.return_value(['interfaces', type, intf, 'bridge-group', 'priority'])
+ # set new node
+ config.set(base + [bridge, 'member', 'interface', intf, 'priority'], value=priority)
+
+ # Delete the old bridge-group assigned to an interface
+ config.delete(['interfaces', type, intf, 'bridge-group'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/op_mode/show_vpn_ra.py b/src/op_mode/show_vpn_ra.py
new file mode 100755
index 000000000..cf6119c2f
--- /dev/null
+++ b/src/op_mode/show_vpn_ra.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 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
+import sys
+import re
+import subprocess
+# from subprocess import Popen, PIPE
+
+# chech connection to pptp and l2tp daemon
+def get_sessions():
+ absent_pptp = False
+ absent_l2tp = False
+ pptp_cmd = ["accel-cmd", "-p 2003", "show sessions"]
+ l2tp_cmd = ["accel-cmd", "-p 2004", "show sessions"]
+ err_pattern = "^Connection.+failed$"
+ # This value for chack only output header without sessions.
+ len_def_header = 170
+
+ # Check pptp
+ ret = subprocess.Popen(pptp_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ (output, err) = ret.communicate()
+ if not err and len(output.decode("utf-8")) > len_def_header and not re.search(err_pattern, output.decode("utf-8")):
+ print(output.decode("utf-8"))
+ else:
+ absent_pptp = True
+
+ # Check l2tp
+ ret = subprocess.Popen(l2tp_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ (output, err) = ret.communicate()
+ if not err and len(output.decode("utf-8")) > len_def_header and not re.search(err_pattern, output.decode("utf-8")):
+ print(output.decode("utf-8"))
+ else:
+ absent_l2tp = True
+
+ if absent_l2tp and absent_pptp:
+ print("No active remote access VPN sessions")
+
+
+def main():
+ get_sessions()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/src/op_mode/snmp_ifmib.py b/src/op_mode/snmp_ifmib.py
index 9d56a950b..180892694 100755
--- a/src/op_mode/snmp_ifmib.py
+++ b/src/op_mode/snmp_ifmib.py
@@ -1,128 +1,134 @@
#!/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/>.
#
# File: snmp_ifmib.py
# Purpose:
# Show SNMP MIB information
# Used by the "run show snmp mib" commands.
import sys
import argparse
import netifaces
import subprocess
from vyos.config import Config
parser = argparse.ArgumentParser(description='Retrieve SNMP interfaces information')
parser.add_argument('--ifindex', action='store', nargs='?', const='all', help='Show interface index')
parser.add_argument('--ifalias', action='store', nargs='?', const='all', help='Show interface aliase')
parser.add_argument('--ifdescr', action='store', nargs='?', const='all', help='Show interface description')
def show_ifindex(i):
proc = subprocess.Popen(['/bin/ip', 'link', 'show', i], stdout=subprocess.PIPE)
(out, err) = proc.communicate()
# convert output to string
string = out.decode("utf-8")
index = 'ifIndex = ' + string.split(':')[0]
return index.replace('\n', '')
def show_ifalias(i):
proc = subprocess.Popen(['/bin/ip', 'link', 'show', i], stdout=subprocess.PIPE)
(out, err) = proc.communicate()
# convert output to string
string = out.decode("utf-8")
if 'alias' in string:
alias = 'ifAlias = ' + string.split('alias')[1].lstrip()
else:
alias = 'ifAlias = ' + i
return alias.replace('\n', '')
def show_ifdescr(i):
ven_id = ''
dev_id = ''
try:
with open(r'/sys/class/net/' + i + '/device/vendor', 'r') as f:
ven_id = f.read().replace('\n', '')
except FileNotFoundError:
pass
try:
with open(r'/sys/class/net/' + i + '/device/device', 'r') as f:
dev_id = f.read().replace('\n', '')
except FileNotFoundError:
pass
if ven_id == '' and dev_id == '':
ret = 'ifDescr = {0}'.format(i)
return ret
device = str(ven_id) + ':' + str(dev_id)
proc = subprocess.Popen(['/usr/bin/lspci', '-mm', '-d', device], stdout=subprocess.PIPE)
(out, err) = proc.communicate()
+ vendor = ""
+ device = ""
+
# convert output to string
string = out.decode("utf-8").split('"')
- vendor = string[3]
- device = string[5]
+ if len(string) >= 3:
+ vendor = string[3]
+
+ if len(string) >= 5:
+ device = string[5]
ret = 'ifDescr = {0} {1}'.format(vendor, device)
return ret.replace('\n', '')
if __name__ == '__main__':
args = parser.parse_args()
# Do nothing if service is not configured
c = Config()
if not c.exists_effective('service snmp'):
print("SNMP service is not configured")
sys.exit(0)
if args.ifindex:
if args.ifindex == 'all':
for i in netifaces.interfaces():
print('{0}: {1}'.format(i, show_ifindex(i)))
else:
print('{0}: {1}'.format(args.ifindex, show_ifindex(args.ifindex)))
elif args.ifalias:
if args.ifalias == 'all':
for i in netifaces.interfaces():
print('{0}: {1}'.format(i, show_ifalias(i)))
else:
print('{0}: {1}'.format(args.ifalias, show_ifalias(args.ifalias)))
elif args.ifdescr:
if args.ifdescr == 'all':
for i in netifaces.interfaces():
print('{0}: {1}'.format(i, show_ifdescr(i)))
else:
print('{0}: {1}'.format(args.ifdescr, show_ifdescr(args.ifdescr)))
else:
#eth0: ifIndex = 2
# ifAlias = NET-MYBLL-MUCI-BACKBONE
# ifDescr = VMware VMXNET3 Ethernet Controller
#lo: ifIndex = 1
for i in netifaces.interfaces():
print('{0}:\t{1}'.format(i, show_ifindex(i)))
print('\t{0}'.format(show_ifalias(i)))
print('\t{0}'.format(show_ifdescr(i)))
sys.exit(1)
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index e11eb6d52..afab9be70 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -1,237 +1,247 @@
#!/usr/bin/env python3
#
# Copyright (C) 2019 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
import sys
import grp
import json
import traceback
import threading
import vyos.config
import bottle
from vyos.configsession import ConfigSession, ConfigSessionError
from vyos.config import VyOSError
DEFAULT_CONFIG_FILE = '/etc/vyos/http-api.conf'
CFG_GROUP = 'vyattacfg'
app = bottle.default_app()
# Giant lock!
lock = threading.Lock()
def load_server_config():
with open(DEFAULT_CONFIG_FILE) as f:
config = json.load(f)
return config
def check_auth(key_list, key):
id = None
for k in key_list:
if k['key'] == key:
id = k['id']
return id
def error(code, msg):
bottle.response.status = code
resp = {"success": False, "error": msg, "data": None}
return json.dumps(resp)
def success(data):
resp = {"success": True, "data": data, "error": None}
return json.dumps(resp)
@app.route('/configure', method='POST')
def configure():
session = app.config['vyos_session']
config = app.config['vyos_config']
api_keys = app.config['vyos_keys']
key = bottle.request.forms.get("key")
id = check_auth(api_keys, key)
if not id:
return error(401, "Valid API key is required")
strict_field = bottle.request.forms.get("strict")
if strict_field == "true":
strict = True
else:
strict = False
commands = bottle.request.forms.get("data")
if not commands:
return error(400, "Non-empty data field is required")
else:
try:
commands = json.loads(commands)
except Exception as e:
return error(400, "Failed to parse JSON: {0}".format(e))
# Allow users to pass just one command
if not isinstance(commands, list):
commands = [commands]
# We don't want multiple people/apps to be able to commit at once,
# or modify the shared session while someone else is doing the same,
# so the lock is really global
lock.acquire()
status = 200
error_msg = None
try:
for c in commands:
# What we've got may not even be a dict
if not isinstance(c, dict):
raise ConfigSessionError("Malformed command \"{0}\": any command must be a dict".format(json.dumps(c)))
# Missing op or path is a show stopper
if not ('op' in c):
raise ConfigSessionError("Malformed command \"{0}\": missing \"op\" field".format(json.dumps(c)))
if not ('path' in c):
raise ConfigSessionError("Malformed command \"{0}\": missing \"path\" field".format(json.dumps(c)))
# Missing value is fine, substitute for empty string
if 'value' in c:
value = c['value']
else:
value = ""
op = c['op']
path = c['path']
if not path:
raise ConfigSessionError("Malformed command \"{0}\": empty path".format(json.dumps(c)))
# Type checking
if not isinstance(path, list):
raise ConfigSessionError("Malformed command \"{0}\": \"path\" field must be a list".format(json.dumps(c)))
if not isinstance(value, str):
raise ConfigSessionError("Malformed command \"{0}\": \"value\" field must be a string".format(json.dumps(c)))
# Account for the case when value field is present and set to null
if not value:
value = ""
# For vyos.configsessios calls that have no separate value arguments,
# and for type checking too
try:
cfg_path = " ".join(path + [value]).strip()
except TypeError:
raise ConfigSessionError("Malformed command \"{0}\": \"path\" field must be a list of strings".format(json.dumps(c)))
if op == 'set':
# XXX: it would be nice to do a strict check for "path already exists",
# but there's probably no way to do that
session.set(path, value=value)
elif op == 'delete':
if strict and not config.exists(cfg_path):
raise ConfigSessionError("Cannot delete [{0}]: path/value does not exist".format(cfg_path))
session.delete(path, value=value)
elif op == 'comment':
session.comment(path, value=value)
else:
raise ConfigSessionError("\"{0}\" is not a valid operation".format(op))
# end for
session.commit()
print("Configuration modified via HTTP API using key \"{0}\"".format(id))
except ConfigSessionError as e:
session.discard()
status = 400
if app.config['vyos_debug']:
print(traceback.format_exc(), file=sys.stderr)
error_msg = str(e)
except Exception as e:
session.discard()
print(traceback.format_exc(), file=sys.stderr)
status = 500
# Don't give the details away to the outer world
error_msg = "An internal error occured. Check the logs for details."
finally:
lock.release()
if status != 200:
return error(status, error_msg)
else:
return success(None)
@app.route('/retrieve', method='POST')
def get_value():
config = app.config['vyos_config']
+ session = app.config['vyos_session']
api_keys = app.config['vyos_keys']
key = bottle.request.forms.get("key")
id = check_auth(api_keys, key)
if not id:
return error(401, "Valid API key is required")
command = bottle.request.forms.get("data")
command = json.loads(command)
- op = command['op']
- path = " ".join(command['path'])
+ try:
+ op = command['op']
+ path = " ".join(command['path'])
+ except KeyError:
+ return error(400, "Missing required field. \"op\" and \"path\" fields are required")
try:
if op == 'returnValue':
res = config.return_value(path)
elif op == 'returnValues':
res = config.return_values(path)
elif op == 'exists':
res = config.exists(path)
+ elif op == 'showConfig':
+ config_format = 'raw'
+ if 'configFormat' in command:
+ config_format = command['configFormat']
+
+ res = session.show_config(command['path'], format=config_format)
else:
return error(400, "\"{0}\" is not a valid operation".format(op))
except VyOSError as e:
return error(400, str(e))
except Exception as e:
print(traceback.format_exc(), file=sys.stderr)
return error(500, "An internal error occured. Check the logs for details.")
return success(res)
if __name__ == '__main__':
# systemd's user and group options don't work, do it by hand here,
# else no one else will be able to commit
cfg_group = grp.getgrnam(CFG_GROUP)
os.setgid(cfg_group.gr_gid)
# Need to set file permissions to 775 too so that every vyattacfg group member
# has write access to the running config
os.umask(0o002)
try:
server_config = load_server_config()
except Exception as e:
print("Failed to load the HTTP API server config: {0}".format(e))
session = ConfigSession(os.getpid())
env = session.get_session_env()
config = vyos.config.Config(session_env=env)
app.config['vyos_session'] = session
app.config['vyos_config'] = config
app.config['vyos_keys'] = server_config['api_keys']
app.config['vyos_debug'] = server_config['debug']
bottle.run(app, host=server_config["listen_address"], port=server_config["port"], debug=True)
diff --git a/src/validators/cidr b/src/validators/cidr
deleted file mode 100755
index 815aa8ba1..000000000
--- a/src/validators/cidr
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-ipaddrcheck --is-any-cidr $1
diff --git a/src/validators/file-exists b/src/validators/file-exists
new file mode 100755
index 000000000..e179805ed
--- /dev/null
+++ b/src/validators/file-exists
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 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/>.
+#
+# Description:
+# Check if a given file exists on the system. Used for files that
+# are referenced from the CLI and need to be preserved during an image upgrade.
+# Warn the user if these aren't under /config
+
+import os
+import sys
+import argparse
+
+parser = argparse.ArgumentParser()
+parser.add_argument("-d", "--directory", type=str, help="File must be present in this directory.")
+parser.add_argument("-e", "--error", action="store_true", help="Tread warnings as errors - change exit code to '1'")
+parser.add_argument("file", type=str, help="Path of file to validate")
+
+args = parser.parse_args()
+
+msg_prefix = "WARNING: "
+if args.error:
+ msg_prefix = "ERROR: "
+
+#
+# Always check if the given file exists
+#
+if not os.path.exists(args.file):
+ print(msg_prefix + "File '{}' not found".format(args.file))
+ if args.error:
+ sys.exit(1)
+ else:
+ sys.exit(0)
+
+#
+# Optional check if the file is under a certain directory path
+#
+if args.directory:
+ # remove directory path from path to verify
+ rel_filename = args.file.replace(args.directory, '').lstrip('/')
+
+ if not os.path.exists(args.directory + '/' + rel_filename):
+ print(msg_prefix + "'{}' lies outside of '{}' directory.\n" \
+ "It will not get preserved during image upgrade!".format(args.file, args.directory))
+ if args.error:
+ sys.exit(1)
+ else:
+ sys.exit(0)
+
+sys.exit(0)
diff --git a/src/validators/ip-cidr b/src/validators/ip-cidr
new file mode 100755
index 000000000..987bf84ca
--- /dev/null
+++ b/src/validators/ip-cidr
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+ipaddrcheck --is-any-cidr $1

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 26, 2:29 PM (1 d, 8 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4285399
Default Alt Text
(220 KB)

Event Timeline