summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/reviewers.yml7
-rw-r--r--.github/workflows/auto-author-assign.yml6
-rw-r--r--.github/workflows/codeql.yml74
-rw-r--r--Makefile5
-rw-r--r--README.md7
-rw-r--r--data/config-mode-dependencies.json12
-rw-r--r--data/configd-include.json1
-rw-r--r--data/op-mode-standardized.json12
-rw-r--r--data/templates/accel-ppp/l2tp.config.j217
-rw-r--r--data/templates/chrony/chrony.conf.j258
-rw-r--r--data/templates/chrony/override.conf.j2 (renamed from data/templates/ntp/override.conf.j2)5
-rw-r--r--data/templates/container/storage.conf.j22
-rw-r--r--data/templates/container/systemd-unit.j217
-rw-r--r--data/templates/dhcp-server/dhcpd.conf.j24
-rw-r--r--data/templates/dns-forwarding/recursor.conf.j23
-rw-r--r--data/templates/firewall/nftables-defines.j221
-rw-r--r--data/templates/firewall/nftables-nat.j222
-rw-r--r--data/templates/firewall/nftables-policy.j231
-rw-r--r--data/templates/firewall/nftables-static-nat.j218
-rw-r--r--data/templates/firewall/nftables.j214
-rw-r--r--data/templates/frr/bgpd.frr.j216
-rw-r--r--data/templates/frr/ospfd.frr.j216
-rw-r--r--data/templates/frr/policy.frr.j23
-rw-r--r--data/templates/high-availability/keepalived.conf.j250
-rw-r--r--data/templates/https/nginx.default.j22
-rw-r--r--data/templates/iproute2/static.conf.j28
-rw-r--r--data/templates/iproute2/vrf.conf.j2 (renamed from data/templates/vrf/vrf.conf.j2)0
-rw-r--r--data/templates/ipsec/charon/eap-radius.conf.j24
-rw-r--r--data/templates/ipsec/swanctl.conf.j234
-rw-r--r--data/templates/ipsec/swanctl/peer.j26
-rw-r--r--data/templates/ntp/ntpd.conf.j249
-rw-r--r--data/templates/ocserv/ocserv_config.j24
-rw-r--r--data/templates/ocserv/radius_conf.j236
-rw-r--r--data/templates/pppoe/peer.j27
-rw-r--r--data/templates/protocols/systemd_vyos_failover_service.j211
-rw-r--r--data/templates/router-advert/radvd.conf.j27
-rw-r--r--data/templates/snmp/etc.snmpd.conf.j235
-rw-r--r--data/templates/snmp/override.conf.j23
-rw-r--r--data/templates/squid/sg_acl.conf.j21
-rw-r--r--data/templates/squid/squid.conf.j27
-rw-r--r--data/templates/squid/squidGuard.conf.j2122
-rw-r--r--data/templates/ssh/sshd_config.j25
-rw-r--r--data/templates/sstp-client/peer.j253
-rw-r--r--data/templates/system/ssh_config.j23
-rw-r--r--data/templates/telegraf/telegraf.j22
-rw-r--r--debian/control12
-rwxr-xr-xdebian/vyos-1x-smoketest.postinst10
-rw-r--r--debian/vyos-1x.install3
-rw-r--r--debian/vyos-1x.postinst17
-rw-r--r--interface-definitions/container.xml.in55
-rw-r--r--interface-definitions/dhcp-server.xml.in13
-rw-r--r--interface-definitions/dns-domain-name.xml.in2
-rw-r--r--interface-definitions/dns-dynamic.xml.in14
-rw-r--r--interface-definitions/dns-forwarding.xml.in4
-rw-r--r--interface-definitions/firewall.xml.in62
-rw-r--r--interface-definitions/high-availability.xml.in54
-rw-r--r--interface-definitions/https.xml.in55
-rw-r--r--interface-definitions/include/accel-ppp/ppp-options-ipv6-interface-id.xml.i54
-rw-r--r--interface-definitions/include/bgp/afi-rd.xml.i2
-rw-r--r--interface-definitions/include/bgp/neighbor-local-role.xml.i42
-rw-r--r--interface-definitions/include/bgp/neighbor-update-source.xml.i2
-rw-r--r--interface-definitions/include/bgp/protocol-common-config.xml.i10
-rw-r--r--interface-definitions/include/certificate-ca.xml.i2
-rw-r--r--interface-definitions/include/certificate-key.xml.i2
-rw-r--r--interface-definitions/include/certificate.xml.i2
-rw-r--r--interface-definitions/include/constraint/interface-name.xml.in4
-rw-r--r--interface-definitions/include/dhcp-interface-multi.xml.i18
-rw-r--r--interface-definitions/include/dhcp-interface.xml.i2
-rw-r--r--interface-definitions/include/firewall/address-mask-ipv6.xml.i14
-rw-r--r--interface-definitions/include/firewall/address-mask.xml.i14
-rw-r--r--interface-definitions/include/firewall/common-rule.xml.i51
-rw-r--r--interface-definitions/include/firewall/connection-mark.xml.i15
-rw-r--r--interface-definitions/include/firewall/fqdn.xml.i14
-rw-r--r--interface-definitions/include/firewall/fwmark.xml.i14
-rw-r--r--interface-definitions/include/firewall/icmpv6-type-name.xml.i16
-rw-r--r--interface-definitions/include/firewall/mac-address.xml.i19
-rw-r--r--interface-definitions/include/firewall/match-interface.xml.i18
-rw-r--r--interface-definitions/include/firewall/rule-log-level.xml.i2
-rw-r--r--interface-definitions/include/firewall/source-destination-group-ipv6.xml.i8
-rw-r--r--interface-definitions/include/generic-description.xml.i4
-rw-r--r--interface-definitions/include/generic-interface-broadcast.xml.i2
-rw-r--r--interface-definitions/include/generic-interface-multi-broadcast.xml.i2
-rw-r--r--interface-definitions/include/generic-interface-multi.xml.i2
-rw-r--r--interface-definitions/include/generic-interface.xml.i2
-rw-r--r--interface-definitions/include/interface/description.xml.i11
-rw-r--r--interface-definitions/include/interface/interface-policy-vif-c.xml.i26
-rw-r--r--interface-definitions/include/interface/interface-policy-vif.xml.i26
-rw-r--r--interface-definitions/include/interface/interface-policy.xml.i26
-rw-r--r--interface-definitions/include/interface/mirror.xml.i18
-rw-r--r--interface-definitions/include/interface/no-peer-dns.xml.i8
-rw-r--r--interface-definitions/include/interface/redirect.xml.i6
-rw-r--r--interface-definitions/include/interface/vif-s.xml.i6
-rw-r--r--interface-definitions/include/interface/vif.xml.i3
-rw-r--r--interface-definitions/include/listen-address-ipv4-single.xml.i17
-rw-r--r--interface-definitions/include/listen-address-single.xml.i1
-rw-r--r--interface-definitions/include/nat-rule.xml.i2
-rw-r--r--interface-definitions/include/ospf/protocol-common-config.xml.i2
-rw-r--r--interface-definitions/include/ospfv3/protocol-common-config.xml.i2
-rw-r--r--interface-definitions/include/policy/route-common-rule-ipv6.xml.i557
-rw-r--r--interface-definitions/include/policy/route-common.xml.i (renamed from interface-definitions/include/policy/route-common-rule.xml.i)766
-rw-r--r--interface-definitions/include/policy/route-ipv4.xml.i45
-rw-r--r--interface-definitions/include/policy/route-ipv6.xml.i196
-rw-r--r--interface-definitions/include/port-number-start-zero.xml.i15
-rw-r--r--interface-definitions/include/qos/bandwidth-auto.xml.i47
-rw-r--r--interface-definitions/include/qos/bandwidth.xml.i32
-rw-r--r--interface-definitions/include/qos/class-match-ipv4-address.xml.i19
-rw-r--r--interface-definitions/include/qos/class-match-ipv6-address.xml.i14
-rw-r--r--interface-definitions/include/qos/class-match.xml.i (renamed from interface-definitions/include/qos/match.xml.i)62
-rw-r--r--interface-definitions/include/qos/class-police-exceed.xml.i (renamed from interface-definitions/include/qos/limiter-actions.xml.i)14
-rw-r--r--interface-definitions/include/qos/class-priority.xml.i15
-rw-r--r--interface-definitions/include/qos/match-dscp.xml.i (renamed from interface-definitions/include/qos/dscp.xml.i)3
-rw-r--r--interface-definitions/include/qos/max-length.xml.i8
-rw-r--r--interface-definitions/include/qos/queue-type.xml.i17
-rw-r--r--interface-definitions/include/qos/set-dscp.xml.i84
-rw-r--r--interface-definitions/include/radius-acct-server-ipv4.xml.i26
-rw-r--r--interface-definitions/include/radius-auth-server-ipv4.xml.i (renamed from interface-definitions/include/radius-server-ipv4.xml.i)4
-rw-r--r--interface-definitions/include/radius-server-acct-port.xml.i15
-rw-r--r--interface-definitions/include/radius-server-auth-port.xml.i (renamed from interface-definitions/include/radius-server-port.xml.i)2
-rw-r--r--interface-definitions/include/radius-server-ipv4-ipv6.xml.i2
-rw-r--r--interface-definitions/include/radius-timeout.xml.i16
-rw-r--r--interface-definitions/include/rip/interface.xml.i2
-rw-r--r--interface-definitions/include/routing-passive-interface.xml.i2
-rw-r--r--interface-definitions/include/server-ipv4-fqdn.xml.i15
-rw-r--r--interface-definitions/include/source-interface.xml.i2
-rw-r--r--interface-definitions/include/static/static-route-interface.xml.i2
-rw-r--r--interface-definitions/include/static/static-route.xml.i3
-rw-r--r--interface-definitions/include/static/static-route6.xml.i3
-rw-r--r--interface-definitions/include/version/container-version.xml.i3
-rw-r--r--interface-definitions/include/version/firewall-version.xml.i2
-rw-r--r--interface-definitions/include/version/https-version.xml.i2
-rw-r--r--interface-definitions/include/version/ipsec-version.xml.i2
-rw-r--r--interface-definitions/include/version/ntp-version.xml.i2
-rw-r--r--interface-definitions/include/version/policy-version.xml.i2
-rw-r--r--interface-definitions/include/version/qos-version.xml.i2
-rw-r--r--interface-definitions/include/version/snmp-version.xml.i2
-rw-r--r--interface-definitions/include/vrrp/garp.xml.i78
-rw-r--r--interface-definitions/interfaces-bonding.xml.in7
-rw-r--r--interface-definitions/interfaces-bridge.xml.in5
-rw-r--r--interface-definitions/interfaces-dummy.xml.in22
-rw-r--r--interface-definitions/interfaces-ethernet.xml.in3
-rw-r--r--interface-definitions/interfaces-geneve.xml.in3
-rw-r--r--interface-definitions/interfaces-input.xml.in3
-rw-r--r--interface-definitions/interfaces-l2tpv3.xml.in3
-rw-r--r--interface-definitions/interfaces-loopback.xml.in2
-rw-r--r--interface-definitions/interfaces-macsec.xml.in3
-rw-r--r--interface-definitions/interfaces-openvpn.xml.in3
-rw-r--r--interface-definitions/interfaces-pppoe.xml.in23
-rw-r--r--interface-definitions/interfaces-pseudo-ethernet.xml.in3
-rw-r--r--interface-definitions/interfaces-sstpc.xml.in47
-rw-r--r--interface-definitions/interfaces-tunnel.xml.in3
-rw-r--r--interface-definitions/interfaces-virtual-ethernet.xml.in45
-rw-r--r--interface-definitions/interfaces-vti.xml.in3
-rw-r--r--interface-definitions/interfaces-vxlan.xml.in3
-rw-r--r--interface-definitions/interfaces-wireguard.xml.in3
-rw-r--r--interface-definitions/interfaces-wireless.xml.in5
-rw-r--r--interface-definitions/interfaces-wwan.xml.in3
-rw-r--r--interface-definitions/netns.xml.in2
-rw-r--r--interface-definitions/ntp.xml.in25
-rw-r--r--interface-definitions/policy-route.xml.in10
-rw-r--r--interface-definitions/policy.xml.in20
-rw-r--r--interface-definitions/protocols-failover.xml.in114
-rw-r--r--interface-definitions/protocols-rip.xml.in2
-rw-r--r--interface-definitions/protocols-ripng.xml.in2
-rw-r--r--interface-definitions/protocols-rpki.xml.in6
-rw-r--r--interface-definitions/protocols-static-arp.xml.in2
-rw-r--r--interface-definitions/protocols-static.xml.in7
-rw-r--r--interface-definitions/qos.xml.in471
-rw-r--r--interface-definitions/service-console-server.xml.in2
-rw-r--r--interface-definitions/service-ipoe-server.xml.in2
-rw-r--r--interface-definitions/service-pppoe-server.xml.in49
-rw-r--r--interface-definitions/service-router-advert.xml.in13
-rw-r--r--interface-definitions/service-upnp.xml.in4
-rw-r--r--interface-definitions/snmp.xml.in29
-rw-r--r--interface-definitions/ssh.xml.in13
-rw-r--r--interface-definitions/system-config-mgmt.xml.in58
-rw-r--r--interface-definitions/system-login.xml.in28
-rw-r--r--interface-definitions/system-option.xml.in1
-rw-r--r--interface-definitions/vpn-ipsec.xml.in78
-rw-r--r--interface-definitions/vpn-l2tp.xml.in4
-rw-r--r--interface-definitions/vpn-openconnect.xml.in40
-rw-r--r--interface-definitions/vpn-pptp.xml.in2
-rw-r--r--interface-definitions/vpn-sstp.xml.in2
-rw-r--r--interface-definitions/vrf.xml.in2
-rw-r--r--interface-definitions/xml-component-version.xml.in1
-rw-r--r--mibs/IANA-ADDRESS-FAMILY-NUMBERS-MIB.txt166
-rw-r--r--mibs/IANA-LANGUAGE-MIB.txt126
-rw-r--r--mibs/IANA-RTPROTO-MIB.txt95
-rw-r--r--mibs/IANAifType-MIB.txt646
-rw-r--r--op-mode-definitions/connect.xml.in1
-rw-r--r--op-mode-definitions/container.xml.in6
-rw-r--r--op-mode-definitions/date.xml.in22
-rw-r--r--op-mode-definitions/dhcp.xml.in30
-rw-r--r--op-mode-definitions/disconnect.xml.in1
-rw-r--r--op-mode-definitions/generate-interfaces-debug-archive.xml.in20
-rw-r--r--op-mode-definitions/generate-ipsec-debug-archive.xml.in2
-rw-r--r--op-mode-definitions/generate-openvpn-config-client.xml.in2
-rwxr-xr-xop-mode-definitions/generate-system-login-user.xml.in90
-rw-r--r--op-mode-definitions/generate-wireguard.xml.in20
-rw-r--r--op-mode-definitions/include/show-route-summary.xml.i8
-rw-r--r--op-mode-definitions/ipv6-route.xml.in2
-rw-r--r--op-mode-definitions/lldp.xml.in10
-rw-r--r--op-mode-definitions/monitor-log.xml.in63
-rw-r--r--op-mode-definitions/nat.xml.in30
-rw-r--r--op-mode-definitions/nat66.xml.in22
-rw-r--r--op-mode-definitions/openvpn.xml.in19
-rw-r--r--op-mode-definitions/show-acceleration.xml.in10
-rw-r--r--op-mode-definitions/show-arp.xml.in2
-rw-r--r--op-mode-definitions/show-bridge.xml.in2
-rw-r--r--op-mode-definitions/show-interfaces-bonding.xml.in12
-rw-r--r--op-mode-definitions/show-interfaces-bridge.xml.in8
-rw-r--r--op-mode-definitions/show-interfaces-dummy.xml.in8
-rw-r--r--op-mode-definitions/show-interfaces-ethernet.xml.in12
-rw-r--r--op-mode-definitions/show-interfaces-geneve.xml.in8
-rw-r--r--op-mode-definitions/show-interfaces-input.xml.in8
-rw-r--r--op-mode-definitions/show-interfaces-l2tpv3.xml.in8
-rw-r--r--op-mode-definitions/show-interfaces-loopback.xml.in12
-rw-r--r--op-mode-definitions/show-interfaces-pppoe.xml.in6
-rw-r--r--op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in8
-rw-r--r--op-mode-definitions/show-interfaces-sstpc.xml.in51
-rw-r--r--op-mode-definitions/show-interfaces-tunnel.xml.in8
-rw-r--r--op-mode-definitions/show-interfaces-virtual-ethernet.xml.in42
-rw-r--r--op-mode-definitions/show-interfaces-vti.xml.in8
-rw-r--r--op-mode-definitions/show-interfaces-vxlan.xml.in8
-rw-r--r--op-mode-definitions/show-interfaces-wireguard.xml.in8
-rw-r--r--op-mode-definitions/show-interfaces-wireless.xml.in14
-rw-r--r--op-mode-definitions/show-interfaces-wwan.xml.in6
-rw-r--r--op-mode-definitions/show-interfaces.xml.in6
-rw-r--r--op-mode-definitions/show-ip-multicast.xml.in8
-rw-r--r--op-mode-definitions/show-ip-route.xml.in22
-rw-r--r--op-mode-definitions/show-ip.xml.in2
-rw-r--r--op-mode-definitions/show-ipv6-route.xml.in22
-rw-r--r--op-mode-definitions/show-log.xml.in61
-rw-r--r--op-mode-definitions/show-ntp.xml.in17
-rw-r--r--op-mode-definitions/show-protocols.xml.in2
-rw-r--r--op-mode-definitions/show-raid.xml.in2
-rw-r--r--op-mode-definitions/show-system.xml.in36
-rw-r--r--op-mode-definitions/vpn-ipsec.xml.in6
-rw-r--r--op-mode-definitions/wireless.xml.in2
-rw-r--r--op-mode-definitions/zone-policy.xml.in6
-rw-r--r--python/setup.py5
-rw-r--r--python/vyos/accel_ppp.py74
-rw-r--r--python/vyos/base.py44
-rw-r--r--python/vyos/component_version.py192
-rw-r--r--python/vyos/component_versions.py57
-rw-r--r--python/vyos/config_mgmt.py686
-rw-r--r--python/vyos/configdep.py95
-rw-r--r--python/vyos/configdiff.py31
-rw-r--r--python/vyos/configsession.py14
-rw-r--r--python/vyos/configtree.py23
-rw-r--r--python/vyos/configverify.py6
-rw-r--r--python/vyos/cpu.py2
-rw-r--r--python/vyos/ethtool.py8
-rw-r--r--python/vyos/firewall.py134
-rw-r--r--python/vyos/formatversions.py109
-rw-r--r--python/vyos/frr.py2
-rw-r--r--python/vyos/ifconfig/__init__.py4
-rw-r--r--python/vyos/ifconfig/ethernet.py12
-rw-r--r--python/vyos/ifconfig/input.py12
-rw-r--r--python/vyos/ifconfig/macvlan.py9
-rw-r--r--python/vyos/ifconfig/sstpc.py40
-rw-r--r--python/vyos/ifconfig/veth.py54
-rw-r--r--python/vyos/migrator.py95
-rw-r--r--python/vyos/nat.py59
-rw-r--r--python/vyos/opmode.py103
-rw-r--r--python/vyos/qos/__init__.py28
-rw-r--r--python/vyos/qos/base.py282
-rw-r--r--python/vyos/qos/cake.py55
-rw-r--r--python/vyos/qos/droptail.py28
-rw-r--r--python/vyos/qos/fairqueue.py31
-rw-r--r--python/vyos/qos/fqcodel.py40
-rw-r--r--python/vyos/qos/limiter.py27
-rw-r--r--python/vyos/qos/netem.py53
-rw-r--r--python/vyos/qos/priority.py41
-rw-r--r--python/vyos/qos/randomdetect.py54
-rw-r--r--python/vyos/qos/ratelimiter.py37
-rw-r--r--python/vyos/qos/roundrobin.py44
-rw-r--r--python/vyos/qos/trafficshaper.py106
-rw-r--r--python/vyos/systemversions.py46
-rw-r--r--python/vyos/template.py12
-rw-r--r--python/vyos/util.py59
-rwxr-xr-xscripts/check-pr-title-and-commit-messages.py11
-rw-r--r--smoketest/configs/dialup-router-medium-vpn24
-rw-r--r--smoketest/configs/qos-basic38
-rwxr-xr-xsmoketest/scripts/cli/test_component_version.py6
-rwxr-xr-x[-rw-r--r--]smoketest/scripts/cli/test_container.py47
-rwxr-xr-xsmoketest/scripts/cli/test_dependency_graph.py54
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py104
-rwxr-xr-xsmoketest/scripts/cli/test_ha_vrrp.py38
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_dummy.py1
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_ethernet.py2
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_input.py52
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_pppoe.py77
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_virtual_ethernet.py (renamed from src/validators/interface-name)33
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireguard.py10
-rwxr-xr-xsmoketest/scripts/cli/test_load_balancing_wan.py (renamed from smoketest/scripts/cli/test_load_balancning_wan.py)15
-rwxr-xr-xsmoketest/scripts/cli/test_nat.py41
-rwxr-xr-xsmoketest/scripts/cli/test_nat66.py4
-rwxr-xr-xsmoketest/scripts/cli/test_pki.py22
-rwxr-xr-xsmoketest/scripts/cli/test_policy.py5
-rwxr-xr-xsmoketest/scripts/cli/test_policy_route.py79
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py128
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ospf.py19
-rwxr-xr-xsmoketest/scripts/cli/test_qos.py547
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcp-server.py23
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcpv6-relay.py28
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_forwarding.py22
-rwxr-xr-xsmoketest/scripts/cli/test_service_https.py54
-rwxr-xr-xsmoketest/scripts/cli/test_service_ntp.py (renamed from smoketest/scripts/cli/test_system_ntp.py)70
-rwxr-xr-xsmoketest/scripts/cli/test_service_router-advert.py39
-rwxr-xr-xsmoketest/scripts/cli/test_service_snmp.py22
-rwxr-xr-xsmoketest/scripts/cli/test_service_ssh.py37
-rwxr-xr-xsmoketest/scripts/cli/test_service_tftp-server.py31
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_ipsec.py72
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_openconnect.py15
-rwxr-xr-xsmoketest/scripts/system/test_module_load.py5
-rwxr-xr-xsrc/conf_mode/config_mgmt.py96
-rwxr-xr-xsrc/conf_mode/container.py194
-rwxr-xr-xsrc/conf_mode/firewall.py88
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py4
-rwxr-xr-xsrc/conf_mode/high-availability.py30
-rwxr-xr-xsrc/conf_mode/http-api.py10
-rwxr-xr-xsrc/conf_mode/https.py2
-rwxr-xr-xsrc/conf_mode/interfaces-bonding.py7
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py2
-rwxr-xr-xsrc/conf_mode/interfaces-geneve.py10
-rwxr-xr-xsrc/conf_mode/interfaces-input.py70
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py4
-rwxr-xr-xsrc/conf_mode/interfaces-pseudo-ethernet.py4
-rwxr-xr-xsrc/conf_mode/interfaces-sstpc.py145
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py4
-rwxr-xr-xsrc/conf_mode/interfaces-virtual-ethernet.py114
-rwxr-xr-xsrc/conf_mode/interfaces-vxlan.py8
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py7
-rwxr-xr-xsrc/conf_mode/nat.py85
-rwxr-xr-xsrc/conf_mode/ntp.py23
-rwxr-xr-xsrc/conf_mode/pki.py76
-rwxr-xr-xsrc/conf_mode/policy-route-interface.py132
-rwxr-xr-xsrc/conf_mode/policy-route.py109
-rwxr-xr-xsrc/conf_mode/policy.py5
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py169
-rwxr-xr-xsrc/conf_mode/protocols_failover.py121
-rwxr-xr-xsrc/conf_mode/protocols_mpls.py5
-rwxr-xr-xsrc/conf_mode/protocols_ospfv3.py4
-rwxr-xr-xsrc/conf_mode/protocols_static.py8
-rwxr-xr-xsrc/conf_mode/qos.py203
-rwxr-xr-xsrc/conf_mode/service_console-server.py2
-rwxr-xr-xsrc/conf_mode/service_monitoring_telegraf.py2
-rwxr-xr-xsrc/conf_mode/service_pppoe-server.py13
-rwxr-xr-xsrc/conf_mode/service_sla.py6
-rwxr-xr-xsrc/conf_mode/service_webproxy.py102
-rwxr-xr-xsrc/conf_mode/snmp.py6
-rwxr-xr-xsrc/conf_mode/ssh.py2
-rwxr-xr-xsrc/conf_mode/system-option.py17
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py48
-rwxr-xr-xsrc/conf_mode/vpn_l2tp.py51
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py101
-rwxr-xr-xsrc/conf_mode/vrf.py6
-rwxr-xr-xsrc/etc/commit/post-hooks.d/00vyos-sync7
-rw-r--r--src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf4
-rw-r--r--src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup4
-rwxr-xr-xsrc/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook2
-rw-r--r--src/etc/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook6
-rw-r--r--src/etc/modprobe.d/ifb.conf1
-rwxr-xr-xsrc/etc/ppp/ip-down.d/98-vyos-pppoe-cleanup-nameservers15
-rwxr-xr-xsrc/etc/ppp/ip-up.d/96-vyos-sstpc-callback49
-rwxr-xr-xsrc/etc/ppp/ip-up.d/98-vyos-pppoe-setup-nameservers24
-rw-r--r--src/etc/sysctl.d/30-vyos-router.conf3
-rwxr-xr-xsrc/helpers/system-versions-foot.py21
-rwxr-xr-xsrc/helpers/vyos-domain-group-resolve.py60
-rwxr-xr-xsrc/helpers/vyos-domain-resolver.py183
-rwxr-xr-xsrc/helpers/vyos-failover.py195
-rwxr-xr-xsrc/migration-scripts/container/0-to-177
-rwxr-xr-xsrc/migration-scripts/firewall/8-to-991
-rwxr-xr-xsrc/migration-scripts/https/3-to-453
-rwxr-xr-xsrc/migration-scripts/ipsec/10-to-1185
-rwxr-xr-xsrc/migration-scripts/ipsec/9-to-108
-rwxr-xr-xsrc/migration-scripts/ntp/1-to-272
-rwxr-xr-xsrc/migration-scripts/policy/4-to-592
-rwxr-xr-xsrc/migration-scripts/qos/1-to-2126
-rwxr-xr-xsrc/migration-scripts/snmp/2-to-357
-rwxr-xr-xsrc/op_mode/accelppp.py133
-rwxr-xr-xsrc/op_mode/bgp.py120
-rwxr-xr-xsrc/op_mode/bridge.py6
-rwxr-xr-xsrc/op_mode/config_mgmt.py85
-rwxr-xr-xsrc/op_mode/connect_disconnect.py4
-rwxr-xr-xsrc/op_mode/conntrack.py2
-rwxr-xr-xsrc/op_mode/container.py24
-rwxr-xr-xsrc/op_mode/dhcp.py291
-rwxr-xr-xsrc/op_mode/dns.py4
-rwxr-xr-xsrc/op_mode/firewall.py2
-rwxr-xr-xsrc/op_mode/generate_interfaces_debug_archive.py115
-rwxr-xr-xsrc/op_mode/generate_ipsec_debug_archive.py89
-rwxr-xr-xsrc/op_mode/generate_ipsec_debug_archive.sh36
-rwxr-xr-xsrc/op_mode/generate_system_login_user.py77
-rwxr-xr-xsrc/op_mode/igmp-proxy.py99
-rwxr-xr-xsrc/op_mode/interfaces.py412
-rwxr-xr-xsrc/op_mode/ipsec.py325
-rwxr-xr-xsrc/op_mode/lldp.py149
-rwxr-xr-xsrc/op_mode/lldp_op.py127
-rwxr-xr-xsrc/op_mode/log.py94
-rwxr-xr-xsrc/op_mode/memory.py27
-rwxr-xr-xsrc/op_mode/nat.py62
-rwxr-xr-xsrc/op_mode/openconnect.py14
-rwxr-xr-xsrc/op_mode/openvpn.py220
-rwxr-xr-xsrc/op_mode/ping.py83
-rwxr-xr-xsrc/op_mode/policy_route.py42
-rwxr-xr-xsrc/op_mode/route.py46
-rwxr-xr-xsrc/op_mode/show_acceleration.py22
-rwxr-xr-xsrc/op_mode/show_dhcp.py260
-rwxr-xr-xsrc/op_mode/show_dhcpv6.py220
-rwxr-xr-xsrc/op_mode/show_igmpproxy.py241
-rwxr-xr-xsrc/op_mode/show_ipsec_sa.py130
-rwxr-xr-xsrc/op_mode/show_nat66_statistics.py63
-rwxr-xr-xsrc/op_mode/show_nat66_translations.py204
-rwxr-xr-xsrc/op_mode/show_nat_statistics.py63
-rwxr-xr-xsrc/op_mode/show_nat_translations.py216
-rwxr-xr-xsrc/op_mode/show_ntp.sh31
-rwxr-xr-xsrc/op_mode/show_openvpn.py6
-rwxr-xr-xsrc/op_mode/show_raid.sh10
-rwxr-xr-xsrc/op_mode/storage.py18
-rwxr-xr-xsrc/op_mode/traceroute.py85
-rwxr-xr-xsrc/op_mode/vpn_ipsec.py32
-rwxr-xr-xsrc/op_mode/vrf.py6
-rwxr-xr-xsrc/op_mode/webproxy_update_blacklist.sh29
-rwxr-xr-xsrc/op_mode/zone.py215
-rwxr-xr-xsrc/op_mode/zone_policy.py81
-rw-r--r--src/services/api/graphql/__init__.py0
-rw-r--r--src/services/api/graphql/bindings.py14
-rw-r--r--src/services/api/graphql/generate/composite_function.py11
-rw-r--r--src/services/api/graphql/generate/config_session_function.py (renamed from src/services/api/graphql/utils/config_session_function.py)0
-rwxr-xr-xsrc/services/api/graphql/generate/schema_from_composite.py165
-rwxr-xr-xsrc/services/api/graphql/generate/schema_from_config_session.py (renamed from src/services/api/graphql/utils/schema_from_config_session.py)60
-rwxr-xr-xsrc/services/api/graphql/generate/schema_from_op_mode.py (renamed from src/services/api/graphql/utils/schema_from_op_mode.py)58
-rw-r--r--src/services/api/graphql/graphql/auth_token_mutation.py49
-rw-r--r--src/services/api/graphql/graphql/directives.py15
-rw-r--r--src/services/api/graphql/graphql/mutations.py71
-rw-r--r--src/services/api/graphql/graphql/queries.py72
-rw-r--r--src/services/api/graphql/graphql/schema/auth_token.graphql19
-rw-r--r--src/services/api/graphql/graphql/schema/configsession.graphql115
-rw-r--r--src/services/api/graphql/graphql/schema/schema.graphql8
-rw-r--r--src/services/api/graphql/graphql/schema/system_status.graphql18
-rw-r--r--src/services/api/graphql/libs/key_auth.py (renamed from src/services/api/graphql/key_auth.py)2
-rw-r--r--src/services/api/graphql/libs/op_mode.py (renamed from src/services/api/graphql/utils/util.py)20
-rw-r--r--src/services/api/graphql/libs/token_auth.py71
-rwxr-xr-xsrc/services/api/graphql/session/composite/system_status.py2
-rw-r--r--src/services/api/graphql/session/errors/op_mode_errors.py12
-rw-r--r--src/services/api/graphql/session/session.py5
-rwxr-xr-xsrc/services/vyos-hostsd3
-rwxr-xr-xsrc/services/vyos-http-api-server86
-rwxr-xr-xsrc/system/keepalived-fifo.py8
-rw-r--r--src/systemd/vyos-domain-group-resolve.service11
-rw-r--r--src/systemd/vyos-domain-resolver.service13
-rw-r--r--src/tests/test_op_mode.py65
-rw-r--r--src/tests/test_util.py14
-rwxr-xr-xsrc/validators/allowed-vlan19
-rwxr-xr-xsrc/validators/dotted-decimal33
-rwxr-xr-xsrc/validators/file-exists61
-rwxr-xr-xsrc/validators/fqdn29
-rwxr-xr-xsrc/validators/mac-address29
-rwxr-xr-xsrc/validators/mac-address-exclude2
-rwxr-xr-xsrc/validators/mac-address-firewall27
-rwxr-xr-xsrc/validators/tcp-flag17
-rw-r--r--src/xdp/common/common.mk2
-rw-r--r--src/xdp/common/common_user_bpf_xdp.c2
464 files changed, 14843 insertions, 5601 deletions
diff --git a/.github/reviewers.yml b/.github/reviewers.yml
index 8463681fc..a1647d20d 100644
--- a/.github/reviewers.yml
+++ b/.github/reviewers.yml
@@ -1,8 +1,3 @@
---
"**/*":
- - dmbaturin
- - UnicronNL
- - zdc
- - jestabro
- - sever-sever
- - c-po
+ - team: reviewers
diff --git a/.github/workflows/auto-author-assign.yml b/.github/workflows/auto-author-assign.yml
index 81134206b..1a7f8ef0b 100644
--- a/.github/workflows/auto-author-assign.yml
+++ b/.github/workflows/auto-author-assign.yml
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: "Assign Author to PR"
- uses: toshimaru/auto-author-assign@v1.3.5
+ uses: toshimaru/auto-author-assign@v1.6.2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Request review based on files changes and/or groups the author belongs to
- uses: shufo/auto-assign-reviewer-by-files@v1.1.1
+ uses: shufo/auto-assign-reviewer-by-files@v1.1.4
with:
- token: ${{ secrets.GITHUB_TOKEN }}
+ token: ${{ secrets.PR_ACTION_ASSIGN_REVIEWERS }}
config: .github/reviewers.yml
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 000000000..c39800ac8
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,74 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ "current", crux, equuleus ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ "current" ]
+ schedule:
+ - cron: '22 10 * * 0'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'python' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v2
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+
+ # If the Autobuild fails above, remove it and uncomment the following three lines.
+ # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
+
+ # - run: |
+ # echo "Run, Build Application using script"
+ # ./location_of_script_within_repo/buildscript.sh
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
+ with:
+ category: "/language:${{matrix.language}}"
diff --git a/Makefile b/Makefile
index 0a968563c..a4bfac17d 100644
--- a/Makefile
+++ b/Makefile
@@ -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/README.md b/README.md
index fcaad5c89..0e7daa491 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# vyos-1x: VyOS 1.2.0+ Configuration Scripts and Data
+# vyos-1x: VyOS command definitions, configuration scripts, and data
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=vyos%3Avyos-1x&metric=coverage)](https://sonarcloud.io/component_measures?id=vyos%3Avyos-1x&metric=coverage)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fvyos%2Fvyos-1x.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fvyos%2Fvyos-1x?ref=badge_shield)
@@ -36,7 +36,7 @@ src
## Interface/command definitions
Raw `node.def` files for the old backend are no longer written by hand or
-generated by custom sciprts. They are all now produced from a unified XML format
+generated by custom scripts. They are all now produced from a unified XML format
that supports a strict subset of the old backend features. In particular, it
intentionally does not support embedded shell scripts, default values, and value
"types", instead delegating those tasks to external scripts.
@@ -54,8 +54,7 @@ The guidelines in a nutshell:
generating taret config, see our
[documentation](https://docs.vyos.io/en/latest/contributing/development.html#python)
for the common structure
-* Use the `get_config_dict()` API as much as possible when retrieving values from
- the CLI
+* Use the `get_config_dict()` API as much as possible when retrieving values from the CLI
* Use a template processor when the format is more complex than just one line
(Jinja2 and pystache are acceptable options)
diff --git a/data/config-mode-dependencies.json b/data/config-mode-dependencies.json
new file mode 100644
index 000000000..9e943ba2c
--- /dev/null
+++ b/data/config-mode-dependencies.json
@@ -0,0 +1,12 @@
+{
+ "firewall": {"group_resync": ["nat", "policy-route"]},
+ "http_api": {"https": ["https"]},
+ "pki": {
+ "ethernet": ["interfaces-ethernet"],
+ "openvpn": ["interfaces-openvpn"],
+ "https": ["https"],
+ "ipsec": ["vpn_ipsec"],
+ "openconnect": ["vpn_openconnect"],
+ "sstp": ["vpn_sstp"]
+ }
+}
diff --git a/data/configd-include.json b/data/configd-include.json
index 5a4912e30..648655a8b 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -28,6 +28,7 @@
"interfaces-openvpn.py",
"interfaces-pppoe.py",
"interfaces-pseudo-ethernet.py",
+"interfaces-sstpc.py",
"interfaces-tunnel.py",
"interfaces-vti.py",
"interfaces-vxlan.py",
diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json
index 9500d3aa7..abf562984 100644
--- a/data/op-mode-standardized.json
+++ b/data/op-mode-standardized.json
@@ -1,17 +1,27 @@
[
+"accelppp.py",
+"bgp.py",
"bridge.py",
+"config_mgmt.py",
"conntrack.py",
"container.py",
"cpu.py",
+"dhcp.py",
+"dns.py",
+"interfaces.py",
+"lldp.py",
+"log.py",
"memory.py",
"nat.py",
"neighbor.py",
"openconnect.py",
+"openvpn.py",
"route.py",
"system.py",
"ipsec.py",
"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 9eeaf7622..5914fd375 100644
--- a/data/templates/accel-ppp/l2tp.config.j2
+++ b/data/templates/accel-ppp/l2tp.config.j2
@@ -88,6 +88,9 @@ verbose=1
{% for r in radius_server %}
server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }}
{% endfor %}
+{% if radius_dynamic_author.server is vyos_defined %}
+dae-server={{ radius_dynamic_author.server }}:{{ radius_dynamic_author.port }},{{ radius_dynamic_author.key }}
+{% endif %}
{% if radius_acct_inter_jitter %}
acct-interim-jitter={{ radius_acct_inter_jitter }}
{% endif %}
@@ -118,10 +121,18 @@ lcp-echo-failure={{ ppp_echo_failure }}
{% if ccp_disable %}
ccp=0
{% endif %}
-{% if client_ipv6_pool %}
-ipv6=allow
+{% if ppp_ipv6 is vyos_defined %}
+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/container/systemd-unit.j2 b/data/templates/container/systemd-unit.j2
new file mode 100644
index 000000000..fa48384ab
--- /dev/null
+++ b/data/templates/container/systemd-unit.j2
@@ -0,0 +1,17 @@
+### Autogenerated by container.py ###
+[Unit]
+Description=VyOS Container {{ name }}
+
+[Service]
+Environment=PODMAN_SYSTEMD_UNIT=%n
+Restart=on-failure
+ExecStartPre=/bin/rm -f %t/%n.pid %t/%n.cid
+ExecStart=/usr/bin/podman run \
+ --conmon-pidfile %t/%n.pid --cidfile %t/%n.cid --cgroups=no-conmon \
+ {{ run_args }}
+ExecStop=/usr/bin/podman stop --ignore --cidfile %t/%n.cid -t 5
+ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/%n.cid
+ExecStopPost=/bin/rm -f %t/%n.cid
+PIDFile=%t/%n.pid
+KillMode=none
+Type=forking
diff --git a/data/templates/dhcp-server/dhcpd.conf.j2 b/data/templates/dhcp-server/dhcpd.conf.j2
index 4c2da0aa5..639526532 100644
--- a/data/templates/dhcp-server/dhcpd.conf.j2
+++ b/data/templates/dhcp-server/dhcpd.conf.j2
@@ -22,6 +22,7 @@ ddns-update-style {{ 'interim' if dynamic_dns_update is vyos_defined else 'none'
option rfc3442-static-route code 121 = array of integer 8;
option windows-static-route code 249 = array of integer 8;
option wpad-url code 252 = text;
+option rfc8925-ipv6-only-preferred code 108 = unsigned integer 32;
# Vendor specific options - Ubiquiti Networks
option space ubnt;
@@ -127,6 +128,9 @@ shared-network {{ network }} {
{% if subnet_config.wins_server is vyos_defined %}
option netbios-name-servers {{ subnet_config.wins_server | join(', ') }};
{% endif %}
+{% if subnet_config.ipv6_only_preferred is vyos_defined %}
+ option rfc8925-ipv6-only-preferred {{ subnet_config.ipv6_only_preferred }};
+{% endif %}
{% if subnet_config.static_route is vyos_defined %}
{% set static_default_route = '' %}
{% if subnet_config.default_router is vyos_defined %}
diff --git a/data/templates/dns-forwarding/recursor.conf.j2 b/data/templates/dns-forwarding/recursor.conf.j2
index ce1b676d1..e02e6c13d 100644
--- a/data/templates/dns-forwarding/recursor.conf.j2
+++ b/data/templates/dns-forwarding/recursor.conf.j2
@@ -29,6 +29,9 @@ export-etc-hosts={{ 'no' if ignore_hosts_file is vyos_defined else 'yes' }}
# listen-address
local-address={{ listen_address | join(',') }}
+# listen-port
+local-port={{ port }}
+
# dnssec
dnssec={{ dnssec }}
diff --git a/data/templates/firewall/nftables-defines.j2 b/data/templates/firewall/nftables-defines.j2
index 5336f7ee6..0a7e79edd 100644
--- a/data/templates/firewall/nftables-defines.j2
+++ b/data/templates/firewall/nftables-defines.j2
@@ -27,6 +27,14 @@
}
{% endfor %}
{% endif %}
+{% if group.domain_group is vyos_defined %}
+{% for name, name_config in group.domain_group.items() %}
+ set D_{{ name }} {
+ type {{ ip_type }}
+ flags interval
+ }
+{% endfor %}
+{% endif %}
{% if group.mac_group is vyos_defined %}
{% for group_name, group_conf in group.mac_group.items() %}
{% set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
@@ -77,5 +85,18 @@
}
{% endfor %}
{% endif %}
+{% if group.interface_group is vyos_defined %}
+{% for group_name, group_conf in group.interface_group.items() %}
+{% set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
+ set I_{{ group_name }} {
+ type ifname
+ flags interval
+ auto-merge
+{% if group_conf.interface is vyos_defined or includes %}
+ elements = { {{ group_conf.interface | nft_nested_group(includes, group.interface_group, 'interface') | join(",") }} }
+{% endif %}
+ }
+{% endfor %}
+{% endif %}
{% endif %}
{% endmacro %}
diff --git a/data/templates/firewall/nftables-nat.j2 b/data/templates/firewall/nftables-nat.j2
index 55fe6024b..f0be3cf5d 100644
--- a/data/templates/firewall/nftables-nat.j2
+++ b/data/templates/firewall/nftables-nat.j2
@@ -1,5 +1,7 @@
#!/usr/sbin/nft -f
+{% import 'firewall/nftables-defines.j2' as group_tmpl %}
+
{% if helper_functions is vyos_defined('remove') %}
{# NAT if going to be disabled - remove rules and targets from nftables #}
{% set base_command = 'delete rule ip raw' %}
@@ -24,6 +26,7 @@ add rule ip raw NAT_CONNTRACK counter accept
{% if first_install is not vyos_defined %}
delete table ip vyos_nat
{% endif %}
+{% if deleted is not vyos_defined %}
table ip vyos_nat {
#
# Destination NAT rules build up here
@@ -31,11 +34,11 @@ table ip vyos_nat {
chain PREROUTING {
type nat hook prerouting priority -100; policy accept;
counter jump VYOS_PRE_DNAT_HOOK
-{% if destination.rule is vyos_defined %}
-{% for rule, config in destination.rule.items() if config.disable is not vyos_defined %}
+{% if destination.rule is vyos_defined %}
+{% for rule, config in destination.rule.items() if config.disable is not vyos_defined %}
{{ config | nat_rule(rule, 'destination') }}
-{% endfor %}
-{% endif %}
+{% endfor %}
+{% endif %}
}
#
@@ -44,11 +47,11 @@ table ip vyos_nat {
chain POSTROUTING {
type nat hook postrouting priority 100; policy accept;
counter jump VYOS_PRE_SNAT_HOOK
-{% if source.rule is vyos_defined %}
-{% for rule, config in source.rule.items() if config.disable is not vyos_defined %}
+{% if source.rule is vyos_defined %}
+{% for rule, config in source.rule.items() if config.disable is not vyos_defined %}
{{ config | nat_rule(rule, 'source') }}
-{% endfor %}
-{% endif %}
+{% endfor %}
+{% endif %}
}
chain VYOS_PRE_DNAT_HOOK {
@@ -58,4 +61,7 @@ table ip vyos_nat {
chain VYOS_PRE_SNAT_HOOK {
return
}
+
+{{ group_tmpl.groups(firewall_group, False) }}
}
+{% endif %}
diff --git a/data/templates/firewall/nftables-policy.j2 b/data/templates/firewall/nftables-policy.j2
index 40118930b..6cb3b2f95 100644
--- a/data/templates/firewall/nftables-policy.j2
+++ b/data/templates/firewall/nftables-policy.j2
@@ -2,21 +2,24 @@
{% import 'firewall/nftables-defines.j2' as group_tmpl %}
-{% if cleanup_commands is vyos_defined %}
-{% for command in cleanup_commands %}
-{{ command }}
-{% endfor %}
+{% if first_install is not vyos_defined %}
+delete table ip vyos_mangle
+delete table ip6 vyos_mangle
{% endif %}
-
-table ip mangle {
-{% if first_install is vyos_defined %}
+table ip vyos_mangle {
chain VYOS_PBR_PREROUTING {
type filter hook prerouting priority -150; policy accept;
+{% if route is vyos_defined %}
+{% for route_text, conf in route.items() if conf.interface is vyos_defined %}
+ iifname { {{ ",".join(conf.interface) }} } counter jump VYOS_PBR_{{ route_text }}
+{% endfor %}
+{% endif %}
}
+
chain VYOS_PBR_POSTROUTING {
type filter hook postrouting priority -150; policy accept;
}
-{% endif %}
+
{% if route is vyos_defined %}
{% for route_text, conf in route.items() %}
chain VYOS_PBR_{{ route_text }} {
@@ -32,15 +35,20 @@ table ip mangle {
{{ group_tmpl.groups(firewall_group, False) }}
}
-table ip6 mangle {
-{% if first_install is vyos_defined %}
+table ip6 vyos_mangle {
chain VYOS_PBR6_PREROUTING {
type filter hook prerouting priority -150; policy accept;
+{% if route6 is vyos_defined %}
+{% for route_text, conf in route6.items() if conf.interface is vyos_defined %}
+ iifname { {{ ",".join(conf.interface) }} } counter jump VYOS_PBR6_{{ route_text }}
+{% endfor %}
+{% endif %}
}
+
chain VYOS_PBR6_POSTROUTING {
type filter hook postrouting priority -150; policy accept;
}
-{% endif %}
+
{% if route6 is vyos_defined %}
{% for route_text, conf in route6.items() %}
chain VYOS_PBR6_{{ route_text }} {
@@ -52,5 +60,6 @@ table ip6 mangle {
}
{% endfor %}
{% endif %}
+
{{ group_tmpl.groups(firewall_group, True) }}
}
diff --git a/data/templates/firewall/nftables-static-nat.j2 b/data/templates/firewall/nftables-static-nat.j2
index 790c33ce9..e5e3da867 100644
--- a/data/templates/firewall/nftables-static-nat.j2
+++ b/data/templates/firewall/nftables-static-nat.j2
@@ -3,6 +3,7 @@
{% if first_install is not vyos_defined %}
delete table ip vyos_static_nat
{% endif %}
+{% if deleted is not vyos_defined %}
table ip vyos_static_nat {
#
# Destination NAT rules build up here
@@ -10,11 +11,11 @@ table ip vyos_static_nat {
chain PREROUTING {
type nat hook prerouting priority -100; policy accept;
-{% if static.rule is vyos_defined %}
-{% for rule, config in static.rule.items() if config.disable is not vyos_defined %}
+{% if static.rule is vyos_defined %}
+{% for rule, config in static.rule.items() if config.disable is not vyos_defined %}
{{ config | nat_static_rule(rule, 'destination') }}
-{% endfor %}
-{% endif %}
+{% endfor %}
+{% endif %}
}
#
@@ -22,10 +23,11 @@ table ip vyos_static_nat {
#
chain POSTROUTING {
type nat hook postrouting priority 100; policy accept;
-{% if static.rule is vyos_defined %}
-{% for rule, config in static.rule.items() if config.disable is not vyos_defined %}
+{% if static.rule is vyos_defined %}
+{% for rule, config in static.rule.items() if config.disable is not vyos_defined %}
{{ config | nat_static_rule(rule, 'source') }}
-{% endfor %}
-{% endif %}
+{% endfor %}
+{% endif %}
}
}
+{% endif %}
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index a0f0b8c11..2c7115134 100644
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -67,14 +67,12 @@ table ip vyos_filter {
{{ conf | nft_default_rule(name_text) }}
}
{% endfor %}
-{% if group is vyos_defined and group.domain_group is vyos_defined %}
-{% for name, name_config in group.domain_group.items() %}
- set D_{{ name }} {
+{% for set_name in ip_fqdn %}
+ set FQDN_{{ set_name }} {
type ipv4_addr
flags interval
}
-{% endfor %}
-{% endif %}
+{% endfor %}
{% for set_name in ns.sets %}
set RECENT_{{ set_name }} {
type ipv4_addr
@@ -178,6 +176,12 @@ table ip6 vyos_filter {
{{ conf | nft_default_rule(name_text, ipv6=True) }}
}
{% endfor %}
+{% for set_name in ip6_fqdn %}
+ set FQDN_{{ set_name }} {
+ type ipv6_addr
+ flags interval
+ }
+{% endfor %}
{% for set_name in ns.sets %}
set RECENT6_{{ set_name }} {
type ipv6_addr
diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2
index e8d135c78..b8692f344 100644
--- a/data/templates/frr/bgpd.frr.j2
+++ b/data/templates/frr/bgpd.frr.j2
@@ -9,6 +9,11 @@
{% if config.remote_as is vyos_defined %}
neighbor {{ neighbor }} remote-as {{ config.remote_as }}
{% endif %}
+{% if config.local_role is vyos_defined %}
+{% for role, strict in config.local_role.items() %}
+ neighbor {{ neighbor }} local-role {{ role }} {{ 'strict-mode' if strict }}
+{% endfor %}
+{% endif %}
{% if config.interface.remote_as is vyos_defined %}
neighbor {{ neighbor }} interface remote-as {{ config.interface.remote_as }}
{% endif %}
@@ -414,10 +419,14 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
route-target both {{ vni_config.route_target.both }}
{% endif %}
{% if vni_config.route_target.export is vyos_defined %}
- route-target export {{ vni_config.route_target.export }}
+{% for route_target in vni_config.route_target.export %}
+ route-target export {{ route_target }}
+{% endfor %}
{% endif %}
{% if vni_config.route_target.import is vyos_defined %}
- route-target import {{ vni_config.route_target.import }}
+{% for route_target in vni_config.route_target.import %}
+ route-target import {{ route_target }}
+{% endfor %}
{% endif %}
exit-vni
{% endfor %}
@@ -517,6 +526,9 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% if parameters.network_import_check is vyos_defined %}
bgp network import-check
{% endif %}
+{% if parameters.route_reflector_allow_outbound_policy is vyos_defined %}
+bgp route-reflector allow-outbound-policy
+{% endif %}
{% if parameters.no_client_to_client_reflection is vyos_defined %}
no bgp client-to-client reflection
{% endif %}
diff --git a/data/templates/frr/ospfd.frr.j2 b/data/templates/frr/ospfd.frr.j2
index 2a8afefbc..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 }}
@@ -161,10 +163,16 @@ router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% if parameters.abr_type is vyos_defined %}
ospf abr-type {{ parameters.abr_type }}
{% endif %}
+{% if parameters.opaque_lsa is vyos_defined %}
+ ospf opaque-lsa
+{% endif %}
+{% if parameters.rfc1583_compatibility is vyos_defined %}
+ ospf rfc1583compatibility
+{% endif %}
{% 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/frr/policy.frr.j2 b/data/templates/frr/policy.frr.j2
index 5ad4bd28c..9b5e80aed 100644
--- a/data/templates/frr/policy.frr.j2
+++ b/data/templates/frr/policy.frr.j2
@@ -322,6 +322,9 @@ route-map {{ route_map }} {{ rule_config.action }} {{ rule }}
{% if rule_config.set.ipv6_next_hop.prefer_global is vyos_defined %}
set ipv6 next-hop prefer-global
{% endif %}
+{% if rule_config.set.l3vpn_nexthop.encapsulation.gre is vyos_defined %}
+set l3vpn next-hop encapsulation gre
+{% endif %}
{% if rule_config.set.large_community.replace is vyos_defined %}
set large-community {{ rule_config.set.large_community.replace | join(' ') }}
{% endif %}
diff --git a/data/templates/high-availability/keepalived.conf.j2 b/data/templates/high-availability/keepalived.conf.j2
index 706e1c5ae..6ea5f91d0 100644
--- a/data/templates/high-availability/keepalived.conf.j2
+++ b/data/templates/high-availability/keepalived.conf.j2
@@ -2,9 +2,30 @@
# Do not edit this file, all your changes will be lost
# on next commit or reboot
+# Global definitions configuration block
global_defs {
dynamic_interfaces
script_user root
+{% if vrrp.global_parameters.startup_delay is vyos_defined %}
+ vrrp_startup_delay {{ vrrp.global_parameters.startup_delay }}
+{% endif %}
+{% if vrrp.global_parameters.garp is vyos_defined %}
+{% if vrrp.global_parameters.garp.interval is vyos_defined %}
+ vrrp_garp_interval {{ vrrp.global_parameters.garp.interval }}
+{% endif %}
+{% if vrrp.global_parameters.garp.master_delay is vyos_defined %}
+ vrrp_garp_master_delay {{ vrrp.global_parameters.garp.master_delay }}
+{% endif %}
+{% if vrrp.global_parameters.garp.master_refresh is vyos_defined %}
+ vrrp_garp_master_refresh {{ vrrp.global_parameters.garp.master_refresh }}
+{% endif %}
+{% if vrrp.global_parameters.garp.master_refresh_repeat is vyos_defined %}
+ vrrp_garp_master_refresh_repeat {{ vrrp.global_parameters.garp.master_refresh_repeat }}
+{% endif %}
+{% if vrrp.global_parameters.garp.master_repeat is vyos_defined %}
+ vrrp_garp_master_repeat {{ vrrp.global_parameters.garp.master_repeat }}
+{% endif %}
+{% endif %}
notify_fifo /run/keepalived/keepalived_notify_fifo
notify_fifo_script /usr/libexec/vyos/system/keepalived-fifo.py
}
@@ -28,6 +49,23 @@ vrrp_instance {{ name }} {
virtual_router_id {{ group_config.vrid }}
priority {{ group_config.priority }}
advert_int {{ group_config.advertise_interval }}
+{% if group_config.garp is vyos_defined %}
+{% if group_config.garp.interval is vyos_defined %}
+ garp_interval {{ group_config.garp.interval }}
+{% endif %}
+{% if group_config.garp.master_delay is vyos_defined %}
+ garp_master_delay {{ group_config.garp.master_delay }}
+{% endif %}
+{% if group_config.garp.master_repeat is vyos_defined %}
+ garp_master_repeat {{ group_config.garp.master_repeat }}
+{% endif %}
+{% if group_config.garp.master_refresh is vyos_defined %}
+ garp_master_refresh {{ group_config.garp.master_refresh }}
+{% endif %}
+{% if group_config.garp.master_refresh_repeat is vyos_defined %}
+ garp_master_refresh_repeat {{ group_config.garp.master_refresh_repeat }}
+{% endif %}
+{% endif %}
{% if group_config.track.exclude_vrrp_interface is vyos_defined %}
dont_track_primary
{% endif %}
@@ -126,7 +164,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 +199,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/charon/eap-radius.conf.j2 b/data/templates/ipsec/charon/eap-radius.conf.j2
index 8495011fe..364377473 100644
--- a/data/templates/ipsec/charon/eap-radius.conf.j2
+++ b/data/templates/ipsec/charon/eap-radius.conf.j2
@@ -49,8 +49,10 @@ eap-radius {
# Base to use for calculating exponential back off.
# retransmit_base = 1.4
+{% if remote_access.radius.timeout is vyos_defined %}
# Timeout in seconds before sending first retransmit.
- # retransmit_timeout = 2.0
+ retransmit_timeout = {{ remote_access.radius.timeout | float }}
+{% endif %}
# Number of times to retransmit a packet before giving up.
# retransmit_tries = 4
diff --git a/data/templates/ipsec/swanctl.conf.j2 b/data/templates/ipsec/swanctl.conf.j2
index 38d7981c6..d44d0f5e4 100644
--- a/data/templates/ipsec/swanctl.conf.j2
+++ b/data/templates/ipsec/swanctl.conf.j2
@@ -58,23 +58,7 @@ secrets {
{% if site_to_site.peer is vyos_defined %}
{% for peer, peer_conf in site_to_site.peer.items() if peer not in dhcp_no_address and peer_conf.disable is not vyos_defined %}
{% set peer_name = peer.replace("@", "") | dot_colon_to_dash %}
-{% if peer_conf.authentication.mode is vyos_defined('pre-shared-secret') %}
- ike_{{ peer_name }} {
-{% if peer_conf.local_address is vyos_defined %}
- id-local = {{ peer_conf.local_address }} # dhcp:{{ peer_conf.dhcp_interface if 'dhcp_interface' in peer_conf else 'no' }}
-{% endif %}
-{% for address in peer_conf.remote_address %}
- id-remote_{{ address | dot_colon_to_dash }} = {{ address }}
-{% endfor %}
-{% if peer_conf.authentication.local_id is vyos_defined %}
- id-localid = {{ peer_conf.authentication.local_id }}
-{% endif %}
-{% if peer_conf.authentication.remote_id is vyos_defined %}
- id-remoteid = {{ peer_conf.authentication.remote_id }}
-{% endif %}
- secret = "{{ peer_conf.authentication.pre_shared_secret }}"
- }
-{% elif peer_conf.authentication.mode is vyos_defined('x509') %}
+{% if peer_conf.authentication.mode is vyos_defined('x509') %}
private_{{ peer_name }} {
file = {{ peer_conf.authentication.x509.certificate }}.pem
{% if peer_conf.authentication.x509.passphrase is vyos_defined %}
@@ -91,6 +75,21 @@ secrets {
{% endif %}
{% endfor %}
{% endif %}
+{% if authentication.psk is vyos_defined %}
+{% for psk, psk_config in authentication.psk.items() %}
+ ike-{{ psk }} {
+{% if psk_config.id is vyos_defined %}
+ # ID's from auth psk <tag> id xxx
+{% for id in psk_config.id %}
+{% set gen_uuid = '' | generate_uuid4 %}
+ id-{{ gen_uuid }} = "{{ id }}"
+{% endfor %}
+{% endif %}
+ secret = "{{ psk_config.secret }}"
+ }
+{% endfor %}
+{% endif %}
+
{% if remote_access.connection is vyos_defined %}
{% for ra, ra_conf in remote_access.connection.items() if ra_conf.disable is not vyos_defined %}
{% if ra_conf.authentication.server_mode is vyos_defined('pre-shared-secret') %}
@@ -130,4 +129,3 @@ secrets {
{% endif %}
{% endif %}
}
-
diff --git a/data/templates/ipsec/swanctl/peer.j2 b/data/templates/ipsec/swanctl/peer.j2
index d097a04fc..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
@@ -124,7 +120,7 @@
{% endif %}
{% elif tunnel_esp.mode == 'transport' %}
local_ts = {{ peer_conf.local_address }}{{ local_suffix }}
- remote_ts = {{ peer }}{{ remote_suffix }}
+ remote_ts = {{ peer_conf.remote_address | join(",") }}{{ remote_suffix }}
{% endif %}
ipcomp = {{ 'yes' if tunnel_esp.compression is vyos_defined else 'no' }}
mode = {{ tunnel_esp.mode }}
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/ocserv/ocserv_config.j2 b/data/templates/ocserv/ocserv_config.j2
index 3194354e6..aa1073bca 100644
--- a/data/templates/ocserv/ocserv_config.j2
+++ b/data/templates/ocserv/ocserv_config.j2
@@ -10,6 +10,10 @@ udp-port = {{ listen_ports.udp }}
run-as-user = nobody
run-as-group = daemon
+{% if accounting.mode.radius is vyos_defined %}
+acct = "radius [config=/run/ocserv/radiusclient.conf]"
+{% endif %}
+
{% if "radius" in authentication.mode %}
auth = "radius [config=/run/ocserv/radiusclient.conf{{ ',groupconfig=true' if authentication.radius.groupconfig is vyos_defined else '' }}]"
{% elif "local" in authentication.mode %}
diff --git a/data/templates/ocserv/radius_conf.j2 b/data/templates/ocserv/radius_conf.j2
index b6612fee5..1ab322f69 100644
--- a/data/templates/ocserv/radius_conf.j2
+++ b/data/templates/ocserv/radius_conf.j2
@@ -1,20 +1,34 @@
### generated by vpn_openconnect.py ###
nas-identifier VyOS
-{% for srv in server %}
-{% if not "disable" in server[srv] %}
-{% if "port" in server[srv] %}
-authserver {{ srv }}:{{ server[srv]["port"] }}
+
+#### Accounting
+{% if accounting.mode.radius is vyos_defined %}
+{% for acctsrv, srv_conf in accounting.radius.server.items() if 'disable' not in srv_conf %}
+{% if srv_conf.port is vyos_defined %}
+acctserver {{ acctsrv }}:{{ srv_conf.port }}
{% else %}
-authserver {{ srv }}
+acctserver {{ acctsrv }}
{% endif %}
-{% endif %}
-{% endfor %}
-radius_timeout {{ timeout }}
-{% if source_address %}
-bindaddr {{ source_address }}
-{% else %}
+{% endfor %}
+{% endif %}
+
+#### Authentication
+{% if authentication.mode.radius is vyos_defined %}
+{% for authsrv, srv_conf in authentication.radius.server.items() if 'disable' not in srv_conf %}
+{% if srv_conf.port is vyos_defined %}
+authserver {{ authsrv }}:{{ srv_conf.port }}
+{% else %}
+authserver {{ authsrv }}
+{% endif %}
+{% endfor %}
+radius_timeout {{ authentication['radius']['timeout'] }}
+{% if source_address %}
+bindaddr {{ authentication['radius']['source_address'] }}
+{% else %}
bindaddr *
+{% endif %}
{% endif %}
+
servers /run/ocserv/radius_servers
dictionary /etc/radcli/dictionary
default_realm
diff --git a/data/templates/pppoe/peer.j2 b/data/templates/pppoe/peer.j2
index 6221abb9b..f433a9b03 100644
--- a/data/templates/pppoe/peer.j2
+++ b/data/templates/pppoe/peer.j2
@@ -36,10 +36,13 @@ maxfail 0
plugin rp-pppoe.so {{ source_interface }}
{% if access_concentrator is vyos_defined %}
-rp_pppoe_ac '{{ access_concentrator }}'
+pppoe-ac "{{ access_concentrator }}"
{% endif %}
{% if service_name is vyos_defined %}
-rp_pppoe_service '{{ service_name }}'
+pppoe-service "{{ service_name }}"
+{% endif %}
+{% if host_uniq is vyos_defined %}
+pppoe-host-uniq "{{ host_uniq }}"
{% endif %}
persist
diff --git a/data/templates/protocols/systemd_vyos_failover_service.j2 b/data/templates/protocols/systemd_vyos_failover_service.j2
new file mode 100644
index 000000000..e6501e0f5
--- /dev/null
+++ b/data/templates/protocols/systemd_vyos_failover_service.j2
@@ -0,0 +1,11 @@
+[Unit]
+Description=Failover route service
+After=vyos-router.service
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/bin/python3 /usr/libexec/vyos/vyos-failover.py --config /run/vyos-failover.conf
+
+[Install]
+WantedBy=multi-user.target
diff --git a/data/templates/router-advert/radvd.conf.j2 b/data/templates/router-advert/radvd.conf.j2
index a464795ad..4ef4751dd 100644
--- a/data/templates/router-advert/radvd.conf.j2
+++ b/data/templates/router-advert/radvd.conf.j2
@@ -43,6 +43,13 @@ interface {{ iface }} {
};
{% endfor %}
{% endif %}
+{% if iface_config.source_address is vyos_defined %}
+ AdvRASrcAddress {
+{% for source_address in iface_config.source_address %}
+ {{ source_address }};
+{% endfor %}
+ };
+{% endif %}
{% if iface_config.prefix is vyos_defined %}
{% for prefix, prefix_options in iface_config.prefix.items() %}
prefix {{ prefix }} {
diff --git a/data/templates/snmp/etc.snmpd.conf.j2 b/data/templates/snmp/etc.snmpd.conf.j2
index d7dc0ba5d..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 #
########################
@@ -59,27 +62,47 @@ agentaddress unix:/run/snmpd.socket{{ ',' ~ options | join(',') if options is vy
{% if comm_config.client is vyos_defined %}
{% for client in comm_config.client %}
{% if client | is_ipv4 %}
-{{ comm_config.authorization }}community {{ comm }} {{ client }}
+{{ comm_config.authorization }}community {{ comm }} {{ client }} -V RESTRICTED
{% elif client | is_ipv6 %}
-{{ comm_config.authorization }}community6 {{ comm }} {{ client }}
+{{ comm_config.authorization }}community6 {{ comm }} {{ client }} -V RESTRICTED
{% endif %}
{% endfor %}
{% endif %}
{% if comm_config.network is vyos_defined %}
{% for network in comm_config.network %}
{% if network | is_ipv4 %}
-{{ comm_config.authorization }}community {{ comm }} {{ network }}
-{% elif client | is_ipv6 %}
-{{ comm_config.authorization }}community6 {{ comm }} {{ network }}
+{{ comm_config.authorization }}community {{ comm }} {{ network }} -V RESTRICTED
+{% elif network | is_ipv6 %}
+{{ comm_config.authorization }}community6 {{ comm }} {{ network }} -V RESTRICTED
{% endif %}
{% endfor %}
{% endif %}
{% if comm_config.client is not vyos_defined and comm_config.network is not vyos_defined %}
-{{ comm_config.authorization }}community {{ comm }}
+{{ comm_config.authorization }}community {{ comm }} -V RESTRICTED
+{{ comm_config.authorization }}community6 {{ comm }} -V RESTRICTED
{% endif %}
{% endfor %}
{% endif %}
+# Default RESTRICTED view
+view RESTRICTED included .1 80
+{% if 'ip-route-table' not in oid_enable %}
+# ipRouteTable oid: excluded
+view RESTRICTED excluded .1.3.6.1.2.1.4.21
+{% endif %}
+{% if 'ip-net-to-media-table' not in oid_enable %}
+# ipNetToMediaTable oid: excluded
+view RESTRICTED excluded .1.3.6.1.2.1.4.22
+{% endif %}
+{% if 'ip-net-to-physical-phys-address' not in oid_enable %}
+# ipNetToPhysicalPhysAddress oid: excluded
+view RESTRICTED excluded .1.3.6.1.2.1.4.35
+{% endif %}
+{% if 'ip-forward' not in oid_enable %}
+# ipForward oid: excluded
+view RESTRICTED excluded .1.3.6.1.2.1.4.24
+{% endif %}
+
{% if contact is vyos_defined %}
# system contact information
SysContact {{ contact }}
diff --git a/data/templates/snmp/override.conf.j2 b/data/templates/snmp/override.conf.j2
index 5d787de86..443ee64db 100644
--- a/data/templates/snmp/override.conf.j2
+++ b/data/templates/snmp/override.conf.j2
@@ -1,5 +1,4 @@
{% set vrf_command = 'ip vrf exec ' ~ vrf ~ ' ' if vrf is vyos_defined else '' %}
-{% set oid_route_table = ' ' if oid_enable is vyos_defined('route-table') else '-I -ipCidrRouteTable,inetCidrRouteTable' %}
[Unit]
StartLimitIntervalSec=0
After=vyos-router.service
@@ -8,7 +7,7 @@ After=vyos-router.service
Environment=
Environment="MIBDIRS=/usr/share/snmp/mibs:/usr/share/snmp/mibs/iana:/usr/share/snmp/mibs/ietf:/usr/share/vyos/mibs"
ExecStart=
-ExecStart={{ vrf_command }}/usr/sbin/snmpd -LS0-5d -Lf /dev/null -u Debian-snmp -g Debian-snmp {{ oid_route_table }} -f -p /run/snmpd.pid
+ExecStart={{ vrf_command }}/usr/sbin/snmpd -LS0-5d -Lf /dev/null -u Debian-snmp -g Debian-snmp -f -p /run/snmpd.pid
Restart=always
RestartSec=10
diff --git a/data/templates/squid/sg_acl.conf.j2 b/data/templates/squid/sg_acl.conf.j2
index ce72b173a..78297a2b8 100644
--- a/data/templates/squid/sg_acl.conf.j2
+++ b/data/templates/squid/sg_acl.conf.j2
@@ -1,6 +1,5 @@
### generated by service_webproxy.py ###
dbhome {{ squidguard_db_dir }}
-
dest {{ category }}-{{ rule }} {
{% if list_type == 'domains' %}
domainlist {{ category }}/domains
diff --git a/data/templates/squid/squid.conf.j2 b/data/templates/squid/squid.conf.j2
index 5781c883f..b953c8b18 100644
--- a/data/templates/squid/squid.conf.j2
+++ b/data/templates/squid/squid.conf.j2
@@ -24,7 +24,12 @@ acl Safe_ports port {{ port }}
{% endfor %}
{% endif %}
acl CONNECT method CONNECT
-
+{% if domain_block is vyos_defined %}
+{% for domain in domain_block %}
+acl BLOCKDOMAIN dstdomain {{ domain }}
+{% endfor %}
+http_access deny BLOCKDOMAIN
+{% endif %}
{% if authentication is vyos_defined %}
{% if authentication.children is vyos_defined %}
auth_param basic children {{ authentication.children }}
diff --git a/data/templates/squid/squidGuard.conf.j2 b/data/templates/squid/squidGuard.conf.j2
index 1bc4c984f..a93f878df 100644
--- a/data/templates/squid/squidGuard.conf.j2
+++ b/data/templates/squid/squidGuard.conf.j2
@@ -1,10 +1,16 @@
### generated by service_webproxy.py ###
-{% macro sg_rule(category, log, db_dir) %}
+{% macro sg_rule(category, rule, log, db_dir) %}
+{% set domains = db_dir + '/' + category + '/domains' %}
+{% set urls = db_dir + '/' + category + '/urls' %}
{% set expressions = db_dir + '/' + category + '/expressions' %}
-dest {{ category }}-default {
+dest {{ category }}-{{ rule }}{
+{% if domains | is_file %}
domainlist {{ category }}/domains
+{% endif %}
+{% if urls | is_file %}
urllist {{ category }}/urls
+{% endif %}
{% if expressions | is_file %}
expressionlist {{ category }}/expressions
{% endif %}
@@ -17,8 +23,9 @@ dest {{ category }}-default {
{% if url_filtering is vyos_defined and url_filtering.disable is not vyos_defined %}
{% if url_filtering.squidguard is vyos_defined %}
{% set sg_config = url_filtering.squidguard %}
-{% set acl = namespace(value='local-ok-default') %}
+{% set acl = namespace(value='') %}
{% set acl.value = acl.value + ' !in-addr' if sg_config.allow_ipaddr_url is not defined else acl.value %}
+{% set ruleacls = {} %}
dbhome {{ squidguard_db_dir }}
logdir /var/log/squid
@@ -38,24 +45,28 @@ dest local-ok-default {
domainlist local-ok-default/domains
}
{% endif %}
+
{% if sg_config.local_ok_url is vyos_defined %}
{% set acl.value = acl.value + ' local-ok-url-default' %}
dest local-ok-url-default {
urllist local-ok-url-default/urls
}
{% endif %}
+
{% if sg_config.local_block is vyos_defined %}
{% set acl.value = acl.value + ' !local-block-default' %}
dest local-block-default {
domainlist local-block-default/domains
}
{% endif %}
+
{% if sg_config.local_block_url is vyos_defined %}
{% set acl.value = acl.value + ' !local-block-url-default' %}
dest local-block-url-default {
urllist local-block-url-default/urls
}
{% endif %}
+
{% if sg_config.local_block_keyword is vyos_defined %}
{% set acl.value = acl.value + ' !local-block-keyword-default' %}
dest local-block-keyword-default {
@@ -65,16 +76,100 @@ dest local-block-keyword-default {
{% if sg_config.block_category is vyos_defined %}
{% for category in sg_config.block_category %}
-{{ sg_rule(category, sg_config.log, squidguard_db_dir) }}
+{{ sg_rule(category, 'default', sg_config.log, squidguard_db_dir) }}
{% set acl.value = acl.value + ' !' + category + '-default' %}
{% endfor %}
{% endif %}
{% if sg_config.allow_category is vyos_defined %}
{% for category in sg_config.allow_category %}
-{{ sg_rule(category, False, squidguard_db_dir) }}
+{{ sg_rule(category, 'default', False, squidguard_db_dir) }}
{% set acl.value = acl.value + ' ' + category + '-default' %}
{% endfor %}
{% endif %}
+
+
+{% if sg_config.rule is vyos_defined %}
+{% for rule, rule_config in sg_config.rule.items() %}
+{% if rule_config.local_ok is vyos_defined %}
+{% if rule in ruleacls %}
+{% set _dummy = ruleacls.update({rule: ruleacls[rule] + ' local-ok-' + rule}) %}
+{% else %}
+{% set _dummy = ruleacls.update({rule:'local-ok-' + rule}) %}
+{% endif %}
+dest local-ok-{{ rule }} {
+ domainlist local-ok-{{ rule }}/domains
+}
+{% endif %}
+
+{% if rule_config.local_ok_url is vyos_defined %}
+{% if rule in ruleacls %}
+{% set _dummy = ruleacls.update({rule: ruleacls[rule] + ' local-ok-url-' + rule}) %}
+{% else %}
+{% set _dummy = ruleacls.update({rule:'local-ok-url-' + rule}) %}
+{% endif %}
+dest local-ok-url-{{ rule }} {
+ urllist local-ok-url-{{ rule }}/urls
+}
+{% endif %}
+
+{% if rule_config.local_block is vyos_defined %}
+{% if rule in ruleacls %}
+{% set _dummy = ruleacls.update({rule: ruleacls[rule] + ' !local-block-' + rule}) %}
+{% else %}
+{% set _dummy = ruleacls.update({rule:'!local-block-' + rule}) %}
+{% endif %}
+dest local-block-{{ rule }} {
+ domainlist local-block-{{ rule }}/domains
+}
+{% endif %}
+
+{% if rule_config.local_block_url is vyos_defined %}
+{% if rule in ruleacls %}
+{% set _dummy = ruleacls.update({rule: ruleacls[rule] + ' !local-block-url-' + rule}) %}
+{% else %}
+{% set _dummy = ruleacls.update({rule:'!ocal-block-url-' + rule}) %}
+{% endif %}
+dest local-block-url-{{ rule }} {
+ urllist local-block-url-{{ rule }}/urls
+}
+{% endif %}
+
+{% if rule_config.local_block_keyword is vyos_defined %}
+{% if rule in ruleacls %}
+{% set _dummy = ruleacls.update({rule: ruleacls[rule] + ' !local-block-keyword-' + rule}) %}
+{% else %}
+{% set _dummy = ruleacls.update({rule:'!local-block-keyword-' + rule}) %}
+{% endif %}
+dest local-block-keyword-{{ rule }} {
+ expressionlist local-block-keyword-{{ rule }}/expressions
+}
+{% endif %}
+
+{% if rule_config.block_category is vyos_defined %}
+{% for b_category in rule_config.block_category %}
+{% if rule in ruleacls %}
+{% set _dummy = ruleacls.update({rule: ruleacls[rule] + ' !' + b_category + '-' + rule}) %}
+{% else %}
+{% set _dummy = ruleacls.update({rule:'!' + b_category + '-' + rule}) %}
+{% endif %}
+{{ sg_rule(b_category, rule, sg_config.log, squidguard_db_dir) }}
+{% endfor %}
+{% endif %}
+
+{% if rule_config.allow_category is vyos_defined %}
+{% for a_category in rule_config.allow_category %}
+{% if rule in ruleacls %}
+{% set _dummy = ruleacls.update({rule: ruleacls[rule] + ' ' + a_category + '-' + rule}) %}
+{% else %}
+{% set _dummy = ruleacls.update({rule:a_category + '-' + rule}) %}
+{% endif %}
+{{ sg_rule(a_category, rule, sg_config.log, squidguard_db_dir) }}
+{% endfor %}
+{% endif %}
+{% endfor %}
+{% endif %}
+
+
{% if sg_config.source_group is vyos_defined %}
{% for sgroup, sg_config in sg_config.source_group.items() %}
{% if sg_config.address is vyos_defined %}
@@ -83,28 +178,15 @@ src {{ sgroup }} {
ip {{ address }}
{% endfor %}
}
-
{% endif %}
{% endfor %}
{% endif %}
-{% if sg_config.rule is vyos_defined %}
-{% for rule, rule_config in sg_config.rule.items() %}
-{% for b_category in rule_config.block_category %}
-dest {{ b_category }} {
- domainlist {{ b_category }}/domains
- urllist {{ b_category }}/urls
-}
-{% endfor %}
-{% endfor %}
-{% endif %}
acl {
{% if sg_config.rule is vyos_defined %}
{% for rule, rule_config in sg_config.rule.items() %}
{{ rule_config.source_group }} {
-{% for b_category in rule_config.block_category %}
- pass local-ok-1 !in-addr !{{ b_category }} all
-{% endfor %}
+ pass {{ ruleacls[rule] }} {{ 'none' if rule_config.default_action is vyos_defined('block') else 'any' }}
}
{% endfor %}
{% endif %}
@@ -113,7 +195,7 @@ acl {
{% if sg_config.enable_safe_search is vyos_defined %}
rewrite safesearch
{% endif %}
- pass {{ acl.value }} {{ 'none' if sg_config.default_action is vyos_defined('block') else 'allow' }}
+ pass {{ acl.value }} {{ 'none' if sg_config.default_action is vyos_defined('block') else 'any' }}
redirect 302:http://{{ sg_config.redirect_url }}
{% if sg_config.log is vyos_defined %}
log blacklist.log
diff --git a/data/templates/ssh/sshd_config.j2 b/data/templates/ssh/sshd_config.j2
index 5bbfdeb88..93735020c 100644
--- a/data/templates/ssh/sshd_config.j2
+++ b/data/templates/ssh/sshd_config.j2
@@ -62,6 +62,11 @@ ListenAddress {{ address }}
Ciphers {{ ciphers | join(',') }}
{% endif %}
+{% if hostkey_algorithm is vyos_defined %}
+# Specifies the available Host Key signature algorithms
+HostKeyAlgorithms {{ hostkey_algorithm | join(',') }}
+{% endif %}
+
{% if mac is vyos_defined %}
# Specifies the available MAC (message authentication code) algorithms
MACs {{ mac | join(',') }}
diff --git a/data/templates/sstp-client/peer.j2 b/data/templates/sstp-client/peer.j2
new file mode 100644
index 000000000..7a0b0e1f7
--- /dev/null
+++ b/data/templates/sstp-client/peer.j2
@@ -0,0 +1,53 @@
+### Autogenerated by interfaces-sstpc.py ###
+{{ '# ' ~ description if description is vyos_defined else '' }}
+
+# Require peer to provide the local IP address if it is not
+# specified explicitly in the config file.
+noipdefault
+
+# Don't show the password in logfiles:
+hide-password
+
+remotename {{ ifname }}
+linkname {{ ifname }}
+ipparam {{ ifname }}
+ifname {{ ifname }}
+pty "sstpc --ipparam {{ ifname }} --nolaunchpppd {{ server }}:{{ port }} --ca-cert {{ ca_file_path }}"
+
+# Override any connect script that may have been set in /etc/ppp/options.
+connect /bin/true
+
+# We don't need the server to auth itself
+noauth
+
+# We won't want EAP
+refuse-eap
+
+# Don't try to proxy ARP for the remote endpoint. User can set proxy
+# arp entries up manually if they wish. More importantly, having
+# the "proxyarp" parameter set disables the "defaultroute" option.
+noproxyarp
+
+# Unlimited connection attempts
+maxfail 0
+
+plugin sstp-pppd-plugin.so
+sstp-sock /var/run/sstpc/sstpc-{{ ifname }}
+
+persist
+debug
+
+# pppd should create a UUCP-style lock file for the serial device to ensure
+# exclusive access to the device. By default, pppd will not create a lock file.
+lock
+
+# Disables Deflate compression
+nodeflate
+
+{% if authentication is vyos_defined %}
+{{ 'user "' + authentication.user + '"' if authentication.user is vyos_defined }}
+{{ 'password "' + authentication.password + '"' if authentication.password is vyos_defined }}
+{% endif %}
+
+{{ "usepeerdns" if no_peer_dns is not vyos_defined }}
+
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 1f2151284..1e593d378 100644
--- a/debian/control
+++ b/debian/control
@@ -9,6 +9,7 @@ Build-Depends:
gcc-multilib [amd64],
clang [amd64],
llvm [amd64],
+ libbpf-dev [amd64],
libelf-dev (>= 0.2) [amd64],
libpcap-dev [amd64],
build-essential,
@@ -24,6 +25,7 @@ Build-Depends:
python3-setuptools,
python3-sphinx,
python3-xmltodict,
+ python3-pyhumps,
quilt,
whois
Standards-Version: 3.9.6
@@ -66,7 +68,7 @@ Depends:
ipaddrcheck,
iperf,
iperf3,
- iproute2,
+ iproute2 (>= 6.0.0),
iputils-arping,
isc-dhcp-client,
isc-dhcp-relay,
@@ -76,6 +78,7 @@ Depends:
lcdproc,
lcdproc-extra-drivers,
libatomic1,
+ libbpf0 [amd64],
libcharon-extra-plugins (>=5.9),
libcharon-extauth-plugins (>=5.9),
libndp-tools,
@@ -98,8 +101,7 @@ Depends:
nfct,
nftables (>= 0.9.3),
nginx-light,
- ntp,
- ntpdate,
+ chrony,
nvme-cli,
ocserv,
opennhrp,
@@ -129,6 +131,7 @@ Depends:
python3-netifaces,
python3-paramiko,
python3-psutil,
+ python3-pyhumps,
python3-pystache,
python3-pyudev,
python3-six,
@@ -150,8 +153,10 @@ Depends:
squidguard,
sshguard,
ssl-cert,
+ sstp-client,
strongswan (>= 5.9),
strongswan-swanctl (>= 5.9),
+ stunnel4,
sudo,
systemd,
telegraf (>= 1.20),
@@ -191,6 +196,7 @@ Description: VyOS configuration scripts and data for VMware
Package: vyos-1x-smoketest
Architecture: all
Depends:
+ skopeo,
snmp,
vyos-1x
Description: VyOS build sanity checking toolkit
diff --git a/debian/vyos-1x-smoketest.postinst b/debian/vyos-1x-smoketest.postinst
new file mode 100755
index 000000000..18612804c
--- /dev/null
+++ b/debian/vyos-1x-smoketest.postinst
@@ -0,0 +1,10 @@
+#!/bin/sh -e
+
+BUSYBOX_TAG="docker.io/library/busybox:stable"
+OUTPUT_PATH="/usr/share/vyos/busybox-stable.tar"
+
+if [[ -f $OUTPUT_PATH ]]; then
+ rm -f $OUTPUT_PATH
+fi
+
+skopeo copy --additional-tag "$BUSYBOX_TAG" "docker://$BUSYBOX_TAG" "docker-archive:/$OUTPUT_PATH"
diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install
index edd090993..6b5c7793d 100644
--- a/debian/vyos-1x.install
+++ b/debian/vyos-1x.install
@@ -1,8 +1,10 @@
+etc/commit
etc/dhcp
etc/ipsec.d
etc/logrotate.d
etc/netplug
etc/opennhrp
+etc/modprobe.d
etc/ppp
etc/rsyslog.d
etc/securetty
@@ -17,6 +19,7 @@ etc/vyos
lib/
opt/
usr/sbin
+usr/bin/config-mgmt
usr/bin/initial-setup
usr/bin/vyos-config-file-query
usr/bin/vyos-config-to-commands
diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst
index 031e91595..d5f5cbbc7 100644
--- a/debian/vyos-1x.postinst
+++ b/debian/vyos-1x.postinst
@@ -21,13 +21,13 @@ if ! grep -q '^openvpn' /etc/passwd; then
adduser --quiet --firstuid 100 --system --group --shell /usr/sbin/nologin openvpn
fi
-# Add 2FA support for SSH
-sudo grep -qF -- "auth required pam_google_authenticator.so nullok" "/etc/pam.d/sshd" || \
-sudo sed -i '/^@include common-auth/a # Check OTP 2FA, if configured for the user\nauth required pam_google_authenticator.so nullok' /etc/pam.d/sshd
-
-# Add 2FA support for local authentication
-sudo grep -qF -- "auth required pam_google_authenticator.so nullok" "/etc/pam.d/login" || \
-sudo sed -i '/^@include common-auth/a # Check OTP 2FA, if configured for the user\nauth required pam_google_authenticator.so nullok' /etc/pam.d/login
+# Enable 2FA/MFA support for SSH and local logins
+for file in /etc/pam.d/sshd /etc/pam.d/login
+do
+ PAM_CONFIG="auth required pam_google_authenticator.so nullok"
+ grep -qF -- "${PAM_CONFIG}" $file || \
+ sed -i "/^@include common-auth/a # Check 2FA/MFA authentication token if enabled (per user)\n${PAM_CONFIG}" $file
+done
# Add RADIUS operator user for RADIUS authenticated users to map to
if ! grep -q '^radius_user' /etc/passwd; then
@@ -103,7 +103,8 @@ DELETE="/etc/logrotate.d/conntrackd.distrib /etc/init.d/conntrackd /etc/default/
/etc/default/pmacctd /etc/pmacct
/etc/networks_list /etc/networks_whitelist /etc/fastnetmon.conf
/etc/ntp.conf /etc/default/ssh
- /etc/powerdns /etc/default/pdns-recursor"
+ /etc/powerdns /etc/default/pdns-recursor
+ /etc/ppp/ip-up.d/0000usepeerdns /etc/ppp/ip-down.d/0000usepeerdns"
for tmp in $DELETE; do
if [ -e ${tmp} ]; then
rm -rf ${tmp}
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
index 51171d881..b61664125 100644
--- a/interface-definitions/container.xml.in
+++ b/interface-definitions/container.xml.in
@@ -111,7 +111,7 @@
</leafNode>
<leafNode name="memory">
<properties>
- <help>Constrain the memory available to a container</help>
+ <help>Memory (RAM) available to this container</help>
<valueHelp>
<format>u32:0</format>
<description>Unlimited</description>
@@ -127,6 +127,24 @@
</properties>
<defaultValue>512</defaultValue>
</leafNode>
+ <leafNode name="shared-memory">
+ <properties>
+ <help>Shared memory available to this container</help>
+ <valueHelp>
+ <format>u32:0</format>
+ <description>Unlimited</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:1-8192</format>
+ <description>Container memory in megabytes (MB)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-8192"/>
+ </constraint>
+ <constraintErrorMessage>Container memory must be in range 0 to 8192 MB</constraintErrorMessage>
+ </properties>
+ <defaultValue>64</defaultValue>
+ </leafNode>
<tagNode name="network">
<properties>
<help>Attach user defined network to container</help>
@@ -189,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>
@@ -247,6 +274,26 @@
</valueHelp>
</properties>
</leafNode>
+ <leafNode name="mode">
+ <properties>
+ <help>Volume access mode ro/rw</help>
+ <completionHelp>
+ <list>ro rw</list>
+ </completionHelp>
+ <valueHelp>
+ <format>ro</format>
+ <description>Volume mounted into the container as read-only</description>
+ </valueHelp>
+ <valueHelp>
+ <format>rw</format>
+ <description>Volume mounted into the container as read-write</description>
+ </valueHelp>
+ <constraint>
+ <regex>(ro|rw)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>rw</defaultValue>
+ </leafNode>
</children>
</tagNode>
</children>
@@ -254,6 +301,10 @@
<tagNode name="network">
<properties>
<help>Network name</help>
+ <constraint>
+ <regex>[-_a-zA-Z0-9]{1,11}</regex>
+ </constraint>
+ <constraintErrorMessage>Network name cannot be longer than 11 characters</constraintErrorMessage>
</properties>
<children>
<leafNode name="description">
diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in
index 6e1592200..1830cc1ad 100644
--- a/interface-definitions/dhcp-server.xml.in
+++ b/interface-definitions/dhcp-server.xml.in
@@ -373,6 +373,19 @@
</leafNode>
</children>
</tagNode >
+ <leafNode name="ipv6-only-preferred">
+ <properties>
+ <help>Disable IPv4 on IPv6 only hosts (RFC 8925)</help>
+ <valueHelp>
+ <format>u32</format>
+ <description>Seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ <constraintErrorMessage>Seconds must be between 0 and 4294967295 (49 days)</constraintErrorMessage>
+ </properties>
+ </leafNode>
<leafNode name="subnet-parameters">
<properties>
<help>Additional subnet parameters for DHCP server. You must use the syntax of dhcpd.conf in this text-field. Using this without proper knowledge may result in a crashed DHCP server. Check system log to look for errors.</help>
diff --git a/interface-definitions/dns-domain-name.xml.in b/interface-definitions/dns-domain-name.xml.in
index 70b2fb271..9aca38735 100644
--- a/interface-definitions/dns-domain-name.xml.in
+++ b/interface-definitions/dns-domain-name.xml.in
@@ -25,7 +25,7 @@
<constraint>
<validator name="ipv4-address"/>
<validator name="ipv6-address"/>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in
index e41ba7f60..a39e412b2 100644
--- a/interface-definitions/dns-dynamic.xml.in
+++ b/interface-definitions/dns-dynamic.xml.in
@@ -237,19 +237,7 @@
<constraintErrorMessage>Please choose from the list of allowed protocols</constraintErrorMessage>
</properties>
</leafNode>
- <leafNode name="server">
- <properties>
- <help>Server to send DDNS update to</help>
- <valueHelp>
- <format>IPv4</format>
- <description>IP address of DDNS server</description>
- </valueHelp>
- <valueHelp>
- <format>FQDN</format>
- <description>Hostname of DDNS server</description>
- </valueHelp>
- </properties>
- </leafNode>
+ #include <include/server-ipv4-fqdn.xml.i>
<leafNode name="zone">
<properties>
<help>DNS zone to update (only available with CloudFlare)</help>
diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in
index 3de0dc0eb..409028572 100644
--- a/interface-definitions/dns-forwarding.xml.in
+++ b/interface-definitions/dns-forwarding.xml.in
@@ -605,6 +605,10 @@
</properties>
</leafNode>
#include <include/listen-address.xml.i>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>53</defaultValue>
+ </leafNode>
<leafNode name="negative-ttl">
<properties>
<help>Maximum amount of time negative entries are cached</help>
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index 673461036..7d7e0a38f 100644
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -126,7 +126,7 @@
<description>Domain address to match</description>
</valueHelp>
<constraint>
- <regex>[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,99}?(\/.*)?</regex>
+ <validator name="fqdn"/>
</constraint>
<multi/>
</properties>
@@ -134,6 +134,35 @@
#include <include/generic-description.xml.i>
</children>
</tagNode>
+ <tagNode name="interface-group">
+ <properties>
+ <help>Firewall interface-group</help>
+ <constraint>
+ <regex>[a-zA-Z0-9][\w\-\.]*</regex>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="interface">
+ <properties>
+ <help>Interface-group member</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="include">
+ <properties>
+ <help>Include another interface-group</help>
+ <completionHelp>
+ <path>firewall group interface-group</path>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ #include <include/generic-description.xml.i>
+ </children>
+ </tagNode>
<tagNode name="ipv6-address-group">
<properties>
<help>Firewall ipv6-address-group</help>
@@ -218,7 +247,7 @@
<properties>
<help>Mac-group member</help>
<valueHelp>
- <format>&lt;MAC address&gt;</format>
+ <format>macaddr</format>
<description>MAC address to match</description>
</valueHelp>
<constraint>
@@ -408,9 +437,11 @@
</properties>
<children>
#include <include/firewall/address-ipv6.xml.i>
+ #include <include/firewall/fqdn.xml.i>
#include <include/firewall/geoip.xml.i>
#include <include/firewall/source-destination-group-ipv6.xml.i>
#include <include/firewall/port.xml.i>
+ #include <include/firewall/address-mask-ipv6.xml.i>
</children>
</node>
<node name="source">
@@ -419,15 +450,18 @@
</properties>
<children>
#include <include/firewall/address-ipv6.xml.i>
+ #include <include/firewall/fqdn.xml.i>
#include <include/firewall/geoip.xml.i>
#include <include/firewall/source-destination-group-ipv6.xml.i>
#include <include/firewall/port.xml.i>
+ #include <include/firewall/address-mask-ipv6.xml.i>
</children>
</node>
#include <include/firewall/common-rule.xml.i>
#include <include/firewall/dscp.xml.i>
#include <include/firewall/packet-length.xml.i>
#include <include/firewall/hop-limit.xml.i>
+ #include <include/firewall/connection-mark.xml.i>
<node name="icmpv6">
<properties>
<help>ICMPv6 type and code information</help>
@@ -572,9 +606,11 @@
</properties>
<children>
#include <include/firewall/address.xml.i>
+ #include <include/firewall/fqdn.xml.i>
#include <include/firewall/geoip.xml.i>
#include <include/firewall/source-destination-group.xml.i>
#include <include/firewall/port.xml.i>
+ #include <include/firewall/address-mask.xml.i>
</children>
</node>
<node name="source">
@@ -583,14 +619,17 @@
</properties>
<children>
#include <include/firewall/address.xml.i>
+ #include <include/firewall/fqdn.xml.i>
#include <include/firewall/geoip.xml.i>
#include <include/firewall/source-destination-group.xml.i>
#include <include/firewall/port.xml.i>
+ #include <include/firewall/address-mask.xml.i>
</children>
</node>
#include <include/firewall/common-rule.xml.i>
#include <include/firewall/dscp.xml.i>
#include <include/firewall/packet-length.xml.i>
+ #include <include/firewall/connection-mark.xml.i>
<node name="icmp">
<properties>
<help>ICMP type and code information</help>
@@ -656,6 +695,25 @@
</properties>
<defaultValue>disable</defaultValue>
</leafNode>
+ <leafNode name="resolver-cache">
+ <properties>
+ <help>Retains last successful value if domain resolution fails</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="resolver-interval">
+ <properties>
+ <help>Domain resolver update interval</help>
+ <valueHelp>
+ <format>u32:10-3600</format>
+ <description>Interval (seconds)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 10-3600"/>
+ </constraint>
+ </properties>
+ <defaultValue>300</defaultValue>
+ </leafNode>
<leafNode name="send-redirects">
<properties>
<help>Policy for sending IPv4 ICMP redirect messages</help>
diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in
index 0631acdda..6cb40247a 100644
--- a/interface-definitions/high-availability.xml.in
+++ b/interface-definitions/high-availability.xml.in
@@ -11,12 +11,33 @@
<help>Virtual Router Redundancy Protocol settings</help>
</properties>
<children>
+ <node name="global-parameters">
+ <properties>
+ <help>VRRP global parameters</help>
+ </properties>
+ <children>
+ #include <include/vrrp/garp.xml.i>
+ <leafNode name="startup-delay">
+ <properties>
+ <help>Time VRRP startup process (in seconds)</help>
+ <valueHelp>
+ <format>u32:1-600</format>
+ <description>Interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-600"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
<tagNode name="group">
<properties>
<help>VRRP group</help>
</properties>
<children>
#include <include/generic-interface-broadcast.xml.i>
+ #include <include/vrrp/garp.xml.i>
<leafNode name="advertise-interval">
<properties>
<help>Advertise interval</help>
@@ -199,7 +220,7 @@
<description>Interface name</description>
</valueHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
<multi/>
</properties>
@@ -211,16 +232,15 @@
<properties>
<help>Virtual IP address</help>
<valueHelp>
- <format>ipv4</format>
- <description>IPv4 virtual address</description>
+ <format>ipv4net</format>
+ <description>IPv4 address and prefix length</description>
</valueHelp>
<valueHelp>
- <format>ipv6</format>
- <description>IPv6 virtual address</description>
+ <format>ipv6net</format>
+ <description>IPv6 address and prefix length</description>
</valueHelp>
<constraint>
- <validator name="ipv4-host"/>
- <validator name="ipv6-host"/>
+ <validator name="ip-host"/>
</constraint>
</properties>
<children>
@@ -365,7 +385,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 +425,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 +438,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/https.xml.in b/interface-definitions/https.xml.in
index d096c4ff1..6adb07598 100644
--- a/interface-definitions/https.xml.in
+++ b/interface-definitions/https.xml.in
@@ -107,7 +107,7 @@
<valueless/>
</properties>
</leafNode>
- <node name="gql">
+ <node name="graphql">
<properties>
<help>GraphQL support</help>
</properties>
@@ -118,6 +118,59 @@
<valueless/>
</properties>
</leafNode>
+ <node name="authentication">
+ <properties>
+ <help>GraphQL authentication</help>
+ </properties>
+ <children>
+ <leafNode name="type">
+ <properties>
+ <help>Authentication type</help>
+ <completionHelp>
+ <list>key token</list>
+ </completionHelp>
+ <valueHelp>
+ <format>key</format>
+ <description>Use API keys</description>
+ </valueHelp>
+ <valueHelp>
+ <format>token</format>
+ <description>Use JWT token</description>
+ </valueHelp>
+ <constraint>
+ <regex>(key|token)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>key</defaultValue>
+ </leafNode>
+ <leafNode name="expiration">
+ <properties>
+ <help>Token time to expire in seconds</help>
+ <valueHelp>
+ <format>u32:60-31536000</format>
+ <description>Token lifetime in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 60-31536000"/>
+ </constraint>
+ </properties>
+ <defaultValue>3600</defaultValue>
+ </leafNode>
+ <leafNode name="secret-length">
+ <properties>
+ <help>Length of shared secret in bytes</help>
+ <valueHelp>
+ <format>u32:16-65535</format>
+ <description>Byte length of generated shared secret</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 16-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>32</defaultValue>
+ </leafNode>
+ </children>
+ </node>
</children>
</node>
<node name="cors">
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/bgp/afi-rd.xml.i b/interface-definitions/include/bgp/afi-rd.xml.i
index 767502094..beb1447df 100644
--- a/interface-definitions/include/bgp/afi-rd.xml.i
+++ b/interface-definitions/include/bgp/afi-rd.xml.i
@@ -17,7 +17,7 @@
<description>Route Distinguisher, (x.x.x.x:yyy|xxxx:yyyy)</description>
</valueHelp>
<constraint>
- <regex>((25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)(\.(25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)){3}|[0-9]{1,10}):[0-9]{1,5}</regex>
+ <validator name="bgp-rd-rt" argument="--route-distinguisher"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/bgp/neighbor-local-role.xml.i b/interface-definitions/include/bgp/neighbor-local-role.xml.i
new file mode 100644
index 000000000..6ddb4908f
--- /dev/null
+++ b/interface-definitions/include/bgp/neighbor-local-role.xml.i
@@ -0,0 +1,42 @@
+<!-- include start from bgp/neigbhor-local-role.xml.i -->
+<tagNode name="local-role">
+ <properties>
+ <help>Local role for BGP neighbor (RFC9234)</help>
+ <completionHelp>
+ <list>customer peer provider rs-client rs-server</list>
+ </completionHelp>
+ <valueHelp>
+ <format>customer</format>
+ <description>Using Transit</description>
+ </valueHelp>
+ <valueHelp>
+ <format>peer</format>
+ <description>Public/Private Peering</description>
+ </valueHelp>
+ <valueHelp>
+ <format>provider</format>
+ <description>Providing Transit</description>
+ </valueHelp>
+ <valueHelp>
+ <format>rs-client</format>
+ <description>RS Client</description>
+ </valueHelp>
+ <valueHelp>
+ <format>rs-server</format>
+ <description>Route Server</description>
+ </valueHelp>
+ <constraint>
+ <regex>(provider|rs-server|rs-client|customer|peer)</regex>
+ </constraint>
+ <constraintErrorMessage>BGP local-role must be one of the following: customer, peer, provider, rs-client or rs-server</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="strict">
+ <properties>
+ <help>Neighbor must send this exact capability, otherwise a role missmatch notification will be sent</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+</tagNode>
+<!-- include end -->
diff --git a/interface-definitions/include/bgp/neighbor-update-source.xml.i b/interface-definitions/include/bgp/neighbor-update-source.xml.i
index 37faf2cce..60c127e8f 100644
--- a/interface-definitions/include/bgp/neighbor-update-source.xml.i
+++ b/interface-definitions/include/bgp/neighbor-update-source.xml.i
@@ -22,7 +22,7 @@
<constraint>
<validator name="ipv4-address"/>
<validator name="ipv6-address"/>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i
index 70176144d..ec065347c 100644
--- a/interface-definitions/include/bgp/protocol-common-config.xml.i
+++ b/interface-definitions/include/bgp/protocol-common-config.xml.i
@@ -926,7 +926,7 @@
<constraint>
<validator name="ipv4-address"/>
<validator name="ipv6-address"/>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
<children>
@@ -987,6 +987,7 @@
</children>
</node>
#include <include/bgp/neighbor-local-as.xml.i>
+ #include <include/bgp/neighbor-local-role.xml.i>
#include <include/bgp/neighbor-override-capability.xml.i>
#include <include/bgp/neighbor-passive.xml.i>
#include <include/bgp/neighbor-password.xml.i>
@@ -1431,6 +1432,12 @@
<valueless/>
</properties>
</leafNode>
+ <leafNode name="route-reflector-allow-outbound-policy">
+ <properties>
+ <help>Route reflector client allow policy outbound</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="no-client-to-client-reflection">
<properties>
<help>Disable client to client route reflection</help>
@@ -1497,6 +1504,7 @@
#include <include/bgp/neighbor-graceful-restart.xml.i>
#include <include/bgp/neighbor-graceful-restart.xml.i>
#include <include/bgp/neighbor-local-as.xml.i>
+ #include <include/bgp/neighbor-local-role.xml.i>
#include <include/bgp/neighbor-override-capability.xml.i>
#include <include/bgp/neighbor-passive.xml.i>
#include <include/bgp/neighbor-password.xml.i>
diff --git a/interface-definitions/include/certificate-ca.xml.i b/interface-definitions/include/certificate-ca.xml.i
index b97378658..3cde2a48d 100644
--- a/interface-definitions/include/certificate-ca.xml.i
+++ b/interface-definitions/include/certificate-ca.xml.i
@@ -7,7 +7,7 @@
<description>File in /config/auth directory</description>
</valueHelp>
<constraint>
- <validator name="file-exists" argument="--directory /config/auth"/>
+ <validator name="file-path" argument="--strict --parent-dir /config/auth"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/certificate-key.xml.i b/interface-definitions/include/certificate-key.xml.i
index 1db9dd069..2c4d81fbb 100644
--- a/interface-definitions/include/certificate-key.xml.i
+++ b/interface-definitions/include/certificate-key.xml.i
@@ -7,7 +7,7 @@
<description>File in /config/auth directory</description>
</valueHelp>
<constraint>
- <validator name="file-exists" argument="--directory /config/auth"/>
+ <validator name="file-path" argument="--strict --parent-dir /config/auth"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/certificate.xml.i b/interface-definitions/include/certificate.xml.i
index fb5be45cc..6a5b2936c 100644
--- a/interface-definitions/include/certificate.xml.i
+++ b/interface-definitions/include/certificate.xml.i
@@ -7,7 +7,7 @@
<description>File in /config/auth directory</description>
</valueHelp>
<constraint>
- <validator name="file-exists" argument="--directory /config/auth"/>
+ <validator name="file-path" argument="--strict --parent-dir /config/auth"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/constraint/interface-name.xml.in b/interface-definitions/include/constraint/interface-name.xml.in
new file mode 100644
index 000000000..2d1f7b757
--- /dev/null
+++ b/interface-definitions/include/constraint/interface-name.xml.in
@@ -0,0 +1,4 @@
+<!-- include start from constraint/interface-name.xml.in -->
+<regex>(bond|br|dum|en|ersp|eth|gnv|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)[0-9]+(.\d+)?|lo</regex>
+<validator name="file-path --lookup-path /sys/class/net --directory"/>
+<!-- include end -->
diff --git a/interface-definitions/include/dhcp-interface-multi.xml.i b/interface-definitions/include/dhcp-interface-multi.xml.i
new file mode 100644
index 000000000..c74751a19
--- /dev/null
+++ b/interface-definitions/include/dhcp-interface-multi.xml.i
@@ -0,0 +1,18 @@
+<!-- include start from dhcp-interface-multi.xml.i -->
+<leafNode name="dhcp-interface">
+ <properties>
+ <help>DHCP interface supplying next-hop IP address</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>DHCP interface name</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.in>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/dhcp-interface.xml.i b/interface-definitions/include/dhcp-interface.xml.i
index 939b45f15..f5107ba2b 100644
--- a/interface-definitions/include/dhcp-interface.xml.i
+++ b/interface-definitions/include/dhcp-interface.xml.i
@@ -9,7 +9,7 @@
<description>DHCP interface name</description>
</valueHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/firewall/address-mask-ipv6.xml.i b/interface-definitions/include/firewall/address-mask-ipv6.xml.i
new file mode 100644
index 000000000..8c0483209
--- /dev/null
+++ b/interface-definitions/include/firewall/address-mask-ipv6.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from firewall/address-mask-ipv6.xml.i -->
+<leafNode name="address-mask">
+ <properties>
+ <help>IP mask</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IP mask to apply</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/address-mask.xml.i b/interface-definitions/include/firewall/address-mask.xml.i
new file mode 100644
index 000000000..7f6f17d1e
--- /dev/null
+++ b/interface-definitions/include/firewall/address-mask.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from firewall/address-mask.xml.i -->
+<leafNode name="address-mask">
+ <properties>
+ <help>IP mask</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 mask to apply</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </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 a4f66f5cb..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>
@@ -26,14 +34,22 @@
</leafNode>
</children>
</node>
-<leafNode name="inbound-interface">
+<node name="inbound-interface">
<properties>
<help>Match inbound-interface</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
- </completionHelp>
</properties>
-</leafNode>
+ <children>
+ #include <include/firewall/match-interface.xml.i>
+ </children>
+</node>
+<node name="outbound-interface">
+ <properties>
+ <help>Match outbound-interface</help>
+ </properties>
+ <children>
+ #include <include/firewall/match-interface.xml.i>
+ </children>
+</node>
<node name="ipsec">
<properties>
<help>Inbound IPsec packets</help>
@@ -130,14 +146,6 @@
</leafNode>
</children>
</node>
-<leafNode name="outbound-interface">
- <properties>
- <help>Match outbound-interface</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
- </completionHelp>
- </properties>
-</leafNode>
<leafNode name="protocol">
<properties>
<help>Protocol to match (protocol name, number, or "all")</help>
@@ -219,22 +227,7 @@
<children>
#include <include/firewall/address.xml.i>
#include <include/firewall/source-destination-group.xml.i>
- <leafNode name="mac-address">
- <properties>
- <help>Source MAC address</help>
- <valueHelp>
- <format>&lt;MAC address&gt;</format>
- <description>MAC address to match</description>
- </valueHelp>
- <valueHelp>
- <format>!&lt;MAC address&gt;</format>
- <description>Match everything except the specified MAC address</description>
- </valueHelp>
- <constraint>
- <validator name="mac-address-firewall"/>
- </constraint>
- </properties>
- </leafNode>
+ #include <include/firewall/mac-address.xml.i>
#include <include/firewall/port.xml.i>
</children>
</node>
diff --git a/interface-definitions/include/firewall/connection-mark.xml.i b/interface-definitions/include/firewall/connection-mark.xml.i
new file mode 100644
index 000000000..2cb826635
--- /dev/null
+++ b/interface-definitions/include/firewall/connection-mark.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from firewall/connection-mark.xml.i -->
+<leafNode name="connection-mark">
+ <properties>
+ <help>Connection mark</help>
+ <valueHelp>
+ <format>u32:1-2147483647</format>
+ <description>Connection-mark to match</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2147483647"/>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/fqdn.xml.i b/interface-definitions/include/firewall/fqdn.xml.i
new file mode 100644
index 000000000..9eb3925b5
--- /dev/null
+++ b/interface-definitions/include/firewall/fqdn.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from firewall/fqdn.xml.i -->
+<leafNode name="fqdn">
+ <properties>
+ <help>Fully qualified domain name</help>
+ <valueHelp>
+ <format>&lt;fqdn&gt;</format>
+ <description>Fully qualified domain name</description>
+ </valueHelp>
+ <constraint>
+ <validator name="fqdn"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
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/icmpv6-type-name.xml.i b/interface-definitions/include/firewall/icmpv6-type-name.xml.i
index a2e68abfb..e17a20e17 100644
--- a/interface-definitions/include/firewall/icmpv6-type-name.xml.i
+++ b/interface-definitions/include/firewall/icmpv6-type-name.xml.i
@@ -3,7 +3,7 @@
<properties>
<help>ICMPv6 type-name</help>
<completionHelp>
- <list>destination-unreachable packet-too-big time-exceeded echo-request echo-reply mld-listener-query mld-listener-report mld-listener-reduction nd-router-solicit nd-router-advert nd-neighbor-solicit nd-neighbor-advert nd-redirect parameter-problem router-renumbering</list>
+ <list>destination-unreachable packet-too-big time-exceeded echo-request echo-reply mld-listener-query mld-listener-report mld-listener-reduction nd-router-solicit nd-router-advert nd-neighbor-solicit nd-neighbor-advert nd-redirect parameter-problem router-renumbering ind-neighbor-solicit ind-neighbor-advert mld2-listener-report</list>
</completionHelp>
<valueHelp>
<format>destination-unreachable</format>
@@ -65,8 +65,20 @@
<format>router-renumbering</format>
<description>ICMPv6 type 138: router-renumbering</description>
</valueHelp>
+ <valueHelp>
+ <format>ind-neighbor-solicit</format>
+ <description>ICMPv6 type 141: ind-neighbor-solicit</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ind-neighbor-advert</format>
+ <description>ICMPv6 type 142: ind-neighbor-advert</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mld2-listener-report</format>
+ <description>ICMPv6 type 143: mld2-listener-report</description>
+ </valueHelp>
<constraint>
- <regex>(destination-unreachable|packet-too-big|time-exceeded|echo-request|echo-reply|mld-listener-query|mld-listener-report|mld-listener-reduction|nd-router-solicit|nd-router-advert|nd-neighbor-solicit|nd-neighbor-advert|nd-redirect|parameter-problem|router-renumbering)</regex>
+ <regex>(destination-unreachable|packet-too-big|time-exceeded|echo-request|echo-reply|mld-listener-query|mld-listener-report|mld-listener-reduction|nd-router-solicit|nd-router-advert|nd-neighbor-solicit|nd-neighbor-advert|nd-redirect|parameter-problem|router-renumbering|ind-neighbor-solicit|ind-neighbor-advert|mld2-listener-report)</regex>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/firewall/mac-address.xml.i b/interface-definitions/include/firewall/mac-address.xml.i
new file mode 100644
index 000000000..db3e1e312
--- /dev/null
+++ b/interface-definitions/include/firewall/mac-address.xml.i
@@ -0,0 +1,19 @@
+<!-- include start from firewall/mac-address.xml.i -->
+<leafNode name="mac-address">
+ <properties>
+ <help>MAC address</help>
+ <valueHelp>
+ <format>macaddr</format>
+ <description>MAC address to match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!macaddr</format>
+ <description>Match everything except the specified MAC address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="mac-address"/>
+ <validator name="mac-address-exclude"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/match-interface.xml.i b/interface-definitions/include/firewall/match-interface.xml.i
new file mode 100644
index 000000000..675a87574
--- /dev/null
+++ b/interface-definitions/include/firewall/match-interface.xml.i
@@ -0,0 +1,18 @@
+<!-- include start from firewall/match-interface.xml.i -->
+<leafNode name="interface-name">
+ <properties>
+ <help>Match interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+</leafNode>
+<leafNode name="interface-group">
+ <properties>
+ <help>Match interface-group</help>
+ <completionHelp>
+ <path>firewall group interface-group</path>
+ </completionHelp>
+ </properties>
+</leafNode>
+<!-- include end --> \ No newline at end of file
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/firewall/source-destination-group-ipv6.xml.i b/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i
index c2cc7edb3..2a42d236c 100644
--- a/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i
+++ b/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i
@@ -12,6 +12,14 @@
</completionHelp>
</properties>
</leafNode>
+ <leafNode name="domain-group">
+ <properties>
+ <help>Group of domains</help>
+ <completionHelp>
+ <path>firewall group domain-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
#include <include/firewall/mac-group.xml.i>
<leafNode name="network-group">
<properties>
diff --git a/interface-definitions/include/generic-description.xml.i b/interface-definitions/include/generic-description.xml.i
index 03fc564e6..63e5e174e 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:]]{0,256}</regex>
+ </constraint>
+ <constraintErrorMessage>Description too long (limit 256 characters)</constraintErrorMessage>
</properties>
</leafNode>
<!-- include end -->
diff --git a/interface-definitions/include/generic-interface-broadcast.xml.i b/interface-definitions/include/generic-interface-broadcast.xml.i
index 6f76dde1a..af35a888b 100644
--- a/interface-definitions/include/generic-interface-broadcast.xml.i
+++ b/interface-definitions/include/generic-interface-broadcast.xml.i
@@ -10,7 +10,7 @@
<description>Interface name</description>
</valueHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/generic-interface-multi-broadcast.xml.i b/interface-definitions/include/generic-interface-multi-broadcast.xml.i
index 00638f3b7..1ae38fb43 100644
--- a/interface-definitions/include/generic-interface-multi-broadcast.xml.i
+++ b/interface-definitions/include/generic-interface-multi-broadcast.xml.i
@@ -10,7 +10,7 @@
<description>Interface name</description>
</valueHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
<multi/>
</properties>
diff --git a/interface-definitions/include/generic-interface-multi.xml.i b/interface-definitions/include/generic-interface-multi.xml.i
index 65aae28ae..16916ff54 100644
--- a/interface-definitions/include/generic-interface-multi.xml.i
+++ b/interface-definitions/include/generic-interface-multi.xml.i
@@ -10,7 +10,7 @@
<description>Interface name</description>
</valueHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
<multi/>
</properties>
diff --git a/interface-definitions/include/generic-interface.xml.i b/interface-definitions/include/generic-interface.xml.i
index 8b4cf1d65..36ddee417 100644
--- a/interface-definitions/include/generic-interface.xml.i
+++ b/interface-definitions/include/generic-interface.xml.i
@@ -10,7 +10,7 @@
<description>Interface name</description>
</valueHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
</leafNode>
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/interface-policy-vif-c.xml.i b/interface-definitions/include/interface/interface-policy-vif-c.xml.i
deleted file mode 100644
index 866fcd5c0..000000000
--- a/interface-definitions/include/interface/interface-policy-vif-c.xml.i
+++ /dev/null
@@ -1,26 +0,0 @@
-<!-- include start from interface/interface-policy-vif-c.xml.i -->
-<node name="policy" owner="${vyos_conf_scripts_dir}/policy-route-interface.py $VAR(../../../@).$VAR(../../@).$VAR(../@)">
- <properties>
- <priority>620</priority>
- <help>Policy route options</help>
- </properties>
- <children>
- <leafNode name="route">
- <properties>
- <help>IPv4 policy route ruleset for interface</help>
- <completionHelp>
- <path>policy route</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="route6">
- <properties>
- <help>IPv6 policy route ruleset for interface</help>
- <completionHelp>
- <path>policy route6</path>
- </completionHelp>
- </properties>
- </leafNode>
- </children>
-</node>
-<!-- include end -->
diff --git a/interface-definitions/include/interface/interface-policy-vif.xml.i b/interface-definitions/include/interface/interface-policy-vif.xml.i
deleted file mode 100644
index 83510fe59..000000000
--- a/interface-definitions/include/interface/interface-policy-vif.xml.i
+++ /dev/null
@@ -1,26 +0,0 @@
-<!-- include start from interface/interface-policy-vif.xml.i -->
-<node name="policy" owner="${vyos_conf_scripts_dir}/policy-route-interface.py $VAR(../../@).$VAR(../@)">
- <properties>
- <priority>620</priority>
- <help>Policy route options</help>
- </properties>
- <children>
- <leafNode name="route">
- <properties>
- <help>IPv4 policy route ruleset for interface</help>
- <completionHelp>
- <path>policy route</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="route6">
- <properties>
- <help>IPv6 policy route ruleset for interface</help>
- <completionHelp>
- <path>policy route6</path>
- </completionHelp>
- </properties>
- </leafNode>
- </children>
-</node>
-<!-- include end -->
diff --git a/interface-definitions/include/interface/interface-policy.xml.i b/interface-definitions/include/interface/interface-policy.xml.i
deleted file mode 100644
index 42a8fd009..000000000
--- a/interface-definitions/include/interface/interface-policy.xml.i
+++ /dev/null
@@ -1,26 +0,0 @@
-<!-- include start from interface/interface-policy.xml.i -->
-<node name="policy" owner="${vyos_conf_scripts_dir}/policy-route-interface.py $VAR(../@)">
- <properties>
- <priority>620</priority>
- <help>Policy route options</help>
- </properties>
- <children>
- <leafNode name="route">
- <properties>
- <help>IPv4 policy route ruleset for interface</help>
- <completionHelp>
- <path>policy route</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="route6">
- <properties>
- <help>IPv6 policy route ruleset for interface</help>
- <completionHelp>
- <path>policy route6</path>
- </completionHelp>
- </properties>
- </leafNode>
- </children>
-</node>
-<!-- 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/no-peer-dns.xml.i b/interface-definitions/include/interface/no-peer-dns.xml.i
new file mode 100644
index 000000000..d663f04c1
--- /dev/null
+++ b/interface-definitions/include/interface/no-peer-dns.xml.i
@@ -0,0 +1,8 @@
+<!-- include start from interface/no-peer-dns.xml.i -->
+<leafNode name="no-peer-dns">
+ <properties>
+ <help>Do not use DNS servers provided by the peer</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/redirect.xml.i b/interface-definitions/include/interface/redirect.xml.i
index 3be9ee16b..b01e486ce 100644
--- a/interface-definitions/include/interface/redirect.xml.i
+++ b/interface-definitions/include/interface/redirect.xml.i
@@ -1,16 +1,16 @@
<!-- 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>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i
index 916349ade..fdd62b63d 100644
--- a/interface-definitions/include/interface/vif-s.xml.i
+++ b/interface-definitions/include/interface/vif-s.xml.i
@@ -12,13 +12,12 @@
<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>
#include <include/interface/disable.xml.i>
- #include <include/interface/interface-policy-vif.xml.i>
<leafNode name="protocol">
<properties>
<help>Protocol used for service VLAN (default: 802.1ad)</help>
@@ -54,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>
@@ -67,7 +66,6 @@
#include <include/interface/mtu-68-16000.xml.i>
#include <include/interface/redirect.xml.i>
#include <include/interface/vrf.xml.i>
- #include <include/interface/interface-policy-vif-c.xml.i>
</children>
</tagNode>
#include <include/interface/redirect.xml.i>
diff --git a/interface-definitions/include/interface/vif.xml.i b/interface-definitions/include/interface/vif.xml.i
index 73a8c98ff..ec3921bf6 100644
--- a/interface-definitions/include/interface/vif.xml.i
+++ b/interface-definitions/include/interface/vif.xml.i
@@ -12,13 +12,12 @@
<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>
#include <include/interface/disable.xml.i>
- #include <include/interface/interface-policy-vif.xml.i>
<leafNode name="egress-qos">
<properties>
<help>VLAN egress QoS</help>
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/listen-address-single.xml.i b/interface-definitions/include/listen-address-single.xml.i
index b5841cabb..30293b338 100644
--- a/interface-definitions/include/listen-address-single.xml.i
+++ b/interface-definitions/include/listen-address-single.xml.i
@@ -1,3 +1,4 @@
+<!-- include start from listen-address-single.xml.i -->
<leafNode name="listen-address">
<properties>
<help>Local IP addresses to listen on</help>
diff --git a/interface-definitions/include/nat-rule.xml.i b/interface-definitions/include/nat-rule.xml.i
index 84941aa6a..8f2029388 100644
--- a/interface-definitions/include/nat-rule.xml.i
+++ b/interface-definitions/include/nat-rule.xml.i
@@ -20,6 +20,7 @@
<children>
#include <include/nat-address.xml.i>
#include <include/nat-port.xml.i>
+ #include <include/firewall/source-destination-group.xml.i>
</children>
</node>
#include <include/generic-disable-node.xml.i>
@@ -285,6 +286,7 @@
<children>
#include <include/nat-address.xml.i>
#include <include/nat-port.xml.i>
+ #include <include/firewall/source-destination-group.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i
index 0615063af..06609c10e 100644
--- a/interface-definitions/include/ospf/protocol-common-config.xml.i
+++ b/interface-definitions/include/ospf/protocol-common-config.xml.i
@@ -358,7 +358,7 @@
<description>Interface name</description>
</valueHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
<children>
diff --git a/interface-definitions/include/ospfv3/protocol-common-config.xml.i b/interface-definitions/include/ospfv3/protocol-common-config.xml.i
index 630534eea..c0aab912d 100644
--- a/interface-definitions/include/ospfv3/protocol-common-config.xml.i
+++ b/interface-definitions/include/ospfv3/protocol-common-config.xml.i
@@ -118,7 +118,7 @@
<description>Interface used for routing information exchange</description>
</valueHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
<children>
diff --git a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
deleted file mode 100644
index 662206336..000000000
--- a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
+++ /dev/null
@@ -1,557 +0,0 @@
-<!-- include start from policy/route-common-rule.xml.i -->
-#include <include/policy/route-rule-action.xml.i>
-#include <include/generic-description.xml.i>
-<leafNode name="disable">
- <properties>
- <help>Option to disable firewall rule</help>
- <valueless/>
- </properties>
-</leafNode>
-<node name="fragment">
- <properties>
- <help>IP fragment match</help>
- </properties>
- <children>
- <leafNode name="match-frag">
- <properties>
- <help>Second and further fragments of fragmented packets</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="match-non-frag">
- <properties>
- <help>Head fragments or unfragmented packets</help>
- <valueless/>
- </properties>
- </leafNode>
- </children>
-</node>
-<node name="ipsec">
- <properties>
- <help>Inbound IPsec packets</help>
- </properties>
- <children>
- <leafNode name="match-ipsec">
- <properties>
- <help>Inbound IPsec packets</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="match-none">
- <properties>
- <help>Inbound non-IPsec packets</help>
- <valueless/>
- </properties>
- </leafNode>
- </children>
-</node>
-<node name="limit">
- <properties>
- <help>Rate limit using a token bucket filter</help>
- </properties>
- <children>
- <leafNode name="burst">
- <properties>
- <help>Maximum number of packets to allow in excess of rate</help>
- <valueHelp>
- <format>u32:0-4294967295</format>
- <description>Maximum number of packets to allow in excess of rate</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-4294967295"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="rate">
- <properties>
- <help>Maximum average matching rate</help>
- <valueHelp>
- <format>u32:0-4294967295</format>
- <description>Maximum average matching rate</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-4294967295"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
-</node>
-<leafNode name="log">
- <properties>
- <help>Option to log packets matching rule</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable log</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable log</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
-</leafNode>
-<leafNode name="protocol">
- <properties>
- <help>Protocol to match (protocol name, number, or "all")</help>
- <completionHelp>
- <script>cat /etc/protocols | sed -e '/^#.*/d' | awk '{ print $1 }'</script>
- </completionHelp>
- <valueHelp>
- <format>all</format>
- <description>All IP protocols</description>
- </valueHelp>
- <valueHelp>
- <format>tcp_udp</format>
- <description>Both TCP and UDP</description>
- </valueHelp>
- <valueHelp>
- <format>0-255</format>
- <description>IP protocol number</description>
- </valueHelp>
- <valueHelp>
- <format>!&lt;protocol&gt;</format>
- <description>IP protocol number</description>
- </valueHelp>
- <constraint>
- <validator name="ip-protocol"/>
- </constraint>
- </properties>
- <defaultValue>all</defaultValue>
-</leafNode>
-<node name="recent">
- <properties>
- <help>Parameters for matching recently seen sources</help>
- </properties>
- <children>
- <leafNode name="count">
- <properties>
- <help>Source addresses seen more than N times</help>
- <valueHelp>
- <format>u32:1-255</format>
- <description>Source addresses seen more than N times</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-255"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="time">
- <properties>
- <help>Source addresses seen in the last N seconds</help>
- <valueHelp>
- <format>u32:0-4294967295</format>
- <description>Source addresses seen in the last N seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-4294967295"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
-</node>
-<node name="set">
- <properties>
- <help>Packet modifications</help>
- </properties>
- <children>
- <leafNode name="dscp">
- <properties>
- <help>Packet Differentiated Services Codepoint (DSCP)</help>
- <valueHelp>
- <format>u32:0-63</format>
- <description>DSCP number</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-63"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="mark">
- <properties>
- <help>Packet marking</help>
- <valueHelp>
- <format>u32:1-2147483647</format>
- <description>Packet marking</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-2147483647"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="table">
- <properties>
- <help>Routing table to forward packet with</help>
- <valueHelp>
- <format>u32:1-200</format>
- <description>Table number</description>
- </valueHelp>
- <valueHelp>
- <format>main</format>
- <description>Main table</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-200"/>
- <regex>(main)</regex>
- </constraint>
- <completionHelp>
- <list>main</list>
- <path>protocols static table</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="tcp-mss">
- <properties>
- <help>TCP Maximum Segment Size</help>
- <valueHelp>
- <format>u32:500-1460</format>
- <description>Explicitly set TCP MSS value</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 500-1460"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
-</node>
-<node name="source">
- <properties>
- <help>Source parameters</help>
- </properties>
- <children>
- #include <include/firewall/address-ipv6.xml.i>
- #include <include/firewall/source-destination-group.xml.i>
- <leafNode name="mac-address">
- <properties>
- <help>Source MAC address</help>
- <valueHelp>
- <format>&lt;MAC address&gt;</format>
- <description>MAC address to match</description>
- </valueHelp>
- <valueHelp>
- <format>!&lt;MAC address&gt;</format>
- <description>Match everything except the specified MAC address</description>
- </valueHelp>
- <constraint>
- <validator name="mac-address-firewall"/>
- </constraint>
- </properties>
- </leafNode>
- #include <include/firewall/port.xml.i>
- </children>
-</node>
-<node name="state">
- <properties>
- <help>Session state</help>
- </properties>
- <children>
- <leafNode name="established">
- <properties>
- <help>Established state</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="invalid">
- <properties>
- <help>Invalid state</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="new">
- <properties>
- <help>New state</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="related">
- <properties>
- <help>Related state</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- </leafNode>
- </children>
-</node>
-#include <include/firewall/tcp-flags.xml.i>
-<node name="time">
- <properties>
- <help>Time to match rule</help>
- </properties>
- <children>
- <leafNode name="monthdays">
- <properties>
- <help>Monthdays to match rule on</help>
- </properties>
- </leafNode>
- <leafNode name="startdate">
- <properties>
- <help>Date to start matching rule</help>
- </properties>
- </leafNode>
- <leafNode name="starttime">
- <properties>
- <help>Time of day to start matching rule</help>
- </properties>
- </leafNode>
- <leafNode name="stopdate">
- <properties>
- <help>Date to stop matching rule</help>
- </properties>
- </leafNode>
- <leafNode name="stoptime">
- <properties>
- <help>Time of day to stop matching rule</help>
- </properties>
- </leafNode>
- <leafNode name="utc">
- <properties>
- <help>Interpret times for startdate, stopdate, starttime and stoptime to be UTC</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="weekdays">
- <properties>
- <help>Weekdays to match rule on</help>
- </properties>
- </leafNode>
- </children>
-</node>
-<node name="icmpv6">
- <properties>
- <help>ICMPv6 type and code information</help>
- </properties>
- <children>
- <leafNode name="type">
- <properties>
- <help>ICMP type-name</help>
- <completionHelp>
- <list>any echo-reply pong destination-unreachable network-unreachable host-unreachable protocol-unreachable port-unreachable fragmentation-needed source-route-failed network-unknown host-unknown network-prohibited host-prohibited TOS-network-unreachable TOS-host-unreachable communication-prohibited host-precedence-violation precedence-cutoff source-quench redirect network-redirect host-redirect TOS-network-redirect TOS host-redirect echo-request ping router-advertisement router-solicitation time-exceeded ttl-exceeded ttl-zero-during-transit ttl-zero-during-reassembly parameter-problem ip-header-bad required-option-missing timestamp-request timestamp-reply address-mask-request address-mask-reply packet-too-big</list>
- </completionHelp>
- <valueHelp>
- <format>any</format>
- <description>Any ICMP type/code</description>
- </valueHelp>
- <valueHelp>
- <format>echo-reply</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>pong</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>destination-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>network-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>host-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>protocol-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>port-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>fragmentation-needed</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>source-route-failed</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>network-unknown</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>host-unknown</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>network-prohibited</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>host-prohibited</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>TOS-network-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>TOS-host-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>communication-prohibited</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>host-precedence-violation</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>precedence-cutoff</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>source-quench</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>redirect</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>network-redirect</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>host-redirect</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>TOS-network-redirect</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>TOS host-redirect</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>echo-request</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>ping</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>router-advertisement</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>router-solicitation</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>time-exceeded</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>ttl-exceeded</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>ttl-zero-during-transit</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>ttl-zero-during-reassembly</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>parameter-problem</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>ip-header-bad</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>required-option-missing</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>timestamp-request</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>timestamp-reply</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>address-mask-request</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>address-mask-reply</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>packet-too-big</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <constraint>
- <regex>(any|echo-reply|pong|destination-unreachable|network-unreachable|host-unreachable|protocol-unreachable|port-unreachable|fragmentation-needed|source-route-failed|network-unknown|host-unknown|network-prohibited|host-prohibited|TOS-network-unreachable|TOS-host-unreachable|communication-prohibited|host-precedence-violation|precedence-cutoff|source-quench|redirect|network-redirect|host-redirect|TOS-network-redirect|TOS host-redirect|echo-request|ping|router-advertisement|router-solicitation|time-exceeded|ttl-exceeded|ttl-zero-during-transit|ttl-zero-during-reassembly|parameter-problem|ip-header-bad|required-option-missing|timestamp-request|timestamp-reply|address-mask-request|address-mask-reply|packet-too-big)</regex>
- <validator name="numeric" argument="--range 0-255"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
-</node>
-<!-- include end -->
diff --git a/interface-definitions/include/policy/route-common-rule.xml.i b/interface-definitions/include/policy/route-common.xml.i
index 35fccca50..6973d7a8f 100644
--- a/interface-definitions/include/policy/route-common-rule.xml.i
+++ b/interface-definitions/include/policy/route-common.xml.i
@@ -1,406 +1,360 @@
-<!-- include start from policy/route-common-rule.xml.i -->
-#include <include/policy/route-rule-action.xml.i>
-#include <include/generic-description.xml.i>
-<leafNode name="disable">
- <properties>
- <help>Option to disable firewall rule</help>
- <valueless/>
- </properties>
-</leafNode>
-<node name="fragment">
- <properties>
- <help>IP fragment match</help>
- </properties>
- <children>
- <leafNode name="match-frag">
- <properties>
- <help>Second and further fragments of fragmented packets</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="match-non-frag">
- <properties>
- <help>Head fragments or unfragmented packets</help>
- <valueless/>
- </properties>
- </leafNode>
- </children>
-</node>
-<node name="ipsec">
- <properties>
- <help>Inbound IPsec packets</help>
- </properties>
- <children>
- <leafNode name="match-ipsec">
- <properties>
- <help>Inbound IPsec packets</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="match-none">
- <properties>
- <help>Inbound non-IPsec packets</help>
- <valueless/>
- </properties>
- </leafNode>
- </children>
-</node>
-<node name="limit">
- <properties>
- <help>Rate limit using a token bucket filter</help>
- </properties>
- <children>
- <leafNode name="burst">
- <properties>
- <help>Maximum number of packets to allow in excess of rate</help>
- <valueHelp>
- <format>u32:0-4294967295</format>
- <description>Maximum number of packets to allow in excess of rate</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-4294967295"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="rate">
- <properties>
- <help>Maximum average matching rate</help>
- <valueHelp>
- <format>u32:0-4294967295</format>
- <description>Maximum average matching rate</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-4294967295"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
-</node>
-<leafNode name="log">
- <properties>
- <help>Option to log packets matching rule</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable log</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable log</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
-</leafNode>
-<leafNode name="protocol">
- <properties>
- <help>Protocol to match (protocol name, number, or "all")</help>
- <completionHelp>
- <script>cat /etc/protocols | sed -e '/^#.*/d' | awk '{ print $1 }'</script>
- </completionHelp>
- <valueHelp>
- <format>all</format>
- <description>All IP protocols</description>
- </valueHelp>
- <valueHelp>
- <format>tcp_udp</format>
- <description>Both TCP and UDP</description>
- </valueHelp>
- <valueHelp>
- <format>0-255</format>
- <description>IP protocol number</description>
- </valueHelp>
- <valueHelp>
- <format>!&lt;protocol&gt;</format>
- <description>IP protocol number</description>
- </valueHelp>
- <constraint>
- <validator name="ip-protocol"/>
- </constraint>
- </properties>
- <defaultValue>all</defaultValue>
-</leafNode>
-<node name="recent">
- <properties>
- <help>Parameters for matching recently seen sources</help>
- </properties>
- <children>
- <leafNode name="count">
- <properties>
- <help>Source addresses seen more than N times</help>
- <valueHelp>
- <format>u32:1-255</format>
- <description>Source addresses seen more than N times</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-255"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="time">
- <properties>
- <help>Source addresses seen in the last N seconds</help>
- <valueHelp>
- <format>u32:0-4294967295</format>
- <description>Source addresses seen in the last N seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-4294967295"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
-</node>
-<node name="set">
- <properties>
- <help>Packet modifications</help>
- </properties>
- <children>
- <leafNode name="dscp">
- <properties>
- <help>Packet Differentiated Services Codepoint (DSCP)</help>
- <valueHelp>
- <format>u32:0-63</format>
- <description>DSCP number</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-63"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="mark">
- <properties>
- <help>Packet marking</help>
- <valueHelp>
- <format>u32:1-2147483647</format>
- <description>Packet marking</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-2147483647"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="table">
- <properties>
- <help>Routing table to forward packet with</help>
- <valueHelp>
- <format>u32:1-200</format>
- <description>Table number</description>
- </valueHelp>
- <valueHelp>
- <format>main</format>
- <description>Main table</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-200"/>
- <regex>(main)</regex>
- </constraint>
- <completionHelp>
- <list>main</list>
- <path>protocols static table</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="tcp-mss">
- <properties>
- <help>TCP Maximum Segment Size</help>
- <valueHelp>
- <format>u32:500-1460</format>
- <description>Explicitly set TCP MSS value</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 500-1460"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
-</node>
-<node name="source">
- <properties>
- <help>Source parameters</help>
- </properties>
- <children>
- #include <include/firewall/address.xml.i>
- #include <include/firewall/source-destination-group.xml.i>
- <leafNode name="mac-address">
- <properties>
- <help>Source MAC address</help>
- <valueHelp>
- <format>&lt;MAC address&gt;</format>
- <description>MAC address to match</description>
- </valueHelp>
- <valueHelp>
- <format>!&lt;MAC address&gt;</format>
- <description>Match everything except the specified MAC address</description>
- </valueHelp>
- <constraint>
- <validator name="mac-address-firewall"/>
- </constraint>
- </properties>
- </leafNode>
- #include <include/firewall/port.xml.i>
- </children>
-</node>
-<node name="state">
- <properties>
- <help>Session state</help>
- </properties>
- <children>
- <leafNode name="established">
- <properties>
- <help>Established state</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="invalid">
- <properties>
- <help>Invalid state</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="new">
- <properties>
- <help>New state</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="related">
- <properties>
- <help>Related state</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- </leafNode>
- </children>
-</node>
-#include <include/firewall/tcp-flags.xml.i>
-<node name="time">
- <properties>
- <help>Time to match rule</help>
- </properties>
- <children>
- <leafNode name="monthdays">
- <properties>
- <help>Monthdays to match rule on</help>
- </properties>
- </leafNode>
- <leafNode name="startdate">
- <properties>
- <help>Date to start matching rule</help>
- </properties>
- </leafNode>
- <leafNode name="starttime">
- <properties>
- <help>Time of day to start matching rule</help>
- </properties>
- </leafNode>
- <leafNode name="stopdate">
- <properties>
- <help>Date to stop matching rule</help>
- </properties>
- </leafNode>
- <leafNode name="stoptime">
- <properties>
- <help>Time of day to stop matching rule</help>
- </properties>
- </leafNode>
- <leafNode name="utc">
- <properties>
- <help>Interpret times for startdate, stopdate, starttime and stoptime to be UTC</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="weekdays">
- <properties>
- <help>Weekdays to match rule on</help>
- </properties>
- </leafNode>
- </children>
-</node>
-<node name="icmp">
- <properties>
- <help>ICMP type and code information</help>
- </properties>
- <children>
- <leafNode name="code">
- <properties>
- <help>ICMP code (0-255)</help>
- <valueHelp>
- <format>u32:0-255</format>
- <description>ICMP code (0-255)</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-255"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="type">
- <properties>
- <help>ICMP type (0-255)</help>
- <valueHelp>
- <format>u32:0-255</format>
- <description>ICMP type (0-255)</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-255"/>
- </constraint>
- </properties>
- </leafNode>
- #include <include/firewall/icmp-type-name.xml.i>
- </children>
-</node>
-<!-- include end -->
+<!-- include start from policy/route-common.xml.i -->
+#include <include/policy/route-rule-action.xml.i>
+#include <include/generic-description.xml.i>
+<leafNode name="disable">
+ <properties>
+ <help>Option to disable firewall rule</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<node name="fragment">
+ <properties>
+ <help>IP fragment match</help>
+ </properties>
+ <children>
+ <leafNode name="match-frag">
+ <properties>
+ <help>Second and further fragments of fragmented packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="match-non-frag">
+ <properties>
+ <help>Head fragments or unfragmented packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="ipsec">
+ <properties>
+ <help>Inbound IPsec packets</help>
+ </properties>
+ <children>
+ <leafNode name="match-ipsec">
+ <properties>
+ <help>Inbound IPsec packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="match-none">
+ <properties>
+ <help>Inbound non-IPsec packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="limit">
+ <properties>
+ <help>Rate limit using a token bucket filter</help>
+ </properties>
+ <children>
+ <leafNode name="burst">
+ <properties>
+ <help>Maximum number of packets to allow in excess of rate</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>Maximum number of packets to allow in excess of rate</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="rate">
+ <properties>
+ <help>Maximum average matching rate</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>Maximum average matching rate</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<leafNode name="log">
+ <properties>
+ <help>Option to log packets matching rule</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable log</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable log</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+</leafNode>
+<leafNode name="protocol">
+ <properties>
+ <help>Protocol to match (protocol name, number, or "all")</help>
+ <completionHelp>
+ <script>cat /etc/protocols | sed -e '/^#.*/d' | awk '{ print $1 }'</script>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All IP protocols</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp_udp</format>
+ <description>Both TCP and UDP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>0-255</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!&lt;protocol&gt;</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-protocol"/>
+ </constraint>
+ </properties>
+ <defaultValue>all</defaultValue>
+</leafNode>
+<node name="recent">
+ <properties>
+ <help>Parameters for matching recently seen sources</help>
+ </properties>
+ <children>
+ <leafNode name="count">
+ <properties>
+ <help>Source addresses seen more than N times</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>Source addresses seen more than N times</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="time">
+ <properties>
+ <help>Source addresses seen in the last N seconds</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>Source addresses seen in the last N seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="set">
+ <properties>
+ <help>Packet modifications</help>
+ </properties>
+ <children>
+ <leafNode name="connection-mark">
+ <properties>
+ <help>Connection marking</help>
+ <valueHelp>
+ <format>u32:1-2147483647</format>
+ <description>Connection marking</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2147483647"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="dscp">
+ <properties>
+ <help>Packet Differentiated Services Codepoint (DSCP)</help>
+ <valueHelp>
+ <format>u32:0-63</format>
+ <description>DSCP number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-63"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="mark">
+ <properties>
+ <help>Packet marking</help>
+ <valueHelp>
+ <format>u32:1-2147483647</format>
+ <description>Packet marking</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2147483647"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="table">
+ <properties>
+ <help>Routing table to forward packet with</help>
+ <valueHelp>
+ <format>u32:1-200</format>
+ <description>Table number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>main</format>
+ <description>Main table</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-200"/>
+ <regex>(main)</regex>
+ </constraint>
+ <completionHelp>
+ <list>main</list>
+ <path>protocols static table</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="tcp-mss">
+ <properties>
+ <help>TCP Maximum Segment Size</help>
+ <valueHelp>
+ <format>u32:500-1460</format>
+ <description>Explicitly set TCP MSS value</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 500-1460"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="state">
+ <properties>
+ <help>Session state</help>
+ </properties>
+ <children>
+ <leafNode name="established">
+ <properties>
+ <help>Established state</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="invalid">
+ <properties>
+ <help>Invalid state</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="new">
+ <properties>
+ <help>New state</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="related">
+ <properties>
+ <help>Related state</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+#include <include/firewall/tcp-flags.xml.i>
+<node name="time">
+ <properties>
+ <help>Time to match rule</help>
+ </properties>
+ <children>
+ <leafNode name="monthdays">
+ <properties>
+ <help>Monthdays to match rule on</help>
+ </properties>
+ </leafNode>
+ <leafNode name="startdate">
+ <properties>
+ <help>Date to start matching rule</help>
+ </properties>
+ </leafNode>
+ <leafNode name="starttime">
+ <properties>
+ <help>Time of day to start matching rule</help>
+ </properties>
+ </leafNode>
+ <leafNode name="stopdate">
+ <properties>
+ <help>Date to stop matching rule</help>
+ </properties>
+ </leafNode>
+ <leafNode name="stoptime">
+ <properties>
+ <help>Time of day to stop matching rule</help>
+ </properties>
+ </leafNode>
+ <leafNode name="utc">
+ <properties>
+ <help>Interpret times for startdate, stopdate, starttime and stoptime to be UTC</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="weekdays">
+ <properties>
+ <help>Weekdays to match rule on</help>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/policy/route-ipv4.xml.i b/interface-definitions/include/policy/route-ipv4.xml.i
new file mode 100644
index 000000000..1f717a1a4
--- /dev/null
+++ b/interface-definitions/include/policy/route-ipv4.xml.i
@@ -0,0 +1,45 @@
+<!-- include start from policy/route-ipv4.xml.i -->
+<node name="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address.xml.i>
+ #include <include/firewall/source-destination-group.xml.i>
+ #include <include/firewall/mac-address.xml.i>
+ #include <include/firewall/port.xml.i>
+ </children>
+</node>
+<node name="icmp">
+ <properties>
+ <help>ICMP type and code information</help>
+ </properties>
+ <children>
+ <leafNode name="code">
+ <properties>
+ <help>ICMP code (0-255)</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>ICMP code (0-255)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>ICMP type (0-255)</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>ICMP type (0-255)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ #include <include/firewall/icmp-type-name.xml.i>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/policy/route-ipv6.xml.i b/interface-definitions/include/policy/route-ipv6.xml.i
new file mode 100644
index 000000000..d636a654b
--- /dev/null
+++ b/interface-definitions/include/policy/route-ipv6.xml.i
@@ -0,0 +1,196 @@
+<!-- include start from policy/route-ipv6.xml.i -->
+<node name="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address-ipv6.xml.i>
+ #include <include/firewall/source-destination-group.xml.i>
+ #include <include/firewall/mac-address.xml.i>
+ #include <include/firewall/port.xml.i>
+ </children>
+</node>
+<node name="icmpv6">
+ <properties>
+ <help>ICMPv6 type and code information</help>
+ </properties>
+ <children>
+ <leafNode name="type">
+ <properties>
+ <help>ICMP type-name</help>
+ <completionHelp>
+ <list>any echo-reply pong destination-unreachable network-unreachable host-unreachable protocol-unreachable port-unreachable fragmentation-needed source-route-failed network-unknown host-unknown network-prohibited host-prohibited TOS-network-unreachable TOS-host-unreachable communication-prohibited host-precedence-violation precedence-cutoff source-quench redirect network-redirect host-redirect TOS-network-redirect TOS host-redirect echo-request ping router-advertisement router-solicitation time-exceeded ttl-exceeded ttl-zero-during-transit ttl-zero-during-reassembly parameter-problem ip-header-bad required-option-missing timestamp-request timestamp-reply address-mask-request address-mask-reply packet-too-big</list>
+ </completionHelp>
+ <valueHelp>
+ <format>any</format>
+ <description>Any ICMP type/code</description>
+ </valueHelp>
+ <valueHelp>
+ <format>echo-reply</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>pong</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>destination-unreachable</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>network-unreachable</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>host-unreachable</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>protocol-unreachable</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>port-unreachable</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>fragmentation-needed</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>source-route-failed</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>network-unknown</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>host-unknown</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>network-prohibited</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>host-prohibited</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>TOS-network-unreachable</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>TOS-host-unreachable</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>communication-prohibited</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>host-precedence-violation</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>precedence-cutoff</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>source-quench</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>redirect</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>network-redirect</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>host-redirect</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>TOS-network-redirect</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>TOS host-redirect</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>echo-request</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ping</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>router-advertisement</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>router-solicitation</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>time-exceeded</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ttl-exceeded</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ttl-zero-during-transit</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ttl-zero-during-reassembly</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>parameter-problem</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ip-header-bad</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>required-option-missing</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>timestamp-request</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>timestamp-reply</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>address-mask-request</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>address-mask-reply</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>packet-too-big</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <constraint>
+ <regex>(any|echo-reply|pong|destination-unreachable|network-unreachable|host-unreachable|protocol-unreachable|port-unreachable|fragmentation-needed|source-route-failed|network-unknown|host-unknown|network-prohibited|host-prohibited|TOS-network-unreachable|TOS-host-unreachable|communication-prohibited|host-precedence-violation|precedence-cutoff|source-quench|redirect|network-redirect|host-redirect|TOS-network-redirect|TOS host-redirect|echo-request|ping|router-advertisement|router-solicitation|time-exceeded|ttl-exceeded|ttl-zero-during-transit|ttl-zero-during-reassembly|parameter-problem|ip-header-bad|required-option-missing|timestamp-request|timestamp-reply|address-mask-request|address-mask-reply|packet-too-big)</regex>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- 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..fa16a6cb0
--- /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>&lt;number&gt;</format>
+ <description>Bits per second</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;number&gt;bit</format>
+ <description>Bits per second</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;number&gt;kbit</format>
+ <description>Kilobits per second</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;number&gt;mbit</format>
+ <description>Megabits per second</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;number&gt;gbit</format>
+ <description>Gigabits per second</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;number&gt;tbit</format>
+ <description>Terabits per second</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;number&gt;%%</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..cc923f642 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>&lt;number&gt;</format>
- <description>Rate in kbit (kilobit per second)</description>
+ <description>Bits per second</description>
</valueHelp>
<valueHelp>
- <format>&lt;number&gt;&lt;suffix&gt;</format>
- <description>Rate with scaling suffix (mbit, mbps, ...)</description>
+ <format>&lt;number&gt;bit</format>
+ <description>Bits per second</description>
</valueHelp>
+ <valueHelp>
+ <format>&lt;number&gt;kbit</format>
+ <description>Kilobits per second</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;number&gt;mbit</format>
+ <description>Megabits per second</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;number&gt;gbit</format>
+ <description>Gigabits per second</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;number&gt;tbit</format>
+ <description>Terabits per second</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;number&gt;%</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..4ba12f8f7 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>
@@ -195,11 +151,11 @@
<properties>
<help>Match on mark applied by firewall</help>
<valueHelp>
- <format>txt</format>
+ <format>u32</format>
<description>FW mark to match</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 0x0-0xffff"/>
+ <validator name="numeric" argument="--range 0-4294967295"/>
</constraint>
</properties>
</leafNode>
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/radius-acct-server-ipv4.xml.i b/interface-definitions/include/radius-acct-server-ipv4.xml.i
new file mode 100644
index 000000000..9365aa8e9
--- /dev/null
+++ b/interface-definitions/include/radius-acct-server-ipv4.xml.i
@@ -0,0 +1,26 @@
+<!-- include start from radius-acct-server-ipv4.xml.i -->
+<node name="radius">
+ <properties>
+ <help>RADIUS accounting for users OpenConnect VPN sessions OpenConnect authentication mode radius</help>
+ </properties>
+ <children>
+ <tagNode name="server">
+ <properties>
+ <help>RADIUS server configuration</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>RADIUS server IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/generic-disable-node.xml.i>
+ #include <include/radius-server-key.xml.i>
+ #include <include/radius-server-acct-port.xml.i>
+ </children>
+ </tagNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/radius-server-ipv4.xml.i b/interface-definitions/include/radius-auth-server-ipv4.xml.i
index ab4c8e10e..dc6f4d878 100644
--- a/interface-definitions/include/radius-server-ipv4.xml.i
+++ b/interface-definitions/include/radius-auth-server-ipv4.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from radius-server-ipv4.xml.i -->
+<!-- include start from radius-auth-server-ipv4.xml.i -->
<node name="radius">
<properties>
<help>RADIUS based user authentication</help>
@@ -19,7 +19,7 @@
<children>
#include <include/generic-disable-node.xml.i>
#include <include/radius-server-key.xml.i>
- #include <include/radius-server-port.xml.i>
+ #include <include/radius-server-auth-port.xml.i>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/include/radius-server-acct-port.xml.i b/interface-definitions/include/radius-server-acct-port.xml.i
new file mode 100644
index 000000000..0b356fa18
--- /dev/null
+++ b/interface-definitions/include/radius-server-acct-port.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from radius-server-acct-port.xml.i -->
+<leafNode name="port">
+ <properties>
+ <help>Accounting port</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Numeric IP port</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>1813</defaultValue>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/radius-server-port.xml.i b/interface-definitions/include/radius-server-auth-port.xml.i
index c6b691a0f..660fa540f 100644
--- a/interface-definitions/include/radius-server-port.xml.i
+++ b/interface-definitions/include/radius-server-auth-port.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from radius-server-port.xml.i -->
+<!-- include start from radius-server-auth-port.xml.i -->
<leafNode name="port">
<properties>
<help>Authentication port</help>
diff --git a/interface-definitions/include/radius-server-ipv4-ipv6.xml.i b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i
index 5b12bec62..c593512b4 100644
--- a/interface-definitions/include/radius-server-ipv4-ipv6.xml.i
+++ b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i
@@ -23,7 +23,7 @@
<children>
#include <include/generic-disable-node.xml.i>
#include <include/radius-server-key.xml.i>
- #include <include/radius-server-port.xml.i>
+ #include <include/radius-server-auth-port.xml.i>
</children>
</tagNode>
<leafNode name="source-address">
diff --git a/interface-definitions/include/radius-timeout.xml.i b/interface-definitions/include/radius-timeout.xml.i
new file mode 100644
index 000000000..22bb6d312
--- /dev/null
+++ b/interface-definitions/include/radius-timeout.xml.i
@@ -0,0 +1,16 @@
+<!-- include start from radius-timeout.xml.i -->
+<leafNode name="timeout">
+ <properties>
+ <help>Session timeout</help>
+ <valueHelp>
+ <format>u32:1-240</format>
+ <description>Session timeout in seconds (default: 2)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-240"/>
+ </constraint>
+ <constraintErrorMessage>Timeout must be between 1 and 240 seconds</constraintErrorMessage>
+ </properties>
+ <defaultValue>2</defaultValue>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/rip/interface.xml.i b/interface-definitions/include/rip/interface.xml.i
index baeceac1c..e0792cdc1 100644
--- a/interface-definitions/include/rip/interface.xml.i
+++ b/interface-definitions/include/rip/interface.xml.i
@@ -10,7 +10,7 @@
<description>Interface name</description>
</valueHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
<children>
diff --git a/interface-definitions/include/routing-passive-interface.xml.i b/interface-definitions/include/routing-passive-interface.xml.i
index 095b683de..fe229aebe 100644
--- a/interface-definitions/include/routing-passive-interface.xml.i
+++ b/interface-definitions/include/routing-passive-interface.xml.i
@@ -16,7 +16,7 @@
</valueHelp>
<constraint>
<regex>(default)</regex>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
<multi/>
</properties>
diff --git a/interface-definitions/include/server-ipv4-fqdn.xml.i b/interface-definitions/include/server-ipv4-fqdn.xml.i
new file mode 100644
index 000000000..7bab9812c
--- /dev/null
+++ b/interface-definitions/include/server-ipv4-fqdn.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from server-ipv4-fqdn.xml.i -->
+<leafNode name="server">
+ <properties>
+ <help>Remote server to connect to</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Server IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>hostname</format>
+ <description>Server hostname/FQDN</description>
+ </valueHelp>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/source-interface.xml.i b/interface-definitions/include/source-interface.xml.i
index a9c2a0f9d..4c1fddb57 100644
--- a/interface-definitions/include/source-interface.xml.i
+++ b/interface-definitions/include/source-interface.xml.i
@@ -10,7 +10,7 @@
<script>${vyos_completion_dir}/list_interfaces.py</script>
</completionHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/static/static-route-interface.xml.i b/interface-definitions/include/static/static-route-interface.xml.i
index ed4f455e5..cc7a92612 100644
--- a/interface-definitions/include/static/static-route-interface.xml.i
+++ b/interface-definitions/include/static/static-route-interface.xml.i
@@ -10,7 +10,7 @@
<description>Gateway interface name</description>
</valueHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/static/static-route.xml.i b/interface-definitions/include/static/static-route.xml.i
index 2de5dc58f..aeb2044c9 100644
--- a/interface-definitions/include/static/static-route.xml.i
+++ b/interface-definitions/include/static/static-route.xml.i
@@ -14,6 +14,7 @@
#include <include/static/static-route-blackhole.xml.i>
#include <include/static/static-route-reject.xml.i>
#include <include/dhcp-interface.xml.i>
+ #include <include/generic-description.xml.i>
<tagNode name="interface">
<properties>
<help>Next-hop IPv4 router interface</help>
@@ -25,7 +26,7 @@
<description>Gateway interface name</description>
</valueHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
<children>
diff --git a/interface-definitions/include/static/static-route6.xml.i b/interface-definitions/include/static/static-route6.xml.i
index 35feef41c..d5e7a25bc 100644
--- a/interface-definitions/include/static/static-route6.xml.i
+++ b/interface-definitions/include/static/static-route6.xml.i
@@ -13,6 +13,7 @@
<children>
#include <include/static/static-route-blackhole.xml.i>
#include <include/static/static-route-reject.xml.i>
+ #include <include/generic-description.xml.i>
<tagNode name="interface">
<properties>
<help>IPv6 gateway interface name</help>
@@ -24,7 +25,7 @@
<description>Gateway interface name</description>
</valueHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
<children>
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/firewall-version.xml.i b/interface-definitions/include/version/firewall-version.xml.i
index 065925319..bc04f8d51 100644
--- a/interface-definitions/include/version/firewall-version.xml.i
+++ b/interface-definitions/include/version/firewall-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/firewall-version.xml.i -->
-<syntaxVersion component='firewall' version='8'></syntaxVersion>
+<syntaxVersion component='firewall' version='9'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/https-version.xml.i b/interface-definitions/include/version/https-version.xml.i
index 586083649..111076974 100644
--- a/interface-definitions/include/version/https-version.xml.i
+++ b/interface-definitions/include/version/https-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/https-version.xml.i -->
-<syntaxVersion component='https' version='3'></syntaxVersion>
+<syntaxVersion component='https' version='4'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/ipsec-version.xml.i b/interface-definitions/include/version/ipsec-version.xml.i
index 1c978e8e6..8d019b466 100644
--- a/interface-definitions/include/version/ipsec-version.xml.i
+++ b/interface-definitions/include/version/ipsec-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/ipsec-version.xml.i -->
-<syntaxVersion component='ipsec' version='10'></syntaxVersion>
+<syntaxVersion component='ipsec' version='11'></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/policy-version.xml.i b/interface-definitions/include/version/policy-version.xml.i
index 89bde20c7..f1494eaa3 100644
--- a/interface-definitions/include/version/policy-version.xml.i
+++ b/interface-definitions/include/version/policy-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/policy-version.xml.i -->
-<syntaxVersion component='policy' version='4'></syntaxVersion>
+<syntaxVersion component='policy' version='5'></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/include/version/snmp-version.xml.i b/interface-definitions/include/version/snmp-version.xml.i
index 0416288f0..fa58672a5 100644
--- a/interface-definitions/include/version/snmp-version.xml.i
+++ b/interface-definitions/include/version/snmp-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/snmp-version.xml.i -->
-<syntaxVersion component='snmp' version='2'></syntaxVersion>
+<syntaxVersion component='snmp' version='3'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/vrrp/garp.xml.i b/interface-definitions/include/vrrp/garp.xml.i
new file mode 100644
index 000000000..b56b490df
--- /dev/null
+++ b/interface-definitions/include/vrrp/garp.xml.i
@@ -0,0 +1,78 @@
+<!-- include start from vrrp/garp.xml.i -->
+<node name="garp">
+ <properties>
+ <help>Gratuitous ARP parameters</help>
+ </properties>
+ <children>
+ <leafNode name="interval">
+ <properties>
+ <help>Interval between Gratuitous ARP</help>
+ <valueHelp>
+ <format>&lt;0.000-1000&gt;</format>
+ <description>Interval in seconds, resolution microseconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0.000-1000 --float"/>
+ </constraint>
+ </properties>
+ <defaultValue>0</defaultValue>
+ </leafNode>
+ <leafNode name="master-delay">
+ <properties>
+ <help>Delay for second set of gratuitous ARPs after transition to master</help>
+ <valueHelp>
+ <format>u32:1-1000</format>
+ <description>Delay in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-1000"/>
+ </constraint>
+ </properties>
+ <defaultValue>5</defaultValue>
+ </leafNode>
+ <leafNode name="master-refresh">
+ <properties>
+ <help>Minimum time interval for refreshing gratuitous ARPs while beeing master</help>
+ <valueHelp>
+ <format>u32:0</format>
+ <description>No refresh</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>Interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ <defaultValue>5</defaultValue>
+ </leafNode>
+ <leafNode name="master-refresh-repeat">
+ <properties>
+ <help>Number of gratuitous ARP messages to send at a time while beeing master</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>Number of gratuitous ARP messages</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ <defaultValue>1</defaultValue>
+ </leafNode>
+ <leafNode name="master-repeat">
+ <properties>
+ <help>Number of gratuitous ARP messages to send at a time after transition to master</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>Number of gratuitous ARP messages</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ <defaultValue>5</defaultValue>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in
index 41e4a68a8..6e8c5283a 100644
--- a/interface-definitions/interfaces-bonding.xml.in
+++ b/interface-definitions/interfaces-bonding.xml.in
@@ -49,14 +49,13 @@
</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>
#include <include/interface/disable.xml.i>
#include <include/interface/vrf.xml.i>
#include <include/interface/mirror.xml.i>
- #include <include/interface/interface-policy.xml.i>
<leafNode name="hash-policy">
<properties>
<help>Bonding transmit hash policy</help>
@@ -200,7 +199,7 @@
<description>Interface name</description>
</valueHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
<multi/>
</properties>
@@ -219,7 +218,7 @@
<description>Interface name</description>
</valueHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in
index 1e11cd4c6..1636411ec 100644
--- a/interface-definitions/interfaces-bridge.xml.in
+++ b/interface-definitions/interfaces-bridge.xml.in
@@ -34,14 +34,13 @@
</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>
#include <include/interface/disable.xml.i>
#include <include/interface/vrf.xml.i>
#include <include/interface/mtu-68-16000.xml.i>
- #include <include/interface/interface-policy.xml.i>
<leafNode name="forwarding-delay">
<properties>
<help>Forwarding delay</help>
@@ -151,7 +150,7 @@
<description>VLAN id range allowed on this interface (use '-' as delimiter)</description>
</valueHelp>
<constraint>
- <validator name="allowed-vlan"/>
+ <validator name="numeric" argument="--allow-range --range 1-4094"/>
</constraint>
<constraintErrorMessage>not a valid VLAN ID value or range</constraintErrorMessage>
<multi/>
diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in
index fb36741f7..00784fcdf 100644
--- a/interface-definitions/interfaces-dummy.xml.in
+++ b/interface-definitions/interfaces-dummy.xml.in
@@ -17,17 +17,35 @@
</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/interface-policy.xml.i>
<node name="ip">
<properties>
<help>IPv4 routing parameters</help>
</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 77f130e1c..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">
@@ -31,7 +31,6 @@
</leafNode>
#include <include/interface/disable-link-detect.xml.i>
#include <include/interface/disable.xml.i>
- #include <include/interface/interface-policy.xml.i>
<leafNode name="duplex">
<properties>
<help>Duplex mode</help>
diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in
index b959c787d..ac9794870 100644
--- a/interface-definitions/interfaces-geneve.xml.in
+++ b/interface-definitions/interfaces-geneve.xml.in
@@ -17,13 +17,12 @@
</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>
#include <include/interface/mac.xml.i>
#include <include/interface/mtu-1450-16000.xml.i>
- #include <include/interface/interface-policy.xml.i>
<node name="parameters">
<properties>
<help>GENEVE tunnel parameters</help>
diff --git a/interface-definitions/interfaces-input.xml.in b/interface-definitions/interfaces-input.xml.in
index d01c760f8..d90cf936f 100644
--- a/interface-definitions/interfaces-input.xml.in
+++ b/interface-definitions/interfaces-input.xml.in
@@ -17,9 +17,8 @@
</valueHelp>
</properties>
<children>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
- #include <include/interface/interface-policy.xml.i>
#include <include/interface/redirect.xml.i>
</children>
</tagNode>
diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in
index bde68dd5a..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>
@@ -32,7 +32,6 @@
<defaultValue>5000</defaultValue>
</leafNode>
#include <include/interface/disable.xml.i>
- #include <include/interface/interface-policy.xml.i>
<leafNode name="encapsulation">
<properties>
<help>Encapsulation type</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 5c9f4cd76..4b4f9149d 100644
--- a/interface-definitions/interfaces-macsec.xml.in
+++ b/interface-definitions/interfaces-macsec.xml.in
@@ -21,7 +21,6 @@
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
- #include <include/interface/interface-policy.xml.i>
#include <include/interface/mirror.xml.i>
<node name="security">
<properties>
@@ -116,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 3876e31da..63272a25f 100644
--- a/interface-definitions/interfaces-openvpn.xml.in
+++ b/interface-definitions/interfaces-openvpn.xml.in
@@ -33,8 +33,7 @@
</leafNode>
</children>
</node>
- #include <include/interface/description.xml.i>
- #include <include/interface/interface-policy.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 84f76a7ee..c6fd7096b 100644
--- a/interface-definitions/interfaces-pppoe.xml.in
+++ b/interface-definitions/interfaces-pppoe.xml.in
@@ -19,11 +19,10 @@
#include <include/pppoe-access-concentrator.xml.i>
#include <include/interface/authentication.xml.i>
#include <include/interface/dial-on-demand.xml.i>
- #include <include/interface/interface-policy.xml.i>
#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>
@@ -38,6 +37,19 @@
<constraintErrorMessage>Timeout must be in range 0 to 86400</constraintErrorMessage>
</properties>
</leafNode>
+ <leafNode name="host-uniq">
+ <properties>
+ <help>PPPoE RFC2516 host-uniq tag</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Host-uniq tag as byte string in HEX</description>
+ </valueHelp>
+ <constraint>
+ <regex>([a-fA-F0-9][a-fA-F0-9]){1,18}</regex>
+ </constraint>
+ <constraintErrorMessage>Host-uniq must be specified as hex-adecimal byte-string (even number of HEX characters)</constraintErrorMessage>
+ </properties>
+ </leafNode>
<node name="ip">
<properties>
<help>IPv4 routing parameters</help>
@@ -83,12 +95,7 @@
<leafNode name="mtu">
<defaultValue>1492</defaultValue>
</leafNode>
- <leafNode name="no-peer-dns">
- <properties>
- <help>Do not use DNS servers provided by the peer</help>
- <valueless/>
- </properties>
- </leafNode>
+ #include <include/interface/no-peer-dns.xml.i>
<leafNode name="remote-address">
<properties>
<help>IPv4 address of remote end of the PPPoE link</help>
diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in
index 4eb9bf111..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>
@@ -28,7 +28,6 @@
#include <include/source-interface-ethernet.xml.i>
#include <include/interface/mac.xml.i>
#include <include/interface/mirror.xml.i>
- #include <include/interface/interface-policy.xml.i>
<leafNode name="mode">
<properties>
<help>Receive mode (default: private)</help>
diff --git a/interface-definitions/interfaces-sstpc.xml.in b/interface-definitions/interfaces-sstpc.xml.in
new file mode 100644
index 000000000..b569e9bde
--- /dev/null
+++ b/interface-definitions/interfaces-sstpc.xml.in
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="interfaces">
+ <children>
+ <tagNode name="sstpc" owner="${vyos_conf_scripts_dir}/interfaces-sstpc.py">
+ <properties>
+ <help>Secure Socket Tunneling Protocol (SSTP) client Interface</help>
+ <priority>460</priority>
+ <constraint>
+ <regex>sstpc[0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>Secure Socket Tunneling Protocol interface must be named sstpcN</constraintErrorMessage>
+ <valueHelp>
+ <format>sstpcN</format>
+ <description>Secure Socket Tunneling Protocol interface name</description>
+ </valueHelp>
+ </properties>
+ <children>
+ #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>
+ #include <include/interface/default-route-distance.xml.i>
+ #include <include/interface/no-peer-dns.xml.i>
+ #include <include/interface/mtu-68-1500.xml.i>
+ <leafNode name="mtu">
+ <defaultValue>1452</defaultValue>
+ </leafNode>
+ #include <include/server-ipv4-fqdn.xml.i>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>443</defaultValue>
+ </leafNode>
+ <node name="ssl">
+ <properties>
+ <help>Secure Sockets Layer (SSL) configuration</help>
+ </properties>
+ <children>
+ #include <include/pki/ca-certificate.xml.i>
+ </children>
+ </node>
+ #include <include/interface/vrf.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in
index fe49d337a..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>
@@ -29,7 +29,6 @@
#include <include/source-address-ipv4-ipv6.xml.i>
#include <include/interface/tunnel-remote.xml.i>
#include <include/source-interface.xml.i>
- #include <include/interface/interface-policy.xml.i>
<leafNode name="6rd-prefix">
<properties>
<help>6rd network prefix</help>
diff --git a/interface-definitions/interfaces-virtual-ethernet.xml.in b/interface-definitions/interfaces-virtual-ethernet.xml.in
new file mode 100644
index 000000000..864f658da
--- /dev/null
+++ b/interface-definitions/interfaces-virtual-ethernet.xml.in
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="interfaces">
+ <children>
+ <tagNode name="virtual-ethernet" owner="${vyos_conf_scripts_dir}/interfaces-virtual-ethernet.py">
+ <properties>
+ <help>Virtual Ethernet (veth) Interface</help>
+ <priority>300</priority>
+ <constraint>
+ <regex>veth[0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>Virutal Ethernet interface must be named vethN</constraintErrorMessage>
+ <valueHelp>
+ <format>vethN</format>
+ <description>Virtual Ethernet interface name</description>
+ </valueHelp>
+ </properties>
+ <children>
+ #include <include/interface/address-ipv4-ipv6-dhcp.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>
+ #include <include/interface/vrf.xml.i>
+ <leafNode name="peer-name">
+ <properties>
+ <help>Virtual ethernet peer interface name</help>
+ <completionHelp>
+ <path>interfaces virtual-ethernet</path>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Name of peer interface</description>
+ </valueHelp>
+ <constraint>
+ <regex>veth[0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>Virutal Ethernet interface must be named vethN</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in
index eeaea0dc3..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>
@@ -25,7 +25,6 @@
#include <include/interface/mirror.xml.i>
#include <include/interface/redirect.xml.i>
#include <include/interface/vrf.xml.i>
- #include <include/interface/interface-policy.xml.i>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in
index 4902ff36d..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>
@@ -54,7 +54,6 @@
#include <include/interface/mac.xml.i>
#include <include/interface/mtu-1200-16000.xml.i>
#include <include/interface/mirror.xml.i>
- #include <include/interface/interface-policy.xml.i>
<leafNode name="mtu">
<defaultValue>1450</defaultValue>
</leafNode>
diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in
index 23f50d146..6342b21cf 100644
--- a/interface-definitions/interfaces-wireguard.xml.in
+++ b/interface-definitions/interfaces-wireguard.xml.in
@@ -17,11 +17,10 @@
</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>
- #include <include/interface/interface-policy.xml.i>
#include <include/interface/mirror.xml.i>
<leafNode name="mtu">
<defaultValue>1420</defaultValue>
diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in
index 9e7fc29bc..a9538d577 100644
--- a/interface-definitions/interfaces-wireless.xml.in
+++ b/interface-definitions/interfaces-wireless.xml.in
@@ -20,7 +20,6 @@
</properties>
<children>
#include <include/interface/address-ipv4-ipv6-dhcp.xml.i>
- #include <include/interface/interface-policy.xml.i>
<node name="capabilities">
<properties>
<help>HT and VHT capabilities for your card</help>
@@ -468,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">
@@ -726,7 +725,7 @@
<constraintErrorMessage>Invalid WPA pass phrase, must be 8 to 63 printable characters!</constraintErrorMessage>
</properties>
</leafNode>
- #include <include/radius-server-ipv4.xml.i>
+ #include <include/radius-auth-server-ipv4.xml.i>
<node name="radius">
<children>
<tagNode name="server">
diff --git a/interface-definitions/interfaces-wwan.xml.in b/interface-definitions/interfaces-wwan.xml.in
index b0b8367dc..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>
@@ -39,7 +39,6 @@
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
#include <include/interface/dial-on-demand.xml.i>
- #include <include/interface/interface-policy.xml.i>
#include <include/interface/redirect.xml.i>
#include <include/interface/vrf.xml.i>
</children>
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/policy-route.xml.in b/interface-definitions/policy-route.xml.in
index f480f3bd5..d7b159839 100644
--- a/interface-definitions/policy-route.xml.in
+++ b/interface-definitions/policy-route.xml.in
@@ -12,6 +12,7 @@
</properties>
<children>
#include <include/generic-description.xml.i>
+ #include <include/generic-interface-multi.xml.i>
#include <include/firewall/enable-default-log.xml.i>
<tagNode name="rule">
<properties>
@@ -46,10 +47,12 @@
#include <include/firewall/port.xml.i>
</children>
</node>
- #include <include/policy/route-common-rule-ipv6.xml.i>
+ #include <include/policy/route-common.xml.i>
+ #include <include/policy/route-ipv6.xml.i>
#include <include/firewall/dscp.xml.i>
#include <include/firewall/packet-length.xml.i>
#include <include/firewall/hop-limit.xml.i>
+ #include <include/firewall/connection-mark.xml.i>
</children>
</tagNode>
</children>
@@ -64,6 +67,7 @@
</properties>
<children>
#include <include/generic-description.xml.i>
+ #include <include/generic-interface-multi.xml.i>
#include <include/firewall/enable-default-log.xml.i>
<tagNode name="rule">
<properties>
@@ -98,10 +102,12 @@
#include <include/firewall/port.xml.i>
</children>
</node>
- #include <include/policy/route-common-rule.xml.i>
+ #include <include/policy/route-common.xml.i>
+ #include <include/policy/route-ipv4.xml.i>
#include <include/firewall/dscp.xml.i>
#include <include/firewall/packet-length.xml.i>
#include <include/firewall/ttl.xml.i>
+ #include <include/firewall/connection-mark.xml.i>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in
index 6c60276d5..b3745fda0 100644
--- a/interface-definitions/policy.xml.in
+++ b/interface-definitions/policy.xml.in
@@ -1356,6 +1356,26 @@
</leafNode>
</children>
</node>
+ <node name="l3vpn-nexthop">
+ <properties>
+ <help>Next hop Information</help>
+ </properties>
+ <children>
+ <node name="encapsulation">
+ <properties>
+ <help>Encapsulation options (for BGP only)</help>
+ </properties>
+ <children>
+ <leafNode name="gre">
+ <properties>
+ <help>Accept L3VPN traffic over GRE encapsulation</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
<leafNode name="local-preference">
<properties>
<help>BGP local preference attribute</help>
diff --git a/interface-definitions/protocols-failover.xml.in b/interface-definitions/protocols-failover.xml.in
new file mode 100644
index 000000000..900c76eab
--- /dev/null
+++ b/interface-definitions/protocols-failover.xml.in
@@ -0,0 +1,114 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="protocols">
+ <children>
+ <node name="failover" owner="${vyos_conf_scripts_dir}/protocols_failover.py">
+ <properties>
+ <help>Failover Routing</help>
+ <priority>490</priority>
+ </properties>
+ <children>
+ <tagNode name="route">
+ <properties>
+ <help>Failover IPv4 route</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 failover route</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ </constraint>
+ </properties>
+ <children>
+ <tagNode name="next-hop">
+ <properties>
+ <help>Next-hop IPv4 router address</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Next-hop router address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ <children>
+ <node name="check">
+ <properties>
+ <help>Check target options</help>
+ </properties>
+ <children>
+ #include <include/port-number.xml.i>
+ <leafNode name="target">
+ <properties>
+ <help>Check target address</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Address to check</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="timeout">
+ <properties>
+ <help>Timeout between checks</help>
+ <valueHelp>
+ <format>u32:1-300</format>
+ <description>Timeout in seconds between checks</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ <defaultValue>10</defaultValue>
+ </leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>Check type</help>
+ <completionHelp>
+ <list>arp icmp tcp</list>
+ </completionHelp>
+ <valueHelp>
+ <format>arp</format>
+ <description>Check target by ARP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>icmp</format>
+ <description>Check target by ICMP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp</format>
+ <description>Check target by TCP</description>
+ </valueHelp>
+ <constraint>
+ <regex>(arp|icmp|tcp)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>icmp</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ #include <include/static/static-route-interface.xml.i>
+ <leafNode name="metric">
+ <properties>
+ <help>Route metric for this gateway</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>Route metric</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ <defaultValue>1</defaultValue>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/protocols-rip.xml.in b/interface-definitions/protocols-rip.xml.in
index 2195b0316..33aae5015 100644
--- a/interface-definitions/protocols-rip.xml.in
+++ b/interface-definitions/protocols-rip.xml.in
@@ -39,7 +39,7 @@
<script>${vyos_completion_dir}/list_interfaces.py</script>
</completionHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
<children>
diff --git a/interface-definitions/protocols-ripng.xml.in b/interface-definitions/protocols-ripng.xml.in
index d7e4b2514..cd35dbf53 100644
--- a/interface-definitions/protocols-ripng.xml.in
+++ b/interface-definitions/protocols-ripng.xml.in
@@ -40,7 +40,7 @@
<script>${vyos_completion_dir}/list_interfaces.py</script>
</completionHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
<children>
diff --git a/interface-definitions/protocols-rpki.xml.in b/interface-definitions/protocols-rpki.xml.in
index 4535d3990..0098cacb6 100644
--- a/interface-definitions/protocols-rpki.xml.in
+++ b/interface-definitions/protocols-rpki.xml.in
@@ -51,7 +51,7 @@
<properties>
<help>RPKI SSH known hosts file</help>
<constraint>
- <validator name="file-exists"/>
+ <validator name="file-path"/>
</constraint>
</properties>
</leafNode>
@@ -59,7 +59,7 @@
<properties>
<help>RPKI SSH private key file</help>
<constraint>
- <validator name="file-exists"/>
+ <validator name="file-path"/>
</constraint>
</properties>
</leafNode>
@@ -67,7 +67,7 @@
<properties>
<help>RPKI SSH public key file path</help>
<constraint>
- <validator name="file-exists"/>
+ <validator name="file-path"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/protocols-static-arp.xml.in b/interface-definitions/protocols-static-arp.xml.in
index 8b1b3b5e1..52caf435a 100644
--- a/interface-definitions/protocols-static-arp.xml.in
+++ b/interface-definitions/protocols-static-arp.xml.in
@@ -20,7 +20,7 @@
<description>Interface name</description>
</valueHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
<children>
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 e2dbcbeef..757c1f856 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">
@@ -16,7 +17,7 @@
<description>Interface name</description>
</valueHelp>
<constraint>
- <validator name="interface-name"/>
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
<children>
@@ -24,54 +25,137 @@
<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>
- <description>QoS Policy name</description>
+ <description>QoS policy to use</description>
</valueHelp>
+ <constraint>
+ <regex>[[:alnum:]][-_[:alnum:]]*</regex>
+ </constraint>
+ <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="egress">
<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>
- <description>QoS Policy name</description>
+ <description>QoS policy to use</description>
</valueHelp>
+ <constraint>
+ <regex>[[:alnum:]][-_[:alnum:]]*</regex>
+ </constraint>
+ <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage>
</properties>
</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 +209,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 +223,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 +255,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 +269,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 +287,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 +305,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 +320,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 +333,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>&lt;number&gt;</format>
<description>Percentage of packets affected</description>
@@ -270,10 +343,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 +359,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>&lt;number&gt;</format>
<description>Percentage of packets affected</description>
@@ -315,6 +388,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 +406,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 +420,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 +446,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 +488,7 @@
</constraint>
<constraintErrorMessage>Mark probability must be greater than 0</constraintErrorMessage>
</properties>
+ <defaultValue>10</defaultValue>
</leafNode>
<leafNode name="maximum-threshold">
<properties>
@@ -426,6 +502,7 @@
</constraint>
<constraintErrorMessage>Threshold must be between 0 and 4096</constraintErrorMessage>
</properties>
+ <defaultValue>18</defaultValue>
</leafNode>
<leafNode name="minimum-threshold">
<properties>
@@ -457,8 +534,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 +555,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 +580,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 +600,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 +637,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 +652,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 +685,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 +735,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 +753,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-ipoe-server.xml.in b/interface-definitions/service-ipoe-server.xml.in
index ef8569437..d778f9de0 100644
--- a/interface-definitions/service-ipoe-server.xml.in
+++ b/interface-definitions/service-ipoe-server.xml.in
@@ -220,7 +220,7 @@
#include <include/accel-ppp/radius-additions-rate-limit.xml.i>
</children>
</node>
- #include <include/radius-server-ipv4.xml.i>
+ #include <include/radius-auth-server-ipv4.xml.i>
#include <include/accel-ppp/radius-additions.xml.i>
</children>
</node>
diff --git a/interface-definitions/service-pppoe-server.xml.in b/interface-definitions/service-pppoe-server.xml.in
index b31109296..68592b96b 100644
--- a/interface-definitions/service-pppoe-server.xml.in
+++ b/interface-definitions/service-pppoe-server.xml.in
@@ -20,7 +20,7 @@
#include <include/accel-ppp/auth-local-users.xml.i>
#include <include/accel-ppp/auth-mode.xml.i>
#include <include/accel-ppp/auth-protocols.xml.i>
- #include <include/radius-server-ipv4.xml.i>
+ #include <include/radius-auth-server-ipv4.xml.i>
#include <include/accel-ppp/radius-additions.xml.i>
<node name="radius">
<children>
@@ -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/service-router-advert.xml.in b/interface-definitions/service-router-advert.xml.in
index 87ec512d6..8b7364a8c 100644
--- a/interface-definitions/service-router-advert.xml.in
+++ b/interface-definitions/service-router-advert.xml.in
@@ -305,6 +305,19 @@
</leafNode>
</children>
</tagNode>
+ <leafNode name="source-address">
+ <properties>
+ <help>Use IPv6 address as source address. Useful with VRRP.</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address to be advertized (must be configured on interface)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
<leafNode name="reachable-time">
<properties>
<help>Time, in milliseconds, that a node assumes a neighbor is reachable after having received a reachability confirmation</help>
diff --git a/interface-definitions/service-upnp.xml.in b/interface-definitions/service-upnp.xml.in
index ec23d87df..79d8ae42e 100644
--- a/interface-definitions/service-upnp.xml.in
+++ b/interface-definitions/service-upnp.xml.in
@@ -24,7 +24,7 @@
<script>${vyos_completion_dir}/list_interfaces.py</script>
</completionHelp>
<constraint>
- <validator name="interface-name" />
+ #include <include/constraint/interface-name.xml.in>
</constraint>
</properties>
</leafNode>
@@ -119,7 +119,7 @@
</valueHelp>
<multi/>
<constraint>
- <validator name="interface-name" />
+ #include <include/constraint/interface-name.xml.in>
<validator name="ipv4-address"/>
<validator name="ipv4-prefix"/>
<validator name="ipv6-address"/>
diff --git a/interface-definitions/snmp.xml.in b/interface-definitions/snmp.xml.in
index b4f72589e..10dd828a5 100644
--- a/interface-definitions/snmp.xml.in
+++ b/interface-definitions/snmp.xml.in
@@ -13,9 +13,9 @@
<properties>
<help>Community name</help>
<constraint>
- <regex>[a-zA-Z0-9\-_]{1,100}</regex>
+ <regex>[a-zA-Z0-9\-_!@*#]{1,100}</regex>
</constraint>
- <constraintErrorMessage>Community string is limited to alphanumerical characters only with a total lenght of 100</constraintErrorMessage>
+ <constraintErrorMessage>Community string is limited to alphanumerical characters, !, @, * and # with a total lenght of 100</constraintErrorMessage>
</properties>
<children>
<leafNode name="authorization">
@@ -123,18 +123,31 @@
</leafNode>
<leafNode name="oid-enable">
<properties>
- <help>Enable specific OIDs</help>
+ <help>Enable specific OIDs that by default are disable</help>
<completionHelp>
- <list>route-table</list>
+ <list>ip-forward ip-route-table ip-net-to-media-table ip-net-to-physical-phys-address</list>
</completionHelp>
<valueHelp>
- <format>route-table</format>
- <description>Enable routing table OIDs (ipCidrRouteTable inetCidrRouteTable)</description>
+ <format>ip-forward</format>
+ <description>Enable ipForward: .1.3.6.1.2.1.4.24</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ip-route-table</format>
+ <description>Enable ipRouteTable: .1.3.6.1.2.1.4.21</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ip-net-to-media-table</format>
+ <description>Enable ipNetToMediaTable: .1.3.6.1.2.1.4.22</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ip-net-to-physical-phys-address</format>
+ <description>Enable ipNetToPhysicalPhysAddress: .1.3.6.1.2.1.4.35</description>
</valueHelp>
<constraint>
- <regex>(route-table)</regex>
+ <regex>(ip-forward|ip-route-table|ip-net-to-media-table|ip-net-to-physical-phys-address)</regex>
</constraint>
- <constraintErrorMessage>OID must be 'route-table'</constraintErrorMessage>
+ <constraintErrorMessage>OID must be one of the liste options</constraintErrorMessage>
+ <multi/>
</properties>
</leafNode>
#include <include/snmp/protocol.xml.i>
diff --git a/interface-definitions/ssh.xml.in b/interface-definitions/ssh.xml.in
index f3c731fe5..2bcce2cf0 100644
--- a/interface-definitions/ssh.xml.in
+++ b/interface-definitions/ssh.xml.in
@@ -133,6 +133,19 @@
</leafNode>
</children>
</node>
+ <leafNode name="hostkey-algorithm">
+ <properties>
+ <help>Allowed host key signature algorithms</help>
+ <completionHelp>
+ <!-- generated by ssh -Q HostKeyAlgorithms | tr '\n' ' ' as this will not change dynamically -->
+ <list>ssh-ed25519 ssh-ed25519-cert-v01@openssh.com sk-ssh-ed25519@openssh.com sk-ssh-ed25519-cert-v01@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512 ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 sk-ecdsa-sha2-nistp256@openssh.com webauthn-sk-ecdsa-sha2-nistp256@openssh.com ssh-rsa-cert-v01@openssh.com rsa-sha2-256-cert-v01@openssh.com rsa-sha2-512-cert-v01@openssh.com ssh-dss-cert-v01@openssh.com ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp384-cert-v01@openssh.com ecdsa-sha2-nistp521-cert-v01@openssh.com sk-ecdsa-sha2-nistp256-cert-v01@openssh.com</list>
+ </completionHelp>
+ <multi/>
+ <constraint>
+ <regex>(ssh-ed25519|ssh-ed25519-cert-v01@openssh.com|sk-ssh-ed25519@openssh.com|sk-ssh-ed25519-cert-v01@openssh.com|ssh-rsa|rsa-sha2-256|rsa-sha2-512|ssh-dss|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|sk-ecdsa-sha2-nistp256@openssh.com|webauthn-sk-ecdsa-sha2-nistp256@openssh.com|ssh-rsa-cert-v01@openssh.com|rsa-sha2-256-cert-v01@openssh.com|rsa-sha2-512-cert-v01@openssh.com|ssh-dss-cert-v01@openssh.com|ecdsa-sha2-nistp256-cert-v01@openssh.com|ecdsa-sha2-nistp384-cert-v01@openssh.com|ecdsa-sha2-nistp521-cert-v01@openssh.com|sk-ecdsa-sha2-nistp256-cert-v01@openssh.com)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
<leafNode name="key-exchange">
<properties>
<help>Allowed key exchange (KEX) algorithms</help>
diff --git a/interface-definitions/system-config-mgmt.xml.in b/interface-definitions/system-config-mgmt.xml.in
new file mode 100644
index 000000000..1f852d284
--- /dev/null
+++ b/interface-definitions/system-config-mgmt.xml.in
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="system">
+ <children>
+ <node name="config-management" owner="${vyos_conf_scripts_dir}/config_mgmt.py">
+ <properties>
+ <help>Configuration management settings</help>
+ <priority>400</priority>
+ </properties>
+ <children>
+ <node name="commit-archive">
+ <properties>
+ <help>Commit archive settings</help>
+ </properties>
+ <children>
+ <leafNode name="location">
+ <properties>
+ <help>Commit archive location</help>
+ <valueHelp>
+ <format>uri</format>
+ <description>Uniform Resource Identifier</description>
+ </valueHelp>
+ <constraint>
+ <validator name="url --file-transport"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="source-address">
+ <properties>
+ <help>Source address or interface for archive server connections</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ #include <include/constraint/interface-name.xml.in>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="commit-revisions">
+ <properties>
+ <help>Commit revisions</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Number of config backups to keep</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ <constraintErrorMessage>Number of revisions must be between 0 and 65535</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in
index def42544a..e71a647ef 100644
--- a/interface-definitions/system-login.xml.in
+++ b/interface-definitions/system-login.xml.in
@@ -127,32 +127,44 @@
</leafNode>
<leafNode name="type">
<properties>
- <help>Public key type</help>
+ <help>SSH public key type</help>
<completionHelp>
- <list>ssh-dss ssh-rsa ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519</list>
+ <list>ssh-dss ssh-rsa ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 sk-ecdsa-sha2-nistp256@openssh.com sk-ssh-ed25519@openssh.com</list>
</completionHelp>
<valueHelp>
<format>ssh-dss</format>
- <description/>
+ <description>Digital Signature Algorithm (DSA) key support</description>
</valueHelp>
<valueHelp>
<format>ssh-rsa</format>
- <description/>
+ <description>Key pair based on RSA algorithm</description>
</valueHelp>
<valueHelp>
<format>ecdsa-sha2-nistp256</format>
- <description/>
+ <description>Elliptic Curve DSA with NIST P-256 curve</description>
</valueHelp>
<valueHelp>
<format>ecdsa-sha2-nistp384</format>
- <description/>
+ <description>Elliptic Curve DSA with NIST P-384 curve</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ecdsa-sha2-nistp521</format>
+ <description>Elliptic Curve DSA with NIST P-521 curve</description>
</valueHelp>
<valueHelp>
<format>ssh-ed25519</format>
- <description/>
+ <description>Edwards-curve DSA with elliptic curve 25519</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sk-ecdsa-sha2-nistp256@openssh.com</format>
+ <description>Elliptic Curve DSA security key</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sk-ssh-ed25519@openssh.com</format>
+ <description>Elliptic curve 25519 security key</description>
</valueHelp>
<constraint>
- <regex>(ssh-dss|ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|ssh-ed25519)</regex>
+ <regex>(ssh-dss|ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|ssh-ed25519|sk-ecdsa-sha2-nistp256@openssh.com|sk-ssh-ed25519@openssh.com)</regex>
</constraint>
</properties>
</leafNode>
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 4776c53dc..9d20926ec 100644
--- a/interface-definitions/vpn-ipsec.xml.in
+++ b/interface-definitions/vpn-ipsec.xml.in
@@ -11,6 +11,40 @@
<priority>901</priority>
</properties>
<children>
+ <node name="authentication">
+ <properties>
+ <help>Authentication</help>
+ </properties>
+ <children>
+ <tagNode name="psk">
+ <properties>
+ <help>Pre-shared key name</help>
+ </properties>
+ <children>
+ #include <include/dhcp-interface-multi.xml.i>
+ <leafNode name="id">
+ <properties>
+ <help>ID for authentication</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>ID used for authentication</description>
+ </valueHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="secret">
+ <properties>
+ <help>IKE pre-shared secret key</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>IKE pre-shared secret key</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
<leafNode name="disable-uniqreqids">
<properties>
<help>Disable requirement for unique IDs in the Security Database</help>
@@ -465,6 +499,45 @@
</properties>
<defaultValue>2</defaultValue>
</leafNode>
+ <leafNode name="prf">
+ <properties>
+ <help>Pseudo-Random Functions</help>
+ <completionHelp>
+ <list>prfmd5 prfsha1 prfaesxcbc prfaescmac prfsha256 prfsha384 prfsha512</list>
+ </completionHelp>
+ <valueHelp>
+ <format>prfmd5</format>
+ <description>MD5 PRF</description>
+ </valueHelp>
+ <valueHelp>
+ <format>prfsha1</format>
+ <description>SHA1 PRF</description>
+ </valueHelp>
+ <valueHelp>
+ <format>prfaesxcbc</format>
+ <description>AES XCBC PRF</description>
+ </valueHelp>
+ <valueHelp>
+ <format>prfaescmac</format>
+ <description>AES CMAC PRF</description>
+ </valueHelp>
+ <valueHelp>
+ <format>prfsha256</format>
+ <description>SHA2_256 PRF</description>
+ </valueHelp>
+ <valueHelp>
+ <format>prfsha384</format>
+ <description>SHA2_384 PRF</description>
+ </valueHelp>
+ <valueHelp>
+ <format>prfsha512</format>
+ <description>SHA2_512 PRF</description>
+ </valueHelp>
+ <constraint>
+ <regex>(prfmd5|prfsha1|prfaesxcbc|prfaescmac|prfsha256|prfsha384|prfsha512)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
#include <include/vpn-ipsec-encryption.xml.i>
#include <include/vpn-ipsec-hash.xml.i>
</children>
@@ -884,10 +957,11 @@
#include <include/name-server-ipv4-ipv6.xml.i>
</children>
</tagNode>
- #include <include/radius-server-ipv4.xml.i>
+ #include <include/radius-auth-server-ipv4.xml.i>
<node name="radius">
<children>
#include <include/radius-nas-identifier.xml.i>
+ #include <include/radius-timeout.xml.i>
<tagNode name="server">
<children>
#include <include/accel-ppp/radius-additions-disable-accounting.xml.i>
@@ -947,7 +1021,6 @@
</constraint>
</properties>
</leafNode>
- #include <include/ipsec/authentication-pre-shared-secret.xml.i>
<leafNode name="remote-id">
<properties>
<help>ID for remote authentication</help>
@@ -956,6 +1029,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 cb5900e0d..0a92017bd 100644
--- a/interface-definitions/vpn-l2tp.xml.in
+++ b/interface-definitions/vpn-l2tp.xml.in
@@ -178,7 +178,7 @@
#include <include/accel-ppp/ppp-mppe.xml.i>
#include <include/accel-ppp/auth-mode.xml.i>
#include <include/accel-ppp/auth-local-users.xml.i>
- #include <include/radius-server-ipv4.xml.i>
+ #include <include/radius-auth-server-ipv4.xml.i>
<node name="radius">
<children>
<tagNode name="server">
@@ -230,6 +230,7 @@
<properties>
<help>Port for Dynamic Authorization Extension server (DM/CoA)</help>
</properties>
+ <defaultValue>1700</defaultValue>
</leafNode>
<leafNode name="secret">
<properties>
@@ -250,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 3b3a83bd4..a426f604d 100644
--- a/interface-definitions/vpn-openconnect.xml.in
+++ b/interface-definitions/vpn-openconnect.xml.in
@@ -8,6 +8,27 @@
<priority>901</priority>
</properties>
<children>
+ <node name="accounting">
+ <properties>
+ <help>Accounting for users OpenConnect VPN Sessions</help>
+ </properties>
+ <children>
+ <node name="mode">
+ <properties>
+ <help>Accounting mode used by this server</help>
+ </properties>
+ <children>
+ <leafNode name="radius">
+ <properties>
+ <help>Use RADIUS server for accounting</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ #include <include/radius-acct-server-ipv4.xml.i>
+ </children>
+ </node>
<node name="authentication">
<properties>
<help>Authentication for remote access SSL VPN Server</help>
@@ -137,23 +158,10 @@
</tagNode>
</children>
</node>
- #include <include/radius-server-ipv4.xml.i>
+ #include <include/radius-auth-server-ipv4.xml.i>
<node name="radius">
<children>
- <leafNode name="timeout">
- <properties>
- <help>Session timeout</help>
- <valueHelp>
- <format>u32:1-240</format>
- <description>Session timeout in seconds (default: 2)</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-240"/>
- </constraint>
- <constraintErrorMessage>Timeout must be between 1 and 240 seconds</constraintErrorMessage>
- </properties>
- <defaultValue>2</defaultValue>
- </leafNode>
+ #include <include/radius-timeout.xml.i>
<leafNode name="groupconfig">
<properties>
<help>If the groupconfig option is set, then config-per-user will be overriden, and all configuration will be read from RADIUS.</help>
@@ -163,7 +171,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/vpn-pptp.xml.in b/interface-definitions/vpn-pptp.xml.in
index 5e52965fd..00ffd26f9 100644
--- a/interface-definitions/vpn-pptp.xml.in
+++ b/interface-definitions/vpn-pptp.xml.in
@@ -108,7 +108,7 @@
</tagNode>
</children>
</node>
- #include <include/radius-server-ipv4.xml.i>
+ #include <include/radius-auth-server-ipv4.xml.i>
#include <include/accel-ppp/radius-additions.xml.i>
#include <include/accel-ppp/radius-additions-rate-limit.xml.i>
</children>
diff --git a/interface-definitions/vpn-sstp.xml.in b/interface-definitions/vpn-sstp.xml.in
index 195d581df..9e912063f 100644
--- a/interface-definitions/vpn-sstp.xml.in
+++ b/interface-definitions/vpn-sstp.xml.in
@@ -16,7 +16,7 @@
#include <include/accel-ppp/auth-local-users.xml.i>
#include <include/accel-ppp/auth-mode.xml.i>
#include <include/accel-ppp/auth-protocols.xml.i>
- #include <include/radius-server-ipv4.xml.i>
+ #include <include/radius-auth-server-ipv4.xml.i>
#include <include/accel-ppp/radius-additions.xml.i>
<node name="radius">
<children>
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/connect.xml.in b/op-mode-definitions/connect.xml.in
index d0c93195c..116cd6231 100644
--- a/op-mode-definitions/connect.xml.in
+++ b/op-mode-definitions/connect.xml.in
@@ -20,6 +20,7 @@
<help>Bring up a connection-oriented network interface</help>
<completionHelp>
<path>interfaces pppoe</path>
+ <path>interfaces sstpc</path>
<path>interfaces wwan</path>
</completionHelp>
</properties>
diff --git a/op-mode-definitions/container.xml.in b/op-mode-definitions/container.xml.in
index 97a087ce2..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>
@@ -69,7 +69,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo podman build --layers --force-rm --tag "$4" $6</command>
+ <command>sudo podman build --net host --layers --force-rm --tag "$4" $6</command>
</tagNode>
</children>
</tagNode>
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 241cca0ce..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}/show_dhcp.py --leases</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>
@@ -82,34 +82,34 @@
<properties>
<help>Show DHCPv6 server leases</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases</command>
+ <command>sudo ${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6</command>
<children>
<tagNode name="pool">
<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/disconnect.xml.in b/op-mode-definitions/disconnect.xml.in
index 4415c0ed2..843998c4f 100644
--- a/op-mode-definitions/disconnect.xml.in
+++ b/op-mode-definitions/disconnect.xml.in
@@ -10,6 +10,7 @@
<help>Take down a connection-oriented network interface</help>
<completionHelp>
<path>interfaces pppoe</path>
+ <path>interfaces sstpc</path>
<path>interfaces wwan</path>
</completionHelp>
</properties>
diff --git a/op-mode-definitions/generate-interfaces-debug-archive.xml.in b/op-mode-definitions/generate-interfaces-debug-archive.xml.in
new file mode 100644
index 000000000..5e4f4daad
--- /dev/null
+++ b/op-mode-definitions/generate-interfaces-debug-archive.xml.in
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="generate">
+ <children>
+ <node name="interfaces">
+ <properties>
+ <help>Interface specific commands</help>
+ </properties>
+ <children>
+ <node name="debug-archive">
+ <properties>
+ <help>Generate interfaces debug-archive</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/generate_interfaces_debug_archive.py</command>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/generate-ipsec-debug-archive.xml.in b/op-mode-definitions/generate-ipsec-debug-archive.xml.in
index f268d5ae5..a9ce113d1 100644
--- a/op-mode-definitions/generate-ipsec-debug-archive.xml.in
+++ b/op-mode-definitions/generate-ipsec-debug-archive.xml.in
@@ -8,7 +8,7 @@
<properties>
<help>Generate IPSec debug-archive</help>
</properties>
- <command>${vyos_op_scripts_dir}/generate_ipsec_debug_archive.sh</command>
+ <command>sudo ${vyos_op_scripts_dir}/generate_ipsec_debug_archive.py</command>
</node>
</children>
</node>
diff --git a/op-mode-definitions/generate-openvpn-config-client.xml.in b/op-mode-definitions/generate-openvpn-config-client.xml.in
index 4f9f31bfe..baec0842b 100644
--- a/op-mode-definitions/generate-openvpn-config-client.xml.in
+++ b/op-mode-definitions/generate-openvpn-config-client.xml.in
@@ -16,7 +16,7 @@
<properties>
<help>Local interface used for connection</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --type openvpn</script>
+ <path>interfaces openvpn</path>
</completionHelp>
</properties>
<children>
diff --git a/op-mode-definitions/generate-system-login-user.xml.in b/op-mode-definitions/generate-system-login-user.xml.in
new file mode 100755
index 000000000..d0519b6bd
--- /dev/null
+++ b/op-mode-definitions/generate-system-login-user.xml.in
@@ -0,0 +1,90 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="generate">
+ <children>
+ <node name="system">
+ <properties>
+ <help>Generate system related parameters</help>
+ </properties>
+ <children>
+ <node name="login">
+ <properties>
+ <help>Generate system login related parameters</help>
+ </properties>
+ <children>
+ <tagNode name="username">
+ <properties>
+ <help>Username used for authentication</help>
+ <completionHelp>
+ <list>&lt;username&gt;</list>
+ </completionHelp>
+ </properties>
+ <children>
+ <node name="otp-key">
+ <properties>
+ <help>Generate OpenConnect OTP token</help>
+ </properties>
+ <children>
+ <node name="hotp-time">
+ <properties>
+ <help>HOTP time-based token</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5"</command>
+ <children>
+ <tagNode name="rate-limit">
+ <properties>
+ <help>Duration of single time interval</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate_limit "$9"</command>
+ <children>
+ <tagNode name="rate-time">
+ <properties>
+ <help>The number of digits in the one-time password</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate_limit "$9" --rate_time "${11}" </command>
+ <children>
+ <tagNode name="window-size">
+ <properties>
+ <help>The number of digits in the one-time password</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate_limit "$9" --rate_time "${11}" --window_size "${13}"</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="window-size">
+ <properties>
+ <help>The number of digits in the one-time password</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --window_size "${9}"</command>
+ <children>
+ <tagNode name="rate-limit">
+ <properties>
+ <help>Duration of single time interval</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate_limit "${11}" --window_size "${9}"</command>
+ <children>
+ <tagNode name="rate-time">
+ <properties>
+ <help>Duration of single time interval</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate_limit "${11}" --rate_time "${13}" --window_size "${9}"</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/generate-wireguard.xml.in b/op-mode-definitions/generate-wireguard.xml.in
index 259c9a898..6c01619be 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>
@@ -37,7 +19,7 @@
<properties>
<help>Local interface used for connection</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --type wireguard</script>
+ <path>interfaces wireguard</path>
</completionHelp>
</properties>
<children>
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/ipv6-route.xml.in b/op-mode-definitions/ipv6-route.xml.in
index d75caf308..46e416a8a 100644
--- a/op-mode-definitions/ipv6-route.xml.in
+++ b/op-mode-definitions/ipv6-route.xml.in
@@ -26,7 +26,7 @@
<properties>
<help>Show IPv6 neighbor table for specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py -b</script>
+ <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
</completionHelp>
</properties>
<command>${vyos_op_scripts_dir}/neighbor.py show --family inet6 --interface "$5"</command>
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 01462ad8f..ec428a676 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -93,6 +93,12 @@
</properties>
<command>journalctl --no-hostname --boot --follow --unit uacctd.service</command>
</leafNode>
+ <leafNode name="ipoe-server">
+ <properties>
+ <help>Monitor last lines of IPoE server log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit accel-ppp@ipoe.service</command>
+ </leafNode>
<leafNode name="kernel">
<properties>
<help>Monitor last lines of Linux Kernel log</help>
@@ -101,13 +107,19 @@
</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>
- <help>Monitor last lines of PPPoE log</help>
+ <help>Monitor last lines of PPPoE interface log</help>
</properties>
<command>journalctl --no-hostname --boot --follow --unit "ppp@pppoe*.service"</command>
<children>
@@ -115,13 +127,19 @@
<properties>
<help>Monitor last lines of PPPoE log for specific interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py -t pppoe</script>
+ <path>interfaces pppoe</path>
</completionHelp>
</properties>
- <command>journalctl --no-hostname --boot --follow --unit "ppp@$6.service"</command>
+ <command>journalctl --no-hostname --boot --follow --unit "ppp@$5.service"</command>
</tagNode>
</children>
</node>
+ <leafNode name="pppoe-server">
+ <properties>
+ <help>Monitor last lines of PPPoE server log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit accel-ppp@pppoe.service</command>
+ </leafNode>
<node name="protocol">
<properties>
<help>Monitor log for Routing Protocol</help>
@@ -205,13 +223,19 @@
<properties>
<help>Monitor last lines of specific MACsec interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py -t macsec</script>
+ <path>interfaces macsec</path>
</completionHelp>
</properties>
<command>SRC=$(cli-shell-api returnValue interfaces macsec "$5" source-interface); journalctl --no-hostname --boot --follow --unit "wpa_supplicant-macsec@$SRC.service"</command>
</tagNode>
</children>
</node>
+ <leafNode name="router-advert">
+ <properties>
+ <help>Monitor last lines of Router Advertisement Daemon (radvd)</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit radvd.service</command>
+ </leafNode>
<leafNode name="snmp">
<properties>
<help>Monitor last lines of Simple Network Monitoring Protocol (SNMP)</help>
@@ -224,16 +248,33 @@
</properties>
<command>journalctl --no-hostname --boot --follow --unit ssh.service</command>
</leafNode>
+ <node name="sstpc">
+ <properties>
+ <help>Monitor last lines of SSTP client log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit "ppp@sstpc*.service"</command>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Monitor last lines of SSTP client log for specific interface</help>
+ <completionHelp>
+ <path>interfaces sstpc</path>
+ </completionHelp>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit "ppp@$5.service"</command>
+ </tagNode>
+ </children>
+ </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>
@@ -247,6 +288,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 ce0544390..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>&lt;x.x.x.x&gt;</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}/show_nat_statistics.py --destination</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>&lt;x.x.x.x&gt;</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>&lt;h:h:h:h:h:h:h:h&gt;</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>&lt;h:h:h:h:h:h:h:h&gt;</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 301688271..0a2657398 100644
--- a/op-mode-definitions/openvpn.xml.in
+++ b/op-mode-definitions/openvpn.xml.in
@@ -20,10 +20,10 @@
<properties>
<help>Reset OpenVPN process on interface</help>
<completionHelp>
- <script>sudo ${vyos_completion_dir}/list_interfaces.py --type openvpn</script>
+ <path>interfaces openvpn</path>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/reset_openvpn.py $4</command>
+ <command>sudo ${vyos_op_scripts_dir}/openvpn.py reset --interface $4</command>
</tagNode>
</children>
</node>
@@ -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>
@@ -50,10 +51,10 @@
<properties>
<help>Show OpenVPN interface information</help>
<completionHelp>
- <script>sudo ${vyos_completion_dir}/list_interfaces.py --type openvpn</script>
+ <path>interfaces openvpn</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</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>
@@ -109,19 +110,19 @@
<properties>
<help>Show tunnel status for OpenVPN client interfaces</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/show_openvpn.py --mode=client</command>
+ <command>sudo ${vyos_op_scripts_dir}/openvpn.py show --mode client</command>
</leafNode>
<leafNode name="server">
<properties>
<help>Show tunnel status for OpenVPN server interfaces</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/show_openvpn.py --mode=server</command>
+ <command>sudo ${vyos_op_scripts_dir}/openvpn.py show --mode server</command>
</leafNode>
<leafNode name="site-to-site">
<properties>
<help>Show tunnel status for OpenVPN site-to-site interfaces</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/show_openvpn.py --mode=site-to-site</command>
+ <command>sudo ${vyos_op_scripts_dir}/openvpn.py show --mode site-to-site</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/show-acceleration.xml.in b/op-mode-definitions/show-acceleration.xml.in
index d0dcea2d6..6fd3babf5 100644
--- a/op-mode-definitions/show-acceleration.xml.in
+++ b/op-mode-definitions/show-acceleration.xml.in
@@ -29,13 +29,13 @@
<properties>
<help>Intel QAT flows</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_acceleration.py --flow --dev $6</command>
+ <command>sudo ${vyos_op_scripts_dir}/show_acceleration.py --flow --dev $6</command>
</node>
<node name="config">
<properties>
<help>Intel QAT configuration</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_acceleration.py --conf --dev $6</command>
+ <command>sudo ${vyos_op_scripts_dir}/show_acceleration.py --conf --dev $6</command>
</node>
</children>
</tagNode>
@@ -43,16 +43,16 @@
<properties>
<help>Intel QAT status</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_acceleration.py --status</command>
+ <command>sudo ${vyos_op_scripts_dir}/show_acceleration.py --status</command>
</node>
<node name="interrupts">
<properties>
<help>Intel QAT interrupts</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_acceleration.py --interrupts</command>
+ <command>sudo ${vyos_op_scripts_dir}/show_acceleration.py --interrupts</command>
</node>
</children>
- <command>${vyos_op_scripts_dir}/show_acceleration.py --hw</command>
+ <command>sudo ${vyos_op_scripts_dir}/show_acceleration.py --hw</command>
</node>
</children>
</node>
diff --git a/op-mode-definitions/show-arp.xml.in b/op-mode-definitions/show-arp.xml.in
index 8662549fc..3680c20c6 100644
--- a/op-mode-definitions/show-arp.xml.in
+++ b/op-mode-definitions/show-arp.xml.in
@@ -12,7 +12,7 @@
<properties>
<help>Show Address Resolution Protocol (ARP) cache for specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py -b</script>
+ <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
</completionHelp>
</properties>
<command>${vyos_op_scripts_dir}/neighbor.py show --family inet --interface "$4"</command>
diff --git a/op-mode-definitions/show-bridge.xml.in b/op-mode-definitions/show-bridge.xml.in
index dd2a28931..e7a646fdc 100644
--- a/op-mode-definitions/show-bridge.xml.in
+++ b/op-mode-definitions/show-bridge.xml.in
@@ -25,7 +25,7 @@
<properties>
<help>Show bridge information for a given bridge interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --type bridge</script>
+ <path>interfaces bridge</path>
</completionHelp>
</properties>
<command>bridge -c link show | grep "master $3"</command>
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
new file mode 100644
index 000000000..a619a9fd2
--- /dev/null
+++ b/op-mode-definitions/show-interfaces-sstpc.xml.in
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="interfaces">
+ <children>
+ <tagNode name="sstpc">
+ <properties>
+ <help>Show specified SSTP client interface information</help>
+ <completionHelp>
+ <path>interfaces sstpc</path>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=sstpc</command>
+ <children>
+ <leafNode name="log">
+ <properties>
+ <help>Show specified SSTP client interface log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit "ppp@$4".service</command>
+ </leafNode>
+ <leafNode name="statistics">
+ <properties>
+ <help>Show specified SSTP client interface statistics</help>
+ <completionHelp>
+ <path>interfaces sstpc</path>
+ </completionHelp>
+ </properties>
+ <command>if [ -d "/sys/class/net/$4" ]; then /usr/sbin/pppstats "$4"; fi</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="sstpc">
+ <properties>
+ <help>Show SSTP client interface information</help>
+ </properties>
+ <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}/interfaces.py show --intf_type=sstpc</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
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
new file mode 100644
index 000000000..c743492fb
--- /dev/null
+++ b/op-mode-definitions/show-interfaces-virtual-ethernet.xml.in
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="interfaces">
+ <children>
+ <tagNode name="virtual-ethernet">
+ <properties>
+ <help>Show specified virtual-ethernet interface information</help>
+ <completionHelp>
+ <path>interfaces virtual-ethernet</path>
+ </completionHelp>
+ </properties>
+ <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}/interfaces.py show_summary --intf_name="$4" --intf_type=virtual-ethernet</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="virtual-ethernet">
+ <properties>
+ <help>Show virtual-ethernet interface information</help>
+ </properties>
+ <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}/interfaces.py show --intf_type=virtual-ethernet</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
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..75b0cc88e 100644
--- a/op-mode-definitions/show-interfaces-wireguard.xml.in
+++ b/op-mode-definitions/show-interfaces-wireguard.xml.in
@@ -8,10 +8,10 @@
<properties>
<help>Show specified WireGuard interface information</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --type wireguard</script>
+ <path>interfaces wireguard</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=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..cdd591f82 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>
@@ -28,16 +28,16 @@
<properties>
<help>Show specified wireless interface information</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --type wireless</script>
+ <path>interfaces wireless</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=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-multicast.xml.in b/op-mode-definitions/show-ip-multicast.xml.in
index 80d83b424..605d61e8d 100644
--- a/op-mode-definitions/show-ip-multicast.xml.in
+++ b/op-mode-definitions/show-ip-multicast.xml.in
@@ -13,13 +13,7 @@
<properties>
<help>Show multicast interfaces</help>
</properties>
- <command>if ps -C igmpproxy &amp;&gt;/dev/null; then ${vyos_op_scripts_dir}/show_igmpproxy.py --interface; else echo IGMP proxy not configured; fi</command>
- </leafNode>
- <leafNode name="mfc">
- <properties>
- <help>Show multicast fowarding cache</help>
- </properties>
- <command>if ps -C igmpproxy &amp;&gt;/dev/null; then ${vyos_op_scripts_dir}/show_igmpproxy.py --mfc; else echo IGMP proxy not configured; fi</command>
+ <command>${vyos_op_scripts_dir}/igmp-proxy.py show_interface</command>
</leafNode>
<leafNode name="summary">
<properties>
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-ip.xml.in b/op-mode-definitions/show-ip.xml.in
index 0751c50cb..a710e33d2 100644
--- a/op-mode-definitions/show-ip.xml.in
+++ b/op-mode-definitions/show-ip.xml.in
@@ -17,7 +17,7 @@
<properties>
<help>Show IPv4 neighbor table for specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py -b</script>
+ <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
</completionHelp>
</properties>
<command>${vyos_op_scripts_dir}/neighbor.py show --family inet --interface "$5"</command>
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 8906d9ef3..f5e5b1493 100644
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -196,6 +196,12 @@
</tagNode>
</children>
</tagNode>
+ <leafNode name="ipoe-server">
+ <properties>
+ <help>Show log for IPoE server</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit accel-ppp@ipoe.service</command>
+ </leafNode>
<leafNode name="kernel">
<properties>
<help>Show log for Linux Kernel</help>
@@ -204,7 +210,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 +222,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>
@@ -230,7 +242,7 @@
<properties>
<help>Show MACsec log on specific interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py -t macsec</script>
+ <path>interfaces macsec</path>
</completionHelp>
</properties>
<command>SRC=$(cli-shell-api returnValue interfaces macsec "$5" source-interface); journalctl --no-hostname --boot --unit "wpa_supplicant-macsec@$SRC.service"</command>
@@ -256,7 +268,7 @@
</node>
<node name="pppoe">
<properties>
- <help>Show log for PPPoE</help>
+ <help>Show log for PPPoE interface</help>
</properties>
<command>journalctl --no-hostname --boot --unit "ppp@pppoe*.service"</command>
<children>
@@ -264,13 +276,19 @@
<properties>
<help>Show PPPoE log on specific interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py -t pppoe</script>
+ <path>interfaces pppoe</path>
</completionHelp>
</properties>
- <command>journalctl --no-hostname --boot --unit "ppp@$6.service"</command>
+ <command>journalctl --no-hostname --boot --unit "ppp@$5.service"</command>
</tagNode>
</children>
</node>
+ <leafNode name="pppoe-server">
+ <properties>
+ <help>Show log for PPPoE server</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit accel-ppp@pppoe.service</command>
+ </leafNode>
<node name="protocol">
<properties>
<help>Show log for Routing Protocol</help>
@@ -344,6 +362,12 @@
</leafNode>
</children>
</node>
+ <leafNode name="router-advert">
+ <properties>
+ <help>Show log for Router Advertisement Daemon (radvd)</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit radvd.service</command>
+ </leafNode>
<leafNode name="snmp">
<properties>
<help>Show log for Simple Network Monitoring Protocol (SNMP)</help>
@@ -356,6 +380,23 @@
</properties>
<command>journalctl --no-hostname --boot --unit ssh.service</command>
</leafNode>
+ <node name="sstpc">
+ <properties>
+ <help>Show log for SSTP client</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit "ppp@sstpc*.service"</command>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Show SSTP client log on specific interface</help>
+ <completionHelp>
+ <path>interfaces sstpc</path>
+ </completionHelp>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit "ppp@$5.service"</command>
+ </tagNode>
+ </children>
+ </node>
<tagNode name="tail">
<properties>
<help>Show last n changes to messages</help>
@@ -380,7 +421,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>
@@ -394,6 +435,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/show-protocols.xml.in b/op-mode-definitions/show-protocols.xml.in
index 698001b76..27146f90d 100644
--- a/op-mode-definitions/show-protocols.xml.in
+++ b/op-mode-definitions/show-protocols.xml.in
@@ -22,7 +22,7 @@
<properties>
<help>Show Address Resolution Protocol (ARP) cache for specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py -b</script>
+ <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
</completionHelp>
</properties>
<command>/usr/sbin/arp -e -n -i "$6"</command>
diff --git a/op-mode-definitions/show-raid.xml.in b/op-mode-definitions/show-raid.xml.in
index 8bf394552..2ae3fad6a 100644
--- a/op-mode-definitions/show-raid.xml.in
+++ b/op-mode-definitions/show-raid.xml.in
@@ -9,7 +9,7 @@
<script>${vyos_completion_dir}/list_raidset.sh</script>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_raid.sh $3</command>
+ <command>sudo ${vyos_op_scripts_dir}/show_raid.sh $3</command>
</tagNode>
</children>
</node>
diff --git a/op-mode-definitions/show-system.xml.in b/op-mode-definitions/show-system.xml.in
index 4a0e6c3b2..85bfdcdba 100644
--- a/op-mode-definitions/show-system.xml.in
+++ b/op-mode-definitions/show-system.xml.in
@@ -7,6 +7,42 @@
<help>Show system information</help>
</properties>
<children>
+ <node name="commit">
+ <properties>
+ <help>Show commit revision log</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/config_mgmt.py show_commit_log</command>
+ <children>
+ <tagNode name="diff">
+ <properties>
+ <help>Show commit revision diff</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/config_mgmt.py show_commit_diff --rev "$5"</command>
+ </tagNode>
+ <tagNode name="file">
+ <properties>
+ <help>Show commit revision file</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/config_mgmt.py show_commit_file --rev "$5"</command>
+ <children>
+ <tagNode name="compare">
+ <properties>
+ <help>Compare config file revisions</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/config_mgmt.py show_commit_diff --rev "$5" --rev2 "$7"</command>
+ <children>
+ <leafNode name="commands">
+ <properties>
+ <help>Compare config file revision commands</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/config_mgmt.py show_commit_diff --rev "$5" --rev2 "$7" --commands</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
<node name="connections">
<properties>
<help>Show active network connections on the system</help>
diff --git a/op-mode-definitions/vpn-ipsec.xml.in b/op-mode-definitions/vpn-ipsec.xml.in
index f1af65fcb..803ce4cc2 100644
--- a/op-mode-definitions/vpn-ipsec.xml.in
+++ b/op-mode-definitions/vpn-ipsec.xml.in
@@ -137,6 +137,12 @@
<help>Show Internet Protocol Security (IPsec) information</help>
</properties>
<children>
+ <node name="connections">
+ <properties>
+ <help>Show VPN connections</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/ipsec.py show_connections</command>
+ </node>
<node name="policy">
<properties>
<help>Show the in-kernel crypto policies</help>
diff --git a/op-mode-definitions/wireless.xml.in b/op-mode-definitions/wireless.xml.in
index 5d9db1544..f8e53ad21 100644
--- a/op-mode-definitions/wireless.xml.in
+++ b/op-mode-definitions/wireless.xml.in
@@ -21,7 +21,7 @@
<properties>
<help>Clear interface information for a given wireless interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --type wireless</script>
+ <path>interfaces wireless</path>
</completionHelp>
</properties>
<children>
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/setup.py b/python/setup.py
index e2d28bd6b..2d614e724 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -24,4 +24,9 @@ setup(
"Topic :: Utilities",
"License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)",
],
+ entry_points={
+ "console_scripts": [
+ "config-mgmt = vyos.config_mgmt:run",
+ ],
+ },
)
diff --git a/python/vyos/accel_ppp.py b/python/vyos/accel_ppp.py
new file mode 100644
index 000000000..bfc8ee5a9
--- /dev/null
+++ b/python/vyos/accel_ppp.py
@@ -0,0 +1,74 @@
+#!/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 sys
+
+import vyos.opmode
+from vyos.util import rc_cmd
+
+
+def get_server_statistics(accel_statistics, pattern, sep=':') -> dict:
+ import re
+
+ stat_dict = {'sessions': {}}
+
+ cpu = re.search(r'cpu(.*)', accel_statistics).group(0)
+ # Find all lines with pattern, for example 'sstp:'
+ data = re.search(rf'{pattern}(.*)', accel_statistics, re.DOTALL).group(0)
+ session_starting = re.search(r'starting(.*)', data).group(0)
+ session_active = re.search(r'active(.*)', data).group(0)
+
+ for entry in {cpu, session_starting, session_active}:
+ if sep in entry:
+ key, value = entry.split(sep)
+ if key in ['starting', 'active', 'finishing']:
+ stat_dict['sessions'][key] = value.strip()
+ continue
+ stat_dict[key] = value.strip()
+ return stat_dict
+
+
+def accel_cmd(port: int, command: str) -> str:
+ _, output = rc_cmd(f'/usr/bin/accel-cmd -p{port} {command}')
+ return output
+
+
+def accel_out_parse(accel_output: list[str]) -> list[dict[str, str]]:
+ """ Parse accel-cmd show sessions output """
+ data_list: list[dict[str, str]] = list()
+ field_names: list[str] = list()
+
+ field_names_unstripped: list[str] = accel_output.pop(0).split('|')
+ for field_name in field_names_unstripped:
+ field_names.append(field_name.strip())
+
+ while accel_output:
+ if '|' not in accel_output[0]:
+ accel_output.pop(0)
+ continue
+
+ current_item: list[str] = accel_output.pop(0).split('|')
+ item_dict: dict[str, str] = {}
+
+ for field_index in range(len(current_item)):
+ field_name: str = field_names[field_index]
+ field_value: str = current_item[field_index].strip()
+ item_dict[field_name] = field_value
+
+ data_list.append(item_dict)
+
+ return data_list
diff --git a/python/vyos/base.py b/python/vyos/base.py
index 78067d5b2..9b93cb2f2 100644
--- a/python/vyos/base.py
+++ b/python/vyos/base.py
@@ -15,17 +15,47 @@
from textwrap import fill
+
+class BaseWarning:
+ def __init__(self, header, message, **kwargs):
+ self.message = message
+ self.kwargs = kwargs
+ if 'width' not in kwargs:
+ self.width = 72
+ if 'initial_indent' in kwargs:
+ del self.kwargs['initial_indent']
+ if 'subsequent_indent' in kwargs:
+ del self.kwargs['subsequent_indent']
+ self.textinitindent = header
+ self.standardindent = ''
+
+ def print(self):
+ messages = self.message.split('\n')
+ isfirstmessage = True
+ initial_indent = self.textinitindent
+ print('')
+ for mes in messages:
+ mes = fill(mes, initial_indent=initial_indent,
+ subsequent_indent=self.standardindent, **self.kwargs)
+ if isfirstmessage:
+ isfirstmessage = False
+ initial_indent = self.standardindent
+ print(f'{mes}')
+ print('')
+
+
class Warning():
- def __init__(self, message):
- # Reformat the message and trim it to 72 characters in length
- message = fill(message, width=72)
- print(f'\nWARNING: {message}')
+ def __init__(self, message, **kwargs):
+ self.BaseWarn = BaseWarning('WARNING: ', message, **kwargs)
+ self.BaseWarn.print()
+
class DeprecationWarning():
- def __init__(self, message):
+ def __init__(self, message, **kwargs):
# Reformat the message and trim it to 72 characters in length
- message = fill(message, width=72)
- print(f'\nDEPRECATION WARNING: {message}\n')
+ self.BaseWarn = BaseWarning('DEPRECATION WARNING: ', message, **kwargs)
+ self.BaseWarn.print()
+
class ConfigError(Exception):
def __init__(self, message):
diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py
new file mode 100644
index 000000000..a4e318d08
--- /dev/null
+++ b/python/vyos/component_version.py
@@ -0,0 +1,192 @@
+# 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/>.
+
+"""
+Functions for reading/writing component versions.
+
+The config file version string has the following form:
+
+VyOS 1.3/1.4:
+
+// 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.0
+
+VyOS 1.2:
+
+/* 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:pppoe-server@2:pptp@1:qos@1:quagga@7:snmp@1:ssh@1:system@10:vrrp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1" === */
+/* Release version: 1.2.8 */
+
+"""
+
+import os
+import re
+import sys
+import fileinput
+
+from vyos.xml import component_version
+from vyos.version import get_version
+from vyos.defaults import directories
+
+DEFAULT_CONFIG_PATH = os.path.join(directories['config'], 'config.boot')
+
+def from_string(string_line, vintage='vyos'):
+ """
+ Get component version dictionary from string.
+ Return empty dictionary if string contains no config information
+ or raise error if component version string malformed.
+ """
+ version_dict = {}
+
+ if vintage == 'vyos':
+ if re.match(r'// vyos-config-version:.+', string_line):
+ if not re.match(r'// vyos-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s*', string_line):
+ raise ValueError(f"malformed configuration string: {string_line}")
+
+ for pair in re.findall(r'([\w,-]+)@(\d+)', string_line):
+ version_dict[pair[0]] = int(pair[1])
+
+ elif vintage == 'vyatta':
+ if re.match(r'/\* === vyatta-config-version:.+=== \*/$', string_line):
+ if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', string_line):
+ raise ValueError(f"malformed configuration string: {string_line}")
+
+ for pair in re.findall(r'([\w,-]+)@(\d+)', string_line):
+ version_dict[pair[0]] = int(pair[1])
+ else:
+ raise ValueError("Unknown config string vintage")
+
+ return version_dict
+
+def from_file(config_file_name=DEFAULT_CONFIG_PATH, vintage='vyos'):
+ """
+ Get component version dictionary parsing config file line by line
+ """
+ with open(config_file_name, 'r') as f:
+ for line_in_config in f:
+ version_dict = from_string(line_in_config, vintage=vintage)
+ if version_dict:
+ return version_dict
+
+ # no version information
+ return {}
+
+def from_system():
+ """
+ Get system component version dict.
+ """
+ return component_version()
+
+def legacy_from_system():
+ """
+ Get system component version dict from legacy location.
+ This is for a transitional sanity check; the directory will eventually
+ be removed.
+ """
+ system_versions = {}
+ legacy_dir = directories['current']
+
+ # To be removed:
+ if not os.path.isdir(legacy_dir):
+ return system_versions
+
+ try:
+ version_info = os.listdir(legacy_dir)
+ except OSError as err:
+ sys.exit(repr(err))
+
+ for info in version_info:
+ if re.match(r'[\w,-]+@\d+', info):
+ pair = info.split('@')
+ system_versions[pair[0]] = int(pair[1])
+
+ return system_versions
+
+def format_string(ver: dict) -> str:
+ """
+ Version dict to string.
+ """
+ keys = list(ver)
+ keys.sort()
+ l = []
+ for k in keys:
+ v = ver[k]
+ l.append(f'{k}@{v}')
+ sep = ':'
+ return sep.join(l)
+
+def version_footer(ver: dict, vintage='vyos') -> str:
+ """
+ Version footer as string.
+ """
+ ver_str = format_string(ver)
+ release = get_version()
+ if vintage == 'vyos':
+ ret_str = (f'// Warning: Do not remove the following line.\n'
+ + f'// vyos-config-version: "{ver_str}"\n'
+ + f'// Release version: {release}\n')
+ elif vintage == 'vyatta':
+ ret_str = (f'/* Warning: Do not remove the following line. */\n'
+ + f'/* === vyatta-config-version: "{ver_str}" === */\n'
+ + f'/* Release version: {release} */\n')
+ else:
+ raise ValueError("Unknown config string vintage")
+
+ return ret_str
+
+def system_footer(vintage='vyos') -> str:
+ """
+ System version footer as string.
+ """
+ ver_d = from_system()
+ return version_footer(ver_d, vintage=vintage)
+
+def write_version_footer(ver: dict, file_name, vintage='vyos'):
+ """
+ Write version footer to file.
+ """
+ footer = version_footer(ver=ver, vintage=vintage)
+ if file_name:
+ with open(file_name, 'a') as f:
+ f.write(footer)
+ else:
+ sys.stdout.write(footer)
+
+def write_system_footer(file_name, vintage='vyos'):
+ """
+ Write system version footer to file.
+ """
+ ver_d = from_system()
+ return write_version_footer(ver_d, file_name=file_name, vintage=vintage)
+
+def remove_footer(file_name):
+ """
+ Remove old version footer.
+ """
+ for line in fileinput.input(file_name, inplace=True):
+ if re.match(r'/\* Warning:.+ \*/$', line):
+ continue
+ if re.match(r'/\* === vyatta-config-version:.+=== \*/$', line):
+ continue
+ if re.match(r'/\* Release version:.+ \*/$', line):
+ continue
+ if re.match('// vyos-config-version:.+', line):
+ continue
+ if re.match('// Warning:.+', line):
+ continue
+ if re.match('// Release version:.+', line):
+ continue
+ sys.stdout.write(line)
diff --git a/python/vyos/component_versions.py b/python/vyos/component_versions.py
deleted file mode 100644
index 90b458aae..000000000
--- a/python/vyos/component_versions.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright 2017 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/>.
-
-"""
-The version data looks like:
-
-/* Warning: Do not remove the following line. */
-/* === vyatta-config-version:
-"cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@1:dhcp-server@4:firewall@5:ipsec@4:nat@4:qos@1:quagga@2:system@8:vrrp@1:wanloadbalance@3:webgui@1:webproxy@1:zone-policy@1"
-=== */
-/* Release version: 1.2.0-rolling+201806131737 */
-"""
-
-import re
-
-def get_component_version(string_line):
- """
- Get component version dictionary from string
- return empty dictionary if string contains no config information
- or raise error if component version string malformed
- """
- return_value = {}
- if re.match(r'/\* === vyatta-config-version:.+=== \*/$', string_line):
-
- if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', string_line):
- raise ValueError("malformed configuration string: " + str(string_line))
-
- for pair in re.findall(r'([\w,-]+)@(\d+)', string_line):
- if pair[0] in return_value.keys():
- raise ValueError("duplicate unit name: \"" + str(pair[0]) + "\" in string: \"" + string_line + "\"")
- return_value[pair[0]] = int(pair[1])
-
- return return_value
-
-
-def get_component_versions_from_file(config_file_name='/opt/vyatta/etc/config/config.boot'):
- """
- Get component version dictionary parsing config file line by line
- """
- f = open(config_file_name, 'r')
- for line_in_config in f:
- component_version = get_component_version(line_in_config)
- if component_version:
- return component_version
- raise ValueError("no config string in file:", config_file_name)
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py
new file mode 100644
index 000000000..22a49ff50
--- /dev/null
+++ b/python/vyos/config_mgmt.py
@@ -0,0 +1,686 @@
+# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import re
+import sys
+import gzip
+import logging
+from typing import Optional, Tuple, Union
+from filecmp import cmp
+from datetime import datetime
+from tabulate import tabulate
+
+from vyos.config import Config
+from vyos.configtree import ConfigTree
+from vyos.defaults import directories
+from vyos.util import is_systemd_service_active, ask_yes_no, rc_cmd
+
+SAVE_CONFIG = '/opt/vyatta/sbin/vyatta-save-config.pl'
+
+# created by vyatta-cfg-postinst
+commit_post_hook_dir = '/etc/commit/post-hooks.d'
+
+commit_hooks = {'commit_revision': '01vyos-commit-revision',
+ 'commit_archive': '02vyos-commit-archive'}
+
+DEFAULT_TIME_MINUTES = 10
+timer_name = 'commit-confirm'
+
+config_file = os.path.join(directories['config'], 'config.boot')
+archive_dir = os.path.join(directories['config'], 'archive')
+archive_config_file = os.path.join(archive_dir, 'config.boot')
+commit_log_file = os.path.join(archive_dir, 'commits')
+logrotate_conf = os.path.join(archive_dir, 'lr.conf')
+logrotate_state = os.path.join(archive_dir, 'lr.state')
+rollback_config = os.path.join(archive_dir, 'config.boot-rollback')
+prerollback_config = os.path.join(archive_dir, 'config.boot-prerollback')
+tmp_log_entry = '/tmp/commit-rev-entry'
+
+logger = logging.getLogger('config_mgmt')
+logger.setLevel(logging.INFO)
+ch = logging.StreamHandler()
+formatter = logging.Formatter('%(funcName)s: %(levelname)s:%(message)s')
+ch.setFormatter(formatter)
+logger.addHandler(ch)
+
+class ConfigMgmtError(Exception):
+ pass
+
+class ConfigMgmt:
+ def __init__(self, session_env=None, config=None):
+ if session_env:
+ self._session_env = session_env
+ else:
+ self._session_env = None
+
+ if config is None:
+ config = Config()
+
+ d = config.get_config_dict(['system', 'config-management'],
+ key_mangling=('-', '_'),
+ get_first_key=True)
+
+ self.max_revisions = int(d.get('commit_revisions', 0))
+ self.locations = d.get('commit_archive', {}).get('location', [])
+ self.source_address = d.get('commit_archive',
+ {}).get('source_address', '')
+ if config.exists(['system', 'host-name']):
+ self.hostname = config.return_value(['system', 'host-name'])
+ else:
+ self.hostname = 'vyos'
+
+ # upload only on existence of effective values, notably, on boot.
+ # one still needs session self.locations (above) for setting
+ # post-commit hook in conf_mode script
+ path = ['system', 'config-management', 'commit-archive', 'location']
+ if config.exists_effective(path):
+ self.effective_locations = config.return_effective_values(path)
+ else:
+ self.effective_locations = []
+
+ # a call to compare without args is edit_level aware
+ edit_level = os.getenv('VYATTA_EDIT_LEVEL', '')
+ edit_path = [l for l in edit_level.split('/') if l]
+ if edit_path:
+ eff_conf = config.show_config(edit_path, effective=True)
+ self.edit_level_active_config = ConfigTree(eff_conf)
+ conf = config.show_config(edit_path)
+ self.edit_level_working_config = ConfigTree(conf)
+ else:
+ self.edit_level_active_config = None
+ self.edit_level_working_config = None
+
+ self.active_config = config._running_config
+ self.working_config = config._session_config
+
+ @staticmethod
+ def save_config(target):
+ cmd = f'{SAVE_CONFIG} {target}'
+ rc, out = rc_cmd(cmd)
+ if rc != 0:
+ logger.critical(f'save config failed: {out}')
+
+ def _unsaved_commits(self) -> bool:
+ tmp_save = '/tmp/config.boot.check-save'
+ self.save_config(tmp_save)
+ ret = not cmp(tmp_save, config_file, shallow=False)
+ os.unlink(tmp_save)
+ return ret
+
+ # Console script functions
+ #
+ def commit_confirm(self, minutes: int=DEFAULT_TIME_MINUTES,
+ no_prompt: bool=False) -> Tuple[str,int]:
+ """Commit with reboot to saved config in 'minutes' minutes if
+ 'confirm' call is not issued.
+ """
+ if is_systemd_service_active(f'{timer_name}.timer'):
+ msg = 'Another confirm is pending'
+ return msg, 1
+
+ if self._unsaved_commits():
+ W = '\nYou should save previous commits before commit-confirm !\n'
+ else:
+ W = ''
+
+ prompt_str = f'''
+commit-confirm will automatically reboot in {minutes} minutes unless changes
+are confirmed.\n
+Proceed ?'''
+ prompt_str = W + prompt_str
+ if not no_prompt and not ask_yes_no(prompt_str, default=True):
+ msg = 'commit-confirm canceled'
+ return msg, 1
+
+ action = 'sg vyattacfg "/usr/bin/config-mgmt revert"'
+ cmd = f'sudo systemd-run --quiet --on-active={minutes}m --unit={timer_name} {action}'
+ rc, out = rc_cmd(cmd)
+ if rc != 0:
+ raise ConfigMgmtError(out)
+
+ # start notify
+ cmd = f'sudo -b /usr/libexec/vyos/commit-confirm-notify.py {minutes}'
+ os.system(cmd)
+
+ msg = f'Initialized commit-confirm; {minutes} minutes to confirm before reboot'
+ return msg, 0
+
+ def confirm(self) -> Tuple[str,int]:
+ """Do not reboot to saved config following 'commit-confirm'.
+ Update commit log and archive.
+ """
+ if not is_systemd_service_active(f'{timer_name}.timer'):
+ msg = 'No confirm pending'
+ return msg, 0
+
+ cmd = f'sudo systemctl stop --quiet {timer_name}.timer'
+ rc, out = rc_cmd(cmd)
+ if rc != 0:
+ raise ConfigMgmtError(out)
+
+ # kill notify
+ cmd = 'sudo pkill -f commit-confirm-notify.py'
+ rc, out = rc_cmd(cmd)
+ if rc != 0:
+ raise ConfigMgmtError(out)
+
+ entry = self._read_tmp_log_entry()
+ self._add_log_entry(**entry)
+
+ if self._archive_active_config():
+ self._update_archive()
+
+ msg = 'Reboot timer stopped'
+ return msg, 0
+
+ def revert(self) -> Tuple[str,int]:
+ """Reboot to saved config, dropping commits from 'commit-confirm'.
+ """
+ _ = self._read_tmp_log_entry()
+
+ # archived config will be reverted on boot
+ rc, out = rc_cmd('sudo systemctl reboot')
+ if rc != 0:
+ raise ConfigMgmtError(out)
+
+ return '', 0
+
+ def rollback(self, rev: int, no_prompt: bool=False) -> Tuple[str,int]:
+ """Reboot to config revision 'rev'.
+ """
+ from shutil import copy
+
+ msg = ''
+
+ if not self._check_revision_number(rev):
+ msg = f'Invalid revision number {rev}: must be 0 < rev < {maxrev}'
+ return msg, 1
+
+ prompt_str = 'Proceed with reboot ?'
+ if not no_prompt and not ask_yes_no(prompt_str, default=True):
+ msg = 'Canceling rollback'
+ return msg, 0
+
+ rc, out = rc_cmd(f'sudo cp {archive_config_file} {prerollback_config}')
+ if rc != 0:
+ raise ConfigMgmtError(out)
+
+ path = os.path.join(archive_dir, f'config.boot.{rev}.gz')
+ with gzip.open(path) as f:
+ config = f.read()
+ try:
+ with open(rollback_config, 'wb') as f:
+ f.write(config)
+ copy(rollback_config, config_file)
+ except OSError as e:
+ raise ConfigMgmtError from e
+
+ rc, out = rc_cmd('sudo systemctl reboot')
+ if rc != 0:
+ raise ConfigMgmtError(out)
+
+ return msg, 0
+
+ def compare(self, saved: bool=False, commands: bool=False,
+ rev1: Optional[int]=None,
+ rev2: Optional[int]=None) -> Tuple[str,int]:
+ """General compare function for config file revisions:
+ revision n vs. revision m; working version vs. active version;
+ or working version vs. saved version.
+ """
+ from difflib import unified_diff
+
+ ct1 = self.edit_level_active_config
+ if ct1 is None:
+ ct1 = self.active_config
+ ct2 = self.edit_level_working_config
+ if ct2 is None:
+ ct2 = self.working_config
+ msg = 'No changes between working and active configurations.\n'
+ if saved:
+ ct1 = self._get_saved_config_tree()
+ ct2 = self.working_config
+ msg = 'No changes between working and saved configurations.\n'
+ if rev1 is not None:
+ if not self._check_revision_number(rev1):
+ return f'Invalid revision number {rev1}', 1
+ ct1 = self._get_config_tree_revision(rev1)
+ ct2 = self.working_config
+ msg = f'No changes between working and revision {rev1} configurations.\n'
+ if rev2 is not None:
+ if not self._check_revision_number(rev2):
+ return f'Invalid revision number {rev2}', 1
+ # compare older to newer
+ ct2 = ct1
+ ct1 = self._get_config_tree_revision(rev2)
+ msg = f'No changes between revisions {rev2} and {rev1} configurations.\n'
+
+ if commands:
+ lines1 = ct1.to_commands().splitlines(keepends=True)
+ lines2 = ct2.to_commands().splitlines(keepends=True)
+ else:
+ lines1 = ct1.to_string().splitlines(keepends=True)
+ lines2 = ct2.to_string().splitlines(keepends=True)
+
+ out = ''
+ comp = unified_diff(lines1, lines2)
+ for line in comp:
+ if re.match(r'(\-\-)|(\+\+)|(@@)', line):
+ continue
+ out += line
+ if out:
+ msg = out
+
+ return msg, 0
+
+ def wrap_compare(self, options) -> Tuple[str,int]:
+ """Interface to vyatta-cfg-run: args collected as 'options' to parse
+ for compare.
+ """
+ cmnds = False
+ r1 = None
+ r2 = None
+ if 'commands' in options:
+ cmnds=True
+ options.remove('commands')
+ for i in options:
+ if not i.isnumeric():
+ options.remove(i)
+ if len(options) > 0:
+ r1 = int(options[0])
+ if len(options) > 1:
+ r2 = int(options[1])
+
+ return self.compare(commands=cmnds, rev1=r1, rev2=r2)
+
+ # Initialization and post-commit hooks for conf-mode
+ #
+ def initialize_revision(self):
+ """Initialize config archive, logrotate conf, and commit log.
+ """
+ mask = os.umask(0o002)
+ os.makedirs(archive_dir, exist_ok=True)
+
+ self._add_logrotate_conf()
+
+ if (not os.path.exists(commit_log_file) or
+ self._get_number_of_revisions() == 0):
+ user = self._get_user()
+ via = 'init'
+ comment = ''
+ self._add_log_entry(user, via, comment)
+ # add empty init config before boot-config load for revision
+ # and diff consistency
+ if self._archive_active_config():
+ self._update_archive()
+
+ os.umask(mask)
+
+ def commit_revision(self):
+ """Update commit log and rotate archived config.boot.
+
+ commit_revision is called in post-commit-hooks, if
+ ['commit-archive', 'commit-revisions'] is configured.
+ """
+ if os.getenv('IN_COMMIT_CONFIRM', ''):
+ self._new_log_entry(tmp_file=tmp_log_entry)
+ return
+
+ self._add_log_entry()
+
+ if self._archive_active_config():
+ self._update_archive()
+
+ def commit_archive(self):
+ """Upload config to remote archive.
+ """
+ from vyos.remote import upload
+
+ hostname = self.hostname
+ t = datetime.now()
+ timestamp = t.strftime('%Y%m%d_%H%M%S')
+ remote_file = f'config.boot-{hostname}.{timestamp}'
+ source_address = self.source_address
+
+ for location in self.effective_locations:
+ upload(archive_config_file, f'{location}/{remote_file}',
+ source_host=source_address)
+
+ # op-mode functions
+ #
+ def get_raw_log_data(self) -> list:
+ """Return list of dicts of log data:
+ keys: [timestamp, user, commit_via, commit_comment]
+ """
+ log = self._get_log_entries()
+ res_l = []
+ for line in log:
+ d = self._get_log_entry(line)
+ res_l.append(d)
+
+ return res_l
+
+ @staticmethod
+ def format_log_data(data: list) -> str:
+ """Return formatted log data as str.
+ """
+ res_l = []
+ for l_no, l in enumerate(data):
+ time_d = datetime.fromtimestamp(int(l['timestamp']))
+ time_str = time_d.strftime("%Y-%m-%d %H:%M:%S")
+
+ res_l.append([l_no, time_str,
+ f"by {l['user']}", f"via {l['commit_via']}"])
+
+ if l['commit_comment'] != 'commit': # default comment
+ res_l.append([None, l['commit_comment']])
+
+ ret = tabulate(res_l, tablefmt="plain")
+ return ret
+
+ @staticmethod
+ def format_log_data_brief(data: list) -> str:
+ """Return 'brief' form of log data as str.
+
+ Slightly compacted format used in completion help for
+ 'rollback'.
+ """
+ res_l = []
+ for l_no, l in enumerate(data):
+ time_d = datetime.fromtimestamp(int(l['timestamp']))
+ time_str = time_d.strftime("%Y-%m-%d %H:%M:%S")
+
+ res_l.append(['\t', l_no, time_str,
+ f"{l['user']}", f"by {l['commit_via']}"])
+
+ ret = tabulate(res_l, tablefmt="plain")
+ return ret
+
+ def show_commit_diff(self, rev: int, rev2: Optional[int]=None,
+ commands: bool=False) -> str:
+ """Show commit diff at revision number, compared to previous
+ revision, or to another revision.
+ """
+ if rev2 is None:
+ out, _ = self.compare(commands=commands, rev1=rev, rev2=(rev+1))
+ return out
+
+ out, _ = self.compare(commands=commands, rev1=rev, rev2=rev2)
+ return out
+
+ def show_commit_file(self, rev: int) -> str:
+ return self._get_file_revision(rev)
+
+ # utility functions
+ #
+ @staticmethod
+ def _strip_version(s):
+ return re.split(r'(^//)', s, maxsplit=1, flags=re.MULTILINE)[0]
+
+ def _get_saved_config_tree(self):
+ with open(config_file) as f:
+ c = self._strip_version(f.read())
+ return ConfigTree(c)
+
+ def _get_file_revision(self, rev: int):
+ if rev not in range(0, self._get_number_of_revisions()):
+ raise ConfigMgmtError('revision not available')
+ revision = os.path.join(archive_dir, f'config.boot.{rev}.gz')
+ with gzip.open(revision) as f:
+ r = f.read().decode()
+ return r
+
+ def _get_config_tree_revision(self, rev: int):
+ c = self._strip_version(self._get_file_revision(rev))
+ return ConfigTree(c)
+
+ def _add_logrotate_conf(self):
+ conf = f"""{archive_config_file} {{
+ su root vyattacfg
+ rotate {self.max_revisions}
+ start 0
+ compress
+ copy
+}}"""
+ mask = os.umask(0o133)
+
+ with open(logrotate_conf, 'w') as f:
+ f.write(conf)
+
+ os.umask(mask)
+
+ def _archive_active_config(self) -> bool:
+ mask = os.umask(0o113)
+
+ ext = os.getpid()
+ tmp_save = f'/tmp/config.boot.{ext}'
+ self.save_config(tmp_save)
+
+ try:
+ if cmp(tmp_save, archive_config_file, shallow=False):
+ # this will be the case on boot, as well as certain
+ # re-initialiation instances after delete/set
+ os.unlink(tmp_save)
+ return False
+ except FileNotFoundError:
+ pass
+
+ rc, out = rc_cmd(f'sudo mv {tmp_save} {archive_config_file}')
+ os.umask(mask)
+
+ if rc != 0:
+ logger.critical(f'mv file to archive failed: {out}')
+ return False
+
+ return True
+
+ @staticmethod
+ def _update_archive():
+ cmd = f"sudo logrotate -f -s {logrotate_state} {logrotate_conf}"
+ rc, out = rc_cmd(cmd)
+ if rc != 0:
+ logger.critical(f'logrotate failure: {out}')
+
+ @staticmethod
+ def _get_log_entries() -> list:
+ """Return lines of commit log as list of strings
+ """
+ entries = []
+ if os.path.exists(commit_log_file):
+ with open(commit_log_file) as f:
+ entries = f.readlines()
+
+ return entries
+
+ def _get_number_of_revisions(self) -> int:
+ l = self._get_log_entries()
+ return len(l)
+
+ def _check_revision_number(self, rev: int) -> bool:
+ # exclude init revision:
+ maxrev = self._get_number_of_revisions()
+ if not 0 <= rev < maxrev - 1:
+ return False
+ return True
+
+ @staticmethod
+ def _get_user() -> str:
+ import pwd
+
+ try:
+ user = os.getlogin()
+ except OSError:
+ try:
+ user = pwd.getpwuid(os.geteuid())[0]
+ except KeyError:
+ user = 'unknown'
+ return user
+
+ def _new_log_entry(self, user: str='', commit_via: str='',
+ commit_comment: str='', timestamp: Optional[int]=None,
+ tmp_file: str=None) -> Optional[str]:
+ # Format log entry and return str or write to file.
+ #
+ # Usage is within a post-commit hook, using env values. In case of
+ # commit-confirm, it can be written to a temporary file for
+ # inclusion on 'confirm'.
+ from time import time
+
+ if timestamp is None:
+ timestamp = int(time())
+
+ if not user:
+ user = self._get_user()
+ if not commit_via:
+ commit_via = os.getenv('COMMIT_VIA', 'other')
+ if not commit_comment:
+ commit_comment = os.getenv('COMMIT_COMMENT', 'commit')
+
+ # the commit log reserves '|' as field demarcation, so replace in
+ # comment if present; undo this in _get_log_entry, below
+ if re.search(r'\|', commit_comment):
+ commit_comment = commit_comment.replace('|', '%%')
+
+ entry = f'|{timestamp}|{user}|{commit_via}|{commit_comment}|\n'
+
+ mask = os.umask(0o113)
+ if tmp_file is not None:
+ try:
+ with open(tmp_file, 'w') as f:
+ f.write(entry)
+ except OSError as e:
+ logger.critical(f'write to {tmp_file} failed: {e}')
+ os.umask(mask)
+ return None
+
+ os.umask(mask)
+ return entry
+
+ @staticmethod
+ def _get_log_entry(line: str) -> dict:
+ log_fmt = re.compile(r'\|.*\|\n?$')
+ keys = ['user', 'commit_via', 'commit_comment', 'timestamp']
+ if not log_fmt.match(line):
+ logger.critical(f'Invalid log format {line}')
+ return {}
+
+ timestamp, user, commit_via, commit_comment = (
+ tuple(line.strip().strip('|').split('|')))
+
+ commit_comment = commit_comment.replace('%%', '|')
+ d = dict(zip(keys, [user, commit_via,
+ commit_comment, timestamp]))
+
+ return d
+
+ def _read_tmp_log_entry(self) -> dict:
+ try:
+ with open(tmp_log_entry) as f:
+ entry = f.read()
+ os.unlink(tmp_log_entry)
+ except OSError as e:
+ logger.critical(f'error on file {tmp_log_entry}: {e}')
+
+ return self._get_log_entry(entry)
+
+ def _add_log_entry(self, user: str='', commit_via: str='',
+ commit_comment: str='', timestamp: Optional[int]=None):
+ mask = os.umask(0o113)
+
+ entry = self._new_log_entry(user=user, commit_via=commit_via,
+ commit_comment=commit_comment,
+ timestamp=timestamp)
+
+ log_entries = self._get_log_entries()
+ log_entries.insert(0, entry)
+ if len(log_entries) > self.max_revisions:
+ log_entries = log_entries[:-1]
+
+ try:
+ with open(commit_log_file, 'w') as f:
+ f.writelines(log_entries)
+ except OSError as e:
+ logger.critical(e)
+
+ os.umask(mask)
+
+# entry_point for console script
+#
+def run():
+ from argparse import ArgumentParser, REMAINDER
+
+ config_mgmt = ConfigMgmt()
+
+ for s in list(commit_hooks):
+ if sys.argv[0].replace('-', '_').endswith(s):
+ func = getattr(config_mgmt, s)
+ try:
+ func()
+ except Exception as e:
+ print(f'{s}: {e}')
+ sys.exit(0)
+
+ parser = ArgumentParser()
+ subparsers = parser.add_subparsers(dest='subcommand')
+
+ commit_confirm = subparsers.add_parser('commit_confirm',
+ help="Commit with opt-out reboot to saved config")
+ commit_confirm.add_argument('-t', dest='minutes', type=int,
+ default=DEFAULT_TIME_MINUTES,
+ help="Minutes until reboot, unless 'confirm'")
+ commit_confirm.add_argument('-y', dest='no_prompt', action='store_true',
+ help="Execute without prompt")
+
+ subparsers.add_parser('confirm', help="Confirm commit")
+ subparsers.add_parser('revert', help="Revert commit-confirm")
+
+ rollback = subparsers.add_parser('rollback',
+ help="Rollback to earlier config")
+ rollback.add_argument('--rev', type=int,
+ help="Revision number for rollback")
+ rollback.add_argument('-y', dest='no_prompt', action='store_true',
+ help="Excute without prompt")
+
+ compare = subparsers.add_parser('compare',
+ help="Compare config files")
+
+ compare.add_argument('--saved', action='store_true',
+ help="Compare session config with saved config")
+ compare.add_argument('--commands', action='store_true',
+ help="Show difference between commands")
+ compare.add_argument('--rev1', type=int, default=None,
+ help="Compare revision with session config or other revision")
+ compare.add_argument('--rev2', type=int, default=None,
+ help="Compare revisions")
+
+ wrap_compare = subparsers.add_parser('wrap_compare',
+ help="Wrapper interface for vyatta-cfg-run")
+ wrap_compare.add_argument('--options', nargs=REMAINDER)
+
+ args = vars(parser.parse_args())
+
+ func = getattr(config_mgmt, args['subcommand'])
+ del args['subcommand']
+
+ res = ''
+ try:
+ res, rc = func(**args)
+ except ConfigMgmtError as e:
+ print(e)
+ sys.exit(1)
+ if res:
+ print(res)
+ sys.exit(rc)
diff --git a/python/vyos/configdep.py b/python/vyos/configdep.py
new file mode 100644
index 000000000..d4b2cc78f
--- /dev/null
+++ b/python/vyos/configdep.py
@@ -0,0 +1,95 @@
+# 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
+import json
+import typing
+from inspect import stack
+
+from vyos.util import load_as_module
+from vyos.defaults import directories
+from vyos.configsource import VyOSError
+from vyos import ConfigError
+
+# https://peps.python.org/pep-0484/#forward-references
+# for type 'Config'
+if typing.TYPE_CHECKING:
+ from vyos.config import Config
+
+dependent_func: dict[str, list[typing.Callable]] = {}
+
+def canon_name(name: str) -> str:
+ return os.path.splitext(name)[0].replace('-', '_')
+
+def canon_name_of_path(path: str) -> str:
+ script = os.path.basename(path)
+ return canon_name(script)
+
+def caller_name() -> str:
+ return stack()[-1].filename
+
+def read_dependency_dict() -> dict:
+ path = os.path.join(directories['data'],
+ 'config-mode-dependencies.json')
+ with open(path) as f:
+ d = json.load(f)
+ return d
+
+def get_dependency_dict(config: 'Config') -> dict:
+ if hasattr(config, 'cached_dependency_dict'):
+ d = getattr(config, 'cached_dependency_dict')
+ else:
+ d = read_dependency_dict()
+ setattr(config, 'cached_dependency_dict', d)
+ return d
+
+def run_config_mode_script(script: str, config: 'Config'):
+ path = os.path.join(directories['conf_mode'], script)
+ name = canon_name(script)
+ mod = load_as_module(name, path)
+
+ config.set_level([])
+ try:
+ c = mod.get_config(config)
+ mod.verify(c)
+ mod.generate(c)
+ mod.apply(c)
+ except (VyOSError, ConfigError) as e:
+ raise ConfigError(repr(e))
+
+def def_closure(target: str, config: 'Config',
+ tagnode: typing.Optional[str] = None) -> typing.Callable:
+ script = target + '.py'
+ def func_impl():
+ if tagnode:
+ os.environ['VYOS_TAGNODE_VALUE'] = tagnode
+ run_config_mode_script(script, config)
+ return func_impl
+
+def set_dependents(case: str, config: 'Config',
+ tagnode: typing.Optional[str] = None):
+ d = get_dependency_dict(config)
+ k = canon_name_of_path(caller_name())
+ l = dependent_func.setdefault(k, [])
+ for target in d[k][case]:
+ func = def_closure(target, config, tagnode)
+ l.append(func)
+
+def call_dependents():
+ k = canon_name_of_path(caller_name())
+ l = dependent_func.get(k, [])
+ while l:
+ f = l.pop(0)
+ f()
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/configtree.py b/python/vyos/configtree.py
index e9cdb69e4..f2358ee4f 100644
--- a/python/vyos/configtree.py
+++ b/python/vyos/configtree.py
@@ -1,5 +1,5 @@
# configtree -- a standalone VyOS config file manipulation library (Python bindings)
-# Copyright (C) 2018 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 VyOS maintainers and contributors
#
# 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;
@@ -12,6 +12,7 @@
# You should have received a copy of the GNU Lesser General Public License along with this library;
# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+import os
import re
import json
@@ -147,6 +148,8 @@ class ConfigTree(object):
self.__config = address
self.__version = ''
+ self.__migration = os.environ.get('VYOS_MIGRATION')
+
def __del__(self):
if self.__config is not None:
self.__destroy(self.__config)
@@ -191,18 +194,27 @@ class ConfigTree(object):
else:
self.__set_add_value(self.__config, path_str, str(value).encode())
+ if self.__migration:
+ print(f"- op: set path: {path} value: {value} replace: {replace}")
+
def delete(self, path):
check_path(path)
path_str = " ".join(map(str, path)).encode()
self.__delete(self.__config, path_str)
+ if self.__migration:
+ print(f"- op: delete path: {path}")
+
def delete_value(self, path, value):
check_path(path)
path_str = " ".join(map(str, path)).encode()
self.__delete_value(self.__config, path_str, value.encode())
+ if self.__migration:
+ print(f"- op: delete_value path: {path} value: {value}")
+
def rename(self, path, new_name):
check_path(path)
path_str = " ".join(map(str, path)).encode()
@@ -216,6 +228,9 @@ class ConfigTree(object):
if (res != 0):
raise ConfigTreeError("Path [{}] doesn't exist".format(path))
+ if self.__migration:
+ print(f"- op: rename old_path: {path} new_path: {new_path}")
+
def copy(self, old_path, new_path):
check_path(old_path)
check_path(new_path)
@@ -227,7 +242,11 @@ class ConfigTree(object):
raise ConfigTreeError()
res = self.__copy(self.__config, oldpath_str, newpath_str)
if (res != 0):
- raise ConfigTreeError("Path [{}] doesn't exist".format(old_path))
+ msg = self.__get_error().decode()
+ raise ConfigTreeError(msg)
+
+ if self.__migration:
+ print(f"- op: copy old_path: {old_path} new_path: {new_path}")
def exists(self, path):
check_path(path)
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index afa0c5b33..8e0ce701e 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -388,8 +388,10 @@ def verify_accel_ppp_base_service(config, local_users=True):
"""
# vertify auth settings
if local_users and dict_search('authentication.mode', config) == 'local':
- if dict_search(f'authentication.local_users', config) == None:
- raise ConfigError('Authentication mode local requires local users to be configured!')
+ if (dict_search(f'authentication.local_users', config) is None or
+ dict_search(f'authentication.local_users', config) == {}):
+ raise ConfigError(
+ 'Authentication mode local requires local users to be configured!')
for user in dict_search('authentication.local_users.username', config):
user_config = config['authentication']['local_users']['username'][user]
diff --git a/python/vyos/cpu.py b/python/vyos/cpu.py
index 488ae79fb..d2e5f6504 100644
--- a/python/vyos/cpu.py
+++ b/python/vyos/cpu.py
@@ -73,7 +73,7 @@ def _find_physical_cpus():
# On other architectures, e.g. on ARM, there's no such field.
# We just assume they are different CPUs,
# whether single core ones or cores of physical CPUs.
- phys_cpus[num] = cpu[num]
+ phys_cpus[num] = cpus[num]
return phys_cpus
diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py
index 2b6012a73..abf8de688 100644
--- a/python/vyos/ethtool.py
+++ b/python/vyos/ethtool.py
@@ -56,10 +56,10 @@ class Ethtool:
def __init__(self, ifname):
# Get driver used for interface
- sysfs_file = f'/sys/class/net/{ifname}/device/driver/module'
- if os.path.exists(sysfs_file):
- link = os.readlink(sysfs_file)
- self._driver_name = os.path.basename(link)
+ out, err = popen(f'ethtool --driver {ifname}')
+ driver = re.search(r'driver:\s(\w+)', out)
+ if driver:
+ self._driver_name = driver.group(1)
# Build a dictinary of supported link-speed and dupley settings.
out, err = popen(f'ethtool {ifname}')
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 4075e55b0..b4b9e67bb 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -20,6 +20,9 @@ import os
import re
from pathlib import Path
+from socket import AF_INET
+from socket import AF_INET6
+from socket import getaddrinfo
from time import strftime
from vyos.remote import download
@@ -31,65 +34,31 @@ from vyos.util import dict_search_args
from vyos.util import dict_search_recursive
from vyos.util import run
+# Domain Resolver
-# Functions for firewall group domain-groups
-def get_ips_domains_dict(list_domains):
- """
- Get list of IPv4 addresses by list of domains
- Ex: get_ips_domains_dict(['ex1.com', 'ex2.com'])
- {'ex1.com': ['192.0.2.1'], 'ex2.com': ['192.0.2.2', '192.0.2.3']}
- """
- from socket import gethostbyname_ex
- from socket import gaierror
-
- ip_dict = {}
- for domain in list_domains:
- try:
- _, _, ips = gethostbyname_ex(domain)
- ip_dict[domain] = ips
- except gaierror:
- pass
-
- return ip_dict
-
-def nft_init_set(group_name, table="vyos_filter", family="ip"):
- """
- table ip vyos_filter {
- set GROUP_NAME
- type ipv4_addr
- flags interval
- }
- """
- return call(f'nft add set ip {table} {group_name} {{ type ipv4_addr\\; flags interval\\; }}')
-
-
-def nft_add_set_elements(group_name, elements, table="vyos_filter", family="ip"):
- """
- table ip vyos_filter {
- set GROUP_NAME {
- type ipv4_addr
- flags interval
- elements = { 192.0.2.1, 192.0.2.2 }
- }
- """
- elements = ", ".join(elements)
- return call(f'nft add element {family} {table} {group_name} {{ {elements} }} ')
-
-def nft_flush_set(group_name, table="vyos_filter", family="ip"):
- """
- Flush elements of nft set
- """
- return call(f'nft flush set {family} {table} {group_name}')
-
-def nft_update_set_elements(group_name, elements, table="vyos_filter", family="ip"):
- """
- Update elements of nft set
- """
- flush_set = nft_flush_set(group_name, table="vyos_filter", family="ip")
- nft_add_set = nft_add_set_elements(group_name, elements, table="vyos_filter", family="ip")
- return flush_set, nft_add_set
-
-# END firewall group domain-group (sets)
+def fqdn_config_parse(firewall):
+ firewall['ip_fqdn'] = {}
+ firewall['ip6_fqdn'] = {}
+
+ for domain, path in dict_search_recursive(firewall, 'fqdn'):
+ fw_name = path[1] # name/ipv6-name
+ rule = path[3] # rule id
+ suffix = path[4][0] # source/destination (1 char)
+ set_name = f'{fw_name}_{rule}_{suffix}'
+
+ if path[0] == 'name':
+ firewall['ip_fqdn'][set_name] = domain
+ elif path[0] == 'ipv6_name':
+ firewall['ip6_fqdn'][set_name] = domain
+
+def fqdn_resolve(fqdn, ipv6=False):
+ try:
+ res = getaddrinfo(fqdn, None, AF_INET6 if ipv6 else AF_INET)
+ return set(item[4][0] for item in res)
+ except:
+ return None
+
+# End Domain Resolver
def find_nftables_rule(table, chain, rule_matches=[]):
# Find rule in table/chain that matches all criteria and return the handle
@@ -144,12 +113,26 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if side in rule_conf:
prefix = side[0]
side_conf = rule_conf[side]
+ address_mask = side_conf.get('address_mask', None)
if 'address' in side_conf:
suffix = side_conf['address']
- if suffix[0] == '!':
- suffix = f'!= {suffix[1:]}'
- output.append(f'{ip_name} {prefix}addr {suffix}')
+ operator = ''
+ exclude = suffix[0] == '!'
+ if exclude:
+ operator = '!= '
+ suffix = suffix[1:]
+ if address_mask:
+ operator = '!=' if exclude else '=='
+ operator = f'& {address_mask} {operator} '
+ output.append(f'{ip_name} {prefix}addr {operator}{suffix}')
+
+ if 'fqdn' in side_conf:
+ fqdn = side_conf['fqdn']
+ operator = ''
+ if fqdn[0] == '!':
+ operator = '!='
+ output.append(f'{ip_name} {prefix}addr {operator} @FQDN_{fw_name}_{rule_id}_{prefix}')
if dict_search_args(side_conf, 'geoip', 'country_code'):
operator = ''
@@ -192,9 +175,13 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if 'address_group' in group:
group_name = group['address_group']
operator = ''
- if group_name[0] == '!':
+ exclude = group_name[0] == "!"
+ if exclude:
operator = '!='
group_name = group_name[1:]
+ if address_mask:
+ operator = '!=' if exclude else '=='
+ operator = f'& {address_mask} {operator}'
output.append(f'{ip_name} {prefix}addr {operator} @A{def_suffix}_{group_name}')
# Generate firewall group domain-group
elif 'domain_group' in group:
@@ -249,12 +236,20 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
output.append(f'ip6 hoplimit {operator} {value}')
if 'inbound_interface' in rule_conf:
- iiface = rule_conf['inbound_interface']
- output.append(f'iifname {iiface}')
+ if 'interface_name' in rule_conf['inbound_interface']:
+ iiface = rule_conf['inbound_interface']['interface_name']
+ output.append(f'iifname {{{iiface}}}')
+ else:
+ iiface = rule_conf['inbound_interface']['interface_group']
+ output.append(f'iifname @I_{iiface}')
if 'outbound_interface' in rule_conf:
- oiface = rule_conf['outbound_interface']
- output.append(f'oifname {oiface}')
+ if 'interface_name' in rule_conf['outbound_interface']:
+ oiface = rule_conf['outbound_interface']['interface_name']
+ output.append(f'oifname {{{oiface}}}')
+ else:
+ oiface = rule_conf['outbound_interface']['interface_group']
+ output.append(f'oifname @I_{oiface}')
if 'ttl' in rule_conf:
operators = {'eq': '==', 'gt': '>', 'lt': '<'}
@@ -327,6 +322,10 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if tcp_mss:
output.append(f'tcp option maxseg size {tcp_mss}')
+ if 'connection_mark' in rule_conf:
+ conn_mark_str = ','.join(rule_conf['connection_mark'])
+ output.append(f'ct mark {{{conn_mark_str}}}')
+
output.append('counter')
if 'set' in rule_conf:
@@ -373,6 +372,9 @@ def parse_time(time):
def parse_policy_set(set_conf, def_suffix):
out = []
+ if 'connection_mark' in set_conf:
+ conn_mark = set_conf['connection_mark']
+ out.append(f'ct mark set {conn_mark}')
if 'dscp' in set_conf:
dscp = set_conf['dscp']
out.append(f'ip{def_suffix} dscp set {dscp}')
diff --git a/python/vyos/formatversions.py b/python/vyos/formatversions.py
deleted file mode 100644
index 29117a5d3..000000000
--- a/python/vyos/formatversions.py
+++ /dev/null
@@ -1,109 +0,0 @@
-# Copyright 2019 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 sys
-import os
-import re
-import fileinput
-
-def read_vyatta_versions(config_file):
- config_file_versions = {}
-
- with open(config_file, 'r') as config_file_handle:
- for config_line in config_file_handle:
- if re.match(r'/\* === vyatta-config-version:.+=== \*/$', config_line):
- if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', config_line):
- raise ValueError("malformed configuration string: "
- "{}".format(config_line))
-
- for pair in re.findall(r'([\w,-]+)@(\d+)', config_line):
- config_file_versions[pair[0]] = int(pair[1])
-
-
- return config_file_versions
-
-def read_vyos_versions(config_file):
- config_file_versions = {}
-
- with open(config_file, 'r') as config_file_handle:
- for config_line in config_file_handle:
- if re.match(r'// vyos-config-version:.+', config_line):
- if not re.match(r'// vyos-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s*', config_line):
- raise ValueError("malformed configuration string: "
- "{}".format(config_line))
-
- for pair in re.findall(r'([\w,-]+)@(\d+)', config_line):
- config_file_versions[pair[0]] = int(pair[1])
-
- return config_file_versions
-
-def remove_versions(config_file):
- """
- Remove old version string.
- """
- for line in fileinput.input(config_file, inplace=True):
- if re.match(r'/\* Warning:.+ \*/$', line):
- continue
- if re.match(r'/\* === vyatta-config-version:.+=== \*/$', line):
- continue
- if re.match(r'/\* Release version:.+ \*/$', line):
- continue
- if re.match('// vyos-config-version:.+', line):
- continue
- if re.match('// Warning:.+', line):
- continue
- if re.match('// Release version:.+', line):
- continue
- sys.stdout.write(line)
-
-def format_versions_string(config_versions):
- cfg_keys = list(config_versions.keys())
- cfg_keys.sort()
-
- component_version_strings = []
-
- for key in cfg_keys:
- cfg_vers = config_versions[key]
- component_version_strings.append('{}@{}'.format(key, cfg_vers))
-
- separator = ":"
- component_version_string = separator.join(component_version_strings)
-
- return component_version_string
-
-def write_vyatta_versions_foot(config_file, component_version_string,
- os_version_string):
- if config_file:
- with open(config_file, 'a') as config_file_handle:
- config_file_handle.write('/* Warning: Do not remove the following line. */\n')
- config_file_handle.write('/* === vyatta-config-version: "{}" === */\n'.format(component_version_string))
- config_file_handle.write('/* Release version: {} */\n'.format(os_version_string))
- else:
- sys.stdout.write('/* Warning: Do not remove the following line. */\n')
- sys.stdout.write('/* === vyatta-config-version: "{}" === */\n'.format(component_version_string))
- sys.stdout.write('/* Release version: {} */\n'.format(os_version_string))
-
-def write_vyos_versions_foot(config_file, component_version_string,
- os_version_string):
- if config_file:
- with open(config_file, 'a') as config_file_handle:
- config_file_handle.write('// Warning: Do not remove the following line.\n')
- config_file_handle.write('// vyos-config-version: "{}"\n'.format(component_version_string))
- config_file_handle.write('// Release version: {}\n'.format(os_version_string))
- else:
- sys.stdout.write('// Warning: Do not remove the following line.\n')
- sys.stdout.write('// vyos-config-version: "{}"\n'.format(component_version_string))
- sys.stdout.write('// Release version: {}\n'.format(os_version_string))
-
diff --git a/python/vyos/frr.py b/python/vyos/frr.py
index 0ffd5cba9..ccb132dd5 100644
--- a/python/vyos/frr.py
+++ b/python/vyos/frr.py
@@ -477,7 +477,7 @@ class FRRConfig:
# for the listed FRR issues above
pass
if count >= count_max:
- raise ConfigurationNotValid(f'Config commit retry counter ({count_max}) exceeded')
+ raise ConfigurationNotValid(f'Config commit retry counter ({count_max}) exceeded for {daemon} dameon!')
# Save configuration to /run/frr/config/frr.conf
save_configuration()
diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py
index a37615c8f..206b2bba1 100644
--- a/python/vyos/ifconfig/__init__.py
+++ b/python/vyos/ifconfig/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-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
@@ -36,4 +36,6 @@ from vyos.ifconfig.tunnel import TunnelIf
from vyos.ifconfig.wireless import WiFiIf
from vyos.ifconfig.l2tpv3 import L2TPv3If
from vyos.ifconfig.macsec import MACsecIf
+from vyos.ifconfig.veth import VethIf
from vyos.ifconfig.wwan import WWANIf
+from vyos.ifconfig.sstpc import SSTPCIf
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/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py
index 776014bc3..2266879ec 100644
--- a/python/vyos/ifconfig/macvlan.py
+++ b/python/vyos/ifconfig/macvlan.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-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
@@ -30,10 +30,17 @@ class MACVLANIf(Interface):
}
def _create(self):
+ """
+ Create MACvlan interface in OS kernel. Interface is administrative
+ down by default.
+ """
# please do not change the order when assembling the command
cmd = 'ip link add {ifname} link {source_interface} type {type} mode {mode}'
self._cmd(cmd.format(**self.config))
+ # interface is always A/D down. It needs to be enabled explicitly
+ self.set_admin_state('down')
+
def set_mode(self, mode):
ifname = self.config['ifname']
cmd = f'ip link set dev {ifname} type macvlan mode {mode}'
diff --git a/python/vyos/ifconfig/sstpc.py b/python/vyos/ifconfig/sstpc.py
new file mode 100644
index 000000000..50fc6ee6b
--- /dev/null
+++ b/python/vyos/ifconfig/sstpc.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.ifconfig.interface import Interface
+
+@Interface.register
+class SSTPCIf(Interface):
+ iftype = 'sstpc'
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'sstpc',
+ 'prefixes': ['sstpc', ],
+ 'eternal': 'sstpc[0-9]+$',
+ },
+ }
+
+ def _create(self):
+ # we can not create this interface as it is managed outside
+ pass
+
+ def _delete(self):
+ # we can not create this interface as it is managed outside
+ pass
+
+ def get_mac(self):
+ """ Get a synthetic MAC address. """
+ return self.get_mac_synthetic()
diff --git a/python/vyos/ifconfig/veth.py b/python/vyos/ifconfig/veth.py
new file mode 100644
index 000000000..aafbf226a
--- /dev/null
+++ b/python/vyos/ifconfig/veth.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.ifconfig.interface import Interface
+
+
+@Interface.register
+class VethIf(Interface):
+ """
+ Abstraction of a Linux veth interface
+ """
+ iftype = 'veth'
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'virtual-ethernet',
+ 'prefixes': ['veth', ],
+ 'bridgeable': True,
+ },
+ }
+
+ def _create(self):
+ """
+ Create veth interface in OS kernel. Interface is administrative
+ down by default.
+ """
+ # check before create, as we have 2 veth interfaces in our CLI
+ # interface virtual-ethernet veth0 peer-name 'veth1'
+ # interface virtual-ethernet veth1 peer-name 'veth0'
+ #
+ # but iproute2 creates the pair with one command:
+ # ip link add vet0 type veth peer name veth1
+ if self.exists(self.config['peer_name']):
+ return
+
+ # create virtual-ethernet interface
+ cmd = 'ip link add {ifname} type {type}'.format(**self.config)
+ cmd += f' peer name {self.config["peer_name"]}'
+ self._cmd(cmd)
+
+ # interface is always A/D down. It needs to be enabled explicitly
+ self.set_admin_state('down')
diff --git a/python/vyos/migrator.py b/python/vyos/migrator.py
index c6e3435ca..87c74e1ea 100644
--- a/python/vyos/migrator.py
+++ b/python/vyos/migrator.py
@@ -1,4 +1,4 @@
-# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-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
@@ -16,11 +16,13 @@
import sys
import os
import json
-import subprocess
-import vyos.version
+import logging
+
import vyos.defaults
-import vyos.systemversions as systemversions
-import vyos.formatversions as formatversions
+import vyos.component_version as component_version
+from vyos.util import cmd
+
+log_file = os.path.join(vyos.defaults.directories['config'], 'vyos-migrate.log')
class MigratorError(Exception):
pass
@@ -31,9 +33,21 @@ class Migrator(object):
self._force = force
self._set_vintage = set_vintage
self._config_file_vintage = None
- self._log_file = None
self._changed = False
+ def init_logger(self):
+ self.logger = logging.getLogger(__name__)
+ self.logger.setLevel(logging.DEBUG)
+
+ # on adding the file handler, allow write permission for cfg_group;
+ # restore original umask on exit
+ mask = os.umask(0o113)
+ fh = logging.FileHandler(log_file)
+ formatter = logging.Formatter('%(message)s')
+ fh.setFormatter(formatter)
+ self.logger.addHandler(fh)
+ os.umask(mask)
+
def read_config_file_versions(self):
"""
Get component versions from config file footer and set vintage;
@@ -42,13 +56,13 @@ class Migrator(object):
cfg_file = self._config_file
component_versions = {}
- cfg_versions = formatversions.read_vyatta_versions(cfg_file)
+ cfg_versions = component_version.from_file(cfg_file, vintage='vyatta')
if cfg_versions:
self._config_file_vintage = 'vyatta'
component_versions = cfg_versions
- cfg_versions = formatversions.read_vyos_versions(cfg_file)
+ cfg_versions = component_version.from_file(cfg_file, vintage='vyos')
if cfg_versions:
self._config_file_vintage = 'vyos'
@@ -70,34 +84,15 @@ class Migrator(object):
else:
return True
- def open_log_file(self):
- """
- Open log file for migration, catching any error.
- Note that, on boot, migration takes place before the canonical log
- directory is created, hence write to the config file directory.
- """
- self._log_file = os.path.join(vyos.defaults.directories['config'],
- 'vyos-migrate.log')
- # on creation, allow write permission for cfg_group;
- # restore original umask on exit
- mask = os.umask(0o113)
- try:
- log = open('{0}'.format(self._log_file), 'w')
- log.write("List of executed migration scripts:\n")
- except Exception as e:
- os.umask(mask)
- print("Logging error: {0}".format(e))
- return None
-
- os.umask(mask)
- return log
-
def run_migration_scripts(self, config_file_versions, system_versions):
"""
Run migration scripts iteratively, until config file version equals
system component version.
"""
- log = self.open_log_file()
+ os.environ['VYOS_MIGRATION'] = '1'
+ self.init_logger()
+
+ self.logger.info("List of executed migration scripts:")
cfg_versions = config_file_versions
sys_versions = system_versions
@@ -129,8 +124,9 @@ class Migrator(object):
'{}-to-{}'.format(cfg_ver, next_ver))
try:
- subprocess.check_call([migrate_script,
- self._config_file])
+ out = cmd([migrate_script, self._config_file])
+ self.logger.info(f'{migrate_script}')
+ if out: self.logger.info(out)
except FileNotFoundError:
pass
except Exception as err:
@@ -138,38 +134,25 @@ class Migrator(object):
"".format(migrate_script, err))
sys.exit(1)
- if log:
- try:
- log.write('{0}\n'.format(migrate_script))
- except Exception as e:
- print("Error writing log: {0}".format(e))
-
cfg_ver = next_ver
-
rev_versions[key] = cfg_ver
- if log:
- log.close()
-
+ del os.environ['VYOS_MIGRATION']
return rev_versions
def write_config_file_versions(self, cfg_versions):
"""
Write new versions string.
"""
- versions_string = formatversions.format_versions_string(cfg_versions)
-
- os_version_string = vyos.version.get_version()
-
if self._config_file_vintage == 'vyatta':
- formatversions.write_vyatta_versions_foot(self._config_file,
- versions_string,
- os_version_string)
+ component_version.write_version_footer(cfg_versions,
+ self._config_file,
+ vintage='vyatta')
if self._config_file_vintage == 'vyos':
- formatversions.write_vyos_versions_foot(self._config_file,
- versions_string,
- os_version_string)
+ component_version.write_version_footer(cfg_versions,
+ self._config_file,
+ vintage='vyos')
def save_json_record(self, component_versions: dict):
"""
@@ -200,7 +183,7 @@ class Migrator(object):
# This will force calling all migration scripts:
cfg_versions = {}
- sys_versions = systemversions.get_system_component_version()
+ sys_versions = component_version.from_system()
# save system component versions in json file for easy reference
self.save_json_record(sys_versions)
@@ -216,7 +199,7 @@ class Migrator(object):
if not self._changed:
return
- formatversions.remove_versions(cfg_file)
+ component_version.remove_footer(cfg_file)
self.write_config_file_versions(rev_versions)
@@ -237,7 +220,7 @@ class VirtualMigrator(Migrator):
if not self._changed:
return
- formatversions.remove_versions(cfg_file)
+ component_version.remove_footer(cfg_file)
self.write_config_file_versions(cfg_versions)
diff --git a/python/vyos/nat.py b/python/vyos/nat.py
index 31bbdc386..8a311045a 100644
--- a/python/vyos/nat.py
+++ b/python/vyos/nat.py
@@ -16,6 +16,8 @@
from vyos.template import is_ip_network
from vyos.util import dict_search_args
+from vyos.template import bracketize_ipv6
+
def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
output = []
@@ -69,6 +71,7 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
else:
translation_output.append('to')
if addr:
+ addr = bracketize_ipv6(addr)
translation_output.append(addr)
options = []
@@ -85,8 +88,13 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
translation_str += f' {",".join(options)}'
for target in ['source', 'destination']:
+ if target not in rule_conf:
+ continue
+
+ side_conf = rule_conf[target]
prefix = target[:1]
- addr = dict_search_args(rule_conf, target, 'address')
+
+ addr = dict_search_args(side_conf, 'address')
if addr and not (ignore_type_addr and target == nat_type):
operator = ''
if addr[:1] == '!':
@@ -94,7 +102,7 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
addr = addr[1:]
output.append(f'{ip_prefix} {prefix}addr {operator} {addr}')
- addr_prefix = dict_search_args(rule_conf, target, 'prefix')
+ addr_prefix = dict_search_args(side_conf, 'prefix')
if addr_prefix and ipv6:
operator = ''
if addr_prefix[:1] == '!':
@@ -102,7 +110,7 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
addr_prefix = addr[1:]
output.append(f'ip6 {prefix}addr {operator} {addr_prefix}')
- port = dict_search_args(rule_conf, target, 'port')
+ port = dict_search_args(side_conf, 'port')
if port:
protocol = rule_conf['protocol']
if protocol == 'tcp_udp':
@@ -113,6 +121,51 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
port = port[1:]
output.append(f'{protocol} {prefix}port {operator} {{ {port} }}')
+ if 'group' in side_conf:
+ group = side_conf['group']
+ if 'address_group' in group and not (ignore_type_addr and target == nat_type):
+ group_name = group['address_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_prefix} {prefix}addr {operator} @A_{group_name}')
+ # Generate firewall group domain-group
+ elif 'domain_group' in group and not (ignore_type_addr and target == nat_type):
+ group_name = group['domain_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_prefix} {prefix}addr {operator} @D_{group_name}')
+ elif 'network_group' in group and not (ignore_type_addr and target == nat_type):
+ group_name = group['network_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_prefix} {prefix}addr {operator} @N_{group_name}')
+ if 'mac_group' in group:
+ group_name = group['mac_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'ether {prefix}addr {operator} @M_{group_name}')
+ if 'port_group' in group:
+ proto = rule_conf['protocol']
+ group_name = group['port_group']
+
+ if proto == 'tcp_udp':
+ proto = 'th'
+
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+
+ output.append(f'{proto} {prefix}port {operator} @P_{group_name}')
+
output.append('counter')
if 'log' in rule_conf:
diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py
index 7e3545c87..d02ad4de6 100644
--- a/python/vyos/opmode.py
+++ b/python/vyos/opmode.py
@@ -1,4 +1,4 @@
-# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2022-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
@@ -16,11 +16,16 @@
import re
import sys
import typing
+from humps import decamelize
class Error(Exception):
""" Any error that makes requested operation impossible to complete
for reasons unrelated to the user input or script logic.
+
+ This is the base class, scripts should not use it directly
+ and should raise more specific errors instead,
+ whenever possible.
"""
pass
@@ -44,15 +49,45 @@ class PermissionDenied(Error):
"""
pass
+class InsufficientResources(Error):
+ """ Requested operation and its arguments are valid but the system
+ does not have enough resources (such as drive space or memory)
+ to complete it.
+ """
+ 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.
+ """
+ pass
+
+class CommitInProgress(Error):
+ """ Requested operation is valid, but not possible at the time due
+ to a commit being in progress.
+ """
+ pass
+
+class InternalError(Error):
+ """ Any situation when VyOS detects that it could not perform
+ an operation correctly due to logic errors in its own code
+ or errors in underlying software.
+ """
+ pass
+
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|set)", 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
@@ -93,6 +128,51 @@ def _get_arg_type(t):
else:
return t
+def _normalize_field_name(name):
+ # Convert the name to string if it is not
+ # (in some cases they may be numbers)
+ name = str(name)
+
+ # Replace all separators with underscores
+ name = re.sub(r'(\s|[\(\)\[\]\{\}\-\.\,:\"\'\`])+', '_', name)
+
+ # Replace specific characters with textual descriptions
+ name = re.sub(r'@', '_at_', name)
+ name = re.sub(r'%', '_percentage_', name)
+ name = re.sub(r'~', '_tilde_', name)
+
+ # Force all letters to lowercase
+ name = name.lower()
+
+ # Remove leading and trailing underscores, if any
+ name = re.sub(r'(^(_+)(?=[^_])|_+$)', '', name)
+
+ # Ensure there are only single underscores
+ name = re.sub(r'_+', '_', name)
+
+ return name
+
+def _normalize_dict_field_names(old_dict):
+ new_dict = {}
+
+ for key in old_dict:
+ new_key = _normalize_field_name(key)
+ new_dict[new_key] = _normalize_field_names(old_dict[key])
+
+ # Sanity check
+ if len(old_dict) != len(new_dict):
+ raise InternalError("Dictionary fields do not allow unique normalization")
+ else:
+ return new_dict
+
+def _normalize_field_names(value):
+ if isinstance(value, dict):
+ return _normalize_dict_field_names(value)
+ elif isinstance(value, list):
+ return list(map(lambda v: _normalize_field_names(v), value))
+ else:
+ return value
+
def run(module):
from argparse import ArgumentParser
@@ -134,20 +214,25 @@ 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)
if not args["raw"]:
return res
else:
+ if not isinstance(res, dict) and not isinstance(res, list):
+ raise InternalError(f"Bare literal is not an acceptable raw output, must be a list or an object.\
+ The output was:{res}")
+ res = decamelize(res)
+ res = _normalize_field_names(res)
from json import dumps
return dumps(res, indent=4)
else:
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/systemversions.py b/python/vyos/systemversions.py
deleted file mode 100644
index f2da76d4f..000000000
--- a/python/vyos/systemversions.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this library. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-import re
-import sys
-import vyos.defaults
-from vyos.xml import component_version
-
-# legacy version, reading from the file names in
-# /opt/vyatta/etc/config-migrate/current
-def get_system_versions():
- """
- Get component versions from running system; critical failure if
- unable to read migration directory.
- """
- system_versions = {}
-
- try:
- version_info = os.listdir(vyos.defaults.directories['current'])
- except OSError as err:
- print("OS error: {}".format(err))
- sys.exit(1)
-
- for info in version_info:
- if re.match(r'[\w,-]+@\d+', info):
- pair = info.split('@')
- system_versions[pair[0]] = int(pair[1])
-
- return system_versions
-
-# read from xml cache
-def get_system_component_version():
- return component_version()
diff --git a/python/vyos/template.py b/python/vyos/template.py
index 2a4135f9e..15240f815 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -193,6 +193,16 @@ def dot_colon_to_dash(text):
text = text.replace(".", "-")
return text
+@register_filter('generate_uuid4')
+def generate_uuid4(text):
+ """ Generate random unique ID
+ Example:
+ % uuid4()
+ UUID('958ddf6a-ef14-4e81-8cfb-afb12456d1c5')
+ """
+ from uuid import uuid4
+ return uuid4()
+
@register_filter('netmask_from_cidr')
def netmask_from_cidr(prefix):
""" Take CIDR prefix and convert the prefix length to a "subnet mask".
@@ -476,6 +486,8 @@ def get_esp_ike_cipher(group_config, ike_group=None):
continue
tmp = '{encryption}-{hash}'.format(**proposal)
+ if 'prf' in proposal:
+ tmp += '-' + proposal['prf']
if 'dh_group' in proposal:
tmp += '-' + pfs_lut[ 'dh-group' + proposal['dh_group'] ]
elif 'pfs' in group_config and group_config['pfs'] != 'disable':
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 461df9a6e..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
@@ -539,13 +541,16 @@ def seconds_to_human(s, separator=""):
return result
-def bytes_to_human(bytes, initial_exponent=0):
+def bytes_to_human(bytes, initial_exponent=0, precision=2):
""" Converts a value in bytes to a human-readable size string like 640 KB
The initial_exponent parameter is the exponent of 2,
e.g. 10 (1024) for kilobytes, 20 (1024 * 1024) for megabytes.
"""
+ if bytes == 0:
+ return "0 B"
+
from math import log2
bytes = bytes * (2**initial_exponent)
@@ -571,9 +576,40 @@ def bytes_to_human(bytes, initial_exponent=0):
# Add a new case when the first machine with petabyte RAM
# hits the market.
- size_string = "{0:.2f} {1}".format(value, suffix)
+ size_string = "{0:.{1}f} {2}".format(value, precision, suffix)
return size_string
+def human_to_bytes(value):
+ """ Converts a data amount with a unit suffix to bytes, like 2K to 2048 """
+
+ from re import match as re_match
+
+ res = re_match(r'^\s*(\d+(?:\.\d+)?)\s*([a-zA-Z]+)\s*$', value)
+
+ if not res:
+ raise ValueError(f"'{value}' is not a valid data amount")
+ else:
+ amount = float(res.group(1))
+ unit = res.group(2).lower()
+
+ if unit == 'b':
+ res = amount
+ elif (unit == 'k') or (unit == 'kb'):
+ res = amount * 1024
+ elif (unit == 'm') or (unit == 'mb'):
+ res = amount * 1024**2
+ elif (unit == 'g') or (unit == 'gb'):
+ res = amount * 1024**3
+ elif (unit == 't') or (unit == 'tb'):
+ res = amount * 1024**4
+ else:
+ raise ValueError(f"Unsupported data unit '{unit}'")
+
+ # There cannot be fractional bytes, so we convert them to integer.
+ # However, truncating causes problems with conversion back to human unit,
+ # so we round instead -- that seems to work well enough.
+ return round(res)
+
def get_cfg_group_id():
from grp import getgrnam
from vyos.defaults import cfg_group
@@ -1105,3 +1141,18 @@ def sysctl_write(name, value):
call(f'sysctl -wq {name}={value}')
return True
return False
+
+# approach follows a discussion in:
+# https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case
+def camel_to_snake_case(name: str) -> str:
+ pattern = r'\d+|[A-Z]?[a-z]+|\W|[A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$)'
+ words = re.findall(pattern, name)
+ return '_'.join(map(str.lower, words))
+
+def load_as_module(name: str, path: str):
+ import importlib.util
+
+ spec = importlib.util.spec_from_file_location(name, path)
+ mod = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(mod)
+ return mod
diff --git a/scripts/check-pr-title-and-commit-messages.py b/scripts/check-pr-title-and-commit-messages.py
index c30c3ef1f..1d4a3cfe3 100755
--- a/scripts/check-pr-title-and-commit-messages.py
+++ b/scripts/check-pr-title-and-commit-messages.py
@@ -2,12 +2,13 @@
import re
import sys
+import time
import requests
from pprint import pprint
# Use the same regex for PR title and commit messages for now
-title_regex = r'^(([a-zA-Z]+:\s)?)T\d+:\s+[^\s]+.*'
+title_regex = r'^(([a-zA-Z\-_.]+:\s)?)T\d+:\s+[^\s]+.*'
commit_regex = title_regex
def check_pr_title(title):
@@ -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/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_component_version.py b/smoketest/scripts/cli/test_component_version.py
index 1355c1f94..7b1b12c53 100755
--- a/smoketest/scripts/cli/test_component_version.py
+++ b/smoketest/scripts/cli/test_component_version.py
@@ -16,7 +16,7 @@
import unittest
-from vyos.systemversions import get_system_versions, get_system_component_version
+import vyos.component_version as component_version
# After T3474, component versions should be updated in the files in
# vyos-1x/interface-definitions/include/version/
@@ -24,8 +24,8 @@ from vyos.systemversions import get_system_versions, get_system_component_versio
# that in the xml cache.
class TestComponentVersion(unittest.TestCase):
def setUp(self):
- self.legacy_d = get_system_versions()
- self.xml_d = get_system_component_version()
+ self.legacy_d = component_version.legacy_from_system()
+ self.xml_d = component_version.from_system()
self.set_legacy_d = set(self.legacy_d)
self.set_xml_d = set(self.xml_d)
diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py
index cc0cdaec0..902156ee6 100644..100755
--- a/smoketest/scripts/cli/test_container.py
+++ b/smoketest/scripts/cli/test_container.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
+import glob
import json
from base_vyostest_shim import VyOSUnitTestSHIM
@@ -25,10 +26,13 @@ from vyos.util import process_named_running
from vyos.util import read_file
base_path = ['container']
-cont_image = 'busybox'
+cont_image = 'busybox:stable' # busybox is included in vyos-build
prefix = '192.168.205.0/24'
net_name = 'NET01'
-PROCESS_NAME = 'podman'
+PROCESS_NAME = 'conmon'
+PROCESS_PIDFILE = '/run/vyos-container-{0}.service.pid'
+
+busybox_image_path = '/usr/share/vyos/busybox-stable.tar'
def cmd_to_json(command):
c = cmd(command + ' --format=json')
@@ -37,7 +41,34 @@ def cmd_to_json(command):
return data
-class TesContainer(VyOSUnitTestSHIM.TestCase):
+class TestContainer(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestContainer, cls).setUpClass()
+
+ # Load image for smoketest provided in vyos-build
+ try:
+ cmd(f'cat {busybox_image_path} | sudo podman load')
+ except:
+ cls.skipTest(cls, reason='busybox image not available')
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestContainer, cls).tearDownClass()
+
+ # Cleanup podman image
+ cmd(f'sudo podman image rm -f {cont_image}')
+
+ def tearDown(self):
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ # Ensure no container process remains
+ self.assertIsNone(process_named_running(PROCESS_NAME))
+
+ # Ensure systemd units are removed
+ units = glob.glob('/run/systemd/system/vyos-container-*')
+ self.assertEqual(units, [])
def test_01_basic_container(self):
cont_name = 'c1'
@@ -53,13 +84,17 @@ class TesContainer(VyOSUnitTestSHIM.TestCase):
# commit changes
self.cli_commit()
+ pid = 0
+ with open(PROCESS_PIDFILE.format(cont_name), 'r') as f:
+ pid = int(f.read())
+
# Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ self.assertEqual(process_named_running(PROCESS_NAME), pid)
def test_02_container_network(self):
cont_name = 'c2'
cont_ip = '192.168.205.25'
- self.cli_set(base_path + ['network', net_name, 'ipv4-prefix', prefix])
+ self.cli_set(base_path + ['network', net_name, 'prefix', prefix])
self.cli_set(base_path + ['name', cont_name, 'image', cont_image])
self.cli_set(base_path + ['name', cont_name, 'network', net_name, 'address', cont_ip])
@@ -67,7 +102,7 @@ class TesContainer(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
n = cmd_to_json(f'sudo podman network inspect {net_name}')
- json_subnet = n['plugins'][0]['ipam']['ranges'][0][0]['subnet']
+ json_subnet = n['subnets'][0]['subnet']
c = cmd_to_json(f'sudo podman container inspect {cont_name}')
json_ip = c['NetworkSettings']['Networks'][net_name]['IPAddress']
diff --git a/smoketest/scripts/cli/test_dependency_graph.py b/smoketest/scripts/cli/test_dependency_graph.py
new file mode 100755
index 000000000..45a40acc4
--- /dev/null
+++ b/smoketest/scripts/cli/test_dependency_graph.py
@@ -0,0 +1,54 @@
+#!/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 json
+import unittest
+from graphlib import TopologicalSorter, CycleError
+
+DEP_FILE = '/usr/share/vyos/config-mode-dependencies.json'
+
+def graph_from_dict(d):
+ g = {}
+ for k in list(d):
+ g[k] = set()
+ # add the dependencies for every sub-case; should there be cases
+ # that are mutally exclusive in the future, the graphs will be
+ # distinguished
+ for el in list(d[k]):
+ g[k] |= set(d[k][el])
+ return g
+
+class TestDependencyGraph(unittest.TestCase):
+ def setUp(self):
+ with open(DEP_FILE) as f:
+ dd = json.load(f)
+ self.dependency_graph = graph_from_dict(dd)
+
+ def test_cycles(self):
+ ts = TopologicalSorter(self.dependency_graph)
+ out = None
+ try:
+ # get node iterator
+ order = ts.static_order()
+ # try iteration
+ _ = [*order]
+ except CycleError as e:
+ out = e.args
+
+ self.assertIsNone(out)
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 821925bcd..f1c18d761 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -17,11 +17,13 @@
import unittest
from glob import glob
+from time import sleep
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.util import cmd
+from vyos.util import run
sysfs_config = {
'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'default': '0', 'test_value': 'disable'},
@@ -76,6 +78,17 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
break
self.assertTrue(not matched if inverse else matched, msg=search)
+ def wait_for_domain_resolver(self, table, set_name, element, max_wait=10):
+ # Resolver no longer blocks commit, need to wait for daemon to populate set
+ count = 0
+ while count < max_wait:
+ code = run(f'sudo nft get element {table} {set_name} {{ {element} }}')
+ if code == 0:
+ return True
+ count += 1
+ sleep(1)
+ return False
+
def test_geoip(self):
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'drop'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'se'])
@@ -111,6 +124,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '123'])
self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.com'])
self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.org'])
+ self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'eth0'])
+ self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'vtun0'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'accept'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network'])
@@ -121,10 +136,15 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'group', 'mac-group', 'smoketest_mac'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'source', 'group', 'domain-group', 'smoketest_domain'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'outbound-interface', 'interface-group', 'smoketest_interface'])
self.cli_set(['firewall', 'interface', 'eth0', 'in', 'name', 'smoketest'])
self.cli_commit()
+
+ self.wait_for_domain_resolver('ip vyos_filter', 'D_smoketest_domain', '192.0.2.5')
+
nftables_search = [
['iifname "eth0"', 'jump NAME_smoketest'],
['ip saddr @N_smoketest_network', 'ip daddr 172.16.10.10', 'th dport @P_smoketest_port', 'return'],
@@ -135,7 +155,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['set D_smoketest_domain'],
['elements = { 192.0.2.5, 192.0.2.8,'],
['192.0.2.10, 192.0.2.11 }'],
- ['ip saddr @D_smoketest_domain', 'return']
+ ['ip saddr @D_smoketest_domain', 'return'],
+ ['oifname @I_smoketest_interface', 'return']
]
self.verify_nftables(nftables_search, 'ip vyos_filter')
@@ -178,6 +199,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
name = 'smoketest'
interface = 'eth0'
mss_range = '501-1460'
+ conn_mark = '555'
self.cli_set(['firewall', 'name', name, 'default-action', 'drop'])
self.cli_set(['firewall', 'name', name, 'enable-default-log'])
@@ -209,15 +231,18 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'name', name, 'rule', '5', 'protocol', 'tcp'])
self.cli_set(['firewall', 'name', name, 'rule', '5', 'tcp', 'flags', 'syn'])
self.cli_set(['firewall', 'name', name, 'rule', '5', 'tcp', 'mss', mss_range])
- self.cli_set(['firewall', 'name', name, 'rule', '5', 'inbound-interface', interface])
+ self.cli_set(['firewall', 'name', name, 'rule', '5', 'inbound-interface', 'interface-name', interface])
self.cli_set(['firewall', 'name', name, 'rule', '6', 'action', 'return'])
self.cli_set(['firewall', 'name', name, 'rule', '6', 'protocol', 'gre'])
- self.cli_set(['firewall', 'name', name, 'rule', '6', 'outbound-interface', interface])
+ self.cli_set(['firewall', 'name', name, 'rule', '6', 'outbound-interface', 'interface-name', interface])
+ self.cli_set(['firewall', 'name', name, 'rule', '6', 'connection-mark', conn_mark])
self.cli_set(['firewall', 'interface', interface, 'in', 'name', name])
self.cli_commit()
+ mark_hex = "{0:#010x}".format(int(conn_mark))
+
nftables_search = [
[f'iifname "{interface}"', f'jump NAME_{name}'],
['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[smoketest-1-A]" level debug', 'ip ttl 15', 'return'],
@@ -226,7 +251,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['log prefix "[smoketest-default-D]"','smoketest default-action', 'drop'],
['tcp dport 22', 'add @RECENT_smoketest_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'drop'],
['tcp flags & syn == syn', f'tcp option maxseg size {mss_range}', f'iifname "{interface}"'],
- ['meta l4proto gre', f'oifname "{interface}"', 'return']
+ ['meta l4proto gre', f'oifname "{interface}"', f'ct mark {mark_hex}', 'return']
]
self.verify_nftables(nftables_search, 'ip vyos_filter')
@@ -274,6 +299,40 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip vyos_filter')
+ def test_ipv4_mask(self):
+ name = 'smoketest-mask'
+ interface = 'eth0'
+
+ self.cli_set(['firewall', 'group', 'address-group', 'mask_group', 'address', '1.1.1.1'])
+
+ self.cli_set(['firewall', 'name', name, 'default-action', 'drop'])
+ self.cli_set(['firewall', 'name', name, 'enable-default-log'])
+
+ self.cli_set(['firewall', 'name', name, 'rule', '1', 'action', 'drop'])
+ self.cli_set(['firewall', 'name', name, 'rule', '1', 'destination', 'address', '0.0.1.2'])
+ self.cli_set(['firewall', 'name', name, 'rule', '1', 'destination', 'address-mask', '0.0.255.255'])
+
+ self.cli_set(['firewall', 'name', name, 'rule', '2', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', name, 'rule', '2', 'source', 'address', '!0.0.3.4'])
+ self.cli_set(['firewall', 'name', name, 'rule', '2', 'source', 'address-mask', '0.0.255.255'])
+
+ self.cli_set(['firewall', 'name', name, 'rule', '3', 'action', 'drop'])
+ self.cli_set(['firewall', 'name', name, 'rule', '3', 'source', 'group', 'address-group', 'mask_group'])
+ self.cli_set(['firewall', 'name', name, 'rule', '3', 'source', 'address-mask', '0.0.255.255'])
+
+ self.cli_set(['firewall', 'interface', interface, 'in', 'name', name])
+
+ self.cli_commit()
+
+ nftables_search = [
+ [f'daddr & 0.0.255.255 == 0.0.1.2'],
+ [f'saddr & 0.0.255.255 != 0.0.3.4'],
+ [f'saddr & 0.0.255.255 == @A_mask_group']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip vyos_filter')
+
+
def test_ipv6_basic_rules(self):
name = 'v6-smoketest'
interface = 'eth0'
@@ -290,11 +349,11 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'action', 'reject'])
self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'protocol', 'tcp_udp'])
self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'destination', 'port', '8888'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'inbound-interface', interface])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'inbound-interface', 'interface-name', interface])
self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'action', 'return'])
self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'protocol', 'gre'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'outbound-interface', interface])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'outbound-interface', 'interface-name', interface])
self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name])
@@ -353,6 +412,39 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip6 vyos_filter')
+ def test_ipv6_mask(self):
+ name = 'v6-smoketest-mask'
+ interface = 'eth0'
+
+ self.cli_set(['firewall', 'group', 'ipv6-address-group', 'mask_group', 'address', '::beef'])
+
+ self.cli_set(['firewall', 'ipv6-name', name, 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'enable-default-log'])
+
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'action', 'drop'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'destination', 'address', '::1111:2222:3333:4444'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'destination', 'address-mask', '::ffff:ffff:ffff:ffff'])
+
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'source', 'address', '!::aaaa:bbbb:cccc:dddd'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'source', 'address-mask', '::ffff:ffff:ffff:ffff'])
+
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'action', 'drop'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'source', 'group', 'address-group', 'mask_group'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'source', 'address-mask', '::ffff:ffff:ffff:ffff'])
+
+ self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name])
+
+ self.cli_commit()
+
+ nftables_search = [
+ ['daddr & ::ffff:ffff:ffff:ffff == ::1111:2222:3333:4444'],
+ ['saddr & ::ffff:ffff:ffff:ffff != ::aaaa:bbbb:cccc:dddd'],
+ ['saddr & ::ffff:ffff:ffff:ffff == @A6_mask_group']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip6 vyos_filter')
+
def test_state_policy(self):
self.cli_set(['firewall', 'state-policy', 'established', 'action', 'accept'])
self.cli_set(['firewall', 'state-policy', 'related', 'action', 'accept'])
diff --git a/smoketest/scripts/cli/test_ha_vrrp.py b/smoketest/scripts/cli/test_ha_vrrp.py
index 68905e447..3a4de2d8d 100755
--- a/smoketest/scripts/cli/test_ha_vrrp.py
+++ b/smoketest/scripts/cli/test_ha_vrrp.py
@@ -87,11 +87,21 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase):
advertise_interval = '77'
priority = '123'
preempt_delay = '400'
+ startup_delay = '120'
+ garp_master_delay = '2'
+ garp_master_repeat = '3'
+ garp_master_refresh = '4'
+ garp_master_refresh_repeat = '5'
+ garp_interval = '1.5'
+ group_garp_master_delay = '12'
+ group_garp_master_repeat = '13'
+ group_garp_master_refresh = '14'
for group in groups:
vlan_id = group.lstrip('VLAN')
vip = f'100.64.{vlan_id}.1/24'
group_base = base_path + ['vrrp', 'group', group]
+ global_param_base = base_path + ['vrrp', 'global-parameters']
self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]])
@@ -110,9 +120,32 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase):
self.cli_set(group_base + ['authentication', 'type', 'plaintext-password'])
self.cli_set(group_base + ['authentication', 'password', f'{group}'])
+ # GARP
+ self.cli_set(group_base + ['garp', 'master-delay', group_garp_master_delay])
+ self.cli_set(group_base + ['garp', 'master-repeat', group_garp_master_repeat])
+ self.cli_set(group_base + ['garp', 'master-refresh', group_garp_master_refresh])
+
+ # Global parameters
+ #config = getConfig(f'global_defs')
+ self.cli_set(global_param_base + ['startup-delay', f'{startup_delay}'])
+ self.cli_set(global_param_base + ['garp', 'interval', f'{garp_interval}'])
+ self.cli_set(global_param_base + ['garp', 'master-delay', f'{garp_master_delay}'])
+ self.cli_set(global_param_base + ['garp', 'master-repeat', f'{garp_master_repeat}'])
+ self.cli_set(global_param_base + ['garp', 'master-refresh', f'{garp_master_refresh}'])
+ self.cli_set(global_param_base + ['garp', 'master-refresh-repeat', f'{garp_master_refresh_repeat}'])
+
# commit changes
self.cli_commit()
+ # Check Global parameters
+ config = getConfig(f'global_defs')
+ self.assertIn(f'vrrp_startup_delay {startup_delay}', config)
+ self.assertIn(f'vrrp_garp_interval {garp_interval}', config)
+ self.assertIn(f'vrrp_garp_master_delay {garp_master_delay}', config)
+ self.assertIn(f'vrrp_garp_master_repeat {garp_master_repeat}', config)
+ self.assertIn(f'vrrp_garp_master_refresh {garp_master_refresh}', config)
+ self.assertIn(f'vrrp_garp_master_refresh_repeat {garp_master_refresh_repeat}', config)
+
for group in groups:
vlan_id = group.lstrip('VLAN')
vip = f'100.64.{vlan_id}.1/24'
@@ -132,6 +165,11 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'auth_pass "{group}"', config)
self.assertIn(f'auth_type PASS', config)
+ #GARP
+ self.assertIn(f'garp_master_delay {group_garp_master_delay}', config)
+ self.assertIn(f'garp_master_refresh {group_garp_master_refresh}', config)
+ self.assertIn(f'garp_master_repeat {group_garp_master_repeat}', config)
+
def test_03_sync_group(self):
sync_group = 'VyOS'
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_interfaces_pppoe.py b/smoketest/scripts/cli/test_interfaces_pppoe.py
index 8927121a8..08b7f2f46 100755
--- a/smoketest/scripts/cli/test_interfaces_pppoe.py
+++ b/smoketest/scripts/cli/test_interfaces_pppoe.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
@@ -57,8 +57,8 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):
def test_01_pppoe_client(self):
# Check if PPPoE dialer can be configured and runs
for interface in self._interfaces:
- user = 'VyOS-user-' + interface
- passwd = 'VyOS-passwd-' + interface
+ user = f'VyOS-user-{interface}'
+ passwd = f'VyOS-passwd-{interface}'
mtu = '1400'
self.cli_set(base_path + [interface, 'authentication', 'user', user])
@@ -76,23 +76,26 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):
# verify configuration file(s)
for interface in self._interfaces:
- user = 'VyOS-user-' + interface
- password = 'VyOS-passwd-' + interface
+ user = f'VyOS-user-{interface}'
+ passwd = f'VyOS-passwd-{interface}'
tmp = get_config_value(interface, 'mtu')[1]
self.assertEqual(tmp, mtu)
tmp = get_config_value(interface, 'user')[1].replace('"', '')
self.assertEqual(tmp, user)
tmp = get_config_value(interface, 'password')[1].replace('"', '')
- self.assertEqual(tmp, password)
+ self.assertEqual(tmp, passwd)
tmp = get_config_value(interface, 'ifname')[1]
self.assertEqual(tmp, interface)
def test_02_pppoe_client_disabled_interface(self):
# Check if PPPoE Client can be disabled
for interface in self._interfaces:
- self.cli_set(base_path + [interface, 'authentication', 'user', 'vyos'])
- self.cli_set(base_path + [interface, 'authentication', 'password', 'vyos'])
+ user = f'VyOS-user-{interface}'
+ passwd = f'VyOS-passwd-{interface}'
+
+ self.cli_set(base_path + [interface, 'authentication', 'user', user])
+ self.cli_set(base_path + [interface, 'authentication', 'password', passwd])
self.cli_set(base_path + [interface, 'source-interface', self._source_interface])
self.cli_set(base_path + [interface, 'disable'])
@@ -117,7 +120,10 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):
def test_03_pppoe_authentication(self):
# When username or password is set - so must be the other
for interface in self._interfaces:
- self.cli_set(base_path + [interface, 'authentication', 'user', 'vyos'])
+ user = f'VyOS-user-{interface}'
+ passwd = f'VyOS-passwd-{interface}'
+
+ self.cli_set(base_path + [interface, 'authentication', 'user', user])
self.cli_set(base_path + [interface, 'source-interface', self._source_interface])
self.cli_set(base_path + [interface, 'ipv6', 'address', 'autoconf'])
@@ -125,7 +131,7 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_set(base_path + [interface, 'authentication', 'password', 'vyos'])
+ self.cli_set(base_path + [interface, 'authentication', 'password', passwd])
self.cli_commit()
@@ -136,8 +142,11 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):
sla_len = '8'
for interface in self._interfaces:
- self.cli_set(base_path + [interface, 'authentication', 'user', 'vyos'])
- self.cli_set(base_path + [interface, 'authentication', 'password', 'vyos'])
+ user = f'VyOS-user-{interface}'
+ passwd = f'VyOS-passwd-{interface}'
+
+ self.cli_set(base_path + [interface, 'authentication', 'user', user])
+ self.cli_set(base_path + [interface, 'authentication', 'password', passwd])
self.cli_set(base_path + [interface, 'no-default-route'])
self.cli_set(base_path + [interface, 'no-peer-dns'])
self.cli_set(base_path + [interface, 'source-interface', self._source_interface])
@@ -149,18 +158,54 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):
self.cli_set(dhcpv6_pd_base + ['interface', self._source_interface, 'address', address])
self.cli_set(dhcpv6_pd_base + ['interface', self._source_interface, 'sla-id', sla_id])
- # commit changes
- self.cli_commit()
+ # commit changes
+ self.cli_commit()
+
+ for interface in self._interfaces:
+ user = f'VyOS-user-{interface}'
+ passwd = f'VyOS-passwd-{interface}'
# verify "normal" PPPoE value - 1492 is default MTU
tmp = get_config_value(interface, 'mtu')[1]
self.assertEqual(tmp, '1492')
tmp = get_config_value(interface, 'user')[1].replace('"', '')
- self.assertEqual(tmp, 'vyos')
+ self.assertEqual(tmp, user)
tmp = get_config_value(interface, 'password')[1].replace('"', '')
- self.assertEqual(tmp, 'vyos')
+ self.assertEqual(tmp, passwd)
tmp = get_config_value(interface, '+ipv6 ipv6cp-use-ipaddr')
self.assertListEqual(tmp, ['+ipv6', 'ipv6cp-use-ipaddr'])
+ def test_05_pppoe_options(self):
+ # Check if PPPoE dialer can be configured with DHCPv6-PD
+ for interface in self._interfaces:
+ user = f'VyOS-user-{interface}'
+ passwd = f'VyOS-passwd-{interface}'
+ ac_name = f'AC{interface}'
+ service_name = f'SRV{interface}'
+ host_uniq = 'cafebeefBABE123456'
+
+ self.cli_set(base_path + [interface, 'authentication', 'user', user])
+ self.cli_set(base_path + [interface, 'authentication', 'password', passwd])
+ self.cli_set(base_path + [interface, 'source-interface', self._source_interface])
+
+ self.cli_set(base_path + [interface, 'access-concentrator', ac_name])
+ self.cli_set(base_path + [interface, 'service-name', service_name])
+ self.cli_set(base_path + [interface, 'host-uniq', host_uniq])
+
+ # commit changes
+ self.cli_commit()
+
+ for interface in self._interfaces:
+ ac_name = f'AC{interface}'
+ service_name = f'SRV{interface}'
+ host_uniq = 'cafebeefBABE123456'
+
+ tmp = get_config_value(interface, 'pppoe-ac')[1]
+ self.assertEqual(tmp, f'"{ac_name}"')
+ tmp = get_config_value(interface, 'pppoe-service')[1]
+ self.assertEqual(tmp, f'"{service_name}"')
+ tmp = get_config_value(interface, 'pppoe-host-uniq')[1]
+ self.assertEqual(tmp, f'"{host_uniq}"')
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/validators/interface-name b/smoketest/scripts/cli/test_interfaces_virtual_ethernet.py
index 105815eee..4732342fc 100755
--- a/src/validators/interface-name
+++ b/smoketest/scripts/cli/test_interfaces_virtual_ethernet.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# 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
@@ -15,20 +15,25 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import re
+import unittest
-from sys import argv
-from sys import exit
+from vyos.ifconfig import Section
+from base_interfaces_test import BasicInterfaceTest
-pattern = '^(bond|br|dum|en|ersp|eth|gnv|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|vti|vtun|vxlan|wg|wlan|wwan)[0-9]+(.\d+)?|lo$'
+class VEthInterfaceTest(BasicInterfaceTest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls._test_dhcp = True
+ cls._base_path = ['interfaces', 'virtual-ethernet']
-if __name__ == '__main__':
- if len(argv) != 2:
- exit(1)
- interface = argv[1]
+ cls._options = {
+ 'veth0': ['peer-name veth1'],
+ 'veth1': ['peer-name veth0'],
+ }
+
+ cls._interfaces = list(cls._options)
+ # call base-classes classmethod
+ super(VEthInterfaceTest, cls).setUpClass()
- if re.match(pattern, interface):
- exit(0)
- if os.path.exists(f'/sys/class/net/{interface}'):
- exit(0)
- exit(1)
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_wireguard.py b/smoketest/scripts/cli/test_interfaces_wireguard.py
index f3e9670f7..14fc8d109 100755
--- a/smoketest/scripts/cli/test_interfaces_wireguard.py
+++ b/smoketest/scripts/cli/test_interfaces_wireguard.py
@@ -62,10 +62,10 @@ class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase):
self.assertTrue(os.path.isdir(f'/sys/class/net/{intf}'))
-
def test_wireguard_add_remove_peer(self):
# T2939: Create WireGuard interfaces with associated peers.
# Remove one of the configured peers.
+ # T4774: Test prevention of duplicate peer public keys
interface = 'wg0'
port = '12345'
privkey = '6ISOkASm6VhHOOSz/5iIxw+Q9adq9zA17iMM4X40dlc='
@@ -80,11 +80,17 @@ class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + [interface, 'peer', 'PEER01', 'allowed-ips', '10.205.212.10/32'])
self.cli_set(base_path + [interface, 'peer', 'PEER01', 'address', '192.0.2.1'])
- self.cli_set(base_path + [interface, 'peer', 'PEER02', 'public-key', pubkey_2])
+ self.cli_set(base_path + [interface, 'peer', 'PEER02', 'public-key', pubkey_1])
self.cli_set(base_path + [interface, 'peer', 'PEER02', 'port', port])
self.cli_set(base_path + [interface, 'peer', 'PEER02', 'allowed-ips', '10.205.212.11/32'])
self.cli_set(base_path + [interface, 'peer', 'PEER02', 'address', '192.0.2.2'])
+ # Duplicate pubkey_1
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_set(base_path + [interface, 'peer', 'PEER02', 'public-key', pubkey_2])
+
# Commit peers
self.cli_commit()
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_nat.py b/smoketest/scripts/cli/test_nat.py
index f824838c0..9f4e3b831 100755
--- a/smoketest/scripts/cli/test_nat.py
+++ b/smoketest/scripts/cli/test_nat.py
@@ -16,6 +16,7 @@
import jmespath
import json
+import os
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
@@ -28,6 +29,9 @@ src_path = base_path + ['source']
dst_path = base_path + ['destination']
static_path = base_path + ['static']
+nftables_nat_config = '/run/nftables_nat.conf'
+nftables_static_nat_conf = '/run/nftables_static-nat-rules.nft'
+
class TestNAT(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
@@ -40,6 +44,8 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
def tearDown(self):
self.cli_delete(base_path)
self.cli_commit()
+ self.assertFalse(os.path.exists(nftables_nat_config))
+ self.assertFalse(os.path.exists(nftables_static_nat_conf))
def verify_nftables(self, nftables_search, table, inverse=False, args=''):
nftables_output = cmd(f'sudo nft {args} list table {table}')
@@ -52,6 +58,17 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
break
self.assertTrue(not matched if inverse else matched, msg=search)
+ def wait_for_domain_resolver(self, table, set_name, element, max_wait=10):
+ # Resolver no longer blocks commit, need to wait for daemon to populate set
+ count = 0
+ while count < max_wait:
+ code = run(f'sudo nft get element {table} {set_name} {{ {element} }}')
+ if code == 0:
+ return True
+ count += 1
+ sleep(1)
+ return False
+
def test_snat(self):
rules = ['100', '110', '120', '130', '200', '210', '220', '230']
outbound_iface_100 = 'eth0'
@@ -78,6 +95,30 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip vyos_nat')
+ def test_snat_groups(self):
+ address_group = 'smoketest_addr'
+ address_group_member = '192.0.2.1'
+ rule = '100'
+ outbound_iface = 'eth0'
+
+ self.cli_set(['firewall', 'group', 'address-group', address_group, 'address', address_group_member])
+
+ self.cli_set(src_path + ['rule', rule, 'source', 'group', 'address-group', address_group])
+ self.cli_set(src_path + ['rule', rule, 'outbound-interface', outbound_iface])
+ self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade'])
+
+ self.cli_commit()
+
+ nftables_search = [
+ [f'set A_{address_group}'],
+ [f'elements = {{ {address_group_member} }}'],
+ [f'ip saddr @A_{address_group}', f'oifname "{outbound_iface}"', 'masquerade']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip vyos_nat')
+
+ self.cli_delete(['firewall'])
+
def test_dnat(self):
rules = ['100', '110', '120', '130', '200', '210', '220', '230']
inbound_iface_100 = 'eth0'
diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py
index 6cf7ca0a1..50806b3e8 100755
--- a/smoketest/scripts/cli/test_nat66.py
+++ b/smoketest/scripts/cli/test_nat66.py
@@ -136,7 +136,7 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
nftables_search = [
- ['iifname "eth1"', 'tcp dport 4545', 'ip6 saddr 2001:db8:2222::/64', 'tcp sport 8080', 'dnat to 2001:db8:1111::1:5555']
+ ['iifname "eth1"', 'tcp dport 4545', 'ip6 saddr 2001:db8:2222::/64', 'tcp sport 8080', 'dnat to [2001:db8:1111::1]:5555']
]
self.verify_nftables(nftables_search, 'ip6 vyos_nat')
@@ -208,7 +208,7 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
nftables_search = [
- ['oifname "eth1"', 'ip6 saddr 2001:db8:2222::/64', 'tcp dport 9999', 'tcp sport 8080', 'snat to 2001:db8:1111::1:80']
+ ['oifname "eth1"', 'ip6 saddr 2001:db8:2222::/64', 'tcp dport 9999', 'tcp sport 8080', 'snat to [2001:db8:1111::1]:80']
]
self.verify_nftables(nftables_search, 'ip6 vyos_nat')
diff --git a/smoketest/scripts/cli/test_pki.py b/smoketest/scripts/cli/test_pki.py
index cba5ffdde..b18b0b039 100755
--- a/smoketest/scripts/cli/test_pki.py
+++ b/smoketest/scripts/cli/test_pki.py
@@ -246,5 +246,27 @@ class TestPKI(VyOSUnitTestSHIM.TestCase):
self.cli_delete(['service', 'https', 'certificates', 'certificate'])
+ def test_certificate_eapol_update(self):
+ self.cli_set(base_path + ['certificate', 'smoketest', 'certificate', valid_ca_cert.replace('\n','')])
+ self.cli_set(base_path + ['certificate', 'smoketest', 'private', 'key', valid_ca_private_key.replace('\n','')])
+ self.cli_commit()
+
+ self.cli_set(['interfaces', 'ethernet', 'eth1', 'eapol', 'certificate', 'smoketest'])
+ self.cli_commit()
+
+ cert_data = None
+
+ with open('/run/wpa_supplicant/eth1_cert.pem') as f:
+ cert_data = f.read()
+
+ self.cli_set(base_path + ['certificate', 'smoketest', 'certificate', valid_update_cert.replace('\n','')])
+ self.cli_set(base_path + ['certificate', 'smoketest', 'private', 'key', valid_update_private_key.replace('\n','')])
+ self.cli_commit()
+
+ with open('/run/wpa_supplicant/eth1_cert.pem') as f:
+ self.assertNotEqual(cert_data, f.read())
+
+ self.cli_delete(['interfaces', 'ethernet', 'eth1', 'eapol'])
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py
index 2166e63ec..3a4ef666a 100755
--- a/smoketest/scripts/cli/test_policy.py
+++ b/smoketest/scripts/cli/test_policy.py
@@ -1030,6 +1030,7 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
'metric' : '150',
'metric-type' : 'type-1',
'origin' : 'incomplete',
+ 'l3vpn' : '',
'originator-id' : '172.16.10.1',
'src' : '100.0.0.1',
'tag' : '65530',
@@ -1229,6 +1230,8 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
self.cli_set(path + ['rule', rule, 'set', 'ipv6-next-hop', 'local', rule_config['set']['ipv6-next-hop-local']])
if 'ip-next-hop' in rule_config['set']:
self.cli_set(path + ['rule', rule, 'set', 'ip-next-hop', rule_config['set']['ip-next-hop']])
+ if 'l3vpn' in rule_config['set']:
+ self.cli_set(path + ['rule', rule, 'set', 'l3vpn-nexthop', 'encapsulation', 'gre'])
if 'local-preference' in rule_config['set']:
self.cli_set(path + ['rule', rule, 'set', 'local-preference', rule_config['set']['local-preference']])
if 'metric' in rule_config['set']:
@@ -1408,6 +1411,8 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
tmp += 'ipv6 next-hop global ' + rule_config['set']['ipv6-next-hop-global']
elif 'ipv6-next-hop-local' in rule_config['set']:
tmp += 'ipv6 next-hop local ' + rule_config['set']['ipv6-next-hop-local']
+ elif 'l3vpn' in rule_config['set']:
+ tmp += 'l3vpn next-hop encapsulation gre'
elif 'local-preference' in rule_config['set']:
tmp += 'local-preference ' + rule_config['set']['local-preference']
elif 'metric' in rule_config['set']:
diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py
index 046e385bb..cb48a84ff 100755
--- a/smoketest/scripts/cli/test_policy_route.py
+++ b/smoketest/scripts/cli/test_policy_route.py
@@ -21,6 +21,8 @@ from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.util import cmd
mark = '100'
+conn_mark = '555'
+conn_mark_set = '111'
table_mark_offset = 0x7fffffff
table_id = '101'
interface = 'eth0'
@@ -42,18 +44,25 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
super(TestPolicyRoute, cls).tearDownClass()
def tearDown(self):
- self.cli_delete(['interfaces', 'ethernet', interface, 'policy'])
self.cli_delete(['policy', 'route'])
self.cli_delete(['policy', 'route6'])
self.cli_commit()
+ # Verify nftables cleanup
nftables_search = [
['set N_smoketest_network'],
['set N_smoketest_network1'],
['chain VYOS_PBR_smoketest']
]
- self.verify_nftables(nftables_search, 'ip mangle', inverse=True)
+ self.verify_nftables(nftables_search, 'ip vyos_mangle', inverse=True)
+
+ # Verify ip rule cleanup
+ ip_rule_search = [
+ ['fwmark ' + hex(table_mark_offset - int(table_id)), 'lookup ' + table_id]
+ ]
+
+ self.verify_rules(ip_rule_search, inverse=True)
def verify_nftables(self, nftables_search, table, inverse=False):
nftables_output = cmd(f'sudo nft list table {table}')
@@ -66,6 +75,17 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
break
self.assertTrue(not matched if inverse else matched, msg=search)
+ def verify_rules(self, rules_search, inverse=False):
+ rule_output = cmd('ip rule show')
+
+ for search in rules_search:
+ matched = False
+ for line in rule_output.split("\n"):
+ if all(item in line for item in search):
+ matched = True
+ break
+ self.assertTrue(not matched if inverse else matched, msg=search)
+
def test_pbr_group(self):
self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24'])
self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'network', '172.16.101.0/24'])
@@ -74,8 +94,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network'])
self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'group', 'network-group', 'smoketest_network1'])
self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'mark', mark])
-
- self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest'])
+ self.cli_set(['policy', 'route', 'smoketest', 'interface', interface])
self.cli_commit()
@@ -84,7 +103,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
['ip daddr @N_smoketest_network1', 'ip saddr @N_smoketest_network'],
]
- self.verify_nftables(nftables_search, 'ip mangle')
+ self.verify_nftables(nftables_search, 'ip vyos_mangle')
self.cli_delete(['firewall'])
@@ -92,8 +111,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'address', '172.16.20.10'])
self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10'])
self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'mark', mark])
-
- self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest'])
+ self.cli_set(['policy', 'route', 'smoketest', 'interface', interface])
self.cli_commit()
@@ -104,7 +122,26 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
['ip daddr 172.16.10.10', 'ip saddr 172.16.20.10', 'meta mark set ' + mark_hex],
]
- self.verify_nftables(nftables_search, 'ip mangle')
+ self.verify_nftables(nftables_search, 'ip vyos_mangle')
+
+ def test_pbr_mark_connection(self):
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'address', '172.16.20.10'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'connection-mark', conn_mark])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'connection-mark', conn_mark_set])
+ self.cli_set(['policy', 'route', 'smoketest', 'interface', interface])
+
+ self.cli_commit()
+
+ mark_hex = "{0:#010x}".format(int(conn_mark))
+ mark_hex_set = "{0:#010x}".format(int(conn_mark_set))
+
+ nftables_search = [
+ [f'iifname "{interface}"','jump VYOS_PBR_smoketest'],
+ ['ip daddr 172.16.10.10', 'ip saddr 172.16.20.10', 'ct mark ' + mark_hex, 'ct mark set ' + mark_hex_set],
+ ]
+
+ self.verify_nftables(nftables_search, 'ip vyos_mangle')
def test_pbr_table(self):
self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'protocol', 'tcp'])
@@ -116,8 +153,8 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'destination', 'port', '8888'])
self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'set', 'table', table_id])
- self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest'])
- self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route6', 'smoketest6'])
+ self.cli_set(['policy', 'route', 'smoketest', 'interface', interface])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'interface', interface])
self.cli_commit()
@@ -130,7 +167,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
['tcp flags syn / syn,ack', 'tcp dport 8888', 'meta mark set ' + mark_hex]
]
- self.verify_nftables(nftables_search, 'ip mangle')
+ self.verify_nftables(nftables_search, 'ip vyos_mangle')
# IPv6
@@ -139,7 +176,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
['meta l4proto { tcp, udp }', 'th dport 8888', 'meta mark set ' + mark_hex]
]
- self.verify_nftables(nftables6_search, 'ip6 mangle')
+ self.verify_nftables(nftables6_search, 'ip6 vyos_mangle')
# IP rule fwmark -> table
@@ -147,15 +184,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
['fwmark ' + hex(table_mark_offset - int(table_id)), 'lookup ' + table_id]
]
- ip_rule_output = cmd('ip rule show')
-
- for search in ip_rule_search:
- matched = False
- for line in ip_rule_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(matched)
+ self.verify_rules(ip_rule_search)
def test_pbr_matching_criteria(self):
@@ -203,8 +232,8 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'dscp-exclude', '14-19'])
self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'set', 'table', table_id])
- self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest'])
- self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route6', 'smoketest6'])
+ self.cli_set(['policy', 'route', 'smoketest', 'interface', interface])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'interface', interface])
self.cli_commit()
@@ -220,7 +249,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
['ip dscp { 0x29, 0x39-0x3b }', 'meta mark set ' + mark_hex]
]
- self.verify_nftables(nftables_search, 'ip mangle')
+ self.verify_nftables(nftables_search, 'ip vyos_mangle')
# IPv6
nftables6_search = [
@@ -232,7 +261,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
['ip6 dscp != { 0x0e-0x13, 0x3d }', 'meta mark set ' + mark_hex]
]
- self.verify_nftables(nftables6_search, 'ip6 mangle')
+ self.verify_nftables(nftables6_search, 'ip6 vyos_mangle')
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 d2dad8c1a..bf5b2e0f3 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' : '',
@@ -64,6 +68,7 @@ neighbor_config = {
'pfx_list_in' : prefix_list_in,
'pfx_list_out' : prefix_list_out,
'no_send_comm_std' : '',
+ 'local_role' : 'rs-client',
},
'192.0.2.3' : {
'advertise_map' : route_map_in,
@@ -94,6 +99,8 @@ neighbor_config = {
'no_send_comm_std' : '',
'addpath_per_as' : '',
'peer_group' : 'foo-bar',
+ 'local_role' : 'customer',
+ 'local_role_strict': '',
},
'2001:db8::2' : {
'remote_as' : '456',
@@ -150,6 +157,8 @@ peer_group_config = {
'update_src' : 'lo',
'route_map_in' : route_map_in,
'route_map_out' : route_map_out,
+ 'local_role' : 'peer',
+ 'local_role_strict': '',
},
}
@@ -189,6 +198,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:
@@ -208,6 +226,11 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' neighbor {peer} ebgp-multihop {peer_config["multi_hop"]}', frrconfig)
if 'local_as' in peer_config:
self.assertIn(f' neighbor {peer} local-as {peer_config["local_as"]} no-prepend replace-as', frrconfig)
+ if 'local_role' in peer_config:
+ tmp = f' neighbor {peer} local-role {peer_config["local_role"]}'
+ if 'local_role_strict' in peer_config:
+ tmp += ' strict'
+ self.assertIn(tmp, frrconfig)
if 'cap_over' in peer_config:
self.assertIn(f' neighbor {peer} override-capability', frrconfig)
if 'passive' in peer_config:
@@ -294,6 +317,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['parameters', 'minimum-holdtime', min_hold_time])
self.cli_set(base_path + ['parameters', 'no-suppress-duplicates'])
self.cli_set(base_path + ['parameters', 'reject-as-sets'])
+ self.cli_set(base_path + ['parameters', 'route-reflector-allow-outbound-policy'])
self.cli_set(base_path + ['parameters', 'shutdown'])
self.cli_set(base_path + ['parameters', 'suppress-fib-pending'])
@@ -322,6 +346,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' bgp bestpath peer-type multipath-relax', frrconfig)
self.assertIn(f' bgp minimum-holdtime {min_hold_time}', frrconfig)
self.assertIn(f' bgp reject-as-sets', frrconfig)
+ self.assertIn(f' bgp route-reflector allow-outbound-policy', frrconfig)
self.assertIn(f' bgp shutdown', frrconfig)
self.assertIn(f' bgp suppress-fib-pending', frrconfig)
self.assertNotIn(f'bgp ebgp-requires-policy', frrconfig)
@@ -365,6 +390,10 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['neighbor', peer, 'ebgp-multihop', peer_config["multi_hop"]])
if 'local_as' in peer_config:
self.cli_set(base_path + ['neighbor', peer, 'local-as', peer_config["local_as"], 'no-prepend', 'replace-as'])
+ if 'local_role' in peer_config:
+ self.cli_set(base_path + ['neighbor', peer, 'local-role', peer_config["local_role"]])
+ if 'local_role_strict' in peer_config:
+ self.cli_set(base_path + ['neighbor', peer, 'local-role', peer_config["local_role"], 'strict'])
if 'cap_over' in peer_config:
self.cli_set(base_path + ['neighbor', peer, 'override-capability'])
if 'passive' in peer_config:
@@ -461,6 +490,10 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['peer-group', peer_group, 'ebgp-multihop', config["multi_hop"]])
if 'local_as' in config:
self.cli_set(base_path + ['peer-group', peer_group, 'local-as', config["local_as"], 'no-prepend', 'replace-as'])
+ if 'local_role' in config:
+ self.cli_set(base_path + ['peer-group', peer_group, 'local-role', config["local_role"]])
+ if 'local_role_strict' in config:
+ self.cli_set(base_path + ['peer-group', peer_group, 'local-role', config["local_role"], 'strict'])
if 'cap_over' in config:
self.cli_set(base_path + ['peer-group', peer_group, 'override-capability'])
if 'passive' in config:
@@ -957,6 +990,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 51c947537..581959b15 100755
--- a/smoketest/scripts/cli/test_protocols_ospf.py
+++ b/smoketest/scripts/cli/test_protocols_ospf.py
@@ -70,8 +70,15 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['auto-cost', 'reference-bandwidth', bandwidth])
self.cli_set(base_path + ['parameters', 'router-id', router_id])
self.cli_set(base_path + ['parameters', 'abr-type', abr_type])
+ self.cli_set(base_path + ['parameters', 'opaque-lsa'])
+ 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()
@@ -79,11 +86,19 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
# Verify FRR ospfd configuration
frrconfig = self.getFRRconfig('router ospf')
self.assertIn(f'router ospf', frrconfig)
+ self.assertIn(f' compatible rfc1583', frrconfig)
self.assertIn(f' auto-cost reference-bandwidth {bandwidth}', frrconfig)
self.assertIn(f' ospf router-id {router_id}', frrconfig)
self.assertIn(f' ospf abr-type {abr_type}', frrconfig)
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):
@@ -268,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_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py
index 9c9d6d9f1..64ba100a2 100755
--- a/smoketest/scripts/cli/test_service_dhcp-server.py
+++ b/smoketest/scripts/cli/test_service_dhcp-server.py
@@ -102,16 +102,17 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
def test_dhcp_single_pool_options(self):
shared_net_name = 'SMOKE-0815'
- range_0_start = inc_ip(subnet, 10)
- range_0_stop = inc_ip(subnet, 20)
- smtp_server = '1.2.3.4'
- time_server = '4.3.2.1'
- tftp_server = 'tftp.vyos.io'
- search_domains = ['foo.vyos.net', 'bar.vyos.net']
- bootfile_name = 'vyos'
- bootfile_server = '192.0.2.1'
- wpad = 'http://wpad.vyos.io/foo/bar'
- server_identifier = bootfile_server
+ range_0_start = inc_ip(subnet, 10)
+ range_0_stop = inc_ip(subnet, 20)
+ smtp_server = '1.2.3.4'
+ time_server = '4.3.2.1'
+ tftp_server = 'tftp.vyos.io'
+ search_domains = ['foo.vyos.net', 'bar.vyos.net']
+ bootfile_name = 'vyos'
+ bootfile_server = '192.0.2.1'
+ wpad = 'http://wpad.vyos.io/foo/bar'
+ server_identifier = bootfile_server
+ ipv6_only_preferred = '300'
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
# we use the first subnet IP address as default gateway
@@ -132,6 +133,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.cli_set(pool + ['server-identifier', server_identifier])
self.cli_set(pool + ['static-route', '10.0.0.0/24', 'next-hop', '192.0.2.1'])
+ self.cli_set(pool + ['ipv6-only-preferred', ipv6_only_preferred])
# check validate() - No DHCP address range or active static-mapping set
with self.assertRaises(ConfigSessionError):
@@ -169,6 +171,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'max-lease-time 86400;', config)
self.assertIn(f'range {range_0_start} {range_0_stop};', config)
self.assertIn(f'set shared-networkname = "{shared_net_name}";', config)
+ self.assertIn(f'option rfc8925-ipv6-only-preferred {ipv6_only_preferred};', config)
# weird syntax for those static routes
self.assertIn(f'option rfc3442-static-route 24,10,0,0,192,0,2,1, 0,192,0,2,1;', config)
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_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py
index fe2682d50..94e0597ad 100755
--- a/smoketest/scripts/cli/test_service_dns_forwarding.py
+++ b/smoketest/scripts/cli/test_service_dns_forwarding.py
@@ -111,6 +111,10 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase):
tmp = get_config_value('serve-rfc1918')
self.assertEqual(tmp, 'yes')
+ # verify default port configuration
+ tmp = get_config_value('local-port')
+ self.assertEqual(tmp, '53')
+
def test_dnssec(self):
# DNSSEC option testing
@@ -224,5 +228,21 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase):
tmp = get_config_value('dns64-prefix')
self.assertEqual(tmp, dns_prefix)
+ def test_listening_port(self):
+ # We can listen on a different port compared to '53' but only one at a time
+ for port in ['1053', '5353']:
+ self.cli_set(base_path + ['port', port])
+ for network in allow_from:
+ self.cli_set(base_path + ['allow-from', network])
+ for address in listen_adress:
+ self.cli_set(base_path + ['listen-address', address])
+
+ # commit changes
+ self.cli_commit()
+
+ # verify local-port configuration
+ tmp = get_config_value('local-port')
+ self.assertEqual(tmp, port)
+
if __name__ == '__main__':
- unittest.main(verbosity=2)
+ unittest.main(verbosity=2, failfast=True)
diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py
index 72c1d4e43..0f4b1393c 100755
--- a/smoketest/scripts/cli/test_service_https.py
+++ b/smoketest/scripts/cli/test_service_https.py
@@ -143,10 +143,10 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase):
# caught by the resolver, and returns success 'False', so one must
# check the return value.
- self.cli_set(base_path + ['api', 'gql'])
+ self.cli_set(base_path + ['api', 'graphql'])
self.cli_commit()
- gql_url = f'https://{address}/graphql'
+ graphql_url = f'https://{address}/graphql'
query_valid_key = f"""
{{
@@ -160,7 +160,7 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase):
}}
"""
- r = request('POST', gql_url, verify=False, headers=headers, json={'query': query_valid_key})
+ r = request('POST', graphql_url, verify=False, headers=headers, json={'query': query_valid_key})
success = r.json()['data']['SystemStatus']['success']
self.assertTrue(success)
@@ -176,7 +176,7 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase):
}
"""
- r = request('POST', gql_url, verify=False, headers=headers, json={'query': query_invalid_key})
+ r = request('POST', graphql_url, verify=False, headers=headers, json={'query': query_invalid_key})
success = r.json()['data']['SystemStatus']['success']
self.assertFalse(success)
@@ -192,8 +192,52 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase):
}
"""
- r = request('POST', gql_url, verify=False, headers=headers, json={'query': query_no_key})
+ r = request('POST', graphql_url, verify=False, headers=headers, json={'query': query_no_key})
self.assertEqual(r.status_code, 400)
+ # GraphQL token authentication test: request token; pass in header
+ # of query.
+
+ self.cli_set(base_path + ['api', 'graphql', 'authentication', 'type', 'token'])
+ self.cli_commit()
+
+ mutation = """
+ mutation {
+ AuthToken (data: {username: "vyos", password: "vyos"}) {
+ success
+ errors
+ data {
+ result
+ }
+ }
+ }
+ """
+ r = request('POST', graphql_url, verify=False, headers=headers, json={'query': mutation})
+
+ token = r.json()['data']['AuthToken']['data']['result']['token']
+
+ headers = {'Authorization': f'Bearer {token}'}
+
+ query = """
+ {
+ ShowVersion (data: {}) {
+ success
+ errors
+ op_mode_error {
+ name
+ message
+ vyos_code
+ }
+ data {
+ result
+ }
+ }
+ }
+ """
+
+ r = request('POST', graphql_url, verify=False, headers=headers, json={'query': query})
+ success = r.json()['data']['ShowVersion']['success']
+ self.assertTrue(success)
+
if __name__ == '__main__':
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_snmp.py b/smoketest/scripts/cli/test_service_snmp.py
index e80c689cc..b18b9e7a1 100755
--- a/smoketest/scripts/cli/test_service_snmp.py
+++ b/smoketest/scripts/cli/test_service_snmp.py
@@ -123,6 +123,28 @@ class TestSNMPService(VyOSUnitTestSHIM.TestCase):
self.assertTrue(process_named_running(PROCESS_NAME))
self.cli_delete(['interfaces', 'dummy', dummy_if])
+ ## Check communities and default view RESTRICTED
+ for auth in ['ro', 'rw']:
+ community = 'VyOS' + auth
+ for addr in clients:
+ if is_ipv4(addr):
+ entry = auth + 'community ' + community + ' ' + addr + ' -V'
+ else:
+ entry = auth + 'community6 ' + community + ' ' + addr + ' -V'
+ config = get_config_value(entry)
+ expected = 'RESTRICTED'
+ self.assertIn(expected, config)
+ for addr in networks:
+ if is_ipv4(addr):
+ entry = auth + 'community ' + community + ' ' + addr + ' -V'
+ else:
+ entry = auth + 'community6 ' + community + ' ' + addr + ' -V'
+ config = get_config_value(entry)
+ expected = 'RESTRICTED'
+ self.assertIn(expected, config)
+ # And finally check global entry for RESTRICTED view
+ config = get_config_value('view RESTRICTED included .1')
+ self.assertIn('80', config)
def test_snmpv3_sha(self):
# Check if SNMPv3 can be configured with SHA authentication
diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py
index 0b029dd00..8de98f34f 100755
--- a/smoketest/scripts/cli/test_service_ssh.py
+++ b/smoketest/scripts/cli/test_service_ssh.py
@@ -262,5 +262,42 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase):
self.assertFalse(process_named_running(SSHGUARD_PROCESS))
+
+ # Network Device Collaborative Protection Profile
+ def test_ssh_ndcpp(self):
+ ciphers = ['aes128-cbc', 'aes128-ctr', 'aes256-cbc', 'aes256-ctr']
+ host_key_algs = ['sk-ssh-ed25519@openssh.com', 'ssh-rsa', 'ssh-ed25519']
+ kexes = ['diffie-hellman-group14-sha1', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521']
+ macs = ['hmac-sha1', 'hmac-sha2-256', 'hmac-sha2-512']
+ rekey_time = '60'
+ rekey_data = '1024'
+
+ for cipher in ciphers:
+ self.cli_set(base_path + ['ciphers', cipher])
+ for host_key in host_key_algs:
+ self.cli_set(base_path + ['hostkey-algorithm', host_key])
+ for kex in kexes:
+ self.cli_set(base_path + ['key-exchange', kex])
+ for mac in macs:
+ self.cli_set(base_path + ['mac', mac])
+ # Optional rekey parameters
+ self.cli_set(base_path + ['rekey', 'data', rekey_data])
+ self.cli_set(base_path + ['rekey', 'time', rekey_time])
+
+ # commit changes
+ self.cli_commit()
+
+ ssh_lines = ['Ciphers aes128-cbc,aes128-ctr,aes256-cbc,aes256-ctr',
+ 'HostKeyAlgorithms sk-ssh-ed25519@openssh.com,ssh-rsa,ssh-ed25519',
+ 'MACs hmac-sha1,hmac-sha2-256,hmac-sha2-512',
+ 'KexAlgorithms diffie-hellman-group14-sha1,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521',
+ 'RekeyLimit 1024M 60M'
+ ]
+ tmp_sshd_conf = read_file(SSHD_CONF)
+
+ for line in ssh_lines:
+ self.assertIn(line, tmp_sshd_conf)
+
+
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..c8634dd57 100755
--- a/smoketest/scripts/cli/test_vpn_ipsec.py
+++ b/smoketest/scripts/cli/test_vpn_ipsec.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2022 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
@@ -34,11 +34,15 @@ swanctl_file = '/etc/swanctl/swanctl.conf'
peer_ip = '203.0.113.45'
connection_name = 'main-branch'
+local_id = 'left'
+remote_id = 'right'
interface = 'eth1'
vif = '100'
esp_group = 'MyESPGroup'
ike_group = 'MyIKEGroup'
secret = 'MYSECRETKEY'
+PROCESS_NAME = 'charon'
+regex_uuid4 = '[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}'
ca_pem = """
MIIDSzCCAjOgAwIBAgIUQHK+ZgTUYZksvXY2/MyW+Jiels4wDQYJKoZIhvcNAQEL
@@ -137,23 +141,28 @@ 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
self.cli_set(ethernet_path + [interface, 'vif', vif, 'address', 'dhcp']) # Use VLAN to avoid getting IP from qemu dhcp server
+ # vpn ipsec auth psk <tag> id <x.x.x.x>
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret])
+
# Site to site
peer_base_path = base_path + ['site-to-site', 'peer', connection_name]
self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret'])
- self.cli_set(peer_base_path + ['authentication', 'pre-shared-secret', secret])
self.cli_set(peer_base_path + ['ike-group', ike_group])
self.cli_set(peer_base_path + ['default-esp-group', esp_group])
self.cli_set(peer_base_path + ['dhcp-interface', f'{interface}.{vif}'])
@@ -166,21 +175,30 @@ 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'])
- # Site to site
local_address = '192.0.2.10'
priority = '20'
life_bytes = '100000'
life_packets = '2000000'
+
+ # vpn ipsec auth psk <tag> id <x.x.x.x>
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_address])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret])
+
+ # Site to site
peer_base_path = base_path + ['site-to-site', 'peer', connection_name]
self.cli_set(base_path + ['esp-group', esp_group, 'life-bytes', life_bytes])
self.cli_set(base_path + ['esp-group', esp_group, 'life-packets', life_packets])
self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret'])
- self.cli_set(peer_base_path + ['authentication', 'pre-shared-secret', secret])
self.cli_set(peer_base_path + ['ike-group', ike_group])
self.cli_set(peer_base_path + ['default-esp-group', esp_group])
self.cli_set(peer_base_path + ['local-address', local_address])
@@ -227,12 +245,14 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
self.assertIn(line, swanctl_conf)
swanctl_secrets_lines = [
- f'id-local = {local_address} # dhcp:no',
- f'id-remote_{peer_ip.replace(".","-")} = {peer_ip}',
+ f'id-{regex_uuid4} = "{local_id}"',
+ f'id-{regex_uuid4} = "{remote_id}"',
+ f'id-{regex_uuid4} = "{local_address}"',
+ f'id-{regex_uuid4} = "{peer_ip}"',
f'secret = "{secret}"'
]
for line in swanctl_secrets_lines:
- self.assertIn(line, swanctl_conf)
+ self.assertRegex(swanctl_conf, fr'{line}')
def test_03_site_to_site_vti(self):
@@ -246,10 +266,15 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
# VTI interface
self.cli_set(vti_path + [vti, 'address', '10.1.1.1/24'])
+ # vpn ipsec auth psk <tag> id <x.x.x.x>
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret])
+
# Site to site
peer_base_path = base_path + ['site-to-site', 'peer', connection_name]
self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret'])
- self.cli_set(peer_base_path + ['authentication', 'pre-shared-secret', secret])
self.cli_set(peer_base_path + ['connection-type', 'none'])
self.cli_set(peer_base_path + ['force-udp-encapsulation'])
self.cli_set(peer_base_path + ['ike-group', ike_group])
@@ -292,12 +317,12 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
self.assertIn(line, swanctl_conf)
swanctl_secrets_lines = [
- f'id-local = {local_address} # dhcp:no',
- f'id-remote_{peer_ip.replace(".","-")} = {peer_ip}',
+ f'id-{regex_uuid4} = "{local_id}"',
+ f'id-{regex_uuid4} = "{remote_id}"',
f'secret = "{secret}"'
]
for line in swanctl_secrets_lines:
- self.assertIn(line, swanctl_conf)
+ self.assertRegex(swanctl_conf, fr'{line}')
def test_04_dmvpn(self):
@@ -334,6 +359,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '2'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256'])
self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha1'])
+ self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'prf', 'prfsha1'])
# Profile
self.cli_set(base_path + ['profile', 'NHRPVPN', 'authentication', 'mode', 'pre-shared-secret'])
@@ -346,7 +372,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
swanctl_conf = read_file(swanctl_file)
swanctl_lines = [
- f'proposals = aes128-sha1-modp1024,aes256-sha1-modp1024',
+ f'proposals = aes128-sha1-modp1024,aes256-sha1-prfsha1-modp1024',
f'version = 1',
f'rekey_time = {ike_lifetime}s',
f'rekey_time = {esp_lifetime}s',
@@ -450,9 +476,15 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['options', 'interface', 'tun1'])
self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2'])
+ # vpn ipsec auth psk <tag> id <x.x.x.x>
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_address])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret])
+
self.cli_set(peer_base_path + ['authentication', 'local-id', local_id])
self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret'])
- self.cli_set(peer_base_path + ['authentication', 'pre-shared-secret', secret])
self.cli_set(peer_base_path + ['authentication', 'remote-id', remote_id])
self.cli_set(peer_base_path + ['connection-type', 'initiate'])
self.cli_set(peer_base_path + ['ike-group', ike_group])
@@ -482,15 +514,15 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
self.assertIn(line, swanctl_conf)
swanctl_secrets_lines = [
- f'id-local = {local_address} # dhcp:no',
- f'id-remote_{peer_ip.replace(".","-")} = {peer_ip}',
- f'id-localid = {local_id}',
- f'id-remoteid = {remote_id}',
+ f'id-{regex_uuid4} = "{local_id}"',
+ f'id-{regex_uuid4} = "{remote_id}"',
+ f'id-{regex_uuid4} = "{peer_ip}"',
+ f'id-{regex_uuid4} = "{local_address}"',
f'secret = "{secret}"',
]
for line in swanctl_secrets_lines:
- self.assertIn(line, swanctl_conf)
+ self.assertRegex(swanctl_conf, fr'{line}')
# Verify charon configuration
charon_conf = read_file(charon_file)
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/config_mgmt.py b/src/conf_mode/config_mgmt.py
new file mode 100755
index 000000000..c681a8405
--- /dev/null
+++ b/src/conf_mode/config_mgmt.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+
+from vyos import ConfigError
+from vyos.config import Config
+from vyos.config_mgmt import ConfigMgmt
+from vyos.config_mgmt import commit_post_hook_dir, commit_hooks
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['system', 'config-management']
+ if not conf.exists(base):
+ return None
+
+ mgmt = ConfigMgmt(config=conf)
+
+ return mgmt
+
+def verify(_mgmt):
+ return
+
+def generate(mgmt):
+ if mgmt is None:
+ return
+
+ mgmt.initialize_revision()
+
+def apply(mgmt):
+ if mgmt is None:
+ return
+
+ locations = mgmt.locations
+ archive_target = os.path.join(commit_post_hook_dir,
+ commit_hooks['commit_archive'])
+ if locations:
+ try:
+ os.symlink('/usr/bin/config-mgmt', archive_target)
+ except FileExistsError:
+ pass
+ except OSError as exc:
+ raise ConfigError from exc
+ else:
+ try:
+ os.unlink(archive_target)
+ except FileNotFoundError:
+ pass
+ except OSError as exc:
+ raise ConfigError from exc
+
+ revisions = mgmt.max_revisions
+ revision_target = os.path.join(commit_post_hook_dir,
+ commit_hooks['commit_revision'])
+ if revisions > 0:
+ try:
+ os.symlink('/usr/bin/config-mgmt', revision_target)
+ except FileExistsError:
+ pass
+ except OSError as exc:
+ raise ConfigError from exc
+ else:
+ try:
+ os.unlink(revision_target)
+ except FileNotFoundError:
+ pass
+ except OSError as exc:
+ raise ConfigError from exc
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index ac3dc536b..08861053d 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -40,20 +40,7 @@ airbag.enable()
config_containers_registry = '/etc/containers/registries.conf'
config_containers_storage = '/etc/containers/storage.conf'
-
-def _run_rerun(container_cmd):
- counter = 0
- while True:
- if counter >= 10:
- break
- try:
- _cmd(container_cmd)
- break
- except:
- counter = counter +1
- sleep(0.5)
-
- return None
+systemd_unit_path = '/run/systemd/system'
def _cmd(command):
if os.path.exists('/tmp/vyos.container.debug'):
@@ -86,9 +73,28 @@ 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']
+ if 'volume' in default_values:
+ del default_values['volume']
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])
+ # 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 'volume' in container['name'][name]:
+ for volume in container['name'][name]['volume']:
+ default_values = defaults(base + ['name', 'volume'])
+ container['name'][name]['volume'][volume] = dict_merge(
+ default_values, container['name'][name]['volume'][volume])
+
# Delete container network, delete containers
tmp = node_changed(conf, base + ['network'])
if tmp: container.update({'network_remove' : tmp})
@@ -122,7 +128,7 @@ def verify(container):
# of image upgrade and deletion.
image = container_config['image']
if run(f'podman image exists {image}') != 0:
- Warning(f'Image "{image}" used in contianer "{name}" does not exist '\
+ Warning(f'Image "{image}" used in container "{name}" does not exist '\
f'locally. Please use "add container image {image}" to add it '\
f'to the system! Container "{name}" will not be started!')
@@ -136,9 +142,6 @@ def verify(container):
raise ConfigError(f'Container network "{network_name}" does not exist!')
if 'address' in container_config['network'][network_name]:
- if 'network' not in container_config:
- raise ConfigError(f'Can not use "address" without "network" for container "{name}"!')
-
address = container_config['network'][network_name]['address']
network = None
if is_ipv4(address):
@@ -184,6 +187,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}"!')
@@ -220,6 +228,69 @@ def verify(container):
return None
+def generate_run_arguments(name, container_config):
+ image = container_config['image']
+ memory = container_config['memory']
+ shared_memory = container_config['shared_memory']
+ restart = container_config['restart']
+
+ # Add capability options. Should be in uppercase
+ cap_add = ''
+ if 'cap_add' in container_config:
+ for c in container_config['cap_add']:
+ c = c.upper()
+ c = c.replace('-', '_')
+ cap_add += f' --cap-add={c}'
+
+ # Add a host device to the container /dev/x:/dev/x
+ device = ''
+ if 'device' in container_config:
+ for dev, dev_config in container_config['device'].items():
+ source_dev = dev_config['source']
+ dest_dev = dev_config['destination']
+ device += f' --device={source_dev}:{dest_dev}'
+
+ # Check/set environment options "-e foo=bar"
+ env_opt = ''
+ if 'environment' in container_config:
+ for k, v in container_config['environment'].items():
+ env_opt += f" --env \"{k}={v['value']}\""
+
+ # Publish ports
+ port = ''
+ if 'port' in container_config:
+ protocol = ''
+ for portmap in container_config['port']:
+ protocol = container_config['port'][portmap]['protocol']
+ sport = container_config['port'][portmap]['source']
+ dport = container_config['port'][portmap]['destination']
+ port += f' --publish {sport}:{dport}/{protocol}'
+
+ # Bind volume
+ volume = ''
+ if 'volume' in container_config:
+ for vol, vol_config in container_config['volume'].items():
+ svol = vol_config['source']
+ dvol = vol_config['destination']
+ mode = vol_config['mode']
+ volume += f' --volume {svol}:{dvol}:{mode}'
+
+ container_base_cmd = f'--detach --interactive --tty --replace {cap_add} ' \
+ f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \
+ f'--name {name} {device} {port} {volume} {env_opt}'
+
+ if 'allow_host_networks' in container_config:
+ return f'{container_base_cmd} --net host {image}'
+
+ ip_param = ''
+ networks = ",".join(container_config['network'])
+ for network in container_config['network']:
+ if 'address' in container_config['network'][network]:
+ address = container_config['network'][network]['address']
+ ip_param = f'--ip {address}'
+
+ return f'{container_base_cmd} --net {networks} {ip_param} {image}'
+
def generate(container):
# bail out early - looks like removal from running config
if not container:
@@ -263,6 +334,15 @@ def generate(container):
render(config_containers_registry, 'container/registries.conf.j2', container)
render(config_containers_storage, 'container/storage.conf.j2', container)
+ if 'name' in container:
+ for name, container_config in container['name'].items():
+ if 'disable' in container_config:
+ continue
+
+ file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
+ run_args = generate_run_arguments(name, container_config)
+ render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args})
+
return None
def apply(container):
@@ -270,8 +350,12 @@ def apply(container):
# Option "--force" allows to delete containers with any status
if 'container_remove' in container:
for name in container['container_remove']:
- call(f'podman stop --time 3 {name}')
- call(f'podman rm --force {name}')
+ file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
+ call(f'systemctl stop vyos-container-{name}.service')
+ if os.path.exists(file_path):
+ os.unlink(file_path)
+
+ call('systemctl daemon-reload')
# Delete old networks if needed
if 'network_remove' in container:
@@ -282,6 +366,7 @@ def apply(container):
os.unlink(tmp)
# Add container
+ disabled_new = False
if 'name' in container:
for name, container_config in container['name'].items():
image = container_config['image']
@@ -295,70 +380,17 @@ def apply(container):
# check if there is a container by that name running
tmp = _cmd('podman ps -a --format "{{.Names}}"')
if name in tmp:
- _cmd(f'podman stop --time 3 {name}')
- _cmd(f'podman rm --force {name}')
+ file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
+ call(f'systemctl stop vyos-container-{name}.service')
+ if os.path.exists(file_path):
+ disabled_new = True
+ os.unlink(file_path)
continue
- memory = container_config['memory']
- restart = container_config['restart']
+ cmd(f'systemctl restart vyos-container-{name}.service')
- # Add capability options. Should be in uppercase
- cap_add = ''
- if 'cap_add' in container_config:
- for c in container_config['cap_add']:
- c = c.upper()
- c = c.replace('-', '_')
- cap_add += f' --cap-add={c}'
-
- # Add a host device to the container /dev/x:/dev/x
- device = ''
- if 'device' in container_config:
- for dev, dev_config in container_config['device'].items():
- source_dev = dev_config['source']
- dest_dev = dev_config['destination']
- device += f' --device={source_dev}:{dest_dev}'
-
- # Check/set environment options "-e foo=bar"
- env_opt = ''
- if 'environment' in container_config:
- for k, v in container_config['environment'].items():
- env_opt += f" -e \"{k}={v['value']}\""
-
- # Publish ports
- port = ''
- 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'
- sport = container_config['port'][portmap]['source']
- dport = container_config['port'][portmap]['destination']
- port += f' -p {sport}:{dport}{protocol}'
-
- # Bind volume
- volume = ''
- if 'volume' in container_config:
- for vol, vol_config in container_config['volume'].items():
- svol = vol_config['source']
- dvol = vol_config['destination']
- volume += f' -v {svol}:{dvol}'
-
- container_base_cmd = f'podman run --detach --interactive --tty --replace {cap_add} ' \
- f'--memory {memory}m --memory-swap 0 --restart {restart} ' \
- f'--name {name} {device} {port} {volume} {env_opt}'
- if 'allow_host_networks' in container_config:
- _run_rerun(f'{container_base_cmd} --net host {image}')
- else:
- for network in container_config['network']:
- ipparam = ''
- if 'address' in container_config['network'][network]:
- address = container_config['network'][network]['address']
- ipparam = f'--ip {address}'
-
- _run_rerun(f'{container_base_cmd} --net {network} {ipparam} {image}')
+ if disabled_new:
+ call('systemctl daemon-reload')
return None
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index cbd9cbe90..20cf1ead1 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -26,13 +26,10 @@ from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.configdiff import get_config_diff, Diff
+from vyos.configdep import set_dependents, call_dependents
# from vyos.configverify import verify_interface_exists
+from vyos.firewall import fqdn_config_parse
from vyos.firewall import geoip_update
-from vyos.firewall import get_ips_domains_dict
-from vyos.firewall import nft_add_set_elements
-from vyos.firewall import nft_flush_set
-from vyos.firewall import nft_init_set
-from vyos.firewall import nft_update_set_elements
from vyos.template import render
from vyos.util import call
from vyos.util import cmd
@@ -45,7 +42,8 @@ from vyos import ConfigError
from vyos import airbag
airbag.enable()
-policy_route_conf_script = '/usr/libexec/vyos/conf_mode/policy-route.py'
+nat_conf_script = 'nat.py'
+policy_route_conf_script = 'policy-route.py'
nftables_conf = '/run/nftables.conf'
@@ -67,7 +65,8 @@ valid_groups = [
'address_group',
'domain_group',
'network_group',
- 'port_group'
+ 'port_group',
+ 'interface_group'
]
nested_group_types = [
@@ -162,7 +161,10 @@ def get_config(config=None):
for zone in firewall['zone']:
firewall['zone'][zone] = dict_merge(default_values, firewall['zone'][zone])
- firewall['policy_resync'] = bool('group' in firewall or node_changed(conf, base + ['group']))
+ firewall['group_resync'] = bool('group' in firewall or node_changed(conf, base + ['group']))
+ if firewall['group_resync']:
+ # Update nat and policy-route as firewall groups were updated
+ set_dependents('group_resync', conf)
if 'config_trap' in firewall and firewall['config_trap'] == 'enable':
diff = get_config_diff(conf)
@@ -173,6 +175,8 @@ def get_config(config=None):
firewall['geoip_updated'] = geoip_updated(conf, firewall)
+ fqdn_config_parse(firewall)
+
return firewall
def verify_rule(firewall, rule_conf, ipv6):
@@ -232,29 +236,28 @@ def verify_rule(firewall, rule_conf, ipv6):
if side in rule_conf:
side_conf = rule_conf[side]
- if dict_search_args(side_conf, 'geoip', 'country_code'):
- if 'address' in side_conf:
- raise ConfigError('Address and GeoIP cannot both be defined')
-
- if dict_search_args(side_conf, 'group', 'address_group'):
- raise ConfigError('Address-group and GeoIP cannot both be defined')
-
- if dict_search_args(side_conf, 'group', 'network_group'):
- raise ConfigError('Network-group and GeoIP cannot both be defined')
+ if len({'address', 'fqdn', 'geoip'} & set(side_conf)) > 1:
+ raise ConfigError('Only one of address, fqdn or geoip can be specified')
if 'group' in side_conf:
- if {'address_group', 'network_group'} <= set(side_conf['group']):
- raise ConfigError('Only one address-group or network-group can be specified')
+ if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1:
+ raise ConfigError('Only one address-group, network-group or domain-group can be specified')
for group in valid_groups:
if group in side_conf['group']:
group_name = side_conf['group'][group]
+ fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group
+ error_group = fw_group.replace("_", "-")
+
+ if group in ['address_group', 'network_group', 'domain_group']:
+ types = [t for t in ['address', 'fqdn', 'geoip'] if t in side_conf]
+ if types:
+ raise ConfigError(f'{error_group} and {types[0]} cannot both be defined')
+
if group_name and group_name[0] == '!':
group_name = group_name[1:]
- fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group
- error_group = fw_group.replace("_", "-")
group_obj = dict_search_args(firewall, 'group', fw_group, group_name)
if group_obj is None:
@@ -274,6 +277,8 @@ def verify_nested_group(group_name, group, groups, seen):
if 'include' not in group:
return
+ seen.append(group_name)
+
for g in group['include']:
if g not in groups:
raise ConfigError(f'Nested group "{g}" does not exist')
@@ -281,8 +286,6 @@ def verify_nested_group(group_name, group, groups, seen):
if g in seen:
raise ConfigError(f'Group "{group_name}" has a circular reference')
- seen.append(g)
-
if 'include' in groups[g]:
verify_nested_group(g, groups[g], groups, seen)
@@ -466,42 +469,23 @@ def post_apply_trap(firewall):
cmd(base_cmd + ' '.join(objects))
-def resync_policy_route():
- # Update policy route as firewall groups were updated
- tmp, out = rc_cmd(policy_route_conf_script)
- if tmp > 0:
- Warning(f'Failed to re-apply policy route configuration! {out}')
-
def apply(firewall):
install_result, output = rc_cmd(f'nft -f {nftables_conf}')
if install_result == 1:
raise ConfigError(f'Failed to apply firewall: {output}')
- # set firewall group domain-group xxx
- if 'group' in firewall:
- if 'domain_group' in firewall['group']:
- # T970 Enable a resolver (systemd daemon) that checks
- # domain-group addresses and update entries for domains by timeout
- # If router loaded without internet connection or for synchronization
- call('systemctl restart vyos-domain-group-resolve.service')
- for group, group_config in firewall['group']['domain_group'].items():
- domains = []
- if group_config.get('address') is not None:
- for address in group_config.get('address'):
- domains.append(address)
- # Add elements to domain-group, try to resolve domain => ip
- # and add elements to nft set
- ip_dict = get_ips_domains_dict(domains)
- elements = sum(ip_dict.values(), [])
- nft_init_set(f'D_{group}')
- nft_add_set_elements(f'D_{group}', elements)
- else:
- call('systemctl stop vyos-domain-group-resolve.service')
-
apply_sysfs(firewall)
- if firewall['policy_resync']:
- resync_policy_route()
+ if firewall['group_resync']:
+ call_dependents()
+
+ # T970 Enable a resolver (systemd daemon) that checks
+ # domain-group/fqdn addresses and update entries for domains by timeout
+ # If router loaded without internet connection or for synchronization
+ domain_action = 'stop'
+ if dict_search_args(firewall, 'group', 'domain_group') or firewall['ip_fqdn'] or firewall['ip6_fqdn']:
+ domain_action = 'restart'
+ call(f'systemctl {domain_action} vyos-domain-resolver.service')
if firewall['geoip_updated']:
# Call helper script to Update set contents
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..79e407efd 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
@@ -28,6 +28,7 @@ from vyos.template import render
from vyos.template import is_ipv4
from vyos.template import is_ipv6
from vyos.util import call
+from vyos.util import dict_search
from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -49,10 +50,27 @@ def get_config(config=None):
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
if 'vrrp' in ha:
+ if dict_search('vrrp.global_parameters.garp', ha) != None:
+ default_values = defaults(base_vrrp + ['global-parameters', 'garp'])
+ ha['vrrp']['global_parameters']['garp'] = dict_merge(
+ default_values, ha['vrrp']['global_parameters']['garp'])
+
if 'group' in ha['vrrp']:
- default_values_vrrp = defaults(base_vrrp + ['group'])
+ default_values = defaults(base_vrrp + ['group'])
+ default_values_garp = defaults(base_vrrp + ['group', 'garp'])
+
+ # 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 'garp' in default_values:
+ del default_values['garp']
for group in ha['vrrp']['group']:
- ha['vrrp']['group'][group] = dict_merge(default_values_vrrp, ha['vrrp']['group'][group])
+ ha['vrrp']['group'][group] = dict_merge(default_values, ha['vrrp']['group'][group])
+
+ # 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 'garp' in ha['vrrp']['group'][group]:
+ ha['vrrp']['group'][group]['garp'] = dict_merge(
+ default_values_garp, ha['vrrp']['group'][group]['garp'])
# Merge per virtual-server default values
if 'virtual_server' in ha:
@@ -144,8 +162,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/http-api.py b/src/conf_mode/http-api.py
index c196e272b..6328294c1 100755
--- a/src/conf_mode/http-api.py
+++ b/src/conf_mode/http-api.py
@@ -25,6 +25,7 @@ import vyos.defaults
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.configdep import set_dependents, call_dependents
from vyos.template import render
from vyos.util import cmd
from vyos.util import call
@@ -61,6 +62,11 @@ def get_config(config=None):
else:
conf = Config()
+ # reset on creation/deletion of 'api' node
+ https_base = ['service', 'https']
+ if conf.exists(https_base):
+ set_dependents("https", conf)
+
base = ['service', 'https', 'api']
if not conf.exists(base):
return None
@@ -86,7 +92,7 @@ def get_config(config=None):
if 'api_keys' in api_dict:
keys_added = True
- if 'gql' in api_dict:
+ if 'graphql' in api_dict:
api_dict = dict_merge(defaults(base), api_dict)
http_api.update(api_dict)
@@ -132,7 +138,7 @@ def apply(http_api):
# Let uvicorn settle before restarting Nginx
sleep(1)
- cmd(f'{vyos_conf_scripts_dir}/https.py', raising=ConfigError)
+ call_dependents()
if __name__ == '__main__':
try:
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-bonding.py b/src/conf_mode/interfaces-bonding.py
index 21cf204fc..9936620c8 100755
--- a/src/conf_mode/interfaces-bonding.py
+++ b/src/conf_mode/interfaces-bonding.py
@@ -21,6 +21,7 @@ from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import get_interface_dict
+from vyos.configdict import is_node_changed
from vyos.configdict import leaf_node_changed
from vyos.configdict import is_member
from vyos.configdict import is_source_interface
@@ -81,10 +82,10 @@ def get_config(config=None):
if 'mode' in bond:
bond['mode'] = get_bond_mode(bond['mode'])
- tmp = leaf_node_changed(conf, base + [ifname, 'mode'])
+ tmp = is_node_changed(conf, base + [ifname, 'mode'])
if tmp: bond['shutdown_required'] = {}
- tmp = leaf_node_changed(conf, base + [ifname, 'lacp-rate'])
+ tmp = is_node_changed(conf, base + [ifname, 'lacp-rate'])
if tmp: bond['shutdown_required'] = {}
# determine which members have been removed
@@ -116,7 +117,7 @@ def get_config(config=None):
if dict_search('member.interface', bond):
for interface, interface_config in bond['member']['interface'].items():
# Check if member interface is a new member
- if not conf.exists_effective(['member', 'interface', interface]):
+ if not conf.exists_effective(base + [ifname, 'member', 'interface', interface]):
bond['shutdown_required'] = {}
# Check if member interface is disabled
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index e02841831..b49c945cd 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -175,7 +175,7 @@ def generate(ethernet):
loaded_pki_cert = load_certificate(pki_cert['certificate'])
loaded_ca_certs = {load_certificate(c['certificate'])
- for c in ethernet['pki']['ca'].values()}
+ for c in ethernet['pki']['ca'].values()} if 'ca' in ethernet['pki'] else {}
cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs)
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-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index e2fdc7a42..5f0b76f90 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -23,7 +23,6 @@ from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configdict import is_node_changed
-from vyos.configdict import leaf_node_changed
from vyos.configdict import get_pppoe_interfaces
from vyos.configverify import verify_authentication
from vyos.configverify import verify_source_interface
@@ -55,7 +54,8 @@ def get_config(config=None):
# All parameters that can be changed on-the-fly (like interface description)
# should not lead to a reconnect!
for options in ['access-concentrator', 'connect-on-demand', 'service-name',
- 'source-interface', 'vrf', 'no-default-route', 'authentication']:
+ 'source-interface', 'vrf', 'no-default-route',
+ 'authentication', 'host_uniq']:
if is_node_changed(conf, base + [ifname, options]):
pppoe.update({'shutdown_required': {}})
# bail out early - no need to further process other nodes
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-sstpc.py b/src/conf_mode/interfaces-sstpc.py
new file mode 100755
index 000000000..b5cc4cf4e
--- /dev/null
+++ b/src/conf_mode/interfaces-sstpc.py
@@ -0,0 +1,145 @@
+#!/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
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import get_interface_dict
+from vyos.configdict import is_node_changed
+from vyos.configverify import verify_authentication
+from vyos.configverify import verify_vrf
+from vyos.ifconfig import SSTPCIf
+from vyos.pki import encode_certificate
+from vyos.pki import find_chain
+from vyos.pki import load_certificate
+from vyos.template import render
+from vyos.util import call
+from vyos.util import dict_search
+from vyos.util import is_systemd_service_running
+from vyos.util import write_file
+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', 'sstpc']
+ ifname, sstpc = get_interface_dict(conf, base)
+
+ # We should only terminate the SSTP client session if critical parameters
+ # change. All parameters that can be changed on-the-fly (like interface
+ # description) should not lead to a reconnect!
+ for options in ['authentication', 'no_peer_dns', 'no_default_route',
+ 'server', 'ssl']:
+ if is_node_changed(conf, base + [ifname, options]):
+ sstpc.update({'shutdown_required': {}})
+ # bail out early - no need to further process other nodes
+ break
+
+ # Load PKI certificates for later processing
+ sstpc['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+ return sstpc
+
+def verify(sstpc):
+ if 'deleted' in sstpc:
+ return None
+
+ verify_authentication(sstpc)
+ verify_vrf(sstpc)
+
+ if not dict_search('server', sstpc):
+ raise ConfigError('Remote SSTP server must be specified!')
+
+ if not dict_search('ssl.ca_certificate', sstpc):
+ raise ConfigError('Missing mandatory CA certificate!')
+
+ return None
+
+def generate(sstpc):
+ ifname = sstpc['ifname']
+ config_sstpc = f'/etc/ppp/peers/{ifname}'
+
+ sstpc['ca_file_path'] = f'/run/sstpc/{ifname}_ca-cert.pem'
+
+ if 'deleted' in sstpc:
+ for file in [sstpc['ca_file_path'], config_sstpc]:
+ if os.path.exists(file):
+ os.unlink(file)
+ return None
+
+ ca_name = sstpc['ssl']['ca_certificate']
+ pki_ca_cert = sstpc['pki']['ca'][ca_name]
+
+ loaded_ca_cert = load_certificate(pki_ca_cert['certificate'])
+ loaded_ca_certs = {load_certificate(c['certificate'])
+ for c in sstpc['pki']['ca'].values()} if 'ca' in sstpc['pki'] else {}
+
+ ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
+
+ write_file(sstpc['ca_file_path'], '\n'.join(encode_certificate(c) for c in ca_full_chain))
+ render(config_sstpc, 'sstp-client/peer.j2', sstpc, permission=0o640)
+
+ return None
+
+def apply(sstpc):
+ ifname = sstpc['ifname']
+ if 'deleted' in sstpc or 'disable' in sstpc:
+ if os.path.isdir(f'/sys/class/net/{ifname}'):
+ p = SSTPCIf(ifname)
+ p.remove()
+ call(f'systemctl stop ppp@{ifname}.service')
+ return None
+
+ # reconnect should only be necessary when specific options change,
+ # like server, authentication ... (see get_config() for details)
+ if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or
+ 'shutdown_required' in sstpc):
+
+ # cleanup system (e.g. FRR routes first)
+ if os.path.isdir(f'/sys/class/net/{ifname}'):
+ p = SSTPCIf(ifname)
+ p.remove()
+
+ call(f'systemctl restart ppp@{ifname}.service')
+ # When interface comes "live" a hook is called:
+ # /etc/ppp/ip-up.d/96-vyos-sstpc-callback
+ # which triggers SSTPCIf.update()
+ else:
+ if os.path.isdir(f'/sys/class/net/{ifname}'):
+ p = SSTPCIf(ifname)
+ p.update(sstpc)
+
+ 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-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-virtual-ethernet.py b/src/conf_mode/interfaces-virtual-ethernet.py
new file mode 100755
index 000000000..8efe89c41
--- /dev/null
+++ b/src/conf_mode/interfaces-virtual-ethernet.py
@@ -0,0 +1,114 @@
+#!/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 exit
+
+from netifaces import interfaces
+from vyos import ConfigError
+from vyos import airbag
+from vyos.config import Config
+from vyos.configdict import get_interface_dict
+from vyos.configverify import verify_address
+from vyos.configverify import verify_bridge_delete
+from vyos.configverify import verify_vrf
+from vyos.ifconfig import VethIf
+
+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', 'virtual-ethernet']
+ ifname, veth = get_interface_dict(conf, base)
+
+ # We need to know all other veth related interfaces as veth requires a 1:1
+ # mapping for the peer-names. The Linux kernel automatically creates both
+ # interfaces, the local one and the peer-name, but VyOS also needs a peer
+ # interfaces configrued on the CLI so we can assign proper IP addresses etc.
+ veth['other_interfaces'] = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+ return veth
+
+
+def verify(veth):
+ if 'deleted' in veth:
+ verify_bridge_delete(veth)
+ # Prevent to delete veth interface which used for another "vethX peer-name"
+ for iface, iface_config in veth['other_interfaces'].items():
+ if veth['ifname'] in iface_config['peer_name']:
+ ifname = veth['ifname']
+ raise ConfigError(
+ f'Cannot delete "{ifname}" used for "interface {iface} peer-name"'
+ )
+ return None
+
+ verify_vrf(veth)
+ verify_address(veth)
+
+ if 'peer_name' not in veth:
+ raise ConfigError(f'Remote peer name must be set for "{veth["ifname"]}"!')
+
+ peer_name = veth['peer_name']
+ ifname = veth['ifname']
+
+ if veth['peer_name'] not in veth['other_interfaces']:
+ raise ConfigError(f'Used peer-name "{peer_name}" on interface "{ifname}" ' \
+ 'is not configured!')
+
+ if veth['other_interfaces'][peer_name]['peer_name'] != ifname:
+ raise ConfigError(
+ f'Configuration mismatch between "{ifname}" and "{peer_name}"!')
+
+ if peer_name == ifname:
+ raise ConfigError(
+ f'Peer-name "{peer_name}" cannot be the same as interface "{ifname}"!')
+
+ return None
+
+
+def generate(peth):
+ return None
+
+def apply(veth):
+ # Check if the Veth interface already exists
+ if 'rebuild_required' in veth or 'deleted' in veth:
+ if veth['ifname'] in interfaces():
+ p = VethIf(veth['ifname'])
+ p.remove()
+
+ if 'deleted' not in veth:
+ p = VethIf(**veth)
+ p.update(veth)
+
+ 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-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/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index 8d738f55e..762bad94f 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -87,6 +87,8 @@ def verify(wireguard):
'cannot be used for the interface!')
# run checks on individual configured WireGuard peer
+ public_keys = []
+
for tmp in wireguard['peer']:
peer = wireguard['peer'][tmp]
@@ -100,6 +102,11 @@ def verify(wireguard):
raise ConfigError('Both Wireguard port and address must be defined '
f'for peer "{tmp}" if either one of them is set!')
+ if peer['public_key'] in public_keys:
+ raise ConfigError(f'Duplicate public-key defined on peer "{tmp}"')
+
+ public_keys.append(peer['public_key'])
+
def apply(wireguard):
tmp = WireGuardIf(wireguard['ifname'])
if 'deleted' in wireguard:
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 8b1a5a720..9f8221514 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -32,6 +32,7 @@ from vyos.util import cmd
from vyos.util import run
from vyos.util import check_kmod
from vyos.util import dict_search
+from vyos.util import dict_search_args
from vyos.validate import is_addr_assigned
from vyos.xml import defaults
from vyos import ConfigError
@@ -47,6 +48,13 @@ else:
nftables_nat_config = '/run/nftables_nat.conf'
nftables_static_nat_conf = '/run/nftables_static-nat-rules.nft'
+valid_groups = [
+ 'address_group',
+ 'domain_group',
+ 'network_group',
+ 'port_group'
+]
+
def get_handler(json, chain, target):
""" Get nftable rule handler number of given chain/target combination.
Handler is required when adding NAT/Conntrack helper targets """
@@ -60,7 +68,7 @@ def get_handler(json, chain, target):
return None
-def verify_rule(config, err_msg):
+def verify_rule(config, err_msg, groups_dict):
""" Common verify steps used for both source and destination NAT """
if (dict_search('translation.port', config) != None or
@@ -78,6 +86,45 @@ def verify_rule(config, err_msg):
'statically maps a whole network of addresses onto another\n' \
'network of addresses')
+ for side in ['destination', 'source']:
+ if side in config:
+ side_conf = config[side]
+
+ if len({'address', 'fqdn'} & set(side_conf)) > 1:
+ raise ConfigError('Only one of address, fqdn or geoip can be specified')
+
+ if 'group' in side_conf:
+ if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1:
+ raise ConfigError('Only one address-group, network-group or domain-group can be specified')
+
+ for group in valid_groups:
+ if group in side_conf['group']:
+ group_name = side_conf['group'][group]
+ error_group = group.replace("_", "-")
+
+ if group in ['address_group', 'network_group', 'domain_group']:
+ types = [t for t in ['address', 'fqdn'] if t in side_conf]
+ if types:
+ raise ConfigError(f'{error_group} and {types[0]} cannot both be defined')
+
+ if group_name and group_name[0] == '!':
+ group_name = group_name[1:]
+
+ group_obj = dict_search_args(groups_dict, group, group_name)
+
+ if group_obj is None:
+ raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule')
+
+ if not group_obj:
+ Warning(f'{error_group} "{group_name}" has no members!')
+
+ if dict_search_args(side_conf, 'group', 'port_group'):
+ if 'protocol' not in config:
+ raise ConfigError('Protocol must be defined if specifying a port-group')
+
+ if config['protocol'] not in ['tcp', 'udp', 'tcp_udp']:
+ raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port-group')
+
def get_config(config=None):
if config:
conf = config
@@ -105,16 +152,20 @@ def get_config(config=None):
condensed_json = jmespath.search(pattern, nftable_json)
if not conf.exists(base):
- nat['helper_functions'] = 'remove'
-
- # Retrieve current table handler positions
- nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER')
- nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK')
- nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_HELPER')
- nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK')
+ if get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER'):
+ nat['helper_functions'] = 'remove'
+
+ # Retrieve current table handler positions
+ nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER')
+ nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK')
+ nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_HELPER')
+ nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK')
nat['deleted'] = ''
return nat
+ nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
# check if NAT connection tracking helpers need to be set up - this has to
# be done only once
if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'):
@@ -146,6 +197,10 @@ def verify(nat):
if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces():
Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
+ if not dict_search('translation.address', config) and not dict_search('translation.port', config):
+ if 'exclude' not in config:
+ raise ConfigError(f'{err_msg} translation requires address and/or port')
+
addr = dict_search('translation.address', config)
if addr != None and addr != 'masquerade' and not is_ip_network(addr):
for ip in addr.split('-'):
@@ -153,7 +208,7 @@ def verify(nat):
Warning(f'IP address {ip} does not exist on the system!')
# common rule verification
- verify_rule(config, err_msg)
+ verify_rule(config, err_msg, nat['firewall_group'])
if dict_search('destination.rule', nat):
@@ -166,8 +221,12 @@ def verify(nat):
elif config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
+ if not dict_search('translation.address', config) and not dict_search('translation.port', config):
+ if 'exclude' not in config:
+ raise ConfigError(f'{err_msg} translation requires address and/or port')
+
# common rule verification
- verify_rule(config, err_msg)
+ verify_rule(config, err_msg, nat['firewall_group'])
if dict_search('static.rule', nat):
for rule, config in dict_search('static.rule', nat).items():
@@ -178,7 +237,7 @@ def verify(nat):
'inbound-interface not specified')
# common rule verification
- verify_rule(config, err_msg)
+ verify_rule(config, err_msg, nat['firewall_group'])
return None
@@ -204,6 +263,10 @@ def apply(nat):
cmd(f'nft -f {nftables_nat_config}')
cmd(f'nft -f {nftables_static_nat_conf}')
+ if not nat or 'deleted' in nat:
+ os.unlink(nftables_nat_config)
+ os.unlink(nftables_static_nat_conf)
+
return None
if __name__ == '__main__':
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 29ed7b1b7..54de467ca 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -16,20 +16,16 @@
from sys import exit
-import jmespath
-
from vyos.config import Config
+from vyos.configdep import set_dependents, call_dependents
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.pki import is_ca_certificate
from vyos.pki import load_certificate
-from vyos.pki import load_certificate_request
from vyos.pki import load_public_key
from vyos.pki import load_private_key
from vyos.pki import load_crl
from vyos.pki import load_dh_parameters
-from vyos.util import ask_input
-from vyos.util import call
from vyos.util import dict_search_args
from vyos.util import dict_search_recursive
from vyos.xml import defaults
@@ -55,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'
@@ -121,6 +122,39 @@ def get_config(config=None):
get_first_key=True,
no_tag_node_value_mangle=True)
+ if 'changed' in pki:
+ for search in sync_search:
+ for key in search['keys']:
+ changed_key = sync_translate[key]
+
+ if changed_key not in pki['changed']:
+ continue
+
+ for item_name in pki['changed'][changed_key]:
+ node_present = False
+ if changed_key == 'openvpn':
+ node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name)
+ else:
+ node_present = dict_search_args(pki, changed_key, item_name)
+
+ if node_present:
+ search_dict = dict_search_args(pki['system'], *search['path'])
+
+ if not search_dict:
+ continue
+
+ for found_name, found_path in dict_search_recursive(search_dict, key):
+ if found_name == item_name:
+ path = search['path']
+ path_str = ' '.join(path + found_path)
+ print(f'pki: Updating config: {path_str} {found_name}')
+
+ if path[0] == 'interfaces':
+ ifname = found_path[0]
+ set_dependents(path[1], conf, ifname)
+ else:
+ set_dependents(path[1], conf)
+
return pki
def is_valid_certificate(raw_data):
@@ -259,37 +293,7 @@ def apply(pki):
return None
if 'changed' in pki:
- for search in sync_search:
- for key in search['keys']:
- changed_key = sync_translate[key]
-
- if changed_key not in pki['changed']:
- continue
-
- for item_name in pki['changed'][changed_key]:
- node_present = False
- if changed_key == 'openvpn':
- node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name)
- else:
- node_present = dict_search_args(pki, changed_key, item_name)
-
- if node_present:
- search_dict = dict_search_args(pki['system'], *search['path'])
-
- if not search_dict:
- continue
-
- for found_name, found_path in dict_search_recursive(search_dict, key):
- if found_name == item_name:
- path_str = ' '.join(search['path'] + found_path)
- print(f'pki: Updating config: {path_str} {found_name}')
-
- script = search['script']
- if found_path[0] == 'interfaces':
- ifname = found_path[2]
- call(f'VYOS_TAGNODE_VALUE={ifname} {script}')
- else:
- call(script)
+ call_dependents()
return None
diff --git a/src/conf_mode/policy-route-interface.py b/src/conf_mode/policy-route-interface.py
deleted file mode 100755
index 58c5fd93d..000000000
--- a/src/conf_mode/policy-route-interface.py
+++ /dev/null
@@ -1,132 +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 os
-import re
-
-from sys import argv
-from sys import exit
-
-from vyos.config import Config
-from vyos.ifconfig import Section
-from vyos.template import render
-from vyos.util import cmd
-from vyos.util import run
-from vyos import ConfigError
-from vyos import airbag
-airbag.enable()
-
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
-
- ifname = argv[1]
- ifpath = Section.get_config_path(ifname)
- if_policy_path = f'interfaces {ifpath} policy'
-
- if_policy = conf.get_config_dict(if_policy_path, key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
-
- if_policy['ifname'] = ifname
- if_policy['policy'] = conf.get_config_dict(['policy'], key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
-
- return if_policy
-
-def verify_chain(table, chain):
- # Verify policy route applied
- code = run(f'nft list chain {table} {chain}')
- return code == 0
-
-def verify(if_policy):
- # bail out early - looks like removal from running config
- if not if_policy:
- return None
-
- for route in ['route', 'route6']:
- if route in if_policy:
- if route not in if_policy['policy']:
- raise ConfigError('Policy route not configured')
-
- route_name = if_policy[route]
-
- if route_name not in if_policy['policy'][route]:
- raise ConfigError(f'Invalid policy route name "{name}"')
-
- nft_prefix = 'VYOS_PBR6_' if route == 'route6' else 'VYOS_PBR_'
- nft_table = 'ip6 mangle' if route == 'route6' else 'ip mangle'
-
- if not verify_chain(nft_table, nft_prefix + route_name):
- raise ConfigError('Policy route did not apply')
-
- return None
-
-def generate(if_policy):
- return None
-
-def cleanup_rule(table, chain, ifname, new_name=None):
- results = cmd(f'nft -a list chain {table} {chain}').split("\n")
- retval = None
- for line in results:
- if f'ifname "{ifname}"' in line:
- if new_name and f'jump {new_name}' in line:
- # new_name is used to clear rules for any previously referenced chains
- # returns true when rule exists and doesn't need to be created
- retval = True
- continue
-
- handle_search = re.search('handle (\d+)', line)
- if handle_search:
- cmd(f'nft delete rule {table} {chain} handle {handle_search[1]}')
- return retval
-
-def apply(if_policy):
- ifname = if_policy['ifname']
-
- route_chain = 'VYOS_PBR_PREROUTING'
- ipv6_route_chain = 'VYOS_PBR6_PREROUTING'
-
- if 'route' in if_policy:
- name = 'VYOS_PBR_' + if_policy['route']
- rule_exists = cleanup_rule('ip mangle', route_chain, ifname, name)
-
- if not rule_exists:
- cmd(f'nft insert rule ip mangle {route_chain} iifname {ifname} counter jump {name}')
- else:
- cleanup_rule('ip mangle', route_chain, ifname)
-
- if 'route6' in if_policy:
- name = 'VYOS_PBR6_' + if_policy['route6']
- rule_exists = cleanup_rule('ip6 mangle', ipv6_route_chain, ifname, name)
-
- if not rule_exists:
- cmd(f'nft insert rule ip6 mangle {ipv6_route_chain} iifname {ifname} counter jump {name}')
- else:
- cleanup_rule('ip6 mangle', ipv6_route_chain, ifname)
-
- 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/policy-route.py b/src/conf_mode/policy-route.py
index 00539b9c7..40a32efb3 100755
--- a/src/conf_mode/policy-route.py
+++ b/src/conf_mode/policy-route.py
@@ -15,7 +15,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import re
from json import loads
from sys import exit
@@ -25,7 +24,6 @@ from vyos.config import Config
from vyos.template import render
from vyos.util import cmd
from vyos.util import dict_search_args
-from vyos.util import dict_search_recursive
from vyos.util import run
from vyos import ConfigError
from vyos import airbag
@@ -34,48 +32,14 @@ airbag.enable()
mark_offset = 0x7FFFFFFF
nftables_conf = '/run/nftables_policy.conf'
-ROUTE_PREFIX = 'VYOS_PBR_'
-ROUTE6_PREFIX = 'VYOS_PBR6_'
-
-preserve_chains = [
- 'VYOS_PBR_PREROUTING',
- 'VYOS_PBR_POSTROUTING',
- 'VYOS_PBR6_PREROUTING',
- 'VYOS_PBR6_POSTROUTING'
-]
-
valid_groups = [
'address_group',
+ 'domain_group',
'network_group',
- 'port_group'
+ 'port_group',
+ 'interface_group'
]
-group_set_prefix = {
- 'A_': 'address_group',
- 'A6_': 'ipv6_address_group',
-# 'D_': 'domain_group',
- 'M_': 'mac_group',
- 'N_': 'network_group',
- 'N6_': 'ipv6_network_group',
- 'P_': 'port_group'
-}
-
-def get_policy_interfaces(conf):
- out = {}
- interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
- def find_interfaces(iftype_conf, output={}, prefix=''):
- for ifname, if_conf in iftype_conf.items():
- if 'policy' in if_conf:
- output[prefix + ifname] = if_conf['policy']
- for vif in ['vif', 'vif_s', 'vif_c']:
- if vif in if_conf:
- output.update(find_interfaces(if_conf[vif], output, f'{prefix}{ifname}.'))
- return output
- for iftype, iftype_conf in interfaces.items():
- out.update(find_interfaces(iftype_conf))
- return out
-
def get_config(config=None):
if config:
conf = config
@@ -88,7 +52,6 @@ def get_config(config=None):
policy['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True,
no_tag_node_value_mangle=True)
- policy['interfaces'] = get_policy_interfaces(conf)
return policy
@@ -132,8 +95,8 @@ def verify_rule(policy, name, rule_conf, ipv6, rule_id):
side_conf = rule_conf[side]
if 'group' in side_conf:
- if {'address_group', 'network_group'} <= set(side_conf['group']):
- raise ConfigError('Only one address-group or network-group can be specified')
+ if len({'address_group', 'domain_group', 'network_group'} & set(side_conf['group'])) > 1:
+ raise ConfigError('Only one address-group, domain-group or network-group can be specified')
for group in valid_groups:
if group in side_conf['group']:
@@ -168,73 +131,11 @@ def verify(policy):
for rule_id, rule_conf in pol_conf['rule'].items():
verify_rule(policy, name, rule_conf, ipv6, rule_id)
- for ifname, if_policy in policy['interfaces'].items():
- name = dict_search_args(if_policy, 'route')
- ipv6_name = dict_search_args(if_policy, 'route6')
-
- if name and not dict_search_args(policy, 'route', name):
- raise ConfigError(f'Policy route "{name}" is still referenced on interface {ifname}')
-
- if ipv6_name and not dict_search_args(policy, 'route6', ipv6_name):
- raise ConfigError(f'Policy route6 "{ipv6_name}" is still referenced on interface {ifname}')
-
return None
-def cleanup_commands(policy):
- commands = []
- commands_chains = []
- commands_sets = []
- for table in ['ip mangle', 'ip6 mangle']:
- route_node = 'route' if table == 'ip mangle' else 'route6'
- chain_prefix = ROUTE_PREFIX if table == 'ip mangle' else ROUTE6_PREFIX
-
- json_str = cmd(f'nft -t -j list table {table}')
- obj = loads(json_str)
- if 'nftables' not in obj:
- continue
- for item in obj['nftables']:
- if 'chain' in item:
- chain = item['chain']['name']
- if chain in preserve_chains or not chain.startswith("VYOS_PBR"):
- continue
-
- if dict_search_args(policy, route_node, chain.replace(chain_prefix, "", 1)) != None:
- commands.append(f'flush chain {table} {chain}')
- else:
- commands_chains.append(f'delete chain {table} {chain}')
-
- if 'rule' in item:
- rule = item['rule']
- chain = rule['chain']
- handle = rule['handle']
-
- if chain not in preserve_chains:
- continue
-
- target, _ = next(dict_search_recursive(rule['expr'], 'target'))
-
- if target.startswith(chain_prefix):
- if dict_search_args(policy, route_node, target.replace(chain_prefix, "", 1)) == None:
- commands.append(f'delete rule {table} {chain} handle {handle}')
-
- if 'set' in item:
- set_name = item['set']['name']
-
- for prefix, group_type in group_set_prefix.items():
- if set_name.startswith(prefix):
- group_name = set_name.replace(prefix, "", 1)
- if dict_search_args(policy, 'firewall_group', group_type, group_name) != None:
- commands_sets.append(f'flush set {table} {set_name}')
- else:
- commands_sets.append(f'delete set {table} {set_name}')
-
- return commands + commands_chains + commands_sets
-
def generate(policy):
if not os.path.exists(nftables_conf):
policy['first_install'] = True
- else:
- policy['cleanup_commands'] = cleanup_commands(policy)
render(nftables_conf, 'firewall/nftables-policy.j2', policy)
return None
diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py
index a0d288e91..331194fec 100755
--- a/src/conf_mode/policy.py
+++ b/src/conf_mode/policy.py
@@ -167,6 +167,11 @@ def verify(policy):
continue
for rule, rule_config in route_map_config['rule'].items():
+ # Action 'deny' cannot be used with "continue"
+ # FRR does not validate it T4827
+ if rule_config['action'] == 'deny' and 'continue' in rule_config:
+ raise ConfigError(f'rule {rule} "continue" cannot be used with action deny!')
+
# Specified community-list must exist
tmp = dict_search('match.community.community_list',
rule_config)
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index ff568d470..4f05957fa 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:
@@ -140,6 +235,11 @@ def verify(bgp):
raise ConfigError(f'Specified peer-group "{peer_group}" for '\
f'neighbor "{neighbor}" does not exist!')
+ if 'local_role' in peer_config:
+ #Ensure Local Role has only one value.
+ if len(peer_config['local_role']) > 1:
+ raise ConfigError(f'Only one local role can be specified for peer "{peer}"!')
+
if 'local_as' in peer_config:
if len(peer_config['local_as']) > 1:
raise ConfigError(f'Only one local-as number can be specified for peer "{peer}"!')
@@ -324,9 +424,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 +473,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
new file mode 100755
index 000000000..85e984afe
--- /dev/null
+++ b/src/conf_mode/protocols_failover.py
@@ -0,0 +1,121 @@
+#!/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 json
+
+from pathlib import Path
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.template import render
+from vyos.util import call
+from vyos.xml import defaults
+from vyos import ConfigError
+from vyos import airbag
+
+airbag.enable()
+
+
+service_name = 'vyos-failover'
+service_conf = Path(f'/run/{service_name}.conf')
+systemd_service = '/run/systemd/system/vyos-failover.service'
+rt_proto_failover = '/etc/iproute2/rt_protos.d/failover.conf'
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['protocols', 'failover']
+ failover = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+
+ # Set default values only if we set config
+ if failover.get('route'):
+ for route, route_config in failover.get('route').items():
+ for next_hop, next_hop_config in route_config.get('next_hop').items():
+ default_values = defaults(base + ['route'])
+ failover['route'][route]['next_hop'][next_hop] = dict_merge(
+ default_values['next_hop'], failover['route'][route]['next_hop'][next_hop])
+
+ return failover
+
+def verify(failover):
+ # bail out early - looks like removal from running config
+ if not failover:
+ return None
+
+ if 'route' not in failover:
+ raise ConfigError(f'Failover "route" is mandatory!')
+
+ for route, route_config in failover['route'].items():
+ if not route_config.get('next_hop'):
+ raise ConfigError(f'Next-hop for "{route}" is mandatory!')
+
+ for next_hop, next_hop_config in route_config.get('next_hop').items():
+ if 'interface' not in next_hop_config:
+ raise ConfigError(f'Interface for route "{route}" next-hop "{next_hop}" is mandatory!')
+
+ if not next_hop_config.get('check'):
+ raise ConfigError(f'Check target for next-hop "{next_hop}" is mandatory!')
+
+ if 'target' not in next_hop_config['check']:
+ raise ConfigError(f'Check target for next-hop "{next_hop}" is mandatory!')
+
+ check_type = next_hop_config['check']['type']
+ if check_type == 'tcp' and 'port' not in next_hop_config['check']:
+ raise ConfigError(f'Check port for next-hop "{next_hop}" and type TCP is mandatory!')
+
+ return None
+
+def generate(failover):
+ if not failover:
+ service_conf.unlink(missing_ok=True)
+ return None
+
+ # Add own rt_proto 'failover'
+ # Helps to detect all own routes 'proto failover'
+ with open(rt_proto_failover, 'w') as f:
+ f.write('111 failover\n')
+
+ # Write configuration file
+ conf_json = json.dumps(failover, indent=4)
+ service_conf.write_text(conf_json)
+ render(systemd_service, 'protocols/systemd_vyos_failover_service.j2', failover)
+
+ return None
+
+def apply(failover):
+ if not failover:
+ call(f'systemctl stop {service_name}.service')
+ call('ip route flush protocol failover')
+ else:
+ call('systemctl daemon-reload')
+ call(f'systemctl restart {service_name}.service')
+ call(f'ip route flush protocol failover')
+
+ 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/protocols_mpls.py b/src/conf_mode/protocols_mpls.py
index 5da8e7b06..73af6595b 100755
--- a/src/conf_mode/protocols_mpls.py
+++ b/src/conf_mode/protocols_mpls.py
@@ -24,6 +24,7 @@ from vyos.template import render_to_string
from vyos.util import dict_search
from vyos.util import read_file
from vyos.util import sysctl_write
+from vyos.configverify import verify_interface_exists
from vyos import ConfigError
from vyos import frr
from vyos import airbag
@@ -46,6 +47,10 @@ def verify(mpls):
if not mpls:
return None
+ if 'interface' in mpls:
+ for interface in mpls['interface']:
+ verify_interface_exists(interface)
+
# Checks to see if LDP is properly configured
if 'ldp' in mpls:
# If router ID not defined
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..dca713283 100755
--- a/src/conf_mode/qos.py
+++ b/src/conf_mode/qos.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# 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
@@ -14,15 +14,62 @@
# 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.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
+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 +79,172 @@ 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('_', '-')
- default_values = defaults(base + ['policy', policy])
-
- # 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 ['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])
for p_name, p_config in qos['policy'][policy].items():
+ default_values = defaults(base + ['policy', policy_hyphen])
+
+ 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']
+
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():
+ 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():
+ if not os.path.exists(f'/sys/class/net/{interface}'):
+ # When shaper is bound to a dialup (e.g. PPPoE) interface it is
+ # possible that it is yet not availbale when to QoS code runs.
+ # Skip the configuration and inform the user
+ Warning(f'Interface "{interface}" does not exist!')
+ continue
+
+ 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_pppoe-server.py b/src/conf_mode/service_pppoe-server.py
index ba0249efd..600ba4e92 100755
--- a/src/conf_mode/service_pppoe-server.py
+++ b/src/conf_mode/service_pppoe-server.py
@@ -20,6 +20,7 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import get_accel_dict
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_accel_ppp_base_service
from vyos.configverify import verify_interface_exists
from vyos.template import render
@@ -43,6 +44,13 @@ def get_config(config=None):
# retrieve common dictionary keys
pppoe = get_accel_dict(conf, base, pppoe_chap_secrets)
+
+ # reload-or-restart does not implemented in accel-ppp
+ # use this workaround until it will be implemented
+ # https://phabricator.accel-ppp.org/T3
+ if is_node_changed(conf, base + ['client-ip-pool']) or is_node_changed(
+ conf, base + ['client-ipv6-pool']):
+ pppoe.update({'restart_required': {}})
return pppoe
def verify(pppoe):
@@ -95,7 +103,10 @@ def apply(pppoe):
os.unlink(file)
return None
- call(f'systemctl reload-or-restart {systemd_service}')
+ if 'restart_required' in pppoe:
+ call(f'systemctl restart {systemd_service}')
+ else:
+ call(f'systemctl reload-or-restart {systemd_service}')
if __name__ == '__main__':
try:
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 32af31bde..658e496a6 100755
--- a/src/conf_mode/service_webproxy.py
+++ b/src/conf_mode/service_webproxy.py
@@ -28,8 +28,10 @@ from vyos.util import dict_search
from vyos.util import write_file
from vyos.validate import is_addr_assigned
from vyos.xml import defaults
+from vyos.base import Warning
from vyos import ConfigError
from vyos import airbag
+
airbag.enable()
squid_config_file = '/etc/squid/squid.conf'
@@ -37,24 +39,57 @@ squidguard_config_file = '/etc/squidguard/squidGuard.conf'
squidguard_db_dir = '/opt/vyatta/etc/config/url-filtering/squidguard/db'
user_group = 'proxy'
-def generate_sg_localdb(category, list_type, role, proxy):
+
+def check_blacklist_categorydb(config_section):
+ if 'block_category' in config_section:
+ for category in config_section['block_category']:
+ check_categorydb(category)
+ if 'allow_category' in config_section:
+ for category in config_section['allow_category']:
+ check_categorydb(category)
+
+
+def check_categorydb(category: str):
+ """
+ Check if category's db exist
+ :param category:
+ :type str:
+ """
+ path_to_cat: str = f'{squidguard_db_dir}/{category}'
+ if not os.path.exists(f'{path_to_cat}/domains.db') \
+ and not os.path.exists(f'{path_to_cat}/urls.db') \
+ and not os.path.exists(f'{path_to_cat}/expressions.db'):
+ Warning(f'DB of category {category} does not exist.\n '
+ f'Use [update webproxy blacklists] '
+ f'or delete undefined category!')
+
+
+def generate_sg_rule_localdb(category, list_type, role, proxy):
+ if not category or not list_type or not role:
+ return None
+
cat_ = category.replace('-', '_')
- if isinstance(dict_search(f'url_filtering.squidguard.{cat_}', proxy),
- list):
+ if role == 'default':
+ path_to_cat = f'{cat_}'
+ else:
+ path_to_cat = f'rule.{role}.{cat_}'
+ if isinstance(
+ dict_search(f'url_filtering.squidguard.{path_to_cat}', proxy),
+ list):
# local block databases must be generated "on-the-fly"
tmp = {
- 'squidguard_db_dir' : squidguard_db_dir,
- 'category' : f'{category}-default',
- 'list_type' : list_type,
- 'rule' : role
+ 'squidguard_db_dir': squidguard_db_dir,
+ 'category': f'{category}-{role}',
+ 'list_type': list_type,
+ 'rule': role
}
sg_tmp_file = '/tmp/sg.conf'
- db_file = f'{category}-default/{list_type}'
- domains = '\n'.join(dict_search(f'url_filtering.squidguard.{cat_}', proxy))
-
+ db_file = f'{category}-{role}/{list_type}'
+ domains = '\n'.join(
+ dict_search(f'url_filtering.squidguard.{path_to_cat}', proxy))
# local file
- write_file(f'{squidguard_db_dir}/{category}-default/local', '',
+ write_file(f'{squidguard_db_dir}/{category}-{role}/local', '',
user=user_group, group=user_group)
# database input file
write_file(f'{squidguard_db_dir}/{db_file}', domains,
@@ -64,17 +99,18 @@ def generate_sg_localdb(category, list_type, role, proxy):
render(sg_tmp_file, 'squid/sg_acl.conf.j2', tmp,
user=user_group, group=user_group)
- call(f'su - {user_group} -c "squidGuard -d -c {sg_tmp_file} -C {db_file}"')
+ call(
+ f'su - {user_group} -c "squidGuard -d -c {sg_tmp_file} -C {db_file}"')
if os.path.exists(sg_tmp_file):
os.unlink(sg_tmp_file)
-
else:
# if category is not part of our configuration, clean out the
# squidguard lists
- tmp = f'{squidguard_db_dir}/{category}-default'
+ tmp = f'{squidguard_db_dir}/{category}-{role}'
if os.path.exists(tmp):
- rmtree(f'{squidguard_db_dir}/{category}-default')
+ rmtree(f'{squidguard_db_dir}/{category}-{role}')
+
def get_config(config=None):
if config:
@@ -85,7 +121,8 @@ def get_config(config=None):
if not conf.exists(base):
return None
- proxy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ proxy = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True)
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
default_values = defaults(base)
@@ -110,10 +147,11 @@ def get_config(config=None):
default_values = defaults(base + ['cache-peer'])
for peer in proxy['cache_peer']:
proxy['cache_peer'][peer] = dict_merge(default_values,
- proxy['cache_peer'][peer])
+ proxy['cache_peer'][peer])
return proxy
+
def verify(proxy):
if not proxy:
return None
@@ -170,17 +208,30 @@ def generate(proxy):
render(squidguard_config_file, 'squid/squidGuard.conf.j2', proxy)
cat_dict = {
- 'local-block' : 'domains',
- 'local-block-keyword' : 'expressions',
- 'local-block-url' : 'urls',
- 'local-ok' : 'domains',
- 'local-ok-url' : 'urls'
+ 'local-block': 'domains',
+ 'local-block-keyword': 'expressions',
+ 'local-block-url': 'urls',
+ 'local-ok': 'domains',
+ 'local-ok-url': 'urls'
}
- for category, list_type in cat_dict.items():
- generate_sg_localdb(category, list_type, 'default', proxy)
+ if dict_search(f'url_filtering.squidguard', proxy) is not None:
+ squidgard_config_section = proxy['url_filtering']['squidguard']
+
+ for category, list_type in cat_dict.items():
+ generate_sg_rule_localdb(category, list_type, 'default', proxy)
+ check_blacklist_categorydb(squidgard_config_section)
+
+ if 'rule' in squidgard_config_section:
+ for rule in squidgard_config_section['rule']:
+ rule_config_section = squidgard_config_section['rule'][
+ rule]
+ for category, list_type in cat_dict.items():
+ generate_sg_rule_localdb(category, list_type, rule, proxy)
+ check_blacklist_categorydb(rule_config_section)
return None
+
def apply(proxy):
if not proxy:
# proxy is removed in the commit
@@ -195,9 +246,10 @@ 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
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 5cd24db32..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):
@@ -166,6 +166,10 @@ def verify(snmp):
if 'community' not in trap_config:
raise ConfigError(f'Trap target "{trap}" requires a community to be set!')
+ if 'oid_enable' in snmp:
+ Warning(f'Custom OIDs are enabled and may lead to system instability and high resource consumption')
+
+
verify_vrf(snmp)
# bail out early if SNMP v3 is not configured
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 77a425f8b..ce4f13d27 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2022 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
@@ -17,11 +17,13 @@
import ipaddress
import os
import re
+import jmespath
from sys import exit
from time import sleep
from time import time
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import leaf_node_changed
from vyos.configverify import verify_interface_exists
@@ -94,6 +96,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:
@@ -117,18 +120,39 @@ def get_config(config=None):
ipsec['ike_group'][group]['proposal'][proposal] = dict_merge(default_values,
ipsec['ike_group'][group]['proposal'][proposal])
- if 'remote_access' in ipsec and 'connection' in ipsec['remote_access']:
+ # 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('remote_access.connection', ipsec):
default_values = defaults(base + ['remote-access', 'connection'])
for rw in ipsec['remote_access']['connection']:
ipsec['remote_access']['connection'][rw] = dict_merge(default_values,
ipsec['remote_access']['connection'][rw])
- if 'remote_access' in ipsec and 'radius' in ipsec['remote_access'] and 'server' in ipsec['remote_access']['radius']:
+ # 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('remote_access.radius.server', ipsec):
+ # Fist handle the "base" stuff like RADIUS timeout
+ default_values = defaults(base + ['remote-access', 'radius'])
+ if 'server' in default_values:
+ del default_values['server']
+ ipsec['remote_access']['radius'] = dict_merge(default_values,
+ ipsec['remote_access']['radius'])
+
+ # Take care about individual RADIUS servers implemented as tagNodes - this
+ # requires special treatment
default_values = defaults(base + ['remote-access', 'radius', 'server'])
for server in ipsec['remote_access']['radius']['server']:
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'])
@@ -196,6 +220,12 @@ def verify(ipsec):
if not ipsec:
return None
+ if 'authentication' in ipsec:
+ if 'psk' in ipsec['authentication']:
+ for psk, psk_config in ipsec['authentication']['psk'].items():
+ if 'id' not in psk_config or 'secret' not in psk_config:
+ raise ConfigError(f'Authentication psk "{psk}" missing "id" or "secret"')
+
if 'interfaces' in ipsec :
for ifname in ipsec['interface']:
verify_interface_exists(ifname)
@@ -425,6 +455,10 @@ def verify(ipsec):
if 'local_address' in peer_conf and 'dhcp_interface' in peer_conf:
raise ConfigError(f"A single local-address or dhcp-interface is required when using VTI on site-to-site peer {peer}")
+ if dict_search('options.disable_route_autoinstall',
+ ipsec) == None:
+ Warning('It\'s recommended to use ipsec vty with the next command\n[set vpn ipsec option disable-route-autoinstall]')
+
if 'bind' in peer_conf['vti']:
vti_interface = peer_conf['vti']['bind']
if not os.path.exists(f'/sys/class/net/{vti_interface}'):
@@ -575,6 +609,14 @@ def generate(ipsec):
ipsec['site_to_site']['peer'][peer]['tunnel'][tunnel]['passthrough'] = passthrough
+ # auth psk <tag> dhcp-interface <xxx>
+ if jmespath.search('authentication.psk.*.dhcp_interface', ipsec):
+ for psk, psk_config in ipsec['authentication']['psk'].items():
+ if 'dhcp_interface' in psk_config:
+ for iface in psk_config['dhcp_interface']:
+ id = get_dhcp_address(iface)
+ if id:
+ ipsec['authentication']['psk'][psk]['id'].append(id)
render(ipsec_conf, 'ipsec/ipsec.conf.j2', ipsec)
render(ipsec_secrets, 'ipsec/ipsec.secrets.j2', ipsec)
diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py
index fd5a4acd8..65623c2b1 100755
--- a/src/conf_mode/vpn_l2tp.py
+++ b/src/conf_mode/vpn_l2tp.py
@@ -26,7 +26,10 @@ from ipaddress import ip_network
from vyos.config import Config
from vyos.template import is_ipv4
from vyos.template import render
-from vyos.util import call, get_half_cpus
+from vyos.util import call
+from vyos.util import get_half_cpus
+from vyos.util import check_port_availability
+from vyos.util import is_listen_port_bind_service
from vyos import ConfigError
from vyos import airbag
@@ -43,6 +46,7 @@ default_config_data = {
'client_ip_pool': None,
'client_ip_subnets': [],
'client_ipv6_pool': [],
+ 'client_ipv6_pool_configured': False,
'client_ipv6_delegate_prefix': [],
'dnsv4': [],
'dnsv6': [],
@@ -54,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',
@@ -64,7 +71,7 @@ default_config_data = {
'radius_source_address': '',
'radius_shaper_attr': '',
'radius_shaper_vendor': '',
- 'radius_dynamic_author': '',
+ 'radius_dynamic_author': {},
'wins': [],
'ip6_column': [],
'thread_cnt': get_half_cpus()
@@ -205,21 +212,21 @@ def get_config(config=None):
l2tp['radius_source_address'] = conf.return_value(['source-address'])
# Dynamic Authorization Extensions (DOA)/Change Of Authentication (COA)
- if conf.exists(['dynamic-author']):
+ if conf.exists(['dae-server']):
dae = {
'port' : '',
'server' : '',
'key' : ''
}
- if conf.exists(['dynamic-author', 'server']):
- dae['server'] = conf.return_value(['dynamic-author', 'server'])
+ if conf.exists(['dae-server', 'ip-address']):
+ dae['server'] = conf.return_value(['dae-server', 'ip-address'])
- if conf.exists(['dynamic-author', 'port']):
- dae['port'] = conf.return_value(['dynamic-author', 'port'])
+ if conf.exists(['dae-server', 'port']):
+ dae['port'] = conf.return_value(['dae-server', 'port'])
- if conf.exists(['dynamic-author', 'key']):
- dae['key'] = conf.return_value(['dynamic-author', 'key'])
+ if conf.exists(['dae-server', 'secret']):
+ dae['key'] = conf.return_value(['dae-server', 'secret'])
l2tp['radius_dynamic_author'] = dae
@@ -244,6 +251,7 @@ def get_config(config=None):
l2tp['client_ip_subnets'] = conf.return_values(['client-ip-pool', 'subnet'])
if conf.exists(['client-ipv6-pool', 'prefix']):
+ l2tp['client_ipv6_pool_configured'] = True
l2tp['ip6_column'].append('ip6')
for prefix in conf.list_nodes(['client-ipv6-pool', 'prefix']):
tmp = {
@@ -306,6 +314,18 @@ def get_config(config=None):
if conf.exists(['ppp-options', 'lcp-echo-interval']):
l2tp['ppp_echo_interval'] = conf.return_value(['ppp-options', 'lcp-echo-interval'])
+ 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
@@ -329,6 +349,19 @@ def verify(l2tp):
if not radius['key']:
raise ConfigError(f"Missing RADIUS secret for server { radius['key'] }")
+ if l2tp['radius_dynamic_author']:
+ if not l2tp['radius_dynamic_author']['server']:
+ raise ConfigError("Missing ip-address for dae-server")
+ if not l2tp['radius_dynamic_author']['key']:
+ raise ConfigError("Missing secret for dae-server")
+ address = l2tp['radius_dynamic_author']['server']
+ port = l2tp['radius_dynamic_author']['port']
+ proto = 'tcp'
+ # check if dae listen port is not used by another service
+ if check_port_availability(address, int(port), proto) is not True and \
+ not is_listen_port_bind_service(int(port), 'accel-pppd'):
+ raise ConfigError(f'"{proto}" port "{port}" is used by another service')
+
# check for the existence of a client ip pool
if not (l2tp['client_ip_pool'] or l2tp['client_ip_subnets']):
raise ConfigError(
diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py
index c050b796b..63ffe2a41 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -46,6 +46,65 @@ radius_servers = cfg_dir + '/radius_servers'
def get_hash(password):
return crypt(password, mksalt(METHOD_SHA512))
+
+def T2665_default_dict_cleanup(origin: dict, default_values: dict) -> dict:
+ """
+ https://phabricator.vyos.net/T2665
+ Clear unnecessary key values in merged config by dict_merge function
+ :param origin: config
+ :type origin: dict
+ :param default_values: default values
+ :type default_values: dict
+ :return: merged dict
+ :rtype: dict
+ """
+ if 'mode' in origin["authentication"] and "local" in \
+ origin["authentication"]["mode"]:
+ del origin['authentication']['local_users']['username']['otp']
+ if not origin["authentication"]["local_users"]["username"]:
+ raise ConfigError(
+ 'Openconnect mode local required at least one user')
+ default_ocserv_usr_values = \
+ default_values['authentication']['local_users']['username']['otp']
+ for user, params in origin['authentication']['local_users'][
+ 'username'].items():
+ # Not every configuration requires OTP settings
+ if origin['authentication']['local_users']['username'][user].get(
+ 'otp'):
+ origin['authentication']['local_users']['username'][user][
+ 'otp'] = dict_merge(default_ocserv_usr_values,
+ origin['authentication'][
+ 'local_users']['username'][user][
+ 'otp'])
+
+ if 'mode' in origin["authentication"] and "radius" in \
+ origin["authentication"]["mode"]:
+ del origin['authentication']['radius']['server']['port']
+ if not origin["authentication"]['radius']['server']:
+ raise ConfigError(
+ 'Openconnect authentication mode radius required at least one radius server')
+ default_values_radius_port = \
+ default_values['authentication']['radius']['server']['port']
+ for server, params in origin['authentication']['radius'][
+ 'server'].items():
+ if 'port' not in params:
+ params['port'] = default_values_radius_port
+
+ if 'mode' in origin["accounting"] and "radius" in \
+ origin["accounting"]["mode"]:
+ del origin['accounting']['radius']['server']['port']
+ if not origin["accounting"]['radius']['server']:
+ raise ConfigError(
+ 'Openconnect accounting mode radius required at least one radius server')
+ default_values_radius_port = \
+ default_values['accounting']['radius']['server']['port']
+ for server, params in origin['accounting']['radius'][
+ 'server'].items():
+ if 'port' not in params:
+ params['port'] = default_values_radius_port
+ return origin
+
+
def get_config():
conf = Config()
base = ['vpn', 'openconnect']
@@ -57,18 +116,8 @@ def get_config():
# options which we need to update into the dictionary retrived.
default_values = defaults(base)
ocserv = dict_merge(default_values, ocserv)
-
- if "local" in ocserv["authentication"]["mode"]:
- # workaround a "know limitation" - https://phabricator.vyos.net/T2665
- del ocserv['authentication']['local_users']['username']['otp']
- if not ocserv["authentication"]["local_users"]["username"]:
- raise ConfigError('openconnect mode local required at least one user')
- default_ocserv_usr_values = default_values['authentication']['local_users']['username']['otp']
- for user, params in ocserv['authentication']['local_users']['username'].items():
- # Not every configuration requires OTP settings
- if ocserv['authentication']['local_users']['username'][user].get('otp'):
- ocserv['authentication']['local_users']['username'][user]['otp'] = dict_merge(default_ocserv_usr_values, ocserv['authentication']['local_users']['username'][user]['otp'])
-
+ # workaround a "know limitation" - https://phabricator.vyos.net/T2665
+ ocserv = T2665_default_dict_cleanup(ocserv, default_values)
if ocserv:
ocserv['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
@@ -85,6 +134,14 @@ def verify(ocserv):
not is_listen_port_bind_service(int(port), 'ocserv-main'):
raise ConfigError(f'"{proto}" port "{port}" is used by another service')
+ # Check accounting
+ if "accounting" in ocserv:
+ if "mode" in ocserv["accounting"] and "radius" in ocserv["accounting"]["mode"]:
+ if "authentication" not in ocserv or "mode" not in ocserv["authentication"]:
+ raise ConfigError('Accounting depends on OpenConnect authentication configuration')
+ elif "radius" not in ocserv["authentication"]["mode"]:
+ raise ConfigError('RADIUS accounting must be used with RADIUS authentication')
+
# Check authentication
if "authentication" in ocserv:
if "mode" in ocserv["authentication"]:
@@ -157,7 +214,7 @@ def verify(ocserv):
ocserv["network_settings"]["push_route"].remove("0.0.0.0/0")
ocserv["network_settings"]["push_route"].append("default")
else:
- ocserv["network_settings"]["push_route"] = "default"
+ ocserv["network_settings"]["push_route"] = ["default"]
else:
raise ConfigError('openconnect network settings required')
@@ -166,10 +223,18 @@ def generate(ocserv):
return None
if "radius" in ocserv["authentication"]["mode"]:
- # Render radius client configuration
- render(radius_cfg, 'ocserv/radius_conf.j2', ocserv["authentication"]["radius"])
- # Render radius servers
- render(radius_servers, 'ocserv/radius_servers.j2', ocserv["authentication"]["radius"])
+ if dict_search(ocserv, 'accounting.mode.radius'):
+ # Render radius client configuration
+ render(radius_cfg, 'ocserv/radius_conf.j2', ocserv)
+ merged_servers = ocserv["accounting"]["radius"]["server"] | ocserv["authentication"]["radius"]["server"]
+ # Render radius servers
+ # Merge the accounting and authentication servers into a single dictionary
+ render(radius_servers, 'ocserv/radius_servers.j2', {'server': merged_servers})
+ else:
+ # Render radius client configuration
+ render(radius_cfg, 'ocserv/radius_conf.j2', ocserv)
+ # Render radius servers
+ render(radius_servers, 'ocserv/radius_servers.j2', ocserv["authentication"]["radius"])
elif "local" in ocserv["authentication"]["mode"]:
# if mode "OTP", generate OTP users file parameters
if "otp" in ocserv["authentication"]["mode"]["local"]:
@@ -247,7 +312,7 @@ def apply(ocserv):
if os.path.exists(file):
os.unlink(file)
else:
- call('systemctl restart ocserv.service')
+ call('systemctl reload-or-restart ocserv.service')
counter = 0
while True:
# exit early when service runs
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/commit/post-hooks.d/00vyos-sync b/src/etc/commit/post-hooks.d/00vyos-sync
new file mode 100755
index 000000000..e3bde3abb
--- /dev/null
+++ b/src/etc/commit/post-hooks.d/00vyos-sync
@@ -0,0 +1,7 @@
+#!/bin/sh
+# When power is lost right after a commit modified files, the
+# system can be corrupted and e.g. login is no longer possible.
+# Always sync files to the backend storage after a commit.
+# https://phabricator.vyos.net/T4975
+sync
+
diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf b/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf
index b1902b585..518abeaec 100644
--- a/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf
+++ b/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf
@@ -33,8 +33,8 @@ if /usr/bin/systemctl -q is-active vyos-hostsd; then
if [ -n "$new_dhcp6_name_servers" ]; then
logmsg info "Deleting nameservers with tag \"dhcpv6-$interface\" via vyos-hostsd-client"
$hostsd_client --delete-name-servers --tag "dhcpv6-$interface"
- logmsg info "Adding nameservers \"$new_dhcpv6_name_servers\" with tag \"dhcpv6-$interface\" via vyos-hostsd-client"
- $hostsd_client --add-name-servers $new_dhcpv6_name_servers --tag "dhcpv6-$interface"
+ logmsg info "Adding nameservers \"$new_dhcp6_name_servers\" with tag \"dhcpv6-$interface\" via vyos-hostsd-client"
+ $hostsd_client --add-name-servers $new_dhcp6_name_servers --tag "dhcpv6-$interface"
hostsd_changes=y
fi
diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup
index ad6a1d5eb..da1bda137 100644
--- a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup
+++ b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup
@@ -8,7 +8,7 @@ hostsd_changes=
/usr/bin/systemctl -q is-active vyos-hostsd
hostsd_status=$?
-if [[ $reason =~ (EXPIRE|FAIL|RELEASE|STOP) ]]; then
+if [[ $reason =~ ^(EXPIRE|FAIL|RELEASE|STOP)$ ]]; then
if [[ $hostsd_status -eq 0 ]]; then
# delete search domains and nameservers via vyos-hostsd
logmsg info "Deleting search domains with tag \"dhcp-$interface\" via vyos-hostsd-client"
@@ -96,7 +96,7 @@ if [[ $reason =~ (EXPIRE|FAIL|RELEASE|STOP) ]]; then
fi
fi
-if [[ $reason =~ (EXPIRE6|RELEASE6|STOP6) ]]; then
+if [[ $reason =~ ^(EXPIRE6|RELEASE6|STOP6)$ ]]; then
if [[ $hostsd_status -eq 0 ]]; then
# delete search domains and nameservers via vyos-hostsd
logmsg info "Deleting search domains with tag \"dhcpv6-$interface\" via vyos-hostsd-client"
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/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook b/src/etc/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook
index eeb8b0782..49bb18372 100644
--- a/src/etc/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook
+++ b/src/etc/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook
@@ -8,12 +8,12 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 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.
-#
+#
# This code was originally developed by Vyatta, Inc.
# Portions created by Vyatta are Copyright (C) 2006, 2007, 2008 Vyatta, Inc.
# All Rights Reserved.
@@ -23,7 +23,7 @@
RUN="yes"
proto=""
-if [[ $reason =~ (REBOOT6|INIT6|EXPIRE6|RELEASE6|STOP6|INFORM6|BOUND6|REBIND6|DELEGATED6) ]]; then
+if [[ $reason =~ ^(REBOOT6|INIT6|EXPIRE6|RELEASE6|STOP6|INFORM6|BOUND6|REBIND6|DELEGATED6)$ ]]; then
proto="v6"
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/etc/ppp/ip-down.d/98-vyos-pppoe-cleanup-nameservers b/src/etc/ppp/ip-down.d/98-vyos-pppoe-cleanup-nameservers
new file mode 100755
index 000000000..222c75f21
--- /dev/null
+++ b/src/etc/ppp/ip-down.d/98-vyos-pppoe-cleanup-nameservers
@@ -0,0 +1,15 @@
+#!/bin/bash
+### Autogenerated by interfaces-pppoe.py ###
+
+interface=$6
+if [ -z "$interface" ]; then
+ exit
+fi
+
+if ! /usr/bin/systemctl -q is-active vyos-hostsd; then
+ exit # vyos-hostsd is not running
+fi
+
+hostsd_client="/usr/bin/vyos-hostsd-client"
+$hostsd_client --delete-name-servers --tag "dhcp-$interface"
+$hostsd_client --apply
diff --git a/src/etc/ppp/ip-up.d/96-vyos-sstpc-callback b/src/etc/ppp/ip-up.d/96-vyos-sstpc-callback
new file mode 100755
index 000000000..4e8804f29
--- /dev/null
+++ b/src/etc/ppp/ip-up.d/96-vyos-sstpc-callback
@@ -0,0 +1,49 @@
+#!/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/>.
+
+# This is a Python hook script which is invoked whenever a SSTP client session
+# goes "ip-up". It will call into our vyos.ifconfig library and will then
+# execute common tasks for the SSTP interface. The reason we have to "hook" this
+# is that we can not create a sstpcX interface in advance in linux and then
+# connect pppd to this already existing interface.
+
+from sys import argv
+from sys import exit
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.configdict import get_interface_dict
+from vyos.ifconfig import SSTPCIf
+
+# When the ppp link comes up, this script is called with the following
+# parameters
+# $1 the interface name used by pppd (e.g. ppp3)
+# $2 the tty device name
+# $3 the tty device speed
+# $4 the local IP address for the interface
+# $5 the remote IP address
+# $6 the parameter specified by the 'ipparam' option to pppd
+
+if (len(argv) < 7):
+ exit(1)
+
+interface = argv[6]
+
+conf = ConfigTreeQuery()
+_, sstpc = get_interface_dict(conf.config, ['interfaces', 'sstpc'], interface)
+
+# Update the config
+p = SSTPCIf(interface)
+p.update(sstpc)
diff --git a/src/etc/ppp/ip-up.d/98-vyos-pppoe-setup-nameservers b/src/etc/ppp/ip-up.d/98-vyos-pppoe-setup-nameservers
new file mode 100755
index 000000000..0fcedbedc
--- /dev/null
+++ b/src/etc/ppp/ip-up.d/98-vyos-pppoe-setup-nameservers
@@ -0,0 +1,24 @@
+#!/bin/bash
+### Autogenerated by interfaces-pppoe.py ###
+
+interface=$6
+if [ -z "$interface" ]; then
+ exit
+fi
+
+if ! /usr/bin/systemctl -q is-active vyos-hostsd; then
+ exit # vyos-hostsd is not running
+fi
+
+hostsd_client="/usr/bin/vyos-hostsd-client"
+
+$hostsd_client --delete-name-servers --tag "dhcp-$interface"
+
+if [ "$USEPEERDNS" ] && [ -n "$DNS1" ]; then
+$hostsd_client --add-name-servers "$DNS1" --tag "dhcp-$interface"
+fi
+if [ "$USEPEERDNS" ] && [ -n "$DNS2" ]; then
+$hostsd_client --add-name-servers "$DNS2" --tag "dhcp-$interface"
+fi
+
+$hostsd_client --apply
diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf
index 411429510..4880605d6 100644
--- a/src/etc/sysctl.d/30-vyos-router.conf
+++ b/src/etc/sysctl.d/30-vyos-router.conf
@@ -98,9 +98,6 @@ net.ipv6.route.skip_notify_on_dev_down=1
# Default value of 20 seems to interfere with larger OSPF and VRRP setups
net.ipv4.igmp_max_memberships = 512
-# Enable conntrack helper by default
-net.netfilter.nf_conntrack_helper=1
-
# Increase default garbage collection thresholds
net.ipv4.neigh.default.gc_thresh1 = 1024
net.ipv4.neigh.default.gc_thresh2 = 4096
diff --git a/src/helpers/system-versions-foot.py b/src/helpers/system-versions-foot.py
index 2aa687221..9614f0d28 100755
--- a/src/helpers/system-versions-foot.py
+++ b/src/helpers/system-versions-foot.py
@@ -1,6 +1,6 @@
#!/usr/bin/python3
-# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019, 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
@@ -16,24 +16,13 @@
# along with this library. If not, see <http://www.gnu.org/licenses/>.
import sys
-import vyos.formatversions as formatversions
-import vyos.systemversions as systemversions
import vyos.defaults
-import vyos.version
-
-sys_versions = systemversions.get_system_component_version()
-
-component_string = formatversions.format_versions_string(sys_versions)
-
-os_version_string = vyos.version.get_version()
+from vyos.component_version import write_system_footer
sys.stdout.write("\n\n")
if vyos.defaults.cfg_vintage == 'vyos':
- formatversions.write_vyos_versions_foot(None, component_string,
- os_version_string)
+ write_system_footer(None, vintage='vyos')
elif vyos.defaults.cfg_vintage == 'vyatta':
- formatversions.write_vyatta_versions_foot(None, component_string,
- os_version_string)
+ write_system_footer(None, vintage='vyatta')
else:
- formatversions.write_vyatta_versions_foot(None, component_string,
- os_version_string)
+ write_system_footer(None, vintage='vyos')
diff --git a/src/helpers/vyos-domain-group-resolve.py b/src/helpers/vyos-domain-group-resolve.py
deleted file mode 100755
index 6b677670b..000000000
--- a/src/helpers/vyos-domain-group-resolve.py
+++ /dev/null
@@ -1,60 +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/>.
-
-
-import time
-
-from vyos.configquery import ConfigTreeQuery
-from vyos.firewall import get_ips_domains_dict
-from vyos.firewall import nft_add_set_elements
-from vyos.firewall import nft_flush_set
-from vyos.firewall import nft_init_set
-from vyos.firewall import nft_update_set_elements
-from vyos.util import call
-
-
-base = ['firewall', 'group', 'domain-group']
-check_required = True
-# count_failed = 0
-# Timeout in sec between checks
-timeout = 300
-
-domain_state = {}
-
-if __name__ == '__main__':
-
- while check_required:
- config = ConfigTreeQuery()
- if config.exists(base):
- domain_groups = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- for set_name, domain_config in domain_groups.items():
- list_domains = domain_config['address']
- elements = []
- ip_dict = get_ips_domains_dict(list_domains)
-
- for domain in list_domains:
- # Resolution succeeded, update domain state
- if domain in ip_dict:
- domain_state[domain] = ip_dict[domain]
- elements += ip_dict[domain]
- # Resolution failed, use previous domain state
- elif domain in domain_state:
- elements += domain_state[domain]
-
- # Resolve successful
- if elements:
- nft_update_set_elements(f'D_{set_name}', elements)
- time.sleep(timeout)
diff --git a/src/helpers/vyos-domain-resolver.py b/src/helpers/vyos-domain-resolver.py
new file mode 100755
index 000000000..e31d9238e
--- /dev/null
+++ b/src/helpers/vyos-domain-resolver.py
@@ -0,0 +1,183 @@
+#!/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 json
+import os
+import time
+
+from vyos.configdict import dict_merge
+from vyos.configquery import ConfigTreeQuery
+from vyos.firewall import fqdn_config_parse
+from vyos.firewall import fqdn_resolve
+from vyos.util import cmd
+from vyos.util import commit_in_progress
+from vyos.util import dict_search_args
+from vyos.util import run
+from vyos.xml import defaults
+
+base = ['firewall']
+timeout = 300
+cache = False
+
+domain_state = {}
+
+ipv4_tables = {
+ 'ip vyos_mangle',
+ 'ip vyos_filter',
+ 'ip vyos_nat'
+}
+
+ipv6_tables = {
+ 'ip6 vyos_mangle',
+ 'ip6 vyos_filter'
+}
+
+def get_config(conf):
+ firewall = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ default_values = defaults(base)
+ for tmp in ['name', 'ipv6_name']:
+ if tmp in default_values:
+ del default_values[tmp]
+
+ if 'zone' in default_values:
+ del default_values['zone']
+
+ firewall = dict_merge(default_values, firewall)
+
+ global timeout, cache
+
+ if 'resolver_interval' in firewall:
+ timeout = int(firewall['resolver_interval'])
+
+ if 'resolver_cache' in firewall:
+ cache = True
+
+ fqdn_config_parse(firewall)
+
+ return firewall
+
+def resolve(domains, ipv6=False):
+ global domain_state
+
+ ip_list = set()
+
+ for domain in domains:
+ resolved = fqdn_resolve(domain, ipv6=ipv6)
+
+ if resolved and cache:
+ domain_state[domain] = resolved
+ elif not resolved:
+ if domain not in domain_state:
+ continue
+ resolved = domain_state[domain]
+
+ ip_list = ip_list | resolved
+ return ip_list
+
+def nft_output(table, set_name, ip_list):
+ output = [f'flush set {table} {set_name}']
+ if ip_list:
+ ip_str = ','.join(ip_list)
+ output.append(f'add element {table} {set_name} {{ {ip_str} }}')
+ return output
+
+def nft_valid_sets():
+ try:
+ valid_sets = []
+ sets_json = cmd('nft -j list sets')
+ sets_obj = json.loads(sets_json)
+
+ for obj in sets_obj['nftables']:
+ if 'set' in obj:
+ family = obj['set']['family']
+ table = obj['set']['table']
+ name = obj['set']['name']
+ valid_sets.append((f'{family} {table}', name))
+
+ return valid_sets
+ except:
+ return []
+
+def update(firewall):
+ conf_lines = []
+ count = 0
+
+ valid_sets = nft_valid_sets()
+
+ domain_groups = dict_search_args(firewall, 'group', 'domain_group')
+ if domain_groups:
+ for set_name, domain_config in domain_groups.items():
+ if 'address' not in domain_config:
+ continue
+
+ nft_set_name = f'D_{set_name}'
+ domains = domain_config['address']
+
+ ip_list = resolve(domains, ipv6=False)
+ for table in ipv4_tables:
+ if (table, nft_set_name) in valid_sets:
+ conf_lines += nft_output(table, nft_set_name, ip_list)
+
+ ip6_list = resolve(domains, ipv6=True)
+ for table in ipv6_tables:
+ if (table, nft_set_name) in valid_sets:
+ conf_lines += nft_output(table, nft_set_name, ip6_list)
+ count += 1
+
+ for set_name, domain in firewall['ip_fqdn'].items():
+ table = 'ip vyos_filter'
+ nft_set_name = f'FQDN_{set_name}'
+
+ ip_list = resolve([domain], ipv6=False)
+
+ if (table, nft_set_name) in valid_sets:
+ conf_lines += nft_output(table, nft_set_name, ip_list)
+ count += 1
+
+ for set_name, domain in firewall['ip6_fqdn'].items():
+ table = 'ip6 vyos_filter'
+ nft_set_name = f'FQDN_{set_name}'
+
+ ip_list = resolve([domain], ipv6=True)
+ if (table, nft_set_name) in valid_sets:
+ conf_lines += nft_output(table, nft_set_name, ip_list)
+ count += 1
+
+ nft_conf_str = "\n".join(conf_lines) + "\n"
+ code = run(f'nft -f -', input=nft_conf_str)
+
+ print(f'Updated {count} sets - result: {code}')
+
+if __name__ == '__main__':
+ print(f'VyOS domain resolver')
+
+ count = 1
+ while commit_in_progress():
+ if ( count % 60 == 0 ):
+ print(f'Commit still in progress after {count}s - waiting')
+ count += 1
+ time.sleep(1)
+
+ conf = ConfigTreeQuery()
+ firewall = get_config(conf)
+
+ print(f'interval: {timeout}s - cache: {cache}')
+
+ while True:
+ update(firewall)
+ time.sleep(timeout)
diff --git a/src/helpers/vyos-failover.py b/src/helpers/vyos-failover.py
new file mode 100755
index 000000000..0de945f20
--- /dev/null
+++ b/src/helpers/vyos-failover.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python3
+#
+# 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
+# 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 json
+import subprocess
+import socket
+import time
+
+from vyos.util import rc_cmd
+from pathlib import Path
+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)
+
+ % get_best_route_options('203.0.113.1')
+ ('192.168.0.1', 'eth1', 1)
+
+ % get_best_route_options('203.0.113.254')
+ (None, None, None)
+ """
+ rc, data = rc_cmd(f'ip --detail --json route show protocol failover {route}')
+ if rc == 0:
+ data = json.loads(data)
+ if len(data) == 0:
+ print(f'\nRoute {route} for protocol failover was not found')
+ return None, None, None
+ # Fake metric 999 by default
+ # Search route with the lowest metric
+ best_metric = 999
+ for entry in data:
+ if debug: print('\n', entry)
+ metric = entry.get('metric')
+ gateway = entry.get('gateway')
+ iface = entry.get('dev')
+ if metric < best_metric:
+ best_metric = metric
+ best_gateway = gateway
+ best_interface = iface
+ if debug:
+ print(f'### Best_route exists: {route}, best_gateway: {best_gateway}, '
+ f'best_metric: {best_metric}, best_iface: {best_interface}')
+ return best_gateway, best_interface, best_metric
+
+def is_port_open(ip, port):
+ """
+ Check connection to remote host and port
+ Return True if host alive
+
+ % is_port_open('example.com', 8080)
+ True
+ """
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
+ s.settimeout(2)
+ try:
+ s.connect((ip, int(port)))
+ s.shutdown(socket.SHUT_RDWR)
+ return True
+ except:
+ return False
+ finally:
+ s.close()
+
+def is_target_alive(target=None, iface='', proto='icmp', port=None, debug=False):
+ """
+ Host availability check by ICMP, ARP, TCP
+ Return True if target checks is successful
+
+ % is_target_alive('192.0.2.1', 'eth1', proto='arp')
+ True
+ """
+ if iface != '':
+ iface = f'-I {iface}'
+ if proto == 'icmp':
+ command = f'/usr/bin/ping -q {target} {iface} -n -c 2 -W 1'
+ rc, response = rc_cmd(command)
+ if debug: print(f' [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]')
+ if rc == 0:
+ return True
+ elif proto == 'arp':
+ command = f'/usr/bin/arping -b -c 2 -f -w 1 -i 1 {iface} {target}'
+ rc, response = rc_cmd(command)
+ if debug: print(f' [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]')
+ if rc == 0:
+ return True
+ elif proto == 'tcp' and port is not None:
+ return True if is_port_open(target, port) else False
+ else:
+ return False
+
+
+if __name__ == '__main__':
+ # Parse command arguments and get config
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-c',
+ '--config',
+ action='store',
+ help='Path to protocols failover configuration',
+ required=True,
+ type=Path)
+
+ args = parser.parse_args()
+ try:
+ config_path = Path(args.config)
+ config = json.loads(config_path.read_text())
+ except Exception as err:
+ print(
+ f'Configuration file "{config_path}" does not exist or malformed: {err}'
+ )
+ exit(1)
+
+ # Useful debug info to console, use debug = True
+ # sudo systemctl stop vyos-failover.service
+ # sudo /usr/libexec/vyos/vyos-failover.py --config /run/vyos-failover.conf
+ debug = False
+
+ while(True):
+
+ for route, route_config in config.get('route').items():
+
+ 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')
+ conf_metric = int(nexthop_config.get('metric'))
+ port = nexthop_config.get('check').get('port')
+ port_opt = f'port {port}' if port else ''
+ proto = nexthop_config.get('check').get('type')
+ target = nexthop_config.get('check').get('target')
+ timeout = nexthop_config.get('check').get('timeout')
+
+ # 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):
+ if debug: print(f' [ ADD ] -- ip route add {route} via {next_hop} dev {conf_iface} '
+ f'metric {conf_metric} proto failover\n###')
+ rc, command = rc_cmd(f'ip route add {route} via {next_hop} dev {conf_iface} '
+ f'metric {conf_metric} proto failover')
+ # If something is wrong and gateway not added
+ # Example: Error: Next-hop has invalid gateway.
+ if rc !=0:
+ if debug: print(f'{command} -- return-code [RC: {rc}] {next_hop} dev {conf_iface}')
+ else:
+ journal.send(f'ip route add {route} via {next_hop} dev {conf_iface} '
+ f'metric {conf_metric} proto failover', SYSLOG_IDENTIFIER=my_name)
+ else:
+ if debug: print(f' [ TARGET_FAIL ] target checks fails for [{target}], do nothing')
+ journal.send(f'Check fail for route {route} target {target} proto {proto} '
+ f'{port_opt}', SYSLOG_IDENTIFIER=my_name)
+
+ # 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 \
+ 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} '
+ f'metric {conf_metric} proto failover [DELETE]')
+ rc_cmd(f'ip route del {route} via {next_hop} dev {conf_iface} metric {conf_metric} proto failover')
+ journal.send(f'ip route del {route} via {next_hop} dev {conf_iface} '
+ f'metric {conf_metric} proto failover', SYSLOG_IDENTIFIER=my_name)
+
+ time.sleep(int(timeout))
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/firewall/8-to-9 b/src/migration-scripts/firewall/8-to-9
new file mode 100755
index 000000000..f7c1bb90d
--- /dev/null
+++ b/src/migration-scripts/firewall/8-to-9
@@ -0,0 +1,91 @@
+#!/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/>.
+
+# T4780: Add firewall interface group
+# cli changes from:
+# set firewall [name | ipv6-name] <name> rule <number> [inbound-interface | outbound-interface] <interface_name>
+# To
+# set firewall [name | ipv6-name] <name> rule <number> [inbound-interface | outbound-interface] [interface-name | interface-group] <interface_name | interface_group>
+
+import re
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+from vyos.ifconfig import Section
+
+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 = ['firewall']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+if config.exists(base + ['name']):
+ for name in config.list_nodes(base + ['name']):
+ if not config.exists(base + ['name', name, 'rule']):
+ continue
+
+ for rule in config.list_nodes(base + ['name', name, 'rule']):
+ rule_iiface = base + ['name', name, 'rule', rule, 'inbound-interface']
+ rule_oiface = base + ['name', name, 'rule', rule, 'outbound-interface']
+
+ if config.exists(rule_iiface):
+ tmp = config.return_value(rule_iiface)
+ config.delete(rule_iiface)
+ config.set(rule_iiface + ['interface-name'], value=tmp)
+
+ if config.exists(rule_oiface):
+ tmp = config.return_value(rule_oiface)
+ config.delete(rule_oiface)
+ config.set(rule_oiface + ['interface-name'], value=tmp)
+
+
+if config.exists(base + ['ipv6-name']):
+ for name in config.list_nodes(base + ['ipv6-name']):
+ if not config.exists(base + ['ipv6-name', name, 'rule']):
+ continue
+
+ for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']):
+ rule_iiface = base + ['ipv6-name', name, 'rule', rule, 'inbound-interface']
+ rule_oiface = base + ['ipv6-name', name, 'rule', rule, 'outbound-interface']
+
+ if config.exists(rule_iiface):
+ tmp = config.return_value(rule_iiface)
+ config.delete(rule_iiface)
+ config.set(rule_iiface + ['interface-name'], value=tmp)
+
+ if config.exists(rule_oiface):
+ tmp = config.return_value(rule_oiface)
+ config.delete(rule_oiface)
+ config.set(rule_oiface + ['interface-name'], value=tmp)
+
+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) \ No newline at end of file
diff --git a/src/migration-scripts/https/3-to-4 b/src/migration-scripts/https/3-to-4
new file mode 100755
index 000000000..5ee528b31
--- /dev/null
+++ b/src/migration-scripts/https/3-to-4
@@ -0,0 +1,53 @@
+#!/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/>.
+
+# T4768 rename node 'gql' to 'graphql'.
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 2):
+ 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)
+
+old_base = ['service', 'https', 'api', 'gql']
+if not config.exists(old_base):
+ # Nothing to do
+ sys.exit(0)
+
+new_base = ['service', 'https', 'api', 'graphql']
+config.set(new_base)
+
+nodes = config.list_nodes(old_base)
+for node in nodes:
+ config.copy(old_base + [node], new_base + [node])
+
+config.delete(old_base)
+
+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/ipsec/10-to-11 b/src/migration-scripts/ipsec/10-to-11
new file mode 100755
index 000000000..ec38d0034
--- /dev/null
+++ b/src/migration-scripts/ipsec/10-to-11
@@ -0,0 +1,85 @@
+#!/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 re
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['vpn', 'ipsec']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+# PEER changes
+if config.exists(base + ['site-to-site', 'peer']):
+ for peer in config.list_nodes(base + ['site-to-site', 'peer']):
+ peer_base = base + ['site-to-site', 'peer', peer]
+
+ # replace: 'ipsec site-to-site peer <tag> authentication pre-shared-secret xxx'
+ # => 'ipsec authentication psk <tag> secret xxx'
+ if config.exists(peer_base + ['authentication', 'pre-shared-secret']):
+ tmp = config.return_value(peer_base + ['authentication', 'pre-shared-secret'])
+ config.delete(peer_base + ['authentication', 'pre-shared-secret'])
+ config.set(base + ['authentication', 'psk', peer, 'secret'], value=tmp)
+ # format as tag node to avoid loading problems
+ config.set_tag(base + ['authentication', 'psk'])
+
+ # Get id's from peers for "ipsec auth psk <tag> id xxx"
+ if config.exists(peer_base + ['authentication', 'local-id']):
+ local_id = config.return_value(peer_base + ['authentication', 'local-id'])
+ config.set(base + ['authentication', 'psk', peer, 'id'], value=local_id, replace=False)
+ if config.exists(peer_base + ['authentication', 'remote-id']):
+ remote_id = config.return_value(peer_base + ['authentication', 'remote-id'])
+ config.set(base + ['authentication', 'psk', peer, 'id'], value=remote_id, replace=False)
+
+ if config.exists(peer_base + ['local-address']):
+ tmp = config.return_value(peer_base + ['local-address'])
+ config.set(base + ['authentication', 'psk', peer, 'id'], value=tmp, replace=False)
+ if config.exists(peer_base + ['remote-address']):
+ tmp = config.return_value(peer_base + ['remote-address'])
+ if tmp:
+ for remote_addr in tmp:
+ if remote_addr == 'any':
+ remote_addr = '%any'
+ config.set(base + ['authentication', 'psk', peer, 'id'], value=remote_addr, replace=False)
+
+ # get DHCP peer interface as psk dhcp-interface
+ if config.exists(peer_base + ['dhcp-interface']):
+ tmp = config.return_value(peer_base + ['dhcp-interface'])
+ config.set(base + ['authentication', 'psk', peer, 'dhcp-interface'], value=tmp)
+
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/migration-scripts/ipsec/9-to-10 b/src/migration-scripts/ipsec/9-to-10
index 1254104cb..de366ef3b 100755
--- a/src/migration-scripts/ipsec/9-to-10
+++ b/src/migration-scripts/ipsec/9-to-10
@@ -85,10 +85,10 @@ if config.exists(base + ['site-to-site', 'peer']):
config.rename(peer_base + ['authentication', 'id'], 'local-id')
# For the peer '@foo' set remote-id 'foo' if remote-id is not defined
- if peer.startswith('@'):
- if not config.exists(peer_base + ['authentication', 'remote-id']):
- tmp = peer.replace('@', '')
- config.set(peer_base + ['authentication', 'remote-id'], value=tmp)
+ # For the peer '192.0.2.1' set remote-id '192.0.2.1' if remote-id is not defined
+ if not config.exists(peer_base + ['authentication', 'remote-id']):
+ tmp = peer.replace('@', '') if peer.startswith('@') else peer
+ config.set(peer_base + ['authentication', 'remote-id'], value=tmp)
# replace: 'peer <tag> force-encapsulation enable'
# => 'peer <tag> force-udp-encapsulation'
diff --git a/src/migration-scripts/ntp/1-to-2 b/src/migration-scripts/ntp/1-to-2
new file mode 100755
index 000000000..d1e510e4c
--- /dev/null
+++ b/src/migration-scripts/ntp/1-to-2
@@ -0,0 +1,72 @@
+#!/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)
+
+# config.copy does not recursively create a path, so create ['service'] if
+# it doesn't yet exist, such as for config.boot.default
+if not config.exists(['service']):
+ config.set(['service'])
+
+# 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/policy/4-to-5 b/src/migration-scripts/policy/4-to-5
new file mode 100755
index 000000000..33c9e6ade
--- /dev/null
+++ b/src/migration-scripts/policy/4-to-5
@@ -0,0 +1,92 @@
+#!/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/>.
+
+# T2199: Migrate interface policy nodes to policy route <name> interface <ifname>
+
+import re
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+from vyos.ifconfig import Section
+
+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()
+
+base4 = ['policy', 'route']
+base6 = ['policy', 'route6']
+config = ConfigTree(config_file)
+
+if not config.exists(base4) and not config.exists(base6):
+ # Nothing to do
+ exit(0)
+
+def migrate_interface(config, iftype, ifname, vif=None, vifs=None, vifc=None):
+ if_path = ['interfaces', iftype, ifname]
+ ifname_full = ifname
+
+ if vif:
+ if_path += ['vif', vif]
+ ifname_full = f'{ifname}.{vif}'
+ elif vifs:
+ if_path += ['vif-s', vifs]
+ ifname_full = f'{ifname}.{vifs}'
+ if vifc:
+ if_path += ['vif-c', vifc]
+ ifname_full = f'{ifname}.{vifs}.{vifc}'
+
+ if not config.exists(if_path + ['policy']):
+ return
+
+ if config.exists(if_path + ['policy', 'route']):
+ route_name = config.return_value(if_path + ['policy', 'route'])
+ config.set(base4 + [route_name, 'interface'], value=ifname_full, replace=False)
+
+ if config.exists(if_path + ['policy', 'route6']):
+ route_name = config.return_value(if_path + ['policy', 'route6'])
+ config.set(base6 + [route_name, 'interface'], value=ifname_full, replace=False)
+
+ config.delete(if_path + ['policy'])
+
+for iftype in config.list_nodes(['interfaces']):
+ for ifname in config.list_nodes(['interfaces', iftype]):
+ migrate_interface(config, iftype, ifname)
+
+ if config.exists(['interfaces', iftype, ifname, 'vif']):
+ for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']):
+ migrate_interface(config, iftype, ifname, vif=vif)
+
+ if config.exists(['interfaces', iftype, ifname, 'vif-s']):
+ for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']):
+ migrate_interface(config, iftype, ifname, vifs=vifs)
+
+ if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']):
+ for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']):
+ migrate_interface(config, iftype, ifname, vifs=vifs, vifc=vifc)
+
+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/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/migration-scripts/snmp/2-to-3 b/src/migration-scripts/snmp/2-to-3
new file mode 100755
index 000000000..5f8d9c88d
--- /dev/null
+++ b/src/migration-scripts/snmp/2-to-3
@@ -0,0 +1,57 @@
+#!/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/>.
+
+# T4857: Implement FRR SNMP recomendations
+# cli changes from:
+# set service snmp oid-enable route-table
+# To
+# set service snmp oid-enable ip-forward
+
+import re
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+from vyos.ifconfig import Section
+
+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 = ['service snmp']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+if config.exists(base + ['oid-enable']):
+ config.delete(base + ['oid-enable'])
+ config.set(base + ['oid-enable'], 'ip-forward')
+
+
+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/accelppp.py b/src/op_mode/accelppp.py
new file mode 100755
index 000000000..2fd045dc3
--- /dev/null
+++ b/src/op_mode/accelppp.py
@@ -0,0 +1,133 @@
+#!/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 sys
+
+import vyos.accel_ppp
+import vyos.opmode
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.util import rc_cmd
+
+
+accel_dict = {
+ 'ipoe': {
+ 'port': 2002,
+ 'path': 'service ipoe-server'
+ },
+ 'pppoe': {
+ 'port': 2001,
+ 'path': 'service pppoe-server'
+ },
+ 'pptp': {
+ 'port': 2003,
+ 'path': 'vpn pptp'
+ },
+ 'l2tp': {
+ 'port': 2004,
+ 'path': 'vpn l2tp'
+ },
+ 'sstp': {
+ 'port': 2005,
+ 'path': 'vpn sstp'
+ }
+}
+
+
+def _get_raw_statistics(accel_output, pattern):
+ return vyos.accel_ppp.get_server_statistics(accel_output, pattern, sep=':')
+
+
+def _get_raw_sessions(port):
+ cmd_options = 'show sessions ifname,username,ip,ip6,ip6-dp,type,state,' \
+ 'uptime-raw,calling-sid,called-sid,sid,comp,rx-bytes-raw,' \
+ 'tx-bytes-raw,rx-pkts,tx-pkts'
+ output = vyos.accel_ppp.accel_cmd(port, cmd_options)
+ parsed_data: list[dict[str, str]] = vyos.accel_ppp.accel_out_parse(
+ output.splitlines())
+ return parsed_data
+
+
+def _verify(func):
+ """Decorator checks if accel-ppp protocol
+ ipoe/pppoe/pptp/l2tp/sstp is configured
+
+ for example:
+ service ipoe-server
+ vpn sstp
+ """
+ from functools import wraps
+
+ @wraps(func)
+ def _wrapper(*args, **kwargs):
+ config = ConfigTreeQuery()
+ protocol_list = accel_dict.keys()
+ protocol = kwargs.get('protocol')
+ # unknown or incorrect protocol query
+ if protocol not in protocol_list:
+ unconf_message = f'unknown protocol "{protocol}"'
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ # Check if config does not exist
+ config_protocol_path = accel_dict[protocol]['path']
+ if not config.exists(config_protocol_path):
+ unconf_message = f'"{config_protocol_path}" is not configured'
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ return func(*args, **kwargs)
+
+ return _wrapper
+
+
+@_verify
+def show_statistics(raw: bool, protocol: str):
+ """show accel-cmd statistics
+ CPU utilization and amount of sessions
+
+ protocol: ipoe/pppoe/ppptp/l2tp/sstp
+ """
+ pattern = f'{protocol}:'
+ port = accel_dict[protocol]['port']
+ rc, output = rc_cmd(f'/usr/bin/accel-cmd -p {port} show stat')
+
+ if raw:
+ return _get_raw_statistics(output, pattern)
+
+ return output
+
+
+@_verify
+def show_sessions(raw: bool, protocol: str):
+ """show accel-cmd sessions
+
+ protocol: ipoe/pppoe/ppptp/l2tp/sstp
+ """
+ port = accel_dict[protocol]['port']
+ if raw:
+ return _get_raw_sessions(port)
+
+ return vyos.accel_ppp.accel_cmd(port,
+ 'show sessions ifname,username,ip,ip6,ip6-dp,'
+ 'calling-sid,rate-limit,state,uptime,rx-bytes,tx-bytes')
+
+
+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/bgp.py b/src/op_mode/bgp.py
new file mode 100755
index 000000000..23001a9d7
--- /dev/null
+++ b/src/op_mode/bgp.py
@@ -0,0 +1,120 @@
+#!/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/>.
+#
+# Purpose:
+# Displays bgp neighbors information.
+# Used by the "show bgp (vrf <tag>) ipv4|ipv6 neighbors" commands.
+
+import re
+import sys
+import typing
+
+import jmespath
+from jinja2 import Template
+from humps import decamelize
+
+from vyos.configquery import ConfigTreeQuery
+
+import vyos.opmode
+
+
+frr_command_template = Template("""
+{% if family %}
+ show bgp
+ {{ 'vrf ' ~ vrf if vrf else '' }}
+ {{ 'ipv6' if family == 'inet6' else 'ipv4'}}
+ {{ 'neighbor ' ~ peer if peer else 'summary' }}
+{% endif %}
+
+{% if raw %}
+ json
+{% endif %}
+""")
+
+
+def _verify(func):
+ """Decorator checks if BGP config exists
+ BGP configuration can be present under vrf <tag>
+ If we do npt get arg 'peer' then it can be 'bgp summary'
+ """
+ from functools import wraps
+
+ @wraps(func)
+ def _wrapper(*args, **kwargs):
+ config = ConfigTreeQuery()
+ afi = 'ipv6' if kwargs.get('family') == 'inet6' else 'ipv4'
+ global_vrfs = ['all', 'default']
+ peer = kwargs.get('peer')
+ vrf = kwargs.get('vrf')
+ unconf_message = f'BGP or neighbor is not configured'
+ # Add option to check the specific neighbor if we have arg 'peer'
+ peer_opt = f'neighbor {peer} address-family {afi}-unicast' if peer else ''
+ vrf_opt = ''
+ if vrf and vrf not in global_vrfs:
+ vrf_opt = f'vrf name {vrf}'
+ # Check if config does not exist
+ if not config.exists(f'{vrf_opt} protocols bgp {peer_opt}'):
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ return func(*args, **kwargs)
+
+ return _wrapper
+
+
+@_verify
+def show_neighbors(raw: bool,
+ family: str,
+ peer: typing.Optional[str],
+ vrf: typing.Optional[str]):
+ kwargs = dict(locals())
+ frr_command = frr_command_template.render(kwargs)
+ frr_command = re.sub(r'\s+', ' ', frr_command)
+
+ from vyos.util import cmd
+ output = cmd(f"vtysh -c '{frr_command}'")
+
+ if raw:
+ from json import loads
+ data = loads(output)
+ # Get list of the peers
+ peers = jmespath.search('*.peers | [0]', data)
+ if peers:
+ # Create new dict, delete old key 'peers'
+ # add key 'peers' neighbors to the list
+ list_peers = []
+ new_dict = jmespath.search('* | [0]', data)
+ if 'peers' in new_dict:
+ new_dict.pop('peers')
+
+ for neighbor, neighbor_options in peers.items():
+ neighbor_options['neighbor'] = neighbor
+ list_peers.append(neighbor_options)
+ new_dict['peers'] = list_peers
+ return decamelize(new_dict)
+ data = jmespath.search('* | [0]', data)
+ return decamelize(data)
+
+ else:
+ return output
+
+
+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/bridge.py b/src/op_mode/bridge.py
index 5a821a287..d6098c158 100755
--- a/src/op_mode/bridge.py
+++ b/src/op_mode/bridge.py
@@ -32,7 +32,7 @@ def _get_json_data():
"""
Get bridge data format JSON
"""
- return cmd(f'sudo bridge --json link show')
+ return cmd(f'bridge --json link show')
def _get_raw_data_summary():
@@ -48,7 +48,7 @@ def _get_raw_data_vlan():
"""
:returns dict
"""
- json_data = cmd('sudo bridge --json --compressvlans vlan show')
+ json_data = cmd('bridge --json --compressvlans vlan show')
data_dict = json.loads(json_data)
return data_dict
@@ -57,7 +57,7 @@ def _get_raw_data_fdb(bridge):
"""Get MAC-address for the bridge brX
:returns list
"""
- code, json_data = rc_cmd(f'sudo bridge --json fdb show br {bridge}')
+ code, json_data = rc_cmd(f'bridge --json fdb show br {bridge}')
# From iproute2 fdb.c, fdb_show() will only exit(-1) in case of
# non-existent bridge device; raise error.
if code == 255:
diff --git a/src/op_mode/config_mgmt.py b/src/op_mode/config_mgmt.py
new file mode 100755
index 000000000..66de26d1f
--- /dev/null
+++ b/src/op_mode/config_mgmt.py
@@ -0,0 +1,85 @@
+#!/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 sys
+import typing
+
+import vyos.opmode
+from vyos.config_mgmt import ConfigMgmt
+
+def show_commit_diff(raw: bool, rev: int, rev2: typing.Optional[int],
+ commands: bool):
+ config_mgmt = ConfigMgmt()
+ config_diff = config_mgmt.show_commit_diff(rev, rev2, commands)
+
+ if raw:
+ rev2 = (rev+1) if rev2 is None else rev2
+ if commands:
+ d = {f'config_command_diff_{rev2}_{rev}': config_diff}
+ else:
+ d = {f'config_file_diff_{rev2}_{rev}': config_diff}
+ return d
+
+ return config_diff
+
+def show_commit_file(raw: bool, rev: int):
+ config_mgmt = ConfigMgmt()
+ config_file = config_mgmt.show_commit_file(rev)
+
+ if raw:
+ d = {f'config_revision_{rev}': config_file}
+ return d
+
+ return config_file
+
+def show_commit_log(raw: bool):
+ config_mgmt = ConfigMgmt()
+
+ msg = ''
+ if config_mgmt.max_revisions == 0:
+ msg = ('commit-revisions is not configured;\n'
+ 'commit log is empty or stale:\n\n')
+
+ data = config_mgmt.get_raw_log_data()
+ if raw:
+ return data
+
+ out = config_mgmt.format_log_data(data)
+ out = msg + out
+
+ return out
+
+def show_commit_log_brief(raw: bool):
+ # used internally for completion help for 'rollback'
+ # option 'raw' will return same as 'show_commit_log'
+ config_mgmt = ConfigMgmt()
+
+ data = config_mgmt.get_raw_log_data()
+ if raw:
+ return data
+
+ out = config_mgmt.format_log_data_brief(data)
+
+ return out
+
+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/connect_disconnect.py b/src/op_mode/connect_disconnect.py
index 936c20bcb..d39e88bf3 100755
--- a/src/op_mode/connect_disconnect.py
+++ b/src/op_mode/connect_disconnect.py
@@ -41,7 +41,7 @@ def check_ppp_running(interface):
def connect(interface):
""" Connect dialer interface """
- if interface.startswith('ppp'):
+ if interface.startswith('pppoe') or interface.startswith('sstpc'):
check_ppp_interface(interface)
# Check if interface is already dialed
if os.path.isdir(f'/sys/class/net/{interface}'):
@@ -62,7 +62,7 @@ def connect(interface):
def disconnect(interface):
""" Disconnect dialer interface """
- if interface.startswith('ppp'):
+ if interface.startswith('pppoe') or interface.startswith('sstpc'):
check_ppp_interface(interface)
# Check if interface is already down
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
new file mode 100755
index 000000000..b9e6e7bc9
--- /dev/null
+++ b/src/op_mode/dhcp.py
@@ -0,0 +1,291 @@
+#!/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 sys
+import typing
+
+from datetime import datetime
+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
+
+from vyos.util import cmd
+from vyos.util import dict_search
+from vyos.util import is_systemd_service_running
+
+config = ConfigTreeQuery()
+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())
+
+
+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 _find_list_of_dict_index(lst, key='ip', value='') -> int:
+ """
+ Find the index entry of list of dict matching the dict value
+ Exampe:
+ % lst = [{'ip': '192.0.2.1'}, {'ip': '192.0.2.2'}]
+ % _find_list_of_dict_index(lst, key='ip', value='192.0.2.2')
+ % 1
+ """
+ idx = next((index for (index, d) in enumerate(lst) if d[key] == value), None)
+ return idx
+
+
+def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[]) -> list:
+ """
+ Get DHCP server leases
+ :return list
+ """
+ lease_file = '/config/dhcpdv6.leases' if family == 'inet6' else '/config/dhcpd.leases'
+ data = []
+ leases = IscDhcpLeases(lease_file).get()
+
+ if pool is None:
+ pool = _get_dhcp_pools(family=family)
+ else:
+ pool = [pool]
+
+ for lease in leases:
+ data_lease = {}
+ data_lease['ip'] = lease.ip
+ data_lease['state'] = lease.binding_state
+ data_lease['pool'] = lease.sets.get('shared-networkname', '')
+ data_lease['end'] = lease.end.timestamp()
+
+ if family == 'inet':
+ data_lease['mac'] = lease.ethernet
+ data_lease['start'] = lease.start.timestamp()
+ data_lease['hostname'] = lease.hostname
+
+ if family == 'inet6':
+ data_lease['last_communication'] = lease.last_communication.timestamp()
+ data_lease['iaid_duid'] = _format_hex_string(lease.host_identifier_string)
+ lease_types_long = {'na': 'non-temporary', 'ta': 'temporary', 'pd': 'prefix delegation'}
+ data_lease['type'] = lease_types_long[lease.type]
+
+ data_lease['remaining'] = lease.end - datetime.utcnow()
+
+ if data_lease['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_lease['remaining'] = str(data_lease["remaining"]).split('.')[0]
+ else:
+ data_lease['remaining'] = ''
+
+ # Do not add old leases
+ 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 = []
+ for entry in data:
+ addr = entry.get('ip')
+ if addr not in checked:
+ checked.append(addr)
+ else:
+ 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='inet'):
+ data_entries = []
+ if family == 'inet':
+ for lease in raw_data:
+ ipaddr = lease.get('ip')
+ 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')
+ end = lease.get('end')
+ end = _utc_to_local(end).strftime('%Y/%m/%d %H:%M:%S')
+ remain = lease.get('remaining')
+ pool = lease.get('pool')
+ hostname = lease.get('hostname')
+ data_entries.append([ipaddr, hw_addr, state, start, end, remain, pool, hostname])
+
+ headers = ['IP Address', 'MAC address', 'State', 'Lease start', 'Lease expiration', 'Remaining', 'Pool',
+ 'Hostname']
+
+ if family == 'inet6':
+ for lease in raw_data:
+ ipaddr = lease.get('ip')
+ state = lease.get('state')
+ start = lease.get('last_communication')
+ start = _utc_to_local(start).strftime('%Y/%m/%d %H:%M:%S')
+ end = lease.get('end')
+ end = _utc_to_local(end).strftime('%Y/%m/%d %H:%M:%S')
+ remain = lease.get('remaining')
+ lease_type = lease.get('type')
+ pool = lease.get('pool')
+ host_identifier = lease.get('iaid_duid')
+ data_entries.append([ipaddr, state, start, end, remain, lease_type, pool, host_identifier])
+
+ headers = ['IPv6 address', 'State', 'Last communication', 'Lease expiration', 'Remaining', 'Type', 'Pool',
+ 'IAID_DUID']
+
+ output = tabulate(data_entries, headers, numalign='left')
+ return output
+
+
+def _get_dhcp_pools(family='inet') -> list:
+ v = 'v6' if family == 'inet6' else ''
+ pools = config.list_nodes(f'service dhcp{v}-server shared-network-name')
+ return pools
+
+
+def _get_pool_size(pool, family='inet'):
+ v = 'v6' if family == 'inet6' else ''
+ base = f'service dhcp{v}-server shared-network-name {pool}'
+ size = 0
+ subnets = config.list_nodes(f'{base} subnet')
+ for subnet in subnets:
+ if family == 'inet6':
+ ranges = config.list_nodes(f'{base} subnet {subnet} address-range start')
+ else:
+ ranges = config.list_nodes(f'{base} subnet {subnet} range')
+ for range in ranges:
+ if family == 'inet6':
+ start = config.list_nodes(f'{base} subnet {subnet} address-range start')[0]
+ stop = config.value(f'{base} subnet {subnet} address-range start {start} stop')
+ else:
+ start = config.value(f'{base} subnet {subnet} range {range} start')
+ stop = config.value(f'{base} subnet {subnet} range {range} stop')
+ # Add +1 because both range boundaries are inclusive
+ size += int(ip_address(stop)) - int(ip_address(start)) + 1
+ return size
+
+
+def _get_raw_pool_statistics(family='inet', pool=None):
+ if pool is None:
+ pool = _get_dhcp_pools(family=family)
+ else:
+ pool = [pool]
+
+ v = 'v6' if family == 'inet6' else ''
+ stats = []
+ for p in pool:
+ subnet = config.list_nodes(f'service dhcp{v}-server shared-network-name {p} subnet')
+ size = _get_pool_size(family=family, pool=p)
+ leases = len(_get_raw_server_leases(family=family, pool=p))
+ use_percentage = round(leases / size * 100) if size != 0 else 0
+ pool_stats = {'pool': p, 'size': size, 'leases': leases,
+ 'available': (size - leases), 'use_percentage': use_percentage, 'subnet': subnet}
+ stats.append(pool_stats)
+ return stats
+
+
+def _get_formatted_pool_statistics(pool_data, family='inet'):
+ data_entries = []
+ for entry in pool_data:
+ pool = entry.get('pool')
+ size = entry.get('size')
+ leases = entry.get('leases')
+ available = entry.get('available')
+ use_percentage = entry.get('use_percentage')
+ use_percentage = f'{use_percentage}%'
+ data_entries.append([pool, size, leases, available, use_percentage])
+
+ headers = ['Pool', 'Size','Leases', 'Available', 'Usage']
+ output = tabulate(data_entries, headers, numalign='left')
+ return output
+
+
+def _verify(func):
+ """Decorator checks if DHCP(v6) config exists"""
+ from functools import wraps
+
+ @wraps(func)
+ def _wrapper(*args, **kwargs):
+ config = ConfigTreeQuery()
+ family = kwargs.get('family')
+ v = 'v6' if family == 'inet6' else ''
+ unconf_message = f'DHCP{v} server is not configured'
+ # Check if config does not exist
+ if not config.exists(f'service dhcp{v}-server'):
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ return func(*args, **kwargs)
+ return _wrapper
+
+
+@_verify
+def show_pool_statistics(raw: bool, family: str, pool: typing.Optional[str]):
+ pool_data = _get_raw_pool_statistics(family=family, pool=pool)
+ if raw:
+ return pool_data
+ else:
+ return _get_formatted_pool_statistics(pool_data, family=family)
+
+
+@_verify
+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.')
+
+ 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 lease_data
+ else:
+ return _get_formatted_server_leases(lease_data, family=family)
+
+
+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/dns.py b/src/op_mode/dns.py
index 9e5b1040c..a0e47d7ad 100755
--- a/src/op_mode/dns.py
+++ b/src/op_mode/dns.py
@@ -54,10 +54,10 @@ def _data_to_dict(data, sep="\t") -> dict:
def _get_raw_forwarding_statistics() -> dict:
- command = cmd('sudo /usr/bin/rec_control --socket-dir=/run/powerdns get-all')
+ command = cmd('rec_control --socket-dir=/run/powerdns get-all')
data = _data_to_dict(command)
data['cache-size'] = "{0:.2f}".format( int(
- cmd('sudo /usr/bin/rec_control --socket-dir=/run/powerdns get cache-bytes')) / 1024 )
+ cmd('rec_control --socket-dir=/run/powerdns get cache-bytes')) / 1024 )
return data
diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py
index 950feb625..46bda5f7e 100755
--- a/src/op_mode/firewall.py
+++ b/src/op_mode/firewall.py
@@ -63,7 +63,7 @@ def get_config_firewall(conf, name=None, ipv6=False, interfaces=True):
get_first_key=True, no_tag_node_value_mangle=True)
if firewall and interfaces:
if name:
- firewall['interface'] = []
+ firewall['interface'] = {}
else:
if 'name' in firewall:
for fw_name, name_conf in firewall['name'].items():
diff --git a/src/op_mode/generate_interfaces_debug_archive.py b/src/op_mode/generate_interfaces_debug_archive.py
new file mode 100755
index 000000000..f5767080a
--- /dev/null
+++ b/src/op_mode/generate_interfaces_debug_archive.py
@@ -0,0 +1,115 @@
+#!/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 datetime import datetime
+from pathlib import Path
+from shutil import rmtree
+from socket import gethostname
+from sys import exit
+from tarfile import open as tar_open
+from vyos.util import rc_cmd
+import os
+
+# define a list of commands that needs to be executed
+
+CMD_LIST: list[str] = [
+ "journalctl -b -n 500",
+ "journalctl -b -k -n 500",
+ "ip -s l",
+ "cat /proc/interrupts",
+ "cat /proc/softirqs",
+ "top -b -d 1 -n 2 -1",
+ "netstat -l",
+ "cat /proc/net/dev",
+ "cat /proc/net/softnet_stat",
+ "cat /proc/net/icmp",
+ "cat /proc/net/udp",
+ "cat /proc/net/tcp",
+ "cat /proc/net/netstat",
+ "sysctl net",
+ "timeout 10 tcpdump -c 500 -eni any port not 22"
+]
+
+CMD_INTERFACES_LIST: list[str] = [
+ "ethtool -i ",
+ "ethtool -S ",
+ "ethtool -g ",
+ "ethtool -c ",
+ "ethtool -a ",
+ "ethtool -k ",
+ "ethtool -i ",
+ "ethtool --phy-statistics "
+]
+
+# get intefaces info
+interfaces_list = os.popen('ls /sys/class/net/').read().split()
+
+# modify CMD_INTERFACES_LIST for all interfaces
+CMD_INTERFACES_LIST_MOD=[]
+for command_interface in interfaces_list:
+ for command_interfacev2 in CMD_INTERFACES_LIST:
+ CMD_INTERFACES_LIST_MOD.append (f'{command_interfacev2}{command_interface}')
+
+# execute a command and save the output to a file
+
+def save_stdout(command: str, file: Path) -> None:
+ rc, stdout = rc_cmd(command)
+ body: str = f'''### {command} ###
+Command: {command}
+Exit code: {rc}
+Stdout:
+{stdout}
+
+'''
+ with file.open(mode='a') as f:
+ f.write(body)
+
+# get local host name
+hostname: str = gethostname()
+# get current time
+time_now: str = datetime.now().isoformat(timespec='seconds')
+
+# define a temporary directory for logs and collected data
+tmp_dir: Path = Path(f'/tmp/drops-debug_{time_now}')
+# set file paths
+drops_file: Path = Path(f'{tmp_dir}/drops.txt')
+interfaces_file: Path = Path(f'{tmp_dir}/interfaces.txt')
+archive_file: str = f'/tmp/packet-drops-debug_{time_now}.tar.bz2'
+
+# create files
+tmp_dir.mkdir()
+drops_file.touch()
+interfaces_file.touch()
+
+try:
+ # execute all commands
+ for command in CMD_LIST:
+ save_stdout(command, drops_file)
+ for command_interface in CMD_INTERFACES_LIST_MOD:
+ save_stdout(command_interface, interfaces_file)
+
+ # create an archive
+ with tar_open(name=archive_file, mode='x:bz2') as tar_file:
+ tar_file.add(tmp_dir)
+
+ # inform user about success
+ print(f'Debug file is generated and located in {archive_file}')
+except Exception as err:
+ print(f'Error during generating a debug file: {err}')
+finally:
+ # cleanup
+ rmtree(tmp_dir)
+ exit()
diff --git a/src/op_mode/generate_ipsec_debug_archive.py b/src/op_mode/generate_ipsec_debug_archive.py
new file mode 100755
index 000000000..1422559a8
--- /dev/null
+++ b/src/op_mode/generate_ipsec_debug_archive.py
@@ -0,0 +1,89 @@
+#!/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 datetime import datetime
+from pathlib import Path
+from shutil import rmtree
+from socket import gethostname
+from sys import exit
+from tarfile import open as tar_open
+from vyos.util import rc_cmd
+
+# define a list of commands that needs to be executed
+CMD_LIST: list[str] = [
+ 'ipsec status',
+ 'swanctl -L',
+ 'swanctl -l',
+ 'swanctl -P',
+ 'ip x sa show',
+ 'ip x policy show',
+ 'ip tunnel show',
+ 'ip address',
+ 'ip rule show',
+ 'ip route | head -100',
+ 'ip route show table 220'
+]
+JOURNALCTL_CMD: str = 'journalctl -b -n 10000 /usr/lib/ipsec/charon'
+
+# execute a command and save the output to a file
+def save_stdout(command: str, file: Path) -> None:
+ rc, stdout = rc_cmd(command)
+ body: str = f'''### {command} ###
+Command: {command}
+Exit code: {rc}
+Stdout:
+{stdout}
+
+'''
+ with file.open(mode='a') as f:
+ f.write(body)
+
+
+# get local host name
+hostname: str = gethostname()
+# get current time
+time_now: str = datetime.now().isoformat(timespec='seconds')
+
+# define a temporary directory for logs and collected data
+tmp_dir: Path = Path(f'/tmp/ipsec_debug_{time_now}')
+# set file paths
+ipsec_status_file: Path = Path(f'{tmp_dir}/ipsec_status.txt')
+journalctl_charon_file: Path = Path(f'{tmp_dir}/journalctl_charon.txt')
+archive_file: str = f'/tmp/ipsec_debug_{time_now}.tar.bz2'
+
+# create files
+tmp_dir.mkdir()
+ipsec_status_file.touch()
+journalctl_charon_file.touch()
+
+try:
+ # execute all commands
+ for command in CMD_LIST:
+ save_stdout(command, ipsec_status_file)
+ save_stdout(JOURNALCTL_CMD, journalctl_charon_file)
+
+ # create an archive
+ with tar_open(name=archive_file, mode='x:bz2') as tar_file:
+ tar_file.add(tmp_dir)
+
+ # inform user about success
+ print(f'Debug file is generated and located in {archive_file}')
+except Exception as err:
+ print(f'Error during generating a debug file: {err}')
+finally:
+ # cleanup
+ rmtree(tmp_dir)
+ exit()
diff --git a/src/op_mode/generate_ipsec_debug_archive.sh b/src/op_mode/generate_ipsec_debug_archive.sh
deleted file mode 100755
index 53d0a6eaa..000000000
--- a/src/op_mode/generate_ipsec_debug_archive.sh
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env bash
-
-# Collecting IPSec Debug Information
-
-DATE=`date +%d-%m-%Y`
-
-a_CMD=(
- "sudo ipsec status"
- "sudo swanctl -L"
- "sudo swanctl -l"
- "sudo swanctl -P"
- "sudo ip x sa show"
- "sudo ip x policy show"
- "sudo ip tunnel show"
- "sudo ip address"
- "sudo ip rule show"
- "sudo ip route"
- "sudo ip route show table 220"
- )
-
-
-echo "DEBUG: ${DATE} on host \"$(hostname)\"" > /tmp/ipsec-status-${DATE}.txt
-date >> /tmp/ipsec-status-${DATE}.txt
-
-# Execute all DEBUG commands and save it to file
-for cmd in "${a_CMD[@]}"; do
- echo -e "\n### ${cmd} ###" >> /tmp/ipsec-status-${DATE}.txt
- ${cmd} >> /tmp/ipsec-status-${DATE}.txt 2>/dev/null
-done
-
-# Collect charon logs, build .tgz archive
-sudo journalctl /usr/lib/ipsec/charon > /tmp/journalctl-charon-${DATE}.txt && \
-sudo tar -zcvf /tmp/ipsec-debug-${DATE}.tgz /tmp/journalctl-charon-${DATE}.txt /tmp/ipsec-status-${DATE}.txt >& /dev/null
-sudo rm -f /tmp/journalctl-charon-${DATE}.txt /tmp/ipsec-status-${DATE}.txt
-
-echo "Debug file is generated and located in /tmp/ipsec-debug-${DATE}.tgz"
diff --git a/src/op_mode/generate_system_login_user.py b/src/op_mode/generate_system_login_user.py
new file mode 100755
index 000000000..8f8827b1b
--- /dev/null
+++ b/src/op_mode/generate_system_login_user.py
@@ -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/>.
+
+import argparse
+import os
+
+from vyos.util import popen
+from secrets import token_hex
+from base64 import b32encode
+
+if os.geteuid() != 0:
+ exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.")
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-u", "--username", type=str, help='Username used for authentication', required=True)
+ parser.add_argument("-l", "--rate_limit", type=str, help='Limit number of logins (rate-limit) per rate-time (default: 3)', default="3", required=False)
+ parser.add_argument("-t", "--rate_time", type=str, help='Limit number of logins (rate-limit) per rate-time (default: 30)', default="30", required=False)
+ parser.add_argument("-w", "--window_size", type=str, help='Set window of concurrently valid codes (default: 3)', default="3", required=False)
+ parser.add_argument("-i", "--interval", type=str, help='Duration of single time interval', default="30", required=False)
+ parser.add_argument("-d", "--digits", type=str, help='The number of digits in the one-time password', default="6", required=False)
+ args = parser.parse_args()
+
+ hostname = os.uname()[1]
+ username = args.username
+ rate_limit = args.rate_limit
+ rate_time = args.rate_time
+ window_size = args.window_size
+ digits = args.digits
+ period = args.interval
+
+ # check variables:
+ if int(rate_limit) < 1 or int(rate_limit) > 10:
+ print("")
+ quit("Number of logins (rate-limit) must be between '1' and '10'")
+
+ if int(rate_time) < 15 or int(rate_time) > 600:
+ print("")
+ quit("The rate-time must be between '15' and '600' seconds")
+
+ if int(window_size) < 1 or int(window_size) > 21:
+ print("")
+ quit("Window of concurrently valid codes must be between '1' and '21' seconds")
+
+ # generate OTP key, URL & QR:
+ key_hex = token_hex(20)
+ key_base32 = b32encode(bytes.fromhex(key_hex)).decode()
+
+ otp_url=''.join(["otpauth://totp/",username,"@",hostname,"?secret=",key_base32,"&digits=",digits,"&period=",period])
+ qrcode,err = popen('qrencode -t ansiutf8', input=otp_url)
+
+ print("# You can share it with the user, he just needs to scan the QR in his OTP app")
+ print("# username: ", username)
+ print("# OTP KEY: ", key_base32)
+ print("# OTP URL: ", otp_url)
+ print(qrcode)
+ print('# To add this OTP key to configuration, run the following commands:')
+ print(f"set system login user {username} authentication otp key '{key_base32}'")
+ if rate_limit != "3":
+ print(f"set system login user {username} authentication otp rate-limit '{rate_limit}'")
+ if rate_time != "30":
+ print(f"set system login user {username} authentication otp rate-time '{rate_time}'")
+ if window_size != "3":
+ print(f"set system login user {username} authentication otp window-size '{window_size}'")
diff --git a/src/op_mode/igmp-proxy.py b/src/op_mode/igmp-proxy.py
new file mode 100755
index 000000000..0086c9aa6
--- /dev/null
+++ b/src/op_mode/igmp-proxy.py
@@ -0,0 +1,99 @@
+#!/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/>.
+
+# File: show_igmpproxy.py
+# Purpose:
+# Display istatistics from IPv4 IGMP proxy.
+# Used by the "run show ip multicast" command tree.
+
+import ipaddress
+import json
+import socket
+import sys
+import tabulate
+
+import vyos.config
+import vyos.opmode
+
+from vyos.util import bytes_to_human, print_error
+
+def _is_configured():
+ """Check if IGMP proxy is configured"""
+ return vyos.config.Config().exists_effective('protocols igmp-proxy')
+
+def _is_running():
+ """Check if IGMP proxy is currently running"""
+ return not vyos.util.run('ps -C igmpproxy')
+
+def _kernel_to_ip(addr):
+ """
+ Convert any given address from Linux kernel to a proper, IPv4 address
+ using the correct host byte order.
+ """
+ # Convert from hex 'FE000A0A' to decimal '4261415434'
+ addr = int(addr, 16)
+ # Kernel ABI _always_ uses network byte order.
+ addr = socket.ntohl(addr)
+ return str(ipaddress.IPv4Address(addr))
+
+def _process_mr_vif():
+ """Read rows from /proc/net/ip_mr_vif into dicts."""
+ result = []
+ with open('/proc/net/ip_mr_vif', 'r') as f:
+ next(f)
+ for line in f:
+ result.append({
+ 'Interface': line.split()[1],
+ 'PktsIn' : int(line.split()[3]),
+ 'PktsOut' : int(line.split()[5]),
+ 'BytesIn' : int(line.split()[2]),
+ 'BytesOut' : int(line.split()[4]),
+ 'Local' : _kernel_to_ip(line.split()[7]),
+ })
+ return result
+
+def show_interface(raw: bool):
+ if data := _process_mr_vif():
+ if raw:
+ # Make the interface name the key for each row.
+ table = {}
+ for v in data:
+ table[v.pop('Interface')] = v
+ return json.loads(json.dumps(table))
+ # Make byte values human-readable for the table.
+ arr = []
+ for x in data:
+ arr.append({k: bytes_to_human(v) if k.startswith('Bytes') \
+ else v for k, v in x.items()})
+ return tabulate.tabulate(arr, headers='keys')
+
+
+if not _is_configured():
+ print_error('IGMP proxy is not configured.')
+ sys.exit(0)
+if not _is_running():
+ print_error('IGMP proxy is not running.')
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print_error(e)
+ sys.exit(1)
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 7ec35d7bd..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
@@ -14,13 +14,16 @@
# 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 typing
from collections import OrderedDict
from hurry import filesize
from re import split as re_split
from tabulate import tabulate
+from subprocess import TimeoutExpired
from vyos.util import call
from vyos.util import convert_data
@@ -43,7 +46,10 @@ def _alphanum_key(key):
def _get_vici_sas():
from vici import Session as vici_session
- session = vici_session()
+ try:
+ session = vici_session()
+ except Exception:
+ raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized")
sas = list(session.list_sas())
return sas
@@ -132,41 +138,307 @@ def _get_formatted_output_sas(sas):
return output
-def get_peer_connections(peer, tunnel, return_all = False):
+# Connections block
+def _get_vici_connections():
+ from vici import Session as vici_session
+
+ try:
+ session = vici_session()
+ except Exception:
+ raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized")
+ connections = list(session.list_conns())
+ return connections
+
+
+def _get_convert_data_connections():
+ get_connections = _get_vici_connections()
+ connections = convert_data(get_connections)
+ return connections
+
+
+def _get_parent_sa_proposal(connection_name: str, data: list) -> dict:
+ """Get parent SA proposals by connection name
+ if connections not in the 'down' state
+
+ Args:
+ connection_name (str): Connection name
+ data (list): List of current SAs from vici
+
+ Returns:
+ str: Parent SA connection proposal
+ AES_CBC/256/HMAC_SHA2_256_128/MODP_1024
+ """
+ if not data:
+ return {}
+ for sa in data:
+ # check if parent SA exist
+ if connection_name not in sa.keys():
+ continue
+ if 'encr-alg' in sa[connection_name]:
+ encr_alg = sa.get(connection_name, '').get('encr-alg')
+ cipher = encr_alg.split('_')[0]
+ mode = encr_alg.split('_')[1]
+ encr_keysize = sa.get(connection_name, '').get('encr-keysize')
+ integ_alg = sa.get(connection_name, '').get('integ-alg')
+ # prf_alg = sa.get(connection_name, '').get('prf-alg')
+ dh_group = sa.get(connection_name, '').get('dh-group')
+ proposal = {
+ 'cipher': cipher,
+ 'mode': mode,
+ 'key_size': encr_keysize,
+ 'hash': integ_alg,
+ 'dh': dh_group
+ }
+ return proposal
+ return {}
+
+
+def _get_parent_sa_state(connection_name: str, data: list) -> str:
+ """Get parent SA state by connection name
+
+ Args:
+ connection_name (str): Connection name
+ data (list): List of current SAs from vici
+
+ Returns:
+ Parent SA connection state
+ """
+ ike_state = 'down'
+ if not data:
+ return ike_state
+ for sa in data:
+ # check if parent SA exist
+ 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,
+ data: list) -> str:
+ """Get child SA state by connection and tunnel name
+
+ Args:
+ connection_name (str): Connection name
+ tunnel_name (str): Tunnel name
+ data (list): List of current SAs from vici
+
+ Returns:
+ str: `up` if child SA state is 'installed' otherwise `down`
+ """
+ child_sa = 'down'
+ if not data:
+ return child_sa
+ for sa in data:
+ # check if parent SA exist
+ if connection_name not in sa.keys():
+ 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 child_sa
+
+
+def _get_child_sa_info(connection_name: str, tunnel_name: str,
+ data: list) -> dict:
+ """Get child SA installed info by connection and tunnel name
+
+ Args:
+ connection_name (str): Connection name
+ tunnel_name (str): Tunnel name
+ data (list): List of current SAs from vici
+
+ Returns:
+ dict: Info of the child SA in the dictionary format
+ """
+ for sa in data:
+ # check if parent SA exist
+ if connection_name not in sa.keys():
+ 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
+ # {'OFFICE-B-tunnel-0-46': {'name': 'OFFICE-B-tunnel-0'}...}
+ # i.e get all data after 'OFFICE-B-tunnel-0-46'
+ child_sa_info = [
+ v for k, v in child_sas.items() if 'name' in v and
+ v['name'] == tunnel_name and v['state'] == 'INSTALLED'
+ ]
+ return child_sa_info[-1] if child_sa_info else {}
+
+
+def _get_child_sa_proposal(child_sa_data: dict) -> dict:
+ if child_sa_data and 'encr-alg' in child_sa_data:
+ encr_alg = child_sa_data.get('encr-alg')
+ cipher = encr_alg.split('_')[0]
+ mode = encr_alg.split('_')[1]
+ key_size = child_sa_data.get('encr-keysize')
+ integ_alg = child_sa_data.get('integ-alg')
+ dh_group = child_sa_data.get('dh-group')
+ proposal = {
+ 'cipher': cipher,
+ 'mode': mode,
+ 'key_size': key_size,
+ 'hash': integ_alg,
+ 'dh': dh_group
+ }
+ return proposal
+ return {}
+
+
+def _get_raw_data_connections(list_connections: list, list_sas: list) -> list:
+ """Get configured VPN IKE connections and IPsec states
+
+ Args:
+ list_connections (list): List of configured connections from vici
+ list_sas (list): List of current SAs from vici
+
+ Returns:
+ list: List and status of IKE/IPsec connections/tunnels
+ """
+ base_dict = []
+ for connections in list_connections:
+ base_list = {}
+ for connection, conn_conf in connections.items():
+ base_list['ike_connection_name'] = connection
+ base_list['ike_connection_state'] = _get_parent_sa_state(
+ connection, list_sas)
+ base_list['ike_remote_address'] = conn_conf['remote_addrs']
+ base_list['ike_proposal'] = _get_parent_sa_proposal(
+ connection, list_sas)
+ base_list['local_id'] = conn_conf.get('local-1', '').get('id')
+ base_list['remote_id'] = conn_conf.get('remote-1', '').get('id')
+ base_list['version'] = conn_conf.get('version', 'IKE')
+ base_list['children'] = []
+ children = conn_conf['children']
+ for tunnel, tun_options in children.items():
+ state = _get_child_sa_state(connection, tunnel, list_sas)
+ local_ts = tun_options.get('local-ts')
+ remote_ts = tun_options.get('remote-ts')
+ dpd_action = tun_options.get('dpd_action')
+ close_action = tun_options.get('close_action')
+ sa_info = _get_child_sa_info(connection, tunnel, list_sas)
+ esp_proposal = _get_child_sa_proposal(sa_info)
+ base_list['children'].append({
+ 'name': tunnel,
+ 'state': state,
+ 'local_ts': local_ts,
+ 'remote_ts': remote_ts,
+ 'dpd_action': dpd_action,
+ 'close_action': close_action,
+ 'sa': sa_info,
+ 'esp_proposal': esp_proposal
+ })
+ base_dict.append(base_list)
+ return base_dict
+
+
+def _get_raw_connections_summary(list_conn, list_sas):
+ import jmespath
+ data = _get_raw_data_connections(list_conn, list_sas)
+ match = '[*].children[]'
+ child = jmespath.search(match, data)
+ tunnels_down = len([k for k in child if k['state'] == 'down'])
+ tunnels_up = len([k for k in child if k['state'] == 'up'])
+ tun_dict = {
+ 'tunnels': child,
+ 'total': len(child),
+ 'down': tunnels_down,
+ 'up': tunnels_up
+ }
+ return tun_dict
+
+
+def _get_formatted_output_conections(data):
+ from tabulate import tabulate
+ data_entries = ''
+ connections = []
+ for entry in data:
+ tunnels = []
+ ike_name = entry['ike_connection_name']
+ ike_state = entry['ike_connection_state']
+ conn_type = entry.get('version', 'IKE')
+ remote_addrs = ','.join(entry['ike_remote_address'])
+ local_ts, remote_ts = '-', '-'
+ local_id = entry['local_id']
+ remote_id = entry['remote_id']
+ proposal = '-'
+ if entry.get('ike_proposal'):
+ proposal = (f'{entry["ike_proposal"]["cipher"]}_'
+ f'{entry["ike_proposal"]["mode"]}/'
+ f'{entry["ike_proposal"]["key_size"]}/'
+ f'{entry["ike_proposal"]["hash"]}/'
+ f'{entry["ike_proposal"]["dh"]}')
+ connections.append([
+ ike_name, ike_state, conn_type, remote_addrs, local_ts, remote_ts,
+ local_id, remote_id, proposal
+ ])
+ for tun in entry['children']:
+ tun_name = tun.get('name')
+ tun_state = tun.get('state')
+ conn_type = 'IPsec'
+ local_ts = '\n'.join(tun.get('local_ts'))
+ remote_ts = '\n'.join(tun.get('remote_ts'))
+ proposal = '-'
+ if tun.get('esp_proposal'):
+ proposal = (f'{tun["esp_proposal"]["cipher"]}_'
+ f'{tun["esp_proposal"]["mode"]}/'
+ f'{tun["esp_proposal"]["key_size"]}/'
+ f'{tun["esp_proposal"]["hash"]}/'
+ f'{tun["esp_proposal"]["dh"]}')
+ connections.append([
+ tun_name, tun_state, conn_type, remote_addrs, local_ts,
+ remote_ts, local_id, remote_id, proposal
+ ])
+ connection_headers = [
+ 'Connection', 'State', 'Type', 'Remote address', 'Local TS',
+ 'Remote TS', 'Local id', 'Remote id', 'Proposal'
+ ]
+ output = tabulate(connections, connection_headers, numalign='left')
+ return output
+
+
+# Connections block end
+
+
+def get_peer_connections(peer, tunnel):
search = rf'^[\s]*({peer}-(tunnel-[\d]+|vti)).*'
matches = []
+ if not os.path.exists(SWANCTL_CONF):
+ raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized")
+ suffix = None if tunnel is None else (f'tunnel-{tunnel}' if
+ tunnel.isnumeric() else tunnel)
with open(SWANCTL_CONF, 'r') as f:
for line in f.readlines():
result = re.match(search, line)
if result:
- suffix = f'tunnel-{tunnel}' if tunnel.isnumeric() else tunnel
- if return_all or (result[2] == suffix):
+ if tunnel is None:
matches.append(result[1])
+ else:
+ if result[2] == suffix:
+ matches.append(result[1])
return matches
-def reset_peer(peer: str, tunnel:str):
- if not peer:
- print('Invalid peer, aborting')
- return
-
- conns = get_peer_connections(peer, tunnel, return_all = (not tunnel or tunnel == 'all'))
+def reset_peer(peer: str, tunnel:typing.Optional[str]):
+ conns = get_peer_connections(peer, tunnel)
if not conns:
- print('Tunnel(s) not found, aborting')
- return
+ raise vyos.opmode.IncorrectValue('Peer or tunnel(s) not found, aborting')
- result = True
for conn in conns:
try:
call(f'sudo /usr/sbin/ipsec down {conn}{{*}}', timeout = 10)
call(f'sudo /usr/sbin/ipsec up {conn}', timeout = 10)
except TimeoutExpired as e:
- print(f'Timed out while resetting {conn}')
- result = False
-
+ raise vyos.opmode.InternalError(f'Timed out while resetting {conn}')
- print('Peer reset result: ' + ('success' if result else 'failed'))
+ print('Peer reset result: success')
def show_sa(raw: bool):
@@ -176,6 +448,23 @@ def show_sa(raw: bool):
return _get_formatted_output_sas(sa_data)
+def show_connections(raw: bool):
+ list_conns = _get_convert_data_connections()
+ list_sas = _get_raw_data_sas()
+ if raw:
+ return _get_raw_data_connections(list_conns, list_sas)
+
+ connections = _get_raw_data_connections(list_conns, list_sas)
+ return _get_formatted_output_conections(connections)
+
+
+def show_connections_summary(raw: bool):
+ list_conns = _get_convert_data_connections()
+ list_sas = _get_raw_data_sas()
+ if raw:
+ return _get_raw_connections_summary(list_conns, list_sas)
+
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
diff --git a/src/op_mode/lldp.py b/src/op_mode/lldp.py
new file mode 100755
index 000000000..1a1b94783
--- /dev/null
+++ b/src/op_mode/lldp.py
@@ -0,0 +1,149 @@
+#!/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 = []
+ tmp = dict_search('lldp.interface', raw_data)
+ if not tmp:
+ return None
+ # One can not always ensure that "interface" is of type list, add safeguard.
+ # E.G. Juniper Networks, Inc. ex2300-c-12t only has a dict, not a list of dicts
+ if isinstance(tmp, dict):
+ tmp = [tmp]
+ for neighbor in tmp:
+ 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)
+ # One can not always ensure that "capability" is of type list, add
+ # safeguard. E.G. Unify US-24-250W only has a dict, not a list of dicts
+ if isinstance(capabilities, dict):
+ capabilities = [capabilities]
+ 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/log.py b/src/op_mode/log.py
new file mode 100755
index 000000000..b0abd6191
--- /dev/null
+++ b/src/op_mode/log.py
@@ -0,0 +1,94 @@
+#!/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 json
+import re
+import sys
+import typing
+
+from jinja2 import Template
+
+from vyos.util import rc_cmd
+
+import vyos.opmode
+
+journalctl_command_template = Template("""
+--no-hostname
+--quiet
+
+{% if boot %}
+ --boot
+{% endif %}
+
+{% if count %}
+ --lines={{ count }}
+{% endif %}
+
+{% if reverse %}
+ --reverse
+{% endif %}
+
+{% if since %}
+ --since={{ since }}
+{% endif %}
+
+{% if unit %}
+ --unit={{ unit }}
+{% endif %}
+
+{% if utc %}
+ --utc
+{% endif %}
+
+{% if raw %}
+{# By default show 100 only lines for raw option if count does not set #}
+{# Protection from parsing the full log by default #}
+{% if not boot %}
+ --lines={{ '' ~ count if count else '100' }}
+{% endif %}
+ --no-pager
+ --output=json
+{% endif %}
+""")
+
+
+def show(raw: bool,
+ boot: typing.Optional[bool],
+ count: typing.Optional[int],
+ facility: typing.Optional[str],
+ reverse: typing.Optional[bool],
+ utc: typing.Optional[bool],
+ unit: typing.Optional[str]):
+ kwargs = dict(locals())
+
+ journalctl_options = journalctl_command_template.render(kwargs)
+ journalctl_options = re.sub(r'\s+', ' ', journalctl_options)
+ rc, output = rc_cmd(f'journalctl {journalctl_options}')
+ if raw:
+ # Each 'journalctl --output json' line is a separate JSON object
+ # So we should return list of dict
+ return [json.loads(line) for line in output.split('\n')]
+ return output
+
+
+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/memory.py b/src/op_mode/memory.py
index 178544be4..7666de646 100755
--- a/src/op_mode/memory.py
+++ b/src/op_mode/memory.py
@@ -20,7 +20,7 @@ import sys
import vyos.opmode
-def _get_system_memory():
+def _get_raw_data():
from re import search as re_search
def find_value(keyword, mem_data):
@@ -38,7 +38,7 @@ def _get_system_memory():
used = total - available
- res = {
+ mem_data = {
"total": total,
"free": available,
"used": used,
@@ -46,24 +46,21 @@ def _get_system_memory():
"cached": cached
}
- return res
-
-def _get_system_memory_human():
- from vyos.util import bytes_to_human
-
- mem = _get_system_memory()
-
- for key in mem:
+ for key in mem_data:
# The Linux kernel exposes memory values in kilobytes,
# so we need to normalize them
- mem[key] = bytes_to_human(mem[key], initial_exponent=10)
+ mem_data[key] = mem_data[key] * 1024
- return mem
-
-def _get_raw_data():
- return _get_system_memory_human()
+ return mem_data
def _get_formatted_output(mem):
+ from vyos.util import bytes_to_human
+
+ # For human-readable outputs, we convert bytes to more convenient units
+ # (100M, 1.3G...)
+ for key in mem:
+ mem[key] = bytes_to_human(mem[key])
+
out = "Total: {}\n".format(mem["total"])
out += "Free: {}\n".format(mem["free"])
out += "Used: {}".format(mem["used"])
diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py
index 845dbbb2c..cf06de0e9 100755
--- a/src/op_mode/nat.py
+++ b/src/op_mode/nat.py
@@ -18,17 +18,21 @@ import jmespath
import json
import sys
import xmltodict
+import typing
-from sys import exit
from tabulate import tabulate
+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
"""
@@ -36,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):
@@ -60,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):
@@ -76,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':
{
@@ -225,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'
@@ -263,20 +270,34 @@ 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")
return output
+def _verify(func):
+ """Decorator checks if NAT config exists"""
+ from functools import wraps
+
+ @wraps(func)
+ def _wrapper(*args, **kwargs):
+ config = ConfigTreeQuery()
+ if not config.exists(base):
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ return func(*args, **kwargs)
+ return _wrapper
+
+
+@_verify
def show_rules(raw: bool, direction: str, family: str):
nat_rules = _get_raw_data_rules(direction, family)
if raw:
@@ -285,6 +306,7 @@ def show_rules(raw: bool, direction: str, family: str):
return _get_formatted_output_rules(nat_rules, direction, family)
+@_verify
def show_statistics(raw: bool, direction: str, family: str):
nat_statistics = _get_raw_data_rules(direction, family)
if raw:
@@ -293,13 +315,21 @@ def show_statistics(raw: bool, direction: str, family: str):
return _get_formatted_output_statistics(nat_statistics, direction)
-def show_translations(raw: bool, direction: str, family: str):
+@_verify
+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/openconnect.py b/src/op_mode/openconnect.py
index 00992c66a..b21890728 100755
--- a/src/op_mode/openconnect.py
+++ b/src/op_mode/openconnect.py
@@ -31,14 +31,7 @@ occtl_socket = '/run/ocserv/occtl.socket'
def _get_raw_data_sessions():
rc, out = rc_cmd(f'sudo {occtl} --json --socket-file {occtl_socket} show users')
if rc != 0:
- output = {'openconnect':
- {
- 'configured': False,
- 'return_code': rc,
- 'reason': out
- }
- }
- return output
+ raise vyos.opmode.DataUnavailable(out)
sessions = json.loads(out)
return sessions
@@ -61,9 +54,8 @@ def _get_formatted_sessions(data):
def show_sessions(raw: bool):
config = ConfigTreeQuery()
- if not config.exists('vpn openconnect') and not raw:
- print('Openconnect is not configured')
- exit(0)
+ if not config.exists('vpn openconnect'):
+ raise vyos.opmode.UnconfiguredSubsystem('Openconnect is not configured')
openconnect_data = _get_raw_data_sessions()
if raw:
diff --git a/src/op_mode/openvpn.py b/src/op_mode/openvpn.py
new file mode 100755
index 000000000..3797a7153
--- /dev/null
+++ b/src/op_mode/openvpn.py
@@ -0,0 +1,220 @@
+#!/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 sys
+from tabulate import tabulate
+
+import vyos.opmode
+from vyos.util import bytes_to_human
+from vyos.util import commit_in_progress
+from vyos.util import call
+from vyos.config import Config
+
+def _get_tunnel_address(peer_host, peer_port, status_file):
+ peer = peer_host + ':' + peer_port
+ lst = []
+
+ with open(status_file, 'r') as f:
+ lines = f.readlines()
+ for line in lines:
+ if peer in line:
+ lst.append(line)
+
+ # filter out subnet entries if iroute:
+ # in the case that one sets, say:
+ # [ ..., 'vtun10', 'server', 'client', 'client1', 'subnet','10.10.2.0/25']
+ # the status file will have an entry:
+ # 10.10.2.0/25,client1,...
+ lst = [l for l in lst[1:] if '/' not in l.split(',')[0]]
+
+ tunnel_ip = lst[0].split(',')[0]
+
+ return tunnel_ip
+
+def _get_interface_status(mode: str, interface: str) -> dict:
+ status_file = f'/run/openvpn/{interface}.status'
+
+ data = {
+ 'mode': mode,
+ 'intf': interface,
+ 'local_host': '',
+ 'local_port': '',
+ 'date': '',
+ 'clients': [],
+ }
+
+ if not os.path.exists(status_file):
+ raise vyos.opmode.DataUnavailable('No information for interface {interface}')
+
+ with open(status_file, 'r') as f:
+ lines = f.readlines()
+ for line_no, line in enumerate(lines):
+ # remove trailing newline character first
+ line = line.rstrip('\n')
+
+ # check first line header
+ if line_no == 0:
+ if mode == 'server':
+ if not line == 'OpenVPN CLIENT LIST':
+ raise vyos.opmode.InternalError('Expected "OpenVPN CLIENT LIST"')
+ else:
+ if not line == 'OpenVPN STATISTICS':
+ raise vyos.opmode.InternalError('Expected "OpenVPN STATISTICS"')
+
+ continue
+
+ # second line informs us when the status file has been last updated
+ if line_no == 1:
+ data['date'] = line.lstrip('Updated,').rstrip('\n')
+ continue
+
+ if mode == 'server':
+ # for line_no > 1, lines appear as follows:
+ #
+ # Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since
+ # client1,172.18.202.10:55904,2880587,2882653,Fri Aug 23 16:25:48 2019
+ # client3,172.18.204.10:41328,2850832,2869729,Fri Aug 23 16:25:43 2019
+ # client2,172.18.203.10:48987,2856153,2871022,Fri Aug 23 16:25:45 2019
+ # ...
+ # ROUTING TABLE
+ # ...
+ if line_no >= 3:
+ # indicator that there are no more clients
+ if line == 'ROUTING TABLE':
+ break
+ # otherwise, get client data
+ remote = (line.split(',')[1]).rsplit(':', maxsplit=1)
+
+ client = {
+ 'name': line.split(',')[0],
+ 'remote_host': remote[0],
+ 'remote_port': remote[1],
+ 'tunnel': 'N/A',
+ 'rx_bytes': bytes_to_human(int(line.split(',')[2]),
+ precision=1),
+ 'tx_bytes': bytes_to_human(int(line.split(',')[3]),
+ precision=1),
+ 'online_since': line.split(',')[4]
+ }
+ client['tunnel'] = _get_tunnel_address(client['remote_host'],
+ client['remote_port'],
+ status_file)
+ data['clients'].append(client)
+ continue
+ else: # mode == 'client' or mode == 'site-to-site'
+ if line_no == 2:
+ client = {
+ 'name': 'N/A',
+ 'remote_host': 'N/A',
+ 'remote_port': 'N/A',
+ 'tunnel': 'N/A',
+ 'rx_bytes': bytes_to_human(int(line.split(',')[1]),
+ precision=1),
+ 'tx_bytes': '',
+ 'online_since': 'N/A'
+ }
+ continue
+
+ if line_no == 3:
+ client['tx_bytes'] = bytes_to_human(int(line.split(',')[1]),
+ precision=1)
+ data['clients'].append(client)
+ break
+
+ return data
+
+def _get_raw_data(mode: str) -> dict:
+ data = {}
+ conf = Config()
+ conf_dict = conf.get_config_dict(['interfaces', 'openvpn'],
+ get_first_key=True)
+ if not conf_dict:
+ return data
+
+ interfaces = [x for x in list(conf_dict) if conf_dict[x]['mode'] == mode]
+ for intf in interfaces:
+ data[intf] = _get_interface_status(mode, intf)
+ d = data[intf]
+ d['local_host'] = conf_dict[intf].get('local-host', '')
+ d['local_port'] = conf_dict[intf].get('local-port', '')
+ if mode in ['client', 'site-to-site']:
+ for client in d['clients']:
+ if 'shared-secret-key-file' in list(conf_dict[intf]):
+ client['name'] = 'None (PSK)'
+ client['remote_host'] = conf_dict[intf].get('remote-host', [''])[0]
+ client['remote_port'] = conf_dict[intf].get('remote-port', '1194')
+
+ return data
+
+def _format_openvpn(data: dict) -> str:
+ if not data:
+ out = 'No OpenVPN interfaces configured'
+ return out
+
+ headers = ['Client CN', 'Remote Host', 'Tunnel IP', 'Local Host',
+ 'TX bytes', 'RX bytes', 'Connected Since']
+
+ out = ''
+ data_out = []
+ for intf in list(data):
+ l_host = data[intf]['local_host']
+ l_port = data[intf]['local_port']
+ for client in list(data[intf]['clients']):
+ r_host = client['remote_host']
+ r_port = client['remote_port']
+
+ out += f'\nOpenVPN status on {intf}\n\n'
+ name = client['name']
+ remote = r_host + ':' + r_port if r_host and r_port else 'N/A'
+ tunnel = client['tunnel']
+ local = l_host + ':' + l_port if l_host and l_port else 'N/A'
+ tx_bytes = client['tx_bytes']
+ rx_bytes = client['rx_bytes']
+ online_since = client['online_since']
+ data_out.append([name, remote, tunnel, local, tx_bytes,
+ rx_bytes, online_since])
+
+ out += tabulate(data_out, headers)
+
+ return out
+
+def show(raw: bool, mode: str) -> str:
+ openvpn_data = _get_raw_data(mode)
+
+ if raw:
+ return openvpn_data
+
+ return _format_openvpn(openvpn_data)
+
+def reset(interface: str):
+ if os.path.isfile(f'/run/openvpn/{interface}.conf'):
+ if commit_in_progress():
+ raise vyos.opmode.CommitInProgress('Retry OpenVPN reset: commit in progress.')
+ call(f'systemctl restart openvpn@{interface}.service')
+ else:
+ raise vyos.opmode.IncorrectValue(f'OpenVPN interface "{interface}" does not exist!')
+
+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/ping.py b/src/op_mode/ping.py
index 60bbc0c78..610e63cb3 100755
--- a/src/op_mode/ping.py
+++ b/src/op_mode/ping.py
@@ -18,6 +18,25 @@ import os
import sys
import socket
import ipaddress
+from vyos.util import get_all_vrfs
+from vyos.ifconfig import Section
+
+
+def interface_list() -> list:
+ """
+ Get list of interfaces in system
+ :rtype: list
+ """
+ return Section.interfaces()
+
+
+def vrf_list() -> list:
+ """
+ Get list of VRFs in system
+ :rtype: list
+ """
+ return list(get_all_vrfs().keys())
+
options = {
'audible': {
@@ -63,6 +82,7 @@ options = {
'interface': {
'ping': '{command} -I {value}',
'type': '<interface>',
+ 'helpfunction': interface_list,
'help': 'Source interface'
},
'interval': {
@@ -128,6 +148,7 @@ options = {
'ping': 'sudo ip vrf exec {value} {command}',
'type': '<vrf>',
'help': 'Use specified VRF table',
+ 'helpfunction': vrf_list,
'dflt': 'default',
},
'verbose': {
@@ -142,20 +163,33 @@ ping = {
}
-class List (list):
- def first (self):
+class List(list):
+ def first(self):
return self.pop(0) if self else ''
def last(self):
return self.pop() if self else ''
- def prepend(self,value):
- self.insert(0,value)
+ def prepend(self, value):
+ self.insert(0, value)
+
+
+def completion_failure(option: str) -> None:
+ """
+ Shows failure message after TAB when option is wrong
+ :param option: failure option
+ :type str:
+ """
+ sys.stderr.write('\n\n Invalid option: {}\n\n'.format(option))
+ sys.stdout.write('<nocomps>')
+ sys.exit(1)
def expension_failure(option, completions):
reason = 'Ambiguous' if completions else 'Invalid'
- sys.stderr.write('\n\n {} command: {} [{}]\n\n'.format(reason,' '.join(sys.argv), option))
+ sys.stderr.write(
+ '\n\n {} command: {} [{}]\n\n'.format(reason, ' '.join(sys.argv),
+ option))
if completions:
sys.stderr.write(' Possible completions:\n ')
sys.stderr.write('\n '.join(completions))
@@ -196,28 +230,44 @@ if __name__ == '__main__':
if host == '--get-options':
args.first() # pop ping
args.first() # pop IP
+ usedoptionslist = []
while args:
- option = args.first()
-
- matched = complete(option)
+ option = args.first() # pop option
+ matched = complete(option) # get option parameters
+ usedoptionslist.append(option) # list of used options
+ # Select options
if not args:
+ # remove from Possible completions used options
+ for o in usedoptionslist:
+ if o in matched:
+ matched.remove(o)
sys.stdout.write(' '.join(matched))
sys.exit(0)
- if len(matched) > 1 :
+ if len(matched) > 1:
sys.stdout.write(' '.join(matched))
sys.exit(0)
+ # If option doesn't have value
+ if matched:
+ if options[matched[0]]['type'] == 'noarg':
+ continue
+ else:
+ # Unexpected option
+ completion_failure(option)
- if options[matched[0]]['type'] == 'noarg':
- continue
-
- value = args.first()
+ value = args.first() # pop option's value
if not args:
matched = complete(option)
- sys.stdout.write(options[matched[0]]['type'])
+ helplines = options[matched[0]]['type']
+ # Run helpfunction to get list of possible values
+ if 'helpfunction' in options[matched[0]]:
+ result = options[matched[0]]['helpfunction']()
+ if result:
+ helplines = '\n' + ' '.join(result)
+ sys.stdout.write(helplines)
sys.exit(0)
- for name,option in options.items():
+ for name, option in options.items():
if 'dflt' in option and name not in args:
args.append(name)
args.append(option['dflt'])
@@ -234,8 +284,7 @@ if __name__ == '__main__':
except ValueError:
sys.exit(f'ping: Unknown host: {host}')
- command = convert(ping[version],args)
+ command = convert(ping[version], args)
# print(f'{command} {host}')
os.system(f'{command} {host}')
-
diff --git a/src/op_mode/policy_route.py b/src/op_mode/policy_route.py
index 5be40082f..5953786f3 100755
--- a/src/op_mode/policy_route.py
+++ b/src/op_mode/policy_route.py
@@ -22,53 +22,13 @@ from vyos.config import Config
from vyos.util import cmd
from vyos.util import dict_search_args
-def get_policy_interfaces(conf, policy, name=None, ipv6=False):
- interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True)
-
- routes = ['route', 'route6']
-
- def parse_if(ifname, if_conf):
- if 'policy' in if_conf:
- for route in routes:
- if route in if_conf['policy']:
- route_name = if_conf['policy'][route]
- name_str = f'({ifname},{route})'
-
- if not name:
- policy[route][route_name]['interface'].append(name_str)
- elif not ipv6 and name == route_name:
- policy['interface'].append(name_str)
-
- for iftype in ['vif', 'vif_s', 'vif_c']:
- if iftype in if_conf:
- for vifname, vif_conf in if_conf[iftype].items():
- parse_if(f'{ifname}.{vifname}', vif_conf)
-
- for iftype, iftype_conf in interfaces.items():
- for ifname, if_conf in iftype_conf.items():
- parse_if(ifname, if_conf)
-
-def get_config_policy(conf, name=None, ipv6=False, interfaces=True):
+def get_config_policy(conf, name=None, ipv6=False):
config_path = ['policy']
if name:
config_path += ['route6' if ipv6 else 'route', name]
policy = conf.get_config_dict(config_path, key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
- if policy and interfaces:
- if name:
- policy['interface'] = []
- else:
- if 'route' in policy:
- for route_name, route_conf in policy['route'].items():
- route_conf['interface'] = []
-
- if 'route6' in policy:
- for route_name, route_conf in policy['route6'].items():
- route_conf['interface'] = []
-
- get_policy_interfaces(conf, policy, name, ipv6)
return policy
diff --git a/src/op_mode/route.py b/src/op_mode/route.py
index e1eee5bbf..7f0f9cbac 100755
--- a/src/op_mode/route.py
+++ b/src/op_mode/route.py
@@ -54,6 +54,45 @@ frr_command_template = Template("""
{% endif %}
""")
+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 {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 {family_cmd} route {vrf_cmd} summary {table_cmd}'")
+ return output
+
def show(raw: bool,
family: str,
net: typing.Optional[str],
@@ -83,7 +122,12 @@ def show(raw: bool,
if raw:
from json import loads
- return loads(output)
+ d = loads(output)
+ collect = []
+ for k,_ in d.items():
+ for l in d[k]:
+ collect.append(l)
+ return collect
else:
return output
diff --git a/src/op_mode/show_acceleration.py b/src/op_mode/show_acceleration.py
index 752db3deb..48c31d4d9 100755
--- a/src/op_mode/show_acceleration.py
+++ b/src/op_mode/show_acceleration.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019 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
@@ -13,7 +13,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 sys
import os
@@ -24,12 +23,11 @@ from vyos.config import Config
from vyos.util import popen
from vyos.util import call
-
def detect_qat_dev():
- output, err = popen('sudo lspci -nn', decode='utf-8')
+ output, err = popen('lspci -nn', decode='utf-8')
if not err:
data = re.findall('(8086:19e2)|(8086:37c8)|(8086:0435)|(8086:6f54)', output)
- #If QAT devices found
+ # QAT devices found
if data:
return
print("\t No QAT device found")
@@ -44,11 +42,11 @@ def show_qat_status():
sys.exit(1)
# Show QAT service
- call('sudo /etc/init.d/qat_service status')
+ call('/etc/init.d/qat_service status')
# Return QAT devices
def get_qat_devices():
- data_st, err = popen('sudo /etc/init.d/qat_service status', decode='utf-8')
+ data_st, err = popen('/etc/init.d/qat_service status', decode='utf-8')
if not err:
elm_lst = re.findall('qat_dev\d', data_st)
print('\n'.join(elm_lst))
@@ -57,7 +55,7 @@ def get_qat_devices():
def get_qat_proc_path(qat_dev):
q_type = ""
q_bsf = ""
- output, err = popen('sudo /etc/init.d/qat_service status', decode='utf-8')
+ output, err = popen('/etc/init.d/qat_service status', decode='utf-8')
if not err:
# Parse QAT service output
data_st = output.split("\n")
@@ -95,20 +93,20 @@ args = parser.parse_args()
if args.hw:
detect_qat_dev()
# Show availible Intel QAT devices
- call('sudo lspci -nn | egrep -e \'8086:37c8|8086:19e2|8086:0435|8086:6f54\'')
+ call('lspci -nn | egrep -e \'8086:37c8|8086:19e2|8086:0435|8086:6f54\'')
elif args.flow and args.dev:
check_qat_if_conf()
- call('sudo cat '+get_qat_proc_path(args.dev)+"fw_counters")
+ call('cat '+get_qat_proc_path(args.dev)+"fw_counters")
elif args.interrupts:
check_qat_if_conf()
# Delete _dev from args.dev
- call('sudo cat /proc/interrupts | grep qat')
+ call('cat /proc/interrupts | grep qat')
elif args.status:
check_qat_if_conf()
show_qat_status()
elif args.conf and args.dev:
check_qat_if_conf()
- call('sudo cat '+get_qat_proc_path(args.dev)+"dev_cfg")
+ call('cat '+get_qat_proc_path(args.dev)+"dev_cfg")
elif args.dev_list:
get_qat_devices()
else:
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_igmpproxy.py b/src/op_mode/show_igmpproxy.py
deleted file mode 100755
index 4714e494b..000000000
--- a/src/op_mode/show_igmpproxy.py
+++ /dev/null
@@ -1,241 +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/>.
-
-# File: show_igmpproxy.py
-# Purpose:
-# Display istatistics from IPv4 IGMP proxy.
-# Used by the "run show ip multicast" command tree.
-
-import sys
-import jinja2
-import argparse
-import ipaddress
-import socket
-
-import vyos.config
-
-# Output Template for "show ip multicast interface" command
-#
-# Example:
-# Interface BytesIn PktsIn BytesOut PktsOut Local
-# eth0 0.0b 0 0.0b 0 xxx.xxx.xxx.65
-# eth1 0.0b 0 0.0b 0 xxx.xxx.xx.201
-# eth0.3 0.0b 0 0.0b 0 xxx.xxx.x.7
-# tun1 0.0b 0 0.0b 0 xxx.xxx.xxx.2
-vif_out_tmpl = """
-{% for r in data %}
-{{ "%-10s"|format(r.interface) }} {{ "%-12s"|format(r.bytes_in) }} {{ "%-12s"|format(r.pkts_in) }} {{ "%-12s"|format(r.bytes_out) }} {{ "%-12s"|format(r.pkts_out) }} {{ "%-15s"|format(r.loc) }}
-{% endfor %}
-"""
-
-# Output Template for "show ip multicast mfc" command
-#
-# Example:
-# Group Origin In Out Pkts Bytes Wrong
-# xxx.xxx.xxx.250 xxx.xx.xxx.75 --
-# xxx.xxx.xx.124 xx.xxx.xxx.26 --
-mfc_out_tmpl = """
-{% for r in data %}
-{{ "%-15s"|format(r.group) }} {{ "%-15s"|format(r.origin) }} {{ "%-12s"|format(r.pkts) }} {{ "%-12s"|format(r.bytes) }} {{ "%-12s"|format(r.wrong) }} {{ "%-10s"|format(r.iif) }} {{ "%-20s"|format(r.oifs|join(', ')) }}
-{% endfor %}
-"""
-
-parser = argparse.ArgumentParser()
-parser.add_argument("--interface", action="store_true", help="Interface Statistics")
-parser.add_argument("--mfc", action="store_true", help="Multicast Forwarding Cache")
-
-def byte_string(size):
- # convert size to integer
- size = int(size)
-
- # One Terrabyte
- s_TB = 1024 * 1024 * 1024 * 1024
- # One Gigabyte
- s_GB = 1024 * 1024 * 1024
- # One Megabyte
- s_MB = 1024 * 1024
- # One Kilobyte
- s_KB = 1024
- # One Byte
- s_B = 1
-
- if size > s_TB:
- return str(round((size/s_TB), 2)) + 'TB'
- elif size > s_GB:
- return str(round((size/s_GB), 2)) + 'GB'
- elif size > s_MB:
- return str(round((size/s_MB), 2)) + 'MB'
- elif size > s_KB:
- return str(round((size/s_KB), 2)) + 'KB'
- else:
- return str(round((size/s_B), 2)) + 'b'
-
- return None
-
-def kernel2ip(addr):
- """
- Convert any given addr from Linux Kernel to a proper, IPv4 address
- using the correct host byte order.
- """
-
- # Convert from hex 'FE000A0A' to decimal '4261415434'
- addr = int(addr, 16)
- # Kernel ABI _always_ uses network byteorder
- addr = socket.ntohl(addr)
-
- return ipaddress.IPv4Address( addr )
-
-def do_mr_vif():
- """
- Read contents of file /proc/net/ip_mr_vif and print a more human
- friendly version to the command line. IPv4 addresses present as
- 32bit integers in hex format are converted to IPv4 notation, too.
- """
-
- with open('/proc/net/ip_mr_vif', 'r') as f:
- lines = len(f.readlines())
- if lines < 2:
- return None
-
- result = {
- 'data': []
- }
-
- # Build up table format string
- table_format = {
- 'interface': 'Interface',
- 'pkts_in' : 'PktsIn',
- 'pkts_out' : 'PktsOut',
- 'bytes_in' : 'BytesIn',
- 'bytes_out': 'BytesOut',
- 'loc' : 'Local'
- }
- result['data'].append(table_format)
-
- # read and parse information from /proc filesystema
- with open('/proc/net/ip_mr_vif', 'r') as f:
- header_line = next(f)
- for line in f:
- data = {
- 'interface': line.split()[1],
- 'pkts_in' : line.split()[3],
- 'pkts_out' : line.split()[5],
-
- # convert raw byte number to something more human readable
- # Note: could be replaced by Python3 hurry.filesize module
- 'bytes_in' : byte_string( line.split()[2] ),
- 'bytes_out': byte_string( line.split()[4] ),
-
- # convert IP address from hex 'FE000A0A' to decimal '4261415434'
- 'loc' : kernel2ip( line.split()[7] ),
- }
- result['data'].append(data)
-
- return result
-
-def do_mr_mfc():
- """
- Read contents of file /proc/net/ip_mr_cache and print a more human
- friendly version to the command line. IPv4 addresses present as
- 32bit integers in hex format are converted to IPv4 notation, too.
- """
-
- with open('/proc/net/ip_mr_cache', 'r') as f:
- lines = len(f.readlines())
- if lines < 2:
- return None
-
- # We need this to convert from interface index to a real interface name
- # Thus we also skip the format identifier on list index 0
- vif = do_mr_vif()['data'][1:]
-
- result = {
- 'data': []
- }
-
- # Build up table format string
- table_format = {
- 'group' : 'Group',
- 'origin': 'Origin',
- 'iif' : 'In',
- 'oifs' : ['Out'],
- 'pkts' : 'Pkts',
- 'bytes' : 'Bytes',
- 'wrong' : 'Wrong'
- }
- result['data'].append(table_format)
-
- # read and parse information from /proc filesystem
- with open('/proc/net/ip_mr_cache', 'r') as f:
- header_line = next(f)
- for line in f:
- data = {
- # convert IP address from hex 'FE000A0A' to decimal '4261415434'
- 'group' : kernel2ip( line.split()[0] ),
- 'origin': kernel2ip( line.split()[1] ),
-
- 'iif' : '--',
- 'pkts' : '',
- 'bytes' : '',
- 'wrong' : '',
- 'oifs' : []
- }
-
- iif = int( line.split()[2] )
- if not ((iif == -1) or (iif == 65535)):
- data['pkts'] = line.split()[3]
- data['bytes'] = byte_string( line.split()[4] )
- data['wrong'] = line.split()[5]
-
- # convert index to real interface name
- data['iif'] = vif[iif]['interface']
-
- # convert each output interface index to a real interface name
- for oif in line.split()[6:]:
- idx = int( oif.split(':')[0] )
- data['oifs'].append( vif[idx]['interface'] )
-
- result['data'].append(data)
-
- return result
-
-if __name__ == '__main__':
- args = parser.parse_args()
-
- # Do nothing if service is not configured
- c = vyos.config.Config()
- if not c.exists_effective('protocols igmp-proxy'):
- print("IGMP proxy is not configured")
- sys.exit(0)
-
- if args.interface:
- data = do_mr_vif()
- if data:
- tmpl = jinja2.Template(vif_out_tmpl)
- print(tmpl.render(data))
-
- sys.exit(0)
- elif args.mfc:
- data = do_mr_mfc()
- if data:
- tmpl = jinja2.Template(mfc_out_tmpl)
- print(tmpl.render(data))
-
- sys.exit(0)
- else:
- parser.print_help()
- sys.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/show_openvpn.py b/src/op_mode/show_openvpn.py
index 9a5adcffb..e29e594a5 100755
--- a/src/op_mode/show_openvpn.py
+++ b/src/op_mode/show_openvpn.py
@@ -59,7 +59,11 @@ def get_vpn_tunnel_address(peer, interface):
for line in lines:
if peer in line:
lst.append(line)
- tunnel_ip = lst[1].split(',')[0]
+
+ # filter out subnet entries
+ lst = [l for l in lst[1:] if '/' not in l.split(',')[0]]
+
+ tunnel_ip = lst[0].split(',')[0]
return tunnel_ip
diff --git a/src/op_mode/show_raid.sh b/src/op_mode/show_raid.sh
index ba4174692..ab5d4d50f 100755
--- a/src/op_mode/show_raid.sh
+++ b/src/op_mode/show_raid.sh
@@ -1,5 +1,13 @@
#!/bin/bash
+if [ "$EUID" -ne 0 ]; then
+ # This should work without sudo because we have read
+ # access to the dev, but for some reason mdadm must be
+ # run as root in order to succeed.
+ echo "Please run as root"
+ exit 1
+fi
+
raid_set_name=$1
raid_sets=`cat /proc/partitions | grep md | awk '{ print $4 }'`
valid_set=`echo $raid_sets | grep $raid_set_name`
@@ -10,7 +18,7 @@ else
# This should work without sudo because we have read
# access to the dev, but for some reason mdadm must be
# run as root in order to succeed.
- sudo /sbin/mdadm --detail /dev/${raid_set_name}
+ mdadm --detail /dev/${raid_set_name}
else
echo "Must be administrator or root to display RAID status"
fi
diff --git a/src/op_mode/storage.py b/src/op_mode/storage.py
index 75964c493..d16e271bd 100755
--- a/src/op_mode/storage.py
+++ b/src/op_mode/storage.py
@@ -20,6 +20,16 @@ import sys
import vyos.opmode
from vyos.util import cmd
+# FIY: As of coreutils from Debian Buster and Bullseye,
+# the outpt looks like this:
+#
+# $ df -h -t ext4 --output=source,size,used,avail,pcent
+# Filesystem Size Used Avail Use%
+# /dev/sda1 16G 7.6G 7.3G 51%
+#
+# Those field names are automatically normalized by vyos.opmode.run,
+# so we don't touch them here,
+# and only normalize values.
def _get_system_storage(only_persistent=False):
if not only_persistent:
@@ -32,11 +42,19 @@ def _get_system_storage(only_persistent=False):
return res
def _get_raw_data():
+ from re import sub as re_sub
+ from vyos.util import human_to_bytes
+
out = _get_system_storage(only_persistent=True)
lines = out.splitlines()
lists = [l.split() for l in lines]
res = {lists[0][i]: lists[1][i] for i in range(len(lists[0]))}
+ res["Size"] = human_to_bytes(res["Size"])
+ res["Used"] = human_to_bytes(res["Used"])
+ res["Avail"] = human_to_bytes(res["Avail"])
+ res["Use%"] = re_sub(r'%', '', res["Use%"])
+
return res
def _get_formatted_output():
diff --git a/src/op_mode/traceroute.py b/src/op_mode/traceroute.py
index 4299d6e5f..6c7030ea0 100755
--- a/src/op_mode/traceroute.py
+++ b/src/op_mode/traceroute.py
@@ -18,6 +18,25 @@ import os
import sys
import socket
import ipaddress
+from vyos.util import get_all_vrfs
+from vyos.ifconfig import Section
+
+
+def interface_list() -> list:
+ """
+ Get list of interfaces in system
+ :rtype: list
+ """
+ return Section.interfaces()
+
+
+def vrf_list() -> list:
+ """
+ Get list of VRFs in system
+ :rtype: list
+ """
+ return list(get_all_vrfs().keys())
+
options = {
'backward-hops': {
@@ -48,6 +67,7 @@ options = {
'interface': {
'traceroute': '{command} -i {value}',
'type': '<interface>',
+ 'helpfunction': interface_list,
'help': 'Source interface'
},
'lookup-as': {
@@ -99,6 +119,7 @@ options = {
'traceroute': 'sudo ip vrf exec {value} {command}',
'type': '<vrf>',
'help': 'Use specified VRF table',
+ 'helpfunction': vrf_list,
'dflt': 'default'}
}
@@ -108,20 +129,33 @@ traceroute = {
}
-class List (list):
- def first (self):
+class List(list):
+ def first(self):
return self.pop(0) if self else ''
def last(self):
return self.pop() if self else ''
- def prepend(self,value):
- self.insert(0,value)
+ def prepend(self, value):
+ self.insert(0, value)
+
+
+def completion_failure(option: str) -> None:
+ """
+ Shows failure message after TAB when option is wrong
+ :param option: failure option
+ :type str:
+ """
+ sys.stderr.write('\n\n Invalid option: {}\n\n'.format(option))
+ sys.stdout.write('<nocomps>')
+ sys.exit(1)
def expension_failure(option, completions):
reason = 'Ambiguous' if completions else 'Invalid'
- sys.stderr.write('\n\n {} command: {} [{}]\n\n'.format(reason,' '.join(sys.argv), option))
+ sys.stderr.write(
+ '\n\n {} command: {} [{}]\n\n'.format(reason, ' '.join(sys.argv),
+ option))
if completions:
sys.stderr.write(' Possible completions:\n ')
sys.stderr.write('\n '.join(completions))
@@ -160,30 +194,46 @@ if __name__ == '__main__':
sys.exit("traceroute: Missing host")
if host == '--get-options':
- args.first() # pop traceroute
+ args.first() # pop ping
args.first() # pop IP
+ usedoptionslist = []
while args:
- option = args.first()
-
- matched = complete(option)
+ option = args.first() # pop option
+ matched = complete(option) # get option parameters
+ usedoptionslist.append(option) # list of used options
+ # Select options
if not args:
+ # remove from Possible completions used options
+ for o in usedoptionslist:
+ if o in matched:
+ matched.remove(o)
sys.stdout.write(' '.join(matched))
sys.exit(0)
- if len(matched) > 1 :
+ if len(matched) > 1:
sys.stdout.write(' '.join(matched))
sys.exit(0)
+ # If option doesn't have value
+ if matched:
+ if options[matched[0]]['type'] == 'noarg':
+ continue
+ else:
+ # Unexpected option
+ completion_failure(option)
- if options[matched[0]]['type'] == 'noarg':
- continue
-
- value = args.first()
+ value = args.first() # pop option's value
if not args:
matched = complete(option)
- sys.stdout.write(options[matched[0]]['type'])
+ helplines = options[matched[0]]['type']
+ # Run helpfunction to get list of possible values
+ if 'helpfunction' in options[matched[0]]:
+ result = options[matched[0]]['helpfunction']()
+ if result:
+ helplines = '\n' + ' '.join(result)
+ sys.stdout.write(helplines)
sys.exit(0)
- for name,option in options.items():
+ for name, option in options.items():
if 'dflt' in option and name not in args:
args.append(name)
args.append(option['dflt'])
@@ -200,8 +250,7 @@ if __name__ == '__main__':
except ValueError:
sys.exit(f'traceroute: Unknown host: {host}')
- command = convert(traceroute[version],args)
+ command = convert(traceroute[version], args)
# print(f'{command} {host}')
os.system(f'{command} {host}')
-
diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py
index 68dc5bc45..2392cfe92 100755
--- a/src/op_mode/vpn_ipsec.py
+++ b/src/op_mode/vpn_ipsec.py
@@ -48,8 +48,8 @@ def reset_peer(peer, tunnel):
result = True
for conn in conns:
try:
- call(f'sudo /usr/sbin/ipsec down {conn}{{*}}', timeout = 10)
- call(f'sudo /usr/sbin/ipsec up {conn}', timeout = 10)
+ call(f'/usr/sbin/ipsec down {conn}{{*}}', timeout = 10)
+ call(f'/usr/sbin/ipsec up {conn}', timeout = 10)
except TimeoutExpired as e:
print(f'Timed out while resetting {conn}')
result = False
@@ -81,8 +81,8 @@ def reset_profile(profile, tunnel):
print('Profile not found, aborting')
return
- call(f'sudo /usr/sbin/ipsec down {conn}')
- result = call(f'sudo /usr/sbin/ipsec up {conn}')
+ call(f'/usr/sbin/ipsec down {conn}')
+ result = call(f'/usr/sbin/ipsec up {conn}')
print('Profile reset result: ' + ('success' if result == 0 else 'failed'))
@@ -90,17 +90,17 @@ def debug_peer(peer, tunnel):
peer = peer.replace(':', '-')
if not peer or peer == "all":
debug_commands = [
- "sudo ipsec statusall",
- "sudo swanctl -L",
- "sudo swanctl -l",
- "sudo swanctl -P",
- "sudo ip x sa show",
- "sudo ip x policy show",
- "sudo ip tunnel show",
- "sudo ip address",
- "sudo ip rule show",
- "sudo ip route | head -100",
- "sudo ip route show table 220"
+ "ipsec statusall",
+ "swanctl -L",
+ "swanctl -l",
+ "swanctl -P",
+ "ip x sa show",
+ "ip x policy show",
+ "ip tunnel show",
+ "ip address",
+ "ip rule show",
+ "ip route | head -100",
+ "ip route show table 220"
]
for debug_cmd in debug_commands:
print(f'\n### {debug_cmd} ###')
@@ -117,7 +117,7 @@ def debug_peer(peer, tunnel):
return
for conn in conns:
- call(f'sudo /usr/sbin/ipsec statusall | grep {conn}')
+ call(f'/usr/sbin/ipsec statusall | grep {conn}')
if __name__ == '__main__':
parser = argparse.ArgumentParser()
diff --git a/src/op_mode/vrf.py b/src/op_mode/vrf.py
index aeb50fe6e..a9a416761 100755
--- a/src/op_mode/vrf.py
+++ b/src/op_mode/vrf.py
@@ -31,14 +31,14 @@ def _get_raw_data(name=None):
If vrf name is set - get only this name data
If vrf name set and not found - return []
"""
- output = cmd('sudo ip --json --brief link show type vrf')
+ output = cmd('ip --json --brief link show type vrf')
data = json.loads(output)
if not data:
return []
if name:
is_vrf_exists = True if [vrf for vrf in data if vrf.get('ifname') == name] else False
if is_vrf_exists:
- output = cmd(f'sudo ip --json --brief link show dev {name}')
+ output = cmd(f'ip --json --brief link show dev {name}')
data = json.loads(output)
return data
return []
@@ -51,7 +51,7 @@ def _get_vrf_members(vrf: str) -> list:
:param vrf: str
:return: list
"""
- output = cmd(f'sudo ip --json --brief link show master {vrf}')
+ output = cmd(f'ip --json --brief link show master {vrf}')
answer = json.loads(output)
interfaces = []
for data in answer:
diff --git a/src/op_mode/webproxy_update_blacklist.sh b/src/op_mode/webproxy_update_blacklist.sh
index 43a4b79fc..4fb9a54c6 100755
--- a/src/op_mode/webproxy_update_blacklist.sh
+++ b/src/op_mode/webproxy_update_blacklist.sh
@@ -18,6 +18,23 @@ blacklist_url='ftp://ftp.univ-tlse1.fr/pub/reseau/cache/squidguard_contrib/black
data_dir="/opt/vyatta/etc/config/url-filtering"
archive="${data_dir}/squidguard/archive"
db_dir="${data_dir}/squidguard/db"
+conf_file="/etc/squidguard/squidGuard.conf"
+tmp_conf_file="/tmp/sg_update_db.conf"
+
+#$1-category
+#$2-type
+#$3-list
+create_sg_db ()
+{
+ FILE=$db_dir/$1/$2
+ if test -f "$FILE"; then
+ rm -f ${tmp_conf_file}
+ printf "dbhome $db_dir\ndest $1 {\n $3 $1/$2\n}\nacl {\n default {\n pass any\n }\n}" >> ${tmp_conf_file}
+ /usr/bin/squidGuard -b -c ${tmp_conf_file} -C $FILE
+ rm -f ${tmp_conf_file}
+ fi
+
+}
while [ $# -gt 0 ]
do
@@ -88,7 +105,17 @@ if [[ -n $update ]] && [[ $update -eq "yes" ]]; then
# fix permissions
chown -R proxy:proxy ${db_dir}
- chmod 2770 ${db_dir}
+
+ #create db
+ category_list=(`find $db_dir -type d -exec basename {} \; `)
+ for category in ${category_list[@]}
+ do
+ create_sg_db $category "domains" "domainlist"
+ create_sg_db $category "urls" "urllist"
+ create_sg_db $category "expressions" "expressionlist"
+ done
+ chown -R proxy:proxy ${db_dir}
+ chmod 755 ${db_dir}
logger --priority WARNING "webproxy blacklist entries updated (${count_before}/${count_after})"
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/__init__.py b/src/services/api/graphql/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/services/api/graphql/__init__.py
diff --git a/src/services/api/graphql/bindings.py b/src/services/api/graphql/bindings.py
index 0b1260912..aa1ba0eb0 100644
--- a/src/services/api/graphql/bindings.py
+++ b/src/services/api/graphql/bindings.py
@@ -18,16 +18,26 @@ from . graphql.queries import query
from . graphql.mutations import mutation
from . graphql.directives import directives_dict
from . graphql.errors import op_mode_error
-from . utils.schema_from_op_mode import generate_op_mode_definitions
+from . graphql.auth_token_mutation import auth_token_mutation
+from . generate.schema_from_op_mode import generate_op_mode_definitions
+from . generate.schema_from_config_session import generate_config_session_definitions
+from . generate.schema_from_composite import generate_composite_definitions
+from . libs.token_auth import init_secret
+from . import state
from ariadne import make_executable_schema, load_schema_from_path, snake_case_fallback_resolvers
def generate_schema():
api_schema_dir = vyos.defaults.directories['api_schema']
generate_op_mode_definitions()
+ generate_config_session_definitions()
+ generate_composite_definitions()
+
+ if state.settings['app'].state.vyos_auth_type == 'token':
+ init_secret()
type_defs = load_schema_from_path(api_schema_dir)
- schema = make_executable_schema(type_defs, query, op_mode_error, mutation, snake_case_fallback_resolvers, directives=directives_dict)
+ schema = make_executable_schema(type_defs, query, op_mode_error, mutation, auth_token_mutation, snake_case_fallback_resolvers, directives=directives_dict)
return schema
diff --git a/src/services/api/graphql/generate/composite_function.py b/src/services/api/graphql/generate/composite_function.py
new file mode 100644
index 000000000..bc9d80fbb
--- /dev/null
+++ b/src/services/api/graphql/generate/composite_function.py
@@ -0,0 +1,11 @@
+# typing information for composite functions: those that invoke several
+# elementary requests, and return the result as a single dict
+import typing
+
+def system_status():
+ pass
+
+queries = {'system_status': system_status}
+
+mutations = {}
+
diff --git a/src/services/api/graphql/utils/config_session_function.py b/src/services/api/graphql/generate/config_session_function.py
index fc0dd7a87..fc0dd7a87 100644
--- a/src/services/api/graphql/utils/config_session_function.py
+++ b/src/services/api/graphql/generate/config_session_function.py
diff --git a/src/services/api/graphql/generate/schema_from_composite.py b/src/services/api/graphql/generate/schema_from_composite.py
new file mode 100755
index 000000000..61a08cb2f
--- /dev/null
+++ b/src/services/api/graphql/generate/schema_from_composite.py
@@ -0,0 +1,165 @@
+#!/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/>.
+#
+#
+# A utility to generate GraphQL schema defintions from typing information of
+# composite functions comprising several requests.
+
+import os
+import sys
+import json
+from inspect import signature, getmembers, isfunction, isclass, getmro
+from jinja2 import Template
+
+from vyos.defaults import directories
+if __package__ is None or __package__ == '':
+ sys.path.append("/usr/libexec/vyos/services/api")
+ from graphql.libs.op_mode import snake_to_pascal_case, map_type_name
+ from composite_function import queries, mutations
+ from vyos.config import Config
+ from vyos.configdict import dict_merge
+ from vyos.xml import defaults
+else:
+ from .. libs.op_mode import snake_to_pascal_case, map_type_name
+ from . composite_function import queries, mutations
+ from .. import state
+
+SCHEMA_PATH = directories['api_schema']
+
+if __package__ is None or __package__ == '':
+ # allow running stand-alone
+ conf = Config()
+ base = ['service', 'https', 'api']
+ graphql_dict = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ get_first_key=True)
+ if 'graphql' not in graphql_dict:
+ exit("graphql is not configured")
+
+ graphql_dict = dict_merge(defaults(base), graphql_dict)
+ auth_type = graphql_dict['graphql']['authentication']['type']
+else:
+ auth_type = state.settings['app'].state.vyos_auth_type
+
+schema_data: dict = {'auth_type': auth_type,
+ 'schema_name': '',
+ 'schema_fields': []}
+
+query_template = """
+{%- if auth_type == 'key' %}
+input {{ schema_name }}Input {
+ key: String!
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- elif schema_fields %}
+input {{ schema_name }}Input {
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- endif %}
+
+type {{ schema_name }} {
+ result: Generic
+}
+
+type {{ schema_name }}Result {
+ data: {{ schema_name }}
+ success: Boolean!
+ errors: [String]
+}
+
+extend type Query {
+{%- if auth_type == 'key' or schema_fields %}
+ {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @compositequery
+{%- else %}
+ {{ schema_name }} : {{ schema_name }}Result @compositequery
+{%- endif %}
+}
+"""
+
+mutation_template = """
+{%- if auth_type == 'key' %}
+input {{ schema_name }}Input {
+ key: String!
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- elif schema_fields %}
+input {{ schema_name }}Input {
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- endif %}
+
+type {{ schema_name }} {
+ result: Generic
+}
+
+type {{ schema_name }}Result {
+ data: {{ schema_name }}
+ success: Boolean!
+ errors: [String]
+}
+
+extend type Mutation {
+{%- if auth_type == 'key' or schema_fields %}
+ {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @compositemutation
+{%- else %}
+ {{ schema_name }} : {{ schema_name }}Result @compositemutation
+{%- endif %}
+}
+"""
+
+def create_schema(func_name: str, func: callable, template: str) -> str:
+ sig = signature(func)
+
+ field_dict = {}
+ for k in sig.parameters:
+ field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation)
+
+ schema_fields = []
+ for k,v in field_dict.items():
+ schema_fields.append(k+': '+v)
+
+ schema_data['schema_name'] = snake_to_pascal_case(func_name)
+ schema_data['schema_fields'] = schema_fields
+
+ j2_template = Template(template)
+ res = j2_template.render(schema_data)
+
+ return res
+
+def generate_composite_definitions():
+ results = []
+ for name,func in queries.items():
+ res = create_schema(name, func, query_template)
+ results.append(res)
+
+ for name,func in mutations.items():
+ res = create_schema(name, func, mutation_template)
+ results.append(res)
+
+ out = '\n'.join(results)
+ with open(f'{SCHEMA_PATH}/composite.graphql', 'w') as f:
+ f.write(out)
+
+if __name__ == '__main__':
+ generate_composite_definitions()
diff --git a/src/services/api/graphql/utils/schema_from_config_session.py b/src/services/api/graphql/generate/schema_from_config_session.py
index ea78aaf88..49bf2440e 100755
--- a/src/services/api/graphql/utils/schema_from_config_session.py
+++ b/src/services/api/graphql/generate/schema_from_config_session.py
@@ -19,28 +19,60 @@
# (wrappers of) native configsession functions.
import os
+import sys
import json
from inspect import signature, getmembers, isfunction, isclass, getmro
from jinja2 import Template
+from vyos.defaults import directories
if __package__ is None or __package__ == '':
- from util import snake_to_pascal_case, map_type_name
+ sys.path.append("/usr/libexec/vyos/services/api")
+ from graphql.libs.op_mode import snake_to_pascal_case, map_type_name
+ from config_session_function import queries, mutations
+ from vyos.config import Config
+ from vyos.configdict import dict_merge
+ from vyos.xml import defaults
else:
- from . util import snake_to_pascal_case, map_type_name
+ from .. libs.op_mode import snake_to_pascal_case, map_type_name
+ from . config_session_function import queries, mutations
+ from .. import state
+
+SCHEMA_PATH = directories['api_schema']
-# this will be run locally before the build
-SCHEMA_PATH = '../graphql/schema'
+if __package__ is None or __package__ == '':
+ # allow running stand-alone
+ conf = Config()
+ base = ['service', 'https', 'api']
+ graphql_dict = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ get_first_key=True)
+ if 'graphql' not in graphql_dict:
+ exit("graphql is not configured")
+
+ graphql_dict = dict_merge(defaults(base), graphql_dict)
+ auth_type = graphql_dict['graphql']['authentication']['type']
+else:
+ auth_type = state.settings['app'].state.vyos_auth_type
-schema_data: dict = {'schema_name': '',
+schema_data: dict = {'auth_type': auth_type,
+ 'schema_name': '',
'schema_fields': []}
query_template = """
+{%- if auth_type == 'key' %}
input {{ schema_name }}Input {
key: String!
{%- for field_entry in schema_fields %}
{{ field_entry }}
{%- endfor %}
}
+{%- elif schema_fields %}
+input {{ schema_name }}Input {
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- endif %}
type {{ schema_name }} {
result: Generic
@@ -53,17 +85,29 @@ type {{ schema_name }}Result {
}
extend type Query {
+{%- if auth_type == 'key' or schema_fields %}
{{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @configsessionquery
+{%- else %}
+ {{ schema_name }} : {{ schema_name }}Result @configsessionquery
+{%- endif %}
}
"""
mutation_template = """
+{%- if auth_type == 'key' %}
input {{ schema_name }}Input {
key: String!
{%- for field_entry in schema_fields %}
{{ field_entry }}
{%- endfor %}
}
+{%- elif schema_fields %}
+input {{ schema_name }}Input {
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- endif %}
type {{ schema_name }} {
result: Generic
@@ -76,7 +120,11 @@ type {{ schema_name }}Result {
}
extend type Mutation {
+{%- if auth_type == 'key' or schema_fields %}
{{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @configsessionmutation
+{%- else %}
+ {{ schema_name }} : {{ schema_name }}Result @configsessionmutation
+{%- endif %}
}
"""
@@ -100,8 +148,6 @@ def create_schema(func_name: str, func: callable, template: str) -> str:
return res
def generate_config_session_definitions():
- from config_session_function import queries, mutations
-
results = []
for name,func in queries.items():
res = create_schema(name, func, query_template)
diff --git a/src/services/api/graphql/utils/schema_from_op_mode.py b/src/services/api/graphql/generate/schema_from_op_mode.py
index 57d63628b..b320a529e 100755
--- a/src/services/api/graphql/utils/schema_from_op_mode.py
+++ b/src/services/api/graphql/generate/schema_from_op_mode.py
@@ -19,17 +19,25 @@
# scripts.
import os
+import sys
import json
from inspect import signature, getmembers, isfunction, isclass, getmro
from jinja2 import Template
from vyos.defaults import directories
+from vyos.opmode import _is_op_mode_function_name as is_op_mode_function_name
+from vyos.util import load_as_module
if __package__ is None or __package__ == '':
- from util import load_as_module, is_op_mode_function_name, is_show_function_name
- from util import snake_to_pascal_case, map_type_name
+ sys.path.append("/usr/libexec/vyos/services/api")
+ from graphql.libs.op_mode import is_show_function_name
+ from graphql.libs.op_mode import snake_to_pascal_case, map_type_name
+ from vyos.config import Config
+ from vyos.configdict import dict_merge
+ from vyos.xml import defaults
else:
- from . util import load_as_module, is_op_mode_function_name, is_show_function_name
- from . util import snake_to_pascal_case, map_type_name
+ from .. libs.op_mode import is_show_function_name
+ from .. libs.op_mode import snake_to_pascal_case, map_type_name
+ from .. import state
OP_MODE_PATH = directories['op_mode']
SCHEMA_PATH = directories['api_schema']
@@ -38,16 +46,40 @@ DATA_DIR = directories['data']
op_mode_include_file = os.path.join(DATA_DIR, 'op-mode-standardized.json')
op_mode_error_schema = 'op_mode_error.graphql'
-schema_data: dict = {'schema_name': '',
+if __package__ is None or __package__ == '':
+ # allow running stand-alone
+ conf = Config()
+ base = ['service', 'https', 'api']
+ graphql_dict = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ get_first_key=True)
+ if 'graphql' not in graphql_dict:
+ exit("graphql is not configured")
+
+ graphql_dict = dict_merge(defaults(base), graphql_dict)
+ auth_type = graphql_dict['graphql']['authentication']['type']
+else:
+ auth_type = state.settings['app'].state.vyos_auth_type
+
+schema_data: dict = {'auth_type': auth_type,
+ 'schema_name': '',
'schema_fields': []}
query_template = """
+{%- if auth_type == 'key' %}
input {{ schema_name }}Input {
key: String!
{%- for field_entry in schema_fields %}
{{ field_entry }}
{%- endfor %}
}
+{%- elif schema_fields %}
+input {{ schema_name }}Input {
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- endif %}
type {{ schema_name }} {
result: Generic
@@ -61,17 +93,29 @@ type {{ schema_name }}Result {
}
extend type Query {
+{%- if auth_type == 'key' or schema_fields %}
{{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @genopquery
+{%- else %}
+ {{ schema_name }} : {{ schema_name }}Result @genopquery
+{%- endif %}
}
"""
mutation_template = """
+{%- if auth_type == 'key' %}
input {{ schema_name }}Input {
key: String!
{%- for field_entry in schema_fields %}
{{ field_entry }}
{%- endfor %}
}
+{%- elif schema_fields %}
+input {{ schema_name }}Input {
+ {%- for field_entry in schema_fields %}
+ {{ field_entry }}
+ {%- endfor %}
+}
+{%- endif %}
type {{ schema_name }} {
result: Generic
@@ -85,7 +129,11 @@ type {{ schema_name }}Result {
}
extend type Mutation {
+{%- if auth_type == 'key' or schema_fields %}
{{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @genopmutation
+{%- else %}
+ {{ schema_name }} : {{ schema_name }}Result @genopquery
+{%- endif %}
}
"""
diff --git a/src/services/api/graphql/graphql/auth_token_mutation.py b/src/services/api/graphql/graphql/auth_token_mutation.py
new file mode 100644
index 000000000..21ac40094
--- /dev/null
+++ b/src/services/api/graphql/graphql/auth_token_mutation.py
@@ -0,0 +1,49 @@
+# 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 jwt
+import datetime
+from typing import Any, Dict
+from ariadne import ObjectType, UnionType
+from graphql import GraphQLResolveInfo
+
+from .. libs.token_auth import generate_token
+from .. import state
+
+auth_token_mutation = ObjectType("Mutation")
+
+@auth_token_mutation.field('AuthToken')
+def auth_token_resolver(obj: Any, info: GraphQLResolveInfo, data: Dict):
+ # non-nullable fields
+ user = data['username']
+ passwd = data['password']
+
+ secret = state.settings['secret']
+ exp_interval = int(state.settings['app'].state.vyos_token_exp)
+ expiration = (datetime.datetime.now(tz=datetime.timezone.utc) +
+ datetime.timedelta(seconds=exp_interval))
+
+ res = generate_token(user, passwd, secret, expiration)
+ if res:
+ data['result'] = res
+ return {
+ "success": True,
+ "data": data
+ }
+
+ return {
+ "success": False,
+ "errors": ['token generation failed']
+ }
diff --git a/src/services/api/graphql/graphql/directives.py b/src/services/api/graphql/graphql/directives.py
index d75d72582..a7919854a 100644
--- a/src/services/api/graphql/graphql/directives.py
+++ b/src/services/api/graphql/graphql/directives.py
@@ -63,16 +63,25 @@ class GenOpMutationDirective(VyosDirective):
super().visit_field_definition(field, object_type,
make_resolver=make_gen_op_mutation_resolver)
-class SystemStatusDirective(VyosDirective):
+class CompositeQueryDirective(VyosDirective):
"""
Class providing implementation of 'system_status' directive in schema.
"""
def visit_field_definition(self, field, object_type):
super().visit_field_definition(field, object_type,
- make_resolver=make_system_status_resolver)
+ make_resolver=make_composite_query_resolver)
+
+class CompositeMutationDirective(VyosDirective):
+ """
+ Class providing implementation of 'system_status' directive in schema.
+ """
+ def visit_field_definition(self, field, object_type):
+ super().visit_field_definition(field, object_type,
+ make_resolver=make_composite_mutation_resolver)
directives_dict = {"configsessionquery": ConfigSessionQueryDirective,
"configsessionmutation": ConfigSessionMutationDirective,
"genopquery": GenOpQueryDirective,
"genopmutation": GenOpMutationDirective,
- "systemstatus": SystemStatusDirective}
+ "compositequery": CompositeQueryDirective,
+ "compositemutation": CompositeMutationDirective}
diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py
index f7d285a77..8254e22b1 100644
--- a/src/services/api/graphql/graphql/mutations.py
+++ b/src/services/api/graphql/graphql/mutations.py
@@ -14,13 +14,13 @@
# along with this library. If not, see <http://www.gnu.org/licenses/>.
from importlib import import_module
-from typing import Any, Dict
-from ariadne import ObjectType, convert_kwargs_to_snake_case, convert_camel_case_to_snake
+from typing import Any, Dict, Optional
+from ariadne import ObjectType, convert_camel_case_to_snake
from graphql import GraphQLResolveInfo
from makefun import with_signature
from .. import state
-from .. import key_auth
+from .. libs import key_auth
from api.graphql.session.session import Session
from api.graphql.session.errors.op_mode_errors import op_mode_err_msg, op_mode_err_code
from vyos.opmode import Error as OpModeError
@@ -42,32 +42,51 @@ def make_mutation_resolver(mutation_name, class_name, session_func):
func_base_name = convert_camel_case_to_snake(class_name)
resolver_name = f'resolve_{func_base_name}'
- func_sig = '(obj: Any, info: GraphQLResolveInfo, data: Dict)'
+ func_sig = '(obj: Any, info: GraphQLResolveInfo, data: Optional[Dict]=None)'
@mutation.field(mutation_name)
- @convert_kwargs_to_snake_case
@with_signature(func_sig, func_name=resolver_name)
async def func_impl(*args, **kwargs):
try:
- if 'data' not in kwargs:
- return {
- "success": False,
- "errors": ['missing data']
- }
-
- data = kwargs['data']
- key = data['key']
-
- auth = key_auth.auth_required(key)
- if auth is None:
- return {
- "success": False,
- "errors": ['invalid API key']
- }
-
- # We are finished with the 'key' entry, and may remove so as to
- # pass the rest of data (if any) to function.
- del data['key']
+ auth_type = state.settings['app'].state.vyos_auth_type
+
+ if auth_type == 'key':
+ data = kwargs['data']
+ key = data['key']
+
+ auth = key_auth.auth_required(key)
+ if auth is None:
+ return {
+ "success": False,
+ "errors": ['invalid API key']
+ }
+
+ # We are finished with the 'key' entry, and may remove so as to
+ # pass the rest of data (if any) to function.
+ del data['key']
+
+ elif auth_type == 'token':
+ data = kwargs['data']
+ if data is None:
+ data = {}
+ info = kwargs['info']
+ user = info.context.get('user')
+ if user is None:
+ error = info.context.get('error')
+ if error is not None:
+ return {
+ "success": False,
+ "errors": [error]
+ }
+ return {
+ "success": False,
+ "errors": ['not authenticated']
+ }
+ else:
+ # AtrributeError will have already been raised if no
+ # vyos_auth_type; validation and defaultValue ensure it is
+ # one of the previous cases, so this is never reached.
+ pass
session = state.settings['app'].state.vyos_session
@@ -112,3 +131,7 @@ def make_config_session_mutation_resolver(mutation_name):
def make_gen_op_mutation_resolver(mutation_name):
return make_mutation_resolver(mutation_name, mutation_name, 'gen_op_mutation')
+
+def make_composite_mutation_resolver(mutation_name):
+ return make_mutation_resolver(mutation_name, mutation_name,
+ convert_camel_case_to_snake(mutation_name))
diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py
index 5f3a7d005..daccc19b2 100644
--- a/src/services/api/graphql/graphql/queries.py
+++ b/src/services/api/graphql/graphql/queries.py
@@ -14,13 +14,13 @@
# along with this library. If not, see <http://www.gnu.org/licenses/>.
from importlib import import_module
-from typing import Any, Dict
-from ariadne import ObjectType, convert_kwargs_to_snake_case, convert_camel_case_to_snake
+from typing import Any, Dict, Optional
+from ariadne import ObjectType, convert_camel_case_to_snake
from graphql import GraphQLResolveInfo
from makefun import with_signature
from .. import state
-from .. import key_auth
+from .. libs import key_auth
from api.graphql.session.session import Session
from api.graphql.session.errors.op_mode_errors import op_mode_err_msg, op_mode_err_code
from vyos.opmode import Error as OpModeError
@@ -42,32 +42,51 @@ def make_query_resolver(query_name, class_name, session_func):
func_base_name = convert_camel_case_to_snake(class_name)
resolver_name = f'resolve_{func_base_name}'
- func_sig = '(obj: Any, info: GraphQLResolveInfo, data: Dict)'
+ func_sig = '(obj: Any, info: GraphQLResolveInfo, data: Optional[Dict]=None)'
@query.field(query_name)
- @convert_kwargs_to_snake_case
@with_signature(func_sig, func_name=resolver_name)
async def func_impl(*args, **kwargs):
try:
- if 'data' not in kwargs:
- return {
- "success": False,
- "errors": ['missing data']
- }
-
- data = kwargs['data']
- key = data['key']
-
- auth = key_auth.auth_required(key)
- if auth is None:
- return {
- "success": False,
- "errors": ['invalid API key']
- }
-
- # We are finished with the 'key' entry, and may remove so as to
- # pass the rest of data (if any) to function.
- del data['key']
+ auth_type = state.settings['app'].state.vyos_auth_type
+
+ if auth_type == 'key':
+ data = kwargs['data']
+ key = data['key']
+
+ auth = key_auth.auth_required(key)
+ if auth is None:
+ return {
+ "success": False,
+ "errors": ['invalid API key']
+ }
+
+ # We are finished with the 'key' entry, and may remove so as to
+ # pass the rest of data (if any) to function.
+ del data['key']
+
+ elif auth_type == 'token':
+ data = kwargs['data']
+ if data is None:
+ data = {}
+ info = kwargs['info']
+ user = info.context.get('user')
+ if user is None:
+ error = info.context.get('error')
+ if error is not None:
+ return {
+ "success": False,
+ "errors": [error]
+ }
+ return {
+ "success": False,
+ "errors": ['not authenticated']
+ }
+ else:
+ # AtrributeError will have already been raised if no
+ # vyos_auth_type; validation and defaultValue ensure it is
+ # one of the previous cases, so this is never reached.
+ pass
session = state.settings['app'].state.vyos_session
@@ -113,5 +132,6 @@ def make_config_session_query_resolver(query_name):
def make_gen_op_query_resolver(query_name):
return make_query_resolver(query_name, query_name, 'gen_op_query')
-def make_system_status_resolver(query_name):
- return make_query_resolver(query_name, query_name, 'system_status')
+def make_composite_query_resolver(query_name):
+ return make_query_resolver(query_name, query_name,
+ convert_camel_case_to_snake(query_name))
diff --git a/src/services/api/graphql/graphql/schema/auth_token.graphql b/src/services/api/graphql/graphql/schema/auth_token.graphql
new file mode 100644
index 000000000..af53a293a
--- /dev/null
+++ b/src/services/api/graphql/graphql/schema/auth_token.graphql
@@ -0,0 +1,19 @@
+
+input AuthTokenInput {
+ username: String!
+ password: String!
+}
+
+type AuthToken {
+ result: Generic
+}
+
+type AuthTokenResult {
+ data: AuthToken
+ success: Boolean!
+ errors: [String]
+}
+
+extend type Mutation {
+ AuthToken(data: AuthTokenInput) : AuthTokenResult
+}
diff --git a/src/services/api/graphql/graphql/schema/configsession.graphql b/src/services/api/graphql/graphql/schema/configsession.graphql
deleted file mode 100644
index b1deac4b3..000000000
--- a/src/services/api/graphql/graphql/schema/configsession.graphql
+++ /dev/null
@@ -1,115 +0,0 @@
-
-input ShowConfigInput {
- key: String!
- path: [String!]!
- configFormat: String = null
-}
-
-type ShowConfig {
- result: Generic
-}
-
-type ShowConfigResult {
- data: ShowConfig
- success: Boolean!
- errors: [String]
-}
-
-extend type Query {
- ShowConfig(data: ShowConfigInput) : ShowConfigResult @configsessionquery
-}
-
-input ShowInput {
- key: String!
- path: [String!]!
-}
-
-type Show {
- result: Generic
-}
-
-type ShowResult {
- data: Show
- success: Boolean!
- errors: [String]
-}
-
-extend type Query {
- Show(data: ShowInput) : ShowResult @configsessionquery
-}
-
-input SaveConfigFileInput {
- key: String!
- fileName: String = null
-}
-
-type SaveConfigFile {
- result: Generic
-}
-
-type SaveConfigFileResult {
- data: SaveConfigFile
- success: Boolean!
- errors: [String]
-}
-
-extend type Mutation {
- SaveConfigFile(data: SaveConfigFileInput) : SaveConfigFileResult @configsessionmutation
-}
-
-input LoadConfigFileInput {
- key: String!
- fileName: String!
-}
-
-type LoadConfigFile {
- result: Generic
-}
-
-type LoadConfigFileResult {
- data: LoadConfigFile
- success: Boolean!
- errors: [String]
-}
-
-extend type Mutation {
- LoadConfigFile(data: LoadConfigFileInput) : LoadConfigFileResult @configsessionmutation
-}
-
-input AddSystemImageInput {
- key: String!
- location: String!
-}
-
-type AddSystemImage {
- result: Generic
-}
-
-type AddSystemImageResult {
- data: AddSystemImage
- success: Boolean!
- errors: [String]
-}
-
-extend type Mutation {
- AddSystemImage(data: AddSystemImageInput) : AddSystemImageResult @configsessionmutation
-}
-
-input DeleteSystemImageInput {
- key: String!
- name: String!
-}
-
-type DeleteSystemImage {
- result: Generic
-}
-
-type DeleteSystemImageResult {
- data: DeleteSystemImage
- success: Boolean!
- errors: [String]
-}
-
-extend type Mutation {
- DeleteSystemImage(data: DeleteSystemImageInput) : DeleteSystemImageResult @configsessionmutation
-} \ No newline at end of file
diff --git a/src/services/api/graphql/graphql/schema/schema.graphql b/src/services/api/graphql/graphql/schema/schema.graphql
index 2acecade4..62b0d30bb 100644
--- a/src/services/api/graphql/graphql/schema/schema.graphql
+++ b/src/services/api/graphql/graphql/schema/schema.graphql
@@ -3,7 +3,8 @@ schema {
mutation: Mutation
}
-directive @systemstatus on FIELD_DEFINITION
+directive @compositequery on FIELD_DEFINITION
+directive @compositemutation on FIELD_DEFINITION
directive @configsessionquery on FIELD_DEFINITION
directive @configsessionmutation on FIELD_DEFINITION
directive @genopquery on FIELD_DEFINITION
@@ -11,8 +12,5 @@ directive @genopmutation on FIELD_DEFINITION
scalar Generic
-type Query {
- SystemStatus(data: SystemStatusInput) : SystemStatusResult @systemstatus
-}
-
+type Query
type Mutation
diff --git a/src/services/api/graphql/graphql/schema/system_status.graphql b/src/services/api/graphql/graphql/schema/system_status.graphql
deleted file mode 100644
index be8d87535..000000000
--- a/src/services/api/graphql/graphql/schema/system_status.graphql
+++ /dev/null
@@ -1,18 +0,0 @@
-"""
-Use 'scalar Generic' for system status output, to avoid attempts to
-JSON-serialize in case of JSON output.
-"""
-
-input SystemStatusInput {
- key: String!
-}
-
-type SystemStatus {
- result: Generic
-}
-
-type SystemStatusResult {
- data: SystemStatus
- success: Boolean!
- errors: [String]
-}
diff --git a/src/services/api/graphql/key_auth.py b/src/services/api/graphql/libs/key_auth.py
index f756ed6d8..2db0f7d48 100644
--- a/src/services/api/graphql/key_auth.py
+++ b/src/services/api/graphql/libs/key_auth.py
@@ -1,5 +1,5 @@
-from . import state
+from .. import state
def check_auth(key_list, key):
if not key_list:
diff --git a/src/services/api/graphql/utils/util.py b/src/services/api/graphql/libs/op_mode.py
index da2bcdb5b..c553bbd67 100644
--- a/src/services/api/graphql/utils/util.py
+++ b/src/services/api/graphql/libs/op_mode.py
@@ -17,25 +17,18 @@ import os
import re
import typing
import importlib.util
+from typing import Union
+from humps import decamelize
from vyos.defaults import directories
-
-def load_as_module(name: str, path: str):
- spec = importlib.util.spec_from_file_location(name, path)
- mod = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(mod)
- return mod
+from vyos.util import load_as_module
+from vyos.opmode import _normalize_field_names
def load_op_mode_as_module(name: str):
path = os.path.join(directories['op_mode'], name)
name = os.path.splitext(name)[0].replace('-', '_')
return load_as_module(name, path)
-def is_op_mode_function_name(name):
- if re.match(r"^(show|clear|reset|restart)", name):
- return True
- return False
-
def is_show_function_name(name):
if re.match(r"^show", name):
return True
@@ -86,7 +79,7 @@ def map_type_name(type_name: type, optional: bool = False) -> str:
if type_name == int:
return 'Int!' if not optional else 'Int = null'
if type_name == bool:
- return 'Boolean!' if not optional else 'Boolean = false'
+ return 'Boolean = false'
if typing.get_origin(type_name) == list:
if not optional:
return f'[{map_type_name(typing.get_args(type_name)[0])}]!'
@@ -98,3 +91,6 @@ def map_type_name(type_name: type, optional: bool = False) -> str:
# scalar 'Generic' is defined in schema.graphql
return 'Generic'
+
+def normalize_output(result: Union[dict, list]) -> Union[dict, list]:
+ return _normalize_field_names(decamelize(result))
diff --git a/src/services/api/graphql/libs/token_auth.py b/src/services/api/graphql/libs/token_auth.py
new file mode 100644
index 000000000..2100eba7f
--- /dev/null
+++ b/src/services/api/graphql/libs/token_auth.py
@@ -0,0 +1,71 @@
+import jwt
+import uuid
+import pam
+from secrets import token_hex
+
+from .. import state
+
+def _check_passwd_pam(username: str, passwd: str) -> bool:
+ if pam.authenticate(username, passwd):
+ return True
+ return False
+
+def init_secret():
+ length = int(state.settings['app'].state.vyos_secret_len)
+ secret = token_hex(length)
+ state.settings['secret'] = secret
+
+def generate_token(user: str, passwd: str, secret: str, exp: int) -> dict:
+ if user is None or passwd is None:
+ return {}
+ if _check_passwd_pam(user, passwd):
+ app = state.settings['app']
+ try:
+ users = app.state.vyos_token_users
+ except AttributeError:
+ app.state.vyos_token_users = {}
+ users = app.state.vyos_token_users
+ user_id = uuid.uuid1().hex
+ payload_data = {'iss': user, 'sub': user_id, 'exp': exp}
+ secret = state.settings.get('secret')
+ if secret is None:
+ return {
+ "success": False,
+ "errors": ['failed secret generation']
+ }
+ token = jwt.encode(payload=payload_data, key=secret, algorithm="HS256")
+
+ users |= {user_id: user}
+ return {'token': token}
+
+def get_user_context(request):
+ context = {}
+ context['request'] = request
+ context['user'] = None
+ if 'Authorization' in request.headers:
+ auth = request.headers['Authorization']
+ scheme, token = auth.split()
+ if scheme.lower() != 'bearer':
+ return context
+
+ try:
+ secret = state.settings.get('secret')
+ payload = jwt.decode(token, secret, algorithms=["HS256"])
+ user_id: str = payload.get('sub')
+ if user_id is None:
+ return context
+ except jwt.exceptions.ExpiredSignatureError:
+ context['error'] = 'expired token'
+ return context
+ except jwt.PyJWTError:
+ return context
+ try:
+ users = state.settings['app'].state.vyos_token_users
+ except AttributeError:
+ return context
+
+ user = users.get(user_id)
+ if user is not None:
+ context['user'] = user
+
+ return context
diff --git a/src/services/api/graphql/session/composite/system_status.py b/src/services/api/graphql/session/composite/system_status.py
index 3c1a3d45b..d809f32e3 100755
--- a/src/services/api/graphql/session/composite/system_status.py
+++ b/src/services/api/graphql/session/composite/system_status.py
@@ -23,7 +23,7 @@ import importlib.util
from vyos.defaults import directories
-from api.graphql.utils.util import load_op_mode_as_module
+from api.graphql.libs.op_mode import load_op_mode_as_module
def get_system_version() -> dict:
show_version = load_op_mode_as_module('version.py')
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 7ba75455d..18d555f2d 100644
--- a/src/services/api/graphql/session/errors/op_mode_errors.py
+++ b/src/services/api/graphql/session/errors/op_mode_errors.py
@@ -1,13 +1,17 @@
-
-
op_mode_err_msg = {
"UnconfiguredSubsystem": "subsystem is not configured or not running",
"DataUnavailable": "data currently unavailable",
- "PermissionDenied": "client does not have permission"
+ "PermissionDenied": "client does not have permission",
+ "InsufficientResources": "insufficient system resources",
+ "IncorrectValue": "argument value is incorrect",
+ "UnsupportedOperation": "operation is not supported (yet)",
}
op_mode_err_code = {
"UnconfiguredSubsystem": 2000,
"DataUnavailable": 2001,
- "PermissionDenied": 1003
+ "InsufficientResources": 2002,
+ "PermissionDenied": 1003,
+ "IncorrectValue": 1002,
+ "UnsupportedOperation": 1004,
}
diff --git a/src/services/api/graphql/session/session.py b/src/services/api/graphql/session/session.py
index f990e63d0..0b77b1433 100644
--- a/src/services/api/graphql/session/session.py
+++ b/src/services/api/graphql/session/session.py
@@ -24,7 +24,8 @@ from vyos.defaults import directories
from vyos.template import render
from vyos.opmode import Error as OpModeError
-from api.graphql.utils.util import load_op_mode_as_module, split_compound_op_mode_name
+from api.graphql.libs.op_mode import load_op_mode_as_module, split_compound_op_mode_name
+from api.graphql.libs.op_mode import normalize_output
op_mode_include_file = os.path.join(directories['data'], 'op-mode-standardized.json')
@@ -149,6 +150,8 @@ class Session:
except OpModeError as e:
raise e
+ res = normalize_output(res)
+
return res
def gen_op_mutation(self):
diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd
index 9ae7b1ea9..a380f2e66 100755
--- a/src/services/vyos-hostsd
+++ b/src/services/vyos-hostsd
@@ -406,8 +406,7 @@ def validate_schema(data):
def pdns_rec_control(command):
- # pdns-r process name is NOT equal to the name shown in ps
- if not process_named_running('pdns-r/worker'):
+ if not process_named_running('pdns_recursor'):
logger.info(f'pdns_recursor not running, not sending "{command}"')
return
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index 4ace981ca..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
@@ -647,21 +691,30 @@ def reset_op(data: ResetModel):
###
def graphql_init(fast_api_app):
- from api.graphql.bindings import generate_schema
-
+ from api.graphql.libs.token_auth import get_user_context
api.graphql.state.init()
api.graphql.state.settings['app'] = app
+ # import after initializaion of state
+ from api.graphql.bindings import generate_schema
schema = generate_schema()
in_spec = app.state.vyos_introspection
if app.state.vyos_origins:
origins = app.state.vyos_origins
- app.add_route('/graphql', CORSMiddleware(GraphQL(schema, debug=True, introspection=in_spec), allow_origins=origins, allow_methods=("GET", "POST", "OPTIONS")))
+ app.add_route('/graphql', CORSMiddleware(GraphQL(schema,
+ context_value=get_user_context,
+ debug=True,
+ introspection=in_spec),
+ allow_origins=origins,
+ allow_methods=("GET", "POST", "OPTIONS"),
+ allow_headers=("Authorization",)))
else:
- app.add_route('/graphql', GraphQL(schema, debug=True, introspection=in_spec))
-
+ app.add_route('/graphql', GraphQL(schema,
+ context_value=get_user_context,
+ debug=True,
+ introspection=in_spec))
###
if __name__ == '__main__':
@@ -688,16 +741,21 @@ if __name__ == '__main__':
app.state.vyos_debug = server_config['debug']
app.state.vyos_strict = server_config['strict']
app.state.vyos_origins = server_config.get('cors', {}).get('allow_origin', [])
- if 'gql' in server_config:
- app.state.vyos_gql = True
- if isinstance(server_config['gql'], dict) and 'introspection' in server_config['gql']:
- app.state.vyos_introspection = True
- else:
- app.state.vyos_introspection = False
+ if 'graphql' in server_config:
+ app.state.vyos_graphql = True
+ if isinstance(server_config['graphql'], dict):
+ if 'introspection' in server_config['graphql']:
+ app.state.vyos_introspection = True
+ else:
+ app.state.vyos_introspection = False
+ # default value is merged in conf_mode http-api.py, if not set
+ app.state.vyos_auth_type = server_config['graphql']['authentication']['type']
+ app.state.vyos_token_exp = server_config['graphql']['authentication']['expiration']
+ app.state.vyos_secret_len = server_config['graphql']['authentication']['secret_length']
else:
- app.state.vyos_gql = False
+ app.state.vyos_graphql = False
- if app.state.vyos_gql:
+ if app.state.vyos_graphql:
graphql_init(app)
try:
diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py
index a0fccd1d0..864ee8419 100755
--- a/src/system/keepalived-fifo.py
+++ b/src/system/keepalived-fifo.py
@@ -67,13 +67,13 @@ class KeepalivedFifo:
# For VRRP configuration to be read, the commit must be finished
count = 1
while commit_in_progress():
- if ( count <= 40 ):
- logger.debug(f'commit in progress try: {count}')
+ if ( count <= 20 ):
+ logger.debug(f'Attempt to load keepalived configuration aborted due to a commit in progress (attempt {count}/20)')
else:
- logger.error(f'commit still in progress after {count} continuing anyway')
+ logger.error(f'Forced keepalived configuration loading despite a commit in progress ({count} wait time expired, not waiting further)')
break
count += 1
- time.sleep(0.5)
+ time.sleep(1)
try:
base = ['high-availability', 'vrrp']
diff --git a/src/systemd/vyos-domain-group-resolve.service b/src/systemd/vyos-domain-group-resolve.service
deleted file mode 100644
index 29628fddb..000000000
--- a/src/systemd/vyos-domain-group-resolve.service
+++ /dev/null
@@ -1,11 +0,0 @@
-[Unit]
-Description=VyOS firewall domain-group resolver
-After=vyos-router.service
-
-[Service]
-Type=simple
-Restart=always
-ExecStart=/usr/bin/python3 /usr/libexec/vyos/vyos-domain-group-resolve.py
-
-[Install]
-WantedBy=multi-user.target
diff --git a/src/systemd/vyos-domain-resolver.service b/src/systemd/vyos-domain-resolver.service
new file mode 100644
index 000000000..c56b51f0c
--- /dev/null
+++ b/src/systemd/vyos-domain-resolver.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=VyOS firewall domain resolver
+After=vyos-router.service
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/vyos-domain-resolver.py
+StandardError=journal
+StandardOutput=journal
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/tests/test_op_mode.py b/src/tests/test_op_mode.py
new file mode 100644
index 000000000..90963b3c5
--- /dev/null
+++ b/src/tests/test_op_mode.py
@@ -0,0 +1,65 @@
+#!/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 unittest import TestCase
+
+import vyos.opmode
+
+class TestVyOSOpMode(TestCase):
+ def test_field_name_normalization(self):
+ from vyos.opmode import _normalize_field_name
+
+ self.assertEqual(_normalize_field_name(" foo bar "), "foo_bar")
+ self.assertEqual(_normalize_field_name("foo-bar"), "foo_bar")
+ self.assertEqual(_normalize_field_name("foo (bar) baz"), "foo_bar_baz")
+ self.assertEqual(_normalize_field_name("load%"), "load_percentage")
+
+ def test_dict_fields_normalization_non_unique(self):
+ from vyos.opmode import _normalize_field_names
+
+ # Space and dot are both replaced by an underscore,
+ # so dicts like this cannor be normalized uniquely
+ data = {"foo bar": True, "foo.bar": False}
+
+ with self.assertRaises(vyos.opmode.InternalError):
+ _normalize_field_names(data)
+
+ def test_dict_fields_normalization_simple_dict(self):
+ from vyos.opmode import _normalize_field_names
+
+ data = {"foo bar": True, "Bar-Baz": False}
+ self.assertEqual(_normalize_field_names(data), {"foo_bar": True, "bar_baz": False})
+
+ def test_dict_fields_normalization_nested_dict(self):
+ from vyos.opmode import _normalize_field_names
+
+ data = {"foo bar": True, "bar-baz": {"baz-quux": {"quux-xyzzy": False}}}
+ self.assertEqual(_normalize_field_names(data),
+ {"foo_bar": True, "bar_baz": {"baz_quux": {"quux_xyzzy": False}}})
+
+ def test_dict_fields_normalization_mixed(self):
+ from vyos.opmode import _normalize_field_names
+
+ data = [{"foo bar": True, "bar-baz": [{"baz-quux": {"quux-xyzzy": [False]}}]}]
+ self.assertEqual(_normalize_field_names(data),
+ [{"foo_bar": True, "bar_baz": [{"baz_quux": {"quux_xyzzy": [False]}}]}])
+
+ def test_dict_fields_normalization_primitive(self):
+ from vyos.opmode import _normalize_field_names
+
+ data = [1, False, "foo"]
+ self.assertEqual(_normalize_field_names(data), [1, False, "foo"])
+
diff --git a/src/tests/test_util.py b/src/tests/test_util.py
index 8ac9a500a..d8b2b7940 100644
--- a/src/tests/test_util.py
+++ b/src/tests/test_util.py
@@ -26,3 +26,17 @@ class TestVyOSUtil(TestCase):
def test_sysctl_read(self):
self.assertEqual(sysctl_read('net.ipv4.conf.lo.forwarding'), '1')
+
+ def test_camel_to_snake_case(self):
+ self.assertEqual(camel_to_snake_case('ConnectionTimeout'),
+ 'connection_timeout')
+ self.assertEqual(camel_to_snake_case('connectionTimeout'),
+ 'connection_timeout')
+ self.assertEqual(camel_to_snake_case('TCPConnectionTimeout'),
+ 'tcp_connection_timeout')
+ self.assertEqual(camel_to_snake_case('TCPPort'),
+ 'tcp_port')
+ self.assertEqual(camel_to_snake_case('UseHTTPProxy'),
+ 'use_http_proxy')
+ self.assertEqual(camel_to_snake_case('CustomerID'),
+ 'customer_id')
diff --git a/src/validators/allowed-vlan b/src/validators/allowed-vlan
deleted file mode 100755
index 11389390b..000000000
--- a/src/validators/allowed-vlan
+++ /dev/null
@@ -1,19 +0,0 @@
-#! /usr/bin/python3
-
-import sys
-import re
-
-if __name__ == '__main__':
- if len(sys.argv)>1:
- allowed_vlan = sys.argv[1]
- if re.search('[0-9]{1,4}-[0-9]{1,4}', allowed_vlan):
- for tmp in allowed_vlan.split('-'):
- if int(tmp) not in range(1, 4095):
- sys.exit(1)
- else:
- if int(allowed_vlan) not in range(1, 4095):
- sys.exit(1)
- else:
- sys.exit(2)
-
- sys.exit(0)
diff --git a/src/validators/dotted-decimal b/src/validators/dotted-decimal
deleted file mode 100755
index 652110346..000000000
--- a/src/validators/dotted-decimal
+++ /dev/null
@@ -1,33 +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/>.
-
-import re
-import sys
-
-area = sys.argv[1]
-
-res = re.match(r'^(\d+)\.(\d+)\.(\d+)\.(\d+)$', area)
-if not res:
- print("\'{0}\' is not a valid dotted decimal value".format(area))
- sys.exit(1)
-else:
- components = res.groups()
- for n in range(0, 4):
- if (int(components[n]) > 255):
- print("Invalid component of a dotted decimal value: {0} exceeds 255".format(components[n]))
- sys.exit(1)
-
-sys.exit(0)
diff --git a/src/validators/file-exists b/src/validators/file-exists
deleted file mode 100755
index 5cef6b199..000000000
--- a/src/validators/file-exists
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2019 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-# Description:
-# Check if a given file exists on the system. Used for files that
-# are referenced from the CLI and need to be preserved during an image upgrade.
-# Warn the user if these aren't under /config
-
-import os
-import sys
-import argparse
-
-
-def exit(strict, message):
- if strict:
- sys.exit(f'ERROR: {message}')
- print(f'WARNING: {message}', file=sys.stderr)
- sys.exit()
-
-
-if __name__ == '__main__':
- parser = argparse.ArgumentParser()
- parser.add_argument("-d", "--directory", type=str, help="File must be present in this directory.")
- parser.add_argument("-e", "--error", action="store_true", help="Tread warnings as errors - change exit code to '1'")
- parser.add_argument("file", type=str, help="Path of file to validate")
-
- args = parser.parse_args()
-
- #
- # Always check if the given file exists
- #
- if not os.path.exists(args.file):
- exit(args.error, f"File '{args.file}' not found")
-
- #
- # Optional check if the file is under a certain directory path
- #
- if args.directory:
- # remove directory path from path to verify
- rel_filename = args.file.replace(args.directory, '').lstrip('/')
-
- if not os.path.exists(args.directory + '/' + rel_filename):
- exit(args.error,
- f"'{args.file}' lies outside of '{args.directory}' directory.\n"
- "It will not get preserved during image upgrade!"
- )
-
- sys.exit()
diff --git a/src/validators/fqdn b/src/validators/fqdn
index a4027e4ca..a65d2d5d4 100755
--- a/src/validators/fqdn
+++ b/src/validators/fqdn
@@ -1,27 +1,2 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2020-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 re
-import sys
-
-pattern = '[A-Za-z0-9][-.A-Za-z0-9]*'
-
-if __name__ == '__main__':
- if len(sys.argv) != 2:
- sys.exit(1)
- if not re.match(pattern, sys.argv[1]):
- sys.exit(1)
- sys.exit(0)
+#!/usr/bin/env sh
+${vyos_libexec_dir}/validate-value --regex "[A-Za-z0-9][-.A-Za-z0-9]*" --value "$1"
diff --git a/src/validators/mac-address b/src/validators/mac-address
index 7d020f387..bb859a603 100755
--- a/src/validators/mac-address
+++ b/src/validators/mac-address
@@ -1,27 +1,2 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import re
-import sys
-
-pattern = "^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$"
-
-if __name__ == '__main__':
- if len(sys.argv) != 2:
- sys.exit(1)
- if not re.match(pattern, sys.argv[1]):
- sys.exit(1)
- sys.exit(0)
+#!/usr/bin/env sh
+${vyos_libexec_dir}/validate-value --regex "([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})" --value "$1"
diff --git a/src/validators/mac-address-exclude b/src/validators/mac-address-exclude
new file mode 100755
index 000000000..c44913023
--- /dev/null
+++ b/src/validators/mac-address-exclude
@@ -0,0 +1,2 @@
+#!/usr/bin/env sh
+${vyos_libexec_dir}/validate-value --regex "!([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})" --value "$1"
diff --git a/src/validators/mac-address-firewall b/src/validators/mac-address-firewall
deleted file mode 100755
index 70551f86d..000000000
--- a/src/validators/mac-address-firewall
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2018-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 re
-import sys
-
-pattern = "^!?([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$"
-
-if __name__ == '__main__':
- if len(sys.argv) != 2:
- sys.exit(1)
- if not re.match(pattern, sys.argv[1]):
- sys.exit(1)
- sys.exit(0)
diff --git a/src/validators/tcp-flag b/src/validators/tcp-flag
deleted file mode 100755
index 1496b904a..000000000
--- a/src/validators/tcp-flag
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/usr/bin/python3
-
-import sys
-import re
-
-if __name__ == '__main__':
- if len(sys.argv)>1:
- flag = sys.argv[1]
- if flag and flag[0] == '!':
- flag = flag[1:]
- if flag not in ['syn', 'ack', 'rst', 'fin', 'urg', 'psh', 'ecn', 'cwr']:
- print(f'Error: {flag} is not a valid TCP flag')
- sys.exit(1)
- else:
- sys.exit(2)
-
- sys.exit(0)
diff --git a/src/xdp/common/common.mk b/src/xdp/common/common.mk
index ebe23a9ed..ffb86a65c 100644
--- a/src/xdp/common/common.mk
+++ b/src/xdp/common/common.mk
@@ -39,7 +39,7 @@ KERN_USER_H ?= $(wildcard common_kern_user.h)
CFLAGS ?= -g -I../include/
BPF_CFLAGS ?= -I../include/
-LIBS = -l:libbpf.a -lelf $(USER_LIBS)
+LIBS = -lbpf -lelf $(USER_LIBS)
all: llvm-check $(USER_TARGETS) $(XDP_OBJ) $(COPY_LOADER) $(COPY_STATS)
diff --git a/src/xdp/common/common_user_bpf_xdp.c b/src/xdp/common/common_user_bpf_xdp.c
index e7ef77174..faf7f4f91 100644
--- a/src/xdp/common/common_user_bpf_xdp.c
+++ b/src/xdp/common/common_user_bpf_xdp.c
@@ -274,7 +274,7 @@ struct bpf_object *load_bpf_and_xdp_attach(struct config *cfg)
exit(EXIT_FAIL_BPF);
}
- strncpy(cfg->progsec, bpf_program__title(bpf_prog, false), sizeof(cfg->progsec));
+ strncpy(cfg->progsec, bpf_program__section_name(bpf_prog), sizeof(cfg->progsec));
prog_fd = bpf_program__fd(bpf_prog);
if (prog_fd <= 0) {