diff options
207 files changed, 5612 insertions, 2552 deletions
@@ -33,11 +33,6 @@ interface_definitions: $(config_xml_obj) # IPSec VPN EAP-RADIUS does not support source-address rm -rf $(TMPL_DIR)/vpn/ipsec/remote-access/radius/source-address - # T4284 neq QoS implementation is not yet live - find $(TMPL_DIR)/interfaces -name redirect -type d -exec rm -rf {} \; - rm -rf $(TMPL_DIR)/qos - rm -rf $(TMPL_DIR)/interfaces/input - # T2472 - EIGRP support rm -rf $(TMPL_DIR)/protocols/eigrp # T2773 - EIGRP support for VRF diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json index a69cf55e9..3205c7d20 100644 --- a/data/op-mode-standardized.json +++ b/data/op-mode-standardized.json @@ -7,6 +7,8 @@ "cpu.py", "dhcp.py", "dns.py", +"interfaces.py", +"lldp.py", "log.py", "memory.py", "nat.py", @@ -19,5 +21,6 @@ "storage.py", "uptime.py", "version.py", -"vrf.py" +"vrf.py", +"zone.py" ] diff --git a/data/templates/accel-ppp/l2tp.config.j2 b/data/templates/accel-ppp/l2tp.config.j2 index 3d1e835a9..5914fd375 100644 --- a/data/templates/accel-ppp/l2tp.config.j2 +++ b/data/templates/accel-ppp/l2tp.config.j2 @@ -126,7 +126,13 @@ ipv6={{ ppp_ipv6 }} {% else %} {{ 'ipv6=allow' if client_ipv6_pool_configured else '' }} {% endif %} - +{% if ppp_ipv6_intf_id is vyos_defined %} +ipv6-intf-id={{ ppp_ipv6_intf_id }} +{% endif %} +{% if ppp_ipv6_peer_intf_id is vyos_defined %} +ipv6-peer-intf-id={{ ppp_ipv6_peer_intf_id }} +{% endif %} +ipv6-accept-peer-intf-id={{ "1" if ppp_ipv6_accept_peer_intf_id else "0" }} {% if client_ipv6_pool %} [ipv6-pool] diff --git a/data/templates/chrony/chrony.conf.j2 b/data/templates/chrony/chrony.conf.j2 new file mode 100644 index 000000000..b3bfc8c0c --- /dev/null +++ b/data/templates/chrony/chrony.conf.j2 @@ -0,0 +1,58 @@ +### Autogenerated by ntp.py ### + +# This would step the system clock if the adjustment is larger than 0.1 seconds, +# but only in the first three clock updates. +makestep 1.0 3 + +# The rtcsync directive enables a mode where the system time is periodically +# copied to the RTC and chronyd does not try to track its drift. This directive +# cannot be used with the rtcfile directive. On Linux, the RTC copy is performed +# by the kernel every 11 minutes. +rtcsync + +# This directive specifies the maximum amount of memory that chronyd is allowed +# to allocate for logging of client accesses and the state that chronyd as an +# NTP server needs to support the interleaved mode for its clients. +clientloglimit 1048576 + +driftfile /run/chrony/drift +dumpdir /run/chrony +pidfile {{ config_file | replace('.conf', '.pid') }} + +# Determine when will the next leap second occur and what is the current offset +leapsectz right/UTC + +user {{ user }} + +# NTP servers to reach out to +{% if server is vyos_defined %} +{% for server, config in server.items() %} +{% set association = 'server' %} +{% if config.pool is vyos_defined %} +{% set association = 'pool' %} +{% endif %} +{{ association }} {{ server | replace('_', '-') }} iburst {{ 'noselect' if config.noselect is vyos_defined }} {{ 'prefer' if config.prefer is vyos_defined }} +{% endfor %} +{% endif %} + +# Allowed clients configuration +{% if allow_client.address is vyos_defined %} +{% for address in allow_client.address %} +allow {{ address }} +{% endfor %} +{% endif %} +deny all + +{% if listen_address is vyos_defined or interface is vyos_defined %} +# NTP should listen on configured addresses only +{% if listen_address is vyos_defined %} +{% for address in listen_address %} +bindaddress {{ address }} +{% endfor %} +{% endif %} +{% if interface is vyos_defined %} +{% for ifname in interface %} +binddevice {{ ifname }} +{% endfor %} +{% endif %} +{% endif %} diff --git a/data/templates/ntp/override.conf.j2 b/data/templates/chrony/override.conf.j2 index 6fed9d7d2..9eaea7608 100644 --- a/data/templates/ntp/override.conf.j2 +++ b/data/templates/chrony/override.conf.j2 @@ -5,10 +5,13 @@ ConditionPathExists={{ config_file }} After=vyos-router.service [Service] +EnvironmentFile= ExecStart= -ExecStart={{ vrf_command }}/usr/sbin/ntpd -g -p {{ config_file | replace('.conf', '.pid') }} -c {{ config_file }} -u ntp:ntp +ExecStart={{ vrf_command }}/usr/sbin/chronyd -F 1 -f {{ config_file }} PIDFile= PIDFile={{ config_file | replace('.conf', '.pid') }} Restart=always RestartSec=10 +# Required for VRF support +ProtectControlGroups=No diff --git a/data/templates/container/storage.conf.j2 b/data/templates/container/storage.conf.j2 index 665f9bf95..39a072c70 100644 --- a/data/templates/container/storage.conf.j2 +++ b/data/templates/container/storage.conf.j2 @@ -1,4 +1,4 @@ ### Autogenerated by container.py ### [storage] - driver = "vfs" + driver = "overlay2" graphroot = "/usr/lib/live/mount/persistence/container/storage" diff --git a/data/templates/frr/ospfd.frr.j2 b/data/templates/frr/ospfd.frr.j2 index 882ec8f97..8c4a81c57 100644 --- a/data/templates/frr/ospfd.frr.j2 +++ b/data/templates/frr/ospfd.frr.j2 @@ -84,11 +84,13 @@ router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% endfor %} {% if area_config.range is vyos_defined %} {% for range, range_config in area_config.range.items() %} -{% if range_config.cost is vyos_defined %} - area {{ area_id }} range {{ range }} cost {{ range_config.cost }} -{% endif %} {% if range_config.not_advertise is vyos_defined %} area {{ area_id }} range {{ range }} not-advertise +{% else %} + area {{ area_id }} range {{ range }} +{% endif %} +{% if range_config.cost is vyos_defined %} + area {{ area_id }} range {{ range }} cost {{ range_config.cost }} {% endif %} {% if range_config.substitute is vyos_defined %} area {{ area_id }} range {{ range }} substitute {{ range_config.substitute }} @@ -170,7 +172,7 @@ router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% if parameters.router_id is vyos_defined %} ospf router-id {{ parameters.router_id }} {% endif %} -{% if passive_interface.default is vyos_defined %} +{% if passive_interface is vyos_defined('default') %} passive-interface default {% endif %} {% if redistribute is vyos_defined %} diff --git a/data/templates/high-availability/keepalived.conf.j2 b/data/templates/high-availability/keepalived.conf.j2 index 706e1c5ae..ebff52e1f 100644 --- a/data/templates/high-availability/keepalived.conf.j2 +++ b/data/templates/high-availability/keepalived.conf.j2 @@ -126,7 +126,12 @@ vrrp_sync_group {{ name }} { {% if virtual_server is vyos_defined %} # Virtual-server configuration {% for vserver, vserver_config in virtual_server.items() %} +# Vserver {{ vserver }} +{% if vserver_config.port is vyos_defined %} virtual_server {{ vserver }} {{ vserver_config.port }} { +{% else %} +virtual_server fwmark {{ vserver_config.fwmark }} { +{% endif %} delay_loop {{ vserver_config.delay_loop }} {% if vserver_config.algorithm is vyos_defined('round-robin') %} lb_algo rr @@ -156,9 +161,14 @@ virtual_server {{ vserver }} {{ vserver_config.port }} { {% for rserver, rserver_config in vserver_config.real_server.items() %} real_server {{ rserver }} {{ rserver_config.port }} { weight 1 +{% if rserver_config.health_check.script is vyos_defined %} + MISC_CHECK { + misc_path {{ rserver_config.health_check.script }} +{% else %} {{ vserver_config.protocol | upper }}_CHECK { -{% if rserver_config.connection_timeout is vyos_defined %} +{% if rserver_config.connection_timeout is vyos_defined %} connect_timeout {{ rserver_config.connection_timeout }} +{% endif %} {% endif %} } } diff --git a/data/templates/https/nginx.default.j2 b/data/templates/https/nginx.default.j2 index dbb08e187..753c3a5c9 100644 --- a/data/templates/https/nginx.default.j2 +++ b/data/templates/https/nginx.default.j2 @@ -34,7 +34,7 @@ server { ssl_protocols TLSv1.2 TLSv1.3; # proxy settings for HTTP API, if enabled; 503, if not - location ~ /(retrieve|configure|config-file|image|generate|show|reset|docs|openapi.json|redoc|graphql) { + location ~ /(retrieve|configure|config-file|image|container-image|generate|show|reset|docs|openapi.json|redoc|graphql) { {% if server.api %} {% if server.api.socket %} proxy_pass http://unix:/run/api.sock; diff --git a/data/templates/iproute2/static.conf.j2 b/data/templates/iproute2/static.conf.j2 new file mode 100644 index 000000000..10c9bdab7 --- /dev/null +++ b/data/templates/iproute2/static.conf.j2 @@ -0,0 +1,8 @@ +# Generated by VyOS (protocols_static.py), do not edit by hand +{% if table is vyos_defined %} +{% for t, t_options in table.items() %} +{% if t_options.description is vyos_defined %} +{{ "%-6s" | format(t) }} {{ "%-40s" | format(t_options.description) }} +{% endif %} +{% endfor %} +{% endif %} diff --git a/data/templates/vrf/vrf.conf.j2 b/data/templates/iproute2/vrf.conf.j2 index d31d23574..d31d23574 100644 --- a/data/templates/vrf/vrf.conf.j2 +++ b/data/templates/iproute2/vrf.conf.j2 diff --git a/data/templates/ipsec/swanctl/peer.j2 b/data/templates/ipsec/swanctl/peer.j2 index 837fa263c..9d95271fe 100644 --- a/data/templates/ipsec/swanctl/peer.j2 +++ b/data/templates/ipsec/swanctl/peer.j2 @@ -45,11 +45,7 @@ {% endif %} } remote { -{% if peer_conf.authentication.remote_id is vyos_defined %} id = "{{ peer_conf.authentication.remote_id }}" -{% else %} - id = "{{ peer }}" -{% endif %} auth = {{ 'psk' if peer_conf.authentication.mode == 'pre-shared-secret' else 'pubkey' }} {% if peer_conf.authentication.mode == 'rsa' %} pubkeys = {{ peer_conf.authentication.rsa.remote_key }}.pem diff --git a/data/templates/ntp/ntpd.conf.j2 b/data/templates/ntp/ntpd.conf.j2 deleted file mode 100644 index 8921826fa..000000000 --- a/data/templates/ntp/ntpd.conf.j2 +++ /dev/null @@ -1,49 +0,0 @@ -### Autogenerated by ntp.py ### - -# -# Non-configurable defaults -# -driftfile /var/lib/ntp/ntp.drift -# By default, only allow ntpd to query time sources, ignore any incoming requests -restrict default noquery nopeer notrap nomodify -# Allow pool associations -restrict source nomodify notrap noquery -# Local users have unrestricted access, allowing reconfiguration via ntpdc -restrict 127.0.0.1 -restrict -6 ::1 - -# -# Configurable section -# -{% if server is vyos_defined %} -{% for server, config in server.items() %} -{% set association = 'server' %} -{% if config.pool is vyos_defined %} -{% set association = 'pool' %} -{% endif %} -{{ association }} {{ server | replace('_', '-') }} iburst {{ 'noselect' if config.noselect is vyos_defined }} {{ 'preempt' if config.preempt is vyos_defined }} {{ 'prefer' if config.prefer is vyos_defined }} -{% endfor %} -{% endif %} - -{% if allow_clients.address is vyos_defined %} -# Allowed clients configuration -restrict default ignore -{% for address in allow_clients.address %} -restrict {{ address | address_from_cidr }} mask {{ address | netmask_from_cidr }} nomodify notrap nopeer -{% endfor %} -{% endif %} - -{% if listen_address is vyos_defined or interface is vyos_defined %} -# NTP should listen on configured addresses only -interface ignore wildcard -{% if listen_address is vyos_defined %} -{% for address in listen_address %} -interface listen {{ address }} -{% endfor %} -{% endif %} -{% if interface is vyos_defined %} -{% for ifname in interface %} -interface listen {{ ifname }} -{% endfor %} -{% endif %} -{% endif %} diff --git a/data/templates/snmp/etc.snmpd.conf.j2 b/data/templates/snmp/etc.snmpd.conf.j2 index 155ee2822..793facc3f 100644 --- a/data/templates/snmp/etc.snmpd.conf.j2 +++ b/data/templates/snmp/etc.snmpd.conf.j2 @@ -26,6 +26,9 @@ monitor -r 10 -e linkDownTrap "Generate linkDown" ifOperStatus == 2 # interface (with different ifIndex) - this is the case on e.g. ppp interfaces interface_replace_old yes +# T4902: exclude container storage from monitoring +ignoreDisk /usr/lib/live/mount/persistence/container + ######################## # configurable section # ######################## diff --git a/data/templates/system/ssh_config.j2 b/data/templates/system/ssh_config.j2 index 1449f95b1..d3ede0971 100644 --- a/data/templates/system/ssh_config.j2 +++ b/data/templates/system/ssh_config.j2 @@ -1,3 +1,6 @@ {% if ssh_client.source_address is vyos_defined %} BindAddress {{ ssh_client.source_address }} {% endif %} +{% if ssh_client.source_interface is vyos_defined %} +BindInterface {{ ssh_client.source_interface }} +{% endif %} diff --git a/data/templates/telegraf/telegraf.j2 b/data/templates/telegraf/telegraf.j2 index 36571ce98..c9f402281 100644 --- a/data/templates/telegraf/telegraf.j2 +++ b/data/templates/telegraf/telegraf.j2 @@ -102,7 +102,7 @@ dirs = ["/proc/sys/net/ipv4/netfilter","/proc/sys/net/netfilter"] [[inputs.ethtool]] interface_include = {{ interfaces_ethernet }} -[[inputs.ntpq]] +[[inputs.chrony]] dns_lookup = true [[inputs.internal]] [[inputs.nstat]] diff --git a/debian/control b/debian/control index 7e69003ff..1e593d378 100644 --- a/debian/control +++ b/debian/control @@ -68,7 +68,7 @@ Depends: ipaddrcheck, iperf, iperf3, - iproute2, + iproute2 (>= 6.0.0), iputils-arping, isc-dhcp-client, isc-dhcp-relay, @@ -101,8 +101,7 @@ Depends: nfct, nftables (>= 0.9.3), nginx-light, - ntp, - ntpdate, + chrony, nvme-cli, ocserv, opennhrp, diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install index edd090993..a54b3b506 100644 --- a/debian/vyos-1x.install +++ b/debian/vyos-1x.install @@ -3,6 +3,7 @@ etc/ipsec.d etc/logrotate.d etc/netplug etc/opennhrp +etc/modprobe.d etc/ppp etc/rsyslog.d etc/securetty diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in index d50039665..4bac305d1 100644 --- a/interface-definitions/container.xml.in +++ b/interface-definitions/container.xml.in @@ -207,14 +207,23 @@ </leafNode> <leafNode name="protocol"> <properties> - <help>Protocol tcp/udp</help> + <help>Transport protocol used for port mapping</help> <completionHelp> <list>tcp udp</list> </completionHelp> + <valueHelp> + <format>tcp</format> + <description>Use Transmission Control Protocol for given port</description> + </valueHelp> + <valueHelp> + <format>udp</format> + <description>Use User Datagram Protocol for given port</description> + </valueHelp> <constraint> <regex>(tcp|udp)</regex> </constraint> </properties> + <defaultValue>tcp</defaultValue> </leafNode> </children> </tagNode> diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in index 784e51151..d67a142d1 100644 --- a/interface-definitions/high-availability.xml.in +++ b/interface-definitions/high-availability.xml.in @@ -365,7 +365,8 @@ </properties> <defaultValue>nat</defaultValue> </leafNode> - #include <include/port-number.xml.i> + #include <include/firewall/fwmark.xml.i> + #include <include/port-number-start-zero.xml.i> <leafNode name="persistence-timeout"> <properties> <help>Timeout for persistent connections</help> @@ -404,7 +405,7 @@ <help>Real server address</help> </properties> <children> - #include <include/port-number.xml.i> + #include <include/port-number-start-zero.xml.i> <leafNode name="connection-timeout"> <properties> <help>Server connection timeout</help> @@ -417,6 +418,21 @@ </constraint> </properties> </leafNode> + <node name="health-check"> + <properties> + <help>Health check script</help> + </properties> + <children> + <leafNode name="script"> + <properties> + <help>Health check script file</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + </children> + </node> </children> </tagNode> </children> diff --git a/interface-definitions/include/accel-ppp/ppp-options-ipv6-interface-id.xml.i b/interface-definitions/include/accel-ppp/ppp-options-ipv6-interface-id.xml.i new file mode 100644 index 000000000..265f7f97c --- /dev/null +++ b/interface-definitions/include/accel-ppp/ppp-options-ipv6-interface-id.xml.i @@ -0,0 +1,54 @@ +<!-- include start from accel-ppp/ppp-options-ipv6-interface-id.xml.i --> +<leafNode name="ipv6-intf-id"> + <properties> + <help>Fixed or random interface identifier for IPv6</help> + <completionHelp> + <list>random</list> + </completionHelp> + <valueHelp> + <format>random</format> + <description>Random interface identifier for IPv6</description> + </valueHelp> + <valueHelp> + <format>x:x:x:x</format> + <description>specify interface identifier for IPv6</description> + </valueHelp> + <constraint> + <regex>(random|((\d+){1,4}:){3}(\d+){1,4})</regex> + </constraint> + </properties> +</leafNode> +<leafNode name="ipv6-peer-intf-id"> + <properties> + <help>Peer interface identifier for IPv6</help> + <completionHelp> + <list>random calling-sid ipv4</list> + </completionHelp> + <valueHelp> + <format>x:x:x:x</format> + <description>Interface identifier for IPv6</description> + </valueHelp> + <valueHelp> + <format>random</format> + <description>Use a random interface identifier for IPv6</description> + </valueHelp> + <valueHelp> + <format>ipv4</format> + <description>Calculate interface identifier from IPv4 address, for example 192:168:0:1</description> + </valueHelp> + <valueHelp> + <format>calling-sid</format> + <description>Calculate interface identifier from calling-station-id</description> + </valueHelp> + <constraint> + <regex>(random|calling-sid|ipv4|((\d+){1,4}:){3}(\d+){1,4})</regex> + </constraint> + </properties> +</leafNode> +<leafNode name="ipv6-accept-peer-intf-id"> + <properties> + <help>Accept peer interface identifier</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i index 75acefd96..3fe3ca872 100644 --- a/interface-definitions/include/firewall/common-rule.xml.i +++ b/interface-definitions/include/firewall/common-rule.xml.i @@ -1,6 +1,14 @@ <!-- include start from firewall/common-rule.xml.i --> #include <include/firewall/action.xml.i> #include <include/generic-description.xml.i> +<node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/mac-address.xml.i> + </children> +</node> <leafNode name="disable"> <properties> <help>Option to disable firewall rule</help> diff --git a/interface-definitions/include/firewall/fwmark.xml.i b/interface-definitions/include/firewall/fwmark.xml.i new file mode 100644 index 000000000..4607ef58f --- /dev/null +++ b/interface-definitions/include/firewall/fwmark.xml.i @@ -0,0 +1,14 @@ +<!-- include start from firewall/fwmark.xml.i --> +<leafNode name="fwmark"> + <properties> + <help>Match fwmark value</help> + <valueHelp> + <format>u32:1-2147483647</format> + <description>Match firewall mark value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2147483647"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/rule-log-level.xml.i b/interface-definitions/include/firewall/rule-log-level.xml.i index 10c8de5e3..3ac473844 100644 --- a/interface-definitions/include/firewall/rule-log-level.xml.i +++ b/interface-definitions/include/firewall/rule-log-level.xml.i @@ -1,4 +1,4 @@ -<!-- include start from firewall/common-rule.xml.i --> +<!-- include start from firewall/rule-log-level.xml.i --> <leafNode name="log-level"> <properties> <help>Set log-level. Log must be enable.</help> diff --git a/interface-definitions/include/generic-description.xml.i b/interface-definitions/include/generic-description.xml.i index 03fc564e6..b030c2495 100644 --- a/interface-definitions/include/generic-description.xml.i +++ b/interface-definitions/include/generic-description.xml.i @@ -6,6 +6,10 @@ <format>txt</format> <description>Description</description> </valueHelp> + <constraint> + <regex>[[:ascii:]]{1,256}</regex> + </constraint> + <constraintErrorMessage>Description too long (limit 256 characters)</constraintErrorMessage> </properties> </leafNode> <!-- include end --> diff --git a/interface-definitions/include/interface/description.xml.i b/interface-definitions/include/interface/description.xml.i deleted file mode 100644 index de01d22ca..000000000 --- a/interface-definitions/include/interface/description.xml.i +++ /dev/null @@ -1,11 +0,0 @@ -<!-- include start from interface/description.xml.i --> -<leafNode name="description"> - <properties> - <help>Interface specific description</help> - <constraint> - <regex>.{1,256}</regex> - </constraint> - <constraintErrorMessage>Description too long (limit 256 characters)</constraintErrorMessage> - </properties> -</leafNode> -<!-- include end --> diff --git a/interface-definitions/include/interface/mirror.xml.i b/interface-definitions/include/interface/mirror.xml.i index 2959551f0..74a172b50 100644 --- a/interface-definitions/include/interface/mirror.xml.i +++ b/interface-definitions/include/interface/mirror.xml.i @@ -1,23 +1,31 @@ <!-- include start from interface/mirror.xml.i --> <node name="mirror"> <properties> - <help>Incoming/outgoing packet mirroring destination</help> + <help>Mirror ingress/egress packets</help> </properties> <children> <leafNode name="ingress"> <properties> - <help>Mirror the ingress traffic of the interface to the destination interface</help> + <help>Mirror ingress traffic to destination interface</help> <completionHelp> - <script>${vyos_completion_dir}/list_interfaces.py</script> + <script>${vyos_completion_dir}/list_interfaces.py</script> </completionHelp> + <valueHelp> + <format>txt</format> + <description>Destination interface name</description> + </valueHelp> </properties> </leafNode> <leafNode name="egress"> <properties> - <help>Mirror the egress traffic of the interface to the destination interface</help> + <help>Mirror egress traffic to destination interface</help> <completionHelp> - <script>${vyos_completion_dir}/list_interfaces.py</script> + <script>${vyos_completion_dir}/list_interfaces.py</script> </completionHelp> + <valueHelp> + <format>txt</format> + <description>Destination interface name</description> + </valueHelp> </properties> </leafNode> </children> diff --git a/interface-definitions/include/interface/redirect.xml.i b/interface-definitions/include/interface/redirect.xml.i index 8df8957ac..b01e486ce 100644 --- a/interface-definitions/include/interface/redirect.xml.i +++ b/interface-definitions/include/interface/redirect.xml.i @@ -1,13 +1,13 @@ <!-- include start from interface/redirect.xml.i --> <leafNode name="redirect"> <properties> - <help>Incoming packet redirection destination</help> + <help>Redirect incoming packet to destination</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces.py</script> </completionHelp> <valueHelp> <format>txt</format> - <description>Interface name</description> + <description>Destination interface name</description> </valueHelp> <constraint> #include <include/constraint/interface-name.xml.in> diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i index 6d50d7238..fdd62b63d 100644 --- a/interface-definitions/include/interface/vif-s.xml.i +++ b/interface-definitions/include/interface/vif-s.xml.i @@ -12,8 +12,8 @@ <constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage> </properties> <children> + #include <include/generic-description.xml.i> #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> - #include <include/interface/description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> #include <include/interface/disable-link-detect.xml.i> @@ -53,8 +53,8 @@ <constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage> </properties> <children> + #include <include/generic-description.xml.i> #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> - #include <include/interface/description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> #include <include/interface/disable-link-detect.xml.i> diff --git a/interface-definitions/include/interface/vif.xml.i b/interface-definitions/include/interface/vif.xml.i index 3f8f113ea..ec3921bf6 100644 --- a/interface-definitions/include/interface/vif.xml.i +++ b/interface-definitions/include/interface/vif.xml.i @@ -12,8 +12,8 @@ <constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage> </properties> <children> + #include <include/generic-description.xml.i> #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> - #include <include/interface/description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> #include <include/interface/disable-link-detect.xml.i> diff --git a/interface-definitions/include/listen-address-ipv4-single.xml.i b/interface-definitions/include/listen-address-ipv4-single.xml.i new file mode 100644 index 000000000..81e947953 --- /dev/null +++ b/interface-definitions/include/listen-address-ipv4-single.xml.i @@ -0,0 +1,17 @@ +<!-- include start from listen-address-ipv4-single.xml.i --> +<leafNode name="listen-address"> + <properties> + <help>Local IPv4 addresses to listen on</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to listen for incoming connections</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/port-number-start-zero.xml.i b/interface-definitions/include/port-number-start-zero.xml.i new file mode 100644 index 000000000..04a144216 --- /dev/null +++ b/interface-definitions/include/port-number-start-zero.xml.i @@ -0,0 +1,15 @@ +<!-- include start from port-number-start-zero.xml.i --> +<leafNode name="port"> + <properties> + <help>Port number used by connection</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + <constraintErrorMessage>Port number must be in range 0 to 65535</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/bandwidth-auto.xml.i b/interface-definitions/include/qos/bandwidth-auto.xml.i new file mode 100644 index 000000000..a86f28296 --- /dev/null +++ b/interface-definitions/include/qos/bandwidth-auto.xml.i @@ -0,0 +1,47 @@ +<!-- include start from qos/bandwidth-auto.xml.i --> +<leafNode name="bandwidth"> + <properties> + <help>Available bandwidth for this policy</help> + <completionHelp> + <list>auto</list> + </completionHelp> + <valueHelp> + <format>auto</format> + <description>Bandwidth matches interface speed</description> + </valueHelp> + <valueHelp> + <format><number></format> + <description>Bits per second</description> + </valueHelp> + <valueHelp> + <format><number>bit</format> + <description>Bits per second</description> + </valueHelp> + <valueHelp> + <format><number>kbit</format> + <description>Kilobits per second</description> + </valueHelp> + <valueHelp> + <format><number>mbit</format> + <description>Megabits per second</description> + </valueHelp> + <valueHelp> + <format><number>gbit</format> + <description>Gigabits per second</description> + </valueHelp> + <valueHelp> + <format><number>tbit</format> + <description>Terabits per second</description> + </valueHelp> + <valueHelp> + <format><number>%%</format> + <description>Percentage of interface link speed</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--positive"/> + <regex>(auto|\d+(bit|kbit|mbit|gbit|tbit)|(100|\d(\d)?)%)</regex> + </constraint> + </properties> + <defaultValue>auto</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/bandwidth.xml.i b/interface-definitions/include/qos/bandwidth.xml.i index 82af22f42..f2848f066 100644 --- a/interface-definitions/include/qos/bandwidth.xml.i +++ b/interface-definitions/include/qos/bandwidth.xml.i @@ -1,15 +1,39 @@ <!-- include start from qos/bandwidth.xml.i --> <leafNode name="bandwidth"> <properties> - <help>Traffic-limit used for this class</help> + <help>Available bandwidth for this policy</help> <valueHelp> <format><number></format> - <description>Rate in kbit (kilobit per second)</description> + <description>Bits per second</description> </valueHelp> <valueHelp> - <format><number><suffix></format> - <description>Rate with scaling suffix (mbit, mbps, ...)</description> + <format><number>bit</format> + <description>Bits per second</description> </valueHelp> + <valueHelp> + <format><number>kbit</format> + <description>Kilobits per second</description> + </valueHelp> + <valueHelp> + <format><number>mbit</format> + <description>Megabits per second</description> + </valueHelp> + <valueHelp> + <format><number>gbit</format> + <description>Gigabits per second</description> + </valueHelp> + <valueHelp> + <format><number>tbit</format> + <description>Terabits per second</description> + </valueHelp> + <valueHelp> + <format><number>%</format> + <description>Percentage of interface link speed</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--positive"/> + <regex>(\d+(bit|kbit|mbit|gbit|tbit)|(100|\d(\d)?)%)</regex> + </constraint> </properties> </leafNode> <!-- include end --> diff --git a/interface-definitions/include/qos/class-match-ipv4-address.xml.i b/interface-definitions/include/qos/class-match-ipv4-address.xml.i new file mode 100644 index 000000000..8e84c988a --- /dev/null +++ b/interface-definitions/include/qos/class-match-ipv4-address.xml.i @@ -0,0 +1,19 @@ +<!-- include start from qos/class-match-ipv4-address.xml.i --> +<leafNode name="address"> + <properties> + <help>IPv4 destination address for this match</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv4-prefix"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/class-match-ipv6-address.xml.i b/interface-definitions/include/qos/class-match-ipv6-address.xml.i new file mode 100644 index 000000000..fd7388127 --- /dev/null +++ b/interface-definitions/include/qos/class-match-ipv6-address.xml.i @@ -0,0 +1,14 @@ +<!-- include start from qos/class-match-ipv6-address.xml.i --> +<leafNode name="address"> + <properties> + <help>IPv6 destination address for this match</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv6"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/match.xml.i b/interface-definitions/include/qos/class-match.xml.i index 7d89e4460..d9c35731d 100644 --- a/interface-definitions/include/qos/match.xml.i +++ b/interface-definitions/include/qos/class-match.xml.i @@ -1,4 +1,4 @@ -<!-- include start from qos/match.xml.i --> +<!-- include start from qos/class-match.xml.i --> <tagNode name="match"> <properties> <help>Class matching rule name</help> @@ -99,22 +99,11 @@ <help>Match on destination port or address</help> </properties> <children> - <leafNode name="address"> - <properties> - <help>IPv4 destination address for this match</help> - <valueHelp> - <format>ipv4net</format> - <description>IPv4 address and prefix length</description> - </valueHelp> - <constraint> - <validator name="ipv4"/> - </constraint> - </properties> - </leafNode> + #include <include/qos/class-match-ipv4-address.xml.i> #include <include/port-number.xml.i> </children> </node> - #include <include/qos/dscp.xml.i> + #include <include/qos/match-dscp.xml.i> #include <include/qos/max-length.xml.i> #include <include/ip-protocol.xml.i> <node name="source"> @@ -122,18 +111,7 @@ <help>Match on source port or address</help> </properties> <children> - <leafNode name="address"> - <properties> - <help>IPv4 source address for this match</help> - <valueHelp> - <format>ipv4net</format> - <description>IPv4 address and prefix length</description> - </valueHelp> - <constraint> - <validator name="ipv4"/> - </constraint> - </properties> - </leafNode> + #include <include/qos/class-match-ipv4-address.xml.i> #include <include/port-number.xml.i> </children> </node> @@ -150,22 +128,11 @@ <help>Match on destination port or address</help> </properties> <children> - <leafNode name="address"> - <properties> - <help>IPv6 destination address for this match</help> - <valueHelp> - <format>ipv6net</format> - <description>IPv6 address and prefix length</description> - </valueHelp> - <constraint> - <validator name="ipv6"/> - </constraint> - </properties> - </leafNode> + #include <include/qos/class-match-ipv6-address.xml.i> #include <include/port-number.xml.i> </children> </node> - #include <include/qos/dscp.xml.i> + #include <include/qos/match-dscp.xml.i> #include <include/qos/max-length.xml.i> #include <include/ip-protocol.xml.i> <node name="source"> @@ -173,18 +140,7 @@ <help>Match on source port or address</help> </properties> <children> - <leafNode name="address"> - <properties> - <help>IPv6 source address for this match</help> - <valueHelp> - <format>ipv6net</format> - <description>IPv6 address and prefix length</description> - </valueHelp> - <constraint> - <validator name="ipv6"/> - </constraint> - </properties> - </leafNode> + #include <include/qos/class-match-ipv6-address.xml.i> #include <include/port-number.xml.i> </children> </node> diff --git a/interface-definitions/include/qos/limiter-actions.xml.i b/interface-definitions/include/qos/class-police-exceed.xml.i index a993423aa..ee2ce16a8 100644 --- a/interface-definitions/include/qos/limiter-actions.xml.i +++ b/interface-definitions/include/qos/class-police-exceed.xml.i @@ -1,13 +1,13 @@ -<!-- include start from qos/limiter-actions.xml.i --> -<leafNode name="exceed-action"> +<!-- include start from qos/police.xml.i --> +<leafNode name="exceed"> <properties> - <help>Default action for packets exceeding the limiter (default: drop)</help> + <help>Default action for packets exceeding the limiter</help> <completionHelp> <list>continue drop ok reclassify pipe</list> </completionHelp> <valueHelp> <format>continue</format> - <description>Don't do anything, just continue with the next action in line</description> + <description>Do not do anything, just continue with the next action in line</description> </valueHelp> <valueHelp> <format>drop</format> @@ -31,15 +31,15 @@ </properties> <defaultValue>drop</defaultValue> </leafNode> -<leafNode name="notexceed-action"> +<leafNode name="not-exceed"> <properties> - <help>Default action for packets not exceeding the limiter (default: ok)</help> + <help>Default action for packets not exceeding the limiter</help> <completionHelp> <list>continue drop ok reclassify pipe</list> </completionHelp> <valueHelp> <format>continue</format> - <description>Don't do anything, just continue with the next action in line</description> + <description>Do not do anything, just continue with the next action in line</description> </valueHelp> <valueHelp> <format>drop</format> diff --git a/interface-definitions/include/qos/class-priority.xml.i b/interface-definitions/include/qos/class-priority.xml.i new file mode 100644 index 000000000..3fd848c93 --- /dev/null +++ b/interface-definitions/include/qos/class-priority.xml.i @@ -0,0 +1,15 @@ +<!-- include start from qos/class-priority.xml.i --> +<leafNode name="priority"> + <properties> + <help>Priority for rule evaluation</help> + <valueHelp> + <format>u32:0-20</format> + <description>Priority for match rule evaluation</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-20"/> + </constraint> + <constraintErrorMessage>Priority must be between 0 and 20</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/dscp.xml.i b/interface-definitions/include/qos/match-dscp.xml.i index bb90850ac..2d2fd0a57 100644 --- a/interface-definitions/include/qos/dscp.xml.i +++ b/interface-definitions/include/qos/match-dscp.xml.i @@ -1,4 +1,4 @@ -<!-- include start from qos/dscp.xml.i --> +<!-- include start from qos/match-dscp.xml.i --> <leafNode name="dscp"> <properties> <help>Match on Differentiated Services Codepoint (DSCP)</help> @@ -137,7 +137,6 @@ <validator name="numeric" argument="--range 0-63"/> <regex>(default|reliability|throughput|lowdelay|priority|immediate|flash|flash-override|critical|internet|network|AF11|AF12|AF13|AF21|AF22|AF23|AF31|AF32|AF33|AF41|AF42|AF43|CS1|CS2|CS3|CS4|CS5|CS6|CS7|EF)</regex> </constraint> - <constraintErrorMessage>Priority must be between 0 and 63</constraintErrorMessage> </properties> </leafNode> <!-- include end --> diff --git a/interface-definitions/include/qos/max-length.xml.i b/interface-definitions/include/qos/max-length.xml.i index 4cc20f8c4..64cdd02ec 100644 --- a/interface-definitions/include/qos/max-length.xml.i +++ b/interface-definitions/include/qos/max-length.xml.i @@ -1,15 +1,15 @@ <!-- include start from qos/max-length.xml.i --> <leafNode name="max-length"> <properties> - <help>Maximum packet length (ipv4)</help> + <help>Maximum packet length</help> <valueHelp> - <format>u32:0-65535</format> + <format>u32:1-65535</format> <description>Maximum packet/payload length</description> </valueHelp> <constraint> - <validator name="numeric" argument="--range 0-65535"/> + <validator name="numeric" argument="--range 1-65535"/> </constraint> - <constraintErrorMessage>Maximum IPv4 total packet length is 65535</constraintErrorMessage> + <constraintErrorMessage>Maximum packet length is 65535</constraintErrorMessage> </properties> </leafNode> <!-- include end --> diff --git a/interface-definitions/include/qos/queue-type.xml.i b/interface-definitions/include/qos/queue-type.xml.i index 634f61024..c7d4cde82 100644 --- a/interface-definitions/include/qos/queue-type.xml.i +++ b/interface-definitions/include/qos/queue-type.xml.i @@ -3,28 +3,31 @@ <properties> <help>Queue type for default traffic</help> <completionHelp> - <list>fq-codel fair-queue drop-tail random-detect</list> + <list>drop-tail fair-queue fq-codel priority random-detect</list> </completionHelp> <valueHelp> - <format>fq-codel</format> - <description>Fair Queue Codel</description> + <format>drop-tail</format> + <description>First-In-First-Out (FIFO)</description> </valueHelp> <valueHelp> <format>fair-queue</format> <description>Stochastic Fair Queue (SFQ)</description> </valueHelp> <valueHelp> - <format>drop-tail</format> - <description>First-In-First-Out (FIFO)</description> + <format>fq-codel</format> + <description>Fair Queue Codel</description> + </valueHelp> + <valueHelp> + <format>priority</format> + <description>Priority queuing</description> </valueHelp> <valueHelp> <format>random-detect</format> <description>Random Early Detection (RED)</description> </valueHelp> <constraint> - <regex>(fq-codel|fair-queue|drop-tail|random-detect)</regex> + <regex>(drop-tail|fair-queue|fq-codel|priority|random-detect)</regex> </constraint> </properties> - <defaultValue>drop-tail</defaultValue> </leafNode> <!-- include end --> diff --git a/interface-definitions/include/qos/set-dscp.xml.i b/interface-definitions/include/qos/set-dscp.xml.i index 55c0ea44d..07f33783f 100644 --- a/interface-definitions/include/qos/set-dscp.xml.i +++ b/interface-definitions/include/qos/set-dscp.xml.i @@ -3,7 +3,7 @@ <properties> <help>Change the Differentiated Services (DiffServ) field in the IP header</help> <completionHelp> - <list>default reliability throughput lowdelay priority immediate flash flash-override critical internet network</list> + <list>default reliability throughput lowdelay priority immediate flash flash-override critical internet network AF11 AF12 AF13 AF21 AF22 AF23 AF31 AF32 AF33 AF41 AF42 AF43 CS1 CS2 CS3 CS4 CS5 CS6 CS7 EF</list> </completionHelp> <valueHelp> <format>u32:0-63</format> @@ -53,9 +53,89 @@ <format>network</format> <description>match DSCP (111000)</description> </valueHelp> + <valueHelp> + <format>AF11</format> + <description>High-throughput data</description> + </valueHelp> + <valueHelp> + <format>AF12</format> + <description>High-throughput data</description> + </valueHelp> + <valueHelp> + <format>AF13</format> + <description>High-throughput data</description> + </valueHelp> + <valueHelp> + <format>AF21</format> + <description>Low-latency data</description> + </valueHelp> + <valueHelp> + <format>AF22</format> + <description>Low-latency data</description> + </valueHelp> + <valueHelp> + <format>AF23</format> + <description>Low-latency data</description> + </valueHelp> + <valueHelp> + <format>AF31</format> + <description>Multimedia streaming</description> + </valueHelp> + <valueHelp> + <format>AF32</format> + <description>Multimedia streaming</description> + </valueHelp> + <valueHelp> + <format>AF33</format> + <description>Multimedia streaming</description> + </valueHelp> + <valueHelp> + <format>AF41</format> + <description>Multimedia conferencing</description> + </valueHelp> + <valueHelp> + <format>AF42</format> + <description>Multimedia conferencing</description> + </valueHelp> + <valueHelp> + <format>AF43</format> + <description>Multimedia conferencing</description> + </valueHelp> + <valueHelp> + <format>CS1</format> + <description>Low-priority data</description> + </valueHelp> + <valueHelp> + <format>CS2</format> + <description>OAM</description> + </valueHelp> + <valueHelp> + <format>CS3</format> + <description>Broadcast video</description> + </valueHelp> + <valueHelp> + <format>CS4</format> + <description>Real-time interactive</description> + </valueHelp> + <valueHelp> + <format>CS5</format> + <description>Signaling</description> + </valueHelp> + <valueHelp> + <format>CS6</format> + <description>Network control</description> + </valueHelp> + <valueHelp> + <format>CS7</format> + <description></description> + </valueHelp> + <valueHelp> + <format>EF</format> + <description>Expedited Forwarding</description> + </valueHelp> <constraint> <validator name="numeric" argument="--range 0-63"/> - <regex>(default|reliability|throughput|lowdelay|priority|immediate|flash|flash-override|critical|internet|network)</regex> + <regex>(default|reliability|throughput|lowdelay|priority|immediate|flash|flash-override|critical|internet|network|AF11|AF12|AF13|AF21|AF22|AF23|AF31|AF32|AF33|AF41|AF42|AF43|CS1|CS2|CS3|CS4|CS5|CS6|CS7|EF)</regex> </constraint> <constraintErrorMessage>Priority must be between 0 and 63</constraintErrorMessage> </properties> diff --git a/interface-definitions/include/version/container-version.xml.i b/interface-definitions/include/version/container-version.xml.i new file mode 100644 index 000000000..129469cec --- /dev/null +++ b/interface-definitions/include/version/container-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/container-version.xml.i --> +<syntaxVersion component='container' version='1'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/ntp-version.xml.i b/interface-definitions/include/version/ntp-version.xml.i index cc4ff9a1c..9eafbf7f0 100644 --- a/interface-definitions/include/version/ntp-version.xml.i +++ b/interface-definitions/include/version/ntp-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/ntp-version.xml.i --> -<syntaxVersion component='ntp' version='1'></syntaxVersion> +<syntaxVersion component='ntp' version='2'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/include/version/qos-version.xml.i b/interface-definitions/include/version/qos-version.xml.i index e4d139349..c67e61e91 100644 --- a/interface-definitions/include/version/qos-version.xml.i +++ b/interface-definitions/include/version/qos-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/qos-version.xml.i --> -<syntaxVersion component='qos' version='1'></syntaxVersion> +<syntaxVersion component='qos' version='2'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in index a8a558348..6e8c5283a 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -49,7 +49,7 @@ </leafNode> </children> </node> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> #include <include/interface/disable-link-detect.xml.i> diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index d52e213b6..1636411ec 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -34,7 +34,7 @@ </properties> <defaultValue>300</defaultValue> </leafNode> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> #include <include/interface/disable-link-detect.xml.i> diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in index eb525b547..00784fcdf 100644 --- a/interface-definitions/interfaces-dummy.xml.in +++ b/interface-definitions/interfaces-dummy.xml.in @@ -17,7 +17,7 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6.xml.i> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/disable.xml.i> <node name="ip"> <properties> @@ -25,8 +25,27 @@ </properties> <children> #include <include/interface/source-validation.xml.i> + #include <include/interface/disable-forwarding.xml.i> </children> </node> + <node name="ipv6"> + <properties> + <help>IPv6 routing parameters</help> + </properties> + <children> + #include <include/interface/disable-forwarding.xml.i> + <node name="address"> + <properties> + <help>IPv6 address configuration modes</help> + </properties> + <children> + #include <include/interface/ipv6-address-eui64.xml.i> + #include <include/interface/ipv6-address-no-default-link-local.xml.i> + </children> + </node> + </children> + </node> + #include <include/interface/mtu-68-16000.xml.i> #include <include/interface/mirror.xml.i> #include <include/interface/netns.xml.i> #include <include/interface/redirect.xml.i> diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index e9ae0acfe..e7c196c5c 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -20,7 +20,7 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> <leafNode name="disable-flow-control"> diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in index f8e9909f8..ac9794870 100644 --- a/interface-definitions/interfaces-geneve.xml.in +++ b/interface-definitions/interfaces-geneve.xml.in @@ -17,7 +17,7 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6.xml.i> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/disable.xml.i> #include <include/interface/ipv4-options.xml.i> #include <include/interface/ipv6-options.xml.i> diff --git a/interface-definitions/interfaces-input.xml.in b/interface-definitions/interfaces-input.xml.in index 97502d954..d90cf936f 100644 --- a/interface-definitions/interfaces-input.xml.in +++ b/interface-definitions/interfaces-input.xml.in @@ -17,7 +17,7 @@ </valueHelp> </properties> <children> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/disable.xml.i> #include <include/interface/redirect.xml.i> </children> diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in index 0ebc3253d..1f0dd3d19 100644 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ b/interface-definitions/interfaces-l2tpv3.xml.in @@ -17,7 +17,7 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6.xml.i> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> <leafNode name="destination-port"> <properties> <help>UDP destination port for L2TPv3 tunnel</help> diff --git a/interface-definitions/interfaces-loopback.xml.in b/interface-definitions/interfaces-loopback.xml.in index 7f59db543..fe0944467 100644 --- a/interface-definitions/interfaces-loopback.xml.in +++ b/interface-definitions/interfaces-loopback.xml.in @@ -17,7 +17,7 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6.xml.i> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> <node name="ip"> <properties> <help>IPv4 routing parameters</help> diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index 441236ec2..4b4f9149d 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -115,7 +115,7 @@ </leafNode> </children> </node> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/disable.xml.i> #include <include/interface/mtu-68-16000.xml.i> <leafNode name="mtu"> diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index 7cfb9ee7a..63272a25f 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -33,7 +33,7 @@ </leafNode> </children> </node> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> <leafNode name="device-type"> <properties> <help>OpenVPN interface device-type</help> diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index 35c4889ea..490f41471 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -22,7 +22,7 @@ #include <include/interface/no-default-route.xml.i> #include <include/interface/default-route-distance.xml.i> #include <include/interface/dhcpv6-options.xml.i> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/disable.xml.i> <leafNode name="idle-timeout"> <properties> diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in index 2fe07ffd5..5c73825c3 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -17,7 +17,7 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> #include <include/interface/disable-link-detect.xml.i> diff --git a/interface-definitions/interfaces-sstpc.xml.in b/interface-definitions/interfaces-sstpc.xml.in index 30b55a9fa..b569e9bde 100644 --- a/interface-definitions/interfaces-sstpc.xml.in +++ b/interface-definitions/interfaces-sstpc.xml.in @@ -16,7 +16,7 @@ </valueHelp> </properties> <children> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/disable.xml.i> #include <include/interface/authentication.xml.i> #include <include/interface/no-default-route.xml.i> diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index 333a5b178..17fe1e285 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -16,7 +16,7 @@ </valueHelp> </properties> <children> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/address-ipv4-ipv6.xml.i> #include <include/interface/disable.xml.i> #include <include/interface/disable-link-detect.xml.i> diff --git a/interface-definitions/interfaces-virtual-ethernet.xml.in b/interface-definitions/interfaces-virtual-ethernet.xml.in index 8059ec33b..864f658da 100644 --- a/interface-definitions/interfaces-virtual-ethernet.xml.in +++ b/interface-definitions/interfaces-virtual-ethernet.xml.in @@ -17,7 +17,7 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> #include <include/interface/disable.xml.i> diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in index 11f001dc0..b116f7386 100644 --- a/interface-definitions/interfaces-vti.xml.in +++ b/interface-definitions/interfaces-vti.xml.in @@ -17,7 +17,7 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6.xml.i> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/disable.xml.i> #include <include/interface/ipv4-options.xml.i> #include <include/interface/ipv6-options.xml.i> diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 331f930d3..fb60c93d0 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -17,7 +17,7 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6.xml.i> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/disable.xml.i> <leafNode name="external"> <properties> diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index 35e223588..6342b21cf 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -17,7 +17,7 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6.xml.i> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/disable.xml.i> #include <include/port-number.xml.i> #include <include/interface/mtu-68-16000.xml.i> diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index 5271df624..aff5071b2 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -467,7 +467,7 @@ <constraintErrorMessage>Invalid ISO/IEC 3166-1 Country Code</constraintErrorMessage> </properties> </leafNode> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> <leafNode name="disable-broadcast-ssid"> diff --git a/interface-definitions/interfaces-wwan.xml.in b/interface-definitions/interfaces-wwan.xml.in index 758784540..5fa3be8db 100644 --- a/interface-definitions/interfaces-wwan.xml.in +++ b/interface-definitions/interfaces-wwan.xml.in @@ -28,7 +28,7 @@ #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> #include <include/interface/authentication.xml.i> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/disable.xml.i> #include <include/interface/disable-link-detect.xml.i> #include <include/interface/mirror.xml.i> diff --git a/interface-definitions/netns.xml.in b/interface-definitions/netns.xml.in index 088985cb6..87880e96a 100644 --- a/interface-definitions/netns.xml.in +++ b/interface-definitions/netns.xml.in @@ -15,7 +15,7 @@ <constraintErrorMessage>Netns name must be alphanumeric and can contain hyphens and underscores.</constraintErrorMessage> </properties> <children> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/ntp.xml.in b/interface-definitions/ntp.xml.in index 85636a50f..65e40ee32 100644 --- a/interface-definitions/ntp.xml.in +++ b/interface-definitions/ntp.xml.in @@ -1,7 +1,7 @@ <?xml version="1.0"?> <!-- NTP configuration --> <interfaceDefinition> - <node name="system"> + <node name="service"> <children> <node name="ntp" owner="${vyos_conf_scripts_dir}/ntp.py"> <properties> @@ -43,12 +43,6 @@ <valueless/> </properties> </leafNode> - <leafNode name="preempt"> - <properties> - <help>Specifies the association as preemptable rather than the default persistent</help> - <valueless/> - </properties> - </leafNode> <leafNode name="prefer"> <properties> <help>Marks the server as preferred</help> @@ -57,24 +51,33 @@ </leafNode> </children> </tagNode> - <node name="allow-clients"> + <node name="allow-client"> <properties> - <help>Network Time Protocol (NTP) server options</help> + <help>Specify NTP clients allowed to access the server</help> </properties> <children> <leafNode name="address"> <properties> <help>IP address</help> <valueHelp> + <format>ipv4</format> + <description>Allowed IPv4 address</description> + </valueHelp> + <valueHelp> <format>ipv4net</format> - <description>IP address and prefix length</description> + <description>Allowed IPv4 prefix</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Allowed IPv6 address</description> </valueHelp> <valueHelp> <format>ipv6net</format> - <description>IPv6 address and prefix length</description> + <description>Allowed IPv6 prefix</description> </valueHelp> <multi/> <constraint> + <validator name="ip-address"/> <validator name="ip-prefix"/> </constraint> </properties> diff --git a/interface-definitions/protocols-static.xml.in b/interface-definitions/protocols-static.xml.in index e89433022..ca4ca2d74 100644 --- a/interface-definitions/protocols-static.xml.in +++ b/interface-definitions/protocols-static.xml.in @@ -26,6 +26,13 @@ </constraint> </properties> <children> + <!-- + iproute2 only considers the first "word" until whitespace in the name field + but does not complain about special characters. + We put an artificial limit here to make table descriptions potentially valid node names + to avoid quoting and simplify future syntax changes if we decide to make any. + --> + #include <include/generic-description.xml.i> #include <include/static/static-route.xml.i> #include <include/static/static-route6.xml.i> </children> diff --git a/interface-definitions/qos.xml.in b/interface-definitions/qos.xml.in index dc807781e..8809369ff 100644 --- a/interface-definitions/qos.xml.in +++ b/interface-definitions/qos.xml.in @@ -3,6 +3,7 @@ <node name="qos" owner="${vyos_conf_scripts_dir}/qos.py"> <properties> <help>Quality of Service (QoS)</help> + <priority>900</priority> </properties> <children> <tagNode name="interface"> @@ -24,17 +25,7 @@ <properties> <help>Interface ingress traffic policy</help> <completionHelp> - <path>traffic-policy drop-tail</path> - <path>traffic-policy fair-queue</path> - <path>traffic-policy fq-codel</path> - <path>traffic-policy limiter</path> - <path>traffic-policy network-emulator</path> - <path>traffic-policy priority-queue</path> - <path>traffic-policy random-detect</path> - <path>traffic-policy rate-control</path> - <path>traffic-policy round-robin</path> - <path>traffic-policy shaper</path> - <path>traffic-policy shaper-hfsc</path> + <path>qos policy limiter</path> </completionHelp> <valueHelp> <format>txt</format> @@ -46,17 +37,17 @@ <properties> <help>Interface egress traffic policy</help> <completionHelp> - <path>traffic-policy drop-tail</path> - <path>traffic-policy fair-queue</path> - <path>traffic-policy fq-codel</path> - <path>traffic-policy limiter</path> - <path>traffic-policy network-emulator</path> - <path>traffic-policy priority-queue</path> - <path>traffic-policy random-detect</path> - <path>traffic-policy rate-control</path> - <path>traffic-policy round-robin</path> - <path>traffic-policy shaper</path> - <path>traffic-policy shaper-hfsc</path> + <path>qos policy cake</path> + <path>qos policy drop-tail</path> + <path>qos policy fair-queue</path> + <path>qos policy fq-codel</path> + <path>qos policy network-emulator</path> + <path>qos policy priority-queue</path> + <path>qos policy random-detect</path> + <path>qos policy rate-control</path> + <path>qos policy round-robin</path> + <path>qos policy shaper</path> + <path>qos policy shaper-hfsc</path> </completionHelp> <valueHelp> <format>txt</format> @@ -66,12 +57,97 @@ </leafNode> </children> </tagNode> - <node name="policy" owner="${vyos_conf_scripts_dir}/qos.py"> + <node name="policy"> <properties> <help>Service Policy definitions</help> - <priority>900</priority> </properties> <children> + <tagNode name="cake"> + <properties> + <help>Common Applications Kept Enhanced (CAKE)</help> + <valueHelp> + <format>txt</format> + <description>Policy name</description> + </valueHelp> + <constraint> + <regex>[[:alnum:]][-_[:alnum:]]*</regex> + </constraint> + <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/qos/bandwidth.xml.i> + <node name="flow-isolation"> + <properties> + <help>Flow isolation settings</help> + </properties> + <children> + <leafNode name="blind"> + <properties> + <help>Disables flow isolation, all traffic passes through a single queue</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="src-host"> + <properties> + <help>Flows are defined only by source address</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="dst-host"> + <properties> + <help>Flows are defined only by destination address</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="host"> + <properties> + <help>Flows are defined by source-destination host pairs</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="flow"> + <properties> + <help>Flows are defined by the entire 5-tuple</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="dual-src-host"> + <properties> + <help>Flows are defined by the 5-tuple, and fairness is applied first over source addresses, then over individual flows</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="dual-dst-host"> + <properties> + <help>Flows are defined by the 5-tuple, and fairness is applied first over destination addresses, then over individual flows</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="nat"> + <properties> + <help>Perform NAT lookup before applying flow-isolation rules</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="rtt"> + <properties> + <help>Round-Trip-Time for Active Queue Management (AQM)</help> + <valueHelp> + <format>u32:1-3600000</format> + <description>RTT in ms</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-3600000"/> + </constraint> + <constraintErrorMessage>RTT must be in range 1 to 3600000 milli-seconds</constraintErrorMessage> + </properties> + <defaultValue>100</defaultValue> + </leafNode> + </children> + </tagNode> <tagNode name="drop-tail"> <properties> <help>Packet limited First In, First Out queue</help> @@ -125,13 +201,13 @@ <properties> <help>Upper limit of the SFQ</help> <valueHelp> - <format>u32:2-127</format> + <format>u32:1-127</format> <description>Queue size in packets</description> </valueHelp> <constraint> - <validator name="numeric" argument="--range 2-127"/> + <validator name="numeric" argument="--range 1-127"/> </constraint> - <constraintErrorMessage>Queue limit must greater than 1 and less than 128</constraintErrorMessage> + <constraintErrorMessage>Queue limit must be in range 1 to 127</constraintErrorMessage> </properties> <defaultValue>127</defaultValue> </leafNode> @@ -139,7 +215,7 @@ </tagNode> <tagNode name="fq-codel"> <properties> - <help>Fair Queuing Controlled Delay</help> + <help>Fair Queuing (FQ) with Controlled Delay (CoDel)</help> <valueHelp> <format>txt</format> <description>Policy name</description> @@ -171,6 +247,7 @@ <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> + #include <include/generic-description.xml.i> <tagNode name="class"> <properties> <help>Class ID</help> @@ -184,23 +261,13 @@ <constraintErrorMessage>Class identifier must be between 1 and 4090</constraintErrorMessage> </properties> <children> + #include <include/generic-description.xml.i> #include <include/qos/bandwidth.xml.i> #include <include/qos/burst.xml.i> - #include <include/generic-description.xml.i> - #include <include/qos/match.xml.i> - #include <include/qos/limiter-actions.xml.i> + #include <include/qos/class-police-exceed.xml.i> + #include <include/qos/class-match.xml.i> + #include <include/qos/class-priority.xml.i> <leafNode name="priority"> - <properties> - <help>Priority for rule evaluation</help> - <valueHelp> - <format>u32:0-20</format> - <description>Priority for match rule evaluation</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-20"/> - </constraint> - <constraintErrorMessage>Priority must be between 0 and 20</constraintErrorMessage> - </properties> <defaultValue>20</defaultValue> </leafNode> </children> @@ -212,10 +279,9 @@ <children> #include <include/qos/bandwidth.xml.i> #include <include/qos/burst.xml.i> - #include <include/qos/limiter-actions.xml.i> + #include <include/qos/class-police-exceed.xml.i> </children> </node> - #include <include/generic-description.xml.i> </children> </tagNode> <tagNode name="network-emulator"> @@ -231,10 +297,9 @@ <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> - #include <include/qos/bandwidth.xml.i> - #include <include/qos/burst.xml.i> #include <include/generic-description.xml.i> - <leafNode name="network-delay"> + #include <include/qos/bandwidth.xml.i> + <leafNode name="delay"> <properties> <help>Adds delay to packets outgoing to chosen network interface</help> <valueHelp> @@ -247,7 +312,7 @@ <constraintErrorMessage>Priority must be between 0 and 65535</constraintErrorMessage> </properties> </leafNode> - <leafNode name="packet-corruption"> + <leafNode name="corruption"> <properties> <help>Introducing error in a random position for chosen percent of packets</help> <valueHelp> @@ -260,9 +325,9 @@ <constraintErrorMessage>Priority must be between 0 and 100</constraintErrorMessage> </properties> </leafNode> - <leafNode name="packet-loss"> + <leafNode name="duplicate"> <properties> - <help>Add independent loss probability to the packets outgoing to chosen network interface</help> + <help>Cosen percent of packets is duplicated before queuing them</help> <valueHelp> <format><number></format> <description>Percentage of packets affected</description> @@ -270,10 +335,10 @@ <constraint> <validator name="numeric" argument="--range 0-100"/> </constraint> - <constraintErrorMessage>Must be between 0 and 100</constraintErrorMessage> + <constraintErrorMessage>Priority must be between 0 and 100</constraintErrorMessage> </properties> </leafNode> - <leafNode name="packet-loss"> + <leafNode name="loss"> <properties> <help>Add independent loss probability to the packets outgoing to chosen network interface</help> <valueHelp> @@ -286,9 +351,9 @@ <constraintErrorMessage>Must be between 0 and 100</constraintErrorMessage> </properties> </leafNode> - <leafNode name="packet-loss"> + <leafNode name="reordering"> <properties> - <help>Packet reordering percentage</help> + <help>Emulated packet reordering percentage</help> <valueHelp> <format><number></format> <description>Percentage of packets affected</description> @@ -315,6 +380,7 @@ <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> + #include <include/generic-description.xml.i> <tagNode name="class"> <properties> <help>Class Handle</help> @@ -332,10 +398,13 @@ #include <include/qos/codel-quantum.xml.i> #include <include/qos/flows.xml.i> #include <include/qos/interval.xml.i> - #include <include/qos/match.xml.i> - #include <include/qos/queue-limit-2-10999.xml.i> - #include <include/qos/target.xml.i> + #include <include/qos/class-match.xml.i> + #include <include/qos/queue-limit-1-4294967295.xml.i> #include <include/qos/queue-type.xml.i> + <leafNode name="queue-type"> + <defaultValue>drop-tail</defaultValue> + </leafNode> + #include <include/qos/target.xml.i> </children> </tagNode> <node name="default"> @@ -343,21 +412,22 @@ <help>Default policy</help> </properties> <children> - #include <include/generic-description.xml.i> #include <include/qos/codel-quantum.xml.i> #include <include/qos/flows.xml.i> #include <include/qos/interval.xml.i> - #include <include/qos/queue-limit-2-10999.xml.i> - #include <include/qos/target.xml.i> + #include <include/qos/queue-limit-1-4294967295.xml.i> #include <include/qos/queue-type.xml.i> + <leafNode name="queue-type"> + <defaultValue>drop-tail</defaultValue> + </leafNode> + #include <include/qos/target.xml.i> </children> </node> - #include <include/generic-description.xml.i> </children> </tagNode> <tagNode name="random-detect"> <properties> - <help>Priority queuing based policy</help> + <help>Weighted Random Early Detect policy</help> <valueHelp> <format>txt</format> <description>Policy name</description> @@ -368,11 +438,8 @@ <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> - #include <include/qos/bandwidth.xml.i> - <leafNode name="bandwidth"> - <defaultValue>auto</defaultValue> - </leafNode> #include <include/generic-description.xml.i> + #include <include/qos/bandwidth-auto.xml.i> <tagNode name="precedence"> <properties> <help>IP precedence</help> @@ -413,6 +480,7 @@ </constraint> <constraintErrorMessage>Mark probability must be greater than 0</constraintErrorMessage> </properties> + <defaultValue>10</defaultValue> </leafNode> <leafNode name="maximum-threshold"> <properties> @@ -426,6 +494,7 @@ </constraint> <constraintErrorMessage>Threshold must be between 0 and 4096</constraintErrorMessage> </properties> + <defaultValue>18</defaultValue> </leafNode> <leafNode name="minimum-threshold"> <properties> @@ -457,8 +526,8 @@ <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> - #include <include/qos/bandwidth.xml.i> #include <include/generic-description.xml.i> + #include <include/qos/bandwidth.xml.i> #include <include/qos/burst.xml.i> <leafNode name="latency"> <properties> @@ -478,7 +547,7 @@ </tagNode> <tagNode name="round-robin"> <properties> - <help>Round-Robin based policy</help> + <help>Deficit Round Robin Scheduler</help> <valueHelp> <format>txt</format> <description>Policy name</description> @@ -503,11 +572,11 @@ <constraintErrorMessage>Class identifier must be between 1 and 4095</constraintErrorMessage> </properties> <children> - #include <include/qos/codel-quantum.xml.i> #include <include/generic-description.xml.i> + #include <include/qos/codel-quantum.xml.i> #include <include/qos/flows.xml.i> #include <include/qos/interval.xml.i> - #include <include/qos/match.xml.i> + #include <include/qos/class-match.xml.i> <leafNode name="quantum"> <properties> <help>Packet scheduling quantum</help> @@ -523,111 +592,26 @@ </leafNode> #include <include/qos/queue-limit-1-4294967295.xml.i> #include <include/qos/queue-type.xml.i> + <leafNode name="queue-type"> + <defaultValue>drop-tail</defaultValue> + </leafNode> #include <include/qos/target.xml.i> </children> </tagNode> - </children> - </tagNode> - <tagNode name="shaper-hfsc"> - <properties> - <help>Hierarchical Fair Service Curve's policy</help> - <valueHelp> - <format>txt</format> - <description>Policy name</description> - </valueHelp> - <constraint> - <regex>[[:alnum:]][-_[:alnum:]]*</regex> - </constraint> - <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> - </properties> - <children> - #include <include/qos/bandwidth.xml.i> - <leafNode name="bandwidth"> - <defaultValue>auto</defaultValue> - </leafNode> - #include <include/generic-description.xml.i> - <tagNode name="class"> - <properties> - <help>Class ID</help> - <valueHelp> - <format>u32:1-4095</format> - <description>Class Identifier</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-4095"/> - </constraint> - <constraintErrorMessage>Class identifier must be between 1 and 4095</constraintErrorMessage> - </properties> - <children> - #include <include/generic-description.xml.i> - <node name="linkshare"> - <properties> - <help>Linkshare class settings</help> - </properties> - <children> - #include <include/qos/hfsc-d.xml.i> - #include <include/qos/hfsc-m1.xml.i> - #include <include/qos/hfsc-m2.xml.i> - </children> - </node> - #include <include/qos/match.xml.i> - <node name="realtime"> - <properties> - <help>Realtime class settings</help> - </properties> - <children> - #include <include/qos/hfsc-d.xml.i> - #include <include/qos/hfsc-m1.xml.i> - #include <include/qos/hfsc-m2.xml.i> - </children> - </node> - <node name="upperlimit"> - <properties> - <help>Upperlimit class settings</help> - </properties> - <children> - #include <include/qos/hfsc-d.xml.i> - #include <include/qos/hfsc-m1.xml.i> - #include <include/qos/hfsc-m2.xml.i> - </children> - </node> - </children> - </tagNode> <node name="default"> <properties> <help>Default policy</help> </properties> <children> - <node name="linkshare"> - <properties> - <help>Linkshare class settings</help> - </properties> - <children> - #include <include/qos/hfsc-d.xml.i> - #include <include/qos/hfsc-m1.xml.i> - #include <include/qos/hfsc-m2.xml.i> - </children> - </node> - <node name="realtime"> - <properties> - <help>Realtime class settings</help> - </properties> - <children> - #include <include/qos/hfsc-d.xml.i> - #include <include/qos/hfsc-m1.xml.i> - #include <include/qos/hfsc-m2.xml.i> - </children> - </node> - <node name="upperlimit"> - <properties> - <help>Upperlimit class settings</help> - </properties> - <children> - #include <include/qos/hfsc-d.xml.i> - #include <include/qos/hfsc-m1.xml.i> - #include <include/qos/hfsc-m2.xml.i> - </children> - </node> + #include <include/qos/codel-quantum.xml.i> + #include <include/qos/flows.xml.i> + #include <include/qos/interval.xml.i> + #include <include/qos/queue-limit-1-4294967295.xml.i> + #include <include/qos/queue-type.xml.i> + <leafNode name="queue-type"> + <defaultValue>fair-queue</defaultValue> + </leafNode> + #include <include/qos/target.xml.i> </children> </node> </children> @@ -645,10 +629,8 @@ <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> - #include <include/qos/bandwidth.xml.i> - <leafNode name="bandwidth"> - <defaultValue>auto</defaultValue> - </leafNode> + #include <include/generic-description.xml.i> + #include <include/qos/bandwidth-auto.xml.i> <tagNode name="class"> <properties> <help>Class ID</help> @@ -662,10 +644,8 @@ <constraintErrorMessage>Class identifier must be between 2 and 4095</constraintErrorMessage> </properties> <children> - #include <include/qos/bandwidth.xml.i> - <leafNode name="bandwidth"> - <defaultValue>100%</defaultValue> - </leafNode> + #include <include/generic-description.xml.i> + #include <include/qos/bandwidth-auto.xml.i> #include <include/qos/burst.xml.i> <leafNode name="ceiling"> <properties> @@ -697,31 +677,19 @@ </properties> </leafNode> #include <include/qos/codel-quantum.xml.i> - #include <include/generic-description.xml.i> #include <include/qos/flows.xml.i> #include <include/qos/interval.xml.i> - #include <include/qos/match.xml.i> - <leafNode name="priority"> - <properties> - <help>Priority for usage of excess bandwidth</help> - <valueHelp> - <format>u32:0-7</format> - <description>Priority order for bandwidth pool</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-7"/> - </constraint> - <constraintErrorMessage>Priority must be between 0 and 7</constraintErrorMessage> - </properties> - <defaultValue>20</defaultValue> - </leafNode> + #include <include/qos/class-match.xml.i> + #include <include/qos/class-priority.xml.i> #include <include/qos/queue-limit-1-4294967295.xml.i> #include <include/qos/queue-type.xml.i> + <leafNode name="queue-type"> + <defaultValue>fq-codel</defaultValue> + </leafNode> #include <include/qos/set-dscp.xml.i> #include <include/qos/target.xml.i> </children> </tagNode> - #include <include/generic-description.xml.i> <node name="default"> <properties> <help>Default policy</help> @@ -759,7 +727,6 @@ </properties> </leafNode> #include <include/qos/codel-quantum.xml.i> - #include <include/generic-description.xml.i> #include <include/qos/flows.xml.i> #include <include/qos/interval.xml.i> <leafNode name="priority"> @@ -778,12 +745,116 @@ </leafNode> #include <include/qos/queue-limit-1-4294967295.xml.i> #include <include/qos/queue-type.xml.i> + <leafNode name="queue-type"> + <defaultValue>fq-codel</defaultValue> + </leafNode> #include <include/qos/set-dscp.xml.i> #include <include/qos/target.xml.i> </children> </node> </children> </tagNode> + <tagNode name="shaper-hfsc"> + <properties> + <help>Hierarchical Fair Service Curve's policy</help> + <valueHelp> + <format>txt</format> + <description>Policy name</description> + </valueHelp> + <constraint> + <regex>[[:alnum:]][-_[:alnum:]]*</regex> + </constraint> + <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/qos/bandwidth-auto.xml.i> + <tagNode name="class"> + <properties> + <help>Class ID</help> + <valueHelp> + <format>u32:1-4095</format> + <description>Class Identifier</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4095"/> + </constraint> + <constraintErrorMessage>Class identifier must be between 1 and 4095</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <node name="linkshare"> + <properties> + <help>Linkshare class settings</help> + </properties> + <children> + #include <include/qos/hfsc-d.xml.i> + #include <include/qos/hfsc-m1.xml.i> + #include <include/qos/hfsc-m2.xml.i> + </children> + </node> + #include <include/qos/class-match.xml.i> + <node name="realtime"> + <properties> + <help>Realtime class settings</help> + </properties> + <children> + #include <include/qos/hfsc-d.xml.i> + #include <include/qos/hfsc-m1.xml.i> + #include <include/qos/hfsc-m2.xml.i> + </children> + </node> + <node name="upperlimit"> + <properties> + <help>Upperlimit class settings</help> + </properties> + <children> + #include <include/qos/hfsc-d.xml.i> + #include <include/qos/hfsc-m1.xml.i> + #include <include/qos/hfsc-m2.xml.i> + </children> + </node> + </children> + </tagNode> + <node name="default"> + <properties> + <help>Default policy</help> + </properties> + <children> + <node name="linkshare"> + <properties> + <help>Linkshare class settings</help> + </properties> + <children> + #include <include/qos/hfsc-d.xml.i> + #include <include/qos/hfsc-m1.xml.i> + #include <include/qos/hfsc-m2.xml.i> + </children> + </node> + <node name="realtime"> + <properties> + <help>Realtime class settings</help> + </properties> + <children> + #include <include/qos/hfsc-d.xml.i> + #include <include/qos/hfsc-m1.xml.i> + #include <include/qos/hfsc-m2.xml.i> + </children> + </node> + <node name="upperlimit"> + <properties> + <help>Upperlimit class settings</help> + </properties> + <children> + #include <include/qos/hfsc-d.xml.i> + #include <include/qos/hfsc-m1.xml.i> + #include <include/qos/hfsc-m2.xml.i> + </children> + </node> + </children> + </node> + </children> + </tagNode> </children> </node> </children> diff --git a/interface-definitions/service-console-server.xml.in b/interface-definitions/service-console-server.xml.in index fb71538dd..fc6dbe954 100644 --- a/interface-definitions/service-console-server.xml.in +++ b/interface-definitions/service-console-server.xml.in @@ -27,7 +27,7 @@ </constraint> </properties> <children> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> <leafNode name="alias"> <properties> <help>Human-readable name for this console</help> diff --git a/interface-definitions/service-pppoe-server.xml.in b/interface-definitions/service-pppoe-server.xml.in index b31109296..47ad96582 100644 --- a/interface-definitions/service-pppoe-server.xml.in +++ b/interface-definitions/service-pppoe-server.xml.in @@ -170,52 +170,7 @@ </properties> </leafNode> #include <include/accel-ppp/ppp-options-ipv6.xml.i> - <leafNode name="ipv6-intf-id"> - <properties> - <help>Fixed or random interface identifier for IPv6</help> - <completionHelp> - <list>random</list> - </completionHelp> - <valueHelp> - <format>random</format> - <description>Random interface identifier for IPv6</description> - </valueHelp> - <valueHelp> - <format>x:x:x:x</format> - <description>specify interface identifier for IPv6</description> - </valueHelp> - </properties> - </leafNode> - <leafNode name="ipv6-peer-intf-id"> - <properties> - <help>Peer interface identifier for IPv6</help> - <completionHelp> - <list>random calling-sid ipv4</list> - </completionHelp> - <valueHelp> - <format>x:x:x:x</format> - <description>Interface identifier for IPv6</description> - </valueHelp> - <valueHelp> - <format>random</format> - <description>Use a random interface identifier for IPv6</description> - </valueHelp> - <valueHelp> - <format>ipv4</format> - <description>Calculate interface identifier from IPv4 address, for example 192:168:0:1</description> - </valueHelp> - <valueHelp> - <format>calling-sid</format> - <description>Calculate interface identifier from calling-station-id</description> - </valueHelp> - </properties> - </leafNode> - <leafNode name="ipv6-accept-peer-intf-id"> - <properties> - <help>Accept peer interface identifier</help> - <valueless /> - </properties> - </leafNode> + #include <include/accel-ppp/ppp-options-ipv6-interface-id.xml.i> </children> </node> <tagNode name="pado-delay"> diff --git a/interface-definitions/system-option.xml.in b/interface-definitions/system-option.xml.in index a9fed81fe..bb15e467e 100644 --- a/interface-definitions/system-option.xml.in +++ b/interface-definitions/system-option.xml.in @@ -121,6 +121,7 @@ </properties> <children> #include <include/source-address-ipv4-ipv6.xml.i> + #include <include/source-interface.xml.i> </children> </node> <leafNode name="startup-beep"> diff --git a/interface-definitions/vpn-ipsec.xml.in b/interface-definitions/vpn-ipsec.xml.in index 64966b540..fd74a51d7 100644 --- a/interface-definitions/vpn-ipsec.xml.in +++ b/interface-definitions/vpn-ipsec.xml.in @@ -957,6 +957,7 @@ <description>ID used for peer authentication</description> </valueHelp> </properties> + <defaultValue>%any</defaultValue> </leafNode> <leafNode name="use-x509-id"> <properties> diff --git a/interface-definitions/vpn-l2tp.xml.in b/interface-definitions/vpn-l2tp.xml.in index 06ca4ece5..86aeb324e 100644 --- a/interface-definitions/vpn-l2tp.xml.in +++ b/interface-definitions/vpn-l2tp.xml.in @@ -251,6 +251,7 @@ <children> #include <include/accel-ppp/lcp-echo-interval-failure.xml.i> #include <include/accel-ppp/ppp-options-ipv6.xml.i> + #include <include/accel-ppp/ppp-options-ipv6-interface-id.xml.i> </children> </node> </children> diff --git a/interface-definitions/vpn-openconnect.xml.in b/interface-definitions/vpn-openconnect.xml.in index 8b60f2e6e..82fe2bbc9 100644 --- a/interface-definitions/vpn-openconnect.xml.in +++ b/interface-definitions/vpn-openconnect.xml.in @@ -150,7 +150,7 @@ </node> </children> </node> - #include <include/listen-address-ipv4.xml.i> + #include <include/listen-address-ipv4-single.xml.i> <leafNode name="listen-address"> <defaultValue>0.0.0.0</defaultValue> </leafNode> diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in index 3604b41c8..96c6d8be2 100644 --- a/interface-definitions/vrf.xml.in +++ b/interface-definitions/vrf.xml.in @@ -26,7 +26,7 @@ </valueHelp> </properties> <children> - #include <include/interface/description.xml.i> + #include <include/generic-description.xml.i> #include <include/interface/disable.xml.i> <node name="ip"> <properties> diff --git a/interface-definitions/xml-component-version.xml.in b/interface-definitions/xml-component-version.xml.in index 914e3bc69..2e6506efc 100644 --- a/interface-definitions/xml-component-version.xml.in +++ b/interface-definitions/xml-component-version.xml.in @@ -6,6 +6,7 @@ #include <include/version/config-management-version.xml.i> #include <include/version/conntrack-sync-version.xml.i> #include <include/version/conntrack-version.xml.i> + #include <include/version/container-version.xml.i> #include <include/version/dhcp-relay-version.xml.i> #include <include/version/dhcp-server-version.xml.i> #include <include/version/dhcpv6-server-version.xml.i> diff --git a/mibs/IANA-ADDRESS-FAMILY-NUMBERS-MIB.txt b/mibs/IANA-ADDRESS-FAMILY-NUMBERS-MIB.txt new file mode 100644 index 000000000..7995fc4ad --- /dev/null +++ b/mibs/IANA-ADDRESS-FAMILY-NUMBERS-MIB.txt @@ -0,0 +1,166 @@ + IANA-ADDRESS-FAMILY-NUMBERS-MIB DEFINITIONS ::= BEGIN + + IMPORTS + MODULE-IDENTITY, + mib-2 FROM SNMPv2-SMI + TEXTUAL-CONVENTION FROM SNMPv2-TC; + + ianaAddressFamilyNumbers MODULE-IDENTITY + LAST-UPDATED "201309250000Z" -- September 25, 2013 + ORGANIZATION "IANA" + CONTACT-INFO + "Postal: Internet Assigned Numbers Authority + Internet Corporation for Assigned Names + and Numbers + 12025 Waterfront Drive, Suite 300 + Los Angeles, CA 90094-2536 + USA + + Tel: +1 310-301-5800 + E-Mail: iana&iana.org" + DESCRIPTION + "The MIB module defines the AddressFamilyNumbers + textual convention." + + -- revision history + + REVISION "201309250000Z" -- September 25, 2013 + DESCRIPTION "Fixed labels for 16389-16390." + + REVISION "201307160000Z" -- July 16, 2013 + DESCRIPTION "Fixed labels for 16389-16390." + + REVISION "201306260000Z" -- June 26, 2013 + DESCRIPTION "Added assignments 26-28." + + REVISION "201306180000Z" -- June 18, 2013 + DESCRIPTION "Added assignments 16384-16390. Assignment + 25 added in 2007 revision." + + REVISION "200203140000Z" -- March 14, 2002 + DESCRIPTION "AddressFamilyNumbers assignment 22 to + fibreChannelWWPN. AddressFamilyNumbers + assignment 23 to fibreChannelWWNN. + AddressFamilyNumers assignment 24 to gwid." + + REVISION "200009080000Z" -- September 8, 2000 + DESCRIPTION "AddressFamilyNumbers assignment 19 to xtpOverIpv4. + AddressFamilyNumbers assignment 20 to xtpOverIpv6. + AddressFamilyNumbers assignment 21 to xtpNativeModeXTP." + + REVISION "200003010000Z" -- March 1, 2000 + DESCRIPTION "AddressFamilyNumbers assignment 17 to distinguishedName. + AddressFamilyNumbers assignment 18 to asNumber." + + REVISION "200002040000Z" -- February 4, 2000 + DESCRIPTION "AddressFamilyNumbers assignment 16 to dns." + + REVISION "9908260000Z" -- August 26, 1999 + DESCRIPTION "Initial version, published as RFC 2677." + ::= { mib-2 72 } + + AddressFamilyNumbers ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The definition of this textual convention with the + addition of newly assigned values is published + periodically by the IANA, in either the Assigned + Numbers RFC, or some derivative of it specific to + Internet Network Management number assignments. + (The latest arrangements can be obtained by + contacting the IANA.) + + The enumerations are described as: + + other(0), -- none of the following + ipV4(1), -- IP Version 4 + ipV6(2), -- IP Version 6 + nsap(3), -- NSAP + hdlc(4), -- (8-bit multidrop) + bbn1822(5), + all802(6), -- (includes all 802 media + -- plus Ethernet 'canonical format') + e163(7), + e164(8), -- (SMDS, Frame Relay, ATM) + f69(9), -- (Telex) + x121(10), -- (X.25, Frame Relay) + ipx(11), -- IPX (Internet Protocol Exchange) + appleTalk(12), -- Apple Talk + decnetIV(13), -- DEC Net Phase IV + banyanVines(14), -- Banyan Vines + e164withNsap(15), + -- (E.164 with NSAP format subaddress) + dns(16), -- (Domain Name System) + distinguishedName(17), -- (Distinguished Name, per X.500) + asNumber(18), -- (16-bit quantity, per the AS number space) + xtpOverIpv4(19), -- XTP over IP version 4 + xtpOverIpv6(20), -- XTP over IP version 6 + xtpNativeModeXTP(21), -- XTP native mode XTP + fibreChannelWWPN(22), -- Fibre Channel World-Wide Port Name + fibreChannelWWNN(23), -- Fibre Channel World-Wide Node Name + gwid(24), -- Gateway Identifier + afi(25), -- AFI for L2VPN information + mplsTpSectionEndpointIdentifier(26), -- MPLS-TP Section Endpoint Identifier + mplsTpLspEndpointIdentifier(27), -- MPLS-TP LSP Endpoint Identifier + mplsTpPseudowireEndpointIdentifier(28), -- MPLS-TP Pseudowire Endpoint Identifier + eigrpCommonServiceFamily(16384), -- EIGRP Common Service Family + eigrpIpv4ServiceFamily(16385), -- EIGRP IPv4 Service Family + eigrpIpv6ServiceFamily(16386), -- EIGRP IPv6 Service Family + lispCanonicalAddressFormat(16387), -- LISP Canonical Address Format (LCAF) + bgpLs(16388), -- BGP-LS + fortyeightBitMacBitMac(16389), -- 48-bit MAC + sixtyfourBitMac(16390), -- 64-bit MAC + oui(16391), -- OUI + mac24(16392), -- MAC/24 + mac40(16393), -- MAC/40 + ipv664(16394), -- IPv6/64 + rBridgePortID(16395), -- RBridge Port ID + reserved(65535) + + Requests for new values should be made to IANA via + email (iana&iana.org)." + SYNTAX INTEGER { + other(0), + ipV4(1), + ipV6(2), + nsap(3), + hdlc(4), + bbn1822(5), + all802(6), + e163(7), + e164(8), + f69(9), + x121(10), + ipx(11), + appleTalk(12), + decnetIV(13), + banyanVines(14), + e164withNsap(15), + dns(16), + distinguishedName(17), -- (Distinguished Name, per X.500) + asNumber(18), -- (16-bit quantity, per the AS number space) + xtpOverIpv4(19), + xtpOverIpv6(20), + xtpNativeModeXTP(21), + fibreChannelWWPN(22), + fibreChannelWWNN(23), + gwid(24), + afi(25), + mplsTpSectionEndpointIdentifier(26), + mplsTpLspEndpointIdentifier(27), + mplsTpPseudowireEndpointIdentifier(28), + eigrpCommonServiceFamily(16384), + eigrpIpv4ServiceFamily(16385), + eigrpIpv6ServiceFamily(16386), + lispCanonicalAddressFormat(16387), + bgpLs(16388), + fortyeightBitMac(16389), + sixtyfourBitMac(16390), + oui(16391), + mac24(16392), + mac40(16393), + ipv664(16394), + rBridgePortID(16395), + reserved(65535) + } + END diff --git a/mibs/IANA-LANGUAGE-MIB.txt b/mibs/IANA-LANGUAGE-MIB.txt new file mode 100644 index 000000000..4b97bdd39 --- /dev/null +++ b/mibs/IANA-LANGUAGE-MIB.txt @@ -0,0 +1,126 @@ +IANA-LANGUAGE-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-IDENTITY, mib-2 + FROM SNMPv2-SMI; + +ianaLanguages MODULE-IDENTITY + LAST-UPDATED "201405220000Z" -- May 22, 2014 + ORGANIZATION "IANA" + CONTACT-INFO + "Internet Assigned Numbers Authority (IANA) + + Postal: ICANN + 12025 Waterfront Drive, Suite 300 + Los Angeles, CA 90094-2536 + + Tel: +1 310-301-5800 + E-Mail: iana&iana.org" + DESCRIPTION + "The MIB module registers object identifier values for + well-known programming and scripting languages. Every + language registration MUST describe the format used + when transferring scripts written in this language. + + Any additions or changes to the contents of this MIB + module require Designated Expert Review as defined in + the Guidelines for Writing IANA Considerations Section + document. The Designated Expert will be selected by + the IESG Area Director of the OPS Area. + + Note, this module does not have to register all possible + languages since languages are identified by object + identifier values. It is therefore possible to registered + languages in private OID trees. The references given below are not + normative with regard to the language version. Other + references might be better suited to describe some newer + versions of this language. The references are only + provided as `a pointer into the right direction'." + + -- Revision log, in reverse chronological order + + REVISION "201405220000Z" -- May 22, 2014 + DESCRIPTION "Updated contact info." + + REVISION "200005100000Z" -- May 10, 2000 + DESCRIPTION "Import mib-2 instead of experimental, so that + this module compiles" + + REVISION "199909090900Z" -- September 9, 1999 + DESCRIPTION "Initial version as published at time of + publication of RFC 2591." + ::= { mib-2 73 } + +ianaLangJavaByteCode OBJECT-IDENTITY + STATUS current + DESCRIPTION + "Java byte code to be processed by a Java virtual machine. + A script written in Java byte code is transferred by using + the Java archive file format (JAR)." + REFERENCE + "The Java Virtual Machine Specification. + ISBN 0-201-63452-X" + ::= { ianaLanguages 1 } + +ianaLangTcl OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The Tool Command Language (Tcl). A script written in the + Tcl language is transferred in Tcl source code format." + REFERENCE + "Tcl and the Tk Toolkit. + ISBN 0-201-63337-X" + ::= { ianaLanguages 2 } + +ianaLangPerl OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The Perl language. A script written in the Perl language + is transferred in Perl source code format." + REFERENCE + "Programming Perl. + ISBN 1-56592-149-6" + ::= { ianaLanguages 3 } + +ianaLangScheme OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The Scheme language. A script written in the Scheme + language is transferred in Scheme source code format." + REFERENCE + "The Revised^4 Report on the Algorithmic Language Scheme. + MIT Press" + ::= { ianaLanguages 4 } + +ianaLangSRSL OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The SNMP Script Language defined by SNMP Research. A + script written in the SNMP Script Language is transferred + in the SNMP Script Language source code format." + ::= { ianaLanguages 5 } + +ianaLangPSL OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The Patrol Script Language defined by BMC Software. A script + written in the Patrol Script Language is transferred in the + Patrol Script Language source code format." + REFERENCE + "PATROL Script Language Reference Manual, Version 3.0, + November 30, 1995. BMC Software, Inc. 2101 City West Blvd., + Houston, Texas 77042." + ::= { ianaLanguages 6 } + +ianaLangSMSL OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The Systems Management Scripting Language. A script written + in the SMSL language is transferred in the SMSL source code + format." + REFERENCE + "ISO/ITU Command Sequencer. + ISO 10164-21 or ITU X.753" + ::= { ianaLanguages 7 } + +END diff --git a/mibs/IANA-RTPROTO-MIB.txt b/mibs/IANA-RTPROTO-MIB.txt new file mode 100644 index 000000000..f7bc1ebc7 --- /dev/null +++ b/mibs/IANA-RTPROTO-MIB.txt @@ -0,0 +1,95 @@ +IANA-RTPROTO-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, mib-2 FROM SNMPv2-SMI + TEXTUAL-CONVENTION FROM SNMPv2-TC; + +ianaRtProtoMIB MODULE-IDENTITY + LAST-UPDATED "201208300000Z" -- August 30, 2012 + ORGANIZATION "IANA" + CONTACT-INFO + " Internet Assigned Numbers Authority + Internet Corporation for Assigned Names and Numbers + 12025 Waterfront Drive, Suite 300 + Los Angeles, CA 90094-2536 + + Phone: +1 310 301 5800 + EMail: iana&iana.org" + DESCRIPTION + "This MIB module defines the IANAipRouteProtocol and + IANAipMRouteProtocol textual conventions for use in MIBs + which need to identify unicast or multicast routing + mechanisms. + + Any additions or changes to the contents of this MIB module + require either publication of an RFC, or Designated Expert + Review as defined in RFC 2434, Guidelines for Writing an + IANA Considerations Section in RFCs. The Designated Expert + will be selected by the IESG Area Director(s) of the Routing + Area." + + REVISION "201208300000Z" -- August 30, 2012 + DESCRIPTION "Added dhcp(19)." + + REVISION "201107220000Z" -- July 22, 2011 + DESCRIPTION "Added rpl(18) ." + + REVISION "200009260000Z" -- September 26, 2000 + DESCRIPTION "Original version, published in coordination + with RFC 2932." + ::= { mib-2 84 } + +IANAipRouteProtocol ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "A mechanism for learning routes. Inclusion of values for + routing protocols is not intended to imply that those + protocols need be supported." + SYNTAX INTEGER { + other (1), -- not specified + local (2), -- local interface + netmgmt (3), -- static route + icmp (4), -- result of ICMP Redirect + + -- the following are all dynamic + -- routing protocols + + egp (5), -- Exterior Gateway Protocol + ggp (6), -- Gateway-Gateway Protocol + hello (7), -- FuzzBall HelloSpeak + rip (8), -- Berkeley RIP or RIP-II + isIs (9), -- Dual IS-IS + esIs (10), -- ISO 9542 + ciscoIgrp (11), -- Cisco IGRP + bbnSpfIgp (12), -- BBN SPF IGP + ospf (13), -- Open Shortest Path First + bgp (14), -- Border Gateway Protocol + idpr (15), -- InterDomain Policy Routing + ciscoEigrp (16), -- Cisco EIGRP + dvmrp (17), -- DVMRP + rpl (18), -- RPL [RFC-ietf-roll-rpl-19] + dhcp (19) -- DHCP [RFC2132] + } + +IANAipMRouteProtocol ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The multicast routing protocol. Inclusion of values for + multicast routing protocols is not intended to imply that + those protocols need be supported." + SYNTAX INTEGER { + other(1), -- none of the following + local(2), -- e.g., manually configured + netmgmt(3), -- set via net.mgmt protocol + dvmrp(4), + mospf(5), + pimSparseDense(6), -- PIMv1, both DM and SM + cbt(7), + pimSparseMode(8), -- PIM-SM + pimDenseMode(9), -- PIM-DM + igmpOnly(10), + bgmp(11), + msdp(12) + } + +END diff --git a/mibs/IANAifType-MIB.txt b/mibs/IANAifType-MIB.txt new file mode 100644 index 000000000..027a1532f --- /dev/null +++ b/mibs/IANAifType-MIB.txt @@ -0,0 +1,646 @@ + IANAifType-MIB DEFINITIONS ::= BEGIN + + IMPORTS + MODULE-IDENTITY, mib-2 FROM SNMPv2-SMI + TEXTUAL-CONVENTION FROM SNMPv2-TC; + + ianaifType MODULE-IDENTITY + LAST-UPDATED "201407030000Z" -- July 3, 2014 + ORGANIZATION "IANA" + CONTACT-INFO " Internet Assigned Numbers Authority + + Postal: ICANN + 12025 Waterfront Drive, Suite 300 + Los Angeles, CA 90094-2536 + + Tel: +1 310-301-5800 + E-Mail: iana&iana.org" + DESCRIPTION "This MIB module defines the IANAifType Textual + Convention, and thus the enumerated values of + the ifType object defined in MIB-II's ifTable." + + REVISION "201407030000Z" -- July 3, 2014 + DESCRIPTION "Registration of new IANAifTypes 277-278." + + REVISION "201405220000Z" -- May 22, 2014 + DESCRIPTION "Updated contact info." + + REVISION "201205170000Z" -- May 17, 2012 + DESCRIPTION "Registration of new IANAifType 272." + + REVISION "201201110000Z" -- January 11, 2012 + DESCRIPTION "Registration of new IANAifTypes 266-271." + + REVISION "201112180000Z" -- December 18, 2011 + DESCRIPTION "Registration of new IANAifTypes 263-265." + + REVISION "201110260000Z" -- October 26, 2011 + DESCRIPTION "Registration of new IANAifType 262." + + REVISION "201109070000Z" -- September 7, 2011 + DESCRIPTION "Registration of new IANAifTypes 260 and 261." + + REVISION "201107220000Z" -- July 22, 2011 + DESCRIPTION "Registration of new IANAifType 259." + + REVISION "201106030000Z" -- June 03, 2011 + DESCRIPTION "Registration of new IANAifType 258." + + REVISION "201009210000Z" -- September 21, 2010 + DESCRIPTION "Registration of new IANAifTypes 256 and 257." + + REVISION "201007210000Z" -- July 21, 2010 + DESCRIPTION "Registration of new IANAifType 255." + + REVISION "201002110000Z" -- February 11, 2010 + DESCRIPTION "Registration of new IANAifType 254." + + REVISION "201002080000Z" -- February 08, 2010 + DESCRIPTION "Registration of new IANAifTypes 252 and 253." + + REVISION "200905060000Z" -- May 06, 2009 + DESCRIPTION "Registration of new IANAifType 251." + + REVISION "200902060000Z" -- February 06, 2009 + DESCRIPTION "Registration of new IANAtunnelType 15." + + REVISION "200810090000Z" -- October 09, 2008 + DESCRIPTION "Registration of new IANAifType 250." + + REVISION "200808120000Z" -- August 12, 2008 + DESCRIPTION "Registration of new IANAifType 249." + + REVISION "200807220000Z" -- July 22, 2008 + DESCRIPTION "Registration of new IANAifTypes 247 and 248." + + REVISION "200806240000Z" -- June 24, 2008 + DESCRIPTION "Registration of new IANAifType 246." + + REVISION "200805290000Z" -- May 29, 2008 + DESCRIPTION "Registration of new IANAifType 245." + + REVISION "200709130000Z" -- September 13, 2007 + DESCRIPTION "Registration of new IANAifTypes 243 and 244." + + REVISION "200705290000Z" -- May 29, 2007 + DESCRIPTION "Changed the description for IANAifType 228." + + REVISION "200703080000Z" -- March 08, 2007 + DESCRIPTION "Registration of new IANAifType 242." + + REVISION "200701230000Z" -- January 23, 2007 + DESCRIPTION "Registration of new IANAifTypes 239, 240, and 241." + + REVISION "200610170000Z" -- October 17, 2006 + DESCRIPTION "Deprecated/Obsoleted IANAifType 230. Registration of + IANAifType 238." + + REVISION "200609250000Z" -- September 25, 2006 + DESCRIPTION "Changed the description for IANA ifType + 184 and added new IANA ifType 237." + + REVISION "200608170000Z" -- August 17, 2006 + DESCRIPTION "Changed the descriptions for IANAifTypes + 20 and 21." + + REVISION "200608110000Z" -- August 11, 2006 + DESCRIPTION "Changed the descriptions for IANAifTypes + 7, 11, 62, 69, and 117." + + REVISION "200607250000Z" -- July 25, 2006 + DESCRIPTION "Registration of new IANA ifType 236." + + REVISION "200606140000Z" -- June 14, 2006 + DESCRIPTION "Registration of new IANA ifType 235." + + REVISION "200603310000Z" -- March 31, 2006 + DESCRIPTION "Registration of new IANA ifType 234." + + REVISION "200603300000Z" -- March 30, 2006 + DESCRIPTION "Registration of new IANA ifType 233." + + REVISION "200512220000Z" -- December 22, 2005 + DESCRIPTION "Registration of new IANA ifTypes 231 and 232." + + REVISION "200510100000Z" -- October 10, 2005 + DESCRIPTION "Registration of new IANA ifType 230." + + REVISION "200509090000Z" -- September 09, 2005 + DESCRIPTION "Registration of new IANA ifType 229." + + REVISION "200505270000Z" -- May 27, 2005 + DESCRIPTION "Registration of new IANA ifType 228." + + REVISION "200503030000Z" -- March 3, 2005 + DESCRIPTION "Added the IANAtunnelType TC and deprecated + IANAifType sixToFour (215) per RFC4087." + + REVISION "200411220000Z" -- November 22, 2004 + DESCRIPTION "Registration of new IANA ifType 227 per RFC4631." + + REVISION "200406170000Z" -- June 17, 2004 + DESCRIPTION "Registration of new IANA ifType 226." + + REVISION "200405120000Z" -- May 12, 2004 + DESCRIPTION "Added description for IANAifType 6, and + changed the descriptions for IANAifTypes + 180, 181, and 182." + + REVISION "200405070000Z" -- May 7, 2004 + DESCRIPTION "Registration of new IANAifType 225." + + REVISION "200308250000Z" -- Aug 25, 2003 + DESCRIPTION "Deprecated IANAifTypes 7 and 11. Obsoleted + IANAifTypes 62, 69, and 117. ethernetCsmacd (6) + should be used instead of these values" + + REVISION "200308180000Z" -- Aug 18, 2003 + DESCRIPTION "Registration of new IANAifType + 224." + + REVISION "200308070000Z" -- Aug 7, 2003 + DESCRIPTION "Registration of new IANAifTypes + 222 and 223." + + REVISION "200303180000Z" -- Mar 18, 2003 + DESCRIPTION "Registration of new IANAifType + 221." + + REVISION "200301130000Z" -- Jan 13, 2003 + DESCRIPTION "Registration of new IANAifType + 220." + + REVISION "200210170000Z" -- Oct 17, 2002 + DESCRIPTION "Registration of new IANAifType + 219." + + REVISION "200207160000Z" -- Jul 16, 2002 + DESCRIPTION "Registration of new IANAifTypes + 217 and 218." + + REVISION "200207100000Z" -- Jul 10, 2002 + DESCRIPTION "Registration of new IANAifTypes + 215 and 216." + + REVISION "200206190000Z" -- Jun 19, 2002 + DESCRIPTION "Registration of new IANAifType + 214." + + REVISION "200201040000Z" -- Jan 4, 2002 + DESCRIPTION "Registration of new IANAifTypes + 211, 212 and 213." + + REVISION "200112200000Z" -- Dec 20, 2001 + DESCRIPTION "Registration of new IANAifTypes + 209 and 210." + + REVISION "200111150000Z" -- Nov 15, 2001 + DESCRIPTION "Registration of new IANAifTypes + 207 and 208." + + REVISION "200111060000Z" -- Nov 6, 2001 + DESCRIPTION "Registration of new IANAifType + 206." + + REVISION "200111020000Z" -- Nov 2, 2001 + DESCRIPTION "Registration of new IANAifType + 205." + + REVISION "200110160000Z" -- Oct 16, 2001 + DESCRIPTION "Registration of new IANAifTypes + 199, 200, 201, 202, 203, and 204." + + REVISION "200109190000Z" -- Sept 19, 2001 + DESCRIPTION "Registration of new IANAifType + 198." + + REVISION "200105110000Z" -- May 11, 2001 + DESCRIPTION "Registration of new IANAifType + 197." + + REVISION "200101120000Z" -- Jan 12, 2001 + DESCRIPTION "Registration of new IANAifTypes + 195 and 196." + + REVISION "200012190000Z" -- Dec 19, 2000 + DESCRIPTION "Registration of new IANAifTypes + 193 and 194." + + REVISION "200012070000Z" -- Dec 07, 2000 + DESCRIPTION "Registration of new IANAifTypes + 191 and 192." + + REVISION "200012040000Z" -- Dec 04, 2000 + DESCRIPTION "Registration of new IANAifType + 190." + + REVISION "200010170000Z" -- Oct 17, 2000 + DESCRIPTION "Registration of new IANAifTypes + 188 and 189." + + REVISION "200010020000Z" -- Oct 02, 2000 + DESCRIPTION "Registration of new IANAifType 187." + + REVISION "200009010000Z" -- Sept 01, 2000 + DESCRIPTION "Registration of new IANAifTypes + 184, 185, and 186." + + REVISION "200008240000Z" -- Aug 24, 2000 + DESCRIPTION "Registration of new IANAifType 183." + + REVISION "200008230000Z" -- Aug 23, 2000 + DESCRIPTION "Registration of new IANAifTypes + 174-182." + + REVISION "200008220000Z" -- Aug 22, 2000 + DESCRIPTION "Registration of new IANAifTypes 170, + 171, 172 and 173." + + REVISION "200004250000Z" -- Apr 25, 2000 + DESCRIPTION "Registration of new IANAifTypes 168 and 169." + + REVISION "200003060000Z" -- Mar 6, 2000 + DESCRIPTION "Fixed a missing semi-colon in the IMPORT. + Also cleaned up the REVISION log a bit. + It is not complete, but from now on it will + be maintained and kept up to date with each + change to this MIB module." + + REVISION "199910081430Z" -- Oct 08, 1999 + DESCRIPTION "Include new name assignments up to cnr(85). + This is the first version available via the WWW + at: ftp://ftp.isi.edu/mib/ianaiftype.mib" + + REVISION "199401310000Z" -- Jan 31, 1994 + DESCRIPTION "Initial version of this MIB as published in + RFC 1573." + ::= { mib-2 30 } + + IANAifType ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "This data type is used as the syntax of the ifType + object in the (updated) definition of MIB-II's + ifTable. + + The definition of this textual convention with the + addition of newly assigned values is published + periodically by the IANA, in either the Assigned + Numbers RFC, or some derivative of it specific to + Internet Network Management number assignments. (The + latest arrangements can be obtained by contacting the + IANA.) + + Requests for new values should be made to IANA via + email (iana&iana.org). + + The relationship between the assignment of ifType + values and of OIDs to particular media-specific MIBs + is solely the purview of IANA and is subject to change + without notice. Quite often, a media-specific MIB's + OID-subtree assignment within MIB-II's 'transmission' + subtree will be the same as its ifType value. + However, in some circumstances this will not be the + case, and implementors must not pre-assume any + specific relationship between ifType values and + transmission subtree OIDs." + SYNTAX INTEGER { + other(1), -- none of the following + regular1822(2), + hdh1822(3), + ddnX25(4), + rfc877x25(5), + ethernetCsmacd(6), -- for all ethernet-like interfaces, + -- regardless of speed, as per RFC3635 + iso88023Csmacd(7), -- Deprecated via RFC3635 + -- ethernetCsmacd (6) should be used instead + iso88024TokenBus(8), + iso88025TokenRing(9), + iso88026Man(10), + starLan(11), -- Deprecated via RFC3635 + -- ethernetCsmacd (6) should be used instead + proteon10Mbit(12), + proteon80Mbit(13), + hyperchannel(14), + fddi(15), + lapb(16), + sdlc(17), + ds1(18), -- DS1-MIB + e1(19), -- Obsolete see DS1-MIB + basicISDN(20), -- no longer used + -- see also RFC2127 + primaryISDN(21), -- no longer used + -- see also RFC2127 + propPointToPointSerial(22), -- proprietary serial + ppp(23), + softwareLoopback(24), + eon(25), -- CLNP over IP + ethernet3Mbit(26), + nsip(27), -- XNS over IP + slip(28), -- generic SLIP + ultra(29), -- ULTRA technologies + ds3(30), -- DS3-MIB + sip(31), -- SMDS, coffee + frameRelay(32), -- DTE only. + rs232(33), + para(34), -- parallel-port + arcnet(35), -- arcnet + arcnetPlus(36), -- arcnet plus + atm(37), -- ATM cells + miox25(38), + sonet(39), -- SONET or SDH + x25ple(40), + iso88022llc(41), + localTalk(42), + smdsDxi(43), + frameRelayService(44), -- FRNETSERV-MIB + v35(45), + hssi(46), + hippi(47), + modem(48), -- Generic modem + aal5(49), -- AAL5 over ATM + sonetPath(50), + sonetVT(51), + smdsIcip(52), -- SMDS InterCarrier Interface + propVirtual(53), -- proprietary virtual/internal + propMultiplexor(54),-- proprietary multiplexing + ieee80212(55), -- 100BaseVG + fibreChannel(56), -- Fibre Channel + hippiInterface(57), -- HIPPI interfaces + frameRelayInterconnect(58), -- Obsolete, use either + -- frameRelay(32) or + -- frameRelayService(44). + aflane8023(59), -- ATM Emulated LAN for 802.3 + aflane8025(60), -- ATM Emulated LAN for 802.5 + cctEmul(61), -- ATM Emulated circuit + fastEther(62), -- Obsoleted via RFC3635 + -- ethernetCsmacd (6) should be used instead + isdn(63), -- ISDN and X.25 + v11(64), -- CCITT V.11/X.21 + v36(65), -- CCITT V.36 + g703at64k(66), -- CCITT G703 at 64Kbps + g703at2mb(67), -- Obsolete see DS1-MIB + qllc(68), -- SNA QLLC + fastEtherFX(69), -- Obsoleted via RFC3635 + -- ethernetCsmacd (6) should be used instead + channel(70), -- channel + ieee80211(71), -- radio spread spectrum + ibm370parChan(72), -- IBM System 360/370 OEMI Channel + escon(73), -- IBM Enterprise Systems Connection + dlsw(74), -- Data Link Switching + isdns(75), -- ISDN S/T interface + isdnu(76), -- ISDN U interface + lapd(77), -- Link Access Protocol D + ipSwitch(78), -- IP Switching Objects + rsrb(79), -- Remote Source Route Bridging + atmLogical(80), -- ATM Logical Port + ds0(81), -- Digital Signal Level 0 + ds0Bundle(82), -- group of ds0s on the same ds1 + bsc(83), -- Bisynchronous Protocol + async(84), -- Asynchronous Protocol + cnr(85), -- Combat Net Radio + iso88025Dtr(86), -- ISO 802.5r DTR + eplrs(87), -- Ext Pos Loc Report Sys + arap(88), -- Appletalk Remote Access Protocol + propCnls(89), -- Proprietary Connectionless Protocol + hostPad(90), -- CCITT-ITU X.29 PAD Protocol + termPad(91), -- CCITT-ITU X.3 PAD Facility + frameRelayMPI(92), -- Multiproto Interconnect over FR + x213(93), -- CCITT-ITU X213 + adsl(94), -- Asymmetric Digital Subscriber Loop + radsl(95), -- Rate-Adapt. Digital Subscriber Loop + sdsl(96), -- Symmetric Digital Subscriber Loop + vdsl(97), -- Very H-Speed Digital Subscrib. Loop + iso88025CRFPInt(98), -- ISO 802.5 CRFP + myrinet(99), -- Myricom Myrinet + voiceEM(100), -- voice recEive and transMit + voiceFXO(101), -- voice Foreign Exchange Office + voiceFXS(102), -- voice Foreign Exchange Station + voiceEncap(103), -- voice encapsulation + voiceOverIp(104), -- voice over IP encapsulation + atmDxi(105), -- ATM DXI + atmFuni(106), -- ATM FUNI + atmIma (107), -- ATM IMA + pppMultilinkBundle(108), -- PPP Multilink Bundle + ipOverCdlc (109), -- IBM ipOverCdlc + ipOverClaw (110), -- IBM Common Link Access to Workstn + stackToStack (111), -- IBM stackToStack + virtualIpAddress (112), -- IBM VIPA + mpc (113), -- IBM multi-protocol channel support + ipOverAtm (114), -- IBM ipOverAtm + iso88025Fiber (115), -- ISO 802.5j Fiber Token Ring + tdlc (116), -- IBM twinaxial data link control + gigabitEthernet (117), -- Obsoleted via RFC3635 + -- ethernetCsmacd (6) should be used instead + hdlc (118), -- HDLC + lapf (119), -- LAP F + v37 (120), -- V.37 + x25mlp (121), -- Multi-Link Protocol + x25huntGroup (122), -- X25 Hunt Group + transpHdlc (123), -- Transp HDLC + interleave (124), -- Interleave channel + fast (125), -- Fast channel + ip (126), -- IP (for APPN HPR in IP networks) + docsCableMaclayer (127), -- CATV Mac Layer + docsCableDownstream (128), -- CATV Downstream interface + docsCableUpstream (129), -- CATV Upstream interface + a12MppSwitch (130), -- Avalon Parallel Processor + tunnel (131), -- Encapsulation interface + coffee (132), -- coffee pot + ces (133), -- Circuit Emulation Service + atmSubInterface (134), -- ATM Sub Interface + l2vlan (135), -- Layer 2 Virtual LAN using 802.1Q + l3ipvlan (136), -- Layer 3 Virtual LAN using IP + l3ipxvlan (137), -- Layer 3 Virtual LAN using IPX + digitalPowerline (138), -- IP over Power Lines + mediaMailOverIp (139), -- Multimedia Mail over IP + dtm (140), -- Dynamic syncronous Transfer Mode + dcn (141), -- Data Communications Network + ipForward (142), -- IP Forwarding Interface + msdsl (143), -- Multi-rate Symmetric DSL + ieee1394 (144), -- IEEE1394 High Performance Serial Bus + if-gsn (145), -- HIPPI-6400 + dvbRccMacLayer (146), -- DVB-RCC MAC Layer + dvbRccDownstream (147), -- DVB-RCC Downstream Channel + dvbRccUpstream (148), -- DVB-RCC Upstream Channel + atmVirtual (149), -- ATM Virtual Interface + mplsTunnel (150), -- MPLS Tunnel Virtual Interface + srp (151), -- Spatial Reuse Protocol + voiceOverAtm (152), -- Voice Over ATM + voiceOverFrameRelay (153), -- Voice Over Frame Relay + idsl (154), -- Digital Subscriber Loop over ISDN + compositeLink (155), -- Avici Composite Link Interface + ss7SigLink (156), -- SS7 Signaling Link + propWirelessP2P (157), -- Prop. P2P wireless interface + frForward (158), -- Frame Forward Interface + rfc1483 (159), -- Multiprotocol over ATM AAL5 + usb (160), -- USB Interface + ieee8023adLag (161), -- IEEE 802.3ad Link Aggregate + bgppolicyaccounting (162), -- BGP Policy Accounting + frf16MfrBundle (163), -- FRF .16 Multilink Frame Relay + h323Gatekeeper (164), -- H323 Gatekeeper + h323Proxy (165), -- H323 Voice and Video Proxy + mpls (166), -- MPLS + mfSigLink (167), -- Multi-frequency signaling link + hdsl2 (168), -- High Bit-Rate DSL - 2nd generation + shdsl (169), -- Multirate HDSL2 + ds1FDL (170), -- Facility Data Link 4Kbps on a DS1 + pos (171), -- Packet over SONET/SDH Interface + dvbAsiIn (172), -- DVB-ASI Input + dvbAsiOut (173), -- DVB-ASI Output + plc (174), -- Power Line Communtications + nfas (175), -- Non Facility Associated Signaling + tr008 (176), -- TR008 + gr303RDT (177), -- Remote Digital Terminal + gr303IDT (178), -- Integrated Digital Terminal + isup (179), -- ISUP + propDocsWirelessMaclayer (180), -- Cisco proprietary Maclayer + propDocsWirelessDownstream (181), -- Cisco proprietary Downstream + propDocsWirelessUpstream (182), -- Cisco proprietary Upstream + hiperlan2 (183), -- HIPERLAN Type 2 Radio Interface + propBWAp2Mp (184), -- PropBroadbandWirelessAccesspt2multipt + -- use of this iftype for IEEE 802.16 WMAN + -- interfaces as per IEEE Std 802.16f is + -- deprecated and ifType 237 should be used instead. + sonetOverheadChannel (185), -- SONET Overhead Channel + digitalWrapperOverheadChannel (186), -- Digital Wrapper + aal2 (187), -- ATM adaptation layer 2 + radioMAC (188), -- MAC layer over radio links + atmRadio (189), -- ATM over radio links + imt (190), -- Inter Machine Trunks + mvl (191), -- Multiple Virtual Lines DSL + reachDSL (192), -- Long Reach DSL + frDlciEndPt (193), -- Frame Relay DLCI End Point + atmVciEndPt (194), -- ATM VCI End Point + opticalChannel (195), -- Optical Channel + opticalTransport (196), -- Optical Transport + propAtm (197), -- Proprietary ATM + voiceOverCable (198), -- Voice Over Cable Interface + infiniband (199), -- Infiniband + teLink (200), -- TE Link + q2931 (201), -- Q.2931 + virtualTg (202), -- Virtual Trunk Group + sipTg (203), -- SIP Trunk Group + sipSig (204), -- SIP Signaling + docsCableUpstreamChannel (205), -- CATV Upstream Channel + econet (206), -- Acorn Econet + pon155 (207), -- FSAN 155Mb Symetrical PON interface + pon622 (208), -- FSAN622Mb Symetrical PON interface + bridge (209), -- Transparent bridge interface + linegroup (210), -- Interface common to multiple lines + voiceEMFGD (211), -- voice E&M Feature Group D + voiceFGDEANA (212), -- voice FGD Exchange Access North American + voiceDID (213), -- voice Direct Inward Dialing + mpegTransport (214), -- MPEG transport interface + sixToFour (215), -- 6to4 interface (DEPRECATED) + gtp (216), -- GTP (GPRS Tunneling Protocol) + pdnEtherLoop1 (217), -- Paradyne EtherLoop 1 + pdnEtherLoop2 (218), -- Paradyne EtherLoop 2 + opticalChannelGroup (219), -- Optical Channel Group + homepna (220), -- HomePNA ITU-T G.989 + gfp (221), -- Generic Framing Procedure (GFP) + ciscoISLvlan (222), -- Layer 2 Virtual LAN using Cisco ISL + actelisMetaLOOP (223), -- Acteleis proprietary MetaLOOP High Speed Link + fcipLink (224), -- FCIP Link + rpr (225), -- Resilient Packet Ring Interface Type + qam (226), -- RF Qam Interface + lmp (227), -- Link Management Protocol + cblVectaStar (228), -- Cambridge Broadband Networks Limited VectaStar + docsCableMCmtsDownstream (229), -- CATV Modular CMTS Downstream Interface + adsl2 (230), -- Asymmetric Digital Subscriber Loop Version 2 + -- (DEPRECATED/OBSOLETED - please use adsl2plus 238 instead) + macSecControlledIF (231), -- MACSecControlled + macSecUncontrolledIF (232), -- MACSecUncontrolled + aviciOpticalEther (233), -- Avici Optical Ethernet Aggregate + atmbond (234), -- atmbond + voiceFGDOS (235), -- voice FGD Operator Services + mocaVersion1 (236), -- MultiMedia over Coax Alliance (MoCA) Interface + -- as documented in information provided privately to IANA + ieee80216WMAN (237), -- IEEE 802.16 WMAN interface + adsl2plus (238), -- Asymmetric Digital Subscriber Loop Version 2, + -- Version 2 Plus and all variants + dvbRcsMacLayer (239), -- DVB-RCS MAC Layer + dvbTdm (240), -- DVB Satellite TDM + dvbRcsTdma (241), -- DVB-RCS TDMA + x86Laps (242), -- LAPS based on ITU-T X.86/Y.1323 + wwanPP (243), -- 3GPP WWAN + wwanPP2 (244), -- 3GPP2 WWAN + voiceEBS (245), -- voice P-phone EBS physical interface + ifPwType (246), -- Pseudowire interface type + ilan (247), -- Internal LAN on a bridge per IEEE 802.1ap + pip (248), -- Provider Instance Port on a bridge per IEEE 802.1ah PBB + aluELP (249), -- Alcatel-Lucent Ethernet Link Protection + gpon (250), -- Gigabit-capable passive optical networks (G-PON) as per ITU-T G.948 + vdsl2 (251), -- Very high speed digital subscriber line Version 2 (as per ITU-T Recommendation G.993.2) + capwapDot11Profile (252), -- WLAN Profile Interface + capwapDot11Bss (253), -- WLAN BSS Interface + capwapWtpVirtualRadio (254), -- WTP Virtual Radio Interface + bits (255), -- bitsport + docsCableUpstreamRfPort (256), -- DOCSIS CATV Upstream RF Port + cableDownstreamRfPort (257), -- CATV downstream RF port + vmwareVirtualNic (258), -- VMware Virtual Network Interface + ieee802154 (259), -- IEEE 802.15.4 WPAN interface + otnOdu (260), -- OTN Optical Data Unit + otnOtu (261), -- OTN Optical channel Transport Unit + ifVfiType (262), -- VPLS Forwarding Instance Interface Type + g9981 (263), -- G.998.1 bonded interface + g9982 (264), -- G.998.2 bonded interface + g9983 (265), -- G.998.3 bonded interface + aluEpon (266), -- Ethernet Passive Optical Networks (E-PON) + aluEponOnu (267), -- EPON Optical Network Unit + aluEponPhysicalUni (268), -- EPON physical User to Network interface + aluEponLogicalLink (269), -- The emulation of a point-to-point link over the EPON layer + aluGponOnu (270), -- GPON Optical Network Unit + aluGponPhysicalUni (271), -- GPON physical User to Network interface + vmwareNicTeam (272), -- VMware NIC Team + docsOfdmDownstream (277), -- CATV Downstream OFDM interface + docsOfdmaUpstream (278) -- CATV Upstream OFDMA interface + } + +IANAtunnelType ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The encapsulation method used by a tunnel. The value + direct indicates that a packet is encapsulated + directly within a normal IP header, with no + intermediate header, and unicast to the remote tunnel + endpoint (e.g., an RFC 2003 IP-in-IP tunnel, or an RFC + 1933 IPv6-in-IPv4 tunnel). The value minimal indicates + that a Minimal Forwarding Header (RFC 2004) is + inserted between the outer header and the payload + packet. The value UDP indicates that the payload + packet is encapsulated within a normal UDP packet + (e.g., RFC 1234). + + The values sixToFour, sixOverFour, and isatap + indicates that an IPv6 packet is encapsulated directly + within an IPv4 header, with no intermediate header, + and unicast to the destination determined by the 6to4, + 6over4, or ISATAP protocol. + + The remaining protocol-specific values indicate that a + header of the protocol of that name is inserted + between the outer header and the payload header. + + The assignment policy for IANAtunnelType values is + identical to the policy for assigning IANAifType + values." + SYNTAX INTEGER { + other(1), -- none of the following + direct(2), -- no intermediate header + gre(3), -- GRE encapsulation + minimal(4), -- Minimal encapsulation + l2tp(5), -- L2TP encapsulation + pptp(6), -- PPTP encapsulation + l2f(7), -- L2F encapsulation + udp(8), -- UDP encapsulation + atmp(9), -- ATMP encapsulation + msdp(10), -- MSDP encapsulation + sixToFour(11), -- 6to4 encapsulation + sixOverFour(12), -- 6over4 encapsulation + isatap(13), -- ISATAP encapsulation + teredo(14), -- Teredo encapsulation + ipHttps(15) -- IPHTTPS + } + + END diff --git a/op-mode-definitions/container.xml.in b/op-mode-definitions/container.xml.in index 786bd66d3..ada9a4d59 100644 --- a/op-mode-definitions/container.xml.in +++ b/op-mode-definitions/container.xml.in @@ -11,7 +11,7 @@ <properties> <help>Pull a new image for container</help> </properties> - <command>sudo podman image pull "${4}"</command> + <command>sudo ${vyos_op_scripts_dir}/container.py add_image --name "${4}"</command> </tagNode> </children> </node> @@ -44,7 +44,7 @@ <script>sudo podman image ls -q</script> </completionHelp> </properties> - <command>sudo podman image rm --force "${4}"</command> + <command>sudo ${vyos_op_scripts_dir}/container.py delete_image --name "${4}"</command> </tagNode> </children> </node> diff --git a/op-mode-definitions/date.xml.in b/op-mode-definitions/date.xml.in index 15a69dbd9..6d8586025 100644 --- a/op-mode-definitions/date.xml.in +++ b/op-mode-definitions/date.xml.in @@ -37,28 +37,6 @@ </properties> <command>/bin/date "$3"</command> </tagNode> - <node name="date"> - <properties> - <help>Set system date and time</help> - </properties> - <children> - <node name="ntp"> - <properties> - <help>Set system date and time from NTP server (default: 0.pool.ntp.org)</help> - </properties> - <command>/usr/sbin/ntpdate -u 0.pool.ntp.org</command> - </node> - <tagNode name="ntp"> - <properties> - <help>Set system date and time from NTP server</help> - <completionHelp> - <script>${vyos_completion_dir}/list_ntp_servers.sh</script> - </completionHelp> - </properties> - <command>/usr/sbin/ntpdate -u "$4"</command> - </tagNode> - </children> - </node> </children> </node> </interfaceDefinition> diff --git a/op-mode-definitions/dhcp.xml.in b/op-mode-definitions/dhcp.xml.in index ce4026ff4..419abe7ad 100644 --- a/op-mode-definitions/dhcp.xml.in +++ b/op-mode-definitions/dhcp.xml.in @@ -16,7 +16,7 @@ <properties> <help>Show DHCP server leases</help> </properties> - <command>sudo ${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet</command> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet</command> <children> <tagNode name="pool"> <properties> @@ -25,25 +25,25 @@ <path>service dhcp-server shared-network-name</path> </completionHelp> </properties> - <command>sudo ${vyos_op_scripts_dir}/show_dhcp.py --leases --pool $6</command> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --pool $6</command> </tagNode> <tagNode name="sort"> <properties> <help>Show DHCP server leases sorted by the specified key</help> <completionHelp> - <script>sudo ${vyos_op_scripts_dir}/show_dhcp.py --allowed sort</script> + <list>end hostname ip mac pool remaining start state</list> </completionHelp> </properties> - <command>sudo ${vyos_op_scripts_dir}/show_dhcp.py --leases --sort $6</command> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --sort $6</command> </tagNode> <tagNode name="state"> <properties> <help>Show DHCP server leases with a specific state (can be multiple, comma-separated)</help> <completionHelp> - <script>sudo ${vyos_op_scripts_dir}/show_dhcp.py --allowed state</script> + <list>abandoned active all backup expired free released reset</list> </completionHelp> </properties> - <command>sudo ${vyos_op_scripts_dir}/show_dhcp.py --leases --state $(echo $6 | tr , " ")</command> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --state $6</command> </tagNode> </children> </node> @@ -51,7 +51,7 @@ <properties> <help>Show DHCP server statistics</help> </properties> - <command>sudo ${vyos_op_scripts_dir}/show_dhcp.py --statistics</command> + <command>${vyos_op_scripts_dir}/dhcp.py show_pool_statistics --family inet</command> <children> <tagNode name="pool"> <properties> @@ -60,7 +60,7 @@ <path>service dhcp-server shared-network-name</path> </completionHelp> </properties> - <command>sudo ${vyos_op_scripts_dir}/show_dhcp.py --statistics --pool $6</command> + <command>${vyos_op_scripts_dir}/dhcp.py show_pool_statistics --family inet --pool $6</command> </tagNode> </children> </node> @@ -88,28 +88,28 @@ <properties> <help>Show DHCPv6 server leases for a specific pool</help> <completionHelp> - <script>sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --allowed pool</script> + <path>service dhcpv6-server shared-network-name</path> </completionHelp> </properties> - <command>sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases --pool $6</command> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --pool $6</command> </tagNode> <tagNode name="sort"> <properties> <help>Show DHCPv6 server leases sorted by the specified key</help> <completionHelp> - <script>sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --allowed sort</script> + <list>end iaid_duid ip last_communication pool remaining state type</list> </completionHelp> </properties> - <command>sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases --sort $6</command> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --sort $6</command> </tagNode> <tagNode name="state"> <properties> <help>Show DHCPv6 server leases with a specific state (can be multiple, comma-separated)</help> <completionHelp> - <script>sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --allowed state</script> + <list>abandoned active all backup expired free released reset</list> </completionHelp> </properties> - <command>sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases --state $(echo $6 | tr , " ")</command> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --state $6</command> </tagNode> </children> </node> diff --git a/op-mode-definitions/generate-wireguard.xml.in b/op-mode-definitions/generate-wireguard.xml.in index 259c9a898..0ef983cd2 100644 --- a/op-mode-definitions/generate-wireguard.xml.in +++ b/op-mode-definitions/generate-wireguard.xml.in @@ -7,24 +7,6 @@ <help>Generate WireGuard keys</help> </properties> <children> - <leafNode name="default-keypair"> - <properties> - <help>generates the wireguard default-keypair</help> - </properties> - <command>echo "This command is deprecated. Please use: \"generate pki wireguard key-pair\""</command> - </leafNode> - <leafNode name="preshared-key"> - <properties> - <help>generate a wireguard preshared key</help> - </properties> - <command>echo "This command is deprecated. Please use: \"generate pki wireguard pre-shared-key\""</command> - </leafNode> - <tagNode name="named-keypairs"> - <properties> - <help>Generates named wireguard keypairs</help> - </properties> - <command>echo "This command is deprecated. Please use: \"generate pki wireguard key-pair install wgN\""</command> - </tagNode> <tagNode name="client-config"> <properties> <help>Generate Client config QR code</help> diff --git a/op-mode-definitions/include/show-route-summary.xml.i b/op-mode-definitions/include/show-route-summary.xml.i deleted file mode 100644 index 471124562..000000000 --- a/op-mode-definitions/include/show-route-summary.xml.i +++ /dev/null @@ -1,8 +0,0 @@ -<!-- included start from show-route-summary.xml.i --> -<leafNode name="summary"> - <properties> - <help>Summary of all routes</help> - </properties> - <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> -</leafNode> -<!-- included end --> diff --git a/op-mode-definitions/lldp.xml.in b/op-mode-definitions/lldp.xml.in index 297ccf1f4..07cafa77f 100644 --- a/op-mode-definitions/lldp.xml.in +++ b/op-mode-definitions/lldp.xml.in @@ -11,14 +11,8 @@ <properties> <help>Show LLDP neighbors</help> </properties> - <command>${vyos_op_scripts_dir}/lldp_op.py --all</command> + <command>${vyos_op_scripts_dir}/lldp.py show_neighbors</command> <children> - <node name="detail"> - <properties> - <help>Show LLDP neighbor details</help> - </properties> - <command>${vyos_op_scripts_dir}/lldp_op.py --detail</command> - </node> <tagNode name="interface"> <properties> <help>Show LLDP for specified interface</help> @@ -26,7 +20,7 @@ <script>${vyos_completion_dir}/list_interfaces.py</script> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/lldp_op.py --interface $5</command> + <command>${vyos_op_scripts_dir}/lldp.py show_neighbors --interface $5</command> </tagNode> </children> </node> diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index 8abc5f4db..b68047bb9 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -101,9 +101,15 @@ </leafNode> <leafNode name="nhrp"> <properties> - <help>Monitor last lines of NHRP log</help> + <help>Monitor last lines of Next Hop Resolution Protocol (NHRP) log</help> </properties> - <command>journalctl --no-hostname --boot --unit opennhrp.service</command> + <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> + </properties> + <command>journalctl --no-hostname --boot --follow --unit chrony.service</command> </leafNode> <node name="pppoe"> <properties> @@ -249,14 +255,14 @@ </node> <node name="vpn"> <properties> - <help>Show log for Virtual Private Network (VPN)</help> + <help>Monitor Virtual Private Network (VPN) services</help> </properties> <children> <leafNode name="all"> <properties> <help>Monitor last lines of ALL VPNs</help> </properties> - <command>journalctl --no-hostname --boot --follow --unit strongswan-starter.service --unit accel-ppp@*.service</command> + <command>journalctl --no-hostname --boot --follow --unit strongswan-starter.service --unit accel-ppp@*.service --unit ocserv.service</command> </leafNode> <leafNode name="ipsec"> <properties> @@ -270,6 +276,12 @@ </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> + </properties> + <command>journalctl --no-hostname --boot --follow --unit ocserv.service</command> + </leafNode> <leafNode name="pptp"> <properties> <help>Monitor last lines of PPTP</help> diff --git a/op-mode-definitions/nat.xml.in b/op-mode-definitions/nat.xml.in index 50abb1555..307a91337 100644 --- a/op-mode-definitions/nat.xml.in +++ b/op-mode-definitions/nat.xml.in @@ -4,7 +4,7 @@ <children> <node name="nat"> <properties> - <help>Show IPv4 to IPv4 Network Address Translation (NAT) information</help> + <help>Show IPv4 Network Address Translation (NAT) information</help> </properties> <children> <node name="source"> @@ -16,13 +16,13 @@ <properties> <help>Show configured source NAT rules</help> </properties> - <command>${vyos_op_scripts_dir}/nat.py show_rules --direction source --family inet</command> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_rules --direction source --family inet</command> </node> <node name="statistics"> <properties> <help>Show statistics for configured source NAT rules</help> </properties> - <command>${vyos_op_scripts_dir}/nat.py show_statistics --direction source --family inet</command> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_statistics --direction source --family inet</command> </node> <node name="translations"> <properties> @@ -36,16 +36,10 @@ <list><x.x.x.x></list> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_nat_translations.py --type=source --verbose --ipaddr="$6"</command> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet --address "$6"</command> </tagNode> - <node name="detail"> - <properties> - <help>Show active source NAT translations detail</help> - </properties> - <command>${vyos_op_scripts_dir}/show_nat_translations.py --type=source --verbose</command> - </node> </children> - <command>${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet</command> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet</command> </node> </children> </node> @@ -58,13 +52,13 @@ <properties> <help>Show configured destination NAT rules</help> </properties> - <command>${vyos_op_scripts_dir}/nat.py show_rules --direction destination --family inet</command> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_rules --direction destination --family inet</command> </node> <node name="statistics"> <properties> <help>Show statistics for configured destination NAT rules</help> </properties> - <command>${vyos_op_scripts_dir}/nat.py show_statistics --direction destination --family inet</command> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_statistics --direction destination --family inet</command> </node> <node name="translations"> <properties> @@ -78,16 +72,10 @@ <list><x.x.x.x></list> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_nat_translations.py --type=destination --verbose --ipaddr="$6"</command> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet --address "$6"</command> </tagNode> - <node name="detail"> - <properties> - <help>Show active destination NAT translations detail</help> - </properties> - <command>${vyos_op_scripts_dir}/show_nat_translations.py --type=destination --verbose</command> - </node> </children> - <command>${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet</command> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet</command> </node> </children> </node> diff --git a/op-mode-definitions/nat66.xml.in b/op-mode-definitions/nat66.xml.in index 25aa04d59..6a8a39000 100644 --- a/op-mode-definitions/nat66.xml.in +++ b/op-mode-definitions/nat66.xml.in @@ -4,7 +4,7 @@ <children> <node name="nat66"> <properties> - <help>Show IPv6 to IPv6 Network Address Translation (NAT66) information</help> + <help>Show IPv6 Network Address Translation (NAT66) information</help> </properties> <children> <node name="source"> @@ -22,7 +22,7 @@ <properties> <help>Show statistics for configured source NAT66 rules</help> </properties> - <command>${vyos_op_scripts_dir}/show_nat66_statistics.py --source</command> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_statistics --direction source --family inet6</command> </node> <node name="translations"> <properties> @@ -36,14 +36,8 @@ <list><h:h:h:h:h:h:h:h></list> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_nat66_translations.py --type=source --verbose --ipaddr="$6"</command> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet6 --address "$6"</command> </tagNode> - <node name="detail"> - <properties> - <help>Show active source NAT66 translations detail</help> - </properties> - <command>${vyos_op_scripts_dir}/show_nat66_translations.py --type=source --verbose</command> - </node> </children> <command>${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet6</command> </node> @@ -64,7 +58,7 @@ <properties> <help>Show statistics for configured destination NAT66 rules</help> </properties> - <command>${vyos_op_scripts_dir}/show_nat66_statistics.py --destination</command> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_statistics --direction destination --family inet6</command> </node> <node name="translations"> <properties> @@ -78,14 +72,8 @@ <list><h:h:h:h:h:h:h:h></list> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_nat66_translations.py --type=destination --verbose --ipaddr="$6"</command> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet6 --address "$6"</command> </tagNode> - <node name="detail"> - <properties> - <help>Show active destination NAT66 translations detail</help> - </properties> - <command>${vyos_op_scripts_dir}/show_nat66_translations.py --type=destination --verbose</command> - </node> </children> <command>${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet6</command> </node> diff --git a/op-mode-definitions/openvpn.xml.in b/op-mode-definitions/openvpn.xml.in index aec09fa48..b2763da81 100644 --- a/op-mode-definitions/openvpn.xml.in +++ b/op-mode-definitions/openvpn.xml.in @@ -37,12 +37,13 @@ <properties> <help>Show OpenVPN interface information</help> </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=openvpn</command> <children> <leafNode name="detail"> <properties> <help>Show detailed OpenVPN interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=openvpn --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=openvpn</command> </leafNode> </children> </node> @@ -53,7 +54,7 @@ <script>sudo ${vyos_completion_dir}/list_interfaces.py --type openvpn</script> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf=$4</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name=$4</command> <children> <tagNode name="user"> <properties> @@ -94,7 +95,7 @@ <properties> <help>Show summary of specified OpenVPN interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4"</command> </leafNode> </children> </tagNode> diff --git a/op-mode-definitions/show-interfaces-bonding.xml.in b/op-mode-definitions/show-interfaces-bonding.xml.in index c5f82b70e..c41e7bd5f 100644 --- a/op-mode-definitions/show-interfaces-bonding.xml.in +++ b/op-mode-definitions/show-interfaces-bonding.xml.in @@ -11,13 +11,13 @@ <path>interfaces bonding</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=bonding</command> <children> <leafNode name="brief"> <properties> <help>Show summary of the specified bonding interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=bonding</command> </leafNode> <leafNode name="detail"> <properties> @@ -38,13 +38,13 @@ <path>interfaces bonding ${COMP_WORDS[3]} vif</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4.$6" --intf_type=bonding</command> <children> <leafNode name="brief"> <properties> <help>Show summary of specified virtual network interface (vif) information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4.$6" --intf_type=bonding</command> </leafNode> </children> </tagNode> @@ -60,13 +60,13 @@ <properties> <help>Show Bonding interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=bonding --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=bonding</command> <children> <leafNode name="detail"> <properties> <help>Show detailed bonding interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=bonding --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=bonding</command> </leafNode> <leafNode name="slaves"> <properties> diff --git a/op-mode-definitions/show-interfaces-bridge.xml.in b/op-mode-definitions/show-interfaces-bridge.xml.in index e1444bd84..22cd3ee67 100644 --- a/op-mode-definitions/show-interfaces-bridge.xml.in +++ b/op-mode-definitions/show-interfaces-bridge.xml.in @@ -11,13 +11,13 @@ <path>interfaces bridge</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=bridge</command> <children> <leafNode name="brief"> <properties> <help>Show summary of the specified bridge interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=bridge</command> </leafNode> </children> </tagNode> @@ -25,13 +25,13 @@ <properties> <help>Show Bridge interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=bridge --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=bridge</command> <children> <leafNode name="detail"> <properties> <help>Show detailed bridge interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=bridge --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=bridge</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces-dummy.xml.in b/op-mode-definitions/show-interfaces-dummy.xml.in index 52d2cc7ee..958d3483d 100644 --- a/op-mode-definitions/show-interfaces-dummy.xml.in +++ b/op-mode-definitions/show-interfaces-dummy.xml.in @@ -11,13 +11,13 @@ <path>interfaces dummy</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=dummy</command> <children> <leafNode name="brief"> <properties> <help>Show summary of the specified dummy interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=dummy</command> </leafNode> </children> </tagNode> @@ -25,13 +25,13 @@ <properties> <help>Show Dummy interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=dummy --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=dummy</command> <children> <leafNode name="detail"> <properties> <help>Show detailed dummy interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=dummy --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=dummy</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces-ethernet.xml.in b/op-mode-definitions/show-interfaces-ethernet.xml.in index f8d1c9395..81759c2b6 100644 --- a/op-mode-definitions/show-interfaces-ethernet.xml.in +++ b/op-mode-definitions/show-interfaces-ethernet.xml.in @@ -11,13 +11,13 @@ <path>interfaces ethernet</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=ethernet</command> <children> <leafNode name="brief"> <properties> <help>Show summary of the specified ethernet interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=ethernet</command> </leafNode> <leafNode name="identify"> <properties> @@ -58,13 +58,13 @@ <path>interfaces ethernet ${COMP_WORDS[3]} vif</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4.$6" --intf_type=ethernet</command> <children> <leafNode name="brief"> <properties> <help>Show summary of specified virtual network interface (vif) information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4.$6" --intf_type=ethernet</command> </leafNode> </children> </tagNode> @@ -80,13 +80,13 @@ <properties> <help>Show Ethernet interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=ethernet --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=ethernet</command> <children> <leafNode name="detail"> <properties> <help>Show detailed ethernet interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=ethernet --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=ethernet</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces-geneve.xml.in b/op-mode-definitions/show-interfaces-geneve.xml.in index a47933315..3cf45878d 100644 --- a/op-mode-definitions/show-interfaces-geneve.xml.in +++ b/op-mode-definitions/show-interfaces-geneve.xml.in @@ -11,13 +11,13 @@ <path>interfaces geneve</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=geneve</command> <children> <leafNode name="brief"> <properties> <help>Show summary of the specified GENEVE interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=geneve</command> </leafNode> </children> </tagNode> @@ -25,13 +25,13 @@ <properties> <help>Show GENEVE interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=geneve --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=geneve</command> <children> <leafNode name="detail"> <properties> <help>Show detailed GENEVE interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=geneve --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=geneve</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces-input.xml.in b/op-mode-definitions/show-interfaces-input.xml.in index 9ae3828c8..5d93dcee6 100644 --- a/op-mode-definitions/show-interfaces-input.xml.in +++ b/op-mode-definitions/show-interfaces-input.xml.in @@ -11,13 +11,13 @@ <path>interfaces input</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=input</command> <children> <leafNode name="brief"> <properties> <help>Show summary of the specified input interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=input</command> </leafNode> </children> </tagNode> @@ -25,13 +25,13 @@ <properties> <help>Show Input (ifb) interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=input --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=input</command> <children> <leafNode name="detail"> <properties> <help>Show detailed input interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=input --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=input</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces-l2tpv3.xml.in b/op-mode-definitions/show-interfaces-l2tpv3.xml.in index 2a1d6a1c6..713e36dac 100644 --- a/op-mode-definitions/show-interfaces-l2tpv3.xml.in +++ b/op-mode-definitions/show-interfaces-l2tpv3.xml.in @@ -11,13 +11,13 @@ <path>interfaces l2tpv3</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=l2tpv3</command> <children> <leafNode name="brief"> <properties> <help>Show summary of the specified L2TPv3 interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=l2tpv3</command> </leafNode> </children> </tagNode> @@ -25,13 +25,13 @@ <properties> <help>Show L2TPv3 interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=l2tpv3 --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=l2tpv3</command> <children> <leafNode name="detail"> <properties> <help>Show detailed L2TPv3 interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=l2tpv3 --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=l2tpv3</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces-loopback.xml.in b/op-mode-definitions/show-interfaces-loopback.xml.in index 25a75ffff..a24151cc3 100644 --- a/op-mode-definitions/show-interfaces-loopback.xml.in +++ b/op-mode-definitions/show-interfaces-loopback.xml.in @@ -11,13 +11,13 @@ <path>interfaces loopback</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=loopback</command> <children> <leafNode name="brief"> <properties> - <help>Show summary of the specified dummy interface information</help> + <help>Show summary of the specified Loopback interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=loopback</command> </leafNode> </children> </tagNode> @@ -25,13 +25,13 @@ <properties> <help>Show Loopback interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=loopback --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=loopback</command> <children> <leafNode name="detail"> <properties> - <help>Show detailed dummy interface information</help> + <help>Show detailed Loopback interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=dummy --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=loopback</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces-pppoe.xml.in b/op-mode-definitions/show-interfaces-pppoe.xml.in index 09608bc04..a34473148 100644 --- a/op-mode-definitions/show-interfaces-pppoe.xml.in +++ b/op-mode-definitions/show-interfaces-pppoe.xml.in @@ -11,7 +11,7 @@ <path>interfaces pppoe</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=pppoe</command> <children> <leafNode name="log"> <properties> @@ -34,13 +34,13 @@ <properties> <help>Show PPPoE interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=pppoe --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=pppoe</command> <children> <leafNode name="detail"> <properties> <help>Show detailed PPPoE interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=pppoe --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=pppoe</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in b/op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in index 2ae4b5a9e..cb62639ee 100644 --- a/op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in +++ b/op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in @@ -11,13 +11,13 @@ <path>interfaces pseudo-ethernet</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=pseudo-ethernet</command> <children> <leafNode name="brief"> <properties> <help>Show summary of the specified pseudo-ethernet/MACvlan interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=pseudo-ethernet</command> </leafNode> </children> </tagNode> @@ -25,13 +25,13 @@ <properties> <help>Show Pseudo-Ethernet/MACvlan interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=pseudo-ethernet --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=pseudo-ethernet</command> <children> <leafNode name="detail"> <properties> <help>Show detailed pseudo-ethernet/MACvlan interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=pseudo-ethernet --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=pseudo-ethernet</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces-sstpc.xml.in b/op-mode-definitions/show-interfaces-sstpc.xml.in index e66d3a0ac..a619a9fd2 100644 --- a/op-mode-definitions/show-interfaces-sstpc.xml.in +++ b/op-mode-definitions/show-interfaces-sstpc.xml.in @@ -11,7 +11,7 @@ <path>interfaces sstpc</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=sstpc</command> <children> <leafNode name="log"> <properties> @@ -34,13 +34,13 @@ <properties> <help>Show SSTP client interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=sstpc --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=sstpc</command> <children> <leafNode name="detail"> <properties> <help>Show detailed SSTP client interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=sstpc --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=sstpc</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces-tunnel.xml.in b/op-mode-definitions/show-interfaces-tunnel.xml.in index 51b25efd9..10e10e655 100644 --- a/op-mode-definitions/show-interfaces-tunnel.xml.in +++ b/op-mode-definitions/show-interfaces-tunnel.xml.in @@ -11,13 +11,13 @@ <path>interfaces tunnel</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=tunnel</command> <children> <leafNode name="brief"> <properties> <help>Show summary of the specified tunnel interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=tunnel</command> </leafNode> </children> </tagNode> @@ -25,13 +25,13 @@ <properties> <help>Show Tunnel interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=tunnel --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=tunnel</command> <children> <leafNode name="detail"> <properties> <help>Show detailed tunnel interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=tunnel --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=tunnel</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces-virtual-ethernet.xml.in b/op-mode-definitions/show-interfaces-virtual-ethernet.xml.in index c70f1e3d1..c743492fb 100644 --- a/op-mode-definitions/show-interfaces-virtual-ethernet.xml.in +++ b/op-mode-definitions/show-interfaces-virtual-ethernet.xml.in @@ -11,13 +11,13 @@ <path>interfaces virtual-ethernet</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=virtual-ethernet</command> <children> <leafNode name="brief"> <properties> <help>Show summary of the specified virtual-ethernet interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=virtual-ethernet</command> </leafNode> </children> </tagNode> @@ -25,13 +25,13 @@ <properties> <help>Show virtual-ethernet interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=virtual-ethernet --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=virtual-ethernet</command> <children> <leafNode name="detail"> <properties> <help>Show detailed virtual-ethernet interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=virtual-ethernet --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=virtual-ethernet</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces-vti.xml.in b/op-mode-definitions/show-interfaces-vti.xml.in index b436b8414..d532894b7 100644 --- a/op-mode-definitions/show-interfaces-vti.xml.in +++ b/op-mode-definitions/show-interfaces-vti.xml.in @@ -11,13 +11,13 @@ <path>interfaces vti</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=vti</command> <children> <leafNode name="brief"> <properties> <help>Show summary of the specified vti interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=vti</command> </leafNode> </children> </tagNode> @@ -25,13 +25,13 @@ <properties> <help>Show VTI interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=vti --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=vti</command> <children> <leafNode name="detail"> <properties> <help>Show detailed vti interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=vti --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=vti</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces-vxlan.xml.in b/op-mode-definitions/show-interfaces-vxlan.xml.in index 1befd428c..fde832551 100644 --- a/op-mode-definitions/show-interfaces-vxlan.xml.in +++ b/op-mode-definitions/show-interfaces-vxlan.xml.in @@ -11,13 +11,13 @@ <path>interfaces vxlan</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=vxlan</command> <children> <leafNode name="brief"> <properties> <help>Show summary of the specified VXLAN interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=vxlan</command> </leafNode> </children> </tagNode> @@ -25,13 +25,13 @@ <properties> <help>Show VXLAN interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=vxlan --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=vxlan</command> <children> <leafNode name="detail"> <properties> <help>Show detailed VXLAN interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=vxlan --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=vxlan</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces-wireguard.xml.in b/op-mode-definitions/show-interfaces-wireguard.xml.in index c9b754dcd..eba8de568 100644 --- a/op-mode-definitions/show-interfaces-wireguard.xml.in +++ b/op-mode-definitions/show-interfaces-wireguard.xml.in @@ -11,7 +11,7 @@ <script>${vyos_completion_dir}/list_interfaces.py --type wireguard</script> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=wireguard</command> <children> <leafNode name="allowed-ips"> <properties> @@ -49,13 +49,13 @@ <properties> <help>Show WireGuard interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireguard --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=wireguard</command> <children> <leafNode name="detail"> <properties> <help>Show detailed Wireguard interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireguard --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=wireguard</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces-wireless.xml.in b/op-mode-definitions/show-interfaces-wireless.xml.in index 4a37417aa..b0a272225 100644 --- a/op-mode-definitions/show-interfaces-wireless.xml.in +++ b/op-mode-definitions/show-interfaces-wireless.xml.in @@ -8,13 +8,13 @@ <properties> <help>Show Wireless (WLAN) interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireless --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=wireless</command> <children> <leafNode name="detail"> <properties> <help>Show detailed wireless interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireless --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=wireless</command> </leafNode> <leafNode name="info"> <properties> @@ -31,13 +31,13 @@ <script>${vyos_completion_dir}/list_interfaces.py --type wireless</script> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=wireless</command> <children> <leafNode name="brief"> <properties> <help>Show summary of the specified wireless interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=wireless</command> </leafNode> <node name="scan"> <properties> @@ -63,13 +63,13 @@ <properties> <help>Show specified virtual network interface (vif) information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4.$6" --intf_type=wireless</command> <children> <leafNode name="brief"> <properties> <help>Show summary of specified virtual network interface (vif) information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6" --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4.$6" --intf_type=wireless</command> </leafNode> </children> </tagNode> diff --git a/op-mode-definitions/show-interfaces-wwan.xml.in b/op-mode-definitions/show-interfaces-wwan.xml.in index 3cd29b38a..17d4111a9 100644 --- a/op-mode-definitions/show-interfaces-wwan.xml.in +++ b/op-mode-definitions/show-interfaces-wwan.xml.in @@ -12,7 +12,7 @@ <script>cd /sys/class/net; ls -d wwan*</script> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=wirelessmodem</command> <children> <leafNode name="capabilities"> <properties> @@ -86,13 +86,13 @@ <properties> <help>Show Wireless Modem (WWAN) interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wirelessmodem --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=wirelessmodem</command> <children> <leafNode name="detail"> <properties> <help>Show detailed Wireless Modem (WWAN( interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wirelessmodem --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=wirelessmodem</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces.xml.in b/op-mode-definitions/show-interfaces.xml.in index 39b0f0a2c..dc61a6f5c 100644 --- a/op-mode-definitions/show-interfaces.xml.in +++ b/op-mode-definitions/show-interfaces.xml.in @@ -6,19 +6,19 @@ <properties> <help>Show network interface information</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --action=show-brief</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary</command> <children> <leafNode name="counters"> <properties> <help>Show network interface counters</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --action=show-count</command> + <command>${vyos_op_scripts_dir}/interfaces.py show_counters</command> </leafNode> <leafNode name="detail"> <properties> <help>Show detailed information of all interfaces</help> </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --action=show</command> + <command>${vyos_op_scripts_dir}/interfaces.py show</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-ip-route.xml.in b/op-mode-definitions/show-ip-route.xml.in index 1e906672d..681aa089f 100644 --- a/op-mode-definitions/show-ip-route.xml.in +++ b/op-mode-definitions/show-ip-route.xml.in @@ -50,10 +50,23 @@ #include <include/show-route-ospf.xml.i> #include <include/show-route-rip.xml.i> #include <include/show-route-static.xml.i> - #include <include/show-route-summary.xml.i> #include <include/show-route-supernets-only.xml.i> #include <include/show-route-table.xml.i> #include <include/show-route-tag.xml.i> + <node name="summary"> + <properties> + <help>Summary of all routes</help> + </properties> + <command>${vyos_op_scripts_dir}/route.py show_summary --family inet</command> + <children> + <tagNode name="table"> + <properties> + <help>Summary of routes in a particular table</help> + </properties> + <command>${vyos_op_scripts_dir}/route.py show_summary --family inet --table $6</command> + </tagNode> + </children> + </node> <tagNode name="vrf"> <properties> <help>Show IP routes in VRF</help> @@ -64,6 +77,12 @@ </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> <children> + <node name="summary"> + <properties> + <help>Summary of all routes in the VRF</help> + </properties> + <command>${vyos_op_scripts_dir}/route.py show_summary --family inet --vrf $5</command> + </node> #include <include/show-route-bgp.xml.i> #include <include/show-route-connected.xml.i> #include <include/show-route-isis.xml.i> @@ -71,7 +90,6 @@ #include <include/show-route-ospf.xml.i> #include <include/show-route-rip.xml.i> #include <include/show-route-static.xml.i> - #include <include/show-route-summary.xml.i> #include <include/show-route-supernets-only.xml.i> #include <include/show-route-tag.xml.i> </children> diff --git a/op-mode-definitions/show-ipv6-route.xml.in b/op-mode-definitions/show-ipv6-route.xml.in index 2c5024991..7df1a873a 100644 --- a/op-mode-definitions/show-ipv6-route.xml.in +++ b/op-mode-definitions/show-ipv6-route.xml.in @@ -50,9 +50,22 @@ #include <include/show-route-ospfv3.xml.i> #include <include/show-route-ripng.xml.i> #include <include/show-route-static.xml.i> - #include <include/show-route-summary.xml.i> #include <include/show-route-table.xml.i> #include <include/show-route-tag.xml.i> + <node name="summary"> + <properties> + <help>Summary of all routes</help> + </properties> + <command>${vyos_op_scripts_dir}/route.py show_summary --family inet6</command> + <children> + <tagNode name="table"> + <properties> + <help>Summary of routes in a particular table</help> + </properties> + <command>${vyos_op_scripts_dir}/route.py show_summary --family inet6 --table $6</command> + </tagNode> + </children> + </node> <tagNode name="vrf"> <properties> <help>Show IPv6 routes in VRF</help> @@ -63,6 +76,12 @@ </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> <children> + <node name="summary"> + <properties> + <help>Summary of all routes in the VRF</help> + </properties> + <command>${vyos_op_scripts_dir}/route.py show_summary --family inet6 --vrf $5</command> + </node> #include <include/show-route-bgp.xml.i> #include <include/show-route-connected.xml.i> #include <include/show-route-isis.xml.i> @@ -70,7 +89,6 @@ #include <include/show-route-ospfv3.xml.i> #include <include/show-route-ripng.xml.i> #include <include/show-route-static.xml.i> - #include <include/show-route-summary.xml.i> #include <include/show-route-supernets-only.xml.i> #include <include/show-route-table.xml.i> #include <include/show-route-tag.xml.i> diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 977ece453..8114f7377 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -204,7 +204,7 @@ </leafNode> <leafNode name="lldp"> <properties> - <help>Show log for LLDP</help> + <help>Show log for Link Layer Discovery Protocol (LLDP)</help> </properties> <command>journalctl --no-hostname --boot --unit lldpd.service</command> </leafNode> @@ -216,10 +216,16 @@ </leafNode> <leafNode name="nhrp"> <properties> - <help>Show log for NHRP</help> + <help>Show log for Next Hop Resolution Protocol (NHRP)</help> </properties> <command>journalctl --no-hostname --boot --unit opennhrp.service</command> </leafNode> + <leafNode name="ntp"> + <properties> + <help>Show log for Network Time Protocol (NTP)</help> + </properties> + <command>journalctl --no-hostname --boot --unit chrony.service</command> + </leafNode> <node name="macsec"> <properties> <help>Show log for MACsec</help> @@ -403,7 +409,7 @@ <properties> <help>Show log for ALL</help> </properties> - <command>journalctl --no-hostname --boot --unit strongswan-starter.service --unit accel-ppp@*.service</command> + <command>journalctl --no-hostname --boot --unit strongswan-starter.service --unit accel-ppp@*.service --unit ocserv.service</command> </leafNode> <leafNode name="ipsec"> <properties> @@ -417,6 +423,12 @@ </properties> <command>journalctl --no-hostname --boot --unit accel-ppp@l2tp.service</command> </leafNode> + <leafNode name="openconnect"> + <properties> + <help>Show log for OpenConnect</help> + </properties> + <command>journalctl --no-hostname --boot --unit ocserv.service</command> + </leafNode> <leafNode name="pptp"> <properties> <help>Show log for PPTP</help> diff --git a/op-mode-definitions/show-ntp.xml.in b/op-mode-definitions/show-ntp.xml.in index 01f4477d8..0907722af 100644 --- a/op-mode-definitions/show-ntp.xml.in +++ b/op-mode-definitions/show-ntp.xml.in @@ -6,22 +6,13 @@ <properties> <help>Show peer status of NTP daemon</help> </properties> - <command>${vyos_op_scripts_dir}/show_ntp.sh --basic</command> + <command>${vyos_op_scripts_dir}/show_ntp.sh --sourcestats</command> <children> - <tagNode name="server"> + <node name="system"> <properties> - <help>Show date and time of specified NTP server</help> - <completionHelp> - <script>${vyos_completion_dir}/list_ntp_servers.sh</script> - </completionHelp> + <help>Show parameters about the system clock performance</help> </properties> - <command>${vyos_op_scripts_dir}/show_ntp.sh --server "$4"</command> - </tagNode> - <node name="info"> - <properties> - <help>Show NTP operational summary</help> - </properties> - <command>${vyos_op_scripts_dir}/show_ntp.sh --info</command> + <command>${vyos_op_scripts_dir}/show_ntp.sh --tracking</command> </node> </children> </node> diff --git a/op-mode-definitions/zone-policy.xml.in b/op-mode-definitions/zone-policy.xml.in index c4b02bcee..9d65ddd3d 100644 --- a/op-mode-definitions/zone-policy.xml.in +++ b/op-mode-definitions/zone-policy.xml.in @@ -11,13 +11,13 @@ <properties> <help>Show summary of zone policy for a specific zone</help> <completionHelp> - <path>zone-policy zone</path> + <path>firewall zone</path> </completionHelp> </properties> - <command>sudo ${vyos_op_scripts_dir}/zone_policy.py --action show --name $4</command> + <command>sudo ${vyos_op_scripts_dir}/zone.py show --zone $4</command> </tagNode> </children> - <command>sudo ${vyos_op_scripts_dir}/zone_policy.py --action show</command> + <command>sudo ${vyos_op_scripts_dir}/zone.py show</command> </node> </children> </node> diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py index 9185575df..ac86af09c 100644 --- a/python/vyos/configdiff.py +++ b/python/vyos/configdiff.py @@ -78,23 +78,34 @@ def get_config_diff(config, key_mangling=None): isinstance(key_mangling[1], str)): raise ValueError("key_mangling must be a tuple of two strings") - diff_t = DiffTree(config._running_config, config._session_config) + if hasattr(config, 'cached_diff_tree'): + diff_t = getattr(config, 'cached_diff_tree') + else: + diff_t = DiffTree(config._running_config, config._session_config) + setattr(config, 'cached_diff_tree', diff_t) - return ConfigDiff(config, key_mangling, diff_tree=diff_t) + if hasattr(config, 'cached_diff_dict'): + diff_d = getattr(config, 'cached_diff_dict') + else: + diff_d = diff_t.dict + setattr(config, 'cached_diff_dict', diff_d) + + return ConfigDiff(config, key_mangling, diff_tree=diff_t, + diff_dict=diff_d) class ConfigDiff(object): """ The class of config changes as represented by comparison between the session config dict and the effective config dict. """ - def __init__(self, config, key_mangling=None, diff_tree=None): + def __init__(self, config, key_mangling=None, diff_tree=None, diff_dict=None): self._level = config.get_level() self._session_config_dict = config.get_cached_root_dict(effective=False) self._effective_config_dict = config.get_cached_root_dict(effective=True) self._key_mangling = key_mangling self._diff_tree = diff_tree - self._diff_dict = diff_tree.dict if diff_tree else {} + self._diff_dict = diff_dict # mirrored from Config; allow path arguments relative to level def _make_path(self, path): @@ -209,9 +220,9 @@ class ConfigDiff(object): if self._diff_tree is None: raise NotImplementedError("diff_tree class not available") else: - add = get_sub_dict(self._diff_tree.dict, ['add'], get_first_key=True) - sub = get_sub_dict(self._diff_tree.dict, ['sub'], get_first_key=True) - inter = get_sub_dict(self._diff_tree.dict, ['inter'], get_first_key=True) + add = get_sub_dict(self._diff_dict, ['add'], get_first_key=True) + sub = get_sub_dict(self._diff_dict, ['sub'], get_first_key=True) + inter = get_sub_dict(self._diff_dict, ['inter'], get_first_key=True) ret = {} ret[enum_to_key(Diff.MERGE)] = session_dict ret[enum_to_key(Diff.DELETE)] = get_sub_dict(sub, self._make_path(path), @@ -284,9 +295,9 @@ class ConfigDiff(object): if self._diff_tree is None: raise NotImplementedError("diff_tree class not available") else: - add = get_sub_dict(self._diff_tree.dict, ['add'], get_first_key=True) - sub = get_sub_dict(self._diff_tree.dict, ['sub'], get_first_key=True) - inter = get_sub_dict(self._diff_tree.dict, ['inter'], get_first_key=True) + add = get_sub_dict(self._diff_dict, ['add'], get_first_key=True) + sub = get_sub_dict(self._diff_dict, ['sub'], get_first_key=True) + inter = get_sub_dict(self._diff_dict, ['inter'], get_first_key=True) ret = {} ret[enum_to_key(Diff.MERGE)] = session_dict ret[enum_to_key(Diff.DELETE)] = get_sub_dict(sub, self._make_path(path)) diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 3a60f6d92..df44fd8d6 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -34,6 +34,8 @@ REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del'] GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate'] SHOW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'show'] RESET = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'reset'] +OP_CMD_ADD = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'add'] +OP_CMD_DELETE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'delete'] # Default "commit via" string APP = "vyos-http-api" @@ -204,3 +206,15 @@ class ConfigSession(object): def reset(self, path): out = self.__run_command(RESET + path) return out + + def add_container_image(self, name): + out = self.__run_command(OP_CMD_ADD + ['container', 'image'] + [name]) + return out + + def delete_container_image(self, name): + out = self.__run_command(OP_CMD_DELETE + ['container', 'image'] + [name]) + return out + + def show_container_image(self): + out = self.__run_command(SHOW + ['container', 'image']) + return out diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 519cfc58c..5080144ff 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -239,7 +239,7 @@ class EthernetIf(Interface): if not isinstance(state, bool): raise ValueError('Value out of range') - rps_cpus = '0' + rps_cpus = 0 queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*')) if state: # Enable RPS on all available CPUs except CPU0 which we will not @@ -248,10 +248,16 @@ class EthernetIf(Interface): # representation of the CPUs which should participate on RPS, we # can enable more CPUs that are physically present on the system, # Linux will clip that internally! - rps_cpus = 'ffffffff,ffffffff,ffffffff,fffffffe' + rps_cpus = (1 << os.cpu_count()) -1 + + # XXX: we should probably reserve one core when the system is under + # high preasure so we can still have a core left for housekeeping. + # This is done by masking out the lowst bit so CPU0 is spared from + # receive packet steering. + rps_cpus &= ~1 for i in range(0, queues): - self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', rps_cpus) + self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', f'{rps_cpus:x}') # send bitmask representation as hex string without leading '0x' return True diff --git a/python/vyos/ifconfig/input.py b/python/vyos/ifconfig/input.py index db7d2b6b4..3e5f5790d 100644 --- a/python/vyos/ifconfig/input.py +++ b/python/vyos/ifconfig/input.py @@ -1,4 +1,4 @@ -# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io> +# 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 @@ -17,6 +17,16 @@ from vyos.ifconfig.interface import Interface @Interface.register class InputIf(Interface): + """ + The Intermediate Functional Block (ifb) pseudo network interface acts as a + QoS concentrator for multiple different sources of traffic. Packets from + or to other interfaces have to be redirected to it using the mirred action + in order to be handled, regularly routed traffic will be dropped. This way, + a single stack of qdiscs, classes and filters can be shared between + multiple interfaces. + """ + + iftype = 'ifb' definition = { **Interface.definition, **{ diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py index 5ff768859..30e893d74 100644 --- a/python/vyos/opmode.py +++ b/python/vyos/opmode.py @@ -45,6 +45,10 @@ class PermissionDenied(Error): """ pass +class UnsupportedOperation(Error): + """ Requested operation is technically valid but is not implemented yet. """ + pass + class IncorrectValue(Error): """ Requested operation is valid, but an argument provided has an incorrect value, preventing successful completion. @@ -66,13 +70,13 @@ class InternalError(Error): def _is_op_mode_function_name(name): - if re.match(r"^(show|clear|reset|restart)", name): + if re.match(r"^(show|clear|reset|restart|add|delete|generate)", name): return True else: return False -def _is_show(name): - if re.match(r"^show", name): +def _capture_output(name): + if re.match(r"^(show|generate)", name): return True else: return False @@ -199,14 +203,14 @@ def run(module): # it would cause an extra argument error when we pass the dict to a function del args["subcommand"] - # Show commands must always get the "raw" argument, - # but other commands (clear/reset/restart) should not, + # Show and generate commands must always get the "raw" argument, + # but other commands (clear/reset/restart/add/delete) should not, # because they produce no output and it makes no sense for them. - if ("raw" not in args) and _is_show(function_name): + if ("raw" not in args) and _capture_output(function_name): args["raw"] = False - if re.match(r"^show", function_name): - # Show commands are slightly special: + if _capture_output(function_name): + # Show and generate commands are slightly special: # they may return human-formatted output # or a raw dict that we need to serialize in JSON for printing res = func(**args) diff --git a/python/vyos/qos/__init__.py b/python/vyos/qos/__init__.py new file mode 100644 index 000000000..a2980ccde --- /dev/null +++ b/python/vyos/qos/__init__.py @@ -0,0 +1,28 @@ +# Copyright 2022 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 vyos.qos.base import QoSBase +from vyos.qos.cake import CAKE +from vyos.qos.droptail import DropTail +from vyos.qos.fairqueue import FairQueue +from vyos.qos.fqcodel import FQCodel +from vyos.qos.limiter import Limiter +from vyos.qos.netem import NetEm +from vyos.qos.priority import Priority +from vyos.qos.randomdetect import RandomDetect +from vyos.qos.ratelimiter import RateLimiter +from vyos.qos.roundrobin import RoundRobin +from vyos.qos.trafficshaper import TrafficShaper +from vyos.qos.trafficshaper import TrafficShaperHFSC diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py new file mode 100644 index 000000000..28635b5e7 --- /dev/null +++ b/python/vyos/qos/base.py @@ -0,0 +1,282 @@ +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os + +from vyos.base import Warning +from vyos.util import cmd +from vyos.util import dict_search +from vyos.util import read_file + +class QoSBase: + _debug = False + _direction = ['egress'] + _parent = 0xffff + + def __init__(self, interface): + if os.path.exists('/tmp/vyos.qos.debug'): + self._debug = True + self._interface = interface + + def _cmd(self, command): + if self._debug: + print(f'DEBUG/QoS: {command}') + return cmd(command) + + def get_direction(self) -> list: + return self._direction + + def _get_class_max_id(self, config) -> int: + if 'class' in config: + tmp = list(config['class'].keys()) + tmp.sort(key=lambda ii: int(ii)) + return tmp[-1] + return None + + def _build_base_qdisc(self, config : dict, cls_id : int): + """ + Add/replace qdisc for every class (also default is a class). This is + a genetic method which need an implementation "per" queue-type. + + This matches the old mapping as defined in Perl here: + https://github.com/vyos/vyatta-cfg-qos/blob/equuleus/lib/Vyatta/Qos/ShaperClass.pm#L223-L229 + """ + queue_type = dict_search('queue_type', config) + default_tc = f'tc qdisc replace dev {self._interface} parent {self._parent}:{cls_id:x}' + + if queue_type == 'priority': + handle = 0x4000 + cls_id + default_tc += f' handle {handle:x}: prio' + self._cmd(default_tc) + + queue_limit = dict_search('queue_limit', config) + for ii in range(1, 4): + tmp = f'tc qdisc replace dev {self._interface} parent {handle:x}:{ii:x} pfifo limit {queue_limit}' + self._cmd(tmp) + + elif queue_type == 'fair-queue': + default_tc += f' sfq' + + tmp = dict_search('queue_limit', config) + if tmp: default_tc += f' limit {tmp}' + + self._cmd(default_tc) + + elif queue_type == 'fq-codel': + default_tc += f' fq_codel' + tmp = dict_search('codel_quantum', config) + if tmp: default_tc += f' quantum {tmp}' + + tmp = dict_search('flows', config) + if tmp: default_tc += f' flows {tmp}' + + tmp = dict_search('interval', config) + if tmp: default_tc += f' interval {tmp}' + + tmp = dict_search('interval', config) + if tmp: default_tc += f' interval {tmp}' + + tmp = dict_search('queue_limit', config) + if tmp: default_tc += f' limit {tmp}' + + tmp = dict_search('target', config) + if tmp: default_tc += f' target {tmp}' + + default_tc += f' noecn' + + self._cmd(default_tc) + + elif queue_type == 'random-detect': + default_tc += f' red' + + self._cmd(default_tc) + + elif queue_type == 'drop-tail': + default_tc += f' pfifo' + + tmp = dict_search('queue_limit', config) + if tmp: default_tc += f' limit {tmp}' + + self._cmd(default_tc) + + def _rate_convert(self, rate) -> int: + rates = { + 'bit' : 1, + 'kbit' : 1000, + 'mbit' : 1000000, + 'gbit' : 1000000000, + 'tbit' : 1000000000000, + } + + if rate == 'auto' or rate.endswith('%'): + speed = read_file(f'/sys/class/net/{self._interface}/speed') + if not speed.isnumeric(): + Warning('Interface speed cannot be determined (assuming 10 Mbit/s)') + speed = 10 + if rate.endswith('%'): + percent = rate.rstrip('%') + speed = int(speed) * int(percent) // 100 + return int(speed) *1000000 # convert to MBit/s + + rate_numeric = int(''.join([n for n in rate if n.isdigit()])) + rate_scale = ''.join([n for n in rate if not n.isdigit()]) + + if int(rate_numeric) <= 0: + raise ValueError(f'{rate_numeric} is not a valid bandwidth <= 0') + + if rate_scale: + return int(rate_numeric * rates[rate_scale]) + else: + # No suffix implies Kbps just as Cisco IOS + return int(rate_numeric * 1000) + + def update(self, config, direction, priority=None): + """ method must be called from derived class after it has completed qdisc setup """ + + if 'class' in config: + for cls, cls_config in config['class'].items(): + self._build_base_qdisc(cls_config, int(cls)) + + if 'match' in cls_config: + for match, match_config in cls_config['match'].items(): + for af in ['ip', 'ipv6']: + # every match criteria has it's tc instance + filter_cmd = f'tc filter replace dev {self._interface} parent {self._parent:x}:' + + if priority: + filter_cmd += f' prio {cls}' + elif 'priority' in cls_config: + prio = cls_config['priority'] + filter_cmd += f' prio {prio}' + + filter_cmd += ' protocol all u32' + + tc_af = af + if af == 'ipv6': + tc_af = 'ip6' + + if af in match_config: + tmp = dict_search(f'{af}.source.address', match_config) + if tmp: filter_cmd += f' match {tc_af} src {tmp}' + + tmp = dict_search(f'{af}.source.port', match_config) + if tmp: filter_cmd += f' match {tc_af} sport {tmp} 0xffff' + + tmp = dict_search(f'{af}.destination.address', match_config) + if tmp: filter_cmd += f' match {tc_af} dst {tmp}' + + tmp = dict_search(f'{af}.destination.port', match_config) + if tmp: filter_cmd += f' match {tc_af} dport {tmp} 0xffff' + + tmp = dict_search(f'{af}.protocol', match_config) + if tmp: filter_cmd += f' match {tc_af} protocol {tmp} 0xff' + + # Will match against total length of an IPv4 packet and + # payload length of an IPv6 packet. + # + # IPv4 : match u16 0x0000 ~MAXLEN at 2 + # IPv6 : match u16 0x0000 ~MAXLEN at 4 + tmp = dict_search(f'{af}.max_length', match_config) + if tmp: + # We need the 16 bit two's complement of the maximum + # packet length + tmp = hex(0xffff & ~int(tmp)) + + if af == 'ip': + filter_cmd += f' match u16 0x0000 {tmp} at 2' + elif af == 'ipv6': + filter_cmd += f' match u16 0x0000 {tmp} at 4' + + # We match against specific TCP flags - we assume the IPv4 + # header length is 20 bytes and assume the IPv6 packet is + # not using extension headers (hence a ip header length of 40 bytes) + # TCP Flags are set on byte 13 of the TCP header. + # IPv4 : match u8 X X at 33 + # IPv6 : match u8 X X at 53 + # with X = 0x02 for SYN and X = 0x10 for ACK + tmp = dict_search(f'{af}.tcp', match_config) + if tmp: + mask = 0 + if 'ack' in tmp: + mask |= 0x10 + if 'syn' in tmp: + mask |= 0x02 + mask = hex(mask) + + if af == 'ip': + filter_cmd += f' match u8 {mask} {mask} at 33' + elif af == 'ipv6': + filter_cmd += f' match u8 {mask} {mask} at 53' + + # The police block allows limiting of the byte or packet rate of + # traffic matched by the filter it is attached to. + # https://man7.org/linux/man-pages/man8/tc-police.8.html + if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in cls_config): + filter_cmd += f' action police' + + if 'exceed' in cls_config: + action = cls_config['exceed'] + filter_cmd += f' conform-exceed {action}' + if 'not_exceed' in cls_config: + action = cls_config['not_exceed'] + filter_cmd += f'/{action}' + + if 'bandwidth' in cls_config: + rate = self._rate_convert(cls_config['bandwidth']) + filter_cmd += f' rate {rate}' + + if 'burst' in cls_config: + burst = cls_config['burst'] + filter_cmd += f' burst {burst}' + + cls = int(cls) + filter_cmd += f' flowid {self._parent:x}:{cls:x}' + self._cmd(filter_cmd) + + if 'default' in config: + if 'class' in config: + class_id_max = self._get_class_max_id(config) + default_cls_id = int(class_id_max) +1 + self._build_base_qdisc(config['default'], default_cls_id) + + filter_cmd = f'tc filter replace dev {self._interface} parent {self._parent:x}: ' + filter_cmd += 'prio 255 protocol all basic' + + # The police block allows limiting of the byte or packet rate of + # traffic matched by the filter it is attached to. + # https://man7.org/linux/man-pages/man8/tc-police.8.html + if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in config['default']): + filter_cmd += f' action police' + + if 'exceed' in config['default']: + action = config['default']['exceed'] + filter_cmd += f' conform-exceed {action}' + if 'not_exceed' in config['default']: + action = config['default']['not_exceed'] + filter_cmd += f'/{action}' + + if 'bandwidth' in config['default']: + rate = self._rate_convert(config['default']['bandwidth']) + filter_cmd += f' rate {rate}' + + if 'burst' in config['default']: + burst = config['default']['burst'] + filter_cmd += f' burst {burst}' + + if 'class' in config: + filter_cmd += f' flowid {self._parent:x}:{default_cls_id:x}' + + self._cmd(filter_cmd) + diff --git a/python/vyos/qos/cake.py b/python/vyos/qos/cake.py new file mode 100644 index 000000000..a89b1de1e --- /dev/null +++ b/python/vyos/qos/cake.py @@ -0,0 +1,55 @@ +# Copyright 2022 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 vyos.qos.base import QoSBase + +class CAKE(QoSBase): + _direction = ['egress'] + + # https://man7.org/linux/man-pages/man8/tc-cake.8.html + def update(self, config, direction): + tmp = f'tc qdisc add dev {self._interface} root handle 1: cake {direction}' + if 'bandwidth' in config: + bandwidth = self._rate_convert(config['bandwidth']) + tmp += f' bandwidth {bandwidth}' + + if 'rtt' in config: + rtt = config['rtt'] + tmp += f' rtt {rtt}ms' + + if 'flow_isolation' in config: + if 'blind' in config['flow_isolation']: + tmp += f' flowblind' + if 'dst_host' in config['flow_isolation']: + tmp += f' dsthost' + if 'dual_dst_host' in config['flow_isolation']: + tmp += f' dual-dsthost' + if 'dual_src_host' in config['flow_isolation']: + tmp += f' dual-srchost' + if 'flow' in config['flow_isolation']: + tmp += f' flows' + if 'host' in config['flow_isolation']: + tmp += f' hosts' + if 'nat' in config['flow_isolation']: + tmp += f' nat' + if 'src_host' in config['flow_isolation']: + tmp += f' srchost ' + else: + tmp += f' nonat' + + self._cmd(tmp) + + # call base class + super().update(config, direction) diff --git a/python/vyos/qos/droptail.py b/python/vyos/qos/droptail.py new file mode 100644 index 000000000..427d43d19 --- /dev/null +++ b/python/vyos/qos/droptail.py @@ -0,0 +1,28 @@ +# Copyright 2022 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 vyos.qos.base import QoSBase + +class DropTail(QoSBase): + # https://man7.org/linux/man-pages/man8/tc-pfifo.8.html + def update(self, config, direction): + tmp = f'tc qdisc add dev {self._interface} root pfifo' + if 'queue_limit' in config: + limit = config["queue_limit"] + tmp += f' limit {limit}' + self._cmd(tmp) + + # call base class + super().update(config, direction) diff --git a/python/vyos/qos/fairqueue.py b/python/vyos/qos/fairqueue.py new file mode 100644 index 000000000..f41d098fb --- /dev/null +++ b/python/vyos/qos/fairqueue.py @@ -0,0 +1,31 @@ +# Copyright 2022 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 vyos.qos.base import QoSBase + +class FairQueue(QoSBase): + # https://man7.org/linux/man-pages/man8/tc-sfq.8.html + def update(self, config, direction): + tmp = f'tc qdisc add dev {self._interface} root sfq' + + if 'hash_interval' in config: + tmp += f' perturb {config["hash_interval"]}' + if 'queue_limit' in config: + tmp += f' limit {config["queue_limit"]}' + + self._cmd(tmp) + + # call base class + super().update(config, direction) diff --git a/python/vyos/qos/fqcodel.py b/python/vyos/qos/fqcodel.py new file mode 100644 index 000000000..cd2340aa2 --- /dev/null +++ b/python/vyos/qos/fqcodel.py @@ -0,0 +1,40 @@ +# Copyright 2022 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 vyos.qos.base import QoSBase + +class FQCodel(QoSBase): + # https://man7.org/linux/man-pages/man8/tc-fq_codel.8.html + def update(self, config, direction): + tmp = f'tc qdisc add dev {self._interface} root fq_codel' + + if 'codel_quantum' in config: + tmp += f' quantum {config["codel_quantum"]}' + if 'flows' in config: + tmp += f' flows {config["flows"]}' + if 'interval' in config: + interval = int(config['interval']) * 1000 + tmp += f' interval {interval}' + if 'queue_limit' in config: + tmp += f' limit {config["queue_limit"]}' + if 'target' in config: + target = int(config['target']) * 1000 + tmp += f' target {target}' + + tmp += f' noecn' + self._cmd(tmp) + + # call base class + super().update(config, direction) diff --git a/python/vyos/qos/limiter.py b/python/vyos/qos/limiter.py new file mode 100644 index 000000000..ace0c0b6c --- /dev/null +++ b/python/vyos/qos/limiter.py @@ -0,0 +1,27 @@ +# Copyright 2022 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 vyos.qos.base import QoSBase + +class Limiter(QoSBase): + _direction = ['ingress'] + + def update(self, config, direction): + tmp = f'tc qdisc add dev {self._interface} handle {self._parent:x}: {direction}' + self._cmd(tmp) + + # base class must be called last + super().update(config, direction) + diff --git a/python/vyos/qos/netem.py b/python/vyos/qos/netem.py new file mode 100644 index 000000000..8bdef300b --- /dev/null +++ b/python/vyos/qos/netem.py @@ -0,0 +1,53 @@ +# Copyright 2022 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 vyos.qos.base import QoSBase + +class NetEm(QoSBase): + # https://man7.org/linux/man-pages/man8/tc-netem.8.html + def update(self, config, direction): + tmp = f'tc qdisc add dev {self._interface} root netem' + if 'bandwidth' in config: + rate = self._rate_convert(config["bandwidth"]) + tmp += f' rate {rate}' + + if 'queue_limit' in config: + limit = config["queue_limit"] + tmp += f' limit {limit}' + + if 'delay' in config: + delay = config["delay"] + tmp += f' delay {delay}ms' + + if 'loss' in config: + drop = config["loss"] + tmp += f' drop {drop}%' + + if 'corruption' in config: + corrupt = config["corruption"] + tmp += f' corrupt {corrupt}%' + + if 'reordering' in config: + reorder = config["reordering"] + tmp += f' reorder {reorder}%' + + if 'duplicate' in config: + duplicate = config["duplicate"] + tmp += f' duplicate {duplicate}%' + + self._cmd(tmp) + + # call base class + super().update(config, direction) diff --git a/python/vyos/qos/priority.py b/python/vyos/qos/priority.py new file mode 100644 index 000000000..6d4a60a43 --- /dev/null +++ b/python/vyos/qos/priority.py @@ -0,0 +1,41 @@ +# Copyright 2022 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 vyos.qos.base import QoSBase +from vyos.util import dict_search + +class Priority(QoSBase): + _parent = 1 + + # https://man7.org/linux/man-pages/man8/tc-prio.8.html + def update(self, config, direction): + if 'class' in config: + class_id_max = self._get_class_max_id(config) + bands = int(class_id_max) +1 + + tmp = f'tc qdisc add dev {self._interface} root handle {self._parent:x}: prio bands {bands} priomap ' \ + f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' \ + f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' \ + f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' \ + f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' + self._cmd(tmp) + + for cls in config['class']: + cls = int(cls) + tmp = f'tc qdisc add dev {self._interface} parent {self._parent:x}:{cls:x} pfifo' + self._cmd(tmp) + + # base class must be called last + super().update(config, direction, priority=True) diff --git a/python/vyos/qos/randomdetect.py b/python/vyos/qos/randomdetect.py new file mode 100644 index 000000000..d7d84260f --- /dev/null +++ b/python/vyos/qos/randomdetect.py @@ -0,0 +1,54 @@ +# Copyright 2022 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 vyos.qos.base import QoSBase + +class RandomDetect(QoSBase): + _parent = 1 + + # https://man7.org/linux/man-pages/man8/tc.8.html + def update(self, config, direction): + + tmp = f'tc qdisc add dev {self._interface} root handle {self._parent}:0 dsmark indices 8 set_tc_index' + self._cmd(tmp) + + tmp = f'tc filter add dev {self._interface} parent {self._parent}:0 protocol ip prio 1 tcindex mask 0xe0 shift 5' + self._cmd(tmp) + + # Generalized Random Early Detection + handle = self._parent +1 + tmp = f'tc qdisc add dev {self._interface} parent {self._parent}:0 handle {handle}:0 gred setup DPs 8 default 0 grio' + self._cmd(tmp) + + bandwidth = self._rate_convert(config['bandwidth']) + + # set VQ (virtual queue) parameters + for precedence, precedence_config in config['precedence'].items(): + precedence = int(precedence) + avg_pkt = int(precedence_config['average_packet']) + limit = int(precedence_config['queue_limit']) * avg_pkt + min_val = int(precedence_config['minimum_threshold']) * avg_pkt + max_val = int(precedence_config['maximum_threshold']) * avg_pkt + + tmp = f'tc qdisc change dev {self._interface} handle {handle}:0 gred limit {limit} min {min_val} max {max_val} avpkt {avg_pkt} ' + + burst = (2 * int(precedence_config['minimum_threshold']) + int(precedence_config['maximum_threshold'])) // 3 + probability = 1 / int(precedence_config['mark_probability']) + tmp += f'burst {burst} bandwidth {bandwidth} probability {probability} DP {precedence} prio {8 - precedence:x}' + + self._cmd(tmp) + + # call base class + super().update(config, direction) diff --git a/python/vyos/qos/ratelimiter.py b/python/vyos/qos/ratelimiter.py new file mode 100644 index 000000000..a4f80a1be --- /dev/null +++ b/python/vyos/qos/ratelimiter.py @@ -0,0 +1,37 @@ +# Copyright 2022 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 vyos.qos.base import QoSBase + +class RateLimiter(QoSBase): + # https://man7.org/linux/man-pages/man8/tc-tbf.8.html + def update(self, config, direction): + # call base class + super().update(config, direction) + + tmp = f'tc qdisc add dev {self._interface} root tbf' + if 'bandwidth' in config: + rate = self._rate_convert(config['bandwidth']) + tmp += f' rate {rate}' + + if 'burst' in config: + burst = config['burst'] + tmp += f' burst {burst}' + + if 'latency' in config: + latency = config['latency'] + tmp += f' latency {latency}ms' + + self._cmd(tmp) diff --git a/python/vyos/qos/roundrobin.py b/python/vyos/qos/roundrobin.py new file mode 100644 index 000000000..80814ddfb --- /dev/null +++ b/python/vyos/qos/roundrobin.py @@ -0,0 +1,44 @@ +# Copyright 2022 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 vyos.qos.base import QoSBase + +class RoundRobin(QoSBase): + _parent = 1 + + # https://man7.org/linux/man-pages/man8/tc-drr.8.html + def update(self, config, direction): + tmp = f'tc qdisc add dev {self._interface} root handle 1: drr' + self._cmd(tmp) + + if 'class' in config: + for cls in config['class']: + cls = int(cls) + tmp = f'tc class replace dev {self._interface} parent 1:1 classid 1:{cls:x} drr' + self._cmd(tmp) + + tmp = f'tc qdisc replace dev {self._interface} parent 1:{cls:x} pfifo' + self._cmd(tmp) + + if 'default' in config: + class_id_max = self._get_class_max_id(config) + default_cls_id = int(class_id_max) +1 + + # class ID via CLI is in range 1-4095, thus 1000 hex = 4096 + tmp = f'tc class replace dev {self._interface} parent 1:1 classid 1:{default_cls_id:x} drr' + self._cmd(tmp) + + # call base class + super().update(config, direction, priority=True) diff --git a/python/vyos/qos/trafficshaper.py b/python/vyos/qos/trafficshaper.py new file mode 100644 index 000000000..f42f4d022 --- /dev/null +++ b/python/vyos/qos/trafficshaper.py @@ -0,0 +1,106 @@ +# Copyright 2022 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 math import ceil +from vyos.qos.base import QoSBase + +# Kernel limits on quantum (bytes) +MAXQUANTUM = 200000 +MINQUANTUM = 1000 + +class TrafficShaper(QoSBase): + _parent = 1 + + # https://man7.org/linux/man-pages/man8/tc-htb.8.html + def update(self, config, direction): + class_id_max = 0 + if 'class' in config: + tmp = list(config['class']) + tmp.sort() + class_id_max = tmp[-1] + + r2q = 10 + # bandwidth is a mandatory CLI node + speed = self._rate_convert(config['bandwidth']) + speed_bps = int(speed) // 8 + + # need a bigger r2q if going fast than 16 mbits/sec + if (speed_bps // r2q) >= MAXQUANTUM: # integer division + r2q = ceil(speed_bps // MAXQUANTUM) + else: + # if there is a slow class then may need smaller value + if 'class' in config: + min_speed = speed_bps + for cls, cls_options in config['class'].items(): + # find class with the lowest bandwidth used + if 'bandwidth' in cls_options: + bw_bps = int(self._rate_convert(cls_options['bandwidth'])) // 8 # bandwidth in bytes per second + if bw_bps < min_speed: + min_speed = bw_bps + + while (r2q > 1) and (min_speed // r2q) < MINQUANTUM: + tmp = r2q -1 + if (speed_bps // tmp) >= MAXQUANTUM: + break + r2q = tmp + + + default_minor_id = int(class_id_max) +1 + tmp = f'tc qdisc replace dev {self._interface} root handle {self._parent:x}: htb r2q {r2q} default {default_minor_id:x}' # default is in hex + self._cmd(tmp) + + tmp = f'tc class replace dev {self._interface} parent {self._parent:x}: classid {self._parent:x}:1 htb rate {speed}' + self._cmd(tmp) + + if 'class' in config: + for cls, cls_config in config['class'].items(): + # class id is used later on and passed as hex, thus this needs to be an int + cls = int(cls) + + # bandwidth is a mandatory CLI node + rate = self._rate_convert(cls_config['bandwidth']) + burst = cls_config['burst'] + quantum = cls_config['codel_quantum'] + + tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{cls:x} htb rate {rate} burst {burst} quantum {quantum}' + if 'priority' in cls_config: + priority = cls_config['priority'] + tmp += f' prio {priority}' + self._cmd(tmp) + + tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{cls:x} sfq' + self._cmd(tmp) + + if 'default' in config: + rate = self._rate_convert(config['default']['bandwidth']) + burst = config['default']['burst'] + quantum = config['default']['codel_quantum'] + tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{default_minor_id:x} htb rate {rate} burst {burst} quantum {quantum}' + if 'priority' in config['default']: + priority = config['default']['priority'] + tmp += f' prio {priority}' + self._cmd(tmp) + + tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{default_minor_id:x} sfq' + self._cmd(tmp) + + # call base class + super().update(config, direction) + +class TrafficShaperHFSC(TrafficShaper): + def update(self, config, direction): + # call base class + super().update(config, direction) + diff --git a/python/vyos/util.py b/python/vyos/util.py index 6a828c0ac..66ded464d 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -348,9 +348,11 @@ def colon_separated_to_dict(data_string, uniquekeys=False): l = l.strip() if l: match = re.match(key_value_re, l) - if match: + if match and (len(match.groups()) == 2): key = match.groups()[0].strip() value = match.groups()[1].strip() + else: + raise ValueError(f"""Line "{l}" could not be parsed a colon-separated pair """, l) if key in data.keys(): if uniquekeys: raise ValueError("Data string has duplicate keys: {0}".format(key)) @@ -486,7 +488,7 @@ def is_listen_port_bind_service(port: int, service: str) -> bool: Example: % is_listen_port_bind_service(443, 'nginx') True - % is_listen_port_bind_service(443, 'ocservr-main') + % is_listen_port_bind_service(443, 'ocserv-main') False """ from psutil import net_connections as connections diff --git a/scripts/check-pr-title-and-commit-messages.py b/scripts/check-pr-title-and-commit-messages.py index 9801b7456..1d4a3cfe3 100755 --- a/scripts/check-pr-title-and-commit-messages.py +++ b/scripts/check-pr-title-and-commit-messages.py @@ -2,6 +2,7 @@ import re import sys +import time import requests from pprint import pprint @@ -27,9 +28,17 @@ if __name__ == '__main__': print("Please specify pull request URL!") sys.exit(1) + # There seems to be a race condition that causes this scripts to receive + # an incomplete PR object that is missing certain fields, + # which causes temporary CI failures that require re-running the script + # + # It's probably better to add a small delay to prevent that + time.sleep(5) + # Get the pull request object pr = requests.get(sys.argv[1]).json() if "title" not in pr: + print("The PR object does not have a title field!") print("Did not receive a valid pull request object, please check the URL!") sys.exit(1) diff --git a/smoketest/configs/basic-qos b/smoketest/configs/basic-qos deleted file mode 100644 index d9baa4a1f..000000000 --- a/smoketest/configs/basic-qos +++ /dev/null @@ -1,194 +0,0 @@ -interfaces { - ethernet eth0 { - address 100.64.0.1/20 - duplex auto - smp-affinity auto - speed auto - } - ethernet eth1 { - duplex auto - speed auto - vif 10 { - traffic-policy { - in M2 - } - } - vif 20 { - traffic-policy { - out FS - } - } - vif 30 { - traffic-policy { - out MY-HTB - } - } - vif 40 { - traffic-policy { - out SHAPER-FOO - } - } - } -} -system { - config-management { - commit-revisions 100 - } - console { - device ttyS0 { - speed 115200 - } - } - host-name vyos - login { - user vyos { - authentication { - encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 - plaintext-password "" - } - } - } - name-server 192.168.0.1 - syslog { - global { - archive { - file 5 - size 512 - } - facility all { - level info - } - } - } - time-zone Europe/Berlin -} -traffic-policy { - limiter M2 { - class 10 { - bandwidth 120mbit - burst 15k - match ADDRESS10 { - ip { - dscp CS4 - } - } - priority 20 - } - default { - bandwidth 100mbit - burst 15k - } - } - shaper FS { - bandwidth auto - class 10 { - bandwidth 100% - burst 15k - match ADDRESS10 { - ip { - source { - address 172.17.1.2/32 - } - } - } - queue-type fair-queue - set-dscp CS4 - } - class 20 { - bandwidth 100% - burst 15k - match ADDRESS20 { - ip { - source { - address 172.17.1.3/32 - } - } - } - queue-type fair-queue - set-dscp CS5 - } - class 30 { - bandwidth 100% - burst 15k - match ADDRESS30 { - ip { - source { - address 172.17.1.4/32 - } - } - } - queue-type fair-queue - set-dscp CS6 - } - default { - bandwidth 10% - burst 15k - ceiling 100% - priority 7 - queue-type fair-queue - } - } - shaper MY-HTB { - bandwidth 10mbit - class 30 { - bandwidth 10% - burst 15k - ceiling 50% - match ADDRESS30 { - ip { - source { - address 10.1.1.0/24 - } - } - } - priority 5 - queue-type fair-queue - } - class 40 { - bandwidth 90% - burst 15k - ceiling 100% - match ADDRESS40 { - ip { - dscp CS4 - source { - address 10.2.1.0/24 - } - } - } - priority 5 - queue-type fair-queue - } - class 50 { - bandwidth 100% - burst 15k - match ADDRESS50 { - ip { - dscp CS5 - } - } - queue-type fair-queue - set-dscp CS7 - } - default { - bandwidth 10% - burst 15k - ceiling 100% - priority 7 - queue-type fair-queue - set-dscp CS1 - } - } - shaper SHAPER-FOO { - bandwidth 1000mbit - default { - bandwidth 100mbit - burst 15k - queue-type fair-queue - set-dscp CS4 - } - } -} -// Warning: Do not remove the following line. -// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" -// Release version: 1.3.2 diff --git a/smoketest/configs/dialup-router-medium-vpn b/smoketest/configs/dialup-router-medium-vpn index 56722d222..503280017 100644 --- a/smoketest/configs/dialup-router-medium-vpn +++ b/smoketest/configs/dialup-router-medium-vpn @@ -68,9 +68,6 @@ interfaces { mtu 1500 name-server auto password password - traffic-policy { - out shape-17mbit - } user-id vyos password vyos } @@ -96,9 +93,6 @@ interfaces { } smp-affinity auto speed auto - traffic-policy { - out shape-94mbit - } } loopback lo { } @@ -719,24 +713,6 @@ system { } time-zone Pacific/Auckland } -traffic-policy { - shaper shape-17mbit { - bandwidth 17mbit - default { - bandwidth 100% - burst 15k - queue-type fq-codel - } - } - shaper shape-94mbit { - bandwidth 94mbit - default { - bandwidth 100% - burst 15k - queue-type fq-codel - } - } -} /* Warning: Do not remove the following line. */ /* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ /* Release version: 1.2.6 */ diff --git a/smoketest/configs/qos-basic b/smoketest/configs/qos-basic index f94a5650d..c279cbf67 100644 --- a/smoketest/configs/qos-basic +++ b/smoketest/configs/qos-basic @@ -16,19 +16,14 @@ interfaces { traffic-policy { out MY-HTB } - } - loopback lo { - } -} -protocols { - static { - route 0.0.0.0/0 { - next-hop 10.9.9.2 { - } - next-hop 10.1.1.1 { + vif 200 { + traffic-policy { + out foo-emulate } } } + loopback lo { + } } system { config-management { @@ -84,13 +79,14 @@ traffic-policy { class 10 { bandwidth 100% burst 15k - match ADDRESS10 { + match ssh4 { ip { - dscp CS4 + destination { + port 22 + } } } queue-type fair-queue - set-dscp CS5 } default { bandwidth 10mbit @@ -120,7 +116,6 @@ traffic-policy { ceiling 100% match ADDRESS40 { ip { - dscp CS4 source { address 10.2.1.0/24 } @@ -133,12 +128,13 @@ traffic-policy { bandwidth 100% burst 15k match ADDRESS50 { - ip { - dscp CS5 + ipv6 { + source { + address "2001:db8::1/64" + } } } queue-type fair-queue - set-dscp CS7 } default { bandwidth 10% @@ -146,7 +142,6 @@ traffic-policy { ceiling 100% priority 7 queue-type fair-queue - set-dscp CS1 } } shaper FS { @@ -162,7 +157,6 @@ traffic-policy { } } queue-type fair-queue - set-dscp CS4 } class 20 { bandwidth 100% @@ -175,7 +169,6 @@ traffic-policy { } } queue-type fair-queue - set-dscp CS5 } class 30 { bandwidth 100% @@ -188,7 +181,6 @@ traffic-policy { } } queue-type fair-queue - set-dscp CS6 } default { bandwidth 10% @@ -198,6 +190,10 @@ traffic-policy { queue-type fair-queue } } + network-emulator foo-emulate { + bandwidth 300mbit + burst 20000 + } } // Warning: Do not remove the following line. // vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" diff --git a/smoketest/scripts/cli/test_interfaces_dummy.py b/smoketest/scripts/cli/test_interfaces_dummy.py index d96ec2c5d..a79e4cb1b 100755 --- a/smoketest/scripts/cli/test_interfaces_dummy.py +++ b/smoketest/scripts/cli/test_interfaces_dummy.py @@ -21,6 +21,7 @@ from base_interfaces_test import BasicInterfaceTest class DummyInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): + cls._test_mtu = True cls._base_path = ['interfaces', 'dummy'] cls._interfaces = ['dum435', 'dum8677', 'dum0931', 'dum089'] # call base-classes classmethod diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index ed611062a..e53413f0d 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -160,7 +160,7 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): self.assertFalse(is_intf_addr_assigned(intf, addr['addr'])) def test_offloading_rps(self): - # enable RPS on all available CPUs, RPS works woth a CPU bitmask, + # enable RPS on all available CPUs, RPS works with a CPU bitmask, # where each bit represents a CPU (core/thread). The formula below # expands to rps_cpus = 255 for a 8 core system rps_cpus = (1 << os.cpu_count()) -1 diff --git a/smoketest/scripts/cli/test_interfaces_input.py b/smoketest/scripts/cli/test_interfaces_input.py new file mode 100755 index 000000000..c6d7febec --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_input.py @@ -0,0 +1,52 @@ +#!/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 unittest + +from vyos.util import read_file +from vyos.ifconfig import Interface +from base_vyostest_shim import VyOSUnitTestSHIM + +base_path = ['interfaces', 'input'] + +# add a classmethod to setup a temporaray PPPoE server for "proper" validation +class InputInterfaceTest(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(InputInterfaceTest, cls).setUpClass() + + cls._interfaces = ['ifb10', 'ifb20', 'ifb30'] + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_01_description(self): + # Check if PPPoE dialer can be configured and runs + for interface in self._interfaces: + self.cli_set(base_path + [interface, 'description', f'foo-{interface}']) + + # commit changes + self.cli_commit() + + # Validate remove interface description "empty" + for interface in self._interfaces: + tmp = read_file(f'/sys/class/net/{interface}/ifalias') + self.assertEqual(tmp, f'foo-{interface}') + self.assertEqual(Interface(interface).get_alias(), f'foo-{interface}') + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_load_balancning_wan.py b/smoketest/scripts/cli/test_load_balancing_wan.py index 23020b9b1..33c69c595 100755 --- a/smoketest/scripts/cli/test_load_balancning_wan.py +++ b/smoketest/scripts/cli/test_load_balancing_wan.py @@ -46,7 +46,6 @@ def cmd_in_netns(netns, cmd): def delete_netns(name): return call(f'sudo ip netns del {name}') - class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): @@ -61,7 +60,6 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): self.cli_commit() def test_table_routes(self): - ns1 = 'ns201' ns2 = 'ns202' ns3 = 'ns203' @@ -79,6 +77,7 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): create_veth_pair(iface1, container_iface1) create_veth_pair(iface2, container_iface2) create_veth_pair(iface3, container_iface3) + move_interface_to_netns(container_iface1, ns1) move_interface_to_netns(container_iface2, ns2) move_interface_to_netns(container_iface3, ns3) @@ -125,7 +124,7 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): self.assertEqual(tmp, original) # Delete veth interfaces and netns - for iface in [iface1, iface2]: + for iface in [iface1, iface2, iface3, container_iface1, container_iface2, container_iface3]: call(f'sudo ip link del dev {iface}') delete_netns(ns1) @@ -196,9 +195,10 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): call(f'sudo ip address add 203.0.113.10/24 dev {iface1}') call(f'sudo ip address add 192.0.2.10/24 dev {iface2}') call(f'sudo ip address add 198.51.100.10/24 dev {iface3}') - call(f'sudo ip link set dev {iface1} up') - call(f'sudo ip link set dev {iface2} up') - call(f'sudo ip link set dev {iface3} up') + + for iface in [iface1, iface2, iface3]: + call(f'sudo ip link set dev {iface} up') + cmd_in_netns(ns1, f'ip link set {container_iface1} name eth0') cmd_in_netns(ns2, f'ip link set {container_iface2} name eth0') cmd_in_netns(ns3, f'ip link set {container_iface3} name eth0') @@ -247,12 +247,11 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): self.assertEqual(tmp, nat_vyos_pre_snat_hook) # Delete veth interfaces and netns - for iface in [iface1, iface2]: + for iface in [iface1, iface2, iface3, container_iface1, container_iface2, container_iface3]: call(f'sudo ip link del dev {iface}') delete_netns(ns1) delete_netns(ns2) - if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index debc8270c..e33be6644 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -34,6 +34,10 @@ prefix_list_in6 = 'pfx-foo-in6' prefix_list_out6 = 'pfx-foo-out6' bfd_profile = 'foo-bar-baz' +import_afi = 'ipv4-unicast' +import_vrf = 'red' +import_rd = ASN + ':100' +import_vrf_base = ['vrf', 'name'] neighbor_config = { '192.0.2.1' : { 'bfd' : '', @@ -189,6 +193,15 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) + + def create_bgp_instances_for_import_test(self): + table = '1000' + self.cli_set(base_path + ['system-as', ASN]) + # testing only one AFI is sufficient as it's generic code + + self.cli_set(import_vrf_base + [import_vrf, 'table', table]) + self.cli_set(import_vrf_base + [import_vrf, 'protocols', 'bgp', 'system-as', ASN]) + def verify_frr_config(self, peer, peer_config, frrconfig): # recurring patterns to verify for both a simple neighbor and a peer-group if 'bfd' in peer_config: @@ -959,6 +972,101 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' neighbor {neighbor} remote-as {remote_asn}', frrconfig) self.assertIn(f' neighbor {neighbor} local-as {local_asn}', frrconfig) + def test_bgp_16_import_rd_rt_compatibility(self): + # Verify if import vrf and rd vpn export + # exist in the same address family + self.create_bgp_instances_for_import_test() + self.cli_set( + base_path + ['address-family', import_afi, 'import', 'vrf', + import_vrf]) + self.cli_set( + base_path + ['address-family', import_afi, 'rd', 'vpn', 'export', + import_rd]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_17_import_rd_rt_compatibility(self): + # Verify if vrf that is in import vrf list contains rd vpn export + self.create_bgp_instances_for_import_test() + self.cli_set( + base_path + ['address-family', import_afi, 'import', 'vrf', + import_vrf]) + self.cli_commit() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}') + + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f'address-family ipv4 unicast', frrconfig) + self.assertIn(f' import vrf {import_vrf}', frrconfig) + self.assertIn(f'router bgp {ASN} vrf {import_vrf}', frrconfig_vrf) + + self.cli_set( + import_vrf_base + [import_vrf] + base_path + ['address-family', + import_afi, 'rd', + 'vpn', 'export', + import_rd]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_18_deleting_import_vrf(self): + # Verify deleting vrf that is in import vrf list + self.create_bgp_instances_for_import_test() + self.cli_set( + base_path + ['address-family', import_afi, 'import', 'vrf', + import_vrf]) + self.cli_commit() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f'address-family ipv4 unicast', frrconfig) + self.assertIn(f' import vrf {import_vrf}', frrconfig) + self.assertIn(f'router bgp {ASN} vrf {import_vrf}', frrconfig_vrf) + self.cli_delete(import_vrf_base + [import_vrf]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_19_deleting_default_vrf(self): + # Verify deleting existent vrf default if other vrfs were created + self.create_bgp_instances_for_import_test() + self.cli_commit() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f'router bgp {ASN} vrf {import_vrf}', frrconfig_vrf) + self.cli_delete(base_path) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_20_import_rd_rt_compatibility(self): + # Verify if vrf that has rd vpn export is in import vrf of other vrfs + self.create_bgp_instances_for_import_test() + self.cli_set( + import_vrf_base + [import_vrf] + base_path + ['address-family', + import_afi, 'rd', + 'vpn', 'export', + import_rd]) + self.cli_commit() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f'router bgp {ASN} vrf {import_vrf}', frrconfig_vrf) + self.assertIn(f'address-family ipv4 unicast', frrconfig_vrf) + self.assertIn(f' rd vpn export {import_rd}', frrconfig_vrf) + + self.cli_set( + base_path + ['address-family', import_afi, 'import', 'vrf', + import_vrf]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_21_import_unspecified_vrf(self): + # Verify if vrf that is in import is unspecified + self.create_bgp_instances_for_import_test() + self.cli_set( + base_path + ['address-family', import_afi, 'import', 'vrf', + 'test']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index 339713bf6..581959b15 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -74,6 +74,11 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['parameters', 'rfc1583-compatibility']) self.cli_set(base_path + ['log-adjacency-changes', 'detail']) self.cli_set(base_path + ['default-metric', metric]) + self.cli_set(base_path + ['passive-interface', 'default']) + self.cli_set(base_path + ['area', '10', 'area-type', 'stub']) + self.cli_set(base_path + ['area', '10', 'network', '10.0.0.0/16']) + self.cli_set(base_path + ['area', '10', 'range', '10.0.1.0/24']) + self.cli_set(base_path + ['area', '10', 'range', '10.0.2.0/24', 'not-advertise']) # commit changes self.cli_commit() @@ -88,6 +93,12 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults self.assertIn(f' capability opaque', frrconfig) self.assertIn(f' default-metric {metric}', frrconfig) + self.assertIn(f' passive-interface default', frrconfig) + self.assertIn(f' area 10 stub', frrconfig) + self.assertIn(f' network 10.0.0.0/16 area 10', frrconfig) + self.assertIn(f' area 10 range 10.0.1.0/24', frrconfig) + self.assertNotIn(f' area 10 range 10.0.1.0/24 not-advertise', frrconfig) + self.assertIn(f' area 10 range 10.0.2.0/24 not-advertise', frrconfig) def test_ospf_03_access_list(self): @@ -272,6 +283,10 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' passive-interface default', frrconfig) + for interface in interfaces: config = self.getFRRconfig(f'interface {interface}') self.assertIn(f'interface {interface}', config) diff --git a/smoketest/scripts/cli/test_qos.py b/smoketest/scripts/cli/test_qos.py new file mode 100755 index 000000000..0092473d6 --- /dev/null +++ b/smoketest/scripts/cli/test_qos.py @@ -0,0 +1,547 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 unittest + +from json import loads +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.util import cmd + +base_path = ['qos'] + +def get_tc_qdisc_json(interface) -> dict: + tmp = cmd(f'tc -detail -json qdisc show dev {interface}') + tmp = loads(tmp) + return next(iter(tmp)) + +def get_tc_filter_json(interface, direction) -> list: + if direction not in ['ingress', 'egress']: + raise ValueError() + tmp = cmd(f'tc -detail -json filter show dev {interface} {direction}') + tmp = loads(tmp) + return tmp + +class TestQoS(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestQoS, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + # We only test on physical interfaces and not VLAN (sub-)interfaces + cls._interfaces = [] + if 'TEST_ETH' in os.environ: + tmp = os.environ['TEST_ETH'].split() + cls._interfaces = tmp + else: + for tmp in Section.interfaces('ethernet', vlan=False): + cls._interfaces.append(tmp) + + def tearDown(self): + # delete testing SSH config + self.cli_delete(base_path) + self.cli_commit() + + def test_01_cake(self): + bandwidth = 1000000 + rtt = 200 + + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', 'cake', policy_name, 'bandwidth', str(bandwidth)]) + self.cli_set(base_path + ['policy', 'cake', policy_name, 'rtt', str(rtt)]) + self.cli_set(base_path + ['policy', 'cake', policy_name, 'flow-isolation', 'dual-src-host']) + + bandwidth += 1000000 + rtt += 20 + + # commit changes + self.cli_commit() + + bandwidth = 1000000 + rtt = 200 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + + self.assertEqual('cake', tmp['kind']) + # TC store rates as a 32-bit unsigned integer in bps (Bytes per second) + self.assertEqual(int(bandwidth *125), tmp['options']['bandwidth']) + # RTT internally is in us + self.assertEqual(int(rtt *1000), tmp['options']['rtt']) + self.assertEqual('dual-srchost', tmp['options']['flowmode']) + self.assertFalse(tmp['options']['ingress']) + self.assertFalse(tmp['options']['nat']) + self.assertTrue(tmp['options']['raw']) + + bandwidth += 1000000 + rtt += 20 + + def test_02_drop_tail(self): + queue_limit = 50 + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', 'drop-tail', policy_name, 'queue-limit', str(queue_limit)]) + + queue_limit += 10 + + # commit changes + self.cli_commit() + + queue_limit = 50 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + + self.assertEqual('pfifo', tmp['kind']) + self.assertEqual(queue_limit, tmp['options']['limit']) + + queue_limit += 10 + + def test_03_fair_queue(self): + hash_interval = 10 + queue_limit = 5 + policy_type = 'fair-queue' + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'hash-interval', str(hash_interval)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'queue-limit', str(queue_limit)]) + + hash_interval += 1 + queue_limit += 1 + + # commit changes + self.cli_commit() + + hash_interval = 10 + queue_limit = 5 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + + self.assertEqual('sfq', tmp['kind']) + self.assertEqual(hash_interval, tmp['options']['perturb']) + self.assertEqual(queue_limit, tmp['options']['limit']) + + hash_interval += 1 + queue_limit += 1 + + def test_04_fq_codel(self): + policy_type = 'fq-codel' + codel_quantum = 1500 + flows = 512 + interval = 100 + queue_limit = 2048 + target = 5 + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'codel-quantum', str(codel_quantum)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'flows', str(flows)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'interval', str(interval)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'queue-limit', str(queue_limit)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'target', str(target)]) + + codel_quantum += 10 + flows += 2 + interval += 10 + queue_limit += 512 + target += 1 + + # commit changes + self.cli_commit() + + codel_quantum = 1500 + flows = 512 + interval = 100 + queue_limit = 2048 + target = 5 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + + self.assertEqual('fq_codel', tmp['kind']) + self.assertEqual(codel_quantum, tmp['options']['quantum']) + self.assertEqual(flows, tmp['options']['flows']) + self.assertEqual(queue_limit, tmp['options']['limit']) + + # due to internal rounding we need to substract 1 from interval and target after converting to milliseconds + # configuration of: + # tc qdisc add dev eth0 root fq_codel quantum 1500 flows 512 interval 100ms limit 2048 target 5ms noecn + # results in: tc -j qdisc show dev eth0 + # [{"kind":"fq_codel","handle":"8046:","root":true,"refcnt":3,"options":{"limit":2048,"flows":512, + # "quantum":1500,"target":4999,"interval":99999,"memory_limit":33554432,"drop_batch":64}}] + self.assertAlmostEqual(tmp['options']['interval'], interval *1000, delta=1) + self.assertAlmostEqual(tmp['options']['target'], target *1000 -1, delta=1) + + codel_quantum += 10 + flows += 2 + interval += 10 + queue_limit += 512 + target += 1 + + def test_05_limiter(self): + qos_config = { + '1' : { + 'bandwidth' : '1000000', + 'match4' : { + 'ssh' : { 'dport' : '22', }, + }, + }, + '2' : { + 'bandwidth' : '1000000', + 'match6' : { + 'ssh' : { 'dport' : '22', }, + }, + }, + } + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'egress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # set default bandwidth parameter for all remaining connections + self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'bandwidth', '500000']) + + for qos_class, qos_class_config in qos_config.items(): + qos_class_base = base_path + ['policy', 'limiter', policy_name, 'class', qos_class] + + if 'match4' in qos_class_config: + for match, match_config in qos_class_config['match4'].items(): + if 'dport' in match_config: + self.cli_set(qos_class_base + ['match', match, 'ip', 'destination', 'port', match_config['dport']]) + + if 'match6' in qos_class_config: + for match, match_config in qos_class_config['match6'].items(): + if 'dport' in match_config: + self.cli_set(qos_class_base + ['match', match, 'ipv6', 'destination', 'port', match_config['dport']]) + + if 'bandwidth' in qos_class_config: + self.cli_set(qos_class_base + ['bandwidth', qos_class_config['bandwidth']]) + + + # commit changes + self.cli_commit() + + for interface in self._interfaces: + for filter in get_tc_filter_json(interface, 'ingress'): + # bail out early if filter has no attached action + if 'options' not in filter or 'actions' not in filter['options']: + continue + + for qos_class, qos_class_config in qos_config.items(): + # Every flowid starts with ffff and we encopde the class number after the colon + if 'flowid' not in filter['options'] or filter['options']['flowid'] != f'ffff:{qos_class}': + continue + + ip_hdr_offset = 20 + if 'match6' in qos_class_config: + ip_hdr_offset = 40 + + self.assertEqual(ip_hdr_offset, filter['options']['match']['off']) + if 'dport' in match_config: + dport = int(match_config['dport']) + self.assertEqual(f'{dport:x}', filter['options']['match']['value']) + + def test_06_network_emulator(self): + policy_type = 'network-emulator' + + bandwidth = 1000000 + corruption = 1 + delay = 2 + duplicate = 3 + loss = 4 + queue_limit = 5 + reordering = 6 + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + + self.cli_set(base_path + ['policy', policy_type, policy_name, 'bandwidth', str(bandwidth)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'corruption', str(corruption)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'delay', str(delay)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'duplicate', str(duplicate)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'loss', str(loss)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'queue-limit', str(queue_limit)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'reordering', str(reordering)]) + + bandwidth += 1000000 + corruption += 1 + delay += 1 + duplicate +=1 + loss += 1 + queue_limit += 1 + reordering += 1 + + # commit changes + self.cli_commit() + + bandwidth = 1000000 + corruption = 1 + delay = 2 + duplicate = 3 + loss = 4 + queue_limit = 5 + reordering = 6 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + self.assertEqual('netem', tmp['kind']) + + self.assertEqual(int(bandwidth *125), tmp['options']['rate']['rate']) + # values are in % + self.assertEqual(corruption/100, tmp['options']['corrupt']['corrupt']) + self.assertEqual(duplicate/100, tmp['options']['duplicate']['duplicate']) + self.assertEqual(loss/100, tmp['options']['loss-random']['loss']) + self.assertEqual(reordering/100, tmp['options']['reorder']['reorder']) + self.assertEqual(delay/1000, tmp['options']['delay']['delay']) + + self.assertEqual(queue_limit, tmp['options']['limit']) + + bandwidth += 1000000 + corruption += 1 + delay += 1 + duplicate += 1 + loss += 1 + queue_limit += 1 + reordering += 1 + + def test_07_priority_queue(self): + priorities = ['1', '2', '3', '4', '5'] + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', 'priority-queue', policy_name, 'default', 'queue-limit', '10']) + + for priority in priorities: + prio_base = base_path + ['policy', 'priority-queue', policy_name, 'class', priority] + self.cli_set(prio_base + ['match', f'prio-{priority}', 'ip', 'destination', 'port', str(1000 + int(priority))]) + + # commit changes + self.cli_commit() + + def test_08_random_detect(self): + self.skipTest('tc returns invalid JSON here - needs iproute2 fix') + bandwidth = 5000 + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', 'random-detect', policy_name, 'bandwidth', str(bandwidth)]) + + bandwidth += 1000 + + # commit changes + self.cli_commit() + + bandwidth = 5000 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + import pprint + pprint.pprint(tmp) + + def test_09_rate_control(self): + bandwidth = 5000 + burst = 20 + latency = 5 + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', 'rate-control', policy_name, 'bandwidth', str(bandwidth)]) + self.cli_set(base_path + ['policy', 'rate-control', policy_name, 'burst', str(burst)]) + self.cli_set(base_path + ['policy', 'rate-control', policy_name, 'latency', str(latency)]) + + bandwidth += 1000 + burst += 5 + latency += 1 + # commit changes + self.cli_commit() + + bandwidth = 5000 + burst = 20 + latency = 5 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + + self.assertEqual('tbf', tmp['kind']) + self.assertEqual(0, tmp['options']['mpu']) + # TC store rates as a 32-bit unsigned integer in bps (Bytes per second) + self.assertEqual(int(bandwidth * 125), tmp['options']['rate']) + + bandwidth += 1000 + burst += 5 + latency += 1 + + def test_10_round_robin(self): + qos_config = { + '1' : { + 'match4' : { + 'ssh' : { 'dport' : '22', }, + }, + }, + '2' : { + 'match6' : { + 'ssh' : { 'dport' : '22', }, + }, + }, + } + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + + for qos_class, qos_class_config in qos_config.items(): + qos_class_base = base_path + ['policy', 'round-robin', policy_name, 'class', qos_class] + + if 'match4' in qos_class_config: + for match, match_config in qos_class_config['match4'].items(): + if 'dport' in match_config: + self.cli_set(qos_class_base + ['match', match, 'ip', 'destination', 'port', match_config['dport']]) + + if 'match6' in qos_class_config: + for match, match_config in qos_class_config['match6'].items(): + if 'dport' in match_config: + self.cli_set(qos_class_base + ['match', match, 'ipv6', 'destination', 'port', match_config['dport']]) + + + # commit changes + self.cli_commit() + + for interface in self._interfaces: + import pprint + tmp = get_tc_qdisc_json(interface) + self.assertEqual('drr', tmp['kind']) + + for filter in get_tc_filter_json(interface, 'ingress'): + # bail out early if filter has no attached action + if 'options' not in filter or 'actions' not in filter['options']: + continue + + for qos_class, qos_class_config in qos_config.items(): + # Every flowid starts with ffff and we encopde the class number after the colon + if 'flowid' not in filter['options'] or filter['options']['flowid'] != f'ffff:{qos_class}': + continue + + ip_hdr_offset = 20 + if 'match6' in qos_class_config: + ip_hdr_offset = 40 + + self.assertEqual(ip_hdr_offset, filter['options']['match']['off']) + if 'dport' in match_config: + dport = int(match_config['dport']) + self.assertEqual(f'{dport:x}', filter['options']['match']['value']) + +if __name__ == '__main__': + unittest.main(verbosity=2, failfast=True) diff --git a/smoketest/scripts/cli/test_service_dhcpv6-relay.py b/smoketest/scripts/cli/test_service_dhcpv6-relay.py index fc206435b..8bb58d296 100755 --- a/smoketest/scripts/cli/test_service_dhcpv6-relay.py +++ b/smoketest/scripts/cli/test_service_dhcpv6-relay.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -34,22 +34,30 @@ listen_addr = '2001:db8:ffff::1/64' interfaces = [] class TestServiceDHCPv6Relay(VyOSUnitTestSHIM.TestCase): - def setUp(self): - for tmp in interfaces: + @classmethod + def setUpClass(cls): + super(TestServiceDHCPv6Relay, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + for tmp in Section.interfaces('ethernet', vlan=False): + interfaces.append(tmp) listen = listen_addr if tmp == upstream_if: listen = upstream_if_addr - self.cli_set(['interfaces', 'ethernet', tmp, 'address', listen]) + cls.cli_set(cls, ['interfaces', 'ethernet', tmp, 'address', listen]) - def tearDown(self): - self.cli_delete(base_path) + @classmethod + def tearDownClass(cls): for tmp in interfaces: listen = listen_addr if tmp == upstream_if: listen = upstream_if_addr - self.cli_delete(['interfaces', 'ethernet', tmp, 'address', listen]) + cls.cli_delete(cls, ['interfaces', 'ethernet', tmp, 'address', listen]) - self.cli_commit() + super(TestServiceDHCPv6Relay, cls).tearDownClass() def test_relay_default(self): dhcpv6_server = '2001:db8::ffff' @@ -100,9 +108,5 @@ class TestServiceDHCPv6Relay(VyOSUnitTestSHIM.TestCase): self.assertTrue(process_named_running(PROCESS_NAME)) if __name__ == '__main__': - for tmp in Section.interfaces('ethernet'): - if '.' not in tmp: - interfaces.append(tmp) - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_service_ntp.py index a0806acf0..3ccd19a31 100755 --- a/smoketest/scripts/cli/test_system_ntp.py +++ b/smoketest/scripts/cli/test_service_ntp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2022 VyOS maintainers and contributors +# Copyright (C) 2019-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 @@ -19,14 +19,12 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.template import address_from_cidr -from vyos.template import netmask_from_cidr -from vyos.util import read_file +from vyos.util import cmd from vyos.util import process_named_running -PROCESS_NAME = 'ntpd' -NTP_CONF = '/run/ntpd/ntpd.conf' -base_path = ['system', 'ntp'] +PROCESS_NAME = 'chronyd' +NTP_CONF = '/run/chrony/chrony.conf' +base_path = ['service', 'ntp'] class TestSystemNTP(VyOSUnitTestSHIM.TestCase): @classmethod @@ -38,6 +36,8 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase): cls.cli_delete(cls, base_path) def tearDown(self): + self.assertTrue(process_named_running(PROCESS_NAME)) + self.cli_delete(base_path) self.cli_commit() @@ -46,7 +46,7 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase): def test_01_ntp_options(self): # Test basic NTP support with multiple servers and their options servers = ['192.0.2.1', '192.0.2.2'] - options = ['noselect', 'preempt', 'prefer'] + options = ['noselect', 'prefer'] pools = ['pool.vyos.io'] for server in servers: @@ -61,12 +61,14 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Check generated configuration - config = read_file(NTP_CONF) - self.assertIn('driftfile /var/lib/ntp/ntp.drift', config) - self.assertIn('restrict default noquery nopeer notrap nomodify', config) - self.assertIn('restrict source nomodify notrap noquery', config) - self.assertIn('restrict 127.0.0.1', config) - self.assertIn('restrict -6 ::1', config) + # this file must be read with higher permissions + config = cmd(f'sudo cat {NTP_CONF}') + self.assertIn('driftfile /run/chrony/drift', config) + self.assertIn('dumpdir /run/chrony', config) + self.assertIn('clientloglimit 1048576', config) + self.assertIn('rtcsync', config) + self.assertIn('makestep 1.0 3', config) + self.assertIn('leapsectz right/UTC', config) for server in servers: self.assertIn(f'server {server} iburst ' + ' '.join(options), config) @@ -80,9 +82,9 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase): for listen in listen_address: self.cli_set(base_path + ['listen-address', listen]) - networks = ['192.0.2.0/24', '2001:db8:1000::/64'] + networks = ['192.0.2.0/24', '2001:db8:1000::/64', '100.64.0.0', '2001:db8::ffff'] for network in networks: - self.cli_set(base_path + ['allow-clients', 'address', network]) + self.cli_set(base_path + ['allow-client', 'address', network]) # Verify "NTP server not configured" verify() statement with self.assertRaises(ConfigSessionError): @@ -95,18 +97,14 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Check generated client address configuration - config = read_file(NTP_CONF) - self.assertIn('restrict default ignore', config) - + # this file must be read with higher permissions + config = cmd(f'sudo cat {NTP_CONF}') for network in networks: - network_address = address_from_cidr(network) - network_netmask = netmask_from_cidr(network) - self.assertIn(f'restrict {network_address} mask {network_netmask} nomodify notrap nopeer', config) + self.assertIn(f'allow {network}', config) # Check listen address - self.assertIn('interface ignore wildcard', config) for listen in listen_address: - self.assertIn(f'interface listen {listen}', config) + self.assertIn(f'bindaddress {listen}', config) def test_03_ntp_interface(self): interfaces = ['eth0', 'eth1'] @@ -120,10 +118,28 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Check generated client address configuration - config = read_file(NTP_CONF) - self.assertIn('interface ignore wildcard', config) + # this file must be read with higher permissions + config = cmd(f'sudo cat {NTP_CONF}') for interface in interfaces: - self.assertIn(f'interface listen {interface}', config) + self.assertIn(f'binddevice {interface}', config) + + def test_04_ntp_vrf(self): + vrf_name = 'vyos-mgmt' + + self.cli_set(['vrf', 'name', vrf_name, 'table', '12345']) + self.cli_set(base_path + ['vrf', vrf_name]) + + servers = ['time1.vyos.net', 'time2.vyos.net'] + for server in servers: + self.cli_set(base_path + ['server', server]) + + self.cli_commit() + + # Check for process in VRF + tmp = cmd(f'ip vrf pids {vrf_name}') + self.assertIn(PROCESS_NAME, tmp) + + self.cli_delete(['vrf', 'name', vrf_name]) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py index 873be7df0..0169b7934 100755 --- a/smoketest/scripts/cli/test_service_router-advert.py +++ b/smoketest/scripts/cli/test_service_router-advert.py @@ -37,7 +37,6 @@ def get_config_value(key): return tmp[0].split()[0].replace(';','') class TestServiceRADVD(VyOSUnitTestSHIM.TestCase): - @classmethod def setUpClass(cls): super(TestServiceRADVD, cls).setUpClass() @@ -114,7 +113,6 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase): tmp = get_config_value('DecrementLifetimes') self.assertEqual(tmp, 'off') - def test_dns(self): nameserver = ['2001:db8::1', '2001:db8::2'] dnssl = ['vyos.net', 'vyos.io'] @@ -150,7 +148,6 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase): tmp = 'DNSSL ' + ' '.join(dnssl) + ' {' self.assertIn(tmp, config) - def test_deprecate_prefix(self): self.cli_set(base_path + ['prefix', prefix, 'valid-lifetime', 'infinity']) self.cli_set(base_path + ['prefix', prefix, 'deprecate-prefix']) @@ -159,13 +156,45 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() - config = read_file(RADVD_CONF) - tmp = get_config_value('DeprecatePrefix') self.assertEqual(tmp, 'on') tmp = get_config_value('DecrementLifetimes') self.assertEqual(tmp, 'on') + def test_route(self): + route = '2001:db8:1000::/64' + + self.cli_set(base_path + ['prefix', prefix]) + self.cli_set(base_path + ['route', route]) + + # commit changes + self.cli_commit() + + config = read_file(RADVD_CONF) + + tmp = f'route {route}' + ' {' + self.assertIn(tmp, config) + + self.assertIn('AdvRouteLifetime 1800;', config) + self.assertIn('AdvRoutePreference medium;', config) + self.assertIn('RemoveRoute on;', config) + + def test_rasrcaddress(self): + ra_src = ['fe80::1', 'fe80::2'] + + self.cli_set(base_path + ['prefix', prefix]) + for src in ra_src: + self.cli_set(base_path + ['source-address', src]) + + # commit changes + self.cli_commit() + + config = read_file(RADVD_CONF) + self.assertIn('AdvRASrcAddress {', config) + for src in ra_src: + self.assertIn(f' {src};', config) + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_tftp-server.py b/smoketest/scripts/cli/test_service_tftp-server.py index b57c33f26..99d81e203 100755 --- a/smoketest/scripts/cli/test_service_tftp-server.py +++ b/smoketest/scripts/cli/test_service_tftp-server.py @@ -33,15 +33,32 @@ address_ipv6 = '2001:db8::1' vrf = 'mgmt' class TestServiceTFTPD(VyOSUnitTestSHIM.TestCase): - def setUp(self): - self.cli_set(dummy_if_path + ['address', address_ipv4 + '/32']) - self.cli_set(dummy_if_path + ['address', address_ipv6 + '/128']) + @classmethod + def setUpClass(cls): + super(TestServiceTFTPD, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + cls.cli_set(cls, dummy_if_path + ['address', address_ipv4 + '/32']) + cls.cli_set(cls, dummy_if_path + ['address', address_ipv6 + '/128']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, dummy_if_path) + super(TestServiceTFTPD, cls).tearDownClass() def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + self.cli_delete(base_path) - self.cli_delete(dummy_if_path) self.cli_commit() + # Check for no longer running process + self.assertFalse(process_named_running(PROCESS_NAME)) + def test_01_tftpd_single(self): directory = '/tmp' port = '69' # default port @@ -61,9 +78,6 @@ class TestServiceTFTPD(VyOSUnitTestSHIM.TestCase): # verify upload self.assertIn('--create --umask 000', config) - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - def test_02_tftpd_multi(self): directory = '/tmp' address = [address_ipv4, address_ipv6] @@ -125,9 +139,6 @@ class TestServiceTFTPD(VyOSUnitTestSHIM.TestCase): # verify upload self.assertIn('--create --umask 000', config) - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - # Check for process in VRF tmp = cmd(f'ip vrf pids {vrf}') self.assertIn(PROCESS_NAME, tmp) diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py index bd242104f..46db0bbf5 100755 --- a/smoketest/scripts/cli/test_vpn_ipsec.py +++ b/smoketest/scripts/cli/test_vpn_ipsec.py @@ -39,6 +39,7 @@ vif = '100' esp_group = 'MyESPGroup' ike_group = 'MyIKEGroup' secret = 'MYSECRETKEY' +PROCESS_NAME = 'charon' ca_pem = """ MIIDSzCCAjOgAwIBAgIUQHK+ZgTUYZksvXY2/MyW+Jiels4wDQYJKoZIhvcNAQEL @@ -137,14 +138,14 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): def tearDown(self): # Check for running process - self.assertTrue(process_named_running('charon')) + self.assertTrue(process_named_running(PROCESS_NAME)) self.cli_delete(base_path) self.cli_delete(tunnel_path) self.cli_commit() # Check for no longer running process - self.assertFalse(process_named_running('charon')) + self.assertFalse(process_named_running(PROCESS_NAME)) def test_01_dhcp_fail_handling(self): # Interface for dhcp-interface @@ -166,6 +167,8 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): dhcp_waiting = read_file(dhcp_waiting_file) self.assertIn(f'{interface}.{vif}', dhcp_waiting) # Ensure dhcp-failed interface was added for dhclient hook + self.cli_delete(ethernet_path + [interface, 'vif', vif, 'address']) + def test_02_site_to_site(self): self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) diff --git a/smoketest/scripts/cli/test_vpn_openconnect.py b/smoketest/scripts/cli/test_vpn_openconnect.py index 8572d6d66..ec8ecacb9 100755 --- a/smoketest/scripts/cli/test_vpn_openconnect.py +++ b/smoketest/scripts/cli/test_vpn_openconnect.py @@ -18,6 +18,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.template import ip_from_cidr from vyos.util import process_named_running from vyos.util import read_file @@ -52,6 +53,9 @@ config_file = '/run/ocserv/ocserv.conf' auth_file = '/run/ocserv/ocpasswd' otp_file = '/run/ocserv/users.oath' +listen_if = 'dum116' +listen_address = '100.64.0.1/32' + class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): @@ -61,6 +65,8 @@ class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase): # out the current configuration :) cls.cli_delete(cls, base_path) + cls.cli_set(cls, ['interfaces', 'dummy', listen_if, 'address', listen_address]) + cls.cli_set(cls, pki_path + ['ca', 'openconnect', 'certificate', cert_data.replace('\n','')]) cls.cli_set(cls, pki_path + ['certificate', 'openconnect', 'certificate', cert_data.replace('\n','')]) cls.cli_set(cls, pki_path + ['certificate', 'openconnect', 'private', 'key', key_data.replace('\n','')]) @@ -68,6 +74,7 @@ class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase): @classmethod def tearDownClass(cls): cls.cli_delete(cls, pki_path) + cls.cli_delete(cls, ['interfaces', 'dummy', listen_if]) super(TestVPNOpenConnect, cls).tearDownClass() def tearDown(self): @@ -104,6 +111,9 @@ class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['ssl', 'ca-certificate', 'openconnect']) self.cli_set(base_path + ['ssl', 'certificate', 'openconnect']) + listen_ip_no_cidr = ip_from_cidr(listen_address) + self.cli_set(base_path + ['listen-address', listen_ip_no_cidr]) + self.cli_commit() # Verify configuration @@ -111,10 +121,15 @@ class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase): # authentication mode local password-otp self.assertIn(f'auth = "plain[passwd=/run/ocserv/ocpasswd,otp=/run/ocserv/users.oath]"', daemon_config) + self.assertIn(f'listen-host = {listen_ip_no_cidr}', daemon_config) self.assertIn(f'ipv4-network = {v4_subnet}', daemon_config) self.assertIn(f'ipv6-network = {v6_prefix}', daemon_config) self.assertIn(f'ipv6-subnet-prefix = {v6_len}', daemon_config) + # defaults + self.assertIn(f'tcp-port = 443', daemon_config) + self.assertIn(f'udp-port = 443', daemon_config) + for ns in name_server: self.assertIn(f'dns = {ns}', daemon_config) for domain in split_dns: diff --git a/smoketest/scripts/system/test_module_load.py b/smoketest/scripts/system/test_module_load.py index 76a41ac4d..bd30c57ec 100755 --- a/smoketest/scripts/system/test_module_load.py +++ b/smoketest/scripts/system/test_module_load.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-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 @@ -23,8 +23,7 @@ modules = { "intel_qat": ["qat_200xx", "qat_200xxvf", "qat_c3xxx", "qat_c3xxxvf", "qat_c62x", "qat_c62xvf", "qat_d15xx", "qat_d15xxvf", "qat_dh895xcc", "qat_dh895xccvf"], - "accel_ppp": ["ipoe", "vlan_mon"], - "misc": ["wireguard"] + "accel_ppp": ["ipoe", "vlan_mon"] } class TestKernelModules(unittest.TestCase): diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 8efeaed54..7567444db 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -73,9 +73,19 @@ def get_config(config=None): # Merge per-container default values if 'name' in container: default_values = defaults(base + ['name']) + if 'port' in default_values: + del default_values['port'] for name in container['name']: container['name'][name] = dict_merge(default_values, container['name'][name]) + # XXX: T2665: we can not safely rely on the defaults() when there are + # tagNodes in place, it is better to blend in the defaults manually. + if 'port' in container['name'][name]: + for port in container['name'][name]['port']: + default_values = defaults(base + ['name', 'port']) + container['name'][name]['port'][port] = dict_merge( + default_values, container['name'][name]['port'][port]) + # Delete container network, delete containers tmp = node_changed(conf, base + ['network']) if tmp: container.update({'network_remove' : tmp}) @@ -168,6 +178,11 @@ def verify(container): if not os.path.exists(source): raise ConfigError(f'Volume "{volume}" source path "{source}" does not exist!') + if 'port' in container_config: + for tmp in container_config['port']: + if not {'source', 'destination'} <= set(container_config['port'][tmp]): + raise ConfigError(f'Both "source" and "destination" must be specified for a port mapping!') + # If 'allow-host-networks' or 'network' not set. if 'allow_host_networks' not in container_config and 'network' not in container_config: raise ConfigError(f'Must either set "network" or "allow-host-networks" for container "{name}"!') @@ -237,14 +252,10 @@ def generate_run_arguments(name, container_config): if 'port' in container_config: protocol = '' for portmap in container_config['port']: - if 'protocol' in container_config['port'][portmap]: - protocol = container_config['port'][portmap]['protocol'] - protocol = f'/{protocol}' - else: - protocol = '/tcp' + protocol = container_config['port'][portmap]['protocol'] sport = container_config['port'][portmap]['source'] dport = container_config['port'][portmap]['destination'] - port += f' -p {sport}:{dport}{protocol}' + port += f' -p {sport}:{dport}/{protocol}' # Bind volume volume = '' diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py index 7e16235c1..f67f1710e 100755 --- a/src/conf_mode/flow_accounting_conf.py +++ b/src/conf_mode/flow_accounting_conf.py @@ -38,7 +38,7 @@ airbag.enable() uacctd_conf_path = '/run/pmacct/uacctd.conf' systemd_service = 'uacctd.service' -systemd_override = f'/etc/systemd/system/{systemd_service}.d/override.conf' +systemd_override = f'/run/systemd/system/{systemd_service}.d/override.conf' nftables_nflog_table = 'raw' nftables_nflog_chain = 'VYOS_CT_PREROUTING_HOOK' egress_nftables_nflog_table = 'inet mangle' @@ -192,7 +192,7 @@ def verify(flow_config): raise ConfigError("All sFlow servers must use the same IP protocol") else: sflow_collector_ipver = ip_address(server).version - + # check if vrf is defined for Sflow sflow_vrf = None if 'vrf' in flow_config: diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py index 8a959dc79..4ed16d0d7 100755 --- a/src/conf_mode/high-availability.py +++ b/src/conf_mode/high-availability.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2022 VyOS maintainers and contributors +# Copyright (C) 2018-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 @@ -144,8 +144,10 @@ def verify(ha): # Virtual-server if 'virtual_server' in ha: for vs, vs_config in ha['virtual_server'].items(): - if 'port' not in vs_config: - raise ConfigError(f'Port is required 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: + raise ConfigError(f'Cannot set both port and fwmark for virtual-server "{vs}"') if 'real_server' not in vs_config: raise ConfigError(f'Real-server ip is required but not set for virtual-server "{vs}"') # Real-server diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index 7cd7ea42e..ce5e63928 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -37,7 +37,7 @@ from vyos import airbag airbag.enable() config_file = '/etc/nginx/sites-available/default' -systemd_override = r'/etc/systemd/system/nginx.service.d/override.conf' +systemd_override = r'/run/systemd/system/nginx.service.d/override.conf' cert_dir = '/etc/ssl/certs' key_dir = '/etc/ssl/private' certbot_dir = vyos.defaults.directories['certbot'] diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 08cc3a48d..f6694ddde 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -14,14 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os - from sys import exit from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict -from vyos.configdict import leaf_node_changed from vyos.configdict import is_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_mtu_ipv6 @@ -49,13 +46,10 @@ def get_config(config=None): # GENEVE interfaces are picky and require recreation if certain parameters # change. But a GENEVE interface should - of course - not be re-created if # it's description or IP address is adjusted. Feels somehow logic doesn't it? - for cli_option in ['remote', 'vni']: - if leaf_node_changed(conf, base + [ifname, cli_option]): + for cli_option in ['remote', 'vni', 'parameters']: + if is_node_changed(conf, base + [ifname, cli_option]): geneve.update({'rebuild_required': {}}) - if is_node_changed(conf, base + [ifname, 'parameters']): - geneve.update({'rebuild_required': {}}) - return geneve def verify(geneve): diff --git a/src/conf_mode/interfaces-input.py b/src/conf_mode/interfaces-input.py new file mode 100755 index 000000000..ad248843d --- /dev/null +++ b/src/conf_mode/interfaces-input.py @@ -0,0 +1,70 @@ +#!/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 exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_mirror_redirect +from vyos.ifconfig import InputIf +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +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 + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'input'] + _, ifb = get_interface_dict(conf, base) + + return ifb + +def verify(ifb): + if 'deleted' in ifb: + return None + + verify_mirror_redirect(ifb) + return None + +def generate(ifb): + return None + +def apply(ifb): + d = InputIf(ifb['ifname']) + + # Remove input interface + if 'deleted' in ifb: + d.remove() + else: + d.update(ifb) + + 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/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index 4c65bc0b6..dce5c2358 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -21,7 +21,7 @@ from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configdict import is_node_changed from vyos.configdict import is_source_interface -from vyos.configdict import leaf_node_changed +from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete @@ -51,7 +51,7 @@ def get_config(config=None): mode = is_node_changed(conf, ['mode']) if mode: peth.update({'shutdown_required' : {}}) - if leaf_node_changed(conf, base + [ifname, 'mode']): + if is_node_changed(conf, base + [ifname, 'mode']): peth.update({'rebuild_required': {}}) if 'source_interface' in peth: diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index acef1fda7..e2701d9d3 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -21,7 +21,7 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict -from vyos.configdict import leaf_node_changed +from vyos.configdict import is_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_interface_exists @@ -52,7 +52,7 @@ def get_config(config=None): ifname, tunnel = get_interface_dict(conf, base) if 'deleted' not in tunnel: - tmp = leaf_node_changed(conf, base + [ifname, 'encapsulation']) + tmp = is_node_changed(conf, base + [ifname, 'encapsulation']) if tmp: tunnel.update({'encapsulation_changed': {}}) # We also need to inspect other configured tunnels as there are Kernel diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index af2d0588d..b1536148c 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -52,13 +52,11 @@ def get_config(config=None): # VXLAN interfaces are picky and require recreation if certain parameters # change. But a VXLAN interface should - of course - not be re-created if # it's description or IP address is adjusted. Feels somehow logic doesn't it? - for cli_option in ['external', 'gpe', 'group', 'port', 'remote', + for cli_option in ['parameters', 'external', 'gpe', 'group', 'port', 'remote', 'source-address', 'source-interface', 'vni']: - if leaf_node_changed(conf, base + [ifname, cli_option]): + if is_node_changed(conf, base + [ifname, cli_option]): vxlan.update({'rebuild_required': {}}) - - if is_node_changed(conf, base + [ifname, 'parameters']): - vxlan.update({'rebuild_required': {}}) + break # We need to verify that no other VXLAN tunnel is configured when external # mode is in use - Linux Kernel limitation diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py index 0ecb4d736..92cb73aab 100755 --- a/src/conf_mode/ntp.py +++ b/src/conf_mode/ntp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2022 VyOS maintainers and contributors +# Copyright (C) 2018-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 @@ -21,26 +21,29 @@ from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf from vyos.configverify import verify_interface_exists from vyos.util import call +from vyos.util import chmod_750 from vyos.util import get_interface_config from vyos.template import render from vyos import ConfigError from vyos import airbag airbag.enable() -config_file = r'/run/ntpd/ntpd.conf' -systemd_override = r'/etc/systemd/system/ntp.service.d/override.conf' +config_file = r'/run/chrony/chrony.conf' +systemd_override = r'/run/systemd/system/chrony.service.d/override.conf' +user_group = '_chrony' def get_config(config=None): if config: conf = config else: conf = Config() - base = ['system', 'ntp'] + base = ['service', 'ntp'] if not conf.exists(base): return None ntp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) ntp['config_file'] = config_file + ntp['user'] = user_group tmp = is_node_changed(conf, base + ['vrf']) if tmp: ntp.update({'restart_required': {}}) @@ -52,7 +55,7 @@ def verify(ntp): if not ntp: return None - if 'allow_clients' in ntp and 'server' not in ntp: + if 'server' not in ntp: raise ConfigError('NTP server not configured') verify_vrf(ntp) @@ -77,13 +80,17 @@ def generate(ntp): if not ntp: return None - render(config_file, 'ntp/ntpd.conf.j2', ntp) - render(systemd_override, 'ntp/override.conf.j2', ntp) + render(config_file, 'chrony/chrony.conf.j2', ntp, user=user_group, group=user_group) + render(systemd_override, 'chrony/override.conf.j2', ntp, user=user_group, group=user_group) + + # Ensure proper permission for chrony command socket + config_dir = os.path.dirname(config_file) + chmod_750(config_dir) return None def apply(ntp): - systemd_service = 'ntp.service' + systemd_service = 'chrony.service' # Reload systemd manager configuration call('systemctl daemon-reload') diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py index e8f3cc87a..54de467ca 100755 --- a/src/conf_mode/pki.py +++ b/src/conf_mode/pki.py @@ -51,6 +51,11 @@ sync_search = [ 'script': '/usr/libexec/vyos/conf_mode/interfaces-openvpn.py' }, { + 'keys': ['ca_certificate'], + 'path': ['interfaces', 'sstpc'], + 'script': '/usr/libexec/vyos/conf_mode/interfaces-sstpc.py' + }, + { 'keys': ['certificate', 'ca_certificate', 'local_key', 'remote_key'], 'path': ['vpn', 'ipsec'], 'script': '/usr/libexec/vyos/conf_mode/vpn_ipsec.py' diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index ff568d470..c410258ee 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -14,8 +14,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os - from sys import exit from sys import argv @@ -57,13 +55,18 @@ def get_config(config=None): # instead of the VRF instance. if vrf: bgp.update({'vrf' : vrf}) + bgp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + bgp['dependent_vrfs'].update({'default': {'protocols': { + 'bgp': conf.get_config_dict(base_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True)}}}) if not conf.exists(base): + # If bgp instance is deleted then mark it bgp.update({'deleted' : ''}) - if not vrf: - # We are running in the default VRF context, thus we can not delete - # our main BGP instance if there are dependent BGP VRF instances. - bgp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'], - key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) return bgp # We also need some additional information from the config, prefix-lists @@ -74,9 +77,91 @@ def get_config(config=None): tmp = conf.get_config_dict(['policy']) # Merge policy dict into "regular" config dict bgp = dict_merge(tmp, bgp) - return bgp + +def verify_vrf_as_import(search_vrf_name: str, afi_name: str, vrfs_config: dict) -> bool: + """ + :param search_vrf_name: search vrf name in import list + :type search_vrf_name: str + :param afi_name: afi/safi name + :type afi_name: str + :param vrfs_config: configuration dependents vrfs + :type vrfs_config: dict + :return: if vrf in import list retrun true else false + :rtype: bool + """ + for vrf_name, vrf_config in vrfs_config.items(): + import_list = dict_search( + f'protocols.bgp.address_family.{afi_name}.import.vrf', + vrf_config) + if import_list: + if search_vrf_name in import_list: + return True + return False + +def verify_vrf_import_options(afi_config: dict) -> bool: + """ + Search if afi contains one of options + :param afi_config: afi/safi + :type afi_config: dict + :return: if vrf contains rd and route-target options return true else false + :rtype: bool + """ + options = [ + f'rd.vpn.export', + f'route_target.vpn.import', + f'route_target.vpn.export', + f'route_target.vpn.both' + ] + for option in options: + if dict_search(option, afi_config): + return True + return False + +def verify_vrf_import(vrf_name: str, vrfs_config: dict, afi_name: str) -> bool: + """ + Verify if vrf exists and contain options + :param vrf_name: name of VRF + :type vrf_name: str + :param vrfs_config: dependent vrfs config + :type vrfs_config: dict + :param afi_name: afi/safi name + :type afi_name: str + :return: if vrf contains rd and route-target options return true else false + :rtype: bool + """ + if vrf_name != 'default': + verify_vrf({'vrf': vrf_name}) + if dict_search(f'{vrf_name}.protocols.bgp.address_family.{afi_name}', + vrfs_config): + afi_config = \ + vrfs_config[vrf_name]['protocols']['bgp']['address_family'][ + afi_name] + if verify_vrf_import_options(afi_config): + return True + return False + +def verify_vrflist_import(afi_name: str, afi_config: dict, vrfs_config: dict) -> bool: + """ + Call function to verify + if scpecific vrf contains rd and route-target + options return true else false + + :param afi_name: afi/safi name + :type afi_name: str + :param afi_config: afi/safi configuration + :type afi_config: dict + :param vrfs_config: dependent vrfs config + :type vrfs_config:dict + :return: if vrf contains rd and route-target options return true else false + :rtype: bool + """ + for vrf_name in afi_config['import']['vrf']: + if verify_vrf_import(vrf_name, vrfs_config, afi_name): + return True + return False + def verify_remote_as(peer_config, bgp_config): if 'remote_as' in peer_config: return peer_config['remote_as'] @@ -113,12 +198,22 @@ def verify_afi(peer_config, bgp_config): return False def verify(bgp): - if not bgp or 'deleted' in bgp: - if 'dependent_vrfs' in bgp: - for vrf, vrf_options in bgp['dependent_vrfs'].items(): - if dict_search('protocols.bgp', vrf_options) != None: - raise ConfigError('Cannot delete default BGP instance, ' \ - 'dependent VRF instance(s) exist!') + if 'deleted' in bgp: + if 'vrf' in bgp: + # Cannot delete vrf if it exists in import vrf list in other vrfs + for tmp_afi in ['ipv4_unicast', 'ipv6_unicast']: + if verify_vrf_as_import(bgp['vrf'],tmp_afi,bgp['dependent_vrfs']): + raise ConfigError(f'Cannot delete vrf {bgp["vrf"]} instance, ' \ + 'Please unconfigure import vrf commands!') + else: + # We are running in the default VRF context, thus we can not delete + # our main BGP instance if there are dependent BGP VRF instances. + if 'dependent_vrfs' in bgp: + for vrf, vrf_options in bgp['dependent_vrfs'].items(): + if vrf != 'default': + if dict_search('protocols.bgp', vrf_options): + raise ConfigError('Cannot delete default BGP instance, ' \ + 'dependent VRF instance(s) exist!') return None if 'system_as' not in bgp: @@ -324,9 +419,43 @@ def verify(bgp): f'{afi} administrative distance {key}!') if afi in ['ipv4_unicast', 'ipv6_unicast']: - if 'import' in afi_config and 'vrf' in afi_config['import']: - # Check if VRF exists - verify_vrf(afi_config['import']['vrf']) + + vrf_name = bgp['vrf'] if dict_search('vrf', bgp) else 'default' + # Verify if currant VRF contains rd and route-target options + # and does not exist in import list in other VRFs + if dict_search(f'rd.vpn.export', afi_config): + if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): + raise ConfigError( + 'Command "import vrf" conflicts with "rd vpn export" command!') + + if dict_search('route_target.vpn.both', afi_config): + if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): + raise ConfigError( + 'Command "import vrf" conflicts with "route-target vpn both" command!') + + if dict_search('route_target.vpn.import', afi_config): + if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): + raise ConfigError( + 'Command "import vrf conflicts" with "route-target vpn import" command!') + + if dict_search('route_target.vpn.export', afi_config): + if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): + raise ConfigError( + 'Command "import vrf" conflicts with "route-target vpn export" command!') + + # Verify if VRFs in import do not contain rd + # and route-target options + if dict_search('import.vrf', afi_config) is not None: + # Verify if VRF with import does not contain rd + # and route-target options + if verify_vrf_import_options(afi_config): + raise ConfigError( + 'Please unconfigure "import vrf" commands before using vpn commands in the same VRF!') + # Verify if VRFs in import list do not contain rd + # and route-target options + if verify_vrflist_import(afi, afi_config, bgp['dependent_vrfs']): + raise ConfigError( + 'Please unconfigure import vrf commands before using vpn commands in dependent VRFs!') # FRR error: please unconfigure vpn to vrf commands before # using import vrf commands @@ -339,7 +468,6 @@ def verify(bgp): tmp = dict_search(f'route_map.vpn.{export_import}', afi_config) if tmp: verify_route_map(tmp, bgp) - return None def generate(bgp): diff --git a/src/conf_mode/protocols_failover.py b/src/conf_mode/protocols_failover.py index 048ba7a89..85e984afe 100755 --- a/src/conf_mode/protocols_failover.py +++ b/src/conf_mode/protocols_failover.py @@ -31,7 +31,7 @@ airbag.enable() service_name = 'vyos-failover' service_conf = Path(f'/run/{service_name}.conf') -systemd_service = '/etc/systemd/system/vyos-failover.service' +systemd_service = '/run/systemd/system/vyos-failover.service' rt_proto_failover = '/etc/iproute2/rt_protos.d/failover.conf' diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py index ee4eaf59d..ed0a8fba2 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -117,6 +117,10 @@ def verify(ospfv3): if 'area_type' in area_config: if len(area_config['area_type']) > 1: raise ConfigError(f'Can only configure one area-type for OSPFv3 area "{area}"!') + if 'range' in area_config: + for range, range_config in area_config['range'].items(): + if {'not_advertise', 'advertise'} <= range_config.keys(): + raise ConfigError(f'"not-advertise" and "advertise" for "range {range}" cannot be both configured at the same time!') if 'interface' in ospfv3: for interface, interface_config in ospfv3['interface'].items(): diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index 58e202928..3e5ebb805 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -25,12 +25,15 @@ from vyos.configdict import get_dhcp_interfaces from vyos.configdict import get_pppoe_interfaces from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_vrf +from vyos.template import render from vyos.template import render_to_string from vyos import ConfigError from vyos import frr from vyos import airbag airbag.enable() +config_file = '/etc/iproute2/rt_tables.d/vyos-static.conf' + def get_config(config=None): if config: conf = config @@ -94,6 +97,9 @@ def verify(static): def generate(static): if not static: return None + + # Put routing table names in /etc/iproute2/rt_tables + render(config_file, 'iproute2/static.conf.j2', static) static['new_frr_config'] = render_to_string('frr/staticd.frr.j2', static) return None diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py index dbe3be225..0418e8d82 100755 --- a/src/conf_mode/qos.py +++ b/src/conf_mode/qos.py @@ -15,14 +15,59 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sys import exit +from netifaces import interfaces from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configverify import verify_interface_exists +from vyos.qos import CAKE +from vyos.qos import DropTail +from vyos.qos import FairQueue +from vyos.qos import FQCodel +from vyos.qos import Limiter +from vyos.qos import NetEm +from vyos.qos import Priority +from vyos.qos import RandomDetect +from vyos.qos import RateLimiter +from vyos.qos import RoundRobin +from vyos.qos import TrafficShaper +from vyos.qos import TrafficShaperHFSC +from vyos.util import call +from vyos.util import dict_search_recursive from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() +map_vyops_tc = { + 'cake' : CAKE, + 'drop_tail' : DropTail, + 'fair_queue' : FairQueue, + 'fq_codel' : FQCodel, + 'limiter' : Limiter, + 'network_emulator' : NetEm, + 'priority_queue' : Priority, + 'random_detect' : RandomDetect, + 'rate_control' : RateLimiter, + 'round_robin' : RoundRobin, + 'shaper' : TrafficShaper, + 'shaper_hfsc' : TrafficShaperHFSC, +} + +def get_shaper(qos, interface_config, direction): + policy_name = interface_config[direction] + # An interface might have a QoS configuration, search the used + # configuration referenced by this. Path will hold the dict element + # referenced by the config, as this will be of sort: + # + # ['policy', 'drop_tail', 'foo-dtail'] <- we are only interested in + # drop_tail as the policy/shaper type + _, path = next(dict_search_recursive(qos, policy_name)) + shaper_type = path[1] + shaper_config = qos['policy'][shaper_type][policy_name] + + return (map_vyops_tc[shaper_type], shaper_config) + def get_config(config=None): if config: conf = config @@ -32,48 +77,167 @@ def get_config(config=None): if not conf.exists(base): return None - qos = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + qos = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) if 'policy' in qos: for policy in qos['policy']: - # CLI mangles - to _ for better Jinja2 compatibility - do we need - # Jinja2 here? - policy = policy.replace('-','_') + # when calling defaults() we need to use the real CLI node, thus we + # need a hyphen + policy_hyphen = policy.replace('_', '-') + + if policy in ['random_detect']: + for rd_name, rd_config in qos['policy'][policy].items(): + # There are eight precedence levels - ensure all are present + # to be filled later down with the appropriate default values + default_precedence = {'precedence' : { '0' : {}, '1' : {}, '2' : {}, '3' : {}, + '4' : {}, '5' : {}, '6' : {}, '7' : {} }} + qos['policy']['random_detect'][rd_name] = dict_merge( + default_precedence, qos['policy']['random_detect'][rd_name]) - default_values = defaults(base + ['policy', policy]) + for p_name, p_config in qos['policy'][policy].items(): + default_values = defaults(base + ['policy', policy_hyphen]) - # class is another tag node which requires individual handling - class_default_values = defaults(base + ['policy', policy, 'class']) - if 'class' in default_values: - del default_values['class'] + if policy in ['priority_queue']: + if 'default' not in p_config: + raise ConfigError(f'QoS policy {p_name} misses "default" class!') + + # XXX: T2665: we can not safely rely on the defaults() when there are + # tagNodes in place, it is better to blend in the defaults manually. + if 'class' in default_values: + del default_values['class'] + if 'precedence' in default_values: + del default_values['precedence'] - for p_name, p_config in qos['policy'][policy].items(): qos['policy'][policy][p_name] = dict_merge( default_values, qos['policy'][policy][p_name]) + # class is another tag node which requires individual handling if 'class' in p_config: + default_values = defaults(base + ['policy', policy_hyphen, 'class']) for p_class in p_config['class']: qos['policy'][policy][p_name]['class'][p_class] = dict_merge( - class_default_values, qos['policy'][policy][p_name]['class'][p_class]) + default_values, qos['policy'][policy][p_name]['class'][p_class]) + + if 'precedence' in p_config: + default_values = defaults(base + ['policy', policy_hyphen, 'precedence']) + # precedence values are a bit more complex as they are calculated + # under specific circumstances - thus we need to iterate two times. + # first blend in the defaults from XML / CLI + for precedence in p_config['precedence']: + qos['policy'][policy][p_name]['precedence'][precedence] = dict_merge( + default_values, qos['policy'][policy][p_name]['precedence'][precedence]) + # second calculate defaults based on actual dictionary + for precedence in p_config['precedence']: + max_thr = int(qos['policy'][policy][p_name]['precedence'][precedence]['maximum_threshold']) + if 'minimum_threshold' not in qos['policy'][policy][p_name]['precedence'][precedence]: + qos['policy'][policy][p_name]['precedence'][precedence]['minimum_threshold'] = str( + int((9 + int(precedence)) * max_thr) // 18); + + if 'queue_limit' not in qos['policy'][policy][p_name]['precedence'][precedence]: + qos['policy'][policy][p_name]['precedence'][precedence]['queue_limit'] = \ + str(int(4 * max_thr)) - import pprint - pprint.pprint(qos) return qos def verify(qos): - if not qos: + if not qos or 'interface' not in qos: return None # network policy emulator # reorder rerquires delay to be set + if 'policy' in qos: + for policy_type in qos['policy']: + for policy, policy_config in qos['policy'][policy_type].items(): + # a policy with it's given name is only allowed to exist once + # on the system. This is because an interface selects a policy + # for ingress/egress traffic, and thus there can only be one + # policy with a given name. + # + # We check if the policy name occurs more then once - error out + # if this is true + counter = 0 + for _, path in dict_search_recursive(qos['policy'], policy): + counter += 1 + if counter > 1: + raise ConfigError(f'Conflicting policy name "{policy}", already in use!') + + if 'class' in policy_config: + for cls, cls_config in policy_config['class'].items(): + # bandwidth is not mandatory for priority-queue - that is why this is on the exception list + if 'bandwidth' not in cls_config and policy_type not in ['priority_queue', 'round_robin']: + raise ConfigError(f'Bandwidth must be defined for policy "{policy}" class "{cls}"!') + if 'match' in cls_config: + for match, match_config in cls_config['match'].items(): + if {'ip', 'ipv6'} <= set(match_config): + raise ConfigError(f'Can not use both IPv6 and IPv4 in one match ({match})!') + + if policy_type in ['random_detect']: + if 'precedence' in policy_config: + for precedence, precedence_config in policy_config['precedence'].items(): + max_tr = int(precedence_config['maximum_threshold']) + if {'maximum_threshold', 'minimum_threshold'} <= set(precedence_config): + min_tr = int(precedence_config['minimum_threshold']) + if min_tr >= max_tr: + raise ConfigError(f'Policy "{policy}" uses min-threshold "{min_tr}" >= max-threshold "{max_tr}"!') + + if {'maximum_threshold', 'queue_limit'} <= set(precedence_config): + queue_lim = int(precedence_config['queue_limit']) + if queue_lim < max_tr: + raise ConfigError(f'Policy "{policy}" uses queue-limit "{queue_lim}" < max-threshold "{max_tr}"!') + + if 'default' in policy_config: + if 'bandwidth' not in policy_config['default'] and policy_type not in ['priority_queue', 'round_robin']: + raise ConfigError('Bandwidth not defined for default traffic!') + + # we should check interface ingress/egress configuration after verifying that + # the policy name is used only once - this makes the logic easier! + for interface, interface_config in qos['interface'].items(): + verify_interface_exists(interface) + + for direction in ['egress', 'ingress']: + # bail out early if shaper for given direction is not used at all + if direction not in interface_config: + continue + + policy_name = interface_config[direction] + if 'policy' not in qos or list(dict_search_recursive(qos['policy'], policy_name)) == []: + raise ConfigError(f'Selected QoS policy "{policy_name}" does not exist!') + + shaper_type, shaper_config = get_shaper(qos, interface_config, direction) + tmp = shaper_type(interface).get_direction() + if direction not in tmp: + raise ConfigError(f'Selected QoS policy on interface "{interface}" only supports "{tmp}"!') - raise ConfigError('123') return None def generate(qos): + if not qos or 'interface' not in qos: + return None + return None def apply(qos): + # Always delete "old" shapers first + for interface in interfaces(): + # Ignore errors (may have no qdisc) + call(f'tc qdisc del dev {interface} parent ffff:') + call(f'tc qdisc del dev {interface} root') + + if not qos or 'interface' not in qos: + return None + + for interface, interface_config in qos['interface'].items(): + for direction in ['egress', 'ingress']: + # bail out early if shaper for given direction is not used at all + if direction not in interface_config: + continue + + shaper_type, shaper_config = get_shaper(qos, interface_config, direction) + tmp = shaper_type(interface) + tmp.update(shaper_config, direction) + return None if __name__ == '__main__': diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py index ee4fe42ab..60eff6543 100755 --- a/src/conf_mode/service_console-server.py +++ b/src/conf_mode/service_console-server.py @@ -27,7 +27,7 @@ from vyos.xml import defaults from vyos import ConfigError config_file = '/run/conserver/conserver.cf' -dropbear_systemd_file = '/etc/systemd/system/dropbear@{port}.service.d/override.conf' +dropbear_systemd_file = '/run/systemd/system/dropbear@{port}.service.d/override.conf' def get_config(config=None): if config: diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py index aafece47a..363408679 100755 --- a/src/conf_mode/service_monitoring_telegraf.py +++ b/src/conf_mode/service_monitoring_telegraf.py @@ -38,7 +38,7 @@ cache_dir = f'/etc/telegraf/.cache' config_telegraf = f'/run/telegraf/telegraf.conf' custom_scripts_dir = '/etc/telegraf/custom_scripts' syslog_telegraf = '/etc/rsyslog.d/50-telegraf.conf' -systemd_override = '/etc/systemd/system/telegraf.service.d/10-override.conf' +systemd_override = '/run/systemd/system/telegraf.service.d/10-override.conf' def get_nft_filter_chains(): """ Get nft chains for table filter """ diff --git a/src/conf_mode/service_sla.py b/src/conf_mode/service_sla.py index e7c3ca59c..b1e22f37b 100755 --- a/src/conf_mode/service_sla.py +++ b/src/conf_mode/service_sla.py @@ -27,15 +27,13 @@ from vyos import ConfigError from vyos import airbag airbag.enable() - owamp_config_dir = '/etc/owamp-server' owamp_config_file = f'{owamp_config_dir}/owamp-server.conf' -systemd_override_owamp = r'/etc/systemd/system/owamp-server.d/20-override.conf' +systemd_override_owamp = r'/run/systemd/system/owamp-server.d/20-override.conf' twamp_config_dir = '/etc/twamp-server' twamp_config_file = f'{twamp_config_dir}/twamp-server.conf' -systemd_override_twamp = r'/etc/systemd/system/twamp-server.d/20-override.conf' - +systemd_override_twamp = r'/run/systemd/system/twamp-server.d/20-override.conf' def get_config(config=None): if config: diff --git a/src/conf_mode/service_webproxy.py b/src/conf_mode/service_webproxy.py index 41a1deaa3..658e496a6 100755 --- a/src/conf_mode/service_webproxy.py +++ b/src/conf_mode/service_webproxy.py @@ -246,7 +246,7 @@ def apply(proxy): if os.path.exists(squidguard_db_dir): chmod_755(squidguard_db_dir) - call('systemctl restart squid.service') + call('systemctl reload-or-restart squid.service') return None diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py index 9651b358e..ab2ccf99e 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -40,7 +40,7 @@ config_file_client = r'/etc/snmp/snmp.conf' config_file_daemon = r'/etc/snmp/snmpd.conf' config_file_access = r'/usr/share/snmp/snmpd.conf' config_file_user = r'/var/lib/snmp/snmpd.conf' -systemd_override = r'/etc/systemd/system/snmpd.service.d/override.conf' +systemd_override = r'/run/systemd/system/snmpd.service.d/override.conf' systemd_service = 'snmpd.service' def get_config(config=None): diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index 8746cc701..8de0617af 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -32,7 +32,7 @@ from vyos import airbag airbag.enable() config_file = r'/run/sshd/sshd_config' -systemd_override = r'/etc/systemd/system/ssh.service.d/override.conf' +systemd_override = r'/run/systemd/system/ssh.service.d/override.conf' sshguard_config_file = '/etc/sshguard/sshguard.conf' sshguard_whitelist = '/etc/sshguard/whitelist' diff --git a/src/conf_mode/system-option.py b/src/conf_mode/system-option.py index 36dbf155b..e6c7a0ed2 100755 --- a/src/conf_mode/system-option.py +++ b/src/conf_mode/system-option.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2022 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 @@ -22,17 +22,19 @@ from time import sleep from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configverify import verify_source_interface from vyos.template import render from vyos.util import cmd from vyos.util import is_systemd_service_running from vyos.validate import is_addr_assigned +from vyos.validate import is_intf_addr_assigned from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() curlrc_config = r'/etc/curlrc' -ssh_config = r'/etc/ssh/ssh_config' +ssh_config = r'/etc/ssh/ssh_config.d/91-vyos-ssh-client-options.conf' systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target' def get_config(config=None): @@ -68,8 +70,17 @@ def verify(options): if 'ssh_client' in options: config = options['ssh_client'] if 'source_address' in config: + address = config['source_address'] if not is_addr_assigned(config['source_address']): - raise ConfigError('No interface with give address specified!') + raise ConfigError('No interface with address "{address}" configured!') + + if 'source_interface' in config: + verify_source_interface(config) + if 'source_address' in config: + address = config['source_address'] + interface = config['source_interface'] + if not is_intf_addr_assigned(interface, address): + raise ConfigError(f'Address "{address}" not assigned on interface "{interface}"!') return None diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index b79e9847a..3af2af4d9 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -95,6 +95,7 @@ def get_config(config=None): del default_values['esp_group'] del default_values['ike_group'] del default_values['remote_access'] + del default_values['site_to_site'] ipsec = dict_merge(default_values, ipsec) if 'esp_group' in ipsec: @@ -143,6 +144,14 @@ def get_config(config=None): ipsec['remote_access']['radius']['server'][server] = dict_merge(default_values, ipsec['remote_access']['radius']['server'][server]) + # XXX: T2665: we can not safely rely on the defaults() when there are + # tagNodes in place, it is better to blend in the defaults manually. + if dict_search('site_to_site.peer', ipsec): + default_values = defaults(base + ['site-to-site', 'peer']) + for peer in ipsec['site_to_site']['peer']: + ipsec['site_to_site']['peer'][peer] = dict_merge(default_values, + ipsec['site_to_site']['peer'][peer]) + ipsec['dhcp_no_address'] = {} ipsec['install_routes'] = 'no' if conf.exists(base + ["options", "disable-route-autoinstall"]) else default_install_routes ipsec['interface_change'] = leaf_node_changed(conf, base + ['interface']) diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py index 27e78db99..65623c2b1 100755 --- a/src/conf_mode/vpn_l2tp.py +++ b/src/conf_mode/vpn_l2tp.py @@ -58,6 +58,9 @@ default_config_data = { 'ppp_echo_failure' : '3', 'ppp_echo_interval' : '30', 'ppp_echo_timeout': '0', + 'ppp_ipv6_accept_peer_intf_id': False, + 'ppp_ipv6_intf_id': None, + 'ppp_ipv6_peer_intf_id': None, 'radius_server': [], 'radius_acct_inter_jitter': '', 'radius_acct_tmo': '3', @@ -314,6 +317,15 @@ def get_config(config=None): if conf.exists(['ppp-options', 'ipv6']): l2tp['ppp_ipv6'] = conf.return_value(['ppp-options', 'ipv6']) + if conf.exists(['ppp-options', 'ipv6-accept-peer-intf-id']): + l2tp['ppp_ipv6_accept_peer_intf_id'] = True + + if conf.exists(['ppp-options', 'ipv6-intf-id']): + l2tp['ppp_ipv6_intf_id'] = conf.return_value(['ppp-options', 'ipv6-intf-id']) + + if conf.exists(['ppp-options', 'ipv6-peer-intf-id']): + l2tp['ppp_ipv6_peer_intf_id'] = conf.return_value(['ppp-options', 'ipv6-peer-intf-id']) + return l2tp diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 1b4156895..c17cca3bd 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -140,11 +140,9 @@ def verify(vrf): def generate(vrf): - render(config_file, 'vrf/vrf.conf.j2', vrf) + render(config_file, 'iproute2/vrf.conf.j2', vrf) # Render nftables zones config - render(nft_vrf_config, 'firewall/nftables-vrf-zones.j2', vrf) - return None diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook b/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook index 61a89e62a..1f1926e17 100755 --- a/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook +++ b/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook @@ -23,7 +23,7 @@ DHCP_HOOK_IFLIST="/tmp/ipsec_dhcp_waiting" if [ -f $DHCP_HOOK_IFLIST ] && [ "$reason" == "BOUND" ]; then if grep -qw $interface $DHCP_HOOK_IFLIST; then sudo rm $DHCP_HOOK_IFLIST - sudo python3 /usr/libexec/vyos/conf_mode/vpn_ipsec.py + sudo /usr/libexec/vyos/conf_mode/vpn_ipsec.py exit 0 fi fi diff --git a/src/etc/modprobe.d/ifb.conf b/src/etc/modprobe.d/ifb.conf new file mode 100644 index 000000000..2dcfb6af4 --- /dev/null +++ b/src/etc/modprobe.d/ifb.conf @@ -0,0 +1 @@ +options ifb numifbs=0 diff --git a/src/helpers/vyos-failover.py b/src/helpers/vyos-failover.py index 1ac193423..0de945f20 100755 --- a/src/helpers/vyos-failover.py +++ b/src/helpers/vyos-failover.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-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 @@ -28,6 +28,17 @@ from systemd import journal my_name = Path(__file__).stem +def is_route_exists(route, gateway, interface, metric): + """Check if route with expected gateway, dev and metric exists""" + rc, data = rc_cmd(f'sudo ip --json route show protocol failover {route} ' + f'via {gateway} dev {interface} metric {metric}') + if rc == 0: + data = json.loads(data) + if len(data) > 0: + return True + return False + + def get_best_route_options(route, debug=False): """ Return current best route ('gateway, interface, metric) @@ -137,7 +148,7 @@ if __name__ == '__main__': for route, route_config in config.get('route').items(): - exists_route = exists_gateway, exists_iface, exists_metric = get_best_route_options(route, debug=debug) + exists_gateway, exists_iface, exists_metric = get_best_route_options(route, debug=debug) for next_hop, nexthop_config in route_config.get('next_hop').items(): conf_iface = nexthop_config.get('interface') @@ -148,8 +159,8 @@ if __name__ == '__main__': target = nexthop_config.get('check').get('target') timeout = nexthop_config.get('check').get('timeout') - # Best route not fonund in the current routing table - if exists_route == (None, None, None): + # Route not found in the current routing table + if not is_route_exists(route, next_hop, conf_iface, conf_metric): if debug: print(f" [NEW_ROUTE_DETECTED] route: [{route}]") # Add route if check-target alive if is_target_alive(target, conf_iface, proto, port, debug=debug): @@ -172,7 +183,7 @@ if __name__ == '__main__': # Route was added, check if the target is alive # We should delete route if check fails only if route exists in the routing table if not is_target_alive(target, conf_iface, proto, port, debug=debug) and \ - exists_route != (None, None, None): + is_route_exists(route, next_hop, conf_iface, conf_metric): if debug: print(f'Nexh_hop {next_hop} fail, target not response') print(f' [ DEL ] -- ip route del {route} via {next_hop} dev {conf_iface} ' diff --git a/src/migration-scripts/container/0-to-1 b/src/migration-scripts/container/0-to-1 new file mode 100755 index 000000000..d0461389b --- /dev/null +++ b/src/migration-scripts/container/0-to-1 @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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/>. + +# T4870: change underlaying container filesystem from vfs to overlay + +import os +import shutil +import sys + +from vyos.configtree import ConfigTree +from vyos.util import call + +if (len(sys.argv) < 1): + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['container', 'name'] +config = ConfigTree(config_file) + +# Check if containers exist and we need to perform image manipulation +if config.exists(base): + for container in config.list_nodes(base): + # Stop any given container first + call(f'systemctl stop vyos-container-{container}.service') + # Export container image for later re-import to new filesystem. We store + # the backup on a real disk as a tmpfs (like /tmp) could probably lack + # memory if a host has too many containers stored. + image_name = config.return_value(base + [container, 'image']) + call(f'podman image save --quiet --output /root/{container}.tar --format oci-archive {image_name}') + +# No need to adjust the strage driver online (this is only used for testing and +# debugging on a live system) - it is already overlay2 when the migration script +# is run during system update. But the specified driver in the image is actually +# overwritten by the still present VFS filesystem on disk. Thus podman still +# thinks it uses VFS until we delete the libpod directory under: +# /usr/lib/live/mount/persistence/container/storage +#call('sed -i "s/vfs/overlay2/g" /etc/containers/storage.conf /usr/share/vyos/templates/container/storage.conf.j2') + +base_path = '/usr/lib/live/mount/persistence/container/storage' +for dir in ['libpod', 'vfs', 'vfs-containers', 'vfs-images', 'vfs-layers']: + if os.path.exists(f'{base_path}/{dir}'): + shutil.rmtree(f'{base_path}/{dir}') + +# Now all remaining information about VFS is gone and we operate in overlayfs2 +# filesystem mode. Time to re-import the images. +if config.exists(base): + for container in config.list_nodes(base): + # Export container image for later re-import to new filesystem + image_name = config.return_value(base + [container, 'image']) + image_path = f'/root/{container}.tar' + call(f'podman image load --quiet --input {image_path}') + + # Start any given container first + call(f'systemctl start vyos-container-{container}.service') + + # Delete temporary container image + if os.path.exists(image_path): + os.unlink(image_path) + diff --git a/src/migration-scripts/ntp/1-to-2 b/src/migration-scripts/ntp/1-to-2 new file mode 100755 index 000000000..4a701e7e5 --- /dev/null +++ b/src/migration-scripts/ntp/1-to-2 @@ -0,0 +1,67 @@ +#!/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/>. + +# T3008: move from ntpd to chrony and migrate "system ntp" to "service ntp" + +import sys + +from vyos.configtree import ConfigTree + +if (len(sys.argv) < 1): + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) + +base_path = ['system', 'ntp'] +new_base_path = ['service', 'ntp'] +if not config.exists(base_path): + # Nothing to do + sys.exit(0) + +# copy "system ntp" to "service ntp" +config.copy(base_path, new_base_path) +config.delete(base_path) + +# chrony does not support the preempt option, drop it +for server in config.list_nodes(new_base_path + ['server']): + server_base = new_base_path + ['server', server] + if config.exists(server_base + ['preempt']): + config.delete(server_base + ['preempt']) + +# Rename "allow-clients" -> "allow-client" +if config.exists(new_base_path + ['allow-clients']): + config.rename(new_base_path + ['allow-clients'], 'allow-client') + +# By default VyOS 1.3 allowed NTP queries for all networks - in chrony we +# explicitly disable this behavior and clients need to be specified using the +# allow-client CLI option. In order to be fully backwards compatible, we specify +# 0.0.0.0/0 and ::/0 as allow networks if not specified otherwise explicitly. +if not config.exists(new_base_path + ['allow-client']): + config.set(new_base_path + ['allow-client', 'address'], value='0.0.0.0/0', replace=False) + config.set(new_base_path + ['allow-client', 'address'], value='::/0', replace=False) + +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)) + sys.exit(1) diff --git a/src/migration-scripts/qos/1-to-2 b/src/migration-scripts/qos/1-to-2 new file mode 100755 index 000000000..41026cbd6 --- /dev/null +++ b/src/migration-scripts/qos/1-to-2 @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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,exit + +from vyos.base import Warning +from vyos.configtree import ConfigTree +from vyos.util import read_file + +def bandwidth_percent_to_val(interface, percent) -> int: + speed = read_file(f'/sys/class/net/{interface}/speed') + if not speed.isnumeric(): + Warning('Interface speed cannot be determined (assuming 10 Mbit/s)') + speed = 10 + speed = int(speed) *1000000 # convert to MBit/s + return speed * int(percent) // 100 # integer division + +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 = ['traffic-policy'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +iface_config = {} + +if config.exists(['interfaces']): + def get_qos(config, interface, interface_base): + if config.exists(interface_base): + tmp = { interface : {} } + if config.exists(interface_base + ['in']): + tmp[interface]['ingress'] = config.return_value(interface_base + ['in']) + if config.exists(interface_base + ['out']): + tmp[interface]['egress'] = config.return_value(interface_base + ['out']) + config.delete(interface_base) + return tmp + return None + + # Migrate "interface ethernet eth0 traffic-policy in|out" to "qos interface eth0 ingress|egress" + for type in config.list_nodes(['interfaces']): + for interface in config.list_nodes(['interfaces', type]): + interface_base = ['interfaces', type, interface, 'traffic-policy'] + tmp = get_qos(config, interface, interface_base) + if tmp: iface_config.update(tmp) + + vif_path = ['interfaces', type, interface, 'vif'] + if config.exists(vif_path): + for vif in config.list_nodes(vif_path): + vif_interface_base = vif_path + [vif, 'traffic-policy'] + ifname = f'{interface}.{vif}' + tmp = get_qos(config, ifname, vif_interface_base) + if tmp: iface_config.update(tmp) + + vif_s_path = ['interfaces', type, interface, 'vif-s'] + if config.exists(vif_s_path): + for vif_s in config.list_nodes(vif_s_path): + vif_s_interface_base = vif_s_path + [vif_s, 'traffic-policy'] + ifname = f'{interface}.{vif_s}' + tmp = get_qos(config, ifname, vif_s_interface_base) + if tmp: iface_config.update(tmp) + + # vif-c interfaces MUST be migrated before their parent vif-s + # interface as the migrate_*() functions delete the path! + vif_c_path = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c'] + if config.exists(vif_c_path): + for vif_c in config.list_nodes(vif_c_path): + vif_c_interface_base = vif_c_path + [vif_c, 'traffic-policy'] + ifname = f'{interface}.{vif_s}.{vif_c}' + tmp = get_qos(config, ifname, vif_s_interface_base) + if tmp: iface_config.update(tmp) + + +# Now we have the information which interface uses which QoS policy. +# Interface binding will be moved to the qos CLi tree +config.set(['qos']) +config.copy(base, ['qos', 'policy']) +config.delete(base) + +# Now map the interface policy binding to the new CLI syntax +if len(iface_config): + config.set(['qos', 'interface']) + config.set_tag(['qos', 'interface']) + +for interface, interface_config in iface_config.items(): + config.set(['qos', 'interface', interface]) + config.set_tag(['qos', 'interface', interface]) + if 'ingress' in interface_config: + config.set(['qos', 'interface', interface, 'ingress'], value=interface_config['ingress']) + if 'egress' in interface_config: + config.set(['qos', 'interface', interface, 'egress'], value=interface_config['egress']) + +# Remove "burst" CLI node from network emulator +netem_base = ['qos', 'policy', 'network-emulator'] +if config.exists(netem_base): + for policy_name in config.list_nodes(netem_base): + if config.exists(netem_base + [policy_name, 'burst']): + config.delete(netem_base + [policy_name, 'burst']) + +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) diff --git a/src/op_mode/conntrack.py b/src/op_mode/conntrack.py index fff537936..df213cc5a 100755 --- a/src/op_mode/conntrack.py +++ b/src/op_mode/conntrack.py @@ -116,7 +116,7 @@ def get_formatted_output(dict_data): reply_src = f'{reply_src}:{reply_sport}' if reply_sport else reply_src reply_dst = f'{reply_dst}:{reply_dport}' if reply_dport else reply_dst state = meta['state'] if 'state' in meta else '' - mark = meta['mark'] + mark = meta['mark'] if 'mark' in meta else '' zone = meta['zone'] if 'zone' in meta else '' data_entries.append( [conn_id, orig_src, orig_dst, reply_src, reply_dst, proto, state, timeout, mark, zone]) diff --git a/src/op_mode/container.py b/src/op_mode/container.py index ce466ffc1..d48766a0c 100755 --- a/src/op_mode/container.py +++ b/src/op_mode/container.py @@ -23,7 +23,6 @@ from vyos.util import cmd import vyos.opmode - def _get_json_data(command: str) -> list: """ Get container command format JSON @@ -36,9 +35,22 @@ def _get_raw_data(command: str) -> list: data = json.loads(json_data) return data +def add_image(name: str): + from vyos.util import rc_cmd + + rc, output = rc_cmd(f'podman image pull {name}') + if rc != 0: + raise vyos.opmode.InternalError(output) + +def delete_image(name: str): + from vyos.util import rc_cmd + + rc, output = rc_cmd(f'podman image rm --force {name}') + if rc != 0: + raise vyos.opmode.InternalError(output) def show_container(raw: bool): - command = 'sudo podman ps --all' + command = 'podman ps --all' container_data = _get_raw_data(command) if raw: return container_data @@ -47,8 +59,8 @@ def show_container(raw: bool): def show_image(raw: bool): - command = 'sudo podman image ls' - container_data = _get_raw_data('sudo podman image ls') + command = 'podman image ls' + container_data = _get_raw_data('podman image ls') if raw: return container_data else: @@ -56,7 +68,7 @@ def show_image(raw: bool): def show_network(raw: bool): - command = 'sudo podman network ls' + command = 'podman network ls' container_data = _get_raw_data(command) if raw: return container_data @@ -67,7 +79,7 @@ def show_network(raw: bool): def restart(name: str): from vyos.util import rc_cmd - rc, output = rc_cmd(f'sudo podman restart {name}') + rc, output = rc_cmd(f'systemctl restart vyos-container-{name}.service') if rc != 0: print(output) return None diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py index 07e9b7d6c..b9e6e7bc9 100755 --- a/src/op_mode/dhcp.py +++ b/src/op_mode/dhcp.py @@ -15,13 +15,14 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import sys -from ipaddress import ip_address import typing from datetime import datetime -from sys import exit -from tabulate import tabulate +from ipaddress import ip_address from isc_dhcp_leases import IscDhcpLeases +from tabulate import tabulate + +import vyos.opmode from vyos.base import Warning from vyos.configquery import ConfigTreeQuery @@ -30,19 +31,10 @@ from vyos.util import cmd from vyos.util import dict_search from vyos.util import is_systemd_service_running -import vyos.opmode - - config = ConfigTreeQuery() -pool_key = "shared-networkname" - - -def _in_pool(lease, pool): - if pool_key in lease.sets: - if lease.sets[pool_key] == pool: - return True - return False - +lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup'] +sort_valid_inet = ['end', 'mac', 'hostname', 'ip', 'pool', 'remaining', 'start', 'state'] +sort_valid_inet6 = ['end', 'iaid_duid', 'ip', 'last_communication', 'pool', 'remaining', 'state', 'type'] def _utc_to_local(utc_dt): return datetime.fromtimestamp((datetime.fromtimestamp(utc_dt) - datetime(1970, 1, 1)).total_seconds()) @@ -71,7 +63,7 @@ def _find_list_of_dict_index(lst, key='ip', value='') -> int: return idx -def _get_raw_server_leases(family, pool=None) -> list: +def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[]) -> list: """ Get DHCP server leases :return list @@ -79,9 +71,12 @@ def _get_raw_server_leases(family, pool=None) -> list: lease_file = '/config/dhcpdv6.leases' if family == 'inet6' else '/config/dhcpd.leases' data = [] leases = IscDhcpLeases(lease_file).get() - if pool is not None: - if config.exists(f'service dhcp-server shared-network-name {pool}'): - leases = list(filter(lambda x: _in_pool(x, pool), leases)) + + if pool is None: + pool = _get_dhcp_pools(family=family) + else: + pool = [pool] + for lease in leases: data_lease = {} data_lease['ip'] = lease.ip @@ -90,7 +85,7 @@ def _get_raw_server_leases(family, pool=None) -> list: data_lease['end'] = lease.end.timestamp() if family == 'inet': - data_lease['hardware'] = lease.ethernet + data_lease['mac'] = lease.ethernet data_lease['start'] = lease.start.timestamp() data_lease['hostname'] = lease.hostname @@ -110,8 +105,9 @@ def _get_raw_server_leases(family, pool=None) -> list: data_lease['remaining'] = '' # Do not add old leases - if data_lease['remaining'] != '': - data.append(data_lease) + if data_lease['remaining'] != '' and data_lease['pool'] in pool: + if not state or data_lease['state'] in state: + data.append(data_lease) # deduplicate checked = [] @@ -123,15 +119,20 @@ def _get_raw_server_leases(family, pool=None) -> list: idx = _find_list_of_dict_index(data, key='ip', value=addr) data.pop(idx) + if sorted: + if sorted == 'ip': + data.sort(key = lambda x:ip_address(x['ip'])) + else: + data.sort(key = lambda x:x[sorted]) return data -def _get_formatted_server_leases(raw_data, family): +def _get_formatted_server_leases(raw_data, family='inet'): data_entries = [] if family == 'inet': for lease in raw_data: ipaddr = lease.get('ip') - hw_addr = lease.get('hardware') + hw_addr = lease.get('mac') state = lease.get('state') start = lease.get('start') start = _utc_to_local(start).strftime('%Y/%m/%d %H:%M:%S') @@ -142,7 +143,7 @@ def _get_formatted_server_leases(raw_data, family): hostname = lease.get('hostname') data_entries.append([ipaddr, hw_addr, state, start, end, remain, pool, hostname]) - headers = ['IP Address', 'Hardware address', 'State', 'Lease start', 'Lease expiration', 'Remaining', 'Pool', + headers = ['IP Address', 'MAC address', 'State', 'Lease start', 'Lease expiration', 'Remaining', 'Pool', 'Hostname'] if family == 'inet6': @@ -256,16 +257,28 @@ def show_pool_statistics(raw: bool, family: str, pool: typing.Optional[str]): @_verify -def show_server_leases(raw: bool, family: str): +def show_server_leases(raw: bool, family: str, pool: typing.Optional[str], + sorted: typing.Optional[str], state: typing.Optional[str]): # if dhcp server is down, inactive leases may still be shown as active, so warn the user. if not is_systemd_service_running('isc-dhcp-server.service'): Warning('DHCP server is configured but not started. Data may be stale.') - leases = _get_raw_server_leases(family) + v = 'v6' if family == 'inet6' else '' + if pool and pool not in _get_dhcp_pools(family=family): + raise vyos.opmode.IncorrectValue(f'DHCP{v} pool "{pool}" does not exist!') + + if state and state not in lease_valid_states: + raise vyos.opmode.IncorrectValue(f'DHCP{v} state "{state}" is invalid!') + + sort_valid = sort_valid_inet6 if family == 'inet6' else sort_valid_inet + if sorted and sorted not in sort_valid: + raise vyos.opmode.IncorrectValue(f'DHCP{v} sort "{sorted}" is invalid!') + + lease_data = _get_raw_server_leases(family=family, pool=pool, sorted=sorted, state=state) if raw: - return leases + return lease_data else: - return _get_formatted_server_leases(leases, family) + return _get_formatted_server_leases(lease_data, family=family) if __name__ == '__main__': diff --git a/src/op_mode/interfaces.py b/src/op_mode/interfaces.py new file mode 100755 index 000000000..678c74980 --- /dev/null +++ b/src/op_mode/interfaces.py @@ -0,0 +1,412 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 re +import sys +import glob +import json +import typing +from datetime import datetime +from tabulate import tabulate + +import vyos.opmode +from vyos.ifconfig import Section +from vyos.ifconfig import Interface +from vyos.ifconfig import VRRP +from vyos.util import cmd, rc_cmd, call + +def catch_broken_pipe(func): + def wrapped(*args, **kwargs): + try: + func(*args, **kwargs) + except (BrokenPipeError, KeyboardInterrupt): + # Flush output to /dev/null and bail out. + os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno()) + return wrapped + +# The original implementation of filtered_interfaces has signature: +# (ifnames: list, iftypes: typing.Union[str, list], vif: bool, vrrp: bool) -> intf: Interface: +# Arg types allowed in CLI (ifnames: str, iftypes: str) were manually +# re-typed from argparse args. +# We include the function in a general form, however op-mode standard +# functions will restrict to the CLI-allowed arg types, wrapped in Optional. +def filtered_interfaces(ifnames: typing.Union[str, list], + iftypes: typing.Union[str, list], + vif: bool, vrrp: bool) -> Interface: + """ + get all interfaces from the OS and return them; ifnames can be used to + filter which interfaces should be considered + + ifnames: a list of interface names to consider, empty do not filter + + return an instance of the Interface class + """ + if isinstance(ifnames, str): + ifnames = [ifnames] if ifnames else [] + if isinstance(iftypes, list): + for iftype in iftypes: + yield from filtered_interfaces(ifnames, iftype, vif, vrrp) + + for ifname in Section.interfaces(iftypes): + # Bail out early if interface name not part of our search list + if ifnames and ifname not in ifnames: + continue + + # As we are only "reading" from the interface - we must use the + # generic base class which exposes all the data via a common API + interface = Interface(ifname, create=False, debug=False) + + # VLAN interfaces have a '.' in their name by convention + if vif and not '.' in ifname: + continue + + if vrrp: + vrrp_interfaces = VRRP.active_interfaces() + if ifname not in vrrp_interfaces: + continue + + yield interface + +def _split_text(text, used=0): + """ + take a string and attempt to split it to fit with the width of the screen + + text: the string to split + used: number of characted already used in the screen + """ + no_tty = call('tty -s') + + returned = cmd('stty size') if not no_tty else '' + returned = returned.split() + if len(returned) == 2: + _, columns = tuple(int(_) for _ in returned) + else: + _, columns = (40, 80) + + desc_len = columns - used + + line = '' + for word in text.split(): + if len(line) + len(word) < desc_len: + line = f'{line} {word}' + continue + if line: + yield line[1:] + else: + line = f'{line} {word}' + + yield line[1:] + +def _get_counter_val(prev, now): + """ + attempt to correct a counter if it wrapped, copied from perl + + prev: previous counter + now: the current counter + """ + # This function has to deal with both 32 and 64 bit counters + if prev == 0: + return now + + # device is using 64 bit values assume they never wrap + value = now - prev + if (now >> 32) != 0: + return value + + # The counter has rolled. If the counter has rolled + # multiple times since the prev value, then this math + # is meaningless. + if value < 0: + value = (4294967296 - prev) + now + + return value + +def _pppoe(ifname): + out = cmd('ps -C pppd -f') + if ifname in out: + return 'C' + if ifname in [_.split('/')[-1] for _ in glob.glob('/etc/ppp/peers/pppoe*')]: + return 'D' + return '' + +def _find_intf_by_ifname(intf_l: list, name: str): + for d in intf_l: + if d['ifname'] == name: + return d + return {} + +# lifted out of operational.py to separate formatting from data +def _format_stats(stats, indent=4): + stat_names = { + 'rx': ['bytes', 'packets', 'errors', 'dropped', 'overrun', 'mcast'], + 'tx': ['bytes', 'packets', 'errors', 'dropped', 'carrier', 'collisions'], + } + + stats_dir = { + 'rx': ['rx_bytes', 'rx_packets', 'rx_errors', 'rx_dropped', 'rx_over_errors', 'multicast'], + 'tx': ['tx_bytes', 'tx_packets', 'tx_errors', 'tx_dropped', 'tx_carrier_errors', 'collisions'], + } + tabs = [] + for rtx in list(stats_dir): + tabs.append([f'{rtx.upper()}:', ] + stat_names[rtx]) + tabs.append(['', ] + [stats[_] for _ in stats_dir[rtx]]) + + s = tabulate( + tabs, + stralign="right", + numalign="right", + tablefmt="plain" + ) + + p = ' '*indent + return f'{p}' + s.replace('\n', f'\n{p}') + +def _get_raw_data(ifname: typing.Optional[str], + iftype: typing.Optional[str], + vif: bool, vrrp: bool) -> list: + if ifname is None: + ifname = '' + if iftype is None: + iftype = '' + ret =[] + for interface in filtered_interfaces(ifname, iftype, vif, vrrp): + res_intf = {} + cache = interface.operational.load_counters() + + out = cmd(f'ip -json addr show {interface.ifname}') + res_intf_l = json.loads(out) + res_intf = res_intf_l[0] + + if res_intf['link_type'] == 'tunnel6': + # Note that 'ip -6 tun show {interface.ifname}' is not json + # aware, so find in list + out = cmd('ip -json -6 tun show') + tunnel = json.loads(out) + res_intf['tunnel6'] = _find_intf_by_ifname(tunnel, + interface.ifname) + if 'ip6_tnl_f_use_orig_tclass' in res_intf['tunnel6']: + res_intf['tunnel6']['tclass'] = 'inherit' + del res_intf['tunnel6']['ip6_tnl_f_use_orig_tclass'] + + res_intf['counters_last_clear'] = int(cache.get('timestamp', 0)) + + res_intf['description'] = interface.get_alias() + + res_intf['stats'] = interface.operational.get_stats() + + ret.append(res_intf) + + # find pppoe interfaces that are in a transitional/dead state + if ifname.startswith('pppoe') and not _find_intf_by_ifname(ret, ifname): + pppoe_intf = {} + pppoe_intf['unhandled'] = None + pppoe_intf['ifname'] = ifname + pppoe_intf['state'] = _pppoe(ifname) + ret.append(pppoe_intf) + + return ret + +def _get_summary_data(ifname: typing.Optional[str], + iftype: typing.Optional[str], + vif: bool, vrrp: bool) -> list: + if ifname is None: + ifname = '' + if iftype is None: + iftype = '' + ret = [] + for interface in filtered_interfaces(ifname, iftype, vif, vrrp): + res_intf = {} + + res_intf['ifname'] = interface.ifname + res_intf['oper_state'] = interface.operational.get_state() + res_intf['admin_state'] = interface.get_admin_state() + res_intf['addr'] = [_ for _ in interface.get_addr() if not _.startswith('fe80::')] + res_intf['description'] = interface.get_alias() + + ret.append(res_intf) + + # find pppoe interfaces that are in a transitional/dead state + if ifname.startswith('pppoe') and not _find_intf_by_ifname(ret, ifname): + pppoe_intf = {} + pppoe_intf['unhandled'] = None + pppoe_intf['ifname'] = ifname + pppoe_intf['state'] = _pppoe(ifname) + ret.append(pppoe_intf) + + return ret + +def _get_counter_data(ifname: typing.Optional[str], + iftype: typing.Optional[str], + vif: bool, vrrp: bool) -> list: + if ifname is None: + ifname = '' + if iftype is None: + iftype = '' + ret = [] + for interface in filtered_interfaces(ifname, iftype, vif, vrrp): + res_intf = {} + + oper = interface.operational.get_state() + + if oper not in ('up','unknown'): + continue + + stats = interface.operational.get_stats() + cache = interface.operational.load_counters() + res_intf['ifname'] = interface.ifname + res_intf['rx_packets'] = _get_counter_val(cache['rx_packets'], stats['rx_packets']) + res_intf['rx_bytes'] = _get_counter_val(cache['rx_bytes'], stats['rx_bytes']) + res_intf['tx_packets'] = _get_counter_val(cache['tx_packets'], stats['tx_packets']) + res_intf['tx_bytes'] = _get_counter_val(cache['tx_bytes'], stats['tx_bytes']) + + ret.append(res_intf) + + return ret + +@catch_broken_pipe +def _format_show_data(data: list): + unhandled = [] + for intf in data: + if 'unhandled' in intf: + unhandled.append(intf) + continue + # instead of reformatting data, call non-json output: + rc, out = rc_cmd(f"ip addr show {intf['ifname']}") + if rc != 0: + continue + out = re.sub('^\d+:\s+','',out) + # add additional data already collected + if 'tunnel6' in intf: + t6_d = intf['tunnel6'] + t6_str = 'encaplimit %s hoplimit %s tclass %s flowlabel %s (flowinfo %s)' % ( + t6_d.get('encap_limit', ''), t6_d.get('hoplimit', ''), + t6_d.get('tclass', ''), t6_d.get('flowlabel', ''), + t6_d.get('flowinfo', '')) + out = re.sub('(\n\s+)(link/tunnel6)', f'\g<1>{t6_str}\g<1>\g<2>', out) + print(out) + ts = intf.get('counters_last_clear', 0) + if ts: + when = datetime.fromtimestamp(ts).strftime("%a %b %d %R:%S %Z %Y") + print(f' Last clear: {when}') + description = intf.get('description', '') + if description: + print(f' Description: {description}') + + stats = intf.get('stats', {}) + if stats: + print() + print(_format_stats(stats)) + + for intf in unhandled: + string = { + 'C': 'Coming up', + 'D': 'Link down' + }[intf['state']] + print(f"{intf['ifname']}: {string}") + + return 0 + +@catch_broken_pipe +def _format_show_summary(data): + format1 = '%-16s %-33s %-4s %s' + format2 = '%-16s %s' + + print('Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down') + print(format1 % ("Interface", "IP Address", "S/L", "Description")) + print(format1 % ("---------", "----------", "---", "-----------")) + + unhandled = [] + for intf in data: + if 'unhandled' in intf: + unhandled.append(intf) + continue + ifname = [intf['ifname'],] + oper = ['u',] if intf['oper_state'] in ('up', 'unknown') else ['D',] + admin = ['u',] if intf['admin_state'] in ('up', 'unknown') else ['A',] + addrs = intf['addr'] or ['-',] + descs = list(_split_text(intf['description'], 0)) + + while ifname or oper or admin or addrs or descs: + i = ifname.pop(0) if ifname else '' + a = addrs.pop(0) if addrs else '' + d = descs.pop(0) if descs else '' + s = [admin.pop(0)] if admin else [] + l = [oper.pop(0)] if oper else [] + if len(a) < 33: + print(format1 % (i, a, '/'.join(s+l), d)) + else: + print(format2 % (i, a)) + print(format1 % ('', '', '/'.join(s+l), d)) + + for intf in unhandled: + string = { + 'C': 'u/D', + 'D': 'A/D' + }[intf['state']] + print(format1 % (ifname, '', string, '')) + + return 0 + +@catch_broken_pipe +def _format_show_counters(data: list): + formatting = '%-12s %10s %10s %10s %10s' + print(formatting % ('Interface', 'Rx Packets', 'Rx Bytes', 'Tx Packets', 'Tx Bytes')) + + for intf in data: + print(formatting % ( + intf['ifname'], + intf['rx_packets'], + intf['rx_bytes'], + intf['tx_packets'], + intf['tx_bytes'] + )) + + return 0 + +def show(raw: bool, intf_name: typing.Optional[str], + intf_type: typing.Optional[str], + vif: bool, vrrp: bool): + data = _get_raw_data(intf_name, intf_type, vif, vrrp) + if raw: + return data + return _format_show_data(data) + +def show_summary(raw: bool, intf_name: typing.Optional[str], + intf_type: typing.Optional[str], + vif: bool, vrrp: bool): + data = _get_summary_data(intf_name, intf_type, vif, vrrp) + if raw: + return data + return _format_show_summary(data) + +def show_counters(raw: bool, intf_name: typing.Optional[str], + intf_type: typing.Optional[str], + vif: bool, vrrp: bool): + data = _get_counter_data(intf_name, intf_type, vif, vrrp) + if raw: + return data + return _format_show_counters(data) + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py index e0d204a0a..f6417764a 100755 --- a/src/op_mode/ipsec.py +++ b/src/op_mode/ipsec.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-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 @@ -173,7 +173,7 @@ def _get_parent_sa_proposal(connection_name: str, data: list) -> dict: for sa in data: # check if parent SA exist if connection_name not in sa.keys(): - return {} + continue if 'encr-alg' in sa[connection_name]: encr_alg = sa.get(connection_name, '').get('encr-alg') cipher = encr_alg.split('_')[0] @@ -203,16 +203,17 @@ def _get_parent_sa_state(connection_name: str, data: list) -> str: Returns: Parent SA connection state """ + ike_state = 'down' if not data: - return 'down' + return ike_state for sa in data: # check if parent SA exist - if connection_name not in sa.keys(): - return 'down' - if sa[connection_name]['state'].lower() == 'established': - return 'up' - else: - return 'down' + for connection, connection_conf in sa.items(): + if connection_name != connection: + continue + if connection_conf['state'].lower() == 'established': + ike_state = 'up' + return ike_state def _get_child_sa_state(connection_name: str, tunnel_name: str, @@ -227,19 +228,20 @@ def _get_child_sa_state(connection_name: str, tunnel_name: str, Returns: str: `up` if child SA state is 'installed' otherwise `down` """ + child_sa = 'down' if not data: - return 'down' + return child_sa for sa in data: # check if parent SA exist if connection_name not in sa.keys(): - return 'down' + continue child_sas = sa[connection_name]['child-sas'] # Get all child SA states # there can be multiple SAs per tunnel child_sa_states = [ v['state'] for k, v in child_sas.items() if v['name'] == tunnel_name ] - return 'up' if 'INSTALLED' in child_sa_states else 'down' + return 'up' if 'INSTALLED' in child_sa_states else child_sa def _get_child_sa_info(connection_name: str, tunnel_name: str, @@ -257,7 +259,7 @@ def _get_child_sa_info(connection_name: str, tunnel_name: str, for sa in data: # check if parent SA exist if connection_name not in sa.keys(): - return {} + continue child_sas = sa[connection_name]['child-sas'] # Get all child SA data # Skip temp SA name (first key), get only SA values as dict diff --git a/src/op_mode/lldp.py b/src/op_mode/lldp.py new file mode 100755 index 000000000..dc2b1e0b5 --- /dev/null +++ b/src/op_mode/lldp.py @@ -0,0 +1,138 @@ +#!/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 jmespath +import json +import sys +import typing + +from tabulate import tabulate + +from vyos.configquery import ConfigTreeQuery +from vyos.util import cmd +from vyos.util import dict_search + +import vyos.opmode +unconf_message = 'LLDP is not configured' +capability_codes = """Capability Codes: R - Router, B - Bridge, W - Wlan r - Repeater, S - Station + D - Docsis, T - Telephone, O - Other + +""" + +def _verify(func): + """Decorator checks if LLDP config exists""" + from functools import wraps + + @wraps(func) + def _wrapper(*args, **kwargs): + config = ConfigTreeQuery() + if not config.exists(['service', 'lldp']): + raise vyos.opmode.UnconfiguredSubsystem(unconf_message) + return func(*args, **kwargs) + return _wrapper + +def _get_raw_data(interface=None, detail=False): + """ + If interface name is not set - get all interfaces + """ + tmp = 'lldpcli -f json show neighbors' + if detail: + tmp += f' details' + if interface: + tmp += f' ports {interface}' + output = cmd(tmp) + data = json.loads(output) + if not data: + return [] + return data + +def _get_formatted_output(raw_data): + data_entries = [] + for neighbor in dict_search('lldp.interface', raw_data): + for local_if, values in neighbor.items(): + tmp = [] + + # Device field + if 'chassis' in values: + tmp.append(next(iter(values['chassis']))) + else: + tmp.append('') + + # Local Port field + tmp.append(local_if) + + # Protocol field + tmp.append(values['via']) + + # Capabilities + cap = '' + capabilities = jmespath.search('chassis.[*][0][0].capability', values) + if capabilities: + 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' + tmp.append(cap) + + # Remote software platform + platform = jmespath.search('chassis.[*][0][0].descr', values) + tmp.append(platform[:37]) + + # Remote interface + interface = jmespath.search('port.descr', values) + if not interface: + interface = jmespath.search('port.id.value', values) + if not interface: + interface = 'Unknown' + tmp.append(interface) + + # Add individual neighbor to output list + data_entries.append(tmp) + + headers = ["Device", "Local Port", "Protocol", "Capability", "Platform", "Remote Port"] + output = tabulate(data_entries, headers, numalign="left") + return capability_codes + output + +@_verify +def show_neighbors(raw: bool, interface: typing.Optional[str], detail: typing.Optional[bool]): + lldp_data = _get_raw_data(interface=interface, detail=detail) + if raw: + return lldp_data + else: + return _get_formatted_output(lldp_data) + +if __name__ == "__main__": + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/lldp_op.py b/src/op_mode/lldp_op.py deleted file mode 100755 index 17f6bf552..000000000 --- a/src/op_mode/lldp_op.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# 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 argparse -import jinja2 -import json - -from sys import exit -from tabulate import tabulate - -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. -lldp_out = """Capability Codes: R - Router, B - Bridge, W - Wlan r - Repeater, S - Station - D - Docsis, T - Telephone, O - Other - -Device ID Local Proto Cap Platform Port ID ---------- ----- ----- --- -------- ------- -{% 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 json show neighbors') - -def parse_data(data, interface): - output = [] - if not isinstance(data, list): - data = [data] - - for neighbor in data: - for local_if, values in neighbor.items(): - if interface is not None and local_if != interface: - continue - cap = '' - for chassis, c_value in values.get('chassis', {}).items(): - # bail out early if no capabilities found - if 'capability' not in c_value: - continue - capabilities = c_value['capability'] - if isinstance(capabilities, dict): - capabilities = [capabilities] - - 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() - tmp = { 'neighbors' : [] } - - c = Config() - if not c.exists_effective(['service', 'lldp']): - print('Service LLDP is not configured') - exit(0) - - if args.detail: - print(cmd('/usr/sbin/lldpctl -f plain')) - exit(0) - elif args.all or args.interface: - tmp = json.loads(get_neighbors()) - neighbors = dict() - - if 'interface' in tmp.get('lldp'): - neighbors = tmp['lldp']['interface'] - - else: - parser.print_help() - exit(1) - - tmpl = jinja2.Template(lldp_out, trim_blocks=True) - config_text = tmpl.render(parse_data(neighbors, interface=args.interface)) - print(config_text) - - exit(0) diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py index f899eb3dc..cf06de0e9 100755 --- a/src/op_mode/nat.py +++ b/src/op_mode/nat.py @@ -18,23 +18,21 @@ import jmespath import json import sys import xmltodict +import typing -from sys import exit from tabulate import tabulate -from vyos.configquery import ConfigTreeQuery +import vyos.opmode +from vyos.configquery import ConfigTreeQuery from vyos.util import cmd from vyos.util import dict_search -import vyos.opmode - - base = 'nat' unconf_message = 'NAT is not configured' -def _get_xml_translation(direction, family): +def _get_xml_translation(direction, family, address=None): """ Get conntrack XML output --src-nat|--dst-nat """ @@ -42,7 +40,10 @@ def _get_xml_translation(direction, family): opt = '--src-nat' if direction == 'destination': opt = '--dst-nat' - return cmd(f'sudo conntrack --dump --family {family} {opt} --output xml') + tmp = f'conntrack --dump --family {family} {opt} --output xml' + if address: + tmp += f' --src {address}' + return cmd(tmp) def _xml_to_dict(xml): @@ -66,7 +67,7 @@ def _get_json_data(direction, family): if direction == 'destination': chain = 'PREROUTING' family = 'ip6' if family == 'inet6' else 'ip' - return cmd(f'sudo nft --json list chain {family} vyos_nat {chain}') + return cmd(f'nft --json list chain {family} vyos_nat {chain}') def _get_raw_data_rules(direction, family): @@ -82,11 +83,11 @@ def _get_raw_data_rules(direction, family): return rules -def _get_raw_translation(direction, family): +def _get_raw_translation(direction, family, address=None): """ Return: dictionary """ - xml = _get_xml_translation(direction, family) + xml = _get_xml_translation(direction, family, address) if len(xml) == 0: output = {'conntrack': { @@ -231,7 +232,7 @@ def _get_formatted_output_statistics(data, direction): return output -def _get_formatted_translation(dict_data, nat_direction, family): +def _get_formatted_translation(dict_data, nat_direction, family, verbose): data_entries = [] if 'error' in dict_data['conntrack']: return 'Entries not found' @@ -269,14 +270,14 @@ def _get_formatted_translation(dict_data, nat_direction, family): reply_src = f'{reply_src}:{reply_sport}' if reply_sport else reply_src reply_dst = f'{reply_dst}:{reply_dport}' if reply_dport else reply_dst state = meta['state'] if 'state' in meta else '' - mark = meta['mark'] + mark = meta.get('mark', '') zone = meta['zone'] if 'zone' in meta else '' if nat_direction == 'source': - data_entries.append( - [orig_src, reply_dst, proto, timeout, mark, zone]) + tmp = [orig_src, reply_dst, proto, timeout, mark, zone] + data_entries.append(tmp) elif nat_direction == 'destination': - data_entries.append( - [orig_dst, reply_src, proto, timeout, mark, zone]) + tmp = [orig_dst, reply_src, proto, timeout, mark, zone] + data_entries.append(tmp) headers = ["Pre-NAT", "Post-NAT", "Proto", "Timeout", "Mark", "Zone"] output = tabulate(data_entries, headers, numalign="left") @@ -315,13 +316,20 @@ def show_statistics(raw: bool, direction: str, family: str): @_verify -def show_translations(raw: bool, direction: str, family: str): +def show_translations(raw: bool, direction: + str, family: str, + address: typing.Optional[str], + verbose: typing.Optional[bool]): family = 'ipv6' if family == 'inet6' else 'ipv4' - nat_translation = _get_raw_translation(direction, family) + nat_translation = _get_raw_translation(direction, + family=family, + address=address) + if raw: return nat_translation else: - return _get_formatted_translation(nat_translation, direction, family) + return _get_formatted_translation(nat_translation, direction, family, + verbose) if __name__ == '__main__': diff --git a/src/op_mode/route.py b/src/op_mode/route.py index d07a34180..7f0f9cbac 100755 --- a/src/op_mode/route.py +++ b/src/op_mode/route.py @@ -54,16 +54,43 @@ frr_command_template = Template(""" {% endif %} """) -def show_summary(raw: bool): +def show_summary(raw: bool, family: str, table: typing.Optional[int], vrf: typing.Optional[str]): from vyos.util import cmd + if family == 'inet': + family_cmd = 'ip' + elif family == 'inet6': + family_cmd = 'ipv6' + else: + raise ValueError(f"Unsupported address family {family}") + + if (table is not None) and (vrf is not None): + raise ValueError("table and vrf options are mutually exclusive") + + # Replace with Jinja if it ever starts growing + if table: + table_cmd = f"table {table}" + else: + table_cmd = "" + + if vrf: + vrf_cmd = f"vrf {vrf}" + else: + vrf_cmd = "" + if raw: from json import loads - output = cmd(f"vtysh -c 'show ip route summary json'") - return loads(output) + output = cmd(f"vtysh -c 'show {family_cmd} route {vrf_cmd} summary {table_cmd} json'").strip() + + # If there are no routes in a table, its "JSON" output is an empty string, + # as of FRR 8.4.1 + if output: + return loads(output) + else: + return {} else: - output = cmd(f"vtysh -c 'show ip route summary'") + output = cmd(f"vtysh -c 'show {family_cmd} route {vrf_cmd} summary {table_cmd}'") return output def show(raw: bool, diff --git a/src/op_mode/show_dhcp.py b/src/op_mode/show_dhcp.py deleted file mode 100755 index 4b1758eea..000000000 --- a/src/op_mode/show_dhcp.py +++ /dev/null @@ -1,260 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-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/>. -# -# TODO: merge with show_dhcpv6.py - -from json import dumps -from argparse import ArgumentParser -from ipaddress import ip_address -from tabulate import tabulate -from sys import exit -from collections import OrderedDict -from datetime import datetime - -from isc_dhcp_leases import Lease, IscDhcpLeases - -from vyos.base import Warning -from vyos.config import Config -from vyos.util import is_systemd_service_running - -lease_file = "/config/dhcpd.leases" -pool_key = "shared-networkname" - -lease_display_fields = OrderedDict() -lease_display_fields['ip'] = 'IP address' -lease_display_fields['hardware_address'] = 'Hardware address' -lease_display_fields['state'] = 'State' -lease_display_fields['start'] = 'Lease start' -lease_display_fields['end'] = 'Lease expiration' -lease_display_fields['remaining'] = 'Remaining' -lease_display_fields['pool'] = 'Pool' -lease_display_fields['hostname'] = 'Hostname' - -lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup'] - -def in_pool(lease, pool): - if pool_key in lease.sets: - if lease.sets[pool_key] == pool: - return True - - return False - -def utc_to_local(utc_dt): - return datetime.fromtimestamp((utc_dt - datetime(1970,1,1)).total_seconds()) - -def get_lease_data(lease): - data = {} - - # isc-dhcp lease times are in UTC so we need to convert them to local time to display - try: - data["start"] = utc_to_local(lease.start).strftime("%Y/%m/%d %H:%M:%S") - except: - data["start"] = "" - - try: - data["end"] = utc_to_local(lease.end).strftime("%Y/%m/%d %H:%M:%S") - except: - data["end"] = "" - - try: - data["remaining"] = lease.end - datetime.utcnow() - # negative timedelta prints wrong so bypass it - if (data["remaining"].days >= 0): - # substraction gives us a timedelta object which can't be formatted with strftime - # so we use str(), split gets rid of the microseconds - data["remaining"] = str(data["remaining"]).split('.')[0] - else: - data["remaining"] = "" - except: - data["remaining"] = "" - - # currently not used but might come in handy - # todo: parse into datetime string - for prop in ['tstp', 'tsfp', 'atsfp', 'cltt']: - if prop in lease.data: - data[prop] = lease.data[prop] - else: - data[prop] = '' - - data["hardware_address"] = lease.ethernet - data["hostname"] = lease.hostname - - data["state"] = lease.binding_state - data["ip"] = lease.ip - - try: - data["pool"] = lease.sets[pool_key] - except: - data["pool"] = "" - - return data - -def get_leases(config, leases, state, pool=None, sort='ip'): - # get leases from file - leases = IscDhcpLeases(lease_file).get() - - # filter leases by state - if 'all' not in state: - leases = list(filter(lambda x: x.binding_state in state, leases)) - - # filter leases by pool name - if pool is not None: - if config.exists_effective("service dhcp-server shared-network-name {0}".format(pool)): - leases = list(filter(lambda x: in_pool(x, pool), leases)) - else: - print("Pool {0} does not exist.".format(pool)) - exit(0) - - # should maybe filter all state=active by lease.valid here? - - # sort by start time to dedupe (newest lease overrides older) - leases = sorted(leases, key = lambda lease: lease.start) - - # dedupe by converting to dict - leases_dict = {} - for lease in leases: - # dedupe by IP - leases_dict[lease.ip] = lease - - # convert the lease data - leases = list(map(get_lease_data, leases_dict.values())) - - # apply output/display sort - if sort == 'ip': - leases = sorted(leases, key = lambda lease: int(ip_address(lease['ip']))) - else: - leases = sorted(leases, key = lambda lease: lease[sort]) - - return leases - -def show_leases(leases): - lease_list = [] - for l in leases: - lease_list_params = [] - for k in lease_display_fields.keys(): - lease_list_params.append(l[k]) - lease_list.append(lease_list_params) - - output = tabulate(lease_list, lease_display_fields.values()) - - print(output) - -def get_pool_size(config, pool): - size = 0 - subnets = config.list_effective_nodes("service dhcp-server shared-network-name {0} subnet".format(pool)) - for s in subnets: - ranges = config.list_effective_nodes("service dhcp-server shared-network-name {0} subnet {1} range".format(pool, s)) - for r in ranges: - start = config.return_effective_value("service dhcp-server shared-network-name {0} subnet {1} range {2} start".format(pool, s, r)) - stop = config.return_effective_value("service dhcp-server shared-network-name {0} subnet {1} range {2} stop".format(pool, s, r)) - - # Add +1 because both range boundaries are inclusive - size += int(ip_address(stop)) - int(ip_address(start)) + 1 - - return size - -def show_pool_stats(stats): - headers = ["Pool", "Size", "Leases", "Available", "Usage"] - output = tabulate(stats, headers) - - print(output) - -if __name__ == '__main__': - parser = ArgumentParser() - - group = parser.add_mutually_exclusive_group() - group.add_argument("-l", "--leases", action="store_true", help="Show DHCP leases") - group.add_argument("-s", "--statistics", action="store_true", help="Show DHCP statistics") - group.add_argument("--allowed", type=str, choices=["sort", "state"], help="Show allowed values for argument") - - parser.add_argument("-p", "--pool", type=str, help="Show lease for specific pool") - parser.add_argument("-S", "--sort", type=str, default='ip', help="Sort by") - parser.add_argument("-t", "--state", type=str, nargs="+", default=["active"], help="Lease state to show (can specify multiple with spaces)") - parser.add_argument("-j", "--json", action="store_true", default=False, help="Produce JSON output") - - args = parser.parse_args() - - conf = Config() - - if args.allowed == 'sort': - print(' '.join(lease_display_fields.keys())) - exit(0) - elif args.allowed == 'state': - print(' '.join(lease_valid_states)) - exit(0) - elif args.allowed: - parser.print_help() - exit(1) - - if args.sort not in lease_display_fields.keys(): - print(f'Invalid sort key, choose from: {list(lease_display_fields.keys())}') - exit(0) - - if not set(args.state) < set(lease_valid_states): - print(f'Invalid lease state, choose from: {lease_valid_states}') - exit(0) - - # Do nothing if service is not configured - if not conf.exists_effective('service dhcp-server'): - print("DHCP service is not configured.") - exit(0) - - # if dhcp server is down, inactive leases may still be shown as active, so warn the user. - if not is_systemd_service_running('isc-dhcp-server.service'): - Warning('DHCP server is configured but not started. Data may be stale.') - - if args.leases: - leases = get_leases(conf, lease_file, args.state, args.pool, args.sort) - - if args.json: - print(dumps(leases, indent=4)) - else: - show_leases(leases) - - elif args.statistics: - pools = [] - - # Get relevant pools - if args.pool: - pools = [args.pool] - else: - pools = conf.list_effective_nodes("service dhcp-server shared-network-name") - - # Get pool usage stats - stats = [] - for p in pools: - size = get_pool_size(conf, p) - leases = len(get_leases(conf, lease_file, state='active', pool=p)) - - use_percentage = round(leases / size * 100) if size != 0 else 0 - - if args.json: - pool_stats = {"pool": p, "size": size, "leases": leases, - "available": (size - leases), "percentage": use_percentage} - else: - # For tabulate - pool_stats = [p, size, leases, size - leases, "{0}%".format(use_percentage)] - stats.append(pool_stats) - - # Print stats - if args.json: - print(dumps(stats, indent=4)) - else: - show_pool_stats(stats) - - else: - parser.print_help() - exit(1) diff --git a/src/op_mode/show_dhcpv6.py b/src/op_mode/show_dhcpv6.py deleted file mode 100755 index b34b730e6..000000000 --- a/src/op_mode/show_dhcpv6.py +++ /dev/null @@ -1,220 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-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/>. -# -# TODO: merge with show_dhcp.py - -from json import dumps -from argparse import ArgumentParser -from ipaddress import ip_address -from tabulate import tabulate -from sys import exit -from collections import OrderedDict -from datetime import datetime - -from isc_dhcp_leases import Lease, IscDhcpLeases - -from vyos.base import Warning -from vyos.config import Config -from vyos.util import is_systemd_service_running - -lease_file = "/config/dhcpdv6.leases" -pool_key = "shared-networkname" - -lease_display_fields = OrderedDict() -lease_display_fields['ip'] = 'IPv6 address' -lease_display_fields['state'] = 'State' -lease_display_fields['last_comm'] = 'Last communication' -lease_display_fields['expires'] = 'Lease expiration' -lease_display_fields['remaining'] = 'Remaining' -lease_display_fields['type'] = 'Type' -lease_display_fields['pool'] = 'Pool' -lease_display_fields['iaid_duid'] = 'IAID_DUID' - -lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup'] - -def in_pool(lease, pool): - if pool_key in lease.sets: - if lease.sets[pool_key] == pool: - return True - - return False - -def format_hex_string(in_str): - out_str = "" - - # if input is divisible by 2, add : every 2 chars - if len(in_str) > 0 and len(in_str) % 2 == 0: - out_str = ':'.join(a+b for a,b in zip(in_str[::2], in_str[1::2])) - else: - out_str = in_str - - return out_str - -def utc_to_local(utc_dt): - return datetime.fromtimestamp((utc_dt - datetime(1970,1,1)).total_seconds()) - -def get_lease_data(lease): - data = {} - - # isc-dhcp lease times are in UTC so we need to convert them to local time to display - try: - data["expires"] = utc_to_local(lease.end).strftime("%Y/%m/%d %H:%M:%S") - except: - data["expires"] = "" - - try: - data["last_comm"] = utc_to_local(lease.last_communication).strftime("%Y/%m/%d %H:%M:%S") - except: - data["last_comm"] = "" - - try: - data["remaining"] = lease.end - datetime.utcnow() - # negative timedelta prints wrong so bypass it - if (data["remaining"].days >= 0): - # substraction gives us a timedelta object which can't be formatted with strftime - # so we use str(), split gets rid of the microseconds - data["remaining"] = str(data["remaining"]).split('.')[0] - else: - data["remaining"] = "" - except: - data["remaining"] = "" - - # isc-dhcp records lease declarations as ia_{na|ta|pd} IAID_DUID {...} - # where IAID_DUID is the combined IAID and DUID - data["iaid_duid"] = format_hex_string(lease.host_identifier_string) - - lease_types_long = {"na": "non-temporary", "ta": "temporary", "pd": "prefix delegation"} - data["type"] = lease_types_long[lease.type] - - data["state"] = lease.binding_state - data["ip"] = lease.ip - - try: - data["pool"] = lease.sets[pool_key] - except: - data["pool"] = "" - - return data - -def get_leases(config, leases, state, pool=None, sort='ip'): - leases = IscDhcpLeases(lease_file).get() - - # filter leases by state - if 'all' not in state: - leases = list(filter(lambda x: x.binding_state in state, leases)) - - # filter leases by pool name - if pool is not None: - if config.exists_effective("service dhcp-server shared-network-name {0}".format(pool)): - leases = list(filter(lambda x: in_pool(x, pool), leases)) - else: - print("Pool {0} does not exist.".format(pool)) - exit(0) - - # should maybe filter all state=active by lease.valid here? - - # sort by last_comm time to dedupe (newest lease overrides older) - leases = sorted(leases, key = lambda lease: lease.last_communication) - - # dedupe by converting to dict - leases_dict = {} - for lease in leases: - # dedupe by IP - leases_dict[lease.ip] = lease - - # convert the lease data - leases = list(map(get_lease_data, leases_dict.values())) - - # apply output/display sort - if sort == 'ip': - leases = sorted(leases, key = lambda k: int(ip_address(k['ip'].split('/')[0]))) - else: - leases = sorted(leases, key = lambda k: k[sort]) - - return leases - -def show_leases(leases): - lease_list = [] - for l in leases: - lease_list_params = [] - for k in lease_display_fields.keys(): - lease_list_params.append(l[k]) - lease_list.append(lease_list_params) - - output = tabulate(lease_list, lease_display_fields.values()) - - print(output) - -if __name__ == '__main__': - parser = ArgumentParser() - - group = parser.add_mutually_exclusive_group() - group.add_argument("-l", "--leases", action="store_true", help="Show DHCPv6 leases") - group.add_argument("-s", "--statistics", action="store_true", help="Show DHCPv6 statistics") - group.add_argument("--allowed", type=str, choices=["pool", "sort", "state"], help="Show allowed values for argument") - - parser.add_argument("-p", "--pool", type=str, help="Show lease for specific pool") - parser.add_argument("-S", "--sort", type=str, default='ip', help="Sort by") - parser.add_argument("-t", "--state", type=str, nargs="+", default=["active"], help="Lease state to show (can specify multiple with spaces)") - parser.add_argument("-j", "--json", action="store_true", default=False, help="Produce JSON output") - - args = parser.parse_args() - - conf = Config() - - if args.allowed == 'pool': - if conf.exists_effective('service dhcpv6-server'): - print(' '.join(conf.list_effective_nodes("service dhcpv6-server shared-network-name"))) - exit(0) - elif args.allowed == 'sort': - print(' '.join(lease_display_fields.keys())) - exit(0) - elif args.allowed == 'state': - print(' '.join(lease_valid_states)) - exit(0) - elif args.allowed: - parser.print_help() - exit(1) - - if args.sort not in lease_display_fields.keys(): - print(f'Invalid sort key, choose from: {list(lease_display_fields.keys())}') - exit(0) - - if not set(args.state) < set(lease_valid_states): - print(f'Invalid lease state, choose from: {lease_valid_states}') - exit(0) - - # Do nothing if service is not configured - if not conf.exists_effective('service dhcpv6-server'): - print("DHCPv6 service is not configured") - exit(0) - - # if dhcp server is down, inactive leases may still be shown as active, so warn the user. - if not is_systemd_service_running('isc-dhcp-server6.service'): - Warning('DHCPv6 server is configured but not started. Data may be stale.') - - if args.leases: - leases = get_leases(conf, lease_file, args.state, args.pool, args.sort) - - if args.json: - print(dumps(leases, indent=4)) - else: - show_leases(leases) - elif args.statistics: - print("DHCPv6 statistics option is not available") - else: - parser.print_help() - exit(1) diff --git a/src/op_mode/show_ipsec_sa.py b/src/op_mode/show_ipsec_sa.py deleted file mode 100755 index 5b8f00dba..000000000 --- a/src/op_mode/show_ipsec_sa.py +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2022 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 re import split as re_split -from sys import exit - -from hurry import filesize -from tabulate import tabulate -from vici import Session as vici_session - -from vyos.util import seconds_to_human - - -def convert(text): - return int(text) if text.isdigit() else text.lower() - - -def alphanum_key(key): - return [convert(c) for c in re_split('([0-9]+)', str(key))] - - -def format_output(sas): - sa_data = [] - - for sa in sas: - for parent_sa in sa.values(): - # create an item for each child-sa - for child_sa in parent_sa.get('child-sas', {}).values(): - # prepare a list for output data - sa_out_name = sa_out_state = sa_out_uptime = sa_out_bytes = sa_out_packets = sa_out_remote_addr = sa_out_remote_id = sa_out_proposal = 'N/A' - - # collect raw data - sa_name = child_sa.get('name') - sa_state = child_sa.get('state') - sa_uptime = child_sa.get('install-time') - sa_bytes_in = child_sa.get('bytes-in') - sa_bytes_out = child_sa.get('bytes-out') - sa_packets_in = child_sa.get('packets-in') - sa_packets_out = child_sa.get('packets-out') - sa_remote_addr = parent_sa.get('remote-host') - sa_remote_id = parent_sa.get('remote-id') - sa_proposal_encr_alg = child_sa.get('encr-alg') - sa_proposal_integ_alg = child_sa.get('integ-alg') - sa_proposal_encr_keysize = child_sa.get('encr-keysize') - sa_proposal_dh_group = child_sa.get('dh-group') - - # format data to display - if sa_name: - sa_out_name = sa_name.decode() - if sa_state: - if sa_state == b'INSTALLED': - sa_out_state = 'up' - else: - sa_out_state = 'down' - if sa_uptime: - sa_out_uptime = seconds_to_human(sa_uptime.decode()) - if sa_bytes_in and sa_bytes_out: - bytes_in = filesize.size(int(sa_bytes_in.decode())) - bytes_out = filesize.size(int(sa_bytes_out.decode())) - sa_out_bytes = f'{bytes_in}/{bytes_out}' - if sa_packets_in and sa_packets_out: - packets_in = filesize.size(int(sa_packets_in.decode()), - system=filesize.si) - packets_out = filesize.size(int(sa_packets_out.decode()), - system=filesize.si) - sa_out_packets = f'{packets_in}/{packets_out}' - if sa_remote_addr: - sa_out_remote_addr = sa_remote_addr.decode() - if sa_remote_id: - sa_out_remote_id = sa_remote_id.decode() - # format proposal - if sa_proposal_encr_alg: - sa_out_proposal = sa_proposal_encr_alg.decode() - if sa_proposal_encr_keysize: - sa_proposal_encr_keysize_str = sa_proposal_encr_keysize.decode() - sa_out_proposal = f'{sa_out_proposal}_{sa_proposal_encr_keysize_str}' - if sa_proposal_integ_alg: - sa_proposal_integ_alg_str = sa_proposal_integ_alg.decode() - sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_integ_alg_str}' - if sa_proposal_dh_group: - sa_proposal_dh_group_str = sa_proposal_dh_group.decode() - sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_dh_group_str}' - - # add a new item to output data - sa_data.append([ - sa_out_name, sa_out_state, sa_out_uptime, sa_out_bytes, - sa_out_packets, sa_out_remote_addr, sa_out_remote_id, - sa_out_proposal - ]) - - # return output data - return sa_data - - -if __name__ == '__main__': - try: - session = vici_session() - sas = list(session.list_sas()) - - sa_data = format_output(sas) - sa_data = sorted(sa_data, key=alphanum_key) - - headers = [ - "Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out", - "Remote address", "Remote ID", "Proposal" - ] - output = tabulate(sa_data, headers) - print(output) - except PermissionError: - print("You do not have a permission to connect to the IPsec daemon") - exit(1) - except ConnectionRefusedError: - print("IPsec is not runing") - exit(1) - except Exception as e: - print("An error occured: {0}".format(e)) - exit(1) diff --git a/src/op_mode/show_nat66_statistics.py b/src/op_mode/show_nat66_statistics.py deleted file mode 100755 index cb10aed9f..000000000 --- a/src/op_mode/show_nat66_statistics.py +++ /dev/null @@ -1,63 +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 <http://www.gnu.org/licenses/>. - -import jmespath -import json - -from argparse import ArgumentParser -from jinja2 import Template -from sys import exit -from vyos.util import cmd - -OUT_TMPL_SRC=""" -rule pkts bytes interface ----- ---- ----- --------- -{% for r in output %} -{% if r.comment %} -{% set packets = r.counter.packets %} -{% set bytes = r.counter.bytes %} -{% set interface = r.interface %} -{# remove rule comment prefix #} -{% set comment = r.comment | replace('SRC-NAT66-', '') | replace('DST-NAT66-', '') %} -{{ "%-4s" | format(comment) }} {{ "%9s" | format(packets) }} {{ "%12s" | format(bytes) }} {{ interface }} -{% endif %} -{% endfor %} -""" - -parser = ArgumentParser() -group = parser.add_mutually_exclusive_group() -group.add_argument("--source", help="Show statistics for configured source NAT rules", action="store_true") -group.add_argument("--destination", help="Show statistics for configured destination NAT rules", action="store_true") -args = parser.parse_args() - -if args.source or args.destination: - tmp = cmd('sudo nft -j list table ip6 vyos_nat') - tmp = json.loads(tmp) - - source = r"nftables[?rule.chain=='POSTROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }" - destination = r"nftables[?rule.chain=='PREROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }" - data = { - 'output' : jmespath.search(source if args.source else destination, tmp), - 'direction' : 'source' if args.source else 'destination' - } - - tmpl = Template(OUT_TMPL_SRC, lstrip_blocks=True) - print(tmpl.render(data)) - exit(0) -else: - parser.print_help() - exit(1) - diff --git a/src/op_mode/show_nat66_translations.py b/src/op_mode/show_nat66_translations.py deleted file mode 100755 index 045d64065..000000000 --- a/src/op_mode/show_nat66_translations.py +++ /dev/null @@ -1,204 +0,0 @@ -#!/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 <http://www.gnu.org/licenses/>. - -''' -show nat translations -''' - -import os -import sys -import ipaddress -import argparse -import xmltodict - -from vyos.util import popen -from vyos.util import DEVNULL - -conntrack = '/usr/sbin/conntrack' - -verbose_format = "%-20s %-18s %-20s %-18s" -normal_format = "%-20s %-20s %-4s %-8s %s" - - -def headers(verbose, pipe): - if verbose: - return verbose_format % ('Pre-NAT src', 'Pre-NAT dst', 'Post-NAT src', 'Post-NAT dst') - return normal_format % ('Pre-NAT', 'Post-NAT', 'Prot', 'Timeout', 'Type' if pipe else '') - - -def command(srcdest, proto, ipaddr): - command = f'{conntrack} -o xml -L -f ipv6' - - if proto: - command += f' -p {proto}' - - if srcdest == 'source': - command += ' -n' - if ipaddr: - command += f' --orig-src {ipaddr}' - if srcdest == 'destination': - command += ' -g' - if ipaddr: - command += f' --orig-dst {ipaddr}' - - return command - - -def run(command): - xml, code = popen(command,stderr=DEVNULL) - if code: - sys.exit('conntrack failed') - return xml - - -def content(xmlfile): - xml = '' - with open(xmlfile,'r') as r: - xml += r.read() - return xml - - -def pipe(): - xml = '' - while True: - line = sys.stdin.readline() - xml += line - if '</conntrack>' in line: - break - - sys.stdin = open('/dev/tty') - return xml - - -def process(data, stats, protocol, pipe, verbose, flowtype=''): - if not data: - return - - parsed = xmltodict.parse(data) - - print(headers(verbose, pipe)) - - # to help the linter to detect typos - ORIGINAL = 'original' - REPLY = 'reply' - INDEPENDANT = 'independent' - SPORT = 'sport' - DPORT = 'dport' - SRC = 'src' - DST = 'dst' - - for rule in parsed['conntrack']['flow']: - src, dst, sport, dport, proto = {}, {}, {}, {}, {} - packet_count, byte_count = {}, {} - timeout, use = 0, 0 - - rule_type = rule.get('type', '') - - for meta in rule['meta']: - # print(meta) - direction = meta['@direction'] - - if direction in (ORIGINAL, REPLY): - if 'layer3' in meta: - l3 = meta['layer3'] - src[direction] = l3[SRC] - dst[direction] = l3[DST] - - if 'layer4' in meta: - l4 = meta['layer4'] - sp = l4.get(SPORT, '') - dp = l4.get(DPORT, '') - if sp: - sport[direction] = sp - if dp: - dport[direction] = dp - proto[direction] = l4.get('@protoname','') - - if stats and 'counters' in meta: - packet_count[direction] = meta['packets'] - byte_count[direction] = meta['bytes'] - continue - - if direction == INDEPENDANT: - timeout = meta['timeout'] - use = meta['use'] - continue - - in_src = '%s:%s' % (src[ORIGINAL], sport[ORIGINAL]) if ORIGINAL in sport else src[ORIGINAL] - in_dst = '%s:%s' % (dst[ORIGINAL], dport[ORIGINAL]) if ORIGINAL in dport else dst[ORIGINAL] - - # inverted the the perl code !!? - out_dst = '%s:%s' % (dst[REPLY], dport[REPLY]) if REPLY in dport else dst[REPLY] - out_src = '%s:%s' % (src[REPLY], sport[REPLY]) if REPLY in sport else src[REPLY] - - if flowtype == 'source': - v = ORIGINAL in sport and REPLY in dport - f = '%s:%s' % (src[ORIGINAL], sport[ORIGINAL]) if v else src[ORIGINAL] - t = '%s:%s' % (dst[REPLY], dport[REPLY]) if v else dst[REPLY] - else: - v = ORIGINAL in dport and REPLY in sport - f = '%s:%s' % (dst[ORIGINAL], dport[ORIGINAL]) if v else dst[ORIGINAL] - t = '%s:%s' % (src[REPLY], sport[REPLY]) if v else src[REPLY] - - # Thomas: I do not believe proto should be an option - p = proto.get('original', '') - if protocol and p != protocol: - continue - - if verbose: - msg = verbose_format % (in_src, in_dst, out_dst, out_src) - p = f'{p}: ' if p else '' - msg += f'\n {p}{f} ==> {t}' - msg += f' timeout: {timeout}' if timeout else '' - msg += f' use: {use} ' if use else '' - msg += f' type: {rule_type}' if rule_type else '' - print(msg) - else: - print(normal_format % (f, t, p, timeout, rule_type if rule_type else '')) - - if stats: - for direction in ('original', 'reply'): - if direction in packet_count: - print(' %-8s: packets %s, bytes %s' % direction, packet_count[direction], byte_count[direction]) - - -def main(): - parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__) - parser.add_argument('--verbose', help='provide more details about the flows', action='store_true') - parser.add_argument('--proto', help='filter by protocol', default='', type=str) - parser.add_argument('--file', help='read the conntrack xml from a file', type=str) - parser.add_argument('--stats', help='add usage statistics', action='store_true') - parser.add_argument('--type', help='NAT type (source, destination)', required=True, type=str) - parser.add_argument('--ipaddr', help='source ip address to filter on', type=ipaddress.ip_address) - parser.add_argument('--pipe', help='read conntrack xml data from stdin', action='store_true') - - arg = parser.parse_args() - - if arg.type not in ('source', 'destination'): - sys.exit('Unknown NAT type!') - - if arg.pipe: - process(pipe(), arg.stats, arg.proto, arg.pipe, arg.verbose, arg.type) - elif arg.file: - process(content(arg.file), arg.stats, arg.proto, arg.pipe, arg.verbose, arg.type) - else: - try: - process(run(command(arg.type, arg.proto, arg.ipaddr)), arg.stats, arg.proto, arg.pipe, arg.verbose, arg.type) - except: - pass - -if __name__ == '__main__': - main() diff --git a/src/op_mode/show_nat_statistics.py b/src/op_mode/show_nat_statistics.py deleted file mode 100755 index be41e083b..000000000 --- a/src/op_mode/show_nat_statistics.py +++ /dev/null @@ -1,63 +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 <http://www.gnu.org/licenses/>. - -import jmespath -import json - -from argparse import ArgumentParser -from jinja2 import Template -from sys import exit -from vyos.util import cmd - -OUT_TMPL_SRC=""" -rule pkts bytes interface ----- ---- ----- --------- -{% for r in output %} -{% if r.comment %} -{% set packets = r.counter.packets %} -{% set bytes = r.counter.bytes %} -{% set interface = r.interface %} -{# remove rule comment prefix #} -{% set comment = r.comment | replace('SRC-NAT-', '') | replace('DST-NAT-', '') | replace(' tcp_udp', '') %} -{{ "%-4s" | format(comment) }} {{ "%9s" | format(packets) }} {{ "%12s" | format(bytes) }} {{ interface }} -{% endif %} -{% endfor %} -""" - -parser = ArgumentParser() -group = parser.add_mutually_exclusive_group() -group.add_argument("--source", help="Show statistics for configured source NAT rules", action="store_true") -group.add_argument("--destination", help="Show statistics for configured destination NAT rules", action="store_true") -args = parser.parse_args() - -if args.source or args.destination: - tmp = cmd('sudo nft -j list table ip vyos_nat') - tmp = json.loads(tmp) - - source = r"nftables[?rule.chain=='POSTROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }" - destination = r"nftables[?rule.chain=='PREROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }" - data = { - 'output' : jmespath.search(source if args.source else destination, tmp), - 'direction' : 'source' if args.source else 'destination' - } - - tmpl = Template(OUT_TMPL_SRC, lstrip_blocks=True) - print(tmpl.render(data)) - exit(0) -else: - parser.print_help() - exit(1) - diff --git a/src/op_mode/show_nat_translations.py b/src/op_mode/show_nat_translations.py deleted file mode 100755 index 508845e23..000000000 --- a/src/op_mode/show_nat_translations.py +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020-2022 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/>. - -''' -show nat translations -''' - -import os -import sys -import ipaddress -import argparse -import xmltodict - -from vyos.util import popen -from vyos.util import DEVNULL - -conntrack = '/usr/sbin/conntrack' - -verbose_format = "%-20s %-18s %-20s %-18s" -normal_format = "%-20s %-20s %-4s %-8s %s" - - -def headers(verbose, pipe): - if verbose: - return verbose_format % ('Pre-NAT src', 'Pre-NAT dst', 'Post-NAT src', 'Post-NAT dst') - return normal_format % ('Pre-NAT', 'Post-NAT', 'Prot', 'Timeout', 'Type' if pipe else '') - - -def command(srcdest, proto, ipaddr): - command = f'{conntrack} -o xml -L' - - if proto: - command += f' -p {proto}' - - if srcdest == 'source': - command += ' -n' - if ipaddr: - command += f' --orig-src {ipaddr}' - if srcdest == 'destination': - command += ' -g' - if ipaddr: - command += f' --orig-dst {ipaddr}' - - return command - - -def run(command): - xml, code = popen(command,stderr=DEVNULL) - if code: - sys.exit('conntrack failed') - return xml - - -def content(xmlfile): - xml = '' - with open(xmlfile,'r') as r: - xml += r.read() - return xml - - -def pipe(): - xml = '' - while True: - line = sys.stdin.readline() - xml += line - if '</conntrack>' in line: - break - - sys.stdin = open('/dev/tty') - return xml - - -def xml_to_dict(xml): - """ - Convert XML to dictionary - Return: dictionary - """ - parse = xmltodict.parse(xml) - # If only one NAT entry we must change dict T4499 - if 'meta' in parse['conntrack']['flow']: - return dict(conntrack={'flow': [parse['conntrack']['flow']]}) - return parse - - -def process(data, stats, protocol, pipe, verbose, flowtype=''): - if not data: - return - - parsed = xml_to_dict(data) - - print(headers(verbose, pipe)) - - # to help the linter to detect typos - ORIGINAL = 'original' - REPLY = 'reply' - INDEPENDANT = 'independent' - SPORT = 'sport' - DPORT = 'dport' - SRC = 'src' - DST = 'dst' - - for rule in parsed['conntrack']['flow']: - src, dst, sport, dport, proto = {}, {}, {}, {}, {} - packet_count, byte_count = {}, {} - timeout, use = 0, 0 - - rule_type = rule.get('type', '') - - for meta in rule['meta']: - # print(meta) - direction = meta['@direction'] - - if direction in (ORIGINAL, REPLY): - if 'layer3' in meta: - l3 = meta['layer3'] - src[direction] = l3[SRC] - dst[direction] = l3[DST] - - if 'layer4' in meta: - l4 = meta['layer4'] - sp = l4.get(SPORT, '') - dp = l4.get(DPORT, '') - if sp: - sport[direction] = sp - if dp: - dport[direction] = dp - proto[direction] = l4.get('@protoname','') - - if stats and 'counters' in meta: - packet_count[direction] = meta['packets'] - byte_count[direction] = meta['bytes'] - continue - - if direction == INDEPENDANT: - timeout = meta['timeout'] - use = meta['use'] - continue - - in_src = '%s:%s' % (src[ORIGINAL], sport[ORIGINAL]) if ORIGINAL in sport else src[ORIGINAL] - in_dst = '%s:%s' % (dst[ORIGINAL], dport[ORIGINAL]) if ORIGINAL in dport else dst[ORIGINAL] - - # inverted the the perl code !!? - out_dst = '%s:%s' % (dst[REPLY], dport[REPLY]) if REPLY in dport else dst[REPLY] - out_src = '%s:%s' % (src[REPLY], sport[REPLY]) if REPLY in sport else src[REPLY] - - if flowtype == 'source': - v = ORIGINAL in sport and REPLY in dport - f = '%s:%s' % (src[ORIGINAL], sport[ORIGINAL]) if v else src[ORIGINAL] - t = '%s:%s' % (dst[REPLY], dport[REPLY]) if v else dst[REPLY] - else: - v = ORIGINAL in dport and REPLY in sport - f = '%s:%s' % (dst[ORIGINAL], dport[ORIGINAL]) if v else dst[ORIGINAL] - t = '%s:%s' % (src[REPLY], sport[REPLY]) if v else src[REPLY] - - # Thomas: I do not believe proto should be an option - p = proto.get('original', '') - if protocol and p != protocol: - continue - - if verbose: - msg = verbose_format % (in_src, in_dst, out_dst, out_src) - p = f'{p}: ' if p else '' - msg += f'\n {p}{f} ==> {t}' - msg += f' timeout: {timeout}' if timeout else '' - msg += f' use: {use} ' if use else '' - msg += f' type: {rule_type}' if rule_type else '' - print(msg) - else: - print(normal_format % (f, t, p, timeout, rule_type if rule_type else '')) - - if stats: - for direction in ('original', 'reply'): - if direction in packet_count: - print(' %-8s: packets %s, bytes %s' % direction, packet_count[direction], byte_count[direction]) - - -def main(): - parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__) - parser.add_argument('--verbose', help='provide more details about the flows', action='store_true') - parser.add_argument('--proto', help='filter by protocol', default='', type=str) - parser.add_argument('--file', help='read the conntrack xml from a file', type=str) - parser.add_argument('--stats', help='add usage statistics', action='store_true') - parser.add_argument('--type', help='NAT type (source, destination)', required=True, type=str) - parser.add_argument('--ipaddr', help='source ip address to filter on', type=ipaddress.ip_address) - parser.add_argument('--pipe', help='read conntrack xml data from stdin', action='store_true') - - arg = parser.parse_args() - - if arg.type not in ('source', 'destination'): - sys.exit('Unknown NAT type!') - - if arg.pipe: - process(pipe(), arg.stats, arg.proto, arg.pipe, arg.verbose, arg.type) - elif arg.file: - process(content(arg.file), arg.stats, arg.proto, arg.pipe, arg.verbose, arg.type) - else: - try: - process(run(command(arg.type, arg.proto, arg.ipaddr)), arg.stats, arg.proto, arg.pipe, arg.verbose, arg.type) - except: - pass - -if __name__ == '__main__': - main() diff --git a/src/op_mode/show_ntp.sh b/src/op_mode/show_ntp.sh index e9dd6c5c9..85f8eda15 100755 --- a/src/op_mode/show_ntp.sh +++ b/src/op_mode/show_ntp.sh @@ -1,39 +1,34 @@ #!/bin/sh -basic=0 -info=0 +sourcestats=0 +tracking=0 while [[ "$#" -gt 0 ]]; do case $1 in - --info) info=1 ;; - --basic) basic=1 ;; - --server) server=$2; shift ;; + --sourcestats) sourcestats=1 ;; + --tracking) tracking=1 ;; *) echo "Unknown parameter passed: $1" ;; esac shift done -if ! ps -C ntpd &>/dev/null; then +if ! ps -C chronyd &>/dev/null; then echo NTP daemon disabled exit 1 fi -PID=$(pgrep ntpd) -VRF_NAME=$(ip vrf identify ${PID}) +PID=$(pgrep chronyd | head -n1) +VRF_NAME=$(ip vrf identify ) if [ ! -z ${VRF_NAME} ]; then VRF_CMD="sudo ip vrf exec ${VRF_NAME}" fi -if [ $basic -eq 1 ]; then - $VRF_CMD ntpq -n -c peers -elif [ $info -eq 1 ]; then - echo "=== sysingo ===" - $VRF_CMD ntpq -n -c sysinfo - echo - echo "=== kerninfo ===" - $VRF_CMD ntpq -n -c kerninfo -elif [ ! -z $server ]; then - $VRF_CMD /usr/sbin/ntpdate -q $server +if [ $sourcestats -eq 1 ]; then + $VRF_CMD chronyc sourcestats -v +elif [ $tracking -eq 1 ]; then + $VRF_CMD chronyc tracking -v +else + echo "Unknown option" fi diff --git a/src/op_mode/zone.py b/src/op_mode/zone.py new file mode 100755 index 000000000..f326215b1 --- /dev/null +++ b/src/op_mode/zone.py @@ -0,0 +1,215 @@ +#!/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 typing +import sys +import vyos.opmode + +import tabulate +from vyos.configquery import ConfigTreeQuery +from vyos.util import dict_search_args +from vyos.util import dict_search + + +def get_config_zone(conf, name=None): + config_path = ['firewall', 'zone'] + if name: + config_path += [name] + + zone_policy = conf.get_config_dict(config_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + return zone_policy + + +def _convert_one_zone_data(zone: str, zone_config: dict) -> dict: + """ + Convert config dictionary of one zone to API dictionary + :param zone: Zone name + :type zone: str + :param zone_config: config dictionary + :type zone_config: dict + :return: AP dictionary + :rtype: dict + """ + list_of_rules = [] + intrazone_dict = {} + if dict_search('from', zone_config): + for from_zone, from_zone_config in zone_config['from'].items(): + from_zone_dict = {'name': from_zone} + if dict_search('firewall.name', from_zone_config): + from_zone_dict['firewall'] = dict_search('firewall.name', + from_zone_config) + if dict_search('firewall.ipv6_name', from_zone_config): + from_zone_dict['firewall_v6'] = dict_search( + 'firewall.ipv6_name', from_zone_config) + list_of_rules.append(from_zone_dict) + + zone_dict = { + 'name': zone, + 'interface': dict_search('interface', zone_config), + 'type': 'LOCAL' if dict_search('local_zone', + zone_config) is not None else None, + } + if list_of_rules: + zone_dict['from'] = list_of_rules + if dict_search('intra_zone_filtering.firewall.name', zone_config): + intrazone_dict['firewall'] = dict_search( + 'intra_zone_filtering.firewall.name', zone_config) + if dict_search('intra_zone_filtering.firewall.ipv6_name', zone_config): + intrazone_dict['firewall_v6'] = dict_search( + 'intra_zone_filtering.firewall.ipv6_name', zone_config) + if intrazone_dict: + zone_dict['intrazone'] = intrazone_dict + return zone_dict + + +def _convert_zones_data(zone_policies: dict) -> list: + """ + Convert all config dictionary to API list of zone dictionaries + :param zone_policies: config dictionary + :type zone_policies: dict + :return: API list + :rtype: list + """ + zone_list = [] + for zone, zone_config in zone_policies.items(): + zone_list.append(_convert_one_zone_data(zone, zone_config)) + return zone_list + + +def _convert_config(zones_config: dict, zone: str = None) -> list: + """ + convert config to API list + :param zones_config: zones config + :type zones_config: + :param zone: zone name + :type zone: str + :return: API list + :rtype: list + """ + if zone: + if zones_config: + output = [_convert_one_zone_data(zone, zones_config)] + else: + raise vyos.opmode.DataUnavailable(f'Zone {zone} not found') + else: + if zones_config: + output = _convert_zones_data(zones_config) + else: + raise vyos.opmode.UnconfiguredSubsystem( + 'Zone entries are not configured') + return output + + +def output_zone_list(zone_conf: dict) -> list: + """ + Format one zone row + :param zone_conf: zone config + :type zone_conf: dict + :return: formatted list of zones + :rtype: list + """ + zone_info = [zone_conf['name']] + if zone_conf['type'] == 'LOCAL': + zone_info.append('LOCAL') + else: + zone_info.append("\n".join(zone_conf['interface'])) + + from_zone = [] + firewall = [] + firewall_v6 = [] + if 'intrazone' in zone_conf: + from_zone.append(zone_conf['name']) + + v4_name = dict_search_args(zone_conf['intrazone'], 'firewall') + v6_name = dict_search_args(zone_conf['intrazone'], 'firewall_v6') + if v4_name: + firewall.append(v4_name) + else: + firewall.append('') + if v6_name: + firewall_v6.append(v6_name) + else: + firewall_v6.append('') + + if 'from' in zone_conf: + for from_conf in zone_conf['from']: + from_zone.append(from_conf['name']) + + v4_name = dict_search_args(from_conf, 'firewall') + v6_name = dict_search_args(from_conf, 'firewall_v6') + if v4_name: + firewall.append(v4_name) + else: + firewall.append('') + if v6_name: + firewall_v6.append(v6_name) + else: + firewall_v6.append('') + + zone_info.append("\n".join(from_zone)) + zone_info.append("\n".join(firewall)) + zone_info.append("\n".join(firewall_v6)) + return zone_info + + +def get_formatted_output(zone_policy: list) -> str: + """ + Formatted output of all zones + :param zone_policy: list of zones + :type zone_policy: list + :return: formatted table with zones + :rtype: str + """ + headers = ["Zone", + "Interfaces", + "From Zone", + "Firewall IPv4", + "Firewall IPv6" + ] + formatted_list = [] + for zone_conf in zone_policy: + formatted_list.append(output_zone_list(zone_conf)) + tabulate.PRESERVE_WHITESPACE = True + output = tabulate.tabulate(formatted_list, headers, numalign="left") + return output + + +def show(raw: bool, zone: typing.Optional[str]): + """ + Show zone-policy command + :param raw: if API + :type raw: bool + :param zone: zone name + :type zone: str + """ + conf: ConfigTreeQuery = ConfigTreeQuery() + zones_config: dict = get_config_zone(conf, zone) + zone_policy_api: list = _convert_config(zones_config, zone) + if raw: + return zone_policy_api + else: + return get_formatted_output(zone_policy_api) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/zone_policy.py b/src/op_mode/zone_policy.py deleted file mode 100755 index 7b43018c2..000000000 --- a/src/op_mode/zone_policy.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python3 -# -# 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/>. - -import argparse -import tabulate - -from vyos.config import Config -from vyos.util import dict_search_args - -def get_config_zone(conf, name=None): - config_path = ['zone-policy'] - if name: - config_path += ['zone', name] - - zone_policy = conf.get_config_dict(config_path, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - return zone_policy - -def output_zone_name(zone, zone_conf): - print(f'\n---------------------------------\nZone: "{zone}"\n') - - interfaces = ', '.join(zone_conf['interface']) if 'interface' in zone_conf else '' - if 'local_zone' in zone_conf: - interfaces = 'LOCAL' - - print(f'Interfaces: {interfaces}\n') - - header = ['From Zone', 'Firewall'] - rows = [] - - if 'from' in zone_conf: - for from_name, from_conf in zone_conf['from'].items(): - row = [from_name] - v4_name = dict_search_args(from_conf, 'firewall', 'name') - v6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name') - - if v4_name: - rows.append(row + [v4_name]) - - if v6_name: - rows.append(row + [f'{v6_name} [IPv6]']) - - if rows: - print('From Zones:\n') - print(tabulate.tabulate(rows, header)) - -def show_zone_policy(zone): - conf = Config() - zone_policy = get_config_zone(conf, zone) - - if not zone_policy: - return - - if 'zone' in zone_policy: - for zone, zone_conf in zone_policy['zone'].items(): - output_zone_name(zone, zone_conf) - elif zone: - output_zone_name(zone, zone_policy) - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument('--action', help='Action', required=False) - parser.add_argument('--name', help='Zone name', required=False, action='store', nargs='?', default='') - - args = parser.parse_args() - - if args.action == 'show': - show_zone_policy(args.name) diff --git a/src/services/api/graphql/libs/op_mode.py b/src/services/api/graphql/libs/op_mode.py index 211f8ce19..c1eb493db 100644 --- a/src/services/api/graphql/libs/op_mode.py +++ b/src/services/api/graphql/libs/op_mode.py @@ -30,7 +30,7 @@ def load_op_mode_as_module(name: str): return load_as_module(name, path) def is_op_mode_function_name(name): - if re.match(r"^(show|clear|reset|restart)", name): + if re.match(r"^(show|clear|reset|restart|add|delete)", name): return True return False diff --git a/src/services/api/graphql/session/errors/op_mode_errors.py b/src/services/api/graphql/session/errors/op_mode_errors.py index 7bc1d1d81..4029fd0a1 100644 --- a/src/services/api/graphql/session/errors/op_mode_errors.py +++ b/src/services/api/graphql/session/errors/op_mode_errors.py @@ -4,12 +4,14 @@ op_mode_err_msg = { "UnconfiguredSubsystem": "subsystem is not configured or not running", "DataUnavailable": "data currently unavailable", "PermissionDenied": "client does not have permission", - "IncorrectValue": "argument value is incorrect" + "IncorrectValue": "argument value is incorrect", + "UnsupportedOperation": "operation is not supported (yet)", } op_mode_err_code = { "UnconfiguredSubsystem": 2000, "DataUnavailable": 2001, "PermissionDenied": 1003, - "IncorrectValue": 1002 + "IncorrectValue": 1002, + "UnsupportedOperation": 1004, } diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index 60ea9a5ee..f59e089ae 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -175,6 +175,19 @@ class ImageModel(ApiModel): } } +class ContainerImageModel(ApiModel): + op: StrictStr + name: StrictStr = None + + class Config: + schema_extra = { + "example": { + "key": "id_key", + "op": "add | delete | show", + "name": "imagename", + } + } + class GenerateModel(ApiModel): op: StrictStr path: List[StrictStr] @@ -389,7 +402,7 @@ class MultipartRoute(APIRoute): if endpoint in ('/retrieve','/generate','/show','/reset'): if request.ERR_NO_OP or request.ERR_NO_PATH: return error(400, "Missing required field. \"op\" and \"path\" fields are required") - if endpoint in ('/config-file', '/image'): + if endpoint in ('/config-file', '/image', '/container-image'): if request.ERR_NO_OP: return error(400, "Missing required field \"op\"") @@ -581,6 +594,37 @@ def image_op(data: ImageModel): return success(res) +@app.post('/container-image') +def image_op(data: ContainerImageModel): + session = app.state.vyos_session + + op = data.op + + try: + if op == 'add': + if data.name: + name = data.name + else: + return error(400, "Missing required field \"name\"") + res = session.add_container_image(name) + elif op == 'delete': + if data.name: + name = data.name + else: + return error(400, "Missing required field \"name\"") + res = session.delete_container_image(name) + elif op == 'show': + res = session.show_container_image() + else: + return error(400, "\"{0}\" is not a valid operation".format(op)) + except ConfigSessionError as e: + return error(400, str(e)) + except Exception as e: + logger.critical(traceback.format_exc()) + return error(500, "An internal error occured. Check the logs for details.") + + return success(res) + @app.post('/generate') def generate_op(data: GenerateModel): session = app.state.vyos_session |