From d41708ee247ce5efc5dc06f2d233e8d620fc6e4a Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sat, 23 May 2020 07:09:10 +0100 Subject: http-api: T2494: always exit with non zero on failure systemd is setup with Restart=on-failure thereforer the service will only be restarted if the daemon died and reported an error. Previously any OsError would cause a exit(0) and therefore the API would not have been restarted. https://www.freedesktop.org/software/systemd/man/systemd.service.html --- src/services/vyos-http-api-server | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index 4c41fa96d..b256add98 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -400,4 +400,4 @@ if __name__ == '__main__': serve(app, host=server_config["listen_address"], port=server_config["port"]) except OSError as e: - print(f"OSError {e}") + sys.exit(f"OSError {e}") -- cgit v1.2.3 From d9fbad6a6bbba292f31fe50e57af893062c7a584 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sat, 23 May 2020 07:33:47 +0100 Subject: systemd: T2494: use Type=notify with daemon Notify systemd via the notify API when the python daemon are ready to take connection https://github.com/torfsen/python-systemd-tutorial --- debian/control | 1 + src/services/vyos-hostsd | 1 + src/services/vyos-http-api-server | 2 ++ src/systemd/vyos-hostsd.service | 2 +- src/systemd/vyos-http-api.service | 2 +- 5 files changed, 6 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/debian/control b/debian/control index 5e14340a8..aaf8fa1e7 100644 --- a/debian/control +++ b/debian/control @@ -36,6 +36,7 @@ Depends: python3, python3-xmltodict, python3-pyudev, python3-voluptuous, + python3-systemd, bsdmainutils, cron, etherwake, diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd index 0079f7e5c..53ac5a770 100755 --- a/src/services/vyos-hostsd +++ b/src/services/vyos-hostsd @@ -592,6 +592,7 @@ if __name__ == '__main__': socket.bind(SOCKET_PATH) os.umask(o_mask) + systemd.daemon.notify('READY=1') while True: # Wait for next request from client msg_json = socket.recv().decode() diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index b256add98..38bf2f8ce 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -28,6 +28,7 @@ import vyos.config from flask import Flask, request from waitress import serve +import systemd.daemon from functools import wraps @@ -396,6 +397,7 @@ if __name__ == '__main__': signal.signal(signal.SIGTERM, sig_handler) + systemd.daemon.notify('READY=1') try: serve(app, host=server_config["listen_address"], port=server_config["port"]) diff --git a/src/systemd/vyos-hostsd.service b/src/systemd/vyos-hostsd.service index b77335778..418601d1a 100644 --- a/src/systemd/vyos-hostsd.service +++ b/src/systemd/vyos-hostsd.service @@ -14,7 +14,7 @@ WorkingDirectory=/run/vyos-hostsd RuntimeDirectory=vyos-hostsd RuntimeDirectoryPreserve=yes ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-hostsd -Type=idle +Type=notify KillMode=process SyslogIdentifier=vyos-hostsd diff --git a/src/systemd/vyos-http-api.service b/src/systemd/vyos-http-api.service index 4fa68b4ff..636973be7 100644 --- a/src/systemd/vyos-http-api.service +++ b/src/systemd/vyos-http-api.service @@ -6,7 +6,7 @@ Requires=vyos-router.service [Service] ExecStartPre=/usr/libexec/vyos/init/vyos-config ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-http-api-server -Type=idle +Type=notify KillMode=process SyslogIdentifier=vyos-http-api -- cgit v1.2.3 From 8d50cb7a4044049b8287ce9b076fbc6b7bb1cfef Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sat, 23 May 2020 07:52:31 +0100 Subject: http-api: T2494: remove inaccurate systemd comment --- src/systemd/vyos-http-api.service | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/systemd/vyos-http-api.service b/src/systemd/vyos-http-api.service index 636973be7..e336ed18b 100644 --- a/src/systemd/vyos-http-api.service +++ b/src/systemd/vyos-http-api.service @@ -14,7 +14,6 @@ SyslogFacility=daemon Restart=on-failure -# Does't work but leave it here User=root Group=vyattacfg -- cgit v1.2.3 From 4d6f9a3f803d1f4a5735867ae3ea124deedbdae1 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sat, 23 May 2020 08:29:16 +0100 Subject: hostsd: T2494: systemd remove inaccurate systemd comment --- src/systemd/vyos-hostsd.service | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/systemd/vyos-hostsd.service b/src/systemd/vyos-hostsd.service index 418601d1a..560e04052 100644 --- a/src/systemd/vyos-hostsd.service +++ b/src/systemd/vyos-hostsd.service @@ -22,7 +22,6 @@ SyslogFacility=daemon Restart=on-failure -# Does't work in Jessie but leave it here User=root Group=hostsd -- cgit v1.2.3 From 54b92aea418ddca07b9f699466411e58ddfc52c3 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sat, 23 May 2020 09:03:10 +0100 Subject: hostsd: T2494: vyos-hostsd is part of systemd vyos.target The install section determine if the package should be enabled. vyos-hostd should be install if vyos.target is enabled. --- src/systemd/vyos-hostsd.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/systemd/vyos-hostsd.service b/src/systemd/vyos-hostsd.service index 560e04052..d10a0a939 100644 --- a/src/systemd/vyos-hostsd.service +++ b/src/systemd/vyos-hostsd.service @@ -30,4 +30,4 @@ Group=hostsd # Note: After= doesn't actually create a dependency, # it just sets order for the case when both services are to start, # and without RequiredBy it *does not* set vyos-hostsd to start. -RequiredBy=cloud-init-local.service vyos-router.service +WantedBy=vyos.target -- cgit v1.2.3 From 1ecb1d76cd0b149d6381a143adc9907adee47607 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sat, 23 May 2020 09:05:43 +0100 Subject: http-api: T2494: remove "barrier" PreExec The PreExec is making sure that the vyos-config-status file exists and blocks until it does. This file is created on boot completion and I can see no reason why the http service has to wait for the end of boot to start. Any barrier to start should be done with systemd itself. --- src/systemd/vyos-http-api.service | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/systemd/vyos-http-api.service b/src/systemd/vyos-http-api.service index e336ed18b..a2920376e 100644 --- a/src/systemd/vyos-http-api.service +++ b/src/systemd/vyos-http-api.service @@ -4,7 +4,6 @@ After=auditd.service systemd-user-sessions.service time-sync.target vyos-router. Requires=vyos-router.service [Service] -ExecStartPre=/usr/libexec/vyos/init/vyos-config ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-http-api-server Type=notify KillMode=process -- cgit v1.2.3 From 8cd23ec0449b3755240b9aebe236fdb66937d7d6 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sat, 23 May 2020 15:02:47 +0100 Subject: http-api: T2494: systemd http-api has no link getty.target WantedBy is about the service installation and is not related to the boot order, linking to vyos.target instead --- src/systemd/vyos-http-api.service | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/systemd/vyos-http-api.service b/src/systemd/vyos-http-api.service index a2920376e..74a97d193 100644 --- a/src/systemd/vyos-http-api.service +++ b/src/systemd/vyos-http-api.service @@ -17,6 +17,4 @@ User=root Group=vyattacfg [Install] -# Installing in a earlier target leaves ExecStartPre waiting -WantedBy=getty.target - +WantedBy=vyos.target -- cgit v1.2.3 From be8cda7f711a7a26c85b51976c299a6837750a63 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Mon, 25 May 2020 17:33:07 +0100 Subject: http-api: T2494: start before vyos-router vyos-router may/is requiring access to the service, make sure it starts before, if not is has no consequence to do so anyway. --- src/systemd/vyos-hostsd.service | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/systemd/vyos-hostsd.service b/src/systemd/vyos-hostsd.service index d10a0a939..26e9c745e 100644 --- a/src/systemd/vyos-hostsd.service +++ b/src/systemd/vyos-hostsd.service @@ -1,5 +1,6 @@ [Unit] Description=VyOS DNS configuration keeper +Before=vyos-router.service # Without this option, lots of default dependencies are added, # among them network.target, which creates a dependency cycle -- cgit v1.2.3 From cc95d6f89282c9d66120f2a9b40a9aaff2f981bb Mon Sep 17 00:00:00 2001 From: fmertz Date: Wed, 24 Jun 2020 16:25:21 +0000 Subject: system display: T2564 Extend VyOS to support appliance LCDs Added support for system LCDs under CLI system display +data/templates/system-display/LCDd.conf.tmpl template for LCDd server configuration file +data/templates/system-display/lcdproc.conf.tmpl template for lcdproc client configuration file +interface-definitions/system-display.xml.in CLI for system display +src/conf_mode/system-display.py processing code for system display +src/systemd/lcdproc.service systemd service definition file for lcdproc client CLI: system display model (SDEC|EZIO) system display config (enabled|disabled) system display show host (cpu|cpu-all|cpu-hist|disk|load-hist|memory|proc|uptime) network interface alias units (bps|Bps|pps) clock (big|mini|date-time) title system display duration system display hello system display bye --- data/templates/system-display/LCDd.conf.tmpl | 1525 +++++++++++++++++++++++ data/templates/system-display/lcdproc.conf.tmpl | 177 +++ interface-definitions/system-display.xml.in | 245 ++++ src/conf_mode/system-display.py | 92 ++ src/systemd/lcdproc.service | 10 + 5 files changed, 2049 insertions(+) create mode 100644 data/templates/system-display/LCDd.conf.tmpl create mode 100644 data/templates/system-display/lcdproc.conf.tmpl create mode 100644 interface-definitions/system-display.xml.in create mode 100755 src/conf_mode/system-display.py create mode 100644 src/systemd/lcdproc.service (limited to 'src') diff --git a/data/templates/system-display/LCDd.conf.tmpl b/data/templates/system-display/LCDd.conf.tmpl new file mode 100644 index 000000000..0b605e09d --- /dev/null +++ b/data/templates/system-display/LCDd.conf.tmpl @@ -0,0 +1,1525 @@ +### 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 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 ? +# IMPORTANT: Make sure to change this setting to reflect your +# specific setup! Otherwise LCDd won't be able to find +# the driver modules and will thus not be able to +# function properly. +# 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 == 'SDEC' %} +Driver=sdeclcd +{%- endif %} + +{%- if model == 'EZIO' %} +Driver=hd44780 +{%- 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="{%- if hello %}{{ hello }}{%- else %}Welcome to VyOS{%- endif %}" + +# GoodBye message: each entry represents a display line; default: builtin +GoodBye="{%- if bye %}{{ bye }}{%- else %}Bye from VyOS{%- endif %}" + +# Sets the interval in microseconds for updating the display. +# [default: 125000 meaning 8Hz] +#FrameInterval=125000 + +# Sets the default time in seconds to displays a screen. [default: 4] +WaitTime={%- if duration %}{{ duration }}{%- else%}4{%- endif %} + +# 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=off + +# 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=no + +# 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=open + +# 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=open + +# set title scrolling speed [default: 10; legal: 0-10] +#TitleSpeed=10 + +# The "...Key=" lines define what the server does with keypresses that +# don't go to any client. The ToggleRotateKey stops rotation of screens, while +# the PrevScreenKey and NextScreenKey go back / forward one screen (even if +# rotation is disabled. +# Assign the key string returned by the driver to the ...Key setting. These +# are the defaults: +ToggleRotateKey=Enter +PrevScreenKey=Left +NextScreenKey=Right +#ScrollUpKey=Up +#ScrollDownKey=Down + +## The menu section. The menu is an internal LCDproc client. ## +[menu] +# If true the server allows transitions between different client's menus +# [default: false; legal: true, false] +#PermissiveGoto=false + +# You can configure what keys the menu should use. Note that the MenuKey +# will be reserved exclusively, the others work in shared mode. + +# Up to six keys are supported. The MenuKey (to enter and exit the menu), the +# EnterKey (to select values) and at least one movement keys are required. +# These are the default key assignments: +MenuKey=Escape +EnterKey=Enter +UpKey=Up +DownKey=Down +#LeftKey=Left +#RightKey=Right + + +### Driver sections are below this line, in alphabetical order ### + + +## EMAC BayRAD driver ## +[bayrad] + +# Select the output device to use [default: /dev/lcd] +Device=/dev/lcd + +# Set the communication speed [default: 9600; legal: 1200, 2400, 9600, 19200] +Speed=9600 + + + +## CrystalFontz driver (for CF632 & CF634) ## +[CFontz] + +# Select the output device to use [default: /dev/lcd] +Device=/dev/ttyS0 +# Select the LCD size [default: 20x4] +Size=20x4 +# Set the initial contrast [default: 560; legal: 0 - 1000] +Contrast=350 +# Set the initial brightness [default: 1000; legal: 0 - 1000] +Brightness=1000 +# Set the initial off-brightness [default: 0; legal: 0 - 1000] +# This value is used when the display is normally +# switched off in case LCDd is inactive +OffBrightness=0 +# Set the communication speed [default: 9600; legal: 1200, 2400, 9600, 19200, +# 115200] +Speed=9600 +# Set the firmware version (New means >= 2.0) [default: no; legal: yes, no] +NewFirmware=no +# Reinitialize the LCD's BIOS [default: no; legal: yes, no] +# normally you shouldn't need this +Reboot=no + + + +## CrystalFontz packet driver (for CFA533, CFA631, CFA633 & CFA635) ## +[CFontzPacket] + +# Select the LCD model [default: 633; legal: 533, 631, 633, 635] +Model=633 + +# Select the output device to use [default: /dev/lcd] +Device=/dev/ttyS1 + +# Set the initial contrast [default: 560; legal: 0 - 1000] +Contrast=350 + +# Set the initial brightness [default: 1000; legal: 0 - 1000] +Brightness=1000 + +# Set the initial off-brightness [default: 0; legal: 0 - 1000] +# This value is used when the display is normally +# switched off in case LCDd is inactive +OffBrightness=50 + +# Reinitialize the LCD's BIOS on driver start. [default: no; legal: yes, no] +Reboot=yes + +# Enable the USB flag if the device is connected to an USB port. For +# serial ports leave it disabled. [default: no; legal: yes, no] +#USB=yes + +# Very old 633 firmware versions do not support partial screen updates using +# 'Send Data to LCD' command (31). For those devices it may be necessary to +# enable this flag. [default: no; legal: yes, no] +#OldFirmware=yes + +# Override the LCD size known for the selected model. Usually setting this +# value should not be necessary. +#Size=20x4 + +# Override the default communication speed known for the selected model. +# Default value depends on model [legal: 19200, 115200] +#Speed=115200 + + + +## Curses driver ## +[curses] + +# color settings +# foreground color [default: blue] +Foreground=blue +# background color when "backlight" is off [default: cyan] +Background=cyan +# background color when "backlight" is on [default: red] +Backlight=red + +# display size [default: 20x4] +Size=20x2 + +# What position (X,Y) to start the left top corner at... +# Default: (7,7) +TopLeftX=7 +TopLeftY=7 + +# use ASC symbols for icons & bars [default: no; legal: yes, no] +UseACS=no + +# draw Border [default: yes; legal: yes, no] +DrawBorder=yes + + + +## Cwlinux driver ## +[CwLnx] + +# Select the LCD model [default: 12232; legal: 12232, 12832, 1602] +Model=12232 + +# Select the output device to use [default: /dev/lcd] +Device=/dev/ttyUSB0 + +# Select the LCD size. Default depends on model: +# 12232: 20x4 +# 12832: 21x4 +# 1602: 16x2 +Size=20x4 + +# Set the communication speed [default: 19200; legal: 9600, 19200] +Speed=19200 + +# Reinitialize the LCD's BIOS [default: no; legal: yes, no] +# normally you shouldn't need this +Reboot=no + +# If you have a keypad connected. Keypad layout is currently not +# configureable from the config file. +Keypad=yes + +# If you have a non-standard keypad you can associate any keystrings to keys. +# There are 6 input keys in the CwLnx hardware that generate characters +# from 'A' to 'F'. +# +# The following is the built-in default mapping hardcoded in the driver. +# You can leave those unchanged if you have a standard keypad. +# You can change it if you want to report other keystrings or have a non +# standard keypad. +# KeyMap_A=Up +# KeyMap_B=Down +# KeyMap_C=Left +# KeyMap_D=Right +# KeyMap_E=Enter +# KeyMap_F=Escape + +# keypad_test_mode permits one to test keypad assignment +# Default value is no +#keypad_test_mode=yes + + + +## ea65 driver for the display in AOpen XC Cube AV EA65 media barebones ## +[ea65] + +# Device is fixed /dev/ttyS1 +# Width and Height are fixed 9x1 + +# As the VFD is self luminescent we don't have a backlight +# But we can use the backlight functions to control the front LEDs +# Brightness 0 to 299 -> LEDs off +# Brightness 300 to 699 -> LEDs half bright +# Brightness 700 to 1000 -> LEDs full bright +Brightness=500 +# OffBrightness is the the value used for the 'backlight off' state +OffBrightness=0 + + + +## EyeboxOne driver ## +[EyeboxOne] + +# Select the output device to use [default: /dev/ttyS1] +# Device=/dev/cua01 +Device=/dev/ttyS1 + +# Set the display size [default: 20x4] +Size=20x4 + +# Switch on the backlight? [default: yes] +Backlight=yes + +# Switch on the cursor? [default: no] +Cursor=no + +# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200] +Speed=19200 + +# Enter Key is a \r character, so it's hardcoded in the driver +LeftKey=D +RightKey=C +UpKey=A +DownKey=B +EscapeKey=P + +# You can find out which key of your display sends which +# character by setting keypad_test_mode to yes and running +# LCDd. LCDd will output all characters it receives. +# Afterwards you can modify the settings above and set +# keypad_set_mode to no again. +keypad_test_mode=no + +## Futaba TOSD-5711BB VFD Driver ## +[futaba] + +## g15 driver for Logitech G15 Keyboard LCDs ## +[g15] + +# Display size (currently unused) +size=20x5 + + + +## glcd generic graphical display driver +[glcd] +# Select what type of connection. See documentation for types. +ConnectionType=t6963 + +# Width and height of the display in pixel. The supported sizes may depend on +# the ConnectionType. [default: 128x64; legal: 1x1 - 640x480] +#Size=128x64 + +# Width and height of a character cell in pixels. This value is only used if +# the driver has been compiled with FreeType and it is enabled. Otherwise the +# default 6x8 cell is used. +#CellSize=12x16 + +# If LCDproc has been compiled with FreeType 2 support this option can be used +# to turn if off intentionally. [default: yes; legal: yes, no] +#useFT2=no + +# Path to font file to use for FreeType rendering. This font must be monospace +# and should contain some special Unicode characters like arrows (Andale Mono +# is recommended and can be fetched at http://corefonts.sf.net). +#normal_font=/usr/local/lib/X11/fonts/TTF/andalemo.ttf + +# Some fonts miss the Unicode characters used to represent icons. In this case +# the built-in 5x8 font can used if this option is turned off. [default: yes; +# legal: yes, no] +#fontHasIcons=no + +# Set the initial contrast if supported by connection type. +# [default: 600; legal: 0 - 1000] +#Contrast=600 + +# Set brightness of the backlight if the backlight is switched 'on'. +# [default: 800; legal: 0 - 1000] +#Brightness=1000 + +# Set brightness of the backlight if the backlight is switched 'off'. Set this +# to zero to completely turn off the backlight. [default: 100; legal: 0 - 1000] +#OffBrightness=0 + +# Time (ms) from first key report to first repeat. Set to 0 to disable repeated +# key reports. [default: 500; legal: 0 - 3000] +#KeyRepeatDelay=500 + +# Time (ms) between repeated key reports. Ignored if KeyRepeatDelay is disabled +# (set to zero). [default: 300; legal: 0 - 3000] +#KeyRepeatInterval=300 + +# Assign key strings to keys. There may be up to 16 keys numbered 'A' to 'Z'. +# By default keys 'A' to 'F' are assigned Up, Down, Left, Right, Enter, Escape. +KeyMap_A=Up +KeyMap_B=Down +KeyMap_C=Enter +KeyMap_D=Escape + +# --- t6963 options --- + +# Parallel port to use [default: 0x378; legal: 0x200 - 0x400] +#Port=0x378 + +# Use LPT port in bi-directional mode. This should work on most LPT port +# and is required for proper timing! [default: yes; legal: yes, no] +#bidirectional=yes + +# Insert additional delays into reads / writes. [default: no; legal: yes, no] +#delayBus=no + +# --- serdisplib options --- + +# Name of the underlying serdisplib driver, e.g. ctinclud. See +# serdisplib documentation for details. +serdisp_name=t6963 + +# The display device to use, e.g. serraw:/dev/ttyS0, +# parport:/dev/parport0 or USB:07c0/1501. +serdisp_device=/dev/ppi0 + +# Options string to pass to serdisplib during initialization. Use +# this to set any display related options (e.g. wiring). The display size is +# always set based on the Size configured above! By default, no options are +# set. +# Important: The value must be quoted as it contains equal signs! +#serdisp_options="INVERT=1" + +# --- x11 options --- + +# PixelSize is size of each dot in pixels + a pixel gap. [default: 3+1] +#x11_PixelSize=3+1 + +# Colors are in RRGGBB format prefixed with "0x". +# PixelColor: The color of each dot at full contrast. [default: 0x000000] +#x11_PixelColor=0x000000 + +# BacklightColor: The color of the backlight as full brightness. +# [default: 0x80FF80] +#x11_BacklightColor=0x80FF80 + +# Border: Adds a border (empty space) around the LCD portion of X11 window. +# [default: 20] +#x11_Border=20 + +# Inverted: inverts the pixels [default: no; legal: yes, no] +#x11_Inverted=no + +# --- picolcdgfx options --- + +# Time in ms for usb_read to wait on a key press. [default: 125; legal: >0] +#picolcdgfx_KeyTimeout=125 + +# Inverted: Inverts the pixels. [default: no; legal: yes or no] +#picolcdgfx_Inverted=no + + + +## glcdlib meta driver for graphical LCDs ## +[glcdlib] + +## mandatory: + +# which graphical display supported by graphlcd-base to use [default: image] +# (see /etc/graphlcd.conf for possible drivers) +Driver=noritake800 + +# no=use graphlcd bitmap fonts (they have only one size / font file) +# yes=use fonts supported by FreeType2 (needs Freetype2 support in +# libglcdprocdriver and its dependants) +UseFT2=yes + +# text resolution in fixed width characters [default: 16x4] +# (if it won't fit according to available physical pixel resolution +# and the minimum available font face size in pixels, then +# 'DebugBorder' will automatically be turned on) +TextResolution=20x4 + +# path to font file to use +FontFile=/usr/share/fonts/corefonts/courbd.ttf + +## these only apply if UseFT2=yes: + +# character encoding to use +CharEncoding=iso8859-2 + +# minimum size in pixels in which fonts should be rendered +MinFontFaceSize=7x12 + +## optional: +Brightness=50 # Brightness (in %) if applicable +Contrast=50 # Contrast (in %) if applicable +Backlight=no # Backlight if applicable +UpsideDown=no # flip image upside down +Invert=no # invert light/dark pixels +ShowDebugFrame=no # turns on/off 1 pixel thick debugging + # border within the usable text area, + # for setting up TextResolution and + # MinFontFaceSize (if using FT2); +ShowBigBorder=no # border around the unused area +ShowThinBorder=yes # border around the unused area +PixelShiftX=0 +PixelShiftY=2 + + + +## Matrix Orbital GLK driver ## +[glk] + +# select the serial device to use [default: /dev/lcd] +Device=/dev/lcd + +# set the initial contrast value [default: 500; legal: 0 - 1000] +Contrast=500 + +# set the serial port speed [default: 19200; legal: 9600, 19200, 38400, 57600, 115200] +Speed=19200 + + + +## Hitachi HD44780 driver ## +[hd44780] +{%- if model == 'EZIO' %} +ConnectionType=ezio +Device=/dev/ttyS1 +Keypad=yes +Size=16x2 +KeyMatrix_4_1=Enter +KeyMatrix_4_2=Up +KeyMatrix_4_3=Down +KeyMatrix_4_4=Escape +{%- endif %} + +# Select what type of connection. See documentation for available types. +#ConnectionType=4bit + +# Select model if have non-standard one which require extra initialization or handling or +# just want extra features it offers. +# Available: standard (default), extended, winstar_oled, pt6314_vfd +# - standard is default, use for LCDs not mentioned below. +# - extended, hd66712, ks0073: allows use 4-line "extended" mode, +# same as deprecated now option ExtendedMode=yes +# - winstar_oled, weh00xxyya: changes initialization for WINSTAR's WEH00xxyyA displays +# and allows handling brightness +# - pt6314_vfd: allows handling brightness on PTC's PT6314 VFDs +# +# This option should be independent of connection type. +#Model = standard + +# I/O address of the LPT port. Usual values are: 0x278, 0x378 and 0x3BC. +# For I2C connections this sets the slave address (usually 0x20). +#Port=0x378 + +# Device of the serial, I2C, or SPI interface [default: /dev/lcd] +#Device=/dev/ttyS0 + +# Bitrate of the serial port (0 for interface default) +#Speed=0 + +# If you have a keypad connected. +# You may also need to configure the keypad layout further on in this file. +#Keypad=no + +# Set the initial contrast (bwctusb, lcd2usb, and usb4all) +# [default: 800; legal: 0 - 1000] +#Contrast=0 + +# Set brightness of the backlight (lcd2usb and usb4all): +# Brightness is the brightness while the backlight is set to 'on'. +# [default: 800; legal: 0 - 1000] +#Brightness=1000 + +# OffBrightness is the brightness while the backlight is set to 'off'. +# [default: 300; legal: 0 - 1000] +#OffBrightness=0 + +# Specify if you have a switchable backlight and if yes, can select method for turning it on/off: +# +# - none - no switchable backlight is available. For compability also boolean +# 0, n, no, off and false are aliases. +# - external - use external pin or any other method defined with ConnectionType backlight +# handling. For backward compability also this value is chosen for boolean +# TRUE values: 1, y, yes, on and true. +# - internal - means that backlight is handled using internal commands according +# to selected display model (with Model option). Depending on model, +# Brightness and OffBrightness options can be taken into account. +# - internalCmds - means that commands for turning on and off backlight are given +# with extra options BacklightOnCmd and BacklightOffCmd, which would be treated +# as catch up (last resort) for other types of displays which have similar features. +# +# You can provide multiple occurences of this option to use more than one method. +# Default is model specific: Winstar OLED and PT6314 VFD enables internal backlight mode, +# for others it is set to none. +#Backlight = none + +# Commands for enabling internal backlight for use with Backlight=internalCmds. +# Up to 4 bytes can be encoded, as integer number in big-endian order. +# +# NOTE: this is advanced option, if command contains bits other than only brighness handling, +# they must be set accordingly to not disrupt display state. If for example 'FUNCTION SET' command +# is used for this purpose, bits of interface length (4-bit / 8-bit) must be set according to +# selected ConnectionType. +#BacklightCmdOn=0x1223 + +# Commands for disabling internal backlight for use with Backlight=internalCmds. +# Up to 4 bytes can be encoded, as integer number in big-endian order. +#BacklightCmdOff=0x1234 + + +# If you have the additional output port ("bargraph") and you want to +# be able to control it with the lcdproc OUTPUT command +#OutputPort=no + +# Specifies if the last line is pixel addressable (yes) or it controls an +# underline effect (no). [default: yes; legal: yes, no] +#Lastline=yes + +# Specifies the size of the LCD. +# In case of multiple combined displays, this should be the total size. +#Size=20x4 + +# For multiple combined displays: how many lines does each display have. +# Vspan=2,2 means both displays have 2 lines. +#vspan=2,2 + +# If you have an HD66712, a KS0073 or another controller with 'extended mode', +# set this flag to get into 4-line mode. On displays with just two lines, do +# not set this flag. +# As an additional restriction, controllers with and without extended mode +# AND 4 lines cannot be mixed for those connection types that support more +# than one display! +# NOTE: This option is deprecated in favour of choosing Model=extended option. +#ExtendedMode=yes + +# In extended mode, on some controllers like the ST7036 (in 3 line mode) +# the next line in DDRAM won't start 0x20 higher. [default: 0x20] +#LineAddress=0x10 + +# Character map to to map ISO-8859-1 to the LCD's character set +# [default: hd44780_default; legal: hd44780_default, hd44780_euro, ea_ks0073, +# sed1278f_0b, hd44780_koi8_r, hd44780_cp1251, hd44780_8859_5, upd16314, +# weh001602a_1] +# (hd44780_koi8_r, hd44780_cp1251, hd44780_8859_5, upd16314 and weh001602a_1 +# are possible if compiled with additional charmaps) +CharMap=hd44780_default + +# Font bank to be used for some displays such as the WINSTAR WEH001602A +# 0: English/Japanese (default) +# 1: Western Europe I +# 2: English/Rusian +# 3: Western Europe II +#FontBank=0 + +# If your display is slow and cannot keep up with the flow of data from +# LCDd, garbage can appear on the LCDd. Set this delay factor to 2 or 4 +# to increase the delays. Default: 1. +#DelayMult=2 + +# Some displays (e.g. vdr-wakeup) need a message from the driver to that it +# is still alive. When set to a value bigger then null the character in the +# upper left corner is updated every seconds. Default: 0. +#KeepAliveDisplay=0 + +# If you experience occasional garbage on your display you can use this +# option as workaround. If set to a value bigger than null it forces a +# full screen refresh seconds. Default: 0. +#RefreshDisplay=5 + +# You can reduce the inserted delays by setting this to false. +# On fast PCs it is possible your LCD does not respond correctly. +# Default: true. +#DelayBus=true + +# If you have a keypad you can assign keystrings to the keys. +# See documentation for used terms and how to wire it. +# For example to give directly connected key 4 the string "Enter", use: +# KeyDirect_4=Enter +# For matrix keys use the X and Y coordinates of the key: +# KeyMatrix_1_3=Enter +#KeyMatrix_4_1=Enter +#KeyMatrix_4_2=Up +#KeyMatrix_4_3=Down +#KeyMatrix_4_4=Escape + +## ICP Peripheral Comminication Protocol driver ## +# Supports A125 and A106 +# +# Short Press Select: Down +# Long Press Select: Up +# Short Press Enter: Enter +# Long Press Enter: Escape +# +[icp_a106] +Device=/dev/ttyS1 + +# Display dimensions +Size=20x2 + + +## Code Mercenaries IO-Warrior driver ## +[IOWarrior] + +# display dimensions +Size=20x4 + +# serial number. Must be exactly as listed by usbview +# (if not given, the 1st IOWarrior found gets used) +#SerialNumber=00000674 + +# If you have an HD66712, a KS0073 or another 'almost HD44780-compatible', +# set this flag to get into extended mode (4-line linear). +#ExtendedMode=yes + +# Specifies if the last line is pixel addressable (yes) or it controls an +# underline effect (no). [default: yes; legal: yes, no] +#Lastline=yes + + + +## Soundgraph/Ahanix/Silverstone/Uneed/Accent iMON driver ## +[imon] + +# select the device to use +Device=/dev/lcd0 + +# display dimensions +Size=16x2 + +# Character map to to map ISO-8859-1 to the displays character set. +# [default: none; legal: none, hd44780_euro, upd16314, hd44780_koi8_r, +# hd44780_cp1251, hd44780_8859_5 ] (upd16314, hd44780_koi8_r, +# hd44780_cp1251, hd44780_8859_5 are possible if compiled with additional +# charmaps) +CharMap=hd44780_euro + +## Soundgraph iMON LCD ## +[imonlcd] +# Specify which iMon protocol should be used +# [legal: 0, 1; default: 0] +# Choose 0 for 15c2:ffdc device, +# Choose 1 for 15c2:0038 device +Protocol=0 + +# Set the exit behavior [legal: 0-2; default: 1] +# 0 means leave shutdown message, +# 1 means show the big clock, +# 2 means blank device +#OnExit=2 + +# Select the output device to use [default: /dev/lcd0] +Device=/dev/lcd0 + +# Select the displays contrast [default: 200; legal: 0-1000] +Contrast=200 + +# Specify the size of the display in pixels [default: 96x16] +#Size=96x16 + +# Set the backlight state [default: on; legal: on, off] +#Backlight=on + +# Set the disc mode [legal: 0,1; default: 0] +# 0 => spin the "slim" disc - two disc segments, +# 1 => their complement spinning; +#DiscMode=0 + + + +## IrMan driver ## +[IrMan] +# in case of trouble with IrMan, try the Lirc emulator for IrMan + +# Select the input device to use +#Device=/dev/irman + +# Select the configuration file to use +#Config=/etc/irman.cfg + + + +## IRtrans driver ## +[irtrans] + +# Does the device have a backlight? [default: no; legal: yes, no] +#Backlight=no + +# IRTrans device to connect to [default: localhost] +#Hostname=localhost + +# display dimensions +Size=16x2 + + + +## Joystick driver ## +[joy] + +# Select the input device to use [default: /dev/js0] +Device=/dev/js0 + +# set the axis map +Map_Axis1neg=Left +Map_Axis1pos=Right +Map_Axis2neg=Up +Map_Axis2pos=Down + +# set the button map +Map_Button1=Enter +Map_Button2=Escape + + +## JW-002 driver ## +[jw002] + +# Select the output device to use [default: /dev/lcd] +#Device=/dev/ttyS0 + +# Set the display size [default: 24x8] +Size=24x8 + +# Optional X and Y offsets (in characters) to center a smaller display +# size on the full 24x8 panel +X_offset=0 +Y_offset=0 + +# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200] +Speed=19200 + +# Pick which font page to use [default: 0] +# Note that different fonts probably have their bargraph chars in different +# spots. For ROM-based fonts 0-3, those characters are already known. +Font=0 + +# The following table translates from jw002 key letters to logical key names. +# By default no keys are mapped, meaning the keypad is not used at all. +#KeyMap_I=Left +#KeyMap_J=Right +#KeyMap_H=Up +#KeyMap_K=Down +#KeyMap_L=Enter +#KeyMap_A=Escape +# See the [menu] section for an explanation of the key mappings + +# You can find out which key of your display sends which +# character by setting keypad_test_mode to yes and running +# LCDd. LCDd will output all characters it receives. +# Afterwards you can modify the settings above and set +# keypad_set_mode to no again. +keypad_test_mode=no + + +## LB216 driver ## +[lb216] + +# Select the output device to use [default: /dev/lcd] +Device=/dev/lcd + +# Set the initial brightness [default: 255; legal: 0 - 255] +Brightness=255 + +# Set the communication speed [default: 9600; legal: 2400, 9600] +Speed=9600 + +# Reinitialize the LCD's BIOS [default: no; legal: yes, no] +Reboot=no + + + +## LCDM001 driver ## +[lcdm001] + +Device=/dev/ttyS1 + +# keypad settings +# Keyname Function +# Normal context Menu context +# ------- -------------- ------------ +# PauseKey Pause/Continue Enter/select +# BackKey Back(Go to previous screen) Up/Left +# ForwardKey Forward(Go to next screen) Down/Right +# MainMenuKey Open main menu Exit/Cancel +PauseKey=LeftKey +BackKey=UpKey +ForwardKey=DownKey +MainMenuKey=RightKey + +# You can rearrange the settings here. +# If your device is broken, have a look at server/drivers/lcdm001.h + + + +## HNE LCTerm driver ## +[lcterm] +Device=/dev/ttyS1 +Size=16x2 + + +## Linux event device input driver ## +[linux_input] + +# Select the input device to use [default: /dev/input/event0]. This may be +# either an absolute path to the input node, starting with '/', or +# an input device name, e.g. "Logitech Gaming Keyboard Gaming Keys". +# Device=/dev/input/event0 + +# specify a non-default key map +#key=1,Escape +#key=28,Enter +#key=96,Enter +#key=105,Left +#key=106,Right +#key=103,Up +#key=108,Down + + +## LIRC input driver ## +[lirc] + +# Specify an alternative location of the lircrc file [default: ~/.lircrc] +#lircrc=/etc/lircrc.lcdproc + +# Must be the same as in your lircrc +#prog=lcdd + + + +## LIS MCE 2005 driver ## +[lis] + +# Set the initial brightness [default: 1000; legal: 0 - 1000] +# 0-250 = 25%, 251-500 = 50%, 501-750 = 75%, 751-1000 = 100% +#Brightness=1000 + +# Columns by lines [default: 20x2] +#Size=20x2 + +# USB Vendor ID [default: 0x0403] +# Change only if testing a compatible device. +#VendorID=0x0403 + +# USB Product ID [default: 0x6001] +# Change only if testing a compatible device. +#ProductID=0x6001 + +# Specifies if the last line is pixel addressable (yes) or it only controls an +# underline effect (no). [default: yes; legal: yes, no] +#Lastline=yes + + + +##The driver for the VFD of the Medion MD8800 PC ## +[MD8800] +# device to use [default: /dev/ttyS1] +#Device=/dev/ttyS1 + +# display size [default: 16x2] +#Size=16x2 + +# Set the initial brightness [default: 1000; legal: 0 - 1000] +Brightness=1000 +# Set the initial off-brightness [default: 0; legal: 0 - 1000] +# This value is used when the display is normally +# switched off in case LCDd is inactive +OffBrightness=50 + + + +## Futuba MDM166A Display +[mdm166a] +# Show self-running clock after LCDd shutdown +# Possible values: [default: no; legal: no, small, big] +Clock=big +# Dim display, no dimming gives full brightness [default: no, legal: yes, no] +Dimming=no +# Dim display in case LCDd is inactive [default: no, legal: yes, no] +OffDimming=yes + + + +## MSI MS-6931 driver for displays in 1HU servers ## +[ms6931] + +# device to use [default: /dev/ttyS1] +Device=/dev/ttyS1 + +# display size [default: 16x2] +#Size=16x2 + + + +## MTC-S16209x driver ## +[mtc_s16209x] + +# Select the output device to use [default: /dev/lcd] +Device=/dev/lcd + +# Set the initial brightness [default: 255; legal: 0 - 255] +Brightness=255 + +# Reinitialize the LCD's BIOS [default: no; legal: yes, no] +Reboot=no + + + +## Matrix Orbital driver ## +[MtxOrb] + +# Select the output device to use [default: /dev/lcd] +Device=/dev/ttyS0 + +# Set the display size [default: 20x4] +Size=20x4 + +# Set the display type [default: lcd; legal: lcd, lkd, vfd, vkd] +Type=lkd + +# Set the initial contrast [default: 480] +# NOTE: The driver will ignore this if the display +# is a vfd or vkd as they don't have this feature +Contrast=480 + +# Some old displays do not have an adjustable backlight but only can +# switch the backlight on/off. If you experience randomly appearing block +# characters, try setting this to false. [default: yes; legal: yes, no] +hasAdjustableBacklight=no + +# Set the initial brightness [default: 1000; legal: 0 - 1000] +Brightness=1000 +# Set the initial off-brightness [default: 0; legal: 0 - 1000] +# This value is used when the display is normally +# switched off in case LCDd is inactive +OffBrightness=0 + +# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200] +Speed=19200 + +# The following table translates from MtxOrb key letters to logical key names. +# By default no keys are mapped, meaning the keypad is not used at all. +#KeyMap_A=Left +#KeyMap_B=Right +#KeyMap_C=Up +#KeyMap_D=Down +#KeyMap_E=Enter +#KeyMap_F=Escape +# See the [menu] section for an explanation of the key mappings + +# You can find out which key of your display sends which +# character by setting keypad_test_mode to yes and running +# LCDd. LCDd will output all characters it receives. +# Afterwards you can modify the settings above and set +# keypad_set_mode to no again. +keypad_test_mode=no + + + +## mx5000 driver for LCD display on the Logitech MX5000 keyboard ## +[mx5000] + +# Select the output device to use [default: /dev/hiddev0] +Device = /dev/hiddev0 +# Time to wait in ms after the refresh screen has been sent [default: 1000] +WaitAfterRefresh = 1000 + + + +## Noritake VFD driver ## +[NoritakeVFD] +# device where the VFD is. Usual values are /dev/ttyS0 and /dev/ttyS1 +# [default: /dev/lcd] +Device=/dev/ttyS0 +# Specifies the size of the LCD. +Size=20x4 +# Set the initial brightness [default: 1000; legal: 0 - 1000] +Brightness=1000 +# Set the initial off-brightness [default: 0; legal: 0 - 1000] +# This value is used when the display is normally +# switched off in case LCDd is inactive +OffBrightness=50 +# set the serial port speed [default: 9600, legal: 1200, 2400, 9600, 19200, 115200] +Speed=9600 +# Set serial data parity [default: 0; legal: 0-2 ] +# Meaning: 0(=none), 1(=odd), 2(=even) +Parity=0 +# re-initialize the VFD [default: no; legal: yes, no] +Reboot=no + + + +## Olimex MOD-LCD1x9 driver ## +[Olimex_MOD_LCD1x9] + +# device file of the i2c controler +Device=/dev/i2c-0 + + +## Mini-box.com picoLCD (usblcd) driver ## +[picolcd] + +# KeyTimeout is only used if the picoLCD driver is built with libusb-0.1. When +# built with libusb-1.0 key and IR data is input asynchronously so there is no +# need to wait for the USB data. +# KeyTimeout is the time in ms that LCDd spends waiting for a key press before +# cycling through other duties. Higher values make LCDd use less CPU time and +# make key presses more detectable. Lower values make LCDd more responsive +# but a little prone to missing key presses. 500 (.5 second) is the default +# and a balanced value. [default: 500; legal: 0 - 1000] +KeyTimeout=500 + +# Key auto repeat is only available if the picoLCD driver is built with +# libusb-1.0. Use KeyRepeatDelay and KeyRepeatInterval to configure key auto +# repeat. +# +# Key auto repeat delay (time in ms from first key report to first repeat). Use +# zero to disable auto repeat. [default: 300; legal: 0 - 3000] +KeyRepeatDelay=300 + +# Key auto repeat interval (time in ms between repeat reports). Only used if +# KeyRepeatDelay is not zero. [default: 200; legal: 0 - 3000] +KeyRepeatInterval=200 + +# Sets the initial state of the backlight upon start-up. +# [default: on; legal: on, off] +#Backlight=on + +# Set the initial brightness [default: 1000; legal: 0 - 1000]. Works only +# with the 20x4 device +Brightness=1000 + +# Set the brightness while the backlight is 'off' [default: 0; legal: 0 - 1000]. +# Works only with the 20x4 device. +#OffBrightness=0 + +# Set the initial contrast [default: 1000; legal: 0 - 1000] +Contrast=1000 + +# Link the key lights to the backlight? [default: on; legal: on, off] +#LinkLights=off + +# Light the keys? [default: on; legal: on, off] +Keylights=on + +# If Keylights is on, the you can unlight specific keys below: +# Key0 is the directional pad. Key1 - Key5 correspond to the F1 - F5 keys. +# There is no LED for the +/- keys. This is a handy way to indicate to users +# which keys are disabled. [default: on; legal: on, off] +Key0Light=on +Key1Light=on +Key2Light=on +Key3Light=on +Key4Light=on +Key5Light=on + +# Host name or IP address of the LIRC instance that is to receive IR codes +# If not set, or set to an empty value, IR support is disabled. +#LircHost=127.0.0.1 + +# UDP port on which LIRC is listening [default: 8765; legal: 1 - 65535] +LircPort=8765 + +# UDP data time unit for LIRC [default: off; legal: on, off] +# On: times sent in microseconds (requires LIRC UDP driver that accepts this). +# Off: times sent in 'jiffies' (1/16384s) (supported by standard LIRC UDP driver). +LircTime_us=on + +# Threshold in microseconds of the gap that triggers flushing the IR data +# to lirc [default: 8000; legal: 1000 - ] +# If LircTime_us is on values greater than 32.767ms will disable the flush +# If LircTime_us is off values greater than 1.999938s will disable the flush +LircFlushThreshold=10000 + + + +## Pyramid LCD driver ## +[pyramid] + +# device to connect to [default: /dev/lcd] +Device=/dev/ttyUSB0 + + + +## rawserial driver ## +[rawserial] + +# Select the output device to use [default: /dev/cuaU0] +Device=/dev/ttyS0 + +# Serial port baudrate [default: 9600] +Speed=9600 + +# Specifies the size of the LCD. If this driver is loaded as a secondary driver +# it always adopts to the size of the primary driver. If loaded as the only +# (or primary) driver, the size can be set. [default: 40x4] +#Size=16x2 + +# How often to dump the LCD contents out the port, in Hertz (times per second) +# 1 = once per second, 4 is 4 times per second, 0.1 is once every 10 seconds. +# [default: 1; legal: 0.0005 - 10] +UpdateRate=1 + + + +## SDEC driver for Watchguard Firebox ## +[sdeclcd] +# No options + + +## Seiko Epson 1330 driver ## +[sed1330] + +# Port where the LPT is. Common values are 0x278, 0x378 and 0x3BC +Port=0x378 + +# Type of LCD module (legal: G321D, G121C, G242C, G191D, G2446, SP14Q002) +# Note: Currently only tested with G321D & SP14Q002. +Type=G321D + +# Width x Height of a character cell in pixels [legal: 6x7 - 8x16; default: 6x10] +CellSize=6x10 + +# Select what type of connection [legal: classic, bitshaker; default: classic] +ConnectionType=classic + + + +## Seiko Epson 1520 driver ## +[sed1520] + +# Port where the LPT is. Usual values are 0x278, 0x378 and 0x3BC +Port=0x378 + +# Select the interface type (wiring) for the display. Supported values are +# 68 for 68-style connection (RESET level high) and 80 for 80-style connection +# (RESET level low). [legal: 68, 80; default: 80] +InterfaceType=80 + +# On fast machines it may be necessary to slow down transfer to the display. +# If this value is set to zero, delay is disabled. Any value greater than +# zero slows down each write by one microsecond. [legal: 0-1000; default: 1] +DelayMult=0 + +# The original wiring used an inverter to drive the control lines. If you do +# not use an inverter set haveInverter to no. [default: yes; legal: yes, no] +HaveInverter=no + +# On some displays column data in memory is mapped to segment lines from right +# to left. This is called inverted mapping (not to be confused with +# 'haveInverter' from above). [default: no; legal: yes, no] +#InvertedMapping=yes + +# At least one display is reported (Everbouquet MG1203D) that requires sending +# three times 0xFF before a reset during initialization. +# [default: no; legal: yes, no] +#UseHardReset=yes + + +## serial POS display driver ## +[serialPOS] + +# Device to use in serial mode [default: /dev/ttyS0] +Device=/dev/ttyS0 + +# Specifies the size of the display in characters. [default: 16x2] +Size=16x2 + +# Specifies the cell size of each character cell on the display in characters. +# [default: 5x8] +Cellsize=5x8 + +# Specifies the number of custom characters supported by the display. +# [default: 0] +Custom_chars=0 + +# Set the communication protocol to use with the POS display. +# [default: AEDEX; legal: AEDEX, CD5220, Epson, Emax, LogicControls, Ultimate] +Type=AEDEX + +# communication baud rate with the display [default: 9600; legal: 1200, 2400, +# 4800, 9600, 19200, 115200] +Speed=9600 + + + +## Serial VFD driver ## +## Drives various (see below) serial 5x7dot VFD's. ## +[serialVFD] + +# Specifies the displaytype.[default: 0] +# 0 NEC (FIPC8367 based) VFDs. +# 1 KD Rev 2.1. +# 2 Noritake VFDs (*). +# 3 Futaba VFDs +# 4 IEE S03601-95B +# 5 IEE S03601-96-080 (*) +# 6 Futaba NA202SD08FA (allmost IEE compatible) +# 7 Samsung 20S207DA4 and 20S207DA6 +# 8 Nixdorf BA6x / VT100 +# (* most should work, not tested yet.) +Type=0 + +# "no" if display connected serial, "yes" if connected parallel. [default: no] +# I.e. serial by default +use_parallel=no + +# Number of Custom-Characters. default is display type dependent +#Custom-Characters=0 + +# Portaddress where the LPT is. Used in parallel mode only. Usual values are +# 0x278, 0x378 and 0x3BC. +Port=0x378 + +# Set parallel port timing delay (us). Used in parallel mode only. +# [default: 2; legal: 0 - 255] +#PortWait=2 + +# Device to use in serial mode. Usual values are /dev/ttyS0 and /dev/ttyS1 +Device=/dev/ttyS1 + +# Specifies the size of the VFD. +Size=20x2 + +# Set the initial brightness [default: 1000; legal: 0 - 1000] +# (4 steps 0-250, 251-500, 501-750, 751-1000) +Brightness=1000 +# Set the initial off-brightness [default: 0; legal: 0 - 1000] +# This value is used when the display is normally +# switched off in case LCDd is inactive +# (4 steps 0-250, 251-500, 501-750, 751-1000) +OffBrightness=0 + +# set the serial port speed [default: 9600; legal: 1200, 2400, 9600, 19200, 115200] +Speed=9600 + +# enable ISO 8859 1 compatibility [default: yes; legal: yes, no] +#ISO_8859_1=yes + + + +## shuttleVFD driver ## +[shuttleVFD] +# No options + + + +## stv5730 driver ## +[stv5730] + +# Port the device is connected to [default: 0x378] +Port=0x378 + + +[SureElec] + +# Port the device is connected to (by default first USB serial port) +Device=/dev/ttyUSB0 + +# Edition level of the device (can be 1, 2 or 3) [default: 2] +#Edition=1 + +# set display size +# Note: The size can be obtained directly from device for edition 2 & 3. +#Size=16x2 + +# Set the initial contrast [default: 480; legal: 0 - 1000] +#Contrast=200 + +# Set the initial brightness [default: 480; legal: 1 - 1000] +#Brightness=480 + +# Set the initial off-brightness [default: 100; legal: 1 - 1000] +# This value is used when the display is normally +# switched off in case LCDd is inactive +#OffBrightness=100 + + +## SVGAlib driver ## +[svga] + +# svgalib mode to use [default: G320x240x256 ] +# legal values are supported svgalib modes +#Mode=G640x480x256 + +# set display size [default: 20x4] +Size=20x4 + +# Set the initial contrast [default: 500; legal: 0 - 1000] +# Can be set but does not change anything internally +Contrast=500 + +# Set the initial brightness [default: 1000; legal: 1 - 1000] +Brightness=1000 + +# Set the initial off-brightness [default: 500; legal: 1 - 1000] +# This value is used when the display is normally +# switched off in case LCDd is inactive +OffBrightness=500 + + + +## Text driver ## +[text] +# Set the display size [default: 20x4] +Size=20x4 + + + +## Toshiba T6963 driver ## +[t6963] + +# set display size in pixels [default: 128x64] +Size=128x64 + +# port to use [default: 0x378; legal: 0x200 - 0x400] +Port=0x378 + +# Use LPT port in bi-directional mode. This should work on most LPT port and +# is required for proper timing! [default: yes; legal: yes, no] +#bidirectional=yes + +# Insert additional delays into reads / writes. [default: no; legal: yes, no] +#delayBus=no + +# Clear graphic memory on start-up. [default: no; legal: yes, no] +#ClearGraphic=no + + + +## Tyan Barebones LCD driver (GS10 & GS12 series) ## +[tyan] + +# Select the output device to use [default: /dev/lcd] +Device=/dev/lcd + +# Set the communication speed [default: 9600; legal: 4800, 9600] +Speed=9600 + +# set display size [default: 16x2] +Size=16x2 + + + +## ELV ula200 driver ## +[ula200] + +# Select the LCD size [default: 20x4] +Size=20x4 + +# If you have a non standard keypad you can associate any keystrings to keys. +# There are 6 input key in the CwLnx hardware that generate characters +# from 'A' to 'F'. +# +# The following it the built-in default mapping hardcoded in the driver. +# You can leave those unchanged if you have a standard keypad. +# You can change it if you want to report other keystrings or have a non +# standard keypad. +# KeyMap_A=Up +# KeyMap_B=Down +# KeyMap_C=Left +# KeyMap_D=Right +# KeyMap_E=Enter +# KeyMap_F=Escape + + + +## Wirz SLI LCD driver ## +[sli] + +# Select the output device to use [default: /dev/lcd] +Device=/dev/lcd + +# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200, +# 38400, 57600, 115200] +Speed=19200 + + + +## vlsys_m428 for VFD/IR combination in Moneual MonCaso 320 ## +[vlsys_m428] + +# Select the output device to use [default: /dev/ttyUSB0] +#Device=/dev/ttyUSB0 + + + +## OnScreen Display using libxosd ## +[xosd] + +# set display size [default: 20x4] +Size=20x4 + +# Offset in pixels from the top-left corner of the monitor [default: 0x0] +Offset=200x200 + +# X font to use, in XLFD format, as given by "xfontsel" +Font=-*-terminus-*-r-*-*-*-320-*-*-*-*-* + +## Y.A.R.D.2 LCD section +[yard2LCD] +Size=20x4 +# If rendering rate is too high, change in server\main.h #define RENDER_FREQ 8 to "1" + +# EOF diff --git a/data/templates/system-display/lcdproc.conf.tmpl b/data/templates/system-display/lcdproc.conf.tmpl new file mode 100644 index 000000000..2fe491e63 --- /dev/null +++ b/data/templates/system-display/lcdproc.conf.tmpl @@ -0,0 +1,177 @@ +### autogenerated by system-display.py ### + +# system display show host (CPU|SMP-CPU|CPU-Graph|Load|Memory|Proc-Size|Disk|Uptime) +# network interface alias +# units (bps|Bps|pps) +# clock (big|mini|date-time) + +# LCDproc client configuration file + +## general options ## +[lcdproc] +# address of the LCDd server to connect to +Server=127.0.0.1 + +# Port of the server to connect to +Port=13666 + +# set reporting level +#ReportLevel=2 + +# report to to syslog ? +ReportToSyslog=true + +# run in foreground [default: false; legal: true, false] +#Foreground=true + +# PidFile location when running as daemon [default: /var/run/lcdproc.pid] +#PidFile=/var/run/lcdproc.pid + +# slow down initial announcement of modes (in 1/100s) +#delay=2 + + +## screen specific configuration options ## +{%- if show %} +# display name for the main menu [default: LCDproc HOST] +DisplayName="{%- if show['title'] %}{{ show['title'] }}{%- else %}VyOS{%- endif %}" + +{%- if show['host'] %} +{#%- for s in show['host'] %#} + +[CPU] +# Show screen +Active={%- if 'cpu' in show['host'] %}true{%- else %}false{%- endif %} +OnTime=1 +OffTime=2 +ShowInvisible=false + +[SMP-CPU] +# Show screen +Active={%- if 'cpu-all' in show['host'] %}true{%- else %}false{%- endif %} + +[Memory] +# Show screen +Active={%- if 'memory' in show['host'] %}true{%- else %}false{%- endif %} + +[Load] +# Show screen +Active={%- if 'load-hist' in show['host'] %}true{%- else %}false{%- endif %} +# Min Load Avg at which the backlight will be turned off [default: 0.05] +LowLoad=0.05 +# Max Load Avg at which the backlight will start blinking [default: 1.3] +HighLoad=1.3 + +[Uptime] +# Show screen +Active={%- if 'uptime' in show['host'] %}true{%- else %}false{%- endif %} + +[CPUGraph] +# Show screen +Active={%- if 'cpu-hist' in show['host'] %}true{%- else %}false{%- endif %} + +[ProcSize] +# Show screen +Active={%- if 'proc' in show['host'] %}true{%- else %}false{%- endif %} + +[Disk] +# Show screen +Active={%- if 'disk' in show['host'] %}true{%- else %}false{%- endif %} +{#%- endfor %#} +{%- else %} {# if show['host'] #} +{# Turn off sections that default active #} + +[CPU] +Active=false + +[Memory] +Active=false + +[Load] +Active=false + +[TimeDate] +Active=false +{%- endif %} {# if show['host'] #} + +[TimeDate] +# Show screen +Active={%- if show['clock'] == 'date-time' %}true{%- else %}false{%- endif %} +# time format [default: %H:%M:%S; legal: see strftime(3)] +TimeFormat="%H:%M:%S" +# date format [default: %x; legal: see strftime(3)] +DateFormat="%x" + +[BigClock] +# Show screen +Active={%- if show['clock'] == 'big' %}true{%- else %}false{%- endif %} + +[MiniClock] +# Show screen +Active={%- if show['clock'] == 'mini' %}true{%- else %}false{%- endif %} +# time format [default: %H:%M; legal: see strftime(3)] +TimeFormat="%H:%M" + +{%- if show['network'] %} +[Iface] +# Show screen +Active={%- if show['network']['interface'] %}true{%- else %}false{%- endif %} +{%- for i in show['network']['interface'] %} +# Show stats for Interface {{ i }} +Interface0={{ i }} +{%- if show['network']['interface'][i]['alias'] %} +# Interface alias name to display [default: ] +Alias0={{ show['network']['interface'][i]['alias'] }} +{%- endif %} +{%- endfor %} + +# Units to display [default: byte; legal: byte, bit, packet] +{%- if show['network']['units'] == 'bps' %} +unit=bit +{%- elif show['network']['units'] == 'Bps'%} +unit=byte +{%- elif show['network']['units'] == 'pps' %} +unit=packet +{%- else %} +unit=bit +{%- endif %} +# add screen with transferred traffic +#transfer=TRUE +{%- endif %} {# if show['network'] #} + +{%- else %} {# if show #} +{# Turn off sections that default active #} + +[CPU] +Active=false + +[Memory] +Active=false + +[Load] +Active=false + +[TimeDate] +Active=false +{%- endif %} + +[Battery] +# Show screen +Active=false + +[About] +# Show screen +Active=false + +[OldTime] +# Show screen +Active=false +# time format [default: %H:%M:%S; legal: see strftime(3)] +TimeFormat="%H:%M:%S" +# date format [default: %x; legal: see strftime(3)] +DateFormat="%x" +# 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 + +# EOF diff --git a/interface-definitions/system-display.xml.in b/interface-definitions/system-display.xml.in new file mode 100644 index 000000000..7a5cf9283 --- /dev/null +++ b/interface-definitions/system-display.xml.in @@ -0,0 +1,245 @@ + + + + + + + + System display LCD/VFD/LED + 400 + + + + + + Model of the display attached to this system [REQUIRED] + + SDEC EZIO + + + (SDEC|EZIO) + + Invalid system display model + + SDEC + Display model for Lanner, Watchguard, Nexcom NSA, Sophos UTM appliances with built-in SDEC LCD + + + EZIO + Display model for Portwell, Caswell appliances with built-in EZIO-100 or EZIO-300 LCD + + + + + + + Configuration status + + enabled disabled + + + (enabled|disabled) + + Invalid system display config status + + enabled + Turn system display configuration ON + + + disabled + Turn system display configuration OFF + + + + + + Select the screens for the system display [REQUIRED] + + + + + + + Select host screens for the system display + + cpu cpu-all cpu-hist disk load-hist memory proc uptime + + + (cpu|cpu-all|cpu-hist|disk|load-hist|memory|proc|uptime) + + Invalid host screen + + cpu + Detailed CPU usage + + + cpu-all + CPU usage overview (one line per CPU) + + + cpu-hist + CPU usage histogram + + + disk + File systems fill level + + + load-hist + Load histogram + + + memory + Memory and swap usage + + + proc + Top processes by size + + + uptime + System uptime + + + + + + + Network settings for system display + + + + + + Show network traffic on the system display [Max 3 interfaces] + + + + + + + + Interface alias + + [A-Za-z0-9]{1,10} + + Invalid alias, must be 1 to 10 char or digit + + + + + + + + Unit for network details + + bps Bps pps + + + (bps|Bps|pps) + + Invalid network detail unit + + bps + Bit(s) per second + + + Bps + Byte(s) per second + + + pps + Packet(s) per second + + + + + + + + + + Show a clock on the system display + + big mini date-time + + + (big|mini|date-time) + + Invalid clock format + + big + Multi-line clock + + + mini + Minimal clock + + + date-time + Clock with Date and Time + + + + + + + Screen title to show on the system display + + [A-Za-z0-9]{1,16} + + Invalid title, must be 1 to 16 char or digit + + + + + + + + Time in sec to show each screen on the system display + + 1-30 + Numer of seconds + + + + + + + + + + Message to show when system display first starts + + .{1,16} + + Hello message must be 1 to 16 char + + + + + + Message to show when system display stops + + .{1,16} + + Bye message must be 1 to 16 char + + + + + + + diff --git a/src/conf_mode/system-display.py b/src/conf_mode/system-display.py new file mode 100755 index 000000000..4db375d8e --- /dev/null +++ b/src/conf_mode/system-display.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 Francois Mertz fireboxled at gmail.com +# +# 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 . + +import os +import re + +from sys import exit + +from vyos.config import Config +from vyos import ConfigError +from vyos.util import run +from vyos.template import render + +from vyos import airbag +airbag.enable() + +def get_config(): + c = Config() + + if not c.exists('system display'): + return None + + c.set_level('system display') + + return c.get_config_dict([]) + +def generate(c): + if c == None: + return None + # Render config file for daemon LCDd + render('/etc/LCDd.conf', 'system-display/LCDd.conf.tmpl', c) + # Render config file for client lcdproc + render('/etc/lcdproc.conf', 'system-display/lcdproc.conf.tmpl', c) + + return None + +def verify(c): + if c == None: + return None + + if c.get('model') == None: + raise ConfigError('For system display, a model is [REQUIRED]') + + if c.get('show') == None: + raise ConfigError('For system display, show cannot be empty') + + if 'network' in c['show'] and 'interface' not in c['show']['network']: + raise ConfigError('system display show network must have at least one interface') + + if 'network' in c['show'] and 'interface' in c['show']['network'] and len(c['show']['network']['interface']) > 3: + raise ConfigError('system display show network interface cannot have more than 3 entries') + + return None + +def apply(c): + if not c or c['config'] == 'disabled': + # Stop client first + run('systemctl stop lcdproc.service') + # Stop server next + return run('systemctl stop LCDd.service') + + # Stop client first + run('systemctl stop lcdproc.service') + # Restart server next + run('systemctl restart LCDd.service') + # Start client + run('systemctl start lcdproc.service') + + 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/systemd/lcdproc.service b/src/systemd/lcdproc.service new file mode 100644 index 000000000..ea9178de9 --- /dev/null +++ b/src/systemd/lcdproc.service @@ -0,0 +1,10 @@ +[Unit] +Description=LCDproc system status information viewer +Documentation=man:lcdproc(8) http://www.lcdproc.org/ + +[Service] +User=root +ExecStart=/usr/bin/lcdproc -f -c /etc/lcdproc.conf + +[Install] +WantedBy=multi-user.target \ No newline at end of file -- cgit v1.2.3 From f3e0a409ee01e1b3ffa0c9a267434a7725f61231 Mon Sep 17 00:00:00 2001 From: fmertz Date: Sun, 28 Jun 2020 20:36:19 +0000 Subject: system display: T2564: Replace "config (enabled|disabled)" with "display disabled" * interface-definitions/system-display.xml.in system disply disabled * src/conf_mode/system-display.py system display disabled --- interface-definitions/system-display.xml.in | 26 ++++++-------------------- src/conf_mode/system-display.py | 2 +- 2 files changed, 7 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/interface-definitions/system-display.xml.in b/interface-definitions/system-display.xml.in index ac9f17b6c..afa6dba10 100644 --- a/interface-definitions/system-display.xml.in +++ b/interface-definitions/system-display.xml.in @@ -2,7 +2,6 @@ @@ -42,27 +42,13 @@ - - + + - Configuration status - - enabled disabled - - - (enabled|disabled) - - Invalid system display config status - - enabled - Turn system display configuration ON - - - disabled - Turn system display configuration OFF - + Disable sytem display + - + Select the screens for the system display [REQUIRED] diff --git a/src/conf_mode/system-display.py b/src/conf_mode/system-display.py index 4db375d8e..e30d591cc 100755 --- a/src/conf_mode/system-display.py +++ b/src/conf_mode/system-display.py @@ -66,7 +66,7 @@ def verify(c): return None def apply(c): - if not c or c['config'] == 'disabled': + if not c or 'disabled' in c.keys(): # Stop client first run('systemctl stop lcdproc.service') # Stop server next -- cgit v1.2.3 From 440d0fb1115bbfb428132ef91804ae213ffca30f Mon Sep 17 00:00:00 2001 From: fmertz Date: Sun, 28 Jun 2020 21:21:27 +0000 Subject: system display: T2564: py code cleanup * src/conf_mode/system-display.py cleanup use of dictionary * data/templates/system-display/lcdproc.conf.tmpl cleanup, removed extra DateTime --- data/templates/system-display/lcdproc.conf.tmpl | 12 ++-- src/conf_mode/system-display.py | 75 +++++++++++++------------ 2 files changed, 43 insertions(+), 44 deletions(-) (limited to 'src') diff --git a/data/templates/system-display/lcdproc.conf.tmpl b/data/templates/system-display/lcdproc.conf.tmpl index 2fe491e63..92aee8efe 100644 --- a/data/templates/system-display/lcdproc.conf.tmpl +++ b/data/templates/system-display/lcdproc.conf.tmpl @@ -37,7 +37,6 @@ ReportToSyslog=true DisplayName="{%- if show['title'] %}{{ show['title'] }}{%- else %}VyOS{%- endif %}" {%- if show['host'] %} -{#%- for s in show['host'] %#} [CPU] # Show screen @@ -77,7 +76,6 @@ Active={%- if 'proc' in show['host'] %}true{%- else %}false{%- endif %} [Disk] # Show screen Active={%- if 'disk' in show['host'] %}true{%- else %}false{%- endif %} -{#%- endfor %#} {%- else %} {# if show['host'] #} {# Turn off sections that default active #} @@ -90,8 +88,6 @@ Active=false [Load] Active=false -[TimeDate] -Active=false {%- endif %} {# if show['host'] #} [TimeDate] @@ -118,10 +114,10 @@ TimeFormat="%H:%M" Active={%- if show['network']['interface'] %}true{%- else %}false{%- endif %} {%- for i in show['network']['interface'] %} # Show stats for Interface {{ i }} -Interface0={{ i }} +Interface{{ loop.index0 }}={{ i }} {%- if show['network']['interface'][i]['alias'] %} # Interface alias name to display [default: ] -Alias0={{ show['network']['interface'][i]['alias'] }} +Alias{{ loop.index0 }}={{ show['network']['interface'][i]['alias'] }} {%- endif %} {%- endfor %} @@ -139,7 +135,7 @@ unit=bit #transfer=TRUE {%- endif %} {# if show['network'] #} -{%- else %} {# if show #} +{%- else %}{# if show #} {# Turn off sections that default active #} [CPU] @@ -153,7 +149,7 @@ Active=false [TimeDate] Active=false -{%- endif %} +{%- endif %}{# if show #} [Battery] # Show screen diff --git a/src/conf_mode/system-display.py b/src/conf_mode/system-display.py index e30d591cc..557b7cf6a 100755 --- a/src/conf_mode/system-display.py +++ b/src/conf_mode/system-display.py @@ -15,7 +15,6 @@ # along with this program. If not, see . import os -import re from sys import exit @@ -28,53 +27,57 @@ from vyos import airbag airbag.enable() def get_config(): - c = Config() + # Return a (possibly empty) configuration dictionary + return Config().get_config_dict(['system', 'display']) - if not c.exists('system display'): - return None - - c.set_level('system display') - - return c.get_config_dict([]) - -def generate(c): - if c == None: +def generate(config_dict): + if not config_dict: return None # Render config file for daemon LCDd - render('/etc/LCDd.conf', 'system-display/LCDd.conf.tmpl', c) + render('/etc/LCDd.conf', 'system-display/LCDd.conf.tmpl', config_dict) # Render config file for client lcdproc - render('/etc/lcdproc.conf', 'system-display/lcdproc.conf.tmpl', c) + render('/etc/lcdproc.conf', 'system-display/lcdproc.conf.tmpl', config_dict) return None -def verify(c): - if c == None: +def verify(config_dict): + if not config_dict: return None - if c.get('model') == None: - raise ConfigError('For system display, a model is [REQUIRED]') + if 'model' not in config_dict: + raise ConfigError('Display model is [REQUIRED]') - if c.get('show') == None: - raise ConfigError('For system display, show cannot be empty') + if ( 'show' not in config_dict + or ( 'clock' not in config_dict['show'] + and 'network' not in config_dict['show'] + and 'host' not in config_dict['show'] + ) + ): + raise ConfigError('Display show must have a clock, host or network') - if 'network' in c['show'] and 'interface' not in c['show']['network']: - raise ConfigError('system display show network must have at least one interface') + if ( 'network' in config_dict['show'] + and 'interface' not in config_dict['show']['network'] + ): + raise ConfigError('Display show network must have an interface') - if 'network' in c['show'] and 'interface' in c['show']['network'] and len(c['show']['network']['interface']) > 3: - raise ConfigError('system display show network interface cannot have more than 3 entries') + if ( 'network' in config_dict['show'] + and 'interface' in config_dict['show']['network'] + and len(config_dict['show']['network']['interface']) > 3 + ): + raise ConfigError('Display show network cannot have > 3 interfaces') return None -def apply(c): - if not c or 'disabled' in c.keys(): - # Stop client first - run('systemctl stop lcdproc.service') - # Stop server next - return run('systemctl stop LCDd.service') - - # Stop client first +def apply(config_dict): + # Stop client run('systemctl stop lcdproc.service') - # Restart server next + + if not config_dict or 'disabled' in config_dict: + # Stop server + run('systemctl stop LCDd.service') + return None + + # Restart server run('systemctl restart LCDd.service') # Start client run('systemctl start lcdproc.service') @@ -83,10 +86,10 @@ def apply(c): if __name__ == '__main__': try: - c = get_config() - verify(c) - generate(c) - apply(c) + config_dict = get_config() + verify(config_dict) + generate(config_dict) + apply(config_dict) except ConfigError as e: print(e) exit(1) -- cgit v1.2.3 From 14e0a2339451492e226869cb977ab858bb1e7f17 Mon Sep 17 00:00:00 2001 From: fmertz Date: Thu, 2 Jul 2020 05:08:20 +0000 Subject: system display: T2564: Conf files to /run * src/conf_mode/system-display.py conf files to /run + src/systemd/LCDd@.service lo - src/systemd/lcdproc.service + src/systemd/lcdproc@.service lo --- src/conf_mode/system-display.py | 12 ++++++------ src/systemd/LCDd@.service | 10 ++++++++++ src/systemd/lcdproc.service | 10 ---------- src/systemd/lcdproc@.service | 10 ++++++++++ 4 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 src/systemd/LCDd@.service delete mode 100644 src/systemd/lcdproc.service create mode 100644 src/systemd/lcdproc@.service (limited to 'src') diff --git a/src/conf_mode/system-display.py b/src/conf_mode/system-display.py index 557b7cf6a..9ab35a331 100755 --- a/src/conf_mode/system-display.py +++ b/src/conf_mode/system-display.py @@ -34,9 +34,9 @@ def generate(config_dict): if not config_dict: return None # Render config file for daemon LCDd - render('/etc/LCDd.conf', 'system-display/LCDd.conf.tmpl', config_dict) + render('/run/LCDd/LCDd.lo.conf', 'system-display/LCDd.conf.tmpl', config_dict) # Render config file for client lcdproc - render('/etc/lcdproc.conf', 'system-display/lcdproc.conf.tmpl', config_dict) + render('/run/lcdproc/lcdproc.lo.conf', 'system-display/lcdproc.conf.tmpl', config_dict) return None @@ -70,17 +70,17 @@ def verify(config_dict): def apply(config_dict): # Stop client - run('systemctl stop lcdproc.service') + run('systemctl stop lcdproc@lo.service') if not config_dict or 'disabled' in config_dict: # Stop server - run('systemctl stop LCDd.service') + run('systemctl stop LCDd@lo.service') return None # Restart server - run('systemctl restart LCDd.service') + run('systemctl restart LCDd@lo.service') # Start client - run('systemctl start lcdproc.service') + run('systemctl start lcdproc@lo.service') return None diff --git a/src/systemd/LCDd@.service b/src/systemd/LCDd@.service new file mode 100644 index 000000000..a4604cf21 --- /dev/null +++ b/src/systemd/LCDd@.service @@ -0,0 +1,10 @@ +[Unit] +Description=LCD display daemon on %I +Documentation=man:LCDd(8) http://www.lcdproc.org/ + +[Service] +User=root +ExecStart=/usr/sbin/LCDd -s 1 -f -c /run/LCDd/LCDd.%I.conf + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/src/systemd/lcdproc.service b/src/systemd/lcdproc.service deleted file mode 100644 index ea9178de9..000000000 --- a/src/systemd/lcdproc.service +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=LCDproc system status information viewer -Documentation=man:lcdproc(8) http://www.lcdproc.org/ - -[Service] -User=root -ExecStart=/usr/bin/lcdproc -f -c /etc/lcdproc.conf - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/src/systemd/lcdproc@.service b/src/systemd/lcdproc@.service new file mode 100644 index 000000000..9a1723dba --- /dev/null +++ b/src/systemd/lcdproc@.service @@ -0,0 +1,10 @@ +[Unit] +Description=LCDproc system status information viewer on %I +Documentation=man:lcdproc(8) http://www.lcdproc.org/ + +[Service] +User=root +ExecStart=/usr/bin/lcdproc -f -c /run/lcdproc/lcdproc.%I.conf + +[Install] +WantedBy=multi-user.target \ No newline at end of file -- cgit v1.2.3 From 48ddea3755c72db55e5366997a82d725e106c3aa Mon Sep 17 00:00:00 2001 From: fmertz Date: Sun, 5 Jul 2020 15:01:21 +0000 Subject: system display: T2564: Dictionary code update * src/conf_mode/system-display.py added get_first_key to dictionary call --- src/conf_mode/system-display.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/conf_mode/system-display.py b/src/conf_mode/system-display.py index 9ab35a331..3eafc30c0 100755 --- a/src/conf_mode/system-display.py +++ b/src/conf_mode/system-display.py @@ -27,18 +27,11 @@ from vyos import airbag airbag.enable() def get_config(): + conf = Config() + base = ['system', 'display'] + display = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) # Return a (possibly empty) configuration dictionary - return Config().get_config_dict(['system', 'display']) - -def generate(config_dict): - if not config_dict: - return None - # Render config file for daemon LCDd - render('/run/LCDd/LCDd.lo.conf', 'system-display/LCDd.conf.tmpl', config_dict) - # Render config file for client lcdproc - render('/run/lcdproc/lcdproc.lo.conf', 'system-display/lcdproc.conf.tmpl', config_dict) - - return None + return display def verify(config_dict): if not config_dict: @@ -68,6 +61,16 @@ def verify(config_dict): return None +def generate(config_dict): + if not config_dict: + return None + # Render config file for daemon LCDd + render('/run/LCDd/LCDd.lo.conf', 'system-display/LCDd.conf.tmpl', config_dict) + # Render config file for client lcdproc + render('/run/lcdproc/lcdproc.lo.conf', 'system-display/lcdproc.conf.tmpl', config_dict) + + return None + def apply(config_dict): # Stop client run('systemctl stop lcdproc@lo.service') -- cgit v1.2.3 From d37652e0848e4d4a3e06c544c8857aff0d728bbc Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 22 Jul 2020 13:15:38 -0500 Subject: http-api: remove unneeded check for VyOSError The only calls to config (return_value, return_values, exists) do not throw VyOSError; remove unneeded except. --- src/services/vyos-http-api-server | 3 --- 1 file changed, 3 deletions(-) (limited to 'src') diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index 4c41fa96d..d5730d86c 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -32,7 +32,6 @@ from waitress import serve from functools import wraps from vyos.configsession import ConfigSession, ConfigSessionError -from vyos.config import VyOSError DEFAULT_CONFIG_FILE = '/etc/vyos/http-api.conf' @@ -231,8 +230,6 @@ def retrieve_op(command): return error(400, "\"{0}\" is not a valid config format".format(config_format)) else: return error(400, "\"{0}\" is not a valid operation".format(op)) - except VyOSError as e: - return error(400, str(e)) except ConfigSessionError as e: return error(400, str(e)) except Exception as e: -- cgit v1.2.3 From 929f7b1713b16c72b0baab2e01dc363dc3928850 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 22 Jul 2020 13:16:17 -0500 Subject: load-config: subclass ConfigSourceSession instead of Config --- src/helpers/vyos-load-config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/helpers/vyos-load-config.py b/src/helpers/vyos-load-config.py index a9fa15778..c2da1bb11 100755 --- a/src/helpers/vyos-load-config.py +++ b/src/helpers/vyos-load-config.py @@ -27,12 +27,12 @@ import sys import tempfile import vyos.defaults import vyos.remote -from vyos.config import Config, VyOSError +from vyos.configsource import ConfigSourceSession, VyOSError from vyos.migrator import Migrator, VirtualMigrator, MigratorError -class LoadConfig(Config): +class LoadConfig(ConfigSourceSession): """A subclass for calling 'loadFile'. - This does not belong in config.py, and only has a single caller. + This does not belong in configsource.py, and only has a single caller. """ def load_config(self, path): return self._run(['/bin/cli-shell-api','loadFile',path]) -- cgit v1.2.3 From faaad2e029fd3520b62983dd78c7692434138360 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Wed, 22 Jul 2020 23:36:44 +0100 Subject: xml: T2582: use xml for is_tag and is_leaf --- python/vyos/validate.py | 7 ++++--- python/vyos/xml/__init__.py | 15 +++++++++++++++ python/vyos/xml/definition.py | 23 +++++++++++++++++++---- python/vyos/xml/test_xml.py | 2 +- src/tests/test_initial_setup.py | 10 ++++++---- 5 files changed, 45 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/python/vyos/validate.py b/python/vyos/validate.py index 9072c5817..a0620e4dd 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -19,6 +19,7 @@ import netifaces import ipaddress from vyos.util import cmd +from vyos import xml # Important note when you are adding new validation functions: # @@ -293,12 +294,12 @@ def is_member(conf, interface, intftype=None): for it in intftype: base = 'interfaces ' + it for intf in conf.list_nodes(base): - memberintf = f'{base} {intf} member interface' - if conf.is_tag(memberintf): + memberintf = [base, intf, 'member', 'interface'] + if xml.is_tag(memberintf): if interface in conf.list_nodes(memberintf): ret_val = intf break - elif conf.is_leaf(memberintf): + elif xml.is_leaf(memberintf): if ( conf.exists(memberintf) and interface in conf.return_values(memberintf) ): ret_val = intf diff --git a/python/vyos/xml/__init__.py b/python/vyos/xml/__init__.py index 6e0e73b1b..0f914fed2 100644 --- a/python/vyos/xml/__init__.py +++ b/python/vyos/xml/__init__.py @@ -35,6 +35,18 @@ def load_configuration(cache=[]): return xml +# def is_multi(lpath): +# return load_configuration().is_multi(lpath) + + +def is_tag(lpath): + return load_configuration().is_tag(lpath) + + +def is_leaf(lpath, flat=True): + return load_configuration().is_leaf(lpath, flat) + + def defaults(lpath, flat=False): return load_configuration().defaults(lpath, flat) @@ -42,3 +54,6 @@ def defaults(lpath, flat=False): if __name__ == '__main__': print(defaults(['service'], flat=True)) print(defaults(['service'], flat=False)) + + print(is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) + print(is_tag(['protocols', 'static', 'multicast', 'route', '0.0.0.0/0', 'next-hop'])) diff --git a/python/vyos/xml/definition.py b/python/vyos/xml/definition.py index b0339b228..098e64f7e 100644 --- a/python/vyos/xml/definition.py +++ b/python/vyos/xml/definition.py @@ -126,10 +126,12 @@ class XML(dict): elif word: if data_node != kw.plainNode or len(passed) == 1: self.options = [_ for _ in self.tree if _.startswith(word)] + self.options.sort() else: self.options = [] else: self.options = named_options + self.options.sort() self.plain = not is_dataNode @@ -143,6 +145,7 @@ class XML(dict): self.word = '' if self.tree.get(kw.node,'') not in (kw.tagNode, kw.leafNode): self.options = [_ for _ in self.tree if not kw.found(_)] + self.options.sort() def checks(self, cmd): # as we move thought the named node twice @@ -300,16 +303,28 @@ class XML(dict): return tree def _get(self, lpath, tag, with_tag=True): - return self._tree(lpath + [tag], with_tag) + tree = self._tree(lpath, with_tag) + if tree is None: + return None + return tree.get(tag, None) def is_multi(self, lpath, with_tag=True): - return self._get(lpath, kw.multi, with_tag) is True + tree = self._get(lpath, kw.multi, with_tag) + if tree is None: + return None + return tree is True def is_tag(self, lpath, with_tag=True): - return self._get(lpath, kw.node, with_tag) == kw.tagNode + tree = self._get(lpath, kw.node, with_tag) + if tree is None: + return None + return tree == kw.tagNode def is_leaf(self, lpath, with_tag=True): - return self._get(lpath, kw.node, with_tag) == kw.leafNode + tree = self._get(lpath, kw.node, with_tag) + if tree is None: + return None + return tree == kw.leafNode def exists(self, lpath, with_tag=True): return self._get(lpath, kw.node, with_tag) is not None diff --git a/python/vyos/xml/test_xml.py b/python/vyos/xml/test_xml.py index ac0620d99..ff55151d2 100644 --- a/python/vyos/xml/test_xml.py +++ b/python/vyos/xml/test_xml.py @@ -33,7 +33,7 @@ class TestSearch(TestCase): last = self.xml.traverse("") self.assertEqual(last, '') self.assertEqual(self.xml.inside, []) - self.assertEqual(self.xml.options, ['protocols', 'service', 'system', 'firewall', 'interfaces', 'vpn', 'nat', 'vrf', 'high-availability']) + self.assertEqual(self.xml.options, ['firewall', 'high-availability', 'interfaces', 'nat', 'protocols', 'service', 'system', 'vpn', 'vrf']) self.assertEqual(self.xml.filling, False) self.assertEqual(self.xml.word, last) self.assertEqual(self.xml.check, False) diff --git a/src/tests/test_initial_setup.py b/src/tests/test_initial_setup.py index c4c59b827..1597025e8 100644 --- a/src/tests/test_initial_setup.py +++ b/src/tests/test_initial_setup.py @@ -21,6 +21,7 @@ import tempfile import unittest from unittest import TestCase, mock +from vyos import xml import vyos.configtree import vyos.initialsetup as vis @@ -30,6 +31,7 @@ class TestInitialSetup(TestCase): with open('tests/data/config.boot.default', 'r') as f: config_string = f.read() self.config = vyos.configtree.ConfigTree(config_string) + self.xml = xml.load_configuration() def test_set_user_password(self): vis.set_user_password(self.config, 'vyos', 'vyosvyos') @@ -56,7 +58,7 @@ class TestInitialSetup(TestCase): self.assertEqual(key_type, 'ssh-rsa') self.assertEqual(key_data, 'fakedata') - self.assertTrue(self.config.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) + self.assertTrue(self.xml.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) def test_set_ssh_key_without_name(self): # If key file doesn't include a name, the function will use user name for the key name @@ -69,7 +71,7 @@ class TestInitialSetup(TestCase): self.assertEqual(key_type, 'ssh-rsa') self.assertEqual(key_data, 'fakedata') - self.assertTrue(self.config.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) + self.assertTrue(self.xml.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) def test_create_user(self): vis.create_user(self.config, 'jrandomhacker', password='qwerty', key=" ssh-rsa fakedata jrandomhacker@foovax ") @@ -95,8 +97,8 @@ class TestInitialSetup(TestCase): vis.set_default_gateway(self.config, '192.0.2.1') self.assertTrue(self.config.exists(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '192.0.2.1'])) - self.assertTrue(self.config.is_tag(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop'])) - self.assertTrue(self.config.is_tag(['protocols', 'static', 'route'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route', '0.0.0.0/0', 'next-hop'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route'])) if __name__ == "__main__": unittest.main() -- cgit v1.2.3 From 61dccd81a1037c06ae883020db51409dda3e41f9 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Fri, 24 Jul 2020 13:20:00 +0300 Subject: T2727: add a dotted decimal validator. Since it's relatively rarely used, Python's startup time should't be much of a problem. --- src/validators/dotted-decimal | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100755 src/validators/dotted-decimal (limited to 'src') diff --git a/src/validators/dotted-decimal b/src/validators/dotted-decimal new file mode 100755 index 000000000..652110346 --- /dev/null +++ b/src/validators/dotted-decimal @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 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 . + +import re +import sys + +area = sys.argv[1] + +res = re.match(r'^(\d+)\.(\d+)\.(\d+)\.(\d+)$', area) +if not res: + print("\'{0}\' is not a valid dotted decimal value".format(area)) + sys.exit(1) +else: + components = res.groups() + for n in range(0, 4): + if (int(components[n]) > 255): + print("Invalid component of a dotted decimal value: {0} exceeds 255".format(components[n])) + sys.exit(1) + +sys.exit(0) -- cgit v1.2.3 From 1a85e758b105d493bb9d95916816bd206345bc5d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Jul 2020 15:59:06 +0200 Subject: vyos.util: add common helper to load kernel modules l2tpv3, wireguard, wirelessmodem, nat all require additional Kernel modules to be present on the system. Each and every interface implemented their own way of loading a module - by copying code. Use a generic function, vyos.util.check_kmod() to load any arbitrary kernel module passed as string or list. --- python/vyos/util.py | 9 +++++++++ src/conf_mode/interfaces-l2tpv3.py | 12 ++++-------- src/conf_mode/interfaces-wireguard.py | 12 +++--------- src/conf_mode/interfaces-wirelessmodem.py | 9 ++------- src/conf_mode/nat.py | 17 ++++++----------- src/op_mode/wireguard.py | 17 ++++++----------- 6 files changed, 30 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/python/vyos/util.py b/python/vyos/util.py index 924df6b3a..7234be6cb 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -652,3 +652,12 @@ def get_bridge_member_config(conf, br, intf): conf.set_level(old_level) return memberconf + +def check_kmod(k_mod): + """ Common utility function to load required kernel modules on demand """ + if isinstance(k_mod, str): + k_mod = k_mod.split() + for module in 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') diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 4ff0bcb57..866419f2c 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -24,11 +24,14 @@ from vyos.config import Config from vyos.ifconfig import L2TPv3If, Interface from vyos import ConfigError from vyos.util import call +from vyos.util import check_kmod from vyos.validate import is_member, is_addr_assigned from vyos import airbag airbag.enable() +k_mod = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6'] + default_config_data = { 'address': [], 'deleted': False, @@ -53,13 +56,6 @@ default_config_data = { 'tunnel_id': '' } -def check_kmod(): - modules = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6'] - for module in modules: - if not os.path.exists(f'/sys/module/{module}'): - if call(f'modprobe {module}') != 0: - raise ConfigError(f'Loading Kernel module {module} failed') - def get_config(): l2tpv3 = deepcopy(default_config_data) conf = Config() @@ -283,7 +279,7 @@ def apply(l2tpv3): if __name__ == '__main__': try: - check_kmod() + check_kmod(k_mod) c = get_config() verify(c) generate(c) diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index c24c9a7ce..982aefa5f 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -25,6 +25,7 @@ from vyos.config import Config from vyos.configdict import list_diff from vyos.ifconfig import WireGuardIf from vyos.util import chown, chmod_750, call +from vyos.util import check_kmod from vyos.validate import is_member, is_ipv6 from vyos import ConfigError @@ -32,6 +33,7 @@ from vyos import airbag airbag.enable() kdir = r'/config/auth/wireguard' +k_mod = 'wireguard' default_config_data = { 'intfc': '', @@ -50,14 +52,6 @@ default_config_data = { 'vrf': '' } -def _check_kmod(): - modules = ['wireguard'] - for module in modules: - if not os.path.exists(f'/sys/module/{module}'): - if call(f'modprobe {module}') != 0: - raise ConfigError(f'Loading Kernel module {module} failed') - - def _migrate_default_keys(): if os.path.exists(f'{kdir}/private.key') and not os.path.exists(f'{kdir}/default/private.key'): location = f'{kdir}/default' @@ -315,7 +309,7 @@ def apply(wg): if __name__ == '__main__': try: - _check_kmod() + check_kmod(k_mod) _migrate_default_keys() c = get_config() verify(c) diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index ec5a85e54..0964a8f4d 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -29,12 +29,7 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def check_kmod(): - modules = ['option', 'usb_wwan', 'usbserial'] - for module in modules: - if not os.path.exists(f'/sys/module/{module}'): - if call(f'modprobe {module}') != 0: - raise ConfigError(f'Loading Kernel module {module} failed') +k_mod = ['option', 'usb_wwan', 'usbserial'] def find_device_file(device): """ Recurively search /dev for the given device file and return its full path. @@ -153,7 +148,7 @@ def apply(wwan): if __name__ == '__main__': try: - check_kmod() + check_kmod(k_mod) c = get_config() verify(c) generate(c) diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 2299717a8..dd34dfd66 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -24,13 +24,17 @@ from netifaces import interfaces from vyos.config import Config from vyos.template import render -from vyos.util import call, cmd +from vyos.util import call +from vyos.util import cmd +from vyos.util import check_kmod from vyos.validate import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() +k_mod = ['nft_nat', 'nft_chain_nat_ipv4'] + default_config_data = { 'deleted': False, 'destination': [], @@ -44,15 +48,6 @@ default_config_data = { iptables_nat_config = '/tmp/vyos-nat-rules.nft' -def _check_kmod(): - """ load required Kernel modules """ - modules = ['nft_nat', 'nft_chain_nat_ipv4'] - for module in modules: - if not os.path.exists(f'/sys/module/{module}'): - if call(f'modprobe {module}') != 0: - raise ConfigError(f'Loading Kernel module {module} failed') - - def get_handler(json, chain, target): """ Get nftable rule handler number of given chain/target combination. Handler is required when adding NAT/Conntrack helper targets """ @@ -269,7 +264,7 @@ def apply(nat): if __name__ == '__main__': try: - _check_kmod() + check_kmod(k_mod) c = get_config() verify(c) generate(c) diff --git a/src/op_mode/wireguard.py b/src/op_mode/wireguard.py index 15bf63e81..e08bc983a 100755 --- a/src/op_mode/wireguard.py +++ b/src/op_mode/wireguard.py @@ -21,22 +21,17 @@ import shutil import syslog as sl import re +from vyos.config import Config from vyos.ifconfig import WireGuardIf - +from vyos.util import cmd +from vyos.util import run +from vyos.util import check_kmod from vyos import ConfigError -from vyos.config import Config -from vyos.util import cmd, run dir = r'/config/auth/wireguard' psk = dir + '/preshared.key' -def check_kmod(): - """ check if kmod is loaded, if not load it """ - if not os.path.exists('/sys/module/wireguard'): - sl.syslog(sl.LOG_NOTICE, "loading wirguard kmod") - if run('sudo modprobe wireguard') != 0: - sl.syslog(sl.LOG_ERR, "modprobe wireguard failed") - raise ConfigError("modprobe wireguard failed") +k_mod = 'wireguard' def generate_keypair(pk, pub): """ generates a keypair which is stored in /config/auth/wireguard """ @@ -106,7 +101,7 @@ def del_key_dir(kname): if __name__ == '__main__': - check_kmod() + check_kmod(k_mod) parser = argparse.ArgumentParser(description='wireguard key management') parser.add_argument( '--genkey', action="store_true", help='generate key-pair') -- cgit v1.2.3 From ebefa38b9fa946fde82a4c9b55122c037598143b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 1 Jul 2020 19:06:52 +0200 Subject: ethernet: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. While providing a new update() method in vyos.ifconfig.interfaces() this is extended for ethernet based interfaces which also supports 802.1q, 802.1ad VLANs. This commit migrates the existing codebase for an ethernet based interfaces and implements the missing parts for VLANs. Adding or migrating other interfaces (e.g. bridge or bond) will become much easier as they must reuse the entire functionality - we now walk towards a single codepath. Thanks for all who made this combined effort possible! Signed-off-by: Christian Poessinger --- interface-definitions/interfaces-ethernet.xml.in | 2 + python/vyos/configdict.py | 29 +- python/vyos/configverify.py | 53 +++- python/vyos/ifconfig/ethernet.py | 101 ++++++- python/vyos/ifconfig/interface.py | 106 +++++++- python/vyos/ifconfig_vlan.py | 24 ++ src/conf_mode/interfaces-ethernet.py | 329 +++++------------------ 7 files changed, 371 insertions(+), 273 deletions(-) (limited to 'src') diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index 1e32a15f8..e8f3f09f1 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -56,6 +56,7 @@ duplex must be auto, half or full + auto #include @@ -265,6 +266,7 @@ Speed must be auto, 10, 100, 1000, 2500, 5000, 10000, 25000, 40000, 50000 or 100000 + auto #include #include diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 0dc7578d8..682caed8f 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -102,12 +102,35 @@ def dict_merge(source, destination): return tmp def list_diff(first, second): - """ - Diff two dictionaries and return only unique items - """ + """ Diff two dictionaries and return only unique items """ second = set(second) return [item for item in first if item not in second] +def T2665_default_dict_cleanup(dict): + """ Cleanup default keys for tag nodes https://phabricator.vyos.net/T2665. """ + # Cleanup + for vif in ['vif', 'vif_s']: + if vif in dict.keys(): + for key in ['ip', 'mtu']: + if key in dict[vif].keys(): + del dict[vif][key] + + # cleanup VIF-S defaults + if 'vif_c' in dict[vif].keys(): + for key in ['ip', 'mtu']: + if key in dict[vif]['vif_c'].keys(): + del dict[vif]['vif_c'][key] + # If there is no vif-c defined and we just cleaned the default + # keys - we can clean the entire vif-c dict as it's useless + if not dict[vif]['vif_c']: + del dict[vif]['vif_c'] + + # If there is no real vif/vif-s defined and we just cleaned the default + # keys - we can clean the entire vif dict as it's useless + if not dict[vif]: + del dict[vif] + + return dict def get_ethertype(ethertype_val): if ethertype_val == '0x88A8': diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 32129a048..36b10c956 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -41,14 +41,14 @@ def verify_vrf(config): def verify_address(config): """ - Common helper function used by interface implementations to - perform recurring validation of IP address assignmenr - when interface also is part of a bridge. + Common helper function used by interface implementations to perform + recurring validation of IP address assignment when interface is part + of a bridge or bond. """ if {'is_bridge_member', 'address'} <= set(config): raise ConfigError( - f'Cannot assign address to interface "{ifname}" as it is a ' - f'member of bridge "{is_bridge_member}"!'.format(**config)) + 'Cannot assign address to interface "{ifname}" as it is a ' + 'member of bridge "{is_bridge_member}"!'.format(**config)) def verify_bridge_delete(config): @@ -62,6 +62,15 @@ def verify_bridge_delete(config): 'Interface "{ifname}" cannot be deleted as it is a ' 'member of bridge "{is_bridge_member}"!'.format(**config)) +def verify_interface_exists(config): + """ + Common helper function used by interface implementations to perform + recurring validation if an interface actually exists. + """ + from netifaces import interfaces + if not config['ifname'] in interfaces(): + raise ConfigError(f'Interface "{ifname}" does not exist!' + .format(**config)) def verify_source_interface(config): """ @@ -76,3 +85,37 @@ def verify_source_interface(config): if not config['source_interface'] in interfaces(): raise ConfigError(f'Source interface {source_interface} does not ' f'exist'.format(**config)) + +def verify_dhcpv6(config): + """ + Common helper function used by interface implementations to perform + recurring validation of DHCPv6 options which are mutually exclusive. + """ + if {'parameters_only', 'temporary'} <= set(config.get('dhcpv6_options', {})): + raise ConfigError('DHCPv6 temporary and parameters-only options ' + 'are mutually exclusive!') + +def verify_vlan_config(config): + """ + Common helper function used by interface implementations to perform + recurring validation of interface VLANs + """ + # 802.1q VLANs + for vlan in config.get('vif', {}).keys(): + 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(): + 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(): + vlan = config['vif_c'][vlan] + verify_dhcpv6(vlan) + verify_address(vlan) + verify_vrf(vlan) diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 5b18926c9..8a50a8699 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -15,13 +15,14 @@ import os import re +import jmespath +from vyos.configdict import get_ethertype from vyos.ifconfig.interface import Interface from vyos.ifconfig.vlan import VLAN from vyos.validate import assert_list from vyos.util import run - @Interface.register @VLAN.enable class EthernetIf(Interface): @@ -252,3 +253,101 @@ class EthernetIf(Interface): >>> i.set_udp_offload('on') """ return self.set_interface('ufo', state) + + + 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. """ + + # now call the regular function from within our base class + super().update(config) + + # disable ethernet flow control (pause frames) + value = 'off' if 'disable_flow_control' in config.keys() else 'on' + self.set_flow_control(value) + + # GRO (generic receive offload) + tmp = jmespath.search('offload_options.generic_receive', config) + value = tmp if (tmp != None) else 'off' + self.set_gro(value) + + # GSO (generic segmentation offload) + tmp = jmespath.search('offload_options.generic_segmentation', config) + value = tmp if (tmp != None) else 'off' + self.set_gso(value) + + # scatter-gather option + tmp = jmespath.search('offload_options.scatter_gather', config) + value = tmp if (tmp != None) else 'off' + self.set_sg(value) + + # TSO (TCP segmentation offloading) + tmp = jmespath.search('offload_options.udp_fragmentation', config) + value = tmp if (tmp != None) else 'off' + self.set_tso(value) + + # UDP fragmentation offloading + tmp = jmespath.search('offload_options.udp_fragmentation', config) + value = tmp if (tmp != None) else 'off' + self.set_ufo(value) + + # Set physical interface speed and duplex + if {'speed', 'duplex'} <= set(config): + speed = config.get('speed') + duplex = config.get('duplex') + self.set_speed_duplex(speed, duplex) + + # Delete old IPv6 EUI64 addresses before changing MAC + + # Change interface MAC address - re-set to real hardware address (hw-id) + # if custom mac is removed. Skip if bond member. + if 'is_bond_member' not in config: + mac = config.get('hw_id') + if 'mac' in config: + mac = config.get('mac') + if mac: + self.set_mac(mac) + + # Add IPv6 EUI-based addresses + tmp = jmespath.search('ipv6.address.eui64', config) + if tmp: + # XXX: T2636 workaround: convert string to a list with one element + if isinstance(tmp, str): + tmp = [tmp] + for addr in tmp: + self.add_ipv6_eui64_address(addr) + + # re-add ourselves to any bridge we might have fallen out of + if 'is_bridge_member' in config: + bridge = config.get('is_bridge_member') + self.add_to_bridge(bridge) + + # remove no longer required 802.1ad (Q-in-Q VLANs) + for vif_s_id in config.get('vif_s_remove', {}): + self.del_vlan(vif_s_id) + + # create/update 802.1ad (Q-in-Q VLANs) + for vif_s_id, vif_s in config.get('vif_s', {}).items(): + tmp=get_ethertype(vif_s.get('ethertype', '0x88A8')) + s_vlan = self.add_vlan(vif_s_id, ethertype=tmp) + s_vlan.update(vif_s) + + # remove no longer required client VLAN (vif-c) + for vif_c_id in vif_s.get('vif_c_remove', {}): + s_vlan.del_vlan(vif_c_id) + + # create/update client VLAN (vif-c) interface + for vif_c_id, vif_c in vif_s.get('vif_c', {}).items(): + c_vlan = s_vlan.add_vlan(vif_c_id) + c_vlan.update(vif_c) + + # remove no longer required 802.1q VLAN interfaces + for vif_id in config.get('vif_remove', {}): + self.del_vlan(vif_id) + + # create/update 802.1q VLAN interfaces + for vif_id, vif in config.get('vif', {}).items(): + vlan = self.add_vlan(vif_id) + vlan.update(vif) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 8d7b247fc..689faa22b 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -16,6 +16,7 @@ import os import re import json +import jmespath from copy import deepcopy from ipaddress import IPv4Network @@ -322,11 +323,11 @@ class Interface(Control): self.set_admin_state('down') self.set_interface('mac', mac) - + # Turn an interface to the 'up' state if it was changed to 'down' by this fucntion if prev_state == 'up': self.set_admin_state('up') - + def set_vrf(self, vrf=''): """ Add/Remove interface from given VRF instance. @@ -773,14 +774,17 @@ class Interface(Control): on any interface. """ # Update interface description - self.set_alias(config.get('description', None)) + self.set_alias(config.get('description', '')) + + # Ignore link state changes + value = '2' if 'disable_link_detect' in config else '1' + self.set_link_detect(value) # Configure assigned interface IP addresses. No longer # configured addresses will be removed first new_addr = config.get('address', []) - # XXX workaround for T2636, convert IP address string to a list - # with one element + # XXX: T2636 workaround: convert string to a list with one element if isinstance(new_addr, str): new_addr = [new_addr] @@ -800,6 +804,96 @@ class Interface(Control): # Bind interface instance into VRF self.set_vrf(config.get('vrf', '')) + # DHCP options + if 'dhcp_options' in config: + dhcp_options = config.get('dhcp_options') + if 'client_id' in dhcp_options: + self.dhcp.v4.options['client_id'] = dhcp_options.get('client_id') + + if 'host_name' in dhcp_options: + self.dhcp.v4.options['hostname'] = dhcp_options.get('host_name') + + if 'vendor_class_id' in dhcp_options: + self.dhcp.v4.options['vendor_class_id'] = dhcp_options.get('vendor_class_id') + + # DHCPv6 options + if 'dhcpv6_options' in config: + dhcpv6_options = config.get('dhcpv6_options') + if 'parameters_only' in dhcpv6_options: + self.dhcp.v6.options['dhcpv6_prm_only'] = True + + if 'temporary' in dhcpv6_options: + self.dhcp.v6.options['dhcpv6_temporary'] = True + + if 'prefix_delegation' in dhcpv6_options: + prefix_delegation = dhcpv6_options.get('prefix_delegation') + if 'length' in prefix_delegation: + self.dhcp.v6.options['dhcpv6_pd_length'] = prefix_delegation.get('length') + + if 'interface' in prefix_delegation: + self.dhcp.v6.options['dhcpv6_pd_interfaces'] = prefix_delegation.get('interface') + + # Configure ARP cache timeout in milliseconds - has default value + tmp = jmespath.search('ip.arp_cache_timeout', config) + value = tmp if (tmp != None) else '30' + self.set_arp_cache_tmo(value) + + # Configure ARP filter configuration + tmp = jmespath.search('ip.disable_arp_filter', config) + value = '0' if (tmp != None) else '1' + self.set_arp_filter(value) + + # Configure ARP accept + tmp = jmespath.search('ip.enable_arp_accept', config) + value = '1' if (tmp != None) else '0' + self.set_arp_accept(value) + + # Configure ARP announce + tmp = jmespath.search('ip.enable_arp_announce', config) + value = '1' if (tmp != None) else '0' + self.set_arp_announce(value) + + # Configure ARP ignore + tmp = jmespath.search('ip.enable_arp_ignore', config) + value = '1' if (tmp != None) else '0' + self.set_arp_ignore(value) + + # Enable proxy-arp on this interface + tmp = jmespath.search('ip.enable_proxy_arp', config) + value = '1' if (tmp != None) else '0' + self.set_proxy_arp(value) + + # Enable private VLAN proxy ARP on this interface + tmp = jmespath.search('ip.proxy_arp_pvlan', config) + value = '1' if (tmp != None) else '0' + self.set_proxy_arp_pvlan(value) + + # IPv6 forwarding + tmp = jmespath.search('ipv6.disable_forwarding', config) + value = '0' if (tmp != None) else '1' + self.set_ipv6_forwarding(value) + + # IPv6 router advertisements + tmp = jmespath.search('ipv6.address.autoconf', config) + value = '2' if (tmp != None) else '1' + if 'dhcpv6' in new_addr: + value = '2' + self.set_ipv6_accept_ra(value) + + # IPv6 address autoconfiguration + tmp = jmespath.search('ipv6.address.autoconf', config) + value = '1' if (tmp != None) else '0' + self.set_ipv6_autoconf(value) + + # IPv6 Duplicate Address Detection (DAD) tries + tmp = jmespath.search('ipv6.dup_addr_detect_transmits', config) + value = tmp if (tmp != None) else '1' + self.set_ipv6_dad_messages(value) + + # MTU - Maximum Transfer Unit + if 'mtu' in config: + self.set_mtu(config.get('mtu')) + # Interface administrative state - state = 'down' if 'disable' in config.keys() else 'up' + state = 'down' if 'disable' in config else 'up' self.set_admin_state(state) diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index 442cb0db8..ecb6796fa 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -16,6 +16,30 @@ from netifaces import interfaces from vyos import ConfigError +def get_removed_vlans(conf, dict): + """ + Common function to parse a dictionary retrieved via get_config_dict() and + determine any added/removed VLAN interfaces - be it 802.1q or Q-in-Q. + """ + from vyos.configdiff import get_config_diff, Diff + + # Check vif, vif-s/vif-c VLAN interfaces for removal + D = get_config_diff(conf, key_mangling=('-', '_')) + D.set_level(conf.get_level()) + # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 + keys = D.get_child_nodes_diff(['vif'], expand_nodes=Diff.DELETE)['delete'].keys() + dict['vif_remove'] = [*keys] + + # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 + keys = D.get_child_nodes_diff(['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys() + dict['vif_s_remove'] = [*keys] + + for vif in dict.get('vif_s', {}).keys(): + keys = D.get_child_nodes_diff(['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys() + dict['vif_s'][vif]['vif_c_remove'] = [*keys] + + return dict + def apply_all_vlans(intf, intfconfig): """ Function applies all VLANs to the passed interface. diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 8b895c4d2..60aafae32 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -17,295 +17,108 @@ import os from sys import exit -from copy import deepcopy -from netifaces import interfaces +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configdict import T2665_default_dict_cleanup +from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_address +from vyos.configverify import verify_vrf +from vyos.configverify import verify_vlan_config from vyos.ifconfig import EthernetIf -from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config -from vyos.configdict import list_diff, intf_to_dict, add_to_dict, interface_default_data +from vyos.ifconfig_vlan import get_removed_vlans from vyos.validate import is_member -from vyos.config import Config +from vyos.xml import defaults from vyos import ConfigError - from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'deleted': False, - 'duplex': 'auto', - 'flow_control': 'on', - 'hw_id': '', - 'ip_arp_cache_tmo': 30, - 'ip_proxy_arp_pvlan': 0, - 'is_bond_member': False, - 'intf': '', - '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': [], - 'vrf': '' -} - 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 """ + conf = Config() + # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - ifname = os.environ['VYOS_TAGNODE_VALUE'] - conf = Config() - - # check if ethernet interface has been removed - cfg_base = ['interfaces', 'ethernet', ifname] - if not conf.exists(cfg_base): - eth = deepcopy(default_config_data) - eth['intf'] = ifname - 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) - - eth, disabled = intf_to_dict(conf, default_config_data) - - # 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') - - # interface duplex - if conf.exists('duplex'): - eth['duplex'] = conf.return_value('duplex') + # retrieve interface default values + base = ['interfaces', 'ethernet'] + default_values = defaults(base) - # 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 private VLAN proxy ARP on this interface - if conf.exists('ip proxy-arp-pvlan'): - eth['ip_proxy_arp_pvlan'] = 1 - - # check if we are a member of any bond - eth['is_bond_member'] = is_member(conf, eth['intf'], 'bonding') - - # 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') - - # remove default IPv6 link-local address if member of a bond - if eth['is_bond_member'] and 'fe80::/64' in eth['ipv6_eui64_prefix']: - eth['ipv6_eui64_prefix'].remove('fe80::/64') - eth['ipv6_eui64_prefix_remove'].append('fe80::/64') - - add_to_dict(conf, disabled, eth, 'vif', 'vif') - add_to_dict(conf, disabled, eth, 'vif-s', 'vif_s') - - return eth - - -def verify(eth): - if eth['deleted']: + ifname = os.environ['VYOS_TAGNODE_VALUE'] + base = base + [ifname] + # setup config level which is extracted in get_removed_vlans() + conf.set_level(base) + ethernet = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + + # Check if interface has been removed + if ethernet == {}: + ethernet.update({'deleted' : ''}) + + # We have gathered the dict representation of the CLI, but there are + # default options which we need to update into the dictionary + # retrived. + ethernet = dict_merge(default_values, ethernet) + + # Add interface instance name into dictionary + ethernet.update({'ifname': ifname}) + + # Check if we are a member of a bridge device + bridge = is_member(conf, ifname, 'bridge') + if bridge: + tmp = {'is_bridge_member' : bridge} + ethernet.update(tmp) + + # Check if we are a member of a bond device + bond = is_member(conf, ifname, 'bonding') + if bond: + tmp = {'is_bond_member' : bond} + ethernet.update(tmp) + + ethernet = T2665_default_dict_cleanup( ethernet ) + # Check vif, vif-s/vif-c VLAN interfaces for removal + ethernet = get_removed_vlans( conf, ethernet ) + return ethernet + +def verify(ethernet): + if 'deleted' in ethernet.keys(): return None - if eth['intf'] not in interfaces(): - raise ConfigError(f"Interface ethernet {eth['intf']} does not exist") + verify_interface_exists(ethernet) - if eth['speed'] == 'auto': - if eth['duplex'] != 'auto': + if ethernet.get('speed', None) == 'auto': + if ethernet.get('duplex', None) != 'auto': raise ConfigError('If speed is hardcoded, duplex must be hardcoded, too') - if eth['duplex'] == 'auto': - if eth['speed'] != 'auto': + if ethernet.get('duplex', None) == 'auto': + if ethernet.get('speed', None) != 'auto': raise ConfigError('If duplex is hardcoded, speed must be hardcoded, too') - if eth['dhcpv6_prm_only'] and eth['dhcpv6_temporary']: - raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + verify_dhcpv6(ethernet) + verify_address(ethernet) + verify_vrf(ethernet) - memberof = eth['is_bridge_member'] if eth['is_bridge_member'] else eth['is_bond_member'] - - if ( memberof - and ( eth['address'] - or eth['ipv6_eui64_prefix'] - or eth['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{eth["intf"]}" ' - f'as it is a member of "{memberof}"!')) - - if eth['vrf']: - if eth['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{eth["vrf"]}" does not exist') - - if memberof: - raise ConfigError(( - f'Interface "{eth["intf"]}" cannot be member of VRF "{eth["vrf"]}" ' - f'and "{memberof}" at the same time!')) - - if eth['mac'] and eth['is_bond_member']: - print('WARNING: "mac {0}" command will be ignored because {1} is a part of {2}'\ - .format(eth['mac'], eth['intf'], eth['is_bond_member'])) + if {'is_bond_member', 'mac'} <= set(ethernet): + print(f'WARNING: changing mac address "{mac}" will be ignored as "{ifname}" ' + f'is a member of bond "{is_bond_member}"'.format(**ethernet)) # use common function to verify VLAN configuration - verify_vlan_config(eth) + verify_vlan_config(ethernet) return None -def generate(eth): +def generate(ethernet): return None -def apply(eth): - e = EthernetIf(eth['intf']) - if eth['deleted']: - # apply all vlans to interface (they need removing too) - apply_all_vlans(e, eth) - +def apply(ethernet): + e = EthernetIf(ethernet['ifname']) + if 'deleted' in ethernet.keys(): # delete interface e.remove() else: - # update interface description used e.g. within SNMP - e.set_alias(eth['description']) - - if eth['dhcp_client_id']: - e.dhcp.v4.options['client_id'] = eth['dhcp_client_id'] - - if eth['dhcp_hostname']: - e.dhcp.v4.options['hostname'] = eth['dhcp_hostname'] - - if eth['dhcp_vendor_class_id']: - e.dhcp.v4.options['vendor_class_id'] = eth['dhcp_vendor_class_id'] - - if eth['dhcpv6_prm_only']: - e.dhcp.v6.options['dhcpv6_prm_only'] = True - - if eth['dhcpv6_temporary']: - e.dhcp.v6.options['dhcpv6_temporary'] = True - - if eth['dhcpv6_pd_length']: - e.dhcp.v6.options['dhcpv6_pd_length'] = eth['dhcpv6_pd_length'] - - if eth['dhcpv6_pd_interfaces']: - e.dhcp.v6.options['dhcpv6_pd_interfaces'] = eth['dhcpv6_pd_interfaces'] - - # ignore link state changes - e.set_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.set_arp_cache_tmo(eth['ip_arp_cache_tmo']) - # configure ARP filter configuration - e.set_arp_filter(eth['ip_disable_arp_filter']) - # configure ARP accept - e.set_arp_accept(eth['ip_enable_arp_accept']) - # configure ARP announce - e.set_arp_announce(eth['ip_enable_arp_announce']) - # configure ARP ignore - e.set_arp_ignore(eth['ip_enable_arp_ignore']) - # Enable proxy-arp on this interface - e.set_proxy_arp(eth['ip_proxy_arp']) - # Enable private VLAN proxy ARP on this interface - e.set_proxy_arp_pvlan(eth['ip_proxy_arp_pvlan']) - # IPv6 accept RA - e.set_ipv6_accept_ra(eth['ipv6_accept_ra']) - # IPv6 address autoconfiguration - e.set_ipv6_autoconf(eth['ipv6_autoconf']) - # IPv6 forwarding - e.set_ipv6_forwarding(eth['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - e.set_ipv6_dad_messages(eth['ipv6_dup_addr_detect']) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in eth['ipv6_eui64_prefix_remove']: - e.del_ipv6_eui64_address(addr) - - # Change interface MAC address - re-set to real hardware address (hw-id) - # if custom mac is removed. Skip if bond member. - if not eth['is_bond_member']: - if eth['mac']: - e.set_mac(eth['mac']) - elif eth['hw_id']: - e.set_mac(eth['hw_id']) - - # Add IPv6 EUI-based addresses - for addr in eth['ipv6_eui64_prefix']: - e.add_ipv6_eui64_address(addr) - - # Maximum Transmission Unit (MTU) - e.set_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']) - - # Enable/Disable interface - if eth['disable']: - e.set_admin_state('down') - else: - e.set_admin_state('up') - - # 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) - - # assign/remove VRF (ONLY when not a member of a bridge or bond, - # otherwise 'nomaster' removes it from it) - if not ( eth['is_bridge_member'] or eth['is_bond_member'] ): - e.set_vrf(eth['vrf']) - - # re-add ourselves to any bridge we might have fallen out of - if eth['is_bridge_member']: - e.add_to_bridge(eth['is_bridge_member']) - - # apply all vlans to interface - apply_all_vlans(e, eth) + e.update(ethernet) if __name__ == '__main__': -- cgit v1.2.3 From a25d7095e009469d8ef60b63deddd94d30921723 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 19 Jul 2020 20:45:29 +0200 Subject: bridge: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. While providing a new update() method in vyos.ifconfig.interfaces() this is extended for bridge interfaces in the derived bridge class. Signed-off-by: Christian Poessinger --- interface-definitions/interfaces-bridge.xml.in | 7 + python/vyos/configdict.py | 97 ++++++ python/vyos/ifconfig/bridge.py | 68 +++- python/vyos/ifconfig/ethernet.py | 20 -- python/vyos/ifconfig/interface.py | 25 ++ python/vyos/ifconfig_vlan.py | 9 +- python/vyos/util.py | 2 +- src/conf_mode/interfaces-bridge.py | 413 +++++-------------------- src/conf_mode/interfaces-ethernet.py | 54 +--- 9 files changed, 296 insertions(+), 399 deletions(-) (limited to 'src') diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index 6b610e623..92356d696 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -32,6 +32,7 @@ + 300 #include #include @@ -51,6 +52,7 @@ Forwarding delay must be between 0 and 200 seconds + 14 @@ -64,6 +66,7 @@ Bridge Hello interval must be between 1 and 10 seconds + 2 @@ -107,6 +110,7 @@ Bridge max aging value must be between 1 and 40 seconds + 20 @@ -133,6 +137,7 @@ Path cost value must be between 1 and 65535 + 100 @@ -146,6 +151,7 @@ Port priority value must be between 0 and 63 + 32 @@ -163,6 +169,7 @@ Bridge priority must be between 0 and 65535 (multiples of 4096) + 32768 diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 682caed8f..4fca426cd 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -17,6 +17,7 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion. """ +import jmespath from enum import Enum from copy import deepcopy @@ -132,6 +133,102 @@ def T2665_default_dict_cleanup(dict): return dict +def leaf_node_changed(conf, key): + """ + Check if a leaf node was altered. If it has been altered - values has been + changed, or it was added/removed, we will return the old value. If nothing + has been changed, None is returned + """ + from vyos.configdiff import get_config_diff + + D = get_config_diff(conf, key_mangling=('-', '_')) + D.set_level(conf.get_level()) + (new, old) = D.get_value_diff(key) + if new != old: + if isinstance(old, str): + return old + elif isinstance(old, list): + if isinstance(new, str): + new = [new] + elif isinstance(new, type(None)): + new = [] + return list_diff(old, new) + + return None + +def get_interface_dict(config, base, ifname): + """ + Common utility function to retrieve and mandgle the interfaces available + in CLI configuration. All interfaces have a common base ground where the + value retrival is identical - so it can and should be reused + + Will return a dictionary with the necessary interface configuration + """ + from vyos.xml import defaults + from vyos.ifconfig_vlan import get_removed_vlans + + # retrieve interface default values + default_values = defaults(base) + + # setup config level which is extracted in get_removed_vlans() + config.set_level(base + [ifname]) + dict = config.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + + # Check if interface has been removed + if dict == {}: + dict.update({'deleted' : ''}) + + # Add interface instance name into dictionary + dict.update({'ifname': ifname}) + + # We have gathered the dict representation of the CLI, but there are + # default options which we need to update into the dictionary + # retrived. + dict = dict_merge(default_values, dict) + + # Check if we are a member of a bridge device + bridge = is_member(config, ifname, 'bridge') + if bridge: + dict.update({'is_bridge_member' : bridge}) + + # Check if we are a member of a bond device + bond = is_member(config, ifname, 'bonding') + if bond: + dict.update({'is_bond_member' : bond}) + + mac = leaf_node_changed(config, ['mac']) + if mac: + dict.update({'mac_old' : mac}) + + eui64 = leaf_node_changed(config, ['ipv6', 'address', 'eui64']) + if eui64: + # XXX: T2636 workaround: convert string to a list with one element + if isinstance(eui64, str): + eui64 = [eui64] + tmp = jmespath.search('ipv6.address', dict) + if not tmp: + dict.update({'ipv6': {'address': {'eui64_old': eui64}}}) + else: + dict['ipv6']['address'].update({'eui64_old': eui64}) + + # remove wrongly inserted values + dict = T2665_default_dict_cleanup(dict) + + # The values are identical for vif, vif-s and vif-c as the all include the same + # XML definitions which hold the defaults + default_vif_values = defaults(base + ['vif']) + for vif, vif_config in dict.get('vif', {}).items(): + vif_config.update(default_vif_values) + for vif_s, vif_s_config in dict.get('vif_s', {}).items(): + vif_s_config.update(default_vif_values) + for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): + vif_c_config.update(default_vif_values) + + # Check vif, vif-s/vif-c VLAN interfaces for removal + dict = get_removed_vlans(config, dict) + + return dict + def get_ethertype(ethertype_val): if ethertype_val == '0x88A8': return '802.1ad' diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 44b92c1db..af950b35d 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -13,12 +13,13 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . +import jmespath from vyos.ifconfig.interface import Interface - +from vyos.ifconfig.stp import STP from vyos.validate import assert_boolean from vyos.validate import assert_positive - +from vyos.util import cmd @Interface.register class BridgeIf(Interface): @@ -187,3 +188,66 @@ class BridgeIf(Interface): >>> BridgeIf('br0').del_port('eth1') """ return self.set_interface('del_port', interface) + + 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. """ + + # now call the regular function from within our base class + super().update(config) + + # Set ageing time + value = config.get('aging') + self.set_ageing_time(value) + + # set bridge forward delay + value = config.get('forwarding_delay') + self.set_forward_delay(value) + + # set hello time + value = config.get('hello_time') + self.set_hello_time(value) + + # set max message age + value = config.get('max_age') + self.set_max_age(value) + + # set bridge priority + value = config.get('priority') + self.set_priority(value) + + # enable/disable spanning tree + value = '1' if 'stp' in config else '0' + self.set_stp(value) + + # enable or disable IGMP querier + tmp = jmespath.search('igmp.querier', config) + value = '1' if (tmp != None) else '0' + self.set_multicast_querier(value) + + # remove interface from bridge + tmp = jmespath.search('member.interface_remove', config) + if tmp: + for member in tmp: + self.del_port(member) + + STPBridgeIf = STP.enable(BridgeIf) + tmp = jmespath.search('member.interface', config) + if tmp: + for interface, interface_config in tmp.items(): + # if we've come here we already verified the interface doesn't + # have addresses configured so just flush any remaining ones + cmd(f'ip addr flush dev "{interface}"') + # enslave interface port to bridge + self.add_port(interface) + + tmp = STPBridgeIf(interface) + # set bridge port path cost + value = interface_config.get('cost') + tmp.set_path_cost(value) + + # set bridge port path priority + value = interface_config.get('priority') + tmp.set_path_priority(value) diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 8a50a8699..1725116e2 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -299,26 +299,6 @@ class EthernetIf(Interface): duplex = config.get('duplex') self.set_speed_duplex(speed, duplex) - # Delete old IPv6 EUI64 addresses before changing MAC - - # Change interface MAC address - re-set to real hardware address (hw-id) - # if custom mac is removed. Skip if bond member. - if 'is_bond_member' not in config: - mac = config.get('hw_id') - if 'mac' in config: - mac = config.get('mac') - if mac: - self.set_mac(mac) - - # Add IPv6 EUI-based addresses - tmp = jmespath.search('ipv6.address.eui64', config) - if tmp: - # XXX: T2636 workaround: convert string to a list with one element - if isinstance(tmp, str): - tmp = [tmp] - for addr in tmp: - self.add_ipv6_eui64_address(addr) - # re-add ourselves to any bridge we might have fallen out of if 'is_bridge_member' in config: bridge = config.get('is_bridge_member') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index be3617f7d..ea770af23 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -922,6 +922,31 @@ class Interface(Control): if 'mtu' in config: self.set_mtu(config.get('mtu')) + # Delete old IPv6 EUI64 addresses before changing MAC + tmp = jmespath.search('ipv6.address.eui64_old', config) + if tmp: + for addr in tmp: + self.del_ipv6_eui64_address(addr) + + # Change interface MAC address - re-set to real hardware address (hw-id) + # if custom mac is removed. Skip if bond member. + if 'is_bond_member' not in config: + mac = config.get('hw_id') + if 'mac' in config: + mac = config.get('mac') + if mac: + self.set_mac(mac) + + # Add IPv6 EUI-based addresses + tmp = jmespath.search('ipv6.address.eui64', config) + if tmp: + # XXX: T2636 workaround: convert string to a list with one element + if isinstance(tmp, str): + tmp = [tmp] + for addr in tmp: + self.add_ipv6_eui64_address(addr) + + # Interface administrative state state = 'down' if 'disable' in config else 'up' self.set_admin_state(state) diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index ecb6796fa..0e4ecda53 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -28,15 +28,18 @@ def get_removed_vlans(conf, dict): D.set_level(conf.get_level()) # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 keys = D.get_child_nodes_diff(['vif'], expand_nodes=Diff.DELETE)['delete'].keys() - dict['vif_remove'] = [*keys] + if keys: + dict.update({'vif_remove': [*keys]}) # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 keys = D.get_child_nodes_diff(['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys() - dict['vif_s_remove'] = [*keys] + if keys: + dict.update({'vif_s_remove': [*keys]}) for vif in dict.get('vif_s', {}).keys(): keys = D.get_child_nodes_diff(['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys() - dict['vif_s'][vif]['vif_c_remove'] = [*keys] + if keys: + dict.update({'vif_s': { vif : {'vif_c_remove': [*keys]}}}) return dict diff --git a/python/vyos/util.py b/python/vyos/util.py index 7234be6cb..7078762df 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -242,7 +242,7 @@ def chown(path, user, group): if not os.path.exists(path): return False - + uid = getpwnam(user).pw_uid gid = getgrnam(group).gr_gid os.chown(path, uid, gid) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 1e4fa5816..7998a251a 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -16,251 +16,116 @@ import os -from copy import deepcopy from sys import exit from netifaces import interfaces -from vyos.ifconfig import BridgeIf, Section -from vyos.ifconfig.stp import STP -from vyos.configdict import list_diff, interface_default_data -from vyos.validate import is_member, has_address_configured from vyos.config import Config -from vyos.util import cmd, get_bridge_member_config +from vyos.configdict import get_interface_dict +from vyos.configdiff import get_config_diff, Diff +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_vrf +from vyos.ifconfig import BridgeIf +from vyos.validate import is_member, has_address_configured +from vyos.xml import defaults + +from vyos.util import cmd from vyos import ConfigError from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'aging': 300, - 'arp_cache_tmo': 30, - 'deleted': False, - 'forwarding_delay': 14, - 'hello_time': 2, - 'igmp_querier': 0, - 'intf': '', - 'max_age': 20, - 'member': [], - 'member_remove': [], - 'priority': 32768, - 'stp': 0 -} +def get_removed_members(conf): + D = get_config_diff(conf, key_mangling=('-', '_')) + D.set_level(conf.get_level()) + # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 + keys = D.get_child_nodes_diff(['member', 'interface'], expand_nodes=Diff.DELETE)['delete'].keys() + return list(keys) def get_config(): - bridge = deepcopy(default_config_data) + """ + 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', 'bridge'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - bridge['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # Check if bridge has been removed - if not conf.exists('interfaces bridge ' + bridge['intf']): - bridge['deleted'] = True - return bridge - - # set new configuration level - conf.set_level('interfaces bridge ' + bridge['intf']) - - # retrieve configured interface addresses - if conf.exists('address'): - bridge['address'] = conf.return_values('address') - - # Determine interface addresses (currently effective) - to determine which - # address is no longer valid and needs to be removed - eff_addr = conf.return_effective_values('address') - bridge['address_remove'] = list_diff(eff_addr, bridge['address']) - - # retrieve aging - how long addresses are retained - if conf.exists('aging'): - bridge['aging'] = int(conf.return_value('aging')) - - # retrieve interface description - if conf.exists('description'): - bridge['description'] = conf.return_value('description') - - # get DHCP client identifier - if conf.exists('dhcp-options client-id'): - bridge['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'): - bridge['dhcp_hostname'] = conf.return_value('dhcp-options host-name') - - # DHCP client vendor identifier - if conf.exists('dhcp-options vendor-class-id'): - bridge['dhcp_vendor_class_id'] = conf.return_value('dhcp-options vendor-class-id') - - # DHCPv6 only acquire config parameters, no address - if conf.exists('dhcpv6-options parameters-only'): - bridge['dhcpv6_prm_only'] = True - - # DHCPv6 temporary IPv6 address - if conf.exists('dhcpv6-options temporary'): - bridge['dhcpv6_temporary'] = True - - # Disable this bridge interface - if conf.exists('disable'): - bridge['disable'] = True - - # Ignore link state changes - if conf.exists('disable-link-detect'): - bridge['disable_link_detect'] = 2 - - # Forwarding delay - if conf.exists('forwarding-delay'): - bridge['forwarding_delay'] = int(conf.return_value('forwarding-delay')) - - # Hello packet advertisment interval - if conf.exists('hello-time'): - bridge['hello_time'] = int(conf.return_value('hello-time')) - - # Enable Internet Group Management Protocol (IGMP) querier - if conf.exists('igmp querier'): - bridge['igmp_querier'] = 1 - - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - bridge['arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - - # ARP filter configuration - if conf.exists('ip disable-arp-filter'): - bridge['ip_disable_arp_filter'] = 0 - - # ARP enable accept - if conf.exists('ip enable-arp-accept'): - bridge['ip_enable_arp_accept'] = 1 - - # ARP enable announce - if conf.exists('ip enable-arp-announce'): - bridge['ip_enable_arp_announce'] = 1 - - # ARP enable ignore - if conf.exists('ip enable-arp-ignore'): - bridge['ip_enable_arp_ignore'] = 1 - - # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC) - if conf.exists('ipv6 address autoconf'): - bridge['ipv6_autoconf'] = 1 - - # Get prefixes for IPv6 addressing based on MAC address (EUI-64) - if conf.exists('ipv6 address eui64'): - bridge['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') - - # Determine currently effective EUI64 addresses - to determine which - # address is no longer valid and needs to be removed - eff_addr = conf.return_effective_values('ipv6 address eui64') - bridge['ipv6_eui64_prefix_remove'] = list_diff(eff_addr, bridge['ipv6_eui64_prefix']) - - # Remove the default link-local address if set. - if conf.exists('ipv6 address no-default-link-local'): - bridge['ipv6_eui64_prefix_remove'].append('fe80::/64') - else: - # add the link-local by default to make IPv6 work - bridge['ipv6_eui64_prefix'].append('fe80::/64') - - # Disable IPv6 forwarding on this interface - if conf.exists('ipv6 disable-forwarding'): - bridge['ipv6_forwarding'] = 0 - - # IPv6 Duplicate Address Detection (DAD) tries - if conf.exists('ipv6 dup-addr-detect-transmits'): - bridge['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits')) - - # Media Access Control (MAC) address - if conf.exists('mac'): - bridge['mac'] = conf.return_value('mac') - - # Find out if MAC has changed - if so, we need to delete all IPv6 EUI64 addresses - # before re-adding them - if ( bridge['mac'] and bridge['intf'] in Section.interfaces(section='bridge') - and bridge['mac'] != BridgeIf(bridge['intf'], create=False).get_mac() ): - bridge['ipv6_eui64_prefix_remove'] += bridge['ipv6_eui64_prefix'] - - # to make IPv6 SLAAC and DHCPv6 work with forwarding=1, - # accept_ra must be 2 - if bridge['ipv6_autoconf'] or 'dhcpv6' in bridge['address']: - bridge['ipv6_accept_ra'] = 2 - - # Interval at which neighbor bridges are removed - if conf.exists('max-age'): - bridge['max_age'] = int(conf.return_value('max-age')) - - # Determine bridge member interface (currently configured) - for intf in conf.list_nodes('member interface'): - # defaults are stored in util.py (they can't be here as all interface - # scripts use the function) - memberconf = get_bridge_member_config(conf, bridge['intf'], intf) - if memberconf: - memberconf['name'] = intf - bridge['member'].append(memberconf) - - # Determine bridge member interface (currently effective) - to determine which - # interfaces is no longer assigend to the bridge and thus can be removed - eff_intf = conf.list_effective_nodes('member interface') - act_intf = conf.list_nodes('member interface') - bridge['member_remove'] = list_diff(eff_intf, act_intf) - - # Priority for this bridge - if conf.exists('priority'): - bridge['priority'] = int(conf.return_value('priority')) - - # Enable spanning tree protocol - if conf.exists('stp'): - bridge['stp'] = 1 - - # retrieve VRF instance - if conf.exists('vrf'): - bridge['vrf'] = conf.return_value('vrf') + ifname = os.environ['VYOS_TAGNODE_VALUE'] + bridge = get_interface_dict(conf, base, ifname) + + # determine which members have been removed + tmp = get_removed_members(conf) + if tmp: + if 'member' in bridge: + bridge['member'].update({'interface_remove': tmp }) + else: + bridge.update({'member': {'interface_remove': tmp }}) + + if 'member' in bridge and 'interface' in bridge['member']: + # XXX TT2665 we need a copy of the dict keys for iteration, else we will get: + # RuntimeError: dictionary changed size during iteration + for interface in list(bridge['member']['interface']): + for key in ['cost', 'priority']: + if interface == key: + del bridge['member']['interface'][key] + continue + + # the default dictionary is not properly paged into the dict (see T2665) + # thus we will ammend it ourself + default_member_values = defaults(base + ['member', 'interface']) + + for interface, interface_config in bridge['member']['interface'].items(): + interface_config.update(default_member_values) + + # Check if we are a member of another bridge device + tmp = is_member(conf, interface, 'bridge') + if tmp and tmp != ifname: + interface_config.update({'is_bridge_member' : tmp}) + + # Check if we are a member of a bond device + tmp = is_member(conf, interface, 'bonding') + if tmp: + interface_config.update({'is_bond_member' : tmp}) + + # Bridge members must not have an assigned address + tmp = has_address_configured(conf, interface) + if tmp: + interface_config.update({'has_address' : ''}) return bridge def verify(bridge): - if bridge['dhcpv6_prm_only'] and bridge['dhcpv6_temporary']: - raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + if 'deleted' in bridge: + return None - vrf_name = bridge['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF "{vrf_name}" does not exist') + verify_dhcpv6(bridge) + verify_vrf(bridge) - conf = Config() - for intf in bridge['member']: - # the interface must exist prior adding it to a bridge - if intf['name'] not in interfaces(): - raise ConfigError(( - f'Cannot add nonexistent interface "{intf["name"]}" ' - f'to bridge "{bridge["intf"]}"')) + if 'member' in bridge: + member = bridge.get('member') + bridge_name = bridge['ifname'] + for interface, interface_config in member.get('interface', {}).items(): + error_msg = f'Can not add interface "{interface}" to bridge "{bridge_name}", ' - if intf['name'] == 'lo': - raise ConfigError('Loopback interface "lo" can not be added to a bridge') + if interface == 'lo': + raise ConfigError('Loopback interface "lo" can not be added to a bridge') - # bridge members aren't allowed to be members of another bridge - for br in conf.list_nodes('interfaces bridge'): - # it makes no sense to verify ourself in this case - if br == bridge['intf']: - continue + if interface not in interfaces(): + raise ConfigError(error_msg + 'it does not exist!') - tmp = conf.list_nodes(f'interfaces bridge {br} member interface') - if intf['name'] in tmp: - raise ConfigError(( - f'Cannot add interface "{intf["name"]}" to bridge ' - f'"{bridge["intf"]}", it is already a member of bridge "{br}"!')) + if 'is_bridge_member' in interface_config: + tmp = interface_config['is_bridge_member'] + raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') - # bridge members are not allowed to be bond members - tmp = is_member(conf, intf['name'], 'bonding') - if tmp: - raise ConfigError(( - f'Cannot add interface "{intf["name"]}" to bridge ' - f'"{bridge["intf"]}", it is already a member of bond "{tmp}"!')) + if 'is_bond_member' in interface_config: + tmp = interface_config['is_bond_member'] + raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') - # bridge members must not have an assigned address - if has_address_configured(conf, intf['name']): - raise ConfigError(( - f'Cannot add interface "{intf["name"]}" to bridge ' - f'"{bridge["intf"]}", it has an address assigned!')) + if 'has_address' in interface_config: + raise ConfigError(error_msg + 'it has an address assigned!') return None @@ -268,120 +133,12 @@ def generate(bridge): return None def apply(bridge): - br = BridgeIf(bridge['intf']) - - if bridge['deleted']: + br = BridgeIf(bridge['ifname']) + if 'deleted' in bridge: # delete interface br.remove() else: - # enable interface - br.set_admin_state('up') - # set ageing time - br.set_ageing_time(bridge['aging']) - # set bridge forward delay - br.set_forward_delay(bridge['forwarding_delay']) - # set hello time - br.set_hello_time(bridge['hello_time']) - # configure ARP filter configuration - br.set_arp_filter(bridge['ip_disable_arp_filter']) - # configure ARP accept - br.set_arp_accept(bridge['ip_enable_arp_accept']) - # configure ARP announce - br.set_arp_announce(bridge['ip_enable_arp_announce']) - # configure ARP ignore - br.set_arp_ignore(bridge['ip_enable_arp_ignore']) - # IPv6 accept RA - br.set_ipv6_accept_ra(bridge['ipv6_accept_ra']) - # IPv6 address autoconfiguration - br.set_ipv6_autoconf(bridge['ipv6_autoconf']) - # IPv6 forwarding - br.set_ipv6_forwarding(bridge['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - br.set_ipv6_dad_messages(bridge['ipv6_dup_addr_detect']) - # set max message age - br.set_max_age(bridge['max_age']) - # set bridge priority - br.set_priority(bridge['priority']) - # turn stp on/off - br.set_stp(bridge['stp']) - # enable or disable IGMP querier - br.set_multicast_querier(bridge['igmp_querier']) - # update interface description used e.g. within SNMP - br.set_alias(bridge['description']) - - if bridge['dhcp_client_id']: - br.dhcp.v4.options['client_id'] = bridge['dhcp_client_id'] - - if bridge['dhcp_hostname']: - br.dhcp.v4.options['hostname'] = bridge['dhcp_hostname'] - - if bridge['dhcp_vendor_class_id']: - br.dhcp.v4.options['vendor_class_id'] = bridge['dhcp_vendor_class_id'] - - if bridge['dhcpv6_prm_only']: - br.dhcp.v6.options['dhcpv6_prm_only'] = True - - if bridge['dhcpv6_temporary']: - br.dhcp.v6.options['dhcpv6_temporary'] = True - - if bridge['dhcpv6_pd_length']: - br.dhcp.v6.options['dhcpv6_pd_length'] = br['dhcpv6_pd_length'] - - if bridge['dhcpv6_pd_interfaces']: - br.dhcp.v6.options['dhcpv6_pd_interfaces'] = br['dhcpv6_pd_interfaces'] - - # assign/remove VRF - br.set_vrf(bridge['vrf']) - - # Delete old IPv6 EUI64 addresses before changing MAC - # (adding members to a fresh bridge changes its MAC too) - for addr in bridge['ipv6_eui64_prefix_remove']: - br.del_ipv6_eui64_address(addr) - - # remove interface from bridge - for intf in bridge['member_remove']: - br.del_port(intf) - - # add interfaces to bridge - for member in bridge['member']: - # if we've come here we already verified the interface doesn't - # have addresses configured so just flush any remaining ones - cmd(f'ip addr flush dev "{member["name"]}"') - br.add_port(member['name']) - - # Change interface MAC address - if bridge['mac']: - br.set_mac(bridge['mac']) - - # Add IPv6 EUI-based addresses (must be done after adding the - # 1st bridge member or setting its MAC) - for addr in bridge['ipv6_eui64_prefix']: - br.add_ipv6_eui64_address(addr) - - # up/down interface - if bridge['disable']: - br.set_admin_state('down') - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in bridge['address_remove']: - br.del_addr(addr) - for addr in bridge['address']: - br.add_addr(addr) - - STPBridgeIf = STP.enable(BridgeIf) - # configure additional bridge member options - for member in bridge['member']: - i = STPBridgeIf(member['name']) - # configure ARP cache timeout - i.set_arp_cache_tmo(member['arp_cache_tmo']) - # ignore link state changes - i.set_link_detect(member['disable_link_detect']) - # set bridge port path cost - i.set_path_cost(member['cost']) - # set bridge port path priority - i.set_path_priority(member['priority']) + br.update(bridge) return None diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 60aafae32..d43552e50 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -19,72 +19,36 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.configdict import T2665_default_dict_cleanup +from vyos.configdict import get_interface_dict from vyos.configverify import verify_interface_exists from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_address from vyos.configverify import verify_vrf from vyos.configverify import verify_vlan_config from vyos.ifconfig import EthernetIf -from vyos.ifconfig_vlan import get_removed_vlans -from vyos.validate import is_member -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag 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', 'ethernet'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - # retrieve interface default values - base = ['interfaces', 'ethernet'] - default_values = defaults(base) - ifname = os.environ['VYOS_TAGNODE_VALUE'] - base = base + [ifname] - # setup config level which is extracted in get_removed_vlans() - conf.set_level(base) - ethernet = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) - - # Check if interface has been removed - if ethernet == {}: - ethernet.update({'deleted' : ''}) - - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary - # retrived. - ethernet = dict_merge(default_values, ethernet) - - # Add interface instance name into dictionary - ethernet.update({'ifname': ifname}) - - # Check if we are a member of a bridge device - bridge = is_member(conf, ifname, 'bridge') - if bridge: - tmp = {'is_bridge_member' : bridge} - ethernet.update(tmp) - - # Check if we are a member of a bond device - bond = is_member(conf, ifname, 'bonding') - if bond: - tmp = {'is_bond_member' : bond} - ethernet.update(tmp) - - ethernet = T2665_default_dict_cleanup( ethernet ) - # Check vif, vif-s/vif-c VLAN interfaces for removal - ethernet = get_removed_vlans( conf, ethernet ) + ethernet = get_interface_dict(conf, base, ifname) return ethernet def verify(ethernet): - if 'deleted' in ethernet.keys(): + if 'deleted' in ethernet: return None verify_interface_exists(ethernet) @@ -114,7 +78,7 @@ def generate(ethernet): def apply(ethernet): e = EthernetIf(ethernet['ifname']) - if 'deleted' in ethernet.keys(): + if 'deleted' in ethernet: # delete interface e.remove() else: -- cgit v1.2.3 From c8cd7951e38ae2819d4c9f87089fcf59b7e6b70d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 19 Jul 2020 22:25:12 +0200 Subject: pseudo-ethernet: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. While providing a new update() method in vyos.ifconfig.interfaces() this is extended for pdeudo-ethernet interfaces in the derived class. --- .../interfaces-pseudo-ethernet.xml.in | 1 + src/conf_mode/interfaces-pseudo-ethernet.py | 216 +++------------------ 2 files changed, 32 insertions(+), 185 deletions(-) (limited to 'src') diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in index d5f9ca661..0ef45e2c2 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -70,6 +70,7 @@ mode must be private, vepa, bridge or passthru + private #include #include diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index fb8237bee..cce9b020b 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -18,115 +18,52 @@ import os from copy import deepcopy from sys import exit -from netifaces import interfaces from vyos.config import Config -from vyos.configdict import list_diff, intf_to_dict, add_to_dict, interface_default_data -from vyos.ifconfig import MACVLANIf, Section -from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config +from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_node_changed +from vyos.configverify import verify_vrf +from vyos.configverify import verify_address +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 import ConfigError from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'deleted': False, - 'intf': '', - 'ip_arp_cache_tmo': 30, - 'ip_proxy_arp_pvlan': 0, - 'source_interface': '', - 'recreating_required': False, - 'mode': 'private', - 'vif_s': {}, - 'vif_s_remove': [], - 'vif': {}, - 'vif_remove': [], - 'vrf': '' -} - def get_config(): - peth = deepcopy(default_config_data) + """ + 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'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - peth['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # Check if interface has been removed - cfg_base = ['interfaces', 'pseudo-ethernet', peth['intf']] - if not conf.exists(cfg_base): - peth['deleted'] = True - return peth - - # set new configuration level - conf.set_level(cfg_base) - - peth, disabled = intf_to_dict(conf, default_config_data) - - # ARP cache entry timeout in seconds - if conf.exists(['ip', 'arp-cache-timeout']): - peth['ip_arp_cache_tmo'] = int(conf.return_value(['ip', 'arp-cache-timeout'])) - - # Enable private VLAN proxy ARP on this interface - if conf.exists(['ip', 'proxy-arp-pvlan']): - peth['ip_proxy_arp_pvlan'] = 1 - - # Physical interface - if conf.exists(['source-interface']): - peth['source_interface'] = conf.return_value(['source-interface']) - tmp = conf.return_effective_value(['source-interface']) - if tmp != peth['source_interface']: - peth['recreating_required'] = True - - # MACvlan mode - if conf.exists(['mode']): - peth['mode'] = conf.return_value(['mode']) - tmp = conf.return_effective_value(['mode']) - if tmp != peth['mode']: - peth['recreating_required'] = True + ifname = os.environ['VYOS_TAGNODE_VALUE'] + peth = get_interface_dict(conf, base, ifname) - add_to_dict(conf, disabled, peth, 'vif', 'vif') - add_to_dict(conf, disabled, peth, 'vif-s', 'vif_s') + mode = leaf_node_changed(conf, ['mode']) + if mode: + peth.update({'mode_old' : mode}) + import pprint + pprint.pprint(peth) return peth def verify(peth): - if peth['deleted']: - if peth['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{peth["intf"]}" as it is a ' - f'member of bridge "{peth["is_bridge_member"]}"!')) - + if 'deleted' in peth: + verify_bridge_delete(peth) return None - if not peth['source_interface']: - raise ConfigError(( - f'Link device must be set for pseudo-ethernet "{peth["intf"]}"')) - - if not peth['source_interface'] in interfaces(): - raise ConfigError(( - f'Pseudo-ethernet "{peth["intf"]}" link device does not exist')) - - if ( peth['is_bridge_member'] - and ( peth['address'] - or peth['ipv6_eui64_prefix'] - or peth['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{peth["intf"]}" ' - f'as it is a member of bridge "{peth["is_bridge_member"]}"!')) - - if peth['vrf']: - if peth['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{peth["vrf"]}" does not exist') - - if peth['is_bridge_member']: - raise ConfigError(( - f'Interface "{peth["intf"]}" cannot be member of VRF ' - f'"{peth["vrf"]}" and bridge {peth["is_bridge_member"]} ' - f'at the same time!')) + verify_source_interface(peth) + verify_vrf(peth) + verify_address(peth) # use common function to verify VLAN configuration verify_vlan_config(peth) @@ -136,17 +73,16 @@ def generate(peth): return None def apply(peth): - if peth['deleted']: + if 'deleted' in peth: # delete interface - MACVLANIf(peth['intf']).remove() + MACVLANIf(peth['ifname']).remove() 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. - if peth['intf'] in interfaces(): - if peth['recreating_required']: - MACVLANIf(peth['intf']).remove() + if 'mode_old' in peth: + MACVLANIf(peth['ifname']).remove() # MACVLAN interface needs to be created on-block instead of passing a ton # of arguments, I just use a dict that is managed by vyos.ifconfig @@ -158,98 +94,8 @@ def apply(peth): # It is safe to "re-create" the interface always, there is a sanity check # that the interface will only be create if its non existent - p = MACVLANIf(peth['intf'], **conf) - - # update interface description used e.g. within SNMP - p.set_alias(peth['description']) - - if peth['dhcp_client_id']: - p.dhcp.v4.options['client_id'] = peth['dhcp_client_id'] - - if peth['dhcp_hostname']: - p.dhcp.v4.options['hostname'] = peth['dhcp_hostname'] - - if peth['dhcp_vendor_class_id']: - p.dhcp.v4.options['vendor_class_id'] = peth['dhcp_vendor_class_id'] - - if peth['dhcpv6_prm_only']: - p.dhcp.v6.options['dhcpv6_prm_only'] = True - - if peth['dhcpv6_temporary']: - p.dhcp.v6.options['dhcpv6_temporary'] = True - - if peth['dhcpv6_pd_length']: - p.dhcp.v6.options['dhcpv6_pd_length'] = peth['dhcpv6_pd_length'] - - if peth['dhcpv6_pd_interfaces']: - p.dhcp.v6.options['dhcpv6_pd_interfaces'] = peth['dhcpv6_pd_interfaces'] - - # ignore link state changes - p.set_link_detect(peth['disable_link_detect']) - # configure ARP cache timeout in milliseconds - p.set_arp_cache_tmo(peth['ip_arp_cache_tmo']) - # configure ARP filter configuration - p.set_arp_filter(peth['ip_disable_arp_filter']) - # configure ARP accept - p.set_arp_accept(peth['ip_enable_arp_accept']) - # configure ARP announce - p.set_arp_announce(peth['ip_enable_arp_announce']) - # configure ARP ignore - p.set_arp_ignore(peth['ip_enable_arp_ignore']) - # Enable proxy-arp on this interface - p.set_proxy_arp(peth['ip_proxy_arp']) - # Enable private VLAN proxy ARP on this interface - p.set_proxy_arp_pvlan(peth['ip_proxy_arp_pvlan']) - # IPv6 accept RA - p.set_ipv6_accept_ra(peth['ipv6_accept_ra']) - # IPv6 address autoconfiguration - p.set_ipv6_autoconf(peth['ipv6_autoconf']) - # IPv6 forwarding - p.set_ipv6_forwarding(peth['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - p.set_ipv6_dad_messages(peth['ipv6_dup_addr_detect']) - - # assign/remove VRF (ONLY when not a member of a bridge, - # otherwise 'nomaster' removes it from it) - if not peth['is_bridge_member']: - p.set_vrf(peth['vrf']) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in peth['ipv6_eui64_prefix_remove']: - p.del_ipv6_eui64_address(addr) - - # Change interface MAC address - if peth['mac']: - p.set_mac(peth['mac']) - - # Add IPv6 EUI-based addresses - for addr in peth['ipv6_eui64_prefix']: - p.add_ipv6_eui64_address(addr) - - # Change interface mode - p.set_mode(peth['mode']) - - # Enable/Disable interface - if peth['disable']: - p.set_admin_state('down') - else: - p.set_admin_state('up') - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in peth['address_remove']: - p.del_addr(addr) - for addr in peth['address']: - p.add_addr(addr) - - # re-add ourselves to any bridge we might have fallen out of - if peth['is_bridge_member']: - p.add_to_bridge(peth['is_bridge_member']) - - # apply all vlans to interface - apply_all_vlans(p, peth) - + p = MACVLANIf(peth['ifname'], **conf) + p.update(peth) return None if __name__ == '__main__': -- cgit v1.2.3 From c9ba8952ad7c373d633516933ddb97e178e339c8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 20 Jul 2020 21:45:24 +0200 Subject: interfaces: ifconfig: T2653: migrate to get_interface_dict() API After switching from raw parsing of the interface options to get_config_dict() this utilizes another utility function which wraps get_config_dict() and adds other common and reused parameters (like deleted or bridge member). Overall this drops redundant code (again) and makes the rest more maintainable as we only utilize a single function. --- src/conf_mode/interfaces-dummy.py | 26 ++++--------- src/conf_mode/interfaces-ethernet.py | 1 - src/conf_mode/interfaces-loopback.py | 24 +++++------- src/conf_mode/interfaces-macsec.py | 57 +++++++++-------------------- src/conf_mode/interfaces-pppoe.py | 43 ++++++++-------------- src/conf_mode/interfaces-pseudo-ethernet.py | 2 - src/conf_mode/interfaces-wirelessmodem.py | 40 +++++++------------- 7 files changed, 64 insertions(+), 129 deletions(-) (limited to 'src') diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 2d62420a6..6d2a78e30 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -19,41 +19,29 @@ import os from sys import exit from vyos.config import Config +from vyos.configdict import get_interface_dict from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.ifconfig import DummyIf -from vyos.validate import is_member from vyos import ConfigError from vyos import airbag 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', 'dummy'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') ifname = os.environ['VYOS_TAGNODE_VALUE'] - base = ['interfaces', 'dummy', ifname] - - dummy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # Check if interface has been removed - if dummy == {}: - dummy.update({'deleted' : ''}) - - # store interface instance name in dictionary - dummy.update({'ifname': ifname}) - - # check if we are a member of any bridge - bridge = is_member(conf, ifname, 'bridge') - if bridge: - tmp = {'is_bridge_member' : bridge} - dummy.update(tmp) - + dummy = get_interface_dict(conf, base, ifname) return dummy def verify(dummy): diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index d43552e50..24ea0af7c 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -30,7 +30,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() - def get_config(): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py index 2368f88a9..68a1392ff 100755 --- a/src/conf_mode/interfaces-loopback.py +++ b/src/conf_mode/interfaces-loopback.py @@ -18,31 +18,27 @@ import os from sys import exit -from vyos.ifconfig import LoopbackIf from vyos.config import Config -from vyos import ConfigError, airbag +from vyos.configdict import get_interface_dict +from vyos.ifconfig import LoopbackIf +from vyos import ConfigError +from vyos import airbag 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', 'loopback'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') ifname = os.environ['VYOS_TAGNODE_VALUE'] - base = ['interfaces', 'loopback', ifname] - - loopback = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # Check if interface has been removed - if loopback == {}: - loopback.update({'deleted' : ''}) - - # store interface instance name in dictionary - loopback.update({'ifname': ifname}) - + loopback = get_interface_dict(conf, base, ifname) return loopback def verify(loopback): diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 56273f71a..06aee9ea0 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -20,16 +20,14 @@ from copy import deepcopy from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.configdict import get_interface_dict from vyos.ifconfig import MACsecIf from vyos.template import render from vyos.util import call -from vyos.validate import is_member from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_source_interface -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -38,50 +36,31 @@ airbag.enable() wpa_suppl_conf = '/run/wpa_supplicant/{source_interface}.conf' 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', 'macsec'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - # retrieve interface default values - base = ['interfaces', 'macsec'] - default_values = defaults(base) - ifname = os.environ['VYOS_TAGNODE_VALUE'] - base = base + [ifname] + macsec = get_interface_dict(conf, base, ifname) - macsec = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) # Check if interface has been removed - if macsec == {}: - tmp = { - 'deleted' : '', - 'source_interface' : conf.return_effective_value( + if 'deleted' in macsec: + source_interface = conf.return_effective_value( base + ['source-interface']) - } - macsec.update(tmp) - - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary - # retrived. - macsec = dict_merge(default_values, macsec) - - # Add interface instance name into dictionary - macsec.update({'ifname': ifname}) - - # Check if we are a member of any bridge - bridge = is_member(conf, ifname, 'bridge') - if bridge: - tmp = {'is_bridge_member' : bridge} - macsec.update(tmp) + macsec.update({'source_interface': source_interface}) return macsec def verify(macsec): - if 'deleted' in macsec.keys(): + if 'deleted' in macsec: verify_bridge_delete(macsec) return None @@ -89,18 +68,18 @@ def verify(macsec): verify_vrf(macsec) verify_address(macsec) - if not (('security' in macsec.keys()) and - ('cipher' in macsec['security'].keys())): + if not (('security' in macsec) and + ('cipher' in macsec['security'])): raise ConfigError( 'Cipher suite must be set for MACsec "{ifname}"'.format(**macsec)) - if (('security' in macsec.keys()) and - ('encrypt' in macsec['security'].keys())): + if (('security' in macsec) and + ('encrypt' in macsec['security'])): tmp = macsec.get('security') - if not (('mka' in tmp.keys()) and - ('cak' in tmp['mka'].keys()) and - ('ckn' in tmp['mka'].keys())): + if not (('mka' in tmp) and + ('cak' in tmp['mka']) and + ('ckn' in tmp['mka'])): raise ConfigError('Missing mandatory MACsec security ' 'keys as encryption is enabled!') diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 3ee57e83c..6947cc1e2 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -22,51 +22,40 @@ from copy import deepcopy from netifaces import interfaces from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.configdict import get_interface_dict from vyos.configverify import verify_source_interface from vyos.configverify import verify_vrf from vyos.template import render from vyos.util import call -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag 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', 'pppoe'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - # retrieve interface default values - base = ['interfaces', 'pppoe'] - default_values = defaults(base) - # PPPoE is "special" the default MTU is 1492 - update accordingly - default_values['mtu'] = '1492' - ifname = os.environ['VYOS_TAGNODE_VALUE'] - base = base + [ifname] + pppoe = get_interface_dict(conf, base, ifname) - pppoe = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # Check if interface has been removed - if pppoe == {}: - pppoe.update({'deleted' : ''}) - - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary - # retrived. - pppoe = dict_merge(default_values, pppoe) - - # Add interface instance name into dictionary - pppoe.update({'ifname': ifname}) + # PPPoE is "special" the default MTU is 1492 - update accordingly + # as the config_level is already st in get_interface_dict() - we can use [] + tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if 'mtu' not in tmp: + pppoe['mtu'] = '1492' return pppoe def verify(pppoe): - if 'deleted' in pppoe.keys(): + if 'deleted' in pppoe: # bail out early return None @@ -92,7 +81,7 @@ def generate(pppoe): config_files = [config_pppoe, script_pppoe_pre_up, script_pppoe_ip_up, script_pppoe_ip_down, script_pppoe_ipv6_up, config_wide_dhcp6c] - if 'deleted' in pppoe.keys(): + if 'deleted' in pppoe: # stop DHCPv6-PD client call(f'systemctl stop dhcp6c@{ifname}.service') # Hang-up PPPoE connection @@ -130,11 +119,11 @@ def generate(pppoe): return None def apply(pppoe): - if 'deleted' in pppoe.keys(): + if 'deleted' in pppoe: # bail out early return None - if 'disable' not in pppoe.keys(): + if 'disable' not in pppoe: # Dial PPPoE connection call('systemctl restart ppp@{ifname}.service'.format(**pppoe)) diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index cce9b020b..55f11e65e 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -52,8 +52,6 @@ def get_config(): if mode: peth.update({'mode_old' : mode}) - import pprint - pprint.pprint(peth) return peth def verify(peth): diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index 0964a8f4d..9a5dae9e0 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -20,11 +20,11 @@ from fnmatch import fnmatch from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.configdict import get_interface_dict from vyos.configverify import verify_vrf from vyos.template import render from vyos.util import call -from vyos.xml import defaults +from vyos.util import check_kmod from vyos import ConfigError from vyos import airbag airbag.enable() @@ -42,44 +42,30 @@ def find_device_file(device): return None 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', 'wirelessmodem'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - # retrieve interface default values - base = ['interfaces', 'wirelessmodem'] - default_values = defaults(base) - ifname = os.environ['VYOS_TAGNODE_VALUE'] - base = base + [ifname] - - wwan = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # Check if interface has been removed - if wwan == {}: - wwan.update({'deleted' : ''}) - - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary - # retrived. - wwan = dict_merge(default_values, wwan) - - # Add interface instance name into dictionary - wwan.update({'ifname': ifname}) + wwan = get_interface_dict(conf, base, ifname) return wwan def verify(wwan): - if 'deleted' in wwan.keys(): + if 'deleted' in wwan: return None - if not 'apn' in wwan.keys(): + if not 'apn' in wwan: raise ConfigError('No APN configured for "{ifname}"'.format(**wwan)) - if not 'device' in wwan.keys(): + if not 'device' in wwan: raise ConfigError('Physical "device" must be configured') # we can not use isfile() here as Linux device files are no regular files @@ -136,11 +122,11 @@ def generate(wwan): return None def apply(wwan): - if 'deleted' in wwan.keys(): + if 'deleted' in wwan: # bail out early return None - if not 'disable' in wwan.keys(): + if not 'disable' in wwan: # "dial" WWAN connection call('systemctl start ppp@{ifname}.service'.format(**wwan)) -- cgit v1.2.3 From 92dd96726dd638593ab0794ab9b66cfb33b2ac67 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Jul 2020 22:00:12 +0200 Subject: geneve: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. --- src/conf_mode/interfaces-geneve.py | 128 +++++++------------------------------ 1 file changed, 23 insertions(+), 105 deletions(-) (limited to 'src') diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 31f6eb6b5..868ab5ccf 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# Copyright (C) 2019-2020 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 @@ -21,102 +21,44 @@ from copy import deepcopy from netifaces import interfaces from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete from vyos.ifconfig import GeneveIf -from vyos.validate import is_member from vyos import ConfigError from vyos import airbag airbag.enable() -default_config_data = { - 'address': [], - 'deleted': False, - 'description': '', - 'disable': False, - 'intf': '', - 'ip_arp_cache_tmo': 30, - 'ip_proxy_arp': 0, - 'is_bridge_member': False, - 'mtu': 1500, - 'remote': '', - 'vni': '' -} - def get_config(): - geneve = deepcopy(default_config_data) + """ + 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', 'geneve'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - geneve['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # check if interface is member if a bridge - geneve['is_bridge_member'] = is_member(conf, geneve['intf'], 'bridge') - - # Check if interface has been removed - if not conf.exists('interfaces geneve ' + geneve['intf']): - geneve['deleted'] = True - return geneve - - # set new configuration level - conf.set_level('interfaces geneve ' + geneve['intf']) - - # retrieve configured interface addresses - if conf.exists('address'): - geneve['address'] = conf.return_values('address') - - # retrieve interface description - if conf.exists('description'): - geneve['description'] = conf.return_value('description') - - # Disable this interface - if conf.exists('disable'): - geneve['disable'] = True - - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - geneve['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'): - geneve['ip_proxy_arp'] = 1 - - # Maximum Transmission Unit (MTU) - if conf.exists('mtu'): - geneve['mtu'] = int(conf.return_value('mtu')) - - # Remote address of GENEVE tunnel - if conf.exists('remote'): - geneve['remote'] = conf.return_value('remote') - - # Virtual Network Identifier - if conf.exists('vni'): - geneve['vni'] = conf.return_value('vni') + ifname = os.environ['VYOS_TAGNODE_VALUE'] + geneve = get_interface_dict(conf, base, ifname) return geneve - def verify(geneve): - if geneve['deleted']: - if geneve['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{geneve["intf"]}" as it is a ' - f'member of bridge "{geneve["is_bridge_member"]}"!')) - + if 'deleted' in geneve: + verify_bridge_delete(geneve) return None - if geneve['is_bridge_member'] and geneve['address']: - raise ConfigError(( - f'Cannot assign address to interface "{geneve["intf"]}" ' - f'as it is a member of bridge "{geneve["is_bridge_member"]}"!')) + verify_address(geneve) - if not geneve['remote']: - raise ConfigError('GENEVE remote must be configured') + if 'remote' not in geneve: + raise ConfigError('Remote side must be configured') - if not geneve['vni']: - raise ConfigError('GENEVE VNI must be configured') + if 'vni' not in geneve: + raise ConfigError('VNI must be configured') return None @@ -127,13 +69,13 @@ def generate(geneve): def apply(geneve): # Check if GENEVE interface already exists - if geneve['intf'] in interfaces(): - g = GeneveIf(geneve['intf']) + if geneve['ifname'] in interfaces(): + g = GeneveIf(geneve['ifname']) # GENEVE is super picky and the tunnel always needs to be recreated, # thus we can simply always delete it first. g.remove() - if not geneve['deleted']: + if 'deleted' not in geneve: # GENEVE interface needs to be created on-block # instead of passing a ton of arguments, I just use a dict # that is managed by vyos.ifconfig @@ -144,32 +86,8 @@ def apply(geneve): conf['remote'] = geneve['remote'] # Finally create the new interface - g = GeneveIf(geneve['intf'], **conf) - # update interface description used e.g. by SNMP - g.set_alias(geneve['description']) - # Maximum Transfer Unit (MTU) - g.set_mtu(geneve['mtu']) - - # configure ARP cache timeout in milliseconds - g.set_arp_cache_tmo(geneve['ip_arp_cache_tmo']) - # Enable proxy-arp on this interface - g.set_proxy_arp(geneve['ip_proxy_arp']) - - # 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 geneve['address']: - g.add_addr(addr) - - # As the GENEVE interface is always disabled first when changing - # parameters we will only re-enable the interface if it is not - # administratively disabled - if not geneve['disable']: - g.set_admin_state('up') - - # re-add ourselves to any bridge we might have fallen out of - if geneve['is_bridge_member']: - g.add_to_bridge(geneve['is_bridge_member']) + g = GeneveIf(geneve['ifname'], **conf) + g.update(geneve) return None -- cgit v1.2.3 From 3ee6030b98f8afdbc3a606ce458e11323e59b23c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Jul 2020 22:08:22 +0200 Subject: vyos.configdict: T2653: add new reusable helper node_changed() This can be used to see if a tagNode has been changed. It will return a list of changed nodes. --- python/vyos/configdict.py | 18 +++++++++++++++--- src/conf_mode/interfaces-bridge.py | 11 ++--------- 2 files changed, 17 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 4fca426cd..7f05a15ed 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -133,17 +133,16 @@ def T2665_default_dict_cleanup(dict): return dict -def leaf_node_changed(conf, key): +def leaf_node_changed(conf, path): """ Check if a leaf node was altered. If it has been altered - values has been changed, or it was added/removed, we will return the old value. If nothing has been changed, None is returned """ from vyos.configdiff import get_config_diff - D = get_config_diff(conf, key_mangling=('-', '_')) D.set_level(conf.get_level()) - (new, old) = D.get_value_diff(key) + (new, old) = D.get_value_diff(path) if new != old: if isinstance(old, str): return old @@ -156,6 +155,19 @@ def leaf_node_changed(conf, key): return None +def node_changed(conf, path): + """ + Check if a leaf node was altered. If it has been altered - values has been + changed, or it was added/removed, we will return the old value. If nothing + has been changed, None is returned + """ + from vyos.configdiff import get_config_diff, Diff + D = get_config_diff(conf, key_mangling=('-', '_')) + D.set_level(conf.get_level()) + # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 + keys = D.get_child_nodes_diff(path, expand_nodes=Diff.DELETE)['delete'].keys() + return list(keys) + def get_interface_dict(config, base, ifname): """ Common utility function to retrieve and mandgle the interfaces available diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 7998a251a..9c43d1983 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -21,7 +21,7 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict -from vyos.configdiff import get_config_diff, Diff +from vyos.configdict import node_changed from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_vrf from vyos.ifconfig import BridgeIf @@ -34,13 +34,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_removed_members(conf): - D = get_config_diff(conf, key_mangling=('-', '_')) - D.set_level(conf.get_level()) - # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 - keys = D.get_child_nodes_diff(['member', 'interface'], expand_nodes=Diff.DELETE)['delete'].keys() - return list(keys) - def get_config(): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the @@ -57,7 +50,7 @@ def get_config(): bridge = get_interface_dict(conf, base, ifname) # determine which members have been removed - tmp = get_removed_members(conf) + tmp = node_changed(conf, ['member', 'interface']) if tmp: if 'member' in bridge: bridge['member'].update({'interface_remove': tmp }) -- cgit v1.2.3 From f81b0443cf09c34cb1f2060094e3eb294b8fa192 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 17:20:50 +0200 Subject: bonding: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. --- interface-definitions/interfaces-bonding.xml.in | 2 + python/vyos/ifconfig/bond.py | 118 ++++++- python/vyos/ifconfig/interface.py | 16 +- python/vyos/validate.py | 5 +- src/conf_mode/interfaces-bonding.py | 437 ++++++------------------ 5 files changed, 241 insertions(+), 337 deletions(-) (limited to 'src') diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in index ddd52979b..7d658f6a0 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -78,6 +78,7 @@ hash-policy must be layer2 layer2+3 or layer3+4 + layer2 @@ -137,6 +138,7 @@ mode must be 802.3ad, active-backup, broadcast, round-robin, transmit-load-balance, adaptive-load-balance, or xor + 802.3ad diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index 47dd4ff34..5a48ac632 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -14,14 +14,15 @@ # License along with this library. If not, see . import os +import jmespath from vyos.ifconfig.interface import Interface from vyos.ifconfig.vlan import VLAN +from vyos.util import cmd from vyos.validate import assert_list from vyos.validate import assert_positive - @Interface.register @VLAN.enable class BondIf(Interface): @@ -179,7 +180,13 @@ class BondIf(Interface): >>> BondIf('bond0').get_arp_ip_target() '192.0.2.1' """ - return self.get_interface('bond_arp_ip_target') + # As this function might also be called from update() of a VLAN interface + # we must check if the bond_arp_ip_target retrieval worked or not - as this + # can not be set for a bond vif interface + try: + return self.get_interface('bond_arp_ip_target') + except FileNotFoundError: + return '' def set_arp_ip_target(self, target): """ @@ -209,11 +216,31 @@ class BondIf(Interface): >>> BondIf('bond0').add_port('eth0') >>> BondIf('bond0').add_port('eth1') """ - # An interface can only be added to a bond if it is in 'down' state. If - # interface is in 'up' state, the following Kernel error will be thrown: - # bond0: eth1 is up - this may be due to an out of date ifenslave. - Interface(interface).set_admin_state('down') - return self.set_interface('bond_add_port', f'+{interface}') + + # From drivers/net/bonding/bond_main.c: + # ... + # bond_set_slave_link_state(new_slave, + # BOND_LINK_UP, + # BOND_SLAVE_NOTIFY_NOW); + # ... + # + # The kernel will ALWAYS place new bond members in "up" state regardless + # what the CLI will tell us! + + # Physical interface must be in admin down state before they can be + # enslaved. If this is not the case an error will be shown: + # bond0: eth0 is up - this may be due to an out of date ifenslave + slave = Interface(interface) + slave_state = slave.get_admin_state() + if slave_state == 'up': + slave.set_admin_state('down') + + ret = self.set_interface('bond_add_port', f'+{interface}') + # The kernel will ALWAYS place new bond members in "up" state regardless + # what the LI is configured for - thus we place the interface in its + # desired state + slave.set_admin_state(slave_state) + return ret def del_port(self, interface): """ @@ -277,3 +304,80 @@ class BondIf(Interface): >>> BondIf('bond0').set_mode('802.3ad') """ return self.set_interface('bond_mode', mode) + + 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. """ + + # use ref-counting function to place an interface into admin down state. + # set_admin_state_up() must be called the same amount of times else the + # interface won't come up. This can/should be used to prevent link flapping + # when changing interface parameters require the interface to be down. + # We will disable it once before reconfiguration and enable it afterwards. + if 'shutdown_required' in config: + self.set_admin_state('down') + + # call base class first + super().update(config) + + # ARP monitor targets need to be synchronized between sysfs and CLI. + # Unfortunately an address can't be send twice to sysfs as this will + # result in the following exception: OSError: [Errno 22] Invalid argument. + # + # We remove ALL addresses prior to adding new ones, this will remove + # addresses manually added by the user too - but as we are limited to 16 adresses + # from the kernel side this looks valid to me. We won't run into an error + # when a user added manual adresses which would result in having more + # then 16 adresses in total. + arp_tgt_addr = list(map(str, self.get_arp_ip_target().split())) + for addr in arp_tgt_addr: + self.set_arp_ip_target('-' + addr) + + # Add configured ARP target addresses + value = jmespath.search('arp_monitor.target', config) + if isinstance(value, str): + value = [value] + if value: + for addr in value: + self.set_arp_ip_target('+' + addr) + + # Bonding transmit hash policy + value = config.get('hash_policy') + if value: self.set_hash_policy(value) + + # Some interface options can only be changed if the interface is + # administratively down + if self.get_admin_state() == 'down': + # Delete bond member port(s) + for interface in self.get_slaves(): + self.del_port(interface) + + # Bonding policy/mode + value = config.get('mode') + if value: self.set_mode(value) + + # Add (enslave) interfaces to bond + value = jmespath.search('member.interface', config) + if value: + for interface in value: + # if we've come here we already verified the interface does + # not have an addresses configured so just flush any + # remaining ones + cmd(f'ip addr flush dev "{interface}"') + self.add_port(interface) + + # Primary device interface - must be set after 'mode' + value = config.get('primary') + if value: self.set_primary(value) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 1fe4f74f2..7e887db1b 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -205,6 +205,7 @@ class Interface(Control): # make sure the ifname is the first argument and not from the dict self.config['ifname'] = ifname + self._admin_state_down_cnt = 0 # we must have updated config before initialising the Interface super().__init__(**kargs) @@ -594,7 +595,13 @@ class Interface(Control): if not int(flags, 16) & 1: return None - return self.set_interface('admin_state', state) + if state == 'up': + self._admin_state_down_cnt -= 1 + if self._admin_state_down_cnt < 1: + return self.set_interface('admin_state', state) + else: + self._admin_state_down_cnt += 1 + return self.set_interface('admin_state', state) def set_proxy_arp(self, enable): """ @@ -829,8 +836,11 @@ class Interface(Control): # There are some items in the configuration which can only be applied # if this instance is not bound to a bridge. This should be checked # by the caller but better save then sorry! - if not config.get('is_bridge_member', False): - # Bind interface instance into VRF + if not any(k in ['is_bond_member', 'is_bridge_member'] for k in config): + # Bind interface to given VRF or unbind it if vrf node is not set. + # unbinding will call 'ip link set dev eth0 nomaster' which will + # also drop the interface out of a bridge or bond - thus this is + # checked before self.set_vrf(config.get('vrf', '')) # DHCP options diff --git a/python/vyos/validate.py b/python/vyos/validate.py index a0620e4dd..ceeb6888a 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -279,7 +279,6 @@ def is_member(conf, interface, intftype=None): False -> interface type cannot have members """ ret_val = None - if intftype not in ['bonding', 'bridge', None]: raise ValueError(( f'unknown interface type "{intftype}" or it cannot ' @@ -292,9 +291,9 @@ def is_member(conf, interface, intftype=None): conf.set_level([]) for it in intftype: - base = 'interfaces ' + it + base = ['interfaces', it] for intf in conf.list_nodes(base): - memberintf = [base, intf, 'member', 'interface'] + memberintf = base + [intf, 'member', 'interface'] if xml.is_tag(memberintf): if interface in conf.list_nodes(memberintf): ret_val = intf diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index a16c4e105..8e87a0059 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -16,41 +16,25 @@ import os -from copy import deepcopy from sys import exit from netifaces import interfaces -from vyos.ifconfig import BondIf -from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config -from vyos.configdict import list_diff, intf_to_dict, add_to_dict, interface_default_data from vyos.config import Config -from vyos.util import call, cmd -from vyos.validate import is_member, has_address_configured +from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_node_changed +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_vlan_config +from vyos.configverify import verify_vrf +from vyos.ifconfig import BondIf +from vyos.validate import is_member +from vyos.validate import has_address_configured from vyos import ConfigError - from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'arp_mon_intvl': 0, - 'arp_mon_tgt': [], - 'deleted': False, - 'hash_policy': 'layer2', - 'intf': '', - 'ip_arp_cache_tmo': 30, - 'ip_proxy_arp_pvlan': 0, - 'mode': '802.3ad', - 'member': [], - 'shutdown_required': False, - 'primary': '', - 'vif_s': {}, - 'vif_s_remove': [], - 'vif': {}, - 'vif_remove': [], -} - - def get_bond_mode(mode): if mode == 'round-robin': return 'balance-rr' @@ -67,339 +51,144 @@ def get_bond_mode(mode): elif mode == 'adaptive-load-balance': return 'balance-alb' else: - raise ConfigError('invalid bond mode "{}"'.format(mode)) + raise ConfigError(f'invalid bond mode "{mode}"') 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 + """ + conf = Config() + base = ['interfaces', 'bonding'] + # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') ifname = os.environ['VYOS_TAGNODE_VALUE'] - conf = Config() + bond = get_interface_dict(conf, base, ifname) + + # To make our own life easier transfor the list of member interfaces + # into a dictionary - we will use this to add additional information + # later on for wach member + if 'member' in bond and 'interface' in bond['member']: + # first convert it to a list if only one member is given + if isinstance(bond['member']['interface'], str): + bond['member']['interface'] = [bond['member']['interface']] + + tmp={} + for interface in bond['member']['interface']: + tmp.update({interface: {}}) + + bond['member']['interface'] = tmp + + if 'mode' in bond: + bond['mode'] = get_bond_mode(bond['mode']) + + tmp = leaf_node_changed(conf, ['mode']) + if tmp: + bond.update({'shutdown_required': ''}) + + # determine which members have been removed + tmp = leaf_node_changed(conf, ['member', 'interface']) + if tmp: + bond.update({'shutdown_required': ''}) + if 'member' in bond: + bond['member'].update({'interface_remove': tmp }) + else: + bond.update({'member': {'interface_remove': tmp }}) + + if 'member' in bond and 'interface' in bond['member']: + for interface, interface_config in bond['member']['interface'].items(): + # Check if we are a member of another bond device + tmp = is_member(conf, interface, 'bridge') + if tmp: + interface_config.update({'is_bridge_member' : tmp}) - # initialize kernel module if not loaded - if not os.path.isfile('/sys/class/net/bonding_masters'): - import syslog - syslog.syslog(syslog.LOG_NOTICE, "loading bonding kernel module") - if call('modprobe bonding max_bonds=0 miimon=250') != 0: - syslog.syslog(syslog.LOG_NOTICE, "failed loading bonding kernel module") - raise ConfigError("failed loading bonding kernel module") - - # check if bond has been removed - cfg_base = 'interfaces bonding ' + ifname - if not conf.exists(cfg_base): - bond = deepcopy(default_config_data) - bond['intf'] = ifname - bond['deleted'] = True - return bond - - # set new configuration level - conf.set_level(cfg_base) - - bond, disabled = intf_to_dict(conf, default_config_data) - - # ARP link monitoring frequency in milliseconds - if conf.exists('arp-monitor interval'): - bond['arp_mon_intvl'] = int(conf.return_value('arp-monitor interval')) - - # IP address to use for ARP monitoring - if conf.exists('arp-monitor target'): - bond['arp_mon_tgt'] = conf.return_values('arp-monitor target') - - # Bonding transmit hash policy - if conf.exists('hash-policy'): - bond['hash_policy'] = conf.return_value('hash-policy') - - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - bond['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - - # Enable private VLAN proxy ARP on this interface - if conf.exists('ip proxy-arp-pvlan'): - bond['ip_proxy_arp_pvlan'] = 1 - - # Bonding mode - if conf.exists('mode'): - act_mode = conf.return_value('mode') - eff_mode = conf.return_effective_value('mode') - if not (act_mode == eff_mode): - bond['shutdown_required'] = True - - bond['mode'] = get_bond_mode(act_mode) - - # determine bond member interfaces (currently configured) - bond['member'] = conf.return_values('member interface') - - # We can not call conf.return_effective_values() as it would not work - # on reboots. Reboots/First boot will return that running config and - # saved config is the same, thus on a reboot the bond members will - # not be added all (https://phabricator.vyos.net/T2030) - live_members = BondIf(bond['intf']).get_slaves() - if not (bond['member'] == live_members): - bond['shutdown_required'] = True - - # Primary device interface - if conf.exists('primary'): - bond['primary'] = conf.return_value('primary') - - add_to_dict(conf, disabled, bond, 'vif', 'vif') - add_to_dict(conf, disabled, bond, 'vif-s', 'vif_s') + # Check if we are a member of a bond device + tmp = is_member(conf, interface, 'bonding') + if tmp and tmp != ifname: + interface_config.update({'is_bond_member' : tmp}) + + # bond members must not have an assigned address + tmp = has_address_configured(conf, interface) + if tmp: + interface_config.update({'has_address' : ''}) return bond def verify(bond): - if bond['deleted']: - if bond['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{bond["intf"]}" as it is a ' - f'member of bridge "{bond["is_bridge_member"]}"!')) - + if 'deleted' in bond: + verify_bridge_delete(bond) return None - if len(bond['arp_mon_tgt']) > 16: - raise ConfigError('The maximum number of arp-monitor targets is 16') + if 'arp_monitor' in bond: + if 'target' in bond['arp_monitor'] and len(int(bond['arp_monitor']['target'])) > 16: + raise ConfigError('The maximum number of arp-monitor targets is 16') + + if 'interval' in bond['arp_monitor'] and len(int(bond['arp_monitor']['interval'])) > 0: + if bond['mode'] in ['802.3ad', 'balance-tlb', 'balance-alb']: + raise ConfigError('ARP link monitoring does not work for mode 802.3ad, ' \ + 'transmit-load-balance or adaptive-load-balance') - if bond['primary']: + if 'primary' in bond: if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: - raise ConfigError(( - 'Mode dependency failed, primary not supported in mode ' - f'"{bond["mode"]}"!')) - - if ( bond['is_bridge_member'] - and ( bond['address'] - or bond['ipv6_eui64_prefix'] - or bond['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{bond["intf"]}" ' - f'as it is a member of bridge "{bond["is_bridge_member"]}"!')) - - if bond['vrf']: - if bond['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{bond["vrf"]}" does not exist') - - if bond['is_bridge_member']: - raise ConfigError(( - f'Interface "{bond["intf"]}" cannot be member of VRF ' - f'"{bond["vrf"]}" and bridge {bond["is_bridge_member"]} ' - f'at the same time!')) + raise ConfigError('Option primary - mode dependency failed, not' + 'supported in mode {mode}!'.format(**bond)) + + verify_address(bond) + verify_dhcpv6(bond) + verify_vrf(bond) # use common function to verify VLAN configuration verify_vlan_config(bond) - conf = Config() - for intf in bond['member']: - # check if member interface is "real" - if intf not in interfaces(): - raise ConfigError(f'Interface {intf} does not exist!') - - # a bonding member interface is only allowed to be assigned to one bond! - all_bonds = conf.list_nodes('interfaces bonding') - # We do not need to check our own bond - all_bonds.remove(bond['intf']) - for tmp in all_bonds: - if conf.exists('interfaces bonding {tmp} member interface {intf}'): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already a member of bond "{tmp}"!')) - - # can not add interfaces with an assigned address to a bond - if has_address_configured(conf, intf): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it has an address assigned!')) - - # bond members are not allowed to be bridge members - tmp = is_member(conf, intf, 'bridge') - if tmp: - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already a member of bridge "{tmp}"!')) - - # bond members are not allowed to be vrrp members - for tmp in conf.list_nodes('high-availability vrrp group'): - if conf.exists('high-availability vrrp group {tmp} interface {intf}'): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already a member of VRRP group "{tmp}"!')) - - # bond members are not allowed to be underlaying psuedo-ethernet devices - for tmp in conf.list_nodes('interfaces pseudo-ethernet'): - if conf.exists('interfaces pseudo-ethernet {tmp} link {intf}'): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already the link of pseudo-ethernet "{tmp}"!')) - - # bond members are not allowed to be underlaying vxlan devices - for tmp in conf.list_nodes('interfaces vxlan'): - if conf.exists('interfaces vxlan {tmp} link {intf}'): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already the link of VXLAN "{tmp}"!')) - - if bond['primary']: - if bond['primary'] not in bond['member']: - raise ConfigError(f'Bond "{bond["intf"]}" primary interface must be a member') + bond_name = bond['ifname'] + if 'member' in bond: + member = bond.get('member') + for interface, interface_config in member.get('interface', {}).items(): + error_msg = f'Can not add interface "{interface}" to bond "{bond_name}", ' + + if interface == 'lo': + raise ConfigError('Loopback interface "lo" can not be added to a bond') + + if interface not in interfaces(): + raise ConfigError(error_msg + 'it does not exist!') + + if 'is_bridge_member' in interface_config: + tmp = interface_config['is_bridge_member'] + raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') + + if 'is_bond_member' in interface_config: + tmp = interface_config['is_bond_member'] + raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') + + if 'has_address' in interface_config: + raise ConfigError(error_msg + 'it has an address assigned!') + + + if 'primary' in bond: + if bond['primary'] not in bond['member']['interface']: + raise ConfigError(f'Primary interface of bond "{bond_name}" must be a member interface') if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: raise ConfigError('primary interface only works for mode active-backup, ' \ 'transmit-load-balance or adaptive-load-balance') - if bond['arp_mon_intvl'] > 0: - if bond['mode'] in ['802.3ad', 'balance-tlb', 'balance-alb']: - raise ConfigError('ARP link monitoring does not work for mode 802.3ad, ' \ - 'transmit-load-balance or adaptive-load-balance') - return None def generate(bond): return None def apply(bond): - b = BondIf(bond['intf']) + b = BondIf(bond['ifname']) - if bond['deleted']: + if 'deleted' in bond: # delete interface b.remove() else: - # ARP link monitoring frequency, reset miimon when arp-montior is inactive - # this is done inside BondIf automatically - b.set_arp_interval(bond['arp_mon_intvl']) - - # ARP monitor targets need to be synchronized between sysfs and CLI. - # Unfortunately an address can't be send twice to sysfs as this will - # result in the following exception: OSError: [Errno 22] Invalid argument. - # - # We remove ALL adresses prior adding new ones, this will remove addresses - # added manually by the user too - but as we are limited to 16 adresses - # from the kernel side this looks valid to me. We won't run into an error - # when a user added manual adresses which would result in having more - # then 16 adresses in total. - arp_tgt_addr = list(map(str, b.get_arp_ip_target().split())) - for addr in arp_tgt_addr: - b.set_arp_ip_target('-' + addr) - - # Add configured ARP target addresses - for addr in bond['arp_mon_tgt']: - b.set_arp_ip_target('+' + addr) - - # update interface description used e.g. within SNMP - b.set_alias(bond['description']) - - if bond['dhcp_client_id']: - b.dhcp.v4.options['client_id'] = bond['dhcp_client_id'] - - if bond['dhcp_hostname']: - b.dhcp.v4.options['hostname'] = bond['dhcp_hostname'] - - if bond['dhcp_vendor_class_id']: - b.dhcp.v4.options['vendor_class_id'] = bond['dhcp_vendor_class_id'] - - if bond['dhcpv6_prm_only']: - b.dhcp.v6.options['dhcpv6_prm_only'] = True - - if bond['dhcpv6_temporary']: - b.dhcp.v6.options['dhcpv6_temporary'] = True - - if bond['dhcpv6_pd_length']: - b.dhcp.v6.options['dhcpv6_pd_length'] = bond['dhcpv6_pd_length'] - - if bond['dhcpv6_pd_interfaces']: - b.dhcp.v6.options['dhcpv6_pd_interfaces'] = bond['dhcpv6_pd_interfaces'] - - # ignore link state changes - b.set_link_detect(bond['disable_link_detect']) - # Bonding transmit hash policy - b.set_hash_policy(bond['hash_policy']) - # configure ARP cache timeout in milliseconds - b.set_arp_cache_tmo(bond['ip_arp_cache_tmo']) - # configure ARP filter configuration - b.set_arp_filter(bond['ip_disable_arp_filter']) - # configure ARP accept - b.set_arp_accept(bond['ip_enable_arp_accept']) - # configure ARP announce - b.set_arp_announce(bond['ip_enable_arp_announce']) - # configure ARP ignore - b.set_arp_ignore(bond['ip_enable_arp_ignore']) - # Enable proxy-arp on this interface - b.set_proxy_arp(bond['ip_proxy_arp']) - # Enable private VLAN proxy ARP on this interface - b.set_proxy_arp_pvlan(bond['ip_proxy_arp_pvlan']) - # IPv6 accept RA - b.set_ipv6_accept_ra(bond['ipv6_accept_ra']) - # IPv6 address autoconfiguration - b.set_ipv6_autoconf(bond['ipv6_autoconf']) - # IPv6 forwarding - b.set_ipv6_forwarding(bond['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - b.set_ipv6_dad_messages(bond['ipv6_dup_addr_detect']) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in bond['ipv6_eui64_prefix_remove']: - b.del_ipv6_eui64_address(addr) - - # Change interface MAC address - if bond['mac']: - b.set_mac(bond['mac']) - - # Add IPv6 EUI-based addresses - for addr in bond['ipv6_eui64_prefix']: - b.add_ipv6_eui64_address(addr) - - # Maximum Transmission Unit (MTU) - b.set_mtu(bond['mtu']) - - # Primary device interface - if bond['primary']: - b.set_primary(bond['primary']) - - # Some parameters can not be changed when the bond is up. - if bond['shutdown_required']: - # Disable bond prior changing of certain properties - b.set_admin_state('down') - - # The bonding mode can not be changed when there are interfaces enslaved - # to this bond, thus we will free all interfaces from the bond first! - for intf in b.get_slaves(): - b.del_port(intf) - - # Bonding policy/mode - b.set_mode(bond['mode']) - - # Add (enslave) interfaces to bond - for intf in bond['member']: - # if we've come here we already verified the interface doesn't - # have addresses configured so just flush any remaining ones - cmd(f'ip addr flush dev "{intf}"') - b.add_port(intf) - - # As the bond interface is always disabled first when changing - # parameters we will only re-enable the interface if it is not - # administratively disabled - if not bond['disable']: - b.set_admin_state('up') - else: - b.set_admin_state('down') - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in bond['address_remove']: - b.del_addr(addr) - for addr in bond['address']: - b.add_addr(addr) - - # assign/remove VRF (ONLY when not a member of a bridge, - # otherwise 'nomaster' removes it from it) - if not bond['is_bridge_member']: - b.set_vrf(bond['vrf']) - - # re-add ourselves to any bridge we might have fallen out of - if bond['is_bridge_member']: - b.add_to_bridge(bond['is_bridge_member']) - - # apply all vlans to interface - apply_all_vlans(b, bond) + b.update(bond) return None -- cgit v1.2.3 From 79af6c7b35164d3313c39dff2bc1bffbb4b326cd Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 22:00:36 +0200 Subject: wireless: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. --- data/templates/wifi/cfg80211.conf.tmpl | 4 +- data/templates/wifi/crda.tmpl | 4 +- data/templates/wifi/hostapd.conf.tmpl | 424 ++++++-------- data/templates/wifi/wpa_supplicant.conf.tmpl | 4 +- interface-definitions/interfaces-wireless.xml.in | 15 +- src/conf_mode/interfaces-wireless.py | 686 +++++------------------ 6 files changed, 337 insertions(+), 800 deletions(-) (limited to 'src') diff --git a/data/templates/wifi/cfg80211.conf.tmpl b/data/templates/wifi/cfg80211.conf.tmpl index b21bacc1e..91df57aab 100644 --- a/data/templates/wifi/cfg80211.conf.tmpl +++ b/data/templates/wifi/cfg80211.conf.tmpl @@ -1,3 +1 @@ -{%- if regdom -%} -options cfg80211 ieee80211_regdom={{ regdom }} -{% endif %} +{{ 'options cfg80211 ieee80211_regdom=' + regdom if regdom is defined }} diff --git a/data/templates/wifi/crda.tmpl b/data/templates/wifi/crda.tmpl index 750ad86ee..6cd125e37 100644 --- a/data/templates/wifi/crda.tmpl +++ b/data/templates/wifi/crda.tmpl @@ -1,3 +1 @@ -{%- if regdom -%} -REGDOMAIN={{ regdom }} -{% endif %} +{{ 'REGDOMAIN=' + regdom if regdom is defined }} diff --git a/data/templates/wifi/hostapd.conf.tmpl b/data/templates/wifi/hostapd.conf.tmpl index d6068e4db..765668c57 100644 --- a/data/templates/wifi/hostapd.conf.tmpl +++ b/data/templates/wifi/hostapd.conf.tmpl @@ -9,7 +9,7 @@ device_name={{ description | truncate(32, True) }} # management frames with the Host AP driver); wlan0 with many nl80211 drivers # Note: This attribute can be overridden by the values supplied with the '-i' # command line parameter. -interface={{ intf }} +interface={{ ifname }} # Driver interface type (hostap/wired/none/nl80211/bsd); # default: hostap). nl80211 is used with all Linux mac80211 drivers. @@ -28,8 +28,7 @@ logger_syslog_level=0 logger_stdout=-1 logger_stdout_level=0 -{%- if country_code %} - +{% if country_code %} # Country code (ISO/IEC 3166-1). Used to set regulatory domain. # Set as needed to indicate country in which device is operating. # This can limit available channels and transmit power. @@ -42,14 +41,12 @@ country_code={{ country_code }} ieee80211d=1 {% endif %} -{%- if ssid %} - +{% if ssid %} # SSID to be used in IEEE 802.11 management frames ssid={{ ssid }} {% endif %} -{%- if channel %} - +{% if channel %} # Channel number (IEEE 802.11) # (default: 0, i.e., not set) # Please note that some drivers do not use this value from hostapd and the @@ -61,8 +58,7 @@ ssid={{ ssid }} channel={{ channel }} {% endif %} -{%- if mode %} - +{% if mode %} # Operation mode (a = IEEE 802.11a (5 GHz), b = IEEE 802.11b (2.4 GHz), # g = IEEE 802.11g (2.4 GHz), ad = IEEE 802.11ad (60 GHz); a/g options are used # with IEEE 802.11n (HT), too, to specify band). For IEEE 802.11ac (VHT), this @@ -71,29 +67,30 @@ channel={{ channel }} # special value "any" can be used to indicate that any support band can be used. # This special case is currently supported only with drivers with which # offloaded ACS is used. -{% if 'n' in mode -%} +{% if 'n' in mode %} hw_mode=g -{% elif 'ac' in mode -%} +{% elif 'ac' in mode %} hw_mode=a ieee80211h=1 ieee80211ac=1 -{% else -%} +{% else %} hw_mode={{ mode }} -{% endif %} +{% endif %} {% endif %} # ieee80211w: Whether management frame protection (MFP) is enabled # 0 = disabled (default) # 1 = optional # 2 = required -{% if 'disabled' in mgmt_frame_protection -%} +{% if 'disabled' in mgmt_frame_protection %} ieee80211w=0 -{% elif 'optional' in mgmt_frame_protection -%} +{% elif 'optional' in mgmt_frame_protection %} ieee80211w=1 -{% elif 'required' in mgmt_frame_protection -%} +{% elif 'required' in mgmt_frame_protection %} ieee80211w=2 {% endif %} +{% if capabilities is defined and capabilities.ht is defined %} # ht_capab: HT capabilities (list of flags) # LDPC coding capability: [LDPC] = supported # Supported channel width set: [HT40-] = both 20 MHz and 40 MHz with secondary @@ -127,79 +124,50 @@ ieee80211w=2 # DSSS/CCK Mode in 40 MHz: [DSSS_CCK-40] = allowed (not allowed if not set) # 40 MHz intolerant [40-INTOLERANT] (not advertised if not set) # L-SIG TXOP protection support: [LSIG-TXOP-PROT] (disabled if not set) -{% if cap_ht %} -ht_capab= -{%- endif -%} - -{%- if cap_ht_40mhz_incapable -%} -[40-INTOLERANT] -{%- endif -%} - -{%- if cap_ht_delayed_block_ack -%} -[DELAYED-BA] -{%- endif -%} - -{%- if cap_ht_dsss_cck_40 -%} -[DSSS_CCK-40] -{%- endif -%} - -{%- if cap_ht_greenfield -%} -[GF] -{%- endif -%} - -{%- if cap_ht_ldpc -%} -[LDPC] -{%- endif -%} - -{%- if cap_ht_lsig_protection -%} -[LSIG-TXOP-PROT] -{%- endif -%} - -{%- if cap_ht_max_amsdu -%} -[MAX-AMSDU-{{ cap_ht_max_amsdu }}] -{%- endif -%} - -{%- if cap_ht_smps -%} -[SMPS-{{ cap_ht_smps | upper }}] -{%- endif -%} - -{%- if cap_ht_chan_set_width -%} -{%- for csw in cap_ht_chan_set_width -%} -[{{ csw | upper }}] -{%- endfor -%} -{%- endif -%} - -{%- if cap_ht_short_gi -%} -{%- for gi in cap_ht_short_gi -%} -[SHORT-GI-{{ gi }}] -{%- endfor -%} -{%- endif -%} - -{%- if cap_ht_stbc_tx -%} -[TX-STBC] -{%- endif -%} -{%- if cap_ht_stbc_rx -%} -[RX-STBC{{ cap_ht_stbc_rx }}] -{%- endif %} +{% set output = '' %} +{% set output = output + '[40-INTOLERANT]' if capabilities.ht.fourtymhz_incapable is defined else '' %} +{% set output = output + '[DELAYED-BA]' if capabilities.ht.delayed_block_ack is defined else '' %} +{% set output = output + '[DSSS_CCK-40]' if capabilities.ht.dsss_cck_40 is defined else '' %} +{% set output = output + '[GF]' if capabilities.ht.greenfield is defined else '' %} +{% set output = output + '[LDPC]' if capabilities.ht.ldpc is defined else '' %} +{% set output = output + '[LSIG-TXOP-PROT]' if capabilities.ht.lsig_protection is defined else '' %} +{% set output = output + '[TX-STBC]' if capabilities.ht.stbc.tx is defined else '' %} +{% set output = output + '[RX-STBC-' + capabilities.ht.stbc.rx | upper + ']' if capabilities.ht.stbc.tx is defined else '' %} +{% set output = output + '[MAX-AMSDU-' + capabilities.ht.max_amsdu + ']' if capabilities.ht.max_amsdu is defined else '' %} +{% set output = output + '[SMPS-' + capabilities.ht.smps | upper + ']' if capabilities.ht.smps is defined else '' %} + +{% if capabilities.ht.channel_set_width is defined %} +{% for csw in capabilities.ht.channel_set_width %} +{% set output = output + '[' + csw | upper + ']' %} +{% endfor %} +{% endif %} -# Required for full HT and VHT functionality -wme_enabled=1 +{% if capabilities.ht.short_gi is defined %} +{% for short_gi in capabilities.ht.short_gi %} +{% set output = output + '[SHORT-GI-' + short_gi | upper + ']' %} +{% endfor %} +{% endif %} -{% if cap_ht_powersave -%} +ht_capab={{ output }} + +{% if capabilities.ht.auto_powersave is defined %} # WMM-PS Unscheduled Automatic Power Save Delivery [U-APSD] # Enable this flag if U-APSD supported outside hostapd (eg., Firmware/driver) uapsd_advertisement_enabled=1 -{%- endif %} +{% endif %} + +{% endif %} + +# Required for full HT and VHT functionality +wme_enabled=1 -{% if cap_req_ht -%} + +{% if capabilities is defined and capabilities.require_ht is defined %} # Require stations to support HT PHY (reject association if they do not) require_ht=1 {% endif %} -{%- if cap_vht_chan_set_width -%} -vht_oper_chwidth={{ cap_vht_chan_set_width }} -{%- endif %} - +{% if capabilities is defined and capabilities.vht is defined %} # vht_capab: VHT capabilities (list of flags) # # vht_max_mpdu_len: [MAX-MPDU-7991] [MAX-MPDU-11454] @@ -316,133 +284,95 @@ vht_oper_chwidth={{ cap_vht_chan_set_width }} # Tx Antenna Pattern Consistency: [TX-ANTENNA-PATTERN] # Indicates the possibility of Tx antenna pattern change # 0 = Tx antenna pattern might change during the lifetime of an association -# 1 = Tx antenna pattern does not change during the lifetime of an association -{% if cap_vht %} -vht_capab= -{%- endif -%} - -{%- if cap_vht_max_mpdu -%} -[MAX-MPDU-{{ cap_vht_max_mpdu }}] -{%- endif -%} - -{%- if cap_vht_max_mpdu_exp -%} -[MAX-A-MPDU-LEN-EXP{{ cap_vht_max_mpdu_exp }}] -{%- endif -%} - -{%- if cap_vht_chan_set_width -%} -{%- if '2' in cap_vht_chan_set_width -%} -[VHT160] -{%- elif '3' in cap_vht_chan_set_width -%} -[VHT160-80PLUS80] -{%- endif -%} -{%- endif -%} - -{%- if cap_vht_stbc_tx -%} -[TX-STBC-2BY1] -{%- endif -%} - -{%- if cap_vht_stbc_rx -%} -[RX-STBC-{{ cap_vht_stbc_rx }}] -{%- endif -%} - -{%- if cap_vht_link_adaptation -%} -{%- if 'unsolicited' in cap_vht_link_adaptation -%} -[VHT-LINK-ADAPT2] -{%- elif 'both' in cap_vht_link_adaptation -%} -[VHT-LINK-ADAPT3] -{%- endif -%} -{%- endif -%} - -{%- if cap_vht_short_gi -%} -{%- for gi in cap_vht_short_gi -%} -[SHORT-GI-{{ gi }}] -{%- endfor -%} -{%- endif -%} - -{%- if cap_vht_ldpc -%} -[RXLDPC] -{%- endif -%} - -{%- if cap_vht_tx_powersave -%} -[VHT-TXOP-PS] -{%- endif -%} - -{%- if cap_vht_vht_cf -%} -[HTC-VHT] -{%- endif -%} - -{%- if cap_vht_beamform -%} -{%- for beamform in cap_vht_beamform -%} -{%- if 'single-user-beamformer' in beamform -%} -[SU-BEAMFORMER] -{%- elif 'single-user-beamformee' in beamform -%} -[SU-BEAMFORMEE] -{%- elif 'multi-user-beamformer' in beamform -%} -[MU-BEAMFORMER] -{%- elif 'multi-user-beamformee' in beamform -%} -[MU-BEAMFORMEE] -{%- endif -%} -{%- endfor -%} -{%- endif -%} - -{%- if cap_vht_antenna_fixed -%} -[RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN] -{%- endif -%} - -{%- if cap_vht_antenna_cnt -%} -{%- if cap_vht_antenna_cnt|int > 1 -%} -{%- if cap_vht_beamform -%} -{%- for beamform in cap_vht_beamform -%} -{%- if 'single-user-beamformer' in beamform -%} -{%- if cap_vht_antenna_cnt|int < 6 -%} -[BF-ANTENNA-{{ cap_vht_antenna_cnt|int -1 }}][SOUNDING-DIMENSION-{{ cap_vht_antenna_cnt|int -1}}] -{%- endif -%} -{%- else -%} -{%- if cap_vht_antenna_cnt|int < 5 -%} -[BF-ANTENNA-{{ cap_vht_antenna_cnt }}][SOUNDING-DIMENSION-{{ cap_vht_antenna_cnt }}] -{%- endif -%} -{%- endif -%} -{%- endfor -%} -{%- else -%} -{%- if cap_vht_antenna_cnt|int < 5 -%} -[BF-ANTENNA-{{ cap_vht_antenna_cnt }}][SOUNDING-DIMENSION-{{ cap_vht_antenna_cnt }}] -{%- endif -%} -{%- endif -%} -{%- endif -%} -{%- endif %} +# 1 = Tx antenna pattern does not change during the lifetime of an + +{% if capabilities.vht.center_channel_freq.freq_1 is defined %} +# center freq = 5 GHz + (5 * index) +# So index 42 gives center freq 5.210 GHz +# which is channel 42 in 5G band +vht_oper_centr_freq_seg0_idx={{ capabilities.vht.center_channel_freq.freq_1 }} +{% endif %} + +{% if capabilities.vht.center_channel_freq.freq_2 is defined %} +# center freq = 5 GHz + (5 * index) +# So index 159 gives center freq 5.795 GHz +# which is channel 159 in 5G band +vht_oper_centr_freq_seg1_idx={{ capabilities.vht.center_channel_freq.freq_2 }} +{% endif %} + +{% if capabilities.vht.channel_set_width is defined %} +vht_oper_chwidth={{ capabilities.vht.channel_set_width }} +{% endif %} + +{% set output = '' %} +{% set output = output + '[TX-STBC-2BY1]' if capabilities.vht.stbc.tx is defined else '' %} +{% set output = output + '[RXLDPC]' if capabilities.vht.ldpc is defined else '' %} +{% set output = output + '[VHT-TXOP-PS]' if capabilities.vht.tx_powersave is defined else '' %} +{% set output = output + '[HTC-VHT]' if capabilities.vht.vht_cf is defined else '' %} +{% set output = output + '[RX-ANTENNA-PATTERN]' if capabilities.vht.antenna_pattern_fixed is defined else '' %} +{% set output = output + '[TX-ANTENNA-PATTERN]' if capabilities.vht.antenna_pattern_fixed is defined else '' %} + +{% set output = output + '[RX-STBC-' + capabilities.vht.stbc.rx + ']' if capabilities.vht.stbc.rx is defined else '' %} +{% set output = output + '[MAX-MPDU-' + capabilities.vht.max_mpdu + ']' if capabilities.vht.max_mpdu is defined else '' %} +{% set output = output + '[MAX-A-MPDU-LEN-EXP-' + capabilities.vht.max_mpdu_exp + ']' if capabilities.vht.max_mpdu_exp is defined else '' %} +{% set output = output + '[MAX-A-MPDU-LEN-EXP-' + capabilities.vht.max_mpdu_exp + ']' if capabilities.vht.max_mpdu_exp is defined else '' %} + +{% set output = output + '[VHT160]' if capabilities.vht.max_mpdu_exp is defined and capabilities.vht.max_mpdu_exp == '2' else '' %} +{% set output = output + '[VHT160-80PLUS80]' if capabilities.vht.max_mpdu_exp is defined and capabilities.vht.max_mpdu_exp == '3' else '' %} +{% set output = output + '[VHT-LINK-ADAPT2]' if capabilities.vht.link_adaptation is defined and capabilities.vht.link_adaptation == 'unsolicited' else '' %} +{% set output = output + '[VHT-LINK-ADAPT3]' if capabilities.vht.link_adaptation is defined and capabilities.vht.link_adaptation == 'both' else '' %} + +{% if capabilities.vht.short_gi is defined %} +{% for short_gi in capabilities.vht.short_gi %} +{% set output = output + '[SHORT-GI-' + short_gi | upper + ']' %} +{% endfor %} +{% endif %} + +{% if capabilities.vht.beamform %} +{% for beamform in capabilities.vht.beamform %} +{% set output = output + '[SU-BEAMFORMER]' if beamform == 'single-user-beamformer' else '' %} +{% set output = output + '[SU-BEAMFORMEE]' if beamform == 'single-user-beamformee' else '' %} +{% set output = output + '[MU-BEAMFORMER]' if beamform == 'multi-user-beamformer' else '' %} +{% set output = output + '[MU-BEAMFORMEE]' if beamform == 'multi-user-beamformee' else '' %} +{% endfor %} +{% endif %} + +{% if capabilities.vht.antenna_count is defined and capabilities.vht.antenna_count|int > 1 %} +{% if capabilities.vht.beamform %} +{% if beamform == 'single-user-beamformer' %} +{% if capabilities.vht.antenna_count is defined and capabilities.vht.antenna_count|int > 1 and capabilities.vht.antenna_count|int < 6 %} +{% set output = output + '[BF-ANTENNA-' + capabilities.vht.antenna_count|int -1 + ']' %} +{% set output = output + '[SOUNDING-DIMENSION-' + capabilities.vht.antenna_count|int -1 + ']' %} +{% endif %} +{% endif %} +{% if capabilities.vht.antenna_count is defined and capabilities.vht.antenna_count|int > 1 and capabilities.vht.antenna_count|int < 5 %} +{% set output = output + '[BF-ANTENNA-' + capabilities.vht.antenna_count + ']' %} +{% set output = output + '[SOUNDING-DIMENSION-' + capabilities.vht.antenna_count+ ']' %} +{% endif %} +{% endif %} +{% endif %} + +vht_capab={{ output }} +{% endif %} # ieee80211n: Whether IEEE 802.11n (HT) is enabled # 0 = disabled (default) # 1 = enabled # Note: You will also need to enable WMM for full HT functionality. # Note: hw_mode=g (2.4 GHz) and hw_mode=a (5 GHz) is used to specify the band. -{% if cap_req_vht -%} +{% if capabilities is defined and capabilities.require_vht is defined %} ieee80211n=0 # Require stations to support VHT PHY (reject association if they do not) require_vht=1 -{% else -%} -{% if 'n' in mode or 'ac' in mode -%} +{% else %} +{% if 'n' in mode or 'ac' in mode %} ieee80211n=1 -{% else -%} +{% else %} ieee80211n=0 -{%- endif %} +{% endif %} {% endif %} -{% if cap_vht_center_freq_1 -%} -# center freq = 5 GHz + (5 * index) -# So index 42 gives center freq 5.210 GHz -# which is channel 42 in 5G band -vht_oper_centr_freq_seg0_idx={{ cap_vht_center_freq_1 }} -{% endif %} - -{% if cap_vht_center_freq_2 -%} -# center freq = 5 GHz + (5 * index) -# So index 159 gives center freq 5.795 GHz -# which is channel 159 in 5G band -vht_oper_centr_freq_seg1_idx={{ cap_vht_center_freq_2 }} -{% endif %} - -{% if disable_broadcast_ssid -%} +{% if disable_broadcast_ssid is defined %} # Send empty SSID in beacons and ignore probe request frames that do not # specify full SSID, i.e., require stations to know SSID. # default: disabled (0) @@ -463,7 +393,7 @@ ignore_broadcast_ssid=1 # 2 = use external RADIUS server (accept/deny lists are searched first) macaddr_acl=0 -{% if max_stations -%} +{% if max_stations is defined %} # Maximum number of stations allowed in station table. New stations will be # rejected after the station table is full. IEEE 802.11 has a limit of 2007 # different association IDs, so this number should not be larger than that. @@ -471,13 +401,13 @@ macaddr_acl=0 max_num_sta={{ max_stations }} {% endif %} -{% if isolate_stations -%} +{% if isolate_stations is defined %} # Client isolation can be used to prevent low-level bridging of frames between # associated stations in the BSS. By default, this bridging is allowed. ap_isolate=1 {% endif %} -{% if reduce_transmit_power -%} +{% if reduce_transmit_power is defined %} # Add Power Constraint element to Beacon and Probe Response frames # This config option adds Power Constraint element when applicable and Country # element is added. Power Constraint element is required by Transmit Power @@ -486,14 +416,15 @@ ap_isolate=1 local_pwr_constraint={{ reduce_transmit_power }} {% endif %} -{% if expunge_failing_stations -%} +{% if expunge_failing_stations is defined %} # Disassociate stations based on excessive transmission failures or other # indications of connection loss. This depends on the driver capabilities and # may not be available with all drivers. disassoc_low_ack=1 {% endif %} -{% if sec_wep -%} + +{% if security is defined and security.wep is defined %} # IEEE 802.11 specifies two authentication algorithms. hostapd can be # configured to allow both of these or only one. Open system authentication # should be used with IEEE 802.1X. @@ -522,13 +453,14 @@ wep_default_key=0 # digits, depending on whether 40-bit (64-bit), 104-bit (128-bit), or # 128-bit (152-bit) WEP is used. # Only the default key must be supplied; the others are optional. -{% if sec_wep_key -%} -{% for key in sec_wep_key -%} -wep_key{{ loop.index -1 }}={{ key}} -{% endfor %} -{%- endif %} +{% if security.wep.key is defined %} +{% for key in sec_wep_key %} +wep_key{{ loop.index -1 }}={{ security.wep.key }} +{% endfor %} +{% endif %} -{% elif sec_wpa -%} + +{% elif security is defined and security.wpa is defined %} ##### WPA/IEEE 802.11i configuration ########################################## # Enable WPA. Setting this variable configures the AP to require WPA (either @@ -542,15 +474,17 @@ wep_key{{ loop.index -1 }}={{ key}} # and/or WPA2 (full IEEE 802.11i/RSN): # bit0 = WPA # bit1 = IEEE 802.11i/RSN (WPA2) (dot11RSNAEnabled) -{% if 'both' in sec_wpa_mode -%} +{% if security.wpa.mode is defined %} +{% if security.wpa.mode == 'both' %} wpa=3 -{%- elif 'wpa2' in sec_wpa_mode -%} +{% elif security.wpa.mode == 'wpa2' %} wpa=2 -{%- elif 'wpa' in sec_wpa_mode -%} +{% elif security.wpa.mode == 'wpa' %} wpa=1 -{%- endif %} +{% endif %} +{% endif %} -{% if sec_wpa_cipher -%} +{% if security.wpa.cipher is defined %} # Set of accepted cipher suites (encryption algorithms) for pairwise keys # (unicast packets). This is a space separated list of algorithms: # CCMP = AES in Counter mode with CBC-MAC (CCMP-128) @@ -563,26 +497,39 @@ wpa=1 # allowed as the pairwise cipher, group cipher will also be CCMP. Otherwise, # TKIP will be used as the group cipher. The optional group_cipher parameter can # be used to override this automatic selection. -{% if 'wpa2' in sec_wpa_mode -%} + +{% if security.wpa.mode is defined and security.wpa.mode == 'wpa2' %} # Pairwise cipher for RSN/WPA2 (default: use wpa_pairwise value) -rsn_pairwise={{ sec_wpa_cipher | join(" ") }} -{% else -%} +{% if security.wpa.cipher is string %} +rsn_pairwise={{ security.wpa.cipher }} +{% else %} +rsn_pairwise={{ security.wpa.cipher | join(" ") }} +{% endif %} +{% else %} # Pairwise cipher for WPA (v1) (default: TKIP) -wpa_pairwise={{ sec_wpa_cipher | join(" ") }} -{%- endif -%} -{% endif %} - -{% if sec_wpa_group_cipher -%} +{% if security.wpa.cipher is string %} +wpa_pairwise={{ security.wpa.cipher }} +{% else %} +wpa_pairwise={{ security.wpa.cipher | join(" ") }} +{% endif %} +{% endif %} +{% endif %} + +{% if security.wpa.group_cipher is defined %} # Optional override for automatic group cipher selection # This can be used to select a specific group cipher regardless of which # pairwise ciphers were enabled for WPA and RSN. It should be noted that # overriding the group cipher with an unexpected value can result in # interoperability issues and in general, this parameter is mainly used for # testing purposes. -group_cipher={{ sec_wpa_group_cipher | join(" ") }} -{% endif %} - -{% if sec_wpa_passphrase -%} +{% if security.wpa.group_cipher is string %} +group_cipher={{ security.wpa.group_cipher }} +{% else %} +group_cipher={{ security.wpa.group_cipher | join(" ") }} +{% endif %} +{% endif %} + +{% if security.wpa.passphrase is defined %} # IEEE 802.11 specifies two authentication algorithms. hostapd can be # configured to allow both of these or only one. Open system authentication # should be used with IEEE 802.1X. @@ -595,7 +542,7 @@ auth_algs=1 # secret in hex format (64 hex digits), wpa_psk, or as an ASCII passphrase # (8..63 characters) that will be converted to PSK. This conversion uses SSID # so the PSK changes when ASCII passphrase is used and the SSID is changed. -wpa_passphrase={{ sec_wpa_passphrase }} +wpa_passphrase={{ security.wpa.passphrase }} # Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The # entries are separated with a space. WPA-PSK-SHA256 and WPA-EAP-SHA256 can be @@ -604,7 +551,7 @@ wpa_passphrase={{ sec_wpa_passphrase }} # WPA-PSK-SHA256 = WPA2-Personal using SHA256 wpa_key_mgmt=WPA-PSK -{% elif sec_wpa_radius -%} +{% elif security.wpa.radius is defined %} ##### IEEE 802.1X-2004 related configuration ################################## # Require IEEE 802.1X authorization ieee8021x=1 @@ -616,40 +563,37 @@ ieee8021x=1 # WPA-EAP-SHA256 = WPA2-Enterprise using SHA256 wpa_key_mgmt=WPA-EAP -{% if sec_wpa_radius_source -%} +{% if security.wpa.radius.server is defined %} # RADIUS client forced local IP address for the access point # Normally the local IP address is determined automatically based on configured # IP addresses, but this field can be used to force a specific address to be # used, e.g., when the device has multiple IP addresses. -radius_client_addr={{ sec_wpa_radius_source }} - -# The own IP address of the access point (used as NAS-IP-Address) -own_ip_addr={{ sec_wpa_radius_source }} -{% else %} # The own IP address of the access point (used as NAS-IP-Address) +{% if security.wpa.radius.source_address is defined %} +radius_client_addr={{ security.wpa.radius.source_address }} +own_ip_addr={{ security.wpa.radius.source_address }} +{% else %} own_ip_addr=127.0.0.1 -{% endif %} +{% endif %} -{% for radius in sec_wpa_radius -%} -{%- if not radius.disabled -%} +{% for radius in security.wpa.radius.server if not radius.disabled %} # RADIUS authentication server auth_server_addr={{ radius.server }} auth_server_port={{ radius.port }} auth_server_shared_secret={{ radius.key }} -{% if radius.acc_port -%} + +{% if radius.acc_port %} # RADIUS accounting server acct_server_addr={{ radius.server }} acct_server_port={{ radius.acc_port }} acct_server_shared_secret={{ radius.key }} -{% endif %} -{% endif %} -{% endfor %} - -{% endif %} - -{% else %} +{% endif %} +{% endfor %} +{% else %} # Open system auth_algs=1 +{% endif %} +{% endif %} {% endif %} # TX queue parameters (EDCF / bursting) diff --git a/data/templates/wifi/wpa_supplicant.conf.tmpl b/data/templates/wifi/wpa_supplicant.conf.tmpl index 2784883f1..9ddad35fd 100644 --- a/data/templates/wifi/wpa_supplicant.conf.tmpl +++ b/data/templates/wifi/wpa_supplicant.conf.tmpl @@ -1,8 +1,8 @@ # WPA supplicant config network={ ssid="{{ ssid }}" -{%- if sec_wpa_passphrase %} - psk="{{ sec_wpa_passphrase }}" +{% if security is defined and security.wpa is defined and security.wpa.passphrase is defined %} + psk="{{ security.wpa.passphrase }}" {% else %} key_mgmt=NONE {% endif %} diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index 06c7734f5..6f0ec9e71 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -320,7 +320,7 @@ VHT link adaptation capabilities - unsolicited both + unsolicited both unsolicited @@ -451,6 +451,7 @@ Disable broadcast of SSID from access-point + #include @@ -551,9 +552,10 @@ 802.11ac - 1300 Mbits/sec - (a|b|g|n|ac) + ^(a|b|g|n|ac)$ + g @@ -637,7 +639,7 @@ Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] - (GCMP-256|GCMP|CCMP-256|CCMP|TKIP) + ^(GCMP-256|GCMP|CCMP-256|CCMP|TKIP)$ Invalid cipher selection @@ -670,7 +672,7 @@ Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] - (GCMP-256|GCMP|CCMP-256|CCMP|TKIP) + ^(GCMP-256|GCMP|CCMP-256|CCMP|TKIP)$ Invalid group cipher selection @@ -695,7 +697,7 @@ Allow both WPA and WPA2 - (wpa|wpa2|both) + ^(wpa|wpa2|both)$ Unknown WPA mode @@ -762,10 +764,11 @@ Passively monitor all packets on the frequency/channel - (access-point|station|monitor) + ^(access-point|station|monitor)$ Type must be access-point, station or monitor + monitor #include #include diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 0162b642c..42b55ee6a 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -15,497 +15,169 @@ # along with this program. If not, see . import os + from sys import exit from re import findall - from copy import deepcopy - -from netifaces import interfaces from netaddr import EUI, mac_unix_expanded from vyos.config import Config -from vyos.configdict import list_diff, intf_to_dict, add_to_dict, interface_default_data -from vyos.ifconfig import WiFiIf, Section -from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config +from vyos.configdict import get_interface_dict +from vyos.configdict import dict_merge +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_vlan_config +from vyos.configverify import verify_vrf +from vyos.ifconfig import WiFiIf from vyos.template import render -from vyos.util import chown, call -from vyos.validate import is_member +from vyos.util import call from vyos import ConfigError - from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'cap_ht' : False, - 'cap_ht_40mhz_incapable' : False, - 'cap_ht_powersave' : False, - 'cap_ht_chan_set_width' : '', - 'cap_ht_delayed_block_ack' : False, - 'cap_ht_dsss_cck_40' : False, - 'cap_ht_greenfield' : False, - 'cap_ht_ldpc' : False, - 'cap_ht_lsig_protection' : False, - 'cap_ht_max_amsdu' : '', - 'cap_ht_short_gi' : [], - 'cap_ht_smps' : '', - 'cap_ht_stbc_rx' : '', - 'cap_ht_stbc_tx' : False, - 'cap_req_ht' : False, - 'cap_req_vht' : False, - 'cap_vht' : False, - 'cap_vht_antenna_cnt' : '', - 'cap_vht_antenna_fixed' : False, - 'cap_vht_beamform' : '', - 'cap_vht_center_freq_1' : '', - 'cap_vht_center_freq_2' : '', - 'cap_vht_chan_set_width' : '', - 'cap_vht_ldpc' : False, - 'cap_vht_link_adaptation' : '', - 'cap_vht_max_mpdu_exp' : '', - 'cap_vht_max_mpdu' : '', - 'cap_vht_short_gi' : [], - 'cap_vht_stbc_rx' : '', - 'cap_vht_stbc_tx' : False, - 'cap_vht_tx_powersave' : False, - 'cap_vht_vht_cf' : False, - 'channel': '', - 'country_code': '', - 'deleted': False, - 'disable_broadcast_ssid' : False, - 'disable_link_detect' : 1, - 'expunge_failing_stations' : False, - 'hw_id' : '', - 'intf': '', - 'isolate_stations' : False, - 'max_stations' : '', - 'mgmt_frame_protection' : 'disabled', - 'mode' : 'g', - 'phy' : '', - 'reduce_transmit_power' : '', - 'sec_wep' : False, - 'sec_wep_key' : [], - 'sec_wpa' : False, - 'sec_wpa_cipher' : [], - 'sec_wpa_mode' : 'both', - 'sec_wpa_passphrase' : '', - 'sec_wpa_radius' : [], - 'ssid' : '', - 'op_mode' : 'monitor', - 'vif': {}, - 'vif_remove': [], - 'vif_s': {}, - 'vif_s_remove': [] -} - # XXX: wpa_supplicant works on the source interface -wpa_suppl_conf = '/run/wpa_supplicant/{intf}.conf' -hostapd_conf = '/run/hostapd/{intf}.conf' +wpa_suppl_conf = '/run/wpa_supplicant/{ifname}.conf' +hostapd_conf = '/run/hostapd/{ifname}.conf' + +def find_other_stations(conf, base, ifname): + """ + Only one wireless interface per phy can be in station mode - + find all interfaces attached to a phy which run in station mode + """ + old_level = conf.get_level() + conf.set_level(base) + dict = {} + for phy in os.listdir('/sys/class/ieee80211'): + list = [] + for interface in conf.list_nodes([]): + if interface == ifname: + continue + # the following node is mandatory + if conf.exists([interface, 'physical-device', phy]): + tmp = conf.return_value([interface, 'type']) + if tmp == 'station': + list.append(interface) + if list: + dict.update({phy: list}) + conf.set_level(old_level) + return dict 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 + """ + conf = Config() + base = ['interfaces', 'wireless'] + # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') ifname = os.environ['VYOS_TAGNODE_VALUE'] - conf = Config() - - # check if wireless interface has been removed - cfg_base = ['interfaces', 'wireless ', ifname] - if not conf.exists(cfg_base): - wifi = deepcopy(default_config_data) - wifi['intf'] = ifname - wifi['deleted'] = True - # we need to know if we're a bridge member so we can refuse deletion - wifi['is_bridge_member'] = is_member(conf, wifi['intf'], 'bridge') - # we can not bail out early as wireless interface can not be removed - # Kernel will complain with: RTNETLINK answers: Operation not supported. - # Thus we need to remove individual settings - return wifi - - # set new configuration level - conf.set_level(cfg_base) - - # get common interface settings - wifi, disabled = intf_to_dict(conf, default_config_data) - - # 40MHz intolerance, use 20MHz only - if conf.exists('capabilities ht 40mhz-incapable'): - wifi['cap_ht'] = True - wifi['cap_ht_40mhz_incapable'] = True - - # WMM-PS Unscheduled Automatic Power Save Delivery [U-APSD] - if conf.exists('capabilities ht auto-powersave'): - wifi['cap_ht'] = True - wifi['cap_ht_powersave'] = True - - # Supported channel set width - if conf.exists('capabilities ht channel-set-width'): - wifi['cap_ht'] = True - wifi['cap_ht_chan_set_width'] = conf.return_values('capabilities ht channel-set-width') - - # HT-delayed Block Ack - if conf.exists('capabilities ht delayed-block-ack'): - wifi['cap_ht'] = True - wifi['cap_ht_delayed_block_ack'] = True - - # DSSS/CCK Mode in 40 MHz - if conf.exists('capabilities ht dsss-cck-40'): - wifi['cap_ht'] = True - wifi['cap_ht_dsss_cck_40'] = True - - # HT-greenfield capability - if conf.exists('capabilities ht greenfield'): - wifi['cap_ht'] = True - wifi['cap_ht_greenfield'] = True - - # LDPC coding capability - if conf.exists('capabilities ht ldpc'): - wifi['cap_ht'] = True - wifi['cap_ht_ldpc'] = True - - # L-SIG TXOP protection capability - if conf.exists('capabilities ht lsig-protection'): - wifi['cap_ht'] = True - wifi['cap_ht_lsig_protection'] = True - - # Set Maximum A-MSDU length - if conf.exists('capabilities ht max-amsdu'): - wifi['cap_ht'] = True - wifi['cap_ht_max_amsdu'] = conf.return_value('capabilities ht max-amsdu') - - # Short GI capabilities - if conf.exists('capabilities ht short-gi'): - wifi['cap_ht'] = True - wifi['cap_ht_short_gi'] = conf.return_values('capabilities ht short-gi') - - # Spatial Multiplexing Power Save (SMPS) settings - if conf.exists('capabilities ht smps'): - wifi['cap_ht'] = True - wifi['cap_ht_smps'] = conf.return_value('capabilities ht smps') - - # Support for receiving PPDU using STBC (Space Time Block Coding) - if conf.exists('capabilities ht stbc rx'): - wifi['cap_ht'] = True - wifi['cap_ht_stbc_rx'] = conf.return_value('capabilities ht stbc rx') - - # Support for sending PPDU using STBC (Space Time Block Coding) - if conf.exists('capabilities ht stbc tx'): - wifi['cap_ht'] = True - wifi['cap_ht_stbc_tx'] = True - - # Require stations to support HT PHY (reject association if they do not) - if conf.exists('capabilities require-ht'): - wifi['cap_req_ht'] = True - - # Require stations to support VHT PHY (reject association if they do not) - if conf.exists('capabilities require-vht'): - wifi['cap_req_vht'] = True - - # Number of antennas on this card - if conf.exists('capabilities vht antenna-count'): - wifi['cap_vht'] = True - wifi['cap_vht_antenna_cnt'] = conf.return_value('capabilities vht antenna-count') - - # set if antenna pattern does not change during the lifetime of an association - if conf.exists('capabilities vht antenna-pattern-fixed'): - wifi['cap_vht'] = True - wifi['cap_vht_antenna_fixed'] = True - - # Beamforming capabilities - if conf.exists('capabilities vht beamform'): - wifi['cap_vht'] = True - wifi['cap_vht_beamform'] = conf.return_values('capabilities vht beamform') - - # VHT operating channel center frequency - center freq 1 (for use with 80, 80+80 and 160 modes) - if conf.exists('capabilities vht center-channel-freq freq-1'): - wifi['cap_vht'] = True - wifi['cap_vht_center_freq_1'] = conf.return_value('capabilities vht center-channel-freq freq-1') - - # VHT operating channel center frequency - center freq 2 (for use with the 80+80 mode) - if conf.exists('capabilities vht center-channel-freq freq-2'): - wifi['cap_vht'] = True - wifi['cap_vht_center_freq_2'] = conf.return_value('capabilities vht center-channel-freq freq-2') - - # VHT operating Channel width - if conf.exists('capabilities vht channel-set-width'): - wifi['cap_vht'] = True - wifi['cap_vht_chan_set_width'] = conf.return_value('capabilities vht channel-set-width') - - # LDPC coding capability - if conf.exists('capabilities vht ldpc'): - wifi['cap_vht'] = True - wifi['cap_vht_ldpc'] = True - - # VHT link adaptation capabilities - if conf.exists('capabilities vht link-adaptation'): - wifi['cap_vht'] = True - wifi['cap_vht_link_adaptation'] = conf.return_value('capabilities vht link-adaptation') - - # Set the maximum length of A-MPDU pre-EOF padding that the station can receive - if conf.exists('capabilities vht max-mpdu-exp'): - wifi['cap_vht'] = True - wifi['cap_vht_max_mpdu_exp'] = conf.return_value('capabilities vht max-mpdu-exp') - - # Increase Maximum MPDU length - if conf.exists('capabilities vht max-mpdu'): - wifi['cap_vht'] = True - wifi['cap_vht_max_mpdu'] = conf.return_value('capabilities vht max-mpdu') - - # Increase Maximum MPDU length - if conf.exists('capabilities vht short-gi'): - wifi['cap_vht'] = True - wifi['cap_vht_short_gi'] = conf.return_values('capabilities vht short-gi') - - # Support for receiving PPDU using STBC (Space Time Block Coding) - if conf.exists('capabilities vht stbc rx'): - wifi['cap_vht'] = True - wifi['cap_vht_stbc_rx'] = conf.return_value('capabilities vht stbc rx') - - # Support for the transmission of at least 2x1 STBC (Space Time Block Coding) - if conf.exists('capabilities vht stbc tx'): - wifi['cap_vht'] = True - wifi['cap_vht_stbc_tx'] = True - - # Support for VHT TXOP Power Save Mode - if conf.exists('capabilities vht tx-powersave'): - wifi['cap_vht'] = True - wifi['cap_vht_tx_powersave'] = True - - # STA supports receiving a VHT variant HT Control field - if conf.exists('capabilities vht vht-cf'): - wifi['cap_vht'] = True - wifi['cap_vht_vht_cf'] = True - - # Wireless radio channel - if conf.exists('channel'): - wifi['channel'] = conf.return_value('channel') - - # Disable broadcast of SSID from access-point - if conf.exists('disable-broadcast-ssid'): - wifi['disable_broadcast_ssid'] = True - - # Disassociate stations based on excessive transmission failures - if conf.exists('expunge-failing-stations'): - wifi['expunge_failing_stations'] = True - - # retrieve real hardware address - if conf.exists('hw-id'): - wifi['hw_id'] = conf.return_value('hw-id') - - # Isolate stations on the AP so they cannot see each other - if conf.exists('isolate-stations'): - wifi['isolate_stations'] = True - - # Wireless physical device - if conf.exists('physical-device'): - wifi['phy'] = conf.return_value('physical-device') - - # Maximum number of wireless radio stations - if conf.exists('max-stations'): - wifi['max_stations'] = conf.return_value('max-stations') - - # Management Frame Protection (MFP) according to IEEE 802.11w - if conf.exists('mgmt-frame-protection'): - wifi['mgmt_frame_protection'] = conf.return_value('mgmt-frame-protection') - - # Wireless radio mode - if conf.exists('mode'): - wifi['mode'] = conf.return_value('mode') - - # Transmission power reduction in dBm - if conf.exists('reduce-transmit-power'): - wifi['reduce_transmit_power'] = conf.return_value('reduce-transmit-power') - - # WEP enabled? - if conf.exists('security wep'): - wifi['sec_wep'] = True - - # WEP encryption key(s) - if conf.exists('security wep key'): - wifi['sec_wep_key'] = conf.return_values('security wep key') - - # WPA enabled? - if conf.exists('security wpa'): - wifi['sec_wpa'] = True - - # WPA Cipher suite - if conf.exists('security wpa cipher'): - wifi['sec_wpa_cipher'] = conf.return_values('security wpa cipher') - - # WPA mode - if conf.exists('security wpa mode'): - wifi['sec_wpa_mode'] = conf.return_value('security wpa mode') - - # WPA default ciphers depend on WPA mode - if not wifi['sec_wpa_cipher']: - if wifi['sec_wpa_mode'] == 'wpa': - wifi['sec_wpa_cipher'].append('TKIP') - wifi['sec_wpa_cipher'].append('CCMP') - - elif wifi['sec_wpa_mode'] == 'wpa2': - wifi['sec_wpa_cipher'].append('CCMP') - - elif wifi['sec_wpa_mode'] == 'both': - wifi['sec_wpa_cipher'].append('CCMP') - wifi['sec_wpa_cipher'].append('TKIP') - - # WPA Group Cipher suite - if conf.exists('security wpa group-cipher'): - wifi['sec_wpa_group_cipher'] = conf.return_values('security wpa group-cipher') - - # WPA personal shared pass phrase - if conf.exists('security wpa passphrase'): - wifi['sec_wpa_passphrase'] = conf.return_value('security wpa passphrase') - - # WPA RADIUS source address - if conf.exists('security wpa radius source-address'): - wifi['sec_wpa_radius_source'] = conf.return_value('security wpa radius source-address') - - # WPA RADIUS server - for server in conf.list_nodes('security wpa radius server'): - # set new configuration level - conf.set_level(cfg_base + ' security wpa radius server ' + server) - radius = { - 'server' : server, - 'acc_port' : '', - 'disabled': False, - 'port' : 1812, - 'key' : '' - } - - # RADIUS server port - if conf.exists('port'): - radius['port'] = int(conf.return_value('port')) - - # receive RADIUS accounting info - if conf.exists('accounting'): - radius['acc_port'] = radius['port'] + 1 - - # Check if RADIUS server was temporary disabled - if conf.exists(['disable']): - radius['disabled'] = True - - # RADIUS server shared-secret - if conf.exists('key'): - radius['key'] = conf.return_value('key') - - # append RADIUS server to list of servers - wifi['sec_wpa_radius'].append(radius) - - # re-set configuration level to parse new nodes - conf.set_level(cfg_base) - - # Wireless access-point service set identifier (SSID) - if conf.exists('ssid'): - wifi['ssid'] = conf.return_value('ssid') - - # Wireless device type for this interface - if conf.exists('type'): - tmp = conf.return_value('type') - if tmp == 'access-point': - tmp = 'ap' - - wifi['op_mode'] = tmp + wifi = get_interface_dict(conf, base, ifname) + + if 'security' in wifi and 'wpa' in wifi['security']: + wpa_cipher = wifi['security']['wpa'].get('cipher') + wpa_mode = wifi['security']['wpa'].get('mode') + if not wpa_cipher: + tmp = None + if wpa_mode == 'wpa': + tmp = {'security': {'wpa': {'cipher' : ['TKIP', 'CCMP']}}} + elif wpa_mode == 'wpa2': + tmp = {'security': {'wpa': {'cipher' : ['CCMP']}}} + elif wpa_mode == 'both': + tmp = {'security': {'wpa': {'cipher' : ['CCMP', 'TKIP']}}} + + if tmp: wifi = dict_merge(tmp, wifi) # retrieve configured regulatory domain - conf.set_level('system') - if conf.exists('wifi-regulatory-domain'): - wifi['country_code'] = conf.return_value('wifi-regulatory-domain') + conf.set_level(['system']) + if conf.exists(['wifi-regulatory-domain']): + wifi['country_code'] = conf.return_value(['wifi-regulatory-domain']) - return wifi + # Only one wireless interface per phy can be in station mode + tmp = find_other_stations(conf, base, wifi['ifname']) + if tmp: wifi['station_interfaces'] = tmp + return wifi def verify(wifi): - if wifi['deleted']: - if wifi['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{wifi["intf"]}" as it is a ' - f'member of bridge "{wifi["is_bridge_member"]}"!')) - + if 'deleted' in wifi: + verify_bridge_delete(wifi) return None - if wifi['op_mode'] != 'monitor' and not wifi['ssid']: - raise ConfigError('SSID must be set for {}'.format(wifi['intf'])) - - if not wifi['phy']: - raise ConfigError('You must specify physical-device') + if 'physical_device' not in wifi: + raise ConfigError('You must specify a physical-device "phy"') - if not wifi['mode']: + if 'type' not in wifi: raise ConfigError('You must specify a WiFi mode') - if wifi['op_mode'] == 'ap': - c = Config() - if not c.exists('system wifi-regulatory-domain'): - raise ConfigError('Wireless regulatory domain is mandatory,\n' \ - 'use "set system wifi-regulatory-domain".') - - if not wifi['channel']: - raise ConfigError('Channel must be set for {}'.format(wifi['intf'])) + if 'ssid' not in wifi and wifi['type'] != 'monitor': + raise ConfigError('SSID must be configured') - if len(wifi['sec_wep_key']) > 4: - raise ConfigError('No more then 4 WEP keys configurable') - - if wifi['cap_vht'] and not wifi['cap_ht']: - raise ConfigError('Specify HT flags if you want to use VHT!') - - if wifi['cap_vht_beamform'] and wifi['cap_vht_antenna_cnt'] == 1: - raise ConfigError('Cannot use beam forming with just one antenna!') - - if wifi['cap_vht_beamform'] == 'single-user-beamformer' and wifi['cap_vht_antenna_cnt'] < 3: - # Nasty Gotcha: see https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf lines 692-705 - raise ConfigError('Single-user beam former requires at least 3 antennas!') - - if wifi['sec_wep'] and (len(wifi['sec_wep_key']) == 0): - raise ConfigError('Missing WEP keys') - - if wifi['sec_wpa'] and not (wifi['sec_wpa_passphrase'] or wifi['sec_wpa_radius']): - raise ConfigError('Misssing WPA key or RADIUS server') - - for radius in wifi['sec_wpa_radius']: - if not radius['key']: - raise ConfigError('Misssing RADIUS shared secret key for server: {}'.format(radius['server'])) - - if ( wifi['is_bridge_member'] - and ( wifi['address'] - or wifi['ipv6_eui64_prefix'] - or wifi['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{wifi["intf"]}" ' - f'as it is a member of bridge "{wifi["is_bridge_member"]}"!')) - - if wifi['vrf']: - if wifi['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{wifi["vrf"]}" does not exist') - - if wifi['is_bridge_member']: - raise ConfigError(( - f'Interface "{wifi["intf"]}" cannot be member of VRF ' - f'"{wifi["vrf"]}" and bridge {wifi["is_bridge_member"]} ' - f'at the same time!')) + if wifi['type'] == 'access-point': + if 'country_code' not in wifi: + raise ConfigError('Wireless regulatory domain is mandatory,\n' \ + 'use "set system wifi-regulatory-domain" for configuration.') + + if 'channel' not in wifi: + raise ConfigError('Wireless channel must be configured!') + + if 'security' in wifi: + if {'wep', 'wpa'} <= set(wifi.get('security', {})): + raise ConfigError('Must either use WEP or WPA security!') + + if 'wep' in wifi['security']: + if 'key' in wifi['security']['wep'] and len(wifi['security']['wep']) > 4: + raise ConfigError('No more then 4 WEP keys configurable') + elif 'key' not in wifi['security']['wep']: + raise ConfigError('Security WEP configured - missing WEP keys!') + + elif 'wpa' in wifi['security']: + wpa = wifi['security']['wpa'] + if not any(i in ['passphrase', 'radius'] for i in wpa): + raise ConfigError('Misssing WPA key or RADIUS server') + + if 'radius' in wpa: + if 'server' in wpa['radius']: + for server in wpa['radius']['server']: + if 'key' not in wpa['radius']['server'][server]: + raise ConfigError(f'Misssing RADIUS shared secret key for server: {server}') + + if 'capabilities' in wifi: + capabilities = wifi['capabilities'] + if 'vht' in capabilities: + if 'ht' not in capabilities: + raise ConfigError('Specify HT flags if you want to use VHT!') + + if {'beamform', 'antenna_count'} <= set(capabilities.get('vht', {})): + if capabilities['vht']['antenna_count'] == '1': + raise ConfigError('Cannot use beam forming with just one antenna!') + + if capabilities['vht']['beamform'] == 'single-user-beamformer': + if int(capabilities['vht']['antenna_count']) < 3: + # Nasty Gotcha: see https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf lines 692-705 + raise ConfigError('Single-user beam former requires at least 3 antennas!') + + if 'station_interfaces' in wifi and wifi['type'] == 'station': + phy = wifi['physical_device'] + if phy in wifi['station_interfaces']: + if len(wifi['station_interfaces'][phy]) > 0: + raise ConfigError('Only one station per wireless physical interface possible!') + + verify_address(wifi) + verify_vrf(wifi) # use common function to verify VLAN configuration verify_vlan_config(wifi) - conf = Config() - # Only one wireless interface per phy can be in station mode - base = ['interfaces', 'wireless'] - for phy in os.listdir('/sys/class/ieee80211'): - stations = [] - for wlan in conf.list_nodes(base): - # the following node is mandatory - if conf.exists(base + [wlan, 'physical-device', phy]): - tmp = conf.return_value(base + [wlan, 'type']) - if tmp == 'station': - stations.append(wlan) - - if len(stations) > 1: - raise ConfigError('Only one station per wireless physical interface possible!') - return None def generate(wifi): - interface = wifi['intf'] + interface = wifi['ifname'] # always stop hostapd service first before reconfiguring it call(f'systemctl stop hostapd@{interface}.service') @@ -513,7 +185,7 @@ def generate(wifi): call(f'systemctl stop wpa_supplicant@{interface}.service') # Delete config files if interface is removed - if wifi['deleted']: + if 'deleted' in wifi: if os.path.isfile(hostapd_conf.format(**wifi)): os.unlink(hostapd_conf.format(**wifi)) @@ -522,10 +194,10 @@ def generate(wifi): return None - if not wifi['mac']: + if 'mac' not in wifi: # http://wiki.stocksy.co.uk/wiki/Multiple_SSIDs_with_hostapd # generate locally administered MAC address from used phy interface - with open('/sys/class/ieee80211/{}/addresses'.format(wifi['phy']), 'r') as f: + with open('/sys/class/ieee80211/{physical_device}/addresses'.format(**wifi), 'r') as f: # some PHYs tend to have multiple interfaces and thus supply multiple MAC # addresses - we only need the first one for our calculation tmp = f.readline().rstrip() @@ -545,20 +217,18 @@ def generate(wifi): wifi['mac'] = str(mac) # render appropriate new config files depending on access-point or station mode - if wifi['op_mode'] == 'ap': - render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.tmpl', wifi) + if wifi['type'] == 'access-point': + render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.tmpl', wifi, trim_blocks=True) - elif wifi['op_mode'] == 'station': - render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.tmpl', wifi) + elif wifi['type'] == 'station': + render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.tmpl', wifi, trim_blocks=True) return None def apply(wifi): - interface = wifi['intf'] - if wifi['deleted']: - w = WiFiIf(interface) - # delete interface - w.remove() + interface = wifi['ifname'] + if 'deleted' in wifi: + WiFiIf(interface).remove() else: # WiFi interface needs to be created on-block (e.g. mode or physical # interface) instead of passing a ton of arguments, I just use a dict @@ -566,97 +236,21 @@ def apply(wifi): conf = deepcopy(WiFiIf.get_config()) # Assign WiFi instance configuration parameters to config dict - conf['phy'] = wifi['phy'] + conf['phy'] = wifi['physical_device'] # Finally create the new interface w = WiFiIf(interface, **conf) - - # assign/remove VRF (ONLY when not a member of a bridge, - # otherwise 'nomaster' removes it from it) - if not wifi['is_bridge_member']: - w.set_vrf(wifi['vrf']) - - # update interface description used e.g. within SNMP - w.set_alias(wifi['description']) - - if wifi['dhcp_client_id']: - w.dhcp.v4.options['client_id'] = wifi['dhcp_client_id'] - - if wifi['dhcp_hostname']: - w.dhcp.v4.options['hostname'] = wifi['dhcp_hostname'] - - if wifi['dhcp_vendor_class_id']: - w.dhcp.v4.options['vendor_class_id'] = wifi['dhcp_vendor_class_id'] - - if wifi['dhcpv6_prm_only']: - w.dhcp.v6.options['dhcpv6_prm_only'] = True - - if wifi['dhcpv6_temporary']: - w.dhcp.v6.options['dhcpv6_temporary'] = True - - if wifi['dhcpv6_pd_length']: - w.dhcp.v6.options['dhcpv6_pd_length'] = wifi['dhcpv6_pd_length'] - - if wifi['dhcpv6_pd_interfaces']: - w.dhcp.v6.options['dhcpv6_pd_interfaces'] = wifi['dhcpv6_pd_interfaces'] - - # ignore link state changes - w.set_link_detect(wifi['disable_link_detect']) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in wifi['ipv6_eui64_prefix_remove']: - w.del_ipv6_eui64_address(addr) - - # Change interface MAC address - re-set to real hardware address (hw-id) - # if custom mac is removed - if wifi['mac']: - w.set_mac(wifi['mac']) - elif wifi['hw_id']: - w.set_mac(wifi['hw_id']) - - # Add IPv6 EUI-based addresses - for addr in wifi['ipv6_eui64_prefix']: - w.add_ipv6_eui64_address(addr) - - # configure ARP filter configuration - w.set_arp_filter(wifi['ip_disable_arp_filter']) - # configure ARP accept - w.set_arp_accept(wifi['ip_enable_arp_accept']) - # configure ARP announce - w.set_arp_announce(wifi['ip_enable_arp_announce']) - # configure ARP ignore - w.set_arp_ignore(wifi['ip_enable_arp_ignore']) - # IPv6 accept RA - w.set_ipv6_accept_ra(wifi['ipv6_accept_ra']) - # IPv6 address autoconfiguration - w.set_ipv6_autoconf(wifi['ipv6_autoconf']) - # IPv6 forwarding - w.set_ipv6_forwarding(wifi['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - w.set_ipv6_dad_messages(wifi['ipv6_dup_addr_detect']) - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in wifi['address_remove']: - w.del_addr(addr) - for addr in wifi['address']: - w.add_addr(addr) - - # apply all vlans to interface - apply_all_vlans(w, wifi) + w.update(wifi) # Enable/Disable interface - interface is always placed in # administrative down state in WiFiIf class - if not wifi['disable']: - w.set_admin_state('up') - + if 'disable' not in wifi: # Physical interface is now configured. Proceed by starting hostapd or # wpa_supplicant daemon. When type is monitor we can just skip this. - if wifi['op_mode'] == 'ap': + if wifi['type'] == 'access-point': call(f'systemctl start hostapd@{interface}.service') - elif wifi['op_mode'] == 'station': + elif wifi['type'] == 'station': call(f'systemctl start wpa_supplicant@{interface}.service') return None -- cgit v1.2.3 From e70a304e36fc6456e16fea81ace4a0a5fd8bd1df Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 25 Jul 2020 00:39:14 +0200 Subject: ifconfig: T2653: make ifname an optional argument to get_interface_dict() Further reduce the boiler-plate code to determine interface tag node or not. It can be passed into get_interface_dict() if explicitly required - else it is taken from the environment. --- python/vyos/configdict.py | 11 ++++++++--- src/conf_mode/interfaces-bonding.py | 10 ++-------- src/conf_mode/interfaces-bridge.py | 11 ++--------- src/conf_mode/interfaces-dummy.py | 8 +------- src/conf_mode/interfaces-ethernet.py | 8 +------- src/conf_mode/interfaces-geneve.py | 9 +-------- src/conf_mode/interfaces-loopback.py | 8 +------- src/conf_mode/interfaces-macsec.py | 8 +------- src/conf_mode/interfaces-pppoe.py | 8 +------- src/conf_mode/interfaces-pseudo-ethernet.py | 8 +------- src/conf_mode/interfaces-wireless.py | 8 +------- src/conf_mode/interfaces-wirelessmodem.py | 9 +-------- 12 files changed, 21 insertions(+), 85 deletions(-) (limited to 'src') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 53b5f9492..126d6195a 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -15,8 +15,8 @@ """ A library for retrieving value dicts from VyOS configs in a declarative fashion. - """ +import os import jmespath from enum import Enum @@ -24,7 +24,6 @@ from copy import deepcopy from vyos import ConfigError from vyos.validate import is_member -from vyos.util import ifname_from_config def retrieve_config(path_hash, base_path, config): """ @@ -195,7 +194,7 @@ def get_removed_vlans(conf, dict): return dict -def get_interface_dict(config, base, ifname): +def get_interface_dict(config, base, ifname=''): """ Common utility function to retrieve and mandgle the interfaces available in CLI configuration. All interfaces have a common base ground where the @@ -205,6 +204,12 @@ def get_interface_dict(config, base, ifname): """ from vyos.xml import defaults + if not ifname: + # determine tagNode instance + if 'VYOS_TAGNODE_VALUE' not in os.environ: + raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') + ifname = os.environ['VYOS_TAGNODE_VALUE'] + # retrieve interface default values default_values = defaults(base) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 8e87a0059..3b238f1ea 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -60,13 +60,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'bonding'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - bond = get_interface_dict(conf, base, ifname) + bond = get_interface_dict(conf, base) # To make our own life easier transfor the list of member interfaces # into a dictionary - we will use this to add additional information @@ -107,7 +101,7 @@ def get_config(): # Check if we are a member of a bond device tmp = is_member(conf, interface, 'bonding') - if tmp and tmp != ifname: + if tmp and tmp != bond['ifname']: interface_config.update({'is_bond_member' : tmp}) # bond members must not have an assigned address diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 9c43d1983..ee8e85e73 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -41,13 +41,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'bridge'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - bridge = get_interface_dict(conf, base, ifname) + bridge = get_interface_dict(conf, base) # determine which members have been removed tmp = node_changed(conf, ['member', 'interface']) @@ -69,13 +63,12 @@ def get_config(): # the default dictionary is not properly paged into the dict (see T2665) # thus we will ammend it ourself default_member_values = defaults(base + ['member', 'interface']) - for interface, interface_config in bridge['member']['interface'].items(): interface_config.update(default_member_values) # Check if we are a member of another bridge device tmp = is_member(conf, interface, 'bridge') - if tmp and tmp != ifname: + if tmp and tmp != bridge['ifname']: interface_config.update({'is_bridge_member' : tmp}) # Check if we are a member of a bond device diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 6d2a78e30..8df86c8ea 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -35,13 +35,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'dummy'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - dummy = get_interface_dict(conf, base, ifname) + dummy = get_interface_dict(conf, base) return dummy def verify(dummy): diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 24ea0af7c..10758e35a 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -37,13 +37,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'ethernet'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - ethernet = get_interface_dict(conf, base, ifname) + ethernet = get_interface_dict(conf, base) return ethernet def verify(ethernet): diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 868ab5ccf..1104bd3c0 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -37,14 +37,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'geneve'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - geneve = get_interface_dict(conf, base, ifname) - + geneve = get_interface_dict(conf, base) return geneve def verify(geneve): diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py index 68a1392ff..0398cd591 100755 --- a/src/conf_mode/interfaces-loopback.py +++ b/src/conf_mode/interfaces-loopback.py @@ -32,13 +32,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'loopback'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - loopback = get_interface_dict(conf, base, ifname) + loopback = get_interface_dict(conf, base) return loopback def verify(loopback): diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 06aee9ea0..ca15212d4 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -42,13 +42,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'macsec'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - macsec = get_interface_dict(conf, base, ifname) + macsec = get_interface_dict(conf, base) # Check if interface has been removed if 'deleted' in macsec: diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 6947cc1e2..b9a88a949 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -38,13 +38,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'pppoe'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - pppoe = get_interface_dict(conf, base, ifname) + pppoe = get_interface_dict(conf, base) # PPPoE is "special" the default MTU is 1492 - update accordingly # as the config_level is already st in get_interface_dict() - we can use [] diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index 55f11e65e..4afea2b3a 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -40,13 +40,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'pseudo-ethernet'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - peth = get_interface_dict(conf, base, ifname) + peth = get_interface_dict(conf, base) mode = leaf_node_changed(conf, ['mode']) if mode: diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 42b55ee6a..b6f247952 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -71,13 +71,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'wireless'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - wifi = get_interface_dict(conf, base, ifname) + wifi = get_interface_dict(conf, base) if 'security' in wifi and 'wpa' in wifi['security']: wpa_cipher = wifi['security']['wpa'].get('cipher') diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index 9a5dae9e0..4081be3c9 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -48,14 +48,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'wirelessmodem'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - wwan = get_interface_dict(conf, base, ifname) - + wwan = get_interface_dict(conf, base) return wwan def verify(wwan): -- cgit v1.2.3 From 56255d62fe1afe9da83e50478a89a720a7dd08ab Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 25 Jul 2020 21:32:48 +0200 Subject: Revert "Merge pull request #423 from thomas-mangin/T2494" This reverts commit bfbf51acb2d4b6b5fe2d22d39f7259686f98d2a0, reversing changes made to 1a85e758b105d493bb9d95916816bd206345bc5d. --- debian/control | 1 - src/services/vyos-hostsd | 1 - src/services/vyos-http-api-server | 4 +--- src/systemd/vyos-hostsd.service | 6 +++--- src/systemd/vyos-http-api.service | 8 ++++++-- 5 files changed, 10 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/debian/control b/debian/control index 089b2d6e2..3a441b47b 100644 --- a/debian/control +++ b/debian/control @@ -36,7 +36,6 @@ Depends: python3, python3-xmltodict, python3-pyudev, python3-voluptuous, - python3-systemd, bsdmainutils, cron, etherwake, diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd index 53ac5a770..0079f7e5c 100755 --- a/src/services/vyos-hostsd +++ b/src/services/vyos-hostsd @@ -592,7 +592,6 @@ if __name__ == '__main__': socket.bind(SOCKET_PATH) os.umask(o_mask) - systemd.daemon.notify('READY=1') while True: # Wait for next request from client msg_json = socket.recv().decode() diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index 3eecaba5a..d5730d86c 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -28,7 +28,6 @@ import vyos.config from flask import Flask, request from waitress import serve -import systemd.daemon from functools import wraps @@ -394,9 +393,8 @@ if __name__ == '__main__': signal.signal(signal.SIGTERM, sig_handler) - systemd.daemon.notify('READY=1') try: serve(app, host=server_config["listen_address"], port=server_config["port"]) except OSError as e: - sys.exit(f"OSError {e}") + print(f"OSError {e}") diff --git a/src/systemd/vyos-hostsd.service b/src/systemd/vyos-hostsd.service index 26e9c745e..b77335778 100644 --- a/src/systemd/vyos-hostsd.service +++ b/src/systemd/vyos-hostsd.service @@ -1,6 +1,5 @@ [Unit] Description=VyOS DNS configuration keeper -Before=vyos-router.service # Without this option, lots of default dependencies are added, # among them network.target, which creates a dependency cycle @@ -15,7 +14,7 @@ WorkingDirectory=/run/vyos-hostsd RuntimeDirectory=vyos-hostsd RuntimeDirectoryPreserve=yes ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-hostsd -Type=notify +Type=idle KillMode=process SyslogIdentifier=vyos-hostsd @@ -23,6 +22,7 @@ SyslogFacility=daemon Restart=on-failure +# Does't work in Jessie but leave it here User=root Group=hostsd @@ -31,4 +31,4 @@ Group=hostsd # Note: After= doesn't actually create a dependency, # it just sets order for the case when both services are to start, # and without RequiredBy it *does not* set vyos-hostsd to start. -WantedBy=vyos.target +RequiredBy=cloud-init-local.service vyos-router.service diff --git a/src/systemd/vyos-http-api.service b/src/systemd/vyos-http-api.service index 74a97d193..4fa68b4ff 100644 --- a/src/systemd/vyos-http-api.service +++ b/src/systemd/vyos-http-api.service @@ -4,8 +4,9 @@ After=auditd.service systemd-user-sessions.service time-sync.target vyos-router. Requires=vyos-router.service [Service] +ExecStartPre=/usr/libexec/vyos/init/vyos-config ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-http-api-server -Type=notify +Type=idle KillMode=process SyslogIdentifier=vyos-http-api @@ -13,8 +14,11 @@ SyslogFacility=daemon Restart=on-failure +# Does't work but leave it here User=root Group=vyattacfg [Install] -WantedBy=vyos.target +# Installing in a earlier target leaves ExecStartPre waiting +WantedBy=getty.target + -- cgit v1.2.3 From 675942ce3e2329a0122da189cd5944df08d7fcab Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 25 Jul 2020 23:52:12 +0200 Subject: l2tpv3: ifconfig: T2653: move implementation to get_interface_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. --- interface-definitions/interfaces-l2tpv3.xml.in | 3 + src/conf_mode/interfaces-l2tpv3.py | 252 +++++-------------------- 2 files changed, 48 insertions(+), 207 deletions(-) (limited to 'src') diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in index 30dd9b604..3a878ad76 100644 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ b/interface-definitions/interfaces-l2tpv3.xml.in @@ -29,6 +29,7 @@ + 5000 #include @@ -50,6 +51,7 @@ Encapsulation must be UDP or IP + udp @@ -138,6 +140,7 @@ + 5000 diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 866419f2c..0978df5b6 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -21,196 +21,65 @@ from copy import deepcopy from netifaces import interfaces from vyos.config import Config -from vyos.ifconfig import L2TPv3If, Interface -from vyos import ConfigError -from vyos.util import call +from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_node_changed +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.ifconfig import L2TPv3If from vyos.util import check_kmod -from vyos.validate import is_member, is_addr_assigned - +from vyos.validate import is_addr_assigned +from vyos import ConfigError from vyos import airbag airbag.enable() k_mod = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6'] -default_config_data = { - 'address': [], - 'deleted': False, - 'description': '', - 'disable': False, - 'encapsulation': 'udp', - 'local_address': '', - 'local_port': 5000, - 'intf': '', - 'ipv6_accept_ra': 1, - 'ipv6_autoconf': 0, - 'ipv6_eui64_prefix': [], - 'ipv6_forwarding': 1, - 'ipv6_dup_addr_detect': 1, - 'is_bridge_member': False, - 'mtu': 1488, - 'peer_session_id': '', - 'peer_tunnel_id': '', - 'remote_address': '', - 'remote_port': 5000, - 'session_id': '', - 'tunnel_id': '' -} def get_config(): - l2tpv3 = deepcopy(default_config_data) + """ + 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', 'l2tpv3'] + l2tpv3 = get_interface_dict(conf, base) - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - l2tpv3['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # check if interface is member of a bridge - l2tpv3['is_bridge_member'] = is_member(conf, l2tpv3['intf'], 'bridge') - - # Check if interface has been removed - if not conf.exists('interfaces l2tpv3 ' + l2tpv3['intf']): - l2tpv3['deleted'] = True - interface = l2tpv3['intf'] - - # to delete the l2tpv3 interface we need the current tunnel_id and session_id - if conf.exists_effective(f'interfaces l2tpv3 {interface} tunnel-id'): - l2tpv3['tunnel_id'] = conf.return_effective_value(f'interfaces l2tpv3 {interface} tunnel-id') - - if conf.exists_effective(f'interfaces l2tpv3 {interface} session-id'): - l2tpv3['session_id'] = conf.return_effective_value(f'interfaces l2tpv3 {interface} session-id') - - return l2tpv3 - - # set new configuration level - conf.set_level('interfaces l2tpv3 ' + l2tpv3['intf']) - - # retrieve configured interface addresses - if conf.exists('address'): - l2tpv3['address'] = conf.return_values('address') - - # retrieve interface description - if conf.exists('description'): - l2tpv3['description'] = conf.return_value('description') - - # get tunnel destination port - if conf.exists('destination-port'): - l2tpv3['remote_port'] = int(conf.return_value('destination-port')) - - # Disable this interface - if conf.exists('disable'): - l2tpv3['disable'] = True - - # get tunnel encapsulation type - if conf.exists('encapsulation'): - l2tpv3['encapsulation'] = conf.return_value('encapsulation') - - # get tunnel local ip address - if conf.exists('local-ip'): - l2tpv3['local_address'] = conf.return_value('local-ip') - - # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC) - if conf.exists('ipv6 address autoconf'): - l2tpv3['ipv6_autoconf'] = 1 - - # Get prefixes for IPv6 addressing based on MAC address (EUI-64) - if conf.exists('ipv6 address eui64'): - l2tpv3['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') - - # Remove the default link-local address if set. - if not ( conf.exists('ipv6 address no-default-link-local') or - l2tpv3['is_bridge_member'] ): - # add the link-local by default to make IPv6 work - l2tpv3['ipv6_eui64_prefix'].append('fe80::/64') - - # Disable IPv6 forwarding on this interface - if conf.exists('ipv6 disable-forwarding'): - l2tpv3['ipv6_forwarding'] = 0 + # L2TPv3 is "special" the default MTU is 1488 - update accordingly + # as the config_level is already st in get_interface_dict() - we can use [] + tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if 'mtu' not in tmp: + l2tpv3['mtu'] = '1488' - # IPv6 Duplicate Address Detection (DAD) tries - if conf.exists('ipv6 dup-addr-detect-transmits'): - l2tpv3['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits')) + # To delete an l2tpv3 interface we need the current tunnel and session-id + if 'deleted' in l2tpv3: + tmp = leaf_node_changed(conf, ['tunnel-id']) + l2tpv3.update({'tunnel_id': tmp}) - # to make IPv6 SLAAC and DHCPv6 work with forwarding=1, - # accept_ra must be 2 - if l2tpv3['ipv6_autoconf'] or 'dhcpv6' in l2tpv3['address']: - l2tpv3['ipv6_accept_ra'] = 2 - - # Maximum Transmission Unit (MTU) - if conf.exists('mtu'): - l2tpv3['mtu'] = int(conf.return_value('mtu')) - - # Remote session id - if conf.exists('peer-session-id'): - l2tpv3['peer_session_id'] = conf.return_value('peer-session-id') - - # Remote tunnel id - if conf.exists('peer-tunnel-id'): - l2tpv3['peer_tunnel_id'] = conf.return_value('peer-tunnel-id') - - # Remote address of L2TPv3 tunnel - if conf.exists('remote-ip'): - l2tpv3['remote_address'] = conf.return_value('remote-ip') - - # Local session id - if conf.exists('session-id'): - l2tpv3['session_id'] = conf.return_value('session-id') - - # get local tunnel port - if conf.exists('source-port'): - l2tpv3['local_port'] = conf.return_value('source-port') - - # get local tunnel id - if conf.exists('tunnel-id'): - l2tpv3['tunnel_id'] = conf.return_value('tunnel-id') + tmp = leaf_node_changed(conf, ['session-id']) + l2tpv3.update({'session_id': tmp}) return l2tpv3 - def verify(l2tpv3): - interface = l2tpv3['intf'] - - if l2tpv3['deleted']: - if l2tpv3['is_bridge_member']: - raise ConfigError(( - f'Interface "{l2tpv3["intf"]}" cannot be deleted as it is a ' - f'member of bridge "{l2tpv3["is_bridge_member"]}"!')) - + if 'deleted' in l2tpv3: + verify_bridge_delete(l2tpv3) return None - if not l2tpv3['local_address']: - raise ConfigError(f'Must configure the l2tpv3 local-ip for {interface}') + interface = l2tpv3['ifname'] - if not is_addr_assigned(l2tpv3['local_address']): - raise ConfigError(f'Must use a configured IP on l2tpv3 local-ip for {interface}') + for key in ['local_ip', 'remote_ip', 'tunnel_id', 'peer_tunnel_id', + 'session_id', 'peer_session_id']: + if key not in l2tpv3: + tmp = key.replace('_', '-') + raise ConfigError(f'L2TPv3 {tmp} must be configured!') - if not l2tpv3['remote_address']: - raise ConfigError(f'Must configure the l2tpv3 remote-ip for {interface}') - - if not l2tpv3['tunnel_id']: - raise ConfigError(f'Must configure the l2tpv3 tunnel-id for {interface}') - - if not l2tpv3['peer_tunnel_id']: - raise ConfigError(f'Must configure the l2tpv3 peer-tunnel-id for {interface}') - - if not l2tpv3['session_id']: - raise ConfigError(f'Must configure the l2tpv3 session-id for {interface}') - - if not l2tpv3['peer_session_id']: - raise ConfigError(f'Must configure the l2tpv3 peer-session-id for {interface}') - - if ( l2tpv3['is_bridge_member'] - and ( l2tpv3['address'] - or l2tpv3['ipv6_eui64_prefix'] - or l2tpv3['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{l2tpv3["intf"]}" ' - f'as it is a member of bridge "{l2tpv3["is_bridge_member"]}"!')) + if not is_addr_assigned(l2tpv3['local_ip']): + raise ConfigError('L2TPv3 local-ip address ' + '"{local_ip}" is not configured!'.format(**l2tpv3)) + verify_address(l2tpv3) return None - def generate(l2tpv3): return None @@ -221,59 +90,28 @@ def apply(l2tpv3): conf = deepcopy(L2TPv3If.get_config()) # Check if L2TPv3 interface already exists - if l2tpv3['intf'] in interfaces(): + if l2tpv3['ifname'] in interfaces(): # L2TPv3 is picky when changing tunnels/sessions, thus we can simply # always delete it first. conf['session_id'] = l2tpv3['session_id'] conf['tunnel_id'] = l2tpv3['tunnel_id'] - l = L2TPv3If(l2tpv3['intf'], **conf) + l = L2TPv3If(l2tpv3['ifname'], **conf) l.remove() - if not l2tpv3['deleted']: + if 'deleted' not in l2tpv3: conf['peer_tunnel_id'] = l2tpv3['peer_tunnel_id'] - conf['local_port'] = l2tpv3['local_port'] - conf['remote_port'] = l2tpv3['remote_port'] + conf['local_port'] = l2tpv3['source_port'] + conf['remote_port'] = l2tpv3['destination_port'] conf['encapsulation'] = l2tpv3['encapsulation'] - conf['local_address'] = l2tpv3['local_address'] - conf['remote_address'] = l2tpv3['remote_address'] + conf['local_address'] = l2tpv3['local_ip'] + conf['remote_address'] = l2tpv3['remote_ip'] conf['session_id'] = l2tpv3['session_id'] conf['tunnel_id'] = l2tpv3['tunnel_id'] conf['peer_session_id'] = l2tpv3['peer_session_id'] # Finally create the new interface - l = L2TPv3If(l2tpv3['intf'], **conf) - # update interface description used e.g. by SNMP - l.set_alias(l2tpv3['description']) - # Maximum Transfer Unit (MTU) - l.set_mtu(l2tpv3['mtu']) - # IPv6 accept RA - l.set_ipv6_accept_ra(l2tpv3['ipv6_accept_ra']) - # IPv6 address autoconfiguration - l.set_ipv6_autoconf(l2tpv3['ipv6_autoconf']) - # IPv6 forwarding - l.set_ipv6_forwarding(l2tpv3['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - l.set_ipv6_dad_messages(l2tpv3['ipv6_dup_addr_detect']) - - # 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 l2tpv3['address']: - l.add_addr(addr) - - # IPv6 EUI-based addresses - for addr in l2tpv3['ipv6_eui64_prefix']: - l.add_ipv6_eui64_address(addr) - - # As the interface is always disabled first when changing parameters - # we will only re-enable the interface if it is not administratively - # disabled - if not l2tpv3['disable']: - l.set_admin_state('up') - - # re-add ourselves to any bridge we might have fallen out of - if l2tpv3['is_bridge_member']: - l.add_to_bridge(l2tpv3['is_bridge_member']) + l = L2TPv3If(l2tpv3['ifname'], **conf) + l.update(l2tpv3) return None -- cgit v1.2.3 From a156d2f1479affe4e7cfa56785e4d2d61a776cea Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 26 Jul 2020 11:16:13 +0200 Subject: vxlan: ifconfig: T2653: move to get_interface_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. --- interface-definitions/interfaces-vxlan.xml.in | 1 + python/vyos/ifconfig/vxlan.py | 20 +- src/conf_mode/interfaces-vxlan.py | 253 ++++---------------------- 3 files changed, 46 insertions(+), 228 deletions(-) (limited to 'src') diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index bd3ab4022..8529f6885 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -93,6 +93,7 @@ + 8472 diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 973b4ef05..0dddab7b7 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -47,8 +47,8 @@ class VXLANIf(Interface): 'port': 8472, # The Linux implementation of VXLAN pre-dates # the IANA's selection of a standard destination port 'remote': '', - 'src_address': '', - 'src_interface': '', + 'source_address': '', + 'source_interface': '', 'vni': 0 } definition = { @@ -60,29 +60,29 @@ class VXLANIf(Interface): } } options = Interface.options + \ - ['group', 'remote', 'src_interface', 'port', 'vni', 'src_address'] + ['group', 'remote', 'source_interface', 'port', 'vni', 'source_address'] mapping = { 'ifname': 'add', 'vni': 'id', 'port': 'dstport', - 'src_address': 'local', - 'src_interface': 'dev', + 'source_address': 'local', + 'source_interface': 'dev', } def _create(self): cmdline = ['ifname', 'type', 'vni', 'port'] - if self.config['src_address']: - cmdline.append('src_address') + if self.config['source_address']: + cmdline.append('source_address') if self.config['remote']: cmdline.append('remote') - if self.config['group'] or self.config['src_interface']: - if self.config['group'] and self.config['src_interface']: + if self.config['group'] or self.config['source_interface']: + if self.config['group'] and self.config['source_interface']: cmdline.append('group') - cmdline.append('src_interface') + cmdline.append('source_interface') else: ifname = self.config['ifname'] raise ConfigError( diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 39db814b4..47c0bdcb8 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -21,197 +21,61 @@ from copy import deepcopy from netifaces import interfaces from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_source_interface from vyos.ifconfig import VXLANIf, Interface -from vyos.validate import is_member from vyos import ConfigError - from vyos import airbag airbag.enable() -default_config_data = { - 'address': [], - 'deleted': False, - 'description': '', - 'disable': False, - 'group': '', - 'intf': '', - 'ip_arp_cache_tmo': 30, - 'ip_disable_arp_filter': 1, - 'ip_enable_arp_accept': 0, - 'ip_enable_arp_announce': 0, - 'ip_enable_arp_ignore': 0, - 'ip_proxy_arp': 0, - 'ipv6_accept_ra': 1, - 'ipv6_autoconf': 0, - 'ipv6_eui64_prefix': [], - 'ipv6_forwarding': 1, - 'ipv6_dup_addr_detect': 1, - 'is_bridge_member': False, - 'source_address': '', - 'source_interface': '', - 'mtu': 1450, - 'remote': '', - 'remote_port': 8472, # The Linux implementation of VXLAN pre-dates - # the IANA's selection of a standard destination port - 'vni': '' -} - def get_config(): - vxlan = deepcopy(default_config_data) + """ + 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', 'vxlan'] + vxlan = get_interface_dict(conf, base) - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - vxlan['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # check if interface is member if a bridge - vxlan['is_bridge_member'] = is_member(conf, vxlan['intf'], 'bridge') - - # Check if interface has been removed - if not conf.exists('interfaces vxlan ' + vxlan['intf']): - vxlan['deleted'] = True - return vxlan - - # set new configuration level - conf.set_level('interfaces vxlan ' + vxlan['intf']) - - # retrieve configured interface addresses - if conf.exists('address'): - vxlan['address'] = conf.return_values('address') - - # retrieve interface description - if conf.exists('description'): - vxlan['description'] = conf.return_value('description') - - # Disable this interface - if conf.exists('disable'): - vxlan['disable'] = True - - # VXLAN multicast grou - if conf.exists('group'): - vxlan['group'] = conf.return_value('group') - - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - vxlan['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - - # ARP filter configuration - if conf.exists('ip disable-arp-filter'): - vxlan['ip_disable_arp_filter'] = 0 - - # ARP enable accept - if conf.exists('ip enable-arp-accept'): - vxlan['ip_enable_arp_accept'] = 1 - - # ARP enable announce - if conf.exists('ip enable-arp-announce'): - vxlan['ip_enable_arp_announce'] = 1 - - # ARP enable ignore - if conf.exists('ip enable-arp-ignore'): - vxlan['ip_enable_arp_ignore'] = 1 - - # Enable proxy-arp on this interface - if conf.exists('ip enable-proxy-arp'): - vxlan['ip_proxy_arp'] = 1 - - # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC) - if conf.exists('ipv6 address autoconf'): - vxlan['ipv6_autoconf'] = 1 - - # Get prefixes for IPv6 addressing based on MAC address (EUI-64) - if conf.exists('ipv6 address eui64'): - vxlan['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') - - # Remove the default link-local address if set. - if not ( conf.exists('ipv6 address no-default-link-local') - or vxlan['is_bridge_member'] ): - # add the link-local by default to make IPv6 work - vxlan['ipv6_eui64_prefix'].append('fe80::/64') - - # Disable IPv6 forwarding on this interface - if conf.exists('ipv6 disable-forwarding'): - vxlan['ipv6_forwarding'] = 0 - - # IPv6 Duplicate Address Detection (DAD) tries - if conf.exists('ipv6 dup-addr-detect-transmits'): - vxlan['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits')) - - # to make IPv6 SLAAC and DHCPv6 work with forwarding=1, - # accept_ra must be 2 - if vxlan['ipv6_autoconf'] or 'dhcpv6' in vxlan['address']: - vxlan['ipv6_accept_ra'] = 2 - - # VXLAN source address - if conf.exists('source-address'): - vxlan['source_address'] = conf.return_value('source-address') - - # VXLAN underlay interface - if conf.exists('source-interface'): - vxlan['source_interface'] = conf.return_value('source-interface') - - # Maximum Transmission Unit (MTU) - if conf.exists('mtu'): - vxlan['mtu'] = int(conf.return_value('mtu')) - - # Remote address of VXLAN tunnel - if conf.exists('remote'): - vxlan['remote'] = conf.return_value('remote') - - # Remote port of VXLAN tunnel - if conf.exists('port'): - vxlan['remote_port'] = int(conf.return_value('port')) - - # Virtual Network Identifier - if conf.exists('vni'): - vxlan['vni'] = conf.return_value('vni') + # VXLAN is "special" the default MTU is 1492 - update accordingly + # as the config_level is already st in get_interface_dict() - we can use [] + tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if 'mtu' not in tmp: + vxlan['mtu'] = '1450' return vxlan - def verify(vxlan): - if vxlan['deleted']: - if vxlan['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{vxlan["intf"]}" as it is a ' - f'member of bridge "{vxlan["is_bridge_member"]}"!')) - + if 'deleted' in vxlan: + verify_bridge_delete(vxlan) return None - if vxlan['mtu'] < 1500: + if int(vxlan['mtu']) < 1500: print('WARNING: RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU') - if vxlan['group']: - if not vxlan['source_interface']: + if 'group' in vxlan: + if 'source_interface' not in vxlan: raise ConfigError('Multicast VXLAN requires an underlaying interface ') - if not vxlan['source_interface'] in interfaces(): - raise ConfigError('VXLAN source interface does not exist') + verify_source_interface(vxlan) - if not (vxlan['group'] or vxlan['remote'] or vxlan['source_address']): + if not any(tmp in ['group', 'remote', 'source_address'] for tmp in vxlan): raise ConfigError('Group, remote or source-address must be configured') - if not vxlan['vni']: + if 'vni' not in vxlan: raise ConfigError('Must configure VNI for VXLAN') - if vxlan['source_interface']: + if 'source_interface' in vxlan: # VXLAN adds a 50 byte overhead - we need to check the underlaying MTU # if our configured MTU is at least 50 bytes less underlay_mtu = int(Interface(vxlan['source_interface']).get_mtu()) - if underlay_mtu < (vxlan['mtu'] + 50): + if underlay_mtu < (int(vxlan['mtu']) + 50): raise ConfigError('VXLAN has a 50 byte overhead, underlaying device ' \ - 'MTU is to small ({})'.format(underlay_mtu)) - - if ( vxlan['is_bridge_member'] - and ( vxlan['address'] - or vxlan['ipv6_eui64_prefix'] - or vxlan['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{vxlan["intf"]}" ' - f'as it is a member of bridge "{vxlan["is_bridge_member"]}"!')) + f'MTU is to small ({underlay_mtu} bytes)') + verify_address(vxlan) return None @@ -221,73 +85,26 @@ def generate(vxlan): def apply(vxlan): # Check if the VXLAN interface already exists - if vxlan['intf'] in interfaces(): - v = VXLANIf(vxlan['intf']) + if vxlan['ifname'] in interfaces(): + v = VXLANIf(vxlan['ifname']) # VXLAN is super picky and the tunnel always needs to be recreated, # thus we can simply always delete it first. v.remove() - if not vxlan['deleted']: + if 'deleted' not in vxlan: # VXLAN interface needs to be created on-block # instead of passing a ton of arguments, I just use a dict # that is managed by vyos.ifconfig conf = deepcopy(VXLANIf.get_config()) # Assign VXLAN instance configuration parameters to config dict - conf['vni'] = vxlan['vni'] - conf['group'] = vxlan['group'] - conf['src_address'] = vxlan['source_address'] - conf['src_interface'] = vxlan['source_interface'] - conf['remote'] = vxlan['remote'] - conf['port'] = vxlan['remote_port'] + for tmp in ['vni', 'group', 'source_address', 'source_interface', 'remote', 'port']: + if tmp in vxlan: + conf[tmp] = vxlan[tmp] # Finally create the new interface - v = VXLANIf(vxlan['intf'], **conf) - # update interface description used e.g. by SNMP - v.set_alias(vxlan['description']) - # Maximum Transfer Unit (MTU) - v.set_mtu(vxlan['mtu']) - - # configure ARP cache timeout in milliseconds - v.set_arp_cache_tmo(vxlan['ip_arp_cache_tmo']) - # configure ARP filter configuration - v.set_arp_filter(vxlan['ip_disable_arp_filter']) - # configure ARP accept - v.set_arp_accept(vxlan['ip_enable_arp_accept']) - # configure ARP announce - v.set_arp_announce(vxlan['ip_enable_arp_announce']) - # configure ARP ignore - v.set_arp_ignore(vxlan['ip_enable_arp_ignore']) - # Enable proxy-arp on this interface - v.set_proxy_arp(vxlan['ip_proxy_arp']) - # IPv6 accept RA - v.set_ipv6_accept_ra(vxlan['ipv6_accept_ra']) - # IPv6 address autoconfiguration - v.set_ipv6_autoconf(vxlan['ipv6_autoconf']) - # IPv6 forwarding - v.set_ipv6_forwarding(vxlan['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - v.set_ipv6_dad_messages(vxlan['ipv6_dup_addr_detect']) - - # 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) - - # IPv6 EUI-based addresses - for addr in vxlan['ipv6_eui64_prefix']: - v.add_ipv6_eui64_address(addr) - - # As the VXLAN interface is always disabled first when changing - # parameters we will only re-enable the interface if it is not - # administratively disabled - if not vxlan['disable']: - v.set_admin_state('up') - - # re-add ourselves to any bridge we might have fallen out of - if vxlan['is_bridge_member']: - v.add_to_bridge(vxlan['is_bridge_member']) + v = VXLANIf(vxlan['ifname'], **conf) + v.update(vxlan) return None -- cgit v1.2.3 From 789775af9f5e3f9239ef4583eb4ef7538e40b37c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 26 Jul 2020 16:36:48 +0200 Subject: wireguard: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. --- interface-definitions/interfaces-wireguard.xml.in | 8 +- python/vyos/ifconfig/wireguard.py | 136 +++++----- src/conf_mode/interfaces-wireguard.py | 300 ++++------------------ 3 files changed, 139 insertions(+), 305 deletions(-) (limited to 'src') diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index edf9bf696..981bce826 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -33,6 +33,7 @@ + 0 @@ -41,6 +42,7 @@ + default @@ -103,7 +105,11 @@ #include - how often send keep alives in seconds + Interval to send keepalive messages + + 1-65535 + Interval in seconds + diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py index 62ca57ca2..fad4ef282 100644 --- a/python/vyos/ifconfig/wireguard.py +++ b/python/vyos/ifconfig/wireguard.py @@ -24,7 +24,7 @@ from hurry.filesize import alternative from vyos.config import Config from vyos.ifconfig import Interface from vyos.ifconfig import Operational - +from vyos.validate import is_ipv6 class WireGuardOperational(Operational): def _dump(self): @@ -169,65 +169,79 @@ class WireGuardIf(Interface): ['port', 'private_key', 'pubkey', 'psk', 'allowed_ips', 'fwmark', 'endpoint', 'keepalive'] - """ - Wireguard interface class, contains a comnfig dictionary since - wireguard VPN is being comnfigured via the wg command rather than - writing the config into a file. Otherwise if a pre-shared key is used - (symetric enryption key), it would we exposed within multiple files. - Currently it's only within the config.boot if the config was saved. - - Example: - >>> from vyos.ifconfig import WireGuardIf as wg_if - >>> wg_intfc = wg_if("wg01") - >>> print (wg_intfc.wg_config) - {'private_key': None, 'keepalive': 0, 'endpoint': None, 'port': 0, - 'allowed_ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'} - >>> wg_intfc.wg_config['keepalive'] = 100 - >>> print (wg_intfc.wg_config) - {'private_key': None, 'keepalive': 100, 'endpoint': None, 'port': 0, - 'allowed_ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'} - """ - - def update(self): - if not self.config['private_key']: - raise ValueError("private key required") - else: - # fmask permission check? - pass - - cmd = 'wg set {ifname}'.format(**self.config) - cmd += ' listen-port {port}'.format(**self.config) - cmd += ' fwmark "{fwmark}" '.format(**self.config) - cmd += ' private-key {private_key}'.format(**self.config) - cmd += ' peer {pubkey}'.format(**self.config) - cmd += ' persistent-keepalive {keepalive}'.format(**self.config) - # allowed-ips must be properly quoted else the interface can't be properly - # created as the wg utility will tread multiple IP addresses as command - # parameters - cmd += ' allowed-ips "{}"'.format(','.join(self.config['allowed-ips'])) - - if self.config['endpoint']: - cmd += ' endpoint "{endpoint}"'.format(**self.config) - - psk_file = '' - if self.config['psk']: - psk_file = '/tmp/{ifname}.psk'.format(**self.config) - with open(psk_file, 'w') as f: - f.write(self.config['psk']) + 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. """ + + # remove no longer associated peers first + if 'peer_remove' in config: + for tmp in config['peer_remove']: + peer = config['peer_remove'][tmp] + peer['ifname'] = config['ifname'] + + cmd = 'wg set {ifname} peer {pubkey} remove' + self._cmd(cmd.format(**peer)) + + # Wireguard base command is identical for every peer + base_cmd = 'wg set {ifname} private-key {private_key}' + if 'port' in config: + base_cmd += ' listen-port {port}' + if 'fwmark' in config: + base_cmd += ' fwmark {fwmark}' + + base_cmd = base_cmd.format(**config) + + for tmp in config['peer']: + peer = config['peer'][tmp] + + # start of with a fresh 'wg' command + cmd = base_cmd + ' peer {pubkey}' + + # If no PSK is given remove it by using /dev/null - passing keys via + # the shell (usually bash) is considered insecure, thus we use a file + no_psk_file = '/dev/null' + psk_file = no_psk_file + if 'preshared_key' in peer: + psk_file = '/tmp/tmp.wireguard.psk' + with open(psk_file, 'w') as f: + f.write(peer['preshared_key']) cmd += f' preshared-key {psk_file}' - self._cmd(cmd) - - # PSK key file is not required to be stored persistently as its backed by CLI - if os.path.exists(psk_file): - os.remove(psk_file) - - def remove_peer(self, peerkey): - """ - Remove a peer of an interface, peers are identified by their public key. - Giving it a readable name is a vyos feature, to remove a peer the pubkey - and the interface is needed, to remove the entry. - """ - cmd = "wg set {0} peer {1} remove".format( - self.config['ifname'], str(peerkey)) - return self._cmd(cmd) + # Persistent keepalive is optional + if 'persistent_keepalive'in peer: + cmd += ' persistent-keepalive {persistent_keepalive}' + + # Multiple allowed-ip ranges can be defined - ensure we are always + # dealing with a list + if isinstance(peer['allowed_ips'], str): + peer['allowed_ips'] = [peer['allowed_ips']] + cmd += ' allowed-ips ' + ','.join(peer['allowed_ips']) + + # Endpoint configuration is optional + if {'address', 'port'} <= set(peer): + if is_ipv6(config['address']): + cmd += ' endpoint [{address}]:{port}' + else: + cmd += ' endpoint {address}:{port}' + + self._cmd(cmd.format(**peer)) + + # PSK key file is not required to be stored persistently as its backed by CLI + if psk_file != no_psk_file and os.path.exists(psk_file): + os.remove(psk_file) + + # call base class + super().update(config) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) + diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 982aefa5f..6325a8b05 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -15,44 +15,29 @@ # along with this program. If not, see . import os -import re from sys import exit from copy import deepcopy -from netifaces import interfaces from vyos.config import Config -from vyos.configdict import list_diff +from vyos.configdict import dict_merge +from vyos.configdict import get_interface_dict +from vyos.configdict import node_changed +from vyos.configdict import leaf_node_changed +from vyos.configverify import verify_vrf +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete from vyos.ifconfig import WireGuardIf -from vyos.util import chown, chmod_750, call +from vyos.util import chown, chmod_750 from vyos.util import check_kmod -from vyos.validate import is_member, is_ipv6 from vyos import ConfigError - from vyos import airbag airbag.enable() -kdir = r'/config/auth/wireguard' k_mod = 'wireguard' -default_config_data = { - 'intfc': '', - 'address': [], - 'address_remove': [], - 'description': '', - 'listen_port': '', - 'deleted': False, - 'disable': False, - 'fwmark': 0, - 'is_bridge_member': False, - 'mtu': 1420, - 'peer': [], - 'peer_remove': [], # stores public keys of peers to remove - 'pk': f'{kdir}/default/private.key', - 'vrf': '' -} - def _migrate_default_keys(): + kdir = r'/config/auth/wireguard' if os.path.exists(f'{kdir}/private.key') and not os.path.exists(f'{kdir}/default/private.key'): location = f'{kdir}/default' if not os.path.exists(location): @@ -65,246 +50,75 @@ def _migrate_default_keys(): 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 + """ conf = Config() base = ['interfaces', 'wireguard'] + wireguard = get_interface_dict(conf, base) - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - wg = deepcopy(default_config_data) - wg['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # check if interface is member if a bridge - wg['is_bridge_member'] = is_member(conf, wg['intf'], 'bridge') - - # Check if interface has been removed - if not conf.exists(base + [wg['intf']]): - wg['deleted'] = True - return wg - - conf.set_level(base + [wg['intf']]) - - # retrieve configured interface addresses - if conf.exists(['address']): - wg['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']) - wg['address_remove'] = list_diff(eff_addr, wg['address']) - - # retrieve interface description - if conf.exists(['description']): - wg['description'] = conf.return_value(['description']) - - # disable interface - if conf.exists(['disable']): - wg['disable'] = True - - # local port to listen on - if conf.exists(['port']): - wg['listen_port'] = conf.return_value(['port']) - - # fwmark value - if conf.exists(['fwmark']): - wg['fwmark'] = int(conf.return_value(['fwmark'])) - - # Maximum Transmission Unit (MTU) - if conf.exists('mtu'): - wg['mtu'] = int(conf.return_value(['mtu'])) - - # retrieve VRF instance - if conf.exists('vrf'): - wg['vrf'] = conf.return_value('vrf') - - # private key - if conf.exists(['private-key']): - wg['pk'] = "{0}/{1}/private.key".format( - kdir, conf.return_value(['private-key'])) - - # peer removal, wg identifies peers by its pubkey - peer_eff = conf.list_effective_nodes(['peer']) - peer_rem = list_diff(peer_eff, conf.list_nodes(['peer'])) - for peer in peer_rem: - wg['peer_remove'].append( - conf.return_effective_value(['peer', peer, 'pubkey'])) - - # peer settings - if conf.exists(['peer']): - for p in conf.list_nodes(['peer']): - # set new config level for this peer - conf.set_level(base + [wg['intf'], 'peer', p]) - peer = { - 'allowed-ips': [], - 'address': '', - 'name': p, - 'persistent_keepalive': '', - 'port': '', - 'psk': '', - 'pubkey': '' - } - - # peer allowed-ips - if conf.exists(['allowed-ips']): - peer['allowed-ips'] = conf.return_values(['allowed-ips']) - - # peer address - if conf.exists(['address']): - peer['address'] = conf.return_value(['address']) - - # peer port - if conf.exists(['port']): - peer['port'] = conf.return_value(['port']) + # Wireguard is "special" the default MTU is 1420 - update accordingly + # as the config_level is already st in get_interface_dict() - we can use [] + tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if 'mtu' not in tmp: + wireguard['mtu'] = '1420' - # persistent-keepalive - if conf.exists(['persistent-keepalive']): - peer['persistent_keepalive'] = conf.return_value(['persistent-keepalive']) + # Mangle private key - it has a default so its always valid + wireguard['private_key'] = '/config/auth/wireguard/{private_key}/private.key'.format(**wireguard) - # preshared-key - if conf.exists(['preshared-key']): - peer['psk'] = conf.return_value(['preshared-key']) + # Determine which Wireguard peer has been removed. + # Peers can only be removed with their public key! + tmp = node_changed(conf, ['peer']) + if tmp: + dict = {} + for peer in tmp: + peer_config = leaf_node_changed(conf, ['peer', peer, 'pubkey']) + dict = dict_merge({'peer_remove' : {peer : {'pubkey' : peer_config}}}, dict) + wireguard.update(dict) - # peer pubkeys - if conf.exists(['pubkey']): - key_eff = conf.return_effective_value(['pubkey']) - key_cfg = conf.return_value(['pubkey']) - peer['pubkey'] = key_cfg + return wireguard - # on a pubkey change we need to remove the pubkey first - # peers are identified by pubkey, so key update means - # peer removal and re-add - if key_eff != key_cfg and key_eff != None: - wg['peer_remove'].append(key_cfg) - - # if a peer is disabled, we have to exec a remove for it's pubkey - if conf.exists(['disable']): - wg['peer_remove'].append(peer['pubkey']) - else: - wg['peer'].append(peer) - - return wg - - -def verify(wg): - if wg['deleted']: - if wg['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{wg["intf"]}" as it is a member ' - f'of bridge "{wg["is_bridge_member"]}"!')) +def verify(wireguard): + if 'deleted' in wireguard: + verify_bridge_delete(wireguard) return None - if wg['is_bridge_member'] and wg['address']: - raise ConfigError(( - f'Cannot assign address to interface "{wg["intf"]}" ' - f'as it is a member of bridge "{wg["is_bridge_member"]}"!')) - - if wg['vrf']: - if wg['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{wg["vrf"]}" does not exist') + verify_address(wireguard) + verify_vrf(wireguard) - if wg['is_bridge_member']: - raise ConfigError(( - f'Interface "{wg["intf"]}" cannot be member of VRF ' - f'"{wg["vrf"]}" and bridge {wg["is_bridge_member"]} ' - f'at the same time!')) + if not os.path.exists(wireguard['private_key']): + raise ConfigError('Wireguard private-key not found! Execute: ' \ + '"run generate wireguard [default-keypair|named-keypairs]"') - if not os.path.exists(wg['pk']): - raise ConfigError('No keys found, generate them by executing:\n' \ - '"run generate wireguard [keypair|named-keypairs]"') + if 'address' not in wireguard: + raise ConfigError('IP address required!') - if not wg['address']: - raise ConfigError(f'IP address required for interface "{wg["intf"]}"!') - - if not wg['peer']: - raise ConfigError(f'Peer required for interface "{wg["intf"]}"!') + if 'peer' not in wireguard: + raise ConfigError('At least one Wireguard peer is required!') # run checks on individual configured WireGuard peer - for peer in wg['peer']: - if not peer['allowed-ips']: - raise ConfigError(f'Peer allowed-ips required for peer "{peer["name"]}"!') - - if not peer['pubkey']: - raise ConfigError(f'Peer public-key required for peer "{peer["name"]}"!') - - if peer['address'] and not peer['port']: - raise ConfigError(f'Peer "{peer["name"]}" port must be defined if address is defined!') + for tmp in wireguard['peer']: + peer = wireguard['peer'][tmp] - if not peer['address'] and peer['port']: - raise ConfigError(f'Peer "{peer["name"]}" address must be defined if port is defined!') + if 'allowed_ips' not in peer: + raise ConfigError(f'Wireguard allowed-ips required for peer "{tmp}"!') + if 'pubkey' not in peer: + raise ConfigError(f'Wireguard public-key required for peer "{tmp}"!') -def apply(wg): - # init wg class - w = WireGuardIf(wg['intf']) + if ('address' in peer and 'port' not in peer) or ('port' in peer and 'address' not in peer): + raise ConfigError('Both Wireguard port and address must be defined ' + f'for peer "{tmp}" if either one of them is set!') - # single interface removal - if wg['deleted']: - w.remove() +def apply(wireguard): + if 'deleted' in wireguard: + WireGuardIf(wireguard['ifname']).remove() return None - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in wg['address_remove']: - w.del_addr(addr) - for addr in wg['address']: - w.add_addr(addr) - - # Maximum Transmission Unit (MTU) - w.set_mtu(wg['mtu']) - - # update interface description used e.g. within SNMP - w.set_alias(wg['description']) - - # assign/remove VRF (ONLY when not a member of a bridge, - # otherwise 'nomaster' removes it from it) - if not wg['is_bridge_member']: - w.set_vrf(wg['vrf']) - - # remove peers - for pub_key in wg['peer_remove']: - w.remove_peer(pub_key) - - # peer pubkey - # setting up the wg interface - w.config['private_key'] = c['pk'] - - for peer in wg['peer']: - # peer pubkey - w.config['pubkey'] = peer['pubkey'] - # peer allowed-ips - w.config['allowed-ips'] = peer['allowed-ips'] - # local listen port - if wg['listen_port']: - w.config['port'] = wg['listen_port'] - # fwmark - if c['fwmark']: - w.config['fwmark'] = wg['fwmark'] - - # endpoint - if peer['address'] and peer['port']: - if is_ipv6(peer['address']): - w.config['endpoint'] = '[{}]:{}'.format(peer['address'], peer['port']) - else: - w.config['endpoint'] = '{}:{}'.format(peer['address'], peer['port']) - - # persistent-keepalive - if peer['persistent_keepalive']: - w.config['keepalive'] = peer['persistent_keepalive'] - - if peer['psk']: - w.config['psk'] = peer['psk'] - - w.update() - - # Enable/Disable interface - if wg['disable']: - w.set_admin_state('down') - else: - w.set_admin_state('up') - + w = WireGuardIf(wireguard['ifname']) + w.update(wireguard) return None if __name__ == '__main__': -- cgit v1.2.3 From 9f7ef46636c8b995a3b758484324db4bc9a4df37 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 29 Jul 2020 17:31:35 +0200 Subject: mdns-repeater: T2742: migrate to get_config_dict() --- data/templates/mdns-repeater/mdns-repeater.tmpl | 2 +- interface-definitions/mdns-repeater.xml.in | 38 -------- interface-definitions/service_mdns-repeater.xml.in | 37 +++++++ src/conf_mode/mdns_repeater.py | 107 --------------------- src/conf_mode/service_mdns-repeater.py | 89 +++++++++++++++++ 5 files changed, 127 insertions(+), 146 deletions(-) delete mode 100644 interface-definitions/mdns-repeater.xml.in create mode 100644 interface-definitions/service_mdns-repeater.xml.in delete mode 100755 src/conf_mode/mdns_repeater.py create mode 100755 src/conf_mode/service_mdns-repeater.py (limited to 'src') diff --git a/data/templates/mdns-repeater/mdns-repeater.tmpl b/data/templates/mdns-repeater/mdns-repeater.tmpl index 3fc4db67e..80f4ab047 100644 --- a/data/templates/mdns-repeater/mdns-repeater.tmpl +++ b/data/templates/mdns-repeater/mdns-repeater.tmpl @@ -1,2 +1,2 @@ ### Autogenerated by mdns_repeater.py ### -DAEMON_ARGS="{{ interfaces | join(' ') }}" +DAEMON_ARGS="{{ interface | join(' ') }}" diff --git a/interface-definitions/mdns-repeater.xml.in b/interface-definitions/mdns-repeater.xml.in deleted file mode 100644 index a59321294..000000000 --- a/interface-definitions/mdns-repeater.xml.in +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - Multicast DNS (mDNS) parameters - - - - - mDNS repeater configuration - 990 - - - - - Disable mDNS repeater service - - - - - - Interface to repeat mDNS advertisements [REQUIRED] - - - - - - - - - - - - - diff --git a/interface-definitions/service_mdns-repeater.xml.in b/interface-definitions/service_mdns-repeater.xml.in new file mode 100644 index 000000000..e21b1b27c --- /dev/null +++ b/interface-definitions/service_mdns-repeater.xml.in @@ -0,0 +1,37 @@ + + + + + + + Multicast DNS (mDNS) parameters + + + + + mDNS repeater configuration + 990 + + + + + Disable mDNS repeater service + + + + + + Interface to repeat mDNS advertisements [REQUIRED] + + + + + + + + + + + + + diff --git a/src/conf_mode/mdns_repeater.py b/src/conf_mode/mdns_repeater.py deleted file mode 100755 index b43f9bdd8..000000000 --- a/src/conf_mode/mdns_repeater.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2017-2020 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 . - -import os - -from sys import exit -from copy import deepcopy -from netifaces import ifaddresses, AF_INET - -from vyos.config import Config -from vyos import ConfigError -from vyos.util import call -from vyos.template import render - -from vyos import airbag -airbag.enable() - -config_file = r'/etc/default/mdns-repeater' - -default_config_data = { - 'disabled': False, - 'interfaces': [] -} - -def get_config(): - mdns = deepcopy(default_config_data) - conf = Config() - base = ['service', 'mdns', 'repeater'] - if not conf.exists(base): - return None - else: - conf.set_level(base) - - # Service can be disabled by user - if conf.exists(['disable']): - mdns['disabled'] = True - return mdns - - # Interface to repeat mDNS advertisements - if conf.exists(['interface']): - mdns['interfaces'] = conf.return_values(['interface']) - - return mdns - -def verify(mdns): - if mdns is None: - return None - - if mdns['disabled']: - return None - - # We need at least two interfaces to repeat mDNS advertisments - if len(mdns['interfaces']) < 2: - raise ConfigError('mDNS repeater requires at least 2 configured interfaces!') - - # For mdns-repeater to work it is essential that the interfaces has - # an IPv4 address assigned - for interface in mdns['interfaces']: - if AF_INET in ifaddresses(interface).keys(): - if len(ifaddresses(interface)[AF_INET]) < 1: - raise ConfigError('mDNS repeater requires an IPv6 address configured on interface %s!'.format(interface)) - - return None - -def generate(mdns): - if mdns is None: - return None - - if mdns['disabled']: - print('Warning: mDNS repeater will be deactivated because it is disabled') - return None - - render(config_file, 'mdns-repeater/mdns-repeater.tmpl', mdns) - return None - -def apply(mdns): - if (mdns is None) or mdns['disabled']: - call('systemctl stop mdns-repeater.service') - if os.path.exists(config_file): - os.unlink(config_file) - else: - call('systemctl restart mdns-repeater.service') - - 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/service_mdns-repeater.py b/src/conf_mode/service_mdns-repeater.py new file mode 100755 index 000000000..1a6b2c328 --- /dev/null +++ b/src/conf_mode/service_mdns-repeater.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2017-2020 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 . + +import os + +from sys import exit +from netifaces import ifaddresses, interfaces, AF_INET + +from vyos.config import Config +from vyos.template import render +from vyos.util import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/etc/default/mdns-repeater' + +def get_config(): + conf = Config() + base = ['service', 'mdns', 'repeater'] + mdns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + return mdns + +def verify(mdns): + if not mdns: + return None + + if 'disable' in mdns: + return None + + # We need at least two interfaces to repeat mDNS advertisments + if 'interface' not in mdns or len(mdns['interface']) < 2: + raise ConfigError('mDNS repeater requires at least 2 configured interfaces!') + + # For mdns-repeater to work it is essential that the interfaces has + # an IPv4 address assigned + for interface in mdns['interface']: + if interface not in interfaces(): + raise ConfigError(f'Interface "{interface}" does not exist!') + + if AF_INET not in ifaddresses(interface): + raise ConfigError('mDNS repeater requires an IPv4 address to be ' + f'configured on interface "{interface}"') + + return None + +def generate(mdns): + if not mdns: + return None + + if 'disable' in mdns: + print('Warning: mDNS repeater will be deactivated because it is disabled') + return None + + render(config_file, 'mdns-repeater/mdns-repeater.tmpl', mdns) + return None + +def apply(mdns): + if not mdns or 'disable' in mdns: + call('systemctl stop mdns-repeater.service') + if os.path.exists(config_file): + os.unlink(config_file) + else: + call('systemctl restart mdns-repeater.service') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) -- cgit v1.2.3 From fe1d2377fe1169d7e13012295036935447ccfed1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 29 Jul 2020 21:28:21 +0200 Subject: wireguard: T2743: move key migration from config script to migration script Migration files on the storage should be done one time by a migration script instead of every time the configuration changes. Moving this to an older migration script is fine as this is around for a long time and all rolling releases are already up2date. It only affects updates from VyOS 1.2 series. --- src/conf_mode/interfaces-wireguard.py | 20 +------------------- src/migration-scripts/interfaces/7-to-8 | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 6325a8b05..8b64cde4d 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -28,27 +28,11 @@ from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.ifconfig import WireGuardIf -from vyos.util import chown, chmod_750 from vyos.util import check_kmod from vyos import ConfigError from vyos import airbag airbag.enable() -k_mod = 'wireguard' - -def _migrate_default_keys(): - kdir = r'/config/auth/wireguard' - if os.path.exists(f'{kdir}/private.key') and not os.path.exists(f'{kdir}/default/private.key'): - location = f'{kdir}/default' - if not os.path.exists(location): - os.makedirs(location) - - chown(location, 'root', 'vyattacfg') - chmod_750(location) - os.rename(f'{kdir}/private.key', f'{location}/private.key') - os.rename(f'{kdir}/public.key', f'{location}/public.key') - - def get_config(): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the @@ -79,7 +63,6 @@ def get_config(): return wireguard - def verify(wireguard): if 'deleted' in wireguard: verify_bridge_delete(wireguard) @@ -123,8 +106,7 @@ def apply(wireguard): if __name__ == '__main__': try: - check_kmod(k_mod) - _migrate_default_keys() + check_kmod('wireguard') c = get_config() verify(c) apply(c) diff --git a/src/migration-scripts/interfaces/7-to-8 b/src/migration-scripts/interfaces/7-to-8 index 8830ffdc7..a4051301f 100755 --- a/src/migration-scripts/interfaces/7-to-8 +++ b/src/migration-scripts/interfaces/7-to-8 @@ -17,8 +17,23 @@ # Split WireGuard endpoint into address / port nodes to make use of common # validators +import os + from sys import exit, argv from vyos.configtree import ConfigTree +from vyos.util import chown, chmod_750 + +def migrate_default_keys(): + kdir = r'/config/auth/wireguard' + if os.path.exists(f'{kdir}/private.key') and not os.path.exists(f'{kdir}/default/private.key'): + location = f'{kdir}/default' + if not os.path.exists(location): + os.makedirs(location) + + chown(location, 'root', 'vyattacfg') + chmod_750(location) + os.rename(f'{kdir}/private.key', f'{location}/private.key') + os.rename(f'{kdir}/public.key', f'{location}/public.key') if __name__ == '__main__': if (len(argv) < 1): @@ -32,6 +47,8 @@ if __name__ == '__main__': config = ConfigTree(config_file) base = ['interfaces', 'wireguard'] + migrate_default_keys() + if not config.exists(base): # Nothing to do exit(0) -- cgit v1.2.3 From f75f9cf6aae10c061e7b757b4e8a658b4457def7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 30 Jul 2020 22:54:48 +0200 Subject: router-advert: T2745: migrate to get_config_dict() --- data/templates/router-advert/radvd.conf.tmpl | 72 +++++---- interface-definitions/service_router-advert.xml.in | 11 +- src/conf_mode/service_router-advert.py | 163 ++++++--------------- 3 files changed, 98 insertions(+), 148 deletions(-) (limited to 'src') diff --git a/data/templates/router-advert/radvd.conf.tmpl b/data/templates/router-advert/radvd.conf.tmpl index 073623eac..cebfc54b5 100644 --- a/data/templates/router-advert/radvd.conf.tmpl +++ b/data/templates/router-advert/radvd.conf.tmpl @@ -1,37 +1,47 @@ ### Autogenerated by service_router-advert.py ### -{% for i in interfaces -%} -interface {{ i.name }} { +{% if interface is defined and interface is not none %} +{% for iface in interface %} +interface {{ iface }} { IgnoreIfMissing on; - AdvDefaultPreference {{ i.default_preference }}; - AdvManagedFlag {{ i.managed_flag }}; - MaxRtrAdvInterval {{ i.interval_max }}; -{% if i.interval_min %} - MinRtrAdvInterval {{ i.interval_min }}; -{% endif %} - AdvReachableTime {{ i.reachable_time }}; - AdvIntervalOpt {{ i.send_advert }}; - AdvSendAdvert {{ i.send_advert }}; -{% if i.default_lifetime %} - AdvDefaultLifetime {{ i.default_lifetime }}; -{% endif %} -{% if i.link_mtu %} - AdvLinkMTU {{ i.link_mtu }}; -{% endif %} - AdvOtherConfigFlag {{ i.other_config_flag }}; - AdvRetransTimer {{ i.retrans_timer }}; - AdvCurHopLimit {{ i.hop_limit }}; -{% for p in i.prefixes %} - prefix {{ p.prefix }} { - AdvAutonomous {{ p.autonomous_flag }}; - AdvValidLifetime {{ p.valid_lifetime }}; - AdvOnLink {{ p.on_link }}; - AdvPreferredLifetime {{ p.preferred_lifetime }}; +{% if interface[iface].default_preference is defined and interface[iface].default_preference is not none %} + AdvDefaultPreference {{ interface[iface].default_preference }}; +{% endif %} +{% if interface[iface].managed_flag is defined and interface[iface].managed_flag is not none %} + AdvManagedFlag {{ 'on' if interface[iface].managed_flag is defined else 'off' }}; +{% endif %} +{% if interface[iface].interval.max is defined and interface[iface].interval.max is not none %} + MaxRtrAdvInterval {{ interface[iface].interval.max }}; +{% endif %} +{% if interface[iface].interval.min is defined and interface[iface].interval.min is not none %} + MinRtrAdvInterval {{ interface[iface].interval.min }}; +{% endif %} +{% if interface[iface].reachable_time is defined and interface[iface].reachable_time is not none %} + AdvReachableTime {{ interface[iface].reachable_time }}; +{% endif %} + AdvIntervalOpt {{ 'off' if interface[iface].no_send_advert is defined else 'on' }}; + AdvSendAdvert {{ 'off' if interface[iface].no_send_advert is defined else 'on' }}; +{% if interface[iface].default_lifetime is defined %} + AdvDefaultLifetime {{ interface[iface].default_lifetime }}; +{% endif %} +{% if interface[iface].link_mtu is defined %} + AdvLinkMTU {{ interface[iface].link_mtu }}; +{% endif %} + AdvOtherConfigFlag {{ 'on' if interface[iface].other_config_flag is defined else 'off' }}; + AdvRetransTimer {{ interface[iface].retrans_timer }}; + AdvCurHopLimit {{ interface[iface].hop_limit }}; +{% for prefix in interface[iface].prefix %} + prefix {{ prefix }} { + AdvAutonomous {{ 'off' if interface[iface].prefix[prefix].no_autonomous_flag is defined else 'on' }}; + AdvValidLifetime {{ interface[iface].prefix[prefix].valid_lifetime }}; + AdvOnLink {{ 'off' if interface[iface].prefix[prefix].no_on_link_flag is defined else 'on' }}; + AdvPreferredLifetime {{ interface[iface].prefix[prefix].preferred_lifetime }}; }; -{% endfor %} -{% if i.name_server %} - RDNSS {{ i.name_server | join(" ") }} { +{% endfor %} +{% if interface[iface].name_server is defined %} + RDNSS {{ interface[iface].name_server | join(" ") }} { }; -{% endif %} +{% endif %} }; -{% endfor -%} +{% endfor -%} +{% endif %} diff --git a/interface-definitions/service_router-advert.xml.in b/interface-definitions/service_router-advert.xml.in index 6a4706ab7..5a472fc9a 100644 --- a/interface-definitions/service_router-advert.xml.in +++ b/interface-definitions/service_router-advert.xml.in @@ -32,6 +32,7 @@ Hop count must be between 0 and 255 + 64 @@ -69,10 +70,11 @@ Default router has high preference - (low|medium|high) + ^(low|medium|high)$ Default preference must be low, medium or high + medium @@ -116,6 +118,7 @@ Maximum interval must be between 4 and 1800 seconds + 600 @@ -191,9 +194,10 @@ - (infinity) + ^(infinity)$ + 14400 @@ -214,6 +218,7 @@ (infinity) + 2592000 @@ -233,6 +238,7 @@ Reachable time must be 0 or between 1 and 3600000 milliseconds + 0 @@ -250,6 +256,7 @@ Retransmit interval must be 0 or between 1 and 4294967295 milliseconds + 0 diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py index ef6148ebd..1b333e1a5 100755 --- a/src/conf_mode/service_router-advert.py +++ b/src/conf_mode/service_router-advert.py @@ -16,145 +16,77 @@ import os -from stat import S_IRUSR, S_IWUSR, S_IRGRP from sys import exit from vyos.config import Config -from vyos import ConfigError -from vyos.util import call +from vyos.configdict import dict_merge from vyos.template import render - +from vyos.util import call +from vyos.xml import defaults +from vyos import ConfigError from vyos import airbag airbag.enable() config_file = r'/run/radvd/radvd.conf' -default_config_data = { - 'interfaces': [] -} - def get_config(): - rtradv = default_config_data conf = Config() - base_level = ['service', 'router-advert'] - - if not conf.exists(base_level): - return rtradv - - for interface in conf.list_nodes(base_level + ['interface']): - intf = { - 'name': interface, - 'hop_limit' : '64', - 'default_lifetime': '', - 'default_preference': 'medium', - 'dnssl': [], - 'link_mtu': '', - 'managed_flag': 'off', - 'interval_max': '600', - 'interval_min': '', - 'name_server': [], - 'other_config_flag': 'off', - 'prefixes' : [], - 'reachable_time': '0', - 'retrans_timer': '0', - 'send_advert': 'on' - } - - # set config level first to reduce boilerplate code - conf.set_level(base_level + ['interface', interface]) - - if conf.exists(['hop-limit']): - intf['hop_limit'] = conf.return_value(['hop-limit']) - - if conf.exists(['default-lifetime']): - intf['default_lifetime'] = conf.return_value(['default-lifetime']) - - if conf.exists(['default-preference']): - intf['default_preference'] = conf.return_value(['default-preference']) - - if conf.exists(['dnssl']): - intf['dnssl'] = conf.return_values(['dnssl']) - - if conf.exists(['link-mtu']): - intf['link_mtu'] = conf.return_value(['link-mtu']) - - if conf.exists(['managed-flag']): - intf['managed_flag'] = 'on' - - if conf.exists(['interval', 'max']): - intf['interval_max'] = conf.return_value(['interval', 'max']) - - if conf.exists(['interval', 'min']): - intf['interval_min'] = conf.return_value(['interval', 'min']) - - if conf.exists(['name-server']): - intf['name_server'] = conf.return_values(['name-server']) - - if conf.exists(['other-config-flag']): - intf['other_config_flag'] = 'on' - - if conf.exists(['reachable-time']): - intf['reachable_time'] = conf.return_value(['reachable-time']) - - if conf.exists(['retrans-timer']): - intf['retrans_timer'] = conf.return_value(['retrans-timer']) - - if conf.exists(['no-send-advert']): - intf['send_advert'] = 'off' - - for prefix in conf.list_nodes(['prefix']): - tmp = { - 'prefix' : prefix, - 'autonomous_flag' : 'on', - 'on_link' : 'on', - 'preferred_lifetime': 14400, - 'valid_lifetime' : 2592000 - - } - - # set config level first to reduce boilerplate code - conf.set_level(base_level + ['interface', interface, 'prefix', prefix]) - - if conf.exists(['no-autonomous-flag']): - tmp['autonomous_flag'] = 'off' - - if conf.exists(['no-on-link-flag']): - tmp['on_link'] = 'off' - - if conf.exists(['preferred-lifetime']): - tmp['preferred_lifetime'] = int(conf.return_value(['preferred-lifetime'])) - - if conf.exists(['valid-lifetime']): - tmp['valid_lifetime'] = int(conf.return_value(['valid-lifetime'])) - - intf['prefixes'].append(tmp) - - rtradv['interfaces'].append(intf) + base = ['service', 'router-advert'] + rtradv = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_interface_values = defaults(base + ['interface']) + # we deal with prefix defaults later on + if 'prefix' in default_interface_values: + del default_interface_values['prefix'] + + default_prefix_values = defaults(base + ['interface', 'prefix']) + + if 'interface' in rtradv: + for interface in rtradv['interface']: + rtradv['interface'][interface] = dict_merge( + default_interface_values, rtradv['interface'][interface]) + + if 'prefix' in rtradv['interface'][interface]: + for prefix in rtradv['interface'][interface]['prefix']: + rtradv['interface'][interface]['prefix'][prefix] = dict_merge( + default_prefix_values, rtradv['interface'][interface]['prefix'][prefix]) + + if 'name_server' in rtradv['interface'][interface]: + # always use a list when dealing with nameservers - eases the template generation + if isinstance(rtradv['interface'][interface]['name_server'], str): + rtradv['interface'][interface]['name_server'] = [ + rtradv['interface'][interface]['name_server']] return rtradv def verify(rtradv): - for interface in rtradv['interfaces']: - for prefix in interface['prefixes']: - if not (prefix['valid_lifetime'] > prefix['preferred_lifetime']): - raise ConfigError('Prefix valid-lifetime must be greater then preferred-lifetime') + if not rtradv: + return None + + if 'interface' not in rtradv: + return None + + for interface in rtradv['interface']: + interface = rtradv['interface'][interface] + if 'prefix' in interface: + for prefix in interface['prefix']: + prefix = interface['prefix'][prefix] + if not (int(prefix['valid_lifetime']) > int(prefix['preferred_lifetime'])): + raise ConfigError('Prefix valid-lifetime must be greater then preferred-lifetime') return None def generate(rtradv): - if not rtradv['interfaces']: + if not rtradv: return None - render(config_file, 'router-advert/radvd.conf.tmpl', rtradv, trim_blocks=True) - - # adjust file permissions of new configuration file - if os.path.exists(config_file): - os.chmod(config_file, S_IRUSR | S_IWUSR | S_IRGRP) - + render(config_file, 'router-advert/radvd.conf.tmpl', rtradv, trim_blocks=True, permission=0o644) return None def apply(rtradv): - if not rtradv['interfaces']: + if not rtradv: # bail out early - looks like removal from running config call('systemctl stop radvd.service') if os.path.exists(config_file): @@ -163,6 +95,7 @@ def apply(rtradv): return None call('systemctl restart radvd.service') + return None if __name__ == '__main__': -- cgit v1.2.3 From 4803a3a169f83a7c6c9ba888d7a111a922e84467 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 2 Aug 2020 14:54:19 +0200 Subject: ipoe-server: T2314: fix improper use of fail-time dictionary key --- src/conf_mode/service_ipoe-server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index b539da98e..fbc1000dd 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -153,7 +153,7 @@ def get_config(): conf.set_level(base_path + ['authentication', 'radius', 'server', server]) if conf.exists(['fail-time']): - radius['fail-time'] = conf.return_value(['fail-time']) + radius['fail_time'] = conf.return_value(['fail-time']) if conf.exists(['port']): radius['port'] = conf.return_value(['port']) -- cgit v1.2.3 From ea10917897bb4478e7f6f576f2e1c42b134cc5c0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 2 Aug 2020 14:58:42 +0200 Subject: accel-ppp: T2756: make RADIUS accounting port configurable Make the port used for RADIUS accounting user configurable. This is now valid for the following services which are based on Accel-PPP: * ipoe-server * pppoe-server * l2tp * pptp * sstp --- data/templates/accel-ppp/ipoe.config.tmpl | 2 +- data/templates/accel-ppp/l2tp.config.tmpl | 2 +- data/templates/accel-ppp/pppoe.config.tmpl | 2 +- data/templates/accel-ppp/pptp.config.tmpl | 2 +- data/templates/accel-ppp/sstp.config.tmpl | 2 +- interface-definitions/include/accel-radius-additions.xml.in | 12 ++++++++++++ src/conf_mode/service_ipoe-server.py | 6 +++++- src/conf_mode/service_pppoe-server.py | 6 +++++- src/conf_mode/vpn_l2tp.py | 6 +++++- src/conf_mode/vpn_pptp.py | 6 +++++- src/conf_mode/vpn_sstp.py | 6 +++++- 11 files changed, 42 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/data/templates/accel-ppp/ipoe.config.tmpl b/data/templates/accel-ppp/ipoe.config.tmpl index 84de5bf51..fca520efa 100644 --- a/data/templates/accel-ppp/ipoe.config.tmpl +++ b/data/templates/accel-ppp/ipoe.config.tmpl @@ -77,7 +77,7 @@ chap-secrets={{ chap_secrets_file }} [radius] verbose=1 {% for r in radius_server %} -server={{ r.server }},{{ r.key }},auth-port={{ r.port }},req-limit=0,fail-time={{ r.fail_time }} +server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }} {% endfor -%} acct-timeout={{ radius_acct_tmo }} diff --git a/data/templates/accel-ppp/l2tp.config.tmpl b/data/templates/accel-ppp/l2tp.config.tmpl index b0ef17525..b9131684d 100644 --- a/data/templates/accel-ppp/l2tp.config.tmpl +++ b/data/templates/accel-ppp/l2tp.config.tmpl @@ -83,7 +83,7 @@ chap-secrets={{ chap_secrets_file }} [radius] verbose=1 {% for r in radius_server %} -server={{ r.server }},{{ r.key }},auth-port={{ r.port }},req-limit=0,fail-time={{ r.fail_time }} +server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }} {% endfor -%} acct-timeout={{ radius_acct_tmo }} diff --git a/data/templates/accel-ppp/pppoe.config.tmpl b/data/templates/accel-ppp/pppoe.config.tmpl index 370ca7946..5ad628fde 100644 --- a/data/templates/accel-ppp/pppoe.config.tmpl +++ b/data/templates/accel-ppp/pppoe.config.tmpl @@ -93,7 +93,7 @@ chap-secrets={{ chap_secrets_file }} [radius] verbose=1 {% for r in radius_server %} -server={{ r.server }},{{ r.key }},auth-port={{ r.port }},req-limit=0,fail-time={{ r.fail_time }} +server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }} {% endfor -%} acct-timeout={{ radius_acct_tmo }} diff --git a/data/templates/accel-ppp/pptp.config.tmpl b/data/templates/accel-ppp/pptp.config.tmpl index 0bbfc13c5..e0f2c6da9 100644 --- a/data/templates/accel-ppp/pptp.config.tmpl +++ b/data/templates/accel-ppp/pptp.config.tmpl @@ -66,7 +66,7 @@ chap-secrets={{ chap_secrets_file }} [radius] verbose=1 {% for r in radius_server %} -server={{ r.server }},{{ r.key }},auth-port={{ r.port }},req-limit=0,fail-time={{ r.fail_time }} +server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }} {% endfor -%} acct-timeout={{ radius_acct_tmo }} diff --git a/data/templates/accel-ppp/sstp.config.tmpl b/data/templates/accel-ppp/sstp.config.tmpl index 2c8c00023..c9e4a1d7d 100644 --- a/data/templates/accel-ppp/sstp.config.tmpl +++ b/data/templates/accel-ppp/sstp.config.tmpl @@ -69,7 +69,7 @@ chap-secrets={{ chap_secrets_file }} [radius] verbose=1 {% for r in radius_server %} -server={{ r.server }},{{ r.key }},auth-port={{ r.port }},req-limit=0,fail-time={{ r.fail_time }} +server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }} {% endfor -%} acct-timeout={{ radius_acct_tmo }} diff --git a/interface-definitions/include/accel-radius-additions.xml.in b/interface-definitions/include/accel-radius-additions.xml.in index 227a043cd..e37b68514 100644 --- a/interface-definitions/include/accel-radius-additions.xml.in +++ b/interface-definitions/include/accel-radius-additions.xml.in @@ -2,6 +2,18 @@ + + + Accounting port + + 1-65535 + Numeric IP port (default: 1813) + + + + + + Mark server unavailable for <n> seconds on failure diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index fbc1000dd..553cc2e97 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -147,7 +147,8 @@ def get_config(): 'server' : server, 'key' : '', 'fail_time' : 0, - 'port' : '1812' + 'port' : '1812', + 'acct_port' : '1813' } conf.set_level(base_path + ['authentication', 'radius', 'server', server]) @@ -158,6 +159,9 @@ def get_config(): if conf.exists(['port']): radius['port'] = conf.return_value(['port']) + if conf.exists(['acct-port']): + radius['acct_port'] = conf.return_value(['acct-port']) + if conf.exists(['key']): radius['key'] = conf.return_value(['key']) diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index 3149bbb2f..a8357f653 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -242,7 +242,8 @@ def get_config(): 'server' : server, 'key' : '', 'fail_time' : 0, - 'port' : '1812' + 'port' : '1812', + 'acct_port' : '1813' } conf.set_level(base_path + ['authentication', 'radius', 'server', server]) @@ -253,6 +254,9 @@ def get_config(): if conf.exists(['port']): radius['port'] = conf.return_value(['port']) + if conf.exists(['acct-port']): + radius['acct_port'] = conf.return_value(['acct-port']) + if conf.exists(['key']): radius['key'] = conf.return_value(['key']) diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py index 88df2902e..26ad1af84 100755 --- a/src/conf_mode/vpn_l2tp.py +++ b/src/conf_mode/vpn_l2tp.py @@ -151,7 +151,8 @@ def get_config(): 'server' : server, 'key' : '', 'fail_time' : 0, - 'port' : '1812' + 'port' : '1812', + 'acct_port' : '1813' } conf.set_level(base_path + ['authentication', 'radius', 'server', server]) @@ -162,6 +163,9 @@ def get_config(): if conf.exists(['port']): radius['port'] = conf.return_value(['port']) + if conf.exists(['acct-port']): + radius['acct_port'] = conf.return_value(['acct-port']) + if conf.exists(['key']): radius['key'] = conf.return_value(['key']) diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py index 4536692d2..32cbadd74 100755 --- a/src/conf_mode/vpn_pptp.py +++ b/src/conf_mode/vpn_pptp.py @@ -111,7 +111,8 @@ def get_config(): 'server' : server, 'key' : '', 'fail_time' : 0, - 'port' : '1812' + 'port' : '1812', + 'acct_port' : '1813' } conf.set_level(base_path + ['authentication', 'radius', 'server', server]) @@ -122,6 +123,9 @@ def get_config(): if conf.exists(['port']): radius['port'] = conf.return_value(['port']) + if conf.exists(['acct-port']): + radius['acct_port'] = conf.return_value(['acct-port']) + if conf.exists(['key']): radius['key'] = conf.return_value(['key']) diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py index 4c4d8e403..ddb499bf4 100755 --- a/src/conf_mode/vpn_sstp.py +++ b/src/conf_mode/vpn_sstp.py @@ -118,7 +118,8 @@ def get_config(): 'server' : server, 'key' : '', 'fail_time' : 0, - 'port' : '1812' + 'port' : '1812', + 'acct_port' : '1813' } conf.set_level(base_path + ['authentication', 'radius', 'server', server]) @@ -129,6 +130,9 @@ def get_config(): if conf.exists(['port']): radius['port'] = conf.return_value(['port']) + if conf.exists(['acct-port']): + radius['acct_port'] = conf.return_value(['acct-port']) + if conf.exists(['key']): radius['key'] = conf.return_value(['key']) -- cgit v1.2.3 From 84331764a81d7e31c5c4dd5466f347054283d377 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 12:56:13 +0200 Subject: ifconfig: T2740: pass config dict to DHCP class for IPv6 This removes additional code paths as we can instatly work with the input dict the same was as it was done for PPPoE. This fixes the entire DHCPv6-PD support on non PPPoE interfaces as this was lost in translation while processing T2653. --- data/templates/dhcp-client/ipv6.tmpl | 53 +++++++++++++++++--------------- data/templates/dhcp-client/ipv6_new.tmpl | 47 ---------------------------- python/vyos/ifconfig/dhcp.py | 36 +++++++++------------- python/vyos/ifconfig/interface.py | 49 ++++++++++++----------------- src/conf_mode/interfaces-pppoe.py | 2 +- 5 files changed, 63 insertions(+), 124 deletions(-) delete mode 100644 data/templates/dhcp-client/ipv6_new.tmpl (limited to 'src') diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.tmpl index 490f14726..112431c5f 100644 --- a/data/templates/dhcp-client/ipv6.tmpl +++ b/data/templates/dhcp-client/ipv6.tmpl @@ -4,41 +4,44 @@ interface {{ ifname }} { request domain-name-servers; request domain-name; -{% if dhcpv6_prm_only %} +{% if dhcpv6_options is defined %} +{% if dhcpv6_options.parameters_only is defined %} information-only; -{% endif %} -{% if not dhcpv6_temporary %} +{% endif %} +{% if dhcpv6_options.temporary is not defined %} send ia-na 1; # non-temporary address -{% endif %} -{% if dhcpv6_pd_interfaces %} +{% endif %} +{% if dhcpv6_options.prefix_delegation is defined %} send ia-pd 2; # prefix delegation +{% endif %} {% endif %} }; -{% if not dhcpv6_temporary %} +{% if dhcpv6_options is defined %} +{% if dhcpv6_options.temporary is not defined %} id-assoc na 1 { # Identity association NA }; -{% endif %} +{% endif %} -{% if dhcpv6_pd_interfaces %} +{% if dhcpv6_options.prefix_delegation is defined %} id-assoc pd 2 { -{% if dhcpv6_pd_length %} - prefix ::/{{ dhcpv6_pd_length }} infinity; -{% endif %} -{% for intf in dhcpv6_pd_interfaces %} - prefix-interface {{ intf.ifname }} { -{% if intf.sla_id %} - sla-id {{ intf.sla_id }}; -{% endif %} -{% if intf.sla_len %} - sla-len {{ intf.sla_len }}; -{% endif %} -{% if intf.if_id %} - ifid {{ intf.if_id }}; -{% endif %} +{% if dhcpv6_options.prefix_delegation.length is defined %} + prefix ::/{{ dhcpv6_options.prefix_delegation.length }} infinity; +{% endif %} +{% for interface in dhcpv6_options.prefix_delegation.interface %} + prefix-interface {{ interface }} { +{% if dhcpv6_options.prefix_delegation.interface[interface].sla_id is defined %} + sla-id {{ dhcpv6_options.prefix_delegation.interface[interface].sla_id }}; +{% endif %} +{% if dhcpv6_options.prefix_delegation.interface[interface].sla_len is defined %} + sla-len {{ dhcpv6_options.prefix_delegation.interface[interface].sla_len }}; +{% endif %} +{% if dhcpv6_options.prefix_delegation.interface[interface].address is defined %} + ifid {{ dhcpv6_options.prefix_delegation.interface[interface].address }}; +{% endif %} }; -{% endfor %} +{% endfor %} }; -{% endif %} - +{% endif %} +{% endif %} diff --git a/data/templates/dhcp-client/ipv6_new.tmpl b/data/templates/dhcp-client/ipv6_new.tmpl deleted file mode 100644 index 112431c5f..000000000 --- a/data/templates/dhcp-client/ipv6_new.tmpl +++ /dev/null @@ -1,47 +0,0 @@ -# generated by dhcp.py -# man https://www.unix.com/man-page/debian/5/dhcp6c.conf/ - -interface {{ ifname }} { - request domain-name-servers; - request domain-name; -{% if dhcpv6_options is defined %} -{% if dhcpv6_options.parameters_only is defined %} - information-only; -{% endif %} -{% if dhcpv6_options.temporary is not defined %} - send ia-na 1; # non-temporary address -{% endif %} -{% if dhcpv6_options.prefix_delegation is defined %} - send ia-pd 2; # prefix delegation -{% endif %} -{% endif %} -}; - -{% if dhcpv6_options is defined %} -{% if dhcpv6_options.temporary is not defined %} -id-assoc na 1 { - # Identity association NA -}; -{% endif %} - -{% if dhcpv6_options.prefix_delegation is defined %} -id-assoc pd 2 { -{% if dhcpv6_options.prefix_delegation.length is defined %} - prefix ::/{{ dhcpv6_options.prefix_delegation.length }} infinity; -{% endif %} -{% for interface in dhcpv6_options.prefix_delegation.interface %} - prefix-interface {{ interface }} { -{% if dhcpv6_options.prefix_delegation.interface[interface].sla_id is defined %} - sla-id {{ dhcpv6_options.prefix_delegation.interface[interface].sla_id }}; -{% endif %} -{% if dhcpv6_options.prefix_delegation.interface[interface].sla_len is defined %} - sla-len {{ dhcpv6_options.prefix_delegation.interface[interface].sla_len }}; -{% endif %} -{% if dhcpv6_options.prefix_delegation.interface[interface].address is defined %} - ifid {{ dhcpv6_options.prefix_delegation.interface[interface].address }}; -{% endif %} - }; -{% endfor %} -}; -{% endif %} -{% endif %} diff --git a/python/vyos/ifconfig/dhcp.py b/python/vyos/ifconfig/dhcp.py index a8b9a2a87..bd37970a2 100644 --- a/python/vyos/ifconfig/dhcp.py +++ b/python/vyos/ifconfig/dhcp.py @@ -15,6 +15,7 @@ import os +from vyos.configverify import verify_dhcpv6 from vyos.dicts import FixedDict from vyos.ifconfig.control import Control from vyos.template import render @@ -82,39 +83,31 @@ class _DHCPv4 (Control): class _DHCPv6 (Control): def __init__(self, ifname): super().__init__() - self.options = FixedDict(**{ - 'ifname': ifname, - 'dhcpv6_prm_only': False, - 'dhcpv6_temporary': False, - 'dhcpv6_pd_interfaces': [], - 'dhcpv6_pd_length': '' - }) - self._conf_file = f'/run/dhcp6c/dhcp6c.{ifname}.conf' + self.options = {'ifname' : ifname} + self._config = f'/run/dhcp6c/dhcp6c.{ifname}.conf' def set(self): """ - Configure interface as DHCPv6 client. The dhclient binary is automatically - started in background! + 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! - if self.options['dhcpv6_prm_only'] and self.options['dhcpv6_temporary']: - raise Exception( - 'DHCPv6 temporary and parameters-only options are mutually exclusive!') + # better save then sorry .. should be checked in interface script but if you + # missed it we are safe! + verify_dhcpv6(self.options) - render(self._conf_file, 'dhcp-client/ipv6.tmpl', self.options, trim_blocks=True) - return self._cmd('systemctl restart dhcp6c@{ifname}.service'.format(**self.options)) + render(self._config, 'dhcp-client/ipv6.tmpl', self.options, trim_blocks=True) + return self._cmd('systemctl restart dhcp6c@{ifname}.service'.format( + **self.options)) def delete(self): """ - De-configure interface as DHCPv6 clinet. All auto generated files like + De-configure interface as DHCPv6 client. All auto generated files like pid, config and lease will be removed. Example: @@ -126,9 +119,8 @@ class _DHCPv6 (Control): self._cmd('systemctl stop dhcp6c@{ifname}.service'.format(**self.options)) # cleanup old config files - if os.path.isfile(self._conf_file): - os.remove(self._conf_file) - + if os.path.isfile(self._config): + os.remove(self._config) class DHCP(object): def __init__(self, ifname): diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index d477153e8..f5e43e172 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -822,6 +822,22 @@ 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: + dhcp_options = config.get('dhcp_options') + if 'client_id' in dhcp_options: + self.dhcp.v4.options['client_id'] = dhcp_options.get('client_id') + + if 'host_name' in dhcp_options: + self.dhcp.v4.options['hostname'] = dhcp_options.get('host_name') + + if 'vendor_class_id' in dhcp_options: + self.dhcp.v4.options['vendor_class_id'] = dhcp_options.get('vendor_class_id') + + # 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', []) @@ -849,35 +865,6 @@ class Interface(Control): # checked before self.set_vrf(config.get('vrf', '')) - # DHCP options - if 'dhcp_options' in config: - dhcp_options = config.get('dhcp_options') - if 'client_id' in dhcp_options: - self.dhcp.v4.options['client_id'] = dhcp_options.get('client_id') - - if 'host_name' in dhcp_options: - self.dhcp.v4.options['hostname'] = dhcp_options.get('host_name') - - if 'vendor_class_id' in dhcp_options: - self.dhcp.v4.options['vendor_class_id'] = dhcp_options.get('vendor_class_id') - - # DHCPv6 options - if 'dhcpv6_options' in config: - dhcpv6_options = config.get('dhcpv6_options') - if 'parameters_only' in dhcpv6_options: - self.dhcp.v6.options['dhcpv6_prm_only'] = True - - if 'temporary' in dhcpv6_options: - self.dhcp.v6.options['dhcpv6_temporary'] = True - - if 'prefix_delegation' in dhcpv6_options: - prefix_delegation = dhcpv6_options.get('prefix_delegation') - if 'length' in prefix_delegation: - self.dhcp.v6.options['dhcpv6_pd_length'] = prefix_delegation.get('length') - - if 'interface' in prefix_delegation: - self.dhcp.v6.options['dhcpv6_pd_interfaces'] = prefix_delegation.get('interface') - # Configure ARP cache timeout in milliseconds - has default value tmp = jmespath.search('ip.arp_cache_timeout', config) value = tmp if (tmp != None) else '30' @@ -982,9 +969,11 @@ class Interface(Control): self.del_vlan(vif_s_id) # create/update 802.1ad (Q-in-Q VLANs) + ifname = config['ifname'] for vif_s_id, vif_s in config.get('vif_s', {}).items(): tmp=get_ethertype(vif_s.get('ethertype', '0x88A8')) s_vlan = self.add_vlan(vif_s_id, ethertype=tmp) + vif_s['ifname'] = f'{ifname}.{vif_s_id}' s_vlan.update(vif_s) # remove no longer required client VLAN (vif-c) @@ -994,6 +983,7 @@ class Interface(Control): # create/update client VLAN (vif-c) interface for vif_c_id, vif_c in vif_s.get('vif_c', {}).items(): c_vlan = s_vlan.add_vlan(vif_c_id) + vif_c['ifname'] = f'{ifname}.{vif_s_id}.{vif_c_id}' c_vlan.update(vif_c) # remove no longer required 802.1q VLAN interfaces @@ -1003,4 +993,5 @@ class Interface(Control): # create/update 802.1q VLAN interfaces for vif_id, vif in config.get('vif', {}).items(): vlan = self.add_vlan(vif_id) + vif['ifname'] = f'{ifname}.{vif_id}' vlan.update(vif) diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index b9a88a949..928113b49 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -108,7 +108,7 @@ def generate(pppoe): if tmp and len(tmp) > 0: # ipv6.tmpl relies on ifname - this should be made consitent in the # future better then double key-ing the same value - render(config_wide_dhcp6c, 'dhcp-client/ipv6_new.tmpl', pppoe, trim_blocks=True) + render(config_wide_dhcp6c, 'dhcp-client/ipv6.tmpl', pppoe, trim_blocks=True) return None -- cgit v1.2.3 From 787879baa0516ade8d9ad91f19e970c510d7aff3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 14:05:12 +0200 Subject: configure: op-mode: move XML here --- op-mode-definitions/configure.xml | 9 +++++++++ src/op_mode/enter_config_mode.sh | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 op-mode-definitions/configure.xml create mode 100755 src/op_mode/enter_config_mode.sh (limited to 'src') diff --git a/op-mode-definitions/configure.xml b/op-mode-definitions/configure.xml new file mode 100644 index 000000000..dc534d586 --- /dev/null +++ b/op-mode-definitions/configure.xml @@ -0,0 +1,9 @@ + + + + + Enter configure mode + + ${vyos_op_scripts_dir}/enter_config_mode.sh + + diff --git a/src/op_mode/enter_config_mode.sh b/src/op_mode/enter_config_mode.sh new file mode 100755 index 000000000..082800ce4 --- /dev/null +++ b/src/op_mode/enter_config_mode.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +if [ `id -u` == 0 ]; then + echo "You are attempting to enter configuration mode as root." + echo "It may have unintended consequences and render your system" + echo "unusable until restart." + echo "Please do it as an administrator level VyOS user instead." +else + if grep -q -e '^overlay.*/filesystem.squashfs' /proc/mounts; then + echo "WARNING: You are currently configuring a live-ISO environment, changes will not persist until installed" + fi + history -w + export _OFR_CONFIGURE=ok + newgrp vyattacfg + unset _OFR_CONFIGURE + _vyatta_op_do_key_bindings + history -r +fi -- cgit v1.2.3 From ee45fcf5f5342d40b5dcd39429a4201c93a49b4c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 17:08:47 +0200 Subject: lldp: op-mode: T2323: "show lldp neighbors detail" only works when service runs The problem exists when LLDP is not configured but one wants to run the detail command. Running "show lldp neighbors" is not possible when LLDP is not running. This case is already handled for "show lldp neighbors" and has been extended for "show lldp neighbors detail". --- op-mode-definitions/lldp.xml | 2 +- src/op_mode/lldp_op.py | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/op-mode-definitions/lldp.xml b/op-mode-definitions/lldp.xml index 105bfe237..e954fb8cf 100644 --- a/op-mode-definitions/lldp.xml +++ b/op-mode-definitions/lldp.xml @@ -17,7 +17,7 @@ Show LLDP neighbor details - /usr/sbin/lldpctl -f plain + ${vyos_op_scripts_dir}/lldp_op.py --detail diff --git a/src/op_mode/lldp_op.py b/src/op_mode/lldp_op.py index 5d48e3210..0df6749aa 100755 --- a/src/op_mode/lldp_op.py +++ b/src/op_mode/lldp_op.py @@ -14,19 +14,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - import argparse import jinja2 -from xml.dom import minidom from sys import exit from tabulate import tabulate +from xml.dom import minidom -from vyos.util import popen +from vyos.util import cmd from vyos.config import Config parser = argparse.ArgumentParser() parser.add_argument("-a", "--all", action="store_true", help="Show LLDP neighbors on all interfaces") +parser.add_argument("-d", "--detail", action="store_true", help="Show detailes LLDP neighbor information on all interfaces") parser.add_argument("-i", "--interface", action="store", help="Show LLDP neighbors on specific interface") # Please be careful if you edit the template. @@ -40,10 +40,8 @@ Device ID Local Proto Cap Platform Port ID {% endfor -%} """ -def _get_neighbors(): - command = '/usr/sbin/lldpcli -f xml show neighbors' - out,_ = popen(command) - return out +def get_neighbors(): + return cmd('/usr/sbin/lldpcli -f xml show neighbors') def extract_neighbor(neighbor): """ @@ -148,12 +146,17 @@ if __name__ == '__main__': exit(0) if args.all: - neighbors = minidom.parseString(_get_neighbors()) + neighbors = minidom.parseString(get_neighbors()) for neighbor in neighbors.getElementsByTagName('interface'): tmp['neighbors'].append( extract_neighbor(neighbor) ) + elif args.detail: + out = cmd('/usr/sbin/lldpctl -f plain') + print(out) + exit(0) + elif args.interface: - neighbors = minidom.parseString(_get_neighbors()) + neighbors = minidom.parseString(get_neighbors()) for neighbor in neighbors.getElementsByTagName('interface'): # check if neighbor appeared on proper interface if neighbor.getAttribute('name') == args.interface: -- cgit v1.2.3 From ca2ab503f42a8446175954e9e7280ecc8e75e927 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 18:38:55 +0200 Subject: ssh: T1076: make configuration volatile Move sshd_config file to /run so it must be generated on every boot and is not stored accidently. --- data/templates/ssh/override.conf.tmpl | 3 ++- src/conf_mode/ssh.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/data/templates/ssh/override.conf.tmpl b/data/templates/ssh/override.conf.tmpl index 4276366ae..843aa927b 100644 --- a/data/templates/ssh/override.conf.tmpl +++ b/data/templates/ssh/override.conf.tmpl @@ -2,9 +2,10 @@ [Unit] StartLimitIntervalSec=0 After=vyos-router.service +ConditionPathExists={{config_file}} [Service] ExecStart= -ExecStart={{vrf_command}}/usr/sbin/sshd -D $SSHD_OPTS +ExecStart={{vrf_command}}/usr/sbin/sshd -f {{config_file}} -D $SSHD_OPTS RestartSec=10 diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index ffb0b700d..7b262565a 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -28,7 +28,7 @@ from vyos.xml import defaults from vyos import airbag airbag.enable() -config_file = r'/etc/ssh/sshd_config' +config_file = r'/run/ssh/sshd_config' systemd_override = r'/etc/systemd/system/ssh.service.d/override.conf' def get_config(): @@ -42,6 +42,8 @@ def get_config(): # options which we need to update into the dictionary retrived. default_values = defaults(base) ssh = dict_merge(default_values, ssh) + # pass config file path - used in override template + ssh['config_file'] = config_file return ssh -- cgit v1.2.3 From 9e6cd018464fcf03a6930116ff5570f68a7c6c45 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 21:18:25 +0200 Subject: op-mode: T2546: use inline shell code for "configure" Commit 787879ba ("configure: op-mode: move XML here") migrated the op-mode command "configure" to vyos-1x. The used shell snipped in turn sometimes complained about: "_vyatta_op_do_key_bindings: command not found". This is now resolved by moving the code entirely into the node.def file. --- op-mode-definitions/configure.xml | 19 +++++++++++++++++-- src/op_mode/enter_config_mode.sh | 18 ------------------ 2 files changed, 17 insertions(+), 20 deletions(-) delete mode 100755 src/op_mode/enter_config_mode.sh (limited to 'src') diff --git a/op-mode-definitions/configure.xml b/op-mode-definitions/configure.xml index dc534d586..3dd5a0f45 100644 --- a/op-mode-definitions/configure.xml +++ b/op-mode-definitions/configure.xml @@ -2,8 +2,23 @@ - Enter configure mode + Enter configuration mode - ${vyos_op_scripts_dir}/enter_config_mode.sh + if [ `id -u` == 0 ]; then + echo "You are attempting to enter configuration mode as root." + echo "It may have unintended consequences and render your system" + echo "unusable until restart." + echo "Please do it as an administrator level VyOS user instead." + else + if grep -q -e '^overlay.*/filesystem.squashfs' /proc/mounts; then + echo "WARNING: You are currently configuring a live-ISO environment, changes will not persist until installed" + fi + history -w + export _OFR_CONFIGURE=ok + newgrp vyattacfg + unset _OFR_CONFIGURE + _vyatta_op_do_key_bindings + history -r + fi diff --git a/src/op_mode/enter_config_mode.sh b/src/op_mode/enter_config_mode.sh deleted file mode 100755 index 082800ce4..000000000 --- a/src/op_mode/enter_config_mode.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -if [ `id -u` == 0 ]; then - echo "You are attempting to enter configuration mode as root." - echo "It may have unintended consequences and render your system" - echo "unusable until restart." - echo "Please do it as an administrator level VyOS user instead." -else - if grep -q -e '^overlay.*/filesystem.squashfs' /proc/mounts; then - echo "WARNING: You are currently configuring a live-ISO environment, changes will not persist until installed" - fi - history -w - export _OFR_CONFIGURE=ok - newgrp vyattacfg - unset _OFR_CONFIGURE - _vyatta_op_do_key_bindings - history -r -fi -- cgit v1.2.3 From 4526ceadf4a9a81b51ec394970188daccd69539b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 4 Aug 2020 08:34:52 +0200 Subject: cleanup: convert multiple files from DOS to UNIX line ending --- data/templates/frr/bgp.frr.tmpl | 2 +- data/templates/system/curlrc.tmpl | 16 +++++----- .../include/bgp-afi-aggregate-address.xml.i | 24 +++++++-------- .../systemd/system/radvd.service.d/override.conf | 34 +++++++++++----------- src/systemd/wpa_supplicant-macsec@.service | 34 +++++++++++----------- 5 files changed, 55 insertions(+), 55 deletions(-) (limited to 'src') diff --git a/data/templates/frr/bgp.frr.tmpl b/data/templates/frr/bgp.frr.tmpl index cd6f31c93..cdf4cb4fe 100644 --- a/data/templates/frr/bgp.frr.tmpl +++ b/data/templates/frr/bgp.frr.tmpl @@ -1 +1 @@ -! +! diff --git a/data/templates/system/curlrc.tmpl b/data/templates/system/curlrc.tmpl index 675e35a0c..3e5ce801c 100644 --- a/data/templates/system/curlrc.tmpl +++ b/data/templates/system/curlrc.tmpl @@ -1,8 +1,8 @@ -{% if http_client is defined %} -{% if http_client.source_interface is defined %} ---interface "{{ http_client.source_interface }}" -{% endif %} -{% if http_client.source_address is defined %} ---interface "{{ http_client.source_address }}" -{% endif %} -{% endif %} +{% if http_client is defined %} +{% if http_client.source_interface is defined %} +--interface "{{ http_client.source_interface }}" +{% endif %} +{% if http_client.source_address is defined %} +--interface "{{ http_client.source_address }}" +{% endif %} +{% endif %} diff --git a/interface-definitions/include/bgp-afi-aggregate-address.xml.i b/interface-definitions/include/bgp-afi-aggregate-address.xml.i index 40c030fc1..050ee0074 100644 --- a/interface-definitions/include/bgp-afi-aggregate-address.xml.i +++ b/interface-definitions/include/bgp-afi-aggregate-address.xml.i @@ -1,12 +1,12 @@ - - - Generate AS-set path information for this aggregate address - - - - - - Announce the aggregate summary network only - - - + + + Generate AS-set path information for this aggregate address + + + + + + Announce the aggregate summary network only + + + diff --git a/src/etc/systemd/system/radvd.service.d/override.conf b/src/etc/systemd/system/radvd.service.d/override.conf index 44c4345e1..c2f640cf5 100644 --- a/src/etc/systemd/system/radvd.service.d/override.conf +++ b/src/etc/systemd/system/radvd.service.d/override.conf @@ -1,17 +1,17 @@ -[Unit] -ConditionPathExists=/run/radvd/radvd.conf -After= -After=vyos-router.service - -[Service] -WorkingDirectory= -WorkingDirectory=/run/radvd -ExecStartPre= -ExecStartPre=/usr/sbin/radvd --logmethod stderr_clean --configtest --config /run/radvd/radvd.conf -ExecStart= -ExecStart=/usr/sbin/radvd --logmethod stderr_clean --config /run/radvd/radvd.conf --pidfile /run/radvd/radvd.pid -ExecReload= -ExecReload=/usr/sbin/radvd --logmethod stderr_clean --configtest --config /run/radvd/radvd.conf -ExecReload=/bin/kill -HUP $MAINPID -PIDFile= -PIDFile=/run/radvd/radvd.pid +[Unit] +ConditionPathExists=/run/radvd/radvd.conf +After= +After=vyos-router.service + +[Service] +WorkingDirectory= +WorkingDirectory=/run/radvd +ExecStartPre= +ExecStartPre=/usr/sbin/radvd --logmethod stderr_clean --configtest --config /run/radvd/radvd.conf +ExecStart= +ExecStart=/usr/sbin/radvd --logmethod stderr_clean --config /run/radvd/radvd.conf --pidfile /run/radvd/radvd.pid +ExecReload= +ExecReload=/usr/sbin/radvd --logmethod stderr_clean --configtest --config /run/radvd/radvd.conf +ExecReload=/bin/kill -HUP $MAINPID +PIDFile= +PIDFile=/run/radvd/radvd.pid diff --git a/src/systemd/wpa_supplicant-macsec@.service b/src/systemd/wpa_supplicant-macsec@.service index 21e189e4b..7e0bee8e1 100644 --- a/src/systemd/wpa_supplicant-macsec@.service +++ b/src/systemd/wpa_supplicant-macsec@.service @@ -1,17 +1,17 @@ -[Unit] -Description=WPA supplicant daemon (macsec-specific version) -Requires=sys-subsystem-net-devices-%i.device -ConditionPathExists=/run/wpa_supplicant/%I.conf -After=vyos-router.service -RequiresMountsFor=/run - -# NetworkManager users will probably want the dbus version instead. - -[Service] -Type=simple -WorkingDirectory=/run/wpa_supplicant -PIDFile=/run/wpa_supplicant/%I.pid -ExecStart=/sbin/wpa_supplicant -c/run/wpa_supplicant/%I.conf -Dmacsec_linux -i%I - -[Install] -WantedBy=multi-user.target +[Unit] +Description=WPA supplicant daemon (macsec-specific version) +Requires=sys-subsystem-net-devices-%i.device +ConditionPathExists=/run/wpa_supplicant/%I.conf +After=vyos-router.service +RequiresMountsFor=/run + +# NetworkManager users will probably want the dbus version instead. + +[Service] +Type=simple +WorkingDirectory=/run/wpa_supplicant +PIDFile=/run/wpa_supplicant/%I.pid +ExecStart=/sbin/wpa_supplicant -c/run/wpa_supplicant/%I.conf -Dmacsec_linux -i%I + +[Install] +WantedBy=multi-user.target -- cgit v1.2.3 From 3658058c6299a107dd5ce5973a9ec5e4be7064b0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 4 Aug 2020 10:12:21 +0200 Subject: router-advert: T2758: bugfix 'infinity' is not a valid integer number Problem was introduced in commit 740ace13 ("router-advert: T2609: add missing verify() for prefix lifetime"). The 'infinity' literal was not converted to its corresponding integer 0xffffffff (4294967295) for the check. --- src/conf_mode/service_router-advert.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py index 1b333e1a5..4e1c432ab 100755 --- a/src/conf_mode/service_router-advert.py +++ b/src/conf_mode/service_router-advert.py @@ -73,7 +73,15 @@ def verify(rtradv): if 'prefix' in interface: for prefix in interface['prefix']: prefix = interface['prefix'][prefix] - if not (int(prefix['valid_lifetime']) > int(prefix['preferred_lifetime'])): + valid_lifetime = prefix['valid_lifetime'] + if valid_lifetime == 'infinity': + valid_lifetime = 4294967295 + + preferred_lifetime = prefix['preferred_lifetime'] + if preferred_lifetime == 'infinity': + preferred_lifetime = 4294967295 + + if not (int(valid_lifetime) > int(preferred_lifetime)): raise ConfigError('Prefix valid-lifetime must be greater then preferred-lifetime') return None -- cgit v1.2.3 From 846e306700af191d22dc874992bbf5f04d2799c4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 4 Aug 2020 22:33:18 +0200 Subject: ssh: T2651: add cli options for source address When running SSH from the VyOS system the source IP address can be set by: set system options ssh-client source-address x.x.x.x --- data/templates/system/ssh_config.tmpl | 3 +++ interface-definitions/system-options.xml.in | 10 +++++++++- src/conf_mode/system-options.py | 31 ++++++++++++++++++++++++----- 3 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 data/templates/system/ssh_config.tmpl (limited to 'src') diff --git a/data/templates/system/ssh_config.tmpl b/data/templates/system/ssh_config.tmpl new file mode 100644 index 000000000..509bd5479 --- /dev/null +++ b/data/templates/system/ssh_config.tmpl @@ -0,0 +1,3 @@ +{% if ssh_client is defined and ssh_client.source_address is defined and ssh_client.source_address is not none %} +BindAddress {{ ssh_client.source_address }} +{% endif %} diff --git a/interface-definitions/system-options.xml.in b/interface-definitions/system-options.xml.in index 194773329..a5fec10db 100644 --- a/interface-definitions/system-options.xml.in +++ b/interface-definitions/system-options.xml.in @@ -46,13 +46,21 @@ - Global options used for HTTP based commands + Global options used for HTTP client #include #include + + + Global options used for SSH client + + + #include + + diff --git a/src/conf_mode/system-options.py b/src/conf_mode/system-options.py index d7c5c0443..0aacd19d8 100755 --- a/src/conf_mode/system-options.py +++ b/src/conf_mode/system-options.py @@ -22,11 +22,13 @@ from sys import exit from vyos.config import Config from vyos.template import render from vyos.util import call +from vyos.validate import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() -config_file = r'/etc/curlrc' +curlrc_config = r'/etc/curlrc' +ssh_config = r'/etc/ssh/ssh_config' systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target' def get_config(): @@ -36,9 +38,9 @@ def get_config(): return options def verify(options): - if 'http_client' in options.keys(): + if 'http_client' in options: config = options['http_client'] - if 'source_interface' in config.keys(): + if 'source_interface' in config: if not config['source_interface'] in interfaces(): raise ConfigError(f'Source interface {source_interface} does not ' f'exist'.format(**config)) @@ -46,10 +48,21 @@ def verify(options): if {'source_address', 'source_interface'} <= set(config): raise ConfigError('Can not define both HTTP source-interface and source-address') + if 'source_address' in config: + if not is_addr_assigned(config['source_address']): + raise ConfigError('No interface with give address specified!') + + if 'ssh_client' in options: + config = options['ssh_client'] + if 'source_address' in config: + if not is_addr_assigned(config['source_address']): + raise ConfigError('No interface with give address specified!') + return None def generate(options): - render(config_file, 'system/curlrc.tmpl', options, trim_blocks=True) + render(curlrc_config, 'system/curlrc.tmpl', options, trim_blocks=True) + render(ssh_config, 'system/ssh_config.tmpl', options, trim_blocks=True) return None def apply(options): @@ -63,12 +76,20 @@ def apply(options): if os.path.exists(systemd_action_file): os.unlink(systemd_action_file) - if 'ctrl_alt_del_action' in options.keys(): + if 'ctrl_alt_del_action' in options: if options['ctrl_alt_del_action'] == 'reboot': os.symlink('/lib/systemd/system/reboot.target', systemd_action_file) elif options['ctrl_alt_del_action'] == 'poweroff': os.symlink('/lib/systemd/system/poweroff.target', systemd_action_file) + if 'http_client' not in options: + if os.path.exists(curlrc_config): + os.unlink(curlrc_config) + + if 'ssh_client' not in options: + if os.path.exists(ssh_config): + os.unlink(ssh_config) + # Reboot system on kernel panic with open('/proc/sys/kernel/panic', 'w') as f: if 'reboot_on_panic' in options.keys(): -- cgit v1.2.3 From f8112b64a9ddfddf1c24a11ba3db236a15df14e6 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 10 Aug 2020 18:58:27 -0500 Subject: host_name: T2784: remove unused arg in verify, unneeded arg in get_config --- src/conf_mode/host_name.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index f2fa64233..65c981d6a 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -47,7 +47,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 +79,7 @@ def get_config(conf): return hosts -def verify(conf, hosts): +def verify(hosts): if hosts is None: return None @@ -168,9 +170,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: -- cgit v1.2.3 From bcbc7c3eaa833d76feedf5676ef85df69cc506c1 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 10 Aug 2020 19:00:51 -0500 Subject: host_name: T2784: remove unused imports --- src/conf_mode/host_name.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'src') diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 65c981d6a..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() -- cgit v1.2.3 From 94d697f5e161293f92a42f88aafc10c7955dda73 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 13 Aug 2020 19:03:38 +0200 Subject: console-server: T2490: use get_first_key=True on get_config_dict() --- src/conf_mode/service_console-server.py | 37 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 19 deletions(-) (limited to 'src') 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 -- cgit v1.2.3 From 93fb90b4f1eca5a9d1828714e06123bb22a7a219 Mon Sep 17 00:00:00 2001 From: sever-sever Date: Fri, 14 Aug 2020 17:02:20 +0000 Subject: dhclient: T2277: Hook for default route in vrf --- src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src') 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..60e001af7 100644 --- a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper +++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper @@ -20,6 +20,7 @@ function iptovtysh () { local VTYSH_NETADDR="" local VTYSH_GATEWAY="" local VTYSH_DEV="" + local VTYSH_VRF_NAME=$(ip -d link show dev $interface | grep $interface | awk '{print $9}') # convert default route to 0.0.0.0/0 if [ "$4" == "default" ] ; then VTYSH_NETADDR="0.0.0.0/0" @@ -40,7 +41,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" + + # check if vrf is present + if [ $(ip -d link show dev $interface | grep vrf | wc -l) -eq 0 ]; then + VTYSH_CMD="ip route $VTYSH_NETADDR $VTYSH_GATEWAY $VTYSH_DEV tag $VTYSH_TAG $VTYSH_DISTANCE" + elif [ $(ip -d link show dev $interface | grep vrf | wc -l) -eq 1 ]; then + VTYSH_CMD="ip route $VTYSH_NETADDR $VTYSH_GATEWAY $VTYSH_DEV tag $VTYSH_TAG $VTYSH_DISTANCE vrf $VTYSH_VRF_NAME" + fi + # delete route if the command is "del" if [ "$3" == "del" ] ; then VTYSH_CMD="no $VTYSH_CMD" -- cgit v1.2.3 From b082a6fb211ef19d75c4c81414be9aa1b9248b45 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 13 Aug 2020 18:31:08 +0200 Subject: lcd: T2564: flatten CLI interface * set system lcd device * set system lcd model Both device and model have completion helpers for supported interfaces and LCD displays. --- data/templates/lcd/LCDd.conf.tmpl | 126 ++ data/templates/lcd/lcdproc.conf.tmpl | 60 + data/templates/system-display/LCDd.conf.tmpl | 1500 -------------------- data/templates/system-display/lcdproc.conf.tmpl | 173 --- interface-definitions/system-display.xml.in | 235 --- interface-definitions/system-lcd.xml.in | 62 + python/vyos/util.py | 12 + src/conf_mode/interfaces-wirelessmodem.py | 12 +- src/conf_mode/system-display.py | 98 -- src/conf_mode/system_lcd.py | 84 ++ .../systemd/system/LCDd.service.d/override.conf | 8 + .../system/hostapd@.service.d/override.conf | 7 +- src/systemd/LCDd@.service | 10 - src/systemd/lcdproc.service | 13 + src/systemd/lcdproc@.service | 10 - 15 files changed, 369 insertions(+), 2041 deletions(-) create mode 100644 data/templates/lcd/LCDd.conf.tmpl create mode 100644 data/templates/lcd/lcdproc.conf.tmpl delete mode 100644 data/templates/system-display/LCDd.conf.tmpl delete mode 100644 data/templates/system-display/lcdproc.conf.tmpl delete mode 100644 interface-definitions/system-display.xml.in create mode 100644 interface-definitions/system-lcd.xml.in delete mode 100755 src/conf_mode/system-display.py create mode 100755 src/conf_mode/system_lcd.py create mode 100644 src/etc/systemd/system/LCDd.service.d/override.conf delete mode 100644 src/systemd/LCDd@.service create mode 100644 src/systemd/lcdproc.service delete mode 100644 src/systemd/lcdproc@.service (limited to 'src') 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 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/data/templates/system-display/LCDd.conf.tmpl b/data/templates/system-display/LCDd.conf.tmpl deleted file mode 100644 index 1dd646202..000000000 --- a/data/templates/system-display/LCDd.conf.tmpl +++ /dev/null @@ -1,1500 +0,0 @@ -### 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 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 ? -# IMPORTANT: Make sure to change this setting to reflect your -# specific setup! Otherwise LCDd won't be able to find -# the driver modules and will thus not be able to -# function properly. -# 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 == 'sdec' %} -Driver=sdeclcd -{%- endif %} - -{%- if model == 'ezio' %} -Driver=hd44780 -{%- endif %} - -{%- if model == 'test' %} -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="{%- if hello %}{{ hello }}{%- else %}Welcome to VyOS{%- endif %}" - -# GoodBye message: each entry represents a display line; default: builtin -GoodBye="{%- if bye %}{{ bye }}{%- else %}Bye from VyOS{%- endif %}" - -# Sets the interval in microseconds for updating the display. -# [default: 125000 meaning 8Hz] -#FrameInterval=125000 - -# Sets the default time in seconds to displays a screen. [default: 4] -WaitTime={%- if time %}{{ time }}{%- else%}4{%- endif %} - -# 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=off - -# 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=no - -# 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=open - -# 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=open - -# set title scrolling speed [default: 10; legal: 0-10] -#TitleSpeed=10 - -# The "...Key=" lines define what the server does with keypresses that -# don't go to any client. The ToggleRotateKey stops rotation of screens, while -# the PrevScreenKey and NextScreenKey go back / forward one screen (even if -# rotation is disabled. -# Assign the key string returned by the driver to the ...Key setting. These -# are the defaults: -ToggleRotateKey=Enter -PrevScreenKey=Left -NextScreenKey=Right -#ScrollUpKey=Up -#ScrollDownKey=Down - -## The menu section. The menu is an internal LCDproc client. ## -[menu] -# If true the server allows transitions between different client's menus -# [default: false; legal: true, false] -#PermissiveGoto=false - -# You can configure what keys the menu should use. Note that the MenuKey -# will be reserved exclusively, the others work in shared mode. - -# Up to six keys are supported. The MenuKey (to enter and exit the menu), the -# EnterKey (to select values) and at least one movement keys are required. -# These are the default key assignments: -MenuKey=Escape -EnterKey=Enter -UpKey=Up -DownKey=Down -#LeftKey=Left -#RightKey=Right - - -### Driver sections are below this line, in alphabetical order ### - - -## EMAC BayRAD driver ## -[bayrad] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/lcd - -# Set the communication speed [default: 9600; legal: 1200, 2400, 9600, 19200] -Speed=9600 - - - -## CrystalFontz driver (for CF632 & CF634) ## -[CFontz] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/ttyS0 -# Select the LCD size [default: 20x4] -Size=20x4 -# Set the initial contrast [default: 560; legal: 0 - 1000] -Contrast=350 -# Set the initial brightness [default: 1000; legal: 0 - 1000] -Brightness=1000 -# Set the initial off-brightness [default: 0; legal: 0 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -OffBrightness=0 -# Set the communication speed [default: 9600; legal: 1200, 2400, 9600, 19200, -# 115200] -Speed=9600 -# Set the firmware version (New means >= 2.0) [default: no; legal: yes, no] -NewFirmware=no -# Reinitialize the LCD's BIOS [default: no; legal: yes, no] -# normally you shouldn't need this -Reboot=no - - - -## CrystalFontz packet driver (for CFA533, CFA631, CFA633 & CFA635) ## -[CFontzPacket] -{%- if model == 'test' %} -Model=533 -Device=/dev/serial/by-bus/usb0b1.1p1.0 -Contrast=350 -Brightness=1000 -OffBrightness=50 -Reboot=yes -USB=yes -{%- endif %} - - -## Curses driver ## -[curses] - -# color settings -# foreground color [default: blue] -Foreground=blue -# background color when "backlight" is off [default: cyan] -Background=cyan -# background color when "backlight" is on [default: red] -Backlight=red - -# display size [default: 20x4] -Size=20x2 - -# What position (X,Y) to start the left top corner at... -# Default: (7,7) -TopLeftX=7 -TopLeftY=7 - -# use ASC symbols for icons & bars [default: no; legal: yes, no] -UseACS=no - -# draw Border [default: yes; legal: yes, no] -DrawBorder=yes - - - -## Cwlinux driver ## -[CwLnx] - -# Select the LCD model [default: 12232; legal: 12232, 12832, 1602] -Model=12232 - -# Select the output device to use [default: /dev/lcd] -Device=/dev/ttyUSB0 - -# Select the LCD size. Default depends on model: -# 12232: 20x4 -# 12832: 21x4 -# 1602: 16x2 -Size=20x4 - -# Set the communication speed [default: 19200; legal: 9600, 19200] -Speed=19200 - -# Reinitialize the LCD's BIOS [default: no; legal: yes, no] -# normally you shouldn't need this -Reboot=no - -# If you have a keypad connected. Keypad layout is currently not -# configureable from the config file. -Keypad=yes - -# If you have a non-standard keypad you can associate any keystrings to keys. -# There are 6 input keys in the CwLnx hardware that generate characters -# from 'A' to 'F'. -# -# The following is the built-in default mapping hardcoded in the driver. -# You can leave those unchanged if you have a standard keypad. -# You can change it if you want to report other keystrings or have a non -# standard keypad. -# KeyMap_A=Up -# KeyMap_B=Down -# KeyMap_C=Left -# KeyMap_D=Right -# KeyMap_E=Enter -# KeyMap_F=Escape - -# keypad_test_mode permits one to test keypad assignment -# Default value is no -#keypad_test_mode=yes - - - -## ea65 driver for the display in AOpen XC Cube AV EA65 media barebones ## -[ea65] - -# Device is fixed /dev/ttyS1 -# Width and Height are fixed 9x1 - -# As the VFD is self luminescent we don't have a backlight -# But we can use the backlight functions to control the front LEDs -# Brightness 0 to 299 -> LEDs off -# Brightness 300 to 699 -> LEDs half bright -# Brightness 700 to 1000 -> LEDs full bright -Brightness=500 -# OffBrightness is the the value used for the 'backlight off' state -OffBrightness=0 - - - -## EyeboxOne driver ## -[EyeboxOne] - -# Select the output device to use [default: /dev/ttyS1] -# Device=/dev/cua01 -Device=/dev/ttyS1 - -# Set the display size [default: 20x4] -Size=20x4 - -# Switch on the backlight? [default: yes] -Backlight=yes - -# Switch on the cursor? [default: no] -Cursor=no - -# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200] -Speed=19200 - -# Enter Key is a \r character, so it's hardcoded in the driver -LeftKey=D -RightKey=C -UpKey=A -DownKey=B -EscapeKey=P - -# You can find out which key of your display sends which -# character by setting keypad_test_mode to yes and running -# LCDd. LCDd will output all characters it receives. -# Afterwards you can modify the settings above and set -# keypad_set_mode to no again. -keypad_test_mode=no - -## Futaba TOSD-5711BB VFD Driver ## -[futaba] - -## g15 driver for Logitech G15 Keyboard LCDs ## -[g15] - -# Display size (currently unused) -size=20x5 - - - -## glcd generic graphical display driver -[glcd] -# Select what type of connection. See documentation for types. -ConnectionType=t6963 - -# Width and height of the display in pixel. The supported sizes may depend on -# the ConnectionType. [default: 128x64; legal: 1x1 - 640x480] -#Size=128x64 - -# Width and height of a character cell in pixels. This value is only used if -# the driver has been compiled with FreeType and it is enabled. Otherwise the -# default 6x8 cell is used. -#CellSize=12x16 - -# If LCDproc has been compiled with FreeType 2 support this option can be used -# to turn if off intentionally. [default: yes; legal: yes, no] -#useFT2=no - -# Path to font file to use for FreeType rendering. This font must be monospace -# and should contain some special Unicode characters like arrows (Andale Mono -# is recommended and can be fetched at http://corefonts.sf.net). -#normal_font=/usr/local/lib/X11/fonts/TTF/andalemo.ttf - -# Some fonts miss the Unicode characters used to represent icons. In this case -# the built-in 5x8 font can used if this option is turned off. [default: yes; -# legal: yes, no] -#fontHasIcons=no - -# Set the initial contrast if supported by connection type. -# [default: 600; legal: 0 - 1000] -#Contrast=600 - -# Set brightness of the backlight if the backlight is switched 'on'. -# [default: 800; legal: 0 - 1000] -#Brightness=1000 - -# Set brightness of the backlight if the backlight is switched 'off'. Set this -# to zero to completely turn off the backlight. [default: 100; legal: 0 - 1000] -#OffBrightness=0 - -# Time (ms) from first key report to first repeat. Set to 0 to disable repeated -# key reports. [default: 500; legal: 0 - 3000] -#KeyRepeatDelay=500 - -# Time (ms) between repeated key reports. Ignored if KeyRepeatDelay is disabled -# (set to zero). [default: 300; legal: 0 - 3000] -#KeyRepeatInterval=300 - -# Assign key strings to keys. There may be up to 16 keys numbered 'A' to 'Z'. -# By default keys 'A' to 'F' are assigned Up, Down, Left, Right, Enter, Escape. -KeyMap_A=Up -KeyMap_B=Down -KeyMap_C=Enter -KeyMap_D=Escape - -# --- t6963 options --- - -# Parallel port to use [default: 0x378; legal: 0x200 - 0x400] -#Port=0x378 - -# Use LPT port in bi-directional mode. This should work on most LPT port -# and is required for proper timing! [default: yes; legal: yes, no] -#bidirectional=yes - -# Insert additional delays into reads / writes. [default: no; legal: yes, no] -#delayBus=no - -# --- serdisplib options --- - -# Name of the underlying serdisplib driver, e.g. ctinclud. See -# serdisplib documentation for details. -serdisp_name=t6963 - -# The display device to use, e.g. serraw:/dev/ttyS0, -# parport:/dev/parport0 or USB:07c0/1501. -serdisp_device=/dev/ppi0 - -# Options string to pass to serdisplib during initialization. Use -# this to set any display related options (e.g. wiring). The display size is -# always set based on the Size configured above! By default, no options are -# set. -# Important: The value must be quoted as it contains equal signs! -#serdisp_options="INVERT=1" - -# --- x11 options --- - -# PixelSize is size of each dot in pixels + a pixel gap. [default: 3+1] -#x11_PixelSize=3+1 - -# Colors are in RRGGBB format prefixed with "0x". -# PixelColor: The color of each dot at full contrast. [default: 0x000000] -#x11_PixelColor=0x000000 - -# BacklightColor: The color of the backlight as full brightness. -# [default: 0x80FF80] -#x11_BacklightColor=0x80FF80 - -# Border: Adds a border (empty space) around the LCD portion of X11 window. -# [default: 20] -#x11_Border=20 - -# Inverted: inverts the pixels [default: no; legal: yes, no] -#x11_Inverted=no - -# --- picolcdgfx options --- - -# Time in ms for usb_read to wait on a key press. [default: 125; legal: >0] -#picolcdgfx_KeyTimeout=125 - -# Inverted: Inverts the pixels. [default: no; legal: yes or no] -#picolcdgfx_Inverted=no - - - -## glcdlib meta driver for graphical LCDs ## -[glcdlib] - -## mandatory: - -# which graphical display supported by graphlcd-base to use [default: image] -# (see /etc/graphlcd.conf for possible drivers) -Driver=noritake800 - -# no=use graphlcd bitmap fonts (they have only one size / font file) -# yes=use fonts supported by FreeType2 (needs Freetype2 support in -# libglcdprocdriver and its dependants) -UseFT2=yes - -# text resolution in fixed width characters [default: 16x4] -# (if it won't fit according to available physical pixel resolution -# and the minimum available font face size in pixels, then -# 'DebugBorder' will automatically be turned on) -TextResolution=20x4 - -# path to font file to use -FontFile=/usr/share/fonts/corefonts/courbd.ttf - -## these only apply if UseFT2=yes: - -# character encoding to use -CharEncoding=iso8859-2 - -# minimum size in pixels in which fonts should be rendered -MinFontFaceSize=7x12 - -## optional: -Brightness=50 # Brightness (in %) if applicable -Contrast=50 # Contrast (in %) if applicable -Backlight=no # Backlight if applicable -UpsideDown=no # flip image upside down -Invert=no # invert light/dark pixels -ShowDebugFrame=no # turns on/off 1 pixel thick debugging - # border within the usable text area, - # for setting up TextResolution and - # MinFontFaceSize (if using FT2); -ShowBigBorder=no # border around the unused area -ShowThinBorder=yes # border around the unused area -PixelShiftX=0 -PixelShiftY=2 - - - -## Matrix Orbital GLK driver ## -[glk] - -# select the serial device to use [default: /dev/lcd] -Device=/dev/lcd - -# set the initial contrast value [default: 500; legal: 0 - 1000] -Contrast=500 - -# set the serial port speed [default: 19200; legal: 9600, 19200, 38400, 57600, 115200] -Speed=19200 - - - -## Hitachi HD44780 driver ## -[hd44780] -{%- if model == 'ezio' %} -ConnectionType=ezio -Device=/dev/ttyUSB0 -Keypad=yes -Size=16x2 -KeyMatrix_4_1=Enter -KeyMatrix_4_2=Up -KeyMatrix_4_3=Down -KeyMatrix_4_4=Escape -{%- endif %} - -# Select what type of connection. See documentation for available types. -#ConnectionType=4bit - -# Select model if have non-standard one which require extra initialization or handling or -# just want extra features it offers. -# Available: standard (default), extended, winstar_oled, pt6314_vfd -# - standard is default, use for LCDs not mentioned below. -# - extended, hd66712, ks0073: allows use 4-line "extended" mode, -# same as deprecated now option ExtendedMode=yes -# - winstar_oled, weh00xxyya: changes initialization for WINSTAR's WEH00xxyyA displays -# and allows handling brightness -# - pt6314_vfd: allows handling brightness on PTC's PT6314 VFDs -# -# This option should be independent of connection type. -#Model = standard - -# I/O address of the LPT port. Usual values are: 0x278, 0x378 and 0x3BC. -# For I2C connections this sets the slave address (usually 0x20). -#Port=0x378 - -# Device of the serial, I2C, or SPI interface [default: /dev/lcd] -#Device=/dev/ttyS0 - -# Bitrate of the serial port (0 for interface default) -#Speed=0 - -# If you have a keypad connected. -# You may also need to configure the keypad layout further on in this file. -#Keypad=no - -# Set the initial contrast (bwctusb, lcd2usb, and usb4all) -# [default: 800; legal: 0 - 1000] -#Contrast=0 - -# Set brightness of the backlight (lcd2usb and usb4all): -# Brightness is the brightness while the backlight is set to 'on'. -# [default: 800; legal: 0 - 1000] -#Brightness=1000 - -# OffBrightness is the brightness while the backlight is set to 'off'. -# [default: 300; legal: 0 - 1000] -#OffBrightness=0 - -# Specify if you have a switchable backlight and if yes, can select method for turning it on/off: -# -# - none - no switchable backlight is available. For compability also boolean -# 0, n, no, off and false are aliases. -# - external - use external pin or any other method defined with ConnectionType backlight -# handling. For backward compability also this value is chosen for boolean -# TRUE values: 1, y, yes, on and true. -# - internal - means that backlight is handled using internal commands according -# to selected display model (with Model option). Depending on model, -# Brightness and OffBrightness options can be taken into account. -# - internalCmds - means that commands for turning on and off backlight are given -# with extra options BacklightOnCmd and BacklightOffCmd, which would be treated -# as catch up (last resort) for other types of displays which have similar features. -# -# You can provide multiple occurences of this option to use more than one method. -# Default is model specific: Winstar OLED and PT6314 VFD enables internal backlight mode, -# for others it is set to none. -#Backlight = none - -# Commands for enabling internal backlight for use with Backlight=internalCmds. -# Up to 4 bytes can be encoded, as integer number in big-endian order. -# -# NOTE: this is advanced option, if command contains bits other than only brighness handling, -# they must be set accordingly to not disrupt display state. If for example 'FUNCTION SET' command -# is used for this purpose, bits of interface length (4-bit / 8-bit) must be set according to -# selected ConnectionType. -#BacklightCmdOn=0x1223 - -# Commands for disabling internal backlight for use with Backlight=internalCmds. -# Up to 4 bytes can be encoded, as integer number in big-endian order. -#BacklightCmdOff=0x1234 - - -# If you have the additional output port ("bargraph") and you want to -# be able to control it with the lcdproc OUTPUT command -#OutputPort=no - -# Specifies if the last line is pixel addressable (yes) or it controls an -# underline effect (no). [default: yes; legal: yes, no] -#Lastline=yes - -# Specifies the size of the LCD. -# In case of multiple combined displays, this should be the total size. -#Size=20x4 - -# For multiple combined displays: how many lines does each display have. -# Vspan=2,2 means both displays have 2 lines. -#vspan=2,2 - -# If you have an HD66712, a KS0073 or another controller with 'extended mode', -# set this flag to get into 4-line mode. On displays with just two lines, do -# not set this flag. -# As an additional restriction, controllers with and without extended mode -# AND 4 lines cannot be mixed for those connection types that support more -# than one display! -# NOTE: This option is deprecated in favour of choosing Model=extended option. -#ExtendedMode=yes - -# In extended mode, on some controllers like the ST7036 (in 3 line mode) -# the next line in DDRAM won't start 0x20 higher. [default: 0x20] -#LineAddress=0x10 - -# Character map to to map ISO-8859-1 to the LCD's character set -# [default: hd44780_default; legal: hd44780_default, hd44780_euro, ea_ks0073, -# sed1278f_0b, hd44780_koi8_r, hd44780_cp1251, hd44780_8859_5, upd16314, -# weh001602a_1] -# (hd44780_koi8_r, hd44780_cp1251, hd44780_8859_5, upd16314 and weh001602a_1 -# are possible if compiled with additional charmaps) -CharMap=hd44780_default - -# Font bank to be used for some displays such as the WINSTAR WEH001602A -# 0: English/Japanese (default) -# 1: Western Europe I -# 2: English/Rusian -# 3: Western Europe II -#FontBank=0 - -# If your display is slow and cannot keep up with the flow of data from -# LCDd, garbage can appear on the LCDd. Set this delay factor to 2 or 4 -# to increase the delays. Default: 1. -#DelayMult=2 - -# Some displays (e.g. vdr-wakeup) need a message from the driver to that it -# is still alive. When set to a value bigger then null the character in the -# upper left corner is updated every seconds. Default: 0. -#KeepAliveDisplay=0 - -# If you experience occasional garbage on your display you can use this -# option as workaround. If set to a value bigger than null it forces a -# full screen refresh seconds. Default: 0. -#RefreshDisplay=5 - -# You can reduce the inserted delays by setting this to false. -# On fast PCs it is possible your LCD does not respond correctly. -# Default: true. -#DelayBus=true - -# If you have a keypad you can assign keystrings to the keys. -# See documentation for used terms and how to wire it. -# For example to give directly connected key 4 the string "Enter", use: -# KeyDirect_4=Enter -# For matrix keys use the X and Y coordinates of the key: -# KeyMatrix_1_3=Enter -#KeyMatrix_4_1=Enter -#KeyMatrix_4_2=Up -#KeyMatrix_4_3=Down -#KeyMatrix_4_4=Escape - -## ICP Peripheral Comminication Protocol driver ## -# Supports A125 and A106 -# -# Short Press Select: Down -# Long Press Select: Up -# Short Press Enter: Enter -# Long Press Enter: Escape -# -[icp_a106] -Device=/dev/ttyS1 - -# Display dimensions -Size=20x2 - - -## Code Mercenaries IO-Warrior driver ## -[IOWarrior] - -# display dimensions -Size=20x4 - -# serial number. Must be exactly as listed by usbview -# (if not given, the 1st IOWarrior found gets used) -#SerialNumber=00000674 - -# If you have an HD66712, a KS0073 or another 'almost HD44780-compatible', -# set this flag to get into extended mode (4-line linear). -#ExtendedMode=yes - -# Specifies if the last line is pixel addressable (yes) or it controls an -# underline effect (no). [default: yes; legal: yes, no] -#Lastline=yes - - - -## Soundgraph/Ahanix/Silverstone/Uneed/Accent iMON driver ## -[imon] - -# select the device to use -Device=/dev/lcd0 - -# display dimensions -Size=16x2 - -# Character map to to map ISO-8859-1 to the displays character set. -# [default: none; legal: none, hd44780_euro, upd16314, hd44780_koi8_r, -# hd44780_cp1251, hd44780_8859_5 ] (upd16314, hd44780_koi8_r, -# hd44780_cp1251, hd44780_8859_5 are possible if compiled with additional -# charmaps) -CharMap=hd44780_euro - -## Soundgraph iMON LCD ## -[imonlcd] -# Specify which iMon protocol should be used -# [legal: 0, 1; default: 0] -# Choose 0 for 15c2:ffdc device, -# Choose 1 for 15c2:0038 device -Protocol=0 - -# Set the exit behavior [legal: 0-2; default: 1] -# 0 means leave shutdown message, -# 1 means show the big clock, -# 2 means blank device -#OnExit=2 - -# Select the output device to use [default: /dev/lcd0] -Device=/dev/lcd0 - -# Select the displays contrast [default: 200; legal: 0-1000] -Contrast=200 - -# Specify the size of the display in pixels [default: 96x16] -#Size=96x16 - -# Set the backlight state [default: on; legal: on, off] -#Backlight=on - -# Set the disc mode [legal: 0,1; default: 0] -# 0 => spin the "slim" disc - two disc segments, -# 1 => their complement spinning; -#DiscMode=0 - - - -## IrMan driver ## -[IrMan] -# in case of trouble with IrMan, try the Lirc emulator for IrMan - -# Select the input device to use -#Device=/dev/irman - -# Select the configuration file to use -#Config=/etc/irman.cfg - - - -## IRtrans driver ## -[irtrans] - -# Does the device have a backlight? [default: no; legal: yes, no] -#Backlight=no - -# IRTrans device to connect to [default: localhost] -#Hostname=localhost - -# display dimensions -Size=16x2 - - - -## Joystick driver ## -[joy] - -# Select the input device to use [default: /dev/js0] -Device=/dev/js0 - -# set the axis map -Map_Axis1neg=Left -Map_Axis1pos=Right -Map_Axis2neg=Up -Map_Axis2pos=Down - -# set the button map -Map_Button1=Enter -Map_Button2=Escape - - -## JW-002 driver ## -[jw002] - -# Select the output device to use [default: /dev/lcd] -#Device=/dev/ttyS0 - -# Set the display size [default: 24x8] -Size=24x8 - -# Optional X and Y offsets (in characters) to center a smaller display -# size on the full 24x8 panel -X_offset=0 -Y_offset=0 - -# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200] -Speed=19200 - -# Pick which font page to use [default: 0] -# Note that different fonts probably have their bargraph chars in different -# spots. For ROM-based fonts 0-3, those characters are already known. -Font=0 - -# The following table translates from jw002 key letters to logical key names. -# By default no keys are mapped, meaning the keypad is not used at all. -#KeyMap_I=Left -#KeyMap_J=Right -#KeyMap_H=Up -#KeyMap_K=Down -#KeyMap_L=Enter -#KeyMap_A=Escape -# See the [menu] section for an explanation of the key mappings - -# You can find out which key of your display sends which -# character by setting keypad_test_mode to yes and running -# LCDd. LCDd will output all characters it receives. -# Afterwards you can modify the settings above and set -# keypad_set_mode to no again. -keypad_test_mode=no - - -## LB216 driver ## -[lb216] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/lcd - -# Set the initial brightness [default: 255; legal: 0 - 255] -Brightness=255 - -# Set the communication speed [default: 9600; legal: 2400, 9600] -Speed=9600 - -# Reinitialize the LCD's BIOS [default: no; legal: yes, no] -Reboot=no - - - -## LCDM001 driver ## -[lcdm001] - -Device=/dev/ttyS1 - -# keypad settings -# Keyname Function -# Normal context Menu context -# ------- -------------- ------------ -# PauseKey Pause/Continue Enter/select -# BackKey Back(Go to previous screen) Up/Left -# ForwardKey Forward(Go to next screen) Down/Right -# MainMenuKey Open main menu Exit/Cancel -PauseKey=LeftKey -BackKey=UpKey -ForwardKey=DownKey -MainMenuKey=RightKey - -# You can rearrange the settings here. -# If your device is broken, have a look at server/drivers/lcdm001.h - - - -## HNE LCTerm driver ## -[lcterm] -Device=/dev/ttyS1 -Size=16x2 - - -## Linux event device input driver ## -[linux_input] - -# Select the input device to use [default: /dev/input/event0]. This may be -# either an absolute path to the input node, starting with '/', or -# an input device name, e.g. "Logitech Gaming Keyboard Gaming Keys". -# Device=/dev/input/event0 - -# specify a non-default key map -#key=1,Escape -#key=28,Enter -#key=96,Enter -#key=105,Left -#key=106,Right -#key=103,Up -#key=108,Down - - -## LIRC input driver ## -[lirc] - -# Specify an alternative location of the lircrc file [default: ~/.lircrc] -#lircrc=/etc/lircrc.lcdproc - -# Must be the same as in your lircrc -#prog=lcdd - - - -## LIS MCE 2005 driver ## -[lis] - -# Set the initial brightness [default: 1000; legal: 0 - 1000] -# 0-250 = 25%, 251-500 = 50%, 501-750 = 75%, 751-1000 = 100% -#Brightness=1000 - -# Columns by lines [default: 20x2] -#Size=20x2 - -# USB Vendor ID [default: 0x0403] -# Change only if testing a compatible device. -#VendorID=0x0403 - -# USB Product ID [default: 0x6001] -# Change only if testing a compatible device. -#ProductID=0x6001 - -# Specifies if the last line is pixel addressable (yes) or it only controls an -# underline effect (no). [default: yes; legal: yes, no] -#Lastline=yes - - - -##The driver for the VFD of the Medion MD8800 PC ## -[MD8800] -# device to use [default: /dev/ttyS1] -#Device=/dev/ttyS1 - -# display size [default: 16x2] -#Size=16x2 - -# Set the initial brightness [default: 1000; legal: 0 - 1000] -Brightness=1000 -# Set the initial off-brightness [default: 0; legal: 0 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -OffBrightness=50 - - - -## Futuba MDM166A Display -[mdm166a] -# Show self-running clock after LCDd shutdown -# Possible values: [default: no; legal: no, small, big] -Clock=big -# Dim display, no dimming gives full brightness [default: no, legal: yes, no] -Dimming=no -# Dim display in case LCDd is inactive [default: no, legal: yes, no] -OffDimming=yes - - - -## MSI MS-6931 driver for displays in 1HU servers ## -[ms6931] - -# device to use [default: /dev/ttyS1] -Device=/dev/ttyS1 - -# display size [default: 16x2] -#Size=16x2 - - - -## MTC-S16209x driver ## -[mtc_s16209x] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/lcd - -# Set the initial brightness [default: 255; legal: 0 - 255] -Brightness=255 - -# Reinitialize the LCD's BIOS [default: no; legal: yes, no] -Reboot=no - - - -## Matrix Orbital driver ## -[MtxOrb] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/ttyS0 - -# Set the display size [default: 20x4] -Size=20x4 - -# Set the display type [default: lcd; legal: lcd, lkd, vfd, vkd] -Type=lkd - -# Set the initial contrast [default: 480] -# NOTE: The driver will ignore this if the display -# is a vfd or vkd as they don't have this feature -Contrast=480 - -# Some old displays do not have an adjustable backlight but only can -# switch the backlight on/off. If you experience randomly appearing block -# characters, try setting this to false. [default: yes; legal: yes, no] -hasAdjustableBacklight=no - -# Set the initial brightness [default: 1000; legal: 0 - 1000] -Brightness=1000 -# Set the initial off-brightness [default: 0; legal: 0 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -OffBrightness=0 - -# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200] -Speed=19200 - -# The following table translates from MtxOrb key letters to logical key names. -# By default no keys are mapped, meaning the keypad is not used at all. -#KeyMap_A=Left -#KeyMap_B=Right -#KeyMap_C=Up -#KeyMap_D=Down -#KeyMap_E=Enter -#KeyMap_F=Escape -# See the [menu] section for an explanation of the key mappings - -# You can find out which key of your display sends which -# character by setting keypad_test_mode to yes and running -# LCDd. LCDd will output all characters it receives. -# Afterwards you can modify the settings above and set -# keypad_set_mode to no again. -keypad_test_mode=no - - - -## mx5000 driver for LCD display on the Logitech MX5000 keyboard ## -[mx5000] - -# Select the output device to use [default: /dev/hiddev0] -Device = /dev/hiddev0 -# Time to wait in ms after the refresh screen has been sent [default: 1000] -WaitAfterRefresh = 1000 - - - -## Noritake VFD driver ## -[NoritakeVFD] -# device where the VFD is. Usual values are /dev/ttyS0 and /dev/ttyS1 -# [default: /dev/lcd] -Device=/dev/ttyS0 -# Specifies the size of the LCD. -Size=20x4 -# Set the initial brightness [default: 1000; legal: 0 - 1000] -Brightness=1000 -# Set the initial off-brightness [default: 0; legal: 0 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -OffBrightness=50 -# set the serial port speed [default: 9600, legal: 1200, 2400, 9600, 19200, 115200] -Speed=9600 -# Set serial data parity [default: 0; legal: 0-2 ] -# Meaning: 0(=none), 1(=odd), 2(=even) -Parity=0 -# re-initialize the VFD [default: no; legal: yes, no] -Reboot=no - - - -## Olimex MOD-LCD1x9 driver ## -[Olimex_MOD_LCD1x9] - -# device file of the i2c controler -Device=/dev/i2c-0 - - -## Mini-box.com picoLCD (usblcd) driver ## -[picolcd] - -# KeyTimeout is only used if the picoLCD driver is built with libusb-0.1. When -# built with libusb-1.0 key and IR data is input asynchronously so there is no -# need to wait for the USB data. -# KeyTimeout is the time in ms that LCDd spends waiting for a key press before -# cycling through other duties. Higher values make LCDd use less CPU time and -# make key presses more detectable. Lower values make LCDd more responsive -# but a little prone to missing key presses. 500 (.5 second) is the default -# and a balanced value. [default: 500; legal: 0 - 1000] -KeyTimeout=500 - -# Key auto repeat is only available if the picoLCD driver is built with -# libusb-1.0. Use KeyRepeatDelay and KeyRepeatInterval to configure key auto -# repeat. -# -# Key auto repeat delay (time in ms from first key report to first repeat). Use -# zero to disable auto repeat. [default: 300; legal: 0 - 3000] -KeyRepeatDelay=300 - -# Key auto repeat interval (time in ms between repeat reports). Only used if -# KeyRepeatDelay is not zero. [default: 200; legal: 0 - 3000] -KeyRepeatInterval=200 - -# Sets the initial state of the backlight upon start-up. -# [default: on; legal: on, off] -#Backlight=on - -# Set the initial brightness [default: 1000; legal: 0 - 1000]. Works only -# with the 20x4 device -Brightness=1000 - -# Set the brightness while the backlight is 'off' [default: 0; legal: 0 - 1000]. -# Works only with the 20x4 device. -#OffBrightness=0 - -# Set the initial contrast [default: 1000; legal: 0 - 1000] -Contrast=1000 - -# Link the key lights to the backlight? [default: on; legal: on, off] -#LinkLights=off - -# Light the keys? [default: on; legal: on, off] -Keylights=on - -# If Keylights is on, the you can unlight specific keys below: -# Key0 is the directional pad. Key1 - Key5 correspond to the F1 - F5 keys. -# There is no LED for the +/- keys. This is a handy way to indicate to users -# which keys are disabled. [default: on; legal: on, off] -Key0Light=on -Key1Light=on -Key2Light=on -Key3Light=on -Key4Light=on -Key5Light=on - -# Host name or IP address of the LIRC instance that is to receive IR codes -# If not set, or set to an empty value, IR support is disabled. -#LircHost=127.0.0.1 - -# UDP port on which LIRC is listening [default: 8765; legal: 1 - 65535] -LircPort=8765 - -# UDP data time unit for LIRC [default: off; legal: on, off] -# On: times sent in microseconds (requires LIRC UDP driver that accepts this). -# Off: times sent in 'jiffies' (1/16384s) (supported by standard LIRC UDP driver). -LircTime_us=on - -# Threshold in microseconds of the gap that triggers flushing the IR data -# to lirc [default: 8000; legal: 1000 - ] -# If LircTime_us is on values greater than 32.767ms will disable the flush -# If LircTime_us is off values greater than 1.999938s will disable the flush -LircFlushThreshold=10000 - - - -## Pyramid LCD driver ## -[pyramid] - -# device to connect to [default: /dev/lcd] -Device=/dev/ttyUSB0 - - - -## rawserial driver ## -[rawserial] - -# Select the output device to use [default: /dev/cuaU0] -Device=/dev/ttyS0 - -# Serial port baudrate [default: 9600] -Speed=9600 - -# Specifies the size of the LCD. If this driver is loaded as a secondary driver -# it always adopts to the size of the primary driver. If loaded as the only -# (or primary) driver, the size can be set. [default: 40x4] -#Size=16x2 - -# How often to dump the LCD contents out the port, in Hertz (times per second) -# 1 = once per second, 4 is 4 times per second, 0.1 is once every 10 seconds. -# [default: 1; legal: 0.0005 - 10] -UpdateRate=1 - - - -## SDEC driver for Watchguard Firebox ## -[sdeclcd] -# No options - - -## Seiko Epson 1330 driver ## -[sed1330] - -# Port where the LPT is. Common values are 0x278, 0x378 and 0x3BC -Port=0x378 - -# Type of LCD module (legal: G321D, G121C, G242C, G191D, G2446, SP14Q002) -# Note: Currently only tested with G321D & SP14Q002. -Type=G321D - -# Width x Height of a character cell in pixels [legal: 6x7 - 8x16; default: 6x10] -CellSize=6x10 - -# Select what type of connection [legal: classic, bitshaker; default: classic] -ConnectionType=classic - - - -## Seiko Epson 1520 driver ## -[sed1520] - -# Port where the LPT is. Usual values are 0x278, 0x378 and 0x3BC -Port=0x378 - -# Select the interface type (wiring) for the display. Supported values are -# 68 for 68-style connection (RESET level high) and 80 for 80-style connection -# (RESET level low). [legal: 68, 80; default: 80] -InterfaceType=80 - -# On fast machines it may be necessary to slow down transfer to the display. -# If this value is set to zero, delay is disabled. Any value greater than -# zero slows down each write by one microsecond. [legal: 0-1000; default: 1] -DelayMult=0 - -# The original wiring used an inverter to drive the control lines. If you do -# not use an inverter set haveInverter to no. [default: yes; legal: yes, no] -HaveInverter=no - -# On some displays column data in memory is mapped to segment lines from right -# to left. This is called inverted mapping (not to be confused with -# 'haveInverter' from above). [default: no; legal: yes, no] -#InvertedMapping=yes - -# At least one display is reported (Everbouquet MG1203D) that requires sending -# three times 0xFF before a reset during initialization. -# [default: no; legal: yes, no] -#UseHardReset=yes - - -## serial POS display driver ## -[serialPOS] - -# Device to use in serial mode [default: /dev/ttyS0] -Device=/dev/ttyS0 - -# Specifies the size of the display in characters. [default: 16x2] -Size=16x2 - -# Specifies the cell size of each character cell on the display in characters. -# [default: 5x8] -Cellsize=5x8 - -# Specifies the number of custom characters supported by the display. -# [default: 0] -Custom_chars=0 - -# Set the communication protocol to use with the POS display. -# [default: AEDEX; legal: AEDEX, CD5220, Epson, Emax, LogicControls, Ultimate] -Type=AEDEX - -# communication baud rate with the display [default: 9600; legal: 1200, 2400, -# 4800, 9600, 19200, 115200] -Speed=9600 - - - -## Serial VFD driver ## -## Drives various (see below) serial 5x7dot VFD's. ## -[serialVFD] - -# Specifies the displaytype.[default: 0] -# 0 NEC (FIPC8367 based) VFDs. -# 1 KD Rev 2.1. -# 2 Noritake VFDs (*). -# 3 Futaba VFDs -# 4 IEE S03601-95B -# 5 IEE S03601-96-080 (*) -# 6 Futaba NA202SD08FA (allmost IEE compatible) -# 7 Samsung 20S207DA4 and 20S207DA6 -# 8 Nixdorf BA6x / VT100 -# (* most should work, not tested yet.) -Type=0 - -# "no" if display connected serial, "yes" if connected parallel. [default: no] -# I.e. serial by default -use_parallel=no - -# Number of Custom-Characters. default is display type dependent -#Custom-Characters=0 - -# Portaddress where the LPT is. Used in parallel mode only. Usual values are -# 0x278, 0x378 and 0x3BC. -Port=0x378 - -# Set parallel port timing delay (us). Used in parallel mode only. -# [default: 2; legal: 0 - 255] -#PortWait=2 - -# Device to use in serial mode. Usual values are /dev/ttyS0 and /dev/ttyS1 -Device=/dev/ttyS1 - -# Specifies the size of the VFD. -Size=20x2 - -# Set the initial brightness [default: 1000; legal: 0 - 1000] -# (4 steps 0-250, 251-500, 501-750, 751-1000) -Brightness=1000 -# Set the initial off-brightness [default: 0; legal: 0 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -# (4 steps 0-250, 251-500, 501-750, 751-1000) -OffBrightness=0 - -# set the serial port speed [default: 9600; legal: 1200, 2400, 9600, 19200, 115200] -Speed=9600 - -# enable ISO 8859 1 compatibility [default: yes; legal: yes, no] -#ISO_8859_1=yes - - - -## shuttleVFD driver ## -[shuttleVFD] -# No options - - - -## stv5730 driver ## -[stv5730] - -# Port the device is connected to [default: 0x378] -Port=0x378 - - -[SureElec] - -# Port the device is connected to (by default first USB serial port) -Device=/dev/ttyUSB0 - -# Edition level of the device (can be 1, 2 or 3) [default: 2] -#Edition=1 - -# set display size -# Note: The size can be obtained directly from device for edition 2 & 3. -#Size=16x2 - -# Set the initial contrast [default: 480; legal: 0 - 1000] -#Contrast=200 - -# Set the initial brightness [default: 480; legal: 1 - 1000] -#Brightness=480 - -# Set the initial off-brightness [default: 100; legal: 1 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -#OffBrightness=100 - - -## SVGAlib driver ## -[svga] - -# svgalib mode to use [default: G320x240x256 ] -# legal values are supported svgalib modes -#Mode=G640x480x256 - -# set display size [default: 20x4] -Size=20x4 - -# Set the initial contrast [default: 500; legal: 0 - 1000] -# Can be set but does not change anything internally -Contrast=500 - -# Set the initial brightness [default: 1000; legal: 1 - 1000] -Brightness=1000 - -# Set the initial off-brightness [default: 500; legal: 1 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -OffBrightness=500 - - - -## Text driver ## -[text] -# Set the display size [default: 20x4] -Size=20x4 - - - -## Toshiba T6963 driver ## -[t6963] - -# set display size in pixels [default: 128x64] -Size=128x64 - -# port to use [default: 0x378; legal: 0x200 - 0x400] -Port=0x378 - -# Use LPT port in bi-directional mode. This should work on most LPT port and -# is required for proper timing! [default: yes; legal: yes, no] -#bidirectional=yes - -# Insert additional delays into reads / writes. [default: no; legal: yes, no] -#delayBus=no - -# Clear graphic memory on start-up. [default: no; legal: yes, no] -#ClearGraphic=no - - - -## Tyan Barebones LCD driver (GS10 & GS12 series) ## -[tyan] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/lcd - -# Set the communication speed [default: 9600; legal: 4800, 9600] -Speed=9600 - -# set display size [default: 16x2] -Size=16x2 - - - -## ELV ula200 driver ## -[ula200] - -# Select the LCD size [default: 20x4] -Size=20x4 - -# If you have a non standard keypad you can associate any keystrings to keys. -# There are 6 input key in the CwLnx hardware that generate characters -# from 'A' to 'F'. -# -# The following it the built-in default mapping hardcoded in the driver. -# You can leave those unchanged if you have a standard keypad. -# You can change it if you want to report other keystrings or have a non -# standard keypad. -# KeyMap_A=Up -# KeyMap_B=Down -# KeyMap_C=Left -# KeyMap_D=Right -# KeyMap_E=Enter -# KeyMap_F=Escape - - - -## Wirz SLI LCD driver ## -[sli] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/lcd - -# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200, -# 38400, 57600, 115200] -Speed=19200 - - - -## vlsys_m428 for VFD/IR combination in Moneual MonCaso 320 ## -[vlsys_m428] - -# Select the output device to use [default: /dev/ttyUSB0] -#Device=/dev/ttyUSB0 - - - -## OnScreen Display using libxosd ## -[xosd] - -# set display size [default: 20x4] -Size=20x4 - -# Offset in pixels from the top-left corner of the monitor [default: 0x0] -Offset=200x200 - -# X font to use, in XLFD format, as given by "xfontsel" -Font=-*-terminus-*-r-*-*-*-320-*-*-*-*-* - -## Y.A.R.D.2 LCD section -[yard2LCD] -Size=20x4 -# If rendering rate is too high, change in server\main.h #define RENDER_FREQ 8 to "1" - -# EOF diff --git a/data/templates/system-display/lcdproc.conf.tmpl b/data/templates/system-display/lcdproc.conf.tmpl deleted file mode 100644 index 92aee8efe..000000000 --- a/data/templates/system-display/lcdproc.conf.tmpl +++ /dev/null @@ -1,173 +0,0 @@ -### autogenerated by system-display.py ### - -# system display show host (CPU|SMP-CPU|CPU-Graph|Load|Memory|Proc-Size|Disk|Uptime) -# network interface alias -# units (bps|Bps|pps) -# clock (big|mini|date-time) - -# LCDproc client configuration file - -## general options ## -[lcdproc] -# address of the LCDd server to connect to -Server=127.0.0.1 - -# Port of the server to connect to -Port=13666 - -# set reporting level -#ReportLevel=2 - -# report to to syslog ? -ReportToSyslog=true - -# run in foreground [default: false; legal: true, false] -#Foreground=true - -# PidFile location when running as daemon [default: /var/run/lcdproc.pid] -#PidFile=/var/run/lcdproc.pid - -# slow down initial announcement of modes (in 1/100s) -#delay=2 - - -## screen specific configuration options ## -{%- if show %} -# display name for the main menu [default: LCDproc HOST] -DisplayName="{%- if show['title'] %}{{ show['title'] }}{%- else %}VyOS{%- endif %}" - -{%- if show['host'] %} - -[CPU] -# Show screen -Active={%- if 'cpu' in show['host'] %}true{%- else %}false{%- endif %} -OnTime=1 -OffTime=2 -ShowInvisible=false - -[SMP-CPU] -# Show screen -Active={%- if 'cpu-all' in show['host'] %}true{%- else %}false{%- endif %} - -[Memory] -# Show screen -Active={%- if 'memory' in show['host'] %}true{%- else %}false{%- endif %} - -[Load] -# Show screen -Active={%- if 'load-hist' in show['host'] %}true{%- else %}false{%- endif %} -# Min Load Avg at which the backlight will be turned off [default: 0.05] -LowLoad=0.05 -# Max Load Avg at which the backlight will start blinking [default: 1.3] -HighLoad=1.3 - -[Uptime] -# Show screen -Active={%- if 'uptime' in show['host'] %}true{%- else %}false{%- endif %} - -[CPUGraph] -# Show screen -Active={%- if 'cpu-hist' in show['host'] %}true{%- else %}false{%- endif %} - -[ProcSize] -# Show screen -Active={%- if 'proc' in show['host'] %}true{%- else %}false{%- endif %} - -[Disk] -# Show screen -Active={%- if 'disk' in show['host'] %}true{%- else %}false{%- endif %} -{%- else %} {# if show['host'] #} -{# Turn off sections that default active #} - -[CPU] -Active=false - -[Memory] -Active=false - -[Load] -Active=false - -{%- endif %} {# if show['host'] #} - -[TimeDate] -# Show screen -Active={%- if show['clock'] == 'date-time' %}true{%- else %}false{%- endif %} -# time format [default: %H:%M:%S; legal: see strftime(3)] -TimeFormat="%H:%M:%S" -# date format [default: %x; legal: see strftime(3)] -DateFormat="%x" - -[BigClock] -# Show screen -Active={%- if show['clock'] == 'big' %}true{%- else %}false{%- endif %} - -[MiniClock] -# Show screen -Active={%- if show['clock'] == 'mini' %}true{%- else %}false{%- endif %} -# time format [default: %H:%M; legal: see strftime(3)] -TimeFormat="%H:%M" - -{%- if show['network'] %} -[Iface] -# Show screen -Active={%- if show['network']['interface'] %}true{%- else %}false{%- endif %} -{%- for i in show['network']['interface'] %} -# Show stats for Interface {{ i }} -Interface{{ loop.index0 }}={{ i }} -{%- if show['network']['interface'][i]['alias'] %} -# Interface alias name to display [default: ] -Alias{{ loop.index0 }}={{ show['network']['interface'][i]['alias'] }} -{%- endif %} -{%- endfor %} - -# Units to display [default: byte; legal: byte, bit, packet] -{%- if show['network']['units'] == 'bps' %} -unit=bit -{%- elif show['network']['units'] == 'Bps'%} -unit=byte -{%- elif show['network']['units'] == 'pps' %} -unit=packet -{%- else %} -unit=bit -{%- endif %} -# add screen with transferred traffic -#transfer=TRUE -{%- endif %} {# if show['network'] #} - -{%- else %}{# if show #} -{# Turn off sections that default active #} - -[CPU] -Active=false - -[Memory] -Active=false - -[Load] -Active=false - -[TimeDate] -Active=false -{%- endif %}{# if show #} - -[Battery] -# Show screen -Active=false - -[About] -# Show screen -Active=false - -[OldTime] -# Show screen -Active=false -# time format [default: %H:%M:%S; legal: see strftime(3)] -TimeFormat="%H:%M:%S" -# date format [default: %x; legal: see strftime(3)] -DateFormat="%x" -# 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 - -# EOF diff --git a/interface-definitions/system-display.xml.in b/interface-definitions/system-display.xml.in deleted file mode 100644 index fbd897996..000000000 --- a/interface-definitions/system-display.xml.in +++ /dev/null @@ -1,235 +0,0 @@ - - - - - - - - System display LCD/VFD/LED - 400 - - - - - - Model of the display attached to this system [REQUIRED] - - sdec ezio test - - - (sdec|ezio|test) - - Invalid system display model - - sdec - Display model for Lanner, Watchguard, Nexcom NSA, Sophos UTM appliances with built-in SDEC LCD - - - ezio - Display model for Portwell, Caswell appliances with built-in EZIO-100 or EZIO-300 LCD - - - test - Test model for USB CrystalFonz CF533 - - - - - - - Disable sytem display - - - - - - Select the screens for the system display [REQUIRED] - - - - - - - Select host screens for the system display - - cpu cpu-all cpu-hist disk load-hist memory proc uptime - - - (cpu|cpu-all|cpu-hist|disk|load-hist|memory|proc|uptime) - - Invalid host screen - - cpu - Detailed CPU usage - - - cpu-all - CPU usage overview (one line per CPU) - - - cpu-hist - CPU usage histogram - - - disk - File systems fill level - - - load-hist - Load histogram - - - memory - Memory and swap usage - - - proc - Top processes by size - - - uptime - System uptime - - - - - - - Network settings for system display - - - - - - Show network traffic on the system display [Max 3 interfaces] - - - - - - - - Interface alias - - [A-Za-z0-9]{1,10} - - Invalid alias, must be 1 to 10 char or digit - - - - - - - - Unit for network details - - bps Bps pps - - - (bps|Bps|pps) - - Invalid network detail unit - - bps - Bit(s) per second - - - Bps - Byte(s) per second - - - pps - Packet(s) per second - - - - - - - - - - Show a clock on the system display - - big mini date-time - - - (big|mini|date-time) - - Invalid clock format - - big - Multi-line clock - - - mini - Minimal clock - - - date-time - Clock with Date and Time - - - - - - - Screen title to show on the system display - - [A-Za-z0-9]{1,16} - - Invalid title, must be 1 to 16 char or digit - - - - - - - - Time in sec to show each screen on the system display - - 1-30 - Numer of seconds - - - - - - - - - - Message to show when system display first starts - - .{1,16} - - Hello message must be 1 to 16 char - - - - - - Message to show when system display stops - - .{1,16} - - Bye message must be 1 to 16 char - - - - - - - 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 @@ + + + + + + + System LCD display + 100 + + + + + Model of the display attached to this system [REQUIRED] + + CFA-533 CFA-631 CFA-633 CFA-635 + + + CFA-533 + Crystalfontz CFA-533 + + + CFA-631 + Crystalfontz CFA-631 + + + CFA-633 + Crystalfontz CFA-633 + + + CFA-635 + Crystalfontz CFA-635 + + + ^(CFA-533|CFA-631|CFA-633|CFA-635)$ + + + + + + Physical device used by LCD display + + + + + + ttySXX + TTY device name, regular serial port + + + usbNbXpY + TTY device name, USB based + + + ^(ttyS[0-9]+|usb[0-9]+b.*)$ + + + + + + + + diff --git a/python/vyos/util.py b/python/vyos/util.py index 7078762df..c07fef599 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -661,3 +661,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/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/system-display.py b/src/conf_mode/system-display.py deleted file mode 100755 index 3eafc30c0..000000000 --- a/src/conf_mode/system-display.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020 Francois Mertz fireboxled at gmail.com -# -# 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 . - -import os - -from sys import exit - -from vyos.config import Config -from vyos import ConfigError -from vyos.util import run -from vyos.template import render - -from vyos import airbag -airbag.enable() - -def get_config(): - conf = Config() - base = ['system', 'display'] - display = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # Return a (possibly empty) configuration dictionary - return display - -def verify(config_dict): - if not config_dict: - return None - - if 'model' not in config_dict: - raise ConfigError('Display model is [REQUIRED]') - - if ( 'show' not in config_dict - or ( 'clock' not in config_dict['show'] - and 'network' not in config_dict['show'] - and 'host' not in config_dict['show'] - ) - ): - raise ConfigError('Display show must have a clock, host or network') - - if ( 'network' in config_dict['show'] - and 'interface' not in config_dict['show']['network'] - ): - raise ConfigError('Display show network must have an interface') - - if ( 'network' in config_dict['show'] - and 'interface' in config_dict['show']['network'] - and len(config_dict['show']['network']['interface']) > 3 - ): - raise ConfigError('Display show network cannot have > 3 interfaces') - - return None - -def generate(config_dict): - if not config_dict: - return None - # Render config file for daemon LCDd - render('/run/LCDd/LCDd.lo.conf', 'system-display/LCDd.conf.tmpl', config_dict) - # Render config file for client lcdproc - render('/run/lcdproc/lcdproc.lo.conf', 'system-display/lcdproc.conf.tmpl', config_dict) - - return None - -def apply(config_dict): - # Stop client - run('systemctl stop lcdproc@lo.service') - - if not config_dict or 'disabled' in config_dict: - # Stop server - run('systemctl stop LCDd@lo.service') - return None - - # Restart server - run('systemctl restart LCDd@lo.service') - # Start client - run('systemctl start lcdproc@lo.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/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 +# +# 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 . + +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/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/systemd/LCDd@.service b/src/systemd/LCDd@.service deleted file mode 100644 index a4604cf21..000000000 --- a/src/systemd/LCDd@.service +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=LCD display daemon on %I -Documentation=man:LCDd(8) http://www.lcdproc.org/ - -[Service] -User=root -ExecStart=/usr/sbin/LCDd -s 1 -f -c /run/LCDd/LCDd.%I.conf - -[Install] -WantedBy=multi-user.target \ No newline at end of file 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 diff --git a/src/systemd/lcdproc@.service b/src/systemd/lcdproc@.service deleted file mode 100644 index 9a1723dba..000000000 --- a/src/systemd/lcdproc@.service +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=LCDproc system status information viewer on %I -Documentation=man:lcdproc(8) http://www.lcdproc.org/ - -[Service] -User=root -ExecStart=/usr/bin/lcdproc -f -c /run/lcdproc/lcdproc.%I.conf - -[Install] -WantedBy=multi-user.target \ No newline at end of file -- cgit v1.2.3 From b8aca00fc5454f3afdb690ca7eb30e49fac48848 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 15 Aug 2020 17:07:14 +0200 Subject: pppoe-server: T2796: at least one server interface must be defined Add missing verify() check that at least one interface must be defined to listen to PPPoE data. --- src/conf_mode/service_pppoe-server.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') 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']: -- cgit v1.2.3 From 1e44607c15002e111bf65573207877cc87f13ab7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 15 Aug 2020 19:17:35 +0200 Subject: pseudo-ethernet: T2800: source-interface must not be member of a bridge --- src/conf_mode/interfaces-pseudo-ethernet.py | 30 +++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) (limited to 'src') 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() -- cgit v1.2.3 From 910fc06cfd4160705f12d890ca6a6b8436326a67 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 15 Aug 2020 22:54:23 +0200 Subject: dhclient: T2277: retrieve VRF inform from sysfs rather then iproute2 --- src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'src') 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 60e001af7..1e102c2d7 100644 --- a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper +++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper @@ -20,7 +20,7 @@ function iptovtysh () { local VTYSH_NETADDR="" local VTYSH_GATEWAY="" local VTYSH_DEV="" - local VTYSH_VRF_NAME=$(ip -d link show dev $interface | grep $interface | awk '{print $9}') + local VTYSH_VRF_NAME=$(basename /sys/class/net/eth1/upper_* | sed -e 's/upper_//') # convert default route to 0.0.0.0/0 if [ "$4" == "default" ] ; then VTYSH_NETADDR="0.0.0.0/0" @@ -42,12 +42,11 @@ function iptovtysh () { VTYSH_DEV=$8 fi - # check if vrf is present - if [ $(ip -d link show dev $interface | grep vrf | wc -l) -eq 0 ]; then - VTYSH_CMD="ip route $VTYSH_NETADDR $VTYSH_GATEWAY $VTYSH_DEV tag $VTYSH_TAG $VTYSH_DISTANCE" - elif [ $(ip -d link show dev $interface | grep vrf | wc -l) -eq 1 ]; then - VTYSH_CMD="ip route $VTYSH_NETADDR $VTYSH_GATEWAY $VTYSH_DEV tag $VTYSH_TAG $VTYSH_DISTANCE vrf $VTYSH_VRF_NAME" + # Add route to VRF routing table + 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 -- cgit v1.2.3 From 3104c0ea8284ac5039e1dbab4a3ccd193e748ede Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 16 Aug 2020 12:37:50 +0200 Subject: ping: op-mode: T2762: always run in default VRF unless defined otherwise When connected to VyOS by SSH through any VRF, every command is executed in the VRF context thus e.g. ping will run in VRF context but no VRF was defined on the CLI. ping should always run in the default VRF no matter where it is executed, unless a VRF instance is explicitly defined by CLI. --- src/op_mode/ping.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src') 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': '', - '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: -- cgit v1.2.3 From b17b8a0129f42c757c1e9a740664406e8da3dd42 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 16 Aug 2020 14:27:03 +0200 Subject: dhclient: T2277: remove VRF route when DHCP client exits --- src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper | 2 +- src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'src') 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 1e102c2d7..d1161e704 100644 --- a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper +++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper @@ -20,7 +20,6 @@ function iptovtysh () { local VTYSH_NETADDR="" local VTYSH_GATEWAY="" local VTYSH_DEV="" - local VTYSH_VRF_NAME=$(basename /sys/class/net/eth1/upper_* | sed -e 's/upper_//') # convert default route to 0.0.0.0/0 if [ "$4" == "default" ] ; then VTYSH_NETADDR="0.0.0.0/0" @@ -43,6 +42,7 @@ function iptovtysh () { fi # 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 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 -- cgit v1.2.3 From b1675e420479cd6b582c66c0902d6e326340658f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 18 Aug 2020 16:40:51 +0200 Subject: lcd: T2564: add support for Lanner, Watchguard, Symantec boxes --- data/templates/lcd/LCDd.conf.tmpl | 12 ++++++++++-- interface-definitions/system-lcd.xml.in | 16 ++++++++++------ src/conf_mode/system_lcd.py | 4 ++++ src/systemd/lcdproc.service | 4 ++-- 4 files changed, 26 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/data/templates/lcd/LCDd.conf.tmpl b/data/templates/lcd/LCDd.conf.tmpl index da749d04a..bde177b6a 100644 --- a/data/templates/lcd/LCDd.conf.tmpl +++ b/data/templates/lcd/LCDd.conf.tmpl @@ -48,8 +48,12 @@ DriverPath=/usr/lib/x86_64-linux-gnu/lcdproc/ # sed1520, serialPOS, serialVFD, shuttleVFD, sli, stv5730, svga, t6963, # text, tyan, ula200, vlsys_m428, xosd, yard2LCD -{% if model is defined and model.startswith('CFA-') %} +{% if model is defined %} +{% if model.startswith('cfa-') %} Driver=CFontzPacket +{% elif model == 'sdec' %} +Driver=sdeclcd +{% endif %} {% endif %} # Tells the driver to bind to the given interface. [default: 127.0.0.1] @@ -112,7 +116,7 @@ Heartbeat=off TitleSpeed=10 {% if model is defined and model is not none %} -{% if model.startswith('CFA-') %} +{% if model.startswith('cfa-') %} ## CrystalFontz packet driver (for CFA533, CFA631, CFA633 & CFA635) ## [CFontzPacket] Model={{ model.split('-')[1] }} @@ -122,5 +126,9 @@ Brightness=500 OffBrightness=50 Reboot=yes USB=yes +{% elif model == 'sdec' %} +## SDEC driver for Lanner, Watchguard, Sophos sppliances ## +[sdeclcd] +# No options {% endif %} {% endif %} diff --git a/interface-definitions/system-lcd.xml.in b/interface-definitions/system-lcd.xml.in index ad59acb6b..36116ae1b 100644 --- a/interface-definitions/system-lcd.xml.in +++ b/interface-definitions/system-lcd.xml.in @@ -12,26 +12,30 @@ Model of the display attached to this system [REQUIRED] - CFA-533 CFA-631 CFA-633 CFA-635 + cfa-533 cfa-631 cfa-633 cfa-635 sdec - CFA-533 + cfa-533 Crystalfontz CFA-533 - CFA-631 + cfa-631 Crystalfontz CFA-631 - CFA-633 + cfa-633 Crystalfontz CFA-633 - CFA-635 + cfa-635 Crystalfontz CFA-635 + + sdec + Lanner, Watchguard, Nexcom NSA, Sophos UTM appliances + - ^(CFA-533|CFA-631|CFA-633|CFA-635)$ + ^(cfa-533|cfa-631|cfa-633|cfa-635|sdec)$ diff --git a/src/conf_mode/system_lcd.py b/src/conf_mode/system_lcd.py index 0ad1318f0..31a09252d 100755 --- a/src/conf_mode/system_lcd.py +++ b/src/conf_mode/system_lcd.py @@ -41,6 +41,10 @@ def verify(lcd): if not lcd: return None + if 'model' in lcd and lcd['model'] in ['sdec']: + # This is a fixed LCD display, no device needed - bail out early + return None + if not {'device', 'model'} <= set(lcd): raise ConfigError('Both device and driver must be set!') diff --git a/src/systemd/lcdproc.service b/src/systemd/lcdproc.service index 5aa99ec78..ef717667a 100644 --- a/src/systemd/lcdproc.service +++ b/src/systemd/lcdproc.service @@ -1,8 +1,8 @@ [Unit] Description=LCDproc system status information viewer on %I Documentation=man:lcdproc(8) http://www.lcdproc.org/ -After=vyos-router.service -After=LCDd.service +After=vyos-router.service LCDd.service +Requires=LCDd.service [Service] User=root -- cgit v1.2.3 From 865cba395223afaa263ff7581fb53ee509f36266 Mon Sep 17 00:00:00 2001 From: Eshenko Dmitriy Date: Wed, 19 Aug 2020 00:37:00 +0300 Subject: anyconnect: T2036: add anyconnect VPN support --- data/templates/ocserv/ocserv_config.tmpl | 82 +++++++ data/templates/ocserv/ocserv_passwd.tmpl | 6 + data/templates/ocserv/radius_conf.tmpl | 22 ++ data/templates/ocserv/radius_servers.tmpl | 7 + debian/control | 1 + interface-definitions/vpn_anyconnect.xml.in | 258 +++++++++++++++++++++ op-mode-definitions/anyconnect.xml | 20 ++ src/conf_mode/vpn_anyconnect.py | 132 +++++++++++ .../systemd/system/ocserv.service.d/override.conf | 14 ++ src/op_mode/anyconnect-control.py | 67 ++++++ 10 files changed, 609 insertions(+) create mode 100644 data/templates/ocserv/ocserv_config.tmpl create mode 100644 data/templates/ocserv/ocserv_passwd.tmpl create mode 100644 data/templates/ocserv/radius_conf.tmpl create mode 100644 data/templates/ocserv/radius_servers.tmpl create mode 100644 interface-definitions/vpn_anyconnect.xml.in create mode 100644 op-mode-definitions/anyconnect.xml create mode 100755 src/conf_mode/vpn_anyconnect.py create mode 100644 src/etc/systemd/system/ocserv.service.d/override.conf create mode 100755 src/op_mode/anyconnect-control.py (limited to 'src') diff --git a/data/templates/ocserv/ocserv_config.tmpl b/data/templates/ocserv/ocserv_config.tmpl new file mode 100644 index 000000000..6aaeff693 --- /dev/null +++ b/data/templates/ocserv/ocserv_config.tmpl @@ -0,0 +1,82 @@ +### generated by vpn_anyconnect.py ### + +tcp-port = {{ listen_ports.tcp }} +udp-port = {{ listen_ports.udp }} + +run-as-user = nobody +run-as-group = daemon + +{% if "radius" in authentication.mode %} +auth = "radius [config=/run/ocserv/radiusclient.conf]" +{% else %} +auth = "plain[/run/ocserv/ocpasswd]" +{% endif %} + +{% if ssl.cert_file %} +server-cert = {{ ssl.cert_file }} +{% endif %} + +{% if ssl.key_file %} +server-key = {{ ssl.key_file }} +{% endif %} + +{% if ssl.ca_cert_file %} +ca-cert = {{ ssl.ca_cert_file }} +{% endif %} + +socket-file = /run/ocserv/ocserv.socket +occtl-socket-file = /run/ocserv/occtl.socket +use-occtl = true +isolate-workers = true +keepalive = 300 +dpd = 60 +mobile-dpd = 300 +switch-to-tcp-timeout = 30 +tls-priorities = "NORMAL:%SERVER_PRECEDENCE:%COMPAT:-RSA:-VERS-SSL3.0:-ARCFOUR-128" +auth-timeout = 240 +idle-timeout = 1200 +mobile-idle-timeout = 1800 +min-reauth-time = 3 +cookie-timeout = 300 +rekey-method = ssl +try-mtu-discovery = true +cisco-client-compat = true +dtls-legacy = true + + +# The name to use for the tun device +device = sslvpn + +# An alternative way of specifying the network: +{% if network_settings %} +# DNS settings +{% if network_settings.name_server is string %} +dns = {{ network_settings.name_server }} +{% else %} +{% for dns in network_settings.name_server %} +dns = {{ dns }} +{% endfor %} +{% endif %} +# IPv4 network pool +{% if network_settings.client_ip_settings %} +{% if network_settings.client_ip_settings.subnet %} +ipv4-network = {{ network_settings.client_ip_settings.subnet }} +{% endif %} +{% endif %} +# IPv6 network pool +{% if network_settings.client_ipv6_pool %} +{% if network_settings.client_ipv6_pool.prefix %} +ipv6-network = {{ network_settings.client_ipv6_pool.prefix }} +ipv6-subnet-prefix = {{ network_settings.client_ipv6_pool.mask }} +{% endif %} +{% endif %} +{% endif %} + +{% if network_settings.push_route is string %} +route = {{ network_settings.push_route }} +{% else %} +{% for route in network_settings.push_route %} +route = {{ route }} +{% endfor %} +{% endif %} + diff --git a/data/templates/ocserv/ocserv_passwd.tmpl b/data/templates/ocserv/ocserv_passwd.tmpl new file mode 100644 index 000000000..ffadb4860 --- /dev/null +++ b/data/templates/ocserv/ocserv_passwd.tmpl @@ -0,0 +1,6 @@ +#:: +{% for user in username if username is defined %} +{% if not "disable" in username[user] %} +{{ user }}:*:{{ username[user].hash }} +{% endif %} +{% endfor %} \ No newline at end of file diff --git a/data/templates/ocserv/radius_conf.tmpl b/data/templates/ocserv/radius_conf.tmpl new file mode 100644 index 000000000..2d19306a0 --- /dev/null +++ b/data/templates/ocserv/radius_conf.tmpl @@ -0,0 +1,22 @@ +### generated by cpn_anyconnect.py ### +nas-identifier VyOS +{% for srv in server %} +{% if not "disable" in server[srv] %} +{% if "port" in server[srv] %} +authserver {{ srv }}:{{server[srv]["port"]}} +{% else %} +authserver {{ srv }} +{% endif %} +{% endif %} +{% endfor %} +radius_timeout {{ timeout }} +{% if source_address %} +bindaddr {{ source_address }} +{% else %} +bindaddr * +{% endif %} +servers /run/ocserv/radius_servers +dictionary /etc/radcli/dictionary +default_realm +radius_retries 3 +# \ No newline at end of file diff --git a/data/templates/ocserv/radius_servers.tmpl b/data/templates/ocserv/radius_servers.tmpl new file mode 100644 index 000000000..ba21fa074 --- /dev/null +++ b/data/templates/ocserv/radius_servers.tmpl @@ -0,0 +1,7 @@ +### generated by cpn_anyconnect.py ### +# server key +{% for srv in server %} +{% if not "disable" in server[srv] %} +{{ srv }} {{ server[srv].key }} +{% endif %} +{% endfor %} diff --git a/debian/control b/debian/control index 34ba4be8e..520401d57 100644 --- a/debian/control +++ b/debian/control @@ -107,6 +107,7 @@ Depends: python3, libatomic1, fastnetmon, libndp-tools, + ocserv, tcptraceroute Description: VyOS configuration scripts and data VyOS configuration scripts, interface definitions, and everything diff --git a/interface-definitions/vpn_anyconnect.xml.in b/interface-definitions/vpn_anyconnect.xml.in new file mode 100644 index 000000000..e74326986 --- /dev/null +++ b/interface-definitions/vpn_anyconnect.xml.in @@ -0,0 +1,258 @@ + + + + + + + SSL VPN AnyConnect + 901 + + + + + Authentication for remote access SSL VPN Server + + + + + Authentication mode used by this server + + local + Use local username/password configuration + + + radius + Use RADIUS server for user autentication + + + (local|radius) + + + local radius + + + + + + Local user authentication for SSL VPN server + + + + + User name for authentication + + + + + Option to disable a SSL VPN Server user + + + + + + Password for authentication + + + + + + + #include + + + + + Session timeout + + 1-30 + Session timeout in seconds (default: 2) + + + + + Timeout must be between 1 and 30 seconds + + 2 + + + + + + + + SSL Certificate, SSL Key and CA (/config/auth) + + + + + tcp port number to accept connections (default: 443) + + 1-65535 + Numeric IP port (default: 443) + + + + + + 443 + + + + udp port number to accept connections (default: 443) + + 1-65535 + Numeric IP port (default: 443) + + + + + + 443 + + + + + + SSL Certificate, SSL Key and CA (/config/auth) + + + + + Certificate Authority certificate + + + + + file + File in /config/auth directory + + + + + + + + + Server Certificate + + file + File in /config/auth directory + + + + + + + + + Privat Key of the Server Certificate + + file + File in /config/auth directory + + + + + + + + + + + Network settings + + + + + Route to be pushed to the client + + ipv4net + IPv4 network and prefix length + + + ipv6net + IPv6 network and prefix length + + + + + + + + + + Client IP pools settings + + + + + Client IP subnet (CIDR notation) + + ipv4net + IPv4 address and prefix length + + + + + Not a valid CIDR formatted prefix + + + + + + + Pool of client IPv6 addresses + + + + + Pool of addresses used to assign to clients + + ipv6net + IPv6 address and prefix length + + + + + + + + + Prefix length used for individual client + + <48-128> + Client prefix length (default: 64) + + + + + + 64 + + + + + + Domain Name Server (DNS) propagated to client + + ipv4 + Domain Name Server (DNS) IPv4 address + + + ipv6 + Domain Name Server (DNS) IPv6 address + + + + + + + + + + + + + + + diff --git a/op-mode-definitions/anyconnect.xml b/op-mode-definitions/anyconnect.xml new file mode 100644 index 000000000..7e8cdd35b --- /dev/null +++ b/op-mode-definitions/anyconnect.xml @@ -0,0 +1,20 @@ + + + + + + + show anyconnect-server information + + + + + Show active anyconnect server sessions + + ${vyos_op_scripts_dir}/anyconnect-control.py --action="show_sessions" + + + + + + diff --git a/src/conf_mode/vpn_anyconnect.py b/src/conf_mode/vpn_anyconnect.py new file mode 100755 index 000000000..45c06bffa --- /dev/null +++ b/src/conf_mode/vpn_anyconnect.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2020 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 . + +import os +from sys import exit + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.xml import defaults +from vyos.template import render +from vyos.util import call +from vyos import ConfigError +from crypt import crypt, mksalt, METHOD_SHA512 + +from vyos import airbag +airbag.enable() + +cfg_dir = '/run/ocserv' +ocserv_conf = cfg_dir + '/ocserv.conf' +ocserv_passwd = cfg_dir + '/ocpasswd' +radius_cfg = cfg_dir + '/radiusclient.conf' +radius_servers = cfg_dir + '/radius_servers' + + +# Generate hash from user cleartext password +def get_hash(password): + return crypt(password, mksalt(METHOD_SHA512)) + + +def get_config(): + conf = Config() + base = ['vpn', 'anyconnect'] + ocserv = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + default_values = defaults(base) + ocserv = dict_merge(default_values, ocserv) + return ocserv + + +def verify(ocserv): + if ocserv is None: + return None + + # Check authentication + if "authentication" in ocserv: + if "mode" in ocserv["authentication"]: + if "local" in ocserv["authentication"]["mode"]: + if not ocserv["authentication"]["local_users"] or not ocserv["authentication"]["local_users"]["username"]: + raise ConfigError('Anyconect mode local required at leat one user') + else: + for user in ocserv["authentication"]["local_users"]["username"]: + if not "password" in ocserv["authentication"]["local_users"]["username"][user]: + raise ConfigError(f'password required for user {user}') + else: + raise ConfigError('anyconnect authentication mode required') + else: + raise ConfigError('anyconnect authentication credentials required') + + # Check ssl + if "ssl" in ocserv: + req_cert = ['ca_cert_file', 'cert_file', 'key_file'] + for cert in req_cert: + if not cert in ocserv["ssl"]: + raise ConfigError('anyconnect ssl {0} required'.format(cert.replace('_', '-'))) + else: + raise ConfigError('anyconnect ssl required') + + # Check network settings + if "network_settings" in ocserv: + if "push_route" in ocserv["network_settings"]: + # Replace default route + if "0.0.0.0/0" in ocserv["network_settings"]["push_route"]: + ocserv["network_settings"]["push_route"].remove("0.0.0.0/0") + ocserv["network_settings"]["push_route"].append("default") + else: + ocserv["network_settings"]["push_route"] = "default" + else: + raise ConfigError('anyconnect network settings required') + + +def generate(ocserv): + if not ocserv: + return None + + if "radius" in ocserv["authentication"]["mode"]: + # Render radius client configuration + render(radius_cfg, 'ocserv/radius_conf.tmpl', ocserv["authentication"]["radius"], trim_blocks=True) + # Render radius servers + render(radius_servers, 'ocserv/radius_servers.tmpl', ocserv["authentication"]["radius"], trim_blocks=True) + else: + if "local_users" in ocserv["authentication"]: + for user in ocserv["authentication"]["local_users"]["username"]: + ocserv["authentication"]["local_users"]["username"][user]["hash"] = get_hash(ocserv["authentication"]["local_users"]["username"][user]["password"]) + # Render local users + render(ocserv_passwd, 'ocserv/ocserv_passwd.tmpl', ocserv["authentication"]["local_users"], trim_blocks=True) + + # Render config + render(ocserv_conf, 'ocserv/ocserv_config.tmpl', ocserv, trim_blocks=True) + + + +def apply(ocserv): + if not ocserv: + call('systemctl stop ocserv.service') + for file in [ocserv_conf, ocserv_passwd]: + if os.path.exists(file): + os.unlink(file) + else: + call('systemctl restart ocserv.service') + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/etc/systemd/system/ocserv.service.d/override.conf b/src/etc/systemd/system/ocserv.service.d/override.conf new file mode 100644 index 000000000..89dbb153f --- /dev/null +++ b/src/etc/systemd/system/ocserv.service.d/override.conf @@ -0,0 +1,14 @@ +[Unit] +RequiresMountsFor=/run +ConditionPathExists=/run/ocserv/ocserv.conf +After= +After=vyos-router.service +After=dbus.service + +[Service] +WorkingDirectory=/run/ocserv +PIDFile= +PIDFile=/run/ocserv/ocserv.pid +ExecStart= +ExecStart=/usr/sbin/ocserv --foreground --pid-file /run/ocserv/ocserv.pid --config /run/ocserv/ocserv.conf + diff --git a/src/op_mode/anyconnect-control.py b/src/op_mode/anyconnect-control.py new file mode 100755 index 000000000..6382016b7 --- /dev/null +++ b/src/op_mode/anyconnect-control.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 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 . + +import sys +import argparse +import json + +from vyos.config import Config +from vyos.util import popen, run, DEVNULL +from tabulate import tabulate + +occtl = '/usr/bin/occtl' +occtl_socket = '/run/ocserv/occtl.socket' + +def show_sessions(): + out, code = popen("sudo {0} -j -s {1} show users".format(occtl, occtl_socket),stderr=DEVNULL) + if code: + sys.exit('Cannot get anyconnect users information') + else: + headers = ["interface", "username", "ip", "remote IP", "RX", "TX", "state", "uptime"] + sessions = json.loads(out) + ses_list = [] + for ses in sessions: + ses_list.append([ses["Device"], ses["Username"], ses["IPv4"], ses["Remote IP"], ses["_RX"], ses["_TX"], ses["State"], ses["_Connected at"]]) + if len(ses_list) > 0: + print(tabulate(ses_list, headers)) + else: + print("No active anyconnect sessions") + +def is_ocserv_configured(): + if not Config().exists_effective('vpn anyconnect'): + print("vpn anyconnect server is not configured") + sys.exit(1) + +def main(): + #parese args + parser = argparse.ArgumentParser() + parser.add_argument('--action', help='Control action', required=True) + parser.add_argument('--selector', help='Selector username|ifname|sid', required=False) + parser.add_argument('--target', help='Target must contain username|ifname|sid', required=False) + args = parser.parse_args() + + + # Check is IPoE configured + is_ocserv_configured() + + if args.action == "restart": + run("systemctl restart ocserv") + sys.exit(0) + elif args.action == "show_sessions": + show_sessions() + +if __name__ == '__main__': + main() -- cgit v1.2.3 From 9627888b89ddb712eec06a19bcc149367e8c3036 Mon Sep 17 00:00:00 2001 From: DmitriyEshenko Date: Wed, 19 Aug 2020 10:16:00 +0000 Subject: anyconnect: T2811: Return None if anyconnect not configured --- src/conf_mode/vpn_anyconnect.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/conf_mode/vpn_anyconnect.py b/src/conf_mode/vpn_anyconnect.py index 45c06bffa..158e1a117 100755 --- a/src/conf_mode/vpn_anyconnect.py +++ b/src/conf_mode/vpn_anyconnect.py @@ -43,6 +43,9 @@ def get_hash(password): def get_config(): conf = Config() base = ['vpn', 'anyconnect'] + if not conf.exists(base): + return None + ocserv = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) default_values = defaults(base) ocserv = dict_merge(default_values, ocserv) -- cgit v1.2.3 From 509f347d0858e37de9fdeaa1a7aeeb6888fb4b7b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 19 Aug 2020 18:30:33 +0200 Subject: lldp: op-mode: convert data processing from XML to JSON --- src/op_mode/lldp_op.py | 180 ++++++++++++++++++------------------------------- 1 file changed, 66 insertions(+), 114 deletions(-) (limited to 'src') diff --git a/src/op_mode/lldp_op.py b/src/op_mode/lldp_op.py index 0df6749aa..06958c605 100755 --- a/src/op_mode/lldp_op.py +++ b/src/op_mode/lldp_op.py @@ -16,10 +16,10 @@ import argparse import jinja2 +import json from sys import exit from tabulate import tabulate -from xml.dom import minidom from vyos.util import cmd from vyos.config import Config @@ -35,106 +35,61 @@ lldp_out = """Capability Codes: R - Router, B - Bridge, W - Wlan r - Repeater, S Device ID Local Proto Cap Platform Port ID --------- ----- ----- --- -------- ------- -{% for n in neighbors -%} -{{ "%-25s" | format(n.chassis) }} {{ "%-9s" | format(n.interface) }} {{ "%-6s" | format(n.proto) }} {{ "%-5s" | format(n.cap) }} {{ "%-20s" | format(n.platform) }} {{ n.port }} -{% endfor -%} +{% for neighbor in neighbors %} +{% for local_if, info in neighbor.items() %} +{{ "%-25s" | format(info.chassis) }} {{ "%-9s" | format(local_if) }} {{ "%-6s" | format(info.proto) }} {{ "%-5s" | format(info.capabilities) }} {{ "%-20s" | format(info.platform[:18]) }} {{ info.remote_if }} +{% endfor %} +{% endfor %} """ def get_neighbors(): - return cmd('/usr/sbin/lldpcli -f xml show neighbors') - -def extract_neighbor(neighbor): - """ - Extract LLDP neighbor information from XML document passed as param neighbor - - - - - 00:50:56:9d:a6:11 - VyOS - VyOS unknown - 172.18.254.203 - fe80::250:56ff:fe9d:a611 - - - - - - - 00:50:56:9d:a6:11 - eth0 - 120 - - 10GigBaseCX4 - X copper over 8 pair 100-Ohm balanced cable - - - eth0.203 - - Network Connectivity Device - - - - - - - - None - 4.19.54-amd64-vyos - 6.00 - VMware-42 1d cf 87 ab 7f da 7e-3 - VMware, Inc. - VMware Virtual Platform - No Asset Tag - - - - - """ - - device = { - 'interface' : neighbor.getAttribute('name'), - 'chassis' : '', - 'proto' : neighbor.getAttribute('via'), - 'descr' : '', - 'cap' : '', - 'platform' : '', - 'port' : '' - } - - # first change to node and then retrieve and - chassis = neighbor.getElementsByTagName('chassis') - device['chassis'] = chassis[0].getElementsByTagName('name')[0].firstChild.data - # Cisco IOS comes with a ',' remove character .... - device['platform'] = chassis[0].getElementsByTagName('descr')[0].firstChild.data[:20].replace(',',' ') - - # extract capabilities - for capability in chassis[0].getElementsByTagName('capability'): - # we are only interested in enabled capabilities ... - if capability.getAttribute('enabled') == "on": - if capability.getAttribute('type') == "Router": - device['cap'] += 'R' - elif capability.getAttribute('type') == "Bridge": - device['cap'] += 'B' - elif capability.getAttribute('type') == "Wlan": - device['cap'] += 'W' - elif capability.getAttribute('type') == "Station": - device['cap'] += 'S' - elif capability.getAttribute('type') == "Repeater": - device['cap'] += 'r' - elif capability.getAttribute('type') == "Telephone": - device['cap'] += 'T' - elif capability.getAttribute('type') == "Docsis": - device['cap'] += 'D' - elif capability.getAttribute('type') == "Other": - device['cap'] += 'O' - - # first change to node and then retrieve - port = neighbor.getElementsByTagName('port') - port = port[0].getElementsByTagName('descr')[0].firstChild.data - device['port'] = port - - - return device + return cmd('/usr/sbin/lldpcli -f json show neighbors') + +def parse_data(data): + output = [] + for tmp in data: + for local_if, values in tmp.items(): + for chassis, c_value in values.get('chassis', {}).items(): + capabilities = c_value['capability'] + if isinstance(capabilities, dict): + capabilities = [capabilities] + + cap = '' + for capability in capabilities: + if capability['enabled']: + if capability['type'] == 'Router': + cap += 'R' + if capability['type'] == 'Bridge': + cap += 'B' + if capability['type'] == 'Wlan': + cap += 'W' + if capability['type'] == 'Station': + cap += 'S' + if capability['type'] == 'Repeater': + cap += 'r' + if capability['type'] == 'Telephone': + cap += 'T' + if capability['type'] == 'Docsis': + cap += 'D' + if capability['type'] == 'Other': + cap += 'O' + + + remote_if = 'Unknown' + if 'descr' in values.get('port', {}): + remote_if = values.get('port', {}).get('descr') + elif 'id' in values.get('port', {}): + remote_if = values.get('port', {}).get('id').get('value', 'Unknown') + + output.append({local_if: {'chassis': chassis, + 'remote_if': remote_if, + 'proto': values.get('via','Unknown'), + 'platform': c_value.get('descr', 'Unknown'), + 'capabilities': cap}}) + + + output = {'neighbors': output} + return output if __name__ == '__main__': args = parser.parse_args() @@ -145,29 +100,26 @@ if __name__ == '__main__': print('Service LLDP is not configured') exit(0) - if args.all: - neighbors = minidom.parseString(get_neighbors()) - for neighbor in neighbors.getElementsByTagName('interface'): - tmp['neighbors'].append( extract_neighbor(neighbor) ) - - elif args.detail: - out = cmd('/usr/sbin/lldpctl -f plain') - print(out) + if args.detail: + print(cmd('/usr/sbin/lldpctl -f plain')) exit(0) + elif args.all or args.interface: + tmp = json.loads(get_neighbors()) - elif args.interface: - neighbors = minidom.parseString(get_neighbors()) - for neighbor in neighbors.getElementsByTagName('interface'): - # check if neighbor appeared on proper interface - if neighbor.getAttribute('name') == args.interface: - tmp['neighbors'].append( extract_neighbor(neighbor) ) + if args.all: + neighbors = tmp['lldp']['interface'] + elif args.interface: + neighbors = [] + for neighbor in tmp['lldp']['interface']: + if args.interface in neighbor: + neighbors.append(neighbor) else: parser.print_help() exit(1) - tmpl = jinja2.Template(lldp_out) - config_text = tmpl.render(tmp) + tmpl = jinja2.Template(lldp_out, trim_blocks=True) + config_text = tmpl.render(parse_data(neighbors)) print(config_text) exit(0) -- cgit v1.2.3 From 93c3a4a4f05014914dd2e9353ddd676e20bced1a Mon Sep 17 00:00:00 2001 From: sever-sever Date: Wed, 19 Aug 2020 17:07:14 +0000 Subject: mpls-ldp: T915: Add discovery hello timers --- data/templates/frr/ldpd.frr.tmpl | 12 ++++++++++++ interface-definitions/protocols-mpls.xml.in | 26 +++++++++++++++++++++++++- src/conf_mode/protocols_mpls.py | 24 +++++++++++++++++++++--- 3 files changed, 58 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/data/templates/frr/ldpd.frr.tmpl b/data/templates/frr/ldpd.frr.tmpl index bbff88ae5..dbaa917e8 100644 --- a/data/templates/frr/ldpd.frr.tmpl +++ b/data/templates/frr/ldpd.frr.tmpl @@ -21,6 +21,18 @@ no discovery transport-address {{ old_ldp.d_transp_ipv4 }} {% if ldp.d_transp_ipv4 -%} discovery transport-address {{ ldp.d_transp_ipv4 }} {% endif -%} +{% if old_ldp.hello_holdtime -%} +no discovery hello holdtime {{ old_ldp.hello_holdtime }} +{% endif -%} +{% if ldp.hello_holdtime -%} +discovery hello holdtime {{ ldp.hello_holdtime }} +{% endif -%} +{% if old_ldp.hello_interval -%} +no discovery hello interval {{ old_ldp.hello_interval }} +{% endif -%} +{% if ldp.hello_interval -%} +discovery hello interval {{ ldp.hello_interval }} +{% endif -%} {% for interface in old_ldp.interfaces -%} no interface {{interface}} {% endfor -%} diff --git a/interface-definitions/protocols-mpls.xml.in b/interface-definitions/protocols-mpls.xml.in index 376323855..3e9edbf72 100644 --- a/interface-definitions/protocols-mpls.xml.in +++ b/interface-definitions/protocols-mpls.xml.in @@ -54,6 +54,30 @@ + + + Hello holdtime + + 1-65535 + Time in seconds + + + + + + + + + Hello interval + + 1-65535 + Time in seconds + + + + + + Transport ipv4 address @@ -95,4 +119,4 @@ - \ No newline at end of file + diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index 72208ffa1..bcb16fa04 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# Copyright (C) 2019-2020 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 @@ -38,13 +38,17 @@ def get_config(): 'interfaces' : [], 'neighbors' : {}, 'd_transp_ipv4' : None, - 'd_transp_ipv6' : None + 'd_transp_ipv6' : None, + 'hello_holdtime' : None, + 'hello_interval' : None }, 'ldp' : { 'interfaces' : [], 'neighbors' : {}, 'd_transp_ipv4' : None, - 'd_transp_ipv6' : None + 'd_transp_ipv6' : None, + 'hello_holdtime' : None, + 'hello_interval' : None } } if not (conf.exists('protocols mpls') or conf.exists_effective('protocols mpls')): @@ -61,6 +65,20 @@ def get_config(): if conf.exists('router-id'): mpls_conf['router_id'] = conf.return_value('router-id') + # Get hello holdtime + if conf.exists_effective('discovery hello-holdtime'): + mpls_conf['old_ldp']['hello_holdtime'] = conf.return_effective_value('discovery hello-holdtime') + + if conf.exists('discovery hello-holdtime'): + mpls_conf['ldp']['hello_holdtime'] = conf.return_value('discovery hello-holdtime') + + # Get hello interval + if conf.exists_effective('discovery hello-interval'): + mpls_conf['old_ldp']['hello_interval'] = conf.return_effective_value('discovery hello-interval') + + if conf.exists('discovery hello-interval'): + mpls_conf['ldp']['hello_interval'] = conf.return_value('discovery hello-interval') + # Get discovery transport-ipv4-address if conf.exists_effective('discovery transport-ipv4-address'): mpls_conf['old_ldp']['d_transp_ipv4'] = conf.return_effective_value('discovery transport-ipv4-address') -- cgit v1.2.3 From 0bc94e588fea31888445d182f54803598e82b615 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 19 Aug 2020 23:15:07 -0500 Subject: certbot: T2815: change config dir to /config/auth/letsencrypt --- python/vyos/defaults.py | 3 ++- src/conf_mode/le_cert.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 3062ed31c..9921e3b5f 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -21,7 +21,8 @@ directories = { "current": "/opt/vyatta/etc/config-migrate/current", "migrate": "/opt/vyatta/etc/config-migrate/migrate", "log": "/var/log/vyatta", - "templates": "/usr/share/vyos/templates/" + "templates": "/usr/share/vyos/templates/", + "certbot": "/config/auth/letsencrypt" } cfg_group = 'vyattacfg' diff --git a/src/conf_mode/le_cert.py b/src/conf_mode/le_cert.py index 5b965f95f..755c89966 100755 --- a/src/conf_mode/le_cert.py +++ b/src/conf_mode/le_cert.py @@ -27,6 +27,7 @@ from vyos import airbag airbag.enable() vyos_conf_scripts_dir = vyos.defaults.directories['conf_mode'] +vyos_certbot_dir = vyos.defaults.directories['certbot'] dependencies = [ 'https.py', @@ -45,7 +46,7 @@ def request_certbot(cert): else: domain_flag = '' - certbot_cmd = 'certbot certonly -n --nginx --agree-tos --no-eff-email --expand {0} {1}'.format(email_flag, domain_flag) + certbot_cmd = f'certbot certonly --config-dir {vyos_certbot_dir} -n --nginx --agree-tos --no-eff-email --expand {email_flag} {domain_flag}' cmd(certbot_cmd, raising=ConfigError, -- cgit v1.2.3 From 41c847593551496770c4d08e09e32af638a30f40 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 19 Aug 2020 23:16:35 -0500 Subject: https: T2815: adjust for change in certbot config directory --- data/templates/https/nginx.default.tmpl | 8 ++++---- src/conf_mode/https.py | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/data/templates/https/nginx.default.tmpl b/data/templates/https/nginx.default.tmpl index f4f2c1848..aaa652e09 100644 --- a/data/templates/https/nginx.default.tmpl +++ b/data/templates/https/nginx.default.tmpl @@ -25,10 +25,10 @@ server { {% endfor %} {% if server.certbot %} - ssl_certificate /etc/letsencrypt/live/{{ server.certbot_dir }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ server.certbot_dir }}/privkey.pem; - include /etc/letsencrypt/options-ssl-nginx.conf; - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + ssl_certificate {{ server.certbot_dir }}/live/{{ server.certbot_domain_dir }}/fullchain.pem; + ssl_certificate_key {{ server.certbot_dir }}/live/{{ server.certbot_domain_dir }}/privkey.pem; + include {{ server.certbot_dir }}/options-ssl-nginx.conf; + ssl_dhparam {{ server.certbot_dir }}/ssl-dhparams.pem; {% elif server.vyos_cert %} include {{ server.vyos_cert.conf }}; {% else %} diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index 7acb629bd..3dae0fddb 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -31,6 +31,7 @@ from vyos import airbag airbag.enable() config_file = '/etc/nginx/sites-available/default' +certbot_dir = vyos.defaults.directories['certbot'] default_server_block = { 'id' : '', @@ -86,8 +87,9 @@ def get_config(): if sub_list: for sb in sub_list: sb['certbot'] = True + sb['certbot_dir'] = certbot_dir # certbot organizes certificates by first domain - sb['certbot_dir'] = certbot_domains[0] + sb['certbot_domain_dir'] = certbot_domains[0] api_somewhere = False api_data = {} -- cgit v1.2.3 From 98c2c5e0585e0806099a353de207f392223eff9a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 22 Aug 2020 23:15:29 +0200 Subject: dhcpv6-pd: T2677: optimize CLI interface for PD configuration The current CLI did not support multiple prefix-delegations per interface. Some ISPs only send one /64 to a client per prefix-delegation request, but they allow the customer to request multiple prefixes. The 'dhcpv6-options prefix-delegation' node has been renamed and converted to a tag node named 'dhcpv6-options pd'. The tag node specifies a PD request (>=0). In the past the user needed to know what prefix will be assigned and required to calculate the sla-len by himself. The 'sla-len' node was dropped and is now calculated in the background from the 'dhcpv6-options pd 0 length' node. It is no longer mandatory to supply the 'sla-id' node, if sla-id is not specified it is 'guessed' by counting upwards. Example configuration: ---------------------- ethernet eth1 { address dhcpv6 dhcpv6-options { pd 0 { length 56 interface eth2 { address 1 } } } } This will request a /56 assignment from the ISP and will delegate a /64 network to interface eth2. VyOS will use the interface address ::1 on the delegate interface (eth2) as its local address. --- data/templates/dhcp-client/ipv6.tmpl | 48 +++++++++------- interface-definitions/include/dhcp-options.xml.i | 2 +- interface-definitions/include/dhcpv6-options.xml.i | 40 ++++++-------- python/vyos/configdict.py | 64 ++++++++++++++++++---- src/conf_mode/interfaces-pppoe.py | 4 +- src/migration-scripts/interfaces/11-to-12 | 58 ++++++++++++++++++++ 6 files changed, 157 insertions(+), 59 deletions(-) create mode 100755 src/migration-scripts/interfaces/11-to-12 (limited to 'src') diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.tmpl index 9673f302b..e9285d86b 100644 --- a/data/templates/dhcp-client/ipv6.tmpl +++ b/data/templates/dhcp-client/ipv6.tmpl @@ -8,37 +8,43 @@ interface {{ ifname }} { information-only; {% endif %} {% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} - send ia-na 1; # non-temporary address + send ia-na 0; # non-temporary address {% endif %} -{% if dhcpv6_options is defined and dhcpv6_options.prefix_delegation is defined %} - send ia-pd 2; # prefix delegation +{% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} +{% for pd in dhcpv6_options.pd %} + send ia-pd {{ pd }}; # prefix delegation #{{ pd }} +{% endfor %} {% endif %} }; {% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} -id-assoc na 1 { - # Identity association NA +id-assoc na 0 { + # Identity association for non temporary address }; {% endif %} -{% if dhcpv6_options is defined and dhcpv6_options.prefix_delegation is defined %} -id-assoc pd 2 { -{% if dhcpv6_options.prefix_delegation.length is defined %} - prefix ::/{{ dhcpv6_options.prefix_delegation.length }} infinity; -{% endif %} -{% for interface in dhcpv6_options.prefix_delegation.interface %} +{% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} +{% for pd in dhcpv6_options.pd %} +id-assoc pd {{ pd }} { +{# length got a default value #} + prefix ::/{{ dhcpv6_options.pd[pd].length }} infinity; +{% set sla_len = 64 - dhcpv6_options.pd[pd].length|int %} +{% set count = namespace(value=0) %} +{% for interface in dhcpv6_options.pd[pd].interface if dhcpv6_options.pd[pd].interface is defined %} prefix-interface {{ interface }} { -{% if dhcpv6_options.prefix_delegation.interface[interface].sla_id is defined %} - sla-id {{ dhcpv6_options.prefix_delegation.interface[interface].sla_id }}; -{% endif %} -{% if dhcpv6_options.prefix_delegation.interface[interface].sla_len is defined %} - sla-len {{ dhcpv6_options.prefix_delegation.interface[interface].sla_len }}; -{% endif %} -{% if dhcpv6_options.prefix_delegation.interface[interface].address is defined %} - ifid {{ dhcpv6_options.prefix_delegation.interface[interface].address }}; -{% endif %} + sla-len {{ sla_len }}; +{% if dhcpv6_options.pd[pd].interface[interface].sla_id is defined and dhcpv6_options.pd[pd].interface[interface].sla_id is not none %} + sla-id {{ dhcpv6_options.pd[pd].interface[interface].sla_id }}; +{% else %} + sla-id {{ count.value }}; +{% endif %} +{% if dhcpv6_options.pd[pd].interface[interface].address is defined and dhcpv6_options.pd[pd].interface[interface].address is not none %} + ifid {{ dhcpv6_options.pd[pd].interface[interface].address }}; +{% endif %} }; -{% endfor %} +{% set count.value = count.value + 1 %} +{% endfor %} }; +{% endfor %} {% endif %} diff --git a/interface-definitions/include/dhcp-options.xml.i b/interface-definitions/include/dhcp-options.xml.i index 0f71d9321..9989291fc 100644 --- a/interface-definitions/include/dhcp-options.xml.i +++ b/interface-definitions/include/dhcp-options.xml.i @@ -1,6 +1,6 @@ - DHCP options + DHCP client settings/options diff --git a/interface-definitions/include/dhcpv6-options.xml.i b/interface-definitions/include/dhcpv6-options.xml.i index 98a87dba2..5d088b83d 100644 --- a/interface-definitions/include/dhcpv6-options.xml.i +++ b/interface-definitions/include/dhcpv6-options.xml.i @@ -1,11 +1,24 @@ - DHCPv6 options + DHCPv6 client settings/options - + + + Acquire only config parameters, no address + + + + - DHCPv6 Prefix Delegation Options + DHCPv6 prefix delegation interface statement + + instance number + Prefix delegation instance (>= 0) + + + + @@ -19,6 +32,7 @@ + 64 @@ -52,28 +66,10 @@ - - - Site-Level aggregator (SLA) length - - 0-128 - Length of delegated prefix - - - - - - - - - - Acquire only config parameters, no address - - - + IPv6 "temporary" address diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 010eda45c..c1e93955e 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -93,7 +93,7 @@ def dict_merge(source, destination): tmp = deepcopy(destination) for key, value in source.items(): - if key not in tmp.keys(): + if key not in tmp: tmp[key] = value elif isinstance(source[key], dict): tmp[key] = dict_merge(source[key], tmp[key]) @@ -109,15 +109,15 @@ def T2665_default_dict_cleanup(dict): """ Cleanup default keys for tag nodes https://phabricator.vyos.net/T2665. """ # Cleanup for vif in ['vif', 'vif_s']: - if vif in dict.keys(): - for key in ['ip', 'mtu']: - if key in dict[vif].keys(): + if vif in dict: + for key in ['ip', 'mtu', 'dhcpv6_options']: + if key in dict[vif]: del dict[vif][key] # cleanup VIF-S defaults - if 'vif_c' in dict[vif].keys(): - for key in ['ip', 'mtu']: - if key in dict[vif]['vif_c'].keys(): + if 'vif_c' in dict[vif]: + for key in ['ip', 'mtu', 'dhcpv6_options']: + if key in dict[vif]['vif_c']: del dict[vif]['vif_c'][key] # If there is no vif-c defined and we just cleaned the default # keys - we can clean the entire vif-c dict as it's useless @@ -129,6 +129,21 @@ def T2665_default_dict_cleanup(dict): if not dict[vif]: del dict[vif] + if 'dhcpv6_options' in dict and 'pd' in dict['dhcpv6_options']: + if 'length' in dict['dhcpv6_options']['pd']: + del dict['dhcpv6_options']['pd']['length'] + + # delete empty dicts + if 'dhcpv6_options' in dict: + if 'pd' in dict['dhcpv6_options']: + # test if 'pd' is an empty node so we can remove it + if not dict['dhcpv6_options']['pd']: + del dict['dhcpv6_options']['pd'] + + # test if 'dhcpv6_options' is an empty node so we can remove it + if not dict['dhcpv6_options']: + del dict['dhcpv6_options'] + return dict def leaf_node_changed(conf, path): @@ -193,6 +208,16 @@ def get_removed_vlans(conf, dict): return dict + +def dict_add_dhcpv6pd_defaults(defaults, config_dict): + # Implant default dictionary for DHCPv6-PD instances + if 'dhcpv6_options' in config_dict and 'pd' in config_dict['dhcpv6_options']: + for pd, pd_config in config_dict['dhcpv6_options']['pd'].items(): + config_dict['dhcpv6_options']['pd'][pd] = dict_merge( + defaults, pd_config) + + return config_dict + def get_interface_dict(config, base, ifname=''): """ Common utility function to retrieve and mandgle the interfaces available @@ -257,15 +282,30 @@ def get_interface_dict(config, base, ifname=''): # remove wrongly inserted values dict = T2665_default_dict_cleanup(dict) - # The values are identical for vif, vif-s and vif-c as the all include the same - # XML definitions which hold the defaults + # Implant default dictionary for DHCPv6-PD instances + default_pd_values = defaults(base + ['dhcpv6-options', 'pd']) + dict = dict_add_dhcpv6pd_defaults(default_pd_values, dict) + + # Implant default dictionary in vif/vif-s VLAN interfaces. Values are + # identical for all types of VLAN interfaces as they all include the same + # XML definitions which hold the defaults. default_vif_values = defaults(base + ['vif']) for vif, vif_config in dict.get('vif', {}).items(): - vif_config = dict_merge(default_vif_values, vif_config) + dict['vif'][vif] = dict_add_dhcpv6pd_defaults( + default_pd_values, vif_config) + dict['vif'][vif] = T2665_default_dict_cleanup( + dict_merge(default_vif_values, vif_config)) + for vif_s, vif_s_config in dict.get('vif_s', {}).items(): - vif_s_config = dict_merge(default_vif_values, vif_s_config) + dict['vif_s'][vif_s] = dict_add_dhcpv6pd_defaults( + default_pd_values, vif_s_config) + dict['vif_s'][vif_s] = T2665_default_dict_cleanup( + dict_merge(default_vif_values, vif_s_config)) for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): - vif_c_config = dict_merge(default_vif_values, vif_c_config) + dict['vif_s'][vif_s]['vif_c'][vif_c] = dict_add_dhcpv6pd_defaults( + default_pd_values, vif_c_config) + dict['vif_s'][vif_s]['vif_c'][vif_c] = T2665_default_dict_cleanup( + dict_merge(default_vif_values, vif_c_config)) # Check vif, vif-s/vif-c VLAN interfaces for removal dict = get_removed_vlans(config, dict) diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 928113b49..901ea769c 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -15,7 +15,6 @@ # along with this program. If not, see . import os -import jmespath from sys import exit from copy import deepcopy @@ -104,8 +103,7 @@ def generate(pppoe): render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl', pppoe, trim_blocks=True, permission=0o755) - tmp = jmespath.search('dhcpv6_options.prefix_delegation.interface', pppoe) - if tmp and len(tmp) > 0: + if 'dhcpv6_options' in pppoe and 'pd' in pppoe['dhcpv6_options']: # ipv6.tmpl relies on ifname - this should be made consitent in the # future better then double key-ing the same value render(config_wide_dhcp6c, 'dhcp-client/ipv6.tmpl', pppoe, trim_blocks=True) diff --git a/src/migration-scripts/interfaces/11-to-12 b/src/migration-scripts/interfaces/11-to-12 new file mode 100755 index 000000000..0dad24642 --- /dev/null +++ b/src/migration-scripts/interfaces/11-to-12 @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 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 . + +# - rename 'dhcpv6-options prefix-delegation' from single node to a new tag node +# 'dhcpv6-options pd 0' +# - delete 'sla-len' from CLI - value is calculated on demand + +from sys import exit, argv +from vyos.configtree import ConfigTree + +if __name__ == '__main__': + if (len(argv) < 1): + print("Must specify file name!") + exit(1) + + file_name = argv[1] + with open(file_name, 'r') as f: + config_file = f.read() + + config = ConfigTree(config_file) + + for type in config.list_nodes(['interfaces']): + for interface in config.list_nodes(['interfaces', type]): + # cache current config tree + base_path = ['interfaces', type, interface, 'dhcpv6-options'] + old_base = base_path + ['prefix-delegation'] + new_base = base_path + ['pd'] + if config.exists(old_base): + config.set(new_base) + config.set_tag(new_base) + config.copy(old_base, new_base + ['0']) + config.delete(old_base) + + for pd in config.list_nodes(new_base): + for tmp in config.list_nodes(new_base + [pd, 'interface']): + sla_config = new_base + [pd, 'interface', tmp, 'sla-len'] + if config.exists(sla_config): + config.delete(sla_config) + + try: + with open(file_name, 'w') as f: + f.write(config.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) -- cgit v1.2.3 From 9fd15ce5c60d6c5074d3c374a4049a194b8c17ca Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 12:25:16 +0200 Subject: hostapd: T2564: bugfix on daemon startup Commit b082a6fb21 ("lcd: T2564: flatten CLI interface") by accident replaced the template for hostapd which an LCD configuration during implementation of T2564. This has been corrected by restoring the content of the service configuration from commit 8efb8ba1e (one commit earlier). --- src/etc/systemd/system/hostapd@.service.d/override.conf | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/etc/systemd/system/hostapd@.service.d/override.conf b/src/etc/systemd/system/hostapd@.service.d/override.conf index b03dbc299..bb8e81d7a 100644 --- a/src/etc/systemd/system/hostapd@.service.d/override.conf +++ b/src/etc/systemd/system/hostapd@.service.d/override.conf @@ -3,7 +3,8 @@ After= After=vyos-router.service [Service] -WorkingDirectory=/run/LCDd +WorkingDirectory=/run/hostapd +EnvironmentFile= ExecStart= -ExecStart=/usr/sbin/LCDd -s 1 -f -c /run/LCDd/LCDd.conf - +ExecStart=/usr/sbin/hostapd -B -P /run/hostapd/%i.pid /run/hostapd/%i.conf +PIDFile=/run/hostapd/%i.pid -- cgit v1.2.3 From a7ab5c9f57d216818cf896ef163a314f07cd7c93 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 26 Aug 2020 13:11:48 -0500 Subject: https: T2830: update to use get_config_dict --- data/templates/https/nginx.default.tmpl | 2 +- src/conf_mode/https.py | 97 +++++++++++++++++++++------------ 2 files changed, 62 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/data/templates/https/nginx.default.tmpl b/data/templates/https/nginx.default.tmpl index aaa652e09..a20be45ae 100644 --- a/data/templates/https/nginx.default.tmpl +++ b/data/templates/https/nginx.default.tmpl @@ -52,7 +52,7 @@ server { error_page 501 502 503 =200 @50*_json; -{% if api_somewhere %} +{% if api_set %} location @50*_json { default_type application/json; return 200 '{"error": "service https api unavailable at this proxy address: set service https api-restrict virtual-host"}'; diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index 3dae0fddb..a13f131ab 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -14,9 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os +import sys -from sys import exit from copy import deepcopy import vyos.defaults @@ -33,6 +32,11 @@ airbag.enable() config_file = '/etc/nginx/sites-available/default' certbot_dir = vyos.defaults.directories['certbot'] +# https config needs to coordinate several subsystems: api, certbot, +# self-signed certificate, as well as the virtual hosts defined within the +# https config definition itself. Consequently, one needs a general dict, +# encompassing the https and other configs, and a list of such virtual hosts +# (server blocks in nginx terminology) to pass to the jinja2 template. default_server_block = { 'id' : '', 'address' : '*', @@ -44,44 +48,57 @@ default_server_block = { } def get_config(): - server_block_list = [] conf = Config() if not conf.exists('service https'): return None - else: - conf.set_level('service https') - if not conf.exists('virtual-host'): + server_block_list = [] + https_dict = conf.get_config_dict('service https', get_first_key=True) + + # organize by vhosts + + vhost_dict = https_dict.get('virtual-host', {}) + + if not vhost_dict: + # no specified virtual hosts (server blocks); use default server_block_list.append(default_server_block) else: - for vhost in conf.list_nodes('virtual-host'): + for vhost in list(vhost_dict): server_block = deepcopy(default_server_block) server_block['id'] = vhost - if conf.exists(f'virtual-host {vhost} listen-address'): - addr = conf.return_value(f'virtual-host {vhost} listen-address') - server_block['address'] = addr - if conf.exists(f'virtual-host {vhost} listen-port'): - port = conf.return_value(f'virtual-host {vhost} listen-port') - server_block['port'] = port - if conf.exists(f'virtual-host {vhost} server-name'): - names = conf.return_values(f'virtual-host {vhost} server-name') - server_block['name'] = names[:] + data = vhost_dict.get(vhost, {}) + server_block['address'] = data.get('listen-address', '*') + server_block['port'] = data.get('listen-port', '443') + name = data.get('server-name', ['_']) + # XXX: T2636 workaround: convert string to a list with one element + if not isinstance(name, list): + name = [name] + server_block['name'] = name server_block_list.append(server_block) + # get certificate data + + cert_dict = https_dict.get('certificates', {}) + + # self-signed certificate + vyos_cert_data = {} - if conf.exists('certificates system-generated-certificate'): + if 'system-generated-certificate' in list(cert_dict): vyos_cert_data = vyos.defaults.vyos_cert_data if vyos_cert_data: for block in server_block_list: block['vyos_cert'] = vyos_cert_data + # letsencrypt certificate using certbot + certbot = False - certbot_domains = [] - if conf.exists('certificates certbot domain-name'): - certbot_domains = conf.return_values('certificates certbot domain-name') - if certbot_domains: + cert_domains = cert_dict.get('certbot', {}).get('domain-name', []) + if cert_domains: + # XXX: T2636 workaround: convert string to a list with one element + if not isinstance(cert_domains, list): + cert_domains = [cert_domains] certbot = True - for domain in certbot_domains: + for domain in cert_domains: sub_list = vyos.certbot_util.choose_server_block(server_block_list, domain) if sub_list: @@ -89,25 +106,30 @@ def get_config(): sb['certbot'] = True sb['certbot_dir'] = certbot_dir # certbot organizes certificates by first domain - sb['certbot_domain_dir'] = certbot_domains[0] + sb['certbot_domain_dir'] = cert_domains[0] - api_somewhere = False + # get api data + + api_set = False api_data = {} - if conf.exists('api'): - api_somewhere = True + if 'api' in list(https_dict): + api_set = True api_data = vyos.defaults.api_data - if conf.exists('api port'): - port = conf.return_value('api port') + api_settings = https_dict.get('api', {}) + if api_settings: + port = api_settings.get('port', '') + if port: api_data['port'] = port - if conf.exists('api-restrict virtual-host'): - vhosts = conf.return_values('api-restrict virtual-host') + vhosts = https_dict.get('api-restrict', {}).get('virtual-host', []) + # XXX: T2636 workaround: convert string to a list with one element + if not isinstance(vhosts, list): + vhosts = [vhosts] + if vhosts: api_data['vhost'] = vhosts[:] if api_data: - # we do not want to include 'vhost' key as part of - # vyos.defaults.api_data, so check for key existence - vhost_list = api_data.get('vhost') - if vhost_list is None: + vhost_list = api_data.get('vhost', []) + if not vhost_list: for block in server_block_list: block['api'] = api_data else: @@ -115,9 +137,12 @@ def get_config(): if block['id'] in vhost_list: block['api'] = api_data + # return dict for use in template + https = {'server_block_list' : server_block_list, - 'api_somewhere': api_somewhere, + 'api_set': api_set, 'certbot': certbot} + return https def verify(https): @@ -157,4 +182,4 @@ if __name__ == '__main__': apply(c) except ConfigError as e: print(e) - exit(1) + sys.exit(1) -- cgit v1.2.3 From efeac80f82810c4d752b9633861d691e36de4385 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 26 Aug 2020 20:19:56 +0200 Subject: pppoe-server: T2829: migrate 'ppp-options mppe' to leafNode --- src/migration-scripts/pppoe-server/2-to-3 | 1 - src/migration-scripts/pppoe-server/3-to-4 | 54 +++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100755 src/migration-scripts/pppoe-server/3-to-4 (limited to 'src') diff --git a/src/migration-scripts/pppoe-server/2-to-3 b/src/migration-scripts/pppoe-server/2-to-3 index fa6ef02da..5f9730a41 100755 --- a/src/migration-scripts/pppoe-server/2-to-3 +++ b/src/migration-scripts/pppoe-server/2-to-3 @@ -17,7 +17,6 @@ # - remove primary/secondary identifier from nameserver import os -import sys from sys import argv, exit from vyos.configtree import ConfigTree diff --git a/src/migration-scripts/pppoe-server/3-to-4 b/src/migration-scripts/pppoe-server/3-to-4 new file mode 100755 index 000000000..ed5a01625 --- /dev/null +++ b/src/migration-scripts/pppoe-server/3-to-4 @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 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 . + +# change mppe node to a leaf node with value prefer + +import os + +from sys import argv, exit +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) +base = ['service', 'pppoe-server'] +if not config.exists(base): + # Nothing to do + exit(0) +else: + mppe_base = base + ['ppp-options', 'mppe'] + if config.exists(mppe_base): + # drop node first ... + config.delete(mppe_base) + # ... and set new default + config.set(mppe_base, value='prefer') + + print(config.to_string()) + exit(1) + + try: + with open(file_name, 'w') as f: + f.write(config.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) -- cgit v1.2.3 From 778a6d590b6a53015fb2bbc3c4a7e1088547eb2a Mon Sep 17 00:00:00 2001 From: sever-sever Date: Thu, 27 Aug 2020 09:23:52 +0000 Subject: rip: T2833: Fix distribute-list filter --- src/conf_mode/protocols_rip.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py index 4f8816d61..95e8ce901 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -97,7 +97,7 @@ def get_config(): # Get distribute list interface old_rip for dist_iface in conf.list_effective_nodes('distribute-list interface'): # Set level 'distribute-list interface ethX' - conf.set_level((str(base)) + ' distribute-list interface ' + dist_iface) + conf.set_level(base + ['distribute-list', 'interface', dist_iface]) rip_conf['rip']['distribute'].update({ dist_iface : { 'iface_access_list_in': conf.return_effective_value('access-list in'.format(dist_iface)), @@ -125,7 +125,7 @@ def get_config(): # Get distribute list interface for dist_iface in conf.list_nodes('distribute-list interface'): # Set level 'distribute-list interface ethX' - conf.set_level((str(base)) + ' distribute-list interface ' + dist_iface) + conf.set_level(base + ['distribute-list', 'interface', dist_iface]) rip_conf['rip']['distribute'].update({ dist_iface : { 'iface_access_list_in': conf.return_value('access-list in'.format(dist_iface)), @@ -148,7 +148,7 @@ def get_config(): if conf.exists('prefix-list out'.format(dist_iface)): rip_conf['rip']['iface_prefix_list_out'] = conf.return_value('prefix-list out'.format(dist_iface)) - conf.set_level((str(base)) + ' distribute-list') + conf.set_level(base + ['distribute-list']) # Get distribute list, access-list in if conf.exists_effective('access-list in'): -- cgit v1.2.3 From c29ed43a720f0205dbafa26a0048076bba9d7333 Mon Sep 17 00:00:00 2001 From: hagbard Date: Fri, 28 Aug 2020 12:01:29 -0700 Subject: T2836: show system integrity broken --- op-mode-definitions/show-system.xml | 2 +- src/op_mode/show_system_integrity.py | 70 ++++++++++++++++++++++++++++++++++++ src/op_mode/system_integrity.py | 70 ------------------------------------ 3 files changed, 71 insertions(+), 71 deletions(-) create mode 100755 src/op_mode/show_system_integrity.py delete mode 100755 src/op_mode/system_integrity.py (limited to 'src') diff --git a/op-mode-definitions/show-system.xml b/op-mode-definitions/show-system.xml index 1b98b559b..0623e3b62 100644 --- a/op-mode-definitions/show-system.xml +++ b/op-mode-definitions/show-system.xml @@ -59,7 +59,7 @@ Checks overall system integrity - sudo ${vyos_op_scripts_dir}/system_integrity.py + sudo ${vyos_op_scripts_dir}/show_system_integrity.py diff --git a/src/op_mode/show_system_integrity.py b/src/op_mode/show_system_integrity.py new file mode 100755 index 000000000..c34d41e80 --- /dev/null +++ b/src/op_mode/show_system_integrity.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 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 . +# +# + +import sys +import os +import re +import json +from datetime import datetime, timedelta + +version_file = r'/usr/share/vyos/version.json' + + +def _get_sys_build_version(): + if not os.path.exists(version_file): + return None + buf = open(version_file, 'r').read() + j = json.loads(buf) + if not 'built_on' in j: + return None + return datetime.strptime(j['built_on'], '%a %d %b %Y %H:%M %Z') + + +def _check_pkgs(build_stamp): + pkg_diffs = { + 'buildtime': str(build_stamp), + 'pkg': {} + } + + pkg_info = os.listdir('/var/lib/dpkg/info/') + for file in pkg_info: + if re.search('\.list$', file): + fts = os.stat('/var/lib/dpkg/info/' + file).st_mtime + dt_str = (datetime.utcfromtimestamp( + fts).strftime('%Y-%m-%d %H:%M:%S')) + fdt = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S') + if fdt > build_stamp: + pkg_diffs['pkg'].update( + {str(re.sub('\.list', '', file)): str(fdt)}) + + if len(pkg_diffs['pkg']) != 0: + return pkg_diffs + else: + return None + + +if __name__ == '__main__': + built_date = _get_sys_build_version() + if not built_date: + sys.exit(1) + pkgs = _check_pkgs(built_date) + if pkgs: + print ( + "The following packages don\'t fit the image creation time\nbuild time:\t" + pkgs['buildtime']) + for k, v in pkgs['pkg'].items(): + print ("installed: " + v + '\t' + k) diff --git a/src/op_mode/system_integrity.py b/src/op_mode/system_integrity.py deleted file mode 100755 index c0e3d1095..000000000 --- a/src/op_mode/system_integrity.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# - -import sys -import os -import re -import itertools -from datetime import datetime, timedelta - -from vyos.util import cmd - -verf = r'/usr/libexec/vyos/op_mode/version.py' - -def get_sys_build_version(): - if not os.path.exists(verf): - return None - - a = cmd('/usr/libexec/vyos/op_mode/version.py') - if re.search('^Built on:.+',a, re.M) == None: - return None - - dt = ( re.sub('Built on: +','', re.search('^Built on:.+',a, re.M).group(0)) ) - return datetime.strptime(dt,'%a %d %b %Y %H:%M %Z') - -def check_pkgs(dt): - pkg_diffs = { - 'buildtime' : str(dt), - 'pkg' : {} - } - - pkg_info = os.listdir('/var/lib/dpkg/info/') - for file in pkg_info: - if re.search('\.list$', file): - fts = os.stat('/var/lib/dpkg/info/' + file).st_mtime - dt_str = (datetime.utcfromtimestamp(fts).strftime('%Y-%m-%d %H:%M:%S')) - fdt = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S') - if fdt > dt: - pkg_diffs['pkg'].update( { str(re.sub('\.list','',file)) : str(fdt)}) - - if len(pkg_diffs['pkg']) != 0: - return pkg_diffs - else: - return None - -def main(): - dt = get_sys_build_version() - pkgs = check_pkgs(dt) - if pkgs != None: - print ("The following packages don\'t fit the image creation time\nbuild time:\t" + pkgs['buildtime']) - for k, v in pkgs['pkg'].items(): - print ("installed: " + v + '\t' + k) - -if __name__ == '__main__': - sys.exit( main() ) - -- cgit v1.2.3 From 0831c666891506d26cf6b4730c88c2e900121d6a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 28 Aug 2020 21:14:00 +0200 Subject: nat: T2813: translation address is mandatory if rule is not excluded --- smoketest/scripts/cli/test_nat.py | 19 +++++++++++++++---- src/conf_mode/nat.py | 5 +++++ 2 files changed, 20 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py index 416810e40..b06fa239d 100755 --- a/smoketest/scripts/cli/test_nat.py +++ b/smoketest/scripts/cli/test_nat.py @@ -23,6 +23,8 @@ from vyos.configsession import ConfigSession, ConfigSessionError from vyos.util import cmd base_path = ['nat'] +source_path = base_path + ['source'] + snat_pattern = 'nftables[?rule].rule[?chain].{chain: chain, comment: comment, address: { network: expr[].match.right.prefix.addr | [0], prefix: expr[].match.right.prefix.len | [0]}}' class TestNAT(unittest.TestCase): @@ -39,16 +41,15 @@ class TestNAT(unittest.TestCase): def test_source_nat(self): """ Configure and validate source NAT rule(s) """ - path = base_path + ['source'] network = '192.168.0.0/16' - self.session.set(path + ['rule', '1', 'destination', 'address', network]) - self.session.set(path + ['rule', '1', 'exclude']) + self.session.set(source_path + ['rule', '1', 'destination', 'address', network]) + self.session.set(source_path + ['rule', '1', 'exclude']) # check validate() - outbound-interface must be defined with self.assertRaises(ConfigSessionError): self.session.commit() - self.session.set(path + ['rule', '1', 'outbound-interface', 'any']) + self.session.set(source_path + ['rule', '1', 'outbound-interface', 'any']) self.session.commit() tmp = cmd('sudo nft -j list table nat') @@ -59,5 +60,15 @@ class TestNAT(unittest.TestCase): self.assertEqual(condensed_json['address']['network'], network.split('/')[0]) self.assertEqual(str(condensed_json['address']['prefix']), network.split('/')[1]) + + def test_validation(self): + """ T2813: Ensure translation address is specified """ + self.session.set(source_path + ['rule', '100', 'outbound-interface', 'eth0']) + + # check validate() - translation address not specified + with self.assertRaises(ConfigSessionError): + self.session.commit() + + if __name__ == '__main__': unittest.main() diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index dd34dfd66..97cd36223 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -232,6 +232,8 @@ def verify(nat): addr = rule['translation_address'] if addr != 'masquerade' and not is_addr_assigned(addr): print(f'Warning: IP address {addr} does not exist on the system!') + elif not rule['exclude']: + raise ConfigError(f'{err_msg} translation address not specified') # common rule verification verify_rule(rule, err_msg) @@ -246,6 +248,9 @@ def verify(nat): if not rule['interface_in']: raise ConfigError(f'{err_msg} inbound-interface not specified') + if not rule['translation_address'] and not rule['exclude']: + raise ConfigError(f'{err_msg} translation address not specified') + # common rule verification verify_rule(rule, err_msg) -- cgit v1.2.3 From be49f336550f5b6dabd1b8d068237f541f37148a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 30 Aug 2020 20:08:12 +0200 Subject: nat: T2814: nftables module for NAT has been renamed Depending on the underlaying Kernel version load the corresponding Kernel module. --- src/conf_mode/nat.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 97cd36223..f79f0f42b 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -19,6 +19,8 @@ import json import os from copy import deepcopy +from distutils.version import LooseVersion +from platform import release as kernel_version from sys import exit from netifaces import interfaces @@ -33,7 +35,10 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -k_mod = ['nft_nat', 'nft_chain_nat_ipv4'] +if LooseVersion(kernel_version()) > LooseVersion('5.1'): + k_mod = ['nft_nat', 'nft_chain_nat'] +else: + k_mod = ['nft_nat', 'nft_chain_nat_ipv4'] default_config_data = { 'deleted': False, -- cgit v1.2.3 From 88985dad133d5e85aca559dbfce53207a2292e0a Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 28 Aug 2020 15:49:55 -0500 Subject: configd: T2582: add config daemon and supporting files --- data/configd-include.json | 1 + data/vyos-configd-env-set | 2 + data/vyos-configd-env-unset | 2 + debian/control | 1 + debian/rules | 1 + src/services/vyos-configd | 224 +++++++++++++++++++++++++++++++++++++++ src/systemd/vyos-configd.service | 27 +++++ 7 files changed, 258 insertions(+) create mode 100644 data/configd-include.json create mode 100644 data/vyos-configd-env-set create mode 100644 data/vyos-configd-env-unset create mode 100755 src/services/vyos-configd create mode 100644 src/systemd/vyos-configd.service (limited to 'src') diff --git a/data/configd-include.json b/data/configd-include.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/data/configd-include.json @@ -0,0 +1 @@ +[] diff --git a/data/vyos-configd-env-set b/data/vyos-configd-env-set new file mode 100644 index 000000000..d6d421eba --- /dev/null +++ b/data/vyos-configd-env-set @@ -0,0 +1,2 @@ +# +export vyshim=/usr/sbin/vyshim diff --git a/data/vyos-configd-env-unset b/data/vyos-configd-env-unset new file mode 100644 index 000000000..9616f9858 --- /dev/null +++ b/data/vyos-configd-env-unset @@ -0,0 +1,2 @@ +# +unset vyshim diff --git a/debian/control b/debian/control index 65f3be228..d9663d07b 100644 --- a/debian/control +++ b/debian/control @@ -6,6 +6,7 @@ Build-Depends: debhelper (>= 9), fakeroot, libvyosconfig0 (>= 0.0.7), + libzmq3-dev, python3, python3-coverage, python3-lxml, diff --git a/debian/rules b/debian/rules index 58bafd333..6b982fd8e 100755 --- a/debian/rules +++ b/debian/rules @@ -33,6 +33,7 @@ override_dh_auto_install: mkdir -p $(DIR)/$(VYOS_SBIN_DIR) mkdir -p $(DIR)/$(VYOS_BIN_DIR) cp -r src/utils/* $(DIR)/$(VYOS_BIN_DIR) + cp src/shim/vyshim $(DIR)/$(VYOS_SBIN_DIR) # Install conf mode scripts mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/conf_mode diff --git a/src/services/vyos-configd b/src/services/vyos-configd new file mode 100755 index 000000000..75f84d3df --- /dev/null +++ b/src/services/vyos-configd @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 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 . +# +# + +import os +import sys +import grp +import re +import json +import logging +import signal +import importlib.util +import zmq + +from vyos.defaults import directories +from vyos.configsource import ConfigSourceString +from vyos.config import Config +from vyos import ConfigError + +CFG_GROUP = 'vyattacfg' + +debug = True + +logger = logging.getLogger(__name__) +logs_handler = logging.StreamHandler() +logger.addHandler(logs_handler) + +if debug: + logger.setLevel(logging.DEBUG) +else: + logger.setLevel(logging.INFO) + +SOCKET_PATH = "ipc:///run/vyos-configd.sock" + +# Response error codes +R_SUCCESS = 1 +R_ERROR_COMMIT = 2 +R_ERROR_DAEMON = 4 +R_PASS = 8 + +vyos_conf_scripts_dir = directories['conf_mode'] +configd_include_file = os.path.join(directories['data'], 'configd-include.json') +configd_env_set_file = os.path.join(directories['data'], 'vyos-configd-env-set') +configd_env_unset_file = os.path.join(directories['data'], 'vyos-configd-env-unset') +# sourced on entering config session +configd_env_file = '/etc/default/vyos-configd-env' + +active_string = '' +session_string = '' + +def key_name_from_file_name(f): + return os.path.splitext(f)[0] + +def module_name_from_key(k): + return k.replace('-', '_') + +def path_from_file_name(f): + return os.path.join(vyos_conf_scripts_dir, f) + +# opt-in to be run by daemon +with open(configd_include_file) as f: + try: + include = json.load(f) + except OSError as e: + logger.critical(f"configd include file error: {e}") + sys.exit(1) + except json.JSONDecodeError as e: + logger.critical(f"JSON load error: {e}") + sys.exit(1) + +# import conf_mode scripts +(_, _, filenames) = next(iter(os.walk(vyos_conf_scripts_dir))) +filenames.sort() + +load_filenames = [f for f in filenames if f in include] +imports = [key_name_from_file_name(f) for f in load_filenames] +module_names = [module_name_from_key(k) for k in imports] +paths = [path_from_file_name(f) for f in load_filenames] +to_load = list(zip(module_names, paths)) + +modules = [] + +for x in to_load: + spec = importlib.util.spec_from_file_location(x[0], x[1]) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + modules.append(module) + +conf_mode_scripts = dict(zip(imports, modules)) + +exclude_set = {key_name_from_file_name(f) for f in filenames if f not in include} +include_set = {key_name_from_file_name(f) for f in filenames if f in include} + + +def run_script(script, config) -> int: + config.set_level([]) + try: + c = script.get_config(config) + script.verify(c) + script.generate(c) + script.apply(c) + except ConfigError as e: + logger.critical(e) + return R_ERROR_COMMIT + except Exception: + return R_ERROR_DAEMON + + return R_SUCCESS + +def initialization(socket): + # Reset config strings: + active_string = '' + session_string = '' + # zmq synchronous for ipc from single client: + active_string = socket.recv().decode() + resp = "active" + socket.send(resp.encode()) + session_string = socket.recv().decode() + resp = "session" + socket.send(resp.encode()) + + configsource = ConfigSourceString(running_config_text=active_string, + session_config_text=session_string) + + config = Config(config_source=configsource) + + return config + +def process_node_data(config, data) -> int: + if not config: + logger.critical(f"Empty config") + return R_ERROR_DAEMON + + script_name = None + + res = re.match(r'^.+\/([^/].+).py(VYOS_TAGNODE_VALUE=.+)?', data) + if res.group(1): + script_name = res.group(1) + if res.group(2): + env = res.group(2).split('=') + os.environ[env[0]] = env[1] + + if not script_name: + logger.critical(f"Missing script_name") + return R_ERROR_DAEMON + + if script_name in exclude_set: + return R_PASS + + result = run_script(conf_mode_scripts[script_name], config) + + return result + +def remove_if_file(f: str): + try: + os.remove(f) + except FileNotFoundError: + pass + except OSError: + raise + +def shutdown(): + remove_if_file(configd_env_file) + os.symlink(configd_env_unset_file, configd_env_file) + sys.exit(0) + +if __name__ == '__main__': + context = zmq.Context() + socket = context.socket(zmq.REP) + + # Set the right permissions on the socket, then change it back + o_mask = os.umask(0) + socket.bind(SOCKET_PATH) + os.umask(o_mask) + + cfg_group = grp.getgrnam(CFG_GROUP) + os.setgid(cfg_group.gr_gid) + + os.environ['SUDO_USER'] = 'vyos' + os.environ['SUDO_GID'] = str(cfg_group.gr_gid) + + def sig_handler(signum, frame): + shutdown() + + signal.signal(signal.SIGTERM, sig_handler) + signal.signal(signal.SIGINT, sig_handler) + + # Define the vyshim environment variable + remove_if_file(configd_env_file) + os.symlink(configd_env_set_file, configd_env_file) + + config = None + + while True: + # Wait for next request from client + msg = socket.recv().decode() + logger.debug(f"Received message: {msg}") + message = json.loads(msg) + + if message["type"] == "init": + resp = "init" + socket.send(resp.encode()) + config = initialization(socket) + elif message["type"] == "node": + res = process_node_data(config, message["data"]) + response = res.to_bytes(1, byteorder=sys.byteorder) + logger.debug(f"Sending response {res}") + socket.send(response) + else: + logger.critical(f"Unexpected message: {message}") diff --git a/src/systemd/vyos-configd.service b/src/systemd/vyos-configd.service new file mode 100644 index 000000000..274ccc787 --- /dev/null +++ b/src/systemd/vyos-configd.service @@ -0,0 +1,27 @@ +[Unit] +Description=VyOS configuration daemon + +# Without this option, lots of default dependencies are added, +# among them network.target, which creates a dependency cycle +DefaultDependencies=no + +# Seemingly sensible way to say "as early as the system is ready" +# All vyos-configd needs is read/write mounted root +After=systemd-remount-fs.service +Before=vyos-router.service + +[Service] +ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-configd +Type=idle + +SyslogIdentifier=vyos-configd +SyslogFacility=daemon + +Restart=on-failure + +# Does't work in Jessie but leave it here +User=root +Group=vyattacfg + +[Install] +WantedBy=vyos.target -- cgit v1.2.3 From 163047aedabc2d43249239b0c0bdf92b8e664131 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 28 Aug 2020 15:50:12 -0500 Subject: configd: T2582: add mkjson for use by shim (https://github.com/Jacajack/mkjson.git) --- src/shim/mkjson/LICENSE | 21 ++++ src/shim/mkjson/makefile | 30 +++++ src/shim/mkjson/mkjson.c | 307 +++++++++++++++++++++++++++++++++++++++++++++++ src/shim/mkjson/mkjson.h | 50 ++++++++ 4 files changed, 408 insertions(+) create mode 100644 src/shim/mkjson/LICENSE create mode 100644 src/shim/mkjson/makefile create mode 100644 src/shim/mkjson/mkjson.c create mode 100644 src/shim/mkjson/mkjson.h (limited to 'src') diff --git a/src/shim/mkjson/LICENSE b/src/shim/mkjson/LICENSE new file mode 100644 index 000000000..8c4284c91 --- /dev/null +++ b/src/shim/mkjson/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Jacek Wieczorek + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/shim/mkjson/makefile b/src/shim/mkjson/makefile new file mode 100644 index 000000000..ba75399d2 --- /dev/null +++ b/src/shim/mkjson/makefile @@ -0,0 +1,30 @@ +CFLAGS = -Wall -Os -I. +CC = gcc +AR = ar + +#USE_ASPRINTF make flag can be used in order to encourage asprintf use inside the library +ifeq ($(USE_ASPRINTF),1) +CFLAGS += -D_GNU_SOURCE +endif + +#Builds object and a static library file +all: clean force + $(CC) $(CFLAGS) -c mkjson.c -o obj/mkjson.o + $(AR) -cvq lib/libmkjson.a obj/mkjson.o + $(AR) -t lib/libmkjson.a + +#Normal cleanup +clean: + -rm -rf obj + -rm -rf lib + +#Environment init +force: + -mkdir obj + -mkdir lib + +#Build the example snippet +example: all + gcc -o example examples/example.c -I. -Llib -lmkjson + + diff --git a/src/shim/mkjson/mkjson.c b/src/shim/mkjson/mkjson.c new file mode 100644 index 000000000..1172664fb --- /dev/null +++ b/src/shim/mkjson/mkjson.c @@ -0,0 +1,307 @@ +/* mkjson.c - a part of mkjson library + * + * Copyright (C) 2018 Jacek Wieczorek + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#include +#include +#include +#include +#include + +// Works like asprintf, but it's always there +// I don't want the name to collide with anything +static int allsprintf( char **strp, const char *fmt, ... ) +{ + int len; + va_list ap; + va_start( ap, fmt ); + + #ifdef _GNU_SOURCE + // Just hand everything to vasprintf, if it's available + len = vasprintf( strp, fmt, ap ); + #else + // Or do it the manual way + char *buf; + len = vsnprintf( NULL, 0, fmt, ap ); + if ( len >= 0 ) + { + buf = malloc( ++len ); + if ( buf != NULL ) + { + // Hopefully, that's the right way to do it + va_end( ap ); + va_start( ap, fmt ); + + // Write and return the data + len = vsnprintf( buf, len, fmt, ap ); + if ( len >= 0 ) + { + *strp = buf; + } + else + { + free( buf ); + } + } + } + #endif + + va_end( ap ); + return len; +} + +// Return JSON string built from va_arg arguments +// If no longer needed, should be passed to free() by user +char *mkjson( enum mkjson_container_type otype, int count, ... ) +{ + int i, len, goodchunks = 0, failure = 0; + char *json, *prefix, **chunks, ign; + + // Value - type and data + enum mkjson_value_type vtype; + const char *key; + long long int intval; + long double dblval; + const char *strval; + + // Since v0.9 count cannot be a negative value and datatype is indicated by a separate argument + // Since I'm not sure whether it's right to put assertions in libraries, the next line is commented out + // assert( count >= 0 && "After v0.9 negative count is prohibited; please use otype argument instead" ); + if ( count < 0 || ( otype != MKJSON_OBJ && otype != MKJSON_ARR ) ) return NULL; + + // Allocate chunk pointer array - on standard platforms each one should be NULL + chunks = calloc( count, sizeof( char* ) ); + if ( chunks == NULL ) return NULL; + + // This should rather be at the point of no return + va_list ap; + va_start( ap, count ); + + // Create chunks + for ( i = 0; i < count && !failure; i++ ) + { + // Get value type + vtype = va_arg( ap, enum mkjson_value_type ); + + // Get key + if ( otype == MKJSON_OBJ ) + { + key = va_arg( ap, char* ); + if ( key == NULL ) + { + failure = 1; + break; + } + } + else key = ""; + + // Generate prefix + if ( allsprintf( &prefix, "%s%s%s", + otype == MKJSON_OBJ ? "\"" : "", // Quote before key + key, // Key + otype == MKJSON_OBJ ? "\": " : "" ) == -1 ) // Quote and colon after key + { + failure = 1; + break; + } + + // Depending on value type + ign = 0; + switch ( vtype ) + { + // Ignore string / JSON data + case MKJSON_IGN_STRING: + case MKJSON_IGN_JSON: + (void) va_arg( ap, const char* ); + ign = 1; + break; + + // Ignore string / JSON data and pass the pointer to free + case MKJSON_IGN_STRING_FREE: + case MKJSON_IGN_JSON_FREE: + free( va_arg( ap, char* ) ); + ign = 1; + break; + + // Ignore int / long long int + case MKJSON_IGN_INT: + case MKJSON_IGN_LLINT: + if ( vtype == MKJSON_IGN_INT ) + (void) va_arg( ap, int ); + else + (void) va_arg( ap, long long int ); + ign = 1; + break; + + // Ignore double / long double + case MKJSON_IGN_DOUBLE: + case MKJSON_IGN_LDOUBLE: + if ( vtype == MKJSON_IGN_DOUBLE ) + (void) va_arg( ap, double ); + else + (void) va_arg( ap, long double ); + ign = 1; + break; + + // Ignore boolean + case MKJSON_IGN_BOOL: + (void) va_arg( ap, int ); + ign = 1; + break; + + // Ignore null value + case MKJSON_IGN_NULL: + ign = 1; + break; + + // A null-terminated string + case MKJSON_STRING: + case MKJSON_STRING_FREE: + strval = va_arg( ap, const char* ); + + // If the pointer points to NULL, the string will be replaced with JSON null value + if ( strval == NULL ) + { + if ( allsprintf( chunks + i, "%snull", prefix ) == -1 ) + chunks[i] = NULL; + } + else + { + if ( allsprintf( chunks + i, "%s\"%s\"", prefix, strval ) == -1 ) + chunks[i] = NULL; + } + + // Optional free + if ( vtype == MKJSON_STRING_FREE ) + free( (char*) strval ); + break; + + // Embed JSON data + case MKJSON_JSON: + case MKJSON_JSON_FREE: + strval = va_arg( ap, const char* ); + + // If the pointer points to NULL, the JSON data is replaced with null value + if ( allsprintf( chunks + i, "%s%s", prefix, strval == NULL ? "null" : strval ) == -1 ) + chunks[i] = NULL; + + // Optional free + if ( vtype == MKJSON_JSON_FREE ) + free( (char*) strval ); + break; + + // int / long long int + case MKJSON_INT: + case MKJSON_LLINT: + if ( vtype == MKJSON_INT ) + intval = va_arg( ap, int ); + else + intval = va_arg( ap, long long int ); + + if ( allsprintf( chunks + i, "%s%Ld", prefix, intval ) == -1 ) chunks[i] = NULL; + break; + + // double / long double + case MKJSON_DOUBLE: + case MKJSON_LDOUBLE: + if ( vtype == MKJSON_DOUBLE ) + dblval = va_arg( ap, double ); + else + dblval = va_arg( ap, long double ); + + if ( allsprintf( chunks + i, "%s%Lf", prefix, dblval ) == -1 ) chunks[i] = NULL; + break; + + // double / long double + case MKJSON_SCI_DOUBLE: + case MKJSON_SCI_LDOUBLE: + if ( vtype == MKJSON_SCI_DOUBLE ) + dblval = va_arg( ap, double ); + else + dblval = va_arg( ap, long double ); + + if ( allsprintf( chunks + i, "%s%Le", prefix, dblval ) == -1 ) chunks[i] = NULL; + break; + + // Boolean + case MKJSON_BOOL: + intval = va_arg( ap, int ); + if ( allsprintf( chunks + i, "%s%s", prefix, intval ? "true" : "false" ) == -1 ) chunks[i] = NULL; + break; + + // JSON null + case MKJSON_NULL: + if ( allsprintf( chunks + i, "%snull", prefix ) == -1 ) chunks[i] = NULL; + break; + + // Bad type specifier + default: + chunks[i] = NULL; + break; + } + + // Free prefix memory + free( prefix ); + + // NULL chunk without ignore flag indicates failure + if ( !ign && chunks[i] == NULL ) failure = 1; + + // NULL chunk now indicates ignore flag + if ( ign ) chunks[i] = NULL; + else goodchunks++; + } + + // We won't use ap anymore + va_end( ap ); + + // If everything is fine, merge chunks and create full JSON table + if ( !failure ) + { + // Get total length (this is without NUL byte) + len = 0; + for ( i = 0; i < count; i++ ) + if ( chunks[i] != NULL ) + len += strlen( chunks[i] ); + + // Total length = Chunks length + 2 brackets + separators + if ( goodchunks == 0 ) goodchunks = 1; + len = len + 2 + ( goodchunks - 1 ) * 2; + + // Allocate memory for the whole thing + json = calloc( len + 1, sizeof( char ) ); + if ( json != NULL ) + { + // Merge chunks (and do not overwrite the first bracket) + for ( i = 0; i < count; i++ ) + { + // Add separators: + // - not on the begining + // - always after valid chunk + // - between two valid chunks + // - between valid and ignored chunk if the latter isn't the last one + if ( i != 0 && chunks[i - 1] != NULL && ( chunks[i] != NULL || ( chunks[i] == NULL && i != count - 1 ) ) ) + strcat( json + 1, ", "); + + if ( chunks[i] != NULL ) + strcat( json + 1, chunks[i] ); + } + + // Add proper brackets + json[0] = otype == MKJSON_OBJ ? '{' : '['; + json[len - 1] = otype == MKJSON_OBJ ? '}' : ']'; + } + } + else json = NULL; + + // Free chunks + for ( i = 0; i < count; i++ ) + free( chunks[i] ); + free( chunks ); + + return json; +} + diff --git a/src/shim/mkjson/mkjson.h b/src/shim/mkjson/mkjson.h new file mode 100644 index 000000000..38cc07b26 --- /dev/null +++ b/src/shim/mkjson/mkjson.h @@ -0,0 +1,50 @@ +/* mkjson.h - a part of mkjson library + * + * Copyright (C) 2018 Jacek Wieczorek + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#ifndef MKJSON_H +#define MKJSON_H + +// JSON container types +enum mkjson_container_type +{ + MKJSON_ARR = 0, // An array + MKJSON_OBJ = 1 // An object (hash or whatever you call it) +}; + +// JSON data types +enum mkjson_value_type +{ + MKJSON_STRING = (int)('s'), // const char* - String data + MKJSON_STRING_FREE = (int)('f'), // char* - String data, but pointer is freed + MKJSON_JSON = (int)('r'), // const char* - JSON data (like string, but no quotes) + MKJSON_JSON_FREE = (int)('j'), // char* - JSON data, but pointer is freed + MKJSON_INT = (int)('i'), // int - An integer + MKJSON_LLINT = (int)('I'), // long long int - A long integer + MKJSON_DOUBLE = (int)('d'), // double - A double + MKJSON_LDOUBLE = (int)('D'), // long double - A long double + MKJSON_SCI_DOUBLE = (int)('e'), // double - A double with scientific notation + MKJSON_SCI_LDOUBLE = (int)('E'), // long double - A long double with scientific notation + MKJSON_BOOL = (int)('b'), // int - A boolean value + MKJSON_NULL = (int)('n'), // -- - JSON null value + + // These cause one argument of certain type to be ignored + MKJSON_IGN_STRING = (-MKJSON_STRING), + MKJSON_IGN_STRING_FREE = (-MKJSON_STRING_FREE), + MKJSON_IGN_JSON = (-MKJSON_JSON), + MKJSON_IGN_JSON_FREE = (-MKJSON_JSON_FREE), + MKJSON_IGN_INT = (-MKJSON_INT), + MKJSON_IGN_LLINT = (-MKJSON_LLINT), + MKJSON_IGN_DOUBLE = (-MKJSON_DOUBLE), + MKJSON_IGN_LDOUBLE = (-MKJSON_LDOUBLE), + MKJSON_IGN_BOOL = (-MKJSON_BOOL), + MKJSON_IGN_NULL = (-MKJSON_NULL) +}; + +extern char *mkjson( enum mkjson_container_type otype, int count, ... ); + +#endif -- cgit v1.2.3 From d469e2f56e299a9d767d7db1f00646741f7ce367 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 28 Aug 2020 15:50:19 -0500 Subject: configd: T2582: add shim as config daemon client --- Makefile | 10 +- debian/vyos-1x.install | 1 + src/shim/Makefile | 20 ++++ src/shim/vyshim.c | 287 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 src/shim/Makefile create mode 100644 src/shim/vyshim.c (limited to 'src') diff --git a/Makefile b/Makefile index 5b7e4da63..e85835eec 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,9 @@ TMPL_DIR := templates-cfg OP_TMPL_DIR := templates-op BUILD_DIR := build DATA_DIR := data +SHIM_DIR := src/shim +CC := gcc +LIBS := -lzmq CFLAGS := src = $(wildcard interface-definitions/*.xml.in) @@ -114,14 +117,19 @@ op_mode_definitions: component_versions: $(BUILD_DIR) $(obj) $(CURDIR)/scripts/build-component-versions $(BUILD_DIR)/interface-definitions $(DATA_DIR) +.PHONY: vyshim +vyshim: + $(MAKE) -C $(SHIM_DIR) + .PHONY: all -all: clean interface_definitions op_mode_definitions component_versions +all: clean interface_definitions op_mode_definitions component_versions vyshim .PHONY: clean clean: rm -rf $(BUILD_DIR) rm -rf $(TMPL_DIR) rm -rf $(OP_TMPL_DIR) + $(MAKE) -C $(SHIM_DIR) clean .PHONY: test test: diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install index 6cd678442..6d5026e91 100644 --- a/debian/vyos-1x.install +++ b/debian/vyos-1x.install @@ -7,6 +7,7 @@ etc/udev etc/vyos lib/ opt/ +usr/sbin/vyshim usr/bin/initial-setup usr/bin/vyos-config-file-query usr/bin/vyos-config-to-commands diff --git a/src/shim/Makefile b/src/shim/Makefile new file mode 100644 index 000000000..c8487e3c2 --- /dev/null +++ b/src/shim/Makefile @@ -0,0 +1,20 @@ +DEBUG = 0 + +CC := gcc +CFLAGS := -I./mkjson -L./mkjson/lib -DDEBUG=${DEBUG} +LIBS := -lmkjson -lzmq + +.PHONY: vyshim +vyshim: vyshim.c libmkjson + $(CC) $(CFLAGS) -o $@ $< $(LIBS) + +.PHONY: libmkjson +libmkjson: + $(MAKE) -C mkjson + +all: vyshim + +.PHONY: clean +clean: + $(MAKE) -C mkjson clean + rm -f vyshim diff --git a/src/shim/vyshim.c b/src/shim/vyshim.c new file mode 100644 index 000000000..8b6feab99 --- /dev/null +++ b/src/shim/vyshim.c @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2020 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mkjson.h" + +/* + * + * + */ + +#if DEBUG +#define DEBUG_ON 1 +#else +#define DEBUG_ON 0 +#endif +#define debug_print(fmt, ...) \ + do { if (DEBUG_ON) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0) +#define debug_call(f) \ + do { if (DEBUG_ON) f; } while (0) + +#define SOCKET_PATH "ipc:///run/vyos-configd.sock" + +#define GET_ACTIVE "cli-shell-api --show-active-only --show-show-defaults --show-ignore-edit showConfig" +#define GET_SESSION "cli-shell-api --show-working-only --show-show-defaults --show-ignore-edit showConfig" + +#define COMMIT_MARKER "/var/tmp/initial_in_commit" + +enum { + SUCCESS = 1 << 0, + ERROR_COMMIT = 1 << 1, + ERROR_DAEMON = 1 << 2, + PASS = 1 << 3 +}; + +volatile int init_alarm = 0; +volatile int timeout = 0; + +int initialization(void *); +int pass_through(char **, int); +void timer_handler(int); + +double get_posix_clock_time(void); + +int main(int argc, char* argv[]) +{ + // string for node data: conf_mode script and tagnode, if applicable + char string_node_data[256]; + string_node_data[0] = '\0'; + + void *context = zmq_ctx_new(); + void *requester = zmq_socket(context, ZMQ_REQ); + + int init_timeout = 0; + + debug_print("Connecting to vyos-configd ...\n"); + zmq_connect(requester, SOCKET_PATH); + + if (access(COMMIT_MARKER, F_OK) != -1) { + init_timeout = initialization(requester); + if (!init_timeout) remove(COMMIT_MARKER); + } + + int end = argc > 3 ? 2 : argc - 1; + + // if initial communication failed, pass through execution of script + if (init_timeout) { + int ret = pass_through(argv, end); + return ret; + } + + for (int i = end; i > 0 ; i--) { + strncat(&string_node_data[0], argv[i], 127); + } + + char error_code[1]; + debug_print("Sending node data ...\n"); + char *string_node_data_msg = mkjson(MKJSON_OBJ, 2, + MKJSON_STRING, "type", "node", + MKJSON_STRING, "data", &string_node_data[0]); + + zmq_send(requester, string_node_data_msg, strlen(string_node_data_msg), 0); + zmq_recv(requester, error_code, 1, 0); + debug_print("Received node data receipt\n"); + + int err = (int)error_code[0]; + + free(string_node_data_msg); + + zmq_close(requester); + zmq_ctx_destroy(context); + + if (err & PASS) { + debug_print("Received PASS\n"); + int ret = pass_through(argv, end); + return ret; + } + + if (err & ERROR_DAEMON) { + debug_print("Received ERROR_DAEMON\n"); + int ret = pass_through(argv, end); + return ret; + } + + if (err & ERROR_COMMIT) { + debug_print("Received ERROR_COMMIT\n"); + return -1; + } + + return 0; +} + +int initialization(void* Requester) +{ + char *active_str = NULL; + size_t active_len = 0; + + char *session_str = NULL; + size_t session_len = 0; + + char *empty_string = "\n"; + + char buffer[16]; + + struct sigaction sa; + struct itimerval timer, none_timer; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = &timer_handler; + sigaction(SIGALRM, &sa, NULL); + + timer.it_value.tv_sec = 0; + timer.it_value.tv_usec = 10000; + timer.it_interval.tv_sec = timer.it_interval.tv_usec = 0; + none_timer.it_value.tv_sec = none_timer.it_value.tv_usec = 0; + none_timer.it_interval.tv_sec = none_timer.it_interval.tv_usec = 0; + + double prev_time_value, time_value; + double time_diff; + + debug_print("Sending init announcement\n"); + char *init_announce = mkjson(MKJSON_OBJ, 1, + MKJSON_STRING, "type", "init"); + + // check for timeout on initial contact + while (!init_alarm) { + debug_call(prev_time_value = get_posix_clock_time()); + + setitimer(ITIMER_REAL, &timer, NULL); + + zmq_send(Requester, init_announce, strlen(init_announce), 0); + zmq_recv(Requester, buffer, 16, 0); + + setitimer(ITIMER_REAL, &none_timer, &timer); + + debug_call(time_value = get_posix_clock_time()); + + debug_print("Received init receipt\n"); + debug_call(time_diff = time_value - prev_time_value); + debug_print("time elapse %f\n", time_diff); + + break; + } + + free(init_announce); + + if (timeout) return -1; + + FILE *fp_a = popen(GET_ACTIVE, "r"); + getdelim(&active_str, &active_len, '\0', fp_a); + int ret = pclose(fp_a); + + if (!ret) { + debug_print("Sending active config\n"); + zmq_send(Requester, active_str, active_len - 1, 0); + zmq_recv(Requester, buffer, 16, 0); + debug_print("Received active receipt\n"); + } else { + debug_print("Sending empty active config\n"); + zmq_send(Requester, empty_string, 0, 0); + zmq_recv(Requester, buffer, 16, 0); + debug_print("Received active receipt\n"); + } + + free(active_str); + + FILE *fp_s = popen(GET_SESSION, "r"); + getdelim(&session_str, &session_len, '\0', fp_s); + pclose(fp_s); + + debug_print("Sending session config\n"); + zmq_send(Requester, session_str, session_len - 1, 0); + zmq_recv(Requester, buffer, 16, 0); + debug_print("Received session receipt\n"); + + free(session_str); + + return 0; +} + +int pass_through(char **argv, int end) +{ + char *newargv[] = { NULL, NULL }; + pid_t child_pid; + + newargv[0] = argv[end]; + if (end > 1) { + putenv(argv[end - 1]); + } + + debug_print("pass-through invoked\n"); + + if ((child_pid=fork()) < 0) { + debug_print("fork() failed\n"); + return -1; + } else if (child_pid == 0) { + if (-1 == execv(argv[end], newargv)) { + debug_print("pass_through execve failed %s: %s\n", + argv[end], strerror(errno)); + return -1; + } + } else if (child_pid > 0) { + int status; + pid_t wait_pid = waitpid(child_pid, &status, 0); + if (wait_pid < 0) { + debug_print("waitpid() failed\n"); + return -1; + } else if (wait_pid == child_pid) { + if (WIFEXITED(status)) { + debug_print("child exited with code %d\n", + WEXITSTATUS(status)); + return WEXITSTATUS(status); + } + } + } + + return 0; +} + +void timer_handler(int signum) +{ + debug_print("timer_handler invoked\n"); + timeout = 1; + init_alarm = 1; + + return; +} + +#ifdef _POSIX_MONOTONIC_CLOCK +double get_posix_clock_time(void) +{ + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { + return (double) (ts.tv_sec + ts.tv_nsec / 1000000000.0); + } else { + return 0; + } +} +#else +double get_posix_clock_time(void) +{return (double)0;} +#endif -- cgit v1.2.3 From ad69fb36201ee0930b76d80f0869284e26846991 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 28 Aug 2020 15:50:50 -0500 Subject: configd: T2582: add scripts to include list for daemon --- data/configd-include.json | 65 ++++++++++++++++++++++++++++- src/conf_mode/bcast_relay.py | 7 +++- src/conf_mode/dhcp_relay.py | 7 +++- src/conf_mode/dhcp_server.py | 7 +++- src/conf_mode/dhcpv6_relay.py | 7 +++- src/conf_mode/dhcpv6_server.py | 7 +++- src/conf_mode/dynamic_dns.py | 7 +++- src/conf_mode/firewall_options.py | 7 +++- src/conf_mode/host_name.py | 7 +++- src/conf_mode/http-api.py | 8 +++- src/conf_mode/https.py | 8 +++- src/conf_mode/igmp_proxy.py | 7 +++- src/conf_mode/intel_qat.py | 7 +++- src/conf_mode/interfaces-bonding.py | 7 +++- src/conf_mode/interfaces-bridge.py | 7 +++- src/conf_mode/interfaces-dummy.py | 7 +++- src/conf_mode/interfaces-ethernet.py | 7 +++- src/conf_mode/interfaces-geneve.py | 7 +++- src/conf_mode/interfaces-l2tpv3.py | 7 +++- src/conf_mode/interfaces-loopback.py | 7 +++- src/conf_mode/interfaces-macsec.py | 7 +++- src/conf_mode/interfaces-openvpn.py | 7 +++- src/conf_mode/interfaces-pppoe.py | 7 +++- src/conf_mode/interfaces-pseudo-ethernet.py | 7 +++- src/conf_mode/interfaces-tunnel.py | 8 +++- src/conf_mode/interfaces-vxlan.py | 7 +++- src/conf_mode/interfaces-wireguard.py | 7 +++- src/conf_mode/interfaces-wireless.py | 7 +++- src/conf_mode/interfaces-wirelessmodem.py | 7 +++- src/conf_mode/ipsec-settings.py | 7 +++- src/conf_mode/lldp.py | 7 +++- src/conf_mode/nat.py | 7 +++- src/conf_mode/ntp.py | 7 +++- src/conf_mode/protocols_igmp.py | 7 +++- src/conf_mode/protocols_mpls.py | 7 +++- src/conf_mode/protocols_pim.py | 7 +++- src/conf_mode/protocols_rip.py | 7 +++- src/conf_mode/protocols_static_multicast.py | 7 +++- src/conf_mode/salt-minion.py | 7 +++- src/conf_mode/service_console-server.py | 7 +++- src/conf_mode/service_ids_fastnetmon.py | 7 +++- src/conf_mode/service_ipoe-server.py | 7 +++- src/conf_mode/service_mdns-repeater.py | 7 +++- src/conf_mode/service_pppoe-server.py | 7 +++- src/conf_mode/service_router-advert.py | 7 +++- src/conf_mode/ssh.py | 7 +++- src/conf_mode/system-ip.py | 7 +++- src/conf_mode/system-ipv6.py | 7 +++- src/conf_mode/system-login-banner.py | 7 +++- src/conf_mode/system-login.py | 7 +++- src/conf_mode/system-options.py | 7 +++- src/conf_mode/system-syslog.py | 7 +++- src/conf_mode/system-timezone.py | 7 +++- src/conf_mode/system-wifi-regdom.py | 7 +++- src/conf_mode/system_console.py | 7 +++- src/conf_mode/system_lcd.py | 7 +++- src/conf_mode/task_scheduler.py | 7 +++- src/conf_mode/tftp_server.py | 7 +++- src/conf_mode/vpn_l2tp.py | 7 +++- src/conf_mode/vpn_pptp.py | 7 +++- src/conf_mode/vpn_sstp.py | 7 +++- src/conf_mode/vrf.py | 7 +++- src/conf_mode/vrrp.py | 7 +++- src/conf_mode/vyos_cert.py | 7 +++- 64 files changed, 382 insertions(+), 127 deletions(-) (limited to 'src') diff --git a/data/configd-include.json b/data/configd-include.json index fe51488c7..11d550f59 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -1 +1,64 @@ -[] +[ +"bcast_relay.py", +"dhcp_relay.py", +"dhcp_server.py", +"dhcpv6_relay.py", +"dhcpv6_server.py", +"dynamic_dns.py", +"firewall_options.py", +"host_name.py", +"http-api.py", +"https.py", +"igmp_proxy.py", +"intel_qat.py", +"interfaces-bonding.py", +"interfaces-bridge.py", +"interfaces-dummy.py", +"interfaces-ethernet.py", +"interfaces-geneve.py", +"interfaces-l2tpv3.py", +"interfaces-loopback.py", +"interfaces-macsec.py", +"interfaces-openvpn.py", +"interfaces-pppoe.py", +"interfaces-pseudo-ethernet.py", +"interfaces-tunnel.py", +"interfaces-vxlan.py", +"interfaces-wireguard.py", +"interfaces-wireless.py", +"interfaces-wirelessmodem.py", +"ipsec-settings.py", +"lldp.py", +"nat.py", +"ntp.py", +"protocols_igmp.py", +"protocols_mpls.py", +"protocols_pim.py", +"protocols_rip.py", +"protocols_static_multicast.py", +"salt-minion.py", +"service_console-server.py", +"service_ids_fastnetmon.py", +"service_ipoe-server.py", +"service_mdns-repeater.py", +"service_pppoe-server.py", +"service_router-advert.py", +"ssh.py", +"system-ip.py", +"system-ipv6.py", +"system-login-banner.py", +"system-options.py", +"system-syslog.py", +"system-timezone.py", +"system-wifi-regdom.py", +"system_console.py", +"system_lcd.py", +"task_scheduler.py", +"tftp_server.py", +"vpn_l2tp.py", +"vpn_pptp.py", +"vpn_sstp.py", +"vrf.py", +"vrrp.py", +"vyos_cert.py" +] \ No newline at end of file diff --git a/src/conf_mode/bcast_relay.py b/src/conf_mode/bcast_relay.py index a3e141a00..4a47b9246 100755 --- a/src/conf_mode/bcast_relay.py +++ b/src/conf_mode/bcast_relay.py @@ -29,8 +29,11 @@ airbag.enable() config_file_base = r'/etc/default/udp-broadcast-relay' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['service', 'broadcast-relay'] relay = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py index f093a005e..352865b9d 100755 --- a/src/conf_mode/dhcp_relay.py +++ b/src/conf_mode/dhcp_relay.py @@ -36,9 +36,12 @@ default_config_data = { 'relay_agent_packets': 'forward' } -def get_config(): +def get_config(config=None): relay = default_config_data - conf = Config() + if config: + conf = config + else: + conf = Config() if not conf.exists(['service', 'dhcp-relay']): return None else: diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index 0eaa14c5b..fd4e2ec61 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -126,9 +126,12 @@ def dhcp_static_route(static_subnet, static_router): return string -def get_config(): +def get_config(config=None): dhcp = default_config_data - conf = Config() + if config: + conf = config + else: + conf = Config() if not conf.exists('service dhcp-server'): return None else: diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py index 6ef290bf0..d4212b8be 100755 --- a/src/conf_mode/dhcpv6_relay.py +++ b/src/conf_mode/dhcpv6_relay.py @@ -35,9 +35,12 @@ default_config_data = { 'options': [], } -def get_config(): +def get_config(config=None): relay = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() if not conf.exists('service dhcpv6-relay'): return None else: diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index 53c8358a5..4ce4cada1 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -37,9 +37,12 @@ default_config_data = { 'shared_network': [] } -def get_config(): +def get_config(config=None): dhcpv6 = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['service', 'dhcpv6-server'] if not conf.exists(base): return None diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py index 5b1883c03..57c910a68 100755 --- a/src/conf_mode/dynamic_dns.py +++ b/src/conf_mode/dynamic_dns.py @@ -50,9 +50,12 @@ default_config_data = { 'deleted': False } -def get_config(): +def get_config(config=None): dyndns = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() base_level = ['service', 'dns', 'dynamic'] if not conf.exists(base_level): diff --git a/src/conf_mode/firewall_options.py b/src/conf_mode/firewall_options.py index 71b2a98b3..67bf5d0e2 100755 --- a/src/conf_mode/firewall_options.py +++ b/src/conf_mode/firewall_options.py @@ -32,9 +32,12 @@ default_config_data = { 'new_chain6': False } -def get_config(): +def get_config(config=None): opts = copy.deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() if not conf.exists('firewall options'): # bail out early return opts diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 9d66bd434..f4c75c257 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -43,8 +43,11 @@ default_config_data = { hostsd_tag = 'system' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() hosts = copy.deepcopy(default_config_data) diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py index b8a084a40..472eb77e4 100755 --- a/src/conf_mode/http-api.py +++ b/src/conf_mode/http-api.py @@ -39,7 +39,7 @@ dependencies = [ 'https.py', ] -def get_config(): +def get_config(config=None): http_api = deepcopy(vyos.defaults.api_data) x = http_api.get('api_keys') if x is None: @@ -48,7 +48,11 @@ def get_config(): default_key = x[0] keys_added = False - conf = Config() + if config: + conf = config + else: + conf = Config() + if not conf.exists('service https api'): return None else: diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index a13f131ab..dc51cb117 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -47,8 +47,12 @@ default_server_block = { 'certbot' : False } -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + if not conf.exists('service https'): return None diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py index 49aea9b7f..754f46566 100755 --- a/src/conf_mode/igmp_proxy.py +++ b/src/conf_mode/igmp_proxy.py @@ -36,9 +36,12 @@ default_config_data = { 'interfaces': [], } -def get_config(): +def get_config(config=None): igmp_proxy = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['protocols', 'igmp-proxy'] if not conf.exists(base): return None diff --git a/src/conf_mode/intel_qat.py b/src/conf_mode/intel_qat.py index 742f09a54..1e5101a9f 100755 --- a/src/conf_mode/intel_qat.py +++ b/src/conf_mode/intel_qat.py @@ -30,8 +30,11 @@ airbag.enable() # Define for recovering gl_ipsec_conf = None -def get_config(): - c = Config() +def get_config(config=None): + if config: + c = config + else: + c = Config() config_data = { 'qat_conf' : None, 'ipsec_conf' : None, diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 3b238f1ea..16e6e4f6e 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -53,12 +53,15 @@ def get_bond_mode(mode): else: raise ConfigError(f'invalid bond mode "{mode}"') -def get_config(): +def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'bonding'] bond = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index ee8e85e73..47c8c05f9 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -34,12 +34,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'bridge'] bridge = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 8df86c8ea..44fc9cb9e 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -28,12 +28,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'dummy'] dummy = get_interface_dict(conf, base) return dummy diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 10758e35a..a8df64cce 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -30,12 +30,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'ethernet'] ethernet = get_interface_dict(conf, base) return ethernet diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 1104bd3c0..cc2cf025a 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -30,12 +30,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'geneve'] geneve = get_interface_dict(conf, base) return geneve diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 0978df5b6..8250a3df8 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -35,12 +35,15 @@ airbag.enable() k_mod = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6'] -def get_config(): +def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'l2tpv3'] l2tpv3 = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py index 0398cd591..30a27abb4 100755 --- a/src/conf_mode/interfaces-loopback.py +++ b/src/conf_mode/interfaces-loopback.py @@ -25,12 +25,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'loopback'] loopback = get_interface_dict(conf, base) return loopback diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index ca15212d4..2866ccc0a 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -35,12 +35,15 @@ airbag.enable() # XXX: wpa_supplicant works on the source interface wpa_suppl_conf = '/run/wpa_supplicant/{source_interface}.conf' -def get_config(): +def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'macsec'] macsec = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 1420b4116..958b305dd 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -192,9 +192,12 @@ def getDefaultServer(network, topology, devtype): return server -def get_config(): +def get_config(config=None): openvpn = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 901ea769c..1b4b9e4ee 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -30,12 +30,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'pppoe'] pppoe = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index fe2d7b1be..59edca1cc 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -34,12 +34,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'pseudo-ethernet'] peth = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index ea15a7fb7..11d8d6edc 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -397,12 +397,16 @@ def ip_proto (afi): return 6 if afi == IP6 else 4 -def get_config(): +def get_config(config=None): ifname = os.environ.get('VYOS_TAGNODE_VALUE','') if not ifname: raise ConfigError('Interface not specified') - config = Config() + if config: + config = config + else: + config = Config() + conf = ConfigurationState(config, ['interfaces', 'tunnel ', ifname], default_config_data) options = conf.options changes = conf.changes diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 47c0bdcb8..bea3aa25b 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -30,12 +30,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'vxlan'] vxlan = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 8b64cde4d..e7c22da1a 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -33,12 +33,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'wireguard'] wireguard = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index b6f247952..9861f72db 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -64,12 +64,15 @@ def find_other_stations(conf, base, ifname): conf.set_level(old_level) return dict -def get_config(): +def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'wireless'] wifi = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index 6d168d918..7d8110096 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -31,12 +31,15 @@ airbag.enable() k_mod = ['option', 'usb_wwan', 'usbserial'] -def get_config(): +def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'wirelessmodem'] wwan = get_interface_dict(conf, base) return wwan diff --git a/src/conf_mode/ipsec-settings.py b/src/conf_mode/ipsec-settings.py index 015d1a480..11a5b7aaa 100755 --- a/src/conf_mode/ipsec-settings.py +++ b/src/conf_mode/ipsec-settings.py @@ -41,8 +41,11 @@ delim_ipsec_l2tp_begin = "### VyOS L2TP VPN Begin ###" delim_ipsec_l2tp_end = "### VyOS L2TP VPN End ###" charon_pidfile = "/var/run/charon.pid" -def get_config(): - config = Config() +def get_config(config=None): + if config: + config = config + else: + config = Config() data = {"install_routes": "yes"} if config.exists("vpn ipsec options disable-route-autoinstall"): diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py index 1b539887a..6b645857a 100755 --- a/src/conf_mode/lldp.py +++ b/src/conf_mode/lldp.py @@ -146,9 +146,12 @@ def get_location(config): return intfs_location -def get_config(): +def get_config(config=None): lldp = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() if not conf.exists(base): return None else: diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index f79f0f42b..eb634fd78 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -167,9 +167,12 @@ def parse_configuration(conf, source_dest): return ruleset -def get_config(): +def get_config(config=None): nat = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() # read in current nftable (once) for further processing tmp = cmd('nft -j list table raw') diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py index bba8f87a4..d6453ec83 100755 --- a/src/conf_mode/ntp.py +++ b/src/conf_mode/ntp.py @@ -27,8 +27,11 @@ airbag.enable() config_file = r'/etc/ntp.conf' systemd_override = r'/etc/systemd/system/ntp.service.d/override.conf' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['system', 'ntp'] ntp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) diff --git a/src/conf_mode/protocols_igmp.py b/src/conf_mode/protocols_igmp.py index ca148fd6a..6f4fc784d 100755 --- a/src/conf_mode/protocols_igmp.py +++ b/src/conf_mode/protocols_igmp.py @@ -29,8 +29,11 @@ airbag.enable() config_file = r'/tmp/igmp.frr' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() igmp_conf = { 'igmp_conf' : False, 'old_ifaces' : {}, diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index bcb16fa04..e515490d0 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -29,8 +29,11 @@ config_file = r'/tmp/ldpd.frr' def sysctl(name, value): call('sysctl -wq {}={}'.format(name, value)) -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() mpls_conf = { 'router_id' : None, 'mpls_ldp' : False, diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index 8aa324bac..6d333e19a 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -29,8 +29,11 @@ airbag.enable() config_file = r'/tmp/pimd.frr' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() pim_conf = { 'pim_conf' : False, 'old_pim' : { diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py index 95e8ce901..8ddd705f2 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -28,8 +28,11 @@ airbag.enable() config_file = r'/tmp/ripd.frr' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['protocols', 'rip'] rip_conf = { 'rip_conf' : False, diff --git a/src/conf_mode/protocols_static_multicast.py b/src/conf_mode/protocols_static_multicast.py index 232d1e181..99157835a 100755 --- a/src/conf_mode/protocols_static_multicast.py +++ b/src/conf_mode/protocols_static_multicast.py @@ -30,8 +30,11 @@ airbag.enable() config_file = r'/tmp/static_mcast.frr' # Get configuration for static multicast route -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() mroute = { 'old_mroute' : {}, 'mroute' : {} diff --git a/src/conf_mode/salt-minion.py b/src/conf_mode/salt-minion.py index 3343d1247..841bf6a39 100755 --- a/src/conf_mode/salt-minion.py +++ b/src/conf_mode/salt-minion.py @@ -44,9 +44,12 @@ default_config_data = { 'master_key': '' } -def get_config(): +def get_config(config=None): salt = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['service', 'salt-minion'] if not conf.exists(base): diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py index 613ec6879..0e5fc75b0 100755 --- a/src/conf_mode/service_console-server.py +++ b/src/conf_mode/service_console-server.py @@ -27,8 +27,11 @@ from vyos import ConfigError config_file = r'/run/conserver/conserver.cf' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['service', 'console-server'] # Retrieve CLI representation as dictionary diff --git a/src/conf_mode/service_ids_fastnetmon.py b/src/conf_mode/service_ids_fastnetmon.py index d46f9578e..27d0ee60c 100755 --- a/src/conf_mode/service_ids_fastnetmon.py +++ b/src/conf_mode/service_ids_fastnetmon.py @@ -28,8 +28,11 @@ airbag.enable() config_file = r'/etc/fastnetmon.conf' networks_list = r'/etc/networks_list' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['service', 'ids', 'ddos-protection'] fastnetmon = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) return fastnetmon diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index 553cc2e97..96cf932d1 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -55,8 +55,11 @@ default_config_data = { 'thread_cnt': get_half_cpus() } -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base_path = ['service', 'ipoe-server'] if not conf.exists(base_path): return None diff --git a/src/conf_mode/service_mdns-repeater.py b/src/conf_mode/service_mdns-repeater.py index 1a6b2c328..729518c96 100755 --- a/src/conf_mode/service_mdns-repeater.py +++ b/src/conf_mode/service_mdns-repeater.py @@ -28,8 +28,11 @@ airbag.enable() config_file = r'/etc/default/mdns-repeater' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['service', 'mdns', 'repeater'] mdns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) return mdns diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index 39d34a7e2..45d3806d5 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -85,8 +85,11 @@ default_config_data = { 'thread_cnt': get_half_cpus() } -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base_path = ['service', 'pppoe-server'] if not conf.exists(base_path): return None diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py index 4e1c432ab..687d7068f 100755 --- a/src/conf_mode/service_router-advert.py +++ b/src/conf_mode/service_router-advert.py @@ -29,8 +29,11 @@ airbag.enable() config_file = r'/run/radvd/radvd.conf' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['service', 'router-advert'] rtradv = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index 7b262565a..a19fa72d8 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -31,8 +31,11 @@ airbag.enable() config_file = r'/run/ssh/sshd_config' systemd_override = r'/etc/systemd/system/ssh.service.d/override.conf' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['service', 'ssh'] if not conf.exists(base): return None diff --git a/src/conf_mode/system-ip.py b/src/conf_mode/system-ip.py index 85f1e3771..64c9e6d05 100755 --- a/src/conf_mode/system-ip.py +++ b/src/conf_mode/system-ip.py @@ -35,9 +35,12 @@ default_config_data = { def sysctl(name, value): call('sysctl -wq {}={}'.format(name, value)) -def get_config(): +def get_config(config=None): ip_opt = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() conf.set_level('system ip') if conf.exists(''): if conf.exists('arp table-size'): diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py index 3417c609d..f70ec2631 100755 --- a/src/conf_mode/system-ipv6.py +++ b/src/conf_mode/system-ipv6.py @@ -41,9 +41,12 @@ default_config_data = { def sysctl(name, value): call('sysctl -wq {}={}'.format(name, value)) -def get_config(): +def get_config(config=None): ip_opt = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() conf.set_level('system ipv6') if conf.exists(''): ip_opt['disable_addr_assignment'] = conf.exists('disable') diff --git a/src/conf_mode/system-login-banner.py b/src/conf_mode/system-login-banner.py index 5c0adc921..569010735 100755 --- a/src/conf_mode/system-login-banner.py +++ b/src/conf_mode/system-login-banner.py @@ -41,9 +41,12 @@ default_config_data = { 'motd': motd } -def get_config(): +def get_config(config=None): banner = default_config_data - conf = Config() + if config: + conf = config + else: + conf = Config() base_level = ['system', 'login', 'banner'] if not conf.exists(base_level): diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index b1dd583b5..2aca199f9 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -56,9 +56,12 @@ def get_local_users(): return local_users -def get_config(): +def get_config(config=None): login = default_config_data - conf = Config() + if config: + conf = config + else: + conf = Config() base_level = ['system', 'login'] # We do not need to check if the nodes exist or not and bail out early diff --git a/src/conf_mode/system-options.py b/src/conf_mode/system-options.py index 0aacd19d8..6ac35a4ab 100755 --- a/src/conf_mode/system-options.py +++ b/src/conf_mode/system-options.py @@ -31,8 +31,11 @@ curlrc_config = r'/etc/curlrc' ssh_config = r'/etc/ssh/ssh_config' systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['system', 'options'] options = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) return options diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py index cfc1ca55f..d29109c41 100755 --- a/src/conf_mode/system-syslog.py +++ b/src/conf_mode/system-syslog.py @@ -27,8 +27,11 @@ from vyos.template import render from vyos import airbag airbag.enable() -def get_config(): - c = Config() +def get_config(config=None): + if config: + c = config + else: + c = Config() if not c.exists('system syslog'): return None c.set_level('system syslog') diff --git a/src/conf_mode/system-timezone.py b/src/conf_mode/system-timezone.py index 0f4513122..4d9f017a6 100755 --- a/src/conf_mode/system-timezone.py +++ b/src/conf_mode/system-timezone.py @@ -29,9 +29,12 @@ default_config_data = { 'name': 'UTC' } -def get_config(): +def get_config(config=None): tz = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() if conf.exists('system time-zone'): tz['name'] = conf.return_value('system time-zone') diff --git a/src/conf_mode/system-wifi-regdom.py b/src/conf_mode/system-wifi-regdom.py index 30ea89098..874f93923 100755 --- a/src/conf_mode/system-wifi-regdom.py +++ b/src/conf_mode/system-wifi-regdom.py @@ -34,9 +34,12 @@ default_config_data = { 'deleted' : False } -def get_config(): +def get_config(config=None): regdom = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['system', 'wifi-regulatory-domain'] # Check if interface has been removed diff --git a/src/conf_mode/system_console.py b/src/conf_mode/system_console.py index 6f83335f3..b17818797 100755 --- a/src/conf_mode/system_console.py +++ b/src/conf_mode/system_console.py @@ -26,8 +26,11 @@ airbag.enable() by_bus_dir = '/dev/serial/by-bus' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['system', 'console'] # retrieve configuration at once diff --git a/src/conf_mode/system_lcd.py b/src/conf_mode/system_lcd.py index 31a09252d..a540d1b9e 100755 --- a/src/conf_mode/system_lcd.py +++ b/src/conf_mode/system_lcd.py @@ -29,8 +29,11 @@ airbag.enable() lcdd_conf = '/run/LCDd/LCDd.conf' lcdproc_conf = '/run/lcdproc/lcdproc.conf' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['system', 'lcd'] lcd = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) diff --git a/src/conf_mode/task_scheduler.py b/src/conf_mode/task_scheduler.py index 51d8684cb..129be5d3c 100755 --- a/src/conf_mode/task_scheduler.py +++ b/src/conf_mode/task_scheduler.py @@ -53,8 +53,11 @@ def make_command(executable, arguments): else: return("sg vyattacfg \"{0}\"".format(executable)) -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() conf.set_level("system task-scheduler task") task_names = conf.list_nodes("") tasks = [] diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py index d31851bef..ad5ee9c33 100755 --- a/src/conf_mode/tftp_server.py +++ b/src/conf_mode/tftp_server.py @@ -40,9 +40,12 @@ default_config_data = { 'listen': [] } -def get_config(): +def get_config(config=None): tftpd = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['service', 'tftp-server'] if not conf.exists(base): return None diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py index 26ad1af84..13831dcd8 100755 --- a/src/conf_mode/vpn_l2tp.py +++ b/src/conf_mode/vpn_l2tp.py @@ -70,8 +70,11 @@ default_config_data = { 'thread_cnt': get_half_cpus() } -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base_path = ['vpn', 'l2tp', 'remote-access'] if not conf.exists(base_path): return None diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py index 32cbadd74..9f3b40534 100755 --- a/src/conf_mode/vpn_pptp.py +++ b/src/conf_mode/vpn_pptp.py @@ -56,8 +56,11 @@ default_pptp = { 'thread_cnt': get_half_cpus() } -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base_path = ['vpn', 'pptp', 'remote-access'] if not conf.exists(base_path): return None diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py index ddb499bf4..7fc370f99 100755 --- a/src/conf_mode/vpn_sstp.py +++ b/src/conf_mode/vpn_sstp.py @@ -65,10 +65,13 @@ default_config_data = { 'thread_cnt' : get_half_cpus() } -def get_config(): +def get_config(config=None): sstp = deepcopy(default_config_data) base_path = ['vpn', 'sstp'] - conf = Config() + if config: + conf = config + else: + conf = Config() if not conf.exists(base_path): return None diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 56ca813ff..2f4da0240 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -76,8 +76,11 @@ def vrf_routing(c, match): return matched -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() vrf_config = deepcopy(default_config_data) cfg_base = ['vrf'] diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py index 292eb0c78..f1ceb261b 100755 --- a/src/conf_mode/vrrp.py +++ b/src/conf_mode/vrrp.py @@ -32,11 +32,14 @@ from vyos.ifconfig.vrrp import VRRP from vyos import airbag airbag.enable() -def get_config(): +def get_config(config=None): vrrp_groups = [] sync_groups = [] - config = vyos.config.Config() + if config: + config = config + else: + config = vyos.config.Config() # Get the VRRP groups for group_name in config.list_nodes("high-availability vrrp group"): diff --git a/src/conf_mode/vyos_cert.py b/src/conf_mode/vyos_cert.py index fb4644d5a..dc7c64684 100755 --- a/src/conf_mode/vyos_cert.py +++ b/src/conf_mode/vyos_cert.py @@ -103,10 +103,13 @@ def generate_self_signed(cert_data): if san_config: san_config.close() -def get_config(): +def get_config(config=None): vyos_cert = vyos.defaults.vyos_cert_data - conf = Config() + if config: + conf = config + else: + conf = Config() if not conf.exists('service https certificates system-generated-certificate'): return None else: -- cgit v1.2.3 From 9c63731d6683f59ea784c08852ed38e3ac22794b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 31 Aug 2020 19:57:06 +0200 Subject: T2636: remove workarounds for get_config_dict() Now that b40c52682a256 ("config: T2636: get_config_dict() returns a list on multi node by default") is implemented the workarounds can be removed. --- data/templates/ntp/ntp.conf.tmpl | 16 ++++------------ data/templates/ssh/sshd_config.tmpl | 16 ++++------------ data/templates/wifi/hostapd.conf.tmpl | 12 ------------ python/vyos/configdict.py | 3 --- python/vyos/ifconfig/interface.py | 7 ------- python/vyos/ifconfig/loopback.py | 5 ----- src/conf_mode/https.py | 9 --------- 7 files changed, 8 insertions(+), 60 deletions(-) (limited to 'src') diff --git a/data/templates/ntp/ntp.conf.tmpl b/data/templates/ntp/ntp.conf.tmpl index 6ef0c0f2c..df8157a41 100644 --- a/data/templates/ntp/ntp.conf.tmpl +++ b/data/templates/ntp/ntp.conf.tmpl @@ -25,23 +25,15 @@ server {{ srv | replace('_', '-') }} iburst {{ options }} {% if allow_clients is defined and allow_clients.address is defined %} # Allowed clients configuration -{% if allow_clients.address is string %} -restrict {{ allow_clients.address|address_from_cidr }} mask {{ allow_clients.address|netmask_from_cidr }} nomodify notrap nopeer -{% else %} -{% for address in allow_clients.address %} +{% for address in allow_clients.address %} restrict {{ address|address_from_cidr }} mask {{ address|netmask_from_cidr }} nomodify notrap nopeer -{% endfor %} -{% endif %} +{% endfor %} {% endif %} {% if listen_address %} # NTP should listen on configured addresses only interface ignore wildcard -{% if listen_address is string %} -interface listen {{ listen_address }} -{% else %} -{% for address in listen_address %} +{% for address in listen_address %} interface listen {{ address }} -{% endfor %} -{% endif %} +{% endfor %} {% endif %} diff --git a/data/templates/ssh/sshd_config.tmpl b/data/templates/ssh/sshd_config.tmpl index 4fde24255..52d537aca 100644 --- a/data/templates/ssh/sshd_config.tmpl +++ b/data/templates/ssh/sshd_config.tmpl @@ -37,13 +37,9 @@ PermitRootLogin no UseDNS {{ "no" if disable_host_validation is defined else "yes" }} # Specifies the port number that sshd(8) listens on -{% if port is string %} -Port {{ port }} -{% else %} -{% for value in port %} +{% for value in port %} Port {{ value }} -{% endfor %} -{% endif %} +{% endfor %} # Gives the verbosity level that is used when logging messages from sshd LogLevel {{ loglevel | upper }} @@ -53,13 +49,9 @@ PasswordAuthentication {{ "no" if disable_password_authentication is defined els {% if listen_address %} # Specifies the local addresses sshd should listen on -{% if listen_address is string %} -ListenAddress {{ listen_address }} -{% else %} -{% for address in listen_address %} +{% for address in listen_address %} ListenAddress {{ address }} -{% endfor %} -{% endif %} +{% endfor %} {% endif %} {% if ciphers %} diff --git a/data/templates/wifi/hostapd.conf.tmpl b/data/templates/wifi/hostapd.conf.tmpl index 765668c57..a7efee6d5 100644 --- a/data/templates/wifi/hostapd.conf.tmpl +++ b/data/templates/wifi/hostapd.conf.tmpl @@ -500,18 +500,10 @@ wpa=1 {% if security.wpa.mode is defined and security.wpa.mode == 'wpa2' %} # Pairwise cipher for RSN/WPA2 (default: use wpa_pairwise value) -{% if security.wpa.cipher is string %} -rsn_pairwise={{ security.wpa.cipher }} -{% else %} rsn_pairwise={{ security.wpa.cipher | join(" ") }} -{% endif %} {% else %} # Pairwise cipher for WPA (v1) (default: TKIP) -{% if security.wpa.cipher is string %} -wpa_pairwise={{ security.wpa.cipher }} -{% else %} wpa_pairwise={{ security.wpa.cipher | join(" ") }} -{% endif %} {% endif %} {% endif %} @@ -522,11 +514,7 @@ wpa_pairwise={{ security.wpa.cipher | join(" ") }} # overriding the group cipher with an unexpected value can result in # interoperability issues and in general, this parameter is mainly used for # testing purposes. -{% if security.wpa.group_cipher is string %} -group_cipher={{ security.wpa.group_cipher }} -{% else %} group_cipher={{ security.wpa.group_cipher | join(" ") }} -{% endif %} {% endif %} {% if security.wpa.passphrase is defined %} diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index bd8624ced..e8c0aa5b3 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -270,9 +270,6 @@ def get_interface_dict(config, base, ifname=''): eui64 = leaf_node_changed(config, ['ipv6', 'address', 'eui64']) if eui64: - # XXX: T2636 workaround: convert string to a list with one element - if isinstance(eui64, str): - eui64 = [eui64] tmp = vyos_dict_search('ipv6.address', dict) if not tmp: dict.update({'ipv6': {'address': {'eui64_old': eui64}}}) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 67ba973c4..ef2336c17 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -898,10 +898,6 @@ class Interface(Control): # configured addresses will be removed first new_addr = config.get('address', []) - # XXX: T2636 workaround: convert string to a list with one element - if isinstance(new_addr, str): - new_addr = [new_addr] - # always ensure DHCP client is stopped (when not configured explicitly) if 'dhcp' not in new_addr: self.del_addr('dhcp') @@ -1023,9 +1019,6 @@ class Interface(Control): # Add IPv6 EUI-based addresses tmp = vyos_dict_search('ipv6.address.eui64', config) if tmp: - # XXX: T2636 workaround: convert string to a list with one element - if isinstance(tmp, str): - tmp = [tmp] for addr in tmp: self.add_ipv6_eui64_address(addr) diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py index 2b4ebfdcc..c70e1773f 100644 --- a/python/vyos/ifconfig/loopback.py +++ b/python/vyos/ifconfig/loopback.py @@ -64,11 +64,6 @@ class LoopbackIf(Interface): on any interface. """ addr = config.get('address', []) - # XXX workaround for T2636, convert IP address string to a list - # with one element - if isinstance(addr, str): - addr = [addr] - # We must ensure that the loopback addresses are never deleted from the system addr += self._persistent_addresses diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index dc51cb117..de228f0f8 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -74,9 +74,6 @@ def get_config(config=None): server_block['address'] = data.get('listen-address', '*') server_block['port'] = data.get('listen-port', '443') name = data.get('server-name', ['_']) - # XXX: T2636 workaround: convert string to a list with one element - if not isinstance(name, list): - name = [name] server_block['name'] = name server_block_list.append(server_block) @@ -98,9 +95,6 @@ def get_config(config=None): certbot = False cert_domains = cert_dict.get('certbot', {}).get('domain-name', []) if cert_domains: - # XXX: T2636 workaround: convert string to a list with one element - if not isinstance(cert_domains, list): - cert_domains = [cert_domains] certbot = True for domain in cert_domains: sub_list = vyos.certbot_util.choose_server_block(server_block_list, @@ -125,9 +119,6 @@ def get_config(config=None): if port: api_data['port'] = port vhosts = https_dict.get('api-restrict', {}).get('virtual-host', []) - # XXX: T2636 workaround: convert string to a list with one element - if not isinstance(vhosts, list): - vhosts = [vhosts] if vhosts: api_data['vhost'] = vhosts[:] -- cgit v1.2.3 From 570886631deae3de8c38ea4ff70710e7a4858cbd Mon Sep 17 00:00:00 2001 From: sever-sever Date: Tue, 1 Sep 2020 16:07:51 +0000 Subject: frr-template: T2850: Add BGP template for FRR --- data/templates/frr/bgp.frr.tmpl | 966 ++++++++++++++++++++++++++++++++++++++++ src/conf_mode/protocols_bgp.py | 69 ++- 2 files changed, 995 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/data/templates/frr/bgp.frr.tmpl b/data/templates/frr/bgp.frr.tmpl index cdf4cb4fe..d011a1e85 100644 --- a/data/templates/frr/bgp.frr.tmpl +++ b/data/templates/frr/bgp.frr.tmpl @@ -1 +1,967 @@ +{% set conf_bgp = nbgp -%} +{% for asn in nbgp -%} ! +router bgp {{ asn }} + no bgp default ipv4-unicast + +{#- set 'conf_bgp[asn].parameters' as bgp_params #} +{%- set bgp_params = conf_bgp[asn].parameters %} +{%- set bgp_afi = conf_bgp[asn].address_family %} + +{#- START Global ASN address-family section; set protocol bgp xxx address-family #} +{%- if 'address_family' in conf_bgp[asn] %} +{%- for type in bgp_afi %} +{%- if type == "ipv4_unicast" %} + ! + address-family ipv4 unicast +{# need to check #} +{%- if 'aggregate_address' in bgp_afi[type] %} +{%- for ip in bgp_afi[type].aggregate_address %} +{%- if ( ('as_set' and 'summary_only') in bgp_afi[type].aggregate_address[ip] ) %} + aggregate-address {{ ip }} as-set summary-only +{%- elif 'as_set' in bgp_afi[type].aggregate_address[ip] %} + aggregate-address {{ ip }} as-set +{%- elif 'summary_only' in bgp_afi[type].aggregate_address[ip] %} + aggregate-address {{ ip }} summary-only +{%- else %} + aggregate-address {{ ip }} +{%- endif %} +{%- endfor %} +{%- endif %} +{# END aggregate address#} +{#- redistribute #} +{# need to check. dont work. + 'metric' and 'route_map' match also only 'route_map' + 'table' parameter also include in protocol, its not what I want #} +{%- if 'redistribute' in bgp_afi[type] %} +{%- if 'table' in bgp_afi[type].redistribute %} + redistribute table {{bgp_afi[type].redistribute.table}} +{%- endif %} +{%- for protocol in bgp_afi[type].redistribute %} +{%- if ( ('metric' and 'route_map') in bgp_afi[type].redistribute[protocol] ) %} + redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}} route-map {{bgp_afi[type].redistribute[protocol].route_map}} +{%- elif 'metric' in bgp_afi[type].redistribute[protocol] %} + redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}} +{%- elif 'route_map' in bgp_afi[type].redistribute[protocol] %} + redistribute {{protocol}} route-map {{bgp_afi[type].redistribute[protocol].route_map}} +{%- else %} + redistribute {{protocol}} +{%- endif %} +{%- endfor %} +{%- endif %} +{#- END redistribute #} + +{%- if 'network' in bgp_afi[type] %} +{%- for net in bgp_afi[type].network %} + network {{ net }} +{%- endfor %} +{%- endif %} + exit-address-family + ! +{%- endif %} + +{%- if type == "ipv6_unicast" %} + ! + address-family ipv6 unicast +{%- if 'aggregate_address' in bgp_afi[type] %} +{%- for ip in bgp_afi[type].aggregate_address %} +{%- if ( ('as_set' and 'summary_only') in bgp_afi[type].aggregate_address[ip] ) %} + aggregate-address {{ ip }} as-set summary-only +{%- elif 'as_set' in bgp_afi[type].aggregate_address[ip] %} + aggregate-address {{ ip }} as-set +{%- elif 'summary_only' in bgp_afi[type].aggregate_address[ip] %} + aggregate-address {{ ip }} summary-only +{%- else %} + aggregate-address {{ ip }} +{%- endif %} +{%- endfor %} +{%- endif %} +{# END aggregate address#} + +{#- redistribute #} +{# need to check. doesn't work. 'metric' and 'route_map' match also only 'route_map' #} +{%- if 'redistribute' in bgp_afi[type] %} +{%- if 'table' in bgp_afi[type].redistribute %} + redistribute table {{bgp_afi[type].redistribute.table}} +{%- endif %} +{%- for protocol in bgp_afi[type].redistribute %} +{%- if ( ('metric' and 'route_map') in bgp_afi[type].redistribute[protocol] ) %} + redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}} route-map {{bgp_afi[type].redistribute[protocol].route_map}} +{%- elif 'metric' in bgp_afi[type].redistribute[protocol] %} + redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}} +{%- elif 'route_map' in bgp_afi[type].redistribute[protocol] %} + redistribute {{protocol}} route-map {{bgp_afi[type].redistribute[protocol].route_map}} +{%- else %} + redistribute {{protocol}} +{%- endif %} +{%- endfor %} +{%- endif %} +{#- END redistribute #} + +{%- if 'network' in bgp_afi[type] %} +{%- for net in bgp_afi[type].network %} + network {{ net }} +{%- endfor %} +{%- endif %} + exit-address-family +! +{%- endif %} +{%- endfor %} +{%- endif %} +{#- END Global ASN address-family section; set protocols bgp 65001 address-family #} + +{#- set protocols nbgp xxxx maximum-paths ibgp x, Generated by default for afi_4 #} +{#- We don't have this parameter in afi_6. But this is supported in the FRR #} +{%- if 'maximum_paths' in conf_bgp[asn] %} +{%- if 'ebgp' in conf_bgp[asn].maximum_paths %} + ! + address-family ipv4 unicast + maximum-paths {{ conf_bgp[asn].maximum_paths.ebgp }} + exit-address-family + ! +{%- endif %} +{%- if 'ibgp' in conf_bgp[asn].maximum_paths %} + ! + address-family ipv4 unicast + maximum-paths ibgp {{ conf_bgp[asn].maximum_paths.ibgp }} + exit-address-family + ! +{%- endif %} +{%- endif %} + +{#- START peer-group; set protocol bgp xxx peer-group #} +{%- if 'peer_group' in conf_bgp[asn] %} +{%- for pr_group in conf_bgp[asn].peer_group %} +{%- set conf_peer_group = conf_bgp[asn].peer_group[pr_group] %} + neighbor {{pr_group}} peer-group + +{#- First parameter for peer-group - remote-as #} +{%- if 'remote_as' in conf_peer_group %} + neighbor {{ pr_group }} remote-as {{ conf_peer_group.remote_as }} +{%- endif %} + +{%- if 'bfd' in conf_peer_group %} + neighbor {{ pr_group }} bfd +{%- endif %} + +{%- if 'capability' in conf_peer_group %} +{%- if 'dynamic' in conf_peer_group.capability %} + neighbor {{ pr_group }} capability dynamic +{%- endif %} +{%- if 'extended_nexthop' in conf_peer_group.capability %} + neighbor {{ pr_group }} capability extended-nexthop +{%- endif %} +{%- endif %} + +{%- if 'description' in conf_peer_group %} + neighbor {{ pr_group }} description {{ conf_peer_group.description }} +{%- endif %} + +{%- if 'disable_capability_negotiation' in conf_peer_group %} + neighbor {{ pr_group }} disable-capability-negotiation +{%- endif %} + +{#- https://phabricator.vyos.net/T2844. 'disable-send-community' only for afi #} +{%- if 'disable_send_community' in conf_peer_group %} + ! +{%- endif %} + +{%- if 'ebgp_multihop' in conf_peer_group %} + neighbor {{ pr_group }} ebgp-multihop {{conf_peer_group.ebgp_multihop}} +{%- endif %} + +{%- if 'local_as' in conf_peer_group %} +{%- for loc_asn in conf_peer_group.local_as %} +{%- if 'no_prepend' in conf_peer_group.local_as[loc_asn] %} + neighbor {{ pr_group }} local-as {{loc_asn}} no-prepend +{%- else %} + neighbor {{ pr_group }} local-as {{loc_asn}} +{%- endif %} +{%- endfor %} +{%- endif %} + +{%- if 'override_capability' in conf_peer_group %} + neighbor {{ pr_group }} override-capability +{%- endif %} + +{%- if 'passive' in conf_peer_group %} + neighbor {{ pr_group }} passive +{%- endif %} + +{%- if 'password' in conf_peer_group %} + neighbor {{ pr_group }} password {{ conf_peer_group.password }} +{%- endif %} + +{%- if 'shutdown' in conf_peer_group %} + neighbor {{ pr_group }} shutdown +{%- endif %} + +{%- if 'ttl_security' in conf_peer_group %} +{%- if 'hops' in conf_peer_group.ttl_security %} + neighbor {{ pr_group }} ttl-security hops {{conf_peer_group.ttl_security.hops}} +{%- endif %} +{%- endif %} + +{%- if 'update_source' in conf_peer_group %} + neighbor {{ pr_group }} update-source {{ conf_peer_group.update_source }} +{%- endif %} + +{# START peer-group afi; set protocols bgp xxx peer-group FOO address-family #} +{%- if 'address_family' in conf_peer_group %} +{%- for afi in conf_peer_group.address_family %} +{%- if afi == "ipv4_unicast" %} + ! + address-family ipv4 unicast + +{%- if 'allowas_in' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'number' in conf_peer_group.address_family.ipv4_unicast.allowas_in %} + neighbor {{ pr_group }} allowas-in {{ conf_peer_group.address_family.ipv4_unicast.allowas_in.number }} +{%- else %} + neighbor {{ pr_group }} allowas-in +{%- endif %} +{%- endif %} + +{#- START Single Params for peer-group; set protocols bgp xxx peer-group FOO address-family ipv4-unicast #} + +{%- if 'remove_private_as' in conf_peer_group.address_family.ipv4_unicast %} + neighbor {{ pr_group }} remove-private-AS +{%- endif %} + +{%- if 'route_reflector_client' in conf_peer_group.address_family.ipv4_unicast %} + neighbor {{ pr_group }} route-reflector-client +{%- endif %} + +{%- if 'weight' in conf_peer_group.address_family.ipv4_unicast %} + neighbor {{ pr_group }} weight {{ conf_peer_group.address_family.ipv4_unicast.weight }} +{%- endif %} +{#- END single params for peer-group #} + +{#- Checks need to be done as-path|med|next-hop #} +{%- if 'attribute_unchanged' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'as_path' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged %} + neighbor {{ pr_group }} attribute-unchanged as-path +{%- else %} + neighbor {{ pr_group }} attribute-unchanged as-path next-hop med +{%- endif %} +{%- endif %} +{#- END attribute-unchanged #} + +{%- if 'capability' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'receive' in conf_peer_group.address_family.ipv4_unicast.capability.orf.prefix_list %} + neighbor {{ pr_group }} capability orf prefix-list receive +{%- endif %} +{%- if 'send' in conf_peer_group.address_family.ipv4_unicast.capability.orf.prefix_list %} + neighbor {{ pr_group }} capability orf prefix-list send +{%- endif %} +{%- endif %} + +{%- if 'default_originate' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'route_map' in conf_peer_group.address_family.ipv4_unicast.default_originate %} + neighbor {{ pr_group }} default-originate route-map {{ conf_peer_group.address_family.ipv4_unicast.default_originate.route_map }} +{%- else %} + neighbor {{ pr_group }} default-originate +{%- endif %} +{%- endif %} + +{%- if 'distribute_list' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'export' in conf_peer_group.address_family.ipv4_unicast.distribute_list %} + neighbor {{ pr_group }} distribute-list {{conf_peer_group.address_family.ipv4_unicast.distribute_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer_group.address_family.ipv4_unicast.distribute_list %} + neighbor {{ pr_group }} distribute-list {{conf_peer_group.address_family.ipv4_unicast.distribute_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'filter_list' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'export' in conf_peer_group.address_family.ipv4_unicast.filter_list %} + neighbor {{ pr_group }} filter-list {{conf_peer_group.address_family.ipv4_unicast.filter_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer_group.address_family.ipv4_unicast.filter_list %} + neighbor {{ pr_group }} filter-list {{conf_peer_group.address_family.ipv4_unicast.filter_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'maximum_prefix' in conf_peer_group.address_family.ipv4_unicast %} + neighbor {{ pr_group }} maximum-prefix {{ conf_peer_group.address_family.ipv4_unicast.maximum_prefix }} +{%- endif %} + +{#- https://phabricator.vyos.net/T1817 #} +{%- if 'nexthop_self' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'force' in conf_peer_group.address_family.ipv4_unicast.nexthop_self %} + neighbor {{ pr_group }} next-hop-self force + neighbor {{ pr_group }} next-hop-self +{%- else %} + neighbor {{ pr_group }} next-hop-self +{%- endif %} +{%- endif %} + +{%- if 'route_server_client' in conf_peer_group.address_family.ipv4_unicast %} + neighbor {{ pr_group }} route-server-client +{%- endif %} + +{%- if 'route_map' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'export' in conf_peer_group.address_family.ipv4_unicast.route_map %} + neighbor {{ pr_group }} route-map {{conf_peer_group.address_family.ipv4_unicast.route_map.export}} out +{%- endif %} +{%- if 'import' in conf_peer_group.address_family.ipv4_unicast.route_map %} + neighbor {{ pr_group }} route-map {{conf_peer_group.address_family.ipv4_unicast.route_map.import}} in +{%- endif %} +{%- endif %} +{%- if 'prefix_list' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'export' in conf_peer_group.address_family.ipv4_unicast.prefix_list %} + neighbor {{ pr_group }} prefix-list {{conf_peer_group.address_family.ipv4_unicast.prefix_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer_group.address_family.ipv4_unicast.prefix_list %} + neighbor {{ pr_group }} prefix-list {{conf_peer_group.address_family.ipv4_unicast.prefix_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'soft_reconfiguration' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'inbound' is defined %} + neighbor {{ pr_group }} soft-reconfiguration inbound +{%- endif %} +{%- endif %} + +{#- Need to check. https://phabricator.vyos.net/T2387#73900 #} +{%- if 'unsuppress_map' in conf_peer_group.address_family.ipv4_unicast %} + neighbor {{ pr_group }} unsuppress-map {{conf_peer_group.address_family.ipv4_unicast.unsuppress_map}} +{%- endif %} + neighbor {{ pr_group }} activate + exit-address-family + ! +{%- endif %} + +{%- if afi == "ipv6_unicast" %} + ! + address-family ipv6 unicast + +{%- if 'allowas_in' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'number' in conf_peer_group.address_family.ipv6_unicast.allowas_in %} + neighbor {{ pr_group }} allowas-in {{ conf_peer_group.address_family.ipv6_unicast.allowas_in.number }} +{%- else %} + neighbor {{ pr_group }} allowas-in +{%- endif %} +{%- endif %} + +{#- START Single Params for peer-group afi6; set protocols bgp xxx peer-group FOO address-family ipv6-unicast #} +{%- if 'remove_private_as' in conf_peer_group.address_family.ipv6_unicast %} + neighbor {{ pr_group }} remove-private-AS +{%- endif %} + +{%- if 'route_reflector_client' in conf_peer_group.address_family.ipv6_unicast %} + neighbor {{ pr_group }} route-reflector-client +{%- endif %} + +{%- if 'weight' in conf_peer_group.address_family.ipv6_unicast %} + neighbor {{ pr_group }} weight {{ conf_peer_group.address_family.ipv6_unicast.weight }} +{%- endif %} +{#- END single params for peer-group afi6 #} + +{#- Checks need to be done as-path|med|next-hop #} +{%- if 'attribute_unchanged' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'as_path' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged %} + neighbor {{ pr_group }} attribute-unchanged as-path +{%- else %} + neighbor {{ pr_group }} attribute-unchanged as-path next-hop med +{%- endif %} +{%- endif %} + +{%- if 'capability' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'receive' in conf_peer_group.address_family.ipv6_unicast.capability.orf.prefix_list %} + neighbor {{ pr_group }} capability orf prefix-list receive +{%- endif %} +{%- if 'send' in conf_peer_group.address_family.ipv6_unicast.capability.orf.prefix_list %} + neighbor {{ pr_group }} capability orf prefix-list send +{%- endif %} +{%- endif %} + +{%- if 'default_originate' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'route_map' in conf_peer_group.address_family.ipv6_unicast.default_originate %} + neighbor {{ pr_group }} default-originate route-map {{ conf_peer_group.address_family.ipv6_unicast.default_originate.route_map }} +{%- else %} + neighbor {{ pr_group }} default-originate +{%- endif %} +{%- endif %} + +{%- if 'distribute_list' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'export' in conf_peer_group.address_family.ipv6_unicast.distribute_list %} + neighbor {{ pr_group }} distribute-list {{conf_peer_group.address_family.ipv6_unicast.distribute_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer_group.address_family.ipv6_unicast.distribute_list %} + neighbor {{ pr_group }} distribute-list {{conf_peer_group.address_family.ipv6_unicast.distribute_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'filter_list' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'export' in conf_peer_group.address_family.ipv6_unicast.filter_list %} + neighbor {{ pr_group }} filter-list {{conf_peer_group.address_family.ipv6_unicast.filter_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer_group.address_family.ipv6_unicast.filter_list %} + neighbor {{ pr_group }} filter-list {{conf_peer_group.address_family.ipv6_unicast.filter_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'maximum_prefix' in conf_peer_group.address_family.ipv6_unicast %} + neighbor {{ pr_group }} maximum-prefix {{ conf_peer_group.address_family.ipv6_unicast.maximum_prefix }} +{%- endif %} + +{#- https://phabricator.vyos.net/T1817 #} +{%- if 'nexthop_self' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'force' in conf_peer_group.address_family.ipv6_unicast.nexthop_self %} + neighbor {{ pr_group }} next-hop-self force + neighbor {{ pr_group }} next-hop-self +{%- else %} + neighbor {{ pr_group }} next-hop-self +{%- endif %} +{%- endif %} + +{%- if 'route_server_client' in conf_peer_group.address_family.ipv6_unicast %} + neighbor {{ pr_group }} route-server-client +{%- endif %} + +{%- if 'route_map' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'export' in conf_peer_group.address_family.ipv6_unicast.route_map %} + neighbor {{ pr_group }} route-map {{conf_peer_group.address_family.ipv6_unicast.route_map.export}} out +{%- endif %} +{%- if 'import' in conf_peer_group.address_family.ipv6_unicast.route_map %} + neighbor {{ pr_group }} route-map {{conf_peer_group.address_family.ipv6_unicast.route_map.import}} in +{%- endif %} +{%- endif %} +{%- if 'prefix_list' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'export' in conf_peer_group.address_family.ipv6_unicast.prefix_list %} + neighbor {{ pr_group }} prefix-list {{conf_peer_group.address_family.ipv6_unicast.prefix_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer_group.address_family.ipv6_unicast.prefix_list %} + neighbor {{ pr_group }} prefix-list {{conf_peer_group.address_family.ipv6_unicast.prefix_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'soft_reconfiguration' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'inbound' is defined %} + neighbor {{ pr_group }} soft-reconfiguration inbound +{%- endif %} +{%- endif %} + +{#- Checks need to be done. https://phabricator.vyos.net/T2387#73900 #} +{%- if 'unsuppress_map' in conf_peer_group.address_family.ipv6_unicast %} + neighbor {{ pr_group }} unsuppress-map {{conf_peer_group.address_family.ipv6_unicast.unsuppress_map}} +{%- endif %} + neighbor {{ pr_group }} activate + exit-address-family + ! +{%- endif %} + +{%- endfor %} +{%- endif %} +{# END peer-group afi; set protocols bgp xxx peer-group FOO address-family #} + +{%- endfor %} +{%- endif %} +{#- END peer-group; set protocol bgp xxx peer-group #} + +{#- START peer section; set protocol bgp xxx neighbor #} +{%- for peer in conf_bgp[asn].neighbor %} +{#- set peer-group as conf_peer #} +{%- set conf_peer = conf_bgp[asn].neighbor[peer] %} + +{#- First parameter for peer-group - remote-as #} +{%- if 'remote_as' in conf_peer %} + neighbor {{ peer }} remote-as {{ conf_peer.remote_as }} +{%- endif %} + +{%- if 'advertisement_interval' in conf_peer %} + neighbor {{ peer }} advertisement-interval {{ conf_peer.advertisement_interval }} +{%- endif %} + +{%- if 'bfd' in conf_peer %} +{%- if 'check_control_plane_failure' in conf_peer.bfd %} + neighbor {{ peer }} bfd + neighbor {{ peer }} bfd check-control-plane-failure +{%- else %} + neighbor {{ peer }} bfd +{%- endif %} +{%- endif %} + +{%- if 'capability' in conf_peer %} +{%- if 'dynamic' in conf_peer.capability %} + neighbor {{ peer }} capability dynamic +{%- endif %} +{%- if 'extended_nexthop' in conf_peer.capability %} + neighbor {{ peer }} capability extended-nexthop +{%- endif %} +{%- endif %} + +{%- if 'description' in conf_peer %} + neighbor {{ peer }} description {{ conf_peer.description }} +{%- endif %} + +{%- if 'disable_capability_negotiation' in conf_peer %} + neighbor {{ peer }} disable-capability-negotiation +{%- endif %} + +{#- https://phabricator.vyos.net/T2844. 'disable-send-community' only for afi #} +{%- if 'disable_send_community' in conf_peer %} + ! +{%- endif %} + +{%- if 'ebgp_multihop' in conf_peer %} + neighbor {{ peer }} ebgp-multihop {{conf_peer.ebgp_multihop}} +{%- endif %} + +{#- Need to check. 'Peer-group' needs to define before this section #} +{%- if 'interface' in conf_peer %} +{%- if 'peer_group' in conf_peer.interface %} + neighbor {{ peer }} interface peer-group {{conf_peer.interface.peer_group}} +{%- endif %} +{%- if 'remote_as' in conf_peer.interface %} + neighbor {{ peer }} interface remote-as {{conf_peer.interface.remote_as}} +{%- endif %} +{%- if 'v6only' in conf_peer.interface %} +{%- if 'peer_group' in conf_peer.interface.v6only %} + neighbor {{ peer }} peer-group {{conf_peer.interface.peer_group}} +{%- endif %} +{%- if 'remote_as' in conf_peer.interface.v6only %} + neighbor {{ peer }} interface v6only remote-as {{conf_peer.interface.v6only.remote_as}} +{%- endif %} +{%- endif %} +{%- endif %} + +{%- if 'local_as' in conf_peer %} +{%- for loc_asn in conf_peer.local_as %} +{%- if 'no_prepend' in conf_peer.local_as[loc_asn] %} + neighbor {{ peer }} local-as {{loc_asn}} no-prepend +{%- else %} + neighbor {{ peer }} local-as {{loc_asn}} +{%- endif %} +{%- endfor %} +{%- endif %} + +{%- if 'override_capability' in conf_peer %} + neighbor {{ peer }} override-capability +{%- endif %} + +{%- if 'passive' in conf_peer %} + neighbor {{ peer }} passive +{%- endif %} + +{%- if 'password' in conf_peer %} + neighbor {{ peer }} password {{ conf_peer.password }} +{%- endif %} + +{%- if 'peer_group' in conf_peer %} + neighbor {{ peer }} peer-group {{ conf_peer.peer_group }} +{%- endif %} + +{%- if 'port' in conf_peer %} + neighbor {{ peer }} port {{ conf_peer.port }} +{%- endif %} + +{%- if 'shutdown' in conf_peer %} + neighbor {{ peer }} shutdown +{%- endif %} + +{%- if 'strict_capability_match' in conf_peer %} + neighbor {{ peer }} strict-capability-match +{%- endif %} + +{#- Need to check #} +{%- if 'timers' in conf_peer %} +{%- if ( ('connect' and 'holdtime' and 'keepalive') in conf_peer.timers ) %} + neighbor {{ peer }} timers {{conf_peer.timers.keepalive}} {{conf_peer.timers.holdtime}} + neighbor {{ peer }} timers connect {{conf_peer.timers.connect}} +{%- endif %} +{%- endif %} + +{%- if 'ttl_security' in conf_peer %} +{%- if 'hops' in conf_peer.ttl_security %} + neighbor {{ peer }} ttl-security hops {{conf_peer.ttl_security.hops}} +{%- endif %} +{%- endif %} + +{%- if 'update_source' in conf_peer %} + neighbor {{ peer }} update-source {{ conf_peer.update_source }} +{%- endif %} + +{#- START address family for peer; set protocols bgp xxx neighbor x.x.x.x address-family ipvX-unicast #} +{%- if 'address_family' in conf_peer %} +{%- for afi in conf_peer.address_family %} +{%- if afi == "ipv4_unicast" %} + ! + address-family ipv4 unicast + +{%- if 'allowas_in' in conf_peer.address_family.ipv4_unicast %} +{%- if 'number' in conf_peer.address_family.ipv4_unicast.allowas_in %} + neighbor {{ peer }} allowas-in {{ conf_peer.address_family.ipv4_unicast.allowas_in.number }} +{%- else %} + neighbor {{ peer }} allowas-in +{%- endif %} +{%- endif %} + +{#- START Single Params for neighbor; #} +{%- if 'as_override' in conf_peer.address_family.ipv4_unicast %} + neighbor {{ peer }} as-override +{%- endif %} + +{%- if 'remove_private_as' in conf_peer.address_family.ipv4_unicast %} + neighbor {{ peer }} remove-private-AS +{%- endif %} + +{%- if 'route_reflector_client' in conf_peer.address_family.ipv4_unicast %} + neighbor {{ peer }} route-reflector-client +{%- endif %} + +{%- if 'weight' in conf_peer.address_family.ipv4_unicast %} + neighbor {{ peer }} weight {{ conf_peer.address_family.ipv4_unicast.weight }} +{%- endif %} +{#- END single params for neighbor #} + +{#- Checks need to be done as-path|med|next-hop #} +{%- if 'attribute_unchanged' in conf_peer.address_family.ipv4_unicast %} +{%- if 'as_path' in conf_peer.address_family.ipv4_unicast.attribute_unchanged %} + neighbor {{ peer }} attribute-unchanged as-path +{%- else %} + neighbor {{ peer }} attribute-unchanged as-path next-hop med +{%- endif %} +{%- endif %} +{#- END attribute-unchanged #} + +{%- if 'capability' in conf_peer.address_family.ipv4_unicast %} +{%- if 'receive' in conf_peer.address_family.ipv4_unicast.capability.orf.prefix_list %} + neighbor {{ peer }} capability orf prefix-list receive +{%- endif %} +{%- if 'send' in conf_peer.address_family.ipv4_unicast.capability.orf.prefix_list %} + neighbor {{ peer }} capability orf prefix-list send +{%- endif %} +{%- endif %} + +{%- if 'default_originate' in conf_peer.address_family.ipv4_unicast %} +{%- if 'route_map' in conf_peer.address_family.ipv4_unicast.default_originate %} + neighbor {{ peer }} default-originate route-map {{ conf_peer.address_family.ipv4_unicast.default_originate.route_map }} +{%- else %} + neighbor {{ peer }} default-originate +{%- endif %} +{%- endif %} + +{%- if 'distribute_list' in conf_peer.address_family.ipv4_unicast %} +{%- if 'export' in conf_peer.address_family.ipv4_unicast.distribute_list %} + neighbor {{ peer }} distribute-list {{conf_peer.address_family.ipv4_unicast.distribute_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer.address_family.ipv4_unicast.distribute_list %} + neighbor {{ peer }} distribute-list {{conf_peer.address_family.ipv4_unicast.distribute_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'filter_list' in conf_peer.address_family.ipv4_unicast %} +{%- if 'export' in conf_peer.address_family.ipv4_unicast.filter_list %} + neighbor {{ peer }} filter-list {{conf_peer.address_family.ipv4_unicast.filter_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer.address_family.ipv4_unicast.filter_list %} + neighbor {{ peer }} filter-list {{conf_peer.address_family.ipv4_unicast.filter_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'maximum_prefix' in conf_peer.address_family.ipv4_unicast %} + neighbor {{ peer }} maximum-prefix {{ conf_peer.address_family.ipv4_unicast.maximum_prefix }} +{%- endif %} + +{#- https://phabricator.vyos.net/T1817 #} +{%- if 'nexthop_self' in conf_peer.address_family.ipv4_unicast %} +{%- if 'force' in conf_peer.address_family.ipv4_unicast.nexthop_self %} + neighbor {{ peer }} next-hop-self force + neighbor {{ peer }} next-hop-self +{%- else %} + neighbor {{ peer }} next-hop-self +{%- endif %} +{%- endif %} + +{%- if 'route_server_client' in conf_peer.address_family.ipv4_unicast %} + neighbor {{ peer }} route-server-client +{%- endif %} + +{%- if 'route_map' in conf_peer.address_family.ipv4_unicast %} +{%- if 'export' in conf_peer.address_family.ipv4_unicast.route_map %} + neighbor {{ peer }} route-map {{conf_peer.address_family.ipv4_unicast.route_map.export}} out +{%- endif %} +{%- if 'import' in conf_peer.address_family.ipv4_unicast.route_map %} + neighbor {{ peer }} route-map {{conf_peer.address_family.ipv4_unicast.route_map.import}} in +{%- endif %} +{%- endif %} +{%- if 'prefix_list' in conf_peer.address_family.ipv4_unicast %} +{%- if 'export' in conf_peer.address_family.ipv4_unicast.prefix_list %} + neighbor {{ peer }} prefix-list {{conf_peer.address_family.ipv4_unicast.prefix_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer.address_family.ipv4_unicast.prefix_list %} + neighbor {{ peer }} prefix-list {{conf_peer.address_family.ipv4_unicast.prefix_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'soft_reconfiguration' in conf_peer.address_family.ipv4_unicast %} +{%- if 'inbound' is defined %} + neighbor {{ peer }} soft-reconfiguration inbound +{%- endif %} +{%- endif %} + +{#- Checks need to be done. https://phabricator.vyos.net/T2387#73900 #} +{%- if 'unsuppress_map' in conf_peer.address_family.ipv4_unicast %} + neighbor {{ peer }} unsuppress-map {{conf_peer.address_family.ipv4_unicast.unsuppress_map}} +{%- endif %} + neighbor {{ peer }} activate + exit-address-family + ! +{%- endif %} + +{%- if afi == "ipv6_unicast" %} + ! + address-family ipv6 unicast + +{%- if 'allowas_in' in conf_peer.address_family.ipv6_unicast %} +{%- if 'number' in conf_peer.address_family.ipv6_unicast.allowas_in %} + neighbor {{ peer }} allowas-in {{ conf_peer.address_family.ipv6_unicast.allowas_in.number }} +{%- else %} + neighbor {{ peer }} allowas-in +{%- endif %} +{%- endif %} + +{#- START Single Params for neighbor #} +{%- if 'as_override' in conf_peer.address_family.ipv6_unicast %} + neighbor {{ peer }} as-override +{%- endif %} + +{%- if 'remove_private_as' in conf_peer.address_family.ipv6_unicast %} + neighbor {{ peer }} remove-private-AS +{%- endif %} + +{%- if 'route_reflector_client' in conf_peer.address_family.ipv6_unicast %} + neighbor {{ peer }} route-reflector-client +{%- endif %} + +{%- if 'weight' in conf_peer.address_family.ipv6_unicast %} + neighbor {{ peer }} weight {{ conf_peer.address_family.ipv6_unicast.weight }} +{%- endif %} +{#- END single params for neighbor #} + +{#- Checks need to be done as-path|med|next-hop #} +{%- if 'attribute_unchanged' in conf_peer.address_family.ipv6_unicast %} +{%- if 'as_path' in conf_peer.address_family.ipv6_unicast.attribute_unchanged %} + neighbor {{ peer }} attribute-unchanged as-path +{%- else %} + neighbor {{ peer }} attribute-unchanged as-path next-hop med +{%- endif %} +{%- endif %} +{#- END attribute-unchanged #} + +{%- if 'capability' in conf_peer.address_family.ipv6_unicast %} +{%- if 'receive' in conf_peer.address_family.ipv6_unicast.capability.orf.prefix_list %} + neighbor {{ peer }} capability orf prefix-list receive +{%- endif %} +{%- if 'send' in conf_peer.address_family.ipv6_unicast.capability.orf.prefix_list %} + neighbor {{ peer }} capability orf prefix-list send +{%- endif %} +{%- endif %} + +{%- if 'default_originate' in conf_peer.address_family.ipv6_unicast %} +{%- if 'route_map' in conf_peer.address_family.ipv6_unicast.default_originate %} + neighbor {{ peer }} default-originate route-map {{ conf_peer.address_family.ipv6_unicast.default_originate.route_map }} +{%- else %} + neighbor {{ peer }} default-originate +{%- endif %} +{%- endif %} + +{%- if 'distribute_list' in conf_peer.address_family.ipv6_unicast %} +{%- if 'export' in conf_peer.address_family.ipv6_unicast.distribute_list %} + neighbor {{ peer }} distribute-list {{conf_peer.address_family.ipv6_unicast.distribute_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer.address_family.ipv6_unicast.distribute_list %} + neighbor {{ peer }} distribute-list {{conf_peer.address_family.ipv6_unicast.distribute_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'filter_list' in conf_peer.address_family.ipv6_unicast %} +{%- if 'export' in conf_peer.address_family.ipv6_unicast.filter_list %} + neighbor {{ peer }} filter-list {{conf_peer.address_family.ipv6_unicast.filter_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer.address_family.ipv6_unicast.filter_list %} + neighbor {{ peer }} filter-list {{conf_peer.address_family.ipv6_unicast.filter_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'maximum_prefix' in conf_peer.address_family.ipv6_unicast %} + neighbor {{ peer }} maximum-prefix {{ conf_peer.address_family.ipv6_unicast.maximum_prefix }} +{%- endif %} + +{#- https://phabricator.vyos.net/T1817 #} +{%- if 'nexthop_self' in conf_peer.address_family.ipv6_unicast %} +{%- if 'force' in conf_peer.address_family.ipv6_unicast.nexthop_self %} + neighbor {{ peer }} next-hop-self force + neighbor {{ peer }} next-hop-self +{%- else %} + neighbor {{ peer }} next-hop-self +{%- endif %} +{%- endif %} + +{%- if 'route_server_client' in conf_peer.address_family.ipv6_unicast %} + neighbor {{ peer }} route-server-client +{%- endif %} + +{%- if 'route_map' in conf_peer.address_family.ipv6_unicast %} +{%- if 'export' in conf_peer.address_family.ipv6_unicast.route_map %} + neighbor {{ peer }} route-map {{conf_peer.address_family.ipv6_unicast.route_map.export}} out +{%- endif %} +{%- if 'import' in conf_peer.address_family.ipv6_unicast.route_map %} + neighbor {{ peer }} route-map {{conf_peer.address_family.ipv6_unicast.route_map.import}} in +{%- endif %} +{%- endif %} +{%- if 'prefix_list' in conf_peer.address_family.ipv6_unicast %} +{%- if 'export' in conf_peer.address_family.ipv6_unicast.prefix_list %} + neighbor {{ peer }} prefix-list {{conf_peer.address_family.ipv6_unicast.prefix_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer.address_family.ipv6_unicast.prefix_list %} + neighbor {{ peer }} prefix-list {{conf_peer.address_family.ipv6_unicast.prefix_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'soft_reconfiguration' in conf_peer.address_family.ipv6_unicast %} +{%- if 'inbound' is defined %} + neighbor {{ peer }} soft-reconfiguration inbound +{%- endif %} +{%- endif %} + +{#- Checks need to be done. https://phabricator.vyos.net/T2387#73900 #} +{%- if 'unsuppress_map' in conf_peer.address_family.ipv6_unicast %} + neighbor {{ peer }} unsuppress-map {{conf_peer.address_family.ipv6_unicast.unsuppress_map}} +{%- endif %} + neighbor {{ peer }} activate + exit-address-family + ! +{%- endif %} + +{%- endfor %} +{%- endif %} +{#- END address family for peer #} + +{%- endfor %} +{#- END peer section; set protocols bgp xxx neighbor #} + +{#- START parameters section; set protocol bgp xxx parameters #} +{%- if 'always_compare_med' in bgp_params %} + bgp always-compare-med +{%- endif %} + +{%- if 'bestpath' in bgp_params %} +{%- if 'compare_routerid' in bgp_params.bestpath %} + bgp bestpath compare-routerid +{%- endif %} +{%- if 'as_path' in bgp_params.bestpath %} +{%- if 'confed' in bgp_params.bestpath.as_path %} + bgp bestpath as-path confed +{%- endif %} +{%- if 'ignore' in bgp_params.bestpath.as_path %} + bgp bestpath as-path ignore +{%- endif %} +{%- if 'multipath_relax' in bgp_params.bestpath.as_path %} + bgp bestpath as-path multipath-relax +{%- endif %} +{%- endif %} +{%- if 'med' in bgp_params.bestpath %} +{%- if ( ('confed' and 'missing_as_worst') in bgp_params.bestpath.med ) %} + bgp bestpath med confed missing-as-worst +{%- elif 'confed' in bgp_params.bestpath.med %} + bgp bestpath med confed +{%- elif 'missing_as_worst' in bgp_params.bestpath.med %} + bgp bestpath med missing-as-worst +{%- endif%} +{%- endif %} +{%- endif %} + +{%- if 'cluster_id' in bgp_params %} + bgp cluster-id {{ bgp_params.cluster_id }} +{%- endif %} + +{%- if 'confederation' in bgp_params %} +{%- if 'identifier' in bgp_params.confederation %} + bgp confederation identifier {{ bgp_params.confederation.identifier }} +{%- endif %} +{%- if 'peers' in bgp_params.confederation %} + bgp confederation peers {{ bgp_params.confederation.peers }} +{%- endif %} +{%- endif %} + +{#- Doesn't work in current FRR configuration (bgp dampening 16 751 2001 61) #} +{%- if 'dampening' in bgp_params %} +{%- if ( ('half_life' and 'max_suppress_time' and 're_use' and 'start_suppress_time') in bgp_params.dampening ) %} + bgp dampening {{ bgp_params.dampening.half_life }} {{ bgp_params.dampening.re_use }} {{ bgp_params.dampening.start_suppress_time }} {{ bgp_params.dampening.max_suppress_time }} +{%- endif %} +{%- endif %} + +{%- if 'default' in bgp_params %} +{%- if 'local_pref' in bgp_params.default %} + bgp default local-preference {{ bgp_params.default.local_pref }} +{%- endif %} +{#- We use this is parameter as default in template (5-th string) #} +{%- if 'no_ipv4_unicast' in bgp_params.default %} + no bgp default ipv4-unicast +{%- endif %} +{%- endif %} + +{%- if 'deterministic_med' in bgp_params %} + bgp deterministic-med +{%- endif %} + +{%- if 'distance' in bgp_params %} +{%- if 'global' in bgp_params.distance %} +{%- if ( ('external' and 'internal' and 'local') in bgp_params.distance.global ) %} + ! + address-family ipv4 unicast + distance bgp {{ bgp_params.distance.global.external }} {{ bgp_params.distance.global.internal }} {{ bgp_params.distance.global.local }} + exit-address-family +! +{%- endif %} +{%- endif %} +{%- if 'prefix' in bgp_params.distance %} + ! + address-family ipv4 unicast +{%- for prfx in bgp_params.distance.prefix %} + distance {{ bgp_params.distance.prefix[prfx].distance }} {{ prfx }} +{%- endfor %} + exit-address-family +! +{%- endif %} +{%- endif %} + +{%- if 'graceful_restart' in bgp_params %} +{%- if 'stalepath_time' in bgp_params.graceful_restart %} + bgp graceful-restart stalepath-time {{ bgp_params.graceful_restart.stalepath_time }} +{%- endif %} +{%- endif %} + +{%- if 'log_neighbor_changes' in bgp_params %} + bgp log-neighbor-changes +{%- endif %} + +{%- if 'network_import_check' in bgp_params %} + bgp network import-check +{%- endif %} + +{%- if 'no_client_to_client_reflection' in bgp_params %} + no bgp client-to-client reflection +{%- endif %} + +{%- if 'no_fast_external_failover' in bgp_params %} + no bgp fast-external-failover +{%- endif %} + +{#- END parameters; set protocols bgp xxx parameters #} + +{%- if 'timers' in conf_bgp[asn] %} +{%- if ( ('holdtime' and 'keepalive') in conf_bgp[asn].timers ) %} + timers bgp {{conf_bgp[asn].timers.keepalive}} {{conf_bgp[asn].timers.holdtime}} +{%- endif %} +{%- endif %} + +{%- if 'route_map' in conf_bgp[asn] %} +! +ip protocol bgp route-map {{conf_bgp[asn].route_map}} +{%- endif %} +! +{%- endfor -%} +{#- END asn; router bgp xxx #} diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 3aa76d866..1978adff5 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -14,83 +14,72 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import jmespath +import os -from copy import deepcopy from sys import exit from vyos.config import Config +from vyos.util import call from vyos.template import render +from vyos.template import render_to_string +from vyos import frr from vyos import ConfigError, airbag airbag.enable() config_file = r'/tmp/bgp.frr' -default_config_data = { - 'as_number': '' -} - def get_config(): - bgp = deepcopy(default_config_data) conf = Config() - - # this lives in the "nbgp" tree until we switch over base = ['protocols', 'nbgp'] + bgp = conf.get_config_dict(base, key_mangling=('-', '_')) if not conf.exists(base): return None - bgp = deepcopy(default_config_data) - # Get full BGP configuration as dictionary - output the configuration for development - # - # vyos@vyos# commit - # [ protocols nbgp 65000 ] - # {'nbgp': {'65000': {'address-family': {'ipv4-unicast': {'aggregate-address': {'1.1.0.0/16': {}, - # '2.2.2.0/24': {}}}, - # 'ipv6-unicast': {'aggregate-address': {'2001:db8::/32': {}}}}, - # 'neighbor': {'192.0.2.1': {'password': 'foo', - # 'remote-as': '100'}}}}} - # - tmp = conf.get_config_dict(base) - - # extract base key from dict as this is our AS number - bgp['as_number'] = jmespath.search('nbgp | keys(@) [0]', tmp) - - # adjust level of dictionary returned by get_config_dict() - # by using jmesgpath and update dictionary - bgp.update(jmespath.search('nbgp.* | [0]', tmp)) - from pprint import pprint pprint(bgp) - # resulting in e.g. - # vyos@vyos# commit - # [ protocols nbgp 65000 ] - # {'address-family': {'ipv4-unicast': {'aggregate-address': {'1.1.0.0/16': {}, - # '2.2.2.0/24': {}}}, - # 'ipv6-unicast': {'aggregate-address': {'2001:db8::/32': {}}}}, - # 'as_number': '65000', - # 'neighbor': {'192.0.2.1': {'password': 'foo', 'remote-as': '100'}}, - # 'timers': {'holdtime': '5'}} return bgp def verify(bgp): - # bail out early - looks like removal from running config if not bgp: return None return None def generate(bgp): - # bail out early - looks like removal from running config if not bgp: return None + # render(config) not needed, its only for debug render(config_file, 'frr/bgp.frr.tmpl', bgp) + + bgp['new_frr_config'] = render_to_string('frr/bgp.frr.tmpl', bgp) + return None def apply(bgp): + if bgp is None: + return None + + # Save original configration prior to starting any commit actions + bgp['original_config'] = frr.get_configuration(daemon='bgpd') + bgp['modified_config'] = frr.replace_section(bgp['original_config'], bgp['new_frr_config'], from_re='router bgp .*') + + # Debugging + print('--------- DEBUGGING ----------') + print(f'Existing config:\n{bgp["original_config"]}\n\n') + print(f'Replacement config:\n{bgp["new_frr_config"]}\n\n') + print(f'Modified config:\n{bgp["modified_config"]}\n\n') + + # Frr Mark configuration will test for syntax errors and exception out if any syntax errors are detected + frr.mark_configuration(bgp['modified_config']) + + # Commit the resulting new configuration to frr, this will render an frr.CommitError() Exception on fail + frr.reload_configuration(bgp['modified_config'], daemon='bgpd') + return None + if __name__ == '__main__': try: c = get_config() -- cgit v1.2.3