summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/dhcp-client/daemon-options.tmpl3
-rw-r--r--data/templates/dhcp-client/ipv4.tmpl4
-rw-r--r--data/templates/dhcp-client/ipv6.tmpl5
-rw-r--r--data/templates/lcd/LCDd.conf.tmpl126
-rw-r--r--data/templates/lcd/lcdproc.conf.tmpl60
-rw-r--r--interface-definitions/include/nat-rule.xml.i6
-rw-r--r--interface-definitions/nat.xml.in6
-rw-r--r--interface-definitions/system-lcd.xml.in62
-rw-r--r--op-mode-definitions/lldp.xml2
-rw-r--r--op-mode-definitions/monitor-ndp.xml6
-rw-r--r--python/vyos/configdict.py2
-rw-r--r--python/vyos/configsession.py2
-rw-r--r--python/vyos/configverify.py12
-rw-r--r--python/vyos/ifconfig/__init__.py2
-rw-r--r--python/vyos/ifconfig/dhcp.py131
-rw-r--r--python/vyos/ifconfig/interface.py95
-rw-r--r--python/vyos/template.py163
-rw-r--r--python/vyos/util.py35
-rwxr-xr-xsrc/conf_mode/host_name.py17
-rwxr-xr-xsrc/conf_mode/interfaces-pseudo-ethernet.py30
-rwxr-xr-xsrc/conf_mode/interfaces-wirelessmodem.py12
-rwxr-xr-xsrc/conf_mode/service_console-server.py37
-rwxr-xr-xsrc/conf_mode/service_pppoe-server.py3
-rwxr-xr-xsrc/conf_mode/system_lcd.py84
-rw-r--r--src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper9
-rw-r--r--src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup10
-rw-r--r--src/etc/systemd/system/LCDd.service.d/override.conf8
-rw-r--r--src/etc/systemd/system/hostapd@.service.d/override.conf7
-rwxr-xr-xsrc/op_mode/ping.py8
-rw-r--r--src/systemd/lcdproc.service13
30 files changed, 671 insertions, 289 deletions
diff --git a/data/templates/dhcp-client/daemon-options.tmpl b/data/templates/dhcp-client/daemon-options.tmpl
index a0ba2c9ef..290aefa49 100644
--- a/data/templates/dhcp-client/daemon-options.tmpl
+++ b/data/templates/dhcp-client/daemon-options.tmpl
@@ -1 +1,4 @@
+### Autogenerated by interface.py ###
+
DHCLIENT_OPTS="-nw -cf /var/lib/dhcp/dhclient_{{ifname}}.conf -pf /var/lib/dhcp/dhclient_{{ifname}}.pid -lf /var/lib/dhcp/dhclient_{{ifname}}.leases {{ifname}}"
+
diff --git a/data/templates/dhcp-client/ipv4.tmpl b/data/templates/dhcp-client/ipv4.tmpl
index fe2a67f08..8a44a9761 100644
--- a/data/templates/dhcp-client/ipv4.tmpl
+++ b/data/templates/dhcp-client/ipv4.tmpl
@@ -1,4 +1,5 @@
-# generated by dhcp.py
+### Autogenerated by interface.py ###
+
option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
timeout 60;
retry 300;
@@ -15,3 +16,4 @@ interface "{{ ifname }}" {
rfc3442-classless-static-routes, domain-name, interface-mtu;
require subnet-mask;
}
+
diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.tmpl
index 112431c5f..5c0cea280 100644
--- a/data/templates/dhcp-client/ipv6.tmpl
+++ b/data/templates/dhcp-client/ipv6.tmpl
@@ -1,6 +1,6 @@
-# generated by dhcp.py
-# man https://www.unix.com/man-page/debian/5/dhcp6c.conf/
+### Autogenerated by interface.py ###
+# man https://www.unix.com/man-page/debian/5/dhcp6c.conf/
interface {{ ifname }} {
request domain-name-servers;
request domain-name;
@@ -45,3 +45,4 @@ id-assoc pd 2 {
};
{% endif %}
{% endif %}
+
diff --git a/data/templates/lcd/LCDd.conf.tmpl b/data/templates/lcd/LCDd.conf.tmpl
new file mode 100644
index 000000000..da749d04a
--- /dev/null
+++ b/data/templates/lcd/LCDd.conf.tmpl
@@ -0,0 +1,126 @@
+### Autogenerted by system-display.py ##
+
+# LCDd.conf -- configuration file for the LCDproc server daemon LCDd
+#
+# This file contains the configuration for the LCDd server.
+#
+# The format is ini-file-like. It is divided into sections that start at
+# markers that look like [section]. Comments are all line-based comments,
+# and are lines that start with '#' or ';'.
+#
+# The server has a 'central' section named [server]. For the menu there is
+# a section called [menu]. Further each driver has a section which
+# defines how the driver acts.
+#
+# The drivers are activated by specifying them in a driver= line in the
+# server section, like:
+#
+# Driver=curses
+#
+# This tells LCDd to use the curses driver.
+# The first driver that is loaded and is capable of output defines the
+# size of the display. The default driver to use is curses.
+# If the driver is specified using the -d <driver> command line option,
+# the Driver= options in the config file are ignored.
+#
+# The drivers read their own options from the respective sections.
+
+## Server section with all kinds of settings for the LCDd server ##
+[server]
+
+# Where can we find the driver modules ?
+# NOTE: Always place a slash as last character !
+DriverPath=/usr/lib/x86_64-linux-gnu/lcdproc/
+
+# Tells the server to load the given drivers. Multiple lines can be given.
+# The name of the driver is case sensitive and determines the section
+# where to look for further configuration options of the specific driver
+# as well as the name of the dynamic driver module to load at runtime.
+# The latter one can be changed by giving a File= directive in the
+# driver specific section.
+#
+# The following drivers are supported:
+# bayrad, CFontz, CFontzPacket, curses, CwLnx, ea65, EyeboxOne, futaba,
+# g15, glcd, glcdlib, glk, hd44780, icp_a106, imon, imonlcd,, IOWarrior,
+# irman, joy, lb216, lcdm001, lcterm, linux_input, lirc, lis, MD8800,
+# mdm166a, ms6931, mtc_s16209x, MtxOrb, mx5000, NoritakeVFD,
+# Olimex_MOD_LCD1x9, picolcd, pyramid, rawserial, sdeclcd, sed1330,
+# sed1520, serialPOS, serialVFD, shuttleVFD, sli, stv5730, svga, t6963,
+# text, tyan, ula200, vlsys_m428, xosd, yard2LCD
+
+{% if model is defined and model.startswith('CFA-') %}
+Driver=CFontzPacket
+{% endif %}
+
+# Tells the driver to bind to the given interface. [default: 127.0.0.1]
+Bind=127.0.0.1
+
+# Listen on this specified port. [default: 13666]
+Port=13666
+
+# Sets the reporting level; defaults to warnings and errors only.
+# [default: 2; legal: 0-5]
+ReportLevel=3
+
+# Should we report to syslog instead of stderr? [default: no; legal: yes, no]
+ReportToSyslog=yes
+
+# User to run as. LCDd will drop its root privileges and run as this user
+# instead. [default: nobody]
+User=nobody
+
+# The server will stay in the foreground if set to yes.
+# [default: no, legal: yes, no]
+Foreground=yes
+
+# Hello message: each entry represents a display line; default: builtin
+Hello="Starting VyOS"
+Hello=" ... "
+
+# GoodBye message: each entry represents a display line; default: builtin
+GoodBye=" VyOS shutting"
+GoodBye=" down... "
+
+# Sets the interval in microseconds for updating the display.
+# [default: 125000 meaning 8Hz]
+FrameInterval=250000 # 4 updates per second
+
+# Sets the default time in seconds to displays a screen. [default: 4]
+WaitTime=1
+
+# If set to no, LCDd will start with screen rotation disabled. This has the
+# same effect as if the ToggleRotateKey had been pressed. Rotation will start
+# if the ToggleRotateKey is pressed. Note that this setting does not turn off
+# priority sorting of screens. [default: on; legal: on, off]
+AutoRotate=on
+
+# If yes, the the serverscreen will be rotated as a usual info screen. If no,
+# it will be a background screen, only visible when no other screens are
+# active. The special value 'blank' is similar to no, but only a blank screen
+# is displayed. [default: on; legal: on, off, blank]
+ServerScreen=blank
+
+# Set master backlight setting. If set to 'open' a client may control the
+# backlight for its own screens (only). [default: open; legal: off, open, on]
+Backlight=on
+
+# Set master heartbeat setting. If set to 'open' a client may control the
+# heartbeat for its own screens (only). [default: open; legal: off, open, on]
+Heartbeat=off
+
+# set title scrolling speed [default: 10; legal: 0-10]
+TitleSpeed=10
+
+{% if model is defined and model is not none %}
+{% if model.startswith('CFA-') %}
+## CrystalFontz packet driver (for CFA533, CFA631, CFA633 & CFA635) ##
+[CFontzPacket]
+Model={{ model.split('-')[1] }}
+Device={{ device }}
+Contrast=350
+Brightness=500
+OffBrightness=50
+Reboot=yes
+USB=yes
+{% endif %}
+{% endif %}
diff --git a/data/templates/lcd/lcdproc.conf.tmpl b/data/templates/lcd/lcdproc.conf.tmpl
new file mode 100644
index 000000000..c79f3cd0d
--- /dev/null
+++ b/data/templates/lcd/lcdproc.conf.tmpl
@@ -0,0 +1,60 @@
+### autogenerated by system-lcd.py ###
+
+# LCDproc client configuration file
+
+[lcdproc]
+Server=127.0.0.1
+Port=13666
+
+# set reporting level
+ReportLevel=3
+
+# report to to syslog ?
+ReportToSyslog=true
+
+Foreground=yes
+
+[CPU]
+Active=true
+OnTime=1
+OffTime=2
+ShowInvisible=false
+
+[SMP-CPU]
+Active=false
+
+[Memory]
+Active=false
+
+[Load]
+Active=false
+
+[Uptime]
+Active=true
+
+[ProcSize]
+Active=false
+
+[Disk]
+Active=false
+
+[About]
+Active=false
+
+[TimeDate]
+Active=true
+TimeFormat="%H:%M:%S"
+
+[OldTime]
+Active=false
+
+[BigClock]
+Active=false
+
+[MiniClock]
+Active=false
+
+# Display the title bar in two-line mode. Note that with four lines or more
+# the title is always shown. [default: true; legal: true, false]
+ShowTitle=false
+
diff --git a/interface-definitions/include/nat-rule.xml.i b/interface-definitions/include/nat-rule.xml.i
index f62a08987..a2d058479 100644
--- a/interface-definitions/include/nat-rule.xml.i
+++ b/interface-definitions/include/nat-rule.xml.i
@@ -2,13 +2,13 @@
<properties>
<help>Rule number for NAT</help>
<valueHelp>
- <format>1-9999</format>
+ <format>1-999999</format>
<description>Number for this NAT rule</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 1-9999"/>
+ <validator name="numeric" argument="--range 1-999999"/>
</constraint>
- <constraintErrorMessage>NAT rule number must be between 1 and 9999</constraintErrorMessage>
+ <constraintErrorMessage>NAT rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
<children>
<leafNode name="description">
diff --git a/interface-definitions/nat.xml.in b/interface-definitions/nat.xml.in
index f8415b7c0..8a14f4d25 100644
--- a/interface-definitions/nat.xml.in
+++ b/interface-definitions/nat.xml.in
@@ -61,13 +61,13 @@
<properties>
<help>NPTv6 rule number</help>
<valueHelp>
- <format>1-9999</format>
+ <format>1-999999</format>
<description>Number for this rule</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 1-9999"/>
+ <validator name="numeric" argument="--range 1-999999"/>
</constraint>
- <constraintErrorMessage>NAT rule number must be between 1 and 9999</constraintErrorMessage>
+ <constraintErrorMessage>NAT rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
<children>
<leafNode name="description">
diff --git a/interface-definitions/system-lcd.xml.in b/interface-definitions/system-lcd.xml.in
new file mode 100644
index 000000000..ad59acb6b
--- /dev/null
+++ b/interface-definitions/system-lcd.xml.in
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="system">
+ <children>
+ <node name="lcd" owner="${vyos_conf_scripts_dir}/system_lcd.py">
+ <properties>
+ <help>System LCD display</help>
+ <priority>100</priority>
+ </properties>
+ <children>
+ <leafNode name="model">
+ <properties>
+ <help>Model of the display attached to this system [REQUIRED]</help>
+ <completionHelp>
+ <list>CFA-533 CFA-631 CFA-633 CFA-635</list>
+ </completionHelp>
+ <valueHelp>
+ <format>CFA-533</format>
+ <description>Crystalfontz CFA-533</description>
+ </valueHelp>
+ <valueHelp>
+ <format>CFA-631</format>
+ <description>Crystalfontz CFA-631</description>
+ </valueHelp>
+ <valueHelp>
+ <format>CFA-633</format>
+ <description>Crystalfontz CFA-633</description>
+ </valueHelp>
+ <valueHelp>
+ <format>CFA-635</format>
+ <description>Crystalfontz CFA-635</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(CFA-533|CFA-631|CFA-633|CFA-635)$</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="device">
+ <properties>
+ <help>Physical device used by LCD display</help>
+ <completionHelp>
+ <script>ls -1 /dev | grep ttyS</script>
+ <script>if [ -d /dev/serial/by-bus ]; then ls -1 /dev/serial/by-bus; fi</script>
+ </completionHelp>
+ <valueHelp>
+ <format>ttySXX</format>
+ <description>TTY device name, regular serial port</description>
+ </valueHelp>
+ <valueHelp>
+ <format>usbNbXpY</format>
+ <description>TTY device name, USB based</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(ttyS[0-9]+|usb[0-9]+b.*)$</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/lldp.xml b/op-mode-definitions/lldp.xml
index e954fb8cf..297ccf1f4 100644
--- a/op-mode-definitions/lldp.xml
+++ b/op-mode-definitions/lldp.xml
@@ -26,7 +26,7 @@
<script>${vyos_completion_dir}/list_interfaces.py</script>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/lldp_op.py --interface $4</command>
+ <command>${vyos_op_scripts_dir}/lldp_op.py --interface $5</command>
</tagNode>
</children>
</node>
diff --git a/op-mode-definitions/monitor-ndp.xml b/op-mode-definitions/monitor-ndp.xml
index e25eccf3a..1ac6ce39b 100644
--- a/op-mode-definitions/monitor-ndp.xml
+++ b/op-mode-definitions/monitor-ndp.xml
@@ -4,7 +4,7 @@
<children>
<node name="ndp">
<properties>
- <help>Monitors the NDP information received by the router through the device</help>
+ <help>Monitor the NDP information received by the router through the device</help>
</properties>
<command>sudo ndptool monitor</command>
<children>
@@ -20,7 +20,7 @@
<tagNode name="type">
<command>sudo ndptool monitor --ifname=$4 --msg-type=$6</command>
<properties>
- <help>Monitor ndp protocol on specified interface</help>
+ <help>Monitor specific types of NDP protocols</help>
<completionHelp>
<list>rs ra ns na</list>
</completionHelp>
@@ -31,7 +31,7 @@
<tagNode name="type">
<command>sudo ndptool monitor --msg-type=$4</command>
<properties>
- <help>Monitor ndp protocol on specified interface</help>
+ <help>Monitor specific types of NDP protocols</help>
<completionHelp>
<list>rs ra ns na</list>
</completionHelp>
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 126d6195a..010eda45c 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -23,7 +23,6 @@ from enum import Enum
from copy import deepcopy
from vyos import ConfigError
-from vyos.validate import is_member
def retrieve_config(path_hash, base_path, config):
"""
@@ -203,6 +202,7 @@ def get_interface_dict(config, base, ifname=''):
Will return a dictionary with the necessary interface configuration
"""
from vyos.xml import defaults
+ from vyos.validate import is_member
if not ifname:
# determine tagNode instance
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index f2524b37e..0994fd974 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -26,7 +26,7 @@ DISCARD = '/opt/vyatta/sbin/my_discard'
SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig']
LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile']
SAVE_CONFIG = ['/opt/vyatta/sbin/vyatta-save-config.pl']
-INSTALL_IMAGE = ['/opt/vyatta/sbin/install-image']
+INSTALL_IMAGE = ['/opt/vyatta/sbin/install-image', '--url']
REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del']
GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate']
SHOW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'show']
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index bb590a514..d1519b0ac 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -29,11 +29,11 @@ def verify_vrf(config):
recurring validation of VRF configuration.
"""
from netifaces import interfaces
- if 'vrf' in config.keys():
+ if 'vrf' in config:
if config['vrf'] not in interfaces():
raise ConfigError('VRF "{vrf}" does not exist'.format(**config))
- if 'is_bridge_member' in config.keys():
+ if 'is_bridge_member' in config:
raise ConfigError(
'Interface "{ifname}" cannot be both a member of VRF "{vrf}" '
'and bridge "{is_bridge_member}"!'.format(**config))
@@ -57,7 +57,7 @@ def verify_bridge_delete(config):
perform recurring validation of IP address assignmenr
when interface also is part of a bridge.
"""
- if 'is_bridge_member' in config.keys():
+ if 'is_bridge_member' in config:
raise ConfigError(
'Interface "{ifname}" cannot be deleted as it is a '
'member of bridge "{is_bridge_member}"!'.format(**config))
@@ -101,20 +101,20 @@ def verify_vlan_config(config):
recurring validation of interface VLANs
"""
# 802.1q VLANs
- for vlan in config.get('vif', {}).keys():
+ for vlan in config.get('vif', {}):
vlan = config['vif'][vlan]
verify_dhcpv6(vlan)
verify_address(vlan)
verify_vrf(vlan)
# 802.1ad (Q-in-Q) VLANs
- for vlan in config.get('vif_s', {}).keys():
+ for vlan in config.get('vif_s', {}):
vlan = config['vif_s'][vlan]
verify_dhcpv6(vlan)
verify_address(vlan)
verify_vrf(vlan)
- for vlan in config.get('vif_s', {}).get('vif_c', {}).keys():
+ for vlan in config.get('vif_s', {}).get('vif_c', {}):
vlan = config['vif_c'][vlan]
verify_dhcpv6(vlan)
verify_address(vlan)
diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py
index a7cdeadd1..9cd8d44c1 100644
--- a/python/vyos/ifconfig/__init__.py
+++ b/python/vyos/ifconfig/__init__.py
@@ -13,12 +13,10 @@
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
-
from vyos.ifconfig.section import Section
from vyos.ifconfig.control import Control
from vyos.ifconfig.interface import Interface
from vyos.ifconfig.operational import Operational
-from vyos.ifconfig.dhcp import DHCP
from vyos.ifconfig.vrrp import VRRP
from vyos.ifconfig.bond import BondIf
diff --git a/python/vyos/ifconfig/dhcp.py b/python/vyos/ifconfig/dhcp.py
deleted file mode 100644
index 63224fc0f..000000000
--- a/python/vyos/ifconfig/dhcp.py
+++ /dev/null
@@ -1,131 +0,0 @@
-# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-import jmespath
-
-from vyos.configdict import dict_merge
-from vyos.configverify import verify_dhcpv6
-from vyos.ifconfig.control import Control
-from vyos.template import render
-
-class _DHCPv4 (Control):
- def __init__(self, ifname):
- super().__init__()
- config_base = r'/var/lib/dhcp/dhclient'
- self._conf_file = f'{config_base}_{ifname}.conf'
- self._options_file = f'{config_base}_{ifname}.options'
- self._pid_file = f'{config_base}_{ifname}.pid'
- self._lease_file = f'{config_base}_{ifname}.leases'
- self.options = {'ifname' : ifname}
-
- # replace dhcpv4/v6 with systemd.networkd?
- def set(self):
- """
- Configure interface as DHCP client. The dhclient binary is automatically
- started in background!
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> j = Interface('eth0')
- >>> j.dhcp.v4.set()
- """
-
- if jmespath.search('dhcp_options.host_name', self.options) == None:
- # read configured system hostname.
- # maybe change to vyos hostd client ???
- hostname = 'vyos'
- with open('/etc/hostname', 'r') as f:
- hostname = f.read().rstrip('\n')
- tmp = {'dhcp_options' : { 'host_name' : hostname}}
- self.options = dict_merge(tmp, self.options)
-
- render(self._options_file, 'dhcp-client/daemon-options.tmpl',
- self.options, trim_blocks=True)
- render(self._conf_file, 'dhcp-client/ipv4.tmpl',
- self.options, trim_blocks=True)
-
- return self._cmd('systemctl restart dhclient@{ifname}.service'.format(**self.options))
-
- def delete(self):
- """
- De-configure interface as DHCP clinet. All auto generated files like
- pid, config and lease will be removed.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> j = Interface('eth0')
- >>> j.dhcp.v4.delete()
- """
- if not os.path.isfile(self._pid_file):
- self._debug_msg('No DHCP client PID found')
- return None
-
- self._cmd('systemctl stop dhclient@{ifname}.service'.format(**self.options))
-
- # cleanup old config files
- for file in [self._conf_file, self._options_file, self._pid_file, self._lease_file]:
- if os.path.isfile(file):
- os.remove(file)
-
-class _DHCPv6 (Control):
- def __init__(self, ifname):
- super().__init__()
- self.options = {'ifname' : ifname}
- self._config = f'/run/dhcp6c/dhcp6c.{ifname}.conf'
-
- def set(self):
- """
- Configure interface as DHCPv6 client. The client is automatically
- started in background when address is configured as DHCP.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> j = Interface('eth0')
- >>> j.dhcp.v6.set()
- """
-
- # better save then sorry .. should be checked in interface script but if you
- # missed it we are safe!
- verify_dhcpv6(self.options)
-
- render(self._config, 'dhcp-client/ipv6.tmpl',
- self.options, trim_blocks=True)
-
- # We must ignore any return codes. This is required to enable DHCPv6-PD
- # for interfaces which are yet not up and running.
- return self._popen('systemctl restart dhcp6c@{ifname}.service'.format(
- **self.options))
-
- def delete(self):
- """
- De-configure interface as DHCPv6 client. All auto generated files like
- pid, config and lease will be removed.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> j = Interface('eth0')
- >>> j.dhcp.v6.delete()
- """
- self._cmd('systemctl stop dhcp6c@{ifname}.service'.format(**self.options))
-
- # cleanup old config files
- if os.path.isfile(self._config):
- os.remove(self._config)
-
-class DHCP(object):
- def __init__(self, ifname):
- self.v4 = _DHCPv4(ifname)
- self.v6 = _DHCPv6(ifname)
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 214ece8cd..36f258301 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -31,6 +31,8 @@ from netifaces import AF_INET6
from vyos import ConfigError
from vyos.configdict import list_diff
+from vyos.configdict import dict_merge
+from vyos.template import render
from vyos.util import mac2eui64
from vyos.validate import is_ipv4
from vyos.validate import is_ipv6
@@ -43,7 +45,6 @@ from vyos.validate import assert_positive
from vyos.validate import assert_range
from vyos.ifconfig.control import Control
-from vyos.ifconfig.dhcp import DHCP
from vyos.ifconfig.vrrp import VRRP
from vyos.ifconfig.operational import Operational
from vyos.ifconfig import Section
@@ -216,7 +217,6 @@ class Interface(Control):
# we must have updated config before initialising the Interface
super().__init__(**kargs)
self.ifname = ifname
- self.dhcp = DHCP(ifname)
if not self.exists(ifname):
# Any instance of Interface, such as Interface('eth0')
@@ -722,9 +722,9 @@ class Interface(Control):
# add to interface
if addr == 'dhcp':
- self.dhcp.v4.set()
+ self.set_dhcp(True)
elif addr == 'dhcpv6':
- self.dhcp.v6.set()
+ self.set_dhcpv6(True)
elif not is_intf_addr_assigned(self.ifname, addr):
self._cmd(f'ip addr add "{addr}" '
f'{"brd + " if addr_is_v4 else ""}dev "{self.ifname}"')
@@ -763,9 +763,9 @@ class Interface(Control):
# remove from interface
if addr == 'dhcp':
- self.dhcp.v4.delete()
+ self.set_dhcp(False)
elif addr == 'dhcpv6':
- self.dhcp.v6.delete()
+ self.set_dhcpv6(False)
elif is_intf_addr_assigned(self.ifname, addr):
self._cmd(f'ip addr del "{addr}" dev "{self.ifname}"')
else:
@@ -784,8 +784,8 @@ class Interface(Control):
Will raise an exception on error.
"""
# stop DHCP(v6) if running
- self.dhcp.v4.delete()
- self.dhcp.v6.delete()
+ self.set_dhcp(False)
+ self.set_dhcpv6(False)
# flush all addresses
self._cmd(f'ip addr flush dev "{self.ifname}"')
@@ -809,12 +809,83 @@ class Interface(Control):
return True
+ def set_dhcp(self, enable):
+ """
+ Enable/Disable DHCP client on a given interface.
+ """
+ if enable not in [True, False]:
+ raise ValueError()
+
+ ifname = self.ifname
+ config_base = r'/var/lib/dhcp/dhclient'
+ config_file = f'{config_base}_{ifname}.conf'
+ options_file = f'{config_base}_{ifname}.options'
+ pid_file = f'{config_base}_{ifname}.pid'
+ lease_file = f'{config_base}_{ifname}.leases'
+
+ if enable and 'disable' not in self._config:
+ if jmespath.search('dhcp_options.host_name', self._config) == None:
+ # read configured system hostname.
+ # maybe change to vyos hostd client ???
+ hostname = 'vyos'
+ with open('/etc/hostname', 'r') as f:
+ hostname = f.read().rstrip('\n')
+ tmp = {'dhcp_options' : { 'host_name' : hostname}}
+ self._config = dict_merge(tmp, self._config)
+
+ render(options_file, 'dhcp-client/daemon-options.tmpl',
+ self._config, trim_blocks=True)
+ render(config_file, 'dhcp-client/ipv4.tmpl',
+ self._config, trim_blocks=True)
+
+ # 'up' check is mandatory b/c even if the interface is A/D, as soon as
+ # the DHCP client is started the interface will be placed in u/u state.
+ # This is not what we intended to do when disabling an interface.
+ return self._cmd(f'systemctl restart dhclient@{ifname}.service')
+ else:
+ self._cmd(f'systemctl stop dhclient@{ifname}.service')
+
+ # cleanup old config files
+ for file in [config_file, options_file, pid_file, lease_file]:
+ if os.path.isfile(file):
+ os.remove(file)
+
+
+ def set_dhcpv6(self, enable):
+ """
+ Enable/Disable DHCPv6 client on a given interface.
+ """
+ if enable not in [True, False]:
+ raise ValueError()
+
+ ifname = self.ifname
+ config_file = f'/run/dhcp6c/dhcp6c.{ifname}.conf'
+
+ if enable and 'disable' not in self._config:
+ render(config_file, 'dhcp-client/ipv6.tmpl',
+ self._config, trim_blocks=True)
+
+ # We must ignore any return codes. This is required to enable DHCPv6-PD
+ # for interfaces which are yet not up and running.
+ return self._popen(f'systemctl restart dhcp6c@{ifname}.service')
+ else:
+ self._popen(f'systemctl stop dhcp6c@{ifname}.service')
+
+ if os.path.isfile(config_file):
+ os.remove(config_file)
+
+
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
interface setup code and provide a single point of entry when workin
on any interface. """
+ # Cache the configuration - it will be reused inside e.g. DHCP handler
+ # XXX: maybe pass the option via __init__ in the future and rename this
+ # method to apply()?
+ self._config = config
+
# Update interface description
self.set_alias(config.get('description', ''))
@@ -822,14 +893,6 @@ class Interface(Control):
value = '2' if 'disable_link_detect' in config else '1'
self.set_link_detect(value)
- # DHCP options
- if 'dhcp_options' in config:
- self.dhcp.v4.options = config
-
- # DHCPv6 options
- if 'dhcpv6_options' in config:
- self.dhcp.v6.options = config
-
# Configure assigned interface IP addresses. No longer
# configured addresses will be removed first
new_addr = config.get('address', [])
diff --git a/python/vyos/template.py b/python/vyos/template.py
index d9b0c749d..c88ab04a0 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -1,4 +1,4 @@
-# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-2020 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -13,78 +13,131 @@
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+import functools
import os
+from ipaddress import ip_network
from jinja2 import Environment
from jinja2 import FileSystemLoader
from vyos.defaults import directories
from vyos.util import chmod, chown, makedir
-# reuse the same Environment to improve performance
-_templates_env = {
- False: Environment(loader=FileSystemLoader(directories['templates'])),
- True: Environment(loader=FileSystemLoader(directories['templates']), trim_blocks=True),
-}
-_templates_mem = {
- False: {},
- True: {},
-}
-def vyos_address_from_cidr(text):
- """ Take an IPv4/IPv6 CIDR prefix and convert the network to an "address".
- Example:
- 192.0.2.0/24 -> 192.0.2.0, 2001:db8::/48 -> 2001:db8::
- """
- from ipaddress import ip_network
- return ip_network(text).network_address
+# Holds template filters registered via register_filter()
+_FILTERS = {}
-def vyos_netmask_from_cidr(text):
- """ Take an IPv4/IPv6 CIDR prefix and convert the prefix length to a "subnet mask".
- Example:
- 192.0.2.0/24 -> 255.255.255.0, 2001:db8::/48 -> ffff:ffff:ffff::
- """
- from ipaddress import ip_network
- return ip_network(text).netmask
-def render(destination, template, content, trim_blocks=False, formater=None, permission=None, user=None, group=None):
+# reuse Environments with identical trim_blocks setting to improve performance
+@functools.lru_cache(maxsize=2)
+def _get_environment(trim_blocks):
+ env = Environment(
+ # Don't check if template files were modified upon re-rendering
+ auto_reload=False,
+ # Cache up to this number of templates for quick re-rendering
+ cache_size=100,
+ loader=FileSystemLoader(directories["templates"]),
+ trim_blocks=trim_blocks,
+ )
+ env.filters.update(_FILTERS)
+ return env
+
+
+def register_filter(name, func=None):
+ """Register a function to be available as filter in templates under given name.
+
+ It can also be used as a decorator, see below in this module for examples.
+
+ :raise RuntimeError:
+ when trying to register a filter after a template has been rendered already
+ :raise ValueError: when trying to register a name which was taken already
"""
- render a template from the template directory, it will raise on any errors
- destination: the file where the rendered template must be saved
- template: the path to the template relative to the template folder
- content: the dictionary to use to render the template
-
- This classes cache the renderer, so rendering the same file multiple time
- does not cause as too much overhead. If use everywhere, it could be changed
- and load the template from python environement variables from an import
- python module generated when the debian package is build
- (recovering the load time and overhead caused by having the file out of the code)
+ if func is None:
+ return functools.partial(register_filter, name)
+ if _get_environment.cache_info().currsize:
+ raise RuntimeError(
+ "Filters can only be registered before rendering the first template"
+ )
+ if name in _FILTERS:
+ raise ValueError(f"A filter with name {name!r} was registered already")
+ _FILTERS[name] = func
+ return func
+
+
+def render_to_string(template, content, trim_blocks=False, formater=None):
+ """Render a template from the template directory, raise on any errors.
+
+ :param template: the path to the template relative to the template folder
+ :param content: the dictionary of variables to put into rendering context
+ :param trim_blocks: controls the trim_blocks jinja2 feature
+ :param formater:
+ if given, it has to be a callable the rendered string is passed through
+
+ The parsed template files are cached, so rendering the same file multiple times
+ does not cause as too much overhead.
+ If used everywhere, it could be changed to load the template from Python
+ environment variables from an importable Python module generated when the Debian
+ package is build (recovering the load time and overhead caused by having the
+ file out of the code).
"""
+ template = _get_environment(bool(trim_blocks)).get_template(template)
+ rendered = template.render(content)
+ if formater is not None:
+ rendered = formater(rendered)
+ return rendered
+
+
+def render(
+ destination,
+ template,
+ content,
+ trim_blocks=False,
+ formater=None,
+ permission=None,
+ user=None,
+ group=None,
+):
+ """Render a template from the template directory to a file, raise on any errors.
- # Create the directory if it does not exists
+ :param destination: path to the file to save the rendered template in
+ :param permission: permission bitmask to set for the output file
+ :param user: user to own the output file
+ :param group: group to own the output file
+
+ All other parameters are as for :func:`render_to_string`.
+ """
+ # Create the directory if it does not exist
folder = os.path.dirname(destination)
makedir(folder, user, group)
- # Setup a renderer for the given template
- # This is cached and re-used for performance
- if template not in _templates_mem[trim_blocks]:
- _env = _templates_env[trim_blocks]
- _env.filters['address_from_cidr'] = vyos_address_from_cidr
- _env.filters['netmask_from_cidr'] = vyos_netmask_from_cidr
- _templates_mem[trim_blocks][template] = _env.get_template(template)
+ # As we are opening the file with 'w', we are performing the rendering before
+ # calling open() to not accidentally erase the file if rendering fails
+ rendered = render_to_string(template, content, trim_blocks, formater)
- template = _templates_mem[trim_blocks][template]
+ # Write to file
+ with open(destination, "w") as file:
+ chmod(file.fileno(), permission)
+ chown(file.fileno(), user, group)
+ file.write(rendered)
- # As we are opening the file with 'w', we are performing the rendering
- # before calling open() to not accidentally erase the file if the
- # templating fails
- content = template.render(content)
- if formater:
- content = formater(content)
+##################################
+# Custom template filters follow #
+##################################
- # Write client config file
- with open(destination, 'w') as f:
- f.write(content)
- chmod(destination, permission)
- chown(destination, user, group)
+@register_filter("address_from_cidr")
+def vyos_address_from_cidr(text):
+ """ Take an IPv4/IPv6 CIDR prefix and convert the network to an "address".
+ Example:
+ 192.0.2.0/24 -> 192.0.2.0, 2001:db8::/48 -> 2001:db8::
+ """
+ return ip_network(text).network_address
+
+
+@register_filter("netmask_from_cidr")
+def vyos_netmask_from_cidr(text):
+ """ Take an IPv4/IPv6 CIDR prefix and convert the prefix length to a "subnet mask".
+ Example:
+ 192.0.2.0/24 -> 255.255.255.0, 2001:db8::/48 -> ffff:ffff:ffff::
+ """
+ return ip_network(text).netmask
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 7078762df..d27a8a3e7 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -240,7 +240,8 @@ def chown(path, user, group):
if user is None or group is None:
return False
- if not os.path.exists(path):
+ # path may also be an open file descriptor
+ if not isinstance(path, int) and not os.path.exists(path):
return False
uid = getpwnam(user).pw_uid
@@ -250,7 +251,8 @@ def chown(path, user, group):
def chmod(path, bitmask):
- if not os.path.exists(path):
+ # path may also be an open file descriptor
+ if not isinstance(path, int) and not os.path.exists(path):
return
if bitmask is None:
return
@@ -261,28 +263,25 @@ def chmod_600(path):
""" make file only read/writable by owner """
from stat import S_IRUSR, S_IWUSR
- if os.path.exists(path):
- bitmask = S_IRUSR | S_IWUSR
- os.chmod(path, bitmask)
+ bitmask = S_IRUSR | S_IWUSR
+ chmod(path, bitmask)
def chmod_750(path):
""" make file/directory only executable to user and group """
from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP
- if os.path.exists(path):
- bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP
- os.chmod(path, bitmask)
+ bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP
+ chmod(path, bitmask)
def chmod_755(path):
""" make file executable by all """
from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH
- if os.path.exists(path):
- bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | \
- S_IROTH | S_IXOTH
- os.chmod(path, bitmask)
+ bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | \
+ S_IROTH | S_IXOTH
+ chmod(path, bitmask)
def makedir(path, user=None, group=None):
@@ -661,3 +660,15 @@ def check_kmod(k_mod):
if not os.path.exists(f'/sys/module/{module}'):
if call(f'modprobe {module}') != 0:
raise ConfigError(f'Loading Kernel module {module} failed')
+
+def find_device_file(device):
+ """ Recurively search /dev for the given device file and return its full path.
+ If no device file was found 'None' is returned """
+ from fnmatch import fnmatch
+
+ for root, dirs, files in os.walk('/dev'):
+ for basename in files:
+ if fnmatch(basename, device):
+ return os.path.join(root, basename)
+
+ return None
diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py
index f2fa64233..9d66bd434 100755
--- a/src/conf_mode/host_name.py
+++ b/src/conf_mode/host_name.py
@@ -18,20 +18,16 @@
conf-mode script for 'system host-name' and 'system domain-name'.
"""
-import os
import re
import sys
import copy
-import glob
-import argparse
-import jinja2
import vyos.util
import vyos.hostsd_client
from vyos.config import Config
from vyos import ConfigError
-from vyos.util import cmd, call, run, process_named_running
+from vyos.util import cmd, call, process_named_running
from vyos import airbag
airbag.enable()
@@ -47,7 +43,9 @@ default_config_data = {
hostsd_tag = 'system'
-def get_config(conf):
+def get_config():
+ conf = Config()
+
hosts = copy.deepcopy(default_config_data)
hosts['hostname'] = conf.return_value("system host-name")
@@ -77,7 +75,7 @@ def get_config(conf):
return hosts
-def verify(conf, hosts):
+def verify(hosts):
if hosts is None:
return None
@@ -168,9 +166,8 @@ def apply(config):
if __name__ == '__main__':
try:
- conf = Config()
- c = get_config(conf)
- verify(conf, c)
+ c = get_config()
+ verify(c)
generate(c)
apply(c)
except ConfigError as e:
diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py
index 4afea2b3a..fe2d7b1be 100755
--- a/src/conf_mode/interfaces-pseudo-ethernet.py
+++ b/src/conf_mode/interfaces-pseudo-ethernet.py
@@ -28,6 +28,7 @@ from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_source_interface
from vyos.configverify import verify_vlan_config
from vyos.ifconfig import MACVLANIf
+from vyos.validate import is_member
from vyos import ConfigError
from vyos import airbag
@@ -35,8 +36,8 @@ airbag.enable()
def get_config():
"""
- Retrive CLI config as dictionary. Dictionary can never be empty, as at least the
- interface name will be added or a deleted flag
+ Retrive CLI config as dictionary. Dictionary can never be empty, as at
+ least the interface name will be added or a deleted flag
"""
conf = Config()
base = ['interfaces', 'pseudo-ethernet']
@@ -46,6 +47,17 @@ def get_config():
if mode:
peth.update({'mode_old' : mode})
+ # Check if source-interface is member of a bridge device
+ if 'source_interface' in peth:
+ bridge = is_member(conf, peth['source_interface'], 'bridge')
+ if bridge:
+ peth.update({'source_interface_is_bridge_member' : bridge})
+
+ # Check if we are a member of a bond device
+ bond = is_member(conf, peth['source_interface'], 'bonding')
+ if bond:
+ peth.update({'source_interface_is_bond_member' : bond})
+
return peth
def verify(peth):
@@ -57,6 +69,16 @@ def verify(peth):
verify_vrf(peth)
verify_address(peth)
+ if 'source_interface_is_bridge_member' in peth:
+ raise ConfigError(
+ 'Source interface "{source_interface}" can not be used as it is already a '
+ 'member of bridge "{source_interface_is_bridge_member}"!'.format(**peth))
+
+ if 'source_interface_is_bond_member' in peth:
+ raise ConfigError(
+ 'Source interface "{source_interface}" can not be used as it is already a '
+ 'member of bond "{source_interface_is_bond_member}"!'.format(**peth))
+
# use common function to verify VLAN configuration
verify_vlan_config(peth)
return None
@@ -71,8 +93,8 @@ def apply(peth):
return None
# Check if MACVLAN interface already exists. Parameters like the underlaying
- # source-interface device or mode can not be changed on the fly and the interface
- # needs to be recreated from the bottom.
+ # source-interface device or mode can not be changed on the fly and the
+ # interface needs to be recreated from the bottom.
if 'mode_old' in peth:
MACVLANIf(peth['ifname']).remove()
diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py
index 4081be3c9..6d168d918 100755
--- a/src/conf_mode/interfaces-wirelessmodem.py
+++ b/src/conf_mode/interfaces-wirelessmodem.py
@@ -16,7 +16,6 @@
import os
-from fnmatch import fnmatch
from sys import exit
from vyos.config import Config
@@ -25,22 +24,13 @@ from vyos.configverify import verify_vrf
from vyos.template import render
from vyos.util import call
from vyos.util import check_kmod
+from vyos.util import find_device_file
from vyos import ConfigError
from vyos import airbag
airbag.enable()
k_mod = ['option', 'usb_wwan', 'usbserial']
-def find_device_file(device):
- """ Recurively search /dev for the given device file and return its full path.
- If no device file was found 'None' is returned """
- for root, dirs, files in os.walk('/dev'):
- for basename in files:
- if fnmatch(basename, device):
- return os.path.join(root, basename)
-
- return None
-
def get_config():
"""
Retrive CLI config as dictionary. Dictionary can never be empty, as at least the
diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py
index ace6b8ca4..613ec6879 100755
--- a/src/conf_mode/service_console-server.py
+++ b/src/conf_mode/service_console-server.py
@@ -31,11 +31,9 @@ def get_config():
conf = Config()
base = ['service', 'console-server']
- if not conf.exists(base):
- return None
-
# Retrieve CLI representation as dictionary
- proxy = conf.get_config_dict(base, key_mangling=('-', '_'))
+ proxy = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True)
# The retrieved dictionary will look something like this:
#
# {'device': {'usb0b2.4p1.0': {'speed': '9600'},
@@ -47,9 +45,10 @@ def get_config():
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
default_values = defaults(base + ['device'])
- for device in proxy['device'].keys():
- tmp = dict_merge(default_values, proxy['device'][device])
- proxy['device'][device] = tmp
+ if 'device' in proxy:
+ for device in proxy['device']:
+ tmp = dict_merge(default_values, proxy['device'][device])
+ proxy['device'][device] = tmp
return proxy
@@ -57,15 +56,14 @@ def verify(proxy):
if not proxy:
return None
- for device in proxy['device']:
- keys = proxy['device'][device].keys()
- if 'speed' not in keys:
- raise ConfigError(f'Serial port speed must be defined for "{tmp}"!')
+ if 'device' in proxy:
+ for device in proxy['device']:
+ if 'speed' not in proxy['device'][device]:
+ raise ConfigError(f'Serial port speed must be defined for "{device}"!')
- if 'ssh' in keys:
- ssh_keys = proxy['device'][device]['ssh'].keys()
- if 'port' not in ssh_keys:
- raise ConfigError(f'SSH port must be defined for "{tmp}"!')
+ if 'ssh' in proxy['device'][device]:
+ if 'port' not in proxy['device'][device]['ssh']:
+ raise ConfigError(f'SSH port must be defined for "{device}"!')
return None
@@ -86,10 +84,11 @@ def apply(proxy):
call('systemctl restart conserver-server.service')
- for device in proxy['device']:
- if 'ssh' in proxy['device'][device].keys():
- port = proxy['device'][device]['ssh']['port']
- call(f'systemctl restart dropbear@{device}.service')
+ if 'device' in proxy:
+ for device in proxy['device']:
+ if 'ssh' in proxy['device'][device]:
+ port = proxy['device'][device]['ssh']['port']
+ call(f'systemctl restart dropbear@{device}.service')
return None
diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py
index a8357f653..39d34a7e2 100755
--- a/src/conf_mode/service_pppoe-server.py
+++ b/src/conf_mode/service_pppoe-server.py
@@ -421,6 +421,9 @@ def verify(pppoe):
if len(pppoe['dnsv6']) > 3:
raise ConfigError('Not more then three IPv6 DNS name-servers can be configured')
+ if not pppoe['interfaces']:
+ raise ConfigError('At least one listen interface must be defined!')
+
# local ippool and gateway settings config checks
if pppoe['client_ip_subnets'] or pppoe['client_ip_pool']:
if not pppoe['ppp_gw']:
diff --git a/src/conf_mode/system_lcd.py b/src/conf_mode/system_lcd.py
new file mode 100755
index 000000000..0ad1318f0
--- /dev/null
+++ b/src/conf_mode/system_lcd.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.util import call
+from vyos.util import find_device_file
+from vyos.template import render
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+lcdd_conf = '/run/LCDd/LCDd.conf'
+lcdproc_conf = '/run/lcdproc/lcdproc.conf'
+
+def get_config():
+ conf = Config()
+ base = ['system', 'lcd']
+ lcd = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True)
+ # Return (possibly empty) dictionary
+ return lcd
+
+def verify(lcd):
+ if not lcd:
+ return None
+
+ if not {'device', 'model'} <= set(lcd):
+ raise ConfigError('Both device and driver must be set!')
+
+ return None
+
+def generate(lcd):
+ if not lcd:
+ return None
+
+ if 'device' in lcd:
+ lcd['device'] = find_device_file(lcd['device'])
+
+ # Render config file for daemon LCDd
+ render(lcdd_conf, 'lcd/LCDd.conf.tmpl', lcd, trim_blocks=True)
+ # Render config file for client lcdproc
+ render(lcdproc_conf, 'lcd/lcdproc.conf.tmpl', lcd, trim_blocks=True)
+
+ return None
+
+def apply(lcd):
+ if not lcd:
+ call('systemctl stop lcdproc.service LCDd.service')
+
+ for file in [lcdd_conf, lcdproc_conf]:
+ if os.path.exists(file):
+ os.remove(file)
+ else:
+ # Restart server
+ call('systemctl restart LCDd.service lcdproc.service')
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ config_dict = get_config()
+ verify(config_dict)
+ generate(config_dict)
+ apply(config_dict)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper
index f1167fcd2..d1161e704 100644
--- a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper
+++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper
@@ -40,7 +40,14 @@ function iptovtysh () {
elif [ "$7" == "dev" ]; then
VTYSH_DEV=$8
fi
- VTYSH_CMD="ip route $VTYSH_NETADDR $VTYSH_GATEWAY $VTYSH_DEV tag $VTYSH_TAG $VTYSH_DISTANCE"
+
+ # Add route to VRF routing table
+ local VTYSH_VRF_NAME=$(basename /sys/class/net/$VTYSH_DEV/upper_* | sed -e 's/upper_//')
+ if [ -n $VTYSH_VRF_NAME ]; then
+ VTYSH_VRF="vrf $VTYSH_VRF_NAME"
+ fi
+ VTYSH_CMD="ip route $VTYSH_NETADDR $VTYSH_GATEWAY $VTYSH_DEV tag $VTYSH_TAG $VTYSH_DISTANCE $VTYSH_VRF"
+
# delete route if the command is "del"
if [ "$3" == "del" ] ; then
VTYSH_CMD="no $VTYSH_CMD"
diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup
index 01981ad04..b768e1ae5 100644
--- a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup
+++ b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup
@@ -15,8 +15,14 @@ if [[ $reason =~ (EXPIRE|FAIL|RELEASE|STOP) ]]; then
# try to delete default ip route
for router in $old_routers; do
- logmsg info "Deleting default route: via $router dev ${interface}"
- ip -4 route del default via $router dev ${interface}
+ # check if we are bound to a VRF
+ local vrf_name=$(basename /sys/class/net/${interface}/upper_* | sed -e 's/upper_//')
+ if [ -n $vrf_name ]; then
+ vrf="vrf $vrf_name"
+ fi
+
+ logmsg info "Deleting default route: via $router dev ${interface} ${vrf}"
+ ip -4 route del default via $router dev ${interface} ${vrf}
done
# delete rfc3442 routes
diff --git a/src/etc/systemd/system/LCDd.service.d/override.conf b/src/etc/systemd/system/LCDd.service.d/override.conf
new file mode 100644
index 000000000..5f3f0dc95
--- /dev/null
+++ b/src/etc/systemd/system/LCDd.service.d/override.conf
@@ -0,0 +1,8 @@
+[Unit]
+After=
+After=vyos-router.service
+
+[Service]
+ExecStart=
+ExecStart=/usr/sbin/LCDd -c /run/LCDd/LCDd.conf
+
diff --git a/src/etc/systemd/system/hostapd@.service.d/override.conf b/src/etc/systemd/system/hostapd@.service.d/override.conf
index bb8e81d7a..b03dbc299 100644
--- a/src/etc/systemd/system/hostapd@.service.d/override.conf
+++ b/src/etc/systemd/system/hostapd@.service.d/override.conf
@@ -3,8 +3,7 @@ After=
After=vyos-router.service
[Service]
-WorkingDirectory=/run/hostapd
-EnvironmentFile=
+WorkingDirectory=/run/LCDd
ExecStart=
-ExecStart=/usr/sbin/hostapd -B -P /run/hostapd/%i.pid /run/hostapd/%i.conf
-PIDFile=/run/hostapd/%i.pid
+ExecStart=/usr/sbin/LCDd -s 1 -f -c /run/LCDd/LCDd.conf
+
diff --git a/src/op_mode/ping.py b/src/op_mode/ping.py
index e56952c38..29b430d53 100755
--- a/src/op_mode/ping.py
+++ b/src/op_mode/ping.py
@@ -118,7 +118,8 @@ options = {
'vrf': {
'ping': 'sudo ip vrf exec {value} {command}',
'type': '<vrf>',
- 'help': 'Use specified VRF table'
+ 'help': 'Use specified VRF table',
+ 'dflt': 'default',
},
'verbose': {
'ping': '{command} -v',
@@ -207,6 +208,11 @@ if __name__ == '__main__':
sys.stdout.write(options[matched[0]]['type'])
sys.exit(0)
+ for name,option in options.items():
+ if 'dflt' in option and name not in args:
+ args.append(name)
+ args.append(option['dflt'])
+
try:
ip = socket.gethostbyname(host)
except socket.gaierror:
diff --git a/src/systemd/lcdproc.service b/src/systemd/lcdproc.service
new file mode 100644
index 000000000..5aa99ec78
--- /dev/null
+++ b/src/systemd/lcdproc.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=LCDproc system status information viewer on %I
+Documentation=man:lcdproc(8) http://www.lcdproc.org/
+After=vyos-router.service
+After=LCDd.service
+
+[Service]
+User=root
+ExecStart=/usr/bin/lcdproc -f -c /run/lcdproc/lcdproc.conf
+PIDFile=/run/lcdproc/lcdproc.pid
+
+[Install]
+WantedBy=multi-user.target