summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2019-09-22 19:13:07 +0200
committerChristian Poessinger <christian@poessinger.com>2019-09-22 19:13:07 +0200
commit79a655a12875f5f152abba2d17eb6a1033b59131 (patch)
tree5a75960039a0dd81608b5a38351a8c8314cabccf
parentc4d0b9ed4736911d341efdebf34997e6cee8c5a8 (diff)
parent2b9c84594a693c66b949183a25cc32dfcdee72e1 (diff)
downloadvyos-1x-79a655a12875f5f152abba2d17eb6a1033b59131.tar.gz
vyos-1x-79a655a12875f5f152abba2d17eb6a1033b59131.zip
Merge branch 'current' of github.com:vyos/vyos-1x into equuleus
* 'current' of github.com:vyos/vyos-1x: (49 commits) Jenkins: ease Pipeline vxlan: T1636: simplyfy code (don't delete intf addresses) ethernet: T1637: interfaces in a bond can be disabled ethernet: T1637: fix calling arp_cache_tmo property ethernet: T1637: do not overwrite interface description with interface name ethernet: T1637: support offloading functions Python/ifconfig: T1557: ethernet: add offloading interfaces Python/ifconfig: T1557: update comments Python/ifconfig: T1557: delete all assigned IP addresses on remove() ethernet: T1637: call remove() on interface deletion Python/ifconfig: T1557: use proper inheritance levels on remove() ethernet: T1637: remove debug pprint bridge: T1556: minor comment cleanup bonding: T1614: minor comment cleanup Python/ifconfig: T1557: unify '/sys/class/net/{}' path Python/ifconfig: T1557: vmxnet3/virtio_net do not support changing speed/duplex control Python/ifconfig: T1557: vmxnet3/virtio_net do not support changing flow control Python/ifconfig: T1557: query driver if it supports auto negotiation Python/ifconfig: T1557: call ethtool with full path Python/ifconfig: T1557: return stdout string for _cmd() ...
-rw-r--r--Jenkinsfile263
-rw-r--r--Makefile6
-rw-r--r--interface-definitions/interfaces-ethernet.xml872
-rw-r--r--interface-definitions/interfaces-openvpn.xml70
-rw-r--r--interface-definitions/ipoe-server.xml9
-rw-r--r--python/vyos/configdict.py22
-rw-r--r--python/vyos/configsession.py6
-rw-r--r--python/vyos/defaults.py1
-rw-r--r--python/vyos/ifconfig.py407
-rwxr-xr-xscripts/import-conf-mode-commands240
-rwxr-xr-xsrc/conf_mode/interface-bonding.py12
-rwxr-xr-xsrc/conf_mode/interface-bridge.py3
-rwxr-xr-xsrc/conf_mode/interface-ethernet.py382
-rwxr-xr-xsrc/conf_mode/interface-openvpn.py36
-rwxr-xr-xsrc/conf_mode/interface-vxlan.py16
-rwxr-xr-xsrc/conf_mode/interface-wireguard.py26
-rwxr-xr-xsrc/conf_mode/ipoe_server.py23
-rwxr-xr-xsrc/helpers/vyos-boot-config-loader.py153
-rwxr-xr-xsrc/services/vyos-hostsd2
19 files changed, 2281 insertions, 268 deletions
diff --git a/Jenkinsfile b/Jenkinsfile
index 835a683a8..e254a2dca 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -1,131 +1,162 @@
-pipeline {
- agent none
- stages {
- stage('build-package') {
- parallel {
- stage('Build package amd64') {
- agent {
- docker {
- label 'jessie-amd64'
- args '--privileged --sysctl net.ipv6.conf.lo.disable_ipv6=0 -e GOSU_UID=1006 -e GOSU_GID=1006 -v /tmp:/tmp'
- image 'higebu/vyos-build:equuleus'
- }
+// Copyright (C) 2019 VyOS maintainers and contributors
+//
+// This program is free software; you can redistribute it and/or modify
+// in order to easy exprort images built to "external" world
+// 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/>.
+
+@NonCPS
+
+def getGitBranchName() {
+ def branch = scm.branches[0].name
+ return branch.split('/')[-1]
+}
+
+def getGitRepoURL() {
+ return scm.userRemoteConfigs[0].url
+}
+
+// Returns true if this is a custom build launched on any project fork,
+// returns false if this is build from git@github.com:vyos/env.JOB_NAME
+// env.JOB_NAME is e.g. vyos-build or vyos-1x and so on ....
+def isCustomBuild() {
+ // GitHub organisation base URL
+ def gitURI = 'git@github.com:vyos/' + env.JOB_NAME
+ def httpURI = 'https://github.com/vyos' + env.JOB_NAME
+
+ return ! ((getGitRepoURL() == gitURI) || (getGitRepoURL() == httpURI))
+}
+
+def setDescription() {
+ def item = Jenkins.instance.getItemByFullName(env.JOB_NAME)
+
+ // build up the main description text
+ def description = ""
+ description += "<h2>Build VyOS ISO image</h2>"
+
+ if (isCustomBuild()) {
+ description += "<p style='border: 3px dashed red; width: 50%;'>"
+ description += "<b>Build not started from official Git repository!</b><br>"
+ description += "<br>"
+ description += "Repository: <font face = 'courier'>" + getGitRepoURL() + "</font><br>"
+ description += "Branch: <font face = 'courier'>" + getGitBranchName() + "</font><br>"
+ description += "</p>"
+ } else {
+ description += "Sources taken from Git branch: <font face = 'courier'>" + getGitBranchName() + "</font><br>"
+ }
+
+ item.setDescription(description)
+ item.save()
+}
- }
- steps {
- sh '''#!/bin/bash
-git clone --single-branch --branch $GIT_BRANCH $GIT_URL $BUILD_NUMBER
-cd $BUILD_NUMBER
-sudo pip3 uninstall vyos -y || true
-sudo pip3 install -r test-requirements.txt
-python3 -m "pylint" src -r n --msg-template="{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}" > pylint-report.txt || true
-make test
-sudo apt-get -o Acquire::Check-Valid-Until=false update
-sudo mk-build-deps -i -r -t \'apt-get --no-install-recommends -yq\' debian/control
-dpkg-buildpackage -b -us -uc -tc
-mkdir -p /tmp/$GIT_BRANCH/packages/script
-mv ../*.deb /tmp/$GIT_BRANCH/packages/'''
- }
+/* Only keep the 10 most recent builds. */
+def projectProperties = [
+ [$class: 'BuildDiscarderProperty',strategy: [$class: 'LogRotator', numToKeepStr: '1']],
+]
+
+properties(projectProperties)
+setDescription()
+
+pipeline {
+ agent {
+ docker {
+ label 'Docker'
+ args '--sysctl net.ipv6.conf.lo.disable_ipv6=0 -e GOSU_UID=1006 -e GOSU_GID=1006'
+ image 'vyos/vyos-build:equuleus'
}
- stage('Build package armhf') {
- agent {
- docker {
- label 'jessie-amd64'
- image 'vyos-build-armhf:equuleus'
- args '--privileged --sysctl net.ipv6.conf.lo.disable_ipv6=0 -e GOSU_UID=1006 -e GOSU_GID=1006 -v /tmp:/tmp'
+ }
+ options {
+ disableConcurrentBuilds()
+ skipDefaultCheckout()
+ timeout(time: 30, unit: 'MINUTES')
+ timestamps()
+ }
+ stages {
+ stage('Fetch') {
+ steps {
+ script {
+ dir('build') {
+ git branch: getGitBranchName(), url: getGitRepoURL()
+ }
+ }
}
-
- }
- steps {
- sh '''#!/bin/bash
-git clone --single-branch --branch $GIT_BRANCH $GIT_URL $BUILD_NUMBER
-cd $BUILD_NUMBER
-sudo pip3 uninstall vyos -y || true
-sudo pip3 install -r test-requirements.txt
-python3 -m "pylint" src -r n --msg-template="{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}" > pylint-report.txt || true
-make test
-sudo apt-get -o Acquire::Check-Valid-Until=false update
-sudo mk-build-deps -i -r -t \'apt-get --no-install-recommends -yq\' debian/control
-dpkg-buildpackage -b -us -uc -tc
-mkdir -p /tmp/$GIT_BRANCH/packages/script
-mv ../*.deb /tmp/$GIT_BRANCH/packages/'''
- }
}
- stage('Build package arm64') {
- agent {
- docker {
- label 'jessie-amd64'
- args '--privileged --sysctl net.ipv6.conf.lo.disable_ipv6=0 -e GOSU_UID=1006 -e GOSU_GID=1006 -v /tmp:/tmp'
- image 'vyos-build-arm64:equuleus'
+ stage('Build') {
+ steps {
+ script {
+ dir('build') {
+ sh """
+ #!/bin/bash
+ sudo apt-get -o Acquire::Check-Valid-Until=false update
+ sudo mk-build-deps -i -r -t \'apt-get --no-install-recommends -yq\' debian/control
+ dpkg-buildpackage -b -us -uc -tc
+ """
+ }
+ }
}
-
- }
- steps {
- sh '''#!/bin/bash
-git clone --single-branch --branch $GIT_BRANCH $GIT_URL $BUILD_NUMBER
-cd $BUILD_NUMBER
-sudo pip3 uninstall vyos -y || true
-sudo pip3 install -r test-requirements.txt
-python3 -m "pylint" src -r n --msg-template="{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}" > pylint-report.txt || true
-make test
-sudo apt-get -o Acquire::Check-Valid-Until=false update
-sudo mk-build-deps -i -r -t \'apt-get --no-install-recommends -yq\' debian/control
-dpkg-buildpackage -b -us -uc -tc
-mkdir -p /tmp/$GIT_BRANCH/packages/script
-mv ../*.deb /tmp/$GIT_BRANCH/packages/'''
- }
}
- }
}
- stage('Deploy packages') {
- agent {
- node {
- label 'jessie-amd64'
+ post {
+ cleanup {
+ deleteDir()
}
+ success {
+ script {
+ // archive *.deb artifact on custom builds, deploy to repo otherwise
+ if ( isCustomBuild()) {
+ archiveArtifacts artifacts: '*.deb', fingerprint: true
+ } else {
+ // publish build result, using SSH-dev.packages.vyos.net Jenkins Credentials
+ sshagent(['SSH-dev.packages.vyos.net']) {
+ // build up some fancy groovy variables so we do not need to write/copy
+ // every option over and over again!
- }
- steps {
- sh '''#!/bin/bash
-cd /tmp/$GIT_BRANCH/packages/script
-/var/lib/vyos-build/pkg-build.sh $GIT_BRANCH'''
- }
- }
- stage('Cleanup') {
- parallel {
- stage('Cleanup amd64') {
- agent {
- node {
- label 'jessie-amd64'
- }
+ def VYOS_REPO_PATH = '/home/sentrium/web/dev.packages.vyos.net/public_html/repositories/' + getGitBranchName() + '/'
+ if (getGitBranchName() != "equuleus")
+ VYOS_REPO_PATH += 'vyos/'
- }
- steps {
- cleanWs(cleanWhenAborted: true, cleanWhenFailure: true, cleanWhenNotBuilt: true, cleanWhenSuccess: true, cleanWhenUnstable: true, cleanupMatrixParent: true, deleteDirs: true, disableDeferredWipeout: true)
- }
- }
- stage('Cleanup armhf') {
- agent {
- node {
- label 'jessie-amd64'
- }
+ def SSH_OPTS = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=ERROR'
+ def SSH_REMOTE = 'khagen@10.217.48.113'
- }
- steps {
- cleanWs(cleanWhenAborted: true, cleanWhenFailure: true, cleanWhenNotBuilt: true, cleanWhenSuccess: true, cleanWhenUnstable: true, cleanupMatrixParent: true, deleteDirs: true, disableDeferredWipeout: true)
- }
- }
- stage('Cleanup arm64') {
- agent {
- node {
- label 'jessie-amd64'
- }
+ echo "Uploading package(s) and updating package(s) in the repository ..."
+
+ files = findFiles(glob: '*.deb')
+ files.each { PACKAGE ->
+ def RELEASE = getGitBranchName()
+ def ARCH = sh(returnStdout: true, script: "dpkg-deb -f ${pkg} Architecture").trim()
+ def SUBSTRING = sh(returnStdout: true, script: "dpkg-deb -f ${pkg} Package").trim()
+ def SSH_DIR = '~/VyOS/' + RELEASE + '/' + ARCH
- }
- steps {
- cleanWs(cleanWhenAborted: true, cleanWhenFailure: true, cleanWhenNotBuilt: true, cleanWhenSuccess: true, cleanWhenUnstable: true, cleanupMatrixParent: true, deleteDirs: true, disableDeferredWipeout: true)
- }
+ // No need to explicitly check the return code. The pipeline
+ // will fail if sh returns a non 0 exit code
+ sh """
+ ssh ${SSH_OPTS} ${SSH_REMOTE} -t "bash --login -c 'mkdir -p ${SSH_DIR}'"
+ """
+ sh """
+ scp ${SSH_OPTS} ${PACKAGE} ${SSH_REMOTE}:${SSH_DIR}/
+ """
+ sh """
+ ssh ${SSH_OPTS} ${SSH_REMOTE} -t "uncron-add 'reprepro -v -b ${VYOS_REPO_PATH} -A ${ARCH} remove ${RELEASE} ${SUBSTRING}'"
+ """
+ sh """
+ ssh ${SSH_OPTS} ${SSH_REMOTE} -t "uncron-add 'reprepro -v -b ${VYOS_REPO_PATH} deleteunreferenced'"
+ """
+ sh """
+ ssh ${SSH_OPTS} ${SSH_REMOTE} -t "uncron-add 'reprepro -v -b ${VYOS_REPO_PATH} -A ${ARCH} includedeb ${RELEASE} ${SSH_DIR}/${PACKAGE}'"
+ """
+ }
+ }
+ }
+ }
}
- }
}
- }
}
+
diff --git a/Makefile b/Makefile
index 61bc06c47..881fc36b1 100644
--- a/Makefile
+++ b/Makefile
@@ -11,8 +11,12 @@ interface_definitions:
# 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)/interfaces/bonding/node.tag/ip/node.def
+ rm -f $(TMPL_DIR)/interfaces/bridge/node.tag/ip/node.def
+ rm -f $(TMPL_DIR)/interfaces/ethernet/node.tag/ip/node.def
+ rm -f $(TMPL_DIR)/interfaces/ethernet/node.tag/vif/node.tag/ip/node.def
+ rm -f $(TMPL_DIR)/interfaces/ethernet/node.tag/vif-s/node.tag/ip/node.def
+ rm -f $(TMPL_DIR)/interfaces/ethernet/node.tag/vif-s/node.tag/vif-c/node.tag/ip/node.def
rm -f $(TMPL_DIR)/interfaces/vxlan/node.tag/ip/node.def
rm -f $(TMPL_DIR)/protocols/node.def
rm -f $(TMPL_DIR)/protocols/static/node.def
diff --git a/interface-definitions/interfaces-ethernet.xml b/interface-definitions/interfaces-ethernet.xml
new file mode 100644
index 000000000..9244f3b5f
--- /dev/null
+++ b/interface-definitions/interfaces-ethernet.xml
@@ -0,0 +1,872 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="interfaces">
+ <children>
+ <tagNode name="ethernet" owner="${vyos_conf_scripts_dir}/interface-ethernet.py">
+ <properties>
+ <help>Ethernet interface name</help>
+ <priority>318</priority>
+ <constraint>
+ <regex>((eth|lan)[0-9]+|(eno|ens|enp|enx).+)$</regex>
+ </constraint>
+ <constraintErrorMessage>Invalid Ethernet interface name</constraintErrorMessage>
+ <valueHelp>
+ <format>ethN</format>
+ <description>Ethernet interface name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>en[ospx]N</format>
+ <description>Ethernet 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="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-flow-control">
+ <properties>
+ <help>Disable Ethernet flow control (pause frames)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <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>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="duplex">
+ <properties>
+ <help>Duplex mode</help>
+ <completionHelp>
+ <list>auto half full</list>
+ </completionHelp>
+ <valueHelp>
+ <format>auto</format>
+ <description>Auto negotiation (default)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>half</format>
+ <description>Half duplex</description>
+ </valueHelp>
+ <valueHelp>
+ <format>full</format>
+ <description>Full duplex</description>
+ </valueHelp>
+ <constraint>
+ <regex>(auto|half|full)</regex>
+ </constraint>
+ <constraintErrorMessage>duplex must be auto, half or full</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="hw-id">
+ <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>
+ <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>ARP cache entry timeout must be between 1 and 86400 seconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="enable-proxy-arp">
+ <properties>
+ <help>Enable proxy-arp on this interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="proxy-arp-pvlan">
+ <properties>
+ <help>Enable private VLAN proxy ARP on this interface</help>
+ <valueless/>
+ </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="mtu">
+ <properties>
+ <help>Maximum Transmission Unit (MTU)</help>
+ <valueHelp>
+ <format>68-9000</format>
+ <description>Maximum Transmission Unit</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 68-9000"/>
+ </constraint>
+ <constraintErrorMessage>MTU must be between 68 and 9000</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <node name="offload-options">
+ <properties>
+ <help>Configurable offload options</help>
+ </properties>
+ <children>
+ <leafNode name="generic-receive">
+ <properties>
+ <help>Configure GRO (generic receive offload)</help>
+ <completionHelp>
+ <list>on off</list>
+ </completionHelp>
+ <valueHelp>
+ <format>on</format>
+ <description>Enable GRO (generic receive offload)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>off</format>
+ <description>Disable GRO (generic receive offload)</description>
+ </valueHelp>
+ <constraint>
+ <regex>(on|off)</regex>
+ </constraint>
+ <constraintErrorMessage>Must be either 'on' or 'off'</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="generic-segmentation">
+ <properties>
+ <help>Configure GSO (generic segmentation offload)</help>
+ <completionHelp>
+ <list>on off</list>
+ </completionHelp>
+ <valueHelp>
+ <format>on</format>
+ <description>Enable GSO (generic segmentation offload)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>off</format>
+ <description>Disable GSO (generic segmentation offload)</description>
+ </valueHelp>
+ <constraint>
+ <regex>(on|off)</regex>
+ </constraint>
+ <constraintErrorMessage>Must be either 'on' or 'off'</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="scatter-gather">
+ <properties>
+ <help>Configure scatter-gather option</help>
+ <completionHelp>
+ <list>on off</list>
+ </completionHelp>
+ <valueHelp>
+ <format>on</format>
+ <description>Enable scatter-gather</description>
+ </valueHelp>
+ <valueHelp>
+ <format>off</format>
+ <description>Disable scatter-gather</description>
+ </valueHelp>
+ <constraint>
+ <regex>(on|off)</regex>
+ </constraint>
+ <constraintErrorMessage>Must be either 'on' or 'off'</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="tcp-segmentation">
+ <properties>
+ <help>Configure TSO (TCP segmentation offloading)</help>
+ <completionHelp>
+ <list>on off</list>
+ </completionHelp>
+ <valueHelp>
+ <format>on</format>
+ <description>Enable TSO (TCP segmentation offloading)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>off</format>
+ <description>Disable TSO (TCP segmentation offloading)</description>
+ </valueHelp>
+ <constraint>
+ <regex>(on|off)</regex>
+ </constraint>
+ <constraintErrorMessage>Must be either 'on' or 'off'</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="udp-fragmentation">
+ <properties>
+ <help>Configure UDP fragmentation offloading</help>
+ <completionHelp>
+ <list>on off</list>
+ </completionHelp>
+ <valueHelp>
+ <format>on</format>
+ <description>Enable UDP fragmentation offloading</description>
+ </valueHelp>
+ <valueHelp>
+ <format>off</format>
+ <description>Disable UDP fragmentation offloading</description>
+ </valueHelp>
+ <constraint>
+ <regex>(on|off)</regex>
+ </constraint>
+ <constraintErrorMessage>Must be either 'on' or 'off'</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="smp-affinity">
+ <properties>
+ <help>CPU interrupt affinity mask</help>
+ <completionHelp>
+ <list>auto 10 100 1000 2500 5000 10000</list>
+ </completionHelp>
+ <valueHelp>
+ <format>auto</format>
+ <description>Auto negotiation (default)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>hex</format>
+ <description>Bitmask representing CPUs that this NIC will interrupt</description>
+ </valueHelp>
+ <valueHelp>
+ <format>hex,hex</format>
+ <description>Bitmasks representing CPUs for interrupt and receive processing</description>
+ </valueHelp>
+ <constraint>
+ <regex>(auto)</regex>
+ <regex>[0-9a-f]+(|,[0-9a-f]+)$</regex>
+ </constraint>
+ <constraintErrorMessage>IRQ affinity mask must be hex value or auto</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="speed">
+ <properties>
+ <help>Link speed</help>
+ <completionHelp>
+ <list>auto 10 100 1000 2500 5000 10000 25000 40000 50000 100000</list>
+ </completionHelp>
+ <valueHelp>
+ <format>auto</format>
+ <description>Auto negotiation (default)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>10</format>
+ <description>10 Mbit/sec</description>
+ </valueHelp>
+ <valueHelp>
+ <format>100</format>
+ <description>100 Mbit/sec</description>
+ </valueHelp>
+ <valueHelp>
+ <format>1000</format>
+ <description>1 Gbit/sec</description>
+ </valueHelp>
+ <valueHelp>
+ <format>2500</format>
+ <description>2.5 Gbit/sec</description>
+ </valueHelp>
+ <valueHelp>
+ <format>5000</format>
+ <description>5 Gbit/sec</description>
+ </valueHelp>
+ <valueHelp>
+ <format>10000</format>
+ <description>10 Gbit/sec</description>
+ </valueHelp>
+ <valueHelp>
+ <format>25000</format>
+ <description>25 Gbit/sec</description>
+ </valueHelp>
+ <valueHelp>
+ <format>40000</format>
+ <description>40 Gbit/sec</description>
+ </valueHelp>
+ <valueHelp>
+ <format>50000</format>
+ <description>50 Gbit/sec</description>
+ </valueHelp>
+ <valueHelp>
+ <format>100000</format>
+ <description>100 Gbit/sec</description>
+ </valueHelp>
+ <constraint>
+ <regex>(auto|10|100|1000|2500|5000|10000|25000|40000|50000|100000)</regex>
+ </constraint>
+ <constraintErrorMessage>Speed must be auto, 10, 100, 1000, 2500, 5000, 10000, 25000, 40000, 50000 or 100000</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <tagNode name="vif-s">
+ <properties>
+ <help>QinQ TAG-S Virtual Local Area Network (VLAN) ID</help>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4094"/>
+ </constraint>
+ <constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage>
+ </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="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>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ethertype">
+ <properties>
+ <help>Set Ethertype</help>
+ <completionHelp>
+ <list>0x88A8 0x8100</list>
+ </completionHelp>
+ <valueHelp>
+ <format>0x88A8</format>
+ <description>802.1ad</description>
+ </valueHelp>
+ <valueHelp>
+ <format>0x8100</format>
+ <description>802.1q</description>
+ </valueHelp>
+ <constraint>
+ <regex>(0x88A8|0x8100)</regex>
+ </constraint>
+ <constraintErrorMessage>Ethertype must be 0x88A8 or 0x8100</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <node name="ip">
+ <children>
+ <leafNode name="enable-proxy-arp">
+ <properties>
+ <help>Enable proxy-arp on this interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="proxy-arp-pvlan">
+ <properties>
+ <help>Enable private VLAN proxy ARP on this interface</help>
+ <valueless/>
+ </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="mtu">
+ <properties>
+ <help>Maximum Transmission Unit (MTU)</help>
+ <valueHelp>
+ <format>68-9000</format>
+ <description>Maximum Transmission Unit</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 68-9000"/>
+ </constraint>
+ <constraintErrorMessage>MTU must be between 68 and 9000</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <tagNode name="vif-c">
+ <properties>
+ <help>QinQ TAG-C Virtual Local Area Network (VLAN) ID</help>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4094"/>
+ </constraint>
+ <constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage>
+ </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="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>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <node name="ip">
+ <children>
+ <leafNode name="enable-proxy-arp">
+ <properties>
+ <help>Enable proxy-arp on this interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="proxy-arp-pvlan">
+ <properties>
+ <help>Enable private VLAN proxy ARP on this interface</help>
+ <valueless/>
+ </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="mtu">
+ <properties>
+ <help>Maximum Transmission Unit (MTU)</help>
+ <valueHelp>
+ <format>68-9000</format>
+ <description>Maximum Transmission Unit</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 68-9000"/>
+ </constraint>
+ <constraintErrorMessage>MTU must be between 68 and 9000</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="vif">
+ <properties>
+ <help>Virtual Local Area Network (VLAN) ID</help>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4094"/>
+ </constraint>
+ <constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage>
+ </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="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>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="egress-qos">
+ <properties>
+ <help>VLAN egress QoS</help>
+ <completionHelp>
+ <script>echo Format for qos mapping \"0:1 1:6 7:6\"</script>
+ </completionHelp>
+ <constraint>
+ <regex>[:0-7 ]+$</regex>
+ </constraint>
+ <constraintErrorMessage>QoS mapping should be in the format of \"0:7 2:3\" with numbers 0-9</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="ingress-qos">
+ <properties>
+ <help>VLAN ingress QoS</help>
+ <completionHelp>
+ <script>echo Format for qos mapping \"0:1 1:6 7:6\"</script>
+ </completionHelp>
+ <constraint>
+ <regex>[:0-7 ]+$</regex>
+ </constraint>
+ <constraintErrorMessage>QoS mapping should be in the format of \"0:7 2:3\" with numbers 0-9</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <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>ARP cache entry timeout must be between 1 and 86400 seconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="enable-proxy-arp">
+ <properties>
+ <help>Enable proxy-arp on this interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="proxy-arp-pvlan">
+ <properties>
+ <help>Enable private VLAN proxy ARP on this interface</help>
+ <valueless/>
+ </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="mtu">
+ <properties>
+ <help>Maximum Transmission Unit (MTU)</help>
+ <valueHelp>
+ <format>68-9000</format>
+ <description>Maximum Transmission Unit</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 68-9000"/>
+ </constraint>
+ <constraintErrorMessage>MTU must be between 68 and 9000</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/interfaces-openvpn.xml b/interface-definitions/interfaces-openvpn.xml
index d282a8773..fb2564cbd 100644
--- a/interface-definitions/interfaces-openvpn.xml
+++ b/interface-definitions/interfaces-openvpn.xml
@@ -518,29 +518,99 @@
<help>Transport Layer Security (TLS) options</help>
</properties>
<children>
+ <leafNode name="auth-file">
+ <properties>
+ <help>File containing tls static key for tls-auth</help>
+ <valueHelp>
+ <format>file</format>
+ <description>File in /config/auth directory</description>
+ </valueHelp>
+ <constraint>
+ <validator name="file-exists" argument="--directory /config/auth"/>
+ </constraint>
+ </properties>
+ </leafNode>
<leafNode name="ca-cert-file">
<properties>
<help>File containing certificate for Certificate Authority (CA)</help>
+ <valueHelp>
+ <format>file</format>
+ <description>File in /config/auth directory</description>
+ </valueHelp>
+ <constraint>
+ <validator name="file-exists" argument="--directory /config/auth"/>
+ </constraint>
</properties>
</leafNode>
<leafNode name="cert-file">
<properties>
<help>File containing certificate for this host</help>
+ <valueHelp>
+ <format>file</format>
+ <description>File in /config/auth directory</description>
+ </valueHelp>
+ <constraint>
+ <validator name="file-exists" argument="--directory /config/auth"/>
+ </constraint>
</properties>
</leafNode>
<leafNode name="crl-file">
<properties>
<help>File containing certificate revocation list (CRL) for this host</help>
+ <valueHelp>
+ <format>file</format>
+ <description>File in /config/auth directory</description>
+ </valueHelp>
+ <constraint>
+ <validator name="file-exists" argument="--directory /config/auth"/>
+ </constraint>
</properties>
</leafNode>
<leafNode name="dh-file">
<properties>
<help>File containing Diffie Hellman parameters (server only)</help>
+ <valueHelp>
+ <format>file</format>
+ <description>File in /config/auth directory</description>
+ </valueHelp>
+ <constraint>
+ <validator name="file-exists" argument="--directory /config/auth"/>
+ </constraint>
</properties>
</leafNode>
<leafNode name="key-file">
<properties>
<help>File containing this host's private key</help>
+ <valueHelp>
+ <format>file</format>
+ <description>File in /config/auth directory</description>
+ </valueHelp>
+ <constraint>
+ <validator name="file-exists" argument="--directory /config/auth"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="tls-version-min">
+ <properties>
+ <help>Specify the minimum required TLS version</help>
+ <completionHelp>
+ <list>1.0 1.1 1.2</list>
+ </completionHelp>
+ <valueHelp>
+ <format>1.0</format>
+ <description>TLS v1.0</description>
+ </valueHelp>
+ <valueHelp>
+ <format>1.1</format>
+ <description>TLS v1.1</description>
+ </valueHelp>
+ <valueHelp>
+ <format>1.2</format>
+ <description>TLS v1.2</description>
+ </valueHelp>
+ <constraint>
+ <regex>(1.0|1.1|1.2)</regex>
+ </constraint>
</properties>
</leafNode>
<leafNode name="role">
diff --git a/interface-definitions/ipoe-server.xml b/interface-definitions/ipoe-server.xml
index 6c93d3699..fd84439b5 100644
--- a/interface-definitions/ipoe-server.xml
+++ b/interface-definitions/ipoe-server.xml
@@ -255,6 +255,15 @@
</leafNode>
</children>
</node>
+ <leafNode name="vlan-id">
+ <properties>
+ <help>VLAN-ID of the client network</help>
+ <constraint>
+ <validator name="numeric" argument="--range 1-4096"/>
+ </constraint>
+ <constraintErrorMessage>VLAN ID needs to be between 1 and 4096</constraintErrorMessage>
+ </properties>
+ </leafNode>
</children>
</tagNode>
</children>
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 4bc8863bb..1c9cf6897 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -116,6 +116,10 @@ def vlan_to_dict(conf):
'dhcpv6_temporary': False,
'disable': False,
'disable_link_detect': 1,
+ 'egress_qos': '',
+ 'egress_qos_changed': False,
+ 'ingress_qos': '',
+ 'ingress_qos_changed': False,
'mac': '',
'mtu': 1500
}
@@ -153,7 +157,7 @@ def vlan_to_dict(conf):
if conf.exists('disable-link-detect'):
vlan['disable_link_detect'] = 2
- # disable bond interface
+ # disable VLAN interface
if conf.exists('disable'):
vlan['disable'] = True
@@ -165,6 +169,22 @@ def vlan_to_dict(conf):
if conf.exists('mtu'):
vlan['mtu'] = int(conf.return_value('mtu'))
+ # VLAN egress QoS
+ if conf.exists('egress-qos'):
+ vlan['egress_qos'] = conf.return_value('egress-qos')
+
+ # egress changes QoS require VLAN interface recreation
+ if vlan['egress_qos'] != conf.return_effective_value('egress-qos'):
+ vlan['egress_qos_changed'] = True
+
+ # VLAN ingress QoS
+ if conf.exists('ingress-qos'):
+ vlan['ingress_qos'] = conf.return_value('ingress-qos')
+
+ # ingress changes QoS require VLAN interface recreation
+ if vlan['ingress_qos'] != conf.return_effective_value('ingress-qos'):
+ vlan['ingress_qos_changed'] = True
+
# ethertype is mandatory on vif-s nodes and only exists here!
# check if this is a vif-s node at all:
if conf.get_level().split()[-2] == 'vif-s':
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index acbdd3d5f..09fae78a1 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -145,7 +145,8 @@ class ConfigSession(object):
self.__run_command([COMMENT] + path + value)
def commit(self):
- self.__run_command([COMMIT])
+ out = self.__run_command([COMMIT])
+ return out
def discard(self):
self.__run_command([DISCARD])
@@ -157,4 +158,5 @@ class ConfigSession(object):
return config_data
def load_config(self, file_path):
- self.__run_command(LOAD_CONFIG + [file_path])
+ out = self.__run_command(LOAD_CONFIG + [file_path])
+ return out
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index 85d27d60d..dedb929b4 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -20,6 +20,7 @@ directories = {
"config": "/opt/vyatta/etc/config",
"current": "/opt/vyatta/etc/config-migrate/current",
"migrate": "/opt/vyatta/etc/config-migrate/migrate",
+ "log": "/var/log/vyatta",
}
cfg_group = 'vyattacfg'
diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py
index 0793fad39..777b185a6 100644
--- a/python/vyos/ifconfig.py
+++ b/python/vyos/ifconfig.py
@@ -15,12 +15,12 @@
import os
import re
-import subprocess
import jinja2
from vyos.validate import *
from ipaddress import IPv4Network, IPv6Address
from netifaces import ifaddresses, AF_INET, AF_INET6
+from subprocess import Popen, PIPE, STDOUT
from time import sleep
dhcp_cfg = """
@@ -43,7 +43,6 @@ dhclient_base = r'/var/lib/dhcp/dhclient_'
class Interface:
-
def __init__(self, ifname, type=None):
"""
This is the base interface class which supports basic IP/MAC address
@@ -84,48 +83,15 @@ class Interface:
if os.path.isfile('/tmp/vyos.ifconfig.debug'):
print('DEBUG/{:<6} {}'.format(self._ifname, msg))
- def remove(self):
- """
- Remove interface from operating system. Removing the interface
- deconfigures all assigned IP addresses and clear possible DHCP(v6)
- client processes.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> i = Interface('eth0')
- >>> i.remove()
- """
-
- # do we have sub interfaces (VLANs)?
- # we apply a regex matching subinterfaces (indicated by a .) of a
- # parent interface. 'bond0(?:\.\d+){1,2}' will match vif and vif-s/vif-c
- # subinterfaces
- vlan_ifs = [f for f in os.listdir(r'/sys/class/net') \
- if re.match(self._ifname + r'(?:\.\d+){1,2}', f)]
-
- for vlan in vlan_ifs:
- Interface(vlan).remove()
-
- # All subinterfaces are now removed, continue on the physical interface
-
- # stop DHCP(v6) if running
- self._del_dhcp()
- self._del_dhcpv6()
-
- # NOTE (Improvement):
- # after interface removal no other commands should be allowed
- # to be called and instead should raise an Exception:
- cmd = 'ip link del dev {}'.format(self._ifname)
- self._cmd(cmd)
-
def _cmd(self, command):
+ p = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True)
+ tmp = p.communicate()[0].strip()
self._debug_msg("cmd '{}'".format(command))
+ if tmp.decode():
+ self._debug_msg("returned:\n{}".format(tmp.decode()))
- process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)
- proc_stdout = process.communicate()[0].strip()
-
- # add exception handling code
- pass
+ # do we need some error checking code here?
+ return tmp
def _read_sysfs(self, filename):
"""
@@ -148,6 +114,37 @@ class Interface:
return None
+ def remove(self):
+ """
+ Remove interface from operating system. Removing the interface
+ deconfigures all assigned IP addresses and clear possible DHCP(v6)
+ client processes.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> i = Interface('eth0')
+ >>> i.remove()
+ """
+ # stop DHCP(v6) if running
+ self._del_dhcp()
+ self._del_dhcpv6()
+
+ # remove all assigned IP addresses from interface - this is a bit redundant
+ # as the kernel will remove all addresses on interface deletion, but we
+ # can not delete ALL interfaces, see below
+ for addr in self.get_addr():
+ self.del_addr(addr)
+
+ # Ethernet interfaces can not be removed
+ if type(self) == type(EthernetIf(self._ifname)):
+ return
+
+ # NOTE (Improvement):
+ # after interface removal no other commands should be allowed
+ # to be called and instead should raise an Exception:
+ cmd = 'ip link del dev {}'.format(self._ifname)
+ self._cmd(cmd)
+
@property
def mtu(self):
"""
@@ -158,7 +155,7 @@ class Interface:
>>> Interface('eth0').mtu
'1500'
"""
- return self._read_sysfs('/sys/class/net/{0}/mtu'
+ return self._read_sysfs('/sys/class/net/{}/mtu'
.format(self._ifname))
@mtu.setter
@@ -175,7 +172,7 @@ class Interface:
if mtu < 68 or mtu > 9000:
raise ValueError('Invalid MTU size: "{}"'.format(mru))
- return self._write_sysfs('/sys/class/net/{0}/mtu'
+ return self._write_sysfs('/sys/class/net/{}/mtu'
.format(self._ifname), mtu)
@property
@@ -188,7 +185,7 @@ class Interface:
>>> Interface('eth0').mac
'00:0c:29:11:aa:cc'
"""
- return self._read_sysfs('/sys/class/net/{0}/address'
+ return self._read_sysfs('/sys/class/net/{}/address'
.format(self._ifname))
@mac.setter
@@ -202,6 +199,10 @@ class Interface:
>>> Interface('eth0').mac
'00:90:43:fe:fe:1b'
"""
+ # on interface removal (ethernet) an empty string is passed - ignore it
+ if not mac:
+ return None
+
# a mac address consits out of 6 octets
octets = len(mac.split(':'))
if octets != 6:
@@ -303,7 +304,7 @@ class Interface:
>>> Interface('eth0').ifalias
''
"""
- return self._read_sysfs('/sys/class/net/{0}/ifalias'
+ return self._read_sysfs('/sys/class/net/{}/ifalias'
.format(self._ifname))
@ifalias.setter
@@ -327,7 +328,7 @@ class Interface:
# clear interface alias
ifalias = '\0'
- self._write_sysfs('/sys/class/net/{0}/ifalias'
+ self._write_sysfs('/sys/class/net/{}/ifalias'
.format(self._ifname), ifalias)
@property
@@ -340,7 +341,7 @@ class Interface:
>>> Interface('eth0').state
'up'
"""
- return self._read_sysfs('/sys/class/net/{0}/operstate'
+ return self._read_sysfs('/sys/class/net/{}/operstate'
.format(self._ifname))
@state.setter
@@ -759,7 +760,7 @@ class BridgeIf(Interface):
>>> BridgeIf('br0').aging_time
'300'
"""
- return (self._read_sysfs('/sys/class/net/{0}/bridge/ageing_time'
+ return (self._read_sysfs('/sys/class/net/{}/bridge/ageing_time'
.format(self._ifname)) / 100)
@ageing_time.setter
@@ -773,7 +774,7 @@ class BridgeIf(Interface):
>>> BridgeIf('br0').ageing_time = 2
"""
time = int(time) * 100
- return self._write_sysfs('/sys/class/net/{0}/bridge/ageing_time'
+ return self._write_sysfs('/sys/class/net/{}/bridge/ageing_time'
.format(self._ifname), time)
@property
@@ -787,7 +788,7 @@ class BridgeIf(Interface):
>>> BridgeIf('br0').ageing_time
'3'
"""
- return (self._read_sysfs('/sys/class/net/{0}/bridge/forward_delay'
+ return (self._read_sysfs('/sys/class/net/{}/bridge/forward_delay'
.format(self._ifname)) / 100)
@forward_delay.setter
@@ -800,7 +801,7 @@ class BridgeIf(Interface):
>>> from vyos.ifconfig import Interface
>>> BridgeIf('br0').forward_delay = 15
"""
- return self._write_sysfs('/sys/class/net/{0}/bridge/forward_delay'
+ return self._write_sysfs('/sys/class/net/{}/bridge/forward_delay'
.format(self._ifname), (int(time) * 100))
@property
@@ -814,7 +815,7 @@ class BridgeIf(Interface):
>>> BridgeIf('br0').hello_time
'2'
"""
- return (self._read_sysfs('/sys/class/net/{0}/bridge/hello_time'
+ return (self._read_sysfs('/sys/class/net/{}/bridge/hello_time'
.format(self._ifname)) / 100)
@hello_time.setter
@@ -827,7 +828,7 @@ class BridgeIf(Interface):
>>> from vyos.ifconfig import Interface
>>> BridgeIf('br0').hello_time = 2
"""
- return self._write_sysfs('/sys/class/net/{0}/bridge/hello_time'
+ return self._write_sysfs('/sys/class/net/{}/bridge/hello_time'
.format(self._ifname), (int(time) * 100))
@property
@@ -842,7 +843,7 @@ class BridgeIf(Interface):
'20'
"""
- return (self._read_sysfs('/sys/class/net/{0}/bridge/max_age'
+ return (self._read_sysfs('/sys/class/net/{}/bridge/max_age'
.format(self._ifname)) / 100)
@max_age.setter
@@ -855,7 +856,7 @@ class BridgeIf(Interface):
>>> from vyos.ifconfig import Interface
>>> BridgeIf('br0').max_age = 30
"""
- return self._write_sysfs('/sys/class/net/{0}/bridge/max_age'
+ return self._write_sysfs('/sys/class/net/{}/bridge/max_age'
.format(self._ifname), (int(time) * 100))
@property
@@ -868,7 +869,7 @@ class BridgeIf(Interface):
>>> BridgeIf('br0').priority
'32768'
"""
- return self._read_sysfs('/sys/class/net/{0}/bridge/priority'
+ return self._read_sysfs('/sys/class/net/{}/bridge/priority'
.format(self._ifname))
@priority.setter
@@ -880,7 +881,7 @@ class BridgeIf(Interface):
>>> from vyos.ifconfig import Interface
>>> BridgeIf('br0').priority = 8192
"""
- return self._write_sysfs('/sys/class/net/{0}/bridge/priority'
+ return self._write_sysfs('/sys/class/net/{}/bridge/priority'
.format(self._ifname), priority)
@property
@@ -895,7 +896,7 @@ class BridgeIf(Interface):
"""
state = 0
- with open('/sys/class/net/{0}/bridge/stp_state'.format(self._ifname), 'r') as f:
+ with open('/sys/class/net/{}/bridge/stp_state'.format(self._ifname), 'r') as f:
state = int(f.read().rstrip('\n'))
return state
@@ -911,7 +912,7 @@ class BridgeIf(Interface):
"""
if int(state) >= 0 and int(state) <= 1:
- return self._write_sysfs('/sys/class/net/{0}/bridge/stp_state'
+ return self._write_sysfs('/sys/class/net/{}/bridge/stp_state'
.format(self._ifname), state)
else:
raise ValueError("Value out of range")
@@ -926,7 +927,7 @@ class BridgeIf(Interface):
>>> BridgeIf('br0').multicast_querier
'0'
"""
- return self._read_sysfs('/sys/class/net/{0}/bridge/multicast_querier'
+ return self._read_sysfs('/sys/class/net/{}/bridge/multicast_querier'
.format(self._ifname))
@multicast_querier.setter
@@ -944,7 +945,7 @@ class BridgeIf(Interface):
>>> BridgeIf('br0').multicast_querier = 1
"""
if int(enable) >= 0 and int(enable) <= 1:
- return self._write_sysfs('/sys/class/net/{0}/bridge/multicast_querier'
+ return self._write_sysfs('/sys/class/net/{}/bridge/multicast_querier'
.format(self._ifname), enable)
else:
raise ValueError("Value out of range")
@@ -997,12 +998,40 @@ class BridgeIf(Interface):
.format(self._ifname, interface), priority)
-class EthernetIf(Interface):
-
+class VLANIf(Interface):
+ """
+ This class handels the creation and removal of a VLAN interface. It serves
+ as base class for BondIf and EthernetIf.
+ """
def __init__(self, ifname, type=None):
super().__init__(ifname, type)
- def add_vlan(self, vlan_id, ethertype=''):
+ def remove(self):
+ """
+ Remove interface from operating system. Removing the interface
+ deconfigures all assigned IP addresses and clear possible DHCP(v6)
+ client processes.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> i = Interface('eth0')
+ >>> i.remove()
+ """
+ # do we have sub interfaces (VLANs)?
+ # we apply a regex matching subinterfaces (indicated by a .) of a
+ # parent interface. 'bond0(?:\.\d+){1,2}' will match vif and vif-s/vif-c
+ # subinterfaces
+ vlan_ifs = [f for f in os.listdir(r'/sys/class/net') \
+ if re.match(self._ifname + r'(?:\.\d+){1,2}', f)]
+
+ for vlan in vlan_ifs:
+ Interface(vlan).remove()
+
+ # All subinterfaces are now removed, continue on the physical interface
+ super().remove()
+
+
+ def add_vlan(self, vlan_id, ethertype='', ingress_qos='', egress_qos=''):
"""
A virtual LAN (VLAN) is any broadcast domain that is partitioned and
isolated in a computer network at the data link layer (OSI layer 2).
@@ -1012,8 +1041,20 @@ class EthernetIf(Interface):
This function creates both 802.1q and 802.1ad (Q-in-Q) interfaces. Proto
parameter is used to indicate VLAN type.
- A new object of type EthernetIf is returned once the interface has been
+ A new object of type VLANIf is returned once the interface has been
created.
+
+ @param ethertype: If specified, create 802.1ad or 802.1q Q-in-Q VLAN
+ interface
+ @param ingress_qos: Defines a mapping of VLAN header prio field to the
+ Linux internal packet priority on incoming frames.
+ @param ingress_qos: Defines a mapping of Linux internal packet priority
+ to VLAN header prio field but for outgoing frames.
+
+ Example:
+ >>> from vyos.ifconfig import VLANIf
+ >>> i = VLANIf('eth0')
+ >>> i.add_vlan(10)
"""
vlan_ifname = self._ifname + '.' + str(vlan_id)
if not os.path.exists('/sys/class/net/{}'.format(vlan_ifname)):
@@ -1023,29 +1064,224 @@ class EthernetIf(Interface):
self._ethertype = ethertype
ethertype = 'proto {}'.format(ethertype)
+ # Optional ingress QOS mapping
+ opt_i = ''
+ if ingress_qos:
+ opt_i = 'ingress-qos-map ' + ingress_qos
+ # Optional egress QOS mapping
+ opt_e = ''
+ if egress_qos:
+ opt_e = 'egress-qos-map ' + egress_qos
+
# create interface in the system
- cmd = 'ip link add link {intf} name {intf}.{vlan} type vlan {proto} id {vlan}'.format(
- intf=self._ifname, vlan=self._vlan_id, proto=ethertype)
+ cmd = 'ip link add link {intf} name {intf}.{vlan} type vlan {proto} id {vlan} {opt_e} {opt_i}' \
+ .format(intf=self._ifname, vlan=self._vlan_id, proto=ethertype, opt_e=opt_e, opt_i=opt_i)
self._cmd(cmd)
# return new object mapping to the newly created interface
# we can now work on this object for e.g. IP address setting
# or interface description and so on
- return EthernetIf(vlan_ifname)
+ return VLANIf(vlan_ifname)
+
def del_vlan(self, vlan_id):
"""
Remove VLAN interface from operating system. Removing the interface
deconfigures all assigned IP addresses and clear possible DHCP(v6)
client processes.
+
+ Example:
+ >>> from vyos.ifconfig import VLANIf
+ >>> i = VLANIf('eth0.10')
+ >>> i.del_vlan()
"""
vlan_ifname = self._ifname + '.' + str(vlan_id)
- tmp = EthernetIf(vlan_ifname)
+ tmp = VLANIf(vlan_ifname)
tmp.remove()
-class BondIf(EthernetIf):
+class EthernetIf(VLANIf):
+ """
+ Abstraction of a Linux Ethernet Interface
+ """
+ def __init__(self, ifname):
+ super().__init__(ifname)
+
+ def get_driver_name(self):
+ """
+ Return the driver name used by NIC. Some NICs don't support all
+ features e.g. changing link-speed, duplex
+
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.get_driver_name()
+ 'vmxnet3'
+ """
+ link = os.readlink('/sys/class/net/{}/device/driver/module'.format(self._ifname))
+ return os.path.basename(link)
+
+
+ def has_autoneg(self):
+ """
+ Not all drivers support autonegotiation.
+
+ returns True -> Autonegotiation is supported by driver
+ False -> Autonegotiation is not supported by driver
+
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.has_autoneg()
+ 'True'
+ """
+ regex = 'Supports auto-negotiation:[ ]\w+'
+ tmp = self._cmd('/sbin/ethtool {}'.format(self._ifname))
+ tmp = re.search(regex, tmp.decode())
+
+ # Output is either 'Supports auto-negotiation: Yes' or
+ # 'Supports auto-negotiation: No'
+ if tmp.group().split(':')[1].lstrip() == "Yes":
+ return True
+ else:
+ return False
+
+
+ def set_flow_control(self, enable):
+ """
+ Changes the pause parameters of the specified Ethernet device.
+
+ @param enable: true -> enable pause frames, false -> disable pause frames
+
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_flow_control(True)
+ """
+ if enable not in ['on', 'off']:
+ raise ValueError("Value out of range")
+
+ if self.get_driver_name() in ['vmxnet3', 'virtio_net']:
+ self._debug_msg('{} driver does not support changing flow control settings!'
+ .format(self.get_driver_name()))
+ return
+
+ # Assemble command executed on system. Unfortunately there is no way
+ # to change this setting via sysfs
+ cmd = '/sbin/ethtool --pause {0} autoneg {1} tx {1} rx {1}'.format(
+ self._ifname, enable)
+ try:
+ # An exception will be thrown if the settings are not changed
+ self._cmd(cmd)
+ except CalledProcessError:
+ pass
+
+
+ def set_speed_duplex(self, speed, duplex):
+ """
+ Set link speed in Mbit/s and duplex.
+
+ @speed can be any link speed in MBit/s, e.g. 10, 100, 1000 auto
+ @duplex can be half, full, auto
+
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_speed_duplex('auto', 'auto')
+ """
+
+ if speed not in ['auto', '10', '100', '1000', '2500', '5000', '10000', '25000', '40000', '50000', '100000', '400000']:
+ raise ValueError("Value out of range (speed)")
+
+ if duplex not in ['auto', 'full', 'half']:
+ raise ValueError("Value out of range (duplex)")
+
+ if self.get_driver_name() in ['vmxnet3', 'virtio_net']:
+ self._debug_msg('{} driver does not support changing speed/duplex settings!'
+ .format(self.get_driver_name()))
+ return
+
+
+ cmd = '/sbin/ethtool -s {}'.format(self._ifname)
+ if speed == 'auto' or duplex == 'auto':
+ cmd += ' autoneg on'
+ else:
+ cmd += ' speed {} duplex {} autoneg off'.format(speed, duplex)
+
+ return self._cmd(cmd)
+
+
+ def set_gro(self, state):
+ """
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_gro('on')
+ """
+ if state not in ['on', 'off']:
+ raise ValueError('state must be "on" or "off"')
+
+ cmd = '/sbin/ethtool -K {} gro {}'.format(self._ifname, state)
+ return self._cmd(cmd)
+
+ def set_gso(self, state):
+ """
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_gso('on')
+ """
+ if state not in ['on', 'off']:
+ raise ValueError('state must be "on" or "off"')
+
+ cmd = '/sbin/ethtool -K {} gso {}'.format(self._ifname, state)
+ return self._cmd(cmd)
+
+
+ def set_sg(self, state):
+ """
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_sg('on')
+ """
+ if state not in ['on', 'off']:
+ raise ValueError('state must be "on" or "off"')
+
+ cmd = '/sbin/ethtool -K {} sg {}'.format(self._ifname, state)
+ return self._cmd(cmd)
+
+
+ def set_tso(self, state):
+ """
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_tso('on')
+ """
+ if state not in ['on', 'off']:
+ raise ValueError('state must be "on" or "off"')
+
+ cmd = '/sbin/ethtool -K {} tso {}'.format(self._ifname, state)
+ return self._cmd(cmd)
+
+
+ def set_ufo(self, state):
+ """
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_udp_offload('on')
+ """
+ if state not in ['on', 'off']:
+ raise ValueError('state must be "on" or "off"')
+
+ cmd = '/sbin/ethtool -K {} ufo {}'.format(self._ifname, state)
+ return self._cmd(cmd)
+
+
+class BondIf(VLANIf):
"""
The Linux bonding driver provides a method for aggregating multiple network
interfaces into a single logical "bonded" interface. The behavior of the
@@ -1053,10 +1289,39 @@ class BondIf(EthernetIf):
either hot standby or load balancing services. Additionally, link integrity
monitoring may be performed.
"""
-
def __init__(self, ifname):
super().__init__(ifname, type='bond')
+ def remove(self):
+ """
+ Remove interface from operating system. Removing the interface
+ deconfigures all assigned IP addresses and clear possible DHCP(v6)
+ client processes.
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> i = Interface('eth0')
+ >>> i.remove()
+ """
+ # when a bond member gets deleted, all members are placed in A/D state
+ # even when they are enabled inside CLI. This will make the config
+ # and system look async.
+ slave_list = []
+ for s in self.get_slaves():
+ slave = {
+ 'ifname' : s,
+ 'state': Interface(s).state
+ }
+ slave_list.append(slave)
+
+ # remove bond master which places members in disabled state
+ super().remove()
+
+ # replicate previous interface state before bond destruction back to
+ # physical interface
+ for slave in slave_list:
+ i = Interface(slave['ifname'])
+ i.state = slave['state']
+
@property
def xmit_hash_policy(self):
"""
diff --git a/scripts/import-conf-mode-commands b/scripts/import-conf-mode-commands
new file mode 100755
index 000000000..4bdd5ee03
--- /dev/null
+++ b/scripts/import-conf-mode-commands
@@ -0,0 +1,240 @@
+#!/usr/bin/env python3
+#
+# build-command-template: converts old style commands definitions to XML
+#
+# Copyright (C) 2019 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 os
+import re
+import sys
+
+from lxml import etree
+
+
+# Node types
+NODE = 0
+LEAF_NODE = 1
+TAG_NODE = 2
+
+def parse_command_data(t):
+ regs = {
+ 'help': r'\bhelp:(.*)(?:\n|$)',
+ 'priority': r'\bpriority:(.*)(?:\n|$)',
+ 'type': r'\btype:(.*)(?:\n|$)'
+ }
+
+ data = {'multi': False, 'help': ""}
+
+ for r in regs:
+ try:
+ data[r] = re.search(regs[r], t).group(1).strip()
+ except:
+ data[r] = None
+
+ # val_help is special: there can be multiple instances
+ val_help_strings = re.findall(r'\bval_help:(.*)(?:\n|$)', t)
+ val_help = []
+ for v in val_help_strings:
+ try:
+ fmt, msg = re.match(r'\s*(.*)\s*;\s*(.*)\s*(?:\n|$)', v).groups()
+ except:
+ fmt = "<text>"
+ msg = v
+ val_help.append((fmt, msg))
+ data['val_help'] = val_help
+
+ # multi is on/off
+ if re.match(r'\bmulti:', t):
+ data['multi'] = True
+
+ return(data)
+
+def walk(tree, base_path, name):
+ path = os.path.join(base_path, name)
+
+ contents = os.listdir(path)
+
+ # Determine node type and create XML element for the node
+ # Tag node dirs will always have 'node.tag' subdir and 'node.def' file
+ # Leaf node dirs have nothing but a 'node.def' file
+ # Everything that doesn't match either of these patterns is a normal node
+ if contents == ['node.tag', 'node.def']:
+ print("Creating a tag node from {0}".format(path))
+ elem = etree.Element('tagNode')
+ node_type = TAG_NODE
+ elif contents == ['node.def']:
+ print("Creating a leaf node from {0}".format(path))
+ elem = etree.Element('leafNode')
+ node_type = LEAF_NODE
+ else:
+ print("Creating a node from {0}".format(path))
+ elem = etree.Element('node')
+ node_type = NODE
+
+ # Read and parse the command definition data (the 'node.def' file)
+ with open(os.path.join(path, 'node.def'), 'r') as f:
+ node_def = f.read()
+ data = parse_command_data(node_def)
+
+ # Import the data into the properties element
+ props_elem = etree.Element('properties')
+
+ if data['priority']:
+ # Priority values sometimes come with comments that explain the value choice
+ try:
+ prio, prio_comment = re.match(r'\s*(\d+)\s*#(.*)', data['priority']).groups()
+ except:
+ prio = data['priority'].strip()
+ prio_comment = None
+ prio_elem = etree.Element('priority')
+ prio_elem.text = prio
+ props_elem.append(prio_elem)
+ if prio_comment:
+ prio_comment_elem = etree.Comment(prio_comment)
+ props_elem.append(prio_comment_elem)
+
+ if data['multi']:
+ multi_elem = etree.Element('multi')
+ props_elem.append(multi_elem)
+
+ if data['help']:
+ help_elem = etree.Element('help')
+ help_elem.text = data['help']
+ props_elem.append(help_elem)
+
+ # For leaf nodes, absense of a type: tag means they take no values
+ # For any other nodes, it doesn't mean anything
+ if not data['type'] and (node_type == LEAF_NODE):
+ valueless = etree.Element('valueless')
+ props_elem.append(valueless)
+
+ # There can be only one constraint element in the definition
+ # Create it now, we'll modify it in the next two cases, then append
+ constraint_elem = etree.Element('constraint')
+ has_constraint = False
+
+ if data['val_help']:
+ for vh in data['val_help']:
+ vh_elem = etree.Element('valueHelp')
+
+ vh_fmt_elem = etree.Element('format')
+ # Many commands use special "u32:<start>-<end>" format for ranges
+ if re.match(r'u32:', vh[0]):
+ vh_fmt = re.match(r'u32:(.*)', vh[0]).group(1).strip()
+
+ # If valid range of values is specified in val_help, we can automatically
+ # create a constraint for it
+ # Extracting it from syntax:expression: would be much more complicated
+ vh_validator = etree.Element('validator')
+ vh_validator.set("name", "numeric")
+ vh_validator.set("argument", "--range {0}".format(vh_fmt))
+ constraint_elem.append(vh_validator)
+ has_constraint = True
+ else:
+ vh_fmt = vh[0]
+ vh_fmt_elem.text = vh_fmt
+
+ vh_help_elem = etree.Element('description')
+ vh_help_elem.text = vh[1]
+
+ vh_elem.append(vh_fmt_elem)
+ vh_elem.append(vh_help_elem)
+ props_elem.append(vh_elem)
+
+ # Translate the "type:" to the new validator system
+ if data['type']:
+ t = data['type']
+ if t == 'txt':
+ # Can't infer anything from the generic "txt" type
+ pass
+ else:
+ validator = etree.Element('validator')
+ if t == 'u32':
+ validator.set('name', 'numeric')
+ validator.set('argument', '--non-negative')
+ elif t == 'ipv4':
+ validator.set('name', 'ipv4-address')
+ elif t == 'ipv4net':
+ validator.set('name', 'ipv4-prefix')
+ elif t == 'ipv6':
+ validator.set('name', 'ipv6-address')
+ elif t == 'ipv6net':
+ validator.set('name', 'ipv6-prefix')
+ elif t == 'macaddr':
+ validator.set('name', 'mac-address')
+ else:
+ print("Warning: unsupported type \'{0}\'".format(t))
+ validator = None
+
+ if (validator is not None) and (not has_constraint):
+ # If has_constraint is true, it means a more specific validator
+ # was already extracted from another option
+ constraint_elem.append(validator)
+ has_constraint = True
+
+ if has_constraint:
+ props_elem.append(constraint_elem)
+
+ elem.append(props_elem)
+
+ elem.set("name", name)
+
+ if node_type != LEAF_NODE:
+ children = etree.Element('children')
+
+ # Create the next level dir path,
+ # accounting for the "virtual" node.tag subdir for tag nodes
+ next_level = path
+ if node_type == TAG_NODE:
+ next_level = os.path.join(path, 'node.tag')
+
+ # Walk the subdirs of the next level
+ for d in os.listdir(next_level):
+ dp = os.path.join(next_level, d)
+ if os.path.isdir(dp):
+ walk(children, next_level, d)
+
+ elem.append(children)
+
+ tree.append(elem)
+
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ print("Usage: {0} <base path>".format(sys.argv[0]))
+ sys.exit(1)
+ else:
+ base_path = sys.argv[1]
+
+ root = etree.Element('interfaceDefinition')
+ contents = os.listdir(base_path)
+ elem = etree.Element('node')
+ elem.set('name', os.path.basename(base_path))
+ children = etree.Element('children')
+
+ for c in contents:
+ path = os.path.join(base_path, c)
+ if os.path.isdir(path):
+ walk(children, base_path, c)
+
+ elem.append(children)
+ root.append(elem)
+
+ xml_data = etree.tostring(root, pretty_print=True).decode()
+ with open('output.xml', 'w') as f:
+ f.write(xml_data)
diff --git a/src/conf_mode/interface-bonding.py b/src/conf_mode/interface-bonding.py
index f0a33beff..9049913e6 100755
--- a/src/conf_mode/interface-bonding.py
+++ b/src/conf_mode/interface-bonding.py
@@ -22,7 +22,7 @@ from copy import deepcopy
from sys import exit
from netifaces import interfaces
-from vyos.ifconfig import BondIf, EthernetIf
+from vyos.ifconfig import BondIf, VLANIf
from vyos.configdict import list_diff, vlan_to_dict
from vyos.config import Config
from vyos import ConfigError
@@ -82,7 +82,7 @@ def apply_vlan_config(vlan, config):
to a VLAN interface
"""
- if type(vlan) != type(EthernetIf("lo")):
+ if type(vlan) != type(VLANIf("lo")):
raise TypeError()
# update interface description used e.g. within SNMP
@@ -279,11 +279,6 @@ def verify(bond):
raise ConfigError('can not enslave interface {} which already ' \
'belongs to {}'.format(intf, tmp))
- # we can not add disabled slave interfaces to our bond
- if conf.exists('interfaces ethernet ' + intf + ' disable'):
- raise ConfigError('can not enslave disabled interface {}' \
- .format(intf))
-
# can not add interfaces with an assigned address to a bond
if conf.exists('interfaces ethernet ' + intf + ' address'):
raise ConfigError('can not enslave interface {} which has an address ' \
@@ -339,8 +334,7 @@ def apply(bond):
b = BondIf(bond['intf'])
if bond['deleted']:
- #
- # delete bonding interface
+ # delete interface
b.remove()
else:
# Some parameters can not be changed when the bond is up.
diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py
index 401182a0d..62589c798 100755
--- a/src/conf_mode/interface-bridge.py
+++ b/src/conf_mode/interface-bridge.py
@@ -183,8 +183,7 @@ def apply(bridge):
br = BridgeIf(bridge['intf'])
if bridge['deleted']:
- # delete bridge interface
- # DHCP is stopped inside remove()
+ # delete interface
br.remove()
else:
# enable interface
diff --git a/src/conf_mode/interface-ethernet.py b/src/conf_mode/interface-ethernet.py
new file mode 100755
index 000000000..f82105847
--- /dev/null
+++ b/src/conf_mode/interface-ethernet.py
@@ -0,0 +1,382 @@
+#!/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
+
+from copy import deepcopy
+from sys import exit
+
+from vyos.ifconfig import EthernetIf, VLANIf
+from vyos.configdict import list_diff, vlan_to_dict
+from vyos.config import Config
+from vyos import ConfigError
+
+default_config_data = {
+ 'address': [],
+ 'address_remove': [],
+ 'description': '',
+ 'deleted': False,
+ 'dhcp_client_id': '',
+ 'dhcp_hostname': '',
+ 'dhcpv6_prm_only': False,
+ 'dhcpv6_temporary': False,
+ 'disable': False,
+ 'disable_link_detect': 1,
+ 'duplex': 'auto',
+ 'flow_control': 'on',
+ 'hw_id': '',
+ 'ip_arp_cache_tmo': 30,
+ 'ip_proxy_arp': 0,
+ 'ip_proxy_arp_pvlan': 0,
+ 'intf': '',
+ 'mac': '',
+ 'mtu': 1500,
+ 'offload_gro': 'off',
+ 'offload_gso': 'off',
+ 'offload_sg': 'off',
+ 'offload_tso': 'off',
+ 'offload_ufo': 'off',
+ 'speed': 'auto',
+ 'vif_s': [],
+ 'vif_s_remove': [],
+ 'vif': [],
+ 'vif_remove': []
+}
+
+
+def apply_vlan_config(vlan, config):
+ """
+ Generic function to apply a VLAN configuration from a dictionary
+ to a VLAN interface
+ """
+
+ if type(vlan) != type(VLANIf("lo")):
+ raise TypeError()
+
+ # update interface description used e.g. within SNMP
+ vlan.ifalias = config['description']
+ # ignore link state changes
+ vlan.link_detect = config['disable_link_detect']
+ # Maximum Transmission Unit (MTU)
+ vlan.mtu = config['mtu']
+ # Change VLAN interface MAC address
+ if config['mac']:
+ vlan.mac = config['mac']
+
+ # enable/disable VLAN interface
+ if config['disable']:
+ vlan.state = 'down'
+ else:
+ vlan.state = 'up'
+
+ # Configure interface address(es)
+ # - not longer required addresses get removed first
+ # - newly addresses will be added second
+ for addr in config['address_remove']:
+ vlan.del_addr(addr)
+ for addr in config['address']:
+ vlan.add_addr(addr)
+
+
+def get_config():
+ eth = deepcopy(default_config_data)
+ conf = Config()
+
+ # determine tagNode instance
+ try:
+ eth['intf'] = os.environ['VYOS_TAGNODE_VALUE']
+ except KeyError as E:
+ print("Interface not specified")
+
+ # check if ethernet interface has been removed
+ cfg_base = 'interfaces ethernet ' + eth['intf']
+ if not conf.exists(cfg_base):
+ eth['deleted'] = True
+ # we can not bail out early as ethernet interface can not be removed
+ # Kernel will complain with: RTNETLINK answers: Operation not supported.
+ # Thus we need to remove individual settings
+ return eth
+
+ # set new configuration level
+ conf.set_level(cfg_base)
+
+ # retrieve configured interface addresses
+ if conf.exists('address'):
+ eth['address'] = conf.return_values('address')
+
+ # get interface addresses (currently effective) - to determine which
+ # address is no longer valid and needs to be removed
+ eff_addr = conf.return_effective_values('address')
+ eth['address_remove'] = list_diff(eff_addr, eth['address'])
+
+ # retrieve interface description
+ if conf.exists('description'):
+ eth['description'] = conf.return_value('description')
+
+ # get DHCP client identifier
+ if conf.exists('dhcp-options client-id'):
+ eth['dhcp_client_id'] = conf.return_value('dhcp-options client-id')
+
+ # DHCP client host name (overrides the system host name)
+ if conf.exists('dhcp-options host-name'):
+ eth['dhcp_hostname'] = conf.return_value('dhcp-options host-name')
+
+ # DHCPv6 only acquire config parameters, no address
+ if conf.exists('dhcpv6-options parameters-only'):
+ eth['dhcpv6_prm_only'] = conf.return_value('dhcpv6-options parameters-only')
+
+ # DHCPv6 temporary IPv6 address
+ if conf.exists('dhcpv6-options temporary'):
+ eth['dhcpv6_temporary'] = conf.return_value('dhcpv6-options temporary')
+
+ # ignore link state changes
+ if conf.exists('disable-link-detect'):
+ eth['disable_link_detect'] = 2
+
+ # disable ethernet flow control (pause frames)
+ if conf.exists('disable-flow-control'):
+ eth['flow_control'] = 'off'
+
+ # retrieve real hardware address
+ if conf.exists('hw-id'):
+ eth['hw_id'] = conf.return_value('hw-id')
+
+ # disable interface
+ if conf.exists('disable'):
+ eth['disable'] = True
+
+ # interface duplex
+ if conf.exists('duplex'):
+ eth['duplex'] = conf.return_value('duplex')
+
+ # ARP cache entry timeout in seconds
+ if conf.exists('ip arp-cache-timeout'):
+ eth['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout'))
+
+ # Enable proxy-arp on this interface
+ if conf.exists('ip enable-proxy-arp'):
+ eth['ip_proxy_arp'] = 1
+
+ # Enable private VLAN proxy ARP on this interface
+ if conf.exists('ip proxy-arp-pvlan'):
+ eth['ip_proxy_arp_pvlan'] = 1
+
+ # Media Access Control (MAC) address
+ if conf.exists('mac'):
+ eth['mac'] = conf.return_value('mac')
+
+ # Maximum Transmission Unit (MTU)
+ if conf.exists('mtu'):
+ eth['mtu'] = int(conf.return_value('mtu'))
+
+ # GRO (generic receive offload)
+ if conf.exists('offload-options generic-receive'):
+ eth['offload_gro'] = conf.return_value('offload-options generic-receive')
+
+ # GSO (generic segmentation offload)
+ if conf.exists('offload-options generic-segmentation'):
+ eth['offload_gso'] = conf.return_value('offload-options generic-segmentation')
+
+ # scatter-gather option
+ if conf.exists('offload-options scatter-gather'):
+ eth['offload_sg'] = conf.return_value('offload-options scatter-gather')
+
+ # TSO (TCP segmentation offloading)
+ if conf.exists('offload-options tcp-segmentation'):
+ eth['offload_tso'] = conf.return_value('offload-options tcp-segmentation')
+
+ # UDP fragmentation offloading
+ if conf.exists('offload-options udp-fragmentation'):
+ eth['offload_ufo'] = conf.return_value('offload-options udp-fragmentation')
+
+ # interface speed
+ if conf.exists('speed'):
+ eth['speed'] = conf.return_value('speed')
+
+ # re-set configuration level and retrieve vif-s interfaces
+ conf.set_level(cfg_base)
+ # get vif-s interfaces (currently effective) - to determine which vif-s
+ # interface is no longer present and needs to be removed
+ eff_intf = conf.list_effective_nodes('vif-s')
+ act_intf = conf.list_nodes('vif-s')
+ eth['vif_s_remove'] = list_diff(eff_intf, act_intf)
+
+ if conf.exists('vif-s'):
+ for vif_s in conf.list_nodes('vif-s'):
+ # set config level to vif-s interface
+ conf.set_level(cfg_base + ' vif-s ' + vif_s)
+ eth['vif_s'].append(vlan_to_dict(conf))
+
+ # re-set configuration level and retrieve vif-s interfaces
+ conf.set_level(cfg_base)
+ # Determine vif interfaces (currently effective) - to determine which
+ # vif interface is no longer present and needs to be removed
+ eff_intf = conf.list_effective_nodes('vif')
+ act_intf = conf.list_nodes('vif')
+ eth['vif_remove'] = list_diff(eff_intf, act_intf)
+
+ if conf.exists('vif'):
+ for vif in conf.list_nodes('vif'):
+ # set config level to vif interface
+ conf.set_level(cfg_base + ' vif ' + vif)
+ eth['vif'].append(vlan_to_dict(conf))
+
+ return eth
+
+
+def verify(eth):
+ if eth['deleted']:
+ return None
+
+ if eth['speed'] == 'auto':
+ if eth['duplex'] != 'auto':
+ raise ConfigError('If speed is hardcoded, duplex must be hardcoded, too')
+
+ if eth['duplex'] == 'auto':
+ if eth['speed'] != 'auto':
+ raise ConfigError('If duplex is hardcoded, speed must be hardcoded, too')
+
+ conf = Config()
+ # some options can not be changed when interface is enslaved to a bond
+ for bond in conf.list_nodes('interfaces bonding'):
+ if conf.exists('interfaces bonding ' + bond + ' member interface'):
+ bond_member = conf.return_values('interfaces bonding ' + bond + ' member interface')
+ if eth['name'] in bond_member:
+ if eth['address']:
+ raise ConfigError('Can not assign address to interface {} which is a member of {}').format(eth['intf'], bond)
+
+
+ return None
+
+def generate(eth):
+ return None
+
+def apply(eth):
+ e = EthernetIf(eth['intf'])
+ if eth['deleted']:
+ # delete interface
+ e.remove()
+ else:
+ # update interface description used e.g. within SNMP
+ e.ifalias = eth['description']
+
+ #
+ # missing DHCP/DHCPv6 options go here
+ #
+
+ # ignore link state changes
+ e.link_detect = eth['disable_link_detect']
+ # disable ethernet flow control (pause frames)
+ e.set_flow_control(eth['flow_control'])
+ # configure ARP cache timeout in milliseconds
+ e.arp_cache_tmo = eth['ip_arp_cache_tmo']
+ # Enable proxy-arp on this interface
+ e.proxy_arp = eth['ip_proxy_arp']
+ # Enable private VLAN proxy ARP on this interface
+ e.proxy_arp_pvlan = eth['ip_proxy_arp_pvlan']
+
+ # Change interface MAC address - re-set to real hardware address (hw-id)
+ # if custom mac is removed
+ if eth['mac']:
+ e.mac = eth['mac']
+ else:
+ e.mac = eth['hw_id']
+
+ # Maximum Transmission Unit (MTU)
+ e.mtu = eth['mtu']
+
+ # GRO (generic receive offload)
+ e.set_gro(eth['offload_gro'])
+
+ # GSO (generic segmentation offload)
+ e.set_gso(eth['offload_gso'])
+
+ # scatter-gather option
+ e.set_sg(eth['offload_sg'])
+
+ # TSO (TCP segmentation offloading)
+ e.set_tso(eth['offload_tso'])
+
+ # UDP fragmentation offloading
+ e.set_ufo(eth['offload_ufo'])
+
+ # Set physical interface speed and duplex
+ e.set_speed_duplex(eth['speed'], eth['duplex'])
+
+ # Configure interface address(es)
+ # - not longer required addresses get removed first
+ # - newly addresses will be added second
+ for addr in eth['address_remove']:
+ e.del_addr(addr)
+ for addr in eth['address']:
+ e.add_addr(addr)
+
+ # Enable/Disable interface
+ if eth['disable']:
+ e.state = 'down'
+ else:
+ e.state = 'up'
+
+ # remove no longer required service VLAN interfaces (vif-s)
+ for vif_s in eth['vif_s_remove']:
+ e.del_vlan(vif_s)
+
+ # create service VLAN interfaces (vif-s)
+ for vif_s in eth['vif_s']:
+ s_vlan = e.add_vlan(vif_s['id'], ethertype=vif_s['ethertype'])
+ apply_vlan_config(s_vlan, vif_s)
+
+ # remove no longer required client VLAN interfaces (vif-c)
+ # on lower service VLAN interface
+ for vif_c in vif_s['vif_c_remove']:
+ s_vlan.del_vlan(vif_c)
+
+ # create client VLAN interfaces (vif-c)
+ # on lower service VLAN interface
+ for vif_c in vif_s['vif_c']:
+ c_vlan = s_vlan.add_vlan(vif_c['id'])
+ apply_vlan_config(c_vlan, vif_c)
+
+ # remove no longer required VLAN interfaces (vif)
+ for vif in eth['vif_remove']:
+ e.del_vlan(vif)
+
+ # create VLAN interfaces (vif)
+ for vif in eth['vif']:
+ # QoS priority mapping can only be set during interface creation
+ # so we delete the interface first if required.
+ if vif['egress_qos_changed'] or vif['ingress_qos_changed']:
+ try:
+ # on system bootup the above condition is true but the interface
+ # does not exists, which throws an exception, but that's legal
+ e.del_vlan(vif['id'])
+ except:
+ pass
+
+ vlan = e.add_vlan(vif['id'], ingress_qos=vif['ingress_qos'], egress_qos=vif['egress_qos'])
+ apply_vlan_config(vlan, vif)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/interface-openvpn.py b/src/conf_mode/interface-openvpn.py
index 34c094862..35e7928c2 100755
--- a/src/conf_mode/interface-openvpn.py
+++ b/src/conf_mode/interface-openvpn.py
@@ -167,10 +167,18 @@ key {{ tls_key }}
crl-verify {{ tls_crl }}
{% endif %}
+{%- if tls_version_min %}
+tls-version-min {{tls_version_min}}
+{% endif %}
+
{%- if tls_dh %}
dh {{ tls_dh }}
{% endif %}
+{%- if tls_auth %}
+tls-auth {{tls_auth}}
+{% endif %}
+
{%- if 'active' in tls_role %}
tls-client
{%- elif 'passive' in tls_role %}
@@ -277,12 +285,14 @@ default_config_data = {
'server_topology': '',
'shared_secret_file': '',
'tls': False,
+ 'tls_auth': '',
'tls_ca_cert': '',
'tls_cert': '',
'tls_crl': '',
'tls_dh': '',
'tls_key': '',
'tls_role': '',
+ 'tls_version_min': '',
'type': 'tun',
'uid': user,
'gid': group,
@@ -532,6 +542,11 @@ def get_config():
if conf.exists('server reject-unconfigured-clients'):
openvpn['server_reject_unconfigured'] = True
+ # File containing TLS auth static key
+ if conf.exists('tls auth-file'):
+ openvpn['tls_auth'] = conf.return_value('tls auth-file')
+ openvpn['tls'] = True
+
# File containing certificate for Certificate Authority (CA)
if conf.exists('tls ca-cert-file'):
openvpn['tls_ca_cert'] = conf.return_value('tls ca-cert-file')
@@ -562,6 +577,10 @@ def get_config():
openvpn['tls_role'] = conf.return_value('tls role')
openvpn['tls'] = True
+ # Minimum required TLS version
+ if conf.exists('tls tls-version-min'):
+ openvpn['tls_version_min'] = conf.return_value('tls tls-version-min')
+
if conf.exists('shared-secret-key-file'):
openvpn['shared_secret_file'] = conf.return_value('shared-secret-key-file')
@@ -714,11 +733,17 @@ def verify(openvpn):
if not checkCertHeader('-----BEGIN CERTIFICATE-----', openvpn['tls_ca_cert']):
raise ConfigError('Specified ca-cert-file "{}" is invalid'.format(openvpn['tls_ca_cert']))
- if not checkCertHeader('-----BEGIN CERTIFICATE-----', openvpn['tls_cert']):
- raise ConfigError('Specified cert-file "{}" is invalid'.format(openvpn['tls_cert']))
+ if openvpn['tls_auth']:
+ if not checkCertHeader('-----BEGIN OpenVPN Static key V1-----', openvpn['tls_auth']):
+ raise ConfigError('Specified auth-file "{}" is invalid'.format(openvpn['tls_auth']))
+
+ if openvpn['tls_cert']:
+ if not checkCertHeader('-----BEGIN CERTIFICATE-----', openvpn['tls_cert']):
+ raise ConfigError('Specified cert-file "{}" is invalid'.format(openvpn['tls_cert']))
- if not checkCertHeader('-----BEGIN (?:RSA )?PRIVATE KEY-----', openvpn['tls_key']):
- raise ConfigError('Specified key-file "{}" is not valid'.format(openvpn['tls_key']))
+ if openvpn['tls_key']:
+ if not checkCertHeader('-----BEGIN (?:RSA )?PRIVATE KEY-----', openvpn['tls_key']):
+ raise ConfigError('Specified key-file "{}" is not valid'.format(openvpn['tls_key']))
if openvpn['tls_crl']:
if not checkCertHeader('-----BEGIN X509 CRL-----', openvpn['tls_crl']):
@@ -730,7 +755,8 @@ def verify(openvpn):
if openvpn['tls_role']:
if openvpn['mode'] in ['client', 'server']:
- raise ConfigError('Cannot specify "tls role" in client-server mode')
+ if not openvpn['tls_auth']:
+ raise ConfigError('Cannot specify "tls role" in client-server mode')
if openvpn['tls_role'] == 'active':
if openvpn['protocol'] == 'tcp-passive':
diff --git a/src/conf_mode/interface-vxlan.py b/src/conf_mode/interface-vxlan.py
index 59022238e..e97b4bf99 100755
--- a/src/conf_mode/interface-vxlan.py
+++ b/src/conf_mode/interface-vxlan.py
@@ -28,7 +28,6 @@ from netifaces import interfaces
default_config_data = {
'address': [],
- 'address_remove': [],
'deleted': False,
'description': '',
'disable': False,
@@ -43,7 +42,6 @@ default_config_data = {
# the IANA's selection of a standard destination port
}
-
def get_config():
vxlan = deepcopy(default_config_data)
conf = Config()
@@ -66,12 +64,6 @@ def get_config():
if conf.exists('address'):
vxlan['address'] = conf.return_values('address')
- # Determine interface addresses (currently effective) - to determine which
- # address is no longer valid and needs to be removed from the interface
- eff_addr = conf.return_effective_values('address')
- act_addr = conf.return_values('address')
- vxlan['address_remove'] = list_diff(eff_addr, act_addr)
-
# retrieve interface description
if conf.exists('description'):
vxlan['description'] = conf.return_value('description')
@@ -180,11 +172,9 @@ def apply(vxlan):
# Enable proxy-arp on this interface
v.proxy_arp = vxlan['ip_proxy_arp']
- # Configure interface address(es)
- # - not longer required addresses get removed first
- # - newly addresses will be added second
- for addr in vxlan['address_remove']:
- v.del_addr(addr)
+ # Configure interface address(es) - no need to implicitly delete the
+ # old addresses as they have already been removed by deleting the
+ # interface above
for addr in vxlan['address']:
v.add_addr(addr)
diff --git a/src/conf_mode/interface-wireguard.py b/src/conf_mode/interface-wireguard.py
index d51a7a08d..4ae3251fe 100755
--- a/src/conf_mode/interface-wireguard.py
+++ b/src/conf_mode/interface-wireguard.py
@@ -26,12 +26,16 @@ from vyos.config import Config
from vyos import ConfigError
from vyos.ifconfig import WireGuardIf
-ifname = str(os.environ['VYOS_TAGNODE_VALUE'])
-intfc = WireGuardIf(ifname)
+try:
+ ifname = str(os.environ['VYOS_TAGNODE_VALUE'])
+ intfc = WireGuardIf(ifname)
+except KeyError:
+ print("Interface not specified")
+ sys.exit(1)
kdir = r'/config/auth/wireguard'
-def check_kmod():
+def _check_kmod():
if not os.path.exists('/sys/module/wireguard'):
sl.syslog(sl.LOG_NOTICE, "loading wirguard kmod")
if os.system('sudo modprobe wireguard') != 0:
@@ -39,6 +43,19 @@ def check_kmod():
raise ConfigError("modprobe wireguard failed")
+def _migrate_default_keys():
+ if os.path.exists('{}/private.key'.format(kdir)) and not os.path.exists('{}/default/private.key'.format(kdir)):
+ sl.syslog(sl.LOG_NOTICE, "migrate keypair to default")
+ old_umask = os.umask(0o027)
+ location = '{}/default'.format(kdir)
+ subprocess.call(['sudo mkdir -p ' + location], shell=True)
+ subprocess.call(['sudo chgrp vyattacfg ' + location], shell=True)
+ subprocess.call(['sudo chmod 750 ' + location], shell=True)
+ os.rename('{}/private.key'.format(kdir),'{}/private.key'.format(location))
+ os.rename('{}/public.key'.format(kdir),'{}/public.key'.format(location))
+ os.umask(old_umask)
+
+
def get_config():
c = Config()
if not c.exists('interfaces wireguard'):
@@ -257,7 +274,8 @@ def apply(c):
if __name__ == '__main__':
try:
- check_kmod()
+ _check_kmod()
+ _migrate_default_keys()
c = get_config()
verify(c)
apply(c)
diff --git a/src/conf_mode/ipoe_server.py b/src/conf_mode/ipoe_server.py
index ca6b423e5..a60379760 100755
--- a/src/conf_mode/ipoe_server.py
+++ b/src/conf_mode/ipoe_server.py
@@ -41,7 +41,6 @@ ipoe_config = '''
### generated by ipoe.py ###
[modules]
log_syslog
-ippool
ipoe
shaper
ipv6pool
@@ -50,6 +49,7 @@ ipv6_dhcp
{% if auth['mech'] == 'radius' %}
radius
{% endif -%}
+ippool
{% if auth['mech'] == 'local' %}
chap-secrets
{% endif %}
@@ -65,7 +65,11 @@ level=5
[ipoe]
verbose=1
{% for intfc in interfaces %}
+{% if interfaces[intfc]['vlan_mon'] %}
+interface=re:{{intfc}}\.\d+,\
+{% else %}
interface={{intfc}},\
+{% endif %}
shared={{interfaces[intfc]['shared']}},\
mode={{interfaces[intfc]['mode']}},\
ifcfg={{interfaces[intfc]['ifcfg']}},\
@@ -83,8 +87,7 @@ password=csid
{%- for intfc in interfaces %}
{% if (interfaces[intfc]['shared'] == '0') and (interfaces[intfc]['vlan_mon']) %}
-vlan_mon={{interfaces[intfc]['vlan_mon']|join(',')}}
-interface=re:{{intfc}}\.(409[0-6]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{0,2})
+vlan-mon={{intfc}},{{interfaces[intfc]['vlan_mon']|join(',')}}
{% endif %}
{% endfor %}
@@ -160,16 +163,24 @@ nas-identifier={{auth['radsettings']['nas-identifier']}}
tcp=127.0.0.1:2002
'''
-### pppoe chap secrets
+### chap secrets
chap_secrets_conf = '''
# username server password acceptable local IP addresses shaper
{% for aifc in auth['auth_if'] %}
{% for mac in auth['auth_if'][aifc] %}
{% if (auth['auth_if'][aifc][mac]['up']) and (auth['auth_if'][aifc][mac]['down']) %}
+{% if auth['auth_if'][aifc][mac]['vlan'] %}
+{{aifc}}.{{auth['auth_if'][aifc][mac]['vlan']}}\t*\t{{mac.lower()}}\t*\t{{auth['auth_if'][aifc][mac]['down']}}/{{auth['auth_if'][aifc][mac]['up']}}
+{% else %}
{{aifc}}\t*\t{{mac.lower()}}\t*\t{{auth['auth_if'][aifc][mac]['down']}}/{{auth['auth_if'][aifc][mac]['up']}}
+{% endif %}
+{% else %}
+{% if auth['auth_if'][aifc][mac]['vlan'] %}
+{{aifc}}.{{auth['auth_if'][aifc][mac]['vlan']}}\t*\t{{mac.lower()}}\t*
{% else %}
{{aifc}}\t*\t{{mac.lower()}}\t*
{% endif %}
+{% endif %}
{% endfor %}
{% endfor %}
'''
@@ -213,6 +224,7 @@ def accel_cmd(cmd=''):
### chap_secrets file if auth mode local
def gen_chap_secrets(c):
+
tmpl = jinja2.Template(chap_secrets_conf, trim_blocks=True)
chap_secrets_txt = tmpl.render(c)
old_umask = os.umask(0o077)
@@ -296,6 +308,9 @@ def get_config():
config_data['auth']['auth_if'][auth_int][mac] = {}
config_data['auth']['auth_if'][auth_int][mac]['up'] = None
config_data['auth']['auth_if'][auth_int][mac]['down'] = None
+ ## client vlan-id
+ if c.exists('authentication interface ' + auth_int + ' mac-address ' + mac + ' vlan-id'):
+ config_data['auth']['auth_if'][auth_int][mac]['vlan'] = c.return_value('authentication interface ' + auth_int + ' mac-address ' + mac + ' vlan-id')
if c.exists('authentication mode radius'):
for rsrv in c.list_nodes('authentication radius-server'):
config_data['auth']['radius'][rsrv] = {}
diff --git a/src/helpers/vyos-boot-config-loader.py b/src/helpers/vyos-boot-config-loader.py
index 06c95765f..7c81a4c3c 100755
--- a/src/helpers/vyos-boot-config-loader.py
+++ b/src/helpers/vyos-boot-config-loader.py
@@ -18,41 +18,72 @@
import os
import sys
+import pwd
+import grp
import subprocess
import traceback
+from datetime import datetime
+from vyos.defaults import directories
from vyos.configsession import ConfigSession, ConfigSessionError
from vyos.configtree import ConfigTree
STATUS_FILE = '/tmp/vyos-config-status'
TRACE_FILE = '/tmp/boot-config-trace'
-session = ConfigSession(os.getpid(), 'vyos-boot-config-loader')
-env = session.get_session_env()
+CFG_GROUP = 'vyattacfg'
-default_file_name = env['vyatta_sysconfdir'] + '/config.boot.default'
-
-if len(sys.argv) < 1:
- print("Must be called with argument.")
- sys.exit(1)
+if 'log' in directories:
+ LOG_DIR = directories['log']
else:
- file_name = sys.argv[1]
+ LOG_DIR = '/var/log/vyatta'
+
+LOG_FILE = LOG_DIR + '/vyos-boot-config-loader.log'
+
+try:
+ with open('/proc/cmdline', 'r') as f:
+ cmdline = f.read()
+ if 'vyos-debug' in cmdline:
+ os.environ['VYOS_DEBUG'] = 'yes'
+except Exception as e:
+ print('{0}'.format(e))
def write_config_status(status):
- with open(STATUS_FILE, 'w') as f:
- f.write('{0}\n'.format(status))
+ try:
+ with open(STATUS_FILE, 'w') as f:
+ f.write('{0}\n'.format(status))
+ except Exception as e:
+ print('{0}'.format(e))
def trace_to_file(trace_file_name):
- with open(trace_file_name, 'w') as trace_file:
- traceback.print_exc(file=trace_file)
+ try:
+ with open(trace_file_name, 'w') as trace_file:
+ traceback.print_exc(file=trace_file)
+ except Exception as e:
+ print('{0}'.format(e))
+
+def failsafe(config_file_name):
+ fail_msg = """
+ !!!!!
+ There were errors loading the configuration
+ Please examine the errors in
+ {0}
+ and correct
+ !!!!!
+ """.format(TRACE_FILE)
+
+ print(fail_msg, file=sys.stderr)
+
+ users = [x[0] for x in pwd.getpwall()]
+ if 'vyos' in users:
+ return
-def failsafe():
try:
- with open(default_file_name, 'r') as f:
+ with open(config_file_name, 'r') as f:
config_file = f.read()
except Exception as e:
print("Catastrophic: no default config file "
- "'{0}'".format(default_file_name))
+ "'{0}'".format(config_file_name))
sys.exit(1)
config = ConfigTree(config_file)
@@ -73,29 +104,73 @@ def failsafe():
except subprocess.CalledProcessError as e:
sys.exit("{0}".format(e))
- with open('/etc/motd', 'a+') as f:
- f.write('\n\n')
- f.write('!!!!!\n')
- f.write('There were errors loading the initial configuration;\n')
- f.write('please examine the errors in {0} and correct.'
- '\n'.format(TRACE_FILE))
- f.write('!!!!!\n\n')
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ print("Must specify boot config file.")
+ sys.exit(1)
+ else:
+ file_name = sys.argv[1]
-try:
- with open(file_name, 'r') as f:
- config_file = f.read()
-except Exception as e:
- write_config_status(1)
- failsafe()
- trace_to_file(TRACE_FILE)
- sys.exit("{0}".format(e))
+ # Set user and group options, so that others will be able to commit
+ # Currently, the only caller does 'sg CFG_GROUP', but that may change
+ cfg_group = grp.getgrnam(CFG_GROUP)
+ os.setgid(cfg_group.gr_gid)
-try:
- session.load_config(file_name)
- session.commit()
- write_config_status(0)
-except ConfigSessionError as e:
- write_config_status(1)
- failsafe()
- trace_to_file(TRACE_FILE)
- sys.exit(1)
+ # Need to set file permissions to 775 so that every vyattacfg group
+ # member has write access to the running config
+ os.umask(0o002)
+
+ session = ConfigSession(os.getpid(), 'vyos-boot-config-loader')
+ env = session.get_session_env()
+
+ default_file_name = env['vyatta_sysconfdir'] + '/config.boot.default'
+
+ try:
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+ except Exception:
+ write_config_status(1)
+ failsafe(default_file_name)
+ trace_to_file(TRACE_FILE)
+ sys.exit(1)
+
+ try:
+ time_begin_load = datetime.now()
+ load_out = session.load_config(file_name)
+ time_end_load = datetime.now()
+ time_begin_commit = datetime.now()
+ commit_out = session.commit()
+ time_end_commit = datetime.now()
+ write_config_status(0)
+ except ConfigSessionError:
+ # If here, there is no use doing session.discard, as we have no
+ # recoverable config environment, and will only throw an error
+ write_config_status(1)
+ failsafe(default_file_name)
+ trace_to_file(TRACE_FILE)
+ sys.exit(1)
+
+ time_elapsed_load = time_end_load - time_begin_load
+ time_elapsed_commit = time_end_commit - time_begin_commit
+
+ try:
+ if not os.path.exists(LOG_DIR):
+ os.mkdir(LOG_DIR)
+ with open(LOG_FILE, 'a') as f:
+ f.write('\n\n')
+ f.write('{0} Begin config load\n'
+ ''.format(time_begin_load))
+ f.write(load_out)
+ f.write('{0} End config load\n'
+ ''.format(time_end_load))
+ f.write('Elapsed time for config load: {0}\n'
+ ''.format(time_elapsed_load))
+ f.write('{0} Begin config commit\n'
+ ''.format(time_begin_commit))
+ f.write(commit_out)
+ f.write('{0} End config commit\n'
+ ''.format(time_end_commit))
+ f.write('Elapsed time for config commit: {0}\n'
+ ''.format(time_elapsed_commit))
+ except Exception as e:
+ print('{0}'.format(e))
diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd
index 8f70eb4e9..e7ecd8573 100755
--- a/src/services/vyos-hostsd
+++ b/src/services/vyos-hostsd
@@ -43,7 +43,7 @@ hosts_tmpl_source = """
# Local host
127.0.0.1 localhost
-127.0.1.1 {{ host_name }}{% if domain_name %}.{{ domain_name }}{% endif %}
+127.0.1.1 {{ host_name }}{% if domain_name %}.{{ domain_name }} {{ host_name }}{% endif %}
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback