diff options
39 files changed, 1358 insertions, 119 deletions
diff --git a/.gitignore b/.gitignore index fe92f5b9d..e766a2c27 100644 --- a/.gitignore +++ b/.gitignore @@ -133,6 +133,10 @@ debian/*.substvars *.vpwhist *.vtg +# VS Code +.vscode/* +!.vscode/settings.json + # VIM *.swp diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..994718d2e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "files.trimTrailingWhitespace": true, + "editor.tabSize": 4, + "editor.insertSpaces": true, + "files.insertFinalNewline": true, + "files.eol": "\n", +} diff --git a/data/templates/high-availability/keepalived.conf.j2 b/data/templates/high-availability/keepalived.conf.j2 index bcd92358f..47408e345 100644 --- a/data/templates/high-availability/keepalived.conf.j2 +++ b/data/templates/high-availability/keepalived.conf.j2 @@ -173,7 +173,7 @@ vrrp_sync_group {{ name }} { {% for vserver, vserver_config in virtual_server.items() %} # Vserver {{ vserver }} {% if vserver_config.port is vyos_defined %} -virtual_server {{ vserver }} {{ vserver_config.port }} { +virtual_server {{ vserver_config.address }} {{ vserver_config.port }} { {% else %} virtual_server fwmark {{ vserver_config.fwmark }} { {% endif %} diff --git a/data/templates/vpp/sysctl.conf.j2 b/data/templates/vpp/sysctl.conf.j2 deleted file mode 100644 index 2207e2e38..000000000 --- a/data/templates/vpp/sysctl.conf.j2 +++ /dev/null @@ -1,15 +0,0 @@ -# Number of 2MB hugepages desired -vm.nr_hugepages=1024 - -# Must be greater than or equal to (2 * vm.nr_hugepages). -vm.max_map_count=3096 - -# All groups allowed to access hugepages -vm.hugetlb_shm_group=0 - -# Shared Memory Max must be greater or equal to the total size of hugepages. -# For 2MB pages, TotalHugepageSize = vm.nr_hugepages * 2 * 1024 * 1024 -# If the existing kernel.shmmax setting (cat /proc/sys/kernel/shmmax) -# is greater than the calculated TotalHugepageSize then set this parameter -# to current shmmax value. -kernel.shmmax=2147483648 diff --git a/debian/control b/debian/control index 40920cadc..8e9aaa702 100644 --- a/debian/control +++ b/debian/control @@ -72,6 +72,7 @@ Depends: iperf, iperf3, iproute2 (>= 6.0.0), + iptables, iputils-arping, isc-dhcp-client, isc-dhcp-relay, @@ -92,6 +93,7 @@ Depends: libstrongswan-standard-plugins (>=5.9), libvppinfra, libvyosconfig0, + linux-cpupower, lldpd, lm-sensors, lsscsi, diff --git a/debian/rules b/debian/rules index e613f1e0a..9ada2bf87 100755 --- a/debian/rules +++ b/debian/rules @@ -51,6 +51,10 @@ override_dh_auto_install: mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/op_mode cp -r src/op_mode/* $(DIR)/$(VYOS_LIBEXEC_DIR)/op_mode + # Install op mode scripts + mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/init + cp -r src/init/* $(DIR)/$(VYOS_LIBEXEC_DIR)/init + # Install validators mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/validators cp -r src/validators/* $(DIR)/$(VYOS_LIBEXEC_DIR)/validators @@ -123,3 +127,7 @@ override_dh_auto_install: # Install udev script mkdir -p $(DIR)/usr/lib/udev cp src/helpers/vyos_net_name $(DIR)/usr/lib/udev + +override_dh_installsystemd: + dh_installsystemd -pvyos-1x --name vyos-router vyos-router.service + dh_installsystemd -pvyos-1x --name vyos vyos.target diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install index 07cdf8c74..9e43669be 100644 --- a/debian/vyos-1x.install +++ b/debian/vyos-1x.install @@ -30,6 +30,7 @@ usr/bin/vyos-hostsd-client usr/lib usr/libexec/vyos/completion usr/libexec/vyos/conf_mode +usr/libexec/vyos/init usr/libexec/vyos/op_mode usr/libexec/vyos/services usr/libexec/vyos/system diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index 2958afd0a..93e7ced9b 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -186,5 +186,8 @@ systemctl enable vyos-config-cloud-init.service # # sudo mv /opt/vyatta/share/vyatta-cfg/vpp /opt/vyatta/share/vyatta-cfg/templates/vpp if [ -d /opt/vyatta/share/vyatta-cfg/templates/vpp ]; then + if [ -d /opt/vyatta/share/vyatta-cfg/vpp ]; then + rm -rf /opt/vyatta/share/vyatta-cfg/vpp + fi mv /opt/vyatta/share/vyatta-cfg/templates/vpp /opt/vyatta/share/vyatta-cfg/vpp fi diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in index d1bbcc365..4f55916fa 100644 --- a/interface-definitions/high-availability.xml.in +++ b/interface-definitions/high-availability.xml.in @@ -336,9 +336,10 @@ </node> <tagNode name="virtual-server"> <properties> - <help>Load-balancing virtual server address</help> + <help>Load-balancing virtual server alias</help> </properties> <children> + #include <include/address-ipv4-ipv6-single.xml.i> <leafNode name="algorithm"> <properties> <help>Schedule algorithm (default - least-connection)</help> diff --git a/interface-definitions/include/address-ipv4-ipv6-single.xml.i b/interface-definitions/include/address-ipv4-ipv6-single.xml.i new file mode 100644 index 000000000..dc3d6fc1b --- /dev/null +++ b/interface-definitions/include/address-ipv4-ipv6-single.xml.i @@ -0,0 +1,18 @@ +<!-- include start from interface/address-ipv4-ipv6.xml.i --> +<leafNode name="address"> + <properties> + <help>IP address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index f95031a75..07b6868ee 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -1575,7 +1575,9 @@ </properties> <children> #include <include/bgp/neighbor-afi-ipv4-unicast.xml.i> + #include <include/bgp/neighbor-afi-ipv4-vpn.xml.i> #include <include/bgp/neighbor-afi-ipv6-unicast.xml.i> + #include <include/bgp/neighbor-afi-ipv6-vpn.xml.i> #include <include/bgp/neighbor-afi-l2vpn-evpn.xml.i> </children> </node> diff --git a/interface-definitions/include/version/vrrp-version.xml.i b/interface-definitions/include/version/vrrp-version.xml.i index 626dd6cbc..1514b19ab 100644 --- a/interface-definitions/include/version/vrrp-version.xml.i +++ b/interface-definitions/include/version/vrrp-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/vrrp-version.xml.i --> -<syntaxVersion component='vrrp' version='3'></syntaxVersion> +<syntaxVersion component='vrrp' version='4'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/protocols-mpls.xml.in b/interface-definitions/protocols-mpls.xml.in index 43ca659e9..831601fc6 100644 --- a/interface-definitions/protocols-mpls.xml.in +++ b/interface-definitions/protocols-mpls.xml.in @@ -6,7 +6,7 @@ <node name="mpls" owner="${vyos_conf_scripts_dir}/protocols_mpls.py"> <properties> <help>Multiprotocol Label Switching (MPLS)</help> - <priority>400</priority> + <priority>490</priority> </properties> <children> <node name="ldp"> diff --git a/interface-definitions/service-config-sync.xml.in b/interface-definitions/service-config-sync.xml.in new file mode 100644 index 000000000..e804e17f7 --- /dev/null +++ b/interface-definitions/service-config-sync.xml.in @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="config-sync" owner="${vyos_conf_scripts_dir}/service_config_sync.py"> + <properties> + <help>Configuration synchronization</help> + </properties> + <children> + <node name="secondary"> + <properties> + <help>Secondary server parameters</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IP address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address to match</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>FQDN address to match</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv6-address"/> + <validator name="fqdn"/> + </constraint> + </properties> + </leafNode> + <leafNode name="timeout"> + <properties> + <help>Connection API timeout</help> + <valueHelp> + <format>u32:1-300</format> + <description>Connection API timeout</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-300"/> + </constraint> + </properties> + <defaultValue>60</defaultValue> + </leafNode> + <leafNode name="key"> + <properties> + <help>HTTP API key</help> + </properties> + </leafNode> + </children> + </node> + <leafNode name="mode"> + <properties> + <help>Synchronization mode</help> + <completionHelp> + <list>load set</list> + </completionHelp> + <valueHelp> + <format>load</format> + <description>Load and replace configuration section</description> + </valueHelp> + <valueHelp> + <format>set</format> + <description>Set configuration section</description> + </valueHelp> + <constraint> + <regex>(load|set)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="section"> + <properties> + <help>Section for synchronization</help> + <completionHelp> + <list>nat nat66 firewall</list> + </completionHelp> + <valueHelp> + <format>nat</format> + <description>NAT</description> + </valueHelp> + <valueHelp> + <format>nat66</format> + <description>NAT66</description> + </valueHelp> + <valueHelp> + <format>firewall</format> + <description>firewall</description> + </valueHelp> + <constraint> + <regex>(nat|nat66|firewall)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/container.xml.in b/op-mode-definitions/container.xml.in index ada9a4d59..f581d39fa 100644 --- a/op-mode-definitions/container.xml.in +++ b/op-mode-definitions/container.xml.in @@ -83,7 +83,7 @@ <children> <tagNode name="container"> <properties> - <help>Monitor last lines of container logs</help> + <help>Monitor last lines of container log</help> <completionHelp> <path>container name</path> </completionHelp> diff --git a/op-mode-definitions/dns-dynamic.xml.in b/op-mode-definitions/dns-dynamic.xml.in index 4f0399964..8f32f63f9 100644 --- a/op-mode-definitions/dns-dynamic.xml.in +++ b/op-mode-definitions/dns-dynamic.xml.in @@ -6,7 +6,7 @@ <children> <node name="dns"> <properties> - <help>Monitor last lines of Domain Name System (DNS) related services</help> + <help>Monitor last lines of Domain Name System related services</help> </properties> <children> <node name="dynamic"> @@ -27,7 +27,7 @@ <children> <node name="dns"> <properties> - <help>Show log for Domain Name System (DNS) related services</help> + <help>Show log for Domain Name System related services</help> </properties> <children> <node name="dynamic"> @@ -42,7 +42,7 @@ </node> <node name="dns"> <properties> - <help>Show Domain Name System (DNS) related information</help> + <help>Show Domain Name System related information</help> </properties> <children> <node name="dynamic"> @@ -66,7 +66,7 @@ <children> <node name="dns"> <properties> - <help>Restart specific Domain Name System (DNS) related service</help> + <help>Restart specific Domain Name System related service</help> </properties> <children> <node name="dynamic"> @@ -86,7 +86,7 @@ <children> <node name="dns"> <properties> - <help>Update Domain Name System (DNS) related information</help> + <help>Update Domain Name System related information</help> </properties> <children> <node name="dynamic"> diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index c577c5a39..2e5439414 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -16,7 +16,7 @@ </node> <node name="ids"> <properties> - <help>Monitor log for Intrusion Detection System</help> + <help>Monitor Intrusion Detection System log</help> </properties> <children> <leafNode name="ddos-protection"> @@ -29,18 +29,18 @@ </node> <node name="dhcp"> <properties> - <help>Monitor last lines of Dynamic Host Control Protocol (DHCP)</help> + <help>Monitor last lines of Dynamic Host Control Protocol log</help> </properties> <children> <node name="server"> <properties> - <help>Monitor last lines of DHCP server</help> + <help>Monitor last lines of DHCP server log</help> </properties> <command>journalctl --no-hostname --follow --boot --unit isc-dhcp-server.service</command> </node> <node name="client"> <properties> - <help>Monitor last lines of DHCP client</help> + <help>Monitor last lines of DHCP client log</help> </properties> <command>journalctl --no-hostname --follow --boot --unit "dhclient@*.service"</command> <children> @@ -59,18 +59,18 @@ </node> <node name="dhcpv6"> <properties> - <help>Monitor last lines of Dynamic Host Control Protocol IPv6 (DHCPv6)</help> + <help>Monitor last lines of Dynamic Host Control Protocol IPv6 log</help> </properties> <children> <node name="server"> <properties> - <help>Monitor last lines of DHCPv6 server</help> + <help>Monitor last lines of DHCPv6 server log</help> </properties> <command>journalctl --no-hostname --follow --boot --unit isc-dhcp-server6.service</command> </node> <node name="client"> <properties> - <help>Monitor last lines of DHCPv6 client</help> + <help>Monitor last lines of DHCPv6 client log</help> </properties> <command>journalctl --no-hostname --follow --boot --unit "dhcp6c@*.service"</command> <children> @@ -95,7 +95,7 @@ </leafNode> <leafNode name="ipoe-server"> <properties> - <help>Monitor last lines of IPoE server log</help> + <help>Monitor last lines of IP over Ethernet server log</help> </properties> <command>journalctl --no-hostname --boot --follow --unit accel-ppp@ipoe.service</command> </leafNode> @@ -107,13 +107,13 @@ </leafNode> <leafNode name="nhrp"> <properties> - <help>Monitor last lines of Next Hop Resolution Protocol (NHRP) log</help> + <help>Monitor last lines of Next Hop Resolution Protocol log</help> </properties> <command>journalctl --no-hostname --boot --follow --unit opennhrp.service</command> </leafNode> <leafNode name="ntp"> <properties> - <help>Monitor last lines of Network Time Protocol (NTP) log</help> + <help>Monitor last lines of Network Time Protocol log</help> </properties> <command>journalctl --no-hostname --boot --follow --unit chrony.service</command> </leafNode> @@ -142,7 +142,7 @@ </leafNode> <node name="protocol"> <properties> - <help>Monitor log for Routing Protocol</help> + <help>Monitor routing protocol logs</help> </properties> <children> <leafNode name="ospf"> @@ -232,25 +232,25 @@ </node> <leafNode name="router-advert"> <properties> - <help>Monitor last lines of Router Advertisement Daemon (radvd)</help> + <help>Monitor last lines of Router Advertisement Daemon log</help> </properties> <command>journalctl --no-hostname --boot --follow --unit radvd.service</command> </leafNode> <leafNode name="snmp"> <properties> - <help>Monitor last lines of Simple Network Monitoring Protocol (SNMP)</help> + <help>Monitor last lines of Simple Network Monitoring Protocol log</help> </properties> <command>journalctl --no-hostname --boot --follow --unit snmpd.service</command> </leafNode> <leafNode name="ssh"> <properties> - <help>Monitor last lines of Secure Shell (SSH)</help> + <help>Monitor last lines of Secure Shell log</help> </properties> <command>journalctl --no-hostname --boot --follow --unit ssh.service</command> </leafNode> <node name="sstpc"> <properties> - <help>Monitor last lines of SSTP client log</help> + <help>Monitor last lines of Secure Socket Tunneling Protocol log</help> </properties> <command>journalctl --no-hostname --boot --follow --unit "ppp@sstpc*.service"</command> <children> @@ -267,7 +267,7 @@ </node> <node name="vpn"> <properties> - <help>Monitor Virtual Private Network (VPN) services</help> + <help>Monitor Virtual Private Network services</help> </properties> <children> <leafNode name="all"> @@ -278,36 +278,54 @@ </leafNode> <leafNode name="ipsec"> <properties> - <help>Monitor last lines of IPsec</help> + <help>Monitor last lines of IPsec log</help> </properties> <command>journalctl --no-hostname --boot --follow --unit strongswan.service</command> </leafNode> <leafNode name="l2tp"> <properties> - <help>Monitor last lines of L2TP</help> + <help>Monitor last lines of L2TP log</help> </properties> <command>journalctl --no-hostname --boot --follow --unit accel-ppp@l2tp.service</command> </leafNode> <leafNode name="openconnect"> <properties> - <help>Monitor last lines of OpenConnect</help> + <help>Monitor last lines of OpenConnect log</help> </properties> <command>journalctl --no-hostname --boot --follow --unit ocserv.service</command> </leafNode> <leafNode name="pptp"> <properties> - <help>Monitor last lines of PPTP</help> + <help>Monitor last lines of PPTP log</help> </properties> <command>journalctl --no-hostname --boot --follow --unit accel-ppp@pptp.service</command> </leafNode> <leafNode name="sstp"> <properties> - <help>Monitor last lines of SSTP</help> + <help>Monitor last lines of SSTP log</help> </properties> <command>journalctl --no-hostname --boot --follow --unit accel-ppp@sstp.service</command> </leafNode> </children> </node> + <leafNode name="vpp"> + <properties> + <help>Monitor last lines of Vector Packet Processor log</help> + </properties> + <command>journalctl --no-hostname --boot --unit vpp.service</command> + </leafNode> + <leafNode name="vrrp"> + <properties> + <help>Monitor last lines of Virtual Router Redundancy Protocol log</help> + </properties> + <command>journalctl --no-hostname --boot --unit keepalived.service</command> + </leafNode> + <leafNode name="webproxy"> + <properties> + <help>Monitor last lines of Webproxy log</help> + </properties> + <command>journalctl --no-hostname --boot --unit squid.service</command> + </leafNode> </children> </node> </children> diff --git a/op-mode-definitions/pki.xml.in b/op-mode-definitions/pki.xml.in index 346febec0..c5abf86cd 100644 --- a/op-mode-definitions/pki.xml.in +++ b/op-mode-definitions/pki.xml.in @@ -505,6 +505,14 @@ </completionHelp> </properties> <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --ca "$4"</command> + <children> + <leafNode name="pem"> + <properties> + <help>Show x509 CA certificate in PEM format</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --ca "$4" --pem</command> + </leafNode> + </children> </tagNode> <leafNode name="certificate"> <properties> @@ -520,6 +528,14 @@ </completionHelp> </properties> <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "$4"</command> + <children> + <leafNode name="pem"> + <properties> + <help>Show x509 certificate in PEM format</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "$4" --pem</command> + </leafNode> + </children> </tagNode> <leafNode name="crl"> <properties> @@ -527,6 +543,23 @@ </properties> <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --crl "all"</command> </leafNode> + <tagNode name="crl"> + <properties> + <help>Show x509 certificate revocation lists by CA name</help> + <completionHelp> + <path>pki ca</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --crl "$4"</command> + <children> + <leafNode name="pem"> + <properties> + <help>Show x509 certificate revocation lists by CA name in PEM format</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --crl "$4" --pem</command> + </leafNode> + </children> + </tagNode> </children> <command>sudo ${vyos_op_scripts_dir}/pki.py --action show</command> </node> diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 7663e4c00..579e348f7 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -2,9 +2,18 @@ <interfaceDefinition> <node name="show"> <children> + <tagNode name="log"> + <properties> + <help>Show last number of messages in master logging buffer</help> + <completionHelp> + <list><1-9999></list> + </completionHelp> + </properties> + <command>if ${vyos_validators_dir}/numeric --range 1-9999 "$3"; then journalctl --no-hostname --boot --lines "$3"; fi</command> + </tagNode> <node name="log"> <properties> - <help>Show contents of current master log file</help> + <help>Show contents of current master logging buffer</help> </properties> <command>journalctl --no-hostname --boot</command> <children> @@ -461,6 +470,12 @@ </leafNode> </children> </node> + <leafNode name="vpp"> + <properties> + <help>Show log for Vector Packet Processor (VPP)</help> + </properties> + <command>journalctl --no-hostname --boot --unit vpp.service</command> + </leafNode> <leafNode name="vrrp"> <properties> <help>Show log for Virtual Router Redundancy Protocol (VRRP)</help> diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py index 26114149f..e1870aa0a 100644 --- a/python/vyos/config_mgmt.py +++ b/python/vyos/config_mgmt.py @@ -21,6 +21,8 @@ import logging from typing import Optional, Tuple, Union from filecmp import cmp from datetime import datetime +from textwrap import dedent +from pathlib import Path from tabulate import tabulate from vyos.config import Config @@ -456,19 +458,18 @@ Proceed ?''' return ConfigTree(c) def _add_logrotate_conf(self): - conf = f"""{archive_config_file} {{ - su root vyattacfg - rotate {self.max_revisions} - start 0 - compress - copy -}}""" - mask = os.umask(0o133) - - with open(logrotate_conf, 'w') as f: - f.write(conf) - - os.umask(mask) + conf: str = dedent(f"""\ + {archive_config_file} {{ + su root vyattacfg + rotate {self.max_revisions} + start 0 + compress + copy + }} + """) + conf_file = Path(logrotate_conf) + conf_file.write_text(conf) + conf_file.chmod(0o644) def _archive_active_config(self) -> bool: mask = os.umask(0o113) diff --git a/python/vyos/util.py b/python/vyos/util.py index 33da5da40..ed651fdc3 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -913,7 +913,7 @@ def get_interface_config(interface): if not os.path.exists(f'/sys/class/net/{interface}'): return None from json import loads - tmp = loads(cmd(f'ip -d -j link show {interface}'))[0] + tmp = loads(cmd(f'ip --detail --json link show dev {interface}'))[0] return tmp def get_interface_address(interface): @@ -923,7 +923,7 @@ def get_interface_address(interface): if not os.path.exists(f'/sys/class/net/{interface}'): return None from json import loads - tmp = loads(cmd(f'ip -d -j addr show {interface}'))[0] + tmp = loads(cmd(f'ip --detail --json addr show dev {interface}'))[0] return tmp def get_interface_namespace(iface): @@ -937,17 +937,17 @@ def get_interface_namespace(iface): return None for ns in tmp: - namespace = f'{ns["name"]}' + netns = f'{ns["name"]}' # Search interface in each netns - data = loads(cmd(f'ip netns exec {namespace} ip -j link show')) - for compare in data: - if iface == compare["ifname"]: - return namespace + data = loads(cmd(f'ip netns exec {netns} ip --json link show')) + for tmp in data: + if iface == tmp["ifname"]: + return netns def get_all_vrfs(): """ Return a dictionary of all system wide known VRF instances """ from json import loads - tmp = loads(cmd('ip -j vrf list')) + tmp = loads(cmd('ip --json vrf list')) # Result is of type [{"name":"red","table":1000},{"name":"blue","table":2000}] # so we will re-arrange it to a more nicer representation: # {'red': {'table': 1000}, 'blue': {'table': 2000}} diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py index 72b7ca6da..7386d44f0 100644 --- a/python/vyos/utils/network.py +++ b/python/vyos/utils/network.py @@ -15,7 +15,6 @@ import os - def get_protocol_by_name(protocol_name): """Get protocol number by protocol name @@ -28,3 +27,10 @@ def get_protocol_by_name(protocol_name): return protocol_number except socket.error: return protocol_name + +def interface_exists_in_netns(interface_name, netns): + from vyos.util import rc_cmd + rc, out = rc_cmd(f'ip netns exec {netns} ip link show dev {interface_name}') + if rc == 0: + return True + return False diff --git a/python/vyos/utils/system.py b/python/vyos/utils/system.py new file mode 100644 index 000000000..7102d5985 --- /dev/null +++ b/python/vyos/utils/system.py @@ -0,0 +1,82 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from subprocess import run + + +def sysctl_read(name: str) -> str: + """Read and return current value of sysctl() option + + Args: + name (str): sysctl key name + + Returns: + str: sysctl key value + """ + tmp = run(['sysctl', '-nb', name], capture_output=True) + return tmp.stdout.decode() + + +def sysctl_write(name: str, value: str | int) -> bool: + """Change value via sysctl() + + Args: + name (str): sysctl key name + value (str | int): sysctl key value + + Returns: + bool: True if changed, False otherwise + """ + # convert other types to string before comparison + if not isinstance(value, str): + value = str(value) + # do not change anything if a value is already configured + if sysctl_read(name) == value: + return True + # return False if sysctl call failed + if run(['sysctl', '-wq', f'{name}={value}']).returncode != 0: + return False + # compare old and new values + # sysctl may apply value, but its actual value will be + # different from requested + if sysctl_read(name) == value: + return True + # False in other cases + return False + + +def sysctl_apply(sysctl_dict: dict[str, str], revert: bool = True) -> bool: + """Apply sysctl values. + + Args: + sysctl_dict (dict[str, str]): dictionary with sysctl keys with values + revert (bool, optional): Revert to original values if new were not + applied. Defaults to True. + + Returns: + bool: True if all params configured properly, False in other cases + """ + # get current values + sysctl_original: dict[str, str] = {} + for key_name in sysctl_dict.keys(): + sysctl_original[key_name] = sysctl_read(key_name) + # apply new values and revert in case one of them was not applied + for key_name, value in sysctl_dict.items(): + if not sysctl_write(key_name, value): + if revert: + sysctl_apply(sysctl_original, revert=False) + return False + # everything applied + return True diff --git a/python/vyos/xml_ref/definition.py b/python/vyos/xml_ref/definition.py index 33a49ca69..7634773d6 100644 --- a/python/vyos/xml_ref/definition.py +++ b/python/vyos/xml_ref/definition.py @@ -123,9 +123,6 @@ class Xml: return d def multi_to_list(self, rpath: list, conf: dict) -> dict: - if rpath and rpath[-1] in list(conf): - raise ValueError('rpath should be disjoint from conf keys') - res: Any = {} for k in list(conf): @@ -246,10 +243,6 @@ class Xml: if not conf: return self.get_defaults(path, get_first_key=get_first_key, recursive=recursive) - if path and path[-1] in list(conf): - conf = conf[path[-1]] - conf = {} if not isinstance(conf, dict) else conf - if not self._well_defined(path, conf): print('path to config dict does not define full config paths') return {} diff --git a/smoketest/scripts/cli/test_ha_virtual_server.py b/smoketest/scripts/cli/test_ha_virtual_server.py index e3a91283e..6327df9df 100755 --- a/smoketest/scripts/cli/test_ha_virtual_server.py +++ b/smoketest/scripts/cli/test_ha_virtual_server.py @@ -47,6 +47,7 @@ class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase): delay = '10' method = 'nat' persistence_timeout = '600' + vs = 'serv-one' vip = '203.0.113.111' vport = '2222' rservers = ['192.0.2.21', '192.0.2.22', '192.0.2.23'] @@ -56,21 +57,23 @@ class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase): vserver_base = base_path + ['virtual-server'] - self.cli_set(vserver_base + [vip, 'algorithm', algo]) - self.cli_set(vserver_base + [vip, 'delay-loop', delay]) - self.cli_set(vserver_base + [vip, 'forward-method', method]) - self.cli_set(vserver_base + [vip, 'persistence-timeout', persistence_timeout]) - self.cli_set(vserver_base + [vip, 'port', vport]) - self.cli_set(vserver_base + [vip, 'protocol', proto]) + self.cli_set(vserver_base + [vs, 'address', vip]) + self.cli_set(vserver_base + [vs, 'algorithm', algo]) + self.cli_set(vserver_base + [vs, 'delay-loop', delay]) + self.cli_set(vserver_base + [vs, 'forward-method', method]) + self.cli_set(vserver_base + [vs, 'persistence-timeout', persistence_timeout]) + self.cli_set(vserver_base + [vs, 'port', vport]) + self.cli_set(vserver_base + [vs, 'protocol', proto]) for rs in rservers: - self.cli_set(vserver_base + [vip, 'real-server', rs, 'connection-timeout', connection_timeout]) - self.cli_set(vserver_base + [vip, 'real-server', rs, 'port', rport]) + self.cli_set(vserver_base + [vs, 'real-server', rs, 'connection-timeout', connection_timeout]) + self.cli_set(vserver_base + [vs, 'real-server', rs, 'port', rport]) # commit changes self.cli_commit() config = read_file(KEEPALIVED_CONF) + self.assertIn(f'virtual_server {vip} {vport}', config) self.assertIn(f'delay_loop {delay}', config) self.assertIn(f'lb_algo lc', config) self.assertIn(f'lb_kind {method.upper()}', config) @@ -86,6 +89,7 @@ class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase): delay = '15' method = 'nat' persistence_timeout = '300' + vs = 'serv-two' vip = '203.0.113.222' vport = '22322' rservers = ['192.0.2.11', '192.0.2.12'] @@ -107,15 +111,16 @@ class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase): self.cli_set(vrrp_base + [group, 'vrid', vrid]) # Virtual-server config - self.cli_set(vserver_base + [vip, 'algorithm', algo]) - self.cli_set(vserver_base + [vip, 'delay-loop', delay]) - self.cli_set(vserver_base + [vip, 'forward-method', method]) - self.cli_set(vserver_base + [vip, 'persistence-timeout', persistence_timeout]) - self.cli_set(vserver_base + [vip, 'port', vport]) - self.cli_set(vserver_base + [vip, 'protocol', proto]) + self.cli_set(vserver_base + [vs, 'address', vip]) + self.cli_set(vserver_base + [vs, 'algorithm', algo]) + self.cli_set(vserver_base + [vs, 'delay-loop', delay]) + self.cli_set(vserver_base + [vs, 'forward-method', method]) + self.cli_set(vserver_base + [vs, 'persistence-timeout', persistence_timeout]) + self.cli_set(vserver_base + [vs, 'port', vport]) + self.cli_set(vserver_base + [vs, 'protocol', proto]) for rs in rservers: - self.cli_set(vserver_base + [vip, 'real-server', rs, 'connection-timeout', connection_timeout]) - self.cli_set(vserver_base + [vip, 'real-server', rs, 'port', rport]) + self.cli_set(vserver_base + [vs, 'real-server', rs, 'connection-timeout', connection_timeout]) + self.cli_set(vserver_base + [vs, 'real-server', rs, 'port', rport]) # commit changes self.cli_commit() @@ -131,6 +136,7 @@ class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase): self.assertIn(f'preempt_delay 0', config) # default value # Keepalived virtual-server + self.assertIn(f'virtual_server {vip} {vport}', config) self.assertIn(f'delay_loop {delay}', config) self.assertIn(f'lb_algo lc', config) self.assertIn(f'lb_kind {method.upper()}', config) diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py index e18b426b1..2b1cdbd23 100755 --- a/src/conf_mode/high-availability.py +++ b/src/conf_mode/high-availability.py @@ -175,6 +175,11 @@ def verify(ha): # Virtual-server if 'virtual_server' in ha: for vs, vs_config in ha['virtual_server'].items(): + + if 'address' not in vs_config and 'fwmark' not in vs_config: + raise ConfigError('Either address or fwmark is required ' + f'but not set for virtual-server "{vs}"') + if 'port' not in vs_config and 'fwmark' not in vs_config: raise ConfigError(f'Port or fwmark is required but not set for virtual-server "{vs}"') if 'port' in vs_config and 'fwmark' in vs_config: diff --git a/src/conf_mode/service_config_sync.py b/src/conf_mode/service_config_sync.py new file mode 100755 index 000000000..5cde735a1 --- /dev/null +++ b/src/conf_mode/service_config_sync.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import json +from pathlib import Path + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.xml import defaults +from vyos import ConfigError +from vyos import airbag + +airbag.enable() + + +service_conf = Path(f'/run/config_sync_conf.conf') +post_commit_dir = '/run/scripts/commit/post-hooks.d' +post_commit_file_src = '/usr/libexec/vyos/vyos_config_sync.py' +post_commit_file = f'{post_commit_dir}/vyos_config_sync' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'config-sync'] + if not conf.exists(base): + return None + config = conf.get_config_dict(base, + get_first_key=True, + no_tag_node_value_mangle=True) + + default_values = defaults(base) + config = dict_merge(default_values, config) + + return config + + +def verify(config): + # bail out early - looks like removal from running config + if not config: + return None + + if 'mode' not in config: + raise ConfigError(f'config-sync mode is mandatory!') + + for option in ['secondary', 'section']: + if option not in config: + raise ConfigError(f"config-sync '{option}' is not configured!") + + if 'address' not in config['secondary']: + raise ConfigError(f'secondary address is mandatory!') + if 'key' not in config['secondary']: + raise ConfigError(f'secondary key is mandatory!') + + +def generate(config): + if not config: + + if os.path.exists(post_commit_file): + os.unlink(post_commit_file) + + if service_conf.exists(): + service_conf.unlink() + + return None + + # Write configuration file + conf_json = json.dumps(config, indent=4) + service_conf.write_text(conf_json) + + # Create post commit dir + if not os.path.isdir(post_commit_dir): + os.makedirs(post_commit_dir) + + # Symlink from helpers to post-commit + if not os.path.exists(post_commit_file): + os.symlink(post_commit_file_src, post_commit_file) + + return None + + +def apply(config): + 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/snmp.py b/src/conf_mode/snmp.py index 9b7c04eb0..f4611e15e 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -161,8 +161,12 @@ def verify(snmp): for address in snmp['listen_address']: # We only wan't to configure addresses that exist on the system. # Hint the user if they don't exist - if not is_addr_assigned(address): - Warning(f'SNMP listen address "{address}" not configured!') + if 'vrf' in snmp: + vrf_name = snmp['vrf'] + if not is_addr_assigned(address, vrf_name) and address not in ['::1','127.0.0.1']: + raise ConfigError(f'SNMP listen address "{address}" not configured in vrf "{vrf_name}"!') + elif not is_addr_assigned(address): + raise ConfigError(f'SNMP listen address "{address}" not configured in default vrf!') if 'trap_target' in snmp: for trap, trap_config in snmp['trap_target'].items(): diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py index dc13f4e60..87ebc3ea9 100755 --- a/src/conf_mode/vpp.py +++ b/src/conf_mode/vpp.py @@ -15,7 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os -import psutil +from psutil import virtual_memory from pathlib import Path from re import search as re_search, MULTILINE as re_M @@ -26,6 +26,7 @@ from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.ifconfig import Section from vyos.util import call, rc_cmd, boot_configuration_complete +from vyos.utils.system import sysctl_read, sysctl_apply from vyos.template import render from vyos.xml import defaults @@ -39,10 +40,10 @@ airbag.enable() service_name = 'vpp' service_conf = Path(f'/run/vpp/{service_name}.conf') systemd_override = '/run/systemd/system/vpp.service.d/10-override.conf' -sysctl_vpp = '/etc/sysctl.d/80-vpp.conf' -# Min memory 6GB (2GB reserved for vpp) -MIN_TOTAL_MEMORY = 6 +# Free memory required for VPP +# 2 GB for hugepages + 1 GB for other services +MIN_AVAILABLE_MEMORY: int = 3 * 1024**3 def _get_pci_address_by_interface(iface) -> str: @@ -64,7 +65,6 @@ def _get_pci_address_by_interface(iface) -> str: raise ConfigError(f'Cannot find PCI address for interface {iface}') - def get_config(config=None): if config: conf = config @@ -131,32 +131,45 @@ def verify(config): return None if 'interface' not in config: - raise ConfigError(f'"interface" is required but not set!') + raise ConfigError('"interface" is required but not set!') if 'cpu' in config: - if 'corelist_workers' in config['cpu'] and 'main_core' not in config['cpu']: - raise ConfigError(f'"cpu main-core" is required but not set!') + if 'corelist_workers' in config['cpu'] and 'main_core' not in config[ + 'cpu']: + raise ConfigError('"cpu main-core" is required but not set!') - memory = psutil.virtual_memory() - memory_total = round(memory.total / (1024 ** 3), 2) - if memory_total < MIN_TOTAL_MEMORY: + memory_available: int = virtual_memory().available + if memory_available < MIN_AVAILABLE_MEMORY: raise ConfigError( - f'Not enough installed memory {memory_total}GB! ' - f'The minimum required memory is {MIN_TOTAL_MEMORY}GB.' - ) + 'Not enough free memory to start VPP:\n' + f'available: {round(memory_available / 1024**3, 1)}GB\n' + f'required: {round(MIN_AVAILABLE_MEMORY / 1024**3, 1)}GB') def generate(config): if not config or (len(config) == 1 and 'removed_ifaces' in config): # Remove old config and return service_conf.unlink(missing_ok=True) - if os.path.isfile(sysctl_vpp): - os.unlink(sysctl_vpp) return None render(service_conf, 'vpp/startup.conf.j2', config) render(systemd_override, 'vpp/override.conf.j2', config) - render(sysctl_vpp, 'vpp/sysctl.conf.j2', config) + + # apply default sysctl values from + # https://github.com/FDio/vpp/blob/v23.06/src/vpp/conf/80-vpp.conf + sysctl_config: dict[str, str] = { + 'vm.nr_hugepages': '1024', + 'vm.max_map_count': '3096', + 'vm.hugetlb_shm_group': '0', + 'kernel.shmmax': '2147483648' + } + # we do not want to reduce `kernel.shmmax` + kernel_shmnax_current: str = sysctl_read('kernel.shmmax') + if int(kernel_shmnax_current) > int(sysctl_config['kernel.shmmax']): + sysctl_config['kernel.shmmax'] = kernel_shmnax_current + + if not sysctl_apply(sysctl_config): + raise ConfigError('Cannot configure sysctl parameters for VPP') return None @@ -168,8 +181,6 @@ def apply(config): call('systemctl daemon-reload') call(f'systemctl restart {service_name}.service') - call(f'sysctl -qp {sysctl_vpp}') - # Initialize interfaces removed from VPP for iface in config.get('removed_ifaces', []): host_control = HostControl() diff --git a/src/etc/systemd/system-generators/vyos-generator b/src/etc/systemd/system-generators/vyos-generator new file mode 100755 index 000000000..34faab6a2 --- /dev/null +++ b/src/etc/systemd/system-generators/vyos-generator @@ -0,0 +1,94 @@ +#!/bin/sh +set -f + +LOG="" +DEBUG_LEVEL=1 +LOG_D="/run/vyos-router" +ENABLE="enabled" +DISABLE="disabled" +FOUND="found" +NOTFOUND="notfound" +RUN_ENABLED_FILE="$LOG_D/$ENABLE" +VYOS_SYSTEM_TARGET="/lib/systemd/system/vyos.target" +VYOS_TARGET_NAME="vyos.target" + +debug() { + local lvl="$1" + shift + [ "$lvl" -gt "$DEBUG_LEVEL" ] && return + if [ -z "$LOG" ]; then + local log="$LOG_D/${0##*/}.log" + { [ -d "$LOG_D" ] || mkdir -p "$LOG_D"; } && + { : > "$log"; } >/dev/null 2>&1 && LOG="$log" || + LOG="/dev/kmsg" + fi + echo "$@" >> "$LOG" +} + +default() { + _RET="$ENABLE" +} + +main() { + local normal_d="$1" early_d="$2" late_d="$3" + local target_name="multi-user.target" gen_d="$early_d" + local link_path="$gen_d/${target_name}.wants/${VYOS_TARGET_NAME}" + local ds="$NOTFOUND" + + debug 1 "$0 normal=$normal_d early=$early_d late=$late_d" + debug 2 "$0 $*" + + local search result="error" ret="" + for search in default; do + if $search; then + debug 1 "$search found $_RET" + [ "$_RET" = "$ENABLE" -o "$_RET" = "$DISABLE" ] && + result=$_RET && break + else + ret=$? + debug 0 "search $search returned $ret" + fi + done + + # enable AND ds=found == enable + # enable AND ds=notfound == disable + # disable || <any> == disabled + if [ "$result" = "$ENABLE" ]; then + if [ -e "$link_path" ]; then + debug 1 "already enabled: no change needed" + else + [ -d "${link_path%/*}" ] || mkdir -p "${link_path%/*}" || + debug 0 "failed to make dir $link_path" + if ln -snf "$VYOS_SYSTEM_TARGET" "$link_path"; then + debug 1 "enabled via $link_path -> $VYOS_SYSTEM_TARGET" + else + ret=$? + debug 0 "[$ret] enable failed:" \ + "ln $VYOS_SYSTEM_TARGET $link_path" + fi + fi + : > "$RUN_ENABLED_FILE" + elif [ "$result" = "$DISABLE" ]; then + if [ -f "$link_path" ]; then + if rm -f "$link_path"; then + debug 1 "disabled. removed existing $link_path" + else + ret=$? + debug 0 "[$ret] disable failed, remove $link_path" + fi + else + debug 1 "already disabled: no change needed [no $link_path]" + fi + if [ -e "$RUN_ENABLED_FILE" ]; then + rm -f "$RUN_ENABLED_FILE" + fi + else + debug 0 "unexpected result '$result' 'ds=$ds'" + ret=3 + fi + return $ret +} + +main "$@" + +# vi: ts=4 expandtab diff --git a/src/etc/systemd/system/getty@.service.d/aftervyos.conf b/src/etc/systemd/system/getty@.service.d/aftervyos.conf new file mode 100644 index 000000000..c5753900e --- /dev/null +++ b/src/etc/systemd/system/getty@.service.d/aftervyos.conf @@ -0,0 +1,3 @@ +[Service] +ExecStartPre=-/usr/libexec/vyos/init/vyos-config +StandardOutput=journal+console diff --git a/src/etc/systemd/system/serial-getty@.service.d/aftervyos.conf b/src/etc/systemd/system/serial-getty@.service.d/aftervyos.conf new file mode 100644 index 000000000..8ba42778d --- /dev/null +++ b/src/etc/systemd/system/serial-getty@.service.d/aftervyos.conf @@ -0,0 +1,3 @@ +[Service] +ExecStartPre=-/usr/libexec/vyos/init/vyos-config SERIAL +StandardOutput=journal+console diff --git a/src/helpers/vyos_config_sync.py b/src/helpers/vyos_config_sync.py new file mode 100755 index 000000000..7cfa8fe88 --- /dev/null +++ b/src/helpers/vyos_config_sync.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +import os +import json +import requests +import urllib3 +import logging +from typing import Optional, List, Union, Dict, Any + +from vyos.config import Config +from vyos.template import bracketize_ipv6 + + +CONFIG_FILE = '/run/config_sync_conf.conf' + +# Logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) +logger.name = os.path.basename(__file__) + +# API +API_HEADERS = {'Content-Type': 'application/json'} + + +def post_request(url: str, + data: str, + headers: Dict[str, str]) -> requests.Response: + """Sends a POST request to the specified URL + + Args: + url (str): The URL to send the POST request to. + data (Dict[str, Any]): The data to send with the POST request. + headers (Dict[str, str]): The headers to include with the POST request. + + Returns: + requests.Response: The response object representing the server's response to the request + """ + + response = requests.post(url, + data=data, + headers=headers, + verify=False, + timeout=timeout) + return response + + +def retrieve_config(section: str = None) -> Optional[Dict[str, Any]]: + """Retrieves the configuration from the local server. + + Args: + section: str: The section of the configuration to retrieve. Default is None. + + Returns: + Optional[Dict[str, Any]]: The retrieved configuration as a dictionary, or None if an error occurred. + """ + if section is None: + section = [] + else: + section = section.split() + + conf = Config() + config = conf.get_config_dict(section, get_first_key=True) + if config: + return config + return None + + +def set_remote_config( + address: str, + key: str, + op: str, + path: str = None, + section: Optional[str] = None) -> Optional[Dict[str, Any]]: + """Loads the VyOS configuration in JSON format to a remote host. + + Args: + address (str): The address of the remote host. + key (str): The key to use for loading the configuration. + path (Optional[str]): The path of the configuration. Default is None. + section (Optional[str]): The section of the configuration to load. Default is None. + + Returns: + Optional[Dict[str, Any]]: The response from the remote host as a dictionary, or None if an error occurred. + """ + + if path is None: + path = [] + else: + path = path.split() + headers = {'Content-Type': 'application/json'} + + # Disable the InsecureRequestWarning + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + url = f'https://{address}/configure-section' + data = json.dumps({ + 'op': mode, + 'path': path, + 'section': section, + 'key': key + }) + + try: + config = post_request(url, data, headers) + return config.json() + except requests.exceptions.RequestException as e: + print(f"An error occurred: {e}") + logger.error(f"An error occurred: {e}") + return None + + +def is_section_revised(section: str) -> bool: + from vyos.config_mgmt import is_node_revised + return is_node_revised([section]) + + +def config_sync(secondary_address: str, + secondary_key: str, + sections: List[str], + mode: str): + """Retrieve a config section from primary router in JSON format and send it to + secondary router + """ + # Config sync only if sections changed + if not any(map(is_section_revised, sections)): + return + + logger.info( + f"Config synchronization: Mode={mode}, Secondary={secondary_address}" + ) + + # Sync sections ("nat", "firewall", etc) + for section in sections: + config_json = retrieve_config(section=section) + # Check if config path deesn't exist, for example "set nat" + # we set empty value for config_json data + # As we cannot send to the remote host section "nat None" config + if not config_json: + config_json = "" + logger.debug( + f"Retrieved config for section '{section}': {config_json}") + set_config = set_remote_config(address=secondary_address, + key=secondary_key, + op=mode, + path=section, + section=config_json) + logger.debug(f"Set config for section '{section}': {set_config}") + + +if __name__ == '__main__': + # Read configuration from file + if not os.path.exists(CONFIG_FILE): + logger.error(f"Post-commit: No config file '{CONFIG_FILE}' exists") + exit(0) + + with open(CONFIG_FILE, 'r') as f: + config_data = f.read() + + config = json.loads(config_data) + + mode = config.get('mode') + secondary_address = config.get('secondary', {}).get('address') + secondary_address = bracketize_ipv6(secondary_address) + secondary_key = config.get('secondary', {}).get('key') + sections = config.get('section') + timeout = int(config.get('secondary', {}).get('timeout')) + + if not all([ + mode, secondary_address, secondary_key, sections + ]): + logger.error( + "Missing required configuration data for config synchronization.") + exit(0) + + config_sync(secondary_address, secondary_key, + sections, mode) diff --git a/src/init/vyos-config b/src/init/vyos-config new file mode 100755 index 000000000..356427024 --- /dev/null +++ b/src/init/vyos-config @@ -0,0 +1,16 @@ +#!/bin/bash + +while [ ! -f /tmp/vyos-config-status ] +do + sleep 1 +done + +status=$(cat /tmp/vyos-config-status) + +if [ -z "$1" ]; then + if [ $status -ne 0 ]; then + echo "Configuration error" + else + echo "Configuration success" + fi +fi diff --git a/src/init/vyos-router b/src/init/vyos-router new file mode 100755 index 000000000..7b752b84b --- /dev/null +++ b/src/init/vyos-router @@ -0,0 +1,421 @@ +#!/bin/bash +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +. /lib/lsb/init-functions + +: ${vyatta_env:=/etc/default/vyatta} +source $vyatta_env + +declare progname=${0##*/} +declare action=$1; shift + +declare -x BOOTFILE=$vyatta_sysconfdir/config/config.boot + +# If vyos-config= boot option is present, use that file instead +for x in $(cat /proc/cmdline); do + [[ $x = vyos-config=* ]] || continue + VYOS_CONFIG="${x#vyos-config=}" +done + +if [ ! -z "$VYOS_CONFIG" ]; then + if [ -r "$VYOS_CONFIG" ]; then + echo "Config selected manually: $VYOS_CONFIG" + declare -x BOOTFILE="$VYOS_CONFIG" + else + echo "WARNING: Could not read selected config file, using default!" + fi +fi + +declare -a subinit +declare -a all_subinits=( firewall ) + +if [ $# -gt 0 ] ; then + for s in $@ ; do + [ -x ${vyatta_sbindir}/${s}.init ] && subinit[${#subinit}]=$s + done +else + for s in ${all_subinits[@]} ; do + [ -x ${vyatta_sbindir}/${s}.init ] && subinit[${#subinit}]=$s + done +fi + +GROUP=vyattacfg + +# easy way to make empty file without any command +empty() +{ + >$1 +} + +# check if bootup of this portion is disabled +disabled () { + grep -q -w no-vyos-$1 /proc/cmdline +} + +# if necessary, provide initial config +init_bootfile () { + if [ ! -r $BOOTFILE ] ; then + if [ -f $vyatta_sysconfdir/config.boot.default ]; then + cp $vyatta_sysconfdir/config.boot.default $BOOTFILE + else + $vyos_libexec_dir/system-versions-foot.py > $BOOTFILE + fi + chgrp ${GROUP} $BOOTFILE + chmod 660 $BOOTFILE + fi +} + +# if necessary, migrate initial config +migrate_bootfile () +{ + if [ -x $vyos_libexec_dir/run-config-migration.py ]; then + log_progress_msg migrate + sg ${GROUP} -c "$vyos_libexec_dir/run-config-migration.py $BOOTFILE" + fi +} + +# load the initial config +load_bootfile () +{ + log_progress_msg configure + ( + if [ -f /etc/default/vyatta-load-boot ]; then + # build-specific environment for boot-time config loading + source /etc/default/vyatta-load-boot + fi + if [ -x $vyos_libexec_dir/vyos-boot-config-loader.py ]; then + sg ${GROUP} -c "$vyos_libexec_dir/vyos-boot-config-loader.py $BOOTFILE" + fi + ) +} + +# execute the pre-config script +run_preconfig_script () +{ + if [ -x $vyatta_sysconfdir/config/scripts/vyos-preconfig-bootup.script ]; then + $vyatta_sysconfdir/config/scripts/vyos-preconfig-bootup.script + fi +} + +# execute the post-config scripts +run_postconfig_scripts () +{ + if [ -x $vyatta_sysconfdir/config/scripts/vyatta-postconfig-bootup.script ]; then + $vyatta_sysconfdir/config/scripts/vyatta-postconfig-bootup.script + fi + if [ -x $vyatta_sysconfdir/config/scripts/vyos-postconfig-bootup.script ]; then + $vyatta_sysconfdir/config/scripts/vyos-postconfig-bootup.script + fi +} + +run_postupgrade_script () +{ + if [ -f $vyatta_sysconfdir/config/.upgraded ]; then + # Run the system script + /usr/libexec/vyos/system/post-upgrade + + # Run user scripts + if [ -d $vyatta_sysconfdir/config/scripts/post-upgrade.d ]; then + run-parts $vyatta_sysconfdir/config/scripts/post-upgrade.d + fi + rm -f $vyatta_sysconfdir/config/.upgraded + fi +} + +# +# On image booted machines, we need to mount /boot from the image-specific +# boot directory so that kernel package installation will put the +# files in the right place. We also have to mount /boot/grub from the +# system-wide grub directory so that tools that edit the grub.cfg +# file will find it in the expected location. +# +bind_mount_boot () +{ + persist_path=$(/opt/vyatta/sbin/vyos-persistpath) + if [ $? == 0 ]; then + if [ -e $persist_path/boot ]; then + image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//') + + if [ -n "$image_name" ]; then + mount --bind $persist_path/boot/$image_name /boot + if [ $? -ne 0 ]; then + echo "Couldn't bind mount /boot" + fi + + if [ ! -d /boot/grub ]; then + mkdir /boot/grub + fi + + mount --bind $persist_path/boot/grub /boot/grub + if [ $? -ne 0 ]; then + echo "Couldn't bind mount /boot/grub" + fi + fi + fi + fi +} + +clear_or_override_config_files () +{ + for conf in snmp/snmpd.conf snmp/snmptrapd.conf snmp/snmp.conf \ + keepalived/keepalived.conf cron.d/vyos-crontab \ + ipvsadm.rules default/ipvsadm resolv.conf + do + if [ -s /etc/$conf ] ; then + empty /etc/$conf + chmod 0644 /etc/$conf + fi + done +} + +update_interface_config () +{ + if [ -d /run/udev/vyos ]; then + $vyos_libexec_dir/vyos-interface-rescan.py $BOOTFILE + fi +} + +cleanup_post_commit_hooks () { + # Remove links from the post-commit hooks directory. + # note that this approach only supports hooks that are "configured", + # i.e., it does not support hooks that need to always be present. + cpostdir=$(cli-shell-api getPostCommitHookDir) + # exclude commits hooks from vyatta-cfg + excluded="10vyatta-log-commit.pl 99vyos-user-postcommit-hooks" + if [ -d "$cpostdir" ]; then + for f in $cpostdir/*; do + if [[ ! $excluded =~ $(basename $f) ]]; then + rm -f $cpostdir/$(basename $f) + fi + done + fi +} + +# These are all the default security setting which are later +# overridden when configuration is read. These are the values the +# system defaults. +security_reset () +{ + # restore PAM back to virgin state (no radius/tacacs services) + pam-auth-update --package --remove radius + rm -f /etc/pam_radius_auth.conf + pam-auth-update --package --remove tacplus + rm -f /etc/tacplus_nss.conf /etc/tacplus_servers + + # Certain configuration files are re-generated by the configuration + # subsystem and must reside under /etc and can not easily be moved to /run. + # So on every boot we simply delete any remaining files and let the CLI + # regenearte them. + + # PPPoE + rm -f /etc/ppp/peers/pppoe* /etc/ppp/peers/wlm* + + # IPSec + rm -rf /etc/ipsec.conf /etc/ipsec.secrets + find /etc/swanctl -type f | xargs rm -f + + # limit cleanup + rm -f /etc/security/limits.d/10-vyos.conf + + # iproute2 cleanup + rm -f /etc/iproute2/rt_tables.d/vyos-*.conf + + # Container + rm -f /etc/containers/storage.conf /etc/containers/registries.conf /etc/containers/containers.conf + # Clean all networks and re-create them from our CLI + rm -f /etc/containers/networks/* + + # System Options (SSH/cURL) + rm -f /etc/ssh/ssh_config.d/*vyos*.conf + rm -f /etc/curlrc +} + +# XXX: T3885 - generate persistend DHCPv6 DUID (Type4 - UUID based) +gen_duid () +{ + DUID_FILE="/var/lib/dhcpv6/dhcp6c_duid" + UUID_FILE="/sys/class/dmi/id/product_uuid" + UUID_FILE_ALT="/sys/class/dmi/id/product_serial" + if [ ! -f ${UUID_FILE} ] && [ ! -f ${UUID_FILE_ALT} ]; then + return 1 + fi + + # DUID is based on the BIOS/EFI UUID. We omit additional - characters + if [ -f ${UUID_FILE} ]; then + UUID=$(cat ${UUID_FILE} | tr -d -) + fi + if [ -z ${UUID} ]; then + UUID=$(uuidgen --sha1 --namespace @dns --name $(cat ${UUID_FILE_ALT}) | tr -d -) + fi + # Add DUID type4 (UUID) information + DUID_TYPE="0004" + + # The length-information (as per RFC6355 UUID is 128 bits long) is in big-endian + # format - beware when porting to ARM64. The length field consists out of the + # UUID (128 bit + 16 bits DUID type) resulting in hex 12. + DUID_LEN="0012" + if [ "$(echo -n I | od -to2 | head -n1 | cut -f2 -d" " | cut -c6 )" -eq 1 ]; then + # true on little-endian (x86) systems + DUID_LEN="1200" + fi + + for i in $(echo -n ${DUID_LEN}${DUID_TYPE}${UUID} | sed 's/../& /g'); do + echo -ne "\x$i" + done > ${DUID_FILE} +} + +start () +{ + # reset and clean config files + security_reset || log_failure_msg "security reset failed" + + # some legacy directories migrated over from old rl-system.init + mkdir -p /var/run/vyatta /var/log/vyatta + chgrp vyattacfg /var/run/vyatta /var/log/vyatta + chmod 775 /var/run/vyatta /var/log/vyatta + + log_daemon_msg "Waiting for NICs to settle down" + # On boot time udev migth take a long time to reorder nic's, this will ensure that + # all udev activity is completed and all nics presented at boot-time will have their + # final name before continuing with vyos-router initialization. + SECONDS=0 + udevadm settle + STATUS=$? + log_progress_msg "settled in ${SECONDS}sec." + log_end_msg ${STATUS} + + # mountpoint for bpf maps required by xdp + mount -t bpf none /sys/fs/bpf + + # Clear out Debian APT source config file + empty /etc/apt/sources.list + + # Generate DHCPv6 DUID + gen_duid || log_failure_msg "could not generate DUID" + + # Mount a temporary filesystem for container networks. + # Configuration should be loaded from VyOS cli. + cni_dir="/etc/cni/net.d" + [ ! -d ${cni_dir} ] && mkdir -p ${cni_dir} + mount -t tmpfs none ${cni_dir} + + # Init firewall + nfct helper add rpc inet tcp + nfct helper add rpc inet udp + nfct helper add tns inet tcp + nft -f /usr/share/vyos/vyos-firewall-init.conf || log_failure_msg "could not initiate firewall rules" + + rm -f /etc/hostname + ${vyos_conf_scripts_dir}/host_name.py || log_failure_msg "could not reset host-name" + systemctl start frr.service + + # As VyOS does not execute commands that are not present in the CLI we call + # the script by hand to have a single source for the login banner and MOTD + ${vyos_conf_scripts_dir}/system_console.py || log_failure_msg "could not reset serial console" + ${vyos_conf_scripts_dir}/system-login.py || log_failure_msg "could not reset system login" + ${vyos_conf_scripts_dir}/system-login-banner.py || log_failure_msg "could not reset motd and issue files" + ${vyos_conf_scripts_dir}/system-option.py || log_failure_msg "could not reset system option files" + ${vyos_conf_scripts_dir}/conntrack.py || log_failure_msg "could not reset conntrack subsystem" + ${vyos_conf_scripts_dir}/container.py || log_failure_msg "could not reset container subsystem" + + clear_or_override_config_files || log_failure_msg "could not reset config files" + + # enable some debugging before loading the configuration + if grep -q vyos-debug /proc/cmdline; then + log_action_begin_msg "Enable runtime debugging options" + touch /tmp/vyos.container.debug + touch /tmp/vyos.ifconfig.debug + touch /tmp/vyos.frr.debug + touch /tmp/vyos.container.debug + fi + + log_action_begin_msg "Mounting VyOS Config" + # ensure the vyatta_configdir supports a large number of inodes since + # the config hierarchy is often inode-bound (instead of size). + # impose a minimum and then scale up dynamically with the actual size + # of the system memory. + local tmem=$(sed -n 's/^MemTotal: \+\([0-9]\+\) kB$/\1/p' /proc/meminfo) + local tpages + local tmpfs_opts="nosuid,nodev,mode=775,nr_inodes=0" #automatically allocate inodes + mount -o $tmpfs_opts -t tmpfs none ${vyatta_configdir} \ + && chgrp ${GROUP} ${vyatta_configdir} + log_action_end_msg $? + + disabled bootfile || init_bootfile + + cleanup_post_commit_hooks + + log_daemon_msg "Starting VyOS router" + disabled migrate || migrate_bootfile + + run_preconfig_script + + run_postupgrade_script + + update_interface_config + + for s in ${subinit[@]} ; do + if ! disabled $s; then + log_progress_msg $s + if ! ${vyatta_sbindir}/${s}.init start + then log_failure_msg + exit 1 + fi + fi + done + + bind_mount_boot + + disabled configure || load_bootfile + log_end_msg $? + + telinit q + chmod g-w,o-w / + + run_postconfig_scripts +} + +stop() +{ + local -i status=0 + log_daemon_msg "Stopping VyOS router" + for ((i=${#sub_inits[@]} - 1; i >= 0; i--)) ; do + s=${subinit[$i]} + log_progress_msg $s + ${vyatta_sbindir}/${s}.init stop + let status\|=$? + done + log_end_msg $status + log_action_begin_msg "Un-mounting VyOS Config" + umount ${vyatta_configdir} + log_action_end_msg $? + + systemctl stop frr.service +} + +case "$action" in + start) start ;; + stop) stop ;; + restart|force-reload) stop && start ;; + *) log_failure_msg "usage: $progname [ start|stop|restart ] [ subinit ... ]" ; + false ;; +esac + +exit $? + +# Local Variables: +# mode: shell-script +# sh-indentation: 4 +# End: diff --git a/src/migration-scripts/vrrp/3-to-4 b/src/migration-scripts/vrrp/3-to-4 new file mode 100755 index 000000000..b0a6975c2 --- /dev/null +++ b/src/migration-scripts/vrrp/3-to-4 @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import argv +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() + +base = ['high-availability', 'virtual-server'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +if config.exists(base): + for vs in config.list_nodes(base): + vs_base = base + [vs] + + # If the fwmark is used, the address is not required + if not config.exists(vs_base + ['fwmark']): + # add option: 'virtual-server <tag> address x.x.x.x' + config.set(vs_base + ['address'], value=vs) + + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print(f'Failed to save the modified config: {e}') + exit(1) diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py index b054690b0..7ea295ff1 100755 --- a/src/op_mode/pki.py +++ b/src/op_mode/pki.py @@ -840,7 +840,7 @@ def import_openvpn_secret(name, path): install_openvpn_key(name, key_data, key_version) # Show functions -def show_certificate_authority(name=None): +def show_certificate_authority(name=None, pem=False): headers = ['Name', 'Subject', 'Issuer CN', 'Issued', 'Expiry', 'Private Key', 'Parent'] data = [] certs = get_config_ca_certificate() @@ -852,6 +852,11 @@ def show_certificate_authority(name=None): continue cert = load_certificate(cert_dict['certificate']) + + if name and pem: + print(encode_certificate(cert)) + return + parent_ca_name = get_certificate_ca(cert, certs) cert_issuer_cn = cert.issuer.rfc4514_string().split(",")[0] @@ -867,7 +872,7 @@ def show_certificate_authority(name=None): print("Certificate Authorities:") print(tabulate.tabulate(data, headers)) -def show_certificate(name=None): +def show_certificate(name=None, pem=False): headers = ['Name', 'Type', 'Subject CN', 'Issuer CN', 'Issued', 'Expiry', 'Revoked', 'Private Key', 'CA Present'] data = [] certs = get_config_certificate() @@ -885,6 +890,10 @@ def show_certificate(name=None): if not cert: continue + if name and pem: + print(encode_certificate(cert)) + return + ca_name = get_certificate_ca(cert, ca_certs) cert_subject_cn = cert.subject.rfc4514_string().split(",")[0] cert_issuer_cn = cert.issuer.rfc4514_string().split(",")[0] @@ -906,7 +915,7 @@ def show_certificate(name=None): print("Certificates:") print(tabulate.tabulate(data, headers)) -def show_crl(name=None): +def show_crl(name=None, pem=False): headers = ['CA Name', 'Updated', 'Revokes'] data = [] certs = get_config_ca_certificate() @@ -927,9 +936,16 @@ def show_crl(name=None): if not crl: continue + if name and pem: + print(encode_certificate(crl)) + continue + certs = get_revoked_by_serial_numbers([revoked.serial_number for revoked in crl]) data.append([cert_name, crl.last_update, ", ".join(certs)]) + if name and pem: + return + print("Certificate Revocation Lists:") print(tabulate.tabulate(data, headers)) @@ -943,6 +959,7 @@ if __name__ == '__main__': parser.add_argument('--crl', help='Certificate Revocation List', required=False) parser.add_argument('--sign', help='Sign certificate with specified CA', required=False) parser.add_argument('--self-sign', help='Self-sign the certificate', action='store_true') + parser.add_argument('--pem', help='Output using PEM encoding', action='store_true') # SSH parser.add_argument('--ssh', help='SSH Key', required=False) @@ -1032,16 +1049,16 @@ if __name__ == '__main__': if not conf.exists(['pki', 'ca', ca_name]): print(f'CA "{ca_name}" does not exist!') exit(1) - show_certificate_authority(ca_name) + show_certificate_authority(ca_name, args.pem) elif args.certificate: cert_name = None if args.certificate == 'all' else args.certificate if cert_name: if not conf.exists(['pki', 'certificate', cert_name]): print(f'Certificate "{cert_name}" does not exist!') exit(1) - show_certificate(None if args.certificate == 'all' else args.certificate) + show_certificate(None if args.certificate == 'all' else args.certificate, args.pem) elif args.crl: - show_crl(None if args.crl == 'all' else args.crl) + show_crl(None if args.crl == 'all' else args.crl, args.pem) else: show_certificate_authority() show_certificate() diff --git a/src/systemd/vyos-router.service b/src/systemd/vyos-router.service new file mode 100644 index 000000000..6f683cebb --- /dev/null +++ b/src/systemd/vyos-router.service @@ -0,0 +1,19 @@ +[Unit] +Description=VyOS Router +After=systemd-journald-dev-log.socket time-sync.target local-fs.target cloud-config.service +Requires=frr.service +Conflicts=shutdown.target +Before=systemd-user-sessions.service + +[Service] +Type=simple +Restart=no +TimeoutSec=20min +KillMode=process +RemainAfterExit=yes +ExecStart=/usr/libexec/vyos/init/vyos-router start +ExecStop=/usr/libexec/vyos/init/vyos-router stop +StandardOutput=journal+console + +[Install] +WantedBy=vyos.target diff --git a/src/systemd/vyos.target b/src/systemd/vyos.target new file mode 100644 index 000000000..47c91c1cc --- /dev/null +++ b/src/systemd/vyos.target @@ -0,0 +1,3 @@ +[Unit] +Description=VyOS target +After=multi-user.target |