diff options
-rw-r--r-- | Jenkinsfile | 263 | ||||
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | interface-definitions/interfaces-ethernet.xml | 872 | ||||
-rw-r--r-- | interface-definitions/interfaces-openvpn.xml | 70 | ||||
-rw-r--r-- | interface-definitions/ipoe-server.xml | 9 | ||||
-rw-r--r-- | python/vyos/configdict.py | 22 | ||||
-rw-r--r-- | python/vyos/configsession.py | 6 | ||||
-rw-r--r-- | python/vyos/defaults.py | 1 | ||||
-rw-r--r-- | python/vyos/ifconfig.py | 407 | ||||
-rwxr-xr-x | scripts/import-conf-mode-commands | 240 | ||||
-rwxr-xr-x | src/conf_mode/interface-bonding.py | 12 | ||||
-rwxr-xr-x | src/conf_mode/interface-bridge.py | 3 | ||||
-rwxr-xr-x | src/conf_mode/interface-ethernet.py | 382 | ||||
-rwxr-xr-x | src/conf_mode/interface-openvpn.py | 36 | ||||
-rwxr-xr-x | src/conf_mode/interface-vxlan.py | 16 | ||||
-rwxr-xr-x | src/conf_mode/interface-wireguard.py | 26 | ||||
-rwxr-xr-x | src/conf_mode/ipoe_server.py | 23 | ||||
-rwxr-xr-x | src/helpers/vyos-boot-config-loader.py | 153 | ||||
-rwxr-xr-x | src/services/vyos-hostsd | 2 |
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}'" + """ + } + } + } + } } - } } - } } + @@ -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 |