summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md2
-rw-r--r--.github/workflows/auto-author-assign.yml2
-rw-r--r--.github/workflows/build.yml20
-rw-r--r--CONTRIBUTING.md8
-rw-r--r--Makefile7
-rw-r--r--README.md2
-rw-r--r--data/config-mode-dependencies.json20
-rw-r--r--data/configd-include.json1
-rw-r--r--data/op-mode-standardized.json8
-rw-r--r--data/templates/accel-ppp/config_ip_pool.j212
-rw-r--r--data/templates/accel-ppp/config_shaper_radius.j212
-rw-r--r--data/templates/accel-ppp/ipoe.config.j249
-rw-r--r--data/templates/accel-ppp/l2tp.config.j28
-rw-r--r--data/templates/accel-ppp/pppoe.config.j223
-rw-r--r--data/templates/accel-ppp/pptp.config.j27
-rw-r--r--data/templates/chrony/chrony.conf.j260
-rw-r--r--data/templates/chrony/override.conf.j2 (renamed from data/templates/ntp/override.conf.j2)7
-rw-r--r--data/templates/container/containers.conf.j2709
-rw-r--r--data/templates/container/registries.conf.j26
-rw-r--r--data/templates/container/storage.conf.j24
-rw-r--r--data/templates/dhcp-relay/dhcrelay.conf.j25
-rw-r--r--data/templates/dns-forwarding/recursor.forward-zones.conf.j23
-rw-r--r--data/templates/dynamic-dns/ddclient.conf.j22
-rw-r--r--data/templates/ethernet/wpa_supplicant.conf.j27
-rw-r--r--data/templates/firewall/nftables-policy.j22
-rw-r--r--data/templates/frr/babeld.frr.j285
-rw-r--r--data/templates/frr/bgpd.frr.j221
-rw-r--r--data/templates/frr/daemons.frr.tmpl3
-rw-r--r--data/templates/frr/distribute_list_macro.j230
-rw-r--r--data/templates/frr/ipv6_distribute_list_macro.j230
-rw-r--r--data/templates/frr/isisd.frr.j211
-rw-r--r--data/templates/frr/ospfd.frr.j221
-rw-r--r--data/templates/frr/ripd.frr.j230
-rw-r--r--data/templates/frr/ripngd.frr.j230
-rw-r--r--data/templates/frr/staticd.frr.j22
-rw-r--r--data/templates/frr/vrf-vni.frr.j29
-rw-r--r--data/templates/frr/vrf.route-map.frr.j210
-rw-r--r--data/templates/frr/zebra.route-map.frr.j29
-rw-r--r--data/templates/frr/zebra.vrf.route-map.frr.j228
-rw-r--r--data/templates/high-availability/keepalived.conf.j250
-rw-r--r--data/templates/https/nginx.default.j210
-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/ipsec.conf.j219
-rw-r--r--data/templates/ipsec/ipsec.secrets.j25
-rw-r--r--data/templates/ipsec/swanctl.conf.j234
-rw-r--r--data/templates/ipsec/swanctl/peer.j24
-rw-r--r--data/templates/login/limits.j25
-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/openvpn/server.conf.j25
-rw-r--r--data/templates/pppoe/peer.j213
-rw-r--r--data/templates/router-advert/radvd.conf.j22
-rw-r--r--data/templates/sflow/hsflowd.conf.j232
-rw-r--r--data/templates/sflow/override.conf.j216
-rw-r--r--data/templates/snmp/etc.snmpd.conf.j234
-rw-r--r--data/templates/snmp/override.conf.j23
-rw-r--r--data/templates/ssh/sshd_config.j24
-rw-r--r--data/templates/sstp-client/peer.j22
-rw-r--r--data/templates/system/ssh_config.j23
-rw-r--r--data/templates/telegraf/telegraf.j24
-rw-r--r--debian/compat2
-rw-r--r--debian/control22
-rwxr-xr-xdebian/rules4
-rw-r--r--debian/vyos-1x.install4
-rw-r--r--debian/vyos-1x.postinst16
-rw-r--r--interface-definitions/bcast-relay.xml.in6
-rw-r--r--interface-definitions/container.xml.in90
-rw-r--r--interface-definitions/dhcp-relay.xml.in32
-rw-r--r--interface-definitions/dhcpv6-relay.xml.in4
-rw-r--r--interface-definitions/dns-domain-name.xml.in6
-rw-r--r--interface-definitions/dns-dynamic.xml.in2
-rw-r--r--interface-definitions/dns-forwarding.xml.in58
-rw-r--r--interface-definitions/firewall.xml.in15
-rw-r--r--interface-definitions/high-availability.xml.in56
-rw-r--r--interface-definitions/https.xml.in1
-rw-r--r--interface-definitions/igmp-proxy.xml.in2
-rw-r--r--interface-definitions/include/accel-ppp/auth-mode.xml.i8
-rw-r--r--interface-definitions/include/accel-ppp/client-ip-pool-name.xml.i30
-rw-r--r--interface-definitions/include/accel-ppp/ppp-options-ipv6-interface-id.xml.i54
-rw-r--r--interface-definitions/include/accel-ppp/shaper.xml.i21
-rw-r--r--interface-definitions/include/allow-client.xml.i35
-rw-r--r--interface-definitions/include/babel/interface.xml.i187
-rw-r--r--interface-definitions/include/bgp/afi-ipv4-prefix-list.xml.i4
-rw-r--r--interface-definitions/include/bgp/afi-ipv6-prefix-list.xml.i4
-rw-r--r--interface-definitions/include/bgp/neighbor-local-role.xml.i42
-rw-r--r--interface-definitions/include/bgp/neighbor-path-attribute.xml.i21
-rw-r--r--interface-definitions/include/bgp/neighbor-update-source.xml.i4
-rw-r--r--interface-definitions/include/bgp/protocol-common-config.xml.i94
-rw-r--r--interface-definitions/include/bgp/timers-holdtime.xml.i4
-rw-r--r--interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i3
-rw-r--r--interface-definitions/include/constraint/host-name.xml.i3
-rw-r--r--interface-definitions/include/constraint/interface-name-with-wildcard.xml.i4
-rw-r--r--interface-definitions/include/constraint/interface-name.xml.i (renamed from interface-definitions/include/constraint/interface-name.xml.in)2
-rw-r--r--interface-definitions/include/dhcp-interface-multi.xml.i18
-rw-r--r--interface-definitions/include/dhcp-interface.xml.i4
-rw-r--r--interface-definitions/include/eigrp/protocol-common-config.xml.i9
-rw-r--r--interface-definitions/include/firewall/action.xml.i8
-rw-r--r--interface-definitions/include/firewall/common-rule.xml.i10
-rw-r--r--interface-definitions/include/firewall/connection-mark.xml.i4
-rw-r--r--interface-definitions/include/firewall/fwmark.xml.i14
-rw-r--r--interface-definitions/include/firewall/match-interface.xml.i2
-rw-r--r--interface-definitions/include/firewall/nft-queue.xml.i34
-rw-r--r--interface-definitions/include/firewall/packet-options.xml.i (renamed from interface-definitions/include/firewall/packet-length.xml.i)29
-rw-r--r--interface-definitions/include/firewall/rule-log-level.xml.i2
-rw-r--r--interface-definitions/include/firewall/rule-log-options.xml.i89
-rw-r--r--interface-definitions/include/generic-description.xml.i4
-rw-r--r--interface-definitions/include/generic-interface-broadcast.xml.i6
-rw-r--r--interface-definitions/include/generic-interface-multi-broadcast.xml.i6
-rw-r--r--interface-definitions/include/generic-interface-multi-wildcard.xml.i18
-rw-r--r--interface-definitions/include/generic-interface-multi.xml.i4
-rw-r--r--interface-definitions/include/generic-interface.xml.i4
-rw-r--r--interface-definitions/include/generic-password.xml.i15
-rw-r--r--interface-definitions/include/generic-username.xml.i15
-rw-r--r--interface-definitions/include/inbound-interface.xml.i2
-rw-r--r--interface-definitions/include/interface/authentication.xml.i20
-rw-r--r--interface-definitions/include/interface/description.xml.i11
-rw-r--r--interface-definitions/include/interface/dhcpv6-options.xml.i2
-rw-r--r--interface-definitions/include/interface/inbound-interface.xml.i2
-rw-r--r--interface-definitions/include/interface/mirror.xml.i18
-rw-r--r--interface-definitions/include/interface/redirect.xml.i8
-rw-r--r--interface-definitions/include/interface/vif-s.xml.i4
-rw-r--r--interface-definitions/include/interface/vif.xml.i2
-rw-r--r--interface-definitions/include/isis/ldp-sync-holddown.xml.i14
-rw-r--r--interface-definitions/include/isis/ldp-sync-interface.xml.i11
-rw-r--r--interface-definitions/include/isis/ldp-sync-protocol.xml.i10
-rw-r--r--interface-definitions/include/isis/protocol-common-config.xml.i25
-rw-r--r--interface-definitions/include/listen-address-ipv4-single.xml.i17
-rw-r--r--interface-definitions/include/name-server-ipv4-ipv6-port.xml.i25
-rw-r--r--interface-definitions/include/nat-interface.xml.i2
-rw-r--r--interface-definitions/include/nat-rule.xml.i27
-rw-r--r--interface-definitions/include/ospf/protocol-common-config.xml.i19
-rw-r--r--interface-definitions/include/ospfv3/protocol-common-config.xml.i13
-rw-r--r--interface-definitions/include/policy/route-common.xml.i4
-rw-r--r--interface-definitions/include/policy/route-rule-action.xml.i16
-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/hfsc-m1.xml.i2
-rw-r--r--interface-definitions/include/qos/hfsc-m2.xml.i2
-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/rip/interface.xml.i4
-rw-r--r--interface-definitions/include/routing-passive-interface.xml.i4
-rw-r--r--interface-definitions/include/source-interface-ethernet.xml.i2
-rw-r--r--interface-definitions/include/source-interface.xml.i4
-rw-r--r--interface-definitions/include/static/static-route-interface.xml.i4
-rw-r--r--interface-definitions/include/static/static-route.xml.i4
-rw-r--r--interface-definitions/include/static/static-route6.xml.i4
-rw-r--r--interface-definitions/include/system-ip-protocol.xml.i56
-rw-r--r--interface-definitions/include/system-ipv6-protocol.xml.i52
-rw-r--r--interface-definitions/include/version/bgp-version.xml.i2
-rw-r--r--interface-definitions/include/version/container-version.xml.i3
-rw-r--r--interface-definitions/include/version/dns-forwarding-version.xml.i2
-rw-r--r--interface-definitions/include/version/interfaces-version.xml.i2
-rw-r--r--interface-definitions/include/version/ipsec-version.xml.i2
-rw-r--r--interface-definitions/include/version/isis-version.xml.i2
-rw-r--r--interface-definitions/include/version/ntp-version.xml.i2
-rw-r--r--interface-definitions/include/version/ospf-version.xml.i2
-rw-r--r--interface-definitions/include/version/qos-version.xml.i2
-rw-r--r--interface-definitions/include/version/quagga-version.xml.i2
-rw-r--r--interface-definitions/include/version/rip-version.xml.i3
-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.in10
-rw-r--r--interface-definitions/interfaces-bridge.xml.in4
-rw-r--r--interface-definitions/interfaces-dummy.xml.in21
-rw-r--r--interface-definitions/interfaces-ethernet.xml.in2
-rw-r--r--interface-definitions/interfaces-geneve.xml.in2
-rw-r--r--interface-definitions/interfaces-input.xml.in2
-rw-r--r--interface-definitions/interfaces-l2tpv3.xml.in2
-rw-r--r--interface-definitions/interfaces-loopback.xml.in2
-rw-r--r--interface-definitions/interfaces-macsec.xml.in6
-rw-r--r--interface-definitions/interfaces-openvpn.xml.in20
-rw-r--r--interface-definitions/interfaces-pppoe.xml.in29
-rw-r--r--interface-definitions/interfaces-pseudo-ethernet.xml.in2
-rw-r--r--interface-definitions/interfaces-sstpc.xml.in2
-rw-r--r--interface-definitions/interfaces-tunnel.xml.in22
-rw-r--r--interface-definitions/interfaces-virtual-ethernet.xml.in2
-rw-r--r--interface-definitions/interfaces-vti.xml.in2
-rw-r--r--interface-definitions/interfaces-vxlan.xml.in2
-rw-r--r--interface-definitions/interfaces-wireguard.xml.in2
-rw-r--r--interface-definitions/interfaces-wireless.xml.in4
-rw-r--r--interface-definitions/interfaces-wwan.xml.in2
-rw-r--r--interface-definitions/load-balancing-wan.xml.in16
-rw-r--r--interface-definitions/nat66.xml.in16
-rw-r--r--interface-definitions/netns.xml.in2
-rw-r--r--interface-definitions/ntp.xml.in35
-rw-r--r--interface-definitions/pki.xml.in12
-rw-r--r--interface-definitions/policy-route.xml.in8
-rw-r--r--interface-definitions/policy.xml.in8
-rw-r--r--interface-definitions/protocols-babel.xml.in254
-rw-r--r--interface-definitions/protocols-bgp.xml.in14
-rw-r--r--interface-definitions/protocols-failover.xml.in21
-rw-r--r--interface-definitions/protocols-igmp.xml.in2
-rw-r--r--interface-definitions/protocols-multicast.xml.in2
-rw-r--r--interface-definitions/protocols-pim.xml.in2
-rw-r--r--interface-definitions/protocols-rip.xml.in13
-rw-r--r--interface-definitions/protocols-ripng.xml.in14
-rw-r--r--interface-definitions/protocols-rpki.xml.in6
-rw-r--r--interface-definitions/protocols-static-arp.xml.in4
-rw-r--r--interface-definitions/protocols-static.xml.in7
-rw-r--r--interface-definitions/qos.xml.in473
-rw-r--r--interface-definitions/service-conntrack-sync.xml.in2
-rw-r--r--interface-definitions/service-console-server.xml.in2
-rw-r--r--interface-definitions/service-ids-ddos-protection.xml.in2
-rw-r--r--interface-definitions/service-ipoe-server.xml.in47
-rw-r--r--interface-definitions/service-monitoring-telegraf.xml.in10
-rw-r--r--interface-definitions/service-pppoe-server.xml.in54
-rw-r--r--interface-definitions/service-router-advert.xml.in2
-rw-r--r--interface-definitions/service-upnp.xml.in8
-rw-r--r--interface-definitions/service-webproxy.xml.in12
-rw-r--r--interface-definitions/snmp.xml.in40
-rw-r--r--interface-definitions/system-config-mgmt.xml.in58
-rw-r--r--interface-definitions/system-conntrack.xml.in4
-rw-r--r--interface-definitions/system-ip.xml.in1
-rw-r--r--interface-definitions/system-ipv6.xml.in1
-rw-r--r--interface-definitions/system-login.xml.in17
-rw-r--r--interface-definitions/system-option.xml.in9
-rw-r--r--interface-definitions/system-proxy.xml.in15
-rw-r--r--interface-definitions/system-sflow.xml.in115
-rw-r--r--interface-definitions/system-time-zone.xml.in2
-rw-r--r--interface-definitions/vpn-ipsec.xml.in93
-rw-r--r--interface-definitions/vpn-l2tp.xml.in9
-rw-r--r--interface-definitions/vpn-openconnect.xml.in25
-rw-r--r--interface-definitions/vpn-pptp.xml.in8
-rw-r--r--interface-definitions/vpn-sstp.xml.in2
-rw-r--r--interface-definitions/vrf.xml.in8
-rw-r--r--interface-definitions/xml-component-version.xml.in2
-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/container.xml.in4
-rw-r--r--op-mode-definitions/counters.xml.in598
-rw-r--r--op-mode-definitions/date.xml.in22
-rw-r--r--op-mode-definitions/dhcp.xml.in32
-rw-r--r--op-mode-definitions/flow-accounting-op.xml.in2
-rw-r--r--op-mode-definitions/force-arp.xml.in6
-rw-r--r--op-mode-definitions/force-ipv6-nd.xml.in2
-rw-r--r--op-mode-definitions/force-ipv6-rd.xml.in2
-rw-r--r--op-mode-definitions/force-mtu-host.xml.in2
-rw-r--r--op-mode-definitions/generate-interfaces-debug-archive.xml.in20
-rw-r--r--op-mode-definitions/generate-openvpn-config-client.xml.in2
-rw-r--r--op-mode-definitions/generate-wireguard.xml.in20
-rw-r--r--op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i6
-rw-r--r--op-mode-definitions/include/bgp/evpn-type-1.xml.i (renamed from op-mode-definitions/include/show-route-summary.xml.i)6
-rw-r--r--op-mode-definitions/include/bgp/evpn-type-2.xml.i8
-rw-r--r--op-mode-definitions/include/bgp/evpn-type-3.xml.i8
-rw-r--r--op-mode-definitions/include/bgp/evpn-type-4.xml.i8
-rw-r--r--op-mode-definitions/include/bgp/evpn-type-5.xml.i8
-rw-r--r--op-mode-definitions/include/bgp/evpn-type-ead.xml.i8
-rw-r--r--op-mode-definitions/include/bgp/evpn-type-es.xml.i8
-rw-r--r--op-mode-definitions/include/bgp/evpn-type-macip.xml.i8
-rw-r--r--op-mode-definitions/include/bgp/evpn-type-multicast.xml.i8
-rw-r--r--op-mode-definitions/include/bgp/evpn-type-prefix.xml.i8
-rw-r--r--op-mode-definitions/include/bgp/martian-next-hop.xml.i15
-rw-r--r--op-mode-definitions/include/bgp/next-hop.xml.i23
-rw-r--r--op-mode-definitions/include/bgp/show-bgp-common.xml.i70
-rw-r--r--op-mode-definitions/include/bgp/show-ip-bgp-common.xml.i6
-rw-r--r--op-mode-definitions/include/isis-common.xml.i67
-rw-r--r--op-mode-definitions/include/ldp-sync.xml.i11
-rw-r--r--op-mode-definitions/include/ospf-common.xml.i29
-rw-r--r--op-mode-definitions/include/ospfv3/interface.xml.i2
-rw-r--r--op-mode-definitions/include/vtysh-generic-interface-tagNode.xml.i11
-rw-r--r--op-mode-definitions/ipv4-route.xml.in2
-rw-r--r--op-mode-definitions/ipv6-route.xml.in4
-rw-r--r--op-mode-definitions/lldp.xml.in12
-rw-r--r--op-mode-definitions/monitor-bandwidth.xml.in2
-rw-r--r--op-mode-definitions/monitor-log.xml.in52
-rw-r--r--op-mode-definitions/monitor-ndp.xml.in2
-rw-r--r--op-mode-definitions/nat.xml.in30
-rw-r--r--op-mode-definitions/nat66.xml.in22
-rw-r--r--op-mode-definitions/nhrp.xml.in4
-rw-r--r--op-mode-definitions/openvpn.xml.in13
-rw-r--r--op-mode-definitions/reset-vpn.xml.in120
-rw-r--r--op-mode-definitions/restart-frr.xml.in6
-rw-r--r--op-mode-definitions/sflow.xml.in15
-rw-r--r--op-mode-definitions/show-arp.xml.in2
-rw-r--r--op-mode-definitions/show-babel.xml.in41
-rw-r--r--op-mode-definitions/show-bgp.xml.in65
-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.in6
-rw-r--r--op-mode-definitions/show-interfaces-tunnel.xml.in8
-rw-r--r--op-mode-definitions/show-interfaces-virtual-ethernet.xml.in8
-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.in39
-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.in56
-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-system.xml.in36
-rw-r--r--op-mode-definitions/show-techsupport_report.xml.in17
-rw-r--r--op-mode-definitions/snmp.xml.in6
-rw-r--r--op-mode-definitions/vpn-ipsec.xml.in162
-rw-r--r--op-mode-definitions/wake-on-lan.xml.in2
-rw-r--r--op-mode-definitions/wireless.xml.in40
-rw-r--r--op-mode-definitions/zone-policy.xml.in6
-rw-r--r--python/setup.py5
-rw-r--r--python/vyos/accel_ppp.py3
-rw-r--r--python/vyos/config_mgmt.py669
-rw-r--r--python/vyos/configdict.py23
-rw-r--r--python/vyos/configdiff.py31
-rw-r--r--python/vyos/configquery.py2
-rw-r--r--python/vyos/configsession.py14
-rw-r--r--python/vyos/configtree.py62
-rw-r--r--python/vyos/configverify.py28
-rw-r--r--python/vyos/cpu.py2
-rw-r--r--python/vyos/defaults.py41
-rw-r--r--python/vyos/ethtool.py12
-rw-r--r--python/vyos/firewall.py31
-rw-r--r--python/vyos/frr.py2
-rw-r--r--python/vyos/ifconfig/ethernet.py12
-rw-r--r--python/vyos/ifconfig/input.py12
-rw-r--r--python/vyos/ifconfig/interface.py14
-rw-r--r--python/vyos/ifconfig/loopback.py2
-rw-r--r--python/vyos/ifconfig/operational.py12
-rw-r--r--python/vyos/ifconfig/tunnel.py14
-rw-r--r--python/vyos/ipsec.py179
-rw-r--r--python/vyos/nat.py3
-rw-r--r--python/vyos/opmode.py71
-rw-r--r--python/vyos/qos/__init__.py28
-rw-r--r--python/vyos/qos/base.py302
-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/template.py50
-rw-r--r--python/vyos/util.py12
-rw-r--r--python/vyos/xml/load.py18
-rw-r--r--schema/interface_definition.rnc2
-rw-r--r--schema/interface_definition.rng15
-rwxr-xr-xscripts/check-pr-title-and-commit-messages.py9
-rwxr-xr-xsmoketest/bin/vyos-smoketest2
-rw-r--r--smoketest/configs/basic-qos194
-rw-r--r--smoketest/configs/dialup-router-medium-vpn24
-rw-r--r--smoketest/configs/egb-igp-route-maps127
-rw-r--r--smoketest/configs/qos-basic56
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py4
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py40
-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_openvpn.py5
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_pppoe.py79
-rwxr-xr-xsmoketest/scripts/cli/test_load_balancing_wan.py (renamed from smoketest/scripts/cli/test_load_balancning_wan.py)28
-rwxr-xr-xsmoketest/scripts/cli/test_nat.py3
-rwxr-xr-xsmoketest/scripts/cli/test_policy_route.py15
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py186
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_isis.py78
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_nhrp.py2
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ospf.py76
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_static.py28
-rwxr-xr-xsmoketest/scripts/cli/test_qos.py547
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcp-relay.py37
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcpv6-relay.py28
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_dynamic.py2
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_forwarding.py27
-rwxr-xr-xsmoketest/scripts/cli/test_service_https.py3
-rwxr-xr-xsmoketest/scripts/cli/test_service_ipoe-server.py93
-rwxr-xr-xsmoketest/scripts/cli/test_service_ntp.py (renamed from smoketest/scripts/cli/test_system_ntp.py)71
-rwxr-xr-xsmoketest/scripts/cli/test_service_pppoe-server.py35
-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_tftp-server.py31
-rwxr-xr-xsmoketest/scripts/cli/test_system_ip.py29
-rwxr-xr-xsmoketest/scripts/cli/test_system_ipv6.py34
-rwxr-xr-xsmoketest/scripts/cli/test_system_login.py23
-rwxr-xr-xsmoketest/scripts/cli/test_system_sflow.py101
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_ipsec.py82
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_openconnect.py15
-rwxr-xr-xsmoketest/scripts/cli/test_vrf.py184
-rwxr-xr-xsmoketest/scripts/system/test_kernel_options.py24
-rwxr-xr-xsmoketest/scripts/system/test_module_load.py5
-rw-r--r--sonar-project.properties8
-rwxr-xr-xsrc/completion/list_interfaces.py54
-rw-r--r--src/completion/list_ipsec_profile_tunnels.py48
-rwxr-xr-xsrc/completion/list_ntp_servers.sh4
-rwxr-xr-xsrc/conf_mode/config_mgmt.py96
-rwxr-xr-xsrc/conf_mode/container.py250
-rwxr-xr-xsrc/conf_mode/dhcp_relay.py15
-rwxr-xr-xsrc/conf_mode/dhcp_server.py4
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py38
-rwxr-xr-xsrc/conf_mode/firewall.py22
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py4
-rwxr-xr-xsrc/conf_mode/high-availability.py30
-rwxr-xr-xsrc/conf_mode/http-api.py7
-rwxr-xr-xsrc/conf_mode/https.py4
-rwxr-xr-xsrc/conf_mode/interfaces-geneve.py10
-rwxr-xr-xsrc/conf_mode/interfaces-input.py70
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py6
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py3
-rwxr-xr-xsrc/conf_mode/interfaces-pseudo-ethernet.py4
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py6
-rwxr-xr-xsrc/conf_mode/interfaces-vxlan.py8
-rwxr-xr-xsrc/conf_mode/interfaces-wwan.py2
-rwxr-xr-xsrc/conf_mode/ntp.py23
-rwxr-xr-xsrc/conf_mode/pki.py5
-rwxr-xr-xsrc/conf_mode/protocols_babel.py163
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py201
-rwxr-xr-xsrc/conf_mode/protocols_eigrp.py12
-rwxr-xr-xsrc/conf_mode/protocols_failover.py2
-rwxr-xr-xsrc/conf_mode/protocols_isis.py14
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py15
-rwxr-xr-xsrc/conf_mode/protocols_ospfv3.py6
-rwxr-xr-xsrc/conf_mode/protocols_static.py16
-rwxr-xr-xsrc/conf_mode/qos.py219
-rwxr-xr-xsrc/conf_mode/service_console-server.py2
-rwxr-xr-xsrc/conf_mode/service_ipoe-server.py115
-rwxr-xr-xsrc/conf_mode/service_monitoring_telegraf.py13
-rwxr-xr-xsrc/conf_mode/service_sla.py6
-rwxr-xr-xsrc/conf_mode/service_webproxy.py2
-rwxr-xr-xsrc/conf_mode/snmp.py11
-rwxr-xr-xsrc/conf_mode/ssh.py2
-rwxr-xr-xsrc/conf_mode/system-ip.py38
-rwxr-xr-xsrc/conf_mode/system-ipv6.py38
-rwxr-xr-xsrc/conf_mode/system-login.py60
-rwxr-xr-xsrc/conf_mode/system-option.py17
-rwxr-xr-xsrc/conf_mode/system_sflow.py124
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py50
-rwxr-xr-xsrc/conf_mode/vpn_l2tp.py12
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py106
-rwxr-xr-xsrc/conf_mode/vpn_pptp.py23
-rwxr-xr-xsrc/conf_mode/vrf.py65
-rw-r--r--[-rwxr-xr-x]src/conf_mode/vrf_vni.py55
-rwxr-xr-xsrc/etc/commit/post-hooks.d/00vyos-sync7
-rw-r--r--src/etc/dhcp/dhclient-enter-hooks.d/99-run-user-hooks5
-rw-r--r--src/etc/dhcp/dhclient-exit-hooks.d/03-vyatta-dhclient-hook (renamed from src/etc/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook)0
-rwxr-xr-xsrc/etc/dhcp/dhclient-exit-hooks.d/98-run-user-hooks5
-rwxr-xr-xsrc/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook (renamed from src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook)2
-rw-r--r--src/etc/modprobe.d/ifb.conf1
-rwxr-xr-xsrc/etc/opennhrp/opennhrp-script.py39
-rw-r--r--src/etc/sysctl.d/30-vyos-router.conf5
-rw-r--r--src/etc/systemd/system/ddclient.service.d/override.conf2
-rw-r--r--src/etc/systemd/system/frr.service.d/override.conf1
-rw-r--r--src/etc/systemd/system/hostapd@.service.d/override.conf2
-rw-r--r--src/etc/systemd/system/keepalived.service.d/override.conf (renamed from src/systemd/keepalived.service)7
-rwxr-xr-xsrc/helpers/vyos-failover.py107
-rwxr-xr-xsrc/migration-scripts/bgp/3-to-464
-rwxr-xr-xsrc/migration-scripts/container/0-to-177
-rwxr-xr-xsrc/migration-scripts/dns-forwarding/3-to-449
-rwxr-xr-xsrc/migration-scripts/firewall/9-to-1080
-rwxr-xr-xsrc/migration-scripts/interfaces/0-to-12
-rwxr-xr-xsrc/migration-scripts/interfaces/1-to-24
-rwxr-xr-xsrc/migration-scripts/interfaces/16-to-172
-rwxr-xr-xsrc/migration-scripts/interfaces/2-to-32
-rwxr-xr-xsrc/migration-scripts/interfaces/20-to-212
-rwxr-xr-xsrc/migration-scripts/interfaces/26-to-2749
-rwxr-xr-xsrc/migration-scripts/interfaces/27-to-2855
-rwxr-xr-xsrc/migration-scripts/interfaces/4-to-52
-rwxr-xr-xsrc/migration-scripts/ipsec/10-to-1183
-rwxr-xr-xsrc/migration-scripts/ipsec/11-to-1253
-rwxr-xr-xsrc/migration-scripts/isis/2-to-363
-rwxr-xr-xsrc/migration-scripts/ntp/1-to-272
-rwxr-xr-xsrc/migration-scripts/ospf/1-to-280
-rwxr-xr-xsrc/migration-scripts/qos/1-to-2148
-rwxr-xr-xsrc/migration-scripts/quagga/10-to-1151
-rwxr-xr-xsrc/migration-scripts/rip/0-to-151
-rwxr-xr-xsrc/migration-scripts/snmp/0-to-16
-rwxr-xr-xsrc/migration-scripts/snmp/2-to-357
-rwxr-xr-xsrc/op_mode/accelppp.py44
-rwxr-xr-xsrc/op_mode/bgp.py3
-rwxr-xr-xsrc/op_mode/config_mgmt.py85
-rwxr-xr-xsrc/op_mode/conntrack.py6
-rwxr-xr-xsrc/op_mode/container.py24
-rwxr-xr-xsrc/op_mode/dhcp.py101
-rwxr-xr-xsrc/op_mode/dns.py4
-rwxr-xr-xsrc/op_mode/dynamic_dns.py92
-rwxr-xr-xsrc/op_mode/generate_interfaces_debug_archive.py115
-rwxr-xr-xsrc/op_mode/generate_public_key_command.py59
-rwxr-xr-xsrc/op_mode/igmp-proxy.py99
-rwxr-xr-xsrc/op_mode/interfaces.py428
-rwxr-xr-xsrc/op_mode/ipsec.py466
-rwxr-xr-xsrc/op_mode/lldp.py149
-rwxr-xr-xsrc/op_mode/lldp_op.py127
-rwxr-xr-xsrc/op_mode/nat.py52
-rwxr-xr-xsrc/op_mode/neighbor.py8
-rwxr-xr-xsrc/op_mode/nhrp.py101
-rwxr-xr-xsrc/op_mode/openvpn.py66
-rwxr-xr-xsrc/op_mode/pki.py3
-rwxr-xr-xsrc/op_mode/reset_vpn.py75
-rwxr-xr-xsrc/op_mode/restart_frr.py2
-rwxr-xr-xsrc/op_mode/route.py39
-rwxr-xr-xsrc/op_mode/sflow.py108
-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_interfaces.py310
-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_openconnect_otp.py2
-rw-r--r--src/op_mode/show_techsupport_report.py303
-rwxr-xr-xsrc/op_mode/show_vpn_ra.py56
-rwxr-xr-xsrc/op_mode/vpn_ipsec.py61
-rwxr-xr-xsrc/op_mode/zone.py215
-rwxr-xr-xsrc/op_mode/zone_policy.py81
-rw-r--r--src/services/api/graphql/bindings.py7
-rw-r--r--src/services/api/graphql/generate/config_session_function.py8
-rwxr-xr-xsrc/services/api/graphql/generate/generate_schema.py26
-rwxr-xr-xsrc/services/api/graphql/generate/schema_from_composite.py123
-rwxr-xr-xsrc/services/api/graphql/generate/schema_from_config_session.py123
-rwxr-xr-xsrc/services/api/graphql/generate/schema_from_op_mode.py185
-rw-r--r--src/services/api/graphql/graphql/auth_token_mutation.py14
-rw-r--r--src/services/api/graphql/graphql/client_op/auth_token.graphql10
-rw-r--r--src/services/api/graphql/graphql/mutations.py3
-rw-r--r--src/services/api/graphql/graphql/queries.py3
-rw-r--r--src/services/api/graphql/libs/op_mode.py22
-rw-r--r--src/services/api/graphql/libs/token_auth.py7
-rw-r--r--src/services/api/graphql/session/errors/op_mode_errors.py10
-rw-r--r--src/services/api/graphql/session/session.py35
-rwxr-xr-xsrc/services/vyos-hostsd2
-rwxr-xr-xsrc/services/vyos-http-api-server52
-rw-r--r--src/tests/test_config_diff.py70
-rw-r--r--src/tests/test_config_parser.py4
-rw-r--r--src/tests/test_configverify.py5
-rwxr-xr-xsrc/validators/timezone4
-rw-r--r--src/xdp/common/common_libbpf.c15
-rw-r--r--src/xdp/common/common_user_bpf_xdp.c47
-rw-r--r--src/xdp/common/xdp_stats_kern.h12
-rw-r--r--src/xdp/xdp_prog_kern.c30
-rw-r--r--tests/data/config.left36
-rw-r--r--tests/data/config.right25
564 files changed, 16555 insertions, 5332 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 61ee1d9ff..47579e1c6 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -19,7 +19,7 @@ the box, please use [x]
## Related Task(s)
<!-- All submitted PRs must be linked to a Task on Phabricator. -->
-* https://phabricator.vyos.net/Txxxx
+* https://vyos.dev/Txxxx
## Component(s) name
<!-- A rather incomplete list of components: ethernet, wireguard, bgp, mpls, ldp, l2tp, dhcp ... -->
diff --git a/.github/workflows/auto-author-assign.yml b/.github/workflows/auto-author-assign.yml
index 13bfd9bb1..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 }}
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 000000000..d77275d38
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,20 @@
+name: Build
+on:
+ push:
+ branches:
+ - current
+ pull_request:
+ types: [opened, synchronize, reopened]
+jobs:
+ sonarcloud:
+ name: SonarCloud
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
+ - name: SonarCloud Scan
+ uses: SonarSource/sonarcloud-github-action@master
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8458d3208..3ff00df88 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -32,7 +32,7 @@ The information is used in three ways:
* Help future maintainers of VyOS (it could be you!) to find out why certain
things have been changed in the codebase or why certain features have been
added
-
+
To make this approach work, every change must be associated with a task number
(prefixed with **T**) and a component. If there is no bug report/feature
request for the changes you are going to make, you have to create a Phabricator
@@ -42,7 +42,7 @@ in your commit message, as shown below:
* `ddclient: T1030: auto create runtime directories`
* `Jenkins: add current Git commit ID to build description`
-If there is no [Phabricator](https://phabricator.vyos.net) reference in the
+If there is no [Phabricator](https://vyos.dev) reference in the
commits of your pull request, we have to ask you to amend the commit message.
Otherwise we will have to reject it.
@@ -126,7 +126,7 @@ also contain information that is helpful for the development team.
### Reporting
In order to open up a bug-report/feature request you need to create yourself
-an account on [Phabricator](https://phabricator.vyos.net). On the left
+an account on [Phabricator](https://vyos.dev). On the left
side of the specific project (VyOS 1.2 or VyOS 1.3) you will find quick-links
for opening a bug-report/feature request.
@@ -141,7 +141,7 @@ for opening a bug-report/feature request.
You have an idea of how to make VyOS better or you are in need of a specific
feature which all users of VyOS would benefit from? To send a feature request
-please search [Phabricator](https://phabricator.vyos.net) if there is already a
+please search [Phabricator](https://vyos.dev) if there is already a
request pending. You can enhance it or if you don't find one, create a new one
by use the quick link in the left side under the specific project.
diff --git a/Makefile b/Makefile
index 0a968563c..e130bec70 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
@@ -70,9 +65,9 @@ op_mode_definitions: $(op_xml_obj)
rm -f $(OP_TMPL_DIR)/generate/node.def
rm -f $(OP_TMPL_DIR)/monitor/node.def
rm -f $(OP_TMPL_DIR)/set/node.def
- rm -f $(OP_TMPL_DIR)/show/interfaces/node.def
rm -f $(OP_TMPL_DIR)/show/node.def
rm -f $(OP_TMPL_DIR)/show/system/node.def
+ rm -f $(OP_TMPL_DIR)/show/tech-support/node.def
# XXX: ping and traceroute must be able to recursivly call itself as the
# options are provided from the script itself
diff --git a/README.md b/README.md
index 0e7daa491..cc6c4e319 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# 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)
+[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=vyos_vyos-1x&metric=coverage)](https://sonarcloud.io/component_measures?id=vyos_vyos-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)
VyOS 1.1.x had its codebase split into way too many submodules for no good
diff --git a/data/config-mode-dependencies.json b/data/config-mode-dependencies.json
index 9e943ba2c..ccee359d1 100644
--- a/data/config-mode-dependencies.json
+++ b/data/config-mode-dependencies.json
@@ -8,5 +8,25 @@
"ipsec": ["vpn_ipsec"],
"openconnect": ["vpn_openconnect"],
"sstp": ["vpn_sstp"]
+ },
+ "qos": {
+ "bonding": ["interfaces-bonding"],
+ "bridge": ["interfaces-bridge"],
+ "dummy": ["interfaces-dummy"],
+ "ethernet": ["interfaces-ethernet"],
+ "geneve": ["interfaces-geneve"],
+ "input": ["interfaces-input"],
+ "l2tpv3": ["interfaces-l2tpv3"],
+ "loopback": ["interfaces-loopback"],
+ "macsec": ["interfaces-macsec"],
+ "openvpn": ["interfaces-openvpn"],
+ "pppoe": ["interfaces-pppoe"],
+ "pseudo-ethernet": ["interfaces-pseudo-ethernet"],
+ "tunnel": ["interfaces-tunnel"],
+ "vti": ["interfaces-vti"],
+ "vxlan": ["interfaces-vxlan"],
+ "wireguard": ["interfaces-wireguard"],
+ "wireless": ["interfaces-wireless"],
+ "wwan": ["interfaces-wwan"]
}
}
diff --git a/data/configd-include.json b/data/configd-include.json
index 648655a8b..456211caa 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -74,6 +74,7 @@
"system-logs.py",
"system-option.py",
"system-proxy.py",
+"system_sflow.py",
"system_sysctl.py",
"system-syslog.py",
"system-timezone.py",
diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json
index a69cf55e9..c7c67198e 100644
--- a/data/op-mode-standardized.json
+++ b/data/op-mode-standardized.json
@@ -2,22 +2,28 @@
"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",
+"nhrp.py",
"openconnect.py",
"openvpn.py",
+"reset_vpn.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/config_ip_pool.j2 b/data/templates/accel-ppp/config_ip_pool.j2
index 0bef4ad69..f7511e445 100644
--- a/data/templates/accel-ppp/config_ip_pool.j2
+++ b/data/templates/accel-ppp/config_ip_pool.j2
@@ -11,4 +11,14 @@ gw-ip-address={{ gateway_address }}
{{ subnet }}
{% endfor %}
{% endif %}
-{% endif %}
+{% if client_ip_pool.name is vyos_defined %}
+{% for pool, pool_config in client_ip_pool.name.items() %}
+{% if pool_config.subnet is vyos_defined %}
+{{ pool_config.subnet }},name={{ pool }}
+{% endif %}
+{% if pool_config.gateway_address is vyos_defined %}
+gw-ip-address={{ pool_config.gateway_address }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% endif %} \ No newline at end of file
diff --git a/data/templates/accel-ppp/config_shaper_radius.j2 b/data/templates/accel-ppp/config_shaper_radius.j2
index 942cdf132..0cf6a6a92 100644
--- a/data/templates/accel-ppp/config_shaper_radius.j2
+++ b/data/templates/accel-ppp/config_shaper_radius.j2
@@ -1,7 +1,7 @@
-{% if authentication.mode is vyos_defined('radius') %}
-{% if authentication.radius.rate_limit.enable is vyos_defined %}
+{% if authentication.mode is vyos_defined('radius') or shaper is vyos_defined %}
[shaper]
verbose=1
+{% if authentication.radius.rate_limit.enable is vyos_defined %}
attr={{ authentication.radius.rate_limit.attribute }}
{% if authentication.radius.rate_limit.vendor is vyos_defined %}
vendor={{ authentication.radius.rate_limit.vendor }}
@@ -10,4 +10,10 @@ vendor={{ authentication.radius.rate_limit.vendor }}
rate-multiplier={{ authentication.radius.rate_limit.multiplier }}
{% endif %}
{% endif %}
-{% endif %}
+{% if shaper is vyos_defined %}
+{% if shaper.fwmark is vyos_defined %}
+fwmark={{ shaper.fwmark }}
+down-limiter=htb
+{% endif %}
+{% endif %}
+{% endif %} \ No newline at end of file
diff --git a/data/templates/accel-ppp/ipoe.config.j2 b/data/templates/accel-ppp/ipoe.config.j2
index 99227ea33..add3dc7e4 100644
--- a/data/templates/accel-ppp/ipoe.config.j2
+++ b/data/templates/accel-ppp/ipoe.config.j2
@@ -25,7 +25,7 @@ verbose=1
{% for iface, iface_config in interface.items() %}
{% set tmp = 'interface=' %}
{% if iface_config.vlan is vyos_defined %}
-{% set tmp = tmp ~ 're:' ~ iface ~ '\.\d+' %}
+{% set tmp = tmp ~ 're:^' ~ iface ~ '\.' ~ iface_config.vlan | range_to_regex ~ '$' %}
{% else %}
{% set tmp = tmp ~ iface %}
{% endif %}
@@ -35,38 +35,49 @@ verbose=1
{% elif iface_config.network is vyos_defined('vlan') %}
{% set shared = 'shared=0,' %}
{% endif %}
-{{ tmp }},{{ shared }}mode={{ iface_config.mode | upper }},ifcfg=1,range={{ iface_config.client_subnet }},start=dhcpv4,ipv6=1
+{% set range = 'range=' ~ iface_config.client_subnet ~ ',' if iface_config.client_subnet is vyos_defined else '' %}
+{{ tmp }},{{ shared }}mode={{ iface_config.mode | upper }},ifcfg=1,{{ range }}start=dhcpv4,ipv6=1
+{% if iface_config.vlan is vyos_defined %}
+vlan-mon={{ iface }},{{ iface_config.vlan | join(',') }}
+{% endif %}
{% endfor %}
{% endif %}
{% if authentication.mode is vyos_defined('noauth') %}
noauth=1
-{% if client_ip_pool.name is vyos_defined %}
+{% elif authentication.mode is vyos_defined('local') %}
+username=ifname
+password=csid
+{% endif %}
+{% if client_ip_pool.name is vyos_defined %}
+{% if first_named_pool is vyos_defined %}
+ip-pool={{ first_named_pool }}
+{% else %}
{% for pool, pool_options in client_ip_pool.name.items() %}
-{% if pool_options.subnet is vyos_defined and pool_options.gateway_address is vyos_defined %}
+{% if pool_options.subnet is vyos_defined %}
ip-pool={{ pool }}
-gw-ip-address={{ pool_options.gateway_address }}/{{ pool_options.subnet.split('/')[1] }}
{% endif %}
{% endfor %}
{% endif %}
-{% elif authentication.mode is vyos_defined('local') %}
-username=ifname
-password=csid
+{% for pool, pool_options in client_ip_pool.name.items() %}
+{% if pool_options.gateway_address is vyos_defined %}
+gw-ip-address={{ pool_options.gateway_address }}/{{ pool_options.subnet.split('/')[1] }}
+{% endif %}
+{% endfor %}
{% endif %}
proxy-arp=1
-{% for interface in interfaces %}
-{% if (interface.shared == '0') and (interface.vlan_mon) %}
-vlan-mon={{ interface.name }},{{ interface.vlan_mon | join(',') }}
-{% endif %}
-{% endfor %}
-
-{% if client_ip_pool.name is vyos_defined %}
+{% if ordered_named_pools is vyos_defined %}
[ip-pool]
-{% for pool, pool_options in client_ip_pool.name.items() %}
-{% if pool_options.subnet is vyos_defined and pool_options.gateway_address is vyos_defined %}
-{{ pool_options.subnet }},name={{ pool }}
+{% for p in ordered_named_pools %}
+{% for pool, pool_options in p.items() %}
+{% set next_named_pool = ',next=' ~ pool_options.next_pool if pool_options.next_pool is vyos_defined else '' %}
+{{ pool_options.subnet }},name={{ pool }}{{ next_named_pool }}
+{% endfor %}
+{% endfor %}
+{% for p in ordered_named_pools %}
+{% for pool, pool_options in p.items() %}
gw-ip-address={{ pool_options.gateway_address }}/{{ pool_options.subnet.split('/')[1] }}
-{% endif %}
+{% endfor %}
{% endfor %}
{% endif %}
diff --git a/data/templates/accel-ppp/l2tp.config.j2 b/data/templates/accel-ppp/l2tp.config.j2
index 3d1e835a9..5914fd375 100644
--- a/data/templates/accel-ppp/l2tp.config.j2
+++ b/data/templates/accel-ppp/l2tp.config.j2
@@ -126,7 +126,13 @@ ipv6={{ ppp_ipv6 }}
{% else %}
{{ 'ipv6=allow' if client_ipv6_pool_configured else '' }}
{% endif %}
-
+{% if ppp_ipv6_intf_id is vyos_defined %}
+ipv6-intf-id={{ ppp_ipv6_intf_id }}
+{% endif %}
+{% if ppp_ipv6_peer_intf_id is vyos_defined %}
+ipv6-peer-intf-id={{ ppp_ipv6_peer_intf_id }}
+{% endif %}
+ipv6-accept-peer-intf-id={{ "1" if ppp_ipv6_accept_peer_intf_id else "0" }}
{% if client_ipv6_pool %}
[ipv6-pool]
diff --git a/data/templates/accel-ppp/pppoe.config.j2 b/data/templates/accel-ppp/pppoe.config.j2
index f4129d3e2..dd53edd28 100644
--- a/data/templates/accel-ppp/pppoe.config.j2
+++ b/data/templates/accel-ppp/pppoe.config.j2
@@ -30,6 +30,11 @@ syslog=accel-pppoe,daemon
copy=1
level=5
+{% if authentication.mode is vyos_defined("noauth") %}
+[auth]
+noauth=1
+{% endif %}
+
{% if snmp.master_agent is vyos_defined %}
[snmp]
master=1
@@ -69,8 +74,6 @@ ccp={{ "1" if ppp_options.ccp is vyos_defined else "0" }}
unit-preallocate={{ "1" if authentication.radius.preallocate_vif is vyos_defined else "0" }}
{% if ppp_options.min_mtu is vyos_defined %}
min-mtu={{ ppp_options.min_mtu }}
-{% else %}
-min-mtu={{ mtu }}
{% endif %}
{% if ppp_options.mru is vyos_defined %}
mru={{ ppp_options.mru }}
@@ -135,6 +138,22 @@ pado-delay={{ pado_delay_param.value }}
called-sid={{ authentication.radius.called_sid_format }}
{% endif %}
+{% if authentication.mode is vyos_defined("local") or authentication.mode is vyos_defined("noauth") %}
+{% if authentication.mode is vyos_defined("noauth") %}
+noauth=1
+{% endif %}
+{% if client_ip_pool.name is vyos_defined %}
+{% for pool, pool_config in client_ip_pool.name.items() %}
+{% if pool_config.subnet is vyos_defined %}
+ip-pool={{ pool }}
+{% endif %}
+{% if pool_config.gateway_address is vyos_defined %}
+gw-ip-address={{ pool_config.gateway_address }}/{{ pool_config.subnet.split('/')[1] }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% endif %}
+
{% if limits is vyos_defined %}
[connlimit]
{% if limits.connection_limit is vyos_defined %}
diff --git a/data/templates/accel-ppp/pptp.config.j2 b/data/templates/accel-ppp/pptp.config.j2
index 442830b6b..78a629d2d 100644
--- a/data/templates/accel-ppp/pptp.config.j2
+++ b/data/templates/accel-ppp/pptp.config.j2
@@ -93,10 +93,15 @@ bind={{ radius_source_address }}
gw-ip-address={{ gw_ip }}
{% endif %}
-{% if radius_shaper_attr %}
+{% if radius_shaper_enable %}
[shaper]
verbose=1
+{% if radius_shaper_attr %}
attr={{ radius_shaper_attr }}
+{% endif %}
+{% if radius_shaper_multiplier %}
+rate-multiplier={{ radius_shaper_multiplier }}
+{% endif %}
{% if radius_shaper_vendor %}
vendor={{ radius_shaper_vendor }}
{% endif %}
diff --git a/data/templates/chrony/chrony.conf.j2 b/data/templates/chrony/chrony.conf.j2
new file mode 100644
index 000000000..7a36fe69d
--- /dev/null
+++ b/data/templates/chrony/chrony.conf.j2
@@ -0,0 +1,60 @@
+### 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
+ntsdumpdir /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 {{ 'nts' if config.nts is vyos_defined }} {{ '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 %}
+{% else %}
+deny all
+{% endif %}
+
+{% 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..b8935ae76 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
+ProcSubset=all
+ProtectControlGroups=no
diff --git a/data/templates/container/containers.conf.j2 b/data/templates/container/containers.conf.j2
new file mode 100644
index 000000000..c635ca213
--- /dev/null
+++ b/data/templates/container/containers.conf.j2
@@ -0,0 +1,709 @@
+### Autogenerated by container.py ###
+
+# The containers configuration file specifies all of the available configuration
+# command-line options/flags for container engine tools like Podman & Buildah,
+# but in a TOML format that can be easily modified and versioned.
+
+# Please refer to containers.conf(5) for details of all configuration options.
+# Not all container engines implement all of the options.
+# All of the options have hard coded defaults and these options will override
+# the built in defaults. Users can then override these options via the command
+# line. Container engines will read containers.conf files in up to three
+# locations in the following order:
+# 1. /usr/share/containers/containers.conf
+# 2. /etc/containers/containers.conf
+# 3. $HOME/.config/containers/containers.conf (Rootless containers ONLY)
+# Items specified in the latter containers.conf, if they exist, override the
+# previous containers.conf settings, or the default settings.
+
+[containers]
+
+# List of annotation. Specified as
+# "key = value"
+# If it is empty or commented out, no annotations will be added
+#
+#annotations = []
+
+# Used to change the name of the default AppArmor profile of container engine.
+#
+#apparmor_profile = "container-default"
+
+# The hosts entries from the base hosts file are added to the containers hosts
+# file. This must be either an absolute path or as special values "image" which
+# uses the hosts file from the container image or "none" which means
+# no base hosts file is used. The default is "" which will use /etc/hosts.
+#
+#base_hosts_file = ""
+
+# Default way to to create a cgroup namespace for the container
+# Options are:
+# `private` Create private Cgroup Namespace for the container.
+# `host` Share host Cgroup Namespace with the container.
+#
+#cgroupns = "private"
+
+# Control container cgroup configuration
+# Determines whether the container will create CGroups.
+# Options are:
+# `enabled` Enable cgroup support within container
+# `disabled` Disable cgroup support, will inherit cgroups from parent
+# `no-conmon` Do not create a cgroup dedicated to conmon.
+#
+#cgroups = "enabled"
+
+# List of default capabilities for containers. If it is empty or commented out,
+# the default capabilities defined in the container engine will be added.
+#
+default_capabilities = [
+ "CHOWN",
+ "DAC_OVERRIDE",
+ "FOWNER",
+ "FSETID",
+ "KILL",
+ "NET_BIND_SERVICE",
+ "SETFCAP",
+ "SETGID",
+ "SETPCAP",
+ "SETUID",
+ "SYS_CHROOT"
+]
+
+# A list of sysctls to be set in containers by default,
+# specified as "name=value",
+# for example:"net.ipv4.ping_group_range=0 0".
+#
+default_sysctls = [
+ "net.ipv4.ping_group_range=0 0",
+]
+
+# A list of ulimits to be set in containers by default, specified as
+# "<ulimit name>=<soft limit>:<hard limit>", for example:
+# "nofile=1024:2048"
+# See setrlimit(2) for a list of resource names.
+# Any limit not specified here will be inherited from the process launching the
+# container engine.
+# Ulimits has limits for non privileged container engines.
+#
+#default_ulimits = [
+# "nofile=1280:2560",
+#]
+
+# List of devices. Specified as
+# "<device-on-host>:<device-on-container>:<permissions>", for example:
+# "/dev/sdc:/dev/xvdc:rwm".
+# If it is empty or commented out, only the default devices will be used
+#
+#devices = []
+
+# List of default DNS options to be added to /etc/resolv.conf inside of the container.
+#
+#dns_options = []
+
+# List of default DNS search domains to be added to /etc/resolv.conf inside of the container.
+#
+#dns_searches = []
+
+# Set default DNS servers.
+# This option can be used to override the DNS configuration passed to the
+# container. The special value "none" can be specified to disable creation of
+# /etc/resolv.conf in the container.
+# The /etc/resolv.conf file in the image will be used without changes.
+#
+#dns_servers = []
+
+# Environment variable list for the conmon process; used for passing necessary
+# environment variables to conmon or the runtime.
+#
+#env = [
+# "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+# "TERM=xterm",
+#]
+
+# Pass all host environment variables into the container.
+#
+#env_host = false
+
+# Set the ip for the host.containers.internal entry in the containers /etc/hosts
+# file. This can be set to "none" to disable adding this entry. By default it
+# will automatically choose the host ip.
+#
+# NOTE: When using podman machine this entry will never be added to the containers
+# hosts file instead the gvproxy dns resolver will resolve this hostname. Therefore
+# it is not possible to disable the entry in this case.
+#
+#host_containers_internal_ip = ""
+
+# Default proxy environment variables passed into the container.
+# The environment variables passed in include:
+# http_proxy, https_proxy, ftp_proxy, no_proxy, and the upper case versions of
+# these. This option is needed when host system uses a proxy but container
+# should not use proxy. Proxy environment variables specified for the container
+# in any other way will override the values passed from the host.
+#
+#http_proxy = true
+
+# Run an init inside the container that forwards signals and reaps processes.
+#
+#init = false
+
+# Container init binary, if init=true, this is the init binary to be used for containers.
+#
+#init_path = "/usr/libexec/podman/catatonit"
+
+# Default way to to create an IPC namespace (POSIX SysV IPC) for the container
+# Options are:
+# "host" Share host IPC Namespace with the container.
+# "none" Create shareable IPC Namespace for the container without a private /dev/shm.
+# "private" Create private IPC Namespace for the container, other containers are not allowed to share it.
+# "shareable" Create shareable IPC Namespace for the container.
+#
+#ipcns = "shareable"
+
+# keyring tells the container engine whether to create
+# a kernel keyring for use within the container.
+#
+#keyring = true
+
+# label tells the container engine whether to use container separation using
+# MAC(SELinux) labeling or not.
+# The label flag is ignored on label disabled systems.
+#
+#label = true
+
+# Logging driver for the container. Available options: k8s-file and journald.
+#
+#log_driver = "k8s-file"
+
+# Maximum size allowed for the container log file. Negative numbers indicate
+# that no size limit is imposed. If positive, it must be >= 8192 to match or
+# exceed conmon's read buffer. The file is truncated and re-opened so the
+# limit is never exceeded.
+#
+#log_size_max = -1
+
+# Specifies default format tag for container log messages.
+# This is useful for creating a specific tag for container log messages.
+# Containers logs default to truncated container ID as a tag.
+#
+#log_tag = ""
+
+# Default way to to create a Network namespace for the container
+# Options are:
+# `private` Create private Network Namespace for the container.
+# `host` Share host Network Namespace with the container.
+# `none` Containers do not use the network
+#
+#netns = "private"
+
+# Create /etc/hosts for the container. By default, container engine manage
+# /etc/hosts, automatically adding the container's own IP address.
+#
+#no_hosts = false
+
+# Default way to to create a PID namespace for the container
+# Options are:
+# `private` Create private PID Namespace for the container.
+# `host` Share host PID Namespace with the container.
+#
+#pidns = "private"
+
+# Maximum number of processes allowed in a container.
+#
+#pids_limit = 2048
+
+# Copy the content from the underlying image into the newly created volume
+# when the container is created instead of when it is started. If false,
+# the container engine will not copy the content until the container is started.
+# Setting it to true may have negative performance implications.
+#
+#prepare_volume_on_create = false
+
+# Path to the seccomp.json profile which is used as the default seccomp profile
+# for the runtime.
+#
+#seccomp_profile = "/usr/share/containers/seccomp.json"
+
+# Size of /dev/shm. Specified as <number><unit>.
+# Unit is optional, values:
+# b (bytes), k (kilobytes), m (megabytes), or g (gigabytes).
+# If the unit is omitted, the system uses bytes.
+#
+#shm_size = "65536k"
+
+# Set timezone in container. Takes IANA timezones as well as "local",
+# which sets the timezone in the container to match the host machine.
+#
+#tz = ""
+
+# Set umask inside the container
+#
+#umask = "0022"
+
+# Default way to to create a User namespace for the container
+# Options are:
+# `auto` Create unique User Namespace for the container.
+# `host` Share host User Namespace with the container.
+#
+#userns = "host"
+
+# Number of UIDs to allocate for the automatic container creation.
+# UIDs are allocated from the "container" UIDs listed in
+# /etc/subuid & /etc/subgid
+#
+#userns_size = 65536
+
+# Default way to to create a UTS namespace for the container
+# Options are:
+# `private` Create private UTS Namespace for the container.
+# `host` Share host UTS Namespace with the container.
+#
+#utsns = "private"
+
+# List of volumes. Specified as
+# "<directory-on-host>:<directory-in-container>:<options>", for example:
+# "/db:/var/lib/db:ro".
+# If it is empty or commented out, no volumes will be added
+#
+#volumes = []
+
+[secrets]
+#driver = "file"
+
+[secrets.opts]
+#root = "/example/directory"
+
+[network]
+
+# Network backend determines what network driver will be used to set up and tear down container networks.
+# Valid values are "cni" and "netavark".
+# The default value is empty which means that it will automatically choose CNI or netavark. If there are
+# already containers/images or CNI networks preset it will choose CNI.
+#
+# Before changing this value all containers must be stopped otherwise it is likely that
+# iptables rules and network interfaces might leak on the host. A reboot will fix this.
+#
+network_backend = "netavark"
+
+# Path to directory where CNI plugin binaries are located.
+#
+#cni_plugin_dirs = [
+# "/usr/local/libexec/cni",
+# "/usr/libexec/cni",
+# "/usr/local/lib/cni",
+# "/usr/lib/cni",
+# "/opt/cni/bin",
+#]
+
+# The network name of the default network to attach pods to.
+#
+#default_network = "podman"
+
+# The default subnet for the default network given in default_network.
+# If a network with that name does not exist, a new network using that name and
+# this subnet will be created.
+# Must be a valid IPv4 CIDR prefix.
+#
+#default_subnet = "10.88.0.0/16"
+
+# DefaultSubnetPools is a list of subnets and size which are used to
+# allocate subnets automatically for podman network create.
+# It will iterate through the list and will pick the first free subnet
+# with the given size. This is only used for ipv4 subnets, ipv6 subnets
+# are always assigned randomly.
+#
+#default_subnet_pools = [
+# {"base" = "10.89.0.0/16", "size" = 24},
+# {"base" = "10.90.0.0/15", "size" = 24},
+# {"base" = "10.92.0.0/14", "size" = 24},
+# {"base" = "10.96.0.0/11", "size" = 24},
+# {"base" = "10.128.0.0/9", "size" = 24},
+#]
+
+# Path to the directory where network configuration files are located.
+# For the CNI backend the default is "/etc/cni/net.d" as root
+# and "$HOME/.config/cni/net.d" as rootless.
+# For the netavark backend "/etc/containers/networks" is used as root
+# and "$graphroot/networks" as rootless.
+#
+#network_config_dir = "/etc/cni/net.d/"
+
+# Port to use for dns forwarding daemon with netavark in rootful bridge
+# mode and dns enabled.
+# Using an alternate port might be useful if other dns services should
+# run on the machine.
+#
+#dns_bind_port = 53
+
+[engine]
+# Index to the active service
+#
+#active_service = production
+
+# The compression format to use when pushing an image.
+# Valid options are: `gzip`, `zstd` and `zstd:chunked`.
+#
+#compression_format = "gzip"
+
+
+# Cgroup management implementation used for the runtime.
+# Valid options "systemd" or "cgroupfs"
+#
+#cgroup_manager = "systemd"
+
+# Environment variables to pass into conmon
+#
+#conmon_env_vars = [
+# "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+#]
+
+# Paths to look for the conmon container manager binary
+#
+#conmon_path = [
+# "/usr/libexec/podman/conmon",
+# "/usr/local/libexec/podman/conmon",
+# "/usr/local/lib/podman/conmon",
+# "/usr/bin/conmon",
+# "/usr/sbin/conmon",
+# "/usr/local/bin/conmon",
+# "/usr/local/sbin/conmon"
+#]
+
+# Enforces using docker.io for completing short names in Podman's compatibility
+# REST API. Note that this will ignore unqualified-search-registries and
+# short-name aliases defined in containers-registries.conf(5).
+#compat_api_enforce_docker_hub = true
+
+# Specify the keys sequence used to detach a container.
+# Format is a single character [a-Z] or a comma separated sequence of
+# `ctrl-<value>`, where `<value>` is one of:
+# `a-z`, `@`, `^`, `[`, `\`, `]`, `^` or `_`
+#
+#detach_keys = "ctrl-p,ctrl-q"
+
+# Determines whether engine will reserve ports on the host when they are
+# forwarded to containers. When enabled, when ports are forwarded to containers,
+# ports are held open by as long as the container is running, ensuring that
+# they cannot be reused by other programs on the host. However, this can cause
+# significant memory usage if a container has many ports forwarded to it.
+# Disabling this can save memory.
+#
+#enable_port_reservation = true
+
+# Environment variables to be used when running the container engine (e.g., Podman, Buildah).
+# For example "http_proxy=internal.proxy.company.com".
+# Note these environment variables will not be used within the container.
+# Set the env section under [containers] table, if you want to set environment variables for the container.
+#
+#env = []
+
+# Define where event logs will be stored, when events_logger is "file".
+#events_logfile_path=""
+
+# Sets the maximum size for events_logfile_path.
+# The size can be b (bytes), k (kilobytes), m (megabytes), or g (gigabytes).
+# The format for the size is `<number><unit>`, e.g., `1b` or `3g`.
+# If no unit is included then the size will be read in bytes.
+# When the limit is exceeded, the logfile will be rotated and the old one will be deleted.
+# If the maximum size is set to 0, then no limit will be applied,
+# and the logfile will not be rotated.
+#events_logfile_max_size = "1m"
+
+# Selects which logging mechanism to use for container engine events.
+# Valid values are `journald`, `file` and `none`.
+#
+#events_logger = "journald"
+
+# A is a list of directories which are used to search for helper binaries.
+#
+#helper_binaries_dir = [
+# "/usr/local/libexec/podman",
+# "/usr/local/lib/podman",
+# "/usr/libexec/podman",
+# "/usr/lib/podman",
+#]
+
+# Path to OCI hooks directories for automatically executed hooks.
+#
+#hooks_dir = [
+# "/usr/share/containers/oci/hooks.d",
+#]
+
+# Manifest Type (oci, v2s2, or v2s1) to use when pulling, pushing, building
+# container images. By default image pulled and pushed match the format of the
+# source image. Building/committing defaults to OCI.
+#
+#image_default_format = ""
+
+# Default transport method for pulling and pushing for images
+#
+#image_default_transport = "docker://"
+
+# Maximum number of image layers to be copied (pulled/pushed) simultaneously.
+# Not setting this field, or setting it to zero, will fall back to containers/image defaults.
+#
+#image_parallel_copies = 0
+
+# Tells container engines how to handle the builtin image volumes.
+# * bind: An anonymous named volume will be created and mounted
+# into the container.
+# * tmpfs: The volume is mounted onto the container as a tmpfs,
+# which allows users to create content that disappears when
+# the container is stopped.
+# * ignore: All volumes are just ignored and no action is taken.
+#
+#image_volume_mode = ""
+
+# Default command to run the infra container
+#
+#infra_command = "/pause"
+
+# Infra (pause) container image name for pod infra containers. When running a
+# pod, we start a `pause` process in a container to hold open the namespaces
+# associated with the pod. This container does nothing other then sleep,
+# reserving the pods resources for the lifetime of the pod. By default container
+# engines run a builtin container using the pause executable. If you want override
+# specify an image to pull.
+#
+#infra_image = ""
+
+# Specify the locking mechanism to use; valid values are "shm" and "file".
+# Change the default only if you are sure of what you are doing, in general
+# "file" is useful only on platforms where cgo is not available for using the
+# faster "shm" lock type. You may need to run "podman system renumber" after
+# you change the lock type.
+#
+#lock_type** = "shm"
+
+# MultiImageArchive - if true, the container engine allows for storing archives
+# (e.g., of the docker-archive transport) with multiple images. By default,
+# Podman creates single-image archives.
+#
+#multi_image_archive = "false"
+
+# Default engine namespace
+# If engine is joined to a namespace, it will see only containers and pods
+# that were created in the same namespace, and will create new containers and
+# pods in that namespace.
+# The default namespace is "", which corresponds to no namespace. When no
+# namespace is set, all containers and pods are visible.
+#
+#namespace = ""
+
+# Path to the slirp4netns binary
+#
+#network_cmd_path = ""
+
+# Default options to pass to the slirp4netns binary.
+# Valid options values are:
+#
+# - allow_host_loopback=true|false: Allow the slirp4netns to reach the host loopback IP (`10.0.2.2`).
+# Default is false.
+# - mtu=MTU: Specify the MTU to use for this network. (Default is `65520`).
+# - cidr=CIDR: Specify ip range to use for this network. (Default is `10.0.2.0/24`).
+# - enable_ipv6=true|false: Enable IPv6. Default is true. (Required for `outbound_addr6`).
+# - outbound_addr=INTERFACE: Specify the outbound interface slirp should bind to (ipv4 traffic only).
+# - outbound_addr=IPv4: Specify the outbound ipv4 address slirp should bind to.
+# - outbound_addr6=INTERFACE: Specify the outbound interface slirp should bind to (ipv6 traffic only).
+# - outbound_addr6=IPv6: Specify the outbound ipv6 address slirp should bind to.
+# - port_handler=rootlesskit: Use rootlesskit for port forwarding. Default.
+# Note: Rootlesskit changes the source IP address of incoming packets to a IP address in the container
+# network namespace, usually `10.0.2.100`. If your application requires the real source IP address,
+# e.g. web server logs, use the slirp4netns port handler. The rootlesskit port handler is also used for
+# rootless containers when connected to user-defined networks.
+# - port_handler=slirp4netns: Use the slirp4netns port forwarding, it is slower than rootlesskit but
+# preserves the correct source IP address. This port handler cannot be used for user-defined networks.
+#
+#network_cmd_options = []
+
+# Whether to use chroot instead of pivot_root in the runtime
+#
+#no_pivot_root = false
+
+# Number of locks available for containers and pods.
+# If this is changed, a lock renumber must be performed (e.g. with the
+# 'podman system renumber' command).
+#
+#num_locks = 2048
+
+# Set the exit policy of the pod when the last container exits.
+#pod_exit_policy = "continue"
+
+# Whether to pull new image before running a container
+#
+#pull_policy = "missing"
+
+# Indicates whether the application should be running in remote mode. This flag modifies the
+# --remote option on container engines. Setting the flag to true will default
+# `podman --remote=true` for access to the remote Podman service.
+#
+#remote = false
+
+# Default OCI runtime
+#
+#runtime = "crun"
+
+# List of the OCI runtimes that support --format=json. When json is supported
+# engine will use it for reporting nicer errors.
+#
+#runtime_supports_json = ["crun", "runc", "kata", "runsc", "krun"]
+
+# List of the OCI runtimes that supports running containers with KVM Separation.
+#
+#runtime_supports_kvm = ["kata", "krun"]
+
+# List of the OCI runtimes that supports running containers without cgroups.
+#
+#runtime_supports_nocgroups = ["crun", "krun"]
+
+# Default location for storing temporary container image content. Can be overridden with the TMPDIR environment
+# variable. If you specify "storage", then the location of the
+# container/storage tmp directory will be used.
+# image_copy_tmp_dir="/var/tmp"
+
+# Number of seconds to wait without a connection
+# before the `podman system service` times out and exits
+#
+#service_timeout = 5
+
+# Directory for persistent engine files (database, etc)
+# By default, this will be configured relative to where the containers/storage
+# stores containers
+# Uncomment to change location from this default
+#
+#static_dir = "/var/lib/containers/storage/libpod"
+
+# Number of seconds to wait for container to exit before sending kill signal.
+#
+#stop_timeout = 10
+
+# Number of seconds to wait before exit command in API process is given to.
+# This mimics Docker's exec cleanup behaviour, where the default is 5 minutes (value is in seconds).
+#
+#exit_command_delay = 300
+
+# map of service destinations
+#
+#[service_destinations]
+# [service_destinations.production]
+# URI to access the Podman service
+# Examples:
+# rootless "unix://run/user/$UID/podman/podman.sock" (Default)
+# rootful "unix://run/podman/podman.sock (Default)
+# remote rootless ssh://engineering.lab.company.com/run/user/1000/podman/podman.sock
+# remote rootful ssh://root@10.10.1.136:22/run/podman/podman.sock
+#
+# uri = "ssh://user@production.example.com/run/user/1001/podman/podman.sock"
+# Path to file containing ssh identity key
+# identity = "~/.ssh/id_rsa"
+
+# Directory for temporary files. Must be tmpfs (wiped after reboot)
+#
+#tmp_dir = "/run/libpod"
+
+# Directory for libpod named volumes.
+# By default, this will be configured relative to where containers/storage
+# stores containers.
+# Uncomment to change location from this default.
+#
+#volume_path = "/var/lib/containers/storage/volumes"
+
+# Default timeout (in seconds) for volume plugin operations.
+# Plugins are external programs accessed via a REST API; this sets a timeout
+# for requests to that API.
+# A value of 0 is treated as no timeout.
+#volume_plugin_timeout = 5
+
+# Paths to look for a valid OCI runtime (crun, runc, kata, runsc, krun, etc)
+[engine.runtimes]
+#crun = [
+# "/usr/bin/crun",
+# "/usr/sbin/crun",
+# "/usr/local/bin/crun",
+# "/usr/local/sbin/crun",
+# "/sbin/crun",
+# "/bin/crun",
+# "/run/current-system/sw/bin/crun",
+#]
+
+#kata = [
+# "/usr/bin/kata-runtime",
+# "/usr/sbin/kata-runtime",
+# "/usr/local/bin/kata-runtime",
+# "/usr/local/sbin/kata-runtime",
+# "/sbin/kata-runtime",
+# "/bin/kata-runtime",
+# "/usr/bin/kata-qemu",
+# "/usr/bin/kata-fc",
+#]
+
+#runc = [
+# "/usr/bin/runc",
+# "/usr/sbin/runc",
+# "/usr/local/bin/runc",
+# "/usr/local/sbin/runc",
+# "/sbin/runc",
+# "/bin/runc",
+# "/usr/lib/cri-o-runc/sbin/runc",
+#]
+
+#runsc = [
+# "/usr/bin/runsc",
+# "/usr/sbin/runsc",
+# "/usr/local/bin/runsc",
+# "/usr/local/sbin/runsc",
+# "/bin/runsc",
+# "/sbin/runsc",
+# "/run/current-system/sw/bin/runsc",
+#]
+
+#krun = [
+# "/usr/bin/krun",
+# "/usr/local/bin/krun",
+#]
+
+[engine.volume_plugins]
+#testplugin = "/run/podman/plugins/test.sock"
+
+[machine]
+# Number of CPU's a machine is created with.
+#
+#cpus=1
+
+# The size of the disk in GB created when init-ing a podman-machine VM.
+#
+#disk_size=10
+
+# Default image URI when creating a new VM using `podman machine init`.
+# Options: On Linux/Mac, `testing`, `stable`, `next`. On Windows, the major
+# version of the OS (e.g `36`) for Fedora 36. For all platforms you can
+# alternatively specify a custom download URL to an image. Container engines
+# translate URIs $OS and $ARCH to the native OS and ARCH. URI
+# "https://example.com/$OS/$ARCH/foobar.ami" becomes
+# "https://example.com/linux/amd64/foobar.ami" on a Linux AMD machine.
+# The default value is `testing`.
+#
+# image = "testing"
+
+# Memory in MB a machine is created with.
+#
+#memory=2048
+
+# The username to use and create on the podman machine OS for rootless
+# container access.
+#
+#user = "core"
+
+# Host directories to be mounted as volumes into the VM by default.
+# Environment variables like $HOME as well as complete paths are supported for
+# the source and destination. An optional third field `:ro` can be used to
+# tell the container engines to mount the volume readonly.
+#
+# volumes = [
+# "$HOME:$HOME",
+#]
+
+# The [machine] table MUST be the last entry in this file.
+# (Unless another table is added)
+# TOML does not provide a way to end a table other than a further table being
+# defined, so every key hereafter will be part of [machine] and not the
+# main config.
diff --git a/data/templates/container/registries.conf.j2 b/data/templates/container/registries.conf.j2
index 2e86466a1..eb7ff8775 100644
--- a/data/templates/container/registries.conf.j2
+++ b/data/templates/container/registries.conf.j2
@@ -23,5 +23,9 @@
# unqualified-search-registries = ["example.com"]
{% if registry is vyos_defined %}
-unqualified-search-registries = {{ registry }}
+{% set registry_list = [] %}
+{% for r, r_options in registry.items() if r_options.disable is not vyos_defined %}
+{% set _ = registry_list.append(r) %}
+{% endfor %}
+unqualified-search-registries = {{ registry_list }}
{% endif %}
diff --git a/data/templates/container/storage.conf.j2 b/data/templates/container/storage.conf.j2
index 665f9bf95..ec2046fb5 100644
--- a/data/templates/container/storage.conf.j2
+++ b/data/templates/container/storage.conf.j2
@@ -1,4 +1,6 @@
### Autogenerated by container.py ###
[storage]
- driver = "vfs"
+ driver = "overlay"
graphroot = "/usr/lib/live/mount/persistence/container/storage"
+ [storage.options]
+ mount_program = "/usr/bin/fuse-overlayfs"
diff --git a/data/templates/dhcp-relay/dhcrelay.conf.j2 b/data/templates/dhcp-relay/dhcrelay.conf.j2
index 11710bd8e..c26c263fd 100644
--- a/data/templates/dhcp-relay/dhcrelay.conf.j2
+++ b/data/templates/dhcp-relay/dhcrelay.conf.j2
@@ -2,5 +2,8 @@
{% set max_size = '-A ' ~ relay_options.max_size if relay_options.max_size is vyos_defined %}
{# hop_count and relay_agents_packets is a default option, thus it is always present #}
+{% if interface is vyos_defined %}
OPTIONS="-c {{ relay_options.hop_count }} -a -m {{ relay_options.relay_agents_packets }} {{ max_size }} -i {{ interface | join(' -i ') }} {{ server | join(' ') }}"
-
+{% else %}
+OPTIONS="-c {{ relay_options.hop_count }} -a -m {{ relay_options.relay_agents_packets }} {{ max_size }} -id {{ listen_interface | join(' -id ') }} -iu {{ upstream_interface | join(' -iu ') }} {{ server | join(' ') }}"
+{% endif %} \ No newline at end of file
diff --git a/data/templates/dns-forwarding/recursor.forward-zones.conf.j2 b/data/templates/dns-forwarding/recursor.forward-zones.conf.j2
index de3269e47..593a98c24 100644
--- a/data/templates/dns-forwarding/recursor.forward-zones.conf.j2
+++ b/data/templates/dns-forwarding/recursor.forward-zones.conf.j2
@@ -23,7 +23,6 @@
{% if forward_zones is vyos_defined %}
# zones added via 'service dns forwarding domain'
{% for zone, zonedata in forward_zones.items() %}
-{{ "+" if zonedata.recursion_desired is vyos_defined }}{{ zone | replace('_', '-') }}={{ zonedata.server | join(', ') }}
+{{ "+" if zonedata.recursion_desired is vyos_defined }}{{ zone | replace('_', '-') }}={{ zonedata.name_server | join(', ') }}
{% endfor %}
{% endif %}
-
diff --git a/data/templates/dynamic-dns/ddclient.conf.j2 b/data/templates/dynamic-dns/ddclient.conf.j2
index 3c2d17cbb..c2c9b1dd6 100644
--- a/data/templates/dynamic-dns/ddclient.conf.j2
+++ b/data/templates/dynamic-dns/ddclient.conf.j2
@@ -10,7 +10,7 @@ ssl=yes
{% set web_skip = ", web-skip='" ~ iface_config.use_web.skip ~ "'" if iface_config.use_web.skip is vyos_defined else '' %}
use=web, web='{{ iface_config.use_web.url }}'{{ web_skip }}
{% else %}
-{{ 'usev6=if' if iface_config.ipv6_enable is vyos_defined else 'use=if' }}, if={{ iface }}
+{{ 'usev6=ifv6' if iface_config.ipv6_enable is vyos_defined else 'use=if' }}, if={{ iface }}
{% endif %}
{% if iface_config.rfc2136 is vyos_defined %}
diff --git a/data/templates/ethernet/wpa_supplicant.conf.j2 b/data/templates/ethernet/wpa_supplicant.conf.j2
index 8f140f6cb..cd35d6d1e 100644
--- a/data/templates/ethernet/wpa_supplicant.conf.j2
+++ b/data/templates/ethernet/wpa_supplicant.conf.j2
@@ -67,6 +67,11 @@ network={
# discards such frames to protect against potential attacks by rogue
# devices, but this option can be used to disable that protection for cases
# where the server/authenticator does not need to be authenticated.
- phase1="allow_canned_success=1"
+ #
+ # "tls_disable_tlsv1_0=0" is used to allow TLSv1 for compatibility with
+ # legacy networks. This follows the behavior of Debian's wpa_supplicant,
+ # which includes a custom patch for allowing TLSv1, but the patch currently
+ # does not work for VyOS' git builds of wpa_supplicant.
+ phase1="allow_canned_success=1 tls_disable_tlsv1_0=0"
}
diff --git a/data/templates/firewall/nftables-policy.j2 b/data/templates/firewall/nftables-policy.j2
index 6cb3b2f95..7a89d29e4 100644
--- a/data/templates/firewall/nftables-policy.j2
+++ b/data/templates/firewall/nftables-policy.j2
@@ -11,7 +11,7 @@ table ip vyos_mangle {
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 }}
+ iifname { {{ conf.interface | join(",") }} } counter jump VYOS_PBR_{{ route_text }}
{% endfor %}
{% endif %}
}
diff --git a/data/templates/frr/babeld.frr.j2 b/data/templates/frr/babeld.frr.j2
new file mode 100644
index 000000000..344a5f988
--- /dev/null
+++ b/data/templates/frr/babeld.frr.j2
@@ -0,0 +1,85 @@
+{% from 'frr/distribute_list_macro.j2' import render_distribute_list %}
+{% from 'frr/ipv6_distribute_list_macro.j2' import render_ipv6_distribute_list %}
+!
+{# Interface specific configuration #}
+{% if interface is vyos_defined %}
+{% for iface, iface_config in interface.items() %}
+interface {{ iface }}
+{% if iface_config.type is vyos_defined('wired') or iface_config.type is vyos_defined('wireless') %}
+ babel {{ iface_config.type }}
+{% endif %}
+{% if iface_config.split_horizon is vyos_defined("enable") %}
+ babel split-horizon
+{% elif iface_config.split_horizon is vyos_defined("disable") %}
+ no babel split-horizon
+{% endif %}
+{% if iface_config.hello_interval is vyos_defined %}
+ babel hello-interval {{ iface_config.hello_interval }}
+{% endif %}
+{% if iface_config.update_interval is vyos_defined %}
+ babel update-interval {{ iface_config.update_interval }}
+{% endif %}
+{% if iface_config.rxcost is vyos_defined %}
+ babel rxcost {{ iface_config.rxcost }}
+{% endif %}
+{% if iface_config.rtt_decay is vyos_defined %}
+ babel rtt-decay {{ iface_config.rtt_decay }}
+{% endif %}
+{% if iface_config.rtt_min is vyos_defined %}
+ babel rtt-min {{ iface_config.rtt_min }}
+{% endif %}
+{% if iface_config.rtt_max is vyos_defined %}
+ babel rtt-max {{ iface_config.rtt_max }}
+{% endif %}
+{% if iface_config.max_rtt_penalty is vyos_defined %}
+ babel max-rtt-penalty {{ iface_config.max_rtt_penalty }}
+{% endif %}
+{% if iface_config.enable_timestamps is vyos_defined %}
+ babel enable-timestamps
+{% endif %}
+{% if iface_config.channel is vyos_defined %}
+ babel channel {{ iface_config.channel | replace("non-interfering", "noninterfering") }}
+{% endif %}
+exit
+!
+{% endfor %}
+{% endif %}
+!
+{# Babel configuration #}
+router babel
+{% if parameters.diversity is vyos_defined %}
+ babel diversity
+{% endif %}
+{% if parameters.diversity_factor is vyos_defined %}
+ babel diversity-factor {{ parameters.diversity_factor }}
+{% endif %}
+{% if parameters.resend_delay is vyos_defined %}
+ babel resend-delay {{ parameters.resend_delay }}
+{% endif %}
+{% if parameters.smoothing_half_life is vyos_defined %}
+ babel smoothing-half-life {{ parameters.smoothing_half_life }}
+{% endif %}
+{% if interface is vyos_defined %}
+{% for iface, iface_config in interface.items() %}
+ network {{ iface }}
+{% endfor %}
+{% endif %}
+{% if redistribute is vyos_defined %}
+{% for address_family in redistribute %}
+{% for protocol, protocol_config in redistribute[address_family].items() %}
+{% if protocol is vyos_defined('ospfv3') %}
+{% set protocol = 'ospf6' %}
+{% endif %}
+ redistribute {{ address_family }} {{ protocol }}
+{% endfor %}
+{% endfor %}
+{% endif %}
+{% if distribute_list.ipv4 is vyos_defined %}
+{{ render_distribute_list(distribute_list.ipv4) }}
+{% endif %}
+{% if distribute_list.ipv6 is vyos_defined %}
+{{ render_ipv6_distribute_list(distribute_list.ipv6) }}
+{% endif %}
+exit
+!
+end
diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2
index 5febd7c66..b749be93f 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 %}
@@ -69,6 +74,9 @@
{% if config.password is vyos_defined %}
neighbor {{ neighbor }} password {{ config.password }}
{% endif %}
+{% if config.path_attribute.discard is vyos_defined %}
+ neighbor {{ neighbor }} path-attribute discard {{ config.path_attribute.discard }}
+{% endif %}
{% if config.port is vyos_defined %}
neighbor {{ neighbor }} port {{ config.port }}
{% endif %}
@@ -240,7 +248,7 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% else %}
no bgp ebgp-requires-policy
{% endif %}
-{# Option must be set before any neighbor - see https://phabricator.vyos.net/T3463 #}
+{# Option must be set before any neighbor - see https://vyos.dev/T3463 #}
no bgp default ipv4-unicast
{# Workaround for T2100 until we have decided about a migration script #}
no bgp network import-check
@@ -414,10 +422,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 %}
@@ -541,6 +553,9 @@ bgp route-reflector allow-outbound-policy
{% if parameters.suppress_fib_pending is vyos_defined %}
bgp suppress-fib-pending
{% endif %}
+{% if parameters.tcp_keepalive.idle is vyos_defined and parameters.tcp_keepalive.interval is vyos_defined and parameters.tcp_keepalive.probes is vyos_defined %}
+ bgp tcp-keepalive {{ parameters.tcp_keepalive.idle }} {{ parameters.tcp_keepalive.interval }} {{ parameters.tcp_keepalive.probes }}
+{% endif %}
{% if timers.keepalive is vyos_defined and timers.holdtime is vyos_defined %}
timers bgp {{ timers.keepalive }} {{ timers.holdtime }}
{% endif %}
diff --git a/data/templates/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl
index df98e74d6..fdff9772a 100644
--- a/data/templates/frr/daemons.frr.tmpl
+++ b/data/templates/frr/daemons.frr.tmpl
@@ -9,7 +9,7 @@ pimd=no
ldpd=yes
nhrpd=no
eigrpd=yes
-babeld=no
+babeld=yes
sharpd=no
pbrd=no
bfdd=yes
@@ -51,4 +51,3 @@ bfdd_options=" --daemon -A 127.0.0.1"
watchfrr_enable=no
valgrind_enable=no
-
diff --git a/data/templates/frr/distribute_list_macro.j2 b/data/templates/frr/distribute_list_macro.j2
new file mode 100644
index 000000000..c10bf732d
--- /dev/null
+++ b/data/templates/frr/distribute_list_macro.j2
@@ -0,0 +1,30 @@
+{% macro render_distribute_list(distribute_list) %}
+{% if distribute_list.access_list.in is vyos_defined %}
+ distribute-list {{ distribute_list.access_list.in }} in
+{% endif %}
+{% if distribute_list.access_list.out is vyos_defined %}
+ distribute-list {{ distribute_list.access_list.out }} out
+{% endif %}
+{% if distribute_list.interface is vyos_defined %}
+{% for interface, interface_config in distribute_list.interface.items() %}
+{% if interface_config.access_list.in is vyos_defined %}
+ distribute-list {{ interface_config.access_list.in }} in {{ interface }}
+{% endif %}
+{% if interface_config.access_list.out is vyos_defined %}
+ distribute-list {{ interface_config.access_list.out }} out {{ interface }}
+{% endif %}
+{% if interface_config.prefix_list.in is vyos_defined %}
+ distribute-list prefix {{ interface_config.prefix_list.in }} in {{ interface }}
+{% endif %}
+{% if interface_config.prefix_list.out is vyos_defined %}
+ distribute-list prefix {{ interface_config.prefix_list.out }} out {{ interface }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% if distribute_list.prefix_list.in is vyos_defined %}
+ distribute-list prefix {{ distribute_list.prefix_list.in }} in
+{% endif %}
+{% if distribute_list.prefix_list.out is vyos_defined %}
+ distribute-list prefix {{ distribute_list.prefix_list.out }} out
+{% endif %}
+{% endmacro %}
diff --git a/data/templates/frr/ipv6_distribute_list_macro.j2 b/data/templates/frr/ipv6_distribute_list_macro.j2
new file mode 100644
index 000000000..c365fbdae
--- /dev/null
+++ b/data/templates/frr/ipv6_distribute_list_macro.j2
@@ -0,0 +1,30 @@
+{% macro render_ipv6_distribute_list(distribute_list) %}
+{% if distribute_list.access_list.in is vyos_defined %}
+ ipv6 distribute-list {{ distribute_list.access_list.in }} in
+{% endif %}
+{% if distribute_list.access_list.out is vyos_defined %}
+ ipv6 distribute-list {{ distribute_list.access_list.out }} out
+{% endif %}
+{% if distribute_list.interface is vyos_defined %}
+{% for interface, interface_config in distribute_list.interface.items() %}
+{% if interface_config.access_list.in is vyos_defined %}
+ ipv6 distribute-list {{ interface_config.access_list.in }} in {{ interface }}
+{% endif %}
+{% if interface_config.access_list.out is vyos_defined %}
+ ipv6 distribute-list {{ interface_config.access_list.out }} out {{ interface }}
+{% endif %}
+{% if interface_config.prefix_list.in is vyos_defined %}
+ ipv6 distribute-list prefix {{ interface_config.prefix_list.in }} in {{ interface }}
+{% endif %}
+{% if interface_config.prefix_list.out is vyos_defined %}
+ ipv6 distribute-list prefix {{ interface_config.prefix_list.out }} out {{ interface }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% if distribute_list.prefix_list.in is vyos_defined %}
+ ipv6 distribute-list prefix {{ distribute_list.prefix_list.in }} in
+{% endif %}
+{% if distribute_list.prefix_list.out is vyos_defined %}
+ ipv6 distribute-list prefix {{ distribute_list.prefix_list.out }} out
+{% endif %}
+{% endmacro %}
diff --git a/data/templates/frr/isisd.frr.j2 b/data/templates/frr/isisd.frr.j2
index 8df1e9513..3c37e28b9 100644
--- a/data/templates/frr/isisd.frr.j2
+++ b/data/templates/frr/isisd.frr.j2
@@ -25,6 +25,12 @@ interface {{ iface }}
{% if iface_config.hello_padding is vyos_defined %}
isis hello padding
{% endif %}
+{% if iface_config.ldp_sync.disable is vyos_defined %}
+ no isis mpls ldp-sync
+{% elif iface_config.ldp_sync.holddown is vyos_defined %}
+ isis mpls ldp-sync
+ isis mpls ldp-sync holddown {{ iface_config.ldp_sync.holddown }}
+{% endif %}
{% if iface_config.metric is vyos_defined %}
isis metric {{ iface_config.metric }}
{% endif %}
@@ -84,6 +90,11 @@ router isis VyOS {{ 'vrf ' + vrf if vrf is vyos_defined }}
{% if max_lsp_lifetime is vyos_defined %}
max-lsp-lifetime {{ max_lsp_lifetime }}
{% endif %}
+{% if ldp_sync.holddown is vyos_defined %}
+ mpls ldp-sync holddown {{ ldp_sync.holddown }}
+{% elif ldp_sync is vyos_defined %}
+ mpls ldp-sync
+{% endif %}
{% if spf_interval is vyos_defined %}
spf-interval {{ spf_interval }}
{% endif %}
diff --git a/data/templates/frr/ospfd.frr.j2 b/data/templates/frr/ospfd.frr.j2
index 882ec8f97..3f97b7325 100644
--- a/data/templates/frr/ospfd.frr.j2
+++ b/data/templates/frr/ospfd.frr.j2
@@ -44,6 +44,12 @@ interface {{ iface }}
{% if iface_config.bfd.profile is vyos_defined %}
ip ospf bfd profile {{ iface_config.bfd.profile }}
{% endif %}
+{% if iface_config.ldp_sync.disable is vyos_defined %}
+ no ip ospf mpls ldp-sync
+{% elif iface_config.ldp_sync.holddown is vyos_defined %}
+ ip ospf mpls ldp-sync
+ ip ospf mpls ldp-sync holddown {{ iface_config.ldp_sync.holddown }}
+{% endif %}
{% if iface_config.mtu_ignore is vyos_defined %}
ip ospf mtu-ignore
{% endif %}
@@ -84,11 +90,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 }}
@@ -131,6 +139,11 @@ router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% if maximum_paths is vyos_defined %}
maximum-paths {{ maximum_paths }}
{% endif %}
+{% if ldp_sync.holddown is vyos_defined %}
+ mpls ldp-sync holddown {{ ldp_sync.holddown }}
+{% elif ldp_sync is vyos_defined %}
+ mpls ldp-sync
+{% endif %}
{% if distance.global is vyos_defined %}
distance {{ distance.global }}
{% endif %}
@@ -170,7 +183,7 @@ router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% if parameters.router_id is vyos_defined %}
ospf router-id {{ parameters.router_id }}
{% endif %}
-{% if passive_interface.default is vyos_defined %}
+{% if passive_interface is vyos_defined('default') %}
passive-interface default
{% endif %}
{% if redistribute is vyos_defined %}
diff --git a/data/templates/frr/ripd.frr.j2 b/data/templates/frr/ripd.frr.j2
index e9e484cc2..1445bf97f 100644
--- a/data/templates/frr/ripd.frr.j2
+++ b/data/templates/frr/ripd.frr.j2
@@ -1,3 +1,4 @@
+{% from 'frr/distribute_list_macro.j2' import render_distribute_list %}
{# RIP key-chain definition #}
{% if interface is vyos_defined %}
{% for iface, iface_config in interface.items() %}
@@ -60,34 +61,7 @@ router rip
{% endfor %}
{% endif %}
{% if distribute_list is vyos_defined %}
-{% if distribute_list.access_list.in is vyos_defined %}
- distribute-list {{ distribute_list.access_list.in }} in
-{% endif %}
-{% if distribute_list.access_list.out is vyos_defined %}
- distribute-list {{ distribute_list.access_list.out }} out
-{% endif %}
-{% if distribute_list.interface is vyos_defined %}
-{% for interface, interface_config in distribute_list.interface.items() %}
-{% if interface_config.access_list.in is vyos_defined %}
- distribute-list {{ interface_config.access_list.in }} in {{ interface }}
-{% endif %}
-{% if interface_config.access_list.out is vyos_defined %}
- distribute-list {{ interface_config.access_list.out }} out {{ interface }}
-{% endif %}
-{% if interface_config.prefix_list.in is vyos_defined %}
- distribute-list prefix {{ interface_config.prefix_list.in }} in {{ interface }}
-{% endif %}
-{% if interface_config.prefix_list.out is vyos_defined %}
- distribute-list prefix {{ interface_config.prefix_list.out }} out {{ interface }}
-{% endif %}
-{% endfor %}
-{% endif %}
-{% if distribute_list.prefix_list.in is vyos_defined %}
- distribute-list prefix {{ distribute_list.prefix_list.in }} in
-{% endif %}
-{% if distribute_list.prefix_list.out is vyos_defined %}
- distribute-list prefix {{ distribute_list.prefix_list.out }} out
-{% endif %}
+{{ render_distribute_list(distribute_list) }}
{% endif %}
{% include 'frr/rip_ripng.frr.j2' %}
{% if version is vyos_defined %}
diff --git a/data/templates/frr/ripngd.frr.j2 b/data/templates/frr/ripngd.frr.j2
index 7919b1bad..e857e9481 100644
--- a/data/templates/frr/ripngd.frr.j2
+++ b/data/templates/frr/ripngd.frr.j2
@@ -1,3 +1,4 @@
+{% from 'frr/ipv6_distribute_list_macro.j2' import render_ipv6_distribute_list %}
{# Interface specific configuration #}
{% if interface is vyos_defined %}
{% for iface, iface_config in interface.items() %}
@@ -19,34 +20,7 @@ router ripng
{% endfor %}
{% endif %}
{% if distribute_list is vyos_defined %}
-{% if distribute_list.access_list.in is vyos_defined %}
- ipv6 distribute-list {{ distribute_list.access_list.in }} in
-{% endif %}
-{% if distribute_list.access_list.out is vyos_defined %}
- ipv6 distribute-list {{ distribute_list.access_list.out }} out
-{% endif %}
-{% if distribute_list.interface is vyos_defined %}
-{% for interface, interface_config in distribute_list.interface.items() %}
-{% if interface_config.access_list.in is vyos_defined %}
- ipv6 distribute-list {{ interface_config.access_list.in }} in {{ interface }}
-{% endif %}
-{% if interface_config.access_list.out is vyos_defined %}
- ipv6 distribute-list {{ interface_config.access_list.out }} out {{ interface }}
-{% endif %}
-{% if interface_config.prefix_list.in is vyos_defined %}
- ipv6 distribute-list prefix {{ interface_config.prefix_list.in }} in {{ interface }}
-{% endif %}
-{% if interface_config.prefix_list.out is vyos_defined %}
- ipv6 distribute-list prefix {{ interface_config.prefix_list.out }} out {{ interface }}
-{% endif %}
-{% endfor %}
-{% endif %}
-{% if distribute_list.prefix_list.in is vyos_defined %}
- ipv6 distribute-list prefix {{ distribute_list.prefix_list.in }} in
-{% endif %}
-{% if distribute_list.prefix_list.out is vyos_defined %}
- ipv6 distribute-list prefix {{ distribute_list.prefix_list.out }} out
-{% endif %}
+{{ render_ipv6_distribute_list(distribute_list) }}
{% endif %}
{% include 'frr/rip_ripng.frr.j2' %}
exit
diff --git a/data/templates/frr/staticd.frr.j2 b/data/templates/frr/staticd.frr.j2
index 55c05ceb7..992a0435c 100644
--- a/data/templates/frr/staticd.frr.j2
+++ b/data/templates/frr/staticd.frr.j2
@@ -37,7 +37,7 @@ vrf {{ vrf }}
{% endfor %}
{% endif %}
{% if vrf is vyos_defined %}
- exit-vrf
+exit-vrf
{% endif %}
!
{# Policy route tables #}
diff --git a/data/templates/frr/vrf-vni.frr.j2 b/data/templates/frr/vrf-vni.frr.j2
deleted file mode 100644
index e5f4810a1..000000000
--- a/data/templates/frr/vrf-vni.frr.j2
+++ /dev/null
@@ -1,9 +0,0 @@
-{% if name is vyos_defined %}
-{% for vrf, vrf_config in name.items() %}
-vrf {{ vrf }}
-{% if vrf_config.vni is vyos_defined %}
- vni {{ vrf_config.vni }}
-{% endif %}
- exit-vrf
-{% endfor %}
-{% endif %}
diff --git a/data/templates/frr/vrf.route-map.frr.j2 b/data/templates/frr/vrf.route-map.frr.j2
deleted file mode 100644
index 5e0c56a7b..000000000
--- a/data/templates/frr/vrf.route-map.frr.j2
+++ /dev/null
@@ -1,10 +0,0 @@
-!
-{% if vrf is vyos_defined and route_map is vyos_defined %}
-vrf {{ vrf }}
- ip protocol {{ protocol }} route-map {{ route_map }}
- exit-vrf
-!
-{% elif route_map is vyos_defined %}
-ip protocol {{ protocol }} route-map {{ route_map }}
-{% endif %}
-!
diff --git a/data/templates/frr/zebra.route-map.frr.j2 b/data/templates/frr/zebra.route-map.frr.j2
new file mode 100644
index 000000000..8e18abbde
--- /dev/null
+++ b/data/templates/frr/zebra.route-map.frr.j2
@@ -0,0 +1,9 @@
+!
+{% if protocol is vyos_defined %}
+{% for protocol_name, protocol_config in protocol.items() %}
+{% if protocol_name is vyos_defined('ospfv3') %}
+{% set protocol_name = 'ospf6' %}
+{% endif %}
+{{ afi }} protocol {{ protocol_name }} route-map {{ protocol_config.route_map }}
+{% endfor %}
+{% endif %}
diff --git a/data/templates/frr/zebra.vrf.route-map.frr.j2 b/data/templates/frr/zebra.vrf.route-map.frr.j2
new file mode 100644
index 000000000..4e1206374
--- /dev/null
+++ b/data/templates/frr/zebra.vrf.route-map.frr.j2
@@ -0,0 +1,28 @@
+!
+{% if name is vyos_defined %}
+{% for vrf, vrf_config in name.items() %}
+{# code path required for vrf_vni.py as we will only render the required VR configuration and not all of them #}
+{% if only_vrf is vyos_defined and vrf is not vyos_defined(only_vrf) %}
+{% continue %}
+{% endif %}
+vrf {{ vrf }}
+{% if vrf_config.ip.protocol is vyos_defined %}
+{% for protocol_name, protocol_config in vrf_config.ip.protocol.items() %}
+ ip protocol {{ protocol_name }} route-map {{ protocol_config.route_map }}
+{% endfor %}
+{% endif %}
+{% if vrf_config.ipv6.protocol is vyos_defined %}
+{% for protocol_name, protocol_config in vrf_config.ipv6.protocol.items() %}
+{% if protocol_name is vyos_defined('ospfv3') %}
+{% set protocol_name = 'ospf6' %}
+{% endif %}
+ ipv6 protocol {{ protocol_name }} route-map {{ protocol_config.route_map }}
+{% endfor %}
+{% endif %}
+{% if vrf_config.vni is vyos_defined and no_vni is not vyos_defined %}
+ vni {{ vrf_config.vni }}
+{% endif %}
+exit-vrf
+{% endfor %}
+!
+{% 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..b541ff309 100644
--- a/data/templates/https/nginx.default.j2
+++ b/data/templates/https/nginx.default.j2
@@ -16,6 +16,8 @@ server {
server_name {{ name }};
{% endfor %}
+ root /srv/localui;
+
{% if server.certbot %}
ssl_certificate {{ server.certbot_dir }}/live/{{ server.certbot_domain_dir }}/fullchain.pem;
ssl_certificate_key {{ server.certbot_dir }}/live/{{ server.certbot_domain_dir }}/privkey.pem;
@@ -34,7 +36,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;
@@ -48,6 +50,12 @@ server {
{% else %}
return 503;
{% endif %}
+{% if server.allow_client %}
+{% for client in server.allow_client %}
+ allow {{ client }};
+{% endfor %}
+ deny all;
+{% endif %}
}
error_page 497 =301 https://$host:{{ server.port }}$request_uri;
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/ipsec.conf.j2 b/data/templates/ipsec/ipsec.conf.j2
deleted file mode 100644
index f63995b38..000000000
--- a/data/templates/ipsec/ipsec.conf.j2
+++ /dev/null
@@ -1,19 +0,0 @@
-# Created by VyOS - manual changes will be overwritten
-
-config setup
-{% set charondebug = '' %}
-{% if log.subsystem is vyos_defined %}
-{% set subsystem = log.subsystem %}
-{% if 'any' in log.subsystem %}
-{% set subsystem = ['dmn', 'mgr', 'ike', 'chd','job', 'cfg', 'knl',
- 'net', 'asn', 'enc', 'lib', 'esp', 'tls', 'tnc',
- 'imc', 'imv', 'pts'] %}
-{% endif %}
-{% set charondebug = subsystem | join (' ' ~ log.level ~ ', ') ~ ' ' ~ log.level %}
-{% endif %}
- charondebug = "{{ charondebug }}"
- uniqueids = {{ "no" if disable_uniqreqids is vyos_defined else "yes" }}
-
-{% if include_ipsec_conf is vyos_defined %}
-include {{ include_ipsec_conf }}
-{% endif %}
diff --git a/data/templates/ipsec/ipsec.secrets.j2 b/data/templates/ipsec/ipsec.secrets.j2
deleted file mode 100644
index a87ac9bc7..000000000
--- a/data/templates/ipsec/ipsec.secrets.j2
+++ /dev/null
@@ -1,5 +0,0 @@
-# Created by VyOS - manual changes will be overwritten
-
-{% if include_ipsec_secrets is vyos_defined %}
-include {{ include_ipsec_secrets }}
-{% endif %}
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 837fa263c..9d95271fe 100644
--- a/data/templates/ipsec/swanctl/peer.j2
+++ b/data/templates/ipsec/swanctl/peer.j2
@@ -45,11 +45,7 @@
{% endif %}
}
remote {
-{% if peer_conf.authentication.remote_id is vyos_defined %}
id = "{{ peer_conf.authentication.remote_id }}"
-{% else %}
- id = "{{ peer }}"
-{% endif %}
auth = {{ 'psk' if peer_conf.authentication.mode == 'pre-shared-secret' else 'pubkey' }}
{% if peer_conf.authentication.mode == 'rsa' %}
pubkeys = {{ peer_conf.authentication.rsa.remote_key }}.pem
diff --git a/data/templates/login/limits.j2 b/data/templates/login/limits.j2
new file mode 100644
index 000000000..5e2c11f35
--- /dev/null
+++ b/data/templates/login/limits.j2
@@ -0,0 +1,5 @@
+# Generated by /usr/libexec/vyos/conf_mode/system-login.py
+
+{% if max_login_session is vyos_defined %}
+* - maxsyslogins {{ max_login_session }}
+{% endif %}
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/openvpn/server.conf.j2 b/data/templates/openvpn/server.conf.j2
index 6dd4ef88d..6332ed9c2 100644
--- a/data/templates/openvpn/server.conf.j2
+++ b/data/templates/openvpn/server.conf.j2
@@ -98,7 +98,7 @@ server-ipv6 {{ subnet }}
{% endif %}
{% if server.client_ip_pool is vyos_defined and server.client_ip_pool.disable is not vyos_defined %}
-ifconfig-pool {{ server.client_ip_pool.start }} {{ server.client_ip_pool.stop }}{{ server.client_ip_pool.subnet_mask if server.client_ip_pool.subnet_mask is vyos_defined }}
+ifconfig-pool {{ server.client_ip_pool.start }} {{ server.client_ip_pool.stop }} {{ server.client_ip_pool.subnet_mask if server.client_ip_pool.subnet_mask is vyos_defined }}
{% endif %}
{% if server.max_connections is vyos_defined %}
max-clients {{ server.max_connections }}
@@ -213,6 +213,9 @@ keysize 256
data-ciphers {{ encryption.ncp_ciphers | openvpn_ncp_ciphers }}
{% endif %}
{% endif %}
+# https://vyos.dev/T5027
+# Required to support BF-CBC (default ciphername when none given)
+providers legacy default
{% if hash is vyos_defined %}
auth {{ hash }}
diff --git a/data/templates/pppoe/peer.j2 b/data/templates/pppoe/peer.j2
index 6221abb9b..f30cefe63 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
@@ -50,7 +53,7 @@ mtu {{ mtu }}
mru {{ mtu }}
{% if authentication is vyos_defined %}
-{{ 'user "' + authentication.user + '"' if authentication.user is vyos_defined }}
+{{ 'user "' + authentication.username + '"' if authentication.username is vyos_defined }}
{{ 'password "' + authentication.password + '"' if authentication.password is vyos_defined }}
{% endif %}
@@ -62,6 +65,10 @@ mru {{ mtu }}
noipv6
{% endif %}
+{% if holdoff is vyos_defined %}
+holdoff {{ holdoff }}
+{% endif %}
+
{% if connect_on_demand is vyos_defined %}
demand
# See T2249. PPP default route options should only be set when in on-demand
diff --git a/data/templates/router-advert/radvd.conf.j2 b/data/templates/router-advert/radvd.conf.j2
index f4b384958..4ef4751dd 100644
--- a/data/templates/router-advert/radvd.conf.j2
+++ b/data/templates/router-advert/radvd.conf.j2
@@ -46,7 +46,7 @@ interface {{ iface }} {
{% if iface_config.source_address is vyos_defined %}
AdvRASrcAddress {
{% for source_address in iface_config.source_address %}
- {{ source_address }}
+ {{ source_address }};
{% endfor %}
};
{% endif %}
diff --git a/data/templates/sflow/hsflowd.conf.j2 b/data/templates/sflow/hsflowd.conf.j2
new file mode 100644
index 000000000..5000956bd
--- /dev/null
+++ b/data/templates/sflow/hsflowd.conf.j2
@@ -0,0 +1,32 @@
+# Genereated by /usr/libexec/vyos/conf_mode/system_sflow.py
+# Parameters http://sflow.net/host-sflow-linux-config.php
+
+sflow {
+{% if polling is vyos_defined %}
+ polling={{ polling }}
+{% endif %}
+{% if sampling_rate is vyos_defined %}
+ sampling={{ sampling_rate }}
+ sampling.bps_ratio=0
+{% endif %}
+{% if agent_address is vyos_defined %}
+ agentIP={{ agent_address }}
+{% endif %}
+{% if agent_interface is vyos_defined %}
+ agent={{ agent_interface }}
+{% endif %}
+{% if server is vyos_defined %}
+{% for server, server_config in server.items() %}
+ collector { ip = {{ server }} udpport = {{ server_config.port }} }
+{% endfor %}
+{% endif %}
+{% if interface is vyos_defined %}
+{% for iface in interface %}
+ pcap { dev={{ iface }} }
+{% endfor %}
+{% endif %}
+{% if drop_monitor_limit is vyos_defined %}
+ dropmon { limit={{ drop_monitor_limit }} start=on sw=on hw=off }
+{% endif %}
+ dbus { }
+}
diff --git a/data/templates/sflow/override.conf.j2 b/data/templates/sflow/override.conf.j2
new file mode 100644
index 000000000..f2a982528
--- /dev/null
+++ b/data/templates/sflow/override.conf.j2
@@ -0,0 +1,16 @@
+[Unit]
+After=
+After=vyos-router.service
+ConditionPathExists=
+ConditionPathExists=/run/sflow/hsflowd.conf
+
+[Service]
+EnvironmentFile=
+ExecStart=
+ExecStart=/usr/sbin/hsflowd -m %m -d -f /run/sflow/hsflowd.conf
+WorkingDirectory=
+WorkingDirectory=/run/sflow
+PIDFile=
+PIDFile=/run/sflow/hsflowd.pid
+Restart=always
+RestartSec=10
diff --git a/data/templates/snmp/etc.snmpd.conf.j2 b/data/templates/snmp/etc.snmpd.conf.j2
index 47bf6878f..9d78d479a 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,28 +62,43 @@ 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 }}
+{{ comm_config.authorization }}community {{ comm }} {{ network }} -V RESTRICTED
{% elif network | is_ipv6 %}
-{{ comm_config.authorization }}community6 {{ comm }} {{ network }}
+{{ 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 }}community6 {{ comm }}
-{% 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/ssh/sshd_config.j2 b/data/templates/ssh/sshd_config.j2
index 93735020c..422969ed8 100644
--- a/data/templates/ssh/sshd_config.j2
+++ b/data/templates/ssh/sshd_config.j2
@@ -29,7 +29,7 @@ PermitRootLogin no
PidFile /run/sshd/sshd.pid
AddressFamily any
DebianBanner no
-PasswordAuthentication no
+KbdInteractiveAuthentication no
#
# User configurable section
@@ -48,7 +48,7 @@ Port {{ value }}
LogLevel {{ loglevel | upper }}
# Specifies whether password authentication is allowed
-ChallengeResponseAuthentication {{ "no" if disable_password_authentication is vyos_defined else "yes" }}
+PasswordAuthentication {{ "no" if disable_password_authentication is vyos_defined else "yes" }}
{% if listen_address is vyos_defined %}
# Specifies the local addresses sshd should listen on
diff --git a/data/templates/sstp-client/peer.j2 b/data/templates/sstp-client/peer.j2
index 7a0b0e1f7..745a09e14 100644
--- a/data/templates/sstp-client/peer.j2
+++ b/data/templates/sstp-client/peer.j2
@@ -45,7 +45,7 @@ lock
nodeflate
{% if authentication is vyos_defined %}
-{{ 'user "' + authentication.user + '"' if authentication.user is vyos_defined }}
+{{ 'user "' + authentication.username + '"' if authentication.username is vyos_defined }}
{{ 'password "' + authentication.password + '"' if authentication.password is vyos_defined }}
{% endif %}
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..5852d6232 100644
--- a/data/templates/telegraf/telegraf.j2
+++ b/data/templates/telegraf/telegraf.j2
@@ -12,7 +12,7 @@
debug = false
quiet = false
logfile = ""
- hostname = ""
+ hostname = "{{ hostname }}"
omit_hostname = false
{% if azure_data_explorer is vyos_defined %}
### Azure Data Explorer ###
@@ -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/compat b/debian/compat
index f599e28b8..48082f72f 100644
--- a/debian/compat
+++ b/debian/compat
@@ -1 +1 @@
-10
+12
diff --git a/debian/control b/debian/control
index 7e69003ff..4a2706fc3 100644
--- a/debian/control
+++ b/debian/control
@@ -8,6 +8,7 @@ Build-Depends:
fakeroot,
gcc-multilib [amd64],
clang [amd64],
+ iproute2,
llvm [amd64],
libbpf-dev [amd64],
libelf-dev (>= 0.2) [amd64],
@@ -15,7 +16,7 @@ Build-Depends:
build-essential,
libvyosconfig0 (>= 0.0.7),
libzmq3-dev,
- python3,
+ python3 (>= 3.10),
python3-coverage,
python3-lxml,
python3-netifaces,
@@ -25,7 +26,6 @@ Build-Depends:
python3-setuptools,
python3-sphinx,
python3-xmltodict,
- python3-pyhumps,
quilt,
whois
Standards-Version: 3.9.6
@@ -33,18 +33,20 @@ Standards-Version: 3.9.6
Package: vyos-1x
Architecture: amd64 arm64
Depends:
- ${python3:Depends},
+ ${python3:Depends} (>= 3.10),
+ aardvark-dns,
accel-ppp,
+ auditd,
avahi-daemon,
beep,
bmon,
bsdmainutils,
+ charon-systemd,
conntrack,
conntrackd,
conserver-client,
conserver-server,
console-data,
- crda,
cron,
curl,
dbus,
@@ -60,15 +62,17 @@ Depends:
frr-pythontools,
frr-rpki-rtrlib,
frr-snmp,
+ fuse-overlayfs,
libpam-google-authenticator,
grc,
hostapd,
+ hsflowd,
hvinfo,
igmpproxy,
ipaddrcheck,
iperf,
iperf3,
- iproute2,
+ iproute2 (>= 6.0.0),
iputils-arping,
isc-dhcp-client,
isc-dhcp-relay,
@@ -78,7 +82,8 @@ Depends:
lcdproc,
lcdproc-extra-drivers,
libatomic1,
- libbpf0 [amd64],
+ libauparse0,
+ libbpf1 [amd64],
libcharon-extra-plugins (>=5.9),
libcharon-extauth-plugins (>=5.9),
libndp-tools,
@@ -97,12 +102,12 @@ Depends:
mtr-tiny,
ndisc6,
ndppd,
+ netavark,
netplug,
nfct,
nftables (>= 0.9.3),
nginx-light,
- ntp,
- ntpdate,
+ chrony,
nvme-cli,
ocserv,
opennhrp,
@@ -131,6 +136,7 @@ Depends:
python3-netaddr,
python3-netifaces,
python3-paramiko,
+ python3-passlib,
python3-psutil,
python3-pyhumps,
python3-pystache,
diff --git a/debian/rules b/debian/rules
index 5a58aeeb6..55e02fae6 100755
--- a/debian/rules
+++ b/debian/rules
@@ -8,6 +8,7 @@ VYOS_DATA_DIR := usr/share/vyos
VYOS_CFG_TMPL_DIR := opt/vyatta/share/vyatta-cfg/templates
VYOS_OP_TMPL_DIR := opt/vyatta/share/vyatta-op/templates
VYOS_MIBS_DIR := usr/share/snmp/mibs
+VYOS_LOCALUI_DIR := srv/localui
MIGRATION_SCRIPTS_DIR := opt/vyatta/etc/config-migrate/migrate
SYSTEM_SCRIPTS_DIR := usr/libexec/vyos/system
@@ -89,6 +90,9 @@ override_dh_auto_install:
mkdir -p $(DIR)/$(VYOS_DATA_DIR)
cp -r data/* $(DIR)/$(VYOS_DATA_DIR)
+ # Create localui dir
+ mkdir -p $(DIR)/$(VYOS_LOCALUI_DIR)
+
# Install SNMP MIBs
mkdir -p $(DIR)/$(VYOS_MIBS_DIR)
cp -d mibs/* $(DIR)/$(VYOS_MIBS_DIR)
diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install
index edd090993..98d1bc0cd 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
@@ -16,7 +18,9 @@ etc/update-motd.d
etc/vyos
lib/
opt/
+srv/localui
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 d5f5cbbc7..ddc189508 100644
--- a/debian/vyos-1x.postinst
+++ b/debian/vyos-1x.postinst
@@ -24,9 +24,9 @@ fi
# 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
+ PAM_CONFIG="# Check 2FA/MFA authentication token if enabled (per user)\nauth required pam_google_authenticator.so nullok forward_pass\n"
+ grep -qF -- "pam_google_authenticator.so" $file || \
+ sed -i "/^# Standard Un\*x authentication\./i${PAM_CONFIG}" $file
done
# Add RADIUS operator user for RADIUS authenticated users to map to
@@ -114,3 +114,13 @@ done
# Remove logrotate items controlled via CLI and VyOS defaults
sed -i '/^\/var\/log\/messages$/d' /etc/logrotate.d/rsyslog
sed -i '/^\/var\/log\/auth.log$/d' /etc/logrotate.d/rsyslog
+
+# Fix FRR pam.d "vtysh_pam" vtysh_pam: Failed in account validation T5110
+if test -f /etc/pam.d/frr; then
+ if grep -q 'pam_rootok.so' /etc/pam.d/frr; then
+ sed -i -re 's/rootok/permit/' /etc/pam.d/frr
+ fi
+fi
+
+# Generate API GraphQL schema
+/usr/libexec/vyos/services/api/graphql/generate/generate_schema.py
diff --git a/interface-definitions/bcast-relay.xml.in b/interface-definitions/bcast-relay.xml.in
index aeaa5ab37..e2993f3f3 100644
--- a/interface-definitions/bcast-relay.xml.in
+++ b/interface-definitions/bcast-relay.xml.in
@@ -34,11 +34,7 @@
</constraint>
</properties>
</leafNode>
- <leafNode name="description">
- <properties>
- <help>Description</help>
- </properties>
- </leafNode>
+ #include <include/generic-description.xml.i>
#include <include/generic-interface-multi.xml.i>
#include <include/port-number.xml.i>
</children>
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
index d50039665..9b6d2369d 100644
--- a/interface-definitions/container.xml.in
+++ b/interface-definitions/container.xml.in
@@ -104,11 +104,47 @@
</leafNode>
</children>
</tagNode>
+ <leafNode name="entrypoint">
+ <properties>
+ <help>Override the default ENTRYPOINT from the image</help>
+ <constraint>
+ <regex>[ !#-%&amp;(-~]+</regex>
+ </constraint>
+ <constraintErrorMessage>Entrypoint must be ascii characters, use &amp;quot; and &amp;apos for double and single quotes respectively</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="host-name">
+ <properties>
+ <help>Container host name</help>
+ <constraint>
+ #include <include/constraint/host-name.xml.i>
+ </constraint>
+ <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage>
+ </properties>
+ </leafNode>
<leafNode name="image">
<properties>
<help>Image name in the hub-registry</help>
</properties>
</leafNode>
+ <leafNode name="command">
+ <properties>
+ <help>Override the default CMD from the image</help>
+ <constraint>
+ <regex>[ !#-%&amp;(-~]+</regex>
+ </constraint>
+ <constraintErrorMessage>Command must be ascii characters, use &amp;quot; and &amp;apos for double and single quotes respectively</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="arguments">
+ <properties>
+ <help>The command's arguments for this container</help>
+ <constraint>
+ <regex>[ !#-%&amp;(-~]+</regex>
+ </constraint>
+ <constraintErrorMessage>The command's arguments must be ascii characters, use &amp;quot; and &amp;apos for double and single quotes respectively</constraintErrorMessage>
+ </properties>
+ </leafNode>
<leafNode name="memory">
<properties>
<help>Memory (RAM) available to this container</help>
@@ -155,15 +191,20 @@
<children>
<leafNode name="address">
<properties>
- <!-- PODMAN currently does not support more then one IPv4 or IPv6 address assignments to a container -->
<help>Assign static IP address to container</help>
<valueHelp>
<format>ipv4</format>
<description>IPv4 address</description>
</valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address</description>
+ </valueHelp>
<constraint>
<validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
</constraint>
+ <multi/>
</properties>
</leafNode>
</children>
@@ -207,14 +248,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>
@@ -265,6 +315,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>
@@ -278,11 +348,7 @@
<constraintErrorMessage>Network name cannot be longer than 11 characters</constraintErrorMessage>
</properties>
<children>
- <leafNode name="description">
- <properties>
- <help>Network description</help>
- </properties>
- </leafNode>
+ #include <include/generic-description.xml.i>
<leafNode name="prefix">
<properties>
<help>Prefix which allocated to that network</help>
@@ -301,15 +367,19 @@
<multi/>
</properties>
</leafNode>
+ #include <include/interface/vrf.xml.i>
</children>
</tagNode>
- <leafNode name="registry">
+ <tagNode name="registry">
<properties>
<help>Registry Name</help>
- <multi/>
</properties>
<defaultValue>docker.io quay.io</defaultValue>
- </leafNode>
+ <children>
+ #include <include/interface/authentication.xml.i>
+ #include <include/generic-disable-node.xml.i>
+ </children>
+ </tagNode>
</children>
</node>
</interfaceDefinition>
diff --git a/interface-definitions/dhcp-relay.xml.in b/interface-definitions/dhcp-relay.xml.in
index 27d0a3e6c..2a2597dd5 100644
--- a/interface-definitions/dhcp-relay.xml.in
+++ b/interface-definitions/dhcp-relay.xml.in
@@ -10,6 +10,38 @@
</properties>
<children>
#include <include/generic-interface-multi-broadcast.xml.i>
+ <leafNode name="listen-interface">
+ <properties>
+ <help>Interface for DHCP Relay Agent to listen for requests</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface name</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="upstream-interface">
+ <properties>
+ <help>Interface for DHCP Relay Agent forward requests out</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface name</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
<node name="relay-options">
<properties>
<help>Relay options</help>
diff --git a/interface-definitions/dhcpv6-relay.xml.in b/interface-definitions/dhcpv6-relay.xml.in
index 5abcbe804..947adef75 100644
--- a/interface-definitions/dhcpv6-relay.xml.in
+++ b/interface-definitions/dhcpv6-relay.xml.in
@@ -13,7 +13,7 @@
<properties>
<help>Interface for DHCPv6 Relay Agent to listen for requests</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
@@ -49,7 +49,7 @@
<properties>
<help>Interface for DHCPv6 Relay Agent forward requests out</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
diff --git a/interface-definitions/dns-domain-name.xml.in b/interface-definitions/dns-domain-name.xml.in
index 9aca38735..e93c49ebd 100644
--- a/interface-definitions/dns-domain-name.xml.in
+++ b/interface-definitions/dns-domain-name.xml.in
@@ -7,7 +7,7 @@
<help>System Domain Name Servers (DNS)</help>
<priority>400</priority>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>ipv4</format>
@@ -25,7 +25,7 @@
<constraint>
<validator name="ipv4-address"/>
<validator name="ipv6-address"/>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
</leafNode>
@@ -34,7 +34,7 @@
<properties>
<help>System host name (default: vyos)</help>
<constraint>
- <regex>[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]</regex>
+ #include <include/constraint/host-name.xml.i>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in
index a39e412b2..58dd48f9d 100644
--- a/interface-definitions/dns-dynamic.xml.in
+++ b/interface-definitions/dns-dynamic.xml.in
@@ -16,7 +16,7 @@
<properties>
<help>Interface to send DDNS updates for</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in
index 409028572..6b7344b1d 100644
--- a/interface-definitions/dns-forwarding.xml.in
+++ b/interface-definitions/dns-forwarding.xml.in
@@ -31,7 +31,7 @@
<properties>
<help>Interfaces whose DHCP client nameservers to forward requests to</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<multi/>
</properties>
@@ -83,26 +83,16 @@
<tagNode name="domain">
<properties>
<help>Domain to forward to a custom DNS server</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>An absolute DNS domain name</description>
+ </valueHelp>
+ <constraint>
+ <validator name="fqdn"/>
+ </constraint>
</properties>
<children>
- <leafNode name="server">
- <properties>
- <help>Domain Name Server (DNS) to forward queries to</help>
- <valueHelp>
- <format>ipv4</format>
- <description>Domain Name Server (DNS) IPv4 address</description>
- </valueHelp>
- <valueHelp>
- <format>ipv6</format>
- <description>Domain Name Server (DNS) IPv6 address</description>
- </valueHelp>
- <multi/>
- <constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
- </constraint>
- </properties>
- </leafNode>
+ #include <include/name-server-ipv4-ipv6-port.xml.i>
<leafNode name="addnta">
<properties>
<help>Add NTA (negative trust anchor) for this domain (must be set if the domain does not support DNSSEC)</help>
@@ -121,11 +111,11 @@
<properties>
<help>Domain to host authoritative records for</help>
<valueHelp>
- <format>text</format>
- <description>An absolute DNS name</description>
+ <format>txt</format>
+ <description>An absolute DNS domain name</description>
</valueHelp>
<constraint>
- <regex>[-_a-zA-Z0-9.]{1,63}</regex>
+ <validator name="fqdn"/>
</constraint>
</properties>
<children>
@@ -138,7 +128,7 @@
<properties>
<help>"A" record</help>
<valueHelp>
- <format>text</format>
+ <format>txt</format>
<description>A DNS name relative to the root record</description>
</valueHelp>
<valueHelp>
@@ -175,7 +165,7 @@
<properties>
<help>"AAAA" record</help>
<valueHelp>
- <format>text</format>
+ <format>txt</format>
<description>A DNS name relative to the root record</description>
</valueHelp>
<valueHelp>
@@ -212,7 +202,7 @@
<properties>
<help>"CNAME" record</help>
<valueHelp>
- <format>text</format>
+ <format>txt</format>
<description>A DNS name relative to the root record</description>
</valueHelp>
<valueHelp>
@@ -244,7 +234,7 @@
<properties>
<help>"MX" record</help>
<valueHelp>
- <format>text</format>
+ <format>txt</format>
<description>A DNS name relative to the root record</description>
</valueHelp>
<valueHelp>
@@ -291,7 +281,7 @@
<properties>
<help>"PTR" record</help>
<valueHelp>
- <format>text</format>
+ <format>txt</format>
<description>A DNS name relative to the root record</description>
</valueHelp>
<valueHelp>
@@ -323,7 +313,7 @@
<properties>
<help>"TXT" record</help>
<valueHelp>
- <format>text</format>
+ <format>txt</format>
<description>A DNS name relative to the root record</description>
</valueHelp>
<valueHelp>
@@ -339,7 +329,7 @@
<properties>
<help>Record contents</help>
<valueHelp>
- <format>text</format>
+ <format>txt</format>
<description>Record contents</description>
</valueHelp>
<multi/>
@@ -353,7 +343,7 @@
<properties>
<help>"SPF" record (type=SPF)</help>
<valueHelp>
- <format>text</format>
+ <format>txt</format>
<description>A DNS name relative to the root record</description>
</valueHelp>
<valueHelp>
@@ -369,7 +359,7 @@
<properties>
<help>Record contents</help>
<valueHelp>
- <format>text</format>
+ <format>txt</format>
<description>Record contents</description>
</valueHelp>
</properties>
@@ -382,7 +372,7 @@
<properties>
<help>"SRV" record</help>
<valueHelp>
- <format>text</format>
+ <format>txt</format>
<description>A DNS name relative to the root record</description>
</valueHelp>
<valueHelp>
@@ -466,7 +456,7 @@
<properties>
<help>"NAPTR" record</help>
<valueHelp>
- <format>text</format>
+ <format>txt</format>
<description>A DNS name relative to the root record</description>
</valueHelp>
<valueHelp>
@@ -635,7 +625,7 @@
</properties>
<defaultValue>1500</defaultValue>
</leafNode>
- #include <include/name-server-ipv4-ipv6.xml.i>
+ #include <include/name-server-ipv4-ipv6-port.xml.i>
<leafNode name="source-address">
<properties>
<help>Local addresses from which to send DNS queries</help>
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index 7d7e0a38f..69901e5d3 100644
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -146,7 +146,7 @@
<properties>
<help>Interface-group member</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<multi/>
</properties>
@@ -347,8 +347,11 @@
<properties>
<help>Interface name to apply firewall configuration</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
+ <constraint>
+ #include <include/constraint/interface-name-with-wildcard.xml.i>
+ </constraint>
</properties>
<children>
<node name="in">
@@ -459,7 +462,7 @@
</node>
#include <include/firewall/common-rule.xml.i>
#include <include/firewall/dscp.xml.i>
- #include <include/firewall/packet-length.xml.i>
+ #include <include/firewall/packet-options.xml.i>
#include <include/firewall/hop-limit.xml.i>
#include <include/firewall/connection-mark.xml.i>
<node name="icmpv6">
@@ -502,6 +505,7 @@
</completionHelp>
</properties>
</leafNode>
+ #include <include/firewall/nft-queue.xml.i>
</children>
</tagNode>
</children>
@@ -628,7 +632,7 @@
</node>
#include <include/firewall/common-rule.xml.i>
#include <include/firewall/dscp.xml.i>
- #include <include/firewall/packet-length.xml.i>
+ #include <include/firewall/packet-options.xml.i>
#include <include/firewall/connection-mark.xml.i>
<node name="icmp">
<properties>
@@ -671,6 +675,7 @@
</properties>
</leafNode>
#include <include/firewall/ttl.xml.i>
+ #include <include/firewall/nft-queue.xml.i>
</children>
</tagNode>
</children>
@@ -910,7 +915,7 @@
<description>Interface associated with zone</description>
</valueHelp>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<multi/>
</properties>
diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in
index 784e51151..ce6603796 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>
@@ -192,14 +213,14 @@
<properties>
<help>Interface name state check</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
+ <script>${vyos_completion_dir}/list_interfaces --broadcast</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface name</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</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 6adb07598..cf30ab2be 100644
--- a/interface-definitions/https.xml.in
+++ b/interface-definitions/https.xml.in
@@ -60,6 +60,7 @@
<multi/>
</properties>
</leafNode>
+ #include <include/allow-client.xml.i>
</children>
</tagNode>
<node name="api" owner="${vyos_conf_scripts_dir}/http-api.py">
diff --git a/interface-definitions/igmp-proxy.xml.in b/interface-definitions/igmp-proxy.xml.in
index 50cb33a93..0eea85060 100644
--- a/interface-definitions/igmp-proxy.xml.in
+++ b/interface-definitions/igmp-proxy.xml.in
@@ -20,7 +20,7 @@
<properties>
<help>Interface for IGMP proxy</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
diff --git a/interface-definitions/include/accel-ppp/auth-mode.xml.i b/interface-definitions/include/accel-ppp/auth-mode.xml.i
index c1a87cfe3..ccaed6f04 100644
--- a/interface-definitions/include/accel-ppp/auth-mode.xml.i
+++ b/interface-definitions/include/accel-ppp/auth-mode.xml.i
@@ -10,11 +10,15 @@
<format>radius</format>
<description>Use RADIUS server for user autentication</description>
</valueHelp>
+ <valueHelp>
+ <format>noauth</format>
+ <description>Authentication disabled</description>
+ </valueHelp>
<constraint>
- <regex>(local|radius)</regex>
+ <regex>(local|radius|noauth)</regex>
</constraint>
<completionHelp>
- <list>local radius</list>
+ <list>local radius noauth</list>
</completionHelp>
</properties>
<defaultValue>local</defaultValue>
diff --git a/interface-definitions/include/accel-ppp/client-ip-pool-name.xml.i b/interface-definitions/include/accel-ppp/client-ip-pool-name.xml.i
new file mode 100644
index 000000000..b442a15b9
--- /dev/null
+++ b/interface-definitions/include/accel-ppp/client-ip-pool-name.xml.i
@@ -0,0 +1,30 @@
+<!-- include start from accel-ppp/client-ip-pool-name.xml.i -->
+<tagNode name="name">
+ <properties>
+ <help>Pool name</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Name of IP pool</description>
+ </valueHelp>
+ <constraint>
+ <regex>[-_a-zA-Z0-9.]+</regex>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/accel-ppp/gateway-address.xml.i>
+ #include <include/accel-ppp/client-ip-pool-subnet-single.xml.i>
+ <leafNode name="next-pool">
+ <properties>
+ <help>Next pool name</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Name of IP pool</description>
+ </valueHelp>
+ <constraint>
+ <regex>[-_a-zA-Z0-9.]+</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</tagNode>
+<!-- include end -->
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/accel-ppp/shaper.xml.i b/interface-definitions/include/accel-ppp/shaper.xml.i
new file mode 100644
index 000000000..b4f9536d2
--- /dev/null
+++ b/interface-definitions/include/accel-ppp/shaper.xml.i
@@ -0,0 +1,21 @@
+<!-- include start from accel-ppp/shaper.xml.i -->
+<node name="shaper">
+ <properties>
+ <help>Traffic shaper bandwidth parameters</help>
+ </properties>
+ <children>
+ <leafNode name="fwmark">
+ <properties>
+ <help>Firewall mark value for traffic that excludes from shaping</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>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/allow-client.xml.i b/interface-definitions/include/allow-client.xml.i
new file mode 100644
index 000000000..1b06e2c17
--- /dev/null
+++ b/interface-definitions/include/allow-client.xml.i
@@ -0,0 +1,35 @@
+<!-- include start from allow-client.xml.i -->
+<node name="allow-client">
+ <properties>
+ <help>Restrict to allowed IP client addresses</help>
+ </properties>
+ <children>
+ <leafNode name="address">
+ <properties>
+ <help>Allowed IP client addresses</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 address and prefix length</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 address and prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-address"/>
+ <validator name="ip-cidr"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/babel/interface.xml.i b/interface-definitions/include/babel/interface.xml.i
new file mode 100644
index 000000000..a122ef024
--- /dev/null
+++ b/interface-definitions/include/babel/interface.xml.i
@@ -0,0 +1,187 @@
+<!-- include start from babel/interface.xml.i -->
+<tagNode name="interface">
+ <properties>
+ <help>Interface name</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface name</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="type">
+ <properties>
+ <help>Interface type</help>
+ <completionHelp>
+ <list>auto wired wireless</list>
+ </completionHelp>
+ <valueHelp>
+ <format>auto</format>
+ <description>Automatically detect interface type</description>
+ </valueHelp>
+ <valueHelp>
+ <format>wired</format>
+ <description>Wired interface</description>
+ </valueHelp>
+ <valueHelp>
+ <format>wireless</format>
+ <description>Wireless interface</description>
+ </valueHelp>
+ <constraint>
+ <regex>(auto|wired|wireless)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>auto</defaultValue>
+ </leafNode>
+ <leafNode name="split-horizon">
+ <properties>
+ <help>Split horizon parameters</help>
+ <completionHelp>
+ <list>default enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>default</format>
+ <description>Enable on wired interfaces, and disable on wireless interfaces</description>
+ </valueHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable split horizon processing</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable split horizon processing</description>
+ </valueHelp>
+ <constraint>
+ <regex>(default|enable|disable)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>default</defaultValue>
+ </leafNode>
+ <leafNode name="hello-interval">
+ <properties>
+ <help>Time between scheduled hellos</help>
+ <valueHelp>
+ <format>u32:20-655340</format>
+ <description>Milliseconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 20-655340"/>
+ </constraint>
+ </properties>
+ <defaultValue>4000</defaultValue>
+ </leafNode>
+ <leafNode name="update-interval">
+ <properties>
+ <help>Time between scheduled updates</help>
+ <valueHelp>
+ <format>u32:20-655340</format>
+ <description>Milliseconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 20-655340"/>
+ </constraint>
+ </properties>
+ <defaultValue>20000</defaultValue>
+ </leafNode>
+ <leafNode name="rxcost">
+ <properties>
+ <help>Base receive cost for this interface</help>
+ <valueHelp>
+ <format>u32:1-65534</format>
+ <description>Base receive cost</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65534"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="rtt-decay">
+ <properties>
+ <help>Decay factor for exponential moving average of RTT samples</help>
+ <valueHelp>
+ <format>u32:1-256</format>
+ <description>Decay factor, in units of 1/256</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-256"/>
+ </constraint>
+ </properties>
+ <defaultValue>42</defaultValue>
+ </leafNode>
+ <leafNode name="rtt-min">
+ <properties>
+ <help>Minimum RTT</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Milliseconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>10</defaultValue>
+ </leafNode>
+ <leafNode name="rtt-max">
+ <properties>
+ <help>Maximum RTT</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Milliseconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>120</defaultValue>
+ </leafNode>
+ <leafNode name="max-rtt-penalty">
+ <properties>
+ <help>Maximum additional cost due to RTT</help>
+ <valueHelp>
+ <format>u32:0-65535</format>
+ <description>Milliseconds (0 to disable the use of RTT-based cost)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>150</defaultValue>
+ </leafNode>
+ <leafNode name="enable-timestamps">
+ <properties>
+ <help>Enable timestamps with each Hello and IHU message in order to compute RTT values</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="channel">
+ <properties>
+ <help>Channel number for diversity routing</help>
+ <completionHelp>
+ <list>interfering non-interfering</list>
+ </completionHelp>
+ <valueHelp>
+ <format>u32:1-254</format>
+ <description>Interfaces with a channel number interfere with interfering interfaces and interfaces with the same channel number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>interfering</format>
+ <description>Interfering interfaces are assumed to interfere with all other channels except non-interfering channels</description>
+ </valueHelp>
+ <valueHelp>
+ <format>non-interfering</format>
+ <description>Non-interfering interfaces only interfere with themselves</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-254"/>
+ <regex>(interfering|non-interfering)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</tagNode>
+<!-- include end -->
diff --git a/interface-definitions/include/bgp/afi-ipv4-prefix-list.xml.i b/interface-definitions/include/bgp/afi-ipv4-prefix-list.xml.i
index 34b5ec7d7..0f760daae 100644
--- a/interface-definitions/include/bgp/afi-ipv4-prefix-list.xml.i
+++ b/interface-definitions/include/bgp/afi-ipv4-prefix-list.xml.i
@@ -15,7 +15,7 @@
<description>Name of IPv4 prefix-list</description>
</valueHelp>
<constraint>
- <regex>[-_a-zA-Z0-9]+</regex>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</constraint>
<constraintErrorMessage>Name of prefix-list can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage>
</properties>
@@ -31,7 +31,7 @@
<description>Name of IPv4 prefix-list</description>
</valueHelp>
<constraint>
- <regex>[-_a-zA-Z0-9]+</regex>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</constraint>
<constraintErrorMessage>Name of prefix-list can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage>
</properties>
diff --git a/interface-definitions/include/bgp/afi-ipv6-prefix-list.xml.i b/interface-definitions/include/bgp/afi-ipv6-prefix-list.xml.i
index 06c661a90..268d9cbc0 100644
--- a/interface-definitions/include/bgp/afi-ipv6-prefix-list.xml.i
+++ b/interface-definitions/include/bgp/afi-ipv6-prefix-list.xml.i
@@ -15,7 +15,7 @@
<description>Name of IPv6 prefix-list</description>
</valueHelp>
<constraint>
- <regex>[-_a-zA-Z0-9]+</regex>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</constraint>
<constraintErrorMessage>Name of prefix-list6 can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage>
</properties>
@@ -31,7 +31,7 @@
<description>Name of IPv6 prefix-list</description>
</valueHelp>
<constraint>
- <regex>[-_a-zA-Z0-9]+</regex>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</constraint>
<constraintErrorMessage>Name of prefix-list6 can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage>
</properties>
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-path-attribute.xml.i b/interface-definitions/include/bgp/neighbor-path-attribute.xml.i
new file mode 100644
index 000000000..f4f2fcfa9
--- /dev/null
+++ b/interface-definitions/include/bgp/neighbor-path-attribute.xml.i
@@ -0,0 +1,21 @@
+<!-- include start from bgp/neighbor-path-attribute.xml.i -->
+<node name="path-attribute">
+ <properties>
+ <help>Manipulate path attributes from incoming UPDATE messages</help>
+ </properties>
+ <children>
+ <leafNode name="discard">
+ <properties>
+ <help>Drop specified attributes from incoming UPDATE messages</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>Attribute number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- 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 60c127e8f..c6aa776c2 100644
--- a/interface-definitions/include/bgp/neighbor-update-source.xml.i
+++ b/interface-definitions/include/bgp/neighbor-update-source.xml.i
@@ -5,7 +5,7 @@
<help>Source IP of routing updates</help>
<completionHelp>
<script>${vyos_completion_dir}/list_local_ips.sh --both</script>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>ipv4</format>
@@ -22,7 +22,7 @@
<constraint>
<validator name="ipv4-address"/>
<validator name="ipv6-address"/>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</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 366630f78..bcc131f83 100644
--- a/interface-definitions/include/bgp/protocol-common-config.xml.i
+++ b/interface-definitions/include/bgp/protocol-common-config.xml.i
@@ -165,6 +165,14 @@
#include <include/bgp/afi-redistribute-metric-route-map.xml.i>
</children>
</node>
+ <node name="babel">
+ <properties>
+ <help>Redistribute Babel routes into BGP</help>
+ </properties>
+ <children>
+ #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
+ </children>
+ </node>
<node name="static">
<properties>
<help>Redistribute static routes into BGP</help>
@@ -351,7 +359,7 @@
<properties>
<help>Interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<multi/>
</properties>
@@ -542,6 +550,14 @@
#include <include/bgp/afi-redistribute-metric-route-map.xml.i>
</children>
</node>
+ <node name="babel">
+ <properties>
+ <help>Redistribute Babel routes into BGP</help>
+ </properties>
+ <children>
+ #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
+ </children>
+ </node>
<node name="static">
<properties>
<help>Redistribute static routes into BGP</help>
@@ -723,7 +739,7 @@
<properties>
<help>Interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<multi/>
</properties>
@@ -794,12 +810,6 @@
</node>
</children>
</node>
- <leafNode name="advertise-all-vni">
- <properties>
- <help>Advertise All local VNIs</help>
- <valueless/>
- </properties>
- </leafNode>
#include <include/bgp/afi-l2vpn-common.xml.i>
<leafNode name="advertise-pip">
<properties>
@@ -926,7 +936,7 @@
<constraint>
<validator name="ipv4-address"/>
<validator name="ipv6-address"/>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
<children>
@@ -987,22 +997,12 @@
</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-path-attribute.xml.i>
#include <include/bgp/neighbor-passive.xml.i>
#include <include/bgp/neighbor-password.xml.i>
#include <include/bgp/peer-group.xml.i>
- <leafNode name="port">
- <properties>
- <help>Neighbor BGP port</help>
- <valueHelp>
- <format>u32:1-65535</format>
- <description>Neighbor BGP port number</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-65535"/>
- </constraint>
- </properties>
- </leafNode>
#include <include/bgp/remote-as.xml.i>
#include <include/bgp/neighbor-shutdown.xml.i>
<leafNode name="solo">
@@ -1044,6 +1044,7 @@
</node>
#include <include/bgp/neighbor-ttl-security.xml.i>
#include <include/bgp/neighbor-update-source.xml.i>
+ #include <include/port-number.xml.i>
</children>
</tagNode>
<node name="parameters">
@@ -1474,13 +1475,56 @@
</properties>
</leafNode>
#include <include/router-id.xml.i>
+ <node name="tcp-keepalive">
+ <properties>
+ <help>TCP keepalive parameters</help>
+ </properties>
+ <children>
+ <leafNode name="idle">
+ <properties>
+ <help>TCP keepalive idle time</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Idle time in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="interval">
+ <properties>
+ <help>TCP keepalive interval</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="probes">
+ <properties>
+ <help>TCP keepalive maximum probes</help>
+ <valueHelp>
+ <format>u32:1-30</format>
+ <description>Maximum probes</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-30"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
</children>
</node>
<tagNode name="peer-group">
<properties>
<help>Name of peer-group</help>
<constraint>
- <regex>[-_a-zA-Z0-9]+</regex>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</constraint>
</properties>
<children>
@@ -1503,16 +1547,18 @@
#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-path-attribute.xml.i>
#include <include/bgp/neighbor-passive.xml.i>
#include <include/bgp/neighbor-password.xml.i>
- #include <include/bgp/remote-as.xml.i>
#include <include/bgp/neighbor-shutdown.xml.i>
#include <include/bgp/neighbor-ttl-security.xml.i>
#include <include/bgp/neighbor-update-source.xml.i>
+ #include <include/bgp/remote-as.xml.i>
+ #include <include/port-number.xml.i>
</children>
</tagNode>
-#include <include/route-map.xml.i>
<node name="timers">
<properties>
<help>BGP protocol timers</help>
diff --git a/interface-definitions/include/bgp/timers-holdtime.xml.i b/interface-definitions/include/bgp/timers-holdtime.xml.i
index 9e86ab13d..31e97f6b8 100644
--- a/interface-definitions/include/bgp/timers-holdtime.xml.i
+++ b/interface-definitions/include/bgp/timers-holdtime.xml.i
@@ -1,14 +1,14 @@
<!-- include start from bgp/timers-holdtime.xml.i -->
<leafNode name="holdtime">
<properties>
- <help>BGP hold timer for this neighbor</help>
+ <help>Hold timer</help>
<valueHelp>
<format>u32:1-65535</format>
<description>Hold timer in seconds</description>
</valueHelp>
<valueHelp>
<format>0</format>
- <description>Hold timer disabled</description>
+ <description>Disable hold timer</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-65535"/>
diff --git a/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i b/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i
new file mode 100644
index 000000000..eb568d7d9
--- /dev/null
+++ b/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i
@@ -0,0 +1,3 @@
+<!-- include start from include/constraint/alpha-numeric-hyphen-underscore.xml.in -->
+<regex>[-_a-zA-Z0-9]+</regex>
+<!-- include end -->
diff --git a/interface-definitions/include/constraint/host-name.xml.i b/interface-definitions/include/constraint/host-name.xml.i
new file mode 100644
index 000000000..202c200f4
--- /dev/null
+++ b/interface-definitions/include/constraint/host-name.xml.i
@@ -0,0 +1,3 @@
+<!-- include start from constraint/host-name.xml.in -->
+<regex>[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]</regex>
+<!-- include end -->
diff --git a/interface-definitions/include/constraint/interface-name-with-wildcard.xml.i b/interface-definitions/include/constraint/interface-name-with-wildcard.xml.i
new file mode 100644
index 000000000..09867b380
--- /dev/null
+++ b/interface-definitions/include/constraint/interface-name-with-wildcard.xml.i
@@ -0,0 +1,4 @@
+<!-- include start from constraint/interface-name-with-wildcard.xml.in -->
+<regex>(bond|br|dum|en|ersp|eth|gnv|ifb|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)([0-9]?)(\*?)(.+)?|lo</regex>
+<validator name="file-path --lookup-path /sys/class/net --directory"/>
+<!-- include end -->
diff --git a/interface-definitions/include/constraint/interface-name.xml.in b/interface-definitions/include/constraint/interface-name.xml.i
index 2d1f7b757..e540e4418 100644
--- a/interface-definitions/include/constraint/interface-name.xml.in
+++ b/interface-definitions/include/constraint/interface-name.xml.i
@@ -1,4 +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>
+<regex>(bond|br|dum|en|ersp|eth|gnv|ifb|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..0db11cf79
--- /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</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>DHCP interface name</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ </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 f5107ba2b..b5c94cb24 100644
--- a/interface-definitions/include/dhcp-interface.xml.i
+++ b/interface-definitions/include/dhcp-interface.xml.i
@@ -2,14 +2,14 @@
<properties>
<help>DHCP interface supplying next-hop IP address</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>DHCP interface name</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/eigrp/protocol-common-config.xml.i b/interface-definitions/include/eigrp/protocol-common-config.xml.i
index 147277102..a21d18424 100644
--- a/interface-definitions/include/eigrp/protocol-common-config.xml.i
+++ b/interface-definitions/include/eigrp/protocol-common-config.xml.i
@@ -59,7 +59,7 @@
<properties>
<help>Suppress routing updates on an interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
</leafNode>
@@ -87,6 +87,10 @@
<description>Routing Information Protocol (RIP)</description>
</valueHelp>
<valueHelp>
+ <format>babel</format>
+ <description>Babel routing protocol (Babel)</description>
+ </valueHelp>
+ <valueHelp>
<format>static</format>
<description>Statically configured routes</description>
</valueHelp>
@@ -98,12 +102,11 @@
<list>bgp connected nhrp ospf rip static vnc</list>
</completionHelp>
<constraint>
- <regex>(bgp|connected|nhrp|ospf|rip|static|vnc)</regex>
+ <regex>(bgp|connected|nhrp|ospf|rip|babel|static|vnc)</regex>
</constraint>
<multi/>
</properties>
</leafNode>
-#include <include/route-map.xml.i>
#include <include/router-id.xml.i>
<!-- FRR timers not implemented yet -->
<leafNode name="variance">
diff --git a/interface-definitions/include/firewall/action.xml.i b/interface-definitions/include/firewall/action.xml.i
index 468340cbb..7c6e33839 100644
--- a/interface-definitions/include/firewall/action.xml.i
+++ b/interface-definitions/include/firewall/action.xml.i
@@ -3,7 +3,7 @@
<properties>
<help>Rule action</help>
<completionHelp>
- <list>accept jump reject return drop</list>
+ <list>accept jump reject return drop queue</list>
</completionHelp>
<valueHelp>
<format>accept</format>
@@ -25,8 +25,12 @@
<format>drop</format>
<description>Drop matching entries</description>
</valueHelp>
+ <valueHelp>
+ <format>queue</format>
+ <description>Enqueue packet to userspace</description>
+ </valueHelp>
<constraint>
- <regex>(accept|jump|reject|return|drop)</regex>
+ <regex>(accept|jump|reject|return|drop|queue)</regex>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i
index 75acefd96..7417a3c58 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>
@@ -111,7 +119,7 @@
</constraint>
</properties>
</leafNode>
-#include <include/firewall/rule-log-level.xml.i>
+#include <include/firewall/rule-log-options.xml.i>
<node name="connection-status">
<properties>
<help>Connection status</help>
diff --git a/interface-definitions/include/firewall/connection-mark.xml.i b/interface-definitions/include/firewall/connection-mark.xml.i
index 2cb826635..69f7fe62c 100644
--- a/interface-definitions/include/firewall/connection-mark.xml.i
+++ b/interface-definitions/include/firewall/connection-mark.xml.i
@@ -3,11 +3,11 @@
<properties>
<help>Connection mark</help>
<valueHelp>
- <format>u32:1-2147483647</format>
+ <format>u32:0-2147483647</format>
<description>Connection-mark to match</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 1-2147483647"/>
+ <validator name="numeric" argument="--range 0-2147483647"/>
</constraint>
<multi/>
</properties>
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/match-interface.xml.i b/interface-definitions/include/firewall/match-interface.xml.i
index 675a87574..3e52422cf 100644
--- a/interface-definitions/include/firewall/match-interface.xml.i
+++ b/interface-definitions/include/firewall/match-interface.xml.i
@@ -3,7 +3,7 @@
<properties>
<help>Match interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
</leafNode>
diff --git a/interface-definitions/include/firewall/nft-queue.xml.i b/interface-definitions/include/firewall/nft-queue.xml.i
new file mode 100644
index 000000000..8799eac74
--- /dev/null
+++ b/interface-definitions/include/firewall/nft-queue.xml.i
@@ -0,0 +1,34 @@
+<!-- include start from firewall/nft-queue.xml.i -->
+<leafNode name="queue">
+ <properties>
+ <help>Queue target to use. Action queue must be defined to use this setting</help>
+ <valueHelp>
+ <format>u32:0-65535</format>
+ <description>Queue target</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--allow-range --range 0-65535"/>
+ </constraint>
+ </properties>
+</leafNode>
+<leafNode name="queue-options">
+ <properties>
+ <help>Options used for queue target. Action queue must be defined to use this setting</help>
+ <completionHelp>
+ <list>bypass fanout</list>
+ </completionHelp>
+ <valueHelp>
+ <format>bypass</format>
+ <description>Let packets go through if userspace application cannot back off</description>
+ </valueHelp>
+ <valueHelp>
+ <format>fanout</format>
+ <description>Distribute packets between several queues</description>
+ </valueHelp>
+ <constraint>
+ <regex>(bypass|fanout)</regex>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/packet-length.xml.i b/interface-definitions/include/firewall/packet-options.xml.i
index fd2eb67b0..cd94e69c2 100644
--- a/interface-definitions/include/firewall/packet-length.xml.i
+++ b/interface-definitions/include/firewall/packet-options.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from firewall/packet-length.xml.i -->
+<!-- include start from firewall/packet-options.xml.i -->
<leafNode name="packet-length">
<properties>
<help>Payload size in bytes, including header and data to match</help>
@@ -33,4 +33,31 @@
<multi/>
</properties>
</leafNode>
+<leafNode name="packet-type">
+ <properties>
+ <help>Packet type</help>
+ <completionHelp>
+ <list>broadcast host multicast other</list>
+ </completionHelp>
+ <valueHelp>
+ <format>broadcast</format>
+ <description>Match broadcast packet type</description>
+ </valueHelp>
+ <valueHelp>
+ <format>host</format>
+ <description>Match host packet type, addressed to local host</description>
+ </valueHelp>
+ <valueHelp>
+ <format>multicast</format>
+ <description>Match multicast packet type</description>
+ </valueHelp>
+ <valueHelp>
+ <format>other</format>
+ <description>Match packet addressed to another host</description>
+ </valueHelp>
+ <constraint>
+ <regex>(broadcast|host|multicast|other)</regex>
+ </constraint>
+ </properties>
+</leafNode>
<!-- include end -->
diff --git a/interface-definitions/include/firewall/rule-log-level.xml.i b/interface-definitions/include/firewall/rule-log-level.xml.i
index 10c8de5e3..3ac473844 100644
--- a/interface-definitions/include/firewall/rule-log-level.xml.i
+++ b/interface-definitions/include/firewall/rule-log-level.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from firewall/common-rule.xml.i -->
+<!-- include start from firewall/rule-log-level.xml.i -->
<leafNode name="log-level">
<properties>
<help>Set log-level. Log must be enable.</help>
diff --git a/interface-definitions/include/firewall/rule-log-options.xml.i b/interface-definitions/include/firewall/rule-log-options.xml.i
new file mode 100644
index 000000000..e8b0cdec3
--- /dev/null
+++ b/interface-definitions/include/firewall/rule-log-options.xml.i
@@ -0,0 +1,89 @@
+<!-- include start from firewall/rule-log-options.xml.i -->
+<node name="log-options">
+ <properties>
+ <help>Log options</help>
+ </properties>
+ <children>
+ <leafNode name="group">
+ <properties>
+ <help>Set log group</help>
+ <valueHelp>
+ <format>u32:0-65535</format>
+ <description>Log group to send messages to</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="snapshot-length">
+ <properties>
+ <help>Length of packet payload to include in netlink message</help>
+ <valueHelp>
+ <format>u32:0-9000</format>
+ <description>Length of packet payload to include in netlink message</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-9000"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="queue-threshold">
+ <properties>
+ <help>Number of packets to queue inside the kernel before sending them to userspace</help>
+ <valueHelp>
+ <format>u32:0-65535</format>
+ <description>Number of packets to queue inside the kernel before sending them to userspace</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="level">
+ <properties>
+ <help>Set log-level</help>
+ <completionHelp>
+ <list>emerg alert crit err warn notice info debug</list>
+ </completionHelp>
+ <valueHelp>
+ <format>emerg</format>
+ <description>Emerg log level</description>
+ </valueHelp>
+ <valueHelp>
+ <format>alert</format>
+ <description>Alert log level</description>
+ </valueHelp>
+ <valueHelp>
+ <format>crit</format>
+ <description>Critical log level</description>
+ </valueHelp>
+ <valueHelp>
+ <format>err</format>
+ <description>Error log level</description>
+ </valueHelp>
+ <valueHelp>
+ <format>warn</format>
+ <description>Warning log level</description>
+ </valueHelp>
+ <valueHelp>
+ <format>notice</format>
+ <description>Notice log level</description>
+ </valueHelp>
+ <valueHelp>
+ <format>info</format>
+ <description>Info log level</description>
+ </valueHelp>
+ <valueHelp>
+ <format>debug</format>
+ <description>Debug log level</description>
+ </valueHelp>
+ <constraint>
+ <regex>(emerg|alert|crit|err|warn|notice|info|debug)</regex>
+ </constraint>
+ <constraintErrorMessage>level must be alert, crit, debug, emerg, err, info, notice or warn</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
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 af35a888b..e37e75012 100644
--- a/interface-definitions/include/generic-interface-broadcast.xml.i
+++ b/interface-definitions/include/generic-interface-broadcast.xml.i
@@ -1,16 +1,16 @@
<!-- include start from generic-interface-broadcast.xml.i -->
<leafNode name="interface">
<properties>
- <help>Interface Name to use</help>
+ <help>Interface to use</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
+ <script>${vyos_completion_dir}/list_interfaces --broadcast</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface name</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</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 1ae38fb43..ed13cf2cf 100644
--- a/interface-definitions/include/generic-interface-multi-broadcast.xml.i
+++ b/interface-definitions/include/generic-interface-multi-broadcast.xml.i
@@ -1,16 +1,16 @@
<!-- include start from generic-interface-multi-broadcast.xml.i -->
<leafNode name="interface">
<properties>
- <help>Interface Name to use</help>
+ <help>Interface to use</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
+ <script>${vyos_completion_dir}/list_interfaces --broadcast</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface name</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
<multi/>
</properties>
diff --git a/interface-definitions/include/generic-interface-multi-wildcard.xml.i b/interface-definitions/include/generic-interface-multi-wildcard.xml.i
new file mode 100644
index 000000000..6c846a795
--- /dev/null
+++ b/interface-definitions/include/generic-interface-multi-wildcard.xml.i
@@ -0,0 +1,18 @@
+<!-- include start from generic-interface-multi-wildcard.xml.i -->
+<leafNode name="interface">
+ <properties>
+ <help>Interface to use</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface name, wildcard (*) supported</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/interface-name-with-wildcard.xml.i>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/generic-interface-multi.xml.i b/interface-definitions/include/generic-interface-multi.xml.i
index 16916ff54..cfc77af3a 100644
--- a/interface-definitions/include/generic-interface-multi.xml.i
+++ b/interface-definitions/include/generic-interface-multi.xml.i
@@ -3,14 +3,14 @@
<properties>
<help>Interface to use</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface name</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
<multi/>
</properties>
diff --git a/interface-definitions/include/generic-interface.xml.i b/interface-definitions/include/generic-interface.xml.i
index 36ddee417..65f5bfbb8 100644
--- a/interface-definitions/include/generic-interface.xml.i
+++ b/interface-definitions/include/generic-interface.xml.i
@@ -3,14 +3,14 @@
<properties>
<help>Interface to use</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface name</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/generic-password.xml.i b/interface-definitions/include/generic-password.xml.i
new file mode 100644
index 000000000..76d5f12d8
--- /dev/null
+++ b/interface-definitions/include/generic-password.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from generic-password.xml.i -->
+<leafNode name="password">
+ <properties>
+ <help>Password used for authentication</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Password</description>
+ </valueHelp>
+ <constraint>
+ <regex>[[:ascii:]]{1,128}</regex>
+ </constraint>
+ <constraintErrorMessage>Password is limited to ASCII characters only, with a total length of 128</constraintErrorMessage>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/generic-username.xml.i b/interface-definitions/include/generic-username.xml.i
new file mode 100644
index 000000000..678f30ddf
--- /dev/null
+++ b/interface-definitions/include/generic-username.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from generic-username.xml.i -->
+<leafNode name="username">
+ <properties>
+ <help>Username used for authentication</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Username</description>
+ </valueHelp>
+ <constraint>
+ <regex>[[:ascii:]]{1,128}</regex>
+ </constraint>
+ <constraintErrorMessage>Username is limited to ASCII characters only, with a total length of 128</constraintErrorMessage>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/inbound-interface.xml.i b/interface-definitions/include/inbound-interface.xml.i
index 3289bbf8f..422f9de75 100644
--- a/interface-definitions/include/inbound-interface.xml.i
+++ b/interface-definitions/include/inbound-interface.xml.i
@@ -4,7 +4,7 @@
<help>Inbound interface of NAT traffic</help>
<completionHelp>
<list>any</list>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
</leafNode>
diff --git a/interface-definitions/include/interface/authentication.xml.i b/interface-definitions/include/interface/authentication.xml.i
index c097ca9dd..0bd792209 100644
--- a/interface-definitions/include/interface/authentication.xml.i
+++ b/interface-definitions/include/interface/authentication.xml.i
@@ -4,24 +4,8 @@
<help>Authentication settings</help>
</properties>
<children>
- <leafNode name="user">
- <properties>
- <help>User name</help>
- <valueHelp>
- <format>txt</format>
- <description>Username used for connection</description>
- </valueHelp>
- </properties>
- </leafNode>
- <leafNode name="password">
- <properties>
- <help>Password</help>
- <valueHelp>
- <format>txt</format>
- <description>Password used for connection</description>
- </valueHelp>
- </properties>
- </leafNode>
+ #include <include/generic-username.xml.i>
+ #include <include/generic-password.xml.i>
</children>
</node>
<!-- include end -->
diff --git a/interface-definitions/include/interface/description.xml.i b/interface-definitions/include/interface/description.xml.i
deleted file mode 100644
index de01d22ca..000000000
--- a/interface-definitions/include/interface/description.xml.i
+++ /dev/null
@@ -1,11 +0,0 @@
-<!-- include start from interface/description.xml.i -->
-<leafNode name="description">
- <properties>
- <help>Interface specific description</help>
- <constraint>
- <regex>.{1,256}</regex>
- </constraint>
- <constraintErrorMessage>Description too long (limit 256 characters)</constraintErrorMessage>
- </properties>
-</leafNode>
-<!-- include end -->
diff --git a/interface-definitions/include/interface/dhcpv6-options.xml.i b/interface-definitions/include/interface/dhcpv6-options.xml.i
index c705af7c2..609af1a2b 100644
--- a/interface-definitions/include/interface/dhcpv6-options.xml.i
+++ b/interface-definitions/include/interface/dhcpv6-options.xml.i
@@ -51,7 +51,7 @@
<properties>
<help>Delegate IPv6 prefix from provider to this interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
+ <script>${vyos_completion_dir}/list_interfaces --broadcast</script>
</completionHelp>
</properties>
<children>
diff --git a/interface-definitions/include/interface/inbound-interface.xml.i b/interface-definitions/include/interface/inbound-interface.xml.i
index 5a8d47280..96ade331d 100644
--- a/interface-definitions/include/interface/inbound-interface.xml.i
+++ b/interface-definitions/include/interface/inbound-interface.xml.i
@@ -3,7 +3,7 @@
<properties>
<help>Inbound Interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
</leafNode>
diff --git a/interface-definitions/include/interface/mirror.xml.i b/interface-definitions/include/interface/mirror.xml.i
index 2959551f0..903c62777 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</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</script>
</completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Destination interface name</description>
+ </valueHelp>
</properties>
</leafNode>
</children>
diff --git a/interface-definitions/include/interface/redirect.xml.i b/interface-definitions/include/interface/redirect.xml.i
index 8df8957ac..9b41cd8ff 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>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>txt</format>
- <description>Interface name</description>
+ <description>Destination interface name</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i
index 6d50d7238..fdd62b63d 100644
--- a/interface-definitions/include/interface/vif-s.xml.i
+++ b/interface-definitions/include/interface/vif-s.xml.i
@@ -12,8 +12,8 @@
<constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage>
</properties>
<children>
+ #include <include/generic-description.xml.i>
#include <include/interface/address-ipv4-ipv6-dhcp.xml.i>
- #include <include/interface/description.xml.i>
#include <include/interface/dhcp-options.xml.i>
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/disable-link-detect.xml.i>
@@ -53,8 +53,8 @@
<constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage>
</properties>
<children>
+ #include <include/generic-description.xml.i>
#include <include/interface/address-ipv4-ipv6-dhcp.xml.i>
- #include <include/interface/description.xml.i>
#include <include/interface/dhcp-options.xml.i>
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/disable-link-detect.xml.i>
diff --git a/interface-definitions/include/interface/vif.xml.i b/interface-definitions/include/interface/vif.xml.i
index 3f8f113ea..ec3921bf6 100644
--- a/interface-definitions/include/interface/vif.xml.i
+++ b/interface-definitions/include/interface/vif.xml.i
@@ -12,8 +12,8 @@
<constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage>
</properties>
<children>
+ #include <include/generic-description.xml.i>
#include <include/interface/address-ipv4-ipv6-dhcp.xml.i>
- #include <include/interface/description.xml.i>
#include <include/interface/dhcp-options.xml.i>
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/disable-link-detect.xml.i>
diff --git a/interface-definitions/include/isis/ldp-sync-holddown.xml.i b/interface-definitions/include/isis/ldp-sync-holddown.xml.i
new file mode 100644
index 000000000..15ac26f07
--- /dev/null
+++ b/interface-definitions/include/isis/ldp-sync-holddown.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from isis/ldp-sync-holddown.xml.i -->
+<leafNode name="holddown">
+ <properties>
+ <help>Hold down timer for LDP-IGP cost restoration</help>
+ <valueHelp>
+ <format>u32:0-10000</format>
+ <description>Time to wait in seconds for LDP-IGP synchronization to occur before restoring interface cost</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-10000"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/isis/ldp-sync-interface.xml.i b/interface-definitions/include/isis/ldp-sync-interface.xml.i
new file mode 100644
index 000000000..222a35256
--- /dev/null
+++ b/interface-definitions/include/isis/ldp-sync-interface.xml.i
@@ -0,0 +1,11 @@
+<!-- include start from isis/ldp-igp-sync.xml.i -->
+<node name="ldp-sync">
+ <properties>
+ <help>LDP-IGP synchronization configuration for interface</help>
+ </properties>
+ <children>
+ #include <include/generic-disable-node.xml.i>
+ #include <include/isis/ldp-sync-holddown.xml.i>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/isis/ldp-sync-protocol.xml.i b/interface-definitions/include/isis/ldp-sync-protocol.xml.i
new file mode 100644
index 000000000..b2e696a70
--- /dev/null
+++ b/interface-definitions/include/isis/ldp-sync-protocol.xml.i
@@ -0,0 +1,10 @@
+<!-- include start from isis/ldp-igp-sync.xml.i -->
+<node name="ldp-sync">
+ <properties>
+ <help>Protocol wide LDP-IGP synchronization configuration</help>
+ </properties>
+ <children>
+ #include <include/isis/ldp-sync-holddown.xml.i>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/isis/protocol-common-config.xml.i b/interface-definitions/include/isis/protocol-common-config.xml.i
index 42bda7a80..4ca7061db 100644
--- a/interface-definitions/include/isis/protocol-common-config.xml.i
+++ b/interface-definitions/include/isis/protocol-common-config.xml.i
@@ -152,6 +152,7 @@
</constraint>
</properties>
</leafNode>
+#include <include/isis/ldp-sync-protocol.xml.i>
<leafNode name="net">
<properties>
<help>A Network Entity Title for this process (ISO only)</help>
@@ -172,7 +173,7 @@
</leafNode>
<node name="traffic-engineering">
<properties>
- <help>Show IS-IS neighbor adjacencies</help>
+ <help>IS-IS traffic engineering extensions</help>
</properties>
<children>
<leafNode name="enable">
@@ -394,6 +395,14 @@
#include <include/isis/redistribute-level-1-2.xml.i>
</children>
</node>
+ <node name="babel">
+ <properties>
+ <help>Redistribute Babel routes into IS-IS</help>
+ </properties>
+ <children>
+ #include <include/isis/redistribute-level-1-2.xml.i>
+ </children>
+ </node>
<node name="static">
<properties>
<help>Redistribute static routes into IS-IS</help>
@@ -449,6 +458,14 @@
#include <include/isis/redistribute-level-1-2.xml.i>
</children>
</node>
+ <node name="babel">
+ <properties>
+ <help>Redistribute Babel routes into IS-IS</help>
+ </properties>
+ <children>
+ #include <include/isis/redistribute-level-1-2.xml.i>
+ </children>
+ </node>
<node name="static">
<properties>
<help>Redistribute static routes into IS-IS</help>
@@ -556,7 +573,7 @@
<properties>
<help>Interface params</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
@@ -615,6 +632,7 @@
</properties>
</leafNode>
#include <include/isis/metric.xml.i>
+ #include <include/isis/ldp-sync-interface.xml.i>
<node name="network">
<properties>
<help>Set network type</help>
@@ -669,5 +687,4 @@
</leafNode>
</children>
</tagNode>
-#include <include/route-map.xml.i>
-<!-- include end --> \ No newline at end of file
+<!-- include end -->
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/name-server-ipv4-ipv6-port.xml.i b/interface-definitions/include/name-server-ipv4-ipv6-port.xml.i
new file mode 100644
index 000000000..fb0a4f4ae
--- /dev/null
+++ b/interface-definitions/include/name-server-ipv4-ipv6-port.xml.i
@@ -0,0 +1,25 @@
+<!-- include start from name-server-ipv4-ipv6-port.xml.i -->
+<tagNode name="name-server">
+ <properties>
+ <help>Domain Name Servers (DNS) addresses to forward queries to</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Domain Name Server (DNS) IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Domain Name Server (DNS) IPv6 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>53</defaultValue>
+ </leafNode>
+ </children>
+</tagNode>
+<!-- include end -->
diff --git a/interface-definitions/include/nat-interface.xml.i b/interface-definitions/include/nat-interface.xml.i
index 68969472f..ef1ffc1ba 100644
--- a/interface-definitions/include/nat-interface.xml.i
+++ b/interface-definitions/include/nat-interface.xml.i
@@ -4,7 +4,7 @@
<help>Outbound interface of NAT traffic</help>
<completionHelp>
<list>any</list>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
</leafNode>
diff --git a/interface-definitions/include/nat-rule.xml.i b/interface-definitions/include/nat-rule.xml.i
index 8f2029388..7b3b8804e 100644
--- a/interface-definitions/include/nat-rule.xml.i
+++ b/interface-definitions/include/nat-rule.xml.i
@@ -31,6 +31,33 @@
<valueless/>
</properties>
</leafNode>
+ <leafNode name="packet-type">
+ <properties>
+ <help>Packet type</help>
+ <completionHelp>
+ <list>broadcast host multicast other</list>
+ </completionHelp>
+ <valueHelp>
+ <format>broadcast</format>
+ <description>Match broadcast packet type</description>
+ </valueHelp>
+ <valueHelp>
+ <format>host</format>
+ <description>Match host packet type, addressed to local host</description>
+ </valueHelp>
+ <valueHelp>
+ <format>multicast</format>
+ <description>Match multicast packet type</description>
+ </valueHelp>
+ <valueHelp>
+ <format>other</format>
+ <description>Match packet addressed to another host</description>
+ </valueHelp>
+ <constraint>
+ <regex>(broadcast|host|multicast|other)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
<leafNode name="protocol">
<properties>
<help>Protocol to NAT</help>
diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i
index 06609c10e..b7f22cb88 100644
--- a/interface-definitions/include/ospf/protocol-common-config.xml.i
+++ b/interface-definitions/include/ospf/protocol-common-config.xml.i
@@ -331,6 +331,7 @@
</constraint>
</properties>
</leafNode>
+#include <include/isis/ldp-sync-protocol.xml.i>
<node name="distance">
<properties>
<help>Administrative distance</help>
@@ -351,14 +352,14 @@
<properties>
<help>Interface configuration</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface name</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
<children>
@@ -385,6 +386,7 @@
#include <include/ospf/authentication.xml.i>
#include <include/ospf/intervals.xml.i>
#include <include/ospf/interface-common.xml.i>
+ #include <include/isis/ldp-sync-interface.xml.i>
<leafNode name="bandwidth">
<properties>
<help>Interface bandwidth (Mbit/s)</help>
@@ -756,6 +758,16 @@
#include <include/route-map.xml.i>
</children>
</node>
+ <node name="babel">
+ <properties>
+ <help>Redistribute Babel routes</help>
+ </properties>
+ <children>
+ #include <include/ospf/metric.xml.i>
+ #include <include/ospf/metric-type.xml.i>
+ #include <include/route-map.xml.i>
+ </children>
+ </node>
<node name="static">
<properties>
<help>Redistribute statically configured routes</help>
@@ -804,7 +816,6 @@
</leafNode>
</children>
</node>
-#include <include/route-map.xml.i>
<node name="timers">
<properties>
<help>Adjust routing timers</help>
@@ -865,4 +876,4 @@
</node>
</children>
</node>
-<!-- include end -->
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/ospfv3/protocol-common-config.xml.i b/interface-definitions/include/ospfv3/protocol-common-config.xml.i
index c0aab912d..a7de50638 100644
--- a/interface-definitions/include/ospfv3/protocol-common-config.xml.i
+++ b/interface-definitions/include/ospfv3/protocol-common-config.xml.i
@@ -111,14 +111,14 @@
<properties>
<help>Enable routing on an IPv6 interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface used for routing information exchange</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
<children>
@@ -238,6 +238,14 @@
#include <include/route-map.xml.i>
</children>
</node>
+ <node name="babel">
+ <properties>
+ <help>Redistribute Babel routes</help>
+ </properties>
+ <children>
+ #include <include/route-map.xml.i>
+ </children>
+ </node>
<node name="static">
<properties>
<help>Redistribute static routes</help>
@@ -248,5 +256,4 @@
</node>
</children>
</node>
-#include <include/route-map.xml.i>
<!-- include end -->
diff --git a/interface-definitions/include/policy/route-common.xml.i b/interface-definitions/include/policy/route-common.xml.i
index 6973d7a8f..216ec9bea 100644
--- a/interface-definitions/include/policy/route-common.xml.i
+++ b/interface-definitions/include/policy/route-common.xml.i
@@ -163,11 +163,11 @@
<properties>
<help>Connection marking</help>
<valueHelp>
- <format>u32:1-2147483647</format>
+ <format>u32:0-2147483647</format>
<description>Connection marking</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 1-2147483647"/>
+ <validator name="numeric" argument="--range 0-2147483647"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/policy/route-rule-action.xml.i b/interface-definitions/include/policy/route-rule-action.xml.i
index 456a21400..c2698db5a 100644
--- a/interface-definitions/include/policy/route-rule-action.xml.i
+++ b/interface-definitions/include/policy/route-rule-action.xml.i
@@ -3,14 +3,26 @@
<properties>
<help>Rule action</help>
<completionHelp>
- <list>drop</list>
+ <list>accept reject return drop</list>
</completionHelp>
<valueHelp>
+ <format>accept</format>
+ <description>Accept matching entries</description>
+ </valueHelp>
+ <valueHelp>
+ <format>reject</format>
+ <description>Reject matching entries</description>
+ </valueHelp>
+ <valueHelp>
+ <format>return</format>
+ <description>Return from the current chain and continue at the next rule of the last chain</description>
+ </valueHelp>
+ <valueHelp>
<format>drop</format>
<description>Drop matching entries</description>
</valueHelp>
<constraint>
- <regex>(drop)</regex>
+ <regex>(accept|reject|return|drop)</regex>
</constraint>
</properties>
</leafNode>
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/hfsc-m1.xml.i b/interface-definitions/include/qos/hfsc-m1.xml.i
index 749d01f57..677d817ba 100644
--- a/interface-definitions/include/qos/hfsc-m1.xml.i
+++ b/interface-definitions/include/qos/hfsc-m1.xml.i
@@ -27,6 +27,6 @@
<description>bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec</description>
</valueHelp>
</properties>
- <defaultValue>100%</defaultValue>
+ <defaultValue>100%%</defaultValue>
</leafNode>
<!-- include end -->
diff --git a/interface-definitions/include/qos/hfsc-m2.xml.i b/interface-definitions/include/qos/hfsc-m2.xml.i
index 24e8f5d63..7690df4b0 100644
--- a/interface-definitions/include/qos/hfsc-m2.xml.i
+++ b/interface-definitions/include/qos/hfsc-m2.xml.i
@@ -27,6 +27,6 @@
<description>bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec</description>
</valueHelp>
</properties>
- <defaultValue>100%</defaultValue>
+ <defaultValue>100%%</defaultValue>
</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/rip/interface.xml.i b/interface-definitions/include/rip/interface.xml.i
index e0792cdc1..8007f0208 100644
--- a/interface-definitions/include/rip/interface.xml.i
+++ b/interface-definitions/include/rip/interface.xml.i
@@ -3,14 +3,14 @@
<properties>
<help>Interface name</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface name</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
<children>
diff --git a/interface-definitions/include/routing-passive-interface.xml.i b/interface-definitions/include/routing-passive-interface.xml.i
index fe229aebe..8fa0d0fe7 100644
--- a/interface-definitions/include/routing-passive-interface.xml.i
+++ b/interface-definitions/include/routing-passive-interface.xml.i
@@ -4,7 +4,7 @@
<help>Suppress routing updates on an interface</help>
<completionHelp>
<list>default</list>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>txt</format>
@@ -16,7 +16,7 @@
</valueHelp>
<constraint>
<regex>(default)</regex>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
<multi/>
</properties>
diff --git a/interface-definitions/include/source-interface-ethernet.xml.i b/interface-definitions/include/source-interface-ethernet.xml.i
index ee04f2cd5..e06e47d6c 100644
--- a/interface-definitions/include/source-interface-ethernet.xml.i
+++ b/interface-definitions/include/source-interface-ethernet.xml.i
@@ -7,7 +7,7 @@
<description>Physical interface used for traffic forwarding</description>
</valueHelp>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py -t ethernet</script>
+ <script>${vyos_completion_dir}/list_interfaces --type ethernet</script>
</completionHelp>
</properties>
</leafNode>
diff --git a/interface-definitions/include/source-interface.xml.i b/interface-definitions/include/source-interface.xml.i
index 4c1fddb57..40fdc6c5e 100644
--- a/interface-definitions/include/source-interface.xml.i
+++ b/interface-definitions/include/source-interface.xml.i
@@ -7,10 +7,10 @@
<description>Interface name</description>
</valueHelp>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</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 cc7a92612..cb5436847 100644
--- a/interface-definitions/include/static/static-route-interface.xml.i
+++ b/interface-definitions/include/static/static-route-interface.xml.i
@@ -3,14 +3,14 @@
<properties>
<help>Gateway interface name</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Gateway interface name</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/static/static-route.xml.i b/interface-definitions/include/static/static-route.xml.i
index aeb2044c9..268cfa005 100644
--- a/interface-definitions/include/static/static-route.xml.i
+++ b/interface-definitions/include/static/static-route.xml.i
@@ -19,14 +19,14 @@
<properties>
<help>Next-hop IPv4 router interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Gateway interface name</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
<children>
diff --git a/interface-definitions/include/static/static-route6.xml.i b/interface-definitions/include/static/static-route6.xml.i
index d5e7a25bc..1f8d54108 100644
--- a/interface-definitions/include/static/static-route6.xml.i
+++ b/interface-definitions/include/static/static-route6.xml.i
@@ -18,14 +18,14 @@
<properties>
<help>IPv6 gateway interface name</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Gateway interface name</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
<children>
diff --git a/interface-definitions/include/system-ip-protocol.xml.i b/interface-definitions/include/system-ip-protocol.xml.i
new file mode 100644
index 000000000..c630eb3f7
--- /dev/null
+++ b/interface-definitions/include/system-ip-protocol.xml.i
@@ -0,0 +1,56 @@
+<!-- include start from system-ip-protocol.xml.i -->
+<tagNode name="protocol">
+ <properties>
+ <help>Filter routing info exchanged between routing protocol and zebra</help>
+ <completionHelp>
+ <list>any babel bgp connected eigrp isis kernel ospf rip static table</list>
+ </completionHelp>
+ <valueHelp>
+ <format>any</format>
+ <description>Any of the above protocols</description>
+ </valueHelp>
+ <valueHelp>
+ <format>babel</format>
+ <description>Babel routing protocol</description>
+ </valueHelp>
+ <valueHelp>
+ <format>bgp</format>
+ <description>Border Gateway Protocol</description>
+ </valueHelp>
+ <valueHelp>
+ <format>connected</format>
+ <description>Connected routes (directly attached subnet or host)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>eigrp</format>
+ <description>Enhanced Interior Gateway Routing Protocol</description>
+ </valueHelp>
+ <valueHelp>
+ <format>isis</format>
+ <description>Intermediate System to Intermediate System</description>
+ </valueHelp>
+ <valueHelp>
+ <format>kernel</format>
+ <description>Kernel routes (not installed via the zebra RIB)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ospf</format>
+ <description>Open Shortest Path First (OSPFv2)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>rip</format>
+ <description>Routing Information Protocol</description>
+ </valueHelp>
+ <valueHelp>
+ <format>static</format>
+ <description>Statically configured routes</description>
+ </valueHelp>
+ <constraint>
+ <regex>(any|babel|bgp|connected|eigrp|isis|kernel|ospf|rip|static|table)</regex>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/route-map.xml.i>
+ </children>
+</tagNode>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/system-ipv6-protocol.xml.i b/interface-definitions/include/system-ipv6-protocol.xml.i
new file mode 100644
index 000000000..485776a71
--- /dev/null
+++ b/interface-definitions/include/system-ipv6-protocol.xml.i
@@ -0,0 +1,52 @@
+<!-- include start from system-ipv6-protocol.xml.i -->
+<tagNode name="protocol">
+ <properties>
+ <help>Filter routing info exchanged between routing protocol and zebra</help>
+ <completionHelp>
+ <list>any babel bgp connected isis kernel ospfv3 ripng static table</list>
+ </completionHelp>
+ <valueHelp>
+ <format>any</format>
+ <description>Any of the above protocols</description>
+ </valueHelp>
+ <valueHelp>
+ <format>babel</format>
+ <description>Babel routing protocol</description>
+ </valueHelp>
+ <valueHelp>
+ <format>bgp</format>
+ <description>Border Gateway Protocol</description>
+ </valueHelp>
+ <valueHelp>
+ <format>connected</format>
+ <description>Connected routes (directly attached subnet or host)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>isis</format>
+ <description>Intermediate System to Intermediate System</description>
+ </valueHelp>
+ <valueHelp>
+ <format>kernel</format>
+ <description>Kernel routes (not installed via the zebra RIB)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ospfv3</format>
+ <description>Open Shortest Path First (OSPFv3)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ripng</format>
+ <description>Routing Information Protocol next-generation</description>
+ </valueHelp>
+ <valueHelp>
+ <format>static</format>
+ <description>Statically configured routes</description>
+ </valueHelp>
+ <constraint>
+ <regex>(any|babel|bgp|connected|isis|kernel|ospfv3|ripng|static|table)</regex>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/route-map.xml.i>
+ </children>
+</tagNode>
+<!-- include end -->
diff --git a/interface-definitions/include/version/bgp-version.xml.i b/interface-definitions/include/version/bgp-version.xml.i
index ced49e729..1386ea9bc 100644
--- a/interface-definitions/include/version/bgp-version.xml.i
+++ b/interface-definitions/include/version/bgp-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/bgp-version.xml.i -->
-<syntaxVersion component='bgp' version='3'></syntaxVersion>
+<syntaxVersion component='bgp' version='4'></syntaxVersion>
<!-- include end -->
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/dns-forwarding-version.xml.i b/interface-definitions/include/version/dns-forwarding-version.xml.i
index fe817940a..86121ae5a 100644
--- a/interface-definitions/include/version/dns-forwarding-version.xml.i
+++ b/interface-definitions/include/version/dns-forwarding-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/dns-forwarding-version.xml.i -->
-<syntaxVersion component='dns-forwarding' version='3'></syntaxVersion>
+<syntaxVersion component='dns-forwarding' version='4'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/interfaces-version.xml.i b/interface-definitions/include/version/interfaces-version.xml.i
index 0a209bc3a..e5e81d316 100644
--- a/interface-definitions/include/version/interfaces-version.xml.i
+++ b/interface-definitions/include/version/interfaces-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/interfaces-version.xml.i -->
-<syntaxVersion component='interfaces' version='26'></syntaxVersion>
+<syntaxVersion component='interfaces' version='28'></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..de7a9c088 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='12'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/isis-version.xml.i b/interface-definitions/include/version/isis-version.xml.i
index 7bf12e81a..f50329b09 100644
--- a/interface-definitions/include/version/isis-version.xml.i
+++ b/interface-definitions/include/version/isis-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/isis-version.xml.i -->
-<syntaxVersion component='isis' version='2'></syntaxVersion>
+<syntaxVersion component='isis' version='3'></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/ospf-version.xml.i b/interface-definitions/include/version/ospf-version.xml.i
index 755965daa..df108837b 100644
--- a/interface-definitions/include/version/ospf-version.xml.i
+++ b/interface-definitions/include/version/ospf-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/ospf-version.xml.i -->
-<syntaxVersion component='ospf' version='1'></syntaxVersion>
+<syntaxVersion component='ospf' version='2'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/qos-version.xml.i b/interface-definitions/include/version/qos-version.xml.i
index e4d139349..c67e61e91 100644
--- a/interface-definitions/include/version/qos-version.xml.i
+++ b/interface-definitions/include/version/qos-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/qos-version.xml.i -->
-<syntaxVersion component='qos' version='1'></syntaxVersion>
+<syntaxVersion component='qos' version='2'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/quagga-version.xml.i b/interface-definitions/include/version/quagga-version.xml.i
index f9944acce..23d884cd4 100644
--- a/interface-definitions/include/version/quagga-version.xml.i
+++ b/interface-definitions/include/version/quagga-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/quagga-version.xml.i -->
-<syntaxVersion component='quagga' version='10'></syntaxVersion>
+<syntaxVersion component='quagga' version='11'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/rip-version.xml.i b/interface-definitions/include/version/rip-version.xml.i
new file mode 100644
index 000000000..30ace486a
--- /dev/null
+++ b/interface-definitions/include/version/rip-version.xml.i
@@ -0,0 +1,3 @@
+<!-- include start from include/version/rip-version.xml.i -->
+<syntaxVersion component='rip' version='1'></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 a8a558348..14b1036b4 100644
--- a/interface-definitions/interfaces-bonding.xml.in
+++ b/interface-definitions/interfaces-bonding.xml.in
@@ -49,7 +49,7 @@
</leafNode>
</children>
</node>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/dhcp-options.xml.i>
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/disable-link-detect.xml.i>
@@ -192,14 +192,14 @@
<properties>
<help>Member interface name</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --bondable</script>
+ <script>${vyos_completion_dir}/list_interfaces --bondable</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface name</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
<multi/>
</properties>
@@ -211,14 +211,14 @@
<properties>
<help>Primary device interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --bondable</script>
+ <script>${vyos_completion_dir}/list_interfaces --bondable</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface name</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in
index d52e213b6..fcfb8686c 100644
--- a/interface-definitions/interfaces-bridge.xml.in
+++ b/interface-definitions/interfaces-bridge.xml.in
@@ -34,7 +34,7 @@
</properties>
<defaultValue>300</defaultValue>
</leafNode>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/dhcp-options.xml.i>
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/disable-link-detect.xml.i>
@@ -121,7 +121,7 @@
<properties>
<help>Member interface name</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --bridgeable</script>
+ <script>${vyos_completion_dir}/list_interfaces --bridgeable</script>
</completionHelp>
</properties>
<children>
diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in
index eb525b547..00784fcdf 100644
--- a/interface-definitions/interfaces-dummy.xml.in
+++ b/interface-definitions/interfaces-dummy.xml.in
@@ -17,7 +17,7 @@
</properties>
<children>
#include <include/interface/address-ipv4-ipv6.xml.i>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
<node name="ip">
<properties>
@@ -25,8 +25,27 @@
</properties>
<children>
#include <include/interface/source-validation.xml.i>
+ #include <include/interface/disable-forwarding.xml.i>
</children>
</node>
+ <node name="ipv6">
+ <properties>
+ <help>IPv6 routing parameters</help>
+ </properties>
+ <children>
+ #include <include/interface/disable-forwarding.xml.i>
+ <node name="address">
+ <properties>
+ <help>IPv6 address configuration modes</help>
+ </properties>
+ <children>
+ #include <include/interface/ipv6-address-eui64.xml.i>
+ #include <include/interface/ipv6-address-no-default-link-local.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+ #include <include/interface/mtu-68-16000.xml.i>
#include <include/interface/mirror.xml.i>
#include <include/interface/netns.xml.i>
#include <include/interface/redirect.xml.i>
diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in
index e9ae0acfe..e7c196c5c 100644
--- a/interface-definitions/interfaces-ethernet.xml.in
+++ b/interface-definitions/interfaces-ethernet.xml.in
@@ -20,7 +20,7 @@
</properties>
<children>
#include <include/interface/address-ipv4-ipv6-dhcp.xml.i>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/dhcp-options.xml.i>
#include <include/interface/dhcpv6-options.xml.i>
<leafNode name="disable-flow-control">
diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in
index f8e9909f8..ac9794870 100644
--- a/interface-definitions/interfaces-geneve.xml.in
+++ b/interface-definitions/interfaces-geneve.xml.in
@@ -17,7 +17,7 @@
</properties>
<children>
#include <include/interface/address-ipv4-ipv6.xml.i>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
diff --git a/interface-definitions/interfaces-input.xml.in b/interface-definitions/interfaces-input.xml.in
index 97502d954..d90cf936f 100644
--- a/interface-definitions/interfaces-input.xml.in
+++ b/interface-definitions/interfaces-input.xml.in
@@ -17,7 +17,7 @@
</valueHelp>
</properties>
<children>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
#include <include/interface/redirect.xml.i>
</children>
diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in
index 0ebc3253d..1f0dd3d19 100644
--- a/interface-definitions/interfaces-l2tpv3.xml.in
+++ b/interface-definitions/interfaces-l2tpv3.xml.in
@@ -17,7 +17,7 @@
</properties>
<children>
#include <include/interface/address-ipv4-ipv6.xml.i>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
<leafNode name="destination-port">
<properties>
<help>UDP destination port for L2TPv3 tunnel</help>
diff --git a/interface-definitions/interfaces-loopback.xml.in b/interface-definitions/interfaces-loopback.xml.in
index 7f59db543..fe0944467 100644
--- a/interface-definitions/interfaces-loopback.xml.in
+++ b/interface-definitions/interfaces-loopback.xml.in
@@ -17,7 +17,7 @@
</properties>
<children>
#include <include/interface/address-ipv4-ipv6.xml.i>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
<node name="ip">
<properties>
<help>IPv4 routing parameters</help>
diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in
index 441236ec2..6bc28e44b 100644
--- a/interface-definitions/interfaces-macsec.xml.in
+++ b/interface-definitions/interfaces-macsec.xml.in
@@ -75,10 +75,10 @@
<help>Secure Connectivity Association Key Name</help>
<valueHelp>
<format>txt</format>
- <description>32-byte (256-bit) hex-string (64 hex-digits)</description>
+ <description>1..32-bytes (8..256 bit) hex-string (2..64 hex-digits)</description>
</valueHelp>
<constraint>
- <regex>[A-Fa-f0-9]{64}</regex>
+ <regex>[A-Fa-f0-9]{2,64}</regex>
</constraint>
</properties>
</leafNode>
@@ -115,7 +115,7 @@
</leafNode>
</children>
</node>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
#include <include/interface/mtu-68-16000.xml.i>
<leafNode name="mtu">
diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in
index 7cfb9ee7a..cf0ff497c 100644
--- a/interface-definitions/interfaces-openvpn.xml.in
+++ b/interface-definitions/interfaces-openvpn.xml.in
@@ -16,24 +16,8 @@
</valueHelp>
</properties>
<children>
- <node name="authentication">
- <properties>
- <help>Authentication options</help>
- </properties>
- <children>
- <leafNode name="password">
- <properties>
- <help>OpenVPN password used for authentication</help>
- </properties>
- </leafNode>
- <leafNode name="username">
- <properties>
- <help>OpenVPN username used for authentication</help>
- </properties>
- </leafNode>
- </children>
- </node>
- #include <include/interface/description.xml.i>
+ #include <include/interface/authentication.xml.i>
+ #include <include/generic-description.xml.i>
<leafNode name="device-type">
<properties>
<help>OpenVPN interface device-type</help>
diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in
index 35c4889ea..b78f92c85 100644
--- a/interface-definitions/interfaces-pppoe.xml.in
+++ b/interface-definitions/interfaces-pppoe.xml.in
@@ -22,7 +22,7 @@
#include <include/interface/no-default-route.xml.i>
#include <include/interface/default-route-distance.xml.i>
#include <include/interface/dhcpv6-options.xml.i>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
<leafNode name="idle-timeout">
<properties>
@@ -37,6 +37,33 @@
<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>
+ <leafNode name="holdoff">
+ <properties>
+ <help>Delay before re-dial to the access concentrator when PPP session terminated by peer (in seconds)</help>
+ <valueHelp>
+ <format>u32:0-86400</format>
+ <description>Holdoff time in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-86400"/>
+ </constraint>
+ <constraintErrorMessage>Holdoff must be in range 0 to 86400</constraintErrorMessage>
+ </properties>
+ <defaultValue>30</defaultValue>
+ </leafNode>
<node name="ip">
<properties>
<help>IPv4 routing parameters</help>
diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in
index 2fe07ffd5..5c73825c3 100644
--- a/interface-definitions/interfaces-pseudo-ethernet.xml.in
+++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in
@@ -17,7 +17,7 @@
</properties>
<children>
#include <include/interface/address-ipv4-ipv6-dhcp.xml.i>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/dhcp-options.xml.i>
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/disable-link-detect.xml.i>
diff --git a/interface-definitions/interfaces-sstpc.xml.in b/interface-definitions/interfaces-sstpc.xml.in
index 30b55a9fa..b569e9bde 100644
--- a/interface-definitions/interfaces-sstpc.xml.in
+++ b/interface-definitions/interfaces-sstpc.xml.in
@@ -16,7 +16,7 @@
</valueHelp>
</properties>
<children>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
#include <include/interface/authentication.xml.i>
#include <include/interface/no-default-route.xml.i>
diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in
index 333a5b178..58f95dddb 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>
@@ -106,24 +106,10 @@
</properties>
</leafNode>
#include <include/interface/mirror.xml.i>
- <leafNode name="multicast">
+ <leafNode name="enable-multicast">
<properties>
- <help>Multicast operation over tunnel</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable multicast</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable multicast (default)</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- <constraintErrorMessage>Must be 'disable' or 'enable'</constraintErrorMessage>
+ <help>Enable multicast operation over tunnel</help>
+ <valueless/>
</properties>
</leafNode>
<node name="parameters">
diff --git a/interface-definitions/interfaces-virtual-ethernet.xml.in b/interface-definitions/interfaces-virtual-ethernet.xml.in
index 8059ec33b..864f658da 100644
--- a/interface-definitions/interfaces-virtual-ethernet.xml.in
+++ b/interface-definitions/interfaces-virtual-ethernet.xml.in
@@ -17,7 +17,7 @@
</properties>
<children>
#include <include/interface/address-ipv4-ipv6-dhcp.xml.i>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/dhcp-options.xml.i>
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/disable.xml.i>
diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in
index 11f001dc0..b116f7386 100644
--- a/interface-definitions/interfaces-vti.xml.in
+++ b/interface-definitions/interfaces-vti.xml.in
@@ -17,7 +17,7 @@
</properties>
<children>
#include <include/interface/address-ipv4-ipv6.xml.i>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in
index 331f930d3..fb60c93d0 100644
--- a/interface-definitions/interfaces-vxlan.xml.in
+++ b/interface-definitions/interfaces-vxlan.xml.in
@@ -17,7 +17,7 @@
</properties>
<children>
#include <include/interface/address-ipv4-ipv6.xml.i>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
<leafNode name="external">
<properties>
diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in
index 35e223588..6342b21cf 100644
--- a/interface-definitions/interfaces-wireguard.xml.in
+++ b/interface-definitions/interfaces-wireguard.xml.in
@@ -17,7 +17,7 @@
</properties>
<children>
#include <include/interface/address-ipv4-ipv6.xml.i>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
#include <include/port-number.xml.i>
#include <include/interface/mtu-68-16000.xml.i>
diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in
index 5271df624..a9538d577 100644
--- a/interface-definitions/interfaces-wireless.xml.in
+++ b/interface-definitions/interfaces-wireless.xml.in
@@ -467,7 +467,7 @@
<constraintErrorMessage>Invalid ISO/IEC 3166-1 Country Code</constraintErrorMessage>
</properties>
</leafNode>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/dhcp-options.xml.i>
#include <include/interface/dhcpv6-options.xml.i>
<leafNode name="disable-broadcast-ssid">
@@ -725,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 758784540..5fa3be8db 100644
--- a/interface-definitions/interfaces-wwan.xml.in
+++ b/interface-definitions/interfaces-wwan.xml.in
@@ -28,7 +28,7 @@
#include <include/interface/dhcp-options.xml.i>
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/authentication.xml.i>
- #include <include/interface/description.xml.i>
+ #include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
#include <include/interface/disable-link-detect.xml.i>
#include <include/interface/mirror.xml.i>
diff --git a/interface-definitions/load-balancing-wan.xml.in b/interface-definitions/load-balancing-wan.xml.in
index c2b6316ae..c1d7e2c67 100644
--- a/interface-definitions/load-balancing-wan.xml.in
+++ b/interface-definitions/load-balancing-wan.xml.in
@@ -44,7 +44,7 @@
<properties>
<help>Interface name</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
@@ -191,15 +191,7 @@
</constraint>
</properties>
<children>
- <leafNode name="description">
- <properties>
- <help>Description for this rule</help>
- <valueHelp>
- <format>txt</format>
- <description>Description for this rule</description>
- </valueHelp>
- </properties>
- </leafNode>
+ #include <include/generic-description.xml.i>
<node name="destination">
<properties>
<help>Destination</help>
@@ -226,7 +218,7 @@
<help>Inbound interface name (e.g., "eth0") [REQUIRED]</help>
<completionHelp>
<list>any</list>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
</leafNode>
@@ -234,7 +226,7 @@
<properties>
<help>Interface name [REQUIRED]</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
diff --git a/interface-definitions/nat66.xml.in b/interface-definitions/nat66.xml.in
index dab4543e0..7a8970bdf 100644
--- a/interface-definitions/nat66.xml.in
+++ b/interface-definitions/nat66.xml.in
@@ -24,11 +24,7 @@
<constraintErrorMessage>NAT66 rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
<children>
- <leafNode name="description">
- <properties>
- <help>Rule description</help>
- </properties>
- </leafNode>
+ #include <include/generic-description.xml.i>
<leafNode name="disable">
<properties>
<help>Disable NAT66 rule</help>
@@ -46,7 +42,7 @@
<properties>
<help>Outbound interface of NAT66 traffic</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
</leafNode>
@@ -156,11 +152,7 @@
<constraintErrorMessage>NAT66 rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
<children>
- <leafNode name="description">
- <properties>
- <help>Rule description</help>
- </properties>
- </leafNode>
+ #include <include/generic-description.xml.i>
<leafNode name="disable">
<properties>
<help>Disable NAT66 rule</help>
@@ -179,7 +171,7 @@
<help>Inbound interface of NAT66 traffic</help>
<completionHelp>
<list>any</list>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
</leafNode>
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..558204a06 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>
@@ -37,15 +37,15 @@
<valueless/>
</properties>
</leafNode>
- <leafNode name="pool">
+ <leafNode name="nts">
<properties>
- <help>Associate with a number of remote servers</help>
+ <help>Enable Network Time Security (NTS) for the server</help>
<valueless/>
</properties>
</leafNode>
- <leafNode name="preempt">
+ <leafNode name="pool">
<properties>
- <help>Specifies the association as preemptable rather than the default persistent</help>
+ <help>Associate with a number of remote servers</help>
<valueless/>
</properties>
</leafNode>
@@ -57,30 +57,7 @@
</leafNode>
</children>
</tagNode>
- <node name="allow-clients">
- <properties>
- <help>Network Time Protocol (NTP) server options</help>
- </properties>
- <children>
- <leafNode name="address">
- <properties>
- <help>IP address</help>
- <valueHelp>
- <format>ipv4net</format>
- <description>IP address and prefix length</description>
- </valueHelp>
- <valueHelp>
- <format>ipv6net</format>
- <description>IPv6 address and prefix length</description>
- </valueHelp>
- <multi/>
- <constraint>
- <validator name="ip-prefix"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </node>
+ #include <include/allow-client.xml.i>
#include <include/generic-interface-multi.xml.i>
#include <include/listen-address.xml.i>
#include <include/interface/vrf.xml.i>
diff --git a/interface-definitions/pki.xml.in b/interface-definitions/pki.xml.in
index c4fde2c78..a13a357fd 100644
--- a/interface-definitions/pki.xml.in
+++ b/interface-definitions/pki.xml.in
@@ -16,11 +16,7 @@
<help>CA certificate in PEM format</help>
</properties>
</leafNode>
- <leafNode name="description">
- <properties>
- <help>Description</help>
- </properties>
- </leafNode>
+ #include <include/generic-description.xml.i>
<node name="private">
<properties>
<help>CA private key in PEM format</help>
@@ -63,11 +59,7 @@
<help>Certificate in PEM format</help>
</properties>
</leafNode>
- <leafNode name="description">
- <properties>
- <help>Description</help>
- </properties>
- </leafNode>
+ #include <include/generic-description.xml.i>
<node name="private">
<properties>
<help>Certificate private key</help>
diff --git a/interface-definitions/policy-route.xml.in b/interface-definitions/policy-route.xml.in
index d7b159839..d4ec75786 100644
--- a/interface-definitions/policy-route.xml.in
+++ b/interface-definitions/policy-route.xml.in
@@ -12,8 +12,8 @@
</properties>
<children>
#include <include/generic-description.xml.i>
- #include <include/generic-interface-multi.xml.i>
#include <include/firewall/enable-default-log.xml.i>
+ #include <include/generic-interface-multi-wildcard.xml.i>
<tagNode name="rule">
<properties>
<help>Policy rule number</help>
@@ -50,7 +50,7 @@
#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/packet-options.xml.i>
#include <include/firewall/hop-limit.xml.i>
#include <include/firewall/connection-mark.xml.i>
</children>
@@ -67,8 +67,8 @@
</properties>
<children>
#include <include/generic-description.xml.i>
- #include <include/generic-interface-multi.xml.i>
#include <include/firewall/enable-default-log.xml.i>
+ #include <include/generic-interface-multi-wildcard.xml.i>
<tagNode name="rule">
<properties>
<help>Policy rule number</help>
@@ -105,7 +105,7 @@
#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/packet-options.xml.i>
#include <include/firewall/ttl.xml.i>
#include <include/firewall/connection-mark.xml.i>
</children>
diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in
index b3745fda0..7d5fe79ef 100644
--- a/interface-definitions/policy.xml.in
+++ b/interface-definitions/policy.xml.in
@@ -242,7 +242,7 @@
<description>BGP extended community-list name</description>
</valueHelp>
<constraint>
- <regex>[-_a-zA-Z0-9]+</regex>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</constraint>
<constraintErrorMessage>Should be an alphanumeric name</constraintErrorMessage>
</properties>
@@ -291,7 +291,7 @@
<description>BGP large-community-list name</description>
</valueHelp>
<constraint>
- <regex>[-_a-zA-Z0-9]+</regex>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</constraint>
<constraintErrorMessage>Should be an alphanumeric name</constraintErrorMessage>
</properties>
@@ -340,7 +340,7 @@
<description>Name of IPv4 prefix-list</description>
</valueHelp>
<constraint>
- <regex>[-_a-zA-Z0-9]+</regex>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</constraint>
<constraintErrorMessage>Name of prefix-list can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage>
</properties>
@@ -408,7 +408,7 @@
<description>Name of IPv6 prefix-list</description>
</valueHelp>
<constraint>
- <regex>[-_a-zA-Z0-9]+</regex>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</constraint>
<constraintErrorMessage>Name of prefix-list6 can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage>
</properties>
diff --git a/interface-definitions/protocols-babel.xml.in b/interface-definitions/protocols-babel.xml.in
new file mode 100644
index 000000000..49fffe230
--- /dev/null
+++ b/interface-definitions/protocols-babel.xml.in
@@ -0,0 +1,254 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="protocols">
+ <children>
+ <node name="babel" owner="${vyos_conf_scripts_dir}/protocols_babel.py">
+ <properties>
+ <help>Babel Routing Protocol</help>
+ <priority>650</priority>
+ </properties>
+ <children>
+ <node name="parameters">
+ <properties>
+ <help>Babel-specific parameters</help>
+ </properties>
+ <children>
+ <leafNode name="diversity">
+ <properties>
+ <help>Enable diversity-aware routing</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="diversity-factor">
+ <properties>
+ <help>Multiplicative factor used for diversity routing</help>
+ <valueHelp>
+ <format>u32:1-256</format>
+ <description>Multiplicative factor, in units of 1/256</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-256"/>
+ </constraint>
+ </properties>
+ <defaultValue>256</defaultValue>
+ </leafNode>
+ <leafNode name="resend-delay">
+ <properties>
+ <help>Time before resending a message</help>
+ <valueHelp>
+ <format>u32:20-655340</format>
+ <description>Milliseconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 20-655340"/>
+ </constraint>
+ </properties>
+ <defaultValue>2000</defaultValue>
+ </leafNode>
+ <leafNode name="smoothing-half-life">
+ <properties>
+ <help>Smoothing half-life</help>
+ <valueHelp>
+ <format>u32:0-65534</format>
+ <description>Seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-65534"/>
+ </constraint>
+ </properties>
+ <defaultValue>4</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ #include <include/babel/interface.xml.i>
+ <node name="redistribute">
+ <properties>
+ <help>Redistribute information from another routing protocol</help>
+ </properties>
+ <children>
+ <node name="ipv4">
+ <properties>
+ <help>Redistribute IPv4 routes</help>
+ </properties>
+ <children>
+ <leafNode name="bgp">
+ <properties>
+ <help>Redistribute BGP routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="connected">
+ <properties>
+ <help>Redistribute connected routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="eigrp">
+ <properties>
+ <help>Redistribute EIGRP routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="isis">
+ <properties>
+ <help>Redistribute IS-IS routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="kernel">
+ <properties>
+ <help>Redistribute kernel routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="nhrp">
+ <properties>
+ <help>Redistribute NHRP routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ospf">
+ <properties>
+ <help>Redistribute OSPF routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="rip">
+ <properties>
+ <help>Redistribute RIP routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="static">
+ <properties>
+ <help>Redistribute static routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="ipv6">
+ <properties>
+ <help>Redistribute IPv6 routes</help>
+ </properties>
+ <children>
+ <leafNode name="bgp">
+ <properties>
+ <help>Redistribute BGP routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="connected">
+ <properties>
+ <help>Redistribute connected routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="isis">
+ <properties>
+ <help>Redistribute IS-IS routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="kernel">
+ <properties>
+ <help>Redistribute kernel routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="nhrp">
+ <properties>
+ <help>Redistribute NHRP routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ospfv3">
+ <properties>
+ <help>Redistribute OSPFv3 routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ripng">
+ <properties>
+ <help>Redistribute RIPng routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="static">
+ <properties>
+ <help>Redistribute static routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="distribute-list">
+ <properties>
+ <help>Filter networks in routing updates</help>
+ </properties>
+ <children>
+ <node name="ipv4">
+ <properties>
+ <help>Filter IPv4 routes</help>
+ </properties>
+ <children>
+ #include <include/rip/access-list.xml.i>
+ <tagNode name="interface">
+ <properties>
+ <help>Apply filtering to an interface</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Apply filtering to an interface</description>
+ </valueHelp>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/rip/access-list.xml.i>
+ #include <include/rip/prefix-list.xml.i>
+ </children>
+ </tagNode>
+ #include <include/rip/prefix-list.xml.i>
+ </children>
+ </node>
+ <node name="ipv6">
+ <properties>
+ <help>Filter IPv6 routes</help>
+ </properties>
+ <children>
+ #include <include/rip/access-list6.xml.i>
+ <tagNode name="interface">
+ <properties>
+ <help>Apply filtering to an interface</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Apply filtering to an interface</description>
+ </valueHelp>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/rip/access-list6.xml.i>
+ #include <include/rip/prefix-list6.xml.i>
+ </children>
+ </tagNode>
+ #include <include/rip/prefix-list6.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/protocols-bgp.xml.in b/interface-definitions/protocols-bgp.xml.in
index e1a822999..78b461f9d 100644
--- a/interface-definitions/protocols-bgp.xml.in
+++ b/interface-definitions/protocols-bgp.xml.in
@@ -9,6 +9,20 @@
</properties>
<children>
#include <include/bgp/protocol-common-config.xml.i>
+ <node name="address-family">
+ <children>
+ <node name="l2vpn-evpn">
+ <children>
+ <leafNode name="advertise-all-vni">
+ <properties>
+ <help>Advertise all local VNIs</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/interface-definitions/protocols-failover.xml.in b/interface-definitions/protocols-failover.xml.in
index 900c76eab..c0caec68e 100644
--- a/interface-definitions/protocols-failover.xml.in
+++ b/interface-definitions/protocols-failover.xml.in
@@ -37,6 +37,26 @@
<help>Check target options</help>
</properties>
<children>
+ <leafNode name="policy">
+ <properties>
+ <help>Policy for check targets</help>
+ <completionHelp>
+ <list>any-available all-available</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all-available</format>
+ <description>All targets must be alive</description>
+ </valueHelp>
+ <valueHelp>
+ <format>any-available</format>
+ <description>Any target must be alive</description>
+ </valueHelp>
+ <constraint>
+ <regex>(all-available|any-available)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>any-available</defaultValue>
+ </leafNode>
#include <include/port-number.xml.i>
<leafNode name="target">
<properties>
@@ -48,6 +68,7 @@
<constraint>
<validator name="ipv4-address"/>
</constraint>
+ <multi/>
</properties>
</leafNode>
<leafNode name="timeout">
diff --git a/interface-definitions/protocols-igmp.xml.in b/interface-definitions/protocols-igmp.xml.in
index e10340512..a055db71e 100644
--- a/interface-definitions/protocols-igmp.xml.in
+++ b/interface-definitions/protocols-igmp.xml.in
@@ -12,7 +12,7 @@
<properties>
<help>IGMP interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
diff --git a/interface-definitions/protocols-multicast.xml.in b/interface-definitions/protocols-multicast.xml.in
index b1791c471..c8e28ed35 100644
--- a/interface-definitions/protocols-multicast.xml.in
+++ b/interface-definitions/protocols-multicast.xml.in
@@ -65,7 +65,7 @@
<properties>
<help>Next-hop interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
diff --git a/interface-definitions/protocols-pim.xml.in b/interface-definitions/protocols-pim.xml.in
index bb5cc797b..e9475930c 100644
--- a/interface-definitions/protocols-pim.xml.in
+++ b/interface-definitions/protocols-pim.xml.in
@@ -13,7 +13,7 @@
<properties>
<help>PIM interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
diff --git a/interface-definitions/protocols-rip.xml.in b/interface-definitions/protocols-rip.xml.in
index 33aae5015..0edd8f2ce 100644
--- a/interface-definitions/protocols-rip.xml.in
+++ b/interface-definitions/protocols-rip.xml.in
@@ -36,10 +36,10 @@
<description>Apply filtering to an interface</description>
</valueHelp>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
<children>
@@ -225,6 +225,14 @@
#include <include/rip/redistribute.xml.i>
</children>
</node>
+ <node name="babel">
+ <properties>
+ <help>Redistribute Babel routes</help>
+ </properties>
+ <children>
+ #include <include/rip/redistribute.xml.i>
+ </children>
+ </node>
</children>
</node>
<leafNode name="route">
@@ -248,4 +256,3 @@
</children>
</node>
</interfaceDefinition>
-
diff --git a/interface-definitions/protocols-ripng.xml.in b/interface-definitions/protocols-ripng.xml.in
index cd35dbf53..9d4d87422 100644
--- a/interface-definitions/protocols-ripng.xml.in
+++ b/interface-definitions/protocols-ripng.xml.in
@@ -37,10 +37,10 @@
<description>Apply filtering to an interface</description>
</valueHelp>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
<children>
@@ -73,7 +73,7 @@
<description>Suppress routing updates on interface</description>
</valueHelp>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<multi/>
</properties>
@@ -123,6 +123,14 @@
#include <include/rip/redistribute.xml.i>
</children>
</node>
+ <node name="babel">
+ <properties>
+ <help>Redistribute Babel routes</help>
+ </properties>
+ <children>
+ #include <include/rip/redistribute.xml.i>
+ </children>
+ </node>
</children>
</node>
<leafNode name="route">
diff --git a/interface-definitions/protocols-rpki.xml.in b/interface-definitions/protocols-rpki.xml.in
index 0098cacb6..c41fa54f2 100644
--- a/interface-definitions/protocols-rpki.xml.in
+++ b/interface-definitions/protocols-rpki.xml.in
@@ -71,11 +71,7 @@
</constraint>
</properties>
</leafNode>
- <leafNode name="username">
- <properties>
- <help>RPKI SSH username</help>
- </properties>
- </leafNode>
+ #include <include/generic-username.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/protocols-static-arp.xml.in b/interface-definitions/protocols-static-arp.xml.in
index 52caf435a..4b338df63 100644
--- a/interface-definitions/protocols-static-arp.xml.in
+++ b/interface-definitions/protocols-static-arp.xml.in
@@ -13,14 +13,14 @@
<properties>
<help>Interface configuration</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface name</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</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 dc807781e..c7bd8606a 100644
--- a/interface-definitions/qos.xml.in
+++ b/interface-definitions/qos.xml.in
@@ -3,20 +3,21 @@
<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">
<properties>
<help>Interface to apply QoS policy</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface name</description>
</valueHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</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-conntrack-sync.xml.in b/interface-definitions/service-conntrack-sync.xml.in
index 6fa6fc5f9..50a4bf62f 100644
--- a/interface-definitions/service-conntrack-sync.xml.in
+++ b/interface-definitions/service-conntrack-sync.xml.in
@@ -127,7 +127,7 @@
<properties>
<help>Interface to use for syncing conntrack entries</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --bridgeable</script>
+ <script>${vyos_completion_dir}/list_interfaces --bridgeable</script>
</completionHelp>
</properties>
<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-ids-ddos-protection.xml.in b/interface-definitions/service-ids-ddos-protection.xml.in
index a661b845d..bb06189bc 100644
--- a/interface-definitions/service-ids-ddos-protection.xml.in
+++ b/interface-definitions/service-ids-ddos-protection.xml.in
@@ -65,7 +65,7 @@
<properties>
<help>Listen interface for mirroring traffic</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<multi/>
</properties>
diff --git a/interface-definitions/service-ipoe-server.xml.in b/interface-definitions/service-ipoe-server.xml.in
index ef8569437..b6e6503d3 100644
--- a/interface-definitions/service-ipoe-server.xml.in
+++ b/interface-definitions/service-ipoe-server.xml.in
@@ -12,7 +12,7 @@
<properties>
<help>Interface to listen dhcp or unclassified packets</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
@@ -108,22 +108,7 @@
<help>Client IP pools and gateway setting</help>
</properties>
<children>
- <tagNode name="name">
- <properties>
- <help>Pool name</help>
- <valueHelp>
- <format>txt</format>
- <description>Name of IP pool</description>
- </valueHelp>
- <constraint>
- <regex>[-_a-zA-Z0-9.]+</regex>
- </constraint>
- </properties>
- <children>
- #include <include/accel-ppp/gateway-address.xml.i>
- #include <include/accel-ppp/client-ip-pool-subnet-single.xml.i>
- </children>
- </tagNode>
+ #include <include/accel-ppp/client-ip-pool-name.xml.i>
</children>
</node>
#include <include/accel-ppp/client-ipv6-pool.xml.i>
@@ -132,34 +117,12 @@
<help>Client authentication methods</help>
</properties>
<children>
- <leafNode name="mode">
- <properties>
- <help>Authetication mode</help>
- <completionHelp>
- <list>local radius noauth</list>
- </completionHelp>
- <constraint>
- <regex>(local|radius|noauth)</regex>
- </constraint>
- <valueHelp>
- <format>local</format>
- <description>Authentication based on local definition</description>
- </valueHelp>
- <valueHelp>
- <format>radius</format>
- <description>Authentication based on a RADIUS server</description>
- </valueHelp>
- <valueHelp>
- <format>noauth</format>
- <description>Authentication disabled</description>
- </valueHelp>
- </properties>
- </leafNode>
+ #include <include/accel-ppp/auth-mode.xml.i>
<tagNode name="interface">
<properties>
<help>Network interface for client MAC addresses</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
@@ -220,7 +183,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-monitoring-telegraf.xml.in b/interface-definitions/service-monitoring-telegraf.xml.in
index f50e5e334..ae0bae900 100644
--- a/interface-definitions/service-monitoring-telegraf.xml.in
+++ b/interface-definitions/service-monitoring-telegraf.xml.in
@@ -74,7 +74,7 @@
<properties>
<help>Application client id</help>
<constraint>
- <regex>[-_a-zA-Z0-9]+</regex>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</constraint>
<constraintErrorMessage>Client-id is limited to alphanumerical characters and can contain hyphen and underscores</constraintErrorMessage>
</properties>
@@ -83,7 +83,7 @@
<properties>
<help>Application client secret</help>
<constraint>
- <regex>[-_a-zA-Z0-9]+</regex>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</constraint>
<constraintErrorMessage>Client-secret is limited to alphanumerical characters and can contain hyphen and underscores</constraintErrorMessage>
</properties>
@@ -92,7 +92,7 @@
<properties>
<help>Set tenant id</help>
<constraint>
- <regex>[-_a-zA-Z0-9]+</regex>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</constraint>
<constraintErrorMessage>Tenant-id is limited to alphanumerical characters and can contain hyphen and underscores</constraintErrorMessage>
</properties>
@@ -107,7 +107,7 @@
<description>Remote database name</description>
</valueHelp>
<constraint>
- <regex>[-_a-zA-Z0-9]+</regex>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</constraint>
<constraintErrorMessage>Database is limited to alphanumerical characters and can contain hyphen and underscores</constraintErrorMessage>
</properties>
@@ -140,7 +140,7 @@
<description>Table name</description>
</valueHelp>
<constraint>
- <regex>[-_a-zA-Z0-9]+</regex>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</constraint>
<constraintErrorMessage>Table is limited to alphanumerical characters and can contain hyphen and underscores</constraintErrorMessage>
</properties>
diff --git a/interface-definitions/service-pppoe-server.xml.in b/interface-definitions/service-pppoe-server.xml.in
index b31109296..022ac2885 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>
@@ -56,6 +56,7 @@
<children>
#include <include/accel-ppp/client-ip-pool-start-stop.xml.i>
#include <include/accel-ppp/client-ip-pool-subnet.xml.i>
+ #include <include/accel-ppp/client-ip-pool-name.xml.i>
</children>
</node>
#include <include/accel-ppp/client-ipv6-pool.xml.i>
@@ -64,7 +65,7 @@
<properties>
<help>interface(s) to listen on</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
@@ -122,6 +123,7 @@
<validator name="numeric" argument="--range 68-65535"/>
</constraint>
</properties>
+ <defaultValue>1280</defaultValue>
</leafNode>
<leafNode name="mru">
<properties>
@@ -170,52 +172,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">
@@ -271,6 +228,7 @@
</properties>
<defaultValue>replace</defaultValue>
</leafNode>
+ #include <include/accel-ppp/shaper.xml.i>
<node name="snmp">
<properties>
<help>Enable SNMP</help>
diff --git a/interface-definitions/service-router-advert.xml.in b/interface-definitions/service-router-advert.xml.in
index 8b7364a8c..16c29022d 100644
--- a/interface-definitions/service-router-advert.xml.in
+++ b/interface-definitions/service-router-advert.xml.in
@@ -12,7 +12,7 @@
<properties>
<help>Interface to send RA on</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
diff --git a/interface-definitions/service-upnp.xml.in b/interface-definitions/service-upnp.xml.in
index 79d8ae42e..1b2e00d91 100644
--- a/interface-definitions/service-upnp.xml.in
+++ b/interface-definitions/service-upnp.xml.in
@@ -21,10 +21,10 @@
<properties>
<help>WAN network interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
</leafNode>
@@ -95,7 +95,7 @@
<help>Local IP addresses for service to listen on</help>
<completionHelp>
<script>${vyos_completion_dir}/list_local_ips.sh --both</script>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>&lt;interface&gt;</format>
@@ -119,7 +119,7 @@
</valueHelp>
<multi/>
<constraint>
- #include <include/constraint/interface-name.xml.in>
+ #include <include/constraint/interface-name.xml.i>
<validator name="ipv4-address"/>
<validator name="ipv4-prefix"/>
<validator name="ipv6-address"/>
diff --git a/interface-definitions/service-webproxy.xml.in b/interface-definitions/service-webproxy.xml.in
index a315aa2ef..b24997816 100644
--- a/interface-definitions/service-webproxy.xml.in
+++ b/interface-definitions/service-webproxy.xml.in
@@ -538,11 +538,7 @@
<multi/>
</properties>
</leafNode>
- <leafNode name="description">
- <properties>
- <help>Description for source-group</help>
- </properties>
- </leafNode>
+ #include <include/generic-description.xml.i>
<leafNode name="domain">
<properties>
<help>Domain for source-group</help>
@@ -644,11 +640,7 @@
</leafNode>
</children>
</tagNode>
- <leafNode name="description">
- <properties>
- <help>Time-period description</help>
- </properties>
- </leafNode>
+ #include <include/generic-description.xml.i>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/snmp.xml.in b/interface-definitions/snmp.xml.in
index 7ec60b2e7..559e09388 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>[[:alnum:]-_!@*#]{1,100}</regex>
</constraint>
- <constraintErrorMessage>Community string is limited to alphanumerical characters, !, @, * and # 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">
@@ -65,6 +65,7 @@
</constraint>
<multi/>
</properties>
+ <defaultValue>0.0.0.0/0 ::/0</defaultValue>
</leafNode>
</children>
</tagNode>
@@ -77,15 +78,7 @@
<constraintErrorMessage>Contact information is limited to 255 characters or less</constraintErrorMessage>
</properties>
</leafNode>
- <leafNode name="description">
- <properties>
- <help>Description information</help>
- <constraint>
- <regex>.{1,255}</regex>
- </constraint>
- <constraintErrorMessage>Description is limited to 255 characters or less</constraintErrorMessage>
- </properties>
- </leafNode>
+ #include <include/generic-description.xml.i>
<tagNode name="listen-address">
<properties>
<help>IP address to listen for incoming SNMP requests</help>
@@ -123,18 +116,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/system-config-mgmt.xml.in b/interface-definitions/system-config-mgmt.xml.in
new file mode 100644
index 000000000..716332d2a
--- /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.i>
+ </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-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in
index 5810a97c6..8dad048b8 100644
--- a/interface-definitions/system-conntrack.xml.in
+++ b/interface-definitions/system-conntrack.xml.in
@@ -68,7 +68,7 @@
<help>Interface to ignore connections tracking on</help>
<completionHelp>
<list>any</list>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
</leafNode>
@@ -310,7 +310,7 @@
<help>Interface to ignore connections tracking on</help>
<completionHelp>
<list>any</list>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
</leafNode>
diff --git a/interface-definitions/system-ip.xml.in b/interface-definitions/system-ip.xml.in
index e00dbf252..abdede979 100644
--- a/interface-definitions/system-ip.xml.in
+++ b/interface-definitions/system-ip.xml.in
@@ -48,6 +48,7 @@
</leafNode>
</children>
</node>
+ #include <include/system-ip-protocol.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/system-ipv6.xml.in b/interface-definitions/system-ipv6.xml.in
index 63260d00c..e17e1c01c 100644
--- a/interface-definitions/system-ipv6.xml.in
+++ b/interface-definitions/system-ipv6.xml.in
@@ -36,6 +36,7 @@
#include <include/arp-ndp-table-size.xml.i>
</children>
</node>
+ #include <include/system-ipv6-protocol.xml.i>
<leafNode name="strict-dad">
<properties>
<help>Disable IPv6 operation on interface when DAD fails on LL addr</help>
diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in
index e71a647ef..258913929 100644
--- a/interface-definitions/system-login.xml.in
+++ b/interface-definitions/system-login.xml.in
@@ -29,8 +29,8 @@
<regex>(\*|\!)</regex>
<regex>[a-zA-Z0-9\.\/]{13}</regex>
<regex>\$1\$[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{22}</regex>
- <regex>\$5\$[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{43}</regex>
- <regex>\$6\$[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{86}</regex>
+ <regex>\$5\$(rounds=[0-9]+\$)?[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{43}</regex>
+ <regex>\$6\$(rounds=[0-9]+\$)?[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{86}</regex>
</constraint>
<constraintErrorMessage>Invalid encrypted password for $VAR(../../@).</constraintErrorMessage>
</properties>
@@ -225,6 +225,19 @@
#include <include/interface/vrf.xml.i>
</children>
</node>
+ <leafNode name="max-login-session">
+ <properties>
+ <help>Maximum number of all login sessions</help>
+ <valueHelp>
+ <format>u32:1-65536</format>
+ <description>Maximum number of all login sessions</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65536"/>
+ </constraint>
+ <constraintErrorMessage>Maximum logins must be between 1 and 65536</constraintErrorMessage>
+ </properties>
+ </leafNode>
<leafNode name="timeout">
<properties>
<help>Session timeout</help>
diff --git a/interface-definitions/system-option.xml.in b/interface-definitions/system-option.xml.in
index a9fed81fe..0fa349e0b 100644
--- a/interface-definitions/system-option.xml.in
+++ b/interface-definitions/system-option.xml.in
@@ -36,13 +36,17 @@
<properties>
<help>System keyboard layout, type ISO2</help>
<completionHelp>
- <list>us fr de es fi jp106 no dk dvorak</list>
+ <list>us uk fr de es fi jp106 no dk dvorak</list>
</completionHelp>
<valueHelp>
<format>us</format>
<description>United States</description>
</valueHelp>
<valueHelp>
+ <format>uk</format>
+ <description>United Kingdom</description>
+ </valueHelp>
+ <valueHelp>
<format>fr</format>
<description>France</description>
</valueHelp>
@@ -75,7 +79,7 @@
<description>Dvorak</description>
</valueHelp>
<constraint>
- <regex>(us|fr|de|es|fi|jp106|no|dk|dvorak)</regex>
+ <regex>(us|uk|fr|de|es|fi|jp106|no|dk|dvorak)</regex>
</constraint>
<constraintErrorMessage>Invalid keyboard layout</constraintErrorMessage>
</properties>
@@ -121,6 +125,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/system-proxy.xml.in b/interface-definitions/system-proxy.xml.in
index 8fb6bfae5..f7ab31d7e 100644
--- a/interface-definitions/system-proxy.xml.in
+++ b/interface-definitions/system-proxy.xml.in
@@ -16,19 +16,8 @@
</properties>
</leafNode>
#include <include/port-number.xml.i>
- <leafNode name="username">
- <properties>
- <help>Proxy username</help>
- <constraint>
- <regex>[a-z0-9-_\.]{1,100}</regex>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="password">
- <properties>
- <help>Proxy password</help>
- </properties>
- </leafNode>
+ #include <include/generic-username.xml.i>
+ #include <include/generic-password.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/system-sflow.xml.in b/interface-definitions/system-sflow.xml.in
new file mode 100644
index 000000000..9c748c24a
--- /dev/null
+++ b/interface-definitions/system-sflow.xml.in
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- sflow configuration -->
+<interfaceDefinition>
+ <node name="system">
+ <children>
+ <node name="sflow" owner="${vyos_conf_scripts_dir}/system_sflow.py">
+ <properties>
+ <help>sFlow settings</help>
+ <priority>990</priority>
+ </properties>
+ <children>
+ <leafNode name="agent-address">
+ <properties>
+ <help>sFlow agent IPv4 or IPv6 address</help>
+ <completionHelp>
+ <list>auto</list>
+ <script>${vyos_completion_dir}/list_local_ips.sh --both</script>
+ </completionHelp>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>sFlow IPv4 agent address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>sFlow IPv6 agent address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ <validator name="ipv6-link-local"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="agent-interface">
+ <properties>
+ <help>IP address associated with this interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface name</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="drop-monitor-limit">
+ <properties>
+ <help>Export headers of dropped by kernel packets</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Maximum rate limit of N drops per second send out in the sFlow datagrams</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ #include <include/generic-interface-multi.xml.i>
+ <leafNode name="polling">
+ <properties>
+ <help>Schedule counter-polling in seconds</help>
+ <valueHelp>
+ <format>u32:1-600</format>
+ <description>Polling rate in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-600"/>
+ </constraint>
+ </properties>
+ <defaultValue>30</defaultValue>
+ </leafNode>
+ <leafNode name="sampling-rate">
+ <properties>
+ <help>sFlow sampling-rate</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Sampling rate (1 in N packets)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>1000</defaultValue>
+ </leafNode>
+ <tagNode name="server">
+ <properties>
+ <help>sFlow destination server</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 server to export sFlow</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 server to export sFlow</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>6343</defaultValue>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/system-time-zone.xml.in b/interface-definitions/system-time-zone.xml.in
index ff815c9d3..f6b291984 100644
--- a/interface-definitions/system-time-zone.xml.in
+++ b/interface-definitions/system-time-zone.xml.in
@@ -7,7 +7,7 @@
<help>Local time zone (default UTC)</help>
<priority>100</priority>
<completionHelp>
- <script>find /usr/share/zoneinfo/posix -type f -or -type l | sed -e s:/usr/share/zoneinfo/posix/:: | sort</script>
+ <script>timedatectl list-timezones</script>
</completionHelp>
<constraint>
<validator name="timezone" argument="--validate"/>
diff --git a/interface-definitions/vpn-ipsec.xml.in b/interface-definitions/vpn-ipsec.xml.in
index 64966b540..64cfbda08 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>
@@ -235,6 +269,7 @@
<regex>(none|hold|restart)</regex>
</constraint>
</properties>
+ <defaultValue>none</defaultValue>
</leafNode>
<node name="dead-peer-detection">
<properties>
@@ -263,6 +298,7 @@
<regex>(hold|clear|restart)</regex>
</constraint>
</properties>
+ <defaultValue>clear</defaultValue>
</leafNode>
<leafNode name="interval">
<properties>
@@ -321,11 +357,11 @@
<properties>
<help>IKE lifetime</help>
<valueHelp>
- <format>u32:30-86400</format>
+ <format>u32:0-86400</format>
<description>IKE lifetime in seconds</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 30-86400"/>
+ <validator name="numeric" argument="--range 0-86400"/>
</constraint>
</properties>
<defaultValue>28800</defaultValue>
@@ -465,22 +501,51 @@
</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>
</tagNode>
</children>
</tagNode>
- <leafNode name="include-ipsec-conf">
- <properties>
- <help>Absolute path to specify a strongSwan config include file</help>
- </properties>
- </leafNode>
- <leafNode name="include-ipsec-secrets">
- <properties>
- <help>Absolute path to a strongSwan secrets include file</help>
- </properties>
- </leafNode>
#include <include/generic-interface-multi.xml.i>
<node name="log">
<properties>
@@ -884,7 +949,7 @@
#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>
@@ -948,7 +1013,6 @@
</constraint>
</properties>
</leafNode>
- #include <include/ipsec/authentication-pre-shared-secret.xml.i>
<leafNode name="remote-id">
<properties>
<help>ID for remote authentication</help>
@@ -957,6 +1021,7 @@
<description>ID used for peer authentication</description>
</valueHelp>
</properties>
+ <defaultValue>%any</defaultValue>
</leafNode>
<leafNode name="use-x509-id">
<properties>
diff --git a/interface-definitions/vpn-l2tp.xml.in b/interface-definitions/vpn-l2tp.xml.in
index 06ca4ece5..6b64c5f5d 100644
--- a/interface-definitions/vpn-l2tp.xml.in
+++ b/interface-definitions/vpn-l2tp.xml.in
@@ -124,11 +124,7 @@
</children>
</node>
#include <include/accel-ppp/client-ipv6-pool.xml.i>
- <leafNode name="description">
- <properties>
- <help>Description for L2TP remote-access settings</help>
- </properties>
- </leafNode>
+ #include <include/generic-description.xml.i>
#include <include/dhcp-interface.xml.i>
<leafNode name="idle">
<properties>
@@ -178,7 +174,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">
@@ -251,6 +247,7 @@
<children>
#include <include/accel-ppp/lcp-echo-interval-failure.xml.i>
#include <include/accel-ppp/ppp-options-ipv6.xml.i>
+ #include <include/accel-ppp/ppp-options-ipv6-interface-id.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/vpn-openconnect.xml.in b/interface-definitions/vpn-openconnect.xml.in
index 8b60f2e6e..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,7 +158,7 @@
</tagNode>
</children>
</node>
- #include <include/radius-server-ipv4.xml.i>
+ #include <include/radius-auth-server-ipv4.xml.i>
<node name="radius">
<children>
#include <include/radius-timeout.xml.i>
@@ -150,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..5a8b4a78a 100644
--- a/interface-definitions/vpn-pptp.xml.in
+++ b/interface-definitions/vpn-pptp.xml.in
@@ -108,9 +108,13 @@
</tagNode>
</children>
</node>
- #include <include/radius-server-ipv4.xml.i>
+ <node name="radius">
+ <children>
+ #include <include/accel-ppp/radius-additions-rate-limit.xml.i>
+ </children>
+ </node>
+ #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>
</node>
</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..3783785ce 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>
@@ -34,6 +34,7 @@
</properties>
<children>
#include <include/interface/disable-forwarding.xml.i>
+ #include <include/system-ip-protocol.xml.i>
</children>
</node>
<node name="ipv6">
@@ -42,6 +43,7 @@
</properties>
<children>
#include <include/interface/disable-forwarding.xml.i>
+ #include <include/system-ipv6-protocol.xml.i>
</children>
</node>
<node name="protocols">
@@ -119,10 +121,10 @@
<constraintErrorMessage>VRF routing table must be in range from 100 to 65535</constraintErrorMessage>
</properties>
</leafNode>
- <leafNode name="vni" owner="${vyos_conf_scripts_dir}/vrf_vni.py">
+ <leafNode name="vni" owner="${vyos_conf_scripts_dir}/vrf_vni.py $VAR(../@)">
<properties>
<help>Virtual Network Identifier</help>
- <!-- priority must be after BGP -->
+ <!-- must be after BGP to keep correct order when removing L3VNIs in FRR -->
<priority>822</priority>
<valueHelp>
<format>u32:0-16777214</format>
diff --git a/interface-definitions/xml-component-version.xml.in b/interface-definitions/xml-component-version.xml.in
index 914e3bc69..e05f64643 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>
@@ -32,6 +33,7 @@
#include <include/version/pptp-version.xml.i>
#include <include/version/qos-version.xml.i>
#include <include/version/quagga-version.xml.i>
+ #include <include/version/rip-version.xml.i>
#include <include/version/rpki-version.xml.i>
#include <include/version/salt-version.xml.i>
#include <include/version/snmp-version.xml.i>
diff --git a/mibs/IANA-ADDRESS-FAMILY-NUMBERS-MIB.txt b/mibs/IANA-ADDRESS-FAMILY-NUMBERS-MIB.txt
new file mode 100644
index 000000000..7995fc4ad
--- /dev/null
+++ b/mibs/IANA-ADDRESS-FAMILY-NUMBERS-MIB.txt
@@ -0,0 +1,166 @@
+ IANA-ADDRESS-FAMILY-NUMBERS-MIB DEFINITIONS ::= BEGIN
+
+ IMPORTS
+ MODULE-IDENTITY,
+ mib-2 FROM SNMPv2-SMI
+ TEXTUAL-CONVENTION FROM SNMPv2-TC;
+
+ ianaAddressFamilyNumbers MODULE-IDENTITY
+ LAST-UPDATED "201309250000Z" -- September 25, 2013
+ ORGANIZATION "IANA"
+ CONTACT-INFO
+ "Postal: Internet Assigned Numbers Authority
+ Internet Corporation for Assigned Names
+ and Numbers
+ 12025 Waterfront Drive, Suite 300
+ Los Angeles, CA 90094-2536
+ USA
+
+ Tel: +1 310-301-5800
+ E-Mail: iana&iana.org"
+ DESCRIPTION
+ "The MIB module defines the AddressFamilyNumbers
+ textual convention."
+
+ -- revision history
+
+ REVISION "201309250000Z" -- September 25, 2013
+ DESCRIPTION "Fixed labels for 16389-16390."
+
+ REVISION "201307160000Z" -- July 16, 2013
+ DESCRIPTION "Fixed labels for 16389-16390."
+
+ REVISION "201306260000Z" -- June 26, 2013
+ DESCRIPTION "Added assignments 26-28."
+
+ REVISION "201306180000Z" -- June 18, 2013
+ DESCRIPTION "Added assignments 16384-16390. Assignment
+ 25 added in 2007 revision."
+
+ REVISION "200203140000Z" -- March 14, 2002
+ DESCRIPTION "AddressFamilyNumbers assignment 22 to
+ fibreChannelWWPN. AddressFamilyNumbers
+ assignment 23 to fibreChannelWWNN.
+ AddressFamilyNumers assignment 24 to gwid."
+
+ REVISION "200009080000Z" -- September 8, 2000
+ DESCRIPTION "AddressFamilyNumbers assignment 19 to xtpOverIpv4.
+ AddressFamilyNumbers assignment 20 to xtpOverIpv6.
+ AddressFamilyNumbers assignment 21 to xtpNativeModeXTP."
+
+ REVISION "200003010000Z" -- March 1, 2000
+ DESCRIPTION "AddressFamilyNumbers assignment 17 to distinguishedName.
+ AddressFamilyNumbers assignment 18 to asNumber."
+
+ REVISION "200002040000Z" -- February 4, 2000
+ DESCRIPTION "AddressFamilyNumbers assignment 16 to dns."
+
+ REVISION "9908260000Z" -- August 26, 1999
+ DESCRIPTION "Initial version, published as RFC 2677."
+ ::= { mib-2 72 }
+
+ AddressFamilyNumbers ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "The definition of this textual convention with the
+ addition of newly assigned values is published
+ periodically by the IANA, in either the Assigned
+ Numbers RFC, or some derivative of it specific to
+ Internet Network Management number assignments.
+ (The latest arrangements can be obtained by
+ contacting the IANA.)
+
+ The enumerations are described as:
+
+ other(0), -- none of the following
+ ipV4(1), -- IP Version 4
+ ipV6(2), -- IP Version 6
+ nsap(3), -- NSAP
+ hdlc(4), -- (8-bit multidrop)
+ bbn1822(5),
+ all802(6), -- (includes all 802 media
+ -- plus Ethernet 'canonical format')
+ e163(7),
+ e164(8), -- (SMDS, Frame Relay, ATM)
+ f69(9), -- (Telex)
+ x121(10), -- (X.25, Frame Relay)
+ ipx(11), -- IPX (Internet Protocol Exchange)
+ appleTalk(12), -- Apple Talk
+ decnetIV(13), -- DEC Net Phase IV
+ banyanVines(14), -- Banyan Vines
+ e164withNsap(15),
+ -- (E.164 with NSAP format subaddress)
+ dns(16), -- (Domain Name System)
+ distinguishedName(17), -- (Distinguished Name, per X.500)
+ asNumber(18), -- (16-bit quantity, per the AS number space)
+ xtpOverIpv4(19), -- XTP over IP version 4
+ xtpOverIpv6(20), -- XTP over IP version 6
+ xtpNativeModeXTP(21), -- XTP native mode XTP
+ fibreChannelWWPN(22), -- Fibre Channel World-Wide Port Name
+ fibreChannelWWNN(23), -- Fibre Channel World-Wide Node Name
+ gwid(24), -- Gateway Identifier
+ afi(25), -- AFI for L2VPN information
+ mplsTpSectionEndpointIdentifier(26), -- MPLS-TP Section Endpoint Identifier
+ mplsTpLspEndpointIdentifier(27), -- MPLS-TP LSP Endpoint Identifier
+ mplsTpPseudowireEndpointIdentifier(28), -- MPLS-TP Pseudowire Endpoint Identifier
+ eigrpCommonServiceFamily(16384), -- EIGRP Common Service Family
+ eigrpIpv4ServiceFamily(16385), -- EIGRP IPv4 Service Family
+ eigrpIpv6ServiceFamily(16386), -- EIGRP IPv6 Service Family
+ lispCanonicalAddressFormat(16387), -- LISP Canonical Address Format (LCAF)
+ bgpLs(16388), -- BGP-LS
+ fortyeightBitMacBitMac(16389), -- 48-bit MAC
+ sixtyfourBitMac(16390), -- 64-bit MAC
+ oui(16391), -- OUI
+ mac24(16392), -- MAC/24
+ mac40(16393), -- MAC/40
+ ipv664(16394), -- IPv6/64
+ rBridgePortID(16395), -- RBridge Port ID
+ reserved(65535)
+
+ Requests for new values should be made to IANA via
+ email (iana&iana.org)."
+ SYNTAX INTEGER {
+ other(0),
+ ipV4(1),
+ ipV6(2),
+ nsap(3),
+ hdlc(4),
+ bbn1822(5),
+ all802(6),
+ e163(7),
+ e164(8),
+ f69(9),
+ x121(10),
+ ipx(11),
+ appleTalk(12),
+ decnetIV(13),
+ banyanVines(14),
+ e164withNsap(15),
+ dns(16),
+ distinguishedName(17), -- (Distinguished Name, per X.500)
+ asNumber(18), -- (16-bit quantity, per the AS number space)
+ xtpOverIpv4(19),
+ xtpOverIpv6(20),
+ xtpNativeModeXTP(21),
+ fibreChannelWWPN(22),
+ fibreChannelWWNN(23),
+ gwid(24),
+ afi(25),
+ mplsTpSectionEndpointIdentifier(26),
+ mplsTpLspEndpointIdentifier(27),
+ mplsTpPseudowireEndpointIdentifier(28),
+ eigrpCommonServiceFamily(16384),
+ eigrpIpv4ServiceFamily(16385),
+ eigrpIpv6ServiceFamily(16386),
+ lispCanonicalAddressFormat(16387),
+ bgpLs(16388),
+ fortyeightBitMac(16389),
+ sixtyfourBitMac(16390),
+ oui(16391),
+ mac24(16392),
+ mac40(16393),
+ ipv664(16394),
+ rBridgePortID(16395),
+ reserved(65535)
+ }
+ END
diff --git a/mibs/IANA-LANGUAGE-MIB.txt b/mibs/IANA-LANGUAGE-MIB.txt
new file mode 100644
index 000000000..4b97bdd39
--- /dev/null
+++ b/mibs/IANA-LANGUAGE-MIB.txt
@@ -0,0 +1,126 @@
+IANA-LANGUAGE-MIB DEFINITIONS ::= BEGIN
+
+IMPORTS
+ MODULE-IDENTITY, OBJECT-IDENTITY, mib-2
+ FROM SNMPv2-SMI;
+
+ianaLanguages MODULE-IDENTITY
+ LAST-UPDATED "201405220000Z" -- May 22, 2014
+ ORGANIZATION "IANA"
+ CONTACT-INFO
+ "Internet Assigned Numbers Authority (IANA)
+
+ Postal: ICANN
+ 12025 Waterfront Drive, Suite 300
+ Los Angeles, CA 90094-2536
+
+ Tel: +1 310-301-5800
+ E-Mail: iana&iana.org"
+ DESCRIPTION
+ "The MIB module registers object identifier values for
+ well-known programming and scripting languages. Every
+ language registration MUST describe the format used
+ when transferring scripts written in this language.
+
+ Any additions or changes to the contents of this MIB
+ module require Designated Expert Review as defined in
+ the Guidelines for Writing IANA Considerations Section
+ document. The Designated Expert will be selected by
+ the IESG Area Director of the OPS Area.
+
+ Note, this module does not have to register all possible
+ languages since languages are identified by object
+ identifier values. It is therefore possible to registered
+ languages in private OID trees. The references given below are not
+ normative with regard to the language version. Other
+ references might be better suited to describe some newer
+ versions of this language. The references are only
+ provided as `a pointer into the right direction'."
+
+ -- Revision log, in reverse chronological order
+
+ REVISION "201405220000Z" -- May 22, 2014
+ DESCRIPTION "Updated contact info."
+
+ REVISION "200005100000Z" -- May 10, 2000
+ DESCRIPTION "Import mib-2 instead of experimental, so that
+ this module compiles"
+
+ REVISION "199909090900Z" -- September 9, 1999
+ DESCRIPTION "Initial version as published at time of
+ publication of RFC 2591."
+ ::= { mib-2 73 }
+
+ianaLangJavaByteCode OBJECT-IDENTITY
+ STATUS current
+ DESCRIPTION
+ "Java byte code to be processed by a Java virtual machine.
+ A script written in Java byte code is transferred by using
+ the Java archive file format (JAR)."
+ REFERENCE
+ "The Java Virtual Machine Specification.
+ ISBN 0-201-63452-X"
+ ::= { ianaLanguages 1 }
+
+ianaLangTcl OBJECT-IDENTITY
+ STATUS current
+ DESCRIPTION
+ "The Tool Command Language (Tcl). A script written in the
+ Tcl language is transferred in Tcl source code format."
+ REFERENCE
+ "Tcl and the Tk Toolkit.
+ ISBN 0-201-63337-X"
+ ::= { ianaLanguages 2 }
+
+ianaLangPerl OBJECT-IDENTITY
+ STATUS current
+ DESCRIPTION
+ "The Perl language. A script written in the Perl language
+ is transferred in Perl source code format."
+ REFERENCE
+ "Programming Perl.
+ ISBN 1-56592-149-6"
+ ::= { ianaLanguages 3 }
+
+ianaLangScheme OBJECT-IDENTITY
+ STATUS current
+ DESCRIPTION
+ "The Scheme language. A script written in the Scheme
+ language is transferred in Scheme source code format."
+ REFERENCE
+ "The Revised^4 Report on the Algorithmic Language Scheme.
+ MIT Press"
+ ::= { ianaLanguages 4 }
+
+ianaLangSRSL OBJECT-IDENTITY
+ STATUS current
+ DESCRIPTION
+ "The SNMP Script Language defined by SNMP Research. A
+ script written in the SNMP Script Language is transferred
+ in the SNMP Script Language source code format."
+ ::= { ianaLanguages 5 }
+
+ianaLangPSL OBJECT-IDENTITY
+ STATUS current
+ DESCRIPTION
+ "The Patrol Script Language defined by BMC Software. A script
+ written in the Patrol Script Language is transferred in the
+ Patrol Script Language source code format."
+ REFERENCE
+ "PATROL Script Language Reference Manual, Version 3.0,
+ November 30, 1995. BMC Software, Inc. 2101 City West Blvd.,
+ Houston, Texas 77042."
+ ::= { ianaLanguages 6 }
+
+ianaLangSMSL OBJECT-IDENTITY
+ STATUS current
+ DESCRIPTION
+ "The Systems Management Scripting Language. A script written
+ in the SMSL language is transferred in the SMSL source code
+ format."
+ REFERENCE
+ "ISO/ITU Command Sequencer.
+ ISO 10164-21 or ITU X.753"
+ ::= { ianaLanguages 7 }
+
+END
diff --git a/mibs/IANA-RTPROTO-MIB.txt b/mibs/IANA-RTPROTO-MIB.txt
new file mode 100644
index 000000000..f7bc1ebc7
--- /dev/null
+++ b/mibs/IANA-RTPROTO-MIB.txt
@@ -0,0 +1,95 @@
+IANA-RTPROTO-MIB DEFINITIONS ::= BEGIN
+
+IMPORTS
+ MODULE-IDENTITY, mib-2 FROM SNMPv2-SMI
+ TEXTUAL-CONVENTION FROM SNMPv2-TC;
+
+ianaRtProtoMIB MODULE-IDENTITY
+ LAST-UPDATED "201208300000Z" -- August 30, 2012
+ ORGANIZATION "IANA"
+ CONTACT-INFO
+ " Internet Assigned Numbers Authority
+ Internet Corporation for Assigned Names and Numbers
+ 12025 Waterfront Drive, Suite 300
+ Los Angeles, CA 90094-2536
+
+ Phone: +1 310 301 5800
+ EMail: iana&iana.org"
+ DESCRIPTION
+ "This MIB module defines the IANAipRouteProtocol and
+ IANAipMRouteProtocol textual conventions for use in MIBs
+ which need to identify unicast or multicast routing
+ mechanisms.
+
+ Any additions or changes to the contents of this MIB module
+ require either publication of an RFC, or Designated Expert
+ Review as defined in RFC 2434, Guidelines for Writing an
+ IANA Considerations Section in RFCs. The Designated Expert
+ will be selected by the IESG Area Director(s) of the Routing
+ Area."
+
+ REVISION "201208300000Z" -- August 30, 2012
+ DESCRIPTION "Added dhcp(19)."
+
+ REVISION "201107220000Z" -- July 22, 2011
+ DESCRIPTION "Added rpl(18) ."
+
+ REVISION "200009260000Z" -- September 26, 2000
+ DESCRIPTION "Original version, published in coordination
+ with RFC 2932."
+ ::= { mib-2 84 }
+
+IANAipRouteProtocol ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "A mechanism for learning routes. Inclusion of values for
+ routing protocols is not intended to imply that those
+ protocols need be supported."
+ SYNTAX INTEGER {
+ other (1), -- not specified
+ local (2), -- local interface
+ netmgmt (3), -- static route
+ icmp (4), -- result of ICMP Redirect
+
+ -- the following are all dynamic
+ -- routing protocols
+
+ egp (5), -- Exterior Gateway Protocol
+ ggp (6), -- Gateway-Gateway Protocol
+ hello (7), -- FuzzBall HelloSpeak
+ rip (8), -- Berkeley RIP or RIP-II
+ isIs (9), -- Dual IS-IS
+ esIs (10), -- ISO 9542
+ ciscoIgrp (11), -- Cisco IGRP
+ bbnSpfIgp (12), -- BBN SPF IGP
+ ospf (13), -- Open Shortest Path First
+ bgp (14), -- Border Gateway Protocol
+ idpr (15), -- InterDomain Policy Routing
+ ciscoEigrp (16), -- Cisco EIGRP
+ dvmrp (17), -- DVMRP
+ rpl (18), -- RPL [RFC-ietf-roll-rpl-19]
+ dhcp (19) -- DHCP [RFC2132]
+ }
+
+IANAipMRouteProtocol ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "The multicast routing protocol. Inclusion of values for
+ multicast routing protocols is not intended to imply that
+ those protocols need be supported."
+ SYNTAX INTEGER {
+ other(1), -- none of the following
+ local(2), -- e.g., manually configured
+ netmgmt(3), -- set via net.mgmt protocol
+ dvmrp(4),
+ mospf(5),
+ pimSparseDense(6), -- PIMv1, both DM and SM
+ cbt(7),
+ pimSparseMode(8), -- PIM-SM
+ pimDenseMode(9), -- PIM-DM
+ igmpOnly(10),
+ bgmp(11),
+ msdp(12)
+ }
+
+END
diff --git a/mibs/IANAifType-MIB.txt b/mibs/IANAifType-MIB.txt
new file mode 100644
index 000000000..027a1532f
--- /dev/null
+++ b/mibs/IANAifType-MIB.txt
@@ -0,0 +1,646 @@
+ IANAifType-MIB DEFINITIONS ::= BEGIN
+
+ IMPORTS
+ MODULE-IDENTITY, mib-2 FROM SNMPv2-SMI
+ TEXTUAL-CONVENTION FROM SNMPv2-TC;
+
+ ianaifType MODULE-IDENTITY
+ LAST-UPDATED "201407030000Z" -- July 3, 2014
+ ORGANIZATION "IANA"
+ CONTACT-INFO " Internet Assigned Numbers Authority
+
+ Postal: ICANN
+ 12025 Waterfront Drive, Suite 300
+ Los Angeles, CA 90094-2536
+
+ Tel: +1 310-301-5800
+ E-Mail: iana&iana.org"
+ DESCRIPTION "This MIB module defines the IANAifType Textual
+ Convention, and thus the enumerated values of
+ the ifType object defined in MIB-II's ifTable."
+
+ REVISION "201407030000Z" -- July 3, 2014
+ DESCRIPTION "Registration of new IANAifTypes 277-278."
+
+ REVISION "201405220000Z" -- May 22, 2014
+ DESCRIPTION "Updated contact info."
+
+ REVISION "201205170000Z" -- May 17, 2012
+ DESCRIPTION "Registration of new IANAifType 272."
+
+ REVISION "201201110000Z" -- January 11, 2012
+ DESCRIPTION "Registration of new IANAifTypes 266-271."
+
+ REVISION "201112180000Z" -- December 18, 2011
+ DESCRIPTION "Registration of new IANAifTypes 263-265."
+
+ REVISION "201110260000Z" -- October 26, 2011
+ DESCRIPTION "Registration of new IANAifType 262."
+
+ REVISION "201109070000Z" -- September 7, 2011
+ DESCRIPTION "Registration of new IANAifTypes 260 and 261."
+
+ REVISION "201107220000Z" -- July 22, 2011
+ DESCRIPTION "Registration of new IANAifType 259."
+
+ REVISION "201106030000Z" -- June 03, 2011
+ DESCRIPTION "Registration of new IANAifType 258."
+
+ REVISION "201009210000Z" -- September 21, 2010
+ DESCRIPTION "Registration of new IANAifTypes 256 and 257."
+
+ REVISION "201007210000Z" -- July 21, 2010
+ DESCRIPTION "Registration of new IANAifType 255."
+
+ REVISION "201002110000Z" -- February 11, 2010
+ DESCRIPTION "Registration of new IANAifType 254."
+
+ REVISION "201002080000Z" -- February 08, 2010
+ DESCRIPTION "Registration of new IANAifTypes 252 and 253."
+
+ REVISION "200905060000Z" -- May 06, 2009
+ DESCRIPTION "Registration of new IANAifType 251."
+
+ REVISION "200902060000Z" -- February 06, 2009
+ DESCRIPTION "Registration of new IANAtunnelType 15."
+
+ REVISION "200810090000Z" -- October 09, 2008
+ DESCRIPTION "Registration of new IANAifType 250."
+
+ REVISION "200808120000Z" -- August 12, 2008
+ DESCRIPTION "Registration of new IANAifType 249."
+
+ REVISION "200807220000Z" -- July 22, 2008
+ DESCRIPTION "Registration of new IANAifTypes 247 and 248."
+
+ REVISION "200806240000Z" -- June 24, 2008
+ DESCRIPTION "Registration of new IANAifType 246."
+
+ REVISION "200805290000Z" -- May 29, 2008
+ DESCRIPTION "Registration of new IANAifType 245."
+
+ REVISION "200709130000Z" -- September 13, 2007
+ DESCRIPTION "Registration of new IANAifTypes 243 and 244."
+
+ REVISION "200705290000Z" -- May 29, 2007
+ DESCRIPTION "Changed the description for IANAifType 228."
+
+ REVISION "200703080000Z" -- March 08, 2007
+ DESCRIPTION "Registration of new IANAifType 242."
+
+ REVISION "200701230000Z" -- January 23, 2007
+ DESCRIPTION "Registration of new IANAifTypes 239, 240, and 241."
+
+ REVISION "200610170000Z" -- October 17, 2006
+ DESCRIPTION "Deprecated/Obsoleted IANAifType 230. Registration of
+ IANAifType 238."
+
+ REVISION "200609250000Z" -- September 25, 2006
+ DESCRIPTION "Changed the description for IANA ifType
+ 184 and added new IANA ifType 237."
+
+ REVISION "200608170000Z" -- August 17, 2006
+ DESCRIPTION "Changed the descriptions for IANAifTypes
+ 20 and 21."
+
+ REVISION "200608110000Z" -- August 11, 2006
+ DESCRIPTION "Changed the descriptions for IANAifTypes
+ 7, 11, 62, 69, and 117."
+
+ REVISION "200607250000Z" -- July 25, 2006
+ DESCRIPTION "Registration of new IANA ifType 236."
+
+ REVISION "200606140000Z" -- June 14, 2006
+ DESCRIPTION "Registration of new IANA ifType 235."
+
+ REVISION "200603310000Z" -- March 31, 2006
+ DESCRIPTION "Registration of new IANA ifType 234."
+
+ REVISION "200603300000Z" -- March 30, 2006
+ DESCRIPTION "Registration of new IANA ifType 233."
+
+ REVISION "200512220000Z" -- December 22, 2005
+ DESCRIPTION "Registration of new IANA ifTypes 231 and 232."
+
+ REVISION "200510100000Z" -- October 10, 2005
+ DESCRIPTION "Registration of new IANA ifType 230."
+
+ REVISION "200509090000Z" -- September 09, 2005
+ DESCRIPTION "Registration of new IANA ifType 229."
+
+ REVISION "200505270000Z" -- May 27, 2005
+ DESCRIPTION "Registration of new IANA ifType 228."
+
+ REVISION "200503030000Z" -- March 3, 2005
+ DESCRIPTION "Added the IANAtunnelType TC and deprecated
+ IANAifType sixToFour (215) per RFC4087."
+
+ REVISION "200411220000Z" -- November 22, 2004
+ DESCRIPTION "Registration of new IANA ifType 227 per RFC4631."
+
+ REVISION "200406170000Z" -- June 17, 2004
+ DESCRIPTION "Registration of new IANA ifType 226."
+
+ REVISION "200405120000Z" -- May 12, 2004
+ DESCRIPTION "Added description for IANAifType 6, and
+ changed the descriptions for IANAifTypes
+ 180, 181, and 182."
+
+ REVISION "200405070000Z" -- May 7, 2004
+ DESCRIPTION "Registration of new IANAifType 225."
+
+ REVISION "200308250000Z" -- Aug 25, 2003
+ DESCRIPTION "Deprecated IANAifTypes 7 and 11. Obsoleted
+ IANAifTypes 62, 69, and 117. ethernetCsmacd (6)
+ should be used instead of these values"
+
+ REVISION "200308180000Z" -- Aug 18, 2003
+ DESCRIPTION "Registration of new IANAifType
+ 224."
+
+ REVISION "200308070000Z" -- Aug 7, 2003
+ DESCRIPTION "Registration of new IANAifTypes
+ 222 and 223."
+
+ REVISION "200303180000Z" -- Mar 18, 2003
+ DESCRIPTION "Registration of new IANAifType
+ 221."
+
+ REVISION "200301130000Z" -- Jan 13, 2003
+ DESCRIPTION "Registration of new IANAifType
+ 220."
+
+ REVISION "200210170000Z" -- Oct 17, 2002
+ DESCRIPTION "Registration of new IANAifType
+ 219."
+
+ REVISION "200207160000Z" -- Jul 16, 2002
+ DESCRIPTION "Registration of new IANAifTypes
+ 217 and 218."
+
+ REVISION "200207100000Z" -- Jul 10, 2002
+ DESCRIPTION "Registration of new IANAifTypes
+ 215 and 216."
+
+ REVISION "200206190000Z" -- Jun 19, 2002
+ DESCRIPTION "Registration of new IANAifType
+ 214."
+
+ REVISION "200201040000Z" -- Jan 4, 2002
+ DESCRIPTION "Registration of new IANAifTypes
+ 211, 212 and 213."
+
+ REVISION "200112200000Z" -- Dec 20, 2001
+ DESCRIPTION "Registration of new IANAifTypes
+ 209 and 210."
+
+ REVISION "200111150000Z" -- Nov 15, 2001
+ DESCRIPTION "Registration of new IANAifTypes
+ 207 and 208."
+
+ REVISION "200111060000Z" -- Nov 6, 2001
+ DESCRIPTION "Registration of new IANAifType
+ 206."
+
+ REVISION "200111020000Z" -- Nov 2, 2001
+ DESCRIPTION "Registration of new IANAifType
+ 205."
+
+ REVISION "200110160000Z" -- Oct 16, 2001
+ DESCRIPTION "Registration of new IANAifTypes
+ 199, 200, 201, 202, 203, and 204."
+
+ REVISION "200109190000Z" -- Sept 19, 2001
+ DESCRIPTION "Registration of new IANAifType
+ 198."
+
+ REVISION "200105110000Z" -- May 11, 2001
+ DESCRIPTION "Registration of new IANAifType
+ 197."
+
+ REVISION "200101120000Z" -- Jan 12, 2001
+ DESCRIPTION "Registration of new IANAifTypes
+ 195 and 196."
+
+ REVISION "200012190000Z" -- Dec 19, 2000
+ DESCRIPTION "Registration of new IANAifTypes
+ 193 and 194."
+
+ REVISION "200012070000Z" -- Dec 07, 2000
+ DESCRIPTION "Registration of new IANAifTypes
+ 191 and 192."
+
+ REVISION "200012040000Z" -- Dec 04, 2000
+ DESCRIPTION "Registration of new IANAifType
+ 190."
+
+ REVISION "200010170000Z" -- Oct 17, 2000
+ DESCRIPTION "Registration of new IANAifTypes
+ 188 and 189."
+
+ REVISION "200010020000Z" -- Oct 02, 2000
+ DESCRIPTION "Registration of new IANAifType 187."
+
+ REVISION "200009010000Z" -- Sept 01, 2000
+ DESCRIPTION "Registration of new IANAifTypes
+ 184, 185, and 186."
+
+ REVISION "200008240000Z" -- Aug 24, 2000
+ DESCRIPTION "Registration of new IANAifType 183."
+
+ REVISION "200008230000Z" -- Aug 23, 2000
+ DESCRIPTION "Registration of new IANAifTypes
+ 174-182."
+
+ REVISION "200008220000Z" -- Aug 22, 2000
+ DESCRIPTION "Registration of new IANAifTypes 170,
+ 171, 172 and 173."
+
+ REVISION "200004250000Z" -- Apr 25, 2000
+ DESCRIPTION "Registration of new IANAifTypes 168 and 169."
+
+ REVISION "200003060000Z" -- Mar 6, 2000
+ DESCRIPTION "Fixed a missing semi-colon in the IMPORT.
+ Also cleaned up the REVISION log a bit.
+ It is not complete, but from now on it will
+ be maintained and kept up to date with each
+ change to this MIB module."
+
+ REVISION "199910081430Z" -- Oct 08, 1999
+ DESCRIPTION "Include new name assignments up to cnr(85).
+ This is the first version available via the WWW
+ at: ftp://ftp.isi.edu/mib/ianaiftype.mib"
+
+ REVISION "199401310000Z" -- Jan 31, 1994
+ DESCRIPTION "Initial version of this MIB as published in
+ RFC 1573."
+ ::= { mib-2 30 }
+
+ IANAifType ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "This data type is used as the syntax of the ifType
+ object in the (updated) definition of MIB-II's
+ ifTable.
+
+ The definition of this textual convention with the
+ addition of newly assigned values is published
+ periodically by the IANA, in either the Assigned
+ Numbers RFC, or some derivative of it specific to
+ Internet Network Management number assignments. (The
+ latest arrangements can be obtained by contacting the
+ IANA.)
+
+ Requests for new values should be made to IANA via
+ email (iana&iana.org).
+
+ The relationship between the assignment of ifType
+ values and of OIDs to particular media-specific MIBs
+ is solely the purview of IANA and is subject to change
+ without notice. Quite often, a media-specific MIB's
+ OID-subtree assignment within MIB-II's 'transmission'
+ subtree will be the same as its ifType value.
+ However, in some circumstances this will not be the
+ case, and implementors must not pre-assume any
+ specific relationship between ifType values and
+ transmission subtree OIDs."
+ SYNTAX INTEGER {
+ other(1), -- none of the following
+ regular1822(2),
+ hdh1822(3),
+ ddnX25(4),
+ rfc877x25(5),
+ ethernetCsmacd(6), -- for all ethernet-like interfaces,
+ -- regardless of speed, as per RFC3635
+ iso88023Csmacd(7), -- Deprecated via RFC3635
+ -- ethernetCsmacd (6) should be used instead
+ iso88024TokenBus(8),
+ iso88025TokenRing(9),
+ iso88026Man(10),
+ starLan(11), -- Deprecated via RFC3635
+ -- ethernetCsmacd (6) should be used instead
+ proteon10Mbit(12),
+ proteon80Mbit(13),
+ hyperchannel(14),
+ fddi(15),
+ lapb(16),
+ sdlc(17),
+ ds1(18), -- DS1-MIB
+ e1(19), -- Obsolete see DS1-MIB
+ basicISDN(20), -- no longer used
+ -- see also RFC2127
+ primaryISDN(21), -- no longer used
+ -- see also RFC2127
+ propPointToPointSerial(22), -- proprietary serial
+ ppp(23),
+ softwareLoopback(24),
+ eon(25), -- CLNP over IP
+ ethernet3Mbit(26),
+ nsip(27), -- XNS over IP
+ slip(28), -- generic SLIP
+ ultra(29), -- ULTRA technologies
+ ds3(30), -- DS3-MIB
+ sip(31), -- SMDS, coffee
+ frameRelay(32), -- DTE only.
+ rs232(33),
+ para(34), -- parallel-port
+ arcnet(35), -- arcnet
+ arcnetPlus(36), -- arcnet plus
+ atm(37), -- ATM cells
+ miox25(38),
+ sonet(39), -- SONET or SDH
+ x25ple(40),
+ iso88022llc(41),
+ localTalk(42),
+ smdsDxi(43),
+ frameRelayService(44), -- FRNETSERV-MIB
+ v35(45),
+ hssi(46),
+ hippi(47),
+ modem(48), -- Generic modem
+ aal5(49), -- AAL5 over ATM
+ sonetPath(50),
+ sonetVT(51),
+ smdsIcip(52), -- SMDS InterCarrier Interface
+ propVirtual(53), -- proprietary virtual/internal
+ propMultiplexor(54),-- proprietary multiplexing
+ ieee80212(55), -- 100BaseVG
+ fibreChannel(56), -- Fibre Channel
+ hippiInterface(57), -- HIPPI interfaces
+ frameRelayInterconnect(58), -- Obsolete, use either
+ -- frameRelay(32) or
+ -- frameRelayService(44).
+ aflane8023(59), -- ATM Emulated LAN for 802.3
+ aflane8025(60), -- ATM Emulated LAN for 802.5
+ cctEmul(61), -- ATM Emulated circuit
+ fastEther(62), -- Obsoleted via RFC3635
+ -- ethernetCsmacd (6) should be used instead
+ isdn(63), -- ISDN and X.25
+ v11(64), -- CCITT V.11/X.21
+ v36(65), -- CCITT V.36
+ g703at64k(66), -- CCITT G703 at 64Kbps
+ g703at2mb(67), -- Obsolete see DS1-MIB
+ qllc(68), -- SNA QLLC
+ fastEtherFX(69), -- Obsoleted via RFC3635
+ -- ethernetCsmacd (6) should be used instead
+ channel(70), -- channel
+ ieee80211(71), -- radio spread spectrum
+ ibm370parChan(72), -- IBM System 360/370 OEMI Channel
+ escon(73), -- IBM Enterprise Systems Connection
+ dlsw(74), -- Data Link Switching
+ isdns(75), -- ISDN S/T interface
+ isdnu(76), -- ISDN U interface
+ lapd(77), -- Link Access Protocol D
+ ipSwitch(78), -- IP Switching Objects
+ rsrb(79), -- Remote Source Route Bridging
+ atmLogical(80), -- ATM Logical Port
+ ds0(81), -- Digital Signal Level 0
+ ds0Bundle(82), -- group of ds0s on the same ds1
+ bsc(83), -- Bisynchronous Protocol
+ async(84), -- Asynchronous Protocol
+ cnr(85), -- Combat Net Radio
+ iso88025Dtr(86), -- ISO 802.5r DTR
+ eplrs(87), -- Ext Pos Loc Report Sys
+ arap(88), -- Appletalk Remote Access Protocol
+ propCnls(89), -- Proprietary Connectionless Protocol
+ hostPad(90), -- CCITT-ITU X.29 PAD Protocol
+ termPad(91), -- CCITT-ITU X.3 PAD Facility
+ frameRelayMPI(92), -- Multiproto Interconnect over FR
+ x213(93), -- CCITT-ITU X213
+ adsl(94), -- Asymmetric Digital Subscriber Loop
+ radsl(95), -- Rate-Adapt. Digital Subscriber Loop
+ sdsl(96), -- Symmetric Digital Subscriber Loop
+ vdsl(97), -- Very H-Speed Digital Subscrib. Loop
+ iso88025CRFPInt(98), -- ISO 802.5 CRFP
+ myrinet(99), -- Myricom Myrinet
+ voiceEM(100), -- voice recEive and transMit
+ voiceFXO(101), -- voice Foreign Exchange Office
+ voiceFXS(102), -- voice Foreign Exchange Station
+ voiceEncap(103), -- voice encapsulation
+ voiceOverIp(104), -- voice over IP encapsulation
+ atmDxi(105), -- ATM DXI
+ atmFuni(106), -- ATM FUNI
+ atmIma (107), -- ATM IMA
+ pppMultilinkBundle(108), -- PPP Multilink Bundle
+ ipOverCdlc (109), -- IBM ipOverCdlc
+ ipOverClaw (110), -- IBM Common Link Access to Workstn
+ stackToStack (111), -- IBM stackToStack
+ virtualIpAddress (112), -- IBM VIPA
+ mpc (113), -- IBM multi-protocol channel support
+ ipOverAtm (114), -- IBM ipOverAtm
+ iso88025Fiber (115), -- ISO 802.5j Fiber Token Ring
+ tdlc (116), -- IBM twinaxial data link control
+ gigabitEthernet (117), -- Obsoleted via RFC3635
+ -- ethernetCsmacd (6) should be used instead
+ hdlc (118), -- HDLC
+ lapf (119), -- LAP F
+ v37 (120), -- V.37
+ x25mlp (121), -- Multi-Link Protocol
+ x25huntGroup (122), -- X25 Hunt Group
+ transpHdlc (123), -- Transp HDLC
+ interleave (124), -- Interleave channel
+ fast (125), -- Fast channel
+ ip (126), -- IP (for APPN HPR in IP networks)
+ docsCableMaclayer (127), -- CATV Mac Layer
+ docsCableDownstream (128), -- CATV Downstream interface
+ docsCableUpstream (129), -- CATV Upstream interface
+ a12MppSwitch (130), -- Avalon Parallel Processor
+ tunnel (131), -- Encapsulation interface
+ coffee (132), -- coffee pot
+ ces (133), -- Circuit Emulation Service
+ atmSubInterface (134), -- ATM Sub Interface
+ l2vlan (135), -- Layer 2 Virtual LAN using 802.1Q
+ l3ipvlan (136), -- Layer 3 Virtual LAN using IP
+ l3ipxvlan (137), -- Layer 3 Virtual LAN using IPX
+ digitalPowerline (138), -- IP over Power Lines
+ mediaMailOverIp (139), -- Multimedia Mail over IP
+ dtm (140), -- Dynamic syncronous Transfer Mode
+ dcn (141), -- Data Communications Network
+ ipForward (142), -- IP Forwarding Interface
+ msdsl (143), -- Multi-rate Symmetric DSL
+ ieee1394 (144), -- IEEE1394 High Performance Serial Bus
+ if-gsn (145), -- HIPPI-6400
+ dvbRccMacLayer (146), -- DVB-RCC MAC Layer
+ dvbRccDownstream (147), -- DVB-RCC Downstream Channel
+ dvbRccUpstream (148), -- DVB-RCC Upstream Channel
+ atmVirtual (149), -- ATM Virtual Interface
+ mplsTunnel (150), -- MPLS Tunnel Virtual Interface
+ srp (151), -- Spatial Reuse Protocol
+ voiceOverAtm (152), -- Voice Over ATM
+ voiceOverFrameRelay (153), -- Voice Over Frame Relay
+ idsl (154), -- Digital Subscriber Loop over ISDN
+ compositeLink (155), -- Avici Composite Link Interface
+ ss7SigLink (156), -- SS7 Signaling Link
+ propWirelessP2P (157), -- Prop. P2P wireless interface
+ frForward (158), -- Frame Forward Interface
+ rfc1483 (159), -- Multiprotocol over ATM AAL5
+ usb (160), -- USB Interface
+ ieee8023adLag (161), -- IEEE 802.3ad Link Aggregate
+ bgppolicyaccounting (162), -- BGP Policy Accounting
+ frf16MfrBundle (163), -- FRF .16 Multilink Frame Relay
+ h323Gatekeeper (164), -- H323 Gatekeeper
+ h323Proxy (165), -- H323 Voice and Video Proxy
+ mpls (166), -- MPLS
+ mfSigLink (167), -- Multi-frequency signaling link
+ hdsl2 (168), -- High Bit-Rate DSL - 2nd generation
+ shdsl (169), -- Multirate HDSL2
+ ds1FDL (170), -- Facility Data Link 4Kbps on a DS1
+ pos (171), -- Packet over SONET/SDH Interface
+ dvbAsiIn (172), -- DVB-ASI Input
+ dvbAsiOut (173), -- DVB-ASI Output
+ plc (174), -- Power Line Communtications
+ nfas (175), -- Non Facility Associated Signaling
+ tr008 (176), -- TR008
+ gr303RDT (177), -- Remote Digital Terminal
+ gr303IDT (178), -- Integrated Digital Terminal
+ isup (179), -- ISUP
+ propDocsWirelessMaclayer (180), -- Cisco proprietary Maclayer
+ propDocsWirelessDownstream (181), -- Cisco proprietary Downstream
+ propDocsWirelessUpstream (182), -- Cisco proprietary Upstream
+ hiperlan2 (183), -- HIPERLAN Type 2 Radio Interface
+ propBWAp2Mp (184), -- PropBroadbandWirelessAccesspt2multipt
+ -- use of this iftype for IEEE 802.16 WMAN
+ -- interfaces as per IEEE Std 802.16f is
+ -- deprecated and ifType 237 should be used instead.
+ sonetOverheadChannel (185), -- SONET Overhead Channel
+ digitalWrapperOverheadChannel (186), -- Digital Wrapper
+ aal2 (187), -- ATM adaptation layer 2
+ radioMAC (188), -- MAC layer over radio links
+ atmRadio (189), -- ATM over radio links
+ imt (190), -- Inter Machine Trunks
+ mvl (191), -- Multiple Virtual Lines DSL
+ reachDSL (192), -- Long Reach DSL
+ frDlciEndPt (193), -- Frame Relay DLCI End Point
+ atmVciEndPt (194), -- ATM VCI End Point
+ opticalChannel (195), -- Optical Channel
+ opticalTransport (196), -- Optical Transport
+ propAtm (197), -- Proprietary ATM
+ voiceOverCable (198), -- Voice Over Cable Interface
+ infiniband (199), -- Infiniband
+ teLink (200), -- TE Link
+ q2931 (201), -- Q.2931
+ virtualTg (202), -- Virtual Trunk Group
+ sipTg (203), -- SIP Trunk Group
+ sipSig (204), -- SIP Signaling
+ docsCableUpstreamChannel (205), -- CATV Upstream Channel
+ econet (206), -- Acorn Econet
+ pon155 (207), -- FSAN 155Mb Symetrical PON interface
+ pon622 (208), -- FSAN622Mb Symetrical PON interface
+ bridge (209), -- Transparent bridge interface
+ linegroup (210), -- Interface common to multiple lines
+ voiceEMFGD (211), -- voice E&M Feature Group D
+ voiceFGDEANA (212), -- voice FGD Exchange Access North American
+ voiceDID (213), -- voice Direct Inward Dialing
+ mpegTransport (214), -- MPEG transport interface
+ sixToFour (215), -- 6to4 interface (DEPRECATED)
+ gtp (216), -- GTP (GPRS Tunneling Protocol)
+ pdnEtherLoop1 (217), -- Paradyne EtherLoop 1
+ pdnEtherLoop2 (218), -- Paradyne EtherLoop 2
+ opticalChannelGroup (219), -- Optical Channel Group
+ homepna (220), -- HomePNA ITU-T G.989
+ gfp (221), -- Generic Framing Procedure (GFP)
+ ciscoISLvlan (222), -- Layer 2 Virtual LAN using Cisco ISL
+ actelisMetaLOOP (223), -- Acteleis proprietary MetaLOOP High Speed Link
+ fcipLink (224), -- FCIP Link
+ rpr (225), -- Resilient Packet Ring Interface Type
+ qam (226), -- RF Qam Interface
+ lmp (227), -- Link Management Protocol
+ cblVectaStar (228), -- Cambridge Broadband Networks Limited VectaStar
+ docsCableMCmtsDownstream (229), -- CATV Modular CMTS Downstream Interface
+ adsl2 (230), -- Asymmetric Digital Subscriber Loop Version 2
+ -- (DEPRECATED/OBSOLETED - please use adsl2plus 238 instead)
+ macSecControlledIF (231), -- MACSecControlled
+ macSecUncontrolledIF (232), -- MACSecUncontrolled
+ aviciOpticalEther (233), -- Avici Optical Ethernet Aggregate
+ atmbond (234), -- atmbond
+ voiceFGDOS (235), -- voice FGD Operator Services
+ mocaVersion1 (236), -- MultiMedia over Coax Alliance (MoCA) Interface
+ -- as documented in information provided privately to IANA
+ ieee80216WMAN (237), -- IEEE 802.16 WMAN interface
+ adsl2plus (238), -- Asymmetric Digital Subscriber Loop Version 2,
+ -- Version 2 Plus and all variants
+ dvbRcsMacLayer (239), -- DVB-RCS MAC Layer
+ dvbTdm (240), -- DVB Satellite TDM
+ dvbRcsTdma (241), -- DVB-RCS TDMA
+ x86Laps (242), -- LAPS based on ITU-T X.86/Y.1323
+ wwanPP (243), -- 3GPP WWAN
+ wwanPP2 (244), -- 3GPP2 WWAN
+ voiceEBS (245), -- voice P-phone EBS physical interface
+ ifPwType (246), -- Pseudowire interface type
+ ilan (247), -- Internal LAN on a bridge per IEEE 802.1ap
+ pip (248), -- Provider Instance Port on a bridge per IEEE 802.1ah PBB
+ aluELP (249), -- Alcatel-Lucent Ethernet Link Protection
+ gpon (250), -- Gigabit-capable passive optical networks (G-PON) as per ITU-T G.948
+ vdsl2 (251), -- Very high speed digital subscriber line Version 2 (as per ITU-T Recommendation G.993.2)
+ capwapDot11Profile (252), -- WLAN Profile Interface
+ capwapDot11Bss (253), -- WLAN BSS Interface
+ capwapWtpVirtualRadio (254), -- WTP Virtual Radio Interface
+ bits (255), -- bitsport
+ docsCableUpstreamRfPort (256), -- DOCSIS CATV Upstream RF Port
+ cableDownstreamRfPort (257), -- CATV downstream RF port
+ vmwareVirtualNic (258), -- VMware Virtual Network Interface
+ ieee802154 (259), -- IEEE 802.15.4 WPAN interface
+ otnOdu (260), -- OTN Optical Data Unit
+ otnOtu (261), -- OTN Optical channel Transport Unit
+ ifVfiType (262), -- VPLS Forwarding Instance Interface Type
+ g9981 (263), -- G.998.1 bonded interface
+ g9982 (264), -- G.998.2 bonded interface
+ g9983 (265), -- G.998.3 bonded interface
+ aluEpon (266), -- Ethernet Passive Optical Networks (E-PON)
+ aluEponOnu (267), -- EPON Optical Network Unit
+ aluEponPhysicalUni (268), -- EPON physical User to Network interface
+ aluEponLogicalLink (269), -- The emulation of a point-to-point link over the EPON layer
+ aluGponOnu (270), -- GPON Optical Network Unit
+ aluGponPhysicalUni (271), -- GPON physical User to Network interface
+ vmwareNicTeam (272), -- VMware NIC Team
+ docsOfdmDownstream (277), -- CATV Downstream OFDM interface
+ docsOfdmaUpstream (278) -- CATV Upstream OFDMA interface
+ }
+
+IANAtunnelType ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "The encapsulation method used by a tunnel. The value
+ direct indicates that a packet is encapsulated
+ directly within a normal IP header, with no
+ intermediate header, and unicast to the remote tunnel
+ endpoint (e.g., an RFC 2003 IP-in-IP tunnel, or an RFC
+ 1933 IPv6-in-IPv4 tunnel). The value minimal indicates
+ that a Minimal Forwarding Header (RFC 2004) is
+ inserted between the outer header and the payload
+ packet. The value UDP indicates that the payload
+ packet is encapsulated within a normal UDP packet
+ (e.g., RFC 1234).
+
+ The values sixToFour, sixOverFour, and isatap
+ indicates that an IPv6 packet is encapsulated directly
+ within an IPv4 header, with no intermediate header,
+ and unicast to the destination determined by the 6to4,
+ 6over4, or ISATAP protocol.
+
+ The remaining protocol-specific values indicate that a
+ header of the protocol of that name is inserted
+ between the outer header and the payload header.
+
+ The assignment policy for IANAtunnelType values is
+ identical to the policy for assigning IANAifType
+ values."
+ SYNTAX INTEGER {
+ other(1), -- none of the following
+ direct(2), -- no intermediate header
+ gre(3), -- GRE encapsulation
+ minimal(4), -- Minimal encapsulation
+ l2tp(5), -- L2TP encapsulation
+ pptp(6), -- PPTP encapsulation
+ l2f(7), -- L2F encapsulation
+ udp(8), -- UDP encapsulation
+ atmp(9), -- ATMP encapsulation
+ msdp(10), -- MSDP encapsulation
+ sixToFour(11), -- 6to4 encapsulation
+ sixOverFour(12), -- 6over4 encapsulation
+ isatap(13), -- ISATAP encapsulation
+ teredo(14), -- Teredo encapsulation
+ ipHttps(15) -- IPHTTPS
+ }
+
+ END
diff --git a/op-mode-definitions/container.xml.in b/op-mode-definitions/container.xml.in
index 786bd66d3..ada9a4d59 100644
--- a/op-mode-definitions/container.xml.in
+++ b/op-mode-definitions/container.xml.in
@@ -11,7 +11,7 @@
<properties>
<help>Pull a new image for container</help>
</properties>
- <command>sudo podman image pull "${4}"</command>
+ <command>sudo ${vyos_op_scripts_dir}/container.py add_image --name "${4}"</command>
</tagNode>
</children>
</node>
@@ -44,7 +44,7 @@
<script>sudo podman image ls -q</script>
</completionHelp>
</properties>
- <command>sudo podman image rm --force "${4}"</command>
+ <command>sudo ${vyos_op_scripts_dir}/container.py delete_image --name "${4}"</command>
</tagNode>
</children>
</node>
diff --git a/op-mode-definitions/counters.xml.in b/op-mode-definitions/counters.xml.in
new file mode 100644
index 000000000..4bf08d201
--- /dev/null
+++ b/op-mode-definitions/counters.xml.in
@@ -0,0 +1,598 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="clear">
+ <children>
+ <node name="interfaces">
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear interface counters for all interfaces</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters</command>
+ </node>
+ <node name="bonding">
+ <properties>
+ <help>Clear Bonding interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all bonding interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="bonding">
+ <properties>
+ <help>Clear interface information for a given bonding interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type bonding</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given bonding interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="bridge">
+ <properties>
+ <help>Clear Bridge interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all bridge interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="bridge">
+ <properties>
+ <help>Clear interface information for a given bridge interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type bridge</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given bridge interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="dummy">
+ <properties>
+ <help>Clear Dummy interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all dummy interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="dummy">
+ <properties>
+ <help>Clear interface information for a given dummy interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type dummy</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given dummy interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="ethernet">
+ <properties>
+ <help>Clear Ethernet interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all ethernet interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="ethernet">
+ <properties>
+ <help>Clear interface information for a given ethernet interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type ethernet</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given ethernet interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="geneve">
+ <properties>
+ <help>Clear GENEVE interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all GENEVE interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="geneve">
+ <properties>
+ <help>Clear interface information for a given GENEVE interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type geneve</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given GENEVE interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="input">
+ <properties>
+ <help>Clear Input (ifb) interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all Input interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="input">
+ <properties>
+ <help>Clear interface information for a given Input interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type input</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given Input interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="l2tpv3">
+ <properties>
+ <help>Clear L2TPv3 interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all L2TPv3 interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="l2tpv3">
+ <properties>
+ <help>Clear interface information for a given L2TPv3 interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type l2tpeth</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given L2TPv3 interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="loopback">
+ <properties>
+ <help>Clear Loopback interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all loopback interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="loopback">
+ <properties>
+ <help>Clear interface information for a given loopback interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type loopback</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given loopback interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="macsec">
+ <properties>
+ <help>Clear MACsec interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all MACsec interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="macsec">
+ <properties>
+ <help>Clear interface information for a given MACsec interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type macsec</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given MACsec interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="openvpn">
+ <properties>
+ <help>Clear OpenVPN interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all OpenVPN interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="openvpn">
+ <properties>
+ <help>Clear interface information for a given OpenVPN interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type openvpn</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given OpenVPN interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="pppoe">
+ <properties>
+ <help>Clear PPPoE interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all PPPoE interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="pppoe">
+ <properties>
+ <help>Clear interface information for a given PPPoE interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type pppoe</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given PPPoE interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="pseudo-ethernet">
+ <properties>
+ <help>Clear Pseudo-Ethernet/MACvlan interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all Pseudo-Ethernet interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="pseudo-ethernet">
+ <properties>
+ <help>Clear interface information for a given Pseudo-Ethernet interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type pseudo-ethernet</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given Pseudo-Ethernet interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="sstp">
+ <properties>
+ <help>Clear SSTP interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all SSTP interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="sstp">
+ <properties>
+ <help>Clear interface information for a given SSTP interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type sstp</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given SSTP interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="tunnel">
+ <properties>
+ <help>Clear Tunnel interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all tunnel interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="tunnel">
+ <properties>
+ <help>Clear interface information for a given tunnel interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type tunnel</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given tunnel interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="virtual-ethernet">
+ <properties>
+ <help>Clear virtual-ethernet interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all virtual-ethernet interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="virtual-ethernet">
+ <properties>
+ <help>Clear interface information for a given virtual-ethernet interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type virtual-ethernet</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given virtual-ethernet interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="vti">
+ <properties>
+ <help>Clear VTI interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all VTI interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="vti">
+ <properties>
+ <help>Clear interface information for a given VTI interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type vti</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given VTI interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="vxlan">
+ <properties>
+ <help>Clear VXLAN interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all VXLAN interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="vxlan">
+ <properties>
+ <help>Clear interface information for a given VXLAN interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type vxlan</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given VXLAN interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="wireguard">
+ <properties>
+ <help>Clear Wireguard interface information</help>
+ </properties>
+ <children>
+ <node name="counters">
+ <properties>
+ <help>Clear all Wireguard interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="wireguard">
+ <properties>
+ <help>Clear interface information for a given Wireguard interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type wireguard</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear interface counters for a given Wireguard interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="wireless">
+ <properties>
+ <help>Clear Wireless (WLAN) interface information</help>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear all wireless interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </leafNode>
+ </children>
+ </node>
+ <tagNode name="wireless">
+ <properties>
+ <help>Clear interface information for a given wireless interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type wireless</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear counters for a given wireless interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="wwan">
+ <properties>
+ <help>Clear Wireless Modem (WWAN) interface information</help>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear all WWAN interface counters</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_type "$3"</command>
+ </leafNode>
+ </children>
+ </node>
+ <tagNode name="wwan">
+ <properties>
+ <help>Clear interface information for a given WWAN interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --type wwan</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear counters for a given WWAN interface</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf_name "$4"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
+
diff --git a/op-mode-definitions/date.xml.in b/op-mode-definitions/date.xml.in
index 15a69dbd9..6d8586025 100644
--- a/op-mode-definitions/date.xml.in
+++ b/op-mode-definitions/date.xml.in
@@ -37,28 +37,6 @@
</properties>
<command>/bin/date "$3"</command>
</tagNode>
- <node name="date">
- <properties>
- <help>Set system date and time</help>
- </properties>
- <children>
- <node name="ntp">
- <properties>
- <help>Set system date and time from NTP server (default: 0.pool.ntp.org)</help>
- </properties>
- <command>/usr/sbin/ntpdate -u 0.pool.ntp.org</command>
- </node>
- <tagNode name="ntp">
- <properties>
- <help>Set system date and time from NTP server</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_ntp_servers.sh</script>
- </completionHelp>
- </properties>
- <command>/usr/sbin/ntpdate -u "$4"</command>
- </tagNode>
- </children>
- </node>
</children>
</node>
</interfaceDefinition>
diff --git a/op-mode-definitions/dhcp.xml.in b/op-mode-definitions/dhcp.xml.in
index ce4026ff4..66584efc3 100644
--- a/op-mode-definitions/dhcp.xml.in
+++ b/op-mode-definitions/dhcp.xml.in
@@ -16,7 +16,7 @@
<properties>
<help>Show DHCP server leases</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet</command>
<children>
<tagNode name="pool">
<properties>
@@ -25,25 +25,25 @@
<path>service dhcp-server shared-network-name</path>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/show_dhcp.py --leases --pool $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --pool $6</command>
</tagNode>
<tagNode name="sort">
<properties>
<help>Show DHCP server leases sorted by the specified key</help>
<completionHelp>
- <script>sudo ${vyos_op_scripts_dir}/show_dhcp.py --allowed sort</script>
+ <list>end hostname ip mac pool remaining start state</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/show_dhcp.py --leases --sort $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --sort $6</command>
</tagNode>
<tagNode name="state">
<properties>
<help>Show DHCP server leases with a specific state (can be multiple, comma-separated)</help>
<completionHelp>
- <script>sudo ${vyos_op_scripts_dir}/show_dhcp.py --allowed state</script>
+ <list>abandoned active all backup expired free released reset</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/show_dhcp.py --leases --state $(echo $6 | tr , " ")</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --state $6</command>
</tagNode>
</children>
</node>
@@ -51,7 +51,7 @@
<properties>
<help>Show DHCP server statistics</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/show_dhcp.py --statistics</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_pool_statistics --family inet</command>
<children>
<tagNode name="pool">
<properties>
@@ -60,7 +60,7 @@
<path>service dhcp-server shared-network-name</path>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/show_dhcp.py --statistics --pool $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_pool_statistics --family inet --pool $6</command>
</tagNode>
</children>
</node>
@@ -88,28 +88,28 @@
<properties>
<help>Show DHCPv6 server leases for a specific pool</help>
<completionHelp>
- <script>sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --allowed pool</script>
+ <path>service dhcpv6-server shared-network-name</path>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases --pool $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --pool $6</command>
</tagNode>
<tagNode name="sort">
<properties>
<help>Show DHCPv6 server leases sorted by the specified key</help>
<completionHelp>
- <script>sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --allowed sort</script>
+ <list>end iaid_duid ip last_communication pool remaining state type</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases --sort $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --sort $6</command>
</tagNode>
<tagNode name="state">
<properties>
<help>Show DHCPv6 server leases with a specific state (can be multiple, comma-separated)</help>
<completionHelp>
- <script>sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --allowed state</script>
+ <list>abandoned active all backup expired free released reset</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases --state $(echo $6 | tr , " ")</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --state $6</command>
</tagNode>
</children>
</node>
@@ -175,7 +175,7 @@
<properties>
<help>Renew DHCP client lease for specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<command>sudo systemctl restart "dhclient@$4.service"</command>
@@ -191,7 +191,7 @@
<properties>
<help>Renew DHCPv6 client lease for specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<command>sudo systemctl restart "dhcp6c@$4.service"</command>
diff --git a/op-mode-definitions/flow-accounting-op.xml.in b/op-mode-definitions/flow-accounting-op.xml.in
index 7aaae5974..46dc77d05 100644
--- a/op-mode-definitions/flow-accounting-op.xml.in
+++ b/op-mode-definitions/flow-accounting-op.xml.in
@@ -13,7 +13,7 @@
<properties>
<help>Show flow accounting statistics for specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<command>${vyos_op_scripts_dir}/flow_accounting_op.py --action show --interface $4</command>
diff --git a/op-mode-definitions/force-arp.xml.in b/op-mode-definitions/force-arp.xml.in
index f9f7c7643..05aa04e6b 100644
--- a/op-mode-definitions/force-arp.xml.in
+++ b/op-mode-definitions/force-arp.xml.in
@@ -19,7 +19,7 @@
<properties>
<help>Send gratuitous ARP reply on specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
+ <script>${vyos_completion_dir}/list_interfaces --broadcast</script>
</completionHelp>
</properties>
<children>
@@ -50,7 +50,7 @@
<properties>
<help>Send gratuitous ARP request on specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
+ <script>${vyos_completion_dir}/list_interfaces --broadcast</script>
</completionHelp>
</properties>
<children>
@@ -81,7 +81,7 @@
<properties>
<help>Send ARP for DAD detection on specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
+ <script>${vyos_completion_dir}/list_interfaces --broadcast</script>
</completionHelp>
</properties>
<children>
diff --git a/op-mode-definitions/force-ipv6-nd.xml.in b/op-mode-definitions/force-ipv6-nd.xml.in
index 49de097f6..664fee4fb 100644
--- a/op-mode-definitions/force-ipv6-nd.xml.in
+++ b/op-mode-definitions/force-ipv6-nd.xml.in
@@ -11,7 +11,7 @@
<properties>
<help>IPv6 Neighbor Discovery on specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
diff --git a/op-mode-definitions/force-ipv6-rd.xml.in b/op-mode-definitions/force-ipv6-rd.xml.in
index 8c901af25..c81b81a49 100644
--- a/op-mode-definitions/force-ipv6-rd.xml.in
+++ b/op-mode-definitions/force-ipv6-rd.xml.in
@@ -11,7 +11,7 @@
<properties>
<help>IPv6 Router Discovery on specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<command>/usr/bin/rdisc6 "$4"</command>
diff --git a/op-mode-definitions/force-mtu-host.xml.in b/op-mode-definitions/force-mtu-host.xml.in
index b92179f11..56241678a 100644
--- a/op-mode-definitions/force-mtu-host.xml.in
+++ b/op-mode-definitions/force-mtu-host.xml.in
@@ -20,7 +20,7 @@
<properties>
<help>Source interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<command>${vyos_op_scripts_dir}/force_mtu_host.sh $4 $6</command>
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-openvpn-config-client.xml.in b/op-mode-definitions/generate-openvpn-config-client.xml.in
index 4f9f31bfe..fc8bfa346 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>
+ <script>${vyos_completion_dir}/list_interfaces --type openvpn</script>
</completionHelp>
</properties>
<children>
diff --git a/op-mode-definitions/generate-wireguard.xml.in b/op-mode-definitions/generate-wireguard.xml.in
index 259c9a898..1dcafcd8b 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>
+ <script>${vyos_completion_dir}/list_interfaces --type wireguard</script>
</completionHelp>
</properties>
<children>
diff --git a/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i
index 7dbc4fde5..820d507fd 100644
--- a/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i
+++ b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i
@@ -195,6 +195,12 @@
</leafNode>
</children>
</node>
+ <leafNode name="filtered-routes">
+ <properties>
+ <help>Show filtered routes from BGP neighbor</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
<leafNode name="received-routes">
<properties>
<help>Show received routes from BGP neighbor</help>
diff --git a/op-mode-definitions/include/show-route-summary.xml.i b/op-mode-definitions/include/bgp/evpn-type-1.xml.i
index 471124562..b5097c8b1 100644
--- a/op-mode-definitions/include/show-route-summary.xml.i
+++ b/op-mode-definitions/include/bgp/evpn-type-1.xml.i
@@ -1,7 +1,7 @@
-<!-- included start from show-route-summary.xml.i -->
-<leafNode name="summary">
+<!-- included start from bgp/evpn-type-1.xml.i -->
+<leafNode name="1">
<properties>
- <help>Summary of all routes</help>
+ <help>EAD (Type-1) route</help>
</properties>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</leafNode>
diff --git a/op-mode-definitions/include/bgp/evpn-type-2.xml.i b/op-mode-definitions/include/bgp/evpn-type-2.xml.i
new file mode 100644
index 000000000..827298d62
--- /dev/null
+++ b/op-mode-definitions/include/bgp/evpn-type-2.xml.i
@@ -0,0 +1,8 @@
+<!-- included start from bgp/evpn-type-2.xml.i -->
+<leafNode name="2">
+ <properties>
+ <help>MAC-IP (Type-2) route</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+</leafNode>
+<!-- included end -->
diff --git a/op-mode-definitions/include/bgp/evpn-type-3.xml.i b/op-mode-definitions/include/bgp/evpn-type-3.xml.i
new file mode 100644
index 000000000..ae90b2e5c
--- /dev/null
+++ b/op-mode-definitions/include/bgp/evpn-type-3.xml.i
@@ -0,0 +1,8 @@
+<!-- included start from bgp/evpn-type-3.xml.i -->
+<leafNode name="3">
+ <properties>
+ <help>Multicast (Type-3) route</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+</leafNode>
+<!-- included end -->
diff --git a/op-mode-definitions/include/bgp/evpn-type-4.xml.i b/op-mode-definitions/include/bgp/evpn-type-4.xml.i
new file mode 100644
index 000000000..7248b4753
--- /dev/null
+++ b/op-mode-definitions/include/bgp/evpn-type-4.xml.i
@@ -0,0 +1,8 @@
+<!-- included start from bgp/evpn-type-4.xml.i -->
+<leafNode name="4">
+ <properties>
+ <help>Ethernet Segment (Type-4) route</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+</leafNode>
+<!-- included end -->
diff --git a/op-mode-definitions/include/bgp/evpn-type-5.xml.i b/op-mode-definitions/include/bgp/evpn-type-5.xml.i
new file mode 100644
index 000000000..e3a72168a
--- /dev/null
+++ b/op-mode-definitions/include/bgp/evpn-type-5.xml.i
@@ -0,0 +1,8 @@
+<!-- included start from bgp/evpn-type-5.xml.i -->
+<leafNode name="5">
+ <properties>
+ <help>Prefix (Type-5) route</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+</leafNode>
+<!-- included end -->
diff --git a/op-mode-definitions/include/bgp/evpn-type-ead.xml.i b/op-mode-definitions/include/bgp/evpn-type-ead.xml.i
new file mode 100644
index 000000000..452de2f9a
--- /dev/null
+++ b/op-mode-definitions/include/bgp/evpn-type-ead.xml.i
@@ -0,0 +1,8 @@
+<!-- included start from bgp/evpn-type-ead.xml.i -->
+<leafNode name="ead">
+ <properties>
+ <help>EAD (Type-1) route</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+</leafNode>
+<!-- included end -->
diff --git a/op-mode-definitions/include/bgp/evpn-type-es.xml.i b/op-mode-definitions/include/bgp/evpn-type-es.xml.i
new file mode 100644
index 000000000..50c40151a
--- /dev/null
+++ b/op-mode-definitions/include/bgp/evpn-type-es.xml.i
@@ -0,0 +1,8 @@
+<!-- included start from bgp/evpn-type-es.xml.i -->
+<leafNode name="es">
+ <properties>
+ <help>Ethernet Segment (Type-4) route</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+</leafNode>
+<!-- included end -->
diff --git a/op-mode-definitions/include/bgp/evpn-type-macip.xml.i b/op-mode-definitions/include/bgp/evpn-type-macip.xml.i
new file mode 100644
index 000000000..6f601eb3f
--- /dev/null
+++ b/op-mode-definitions/include/bgp/evpn-type-macip.xml.i
@@ -0,0 +1,8 @@
+<!-- included start from bgp/evpn-type-macip.xml.i -->
+<leafNode name="macip">
+ <properties>
+ <help>MAC-IP (Type-2) route</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+</leafNode>
+<!-- included end -->
diff --git a/op-mode-definitions/include/bgp/evpn-type-multicast.xml.i b/op-mode-definitions/include/bgp/evpn-type-multicast.xml.i
new file mode 100644
index 000000000..5194dbb56
--- /dev/null
+++ b/op-mode-definitions/include/bgp/evpn-type-multicast.xml.i
@@ -0,0 +1,8 @@
+<!-- included start from bgp/evpn-type-multicast.xml.i -->
+<leafNode name="multicast">
+ <properties>
+ <help>Multicast (Type-3) route</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+</leafNode>
+<!-- included end -->
diff --git a/op-mode-definitions/include/bgp/evpn-type-prefix.xml.i b/op-mode-definitions/include/bgp/evpn-type-prefix.xml.i
new file mode 100644
index 000000000..d5054d86b
--- /dev/null
+++ b/op-mode-definitions/include/bgp/evpn-type-prefix.xml.i
@@ -0,0 +1,8 @@
+<!-- included start from bgp/evpn-type-prefix.xml.i -->
+<leafNode name="prefix">
+ <properties>
+ <help>Prefix (Type-5) route</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+</leafNode>
+<!-- included end -->
diff --git a/op-mode-definitions/include/bgp/martian-next-hop.xml.i b/op-mode-definitions/include/bgp/martian-next-hop.xml.i
new file mode 100644
index 000000000..938d4ffa2
--- /dev/null
+++ b/op-mode-definitions/include/bgp/martian-next-hop.xml.i
@@ -0,0 +1,15 @@
+<!-- included start from bgp/martian-next-hop.xml.i -->
+<node name="martian">
+ <properties>
+ <help>Martian next-hops</help>
+ </properties>
+ <children>
+ <leafNode name="next-hop">
+ <properties>
+ <help>Martian next-hop database</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ </children>
+</node>
+<!-- included end -->
diff --git a/op-mode-definitions/include/bgp/next-hop.xml.i b/op-mode-definitions/include/bgp/next-hop.xml.i
new file mode 100644
index 000000000..517a44888
--- /dev/null
+++ b/op-mode-definitions/include/bgp/next-hop.xml.i
@@ -0,0 +1,23 @@
+<!-- included start from bgp/next-hop.xml.i -->
+<node name="nexthop">
+ <properties>
+ <help>Show BGP nexthop table</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/vtysh-generic-detail.xml.i>
+ </children>
+</node>
+<tagNode name="nexthop">
+ <properties>
+ <help>IPv4/IPv6 nexthop address</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x&gt; &lt;h:h:h:h:h:h:h:h&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/vtysh-generic-detail.xml.i>
+ </children>
+</tagNode>
+<!-- included end -->
diff --git a/op-mode-definitions/include/bgp/show-bgp-common.xml.i b/op-mode-definitions/include/bgp/show-bgp-common.xml.i
index c9a112fca..de794a879 100644
--- a/op-mode-definitions/include/bgp/show-bgp-common.xml.i
+++ b/op-mode-definitions/include/bgp/show-bgp-common.xml.i
@@ -171,66 +171,16 @@
<help>Specify Route type</help>
</properties>
<children>
- <leafNode name="1">
- <properties>
- <help>EAD (Type-1) route</help>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- </leafNode>
- <leafNode name="2">
- <properties>
- <help>MAC-IP (Type-2) route</help>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- </leafNode>
- <leafNode name="3">
- <properties>
- <help>Multicast (Type-3) route</help>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- </leafNode>
- <leafNode name="4">
- <properties>
- <help>Ethernet Segment (Type-4) route</help>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- </leafNode>
- <leafNode name="5">
- <properties>
- <help>Prefix (Type-5) route</help>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- </leafNode>
- <leafNode name="ead">
- <properties>
- <help>EAD (Type-1) route</help>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- </leafNode>
- <leafNode name="es">
- <properties>
- <help>Ethernet Segment (Type-4) route</help>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- </leafNode>
- <leafNode name="macip">
- <properties>
- <help>MAC-IP (Type-2) route</help>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- </leafNode>
- <leafNode name="multicast">
- <properties>
- <help>Multicast (Type-3) route</help>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- </leafNode>
- <leafNode name="prefix">
- <properties>
- <help>Prefix (Type-5) route</help>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- </leafNode>
+ #include <include/bgp/evpn-type-1.xml.i>
+ #include <include/bgp/evpn-type-2.xml.i>
+ #include <include/bgp/evpn-type-3.xml.i>
+ #include <include/bgp/evpn-type-4.xml.i>
+ #include <include/bgp/evpn-type-5.xml.i>
+ #include <include/bgp/evpn-type-ead.xml.i>
+ #include <include/bgp/evpn-type-es.xml.i>
+ #include <include/bgp/evpn-type-macip.xml.i>
+ #include <include/bgp/evpn-type-multicast.xml.i>
+ #include <include/bgp/evpn-type-prefix.xml.i>
</children>
</node>
#include <include/vni-tagnode-all.xml.i>
diff --git a/op-mode-definitions/include/bgp/show-ip-bgp-common.xml.i b/op-mode-definitions/include/bgp/show-ip-bgp-common.xml.i
index 36cc9a3fa..db9021f3e 100644
--- a/op-mode-definitions/include/bgp/show-ip-bgp-common.xml.i
+++ b/op-mode-definitions/include/bgp/show-ip-bgp-common.xml.i
@@ -93,6 +93,12 @@
</properties>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</leafNode>
+ <leafNode name="filtered-routes">
+ <properties>
+ <help>Show the filtered routes from neighbor</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
<leafNode name="received-routes">
<properties>
<help>Show the received routes from neighbor</help>
diff --git a/op-mode-definitions/include/isis-common.xml.i b/op-mode-definitions/include/isis-common.xml.i
index 5674bc22f..e94d868e8 100644
--- a/op-mode-definitions/include/isis-common.xml.i
+++ b/op-mode-definitions/include/isis-common.xml.i
@@ -4,12 +4,7 @@
<help>Show IS-IS link state database</help>
</properties>
<children>
- <leafNode name="detail">
- <properties>
- <help>Show detailed information</help>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- </leafNode>
+ #include <include/vtysh-generic-detail.xml.i>
</children>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</node>
@@ -32,31 +27,26 @@
<properties>
<help>Show IS-IS interfaces</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
- <leafNode name="detail">
- <properties>
- <help>Show detailed information</help>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- </leafNode>
+ #include <include/vtysh-generic-detail.xml.i>
</children>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</node>
-<tagNode name="interface">
+#include <include/vtysh-generic-interface-tagNode.xml.i>
+<node name="mpls">
<properties>
- <help>Show specific IS-IS interface</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
- </completionHelp>
+ <help>Show MPLS information</help>
</properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
-</tagNode>
+ <children>
+ #include <include/ldp-sync.xml.i>
+ </children>
+</node>
<node name="mpls-te">
<properties>
- <help>Show IS-IS MPLS traffic engineering information</help>
+ <help>Show MPLS traffic engineering information</help>
</properties>
<children>
<leafNode name="router">
@@ -71,15 +61,7 @@
</properties>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</leafNode>
- <tagNode name="interface">
- <properties>
- <help>Show specific IS-IS interface</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
- </completionHelp>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- </tagNode>
+ #include <include/vtysh-generic-interface-tagNode.xml.i>
</children>
</node>
<node name="neighbor">
@@ -87,14 +69,9 @@
<help>Show IS-IS neighbor adjacencies</help>
</properties>
<children>
- <leafNode name="detail">
- <properties>
- <help>Show detailed information</help>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- </leafNode>
+ #include <include/vtysh-generic-detail.xml.i>
</children>
- <command>vtysh -c "show isis neighbor"</command>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</node>
<tagNode name="neighbor">
<properties>
@@ -122,8 +99,14 @@
</properties>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</leafNode>
+ <leafNode name="prefix-sid">
+ <properties>
+ <help>Show Prefix-SID information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
</children>
- <command>vtysh -c "show isis route"</command>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</node>
<node name="segment-routing">
<properties>
@@ -136,12 +119,6 @@
</properties>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</leafNode>
- <leafNode name="prefix-sids">
- <properties>
- <help>Show prefix segment IDs</help>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- </leafNode>
</children>
</node>
<leafNode name="spf-delay-ietf">
@@ -176,4 +153,4 @@
</children>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</node>
-<!-- included end -->
+<!-- included end --> \ No newline at end of file
diff --git a/op-mode-definitions/include/ldp-sync.xml.i b/op-mode-definitions/include/ldp-sync.xml.i
new file mode 100644
index 000000000..b7b04e7e5
--- /dev/null
+++ b/op-mode-definitions/include/ldp-sync.xml.i
@@ -0,0 +1,11 @@
+<!-- included start from ldp-sync.xml.i -->
+<node name="ldp-sync">
+ <properties>
+ <help>Show LDP-IGP synchronization information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/vtysh-generic-interface-tagNode.xml.i>
+ </children>
+</node>
+<!-- included end --> \ No newline at end of file
diff --git a/op-mode-definitions/include/ospf-common.xml.i b/op-mode-definitions/include/ospf-common.xml.i
index 23769c8ba..979ffb07e 100644
--- a/op-mode-definitions/include/ospf-common.xml.i
+++ b/op-mode-definitions/include/ospf-common.xml.i
@@ -508,15 +508,15 @@
</properties>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</node>
-<tagNode name="interface">
+#include <include/vtysh-generic-interface-tagNode.xml.i>
+<node name="mpls">
<properties>
- <help>Show IPv4 OSPF information for specified interface</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
- </completionHelp>
+ <help>Show MPLS information</help>
</properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
-</tagNode>
+ <children>
+ #include <include/ldp-sync.xml.i>
+ </children>
+</node>
<node name="neighbor">
<properties>
<help>Show IPv4 OSPF neighbor information</help>
@@ -536,15 +536,24 @@
<help>Show IPv4 OSPF neighbor information for specified IP address or interface</help>
<completionHelp>
<list>&lt;x.x.x.x&gt;</list>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</tagNode>
-<leafNode name="route">
+<node name="route">
<properties>
<help>Show IPv4 OSPF route information</help>
</properties>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
-</leafNode>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show detailed IPv4 OSPF route information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ </children>
+</node>
<!-- included end -->
+
diff --git a/op-mode-definitions/include/ospfv3/interface.xml.i b/op-mode-definitions/include/ospfv3/interface.xml.i
index 0fb66257d..7a0b8ea48 100644
--- a/op-mode-definitions/include/ospfv3/interface.xml.i
+++ b/op-mode-definitions/include/ospfv3/interface.xml.i
@@ -38,7 +38,7 @@
<properties>
<help>Specific insterface to examine</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
diff --git a/op-mode-definitions/include/vtysh-generic-interface-tagNode.xml.i b/op-mode-definitions/include/vtysh-generic-interface-tagNode.xml.i
new file mode 100644
index 000000000..e95961177
--- /dev/null
+++ b/op-mode-definitions/include/vtysh-generic-interface-tagNode.xml.i
@@ -0,0 +1,11 @@
+<!-- included start from vtysh-generic-interface.xml.i -->
+<tagNode name="interface">
+ <properties>
+ <help>Show information about specific interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+</tagNode>
+<!-- included end -->
diff --git a/op-mode-definitions/ipv4-route.xml.in b/op-mode-definitions/ipv4-route.xml.in
index 660b34496..17a0a4ad9 100644
--- a/op-mode-definitions/ipv4-route.xml.in
+++ b/op-mode-definitions/ipv4-route.xml.in
@@ -45,7 +45,7 @@
<properties>
<help>Reset ARP cache for interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<command>sudo ${vyos_op_scripts_dir}/neighbor.py reset --family inet --interface "$5"</command>
diff --git a/op-mode-definitions/ipv6-route.xml.in b/op-mode-definitions/ipv6-route.xml.in
index d75caf308..5ed0b9dba 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 --broadcast</script>
</completionHelp>
</properties>
<command>${vyos_op_scripts_dir}/neighbor.py show --family inet6 --interface "$5"</command>
@@ -71,7 +71,7 @@
<properties>
<help>Reset IPv6 ND cache for interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<command>sudo ip -f inet6 neigh flush dev "$5"</command>
diff --git a/op-mode-definitions/lldp.xml.in b/op-mode-definitions/lldp.xml.in
index 297ccf1f4..985262a89 100644
--- a/op-mode-definitions/lldp.xml.in
+++ b/op-mode-definitions/lldp.xml.in
@@ -11,22 +11,16 @@
<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>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</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-bandwidth.xml.in b/op-mode-definitions/monitor-bandwidth.xml.in
index 9af0a9e70..2da9d34f4 100644
--- a/op-mode-definitions/monitor-bandwidth.xml.in
+++ b/op-mode-definitions/monitor-bandwidth.xml.in
@@ -12,7 +12,7 @@
<properties>
<help>Monitor bandwidth usage on specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
</tagNode>
diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in
index 1b1f53dc2..06b1cf129 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -48,7 +48,7 @@
<properties>
<help>Show DHCP client log on specific interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
+ <script>${vyos_completion_dir}/list_interfaces --broadcast</script>
</completionHelp>
</properties>
<command>journalctl --no-hostname --follow --boot --unit "dhclient@$6.service"</command>
@@ -78,7 +78,7 @@
<properties>
<help>Show DHCPv6 client log on specific interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<command>journalctl --no-hostname --follow --boot --unit "dhcp6c@$6.service"</command>
@@ -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 --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 --unit opennhrp.service</command>
+ <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@$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>
@@ -234,7 +258,7 @@
<properties>
<help>Monitor last lines of SSTP client log for specific interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py -t sstpc</script>
+ <path>interfaces sstpc</path>
</completionHelp>
</properties>
<command>journalctl --no-hostname --boot --follow --unit "ppp@$5.service"</command>
@@ -243,20 +267,20 @@
</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.service --unit accel-ppp@*.service --unit ocserv.service</command>
</leafNode>
<leafNode name="ipsec">
<properties>
<help>Monitor last lines of IPsec</help>
</properties>
- <command>journalctl --no-hostname --boot --follow --unit strongswan-starter.service</command>
+ <command>journalctl --no-hostname --boot --follow --unit strongswan.service</command>
</leafNode>
<leafNode name="l2tp">
<properties>
@@ -264,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/monitor-ndp.xml.in b/op-mode-definitions/monitor-ndp.xml.in
index 1ac6ce39b..26d881f1a 100644
--- a/op-mode-definitions/monitor-ndp.xml.in
+++ b/op-mode-definitions/monitor-ndp.xml.in
@@ -13,7 +13,7 @@
<properties>
<help>Monitor ndp protocol on specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
diff --git a/op-mode-definitions/nat.xml.in b/op-mode-definitions/nat.xml.in
index 50abb1555..307a91337 100644
--- a/op-mode-definitions/nat.xml.in
+++ b/op-mode-definitions/nat.xml.in
@@ -4,7 +4,7 @@
<children>
<node name="nat">
<properties>
- <help>Show IPv4 to IPv4 Network Address Translation (NAT) information</help>
+ <help>Show IPv4 Network Address Translation (NAT) information</help>
</properties>
<children>
<node name="source">
@@ -16,13 +16,13 @@
<properties>
<help>Show configured source NAT rules</help>
</properties>
- <command>${vyos_op_scripts_dir}/nat.py show_rules --direction source --family inet</command>
+ <command>sudo ${vyos_op_scripts_dir}/nat.py show_rules --direction source --family inet</command>
</node>
<node name="statistics">
<properties>
<help>Show statistics for configured source NAT rules</help>
</properties>
- <command>${vyos_op_scripts_dir}/nat.py show_statistics --direction source --family inet</command>
+ <command>sudo ${vyos_op_scripts_dir}/nat.py show_statistics --direction source --family inet</command>
</node>
<node name="translations">
<properties>
@@ -36,16 +36,10 @@
<list>&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}/nat.py show_statistics --direction destination --family inet</command>
+ <command>sudo ${vyos_op_scripts_dir}/nat.py show_statistics --direction destination --family inet</command>
</node>
<node name="translations">
<properties>
@@ -78,16 +72,10 @@
<list>&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/nhrp.xml.in b/op-mode-definitions/nhrp.xml.in
index c10b111a7..11a4b8814 100644
--- a/op-mode-definitions/nhrp.xml.in
+++ b/op-mode-definitions/nhrp.xml.in
@@ -50,13 +50,13 @@
<properties>
<help>Show NHRP interface connection information</help>
</properties>
- <command>if pgrep opennhrp >/dev/null; then sudo opennhrpctl interface show; else echo OpenNHRP is not running; fi</command>
+ <command>${vyos_op_scripts_dir}/nhrp.py show_interface</command>
</leafNode>
<leafNode name="tunnel">
<properties>
<help>Show NHRP tunnel connection information</help>
</properties>
- <command>if pgrep opennhrp >/dev/null; then sudo opennhrpctl show ; else echo OpenNHRP is not running; fi</command>
+ <command>${vyos_op_scripts_dir}/nhrp.py show_tunnel</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/openvpn.xml.in b/op-mode-definitions/openvpn.xml.in
index aec09fa48..94647af02 100644
--- a/op-mode-definitions/openvpn.xml.in
+++ b/op-mode-definitions/openvpn.xml.in
@@ -20,7 +20,7 @@
<properties>
<help>Reset OpenVPN process on interface</help>
<completionHelp>
- <script>sudo ${vyos_completion_dir}/list_interfaces.py --type openvpn</script>
+ <script>sudo ${vyos_completion_dir}/list_interfaces --type openvpn</script>
</completionHelp>
</properties>
<command>sudo ${vyos_op_scripts_dir}/openvpn.py reset --interface $4</command>
@@ -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>
+ <script>sudo ${vyos_completion_dir}/list_interfaces --type openvpn</script>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf=$4</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name=$4</command>
<children>
<tagNode name="user">
<properties>
@@ -94,7 +95,7 @@
<properties>
<help>Show summary of specified OpenVPN interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4"</command>
</leafNode>
</children>
</tagNode>
@@ -121,7 +122,7 @@
<properties>
<help>Show tunnel status for OpenVPN site-to-site interfaces</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/openvpn.py show --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/reset-vpn.xml.in b/op-mode-definitions/reset-vpn.xml.in
index 94ee1c7df..8de95d1cc 100644
--- a/op-mode-definitions/reset-vpn.xml.in
+++ b/op-mode-definitions/reset-vpn.xml.in
@@ -7,82 +7,78 @@
<help>Reset Virtual Private Network (VPN) information</help>
</properties>
<children>
- <node name="remote-access">
+ <node name="l2tp">
<properties>
- <help>Reset remote access VPN connections</help>
+ <help>Reset L2TP server VPN sessions</help>
</properties>
<children>
<node name="all">
<properties>
- <help>Terminate all users current remote access VPN session(s)</help>
+ <help>Reset all L2TP server VPN sessions</help>
</properties>
- <children>
- <node name="protocol">
- <properties>
- <help>Terminate specified users current remote access VPN session(s) with specified protocol</help>
- </properties>
- <children>
- <leafNode name="l2tp">
- <properties>
- <help>Terminate all users current remote access VPN session(s) with L2TP protocol</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py --username="all_users" --protocol="l2tp"</command>
- </leafNode>
- <leafNode name="pptp">
- <properties>
- <help>Terminate all users current remote access VPN session(s) with PPTP protocol</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py --username="all_users" --protocol="pptp"</command>
- </leafNode>
- <leafNode name="sstp">
- <properties>
- <help>Terminate all users current remote access VPN session(s) with SSTP protocol</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py --username="all_users" --protocol="sstp"</command>
- </leafNode>
- </children>
- </node>
- </children>
- <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py --username="all_users"</command>
+ <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="l2tp"</command>
</node>
<tagNode name="interface">
<properties>
- <help>Terminate a remote access VPN interface</help>
+ <help>Reset specified interface on L2TP VPN server</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py --interface="$5"</command>
+ <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="l2tp" --interface="$5"</command>
</tagNode>
<tagNode name="user">
<properties>
- <help>Terminate specified users current remote access VPN session(s)</help>
+ <help>Reset specified user on L2TP VPN server</help>
</properties>
- <children>
- <node name="protocol">
- <properties>
- <help>Terminate specified users current remote access VPN session(s) with specified protocol</help>
- </properties>
- <children>
- <leafNode name="l2tp">
- <properties>
- <help>Terminate all users current remote access VPN session(s) with L2TP protocol</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py --username="$5" --protocol="l2tp"</command>
- </leafNode>
- <leafNode name="pptp">
- <properties>
- <help>Terminate all users current remote access VPN session(s) with PPTP protocol</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py --username="$5" --protocol="pptp"</command>
- </leafNode>
- <leafNode name="sstp">
- <properties>
- <help>Terminate all users current remote access VPN session(s) with SSTP protocol</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py --username="$5" --protocol="sstp"</command>
- </leafNode>
- </children>
- </node>
- </children>
- <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py --username="$5"</command>
+ <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="l2tp" --username="$5"</command>
+ </tagNode>
+ </children>
+ </node>
+ <node name="pptp">
+ <properties>
+ <help>Reset PPTP server VPN sessions</help>
+ </properties>
+ <children>
+ <node name="all">
+ <properties>
+ <help>Reset all PPTP server VPN sessions</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="pptp"</command>
+ </node>
+ <tagNode name="interface">
+ <properties>
+ <help>Reset specified interface on PPTP VPN server</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="pptp" --interface="$5"</command>
+ </tagNode>
+ <tagNode name="user">
+ <properties>
+ <help>Reset specified user on PPTP VPN server</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="pptp" --username="$5"</command>
+ </tagNode>
+ </children>
+ </node>
+ <node name="sstp">
+ <properties>
+ <help>Reset SSTP server VPN sessions</help>
+ </properties>
+ <children>
+ <node name="all">
+ <properties>
+ <help>Reset all SSTP server VPN sessions</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="sstp"</command>
+ </node>
+ <tagNode name="interface">
+ <properties>
+ <help>Reset specified interface on SSTP VPN server</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="sstp" --interface="$5"</command>
+ </tagNode>
+ <tagNode name="user">
+ <properties>
+ <help>Reset specified user on SSTP VPN server</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="sstp" --username="$5"</command>
</tagNode>
</children>
</node>
diff --git a/op-mode-definitions/restart-frr.xml.in b/op-mode-definitions/restart-frr.xml.in
index 4e2be1bf2..4572858b5 100644
--- a/op-mode-definitions/restart-frr.xml.in
+++ b/op-mode-definitions/restart-frr.xml.in
@@ -68,6 +68,12 @@
</properties>
<command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon zebra</command>
</leafNode>
+ <leafNode name="babel">
+ <properties>
+ <help>Restart Babel routing daemon</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon babeld</command>
+ </leafNode>
</children>
</node>
</interfaceDefinition>
diff --git a/op-mode-definitions/sflow.xml.in b/op-mode-definitions/sflow.xml.in
new file mode 100644
index 000000000..9f02dacda
--- /dev/null
+++ b/op-mode-definitions/sflow.xml.in
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- sflow op mode commands -->
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="sflow">
+ <properties>
+ <help>Show sFlow statistics</help>
+ </properties>
+ <!-- requires sudo, do not remove it -->
+ <command>sudo ${vyos_op_scripts_dir}/sflow.py show</command>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/show-arp.xml.in b/op-mode-definitions/show-arp.xml.in
index 8662549fc..84170f070 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 --broadcast</script>
</completionHelp>
</properties>
<command>${vyos_op_scripts_dir}/neighbor.py show --family inet --interface "$4"</command>
diff --git a/op-mode-definitions/show-babel.xml.in b/op-mode-definitions/show-babel.xml.in
new file mode 100644
index 000000000..0a1f1b262
--- /dev/null
+++ b/op-mode-definitions/show-babel.xml.in
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="babel">
+ <properties>
+ <help>Show Babel routing protocol information</help>
+ </properties>
+ <children>
+ <leafNode name="interface">
+ <properties>
+ <help>Show Babel Interface information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ <leafNode name="neighbor">
+ <properties>
+ <help>Show Babel neighbor information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ <tagNode name="neighbor">
+ <properties>
+ <help>Show Babel neighbor information for specified interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </tagNode>
+ <leafNode name="route">
+ <properties>
+ <help>Show Babel route information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/show-bgp.xml.in b/op-mode-definitions/show-bgp.xml.in
index c33a9dacf..3c212614c 100644
--- a/op-mode-definitions/show-bgp.xml.in
+++ b/op-mode-definitions/show-bgp.xml.in
@@ -31,46 +31,14 @@
</leafNode>
</children>
</node>
- <node name="martian">
- <properties>
- <help>martian next-hops</help>
- </properties>
- <children>
- <leafNode name="next-hop">
- <properties>
- <help>martian next-hop database</help>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- </leafNode>
- </children>
- </node>
+ #include <include/bgp/martian-next-hop.xml.i>
<leafNode name="memory">
<properties>
<help>Global BGP memory statistics</help>
</properties>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</leafNode>
- <node name="nexthop">
- <properties>
- <help>Show BGP nexthop table</help>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- <children>
- #include <include/vtysh-generic-detail.xml.i>
- </children>
- </node>
- <tagNode name="nexthop">
- <properties>
- <help>IPv4/IPv6 nexthop address</help>
- <completionHelp>
- <list>&lt;x.x.x.x&gt; &lt;h:h:h:h:h:h:h:h&gt;</list>
- </completionHelp>
- </properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
- <children>
- #include <include/vtysh-generic-detail.xml.i>
- </children>
- </tagNode>
+ #include <include/bgp/next-hop.xml.i>
<leafNode name="statistics">
<properties>
<help>BGP RIB advertisement statistics</help>
@@ -83,6 +51,33 @@
</properties>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</leafNode>
+ #include <include/vni-tagnode-all.xml.i>
+ <tagNode name="vni">
+ <children>
+ <tagNode name="vtep">
+ <properties>
+ <help>Remote VTEP IP address</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </tagNode>
+ <node name="type">
+ <properties>
+ <help>Display number of prefixes for all afi/safi</help>
+ </properties>
+ <children>
+ #include <include/bgp/evpn-type-1.xml.i>
+ #include <include/bgp/evpn-type-2.xml.i>
+ #include <include/bgp/evpn-type-3.xml.i>
+ #include <include/bgp/evpn-type-ead.xml.i>
+ #include <include/bgp/evpn-type-macip.xml.i>
+ #include <include/bgp/evpn-type-multicast.xml.i>
+ </children>
+ </node>
+ </children>
+ </tagNode>
<leafNode name="vrf">
<properties>
<help>Show BGP VRF information</help>
@@ -100,6 +95,8 @@
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
<children>
#include <include/bgp/show-bgp-common.xml.i>
+ #include <include/bgp/martian-next-hop.xml.i>
+ #include <include/bgp/next-hop.xml.i>
</children>
</tagNode>
#include <include/vtysh-generic-wide.xml.i>
diff --git a/op-mode-definitions/show-bridge.xml.in b/op-mode-definitions/show-bridge.xml.in
index dd2a28931..acf3a00c7 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>
+ <script>${vyos_completion_dir}/list_interfaces --type bridge</script>
</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
index e66d3a0ac..a619a9fd2 100644
--- a/op-mode-definitions/show-interfaces-sstpc.xml.in
+++ b/op-mode-definitions/show-interfaces-sstpc.xml.in
@@ -11,7 +11,7 @@
<path>interfaces sstpc</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=sstpc</command>
<children>
<leafNode name="log">
<properties>
@@ -34,13 +34,13 @@
<properties>
<help>Show SSTP client interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=sstpc --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=sstpc</command>
<children>
<leafNode name="detail">
<properties>
<help>Show detailed SSTP client interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=sstpc --action=show</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=sstpc</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/show-interfaces-tunnel.xml.in b/op-mode-definitions/show-interfaces-tunnel.xml.in
index 51b25efd9..10e10e655 100644
--- a/op-mode-definitions/show-interfaces-tunnel.xml.in
+++ b/op-mode-definitions/show-interfaces-tunnel.xml.in
@@ -11,13 +11,13 @@
<path>interfaces tunnel</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=tunnel</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified tunnel interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=tunnel</command>
</leafNode>
</children>
</tagNode>
@@ -25,13 +25,13 @@
<properties>
<help>Show Tunnel interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=tunnel --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=tunnel</command>
<children>
<leafNode name="detail">
<properties>
<help>Show detailed tunnel interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=tunnel --action=show</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=tunnel</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/show-interfaces-virtual-ethernet.xml.in b/op-mode-definitions/show-interfaces-virtual-ethernet.xml.in
index c70f1e3d1..c743492fb 100644
--- a/op-mode-definitions/show-interfaces-virtual-ethernet.xml.in
+++ b/op-mode-definitions/show-interfaces-virtual-ethernet.xml.in
@@ -11,13 +11,13 @@
<path>interfaces virtual-ethernet</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=virtual-ethernet</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified virtual-ethernet interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=virtual-ethernet</command>
</leafNode>
</children>
</tagNode>
@@ -25,13 +25,13 @@
<properties>
<help>Show virtual-ethernet interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=virtual-ethernet --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=virtual-ethernet</command>
<children>
<leafNode name="detail">
<properties>
<help>Show detailed virtual-ethernet interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=virtual-ethernet --action=show</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=virtual-ethernet</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/show-interfaces-vti.xml.in b/op-mode-definitions/show-interfaces-vti.xml.in
index b436b8414..d532894b7 100644
--- a/op-mode-definitions/show-interfaces-vti.xml.in
+++ b/op-mode-definitions/show-interfaces-vti.xml.in
@@ -11,13 +11,13 @@
<path>interfaces vti</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=vti</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified vti interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=vti</command>
</leafNode>
</children>
</tagNode>
@@ -25,13 +25,13 @@
<properties>
<help>Show VTI interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=vti --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=vti</command>
<children>
<leafNode name="detail">
<properties>
<help>Show detailed vti interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=vti --action=show</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=vti</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/show-interfaces-vxlan.xml.in b/op-mode-definitions/show-interfaces-vxlan.xml.in
index 1befd428c..fde832551 100644
--- a/op-mode-definitions/show-interfaces-vxlan.xml.in
+++ b/op-mode-definitions/show-interfaces-vxlan.xml.in
@@ -11,13 +11,13 @@
<path>interfaces vxlan</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=vxlan</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified VXLAN interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=vxlan</command>
</leafNode>
</children>
</tagNode>
@@ -25,13 +25,13 @@
<properties>
<help>Show VXLAN interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=vxlan --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=vxlan</command>
<children>
<leafNode name="detail">
<properties>
<help>Show detailed VXLAN interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=vxlan --action=show</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=vxlan</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/show-interfaces-wireguard.xml.in b/op-mode-definitions/show-interfaces-wireguard.xml.in
index c9b754dcd..d045beafc 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>
+ <script>${vyos_completion_dir}/list_interfaces --type wireguard</script>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=wireguard</command>
<children>
<leafNode name="allowed-ips">
<properties>
@@ -49,13 +49,13 @@
<properties>
<help>Show WireGuard interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireguard --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=wireguard</command>
<children>
<leafNode name="detail">
<properties>
<help>Show detailed Wireguard interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireguard --action=show</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=wireguard</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/show-interfaces-wireless.xml.in b/op-mode-definitions/show-interfaces-wireless.xml.in
index 4a37417aa..f39d402f1 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>
+ <script>${vyos_completion_dir}/list_interfaces --type wireless</script>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=wireless</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified wireless interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4" --intf_type=wireless</command>
</leafNode>
<node name="scan">
<properties>
@@ -63,13 +63,13 @@
<properties>
<help>Show specified virtual network interface (vif) information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6"</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4.$6" --intf_type=wireless</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of specified virtual network interface (vif) information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_name="$4.$6" --intf_type=wireless</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-interfaces-wwan.xml.in b/op-mode-definitions/show-interfaces-wwan.xml.in
index 3cd29b38a..17d4111a9 100644
--- a/op-mode-definitions/show-interfaces-wwan.xml.in
+++ b/op-mode-definitions/show-interfaces-wwan.xml.in
@@ -12,7 +12,7 @@
<script>cd /sys/class/net; ls -d wwan*</script>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_name="$4" --intf_type=wirelessmodem</command>
<children>
<leafNode name="capabilities">
<properties>
@@ -86,13 +86,13 @@
<properties>
<help>Show Wireless Modem (WWAN) interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wirelessmodem --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf_type=wirelessmodem</command>
<children>
<leafNode name="detail">
<properties>
<help>Show detailed Wireless Modem (WWAN( interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wirelessmodem --action=show</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf_type=wirelessmodem</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/show-interfaces.xml.in b/op-mode-definitions/show-interfaces.xml.in
index 39b0f0a2c..dc61a6f5c 100644
--- a/op-mode-definitions/show-interfaces.xml.in
+++ b/op-mode-definitions/show-interfaces.xml.in
@@ -6,19 +6,19 @@
<properties>
<help>Show network interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary</command>
<children>
<leafNode name="counters">
<properties>
<help>Show network interface counters</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --action=show-count</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_counters</command>
</leafNode>
<leafNode name="detail">
<properties>
<help>Show detailed information of all interfaces</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --action=show</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/show-ip-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..c878bf712 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,9 +90,25 @@
#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>
+ <node name="node.tag">
+ <properties>
+ <help>Show IP routes of specified IP address or prefix</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x&gt; &lt;x.x.x.x/x&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ <leafNode name="longer-prefixes">
+ <properties>
+ <help>Show longer prefixes of routes for specified prefix</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ </children>
+ </node>
</children>
</tagNode>
</children>
diff --git a/op-mode-definitions/show-ip.xml.in b/op-mode-definitions/show-ip.xml.in
index 0751c50cb..d5dbb7850 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 --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 64a54015b..7663e4c00 100644
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -8,6 +8,12 @@
</properties>
<command>journalctl --no-hostname --boot</command>
<children>
+ <leafNode name="audit">
+ <properties>
+ <help>Show audit logs</help>
+ </properties>
+ <command>cat /var/log/audit/audit.log</command>
+ </leafNode>
<leafNode name="all">
<properties>
<help>Show contents of all master log files</help>
@@ -66,7 +72,7 @@
<properties>
<help>Show DHCP client log on specific interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
+ <script>${vyos_completion_dir}/list_interfaces --broadcast</script>
</completionHelp>
</properties>
<command>journalctl --no-hostname --boot --unit "dhclient@$6.service"</command>
@@ -96,7 +102,7 @@
<properties>
<help>Show DHCPv6 client log on specific interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<command>journalctl --no-hostname --boot --unit "dhcp6c@$6.service"</command>
@@ -196,6 +202,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 +216,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 +228,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 +248,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 +274,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 +282,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@$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 +368,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>
@@ -366,7 +396,7 @@
<properties>
<help>Show SSTP client log on specific interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py -t sstpc</script>
+ <path>interfaces sstpc</path>
</completionHelp>
</properties>
<command>journalctl --no-hostname --boot --unit "ppp@$5.service"</command>
@@ -397,13 +427,13 @@
<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.service --unit accel-ppp@*.service --unit ocserv.service</command>
</leafNode>
<leafNode name="ipsec">
<properties>
<help>Show log for IPsec</help>
</properties>
- <command>journalctl --no-hostname --boot --unit strongswan-starter.service</command>
+ <command>journalctl --no-hostname --boot --unit strongswan.service</command>
</leafNode>
<leafNode name="l2tp">
<properties>
@@ -411,6 +441,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..8f98f3a4c 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 --broadcast</script>
</completionHelp>
</properties>
<command>/usr/sbin/arp -e -n -i "$6"</command>
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/show-techsupport_report.xml.in b/op-mode-definitions/show-techsupport_report.xml.in
new file mode 100644
index 000000000..aa51eacd9
--- /dev/null
+++ b/op-mode-definitions/show-techsupport_report.xml.in
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="tech-support">
+ <children>
+ <node name="report">
+ <properties>
+ <help>Show consolidated tech-support report (contains private information)</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_techsupport_report.py</command>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/snmp.xml.in b/op-mode-definitions/snmp.xml.in
index a0a47da40..894005e7b 100644
--- a/op-mode-definitions/snmp.xml.in
+++ b/op-mode-definitions/snmp.xml.in
@@ -39,7 +39,7 @@
<properties>
<help>Show SNMP ifAlias for specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<command>${vyos_op_scripts_dir}/snmp_ifmib.py --ifalias="$6"</command>
@@ -48,7 +48,7 @@
<properties>
<help>Show SNMP ifDescr for specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<command>${vyos_op_scripts_dir}/snmp_ifmib.py --ifdescr="$6"</command>
@@ -57,7 +57,7 @@
<properties>
<help>Show SNMP ifDescr for specified interface</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<command>${vyos_op_scripts_dir}/snmp_ifmib.py --ifindex="$6"</command>
diff --git a/op-mode-definitions/vpn-ipsec.xml.in b/op-mode-definitions/vpn-ipsec.xml.in
index 803ce4cc2..1eb5a3709 100644
--- a/op-mode-definitions/vpn-ipsec.xml.in
+++ b/op-mode-definitions/vpn-ipsec.xml.in
@@ -7,57 +7,112 @@
<help>Reset Virtual Private Network (VPN) information</help>
</properties>
<children>
- <tagNode name="ipsec-peer">
+ <node name="ipsec">
<properties>
- <help>Reset all tunnels for given peer</help>
- <completionHelp>
- <path>vpn ipsec site-to-site peer</path>
- </completionHelp>
+ <help>Reset IPSec VPN sessions</help>
</properties>
<children>
- <tagNode name="tunnel">
+ <tagNode name="profile">
<properties>
- <help>Reset a specific tunnel for given peer</help>
+ <help>Reset a specific tunnel for given DMVPN profile</help>
+ <completionHelp>
+ <path>vpn ipsec profile</path>
+ </completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_peer --peer="$4" --tunnel="$6"</command>
+ <children>
+ <tagNode name="tunnel">
+ <properties>
+ <help>Reset a specific tunnel for given DMVPN profile</help>
+ <completionHelp>
+ <script>sudo ${vyos_completion_dir}/list_ipsec_profile_tunnels.py --profile ${COMP_WORDS[4]}</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="remote-host">
+ <properties>
+ <help>Reset a specific tunnel for given DMVPN NBMA</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x&gt; &lt;h:h:h:h:h:h:h:h&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_profile_dst --profile="$5" --tunnel="$7" --nbma_dst="$9"</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_profile_all --profile="$5" --tunnel="$7"</command>
+ </tagNode>
+ </children>
</tagNode>
- <node name="vti">
+ <node name="remote-access">
<properties>
- <help>Reset the VTI tunnel for given peer</help>
+ <help>Reset remote access IPSec VPN connections</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_peer --peer="$4" --tunnel="vti"</command>
+ <children>
+ <node name="all">
+ <properties>
+ <help>Reset all users current remote access IPSec VPN sessions</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_ra</command>
+ </node>
+ <tagNode name="user">
+ <properties>
+ <help>Reset specified user current remote access IPsec VPN session(s)</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_ra --user="$6"</command>
+ </tagNode>
+ </children>
</node>
- </children>
- <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_peer --peer="$4" --tunnel="all"</command>
- </tagNode>
- <tagNode name="ipsec-profile">
- <properties>
- <help>Reset all tunnels for given DMVPN profile</help>
- <completionHelp>
- <path>vpn ipsec profile</path>
- </completionHelp>
- </properties>
- <children>
- <tagNode name="tunnel">
+ <node name="site-to-site">
<properties>
- <help>Reset a specific tunnel for given DMVPN profile</help>
+ <help>Reset site-to-site IPSec VPN connections</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/vpn_ipsec.py --action="reset-profile" --name="$4" --tunnel="$6"</command>
- </tagNode>
+ <children>
+ <node name="all">
+ <properties>
+ <help>Reset all site-to-site IPSec VPN sessions</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_all_peers</command>
+ </node>
+ <tagNode name="peer">
+ <properties>
+ <help>Reset all tunnels for given peer</help>
+ <completionHelp>
+ <path>vpn ipsec site-to-site peer</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="tunnel">
+ <properties>
+ <help>Reset a specific tunnel for given peer</help>
+ <completionHelp>
+ <path>vpn ipsec site-to-site peer ${COMP_WORDS[5]} tunnel</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_peer --peer="$6" --tunnel="$8"</command>
+ </tagNode>
+ <node name="vti">
+ <properties>
+ <help>Reset the VTI tunnel for given peer</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_peer --peer="$6" --tunnel="vti"</command>
+ </node>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_peer --peer="$6"</command>
+ </tagNode>
+ </children>
+ </node>
</children>
- <command>sudo ${vyos_op_scripts_dir}/vpn_ipsec.py --action="reset-profile" --name="$4" --tunnel="all"</command>
- </tagNode>
+ </node>
</children>
</node>
</children>
</node>
<node name="restart">
<children>
- <node name="vpn">
+ <node name="ipsec">
<properties>
<help>Restart the IPsec VPN process</help>
</properties>
- <command>if pgrep charon >/dev/null ; then sudo ipsec restart ; sleep 3 ; sudo swanctl -q ; else echo "IPsec process not running" ; fi</command>
+ <command>if systemctl is-active --quiet strongswan; then sudo systemctl restart strongswan ; echo "IPsec process restarted"; else echo "IPsec process not running" ; fi</command>
</node>
</children>
</node>
@@ -128,7 +183,7 @@
<properties>
<help>Show summary of IKE process information</help>
</properties>
- <command>if pgrep charon >/dev/null ; then echo "Running: $(pgrep charon)" ; else echo "Process is not running" ; fi</command>
+ <command>if systemctl is-active --quiet strongswan ; then systemctl status strongswan ; else echo "Process is not running" ; fi</command>
</node>
</children>
</node>
@@ -149,12 +204,37 @@
</properties>
<command>sudo ip xfrm policy list</command>
</node>
- <leafNode name="remote-access">
- <properties>
- <help>Show active VPN server sessions</help>
- </properties>
- <command>${vyos_op_scripts_dir}/show_vpn_ra.py</command>
- </leafNode>
+ <node name="remote-access">
+ <properties>
+ <help>Show active VPN server sessions</help>
+ </properties>
+ <children>
+ <node name="detail">
+ <properties>
+ <help>Show detail active IKEv2 RA sessions</help>
+ </properties>
+ <command>if systemctl is-active --quiet strongswan ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_ra_detail; else echo "IPsec process not running" ; fi</command>
+ </node>
+ <tagNode name="connection-id">
+ <properties>
+ <help>Show detail active IKEv2 RA sessions by connection-id</help>
+ </properties>
+ <command>if systemctl is-active --quiet strongswan ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_ra_detail --conn_id="$6"; else echo "IPsec process not running" ; fi</command>
+ </tagNode>
+ <node name="summary">
+ <properties>
+ <help>Show active IKEv2 RA sessions summary</help>
+ </properties>
+ <command>if systemctl is-active --quiet strongswan ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_ra_summary; else echo "IPsec process not running" ; fi</command>
+ </node>
+ <tagNode name="username">
+ <properties>
+ <help>Show detail active IKEv2 RA sessions by username</help>
+ </properties>
+ <command>if systemctl is-active --quiet strongswan ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_ra_detail --username="$6"; else echo "IPsec process not running" ; fi</command>
+ </tagNode>
+ </children>
+ </node>
<node name="sa">
<properties>
<help>Show all active IPsec Security Associations (SA)</help>
@@ -186,14 +266,14 @@
<command></command>
</tagNode>
-->
- <node name="verbose">
+ <node name="detail">
<properties>
<help>Show Verbose Detail on all active IPsec Security Associations (SA)</help>
</properties>
- <command>if pgrep charon >/dev/null ; then sudo /usr/sbin/ipsec statusall ; else echo "IPsec process not running" ; fi</command>
+ <command>if systemctl is-active --quiet strongswan ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_sa_detail ; else echo "IPsec process not running" ; fi</command>
</node>
</children>
- <command>if pgrep charon >/dev/null ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_sa ; else echo "IPsec process not running" ; fi</command>
+ <command>if systemctl is-active --quiet strongswan ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_sa ; else echo "IPsec process not running" ; fi</command>
</node>
<node name="state">
<properties>
@@ -205,7 +285,7 @@
<properties>
<help>Show status of IPsec process</help>
</properties>
- <command>if pgrep charon >/dev/null ; then echo -e "IPsec Process Running: $(pgrep charon)\n$(sudo /usr/sbin/ipsec status)" ; else echo "IPsec process not running" ; fi</command>
+ <command>if systemctl is-active --quiet strongswan >/dev/null ; then echo -e "IPsec Process Running: $(pgrep charon)\n$(sudo /usr/sbin/ipsec status)" ; else echo "IPsec process not running" ; fi</command>
</node>
</children>
</node>
diff --git a/op-mode-definitions/wake-on-lan.xml.in b/op-mode-definitions/wake-on-lan.xml.in
index 1a9b88596..7119eeb65 100644
--- a/op-mode-definitions/wake-on-lan.xml.in
+++ b/op-mode-definitions/wake-on-lan.xml.in
@@ -9,7 +9,7 @@
<properties>
<help>Interface where the station is connected</help>
<completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
+ <script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
diff --git a/op-mode-definitions/wireless.xml.in b/op-mode-definitions/wireless.xml.in
deleted file mode 100644
index 5d9db1544..000000000
--- a/op-mode-definitions/wireless.xml.in
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="clear">
- <children>
- <node name="interfaces">
- <children>
- <node name="wireless">
- <properties>
- <help>Clear wireless interface information</help>
- </properties>
- <children>
- <leafNode name="counters">
- <properties>
- <help>Clear all wireless interface counters</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/show_interfaces.py --action=clear --intf-type="$3"</command>
- </leafNode>
- </children>
- </node>
- <tagNode name="wireless">
- <properties>
- <help>Clear interface information for a given wireless interface</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py --type wireless</script>
- </completionHelp>
- </properties>
- <children>
- <leafNode name="counters">
- <properties>
- <help>Clear all wireless interface counters</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/show_interfaces.py --action=clear --intf="$4"</command>
- </leafNode>
- </children>
- </tagNode>
- </children>
- </node>
- </children>
- </node>
-</interfaceDefinition>
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
index bfc8ee5a9..0af311e57 100644
--- a/python/vyos/accel_ppp.py
+++ b/python/vyos/accel_ppp.py
@@ -38,6 +38,9 @@ def get_server_statistics(accel_statistics, pattern, sep=':') -> dict:
if key in ['starting', 'active', 'finishing']:
stat_dict['sessions'][key] = value.strip()
continue
+ if key == 'cpu':
+ stat_dict['cpu_load_percentage'] = int(re.sub(r'%', '', value.strip()))
+ continue
stat_dict[key] = value.strip()
return stat_dict
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py
new file mode 100644
index 000000000..fade3081c
--- /dev/null
+++ b/python/vyos/config_mgmt.py
@@ -0,0 +1,669 @@
+# 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, ConfigTreeError, show_diff
+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', '')
+ self.edit_path = [l for l in edit_level.split('/') if l]
+
+ 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.
+ """
+ ct1 = self.active_config
+ 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'
+
+ out = ''
+ path = [] if commands else self.edit_path
+ try:
+ if commands:
+ out = show_diff(ct1, ct2, path=path, commands=True)
+ else:
+ out = show_diff(ct1, ct2, path=path)
+ except ConfigTreeError as e:
+ return e, 1
+
+ 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/configdict.py b/python/vyos/configdict.py
index 53decfbf5..6ab5c252c 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -333,8 +333,9 @@ def get_dhcp_interfaces(conf, vrf=None):
if dict_search('dhcp_options.default_route_distance', config) != None:
options.update({'dhcp_options' : config['dhcp_options']})
if 'vrf' in config:
- if vrf is config['vrf']: tmp.update({ifname : options})
- else: tmp.update({ifname : options})
+ if vrf == config['vrf']: tmp.update({ifname : options})
+ else:
+ if vrf is None: tmp.update({ifname : options})
return tmp
@@ -382,8 +383,9 @@ def get_pppoe_interfaces(conf, vrf=None):
if 'no_default_route' in ifconfig:
options.update({'no_default_route' : {}})
if 'vrf' in ifconfig:
- if vrf is ifconfig['vrf']: pppoe_interfaces.update({ifname : options})
- else: pppoe_interfaces.update({ifname : options})
+ if vrf == ifconfig['vrf']: pppoe_interfaces.update({ifname : options})
+ else:
+ if vrf is None: pppoe_interfaces.update({ifname : options})
return pppoe_interfaces
@@ -427,6 +429,10 @@ def get_interface_dict(config, base, ifname=''):
# Add interface instance name into dictionary
dict.update({'ifname': ifname})
+ # Check if QoS policy applied on this interface - See ifconfig.interface.set_mirror_redirect()
+ if config.exists(['qos', 'interface', ifname]):
+ dict.update({'traffic_policy': {}})
+
# XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely
# remove the default values from the dict.
if 'dhcpv6_options' not in dict:
@@ -498,6 +504,9 @@ def get_interface_dict(config, base, ifname=''):
# Add subinterface name to dictionary
dict['vif'][vif].update({'ifname' : f'{ifname}.{vif}'})
+ if config.exists(['qos', 'interface', f'{ifname}.{vif}']):
+ dict['vif'][vif].update({'traffic_policy': {}})
+
default_vif_values = defaults(base + ['vif'])
# XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely
# remove the default values from the dict.
@@ -532,6 +541,9 @@ def get_interface_dict(config, base, ifname=''):
# Add subinterface name to dictionary
dict['vif_s'][vif_s].update({'ifname' : f'{ifname}.{vif_s}'})
+ if config.exists(['qos', 'interface', f'{ifname}.{vif_s}']):
+ dict['vif_s'][vif_s].update({'traffic_policy': {}})
+
default_vif_s_values = defaults(base + ['vif-s'])
# XXX: T2665: we only wan't the vif-s defaults - do not care about vif-c
if 'vif_c' in default_vif_s_values: del default_vif_s_values['vif_c']
@@ -571,6 +583,9 @@ def get_interface_dict(config, base, ifname=''):
# Add subinterface name to dictionary
dict['vif_s'][vif_s]['vif_c'][vif_c].update({'ifname' : f'{ifname}.{vif_s}.{vif_c}'})
+ if config.exists(['qos', 'interface', f'{ifname}.{vif_s}.{vif_c}']):
+ dict['vif_s'][vif_s]['vif_c'][vif_c].update({'traffic_policy': {}})
+
default_vif_c_values = defaults(base + ['vif-s', 'vif-c'])
# XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely
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/configquery.py b/python/vyos/configquery.py
index 5b097b312..85fef8777 100644
--- a/python/vyos/configquery.py
+++ b/python/vyos/configquery.py
@@ -88,7 +88,7 @@ class ConfigTreeQuery(GenericConfigQuery):
with open(config_file) as f:
config_string = f.read()
except OSError as err:
- raise ConfigQueryError('No config file available') from err
+ config_string = ''
config_source = ConfigSourceString(running_config_text=config_string,
session_config_text=config_string)
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 b88615513..9308bdde4 100644
--- a/python/vyos/configtree.py
+++ b/python/vyos/configtree.py
@@ -16,7 +16,7 @@ import os
import re
import json
-from ctypes import cdll, c_char_p, c_void_p, c_int
+from ctypes import cdll, c_char_p, c_void_p, c_int, c_bool
LIBPATH = '/usr/lib/libvyosconfig.so.0'
@@ -60,7 +60,7 @@ class ConfigTree(object):
self.__get_error.restype = c_char_p
self.__to_string = self.__lib.to_string
- self.__to_string.argtypes = [c_void_p]
+ self.__to_string.argtypes = [c_void_p, c_bool]
self.__to_string.restype = c_char_p
self.__to_commands = self.__lib.to_commands
@@ -160,8 +160,8 @@ class ConfigTree(object):
def _get_config(self):
return self.__config
- def to_string(self):
- config_string = self.__to_string(self.__config).decode()
+ def to_string(self, ordered_values=False):
+ config_string = self.__to_string(self.__config, ordered_values).decode()
config_string = "{0}\n{1}".format(config_string, self.__version)
return config_string
@@ -242,7 +242,8 @@ 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}")
@@ -321,6 +322,57 @@ class ConfigTree(object):
subt = ConfigTree(address=res)
return subt
+def show_diff(left, right, path=[], commands=False, libpath=LIBPATH):
+ if left is None:
+ left = ConfigTree(config_string='\n')
+ if right is None:
+ right = ConfigTree(config_string='\n')
+ if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)):
+ raise TypeError("Arguments must be instances of ConfigTree")
+ if path:
+ if (not left.exists(path)) and (not right.exists(path)):
+ raise ConfigTreeError(f"Path {path} doesn't exist")
+
+ check_path(path)
+ path_str = " ".join(map(str, path)).encode()
+
+ __lib = cdll.LoadLibrary(libpath)
+ __show_diff = __lib.show_diff
+ __show_diff.argtypes = [c_bool, c_char_p, c_void_p, c_void_p]
+ __show_diff.restype = c_char_p
+ __get_error = __lib.get_error
+ __get_error.argtypes = []
+ __get_error.restype = c_char_p
+
+ res = __show_diff(commands, path_str, left._get_config(), right._get_config())
+ res = res.decode()
+ if res == "#1@":
+ msg = __get_error().decode()
+ raise ConfigTreeError(msg)
+
+ return res
+
+def union(left, right, libpath=LIBPATH):
+ if left is None:
+ left = ConfigTree(config_string='\n')
+ if right is None:
+ right = ConfigTree(config_string='\n')
+ if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)):
+ raise TypeError("Arguments must be instances of ConfigTree")
+
+ __lib = cdll.LoadLibrary(libpath)
+ __tree_union = __lib.tree_union
+ __tree_union.argtypes = [c_void_p, c_void_p]
+ __tree_union.restype = c_void_p
+ __get_error = __lib.get_error
+ __get_error.argtypes = []
+ __get_error.restype = c_char_p
+
+ res = __tree_union( left._get_config(), right._get_config())
+ tree = ConfigTree(address=res)
+
+ return tree
+
class DiffTree:
def __init__(self, left, right, path=[], libpath=LIBPATH):
if left is None:
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 8e0ce701e..8fddd91d0 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -1,4 +1,4 @@
-# Copyright 2020-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2020-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
@@ -23,6 +23,7 @@
from vyos import ConfigError
from vyos.util import dict_search
+from vyos.util import dict_search_recursive
def verify_mtu(config):
"""
@@ -35,8 +36,14 @@ def verify_mtu(config):
mtu = int(config['mtu'])
tmp = Interface(config['ifname'])
- min_mtu = tmp.get_min_mtu()
- max_mtu = tmp.get_max_mtu()
+ # Not all interfaces support min/max MTU
+ # https://vyos.dev/T5011
+ try:
+ min_mtu = tmp.get_min_mtu()
+ max_mtu = tmp.get_max_mtu()
+ except: # Fallback to defaults
+ min_mtu = 68
+ max_mtu = 9000
if mtu < min_mtu:
raise ConfigError(f'Interface MTU too low, ' \
@@ -232,7 +239,7 @@ def verify_authentication(config):
"""
if 'authentication' not in config:
return
- if not {'user', 'password'} <= set(config['authentication']):
+ if not {'username', 'password'} <= set(config['authentication']):
raise ConfigError('Authentication requires both username and ' \
'password to be set!')
@@ -414,7 +421,18 @@ def verify_accel_ppp_base_service(config, local_users=True):
if 'key' not in radius_config:
raise ConfigError(f'Missing RADIUS secret key for server "{server}"')
- if 'gateway_address' not in config:
+ # Check global gateway or gateway in named pool
+ gateway = False
+ if 'gateway_address' in config:
+ gateway = True
+ else:
+ if 'client_ip_pool' in config:
+ if dict_search_recursive(config, 'gateway_address', ['client_ip_pool', 'name']):
+ for _, v in config['client_ip_pool']['name'].items():
+ if 'gateway_address' in v:
+ gateway = True
+ break
+ if not gateway:
raise ConfigError('Server requires gateway-address to be configured!')
if 'name_server_ipv4' in config:
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/defaults.py b/python/vyos/defaults.py
index 7de458960..d4ffc249e 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -1,4 +1,4 @@
-# Copyright 2018 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2018-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
@@ -15,19 +15,24 @@
import os
+base_dir = '/usr/libexec/vyos/'
+
directories = {
- "data": "/usr/share/vyos/",
- "conf_mode": "/usr/libexec/vyos/conf_mode",
- "op_mode": "/usr/libexec/vyos/op_mode",
- "config": "/opt/vyatta/etc/config",
- "current": "/opt/vyatta/etc/config-migrate/current",
- "migrate": "/opt/vyatta/etc/config-migrate/migrate",
- "log": "/var/log/vyatta",
- "templates": "/usr/share/vyos/templates/",
- "certbot": "/config/auth/letsencrypt",
- "api_schema": "/usr/libexec/vyos/services/api/graphql/graphql/schema/",
- "api_templates": "/usr/libexec/vyos/services/api/graphql/session/templates/",
- "vyos_udev_dir": "/run/udev/vyos"
+ 'base' : base_dir,
+ 'data' : '/usr/share/vyos/',
+ 'conf_mode' : f'{base_dir}/conf_mode',
+ 'op_mode' : f'{base_dir}/op_mode',
+ 'services' : f'{base_dir}/services',
+ 'config' : '/opt/vyatta/etc/config',
+ 'current' : '/opt/vyatta/etc/config-migrate/current',
+ 'migrate' : '/opt/vyatta/etc/config-migrate/migrate',
+ 'log' : '/var/log/vyatta',
+ 'templates' : '/usr/share/vyos/templates/',
+ 'certbot' : '/config/auth/letsencrypt',
+ 'api_schema': f'{base_dir}/services/api/graphql/graphql/schema/',
+ 'api_client_op': f'{base_dir}/services/api/graphql/graphql/client_op/',
+ 'api_templates': f'{base_dir}/services/api/graphql/session/templates/',
+ 'vyos_udev_dir' : '/run/udev/vyos'
}
config_status = '/tmp/vyos-config-status'
@@ -50,12 +55,12 @@ api_data = {
'socket' : False,
'strict' : False,
'debug' : False,
- 'api_keys' : [ {"id": "testapp", "key": "qwerty"} ]
+ 'api_keys' : [ {'id' : 'testapp', 'key' : 'qwerty'} ]
}
vyos_cert_data = {
- "conf": "/etc/nginx/snippets/vyos-cert.conf",
- "crt": "/etc/ssl/certs/vyos-selfsigned.crt",
- "key": "/etc/ssl/private/vyos-selfsign",
- "lifetime": "365",
+ 'conf' : '/etc/nginx/snippets/vyos-cert.conf',
+ 'crt' : '/etc/ssl/certs/vyos-selfsigned.crt',
+ 'key' : '/etc/ssl/private/vyos-selfsign',
+ 'lifetime' : '365',
}
diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py
index 2b6012a73..bc3402059 100644
--- a/python/vyos/ethtool.py
+++ b/python/vyos/ethtool.py
@@ -1,4 +1,4 @@
-# Copyright 2021-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2021-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
@@ -21,7 +21,7 @@ from vyos.util import popen
# These drivers do not support using ethtool to change the speed, duplex, or
# flow control settings
_drivers_without_speed_duplex_flow = ['vmxnet3', 'virtio_net', 'xen_netfront',
- 'iavf', 'ice', 'i40e', 'hv_netvsc']
+ 'iavf', 'ice', 'i40e', 'hv_netvsc', 'veth']
class Ethtool:
"""
@@ -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 b4b9e67bb..919032a41 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -223,10 +223,23 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
action = rule_conf['action'] if 'action' in rule_conf else 'accept'
output.append(f'log prefix "[{fw_name[:19]}-{rule_id}-{action[:1].upper()}]"')
- if 'log_level' in rule_conf:
- log_level = rule_conf['log_level']
- output.append(f'level {log_level}')
+ if 'log_options' in rule_conf:
+ if 'level' in rule_conf['log_options']:
+ log_level = rule_conf['log_options']['level']
+ output.append(f'log level {log_level}')
+
+ if 'group' in rule_conf['log_options']:
+ log_group = rule_conf['log_options']['group']
+ output.append(f'log group {log_group}')
+
+ if 'queue_threshold' in rule_conf['log_options']:
+ queue_threshold = rule_conf['log_options']['queue_threshold']
+ output.append(f'queue-threshold {queue_threshold}')
+
+ if 'snapshot_length' in rule_conf['log_options']:
+ log_snaplen = rule_conf['log_options']['snapshot_length']
+ output.append(f'snaplen {log_snaplen}')
if 'hop_limit' in rule_conf:
operators = {'eq': '==', 'gt': '>', 'lt': '<'}
@@ -277,6 +290,9 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
negated_lengths_str = ','.join(rule_conf['packet_length_exclude'])
output.append(f'ip{def_suffix} length != {{{negated_lengths_str}}}')
+ if 'packet_type' in rule_conf:
+ output.append(f'pkttype ' + rule_conf['packet_type'])
+
if 'dscp' in rule_conf:
dscp_str = ','.join(rule_conf['dscp'])
output.append(f'ip{def_suffix} dscp {{{dscp_str}}}')
@@ -337,6 +353,15 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
target = rule_conf['jump_target']
output.append(f'NAME{def_suffix}_{target}')
+ if 'queue' in rule_conf['action']:
+ if 'queue' in rule_conf:
+ target = rule_conf['queue']
+ output.append(f'num {target}')
+
+ if 'queue_options' in rule_conf:
+ queue_opts = ','.join(rule_conf['queue_options'])
+ output.append(f'{queue_opts}')
+
else:
output.append('return')
diff --git a/python/vyos/frr.py b/python/vyos/frr.py
index ccb132dd5..a84f183ef 100644
--- a/python/vyos/frr.py
+++ b/python/vyos/frr.py
@@ -85,7 +85,7 @@ LOG.addHandler(ch2)
_frr_daemons = ['zebra', 'bgpd', 'fabricd', 'isisd', 'ospf6d', 'ospfd', 'pbrd',
'pimd', 'ripd', 'ripngd', 'sharpd', 'staticd', 'vrrpd', 'ldpd',
- 'bfdd', 'eigrpd']
+ 'bfdd', 'eigrpd', 'babeld']
path_vtysh = '/usr/bin/vtysh'
path_frr_reload = '/usr/lib/frr/frr-reload.py'
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/interface.py b/python/vyos/ifconfig/interface.py
index c50ead89f..f62b9f7d2 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -751,8 +751,8 @@ class Interface(Control):
elif all_rp_filter == 2: global_setting = 'loose'
from vyos.base import Warning
- Warning(f'Global source-validation is set to "{global_setting} '\
- f'this overrides per interface setting!')
+ Warning(f'Global source-validation is set to "{global_setting}", this '\
+ f'overrides per interface setting on "{self.ifname}"!')
tmp = self.get_interface('rp_filter')
if int(tmp) == value:
@@ -1365,7 +1365,7 @@ class Interface(Control):
if not isinstance(state, bool):
raise ValueError("Value out of range")
- # https://phabricator.vyos.net/T3448 - there is (yet) no RPI support for XDP
+ # https://vyos.dev/T3448 - there is (yet) no RPI support for XDP
if not os.path.exists('/usr/sbin/xdp_loader'):
return
@@ -1709,6 +1709,14 @@ class VLANIf(Interface):
if self.exists(f'{self.ifname}'):
return
+ # If source_interface or vlan_id was not explicitly defined (e.g. when
+ # calling VLANIf('eth0.1').remove() we can define source_interface and
+ # vlan_id here, as it's quiet obvious that it would be eth0 in that case.
+ if 'source_interface' not in self.config:
+ self.config['source_interface'] = '.'.join(self.ifname.split('.')[:-1])
+ if 'vlan_id' not in self.config:
+ self.config['vlan_id'] = self.ifname.split('.')[-1]
+
cmd = 'ip link add link {source_interface} name {ifname} type vlan id {vlan_id}'
if 'protocol' in self.config:
cmd += ' protocol {protocol}'
diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py
index b3babfadc..e1d041839 100644
--- a/python/vyos/ifconfig/loopback.py
+++ b/python/vyos/ifconfig/loopback.py
@@ -46,7 +46,7 @@ class LoopbackIf(Interface):
if addr in self._persistent_addresses:
# Do not allow deletion of the default loopback addresses as
# this will cause weird system behavior like snmp/ssh no longer
- # operating as expected, see https://phabricator.vyos.net/T2034.
+ # operating as expected, see https://vyos.dev/T2034.
continue
self.del_addr(addr)
diff --git a/python/vyos/ifconfig/operational.py b/python/vyos/ifconfig/operational.py
index 33e8614f0..dc2742123 100644
--- a/python/vyos/ifconfig/operational.py
+++ b/python/vyos/ifconfig/operational.py
@@ -143,15 +143,17 @@ class Operational(Control):
except IOError:
return no_stats
- def clear_counters(self, counters=None):
- clear = self._stats_all if counters is None else []
- stats = self.load_counters()
+ def clear_counters(self):
+ stats = self.get_stats()
for counter, value in stats.items():
- stats[counter] = 0 if counter in clear else value
+ stats[counter] = value
self.save_counters(stats)
def reset_counters(self):
- os.remove(self.cachefile(self.ifname))
+ try:
+ os.remove(self.cachefile(self.ifname))
+ except FileNotFoundError:
+ pass
def get_stats(self):
""" return a dict() with the value for each interface counter """
diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py
index 5258a2cb1..b7bf7d982 100644
--- a/python/vyos/ifconfig/tunnel.py
+++ b/python/vyos/ifconfig/tunnel.py
@@ -83,11 +83,6 @@ class TunnelIf(Interface):
'convert': enable_to_on,
'shellcmd': 'ip link set dev {ifname} multicast {value}',
},
- 'allmulticast': {
- 'validate': lambda v: assert_list(v, ['enable', 'disable']),
- 'convert': enable_to_on,
- 'shellcmd': 'ip link set dev {ifname} allmulticast {value}',
- },
}
}
@@ -162,6 +157,10 @@ class TunnelIf(Interface):
""" Get a synthetic MAC address. """
return self.get_mac_synthetic()
+ def set_multicast(self, enable):
+ """ Change the MULTICAST flag on the device """
+ return self.set_interface('multicast', enable)
+
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
@@ -170,5 +169,10 @@ class TunnelIf(Interface):
# Adjust iproute2 tunnel parameters if necessary
self._change_options()
+ # IP Multicast
+ tmp = dict_search('enable_multicast', config)
+ value = 'enable' if (tmp != None) else 'disable'
+ self.set_multicast(value)
+
# call base class first
super().update(config)
diff --git a/python/vyos/ipsec.py b/python/vyos/ipsec.py
new file mode 100644
index 000000000..bb5611025
--- /dev/null
+++ b/python/vyos/ipsec.py
@@ -0,0 +1,179 @@
+# Copyright 2020-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/>.
+
+#Package to communicate with Strongswan VICI
+
+class ViciInitiateError(Exception):
+ """
+ VICI can't initiate a session.
+ """
+ pass
+class ViciCommandError(Exception):
+ """
+ VICI can't execute a command by any reason.
+ """
+ pass
+
+def get_vici_sas():
+ from vici import Session as vici_session
+
+ try:
+ session = vici_session()
+ except Exception:
+ raise ViciInitiateError("IPsec not initialized")
+ sas = list(session.list_sas())
+ return sas
+
+
+def get_vici_connections():
+ from vici import Session as vici_session
+
+ try:
+ session = vici_session()
+ except Exception:
+ raise ViciInitiateError("IPsec not initialized")
+ connections = list(session.list_conns())
+ return connections
+
+
+def get_vici_sas_by_name(ike_name: str, tunnel: str) -> list:
+ """
+ Find sas by IKE_SA name and/or CHILD_SA name
+ and return list of OrdinaryDicts with SASs info
+ If tunnel is not None return value is list of OrdenaryDicts contained only
+ CHILD_SAs wich names equal tunnel value.
+ :param ike_name: IKE SA name
+ :type ike_name: str
+ :param tunnel: CHILD SA name
+ :type tunnel: str
+ :return: list of Ordinary Dicts with SASs
+ :rtype: list
+ """
+ from vici import Session as vici_session
+
+ try:
+ session = vici_session()
+ except Exception:
+ raise ViciInitiateError("IPsec not initialized")
+ vici_dict = {}
+ if ike_name:
+ vici_dict['ike'] = ike_name
+ if tunnel:
+ vici_dict['child'] = tunnel
+ try:
+ sas = list(session.list_sas(vici_dict))
+ return sas
+ except Exception:
+ raise ViciCommandError(f'Failed to get SAs')
+
+
+def terminate_vici_ikeid_list(ike_id_list: list) -> None:
+ """
+ Terminate IKE SAs by their id that contained in the list
+ :param ike_id_list: list of IKE SA id
+ :type ike_id_list: list
+ """
+ from vici import Session as vici_session
+
+ try:
+ session = vici_session()
+ except Exception:
+ raise ViciInitiateError("IPsec not initialized")
+ try:
+ for ikeid in ike_id_list:
+ session_generator = session.terminate(
+ {'ike-id': ikeid, 'timeout': '-1'})
+ # a dummy `for` loop is required because of requirements
+ # from vici. Without a full iteration on the output, the
+ # command to vici may not be executed completely
+ for _ in session_generator:
+ pass
+ except Exception:
+ raise ViciCommandError(
+ f'Failed to terminate SA for IKE ids {ike_id_list}')
+
+
+def terminate_vici_by_name(ike_name: str, child_name: str) -> None:
+ """
+ Terminate IKE SAs by name if CHILD SA name is None.
+ Terminate CHILD SAs by name if CHILD SA name is specified
+ :param ike_name: IKE SA name
+ :type ike_name: str
+ :param child_name: CHILD SA name
+ :type child_name: str
+ """
+ from vici import Session as vici_session
+
+ try:
+ session = vici_session()
+ except Exception:
+ raise ViciInitiateError("IPsec not initialized")
+ try:
+ vici_dict: dict= {}
+ if ike_name:
+ vici_dict['ike'] = ike_name
+ if child_name:
+ vici_dict['child'] = child_name
+ session_generator = session.terminate(vici_dict)
+ # a dummy `for` loop is required because of requirements
+ # from vici. Without a full iteration on the output, the
+ # command to vici may not be executed completely
+ for _ in session_generator:
+ pass
+ except Exception:
+ if child_name:
+ raise ViciCommandError(
+ f'Failed to terminate SA for IPSEC {child_name}')
+ else:
+ raise ViciCommandError(
+ f'Failed to terminate SA for IKE {ike_name}')
+
+
+def vici_initiate(ike_sa_name: str, child_sa_name: str, src_addr: str,
+ dst_addr: str) -> bool:
+ """Initiate IKE SA connection with specific peer
+
+ Args:
+ ike_sa_name (str): an IKE SA connection name
+ child_sa_name (str): a child SA profile name
+ src_addr (str): source address
+ dst_addr (str): remote address
+
+ Returns:
+ bool: a result of initiation command
+ """
+ from vici import Session as vici_session
+
+ try:
+ session = vici_session()
+ except Exception:
+ raise ViciInitiateError("IPsec not initialized")
+
+ try:
+ session_generator = session.initiate({
+ 'ike': ike_sa_name,
+ 'child': child_sa_name,
+ 'timeout': '-1',
+ 'my-host': src_addr,
+ 'other-host': dst_addr
+ })
+ # a dummy `for` loop is required because of requirements
+ # from vici. Without a full iteration on the output, the
+ # command to vici may not be executed completely
+ for _ in session_generator:
+ pass
+ return True
+ except Exception:
+ raise ViciCommandError(f'Failed to initiate SA for IKE {ike_sa_name}') \ No newline at end of file
diff --git a/python/vyos/nat.py b/python/vyos/nat.py
index 8a311045a..53fd7fb33 100644
--- a/python/vyos/nat.py
+++ b/python/vyos/nat.py
@@ -47,6 +47,9 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
protocol = '{ tcp, udp }'
output.append(f'meta l4proto {protocol}')
+ if 'packet_type' in rule_conf:
+ output.append(f'pkttype ' + rule_conf['packet_type'])
+
if 'exclude' in rule_conf:
translation_str = 'return'
log_suffix = '-EXCL'
diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py
index 5ff768859..d7172a0b5 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
@@ -22,6 +22,10 @@ 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
@@ -45,6 +49,17 @@ 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.
@@ -66,13 +81,13 @@ class InternalError(Error):
def _is_op_mode_function_name(name):
- if re.match(r"^(show|clear|reset|restart)", name):
+ if re.match(r"^(show|clear|reset|restart|add|delete|generate|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
@@ -113,6 +128,25 @@ def _get_arg_type(t):
else:
return t
+def _is_literal_type(t):
+ if _is_optional_type(t):
+ t = _get_arg_type(t)
+
+ if typing.get_origin(t) == typing.Literal:
+ return True
+
+ return False
+
+def _get_literal_values(t):
+ """ Returns the tuple of allowed values for a Literal type
+ """
+ if not _is_literal_type(t):
+ return tuple()
+ if _is_optional_type(t):
+ t = _get_arg_type(t)
+
+ return typing.get_args(t)
+
def _normalize_field_name(name):
# Convert the name to string if it is not
# (in some cases they may be numbers)
@@ -179,9 +213,21 @@ def run(module):
subparser.add_argument(f"--{opt}", action='store_true')
else:
if _is_optional_type(th):
- subparser.add_argument(f"--{opt}", type=_get_arg_type(th), default=None)
+ if _is_literal_type(th):
+ subparser.add_argument(f"--{opt}",
+ choices=list(_get_literal_values(th)),
+ default=None)
+ else:
+ subparser.add_argument(f"--{opt}",
+ type=_get_arg_type(th), default=None)
else:
- subparser.add_argument(f"--{opt}", type=_get_arg_type(th), required=True)
+ if _is_literal_type(th):
+ subparser.add_argument(f"--{opt}",
+ choices=list(_get_literal_values(th)),
+ required=True)
+ else:
+ subparser.add_argument(f"--{opt}",
+ type=_get_arg_type(th), required=True)
# Get options as a dict rather than a namespace,
# so that we can modify it and pack for passing to functions
@@ -199,20 +245,23 @@ 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
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..33bb8ae28
--- /dev/null
+++ b/python/vyos/qos/base.py
@@ -0,0 +1,302 @@
+# 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 = 10
+ # Not all interfaces have valid entries in the speed file. PPPoE
+ # interfaces have the appropriate speed file, but you can not read it:
+ # cat: /sys/class/net/pppoe7/speed: Invalid argument
+ try:
+ speed = read_file(f'/sys/class/net/{self._interface}/speed')
+ if not speed.isnumeric():
+ Warning('Interface speed cannot be determined (assuming 10 Mbit/s)')
+ if rate.endswith('%'):
+ percent = rate.rstrip('%')
+ speed = int(speed) * int(percent) // 100
+ except:
+ pass
+
+ 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 self._debug:
+ import pprint
+ pprint.pprint(config)
+
+ if 'class' in config:
+ for cls, cls_config in config['class'].items():
+ self._build_base_qdisc(cls_config, int(cls))
+
+ # 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'
+
+ if 'match' in cls_config:
+ for match, match_config in cls_config['match'].items():
+ if 'mark' in match_config:
+ mark = match_config['mark']
+ filter_cmd += f' handle {mark} fw'
+
+ for af in ['ip', 'ipv6']:
+ tc_af = af
+ if af == 'ipv6':
+ tc_af = 'ip6'
+
+ if af in match_config:
+ filter_cmd += ' u32'
+
+ 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'
+
+ else:
+
+ filter_cmd += ' 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 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/template.py b/python/vyos/template.py
index 2a4135f9e..254a15e3a 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-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
@@ -44,6 +44,7 @@ def _get_environment(location=None):
loader=loc_loader,
trim_blocks=True,
undefined=ChainableUndefined,
+ extensions=['jinja2.ext.loopcontrols']
)
env.filters.update(_FILTERS)
env.tests.update(_TESTS)
@@ -158,6 +159,24 @@ def force_to_list(value):
else:
return [value]
+@register_filter('seconds_to_human')
+def seconds_to_human(seconds, separator=""):
+ """ Convert seconds to human-readable values like 1d6h15m23s """
+ from vyos.util import seconds_to_human
+ return seconds_to_human(seconds, separator=separator)
+
+@register_filter('bytes_to_human')
+def bytes_to_human(bytes, initial_exponent=0, precision=2):
+ """ Convert bytes to human-readable values like 1.44M """
+ from vyos.util import bytes_to_human
+ return bytes_to_human(bytes, initial_exponent=initial_exponent, precision=precision)
+
+@register_filter('human_to_bytes')
+def human_to_bytes(value):
+ """ Convert a data amount with a unit suffix to bytes, like 2K to 2048 """
+ from vyos.util import human_to_bytes
+ return human_to_bytes(value)
+
@register_filter('ip_from_cidr')
def ip_from_cidr(prefix):
""" Take an IPv4/IPv6 CIDR host and strip cidr mask.
@@ -193,6 +212,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 +505,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':
@@ -633,7 +664,24 @@ def nat_static_rule(rule_conf, rule_id, nat_type):
@register_filter('range_to_regex')
def range_to_regex(num_range):
+ """Convert range of numbers or list of ranges
+ to regex
+
+ % range_to_regex('11-12')
+ '(1[1-2])'
+ % range_to_regex(['11-12', '14-15'])
+ '(1[1-2]|1[4-5])'
+ """
from vyos.range_regex import range_to_regex
+ if isinstance(num_range, list):
+ data = []
+ for entry in num_range:
+ if '-' not in entry:
+ data.append(entry)
+ else:
+ data.append(range_to_regex(entry))
+ return f'({"|".join(data)})'
+
if '-' not in num_range:
return num_range
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 6a828c0ac..0593184cc 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
@@ -872,12 +874,16 @@ def convert_data(data):
Returns:
str | list | dict: converted data
"""
+ from base64 import b64encode
from collections import OrderedDict
if isinstance(data, str):
return data
if isinstance(data, bytes):
- return data.decode()
+ try:
+ return data.decode()
+ except UnicodeDecodeError:
+ return b64encode(data).decode()
if isinstance(data, list):
list_tmp = []
for item in data:
diff --git a/python/vyos/xml/load.py b/python/vyos/xml/load.py
index c3022f3d6..f842ff9ce 100644
--- a/python/vyos/xml/load.py
+++ b/python/vyos/xml/load.py
@@ -71,16 +71,12 @@ def _merge(dict1, dict2):
continue
if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
dict1[k] = _merge(dict1[k], dict2[k])
- elif isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
+ elif isinstance(dict1[k], list) and isinstance(dict2[k], list):
dict1[k].extend(dict2[k])
elif dict1[k] == dict2[k]:
- # A definition shared between multiple files
- if k in (kw.valueless, kw.multi, kw.hidden, kw.node, kw.summary, kw.owner, kw.priority):
- continue
- _fatal()
- raise RuntimeError('parsing issue - undefined leaf?')
+ continue
else:
- raise RuntimeError('parsing issue - we messed up?')
+ dict1[k] = dict2[k]
return dict1
@@ -131,7 +127,7 @@ def _format_nodes(inside, conf, xml):
name = node.pop('@name')
into = inside + [name]
if name in r:
- r[name].update(_format_node(into, node, xml))
+ _merge(r[name], _format_node(into, node, xml))
else:
r[name] = _format_node(into, node, xml)
r[name][kw.node] = nodename
@@ -141,7 +137,7 @@ def _format_nodes(inside, conf, xml):
name = node.pop('@name')
into = inside + [name]
if name in r:
- r[name].update(_format_node(inside + [name], node, xml))
+ _merge(r[name], _format_node(inside + [name], node, xml))
else:
r[name] = _format_node(inside + [name], node, xml)
r[name][kw.node] = nodename
@@ -180,10 +176,10 @@ def _format_node(inside, conf, xml):
if isinstance(conf, list):
for child in children:
- r = _safe_update(r, _format_nodes(inside, child, xml))
+ _merge(r, _format_nodes(inside, child, xml))
else:
child = children
- r = _safe_update(r, _format_nodes(inside, child, xml))
+ _merge(r, _format_nodes(inside, child, xml))
elif 'properties' in keys:
properties = conf.pop('properties')
diff --git a/schema/interface_definition.rnc b/schema/interface_definition.rnc
index d7fc4966c..758d9ce1c 100644
--- a/schema/interface_definition.rnc
+++ b/schema/interface_definition.rnc
@@ -50,7 +50,7 @@ node = element node
tagNode = element tagNode
{
(ownerAttr? & nodeNameAttr),
- (properties? & children )
+ (defaultValue? & properties? & children )
}
# Leaf nodes are terminal configuration nodes that can't have children,
diff --git a/schema/interface_definition.rng b/schema/interface_definition.rng
index 3ff60cf18..94a828c3b 100644
--- a/schema/interface_definition.rng
+++ b/schema/interface_definition.rng
@@ -2,19 +2,19 @@
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<!--
interface_definition.rnc: VyConf reference tree XML grammar
-
+
Copyright (C) 2014. 2017 VyOS maintainers and contributors <maintainers@vyos.net>
-
+
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
@@ -84,6 +84,9 @@
</interleave>
<interleave>
<optional>
+ <ref name="defaultValue"/>
+ </optional>
+ <optional>
<ref name="properties"/>
</optional>
<ref name="children"/>
@@ -139,7 +142,7 @@
Nodes may have properties
For simplicity, any property is allowed in any node,
but whether they are used or not is implementation-defined
-
+
Leaf nodes may differ in number of values that can be
associated with them.
By default, a leaf node can have only one value.
@@ -147,7 +150,7 @@
"valueless" means it can have no values at all.
"hidden" means node visibility can be toggled, eg 'dangerous' commands,
"secret" allows a node to hide its value from unprivileged users.
-
+
"priority" is used to influence node processing order for nodes
with exact same dependencies and in compatibility modes.
-->
diff --git a/scripts/check-pr-title-and-commit-messages.py b/scripts/check-pr-title-and-commit-messages.py
index 9801b7456..1d4a3cfe3 100755
--- a/scripts/check-pr-title-and-commit-messages.py
+++ b/scripts/check-pr-title-and-commit-messages.py
@@ -2,6 +2,7 @@
import re
import sys
+import time
import requests
from pprint import pprint
@@ -27,9 +28,17 @@ if __name__ == '__main__':
print("Please specify pull request URL!")
sys.exit(1)
+ # There seems to be a race condition that causes this scripts to receive
+ # an incomplete PR object that is missing certain fields,
+ # which causes temporary CI failures that require re-running the script
+ #
+ # It's probably better to add a small delay to prevent that
+ time.sleep(5)
+
# Get the pull request object
pr = requests.get(sys.argv[1]).json()
if "title" not in pr:
+ print("The PR object does not have a title field!")
print("Did not receive a valid pull request object, please check the URL!")
sys.exit(1)
diff --git a/smoketest/bin/vyos-smoketest b/smoketest/bin/vyos-smoketest
index cb039db42..135388afe 100755
--- a/smoketest/bin/vyos-smoketest
+++ b/smoketest/bin/vyos-smoketest
@@ -26,7 +26,7 @@ for root, dirs, files in os.walk('/usr/libexec/vyos/tests/smoke'):
test_file = os.path.join(root, name)
mode = os.stat(test_file).st_mode
- if mode & S_IXOTH:
+ if name.startswith("test_") and mode & S_IXOTH:
print('Running Testcase: ' + test_file)
process = Popen([test_file], stdout=PIPE)
(output, err) = process.communicate()
diff --git a/smoketest/configs/basic-qos b/smoketest/configs/basic-qos
deleted file mode 100644
index d9baa4a1f..000000000
--- a/smoketest/configs/basic-qos
+++ /dev/null
@@ -1,194 +0,0 @@
-interfaces {
- ethernet eth0 {
- address 100.64.0.1/20
- duplex auto
- smp-affinity auto
- speed auto
- }
- ethernet eth1 {
- duplex auto
- speed auto
- vif 10 {
- traffic-policy {
- in M2
- }
- }
- vif 20 {
- traffic-policy {
- out FS
- }
- }
- vif 30 {
- traffic-policy {
- out MY-HTB
- }
- }
- vif 40 {
- traffic-policy {
- out SHAPER-FOO
- }
- }
- }
-}
-system {
- config-management {
- commit-revisions 100
- }
- console {
- device ttyS0 {
- speed 115200
- }
- }
- host-name vyos
- login {
- user vyos {
- authentication {
- encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0
- plaintext-password ""
- }
- }
- }
- name-server 192.168.0.1
- syslog {
- global {
- archive {
- file 5
- size 512
- }
- facility all {
- level info
- }
- }
- }
- time-zone Europe/Berlin
-}
-traffic-policy {
- limiter M2 {
- class 10 {
- bandwidth 120mbit
- burst 15k
- match ADDRESS10 {
- ip {
- dscp CS4
- }
- }
- priority 20
- }
- default {
- bandwidth 100mbit
- burst 15k
- }
- }
- shaper FS {
- bandwidth auto
- class 10 {
- bandwidth 100%
- burst 15k
- match ADDRESS10 {
- ip {
- source {
- address 172.17.1.2/32
- }
- }
- }
- queue-type fair-queue
- set-dscp CS4
- }
- class 20 {
- bandwidth 100%
- burst 15k
- match ADDRESS20 {
- ip {
- source {
- address 172.17.1.3/32
- }
- }
- }
- queue-type fair-queue
- set-dscp CS5
- }
- class 30 {
- bandwidth 100%
- burst 15k
- match ADDRESS30 {
- ip {
- source {
- address 172.17.1.4/32
- }
- }
- }
- queue-type fair-queue
- set-dscp CS6
- }
- default {
- bandwidth 10%
- burst 15k
- ceiling 100%
- priority 7
- queue-type fair-queue
- }
- }
- shaper MY-HTB {
- bandwidth 10mbit
- class 30 {
- bandwidth 10%
- burst 15k
- ceiling 50%
- match ADDRESS30 {
- ip {
- source {
- address 10.1.1.0/24
- }
- }
- }
- priority 5
- queue-type fair-queue
- }
- class 40 {
- bandwidth 90%
- burst 15k
- ceiling 100%
- match ADDRESS40 {
- ip {
- dscp CS4
- source {
- address 10.2.1.0/24
- }
- }
- }
- priority 5
- queue-type fair-queue
- }
- class 50 {
- bandwidth 100%
- burst 15k
- match ADDRESS50 {
- ip {
- dscp CS5
- }
- }
- queue-type fair-queue
- set-dscp CS7
- }
- default {
- bandwidth 10%
- burst 15k
- ceiling 100%
- priority 7
- queue-type fair-queue
- set-dscp CS1
- }
- }
- shaper SHAPER-FOO {
- bandwidth 1000mbit
- default {
- bandwidth 100mbit
- burst 15k
- queue-type fair-queue
- set-dscp CS4
- }
- }
-}
-// Warning: Do not remove the following line.
-// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1"
-// Release version: 1.3.2
diff --git a/smoketest/configs/dialup-router-medium-vpn b/smoketest/configs/dialup-router-medium-vpn
index 56722d222..503280017 100644
--- a/smoketest/configs/dialup-router-medium-vpn
+++ b/smoketest/configs/dialup-router-medium-vpn
@@ -68,9 +68,6 @@ interfaces {
mtu 1500
name-server auto
password password
- traffic-policy {
- out shape-17mbit
- }
user-id vyos
password vyos
}
@@ -96,9 +93,6 @@ interfaces {
}
smp-affinity auto
speed auto
- traffic-policy {
- out shape-94mbit
- }
}
loopback lo {
}
@@ -719,24 +713,6 @@ system {
}
time-zone Pacific/Auckland
}
-traffic-policy {
- shaper shape-17mbit {
- bandwidth 17mbit
- default {
- bandwidth 100%
- burst 15k
- queue-type fq-codel
- }
- }
- shaper shape-94mbit {
- bandwidth 94mbit
- default {
- bandwidth 100%
- burst 15k
- queue-type fq-codel
- }
- }
-}
/* Warning: Do not remove the following line. */
/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */
/* Release version: 1.2.6 */
diff --git a/smoketest/configs/egb-igp-route-maps b/smoketest/configs/egb-igp-route-maps
new file mode 100644
index 000000000..ca36691d4
--- /dev/null
+++ b/smoketest/configs/egb-igp-route-maps
@@ -0,0 +1,127 @@
+interfaces {
+ ethernet eth0 {
+ address 192.0.2.1/25
+ duplex auto
+ smp-affinity auto
+ speed auto
+ }
+ ethernet eth1 {
+ address 192.0.2.129/25
+ address 2001:db8::1234/64
+ duplex auto
+ smp-affinity auto
+ speed auto
+ }
+ loopback lo {
+ }
+}
+policy {
+ route-map zebra-bgp {
+ rule 10 {
+ action permit
+ }
+ }
+ route-map zebra-isis {
+ rule 10 {
+ action permit
+ }
+ }
+ route-map zebra-ospf {
+ rule 10 {
+ action permit
+ }
+ }
+ route-map zebra-ospfv3 {
+ rule 10 {
+ action permit
+ }
+ }
+ route-map zebra-ripng {
+ rule 10 {
+ action permit
+ }
+ }
+ route-map zebra-static {
+ rule 10 {
+ action permit
+ }
+ }
+}
+protocols {
+ bgp 100 {
+ route-map zebra-bgp
+ }
+ isis {
+ interface eth0 {
+ }
+ net 49.0001.1921.6800.1002.00
+ route-map zebra-isis
+ }
+ ospf {
+ area 0 {
+ network 192.0.2.0/25
+ network 192.0.2.128/25
+ }
+ log-adjacency-changes {
+ }
+ parameters {
+ abr-type cisco
+ router-id 1.1.1.1
+ }
+ passive-interface default
+ passive-interface-exclude eth0
+ passive-interface-exclude eth1
+ route-map zebra-ospf
+ }
+ ospfv3 {
+ area 0 {
+ interface eth1
+ }
+ parameters {
+ router-id 1.1.1.1
+ }
+ route-map zebra-ospfv3
+ }
+ ripng {
+ interface eth1
+ route-map zebra-ripng
+ }
+ static {
+ route-map zebra-static
+ }
+}
+system {
+ config-management {
+ commit-revisions 100
+ }
+ console {
+ device ttyS0 {
+ speed 115200
+ }
+ }
+ host-name vyos
+ login {
+ user vyos {
+ authentication {
+ encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0
+ plaintext-password ""
+ }
+ }
+ }
+ name-server 192.168.0.1
+ syslog {
+ global {
+ archive {
+ file 5
+ size 512
+ }
+ facility all {
+ level info
+ }
+ }
+ }
+ time-zone Europe/Berlin
+}
+// Warning: Do not remove the following line.
+// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1"
+// Release version: 1.3.2
diff --git a/smoketest/configs/qos-basic b/smoketest/configs/qos-basic
index f94a5650d..65a888d38 100644
--- a/smoketest/configs/qos-basic
+++ b/smoketest/configs/qos-basic
@@ -8,7 +8,7 @@ interfaces {
ethernet eth1 {
address 10.2.1.1/24
traffic-policy {
- out M2
+ out ISPC
}
}
ethernet eth2 {
@@ -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 {
@@ -79,24 +74,15 @@ system {
}
}
traffic-policy {
- shaper M2 {
- bandwidth auto
- class 10 {
- bandwidth 100%
- burst 15k
- match ADDRESS10 {
- ip {
- dscp CS4
- }
- }
- queue-type fair-queue
- set-dscp CS5
- }
+ shaper ISPC {
+ bandwidth 600Mbit
default {
- bandwidth 10mbit
- burst 15k
- queue-type fair-queue
+ bandwidth 50%
+ burst 768k
+ ceiling 100%
+ queue-type fq-codel
}
+ description "Outbound Traffic Shaper - ISPC"
}
shaper MY-HTB {
bandwidth 10mbit
@@ -120,7 +106,6 @@ traffic-policy {
ceiling 100%
match ADDRESS40 {
ip {
- dscp CS4
source {
address 10.2.1.0/24
}
@@ -133,12 +118,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 +132,6 @@ traffic-policy {
ceiling 100%
priority 7
queue-type fair-queue
- set-dscp CS1
}
}
shaper FS {
@@ -162,7 +147,6 @@ traffic-policy {
}
}
queue-type fair-queue
- set-dscp CS4
}
class 20 {
bandwidth 100%
@@ -175,7 +159,6 @@ traffic-policy {
}
}
queue-type fair-queue
- set-dscp CS5
}
class 30 {
bandwidth 100%
@@ -188,7 +171,6 @@ traffic-policy {
}
}
queue-type fair-queue
- set-dscp CS6
}
default {
bandwidth 10%
@@ -198,6 +180,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/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index 55343b893..2f730abfb 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -123,7 +123,7 @@ class BasicInterfaceTest:
# Also enable DHCP (ISC DHCP always places interface in admin up
# state so we check that we do not start DHCP client.
- # https://phabricator.vyos.net/T2767
+ # https://vyos.dev/T2767
self.cli_set(self._base_path + [interface, 'address', 'dhcp'])
self.cli_commit()
@@ -476,7 +476,7 @@ class BasicInterfaceTest:
self.assertEqual(to_key, new_egress_qos_to)
def test_vif_8021q_lower_up_down(self):
- # Testcase for https://phabricator.vyos.net/T3349
+ # Testcase for https://vyos.dev/T3349
if not self._test_vlan:
self.skipTest('not supported')
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index f1c18d761..99d3b3ca1 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -198,6 +198,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
def test_ipv4_basic_rules(self):
name = 'smoketest'
interface = 'eth0'
+ interface_wc = 'l2tp*'
mss_range = '501-1460'
conn_mark = '555'
@@ -207,13 +208,13 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'name', name, 'rule', '1', 'source', 'address', '172.16.20.10'])
self.cli_set(['firewall', 'name', name, 'rule', '1', 'destination', 'address', '172.16.10.10'])
self.cli_set(['firewall', 'name', name, 'rule', '1', 'log', 'enable'])
- self.cli_set(['firewall', 'name', name, 'rule', '1', 'log-level', 'debug'])
+ self.cli_set(['firewall', 'name', name, 'rule', '1', 'log-options', 'level', 'debug'])
self.cli_set(['firewall', 'name', name, 'rule', '1', 'ttl', 'eq', '15'])
self.cli_set(['firewall', 'name', name, 'rule', '2', 'action', 'reject'])
self.cli_set(['firewall', 'name', name, 'rule', '2', 'protocol', 'tcp'])
self.cli_set(['firewall', 'name', name, 'rule', '2', 'destination', 'port', '8888'])
self.cli_set(['firewall', 'name', name, 'rule', '2', 'log', 'enable'])
- self.cli_set(['firewall', 'name', name, 'rule', '2', 'log-level', 'err'])
+ self.cli_set(['firewall', 'name', name, 'rule', '2', 'log-options', 'level', 'err'])
self.cli_set(['firewall', 'name', name, 'rule', '2', 'tcp', 'flags', 'syn'])
self.cli_set(['firewall', 'name', name, 'rule', '2', 'tcp', 'flags', 'not', 'ack'])
self.cli_set(['firewall', 'name', name, 'rule', '2', 'ttl', 'gt', '102'])
@@ -227,10 +228,12 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'name', name, 'rule', '4', 'destination', 'port', '22'])
self.cli_set(['firewall', 'name', name, 'rule', '4', 'recent', 'count', '10'])
self.cli_set(['firewall', 'name', name, 'rule', '4', 'recent', 'time', 'minute'])
+ self.cli_set(['firewall', 'name', name, 'rule', '4', 'packet-type', 'host'])
self.cli_set(['firewall', 'name', name, 'rule', '5', 'action', 'accept'])
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', 'packet-type', 'broadcast'])
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'])
@@ -238,6 +241,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'name', name, 'rule', '6', 'connection-mark', conn_mark])
self.cli_set(['firewall', 'interface', interface, 'in', 'name', name])
+ self.cli_set(['firewall', 'interface', interface_wc, 'in', 'name', name])
self.cli_commit()
@@ -245,12 +249,13 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
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'],
- ['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[smoketest-2-R]" level err', 'ip ttl > 102', 'reject'],
+ [f'iifname "{interface_wc}"', f'jump NAME_{name}'],
+ ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[smoketest-1-A]" log level debug', 'ip ttl 15', 'return'],
+ ['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[smoketest-2-R]" log level err', 'ip ttl > 102', 'reject'],
['tcp dport 22', 'limit rate 5/minute', 'return'],
['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}"'],
+ ['tcp dport 22', 'add @RECENT_smoketest_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'meta pkttype host', 'drop'],
+ ['tcp flags & syn == syn', f'tcp option maxseg size {mss_range}', f'iifname "{interface}"', 'meta pkttype broadcast'],
['meta l4proto gre', f'oifname "{interface}"', f'ct mark {mark_hex}', 'return']
]
@@ -270,6 +275,10 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'name', name, 'rule', '6', 'packet-length', '1024'])
self.cli_set(['firewall', 'name', name, 'rule', '6', 'dscp', '17'])
self.cli_set(['firewall', 'name', name, 'rule', '6', 'dscp', '52'])
+ self.cli_set(['firewall', 'name', name, 'rule', '6', 'log', 'enable'])
+ self.cli_set(['firewall', 'name', name, 'rule', '6', 'log-options', 'group', '66'])
+ self.cli_set(['firewall', 'name', name, 'rule', '6', 'log-options', 'snapshot-length', '6666'])
+ self.cli_set(['firewall', 'name', name, 'rule', '6', 'log-options', 'queue-threshold','32000'])
self.cli_set(['firewall', 'name', name, 'rule', '7', 'action', 'accept'])
self.cli_set(['firewall', 'name', name, 'rule', '7', 'packet-length', '1-30000'])
@@ -284,17 +293,28 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'name', name2, 'rule', '1', 'action', 'jump'])
self.cli_set(['firewall', 'name', name2, 'rule', '1', 'jump-target', name])
+ self.cli_set(['firewall', 'name', name2, 'rule', '2', 'protocol', 'tcp'])
+ self.cli_set(['firewall', 'name', name2, 'rule', '2', 'action', 'queue'])
+ self.cli_set(['firewall', 'name', name2, 'rule', '2', 'queue', '3'])
+ self.cli_set(['firewall', 'name', name2, 'rule', '3', 'protocol', 'udp'])
+ self.cli_set(['firewall', 'name', name2, 'rule', '3', 'action', 'queue'])
+ self.cli_set(['firewall', 'name', name2, 'rule', '3', 'queue-options', 'fanout'])
+ self.cli_set(['firewall', 'name', name2, 'rule', '3', 'queue-options', 'bypass'])
+ self.cli_set(['firewall', 'name', name2, 'rule', '3', 'queue', '0-15'])
+
self.cli_set(['firewall', 'interface', interface, 'in', 'name', name])
self.cli_commit()
nftables_search = [
[f'iifname "{interface}"', f'jump NAME_{name}'],
- ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', 'return'],
+ ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', f'log prefix "[{name}-6-A]" log group 66 snaplen 6666 queue-threshold 32000', 'return'],
['ip length 1-30000', 'ip length != 60000-65535', 'ip dscp 0x03-0x0b', 'ip dscp != 0x15-0x19', 'return'],
[f'log prefix "[{name}-default-D]"', 'drop'],
['ip saddr 198.51.100.1', f'jump NAME_{name}'],
- [f'log prefix "[{name2}-default-J]"', f'jump NAME_{name}']
+ [f'log prefix "[{name2}-default-J]"', f'jump NAME_{name}'],
+ [f'meta l4proto tcp','queue to 3'],
+ [f'meta l4proto udp','queue flags bypass,fanout to 0-15']
]
self.verify_nftables(nftables_search, 'ip vyos_filter')
@@ -344,7 +364,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'source', 'address', '2002::1'])
self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'destination', 'address', '2002::1:1'])
self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'log', 'enable'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'log-level', 'crit'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'log-options', 'level', 'crit'])
self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'action', 'reject'])
self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'protocol', 'tcp_udp'])
@@ -361,7 +381,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
nftables_search = [
[f'iifname "{interface}"', f'jump NAME6_{name}'],
- ['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[v6-smoketest-1-A]" level crit', 'return'],
+ ['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[v6-smoketest-1-A]" log level crit', 'return'],
['meta l4proto { tcp, udp }', 'th dport 8888', f'iifname "{interface}"', 'reject'],
['meta l4proto gre', f'oifname "{interface}"', 'return'],
['smoketest default-action', f'log prefix "[{name}-default-D]"', 'drop']
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_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py
index b2143d16e..c80c7cf80 100755
--- a/smoketest/scripts/cli/test_interfaces_openvpn.py
+++ b/smoketest/scripts/cli/test_interfaces_openvpn.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
@@ -368,6 +368,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_set(path + ['hash', auth_hash])
self.cli_set(path + ['mode', 'server'])
self.cli_set(path + ['local-port', port])
+ self.cli_set(path + ['server', 'mfa', 'totp'])
self.cli_set(path + ['server', 'subnet', subnet])
self.cli_set(path + ['server', 'topology', 'subnet'])
self.cli_set(path + ['keep-alive', 'failure-count', '5'])
@@ -388,6 +389,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
for ii in num_range:
interface = f'vtun{ii}'
+ plugin = f'plugin "/usr/lib/openvpn/openvpn-otp.so" "otp_secrets=/config/auth/openvpn/{interface}-otp-secrets otp_slop=180 totp_t0=0 totp_step=30 totp_digits=6 password_is_cr=1"'
subnet = f'192.0.{ii}.0/24'
start_addr = inc_ip(subnet, '2')
@@ -411,6 +413,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'topology subnet', config)
self.assertIn(f'lport {port}', config)
self.assertIn(f'push "redirect-gateway def1"', config)
+ self.assertIn(f'{plugin}', config)
self.assertIn(f'keepalive 5 25', config)
# TLS options
diff --git a/smoketest/scripts/cli/test_interfaces_pppoe.py b/smoketest/scripts/cli/test_interfaces_pppoe.py
index 8927121a8..f4efed641 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,11 +57,11 @@ 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])
+ self.cli_set(base_path + [interface, 'authentication', 'username', user])
self.cli_set(base_path + [interface, 'authentication', 'password', passwd])
self.cli_set(base_path + [interface, 'mtu', mtu])
self.cli_set(base_path + [interface, 'no-peer-dns'])
@@ -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', 'username', 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', 'username', 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', 'username', 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', 'username', 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/smoketest/scripts/cli/test_load_balancning_wan.py b/smoketest/scripts/cli/test_load_balancing_wan.py
index 23020b9b1..8df3471f7 100755
--- a/smoketest/scripts/cli/test_load_balancning_wan.py
+++ b/smoketest/scripts/cli/test_load_balancing_wan.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
@@ -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)
@@ -144,15 +143,15 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase):
container_iface3 = 'ceth2'
mangle_isp1 = """table ip mangle {
chain ISP_veth1 {
- counter ct mark set 0xc9
- counter meta mark set 0xc9
+ counter ct mark set 0xc9
+ counter meta mark set 0xc9
counter accept
}
}"""
mangle_isp2 = """table ip mangle {
chain ISP_veth2 {
- counter ct mark set 0xca
- counter meta mark set 0xca
+ counter ct mark set 0xca
+ counter meta mark set 0xca
counter accept
}
}"""
@@ -164,7 +163,7 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase):
}"""
mangle_wanloadbalance_pre = """table ip mangle {
chain WANLOADBALANCE_PRE {
- iifname "veth3" ip saddr 198.51.100.0/24 ct state new counter jump ISP_veth1
+ iifname "veth3" ip saddr 198.51.100.0/24 ct state new meta random & 2147483647 < 1073741824 counter jump ISP_veth1
iifname "veth3" ip saddr 198.51.100.0/24 ct state new counter jump ISP_veth2
iifname "veth3" ip saddr 198.51.100.0/24 counter meta mark set ct mark
}
@@ -179,7 +178,6 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase):
chain VYOS_PRE_SNAT_HOOK {
type nat hook postrouting priority srcnat - 1; policy accept;
counter jump WANLOADBALANCE
- return
}
}"""
@@ -196,9 +194,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 +246,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 9f4e3b831..1f2b777a8 100755
--- a/smoketest/scripts/cli/test_nat.py
+++ b/smoketest/scripts/cli/test_nat.py
@@ -194,12 +194,13 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'eth1'])
self.cli_set(dst_path + ['rule', '1', 'destination', 'port', '443'])
self.cli_set(dst_path + ['rule', '1', 'protocol', 'tcp'])
+ self.cli_set(dst_path + ['rule', '1', 'packet-type', 'host'])
self.cli_set(dst_path + ['rule', '1', 'translation', 'port', '443'])
self.cli_commit()
nftables_search = [
- ['iifname "eth1"', 'tcp dport 443', 'dnat to :443']
+ ['iifname "eth1"', 'tcp dport 443', 'pkttype host', 'dnat to :443']
]
self.verify_nftables(nftables_search, 'ip vyos_nat')
diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py
index cb48a84ff..a3df6bf4d 100755
--- a/smoketest/scripts/cli/test_policy_route.py
+++ b/smoketest/scripts/cli/test_policy_route.py
@@ -26,6 +26,7 @@ conn_mark_set = '111'
table_mark_offset = 0x7fffffff
table_id = '101'
interface = 'eth0'
+interface_wc = 'ppp*'
interface_ip = '172.16.10.1/24'
class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
@@ -204,6 +205,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'icmp', 'type-name', 'echo-request'])
self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'packet-length', '128'])
self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'packet-length', '1024-2048'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'packet-type', 'other'])
self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'log', 'enable'])
self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'set', 'table', table_id])
self.cli_set(['policy', 'route', 'smoketest', 'rule', '5', 'dscp', '41'])
@@ -226,6 +228,8 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'icmpv6', 'type', 'echo-request'])
self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'packet-length-exclude', '128'])
self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'packet-length-exclude', '1024-2048'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'packet-type', 'multicast'])
+
self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'log', 'enable'])
self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'set', 'table', table_id])
self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'dscp-exclude', '61'])
@@ -233,7 +237,8 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'set', 'table', table_id])
self.cli_set(['policy', 'route', 'smoketest', 'interface', interface])
- self.cli_set(['policy', 'route6', 'smoketest6', 'interface', interface])
+ self.cli_set(['policy', 'route', 'smoketest', 'interface', interface_wc])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'interface', interface_wc])
self.cli_commit()
@@ -241,11 +246,11 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
# IPv4
nftables_search = [
- [f'iifname "{interface}"', 'jump VYOS_PBR_smoketest'],
+ ['iifname { "' + interface + '", "' + interface_wc + '" }', 'jump VYOS_PBR_smoketest'],
['meta l4proto udp', 'drop'],
['tcp flags syn / syn,ack', 'meta mark set ' + mark_hex],
['ct state new', 'tcp dport 22', 'ip saddr 198.51.100.0/24', 'ip ttl > 2', 'meta mark set ' + mark_hex],
- ['meta l4proto icmp', 'log prefix "[smoketest-4-A]"', 'icmp type echo-request', 'ip length { 128, 1024-2048 }', 'meta mark set ' + mark_hex],
+ ['meta l4proto icmp', 'log prefix "[smoketest-4-A]"', 'icmp type echo-request', 'ip length { 128, 1024-2048 }', 'meta pkttype other', 'meta mark set ' + mark_hex],
['ip dscp { 0x29, 0x39-0x3b }', 'meta mark set ' + mark_hex]
]
@@ -253,11 +258,11 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
# IPv6
nftables6_search = [
- [f'iifname "{interface}"', 'jump VYOS_PBR6_smoketest'],
+ [f'iifname "{interface_wc}"', 'jump VYOS_PBR6_smoketest'],
['meta l4proto udp', 'drop'],
['tcp flags syn / syn,ack', 'meta mark set ' + mark_hex],
['ct state new', 'tcp dport 22', 'ip6 saddr 2001:db8::/64', 'ip6 hoplimit > 2', 'meta mark set ' + mark_hex],
- ['meta l4proto ipv6-icmp', 'log prefix "[smoketest6-4-A]"', 'icmpv6 type echo-request', 'ip6 length != { 128, 1024-2048 }', 'meta mark set ' + mark_hex],
+ ['meta l4proto ipv6-icmp', 'log prefix "[smoketest6-4-A]"', 'icmpv6 type echo-request', 'ip6 length != { 128, 1024-2048 }', 'meta pkttype multicast', 'meta mark set ' + mark_hex],
['ip6 dscp != { 0x0e-0x13, 0x3d }', 'meta mark set ' + mark_hex]
]
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index debc8270c..2fd5d0c9b 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' : '',
@@ -51,6 +55,7 @@ neighbor_config = {
'route_map_out' : route_map_out,
'no_send_comm_ext' : '',
'addpath_all' : '',
+ 'p_attr_discard' : '123',
},
'192.0.2.2' : {
'bfd_profile' : bfd_profile,
@@ -64,6 +69,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 +100,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',
@@ -122,10 +130,12 @@ peer_group_config = {
'cap_over' : '',
'ttl_security' : '5',
'disable_conn_chk' : '',
+ 'p_attr_discard' : '250',
},
'bar' : {
'remote_as' : '111',
- 'graceful_rst_no' : ''
+ 'graceful_rst_no' : '',
+ 'port' : '667',
},
'foo-bar' : {
'advertise_map' : route_map_in,
@@ -150,6 +160,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 +201,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,12 +229,19 @@ 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:
self.assertIn(f' neighbor {peer} passive', frrconfig)
if 'password' in peer_config:
self.assertIn(f' neighbor {peer} password {peer_config["password"]}', frrconfig)
+ if 'port' in peer_config:
+ self.assertIn(f' neighbor {peer} port {peer_config["port"]}', frrconfig)
if 'remote_as' in peer_config:
self.assertIn(f' neighbor {peer} remote-as {peer_config["remote_as"]}', frrconfig)
if 'solo' in peer_config:
@@ -238,6 +266,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' no neighbor {peer} send-community extended', frrconfig)
if 'addpath_all' in peer_config:
self.assertIn(f' neighbor {peer} addpath-tx-all-paths', frrconfig)
+ if 'p_attr_discard' in peer_config:
+ self.assertIn(f' neighbor {peer} path-attribute discard {peer_config["p_attr_discard"]}', frrconfig)
if 'addpath_per_as' in peer_config:
self.assertIn(f' neighbor {peer} addpath-tx-bestpath-per-AS', frrconfig)
if 'advertise_map' in peer_config:
@@ -267,6 +297,9 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
max_path_v6ibgp = '16'
cond_adv_timer = '30'
min_hold_time = '2'
+ tcp_keepalive_idle = '66'
+ tcp_keepalive_interval = '77'
+ tcp_keepalive_probes = '22'
self.cli_set(base_path + ['parameters', 'router-id', router_id])
self.cli_set(base_path + ['parameters', 'log-neighbor-changes'])
@@ -294,9 +327,12 @@ 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', 'route-reflector-allow-outbound-policy'])
self.cli_set(base_path + ['parameters', 'shutdown'])
self.cli_set(base_path + ['parameters', 'suppress-fib-pending'])
+ self.cli_set(base_path + ['parameters', 'tcp-keepalive', 'idle', tcp_keepalive_idle])
+ self.cli_set(base_path + ['parameters', 'tcp-keepalive', 'interval', tcp_keepalive_interval])
+ self.cli_set(base_path + ['parameters', 'tcp-keepalive', 'probes', tcp_keepalive_probes])
# AFI maximum path support
self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ebgp', max_path_v4])
@@ -326,6 +362,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
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.assertIn(f' bgp tcp-keepalive {tcp_keepalive_idle} {tcp_keepalive_interval} {tcp_keepalive_probes}', frrconfig)
self.assertNotIn(f'bgp ebgp-requires-policy', frrconfig)
self.assertIn(f' no bgp suppress-duplicates', frrconfig)
@@ -367,6 +404,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:
@@ -387,6 +428,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['neighbor', peer, 'ttl-security', 'hops', peer_config["ttl_security"]])
if 'update_src' in peer_config:
self.cli_set(base_path + ['neighbor', peer, 'update-source', peer_config["update_src"]])
+ if 'p_attr_discard' in peer_config:
+ self.cli_set(base_path + ['neighbor', peer, 'path-attribute', 'discard', peer_config["p_attr_discard"]])
if 'route_map_in' in peer_config:
self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'route-map', 'import', peer_config["route_map_in"]])
if 'route_map_out' in peer_config:
@@ -436,8 +479,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
for peer, peer_config in neighbor_config.items():
if 'adv_interv' in peer_config:
self.assertIn(f' neighbor {peer} advertisement-interval {peer_config["adv_interv"]}', frrconfig)
- if 'port' in peer_config:
- self.assertIn(f' neighbor {peer} port {peer_config["port"]}', frrconfig)
if 'cap_strict' in peer_config:
self.assertIn(f' neighbor {peer} strict-capability-match', frrconfig)
@@ -463,12 +504,18 @@ 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:
self.cli_set(base_path + ['peer-group', peer_group, 'passive'])
if 'password' in config:
self.cli_set(base_path + ['peer-group', peer_group, 'password', config["password"]])
+ if 'port' in config:
+ self.cli_set(base_path + ['peer-group', peer_group, 'port', config["port"]])
if 'remote_as' in config:
self.cli_set(base_path + ['peer-group', peer_group, 'remote-as', config["remote_as"]])
if 'shutdown' in config:
@@ -501,6 +548,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['peer-group', peer_group, 'graceful-restart', 'restart-helper'])
if 'disable_conn_chk' in config:
self.cli_set(base_path + ['peer-group', peer_group, 'disable-connected-check'])
+ if 'p_attr_discard' in config:
+ self.cli_set(base_path + ['peer-group', peer_group, 'path-attribute', 'discard', config["p_attr_discard"]])
# Conditional advertisement
if 'advertise_map' in config:
@@ -664,7 +713,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
for prefix in listen_ranges:
self.assertIn(f' bgp listen range {prefix} peer-group {peer_group}', frrconfig)
-
def test_bgp_07_l2vpn_evpn(self):
vnis = ['10010', '10020', '10030']
neighbors = ['192.0.2.10', '192.0.2.20', '192.0.2.30']
@@ -694,26 +742,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' advertise-default-gw', vniconfig)
self.assertIn(f' advertise-svi-ip', vniconfig)
- def test_bgp_08_zebra_route_map(self):
- # Implemented because of T3328
- self.cli_set(base_path + ['route-map', route_map_in])
- # commit changes
- self.cli_commit()
-
- # Verify FRR configuration
- zebra_route_map = f'ip protocol bgp route-map {route_map_in}'
- frrconfig = self.getFRRconfig(zebra_route_map)
- self.assertIn(zebra_route_map, frrconfig)
-
- # Remove the route-map again
- self.cli_delete(base_path + ['route-map'])
- # commit changes
- self.cli_commit()
-
- # Verify FRR configuration
- frrconfig = self.getFRRconfig(zebra_route_map)
- self.assertNotIn(zebra_route_map, frrconfig)
-
def test_bgp_09_distance_and_flowspec(self):
distance_external = '25'
distance_internal = '30'
@@ -781,7 +809,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_set(vrf_base + ['table', table])
self.cli_set(vrf_base + ['protocols', 'bgp', 'system-as', ASN])
self.cli_set(vrf_base + ['protocols', 'bgp', 'parameters', 'router-id', router_id])
- self.cli_set(vrf_base + ['protocols', 'bgp', 'route-map', route_map_in])
table = str(int(table) + 1000)
# import VRF routes do main RIB
@@ -794,7 +821,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'router bgp {ASN}', frrconfig)
self.assertIn(f' address-family ipv6 unicast', frrconfig)
-
for vrf in vrfs:
self.assertIn(f' import vrf {vrf}', frrconfig)
@@ -803,15 +829,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'router bgp {ASN} vrf {vrf}', frr_vrf_config)
self.assertIn(f' bgp router-id {router_id}', frr_vrf_config)
- # XXX: Currently this is not working as FRR() class does not support
- # route-maps for multiple vrfs because the modify_section() only works
- # on lines and not text blocks.
- #
- # vrfconfig = self.getFRRconfig(f'vrf {vrf}')
- # zebra_route_map = f' ip protocol bgp route-map {route_map_in}'
- # self.assertIn(zebra_route_map, vrfconfig)
-
-
def test_bgp_11_confederation(self):
router_id = '127.10.10.2'
confed_id = str(int(ASN) + 1)
@@ -935,7 +952,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' neighbor {peer_group} remote-as {remote_asn}', frrconfig)
def test_bgp_15_local_as_ebgp(self):
- # https://phabricator.vyos.net/T4560
+ # https://vyos.dev/T4560
# local-as allowed only for ebgp peers
neighbor = '192.0.2.99'
@@ -959,6 +976,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_isis.py b/smoketest/scripts/cli/test_protocols_isis.py
index d11d80a1f..f1a030e77 100755
--- a/smoketest/scripts/cli/test_protocols_isis.py
+++ b/smoketest/scripts/cli/test_protocols_isis.py
@@ -119,39 +119,6 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
self.cli_delete(['vrf', 'name', vrf])
self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf'])
- def test_isis_03_zebra_route_map(self):
- # Implemented because of T3328
- route_map = 'foo-isis-in'
-
- self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit'])
-
- self.isis_base_config()
- self.cli_set(base_path + ['redistribute', 'ipv4', 'connected', 'level-2', 'route-map', route_map])
- self.cli_set(base_path + ['route-map', route_map])
- self.cli_set(base_path + ['level', 'level-2'])
-
- # commit changes
- self.cli_commit()
-
- # Verify FRR configuration
- zebra_route_map = f'ip protocol isis route-map {route_map}'
- frrconfig = self.getFRRconfig(zebra_route_map, daemon='zebra')
- self.assertIn(zebra_route_map, frrconfig)
-
- tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
- self.assertIn(' is-type level-2-only', tmp)
-
- # Remove the route-map again
- self.cli_delete(base_path + ['route-map'])
- # commit changes
- self.cli_commit()
-
- # Verify FRR configuration
- frrconfig = self.getFRRconfig(zebra_route_map, daemon='zebra')
- self.assertNotIn(zebra_route_map, frrconfig)
-
- self.cli_delete(['policy', 'route-map', route_map])
-
def test_isis_04_default_information(self):
metric = '50'
route_map = 'default-foo-'
@@ -293,7 +260,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['segment-routing', 'prefix', prefix_three, 'absolute', 'explicit-null'])
self.cli_set(base_path + ['segment-routing', 'prefix', prefix_four, 'absolute', 'value', prefix_four_value])
self.cli_set(base_path + ['segment-routing', 'prefix', prefix_four, 'absolute', 'no-php-flag'])
-
+
# Commit all changes
self.cli_commit()
@@ -308,5 +275,48 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' segment-routing prefix {prefix_three} absolute {prefix_three_value} explicit-null', tmp)
self.assertIn(f' segment-routing prefix {prefix_four} absolute {prefix_four_value} no-php-flag', tmp)
+ def test_isis_08_ldp_sync(self):
+ holddown = "500"
+ interface = 'lo'
+
+ self.cli_set(base_path + ['net', net])
+ self.cli_set(base_path + ['interface', interface])
+ self.cli_set(base_path + ['ldp-sync', 'holddown', holddown])
+
+ # Commit main ISIS changes
+ self.cli_commit()
+
+ # Verify main ISIS changes
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ self.assertIn(f' net {net}', tmp)
+ self.assertIn(f' mpls ldp-sync', tmp)
+ self.assertIn(f' mpls ldp-sync holddown {holddown}', tmp)
+
+ for interface in self._interfaces:
+ self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'holddown', holddown])
+
+ # Commit interface changes for holddown
+ self.cli_commit()
+
+ # Verify interface changes for holddown
+ tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd')
+ self.assertIn(f'interface {interface}', tmp)
+ self.assertIn(f' ip router isis {domain}', tmp)
+ self.assertIn(f' ipv6 router isis {domain}', tmp)
+ self.assertIn(f' isis mpls ldp-sync holddown {holddown}', tmp)
+
+ for interface in self._interfaces:
+ self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'disable'])
+
+ # Commit interface changes for disable
+ self.cli_commit()
+
+ # Verify interface changes for disable
+ tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd')
+ self.assertIn(f'interface {interface}', tmp)
+ self.assertIn(f' ip router isis {domain}', tmp)
+ self.assertIn(f' ipv6 router isis {domain}', tmp)
+ self.assertIn(f' no isis mpls ldp-sync', tmp)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_protocols_nhrp.py b/smoketest/scripts/cli/test_protocols_nhrp.py
index 59252875b..7dbe836f7 100755
--- a/smoketest/scripts/cli/test_protocols_nhrp.py
+++ b/smoketest/scripts/cli/test_protocols_nhrp.py
@@ -54,7 +54,7 @@ class TestProtocolsNHRP(VyOSUnitTestSHIM.TestCase):
self.cli_set(tunnel_path + [tunnel_if, "address", "172.16.253.134/29"])
self.cli_set(tunnel_path + [tunnel_if, "encapsulation", tunnel_encapsulation])
self.cli_set(tunnel_path + [tunnel_if, "source-address", tunnel_source])
- self.cli_set(tunnel_path + [tunnel_if, "multicast", "enable"])
+ self.cli_set(tunnel_path + [tunnel_if, "enable-multicast"])
self.cli_set(tunnel_path + [tunnel_if, "parameters", "ip", "key", "1"])
# NHRP
diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py
index 339713bf6..6fe6dd979 100755
--- a/smoketest/scripts/cli/test_protocols_ospf.py
+++ b/smoketest/scripts/cli/test_protocols_ospf.py
@@ -74,6 +74,11 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['parameters', 'rfc1583-compatibility'])
self.cli_set(base_path + ['log-adjacency-changes', 'detail'])
self.cli_set(base_path + ['default-metric', metric])
+ self.cli_set(base_path + ['passive-interface', 'default'])
+ self.cli_set(base_path + ['area', '10', 'area-type', 'stub'])
+ self.cli_set(base_path + ['area', '10', 'network', '10.0.0.0/16'])
+ self.cli_set(base_path + ['area', '10', 'range', '10.0.1.0/24'])
+ self.cli_set(base_path + ['area', '10', 'range', '10.0.2.0/24', 'not-advertise'])
# commit changes
self.cli_commit()
@@ -88,6 +93,12 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults
self.assertIn(f' capability opaque', frrconfig)
self.assertIn(f' default-metric {metric}', frrconfig)
+ self.assertIn(f' passive-interface default', frrconfig)
+ self.assertIn(f' area 10 stub', frrconfig)
+ self.assertIn(f' network 10.0.0.0/16 area 10', frrconfig)
+ self.assertIn(f' area 10 range 10.0.1.0/24', frrconfig)
+ self.assertNotIn(f' area 10 range 10.0.1.0/24 not-advertise', frrconfig)
+ self.assertIn(f' area 10 range 10.0.2.0/24 not-advertise', frrconfig)
def test_ospf_03_access_list(self):
@@ -272,6 +283,10 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
# commit changes
self.cli_commit()
+ frrconfig = self.getFRRconfig('router ospf')
+ self.assertIn(f'router ospf', frrconfig)
+ self.assertIn(f' passive-interface default', frrconfig)
+
for interface in interfaces:
config = self.getFRRconfig(f'interface {interface}')
self.assertIn(f'interface {interface}', config)
@@ -285,26 +300,6 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' no ip ospf passive', config)
self.assertIn(f' bandwidth {bandwidth}', config)
- def test_ospf_10_zebra_route_map(self):
- # Implemented because of T3328
- self.cli_set(base_path + ['route-map', route_map])
- # commit changes
- self.cli_commit()
-
- # Verify FRR configuration
- zebra_route_map = f'ip protocol ospf route-map {route_map}'
- frrconfig = self.getFRRconfig(zebra_route_map)
- self.assertIn(zebra_route_map, frrconfig)
-
- # Remove the route-map again
- self.cli_delete(base_path + ['route-map'])
- # commit changes
- self.cli_commit()
-
- # Verify FRR configuration
- frrconfig = self.getFRRconfig(zebra_route_map)
- self.assertNotIn(zebra_route_map, frrconfig)
-
def test_ospf_11_interface_area(self):
area = '0'
interfaces = Section.interfaces('ethernet')
@@ -419,6 +414,47 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' segment-routing prefix {prefix_one} index {prefix_one_value} explicit-null', frrconfig)
self.assertIn(f' segment-routing prefix {prefix_two} index {prefix_two_value} no-php-flag', frrconfig)
+ def test_ospf_15_ldp_sync(self):
+ holddown = "500"
+ interface = 'lo'
+ interfaces = Section.interfaces('ethernet')
+
+ self.cli_set(base_path + ['interface', interface])
+ self.cli_set(base_path + ['ldp-sync', 'holddown', holddown])
+
+ # Commit main OSPF changes
+ self.cli_commit()
+
+ # Verify main OSPF changes
+ frrconfig = self.getFRRconfig('router ospf')
+ self.assertIn(f'router ospf', frrconfig)
+ self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig)
+ self.assertIn(f' mpls ldp-sync holddown {holddown}', frrconfig)
+
+ for interface in interfaces:
+ self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'holddown', holddown])
+
+ # Commit interface changes for holddown
+ self.cli_commit()
+
+ # Verify interface changes for holddown
+ config = self.getFRRconfig(f'interface {interface}')
+ self.assertIn(f'interface {interface}', config)
+ self.assertIn(f' ip ospf dead-interval 40', config)
+ self.assertIn(f' ip ospf mpls ldp-sync', config)
+ self.assertIn(f' ip ospf mpls ldp-sync holddown {holddown}', config)
+
+ for interface in interfaces:
+ self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'disable'])
+
+ # Commit interface changes for disable
+ self.cli_commit()
+
+ # Verify interface changes for disable
+ config = self.getFRRconfig(f'interface {interface}')
+ self.assertIn(f'interface {interface}', config)
+ self.assertIn(f' ip ospf dead-interval 40', config)
+ self.assertIn(f' no ip ospf mpls ldp-sync', config)
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py
index 19efe7786..706663ce5 100755
--- a/smoketest/scripts/cli/test_protocols_static.py
+++ b/smoketest/scripts/cli/test_protocols_static.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
@@ -95,6 +95,7 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
super(TestProtocolsStatic, cls).setUpClass()
+ cls.cli_delete(cls, ['vrf'])
cls.cli_set(cls, ['vrf', 'name', 'black', 'table', '43210'])
@classmethod
@@ -433,30 +434,5 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
self.assertIn(tmp, frrconfig)
- def test_04_static_zebra_route_map(self):
- # Implemented because of T3328
- route_map = 'foo-static-in'
- self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit'])
-
- self.cli_set(base_path + ['route-map', route_map])
- # commit changes
- self.cli_commit()
-
- # Verify FRR configuration
- zebra_route_map = f'ip protocol static route-map {route_map}'
- frrconfig = self.getFRRconfig(zebra_route_map)
- self.assertIn(zebra_route_map, frrconfig)
-
- # Remove the route-map again
- self.cli_delete(base_path + ['route-map'])
- # commit changes
- self.cli_commit()
-
- # Verify FRR configuration
- frrconfig = self.getFRRconfig(zebra_route_map)
- self.assertNotIn(zebra_route_map, frrconfig)
-
- self.cli_delete(['policy', 'route-map', route_map])
-
if __name__ == '__main__':
unittest.main(verbosity=2)
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-relay.py b/smoketest/scripts/cli/test_service_dhcp-relay.py
index bbfd9e032..92f87c06c 100755
--- a/smoketest/scripts/cli/test_service_dhcp-relay.py
+++ b/smoketest/scripts/cli/test_service_dhcp-relay.py
@@ -82,6 +82,43 @@ class TestServiceDHCPRelay(VyOSUnitTestSHIM.TestCase):
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
+ def test_relay_interfaces(self):
+ max_size = '800'
+ hop_count = '20'
+ agents_packets = 'append'
+ servers = ['192.0.2.1', '192.0.2.2']
+ listen_iface = 'eth0'
+ up_iface = 'eth1'
+
+ self.cli_set(base_path + ['interface', up_iface])
+ self.cli_set(base_path + ['listen-interface', listen_iface])
+ # check validate() - backward interface plus listen_interface
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(base_path + ['interface'])
+
+ self.cli_set(base_path + ['upstream-interface', up_iface])
+
+ for server in servers:
+ self.cli_set(base_path + ['server', server])
+
+ # commit changes
+ self.cli_commit()
+
+ # Check configured port
+ config = read_file(RELAY_CONF)
+
+ # Test configured relay interfaces
+ self.assertIn(f'-id {listen_iface}', config)
+ self.assertIn(f'-iu {up_iface}', config)
+
+ # Test relay servers
+ for server in servers:
+ self.assertIn(f' {server}', config)
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
if __name__ == '__main__':
unittest.main(verbosity=2)
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_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py
index 90d10d40b..57705e26f 100755
--- a/smoketest/scripts/cli/test_service_dns_dynamic.py
+++ b/smoketest/scripts/cli/test_service_dns_dynamic.py
@@ -155,7 +155,7 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
self.assertEqual(login, user)
self.assertEqual(pwd, f"'{password}'")
self.assertEqual(server, srv)
- self.assertEqual(usev6, f"if, if={interface}")
+ self.assertEqual(usev6, f"ifv6, if={interface}")
if __name__ == '__main__':
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 94e0597ad..88492e348 100755
--- a/smoketest/scripts/cli/test_service_dns_forwarding.py
+++ b/smoketest/scripts/cli/test_service_dns_forwarding.py
@@ -20,6 +20,7 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
+from vyos.template import bracketize_ipv6
from vyos.util import read_file
from vyos.util import process_named_running
@@ -141,15 +142,20 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase):
for address in listen_adress:
self.cli_set(base_path + ['listen-address', address])
- nameservers = ['192.0.2.1', '192.0.2.2']
- for nameserver in nameservers:
- self.cli_set(base_path + ['name-server', nameserver])
+ nameservers = {'192.0.2.1': {}, '192.0.2.2': {'port': '53'}, '2001:db8::1': {'port': '853'}}
+ for h,p in nameservers.items():
+ if 'port' in p:
+ self.cli_set(base_path + ['name-server', h, 'port', p['port']])
+ else:
+ self.cli_set(base_path + ['name-server', h])
# commit changes
self.cli_commit()
tmp = get_config_value(r'\+.', file=FORWARD_FILE)
- self.assertEqual(tmp, ', '.join(nameservers))
+ canonical_entries = [(lambda h, p: f"{bracketize_ipv6(h)}:{p['port'] if 'port' in p else 53}")(h, p)
+ for (h, p) in nameservers.items()]
+ self.assertEqual(tmp, ', '.join(canonical_entries))
# Do not use local /etc/hosts file in name resolution
# default: yes
@@ -163,10 +169,13 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['listen-address', address])
domains = ['vyos.io', 'vyos.net', 'vyos.com']
- nameservers = ['192.0.2.1', '192.0.2.2']
+ nameservers = {'192.0.2.1': {}, '192.0.2.2': {'port': '53'}, '2001:db8::1': {'port': '853'}}
for domain in domains:
- for nameserver in nameservers:
- self.cli_set(base_path + ['domain', domain, 'server', nameserver])
+ for h,p in nameservers.items():
+ if 'port' in p:
+ self.cli_set(base_path + ['domain', domain, 'name-server', h, 'port', p['port']])
+ else:
+ self.cli_set(base_path + ['domain', domain, 'name-server', h])
# Test 'recursion-desired' flag for only one domain
if domain == domains[0]:
@@ -186,7 +195,9 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase):
if domain == domains[0]: key =f'\+{domain}'
else: key =f'{domain}'
tmp = get_config_value(key, file=FORWARD_FILE)
- self.assertEqual(tmp, ', '.join(nameservers))
+ canonical_entries = [(lambda h, p: f"{bracketize_ipv6(h)}:{p['port'] if 'port' in p else 53}")(h, p)
+ for (h, p) in nameservers.items()]
+ self.assertEqual(tmp, ', '.join(canonical_entries))
# Test 'negative trust anchor' flag for the second domain only
if domain == domains[1]:
diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py
index 0f4b1393c..1adf1f5cf 100755
--- a/smoketest/scripts/cli/test_service_https.py
+++ b/smoketest/scripts/cli/test_service_https.py
@@ -193,7 +193,8 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase):
"""
r = request('POST', graphql_url, verify=False, headers=headers, json={'query': query_no_key})
- self.assertEqual(r.status_code, 400)
+ success = r.json()['data']['SystemStatus']['success']
+ self.assertFalse(success)
# GraphQL token authentication test: request token; pass in header
# of query.
diff --git a/smoketest/scripts/cli/test_service_ipoe-server.py b/smoketest/scripts/cli/test_service_ipoe-server.py
index bdab35834..8a141b8f0 100755
--- a/smoketest/scripts/cli/test_service_ipoe-server.py
+++ b/smoketest/scripts/cli/test_service_ipoe-server.py
@@ -26,6 +26,13 @@ from configparser import ConfigParser
ac_name = 'ACN'
interface = 'eth0'
+
+def getConfig(string, end='cli'):
+ command = f'cat /run/accel-pppd/ipoe.conf | sed -n "/^{string}/,/^{end}/p"'
+ out = cmd(command)
+ return out
+
+
class TestServiceIPoEServer(BasicAccelPPPTest.TestCase):
@classmethod
def setUpClass(cls):
@@ -86,6 +93,92 @@ class TestServiceIPoEServer(BasicAccelPPPTest.TestCase):
tmp = re.findall(regex, tmp)
self.assertTrue(tmp)
+ def test_accel_named_pool(self):
+ first_pool = 'VyOS-pool1'
+ first_subnet = '192.0.2.0/25'
+ first_gateway = '192.0.2.1'
+ second_pool = 'Vyos-pool2'
+ second_subnet = '203.0.113.0/25'
+ second_gateway = '203.0.113.1'
+
+ self.set(['authentication', 'mode', 'noauth'])
+ self.set(['client-ip-pool', 'name', first_pool, 'gateway-address', first_gateway])
+ self.set(['client-ip-pool', 'name', first_pool, 'subnet', first_subnet])
+ self.set(['client-ip-pool', 'name', second_pool, 'gateway-address', second_gateway])
+ self.set(['client-ip-pool', 'name', second_pool, 'subnet', second_subnet])
+ self.set(['interface', interface])
+
+ # commit changes
+ self.cli_commit()
+
+
+ # Validate configuration values
+ conf = ConfigParser(allow_no_value=True, delimiters='=', strict=False)
+ conf.read(self._config_file)
+
+ self.assertTrue(conf['ipoe']['interface'], f'{interface},shared=1,mode=L2,ifcfg=1,start=dhcpv4,ipv6=1')
+ self.assertTrue(conf['ipoe']['noauth'], '1')
+ self.assertTrue(conf['ipoe']['ip-pool'], first_pool)
+ self.assertTrue(conf['ipoe']['ip-pool'], second_pool)
+ self.assertTrue(conf['ipoe']['gw-ip-address'], f'{first_gateway}/25')
+ self.assertTrue(conf['ipoe']['gw-ip-address'], f'{second_gateway}/25')
+
+ config = getConfig('[ip-pool]')
+ pool_config = f'''{second_subnet},name={second_pool}
+{first_subnet},name={first_pool}
+gw-ip-address={second_gateway}/25
+gw-ip-address={first_gateway}/25'''
+ self.assertIn(pool_config, config)
+
+
+ def test_accel_next_pool(self):
+ first_pool = 'VyOS-pool1'
+ first_subnet = '192.0.2.0/25'
+ first_gateway = '192.0.2.1'
+ second_pool = 'Vyos-pool2'
+ second_subnet = '203.0.113.0/25'
+ second_gateway = '203.0.113.1'
+ third_pool = 'Vyos-pool3'
+ third_subnet = '198.51.100.0/24'
+ third_gateway = '198.51.100.1'
+
+ self.set(['authentication', 'mode', 'noauth'])
+ self.set(['client-ip-pool', 'name', first_pool, 'gateway-address', first_gateway])
+ self.set(['client-ip-pool', 'name', first_pool, 'subnet', first_subnet])
+ self.set(['client-ip-pool', 'name', first_pool, 'next-pool', second_pool])
+ self.set(['client-ip-pool', 'name', second_pool, 'gateway-address', second_gateway])
+ self.set(['client-ip-pool', 'name', second_pool, 'subnet', second_subnet])
+ self.set(['client-ip-pool', 'name', second_pool, 'next-pool', third_pool])
+ self.set(['client-ip-pool', 'name', third_pool, 'gateway-address', third_gateway])
+ self.set(['client-ip-pool', 'name', third_pool, 'subnet', third_subnet])
+ self.set(['interface', interface])
+
+ # commit changes
+ self.cli_commit()
+
+
+ # Validate configuration values
+ conf = ConfigParser(allow_no_value=True, delimiters='=', strict=False)
+ conf.read(self._config_file)
+
+ self.assertTrue(conf['ipoe']['interface'], f'{interface},shared=1,mode=L2,ifcfg=1,start=dhcpv4,ipv6=1')
+ self.assertTrue(conf['ipoe']['noauth'], '1')
+ self.assertTrue(conf['ipoe']['ip-pool'], first_pool)
+ self.assertTrue(conf['ipoe']['gw-ip-address'], f'{first_gateway}/25')
+ self.assertTrue(conf['ipoe']['gw-ip-address'], f'{second_gateway}/25')
+ self.assertTrue(conf['ipoe']['gw-ip-address'], f'{third_gateway}/24')
+
+ config = getConfig('[ip-pool]')
+ # T5099 required specific order
+ pool_config = f'''{third_subnet},name={third_pool}
+{second_subnet},name={second_pool},next={third_pool}
+{first_subnet},name={first_pool},next={second_pool}
+gw-ip-address={third_gateway}/24
+gw-ip-address={second_gateway}/25
+gw-ip-address={first_gateway}/25'''
+ self.assertIn(pool_config, config)
+
+
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..046e5eea6 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 = ['nts', 'noselect', 'prefer']
pools = ['pool.vyos.io']
for server in servers:
@@ -61,12 +61,15 @@ 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('ntsdumpdir /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 +83,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 +98,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 +119,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_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py
index 7546c2e3d..4f9181704 100755
--- a/smoketest/scripts/cli/test_service_pppoe-server.py
+++ b/smoketest/scripts/cli/test_service_pppoe-server.py
@@ -143,6 +143,9 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):
self.basic_config()
subnet = '172.18.0.0/24'
+ fwmark = '223'
+ limiter = 'htb'
+
self.set(['client-ip-pool', 'subnet', subnet])
start = '192.0.2.10'
@@ -151,6 +154,7 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):
start_stop = f'{start}-{stop_octet}'
self.set(['client-ip-pool', 'start', start])
self.set(['client-ip-pool', 'stop', stop])
+ self.set(['shaper', 'fwmark', fwmark])
# commit changes
self.cli_commit()
@@ -163,6 +167,37 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):
self.assertEqual(conf['ip-pool'][subnet], None)
self.assertEqual(conf['ip-pool'][start_stop], None)
self.assertEqual(conf['ip-pool']['gw-ip-address'], self._gateway)
+ self.assertEqual(conf['shaper']['fwmark'], fwmark)
+ self.assertEqual(conf['shaper']['down-limiter'], limiter)
+
+
+ def test_pppoe_server_client_ip_pool_name(self):
+ # Test configuration of named client pools
+ self.basic_config()
+
+ subnet = '192.0.2.0/24'
+ gateway = '192.0.2.1'
+ pool = 'VYOS'
+
+ subnet_name = f'{subnet},name'
+ gw_ip_prefix = f'{gateway}/24'
+
+ self.set(['client-ip-pool', 'name', pool, 'subnet', subnet])
+ self.set(['client-ip-pool', 'name', pool, 'gateway-address', gateway])
+ self.cli_delete(self._base_path + ['gateway-address'])
+
+ # commit changes
+ self.cli_commit()
+
+ # Validate configuration values
+ conf = ConfigParser(allow_no_value=True, delimiters='=')
+ conf.read(self._config_file)
+
+ # Validate configuration
+ self.assertEqual(conf['ip-pool'][subnet_name], pool)
+ self.assertEqual(conf['ip-pool']['gw-ip-address'], gateway)
+ self.assertEqual(conf['pppoe']['ip-pool'], pool)
+ self.assertEqual(conf['pppoe']['gw-ip-address'], gw_ip_prefix)
def test_pppoe_server_client_ipv6_pool(self):
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_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_system_ip.py b/smoketest/scripts/cli/test_system_ip.py
index f71ef5b3f..e7f7e3345 100755
--- a/smoketest/scripts/cli/test_system_ip.py
+++ b/smoketest/scripts/cli/test_system_ip.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
@@ -17,6 +17,7 @@
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.configsession import ConfigSessionError
from vyos.util import read_file
base_path = ['system', 'ip']
@@ -82,5 +83,31 @@ class TestSystemIP(VyOSUnitTestSHIM.TestCase):
self.assertEqual(read_file(gc_thresh2), str(size // 2))
self.assertEqual(read_file(gc_thresh1), str(size // 8))
+ def test_system_ip_protocol_route_map(self):
+ protocols = ['any', 'babel', 'bgp', 'connected', 'eigrp', 'isis',
+ 'kernel', 'ospf', 'rip', 'static', 'table']
+
+ for protocol in protocols:
+ self.cli_set(['policy', 'route-map', f'route-map-{protocol}', 'rule', '10', 'action', 'permit'])
+ self.cli_set(base_path + ['protocol', protocol, 'route-map', f'route-map-{protocol}'])
+
+ self.cli_commit()
+
+ # Verify route-map properly applied to FRR
+ frrconfig = self.getFRRconfig('ip protocol', end='', daemon='zebra')
+ for protocol in protocols:
+ self.assertIn(f'ip protocol {protocol} route-map route-map-{protocol}', frrconfig)
+
+ def test_system_ip_protocol_non_existing_route_map(self):
+ non_existing = 'non-existing'
+ self.cli_set(base_path + ['protocol', 'static', 'route-map', non_existing])
+
+ # VRF does yet not exist - an error must be thrown
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(['policy', 'route-map', non_existing, 'rule', '10', 'action', 'deny'])
+ # Commit again
+ self.cli_commit()
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py
index c8aea9100..e91b924fc 100755
--- a/smoketest/scripts/cli/test_system_ipv6.py
+++ b/smoketest/scripts/cli/test_system_ipv6.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
@@ -18,6 +18,7 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.configsession import ConfigSessionError
from vyos.template import is_ipv4
from vyos.util import read_file
from vyos.util import get_interface_config
@@ -88,5 +89,36 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase):
self.assertEqual(read_file(gc_thresh2), str(size // 2))
self.assertEqual(read_file(gc_thresh1), str(size // 8))
+ def test_system_ipv6_protocol_route_map(self):
+ protocols = ['any', 'babel', 'bgp', 'connected', 'isis',
+ 'kernel', 'ospfv3', 'ripng', 'static', 'table']
+
+ for protocol in protocols:
+ route_map = 'route-map-' + protocol.replace('ospfv3', 'ospf6')
+
+ self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit'])
+ self.cli_set(base_path + ['protocol', protocol, 'route-map', route_map])
+
+ self.cli_commit()
+
+ # Verify route-map properly applied to FRR
+ frrconfig = self.getFRRconfig('ipv6 protocol', end='', daemon='zebra')
+ for protocol in protocols:
+ # VyOS and FRR use a different name for OSPFv3 (IPv6)
+ if protocol == 'ospfv3':
+ protocol = 'ospf6'
+ self.assertIn(f'ipv6 protocol {protocol} route-map route-map-{protocol}', frrconfig)
+
+ def test_system_ipv6_protocol_non_existing_route_map(self):
+ non_existing = 'non-existing6'
+ self.cli_set(base_path + ['protocol', 'static', 'route-map', non_existing])
+
+ # VRF does yet not exist - an error must be thrown
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(['policy', 'route-map', non_existing, 'rule', '10', 'action', 'deny'])
+ # Commit again
+ self.cli_commit()
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_system_login.py b/smoketest/scripts/cli/test_system_login.py
index 6006fe0f6..a1d2ba2ad 100755
--- a/smoketest/scripts/cli/test_system_login.py
+++ b/smoketest/scripts/cli/test_system_login.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
@@ -264,5 +264,26 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase):
tmp = re.findall(r'group:\s+mapname\s+files', nsswitch_conf)
self.assertTrue(tmp)
+ def test_system_login_max_login_session(self):
+ max_logins = '2'
+ timeout = '600'
+
+ self.cli_set(base_path + ['max-login-session', max_logins])
+
+ # 'max-login-session' must be only with 'timeout' option
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_set(base_path + ['timeout', timeout])
+
+ self.cli_commit()
+
+ security_limits = read_file('/etc/security/limits.d/10-vyos.conf')
+ self.assertIn(f'* - maxsyslogins {max_logins}', security_limits)
+
+ self.cli_delete(base_path + ['timeout'])
+ self.cli_delete(base_path + ['max-login-session'])
+
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_system_sflow.py b/smoketest/scripts/cli/test_system_sflow.py
new file mode 100755
index 000000000..1aec050a4
--- /dev/null
+++ b/smoketest/scripts/cli/test_system_sflow.py
@@ -0,0 +1,101 @@
+#!/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 base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.configsession import ConfigSessionError
+from vyos.ifconfig import Section
+from vyos.util import cmd
+from vyos.util import process_named_running
+from vyos.util import read_file
+
+PROCESS_NAME = 'hsflowd'
+base_path = ['system', 'sflow']
+
+hsflowd_conf = '/run/sflow/hsflowd.conf'
+
+
+class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestSystemFlowAccounting, 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)
+
+ def tearDown(self):
+ # after service removal process must no longer run
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ # after service removal process must no longer run
+ self.assertFalse(process_named_running(PROCESS_NAME))
+
+ def test_sflow(self):
+ agent_address = '192.0.2.5'
+ agent_interface = 'eth0'
+ polling = '24'
+ sampling_rate = '128'
+ server = '192.0.2.254'
+ local_server = '127.0.0.1'
+ port = '8192'
+ default_port = '6343'
+ mon_limit = '50'
+
+ self.cli_set(
+ ['interfaces', 'dummy', 'dum0', 'address', f'{agent_address}/24'])
+ self.cli_set(base_path + ['agent-address', agent_address])
+ self.cli_set(base_path + ['agent-interface', agent_interface])
+
+ # You need to configure at least one interface for sflow
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ for interface in Section.interfaces('ethernet'):
+ self.cli_set(base_path + ['interface', interface])
+
+ self.cli_set(base_path + ['polling', polling])
+ self.cli_set(base_path + ['sampling-rate', sampling_rate])
+ self.cli_set(base_path + ['server', server, 'port', port])
+ self.cli_set(base_path + ['server', local_server])
+ self.cli_set(base_path + ['drop-monitor-limit', mon_limit])
+
+ # commit changes
+ self.cli_commit()
+
+ # verify configuration
+ hsflowd = read_file(hsflowd_conf)
+
+ self.assertIn(f'polling={polling}', hsflowd)
+ self.assertIn(f'sampling={sampling_rate}', hsflowd)
+ self.assertIn(f'agentIP={agent_address}', hsflowd)
+ self.assertIn(f'agent={agent_interface}', hsflowd)
+ self.assertIn(f'collector {{ ip = {server} udpport = {port} }}', hsflowd)
+ self.assertIn(f'collector {{ ip = {local_server} udpport = {default_port} }}', hsflowd)
+ self.assertIn(f'dropmon {{ limit={mon_limit} start=on sw=on hw=off }}', hsflowd)
+ self.assertIn('dbus { }', hsflowd)
+
+ for interface in Section.interfaces('ethernet'):
+ self.assertIn(f'pcap {{ dev={interface} }}', hsflowd)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py
index bd242104f..b677f0e45 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
@@ -113,6 +117,8 @@ rgiyCHemtMepq57Pl1Nmj49eEA==
"""
class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
+ skip_process_check = False
+
@classmethod
def setUpClass(cls):
super(TestVPNIPsec, cls).setUpClass()
@@ -137,23 +143,34 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
def tearDown(self):
# Check for running process
- self.assertTrue(process_named_running('charon'))
+ if not self.skip_process_check:
+ self.assertTrue(process_named_running(PROCESS_NAME))
+ else:
+ self.skip_process_check = False # Reset
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):
+ # Skip process check - connection is not created for this test
+ self.skip_process_check = True
+
# 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 +183,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 +253,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 +274,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 +325,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):
@@ -310,7 +343,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
self.cli_set(tunnel_path + [tunnel_if, 'address', '172.16.253.134/29'])
self.cli_set(tunnel_path + [tunnel_if, 'encapsulation', 'gre'])
self.cli_set(tunnel_path + [tunnel_if, 'source-address', '192.0.2.1'])
- self.cli_set(tunnel_path + [tunnel_if, 'multicast', 'enable'])
+ self.cli_set(tunnel_path + [tunnel_if, 'enable-multicast'])
self.cli_set(tunnel_path + [tunnel_if, 'parameters', 'ip', 'key', '1'])
# NHRP
@@ -334,6 +367,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 +380,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 +484,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 +522,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/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py
index 176c095fb..926616727 100755
--- a/smoketest/scripts/cli/test_vrf.py
+++ b/smoketest/scripts/cli/test_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
@@ -33,6 +33,8 @@ from vyos.validate import is_intf_addr_assigned
base_path = ['vrf']
vrfs = ['red', 'green', 'blue', 'foo-bar', 'baz_foo']
+v4_protocols = ['any', 'babel', 'bgp', 'connected', 'eigrp', 'isis', 'kernel', 'ospf', 'rip', 'static', 'table']
+v6_protocols = ['any', 'babel', 'bgp', 'connected', 'isis', 'kernel', 'ospfv3', 'ripng', 'static', 'table']
class VRFTest(VyOSUnitTestSHIM.TestCase):
_interfaces = []
@@ -59,7 +61,8 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
self.assertNotIn(vrf, interfaces())
def test_vrf_vni_and_table_id(self):
- table = '1000'
+ base_table = '1000'
+ table = base_table
for vrf in vrfs:
base = base_path + ['name', vrf]
description = f'VyOS-VRF-{vrf}'
@@ -80,7 +83,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify VRF configuration
- table = '1000'
+ table = base_table
iproute2_config = read_file('/etc/iproute2/rt_tables.d/vyos-vrf.conf')
for vrf in vrfs:
description = f'VyOS-VRF-{vrf}'
@@ -194,7 +197,8 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
self.cli_delete(['interfaces', section, interface, 'vrf'])
def test_vrf_static_route(self):
- table = '100'
+ base_table = '100'
+ table = base_table
for vrf in vrfs:
next_hop = f'192.0.{table}.1'
prefix = f'10.0.{table}.0/24'
@@ -215,13 +219,12 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify VRF configuration
- table = '100'
+ table = base_table
for vrf in vrfs:
next_hop = f'192.0.{table}.1'
prefix = f'10.0.{table}.0/24'
self.assertTrue(vrf in interfaces())
- vrf_if = Interface(vrf)
frrconfig = self.getFRRconfig(f'vrf {vrf}')
self.assertIn(f' vni {table}', frrconfig)
@@ -291,5 +294,174 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
self.assertEqual(read_file(f'/proc/sys/net/ipv4/conf/{vrf}/forwarding'), '0')
self.assertEqual(read_file(f'/proc/sys/net/ipv6/conf/{vrf}/forwarding'), '0')
+ def test_vrf_ip_protocol_route_map(self):
+ table = '6000'
+
+ for vrf in vrfs:
+ base = base_path + ['name', vrf]
+ self.cli_set(base + ['table', table])
+
+ for protocol in v4_protocols:
+ self.cli_set(['policy', 'route-map', f'route-map-{vrf}-{protocol}', 'rule', '10', 'action', 'permit'])
+ self.cli_set(base + ['ip', 'protocol', protocol, 'route-map', f'route-map-{vrf}-{protocol}'])
+
+ table = str(int(table) + 1)
+
+ self.cli_commit()
+
+ # Verify route-map properly applied to FRR
+ for vrf in vrfs:
+ frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra')
+ self.assertIn(f'vrf {vrf}', frrconfig)
+ for protocol in v4_protocols:
+ self.assertIn(f' ip protocol {protocol} route-map route-map-{vrf}-{protocol}', frrconfig)
+
+ def test_vrf_ip_ipv6_protocol_non_existing_route_map(self):
+ table = '6100'
+ non_existing = 'non-existing'
+
+ for vrf in vrfs:
+ base = base_path + ['name', vrf]
+ self.cli_set(base + ['table', table])
+ for protocol in v4_protocols:
+ self.cli_set(base + ['ip', 'protocol', protocol, 'route-map', f'v4-{non_existing}'])
+ for protocol in v6_protocols:
+ self.cli_set(base + ['ipv6', 'protocol', protocol, 'route-map', f'v6-{non_existing}'])
+
+ table = str(int(table) + 1)
+
+ # Both v4 and v6 route-maps do not exist yet
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(['policy', 'route-map', f'v4-{non_existing}', 'rule', '10', 'action', 'deny'])
+
+ # v6 route-map does not exist yet
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(['policy', 'route-map', f'v6-{non_existing}', 'rule', '10', 'action', 'deny'])
+
+ # Commit again
+ self.cli_commit()
+
+ def test_vrf_ipv6_protocol_route_map(self):
+ table = '6200'
+
+ for vrf in vrfs:
+ base = base_path + ['name', vrf]
+ self.cli_set(base + ['table', table])
+
+ for protocol in v6_protocols:
+ route_map = f'route-map-{vrf}-{protocol.replace("ospfv3", "ospf6")}'
+ self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit'])
+ self.cli_set(base + ['ipv6', 'protocol', protocol, 'route-map', route_map])
+
+ table = str(int(table) + 1)
+
+ self.cli_commit()
+
+ # Verify route-map properly applied to FRR
+ for vrf in vrfs:
+ frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra')
+ self.assertIn(f'vrf {vrf}', frrconfig)
+ for protocol in v6_protocols:
+ # VyOS and FRR use a different name for OSPFv3 (IPv6)
+ if protocol == 'ospfv3':
+ protocol = 'ospf6'
+ route_map = f'route-map-{vrf}-{protocol}'
+ self.assertIn(f' ipv6 protocol {protocol} route-map {route_map}', frrconfig)
+
+ def test_vrf_vni_duplicates(self):
+ base_table = '6300'
+ table = base_table
+ for vrf in vrfs:
+ base = base_path + ['name', vrf]
+ self.cli_set(base + ['table', str(table)])
+ self.cli_set(base + ['vni', '100'])
+ table = str(int(table) + 1)
+
+ # L3VNIs can only be used once
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ table = base_table
+ for vrf in vrfs:
+ base = base_path + ['name', vrf]
+ self.cli_set(base + ['vni', str(table)])
+ table = str(int(table) + 1)
+
+ # commit changes
+ self.cli_commit()
+
+ # Verify VRF configuration
+ table = base_table
+ for vrf in vrfs:
+ self.assertTrue(vrf in interfaces())
+
+ frrconfig = self.getFRRconfig(f'vrf {vrf}')
+ self.assertIn(f' vni {table}', frrconfig)
+ # Increment table ID for the next run
+ table = str(int(table) + 1)
+
+ def test_vrf_vni_add_change_remove(self):
+ base_table = '6300'
+ table = base_table
+ for vrf in vrfs:
+ base = base_path + ['name', vrf]
+ self.cli_set(base + ['table', str(table)])
+ self.cli_set(base + ['vni', str(table)])
+ table = str(int(table) + 1)
+
+ # commit changes
+ self.cli_commit()
+
+ # Verify VRF configuration
+ table = base_table
+ for vrf in vrfs:
+ self.assertTrue(vrf in interfaces())
+
+ frrconfig = self.getFRRconfig(f'vrf {vrf}')
+ self.assertIn(f' vni {table}', frrconfig)
+ # Increment table ID for the next run
+ table = str(int(table) + 1)
+
+ # Now change all L3VNIs (increment 2)
+ # We must also change the base_table number as we probably could get
+ # duplicate VNI's during the test as VNIs are applied 1:1 to FRR
+ base_table = '5000'
+ table = base_table
+ for vrf in vrfs:
+ base = base_path + ['name', vrf]
+ self.cli_set(base + ['vni', str(table)])
+ table = str(int(table) + 2)
+
+ # commit changes
+ self.cli_commit()
+
+ # Verify VRF configuration
+ table = base_table
+ for vrf in vrfs:
+ self.assertTrue(vrf in interfaces())
+
+ frrconfig = self.getFRRconfig(f'vrf {vrf}')
+ self.assertIn(f' vni {table}', frrconfig)
+ # Increment table ID for the next run
+ table = str(int(table) + 2)
+
+ # Now delete all the VNIs
+ for vrf in vrfs:
+ base = base_path + ['name', vrf]
+ self.cli_delete(base + ['vni'])
+
+ # commit changes
+ self.cli_commit()
+
+ # Verify no VNI is defined
+ for vrf in vrfs:
+ self.assertTrue(vrf in interfaces())
+
+ frrconfig = self.getFRRconfig(f'vrf {vrf}')
+ self.assertNotIn('vni', frrconfig)
+
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/system/test_kernel_options.py b/smoketest/scripts/system/test_kernel_options.py
index 4d9cbacbe..94be0483a 100755
--- a/smoketest/scripts/system/test_kernel_options.py
+++ b/smoketest/scripts/system/test_kernel_options.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
@@ -14,14 +14,19 @@
# 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 gzip
import re
+import os
import platform
import unittest
+from vyos.util import call
from vyos.util import read_file
kernel = platform.release()
config = read_file(f'/boot/config-{kernel}')
+CONFIG = '/proc/config.gz'
+
class TestKernelModules(unittest.TestCase):
""" VyOS makes use of a lot of Kernel drivers, modules and features. The
@@ -42,6 +47,22 @@ class TestKernelModules(unittest.TestCase):
tmp = re.findall(f'{option}=(y|m)', config)
self.assertTrue(tmp)
+ def test_dropmon_enabled(self):
+ options_to_check = [
+ 'CONFIG_NET_DROP_MONITOR=y',
+ 'CONFIG_UPROBE_EVENTS=y',
+ 'CONFIG_BPF_EVENTS=y',
+ 'CONFIG_TRACEPOINTS=y'
+ ]
+ if not os.path.isfile(CONFIG):
+ call('sudo modprobe configs')
+
+ with gzip.open(CONFIG, 'rt') as f:
+ config_data = f.read()
+ for option in options_to_check:
+ self.assertIn(option, config_data,
+ f"Option {option} is not present in /proc/config.gz")
+
def test_qemu_support(self):
# The bond/lacp interface must be enabled in the OS Kernel
for option in ['CONFIG_VIRTIO_BLK', 'CONFIG_SCSI_VIRTIO',
@@ -58,6 +79,7 @@ class TestKernelModules(unittest.TestCase):
tmp = re.findall(f'{option}=(y|m)', config)
self.assertTrue(tmp)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
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/sonar-project.properties b/sonar-project.properties
index 1258da817..8ff358515 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -1,7 +1,8 @@
-sonar.projectKey=vyos:vyos-1x
+sonar.projectKey=vyos_vyos-1x
sonar.projectName=vyos-1x
sonar.projectVersion=1.2.0
sonar.organization=vyos
+sonar.python.version=3.9
sonar.sources=src/conf_mode,src/op_mode,src/completion,src/helpers,src/validators
sonar.language=py
@@ -10,12 +11,11 @@ sonar.sourceEncoding=UTF-8
sonar.links.homepage=https://github.com/vyos/vyos-1x
sonar.links.ci=https://ci.vyos.net/job/vyos-1x/
sonar.links.scm=https://github.com/vyos/vyos-1x
-sonar.links.issue=https://phabricator.vyos.net/
+sonar.links.issue=https://vyos.dev/
sonar.host.url=https://sonarcloud.io
sonar.python.pylint=/usr/local/bin/pylint
sonar.python.pylint_config=.pylintrc
sonar.python.pylint.reportPath=pylint-report.txt
-sonar.python.xunit.reportPath=nosetests.xml
-sonar.python.coverage.reportPath=coverage.xml
+sonar.python.coverage.reportPaths=coverage.xml
diff --git a/src/completion/list_interfaces.py b/src/completion/list_interfaces.py
deleted file mode 100755
index b19b90156..000000000
--- a/src/completion/list_interfaces.py
+++ /dev/null
@@ -1,54 +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 sys
-import argparse
-from vyos.ifconfig import Section
-
-def matching(feature):
- for section in Section.feature(feature):
- for intf in Section.interfaces(section):
- yield intf
-
-parser = argparse.ArgumentParser()
-group = parser.add_mutually_exclusive_group()
-group.add_argument("-t", "--type", type=str, help="List interfaces of specific type")
-group.add_argument("-b", "--broadcast", action="store_true", help="List all broadcast interfaces")
-group.add_argument("-br", "--bridgeable", action="store_true", help="List all bridgeable interfaces")
-group.add_argument("-bo", "--bondable", action="store_true", help="List all bondable interfaces")
-
-args = parser.parse_args()
-
-if args.type:
- try:
- interfaces = Section.interfaces(args.type)
- print(" ".join(interfaces))
- except ValueError as e:
- print(e, file=sys.stderr)
- print("")
-
-elif args.broadcast:
- print(" ".join(matching("broadcast")))
-
-elif args.bridgeable:
- print(" ".join(matching("bridgeable")))
-
-elif args.bondable:
- # we need to filter out VLAN interfaces identified by a dot (.) in their name
- print(" ".join([intf for intf in matching("bondable") if '.' not in intf]))
-
-else:
- print(" ".join(Section.interfaces()))
diff --git a/src/completion/list_ipsec_profile_tunnels.py b/src/completion/list_ipsec_profile_tunnels.py
new file mode 100644
index 000000000..df6c52f6d
--- /dev/null
+++ b/src/completion/list_ipsec_profile_tunnels.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+#
+# 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
+# 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
+import argparse
+
+from vyos.config import Config
+from vyos.util import dict_search
+
+def get_tunnels_from_ipsecprofile(profile):
+ config = Config()
+ base = ['vpn', 'ipsec', 'profile', profile, 'bind']
+ profile_conf = config.get_config_dict(base, effective=True, key_mangling=('-', '_'))
+ tunnels = []
+
+ try:
+ for tunnel in (dict_search('bind.tunnel', profile_conf) or []):
+ tunnels.append(tunnel)
+ except:
+ pass
+
+ return tunnels
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-p", "--profile", type=str, help="List tunnels per profile")
+ args = parser.parse_args()
+
+ tunnels = []
+
+ tunnels = get_tunnels_from_ipsecprofile(args.profile)
+
+ print(" ".join(tunnels))
+
diff --git a/src/completion/list_ntp_servers.sh b/src/completion/list_ntp_servers.sh
deleted file mode 100755
index d0977fbd6..000000000
--- a/src/completion/list_ntp_servers.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-
-# Completion script used to select specific NTP server
-/bin/cli-shell-api -- listEffectiveNodes system ntp server | sed "s/'//g"
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 8efeaed54..aceb27fb0 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.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
@@ -16,18 +16,22 @@
import os
+from hashlib import sha256
from ipaddress import ip_address
from ipaddress import ip_network
-from time import sleep
from json import dumps as json_write
from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
+from vyos.configdict import is_node_changed
+from vyos.configverify import verify_vrf
+from vyos.ifconfig import Interface
from vyos.util import call
from vyos.util import cmd
from vyos.util import run
+from vyos.util import rc_cmd
from vyos.util import write_file
from vyos.template import inc_ip
from vyos.template import is_ipv4
@@ -38,8 +42,9 @@ from vyos import ConfigError
from vyos import airbag
airbag.enable()
-config_containers_registry = '/etc/containers/registries.conf'
-config_containers_storage = '/etc/containers/storage.conf'
+config_containers = '/etc/containers/containers.conf'
+config_registry = '/etc/containers/registries.conf'
+config_storage = '/etc/containers/storage.conf'
systemd_unit_path = '/run/systemd/system'
def _cmd(command):
@@ -68,14 +73,54 @@ def get_config(config=None):
# container base default values can not be merged here - remove and add them later
if 'name' in default_values:
del default_values['name']
+ # registry will be handled below
+ if 'registry' in default_values:
+ del default_values['registry']
container = dict_merge(default_values, container)
# 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])
+ # T5047: Any container related configuration changed? We only
+ # wan't to restart the required containers and not all of them ...
+ tmp = is_node_changed(conf, base + ['name', name])
+ if tmp:
+ if 'container_restart' not in container:
+ container['container_restart'] = [name]
+ else:
+ container['container_restart'].append(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_port = defaults(base + ['name', 'port'])
+ container['name'][name]['port'][port] = dict_merge(
+ default_values_port, 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_volume = defaults(base + ['name', 'volume'])
+ container['name'][name]['volume'][volume] = dict_merge(
+ default_values_volume, container['name'][name]['volume'][volume])
+
+ # registry is a tagNode with default values - merge the list from
+ # default_values['registry'] into the tagNode variables
+ if 'registry' not in container:
+ container.update({'registry' : {}})
+ default_values = defaults(base)
+ for registry in default_values['registry'].split():
+ tmp = {registry : {}}
+ container['registry'] = dict_merge(tmp, container['registry'])
+
# Delete container network, delete containers
tmp = node_changed(conf, base + ['network'])
if tmp: container.update({'network_remove' : tmp})
@@ -123,21 +168,29 @@ def verify(container):
raise ConfigError(f'Container network "{network_name}" does not exist!')
if 'address' in container_config['network'][network_name]:
- address = container_config['network'][network_name]['address']
- network = None
- if is_ipv4(address):
- network = [x for x in container['network'][network_name]['prefix'] if is_ipv4(x)][0]
- elif is_ipv6(address):
- network = [x for x in container['network'][network_name]['prefix'] if is_ipv6(x)][0]
-
- # Specified container IP address must belong to network prefix
- if ip_address(address) not in ip_network(network):
- raise ConfigError(f'Used container address "{address}" not in network "{network}"!')
-
- # We can not use the first IP address of a network prefix as this is used by podman
- if ip_address(address) == ip_network(network)[1]:
- raise ConfigError(f'IP address "{address}" can not be used for a container, '\
- 'reserved for the container engine!')
+ cnt_ipv4 = 0
+ cnt_ipv6 = 0
+ for address in container_config['network'][network_name]['address']:
+ network = None
+ if is_ipv4(address):
+ network = [x for x in container['network'][network_name]['prefix'] if is_ipv4(x)][0]
+ cnt_ipv4 += 1
+ elif is_ipv6(address):
+ network = [x for x in container['network'][network_name]['prefix'] if is_ipv6(x)][0]
+ cnt_ipv6 += 1
+
+ # Specified container IP address must belong to network prefix
+ if ip_address(address) not in ip_network(network):
+ raise ConfigError(f'Used container address "{address}" not in network "{network}"!')
+
+ # We can not use the first IP address of a network prefix as this is used by podman
+ if ip_address(address) == ip_network(network)[1]:
+ raise ConfigError(f'IP address "{address}" can not be used for a container, '\
+ 'reserved for the container engine!')
+
+ if cnt_ipv4 > 1 or cnt_ipv6 > 1:
+ raise ConfigError(f'Only one IP address per address family can be used for '\
+ f'container "{name}". {cnt_ipv4} IPv4 and {cnt_ipv6} IPv6 address(es)!')
if 'device' in container_config:
for dev, dev_config in container_config['device'].items():
@@ -168,6 +221,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}"!')
@@ -194,6 +252,8 @@ def verify(container):
if v6_prefix > 1:
raise ConfigError(f'Only one IPv6 prefix can be defined for network "{network}"!')
+ # Verify VRF exists
+ verify_vrf(network_config)
# A network attached to a container can not be deleted
if {'network_remove', 'name'} <= set(container):
@@ -202,6 +262,13 @@ def verify(container):
if 'network' in container_config and network in container_config['network']:
raise ConfigError(f'Can not remove network "{network}", used by container "{container}"!')
+ if 'registry' in container:
+ for registry, registry_config in container['registry'].items():
+ if 'authentication' not in registry_config:
+ continue
+ if not {'username', 'password'} <= set(registry_config['authentication']):
+ raise ConfigError('If registry username or or password is defined, so must be the other!')
+
return None
def generate_run_arguments(name, container_config):
@@ -230,21 +297,22 @@ def generate_run_arguments(name, container_config):
env_opt = ''
if 'environment' in container_config:
for k, v in container_config['environment'].items():
- env_opt += f" -e \"{k}={v['value']}\""
+ env_opt += f" --env \"{k}={v['value']}\""
+
+ hostname = ''
+ if 'host_name' in container_config:
+ hostname = container_config['host_name']
+ hostname = f'--hostname {hostname}'
# 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'
+ protocol = container_config['port'][portmap]['protocol']
sport = container_config['port'][portmap]['source']
dport = container_config['port'][portmap]['destination']
- port += f' -p {sport}:{dport}{protocol}'
+ port += f' --publish {sport}:{dport}/{protocol}'
# Bind volume
volume = ''
@@ -252,66 +320,101 @@ def generate_run_arguments(name, container_config):
for vol, vol_config in container_config['volume'].items():
svol = vol_config['source']
dvol = vol_config['destination']
- volume += f' -v {svol}:{dvol}'
+ 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}'
+ f'--name {name} {hostname} {device} {port} {volume} {env_opt}'
+
+ entrypoint = ''
+ if 'entrypoint' in container_config:
+ # it needs to be json-formatted with single quote on the outside
+ entrypoint = json_write(container_config['entrypoint'].split()).replace('"', "&quot;")
+ entrypoint = f'--entrypoint &apos;{entrypoint}&apos;'
+
+ hostname = ''
+ if 'host_name' in container_config:
+ hostname = container_config['host_name']
+ hostname = f'--hostname {hostname}'
+
+ command = ''
+ if 'command' in container_config:
+ command = container_config['command'].strip()
+
+ command_arguments = ''
+ if 'arguments' in container_config:
+ command_arguments = container_config['arguments'].strip()
if 'allow_host_networks' in container_config:
- return f'{container_base_cmd} --net host {image}'
+ return f'{container_base_cmd} --net host {entrypoint} {image} {command} {command_arguments}'.strip()
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}'
+ if 'address' not in container_config['network'][network]:
+ continue
+ for address in container_config['network'][network]['address']:
+ if is_ipv6(address):
+ ip_param += f' --ip6 {address}'
+ else:
+ ip_param += f' --ip {address}'
- return f'{container_base_cmd} --net {networks} {ip_param} {image}'
+ return f'{container_base_cmd} --net {networks} {ip_param} {entrypoint} {image} {command} {command_arguments}'.strip()
def generate(container):
# bail out early - looks like removal from running config
if not container:
- if os.path.exists(config_containers_registry):
- os.unlink(config_containers_registry)
- if os.path.exists(config_containers_storage):
- os.unlink(config_containers_storage)
+ for file in [config_containers, config_registry, config_storage]:
+ if os.path.exists(file):
+ os.unlink(file)
return None
if 'network' in container:
for network, network_config in container['network'].items():
tmp = {
- 'cniVersion' : '0.4.0',
- 'name' : network,
- 'plugins' : [{
- 'type': 'bridge',
- 'bridge': f'cni-{network}',
- 'isGateway': True,
- 'ipMasq': False,
- 'hairpinMode': False,
- 'ipam' : {
- 'type': 'host-local',
- 'routes': [],
- 'ranges' : [],
- },
- }]
+ 'name': network,
+ 'id' : sha256(f'{network}'.encode()).hexdigest(),
+ 'driver': 'bridge',
+ 'network_interface': f'pod-{network}',
+ 'subnets': [],
+ 'ipv6_enabled': False,
+ 'internal': False,
+ 'dns_enabled': True,
+ 'ipam_options': {
+ 'driver': 'host-local'
+ }
}
-
for prefix in network_config['prefix']:
- net = [{'gateway' : inc_ip(prefix, 1), 'subnet' : prefix}]
- tmp['plugins'][0]['ipam']['ranges'].append(net)
+ net = {'subnet' : prefix, 'gateway' : inc_ip(prefix, 1)}
+ tmp['subnets'].append(net)
- # install per address-family default orutes
- default_route = '0.0.0.0/0'
if is_ipv6(prefix):
- default_route = '::/0'
- tmp['plugins'][0]['ipam']['routes'].append({'dst': default_route})
+ tmp['ipv6_enabled'] = True
+
+ write_file(f'/etc/containers/networks/{network}.json', json_write(tmp, indent=2))
- write_file(f'/etc/cni/net.d/{network}.conflist', json_write(tmp, indent=2))
+ if 'registry' in container:
+ cmd = f'podman logout --all'
+ rc, out = rc_cmd(cmd)
+ if rc != 0:
+ raise ConfigError(out)
- render(config_containers_registry, 'container/registries.conf.j2', container)
- render(config_containers_storage, 'container/storage.conf.j2', container)
+ for registry, registry_config in container['registry'].items():
+ if 'disable' in registry_config:
+ continue
+ if 'authentication' in registry_config:
+ if {'username', 'password'} <= set(registry_config['authentication']):
+ username = registry_config['authentication']['username']
+ password = registry_config['authentication']['password']
+ cmd = f'podman login --username {username} --password {password} {registry}'
+ rc, out = rc_cmd(cmd)
+ if rc != 0:
+ raise ConfigError(out)
+
+ render(config_containers, 'container/containers.conf.j2', container)
+ render(config_registry, 'container/registries.conf.j2', container)
+ render(config_storage, 'container/storage.conf.j2', container)
if 'name' in container:
for name, container_config in container['name'].items():
@@ -320,7 +423,8 @@ def generate(container):
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})
+ render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args,},
+ formater=lambda _: _.replace("&quot;", '"').replace("&apos;", "'"))
return None
@@ -339,10 +443,7 @@ def apply(container):
# Delete old networks if needed
if 'network_remove' in container:
for network in container['network_remove']:
- call(f'podman network rm {network}')
- tmp = f'/etc/cni/net.d/{network}.conflist'
- if os.path.exists(tmp):
- os.unlink(tmp)
+ call(f'podman network rm {network} >/dev/null 2>&1')
# Add container
disabled_new = False
@@ -366,11 +467,26 @@ def apply(container):
os.unlink(file_path)
continue
- cmd(f'systemctl restart vyos-container-{name}.service')
+ if 'container_restart' in container and name in container['container_restart']:
+ cmd(f'systemctl restart vyos-container-{name}.service')
if disabled_new:
call('systemctl daemon-reload')
+ # Start network and assign it to given VRF if requested. this can only be done
+ # after the containers got started as the podman network interface will
+ # only be enabled by the first container and yet I do not know how to enable
+ # the network interface in advance
+ if 'network' in container:
+ for network, network_config in container['network'].items():
+ network_name = f'pod-{network}'
+ # T5147: Networks are started only as soon as there is a consumer.
+ # If only a network is created in the first place, no need to assign
+ # it to a VRF as there's no consumer, yet.
+ if os.path.exists(f'/sys/class/net/{network_name}'):
+ tmp = Interface(network_name)
+ tmp.set_vrf(network_config.get('vrf', ''))
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py
index 4de2ca2f3..7e702a446 100755
--- a/src/conf_mode/dhcp_relay.py
+++ b/src/conf_mode/dhcp_relay.py
@@ -18,9 +18,11 @@ import os
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.template import render
+from vyos.base import Warning
from vyos.util import call
from vyos.util import dict_search
from vyos.xml import defaults
@@ -59,6 +61,19 @@ def verify(relay):
raise ConfigError('No DHCP relay server(s) configured.\n' \
'At least one DHCP relay server required.')
+ if 'interface' in relay:
+ Warning('DHCP relay interface is DEPRECATED - please use upstream-interface and listen-interface instead!')
+ if 'upstream_interface' in relay or 'listen_interface' in relay:
+ raise ConfigError('<interface> configuration is not compatible with upstream/listen interface')
+ else:
+ Warning('<interface> is going to be deprecated.\n' \
+ 'Please use <listen-interface> and <upstream-interface>')
+
+ if 'upstream_interface' in relay and 'listen_interface' not in relay:
+ raise ConfigError('No listen-interface configured')
+ if 'listen_interface' in relay and 'upstream_interface' not in relay:
+ raise ConfigError('No upstream-interface configured')
+
return None
def generate(relay):
diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py
index 52b682d6d..2b2af252d 100755
--- a/src/conf_mode/dhcp_server.py
+++ b/src/conf_mode/dhcp_server.py
@@ -247,7 +247,7 @@ def verify(dhcp):
net2 = ip_network(n)
if (net != net2):
if net.overlaps(net2):
- raise ConfigError('Conflicting subnet ranges: "{net}" overlaps "{net2}"!')
+ raise ConfigError(f'Conflicting subnet ranges: "{net}" overlaps "{net2}"!')
# Prevent 'disable' for shared-network if only one network is configured
if (shared_networks - disabled_shared_networks) < 1:
@@ -283,7 +283,7 @@ def generate(dhcp):
if not dhcp or 'disable' in dhcp:
return None
- # Please see: https://phabricator.vyos.net/T1129 for quoting of the raw
+ # Please see: https://vyos.dev/T1129 for quoting of the raw
# parameters we can pass to ISC DHCPd
tmp_file = '/tmp/dhcpd.conf'
render(tmp_file, 'dhcp-server/dhcpd.conf.j2', dhcp,
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index d0d87d73e..36c1098fe 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -24,7 +24,7 @@ from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.hostsd_client import Client as hostsd_client
from vyos.template import render
-from vyos.template import is_ipv6
+from vyos.template import bracketize_ipv6
from vyos.util import call
from vyos.util import chown
from vyos.util import dict_search
@@ -58,8 +58,26 @@ def get_config(config=None):
default_values = defaults(base)
# T2665 due to how defaults under tag nodes work, we must clear these out before we merge
del default_values['authoritative_domain']
+ del default_values['name_server']
+ del default_values['domain']['name_server']
dns = dict_merge(default_values, dns)
+ # T2665: we cleared default values for tag node 'name_server' above.
+ # We now need to add them back back in a granular way.
+ if 'name_server' in dns:
+ default_values = defaults(base + ['name-server'])
+ for server in dns['name_server']:
+ dns['name_server'][server] = dict_merge(default_values, dns['name_server'][server])
+
+ # T2665: we cleared default values for tag node 'domain' above.
+ # We now need to add them back back in a granular way.
+ if 'domain' in dns:
+ default_values = defaults(base + ['domain', 'name-server'])
+ for domain in dns['domain'].keys():
+ for server in dns['domain'][domain]['name_server']:
+ dns['domain'][domain]['name_server'][server] = dict_merge(
+ default_values, dns['domain'][domain]['name_server'][server])
+
# some additions to the default dictionary
if 'system' in dns:
base_nameservers = ['system', 'name-server']
@@ -263,7 +281,7 @@ def verify(dns):
# as a domain will contains dot's which is out dictionary delimiter.
if 'domain' in dns:
for domain in dns['domain']:
- if 'server' not in dns['domain'][domain]:
+ if 'name_server' not in dns['domain'][domain]:
raise ConfigError(f'No server configured for domain {domain}!')
if 'dns64_prefix' in dns:
@@ -329,7 +347,12 @@ def apply(dns):
# sources
hc.delete_name_servers([hostsd_tag])
if 'name_server' in dns:
- hc.add_name_servers({hostsd_tag: dns['name_server']})
+ # 'name_server' is of the form
+ # {'192.0.2.1': {'port': 53}, '2001:db8::1': {'port': 853}, ...}
+ # canonicalize them as ['192.0.2.1:53', '[2001:db8::1]:853', ...]
+ nslist = [(lambda h, p: f"{bracketize_ipv6(h)}:{p['port']}")(h, p)
+ for (h, p) in dns['name_server'].items()]
+ hc.add_name_servers({hostsd_tag: nslist})
# delete all nameserver tags
hc.delete_name_server_tags_recursor(hc.get_name_server_tags_recursor())
@@ -358,7 +381,14 @@ def apply(dns):
# the list and keys() are required as get returns a dict, not list
hc.delete_forward_zones(list(hc.get_forward_zones().keys()))
if 'domain' in dns:
- hc.add_forward_zones(dns['domain'])
+ zones = dns['domain']
+ for domain in zones.keys():
+ # 'name_server' is of the form
+ # {'192.0.2.1': {'port': 53}, '2001:db8::1': {'port': 853}, ...}
+ # canonicalize them as ['192.0.2.1:53', '[2001:db8::1]:853', ...]
+ zones[domain]['name_server'] = [(lambda h, p: f"{bracketize_ipv6(h)}:{p['port']}")(h, p)
+ for (h, p) in zones[domain]['name_server'].items()]
+ hc.add_forward_zones(zones)
# hostsd generates NTAs for the authoritative zones
# the list and keys() are required as get returns a dict, not list
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 20cf1ead1..190587980 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -197,6 +197,15 @@ def verify_rule(firewall, rule_conf, ipv6):
if target not in dict_search_args(firewall, 'ipv6_name'):
raise ConfigError(f'Invalid jump-target. Firewall ipv6-name {target} does not exist on the system')
+ if 'queue_options' in rule_conf:
+ if 'queue' not in rule_conf['action']:
+ raise ConfigError('queue-options defined, but action queue needed and it is not defined')
+ if 'fanout' in rule_conf['queue_options'] and ('queue' not in rule_conf or '-' not in rule_conf['queue']):
+ raise ConfigError('queue-options fanout defined, then queue needs to be defined as a range')
+
+ if 'queue' in rule_conf and 'queue' not in rule_conf['action']:
+ raise ConfigError('queue defined, but action queue needed and it is not defined')
+
if 'fragment' in rule_conf:
if {'match_frag', 'match_non_frag'} <= set(rule_conf['fragment']):
raise ConfigError('Cannot specify both "match-frag" and "match-non-frag"')
@@ -273,6 +282,19 @@ def verify_rule(firewall, rule_conf, ipv6):
if rule_conf['protocol'] not in ['tcp', 'udp', 'tcp_udp']:
raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group')
+ if 'port' in side_conf and dict_search_args(side_conf, 'group', 'port_group'):
+ raise ConfigError(f'{side} port-group and port cannot both be defined')
+
+ if 'log_options' in rule_conf:
+ if 'log' not in rule_conf or 'enable' not in rule_conf['log']:
+ raise ConfigError('log-options defined, but log is not enable')
+
+ if 'snapshot_length' in rule_conf['log_options'] and 'group' not in rule_conf['log_options']:
+ raise ConfigError('log-options snapshot-length defined, but log group is not define')
+
+ if 'queue_threshold' in rule_conf['log_options'] and 'group' not in rule_conf['log_options']:
+ raise ConfigError('log-options queue-threshold defined, but log group is not define')
+
def verify_nested_group(group_name, group, groups, seen):
if 'include' not in group:
return
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 6328294c1..7e801eb26 100755
--- a/src/conf_mode/http-api.py
+++ b/src/conf_mode/http-api.py
@@ -79,9 +79,10 @@ def get_config(config=None):
# http-api.conf format for api_keys:
if 'keys' in api_dict:
api_dict['api_keys'] = []
- for el in list(api_dict['keys']['id']):
- key = api_dict['keys']['id'][el]['key']
- api_dict['api_keys'].append({'id': el, 'key': key})
+ for el in list(api_dict['keys'].get('id', {})):
+ key = api_dict['keys']['id'][el].get('key', '')
+ if key:
+ api_dict['api_keys'].append({'id': el, 'key': key})
del api_dict['keys']
# Do we run inside a VRF context?
diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py
index 7cd7ea42e..b0c38e8d3 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']
@@ -159,6 +159,8 @@ def generate(https):
server_block['port'] = data.get('listen-port', '443')
name = data.get('server-name', ['_'])
server_block['name'] = name
+ allow_client = data.get('allow-client', {})
+ server_block['allow_client'] = allow_client.get('address', [])
server_block_list.append(server_block)
# get certificate data
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-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 8155f36c2..6f227b0d1 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.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
@@ -597,7 +597,7 @@ def generate_pki_files(openvpn):
def generate(openvpn):
interface = openvpn['ifname']
directory = os.path.dirname(cfg_file.format(**openvpn))
- plugin_dir = '/usr/lib/openvpn'
+ openvpn['plugin_dir'] = '/usr/lib/openvpn'
# create base config directory on demand
makedir(directory, user, group)
# enforce proper permissions on /run/openvpn
@@ -645,7 +645,7 @@ def generate(openvpn):
user=user, group=group)
# we need to support quoting of raw parameters from OpenVPN CLI
- # see https://phabricator.vyos.net/T1632
+ # see https://vyos.dev/T1632
render(cfg_file.format(**openvpn), 'openvpn/server.conf.j2', openvpn,
formater=lambda _: _.replace("&quot;", '"'), user=user, group=group)
diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index ee4defa0d..5f0b76f90 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -54,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-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index acef1fda7..0a3726e94 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
@@ -136,7 +136,7 @@ def verify(tunnel):
if our_key != None:
if their_address == our_address and their_key == our_key:
raise ConfigError(f'Key "{our_key}" for source-address "{our_address}" ' \
- f'is already used for tunnel "{tunnel_if}"!')
+ f'is already used for tunnel "{o_tunnel}"!')
else:
our_source_if = dict_search('source_interface', tunnel)
their_source_if = dict_search('source_interface', o_tunnel_conf)
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-wwan.py b/src/conf_mode/interfaces-wwan.py
index a14a992ae..9ca495476 100755
--- a/src/conf_mode/interfaces-wwan.py
+++ b/src/conf_mode/interfaces-wwan.py
@@ -171,7 +171,7 @@ def apply(wwan):
options = f'ip-type={ip_type},apn=' + wwan['apn']
if 'authentication' in wwan:
- options += ',user={user},password={password}'.format(**wwan['authentication'])
+ options += ',user={username},password={password}'.format(**wwan['authentication'])
command = f'{base_cmd} --simple-connect="{options}"'
call(command, stdout=DEVNULL)
diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py
index 0ecb4d736..92cb73aab 100755
--- a/src/conf_mode/ntp.py
+++ b/src/conf_mode/ntp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2022 VyOS maintainers and contributors
+# Copyright (C) 2018-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -21,26 +21,29 @@ from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_interface_exists
from vyos.util import call
+from vyos.util import chmod_750
from vyos.util import get_interface_config
from vyos.template import render
from vyos import ConfigError
from vyos import airbag
airbag.enable()
-config_file = r'/run/ntpd/ntpd.conf'
-systemd_override = r'/etc/systemd/system/ntp.service.d/override.conf'
+config_file = r'/run/chrony/chrony.conf'
+systemd_override = r'/run/systemd/system/chrony.service.d/override.conf'
+user_group = '_chrony'
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- base = ['system', 'ntp']
+ base = ['service', 'ntp']
if not conf.exists(base):
return None
ntp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
ntp['config_file'] = config_file
+ ntp['user'] = user_group
tmp = is_node_changed(conf, base + ['vrf'])
if tmp: ntp.update({'restart_required': {}})
@@ -52,7 +55,7 @@ def verify(ntp):
if not ntp:
return None
- if 'allow_clients' in ntp and 'server' not in ntp:
+ if 'server' not in ntp:
raise ConfigError('NTP server not configured')
verify_vrf(ntp)
@@ -77,13 +80,17 @@ def generate(ntp):
if not ntp:
return None
- render(config_file, 'ntp/ntpd.conf.j2', ntp)
- render(systemd_override, 'ntp/override.conf.j2', ntp)
+ render(config_file, 'chrony/chrony.conf.j2', ntp, user=user_group, group=user_group)
+ render(systemd_override, 'chrony/override.conf.j2', ntp, user=user_group, group=user_group)
+
+ # Ensure proper permission for chrony command socket
+ config_dir = os.path.dirname(config_file)
+ chmod_750(config_dir)
return None
def apply(ntp):
- systemd_service = 'ntp.service'
+ systemd_service = 'chrony.service'
# Reload systemd manager configuration
call('systemctl daemon-reload')
diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py
index e8f3cc87a..54de467ca 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -51,6 +51,11 @@ sync_search = [
'script': '/usr/libexec/vyos/conf_mode/interfaces-openvpn.py'
},
{
+ 'keys': ['ca_certificate'],
+ 'path': ['interfaces', 'sstpc'],
+ 'script': '/usr/libexec/vyos/conf_mode/interfaces-sstpc.py'
+ },
+ {
'keys': ['certificate', 'ca_certificate', 'local_key', 'remote_key'],
'path': ['vpn', 'ipsec'],
'script': '/usr/libexec/vyos/conf_mode/vpn_ipsec.py'
diff --git a/src/conf_mode/protocols_babel.py b/src/conf_mode/protocols_babel.py
new file mode 100755
index 000000000..20821c7f2
--- /dev/null
+++ b/src/conf_mode/protocols_babel.py
@@ -0,0 +1,163 @@
+#!/usr/bin/env python3
+#
+# 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
+# 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 dict_merge
+from vyos.configdict import node_changed
+from vyos.configverify import verify_common_route_maps
+from vyos.configverify import verify_access_list
+from vyos.configverify import verify_prefix_list
+from vyos.util import dict_search
+from vyos.xml import defaults
+from vyos.template import render_to_string
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+airbag.enable()
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['protocols', 'babel']
+ babel = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+
+ # FRR has VRF support for different routing daemons. As interfaces belong
+ # to VRFs - or the global VRF, we need to check for changed interfaces so
+ # that they will be properly rendered for the FRR config. Also this eases
+ # removal of interfaces from the running configuration.
+ interfaces_removed = node_changed(conf, base + ['interface'])
+ if interfaces_removed:
+ babel['interface_removed'] = list(interfaces_removed)
+
+ # Bail out early if configuration tree does not exist
+ if not conf.exists(base):
+ babel.update({'deleted' : ''})
+ return babel
+
+ # 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)
+
+ # XXX: T2665: we currently have no nice way for defaults under tag nodes,
+ # clean them out and add them manually :(
+ del default_values['interface']
+
+ # merge in remaining default values
+ babel = dict_merge(default_values, babel)
+
+ # We also need some additional information from the config, prefix-lists
+ # and route-maps for instance. They will be used in verify().
+ #
+ # XXX: one MUST always call this without the key_mangling() option! See
+ # vyos.configverify.verify_common_route_maps() for more information.
+ tmp = conf.get_config_dict(['policy'])
+ # Merge policy dict into "regular" config dict
+ babel = dict_merge(tmp, babel)
+ return babel
+
+def verify(babel):
+ if not babel:
+ return None
+
+ # verify distribute_list
+ if "distribute_list" in babel:
+ acl_keys = {
+ "ipv4": [
+ "distribute_list.ipv4.access_list.in",
+ "distribute_list.ipv4.access_list.out",
+ ],
+ "ipv6": [
+ "distribute_list.ipv6.access_list.in",
+ "distribute_list.ipv6.access_list.out",
+ ]
+ }
+ prefix_list_keys = {
+ "ipv4": [
+ "distribute_list.ipv4.prefix_list.in",
+ "distribute_list.ipv4.prefix_list.out",
+ ],
+ "ipv6":[
+ "distribute_list.ipv6.prefix_list.in",
+ "distribute_list.ipv6.prefix_list.out",
+ ]
+ }
+ for address_family in ["ipv4", "ipv6"]:
+ for iface_key in babel["distribute_list"].get(address_family, {}).get("interface", {}).keys():
+ acl_keys[address_family].extend([
+ f"distribute_list.{address_family}.interface.{iface_key}.access_list.in",
+ f"distribute_list.{address_family}.interface.{iface_key}.access_list.out"
+ ])
+ prefix_list_keys[address_family].extend([
+ f"distribute_list.{address_family}.interface.{iface_key}.prefix_list.in",
+ f"distribute_list.{address_family}.interface.{iface_key}.prefix_list.out"
+ ])
+
+ for address_family, keys in acl_keys.items():
+ for key in keys:
+ acl = dict_search(key, babel)
+ if acl:
+ verify_access_list(acl, babel, version='6' if address_family == 'ipv6' else '')
+
+ for address_family, keys in prefix_list_keys.items():
+ for key in keys:
+ prefix_list = dict_search(key, babel)
+ if prefix_list:
+ verify_prefix_list(prefix_list, babel, version='6' if address_family == 'ipv6' else '')
+
+
+def generate(babel):
+ if not babel or 'deleted' in babel:
+ return None
+
+ babel['new_frr_config'] = render_to_string('frr/babeld.frr.j2', babel)
+ return None
+
+def apply(babel):
+ babel_daemon = 'babeld'
+
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+
+ frr_cfg.load_configuration(babel_daemon)
+ frr_cfg.modify_section('^router babel', stop_pattern='^exit', remove_stop_mark=True)
+
+ for key in ['interface', 'interface_removed']:
+ if key not in babel:
+ continue
+ for interface in babel[key]:
+ frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
+
+ if 'new_frr_config' in babel:
+ frr_cfg.add_before(frr.default_add_before, babel['new_frr_config'])
+ frr_cfg.commit_configuration(babel_daemon)
+
+ 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_bgp.py b/src/conf_mode/protocols_bgp.py
index ff568d470..6e6b3fa96 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
@@ -52,18 +50,31 @@ def get_config(config=None):
bgp = conf.get_config_dict(base, key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
+ bgp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'],
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
# Assign the name of our VRF context. This MUST be done before the return
# statement below, else on deletion we will delete the default instance
# instead of the VRF instance.
- if vrf: bgp.update({'vrf' : vrf})
-
+ if vrf:
+ bgp.update({'vrf' : vrf})
+ # We can not delete the BGP VRF instance if there is a L3VNI configured
+ tmp = ['vrf', 'name', vrf, 'vni']
+ if conf.exists(tmp):
+ bgp.update({'vni' : conf.return_value(tmp)})
+ # We can safely delete ourself from the dependent vrf list
+ if vrf in bgp['dependent_vrfs']:
+ del bgp['dependent_vrfs'][vrf]
+
+ 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 +85,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 +206,26 @@ 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 instance "{bgp["vrf"]}", ' \
+ 'unconfigure "import vrf" commands!')
+ # We can not delete the BGP instance if a L3VNI instance exists
+ if 'vni' in bgp:
+ raise ConfigError(f'Cannot delete VRF instance "{bgp["vrf"]}", ' \
+ f'unconfigure VNI "{bgp["vni"]}" first!')
+ 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 +247,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}"!')
@@ -312,6 +424,11 @@ def verify(bgp):
raise ConfigError('Missing mandatory configuration option for '\
f'global administrative distance {key}!')
+ # TCP keepalive requires all three parameters to be set
+ if dict_search('parameters.tcp_keepalive', bgp) != None:
+ if not {'idle', 'interval', 'probes'} <= set(bgp['parameters']['tcp_keepalive']):
+ raise ConfigError('TCP keepalive incomplete - idle, keepalive and probes must be set')
+
# Address Family specific validation
if 'address_family' in bgp:
for afi, afi_config in bgp['address_family'].items():
@@ -324,9 +441,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,33 +490,21 @@ 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):
if not bgp or 'deleted' in bgp:
return None
- bgp['protocol'] = 'bgp' # required for frr/vrf.route-map.frr.j2
- bgp['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.j2', bgp)
bgp['frr_bgpd_config'] = render_to_string('frr/bgpd.frr.j2', bgp)
-
return None
def apply(bgp):
bgp_daemon = 'bgpd'
- zebra_daemon = 'zebra'
# Save original configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()
- # The route-map used for the FIB (zebra) is part of the zebra daemon
- frr_cfg.load_configuration(zebra_daemon)
- frr_cfg.modify_section(r'(\s+)?ip protocol bgp route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
- if 'frr_zebra_config' in bgp:
- frr_cfg.add_before(frr.default_add_before, bgp['frr_zebra_config'])
- frr_cfg.commit_configuration(zebra_daemon)
-
# Generate empty helper string which can be ammended to FRR commands, it
# will be either empty (default VRF) or contain the "vrf <name" statement
vrf = ''
diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py
index c1a1a45e1..609b39065 100755
--- a/src/conf_mode/protocols_eigrp.py
+++ b/src/conf_mode/protocols_eigrp.py
@@ -69,8 +69,6 @@ def get_config(config=None):
# Merge policy dict into "regular" config dict
eigrp = dict_merge(tmp, eigrp)
- import pprint
- pprint.pprint(eigrp)
return eigrp
def verify(eigrp):
@@ -80,24 +78,14 @@ def generate(eigrp):
if not eigrp or 'deleted' in eigrp:
return None
- eigrp['protocol'] = 'eigrp' # required for frr/vrf.route-map.frr.j2
- eigrp['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.j2', eigrp)
eigrp['frr_eigrpd_config'] = render_to_string('frr/eigrpd.frr.j2', eigrp)
def apply(eigrp):
eigrp_daemon = 'eigrpd'
- zebra_daemon = 'zebra'
# Save original configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()
- # The route-map used for the FIB (zebra) is part of the zebra daemon
- frr_cfg.load_configuration(zebra_daemon)
- frr_cfg.modify_section(r'(\s+)?ip protocol eigrp route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
- if 'frr_zebra_config' in eigrp:
- frr_cfg.add_before(frr.default_add_before, eigrp['frr_zebra_config'])
- frr_cfg.commit_configuration(zebra_daemon)
-
# Generate empty helper string which can be ammended to FRR commands, it
# will be either empty (default VRF) or contain the "vrf <name" statement
vrf = ''
diff --git a/src/conf_mode/protocols_failover.py b/src/conf_mode/protocols_failover.py
index 048ba7a89..85e984afe 100755
--- a/src/conf_mode/protocols_failover.py
+++ b/src/conf_mode/protocols_failover.py
@@ -31,7 +31,7 @@ airbag.enable()
service_name = 'vyos-failover'
service_conf = Path(f'/run/{service_name}.conf')
-systemd_service = '/etc/systemd/system/vyos-failover.service'
+systemd_service = '/run/systemd/system/vyos-failover.service'
rt_proto_failover = '/etc/iproute2/rt_protos.d/failover.conf'
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index cb8ea3be4..af2937db8 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -203,7 +203,7 @@ def verify(isis):
if list(set(global_range) & set(local_range)):
raise ConfigError(f'Segment-Routing Global Block ({g_low_label_value}/{g_high_label_value}) '\
f'conflicts with Local Block ({l_low_label_value}/{l_high_label_value})!')
-
+
# Check for a blank or invalid value per prefix
if dict_search('segment_routing.prefix', isis):
for prefix, prefix_config in isis['segment_routing']['prefix'].items():
@@ -218,7 +218,7 @@ def verify(isis):
if dict_search('segment_routing.prefix', isis):
for prefix, prefix_config in isis['segment_routing']['prefix'].items():
if 'absolute' in prefix_config:
- if ("explicit_null" in prefix_config['absolute']) and ("no_php_flag" in prefix_config['absolute']):
+ if ("explicit_null" in prefix_config['absolute']) and ("no_php_flag" in prefix_config['absolute']):
raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\
f'and no-php-flag configured at the same time.')
elif 'index' in prefix_config:
@@ -232,25 +232,15 @@ def generate(isis):
if not isis or 'deleted' in isis:
return None
- isis['protocol'] = 'isis' # required for frr/vrf.route-map.frr.j2
- isis['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.j2', isis)
isis['frr_isisd_config'] = render_to_string('frr/isisd.frr.j2', isis)
return None
def apply(isis):
isis_daemon = 'isisd'
- zebra_daemon = 'zebra'
# Save original configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()
- # The route-map used for the FIB (zebra) is part of the zebra daemon
- frr_cfg.load_configuration(zebra_daemon)
- frr_cfg.modify_section('(\s+)?ip protocol isis route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
- if 'frr_zebra_config' in isis:
- frr_cfg.add_before(frr.default_add_before, isis['frr_zebra_config'])
- frr_cfg.commit_configuration(zebra_daemon)
-
# Generate empty helper string which can be ammended to FRR commands, it
# will be either empty (default VRF) or contain the "vrf <name" statement
vrf = ''
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index 0582d32be..fbb876123 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -89,7 +89,7 @@ def get_config(config=None):
if 'mpls_te' not in ospf:
del default_values['mpls_te']
- for protocol in ['bgp', 'connected', 'isis', 'kernel', 'rip', 'static', 'table']:
+ for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static', 'table']:
# table is a tagNode thus we need to clean out all occurances for the
# default values and load them in later individually
if protocol == 'table':
@@ -234,7 +234,7 @@ def verify(ospf):
if list(set(global_range) & set(local_range)):
raise ConfigError(f'Segment-Routing Global Block ({g_low_label_value}/{g_high_label_value}) '\
f'conflicts with Local Block ({l_low_label_value}/{l_high_label_value})!')
-
+
# Check for a blank or invalid value per prefix
if dict_search('segment_routing.prefix', ospf):
for prefix, prefix_config in ospf['segment_routing']['prefix'].items():
@@ -256,25 +256,15 @@ def generate(ospf):
if not ospf or 'deleted' in ospf:
return None
- ospf['protocol'] = 'ospf' # required for frr/vrf.route-map.frr.j2
- ospf['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.j2', ospf)
ospf['frr_ospfd_config'] = render_to_string('frr/ospfd.frr.j2', ospf)
return None
def apply(ospf):
ospf_daemon = 'ospfd'
- zebra_daemon = 'zebra'
# Save original configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()
- # The route-map used for the FIB (zebra) is part of the zebra daemon
- frr_cfg.load_configuration(zebra_daemon)
- frr_cfg.modify_section('(\s+)?ip protocol ospf route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
- if 'frr_zebra_config' in ospf:
- frr_cfg.add_before(frr.default_add_before, ospf['frr_zebra_config'])
- frr_cfg.commit_configuration(zebra_daemon)
-
# Generate empty helper string which can be ammended to FRR commands, it
# will be either empty (default VRF) or contain the "vrf <name" statement
vrf = ''
@@ -292,6 +282,7 @@ def apply(ospf):
if 'frr_ospfd_config' in ospf:
frr_cfg.add_before(frr.default_add_before, ospf['frr_ospfd_config'])
+
frr_cfg.commit_configuration(ospf_daemon)
return None
diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py
index ee4eaf59d..ee1fdd399 100755
--- a/src/conf_mode/protocols_ospfv3.py
+++ b/src/conf_mode/protocols_ospfv3.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
@@ -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..7b6150696 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,25 +97,22 @@ 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
def apply(static):
static_daemon = 'staticd'
- zebra_daemon = 'zebra'
# Save original configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()
-
- # The route-map used for the FIB (zebra) is part of the zebra daemon
- frr_cfg.load_configuration(zebra_daemon)
- frr_cfg.modify_section(r'^ip protocol static route-map [-a-zA-Z0-9.]+', '')
- frr_cfg.commit_configuration(zebra_daemon)
frr_cfg.load_configuration(static_daemon)
if 'vrf' in static:
vrf = static['vrf']
- frr_cfg.modify_section(f'^vrf {vrf}', stop_pattern='^exit', remove_stop_mark=True)
+ frr_cfg.modify_section(f'^vrf {vrf}', stop_pattern='^exit-vrf', remove_stop_mark=True)
else:
frr_cfg.modify_section(r'^ip route .*')
frr_cfg.modify_section(r'^ipv6 route .*')
diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py
index dbe3be225..1be2c283f 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,64 @@
# 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.configdep import set_dependents, call_dependents
from vyos.configdict import dict_merge
+from vyos.ifconfig import Section
+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 +81,186 @@ 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 'interface' in qos:
+ for ifname, if_conf in qos['interface'].items():
+ if_node = Section.get_config_path(ifname)
+
+ if not if_node:
+ continue
+
+ path = f'interfaces {if_node}'
+ if conf.exists(f'{path} mirror') or conf.exists(f'{path} redirect'):
+ type_node = path.split(" ")[1] # return only interface type node
+ set_dependents(type_node, conf, ifname)
if 'policy' in qos:
for policy in qos['policy']:
- # CLI mangles - to _ for better Jinja2 compatibility - do we need
- # Jinja2 here?
- policy = policy.replace('-','_')
-
- default_values = defaults(base + ['policy', policy])
+ # when calling defaults() we need to use the real CLI node, thus we
+ # need a hyphen
+ policy_hyphen = policy.replace('_', '-')
- # 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)
+
+ call_dependents()
+
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_ipoe-server.py b/src/conf_mode/service_ipoe-server.py
index e9afd6a55..95c72df47 100755
--- a/src/conf_mode/service_ipoe-server.py
+++ b/src/conf_mode/service_ipoe-server.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
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import jmespath
from sys import exit
@@ -29,9 +30,92 @@ from vyos import ConfigError
from vyos import airbag
airbag.enable()
+
ipoe_conf = '/run/accel-pppd/ipoe.conf'
ipoe_chap_secrets = '/run/accel-pppd/ipoe.chap-secrets'
+
+def get_pools_in_order(data: dict) -> list:
+ """Return a list of dictionaries representing pool data in the order
+ in which they should be allocated. Pool must be defined before we can
+ use it with 'next-pool' option.
+
+ Args:
+ data: A dictionary of pool data, where the keys are pool names and the
+ values are dictionaries containing the 'subnet' key and the optional
+ 'next_pool' key.
+
+ Returns:
+ list: A list of dictionaries
+
+ Raises:
+ ValueError: If a 'next_pool' key references a pool name that
+ has not been defined.
+ ValueError: If a circular reference is found in the 'next_pool' keys.
+
+ Example:
+ config_data = {
+ ... 'first-pool': {
+ ... 'next_pool': 'second-pool',
+ ... 'subnet': '192.0.2.0/25'
+ ... },
+ ... 'second-pool': {
+ ... 'next_pool': 'third-pool',
+ ... 'subnet': '203.0.113.0/25'
+ ... },
+ ... 'third-pool': {
+ ... 'subnet': '198.51.100.0/24'
+ ... },
+ ... 'foo': {
+ ... 'subnet': '100.64.0.0/24',
+ ... 'next_pool': 'second-pool'
+ ... }
+ ... }
+
+ % get_pools_in_order(config_data)
+ [{'third-pool': {'subnet': '198.51.100.0/24'}},
+ {'second-pool': {'next_pool': 'third-pool', 'subnet': '203.0.113.0/25'}},
+ {'first-pool': {'next_pool': 'second-pool', 'subnet': '192.0.2.0/25'}},
+ {'foo': {'next_pool': 'second-pool', 'subnet': '100.64.0.0/24'}}]
+ """
+ pools = []
+ unresolved_pools = {}
+
+ for pool, pool_config in data.items():
+ if 'next_pool' not in pool_config:
+ pools.insert(0, {pool: pool_config})
+ else:
+ unresolved_pools[pool] = pool_config
+
+ while unresolved_pools:
+ resolved_pools = []
+
+ for pool, pool_config in unresolved_pools.items():
+ next_pool_name = pool_config['next_pool']
+
+ if any(p for p in pools if next_pool_name in p):
+ index = next(
+ (i for i, p in enumerate(pools) if next_pool_name in p),
+ None)
+ pools.insert(index + 1, {pool: pool_config})
+ resolved_pools.append(pool)
+ elif next_pool_name in unresolved_pools:
+ # next pool not yet resolved
+ pass
+ else:
+ raise ValueError(
+ f"Pool '{next_pool_name}' not defined in configuration data"
+ )
+
+ if not resolved_pools:
+ raise ValueError("Circular reference in configuration data")
+
+ for pool in resolved_pools:
+ unresolved_pools.pop(pool)
+
+ return pools
+
+
def get_config(config=None):
if config:
conf = config
@@ -43,6 +127,19 @@ def get_config(config=None):
# retrieve common dictionary keys
ipoe = get_accel_dict(conf, base, ipoe_chap_secrets)
+
+ if jmespath.search('client_ip_pool.name', ipoe):
+ dict_named_pools = jmespath.search('client_ip_pool.name', ipoe)
+ # Multiple named pools require ordered values T5099
+ ipoe['ordered_named_pools'] = get_pools_in_order(dict_named_pools)
+ # T5099 'next-pool' option
+ if jmespath.search('client_ip_pool.name.*.next_pool', ipoe):
+ for pool, pool_config in ipoe['client_ip_pool']['name'].items():
+ if 'next_pool' in pool_config:
+ ipoe['first_named_pool'] = pool
+ ipoe['first_named_pool_subnet'] = pool_config
+ break
+
return ipoe
@@ -53,10 +150,24 @@ def verify(ipoe):
if 'interface' not in ipoe:
raise ConfigError('No IPoE interface configured')
- for interface in ipoe['interface']:
+ for interface, iface_config in ipoe['interface'].items():
verify_interface_exists(interface)
+ if 'client_subnet' in iface_config and 'vlan' in iface_config:
+ raise ConfigError('Option "client-subnet" incompatible with "vlan"!'
+ 'Use "ipoe client-ip-pool" instead.')
#verify_accel_ppp_base_service(ipoe, local_users=False)
+ # IPoE server does not have 'gateway' option in the CLI
+ # we cannot use configverify.py verify_accel_ppp_base_service for ipoe-server
+
+ if dict_search('authentication.mode', ipoe) == 'radius':
+ if not dict_search('authentication.radius.server', ipoe):
+ raise ConfigError('RADIUS authentication requires at least one server')
+
+ for server in dict_search('authentication.radius.server', ipoe):
+ radius_config = ipoe['authentication']['radius']['server'][server]
+ if 'key' not in radius_config:
+ raise ConfigError(f'Missing RADIUS secret key for server "{server}"')
if 'client_ipv6_pool' in ipoe:
if 'delegate' in ipoe['client_ipv6_pool'] and 'prefix' not in ipoe['client_ipv6_pool']:
diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py
index aafece47a..47510ce80 100755
--- a/src/conf_mode/service_monitoring_telegraf.py
+++ b/src/conf_mode/service_monitoring_telegraf.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
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import socket
import json
from sys import exit
@@ -38,7 +39,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 """
@@ -57,6 +58,13 @@ def get_nft_filter_chains():
return chain_list
+def get_hostname() -> str:
+ try:
+ hostname = socket.getfqdn()
+ except socket.gaierror:
+ hostname = socket.gethostname()
+ return hostname
+
def get_config(config=None):
if config:
conf = config
@@ -79,6 +87,7 @@ def get_config(config=None):
monitoring = dict_merge(default_values, monitoring)
monitoring['custom_scripts_dir'] = custom_scripts_dir
+ monitoring['hostname'] = get_hostname()
monitoring['interfaces_ethernet'] = Section.interfaces('ethernet', vlan=False)
monitoring['nft_chains'] = get_nft_filter_chains()
diff --git a/src/conf_mode/service_sla.py b/src/conf_mode/service_sla.py
index e7c3ca59c..b1e22f37b 100755
--- a/src/conf_mode/service_sla.py
+++ b/src/conf_mode/service_sla.py
@@ -27,15 +27,13 @@ from vyos import ConfigError
from vyos import airbag
airbag.enable()
-
owamp_config_dir = '/etc/owamp-server'
owamp_config_file = f'{owamp_config_dir}/owamp-server.conf'
-systemd_override_owamp = r'/etc/systemd/system/owamp-server.d/20-override.conf'
+systemd_override_owamp = r'/run/systemd/system/owamp-server.d/20-override.conf'
twamp_config_dir = '/etc/twamp-server'
twamp_config_file = f'{twamp_config_dir}/twamp-server.conf'
-systemd_override_twamp = r'/etc/systemd/system/twamp-server.d/20-override.conf'
-
+systemd_override_twamp = r'/run/systemd/system/twamp-server.d/20-override.conf'
def get_config(config=None):
if config:
diff --git a/src/conf_mode/service_webproxy.py b/src/conf_mode/service_webproxy.py
index 41a1deaa3..658e496a6 100755
--- a/src/conf_mode/service_webproxy.py
+++ b/src/conf_mode/service_webproxy.py
@@ -246,7 +246,7 @@ def apply(proxy):
if os.path.exists(squidguard_db_dir):
chmod_755(squidguard_db_dir)
- call('systemctl restart squid.service')
+ call('systemctl reload-or-restart squid.service')
return None
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 5cd24db32..9b7c04eb0 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):
@@ -92,7 +92,7 @@ def get_config(config=None):
# Always listen on localhost if an explicit address has been configured
# This is a safety measure to not end up with invalid listen addresses
- # that are not configured on this system. See https://phabricator.vyos.net/T850
+ # that are not configured on this system. See https://vyos.dev/T850
if '127.0.0.1' not in snmp['listen_address']:
tmp = {'127.0.0.1': {'port': '161'}}
snmp['listen_address'] = dict_merge(tmp, snmp['listen_address'])
@@ -103,6 +103,9 @@ def get_config(config=None):
if 'community' in snmp:
default_values = defaults(base + ['community'])
+ if 'network' in default_values:
+ # convert multiple default networks to list
+ default_values['network'] = default_values['network'].split()
for community in snmp['community']:
snmp['community'][community] = dict_merge(
default_values, snmp['community'][community])
@@ -166,6 +169,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-ip.py b/src/conf_mode/system-ip.py
index 0c5063ed3..95865c690 100755
--- a/src/conf_mode/system-ip.py
+++ b/src/conf_mode/system-ip.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
@@ -18,12 +18,15 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.configverify import verify_route_map
+from vyos.template import render_to_string
from vyos.util import call
from vyos.util import dict_search
from vyos.util import sysctl_write
from vyos.util import write_file
from vyos.xml import defaults
from vyos import ConfigError
+from vyos import frr
from vyos import airbag
airbag.enable()
@@ -40,13 +43,30 @@ def get_config(config=None):
default_values = defaults(base)
opt = dict_merge(default_values, opt)
+ # When working with FRR we need to know the corresponding address-family
+ opt['afi'] = 'ip'
+
+ # We also need the route-map information from the config
+ #
+ # XXX: one MUST always call this without the key_mangling() option! See
+ # vyos.configverify.verify_common_route_maps() for more information.
+ tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'],
+ get_first_key=True)}}
+ # Merge policy dict into "regular" config dict
+ opt = dict_merge(tmp, opt)
return opt
def verify(opt):
- pass
+ if 'protocol' in opt:
+ for protocol, protocol_options in opt['protocol'].items():
+ if 'route_map' in protocol_options:
+ verify_route_map(protocol_options['route_map'], opt)
+ return
def generate(opt):
- pass
+ if 'protocol' in opt:
+ opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt)
+ return
def apply(opt):
# Apply ARP threshold values
@@ -78,6 +98,18 @@ def apply(opt):
value = '1' if (tmp != None) else '0'
sysctl_write('net.ipv4.fib_multipath_hash_policy', value)
+ if 'protocol' in opt:
+ zebra_daemon = 'zebra'
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+
+ # The route-map used for the FIB (zebra) is part of the zebra daemon
+ frr_cfg.load_configuration(zebra_daemon)
+ frr_cfg.modify_section(r'ip protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
+ if 'frr_zebra_config' in opt:
+ frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config'])
+ frr_cfg.commit_configuration(zebra_daemon)
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py
index 26aacf46b..b6d3a79c3 100755
--- a/src/conf_mode/system-ipv6.py
+++ b/src/conf_mode/system-ipv6.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,11 +19,14 @@ import os
from sys import exit
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.configverify import verify_route_map
+from vyos.template import render_to_string
from vyos.util import dict_search
from vyos.util import sysctl_write
from vyos.util import write_file
from vyos.xml import defaults
from vyos import ConfigError
+from vyos import frr
from vyos import airbag
airbag.enable()
@@ -41,13 +44,30 @@ def get_config(config=None):
default_values = defaults(base)
opt = dict_merge(default_values, opt)
+ # When working with FRR we need to know the corresponding address-family
+ opt['afi'] = 'ipv6'
+
+ # We also need the route-map information from the config
+ #
+ # XXX: one MUST always call this without the key_mangling() option! See
+ # vyos.configverify.verify_common_route_maps() for more information.
+ tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'],
+ get_first_key=True)}}
+ # Merge policy dict into "regular" config dict
+ opt = dict_merge(tmp, opt)
return opt
def verify(opt):
- pass
+ if 'protocol' in opt:
+ for protocol, protocol_options in opt['protocol'].items():
+ if 'route_map' in protocol_options:
+ verify_route_map(protocol_options['route_map'], opt)
+ return
def generate(opt):
- pass
+ if 'protocol' in opt:
+ opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt)
+ return
def apply(opt):
# configure multipath
@@ -78,6 +98,18 @@ def apply(opt):
if name == 'accept_dad':
write_file(os.path.join(root, name), value)
+ if 'protocol' in opt:
+ zebra_daemon = 'zebra'
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+
+ # The route-map used for the FIB (zebra) is part of the zebra daemon
+ frr_cfg.load_configuration(zebra_daemon)
+ frr_cfg.modify_section(r'ipv6 protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
+ if 'frr_zebra_config' in opt:
+ frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config'])
+ frr_cfg.commit_configuration(zebra_daemon)
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py
index e26b81e3d..fbb013cf3 100755
--- a/src/conf_mode/system-login.py
+++ b/src/conf_mode/system-login.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
@@ -16,22 +16,21 @@
import os
-from crypt import crypt
-from crypt import METHOD_SHA512
+from passlib.hosts import linux_context
from psutil import users
from pwd import getpwall
from pwd import getpwnam
-from spwd import getspnam
from sys import exit
from time import sleep
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configverify import verify_vrf
+from vyos.defaults import directories
from vyos.template import render
from vyos.template import is_ipv4
from vyos.util import cmd
-from vyos.util import call
+from vyos.util import call, rc_cmd
from vyos.util import run
from vyos.util import DEVNULL
from vyos.util import dict_search
@@ -41,8 +40,14 @@ from vyos import airbag
airbag.enable()
autologout_file = "/etc/profile.d/autologout.sh"
+limits_file = "/etc/security/limits.d/10-vyos.conf"
radius_config_file = "/etc/pam_radius_auth.conf"
+# LOGIN_TIMEOUT from /etc/loign.defs minus 10 sec
+MAX_RADIUS_TIMEOUT: int = 50
+# MAX_RADIUS_TIMEOUT divided by 2 sec (minimum recomended timeout)
+MAX_RADIUS_COUNT: int = 25
+
def get_local_users():
"""Return list of dynamically allocated users (see Debian Policy Manual)"""
local_users = []
@@ -54,6 +59,13 @@ def get_local_users():
return local_users
+def get_shadow_password(username):
+ with open('/etc/shadow') as f:
+ for user in f.readlines():
+ items = user.split(":")
+ if username == items[0]:
+ return items[1]
+ return None
def get_config(config=None):
if config:
@@ -118,18 +130,27 @@ def verify(login):
if 'radius' in login:
if 'server' not in login['radius']:
raise ConfigError('No RADIUS server defined!')
-
+ sum_timeout: int = 0
+ radius_servers_count: int = 0
fail = True
for server, server_config in dict_search('radius.server', login).items():
if 'key' not in server_config:
raise ConfigError(f'RADIUS server "{server}" requires key!')
-
- if 'disabled' not in server_config:
+ if 'disable' not in server_config:
+ sum_timeout += int(server_config['timeout'])
+ radius_servers_count += 1
fail = False
- continue
+
if fail:
raise ConfigError('All RADIUS servers are disabled')
+ if radius_servers_count > MAX_RADIUS_COUNT:
+ raise ConfigError('Number of RADIUS servers more than 25 ')
+
+ if sum_timeout > MAX_RADIUS_TIMEOUT:
+ raise ConfigError('Sum of RADIUS servers timeouts '
+ 'has to be less or eq 50 sec')
+
verify_vrf(login['radius'])
if 'source_address' in login['radius']:
@@ -144,6 +165,9 @@ def verify(login):
if ipv6_count > 1:
raise ConfigError('Only one IPv6 source-address can be set!')
+ if 'max_login_session' in login and 'timeout' not in login:
+ raise ConfigError('"login timeout" must be configured!')
+
return None
@@ -153,13 +177,13 @@ def generate(login):
for user, user_config in login['user'].items():
tmp = dict_search('authentication.plaintext_password', user_config)
if tmp:
- encrypted_password = crypt(tmp, METHOD_SHA512)
+ encrypted_password = linux_context.hash(tmp)
login['user'][user]['authentication']['encrypted_password'] = encrypted_password
del login['user'][user]['authentication']['plaintext_password']
# remove old plaintext password and set new encrypted password
env = os.environ.copy()
- env['vyos_libexec_dir'] = '/usr/libexec/vyos'
+ env['vyos_libexec_dir'] = directories['base']
# Set default commands for re-adding user with encrypted password
del_user_plain = f"system login user '{user}' authentication plaintext-password"
@@ -183,10 +207,12 @@ def generate(login):
add_user_encrypt = " ".join(add_user_encrypt)
call(f"/opt/vyatta/sbin/my_delete {del_user_plain}", env=env)
- call(f"/opt/vyatta/sbin/my_set {add_user_encrypt}", env=env)
+ ret, out = rc_cmd(f"/opt/vyatta/sbin/my_set {add_user_encrypt}", env=env)
+ if ret:
+ raise ConfigError(out)
else:
try:
- if getspnam(user).sp_pwdp == dict_search('authentication.encrypted_password', user_config):
+ if get_shadow_password(user) == dict_search('authentication.encrypted_password', user_config):
# If the current encrypted bassword matches the encrypted password
# from the config - do not update it. This will remove the encrypted
# value from the system logs.
@@ -204,6 +230,14 @@ def generate(login):
if os.path.isfile(radius_config_file):
os.unlink(radius_config_file)
+ # /etc/security/limits.d/10-vyos.conf
+ if 'max_login_session' in login:
+ render(limits_file, 'login/limits.j2', login,
+ permission=0o644, user='root', group='root')
+ else:
+ if os.path.isfile(limits_file):
+ os.unlink(limits_file)
+
if 'timeout' in login:
render(autologout_file, 'login/autologout.j2', login,
permission=0o755, user='root', group='root')
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/system_sflow.py b/src/conf_mode/system_sflow.py
new file mode 100755
index 000000000..a0c3fca7f
--- /dev/null
+++ b/src/conf_mode/system_sflow.py
@@ -0,0 +1,124 @@
+#!/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
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.template import render
+from vyos.util import call
+from vyos.validate import is_addr_assigned
+from vyos.xml import defaults
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+hsflowd_conf_path = '/run/sflow/hsflowd.conf'
+systemd_service = 'hsflowd.service'
+systemd_override = f'/run/systemd/system/{systemd_service}.d/override.conf'
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['system', 'sflow']
+ if not conf.exists(base):
+ return None
+
+ sflow = 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)
+
+ sflow = dict_merge(default_values, sflow)
+
+ # Ignore default XML values if config doesn't exists
+ # Delete key from dict
+ if 'port' in sflow['server']:
+ del sflow['server']['port']
+
+ # Set default values per server
+ if 'server' in sflow:
+ for server in sflow['server']:
+ default_values = defaults(base + ['server'])
+ sflow['server'][server] = dict_merge(default_values, sflow['server'][server])
+
+ return sflow
+
+
+def verify(sflow):
+ if not sflow:
+ return None
+
+ # Check if configured sflow agent-address exist in the system
+ if 'agent_address' in sflow:
+ tmp = sflow['agent_address']
+ if not is_addr_assigned(tmp):
+ raise ConfigError(
+ f'Configured "sflow agent-address {tmp}" does not exist in the system!'
+ )
+
+ # Check if at least one interface is configured
+ if 'interface' not in sflow:
+ raise ConfigError(
+ 'sFlow requires at least one interface to be configured!')
+
+ # Check if at least one server is configured
+ if 'server' not in sflow:
+ raise ConfigError('You need to configure at least one sFlow server!')
+
+ # return True if all checks were passed
+ return True
+
+
+def generate(sflow):
+ if not sflow:
+ return None
+
+ render(hsflowd_conf_path, 'sflow/hsflowd.conf.j2', sflow)
+ render(systemd_override, 'sflow/override.conf.j2', sflow)
+ # Reload systemd manager configuration
+ call('systemctl daemon-reload')
+
+
+def apply(sflow):
+ if not sflow:
+ # Stop flow-accounting daemon and remove configuration file
+ call(f'systemctl stop {systemd_service}')
+ if os.path.exists(hsflowd_conf_path):
+ os.unlink(hsflowd_conf_path)
+ return
+
+ # Start/reload flow-accounting daemon
+ call(f'systemctl restart {systemd_service}')
+
+
+if __name__ == '__main__':
+ try:
+ config = get_config()
+ verify(config)
+ generate(config)
+ apply(config)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index b79e9847a..63887b278 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,6 +17,7 @@
import ipaddress
import os
import re
+import jmespath
from sys import exit
from time import sleep
@@ -52,8 +53,6 @@ dhcp_wait_attempts = 2
dhcp_wait_sleep = 1
swanctl_dir = '/etc/swanctl'
-ipsec_conf = '/etc/ipsec.conf'
-ipsec_secrets = '/etc/ipsec.secrets'
charon_conf = '/etc/strongswan.d/charon.conf'
charon_dhcp_conf = '/etc/strongswan.d/charon/dhcp.conf'
charon_radius_conf = '/etc/strongswan.d/charon/eap-radius.conf'
@@ -95,6 +94,7 @@ def get_config(config=None):
del default_values['esp_group']
del default_values['ike_group']
del default_values['remote_access']
+ del default_values['site_to_site']
ipsec = dict_merge(default_values, ipsec)
if 'esp_group' in ipsec:
@@ -143,6 +143,14 @@ def get_config(config=None):
ipsec['remote_access']['radius']['server'][server] = dict_merge(default_values,
ipsec['remote_access']['radius']['server'][server])
+ # XXX: T2665: we can not safely rely on the defaults() when there are
+ # tagNodes in place, it is better to blend in the defaults manually.
+ if dict_search('site_to_site.peer', ipsec):
+ default_values = defaults(base + ['site-to-site', 'peer'])
+ for peer in ipsec['site_to_site']['peer']:
+ ipsec['site_to_site']['peer'][peer] = dict_merge(default_values,
+ ipsec['site_to_site']['peer'][peer])
+
ipsec['dhcp_no_address'] = {}
ipsec['install_routes'] = 'no' if conf.exists(base + ["options", "disable-route-autoinstall"]) else default_install_routes
ipsec['interface_change'] = leaf_node_changed(conf, base + ['interface'])
@@ -210,6 +218,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)
@@ -526,8 +540,7 @@ def generate(ipsec):
cleanup_pki_files()
if not ipsec:
- for config_file in [ipsec_conf, ipsec_secrets, charon_dhcp_conf,
- charon_radius_conf, interface_conf, swanctl_conf]:
+ for config_file in [charon_dhcp_conf, charon_radius_conf, interface_conf, swanctl_conf]:
if os.path.isfile(config_file):
os.unlink(config_file)
render(charon_conf, 'ipsec/charon.j2', {'install_routes': default_install_routes})
@@ -536,6 +549,8 @@ def generate(ipsec):
if ipsec['dhcp_no_address']:
with open(DHCP_HOOK_IFLIST, 'w') as f:
f.write(" ".join(ipsec['dhcp_no_address'].values()))
+ elif os.path.exists(DHCP_HOOK_IFLIST):
+ os.unlink(DHCP_HOOK_IFLIST)
for path in [swanctl_dir, CERT_PATH, CA_PATH, CRL_PATH, PUBKEY_PATH]:
if not os.path.exists(path):
@@ -593,9 +608,15 @@ 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)
render(charon_conf, 'ipsec/charon.j2', ipsec)
render(charon_dhcp_conf, 'ipsec/charon/dhcp.conf.j2', ipsec)
render(charon_radius_conf, 'ipsec/charon/eap-radius.conf.j2', ipsec)
@@ -610,25 +631,12 @@ def resync_nhrp(ipsec):
if tmp > 0:
print('ERROR: failed to reapply NHRP settings!')
-def wait_for_vici_socket(timeout=5, sleep_interval=0.1):
- start_time = time()
- test_command = f'sudo socat -u OPEN:/dev/null UNIX-CONNECT:{vici_socket}'
- while True:
- if (start_time + timeout) < time():
- return None
- result = run(test_command)
- if result == 0:
- return True
- sleep(sleep_interval)
-
def apply(ipsec):
- systemd_service = 'strongswan-starter.service'
+ systemd_service = 'strongswan.service'
if not ipsec:
call(f'systemctl stop {systemd_service}')
else:
call(f'systemctl reload-or-restart {systemd_service}')
- if wait_for_vici_socket():
- call('sudo swanctl -q')
resync_nhrp(ipsec)
diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py
index 27e78db99..65623c2b1 100755
--- a/src/conf_mode/vpn_l2tp.py
+++ b/src/conf_mode/vpn_l2tp.py
@@ -58,6 +58,9 @@ default_config_data = {
'ppp_echo_failure' : '3',
'ppp_echo_interval' : '30',
'ppp_echo_timeout': '0',
+ 'ppp_ipv6_accept_peer_intf_id': False,
+ 'ppp_ipv6_intf_id': None,
+ 'ppp_ipv6_peer_intf_id': None,
'radius_server': [],
'radius_acct_inter_jitter': '',
'radius_acct_tmo': '3',
@@ -314,6 +317,15 @@ def get_config(config=None):
if conf.exists(['ppp-options', 'ipv6']):
l2tp['ppp_ipv6'] = conf.return_value(['ppp-options', 'ipv6'])
+ if conf.exists(['ppp-options', 'ipv6-accept-peer-intf-id']):
+ l2tp['ppp_ipv6_accept_peer_intf_id'] = True
+
+ if conf.exists(['ppp-options', 'ipv6-intf-id']):
+ l2tp['ppp_ipv6_intf_id'] = conf.return_value(['ppp-options', 'ipv6-intf-id'])
+
+ if conf.exists(['ppp-options', 'ipv6-peer-intf-id']):
+ l2tp['ppp_ipv6_peer_intf_id'] = conf.return_value(['ppp-options', 'ipv6-peer-intf-id'])
+
return l2tp
diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py
index af3c51efc..68da70d7d 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.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
@@ -46,8 +46,70 @@ radius_servers = cfg_dir + '/radius_servers'
def get_hash(password):
return crypt(password, mksalt(METHOD_SHA512))
-def get_config():
- conf = Config()
+
+def _default_dict_cleanup(origin: dict, default_values: dict) -> dict:
+ """
+ https://vyos.dev/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 authentication mode local requires 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 requires 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 requires 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(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
base = ['vpn', 'openconnect']
if not conf.exists(base):
return None
@@ -57,18 +119,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 'mode' in ocserv["authentication"] and "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://vyos.dev/T2665
+ ocserv = _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 +137,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"]:
@@ -166,10 +226,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"]:
diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py
index 7550c411e..986a19972 100755
--- a/src/conf_mode/vpn_pptp.py
+++ b/src/conf_mode/vpn_pptp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 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
@@ -44,6 +44,8 @@ default_pptp = {
'radius_nas_ip' : '',
'radius_source_address' : '',
'radius_shaper_attr' : '',
+ 'radius_shaper_enable': False,
+ 'radius_shaper_multiplier': '',
'radius_shaper_vendor': '',
'radius_dynamic_author' : '',
'chap_secrets_file': pptp_chap_secrets, # used in Jinja2 template
@@ -183,15 +185,18 @@ def get_config(config=None):
pptp['radius_dynamic_author'] = dae
+ # Rate limit
+ if conf.exists(['rate-limit', 'attribute']):
+ pptp['radius_shaper_attr'] = conf.return_value(['rate-limit', 'attribute'])
+
if conf.exists(['rate-limit', 'enable']):
- pptp['radius_shaper_attr'] = 'Filter-Id'
- c_attr = ['rate-limit', 'enable', 'attribute']
- if conf.exists(c_attr):
- pptp['radius_shaper_attr'] = conf.return_value(c_attr)
-
- c_vendor = ['rate-limit', 'enable', 'vendor']
- if conf.exists(c_vendor):
- pptp['radius_shaper_vendor'] = conf.return_value(c_vendor)
+ pptp['radius_shaper_enable'] = True
+
+ if conf.exists(['rate-limit', 'multiplier']):
+ pptp['radius_shaper_multiplier'] = conf.return_value(['rate-limit', 'multiplier'])
+
+ if conf.exists(['rate-limit', 'vendor']):
+ pptp['radius_shaper_vendor'] = conf.return_value(['rate-limit', 'vendor'])
conf.set_level(base_path)
if conf.exists(['client-ip-pool']):
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index 1b4156895..0b983293e 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
@@ -20,9 +20,12 @@ from sys import exit
from json import loads
from vyos.config import Config
+from vyos.configdict import dict_merge
from vyos.configdict import node_changed
+from vyos.configverify import verify_route_map
from vyos.ifconfig import Interface
from vyos.template import render
+from vyos.template import render_to_string
from vyos.util import call
from vyos.util import cmd
from vyos.util import dict_search
@@ -99,6 +102,20 @@ def get_config(config=None):
routes = vrf_routing(conf, name)
if routes: vrf['vrf_remove'][name]['route'] = routes
+ # We also need the route-map information from the config
+ #
+ # XXX: one MUST always call this without the key_mangling() option! See
+ # vyos.configverify.verify_common_route_maps() for more information.
+ tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'],
+ get_first_key=True)}}
+
+ # L3VNI setup is done via vrf_vni.py as it must be de-configured (on node
+ # deletetion prior to the BGP process. Tell the Jinja2 template no VNI
+ # setup is needed
+ vrf.update({'no_vni' : ''})
+
+ # Merge policy dict into "regular" config dict
+ vrf = dict_merge(tmp, vrf)
return vrf
def verify(vrf):
@@ -113,41 +130,54 @@ def verify(vrf):
f'static routes installed!')
if 'name' in vrf:
- reserved_names = ["add", "all", "broadcast", "default", "delete", "dev", "get", "inet", "mtu", "link", "type",
- "vrf"]
+ reserved_names = ["add", "all", "broadcast", "default", "delete", "dev",
+ "get", "inet", "mtu", "link", "type", "vrf"]
table_ids = []
- for name, config in vrf['name'].items():
+ for name, vrf_config in vrf['name'].items():
# Reserved VRF names
if name in reserved_names:
raise ConfigError(f'VRF name "{name}" is reserved and connot be used!')
# table id is mandatory
- if 'table' not in config:
+ if 'table' not in vrf_config:
raise ConfigError(f'VRF "{name}" table id is mandatory!')
# routing table id can't be changed - OS restriction
if os.path.isdir(f'/sys/class/net/{name}'):
tmp = str(dict_search('linkinfo.info_data.table', get_interface_config(name)))
- if tmp and tmp != config['table']:
+ if tmp and tmp != vrf_config['table']:
raise ConfigError(f'VRF "{name}" table id modification not possible!')
- # VRf routing table ID must be unique on the system
- if config['table'] in table_ids:
+ # VRF routing table ID must be unique on the system
+ if 'table' in vrf_config and vrf_config['table'] in table_ids:
raise ConfigError(f'VRF "{name}" table id is not unique!')
- table_ids.append(config['table'])
+ table_ids.append(vrf_config['table'])
+
+ tmp = dict_search('ip.protocol', vrf_config)
+ if tmp != None:
+ for protocol, protocol_options in tmp.items():
+ if 'route_map' in protocol_options:
+ verify_route_map(protocol_options['route_map'], vrf)
+
+ tmp = dict_search('ipv6.protocol', vrf_config)
+ if tmp != None:
+ for protocol, protocol_options in tmp.items():
+ if 'route_map' in protocol_options:
+ verify_route_map(protocol_options['route_map'], vrf)
return None
def generate(vrf):
- render(config_file, 'vrf/vrf.conf.j2', vrf)
+ # Render iproute2 VR helper names
+ render(config_file, 'iproute2/vrf.conf.j2', vrf)
# Render nftables zones config
-
render(nft_vrf_config, 'firewall/nftables-vrf-zones.j2', vrf)
+ # Render VRF Kernel/Zebra route-map filters
+ vrf['frr_zebra_config'] = render_to_string('frr/zebra.vrf.route-map.frr.j2', vrf)
return None
-
def apply(vrf):
# Documentation
#
@@ -251,6 +281,17 @@ def apply(vrf):
nft_add_element = f'add element inet vrf_zones ct_iface_map {{ "{name}" : {table} }}'
cmd(f'nft {nft_add_element}')
+ # Apply FRR filters
+ zebra_daemon = 'zebra'
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+
+ # The route-map used for the FIB (zebra) is part of the zebra daemon
+ frr_cfg.load_configuration(zebra_daemon)
+ frr_cfg.modify_section(f'^vrf .+', stop_pattern='^exit-vrf', remove_stop_mark=True)
+ if 'frr_zebra_config' in vrf:
+ frr_cfg.add_before(frr.default_add_before, vrf['frr_zebra_config'])
+ frr_cfg.commit_configuration(zebra_daemon)
# return to default lookup preference when no VRF is configured
if 'name' not in vrf:
diff --git a/src/conf_mode/vrf_vni.py b/src/conf_mode/vrf_vni.py
index 585fdbebf..a7424b517 100755..100644
--- a/src/conf_mode/vrf_vni.py
+++ b/src/conf_mode/vrf_vni.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2021 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
@@ -19,36 +19,75 @@ from sys import exit
from vyos.config import Config
from vyos.template import render_to_string
+from vyos.util import dict_search
from vyos import ConfigError
from vyos import frr
from vyos import airbag
airbag.enable()
-frr_daemon = 'zebra'
-
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- base = ['vrf']
- vrf = conf.get_config_dict(base, get_first_key=True)
+ vrf_name = None
+ if len(argv) > 1:
+ vrf_name = argv[1]
+ else:
+ return None
+
+ # Using duplicate L3VNIs makes no sense - it's also forbidden in FRR,
+ # thus VyOS CLI must deny this, too. Instead of getting only the dict for
+ # the requested VRF and den comparing it with depenent VRfs to not have any
+ # duplicate we will just grad ALL VRFs by default but only render/apply
+ # the configuration for the requested VRF - that makes the code easier and
+ # hopefully less error prone
+ vrf = conf.get_config_dict(['vrf'], key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ get_first_key=True)
+
+ # Store name of VRF we are interested in for FRR config rendering
+ vrf.update({'only_vrf' : vrf_name})
+
return vrf
def verify(vrf):
+ if not vrf:
+ return
+
+ if len(argv) < 2:
+ raise ConfigError('VRF parameter not specified when valling vrf_vni.py')
+
+ if 'name' in vrf:
+ vni_ids = []
+ for name, vrf_config in vrf['name'].items():
+ # VRF VNI (Virtual Network Identifier) must be unique on the system
+ if 'vni' in vrf_config:
+ if vrf_config['vni'] in vni_ids:
+ raise ConfigError(f'VRF "{name}" VNI is not unique!')
+ vni_ids.append(vrf_config['vni'])
+
return None
def generate(vrf):
- vrf['new_frr_config'] = render_to_string('frr/vrf-vni.frr.j2', vrf)
+ if not vrf:
+ return
+
+ vrf['new_frr_config'] = render_to_string('frr/zebra.vrf.route-map.frr.j2', vrf)
return None
def apply(vrf):
+ frr_daemon = 'zebra'
+
# add configuration to FRR
frr_cfg = frr.FRRConfig()
frr_cfg.load_configuration(frr_daemon)
- frr_cfg.modify_section(f'^vrf .+', stop_pattern='^exit-vrf', remove_stop_mark=True)
- if 'new_frr_config' in vrf:
+ # There is only one VRF inside the dict as we read only one in get_config()
+ if vrf and 'name' in vrf:
+ vrf_name = vrf['only_vrf']
+ frr_cfg.modify_section(f'^vrf {vrf_name}', stop_pattern='^exit-vrf', remove_stop_mark=True)
+ if vrf and 'new_frr_config' in vrf:
frr_cfg.add_before(frr.default_add_before, vrf['new_frr_config'])
frr_cfg.commit_configuration(frr_daemon)
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..8ec732df0
--- /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://vyos.dev/T4975
+sync
+
diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/99-run-user-hooks b/src/etc/dhcp/dhclient-enter-hooks.d/99-run-user-hooks
new file mode 100644
index 000000000..b4b4d516d
--- /dev/null
+++ b/src/etc/dhcp/dhclient-enter-hooks.d/99-run-user-hooks
@@ -0,0 +1,5 @@
+#!/bin/bash
+DHCP_PRE_HOOKS="/config/scripts/dhcp-client/pre-hooks.d/"
+if [ -d "${DHCP_PRE_HOOKS}" ] ; then
+ run-parts "${DHCP_PRE_HOOKS}"
+fi
diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook b/src/etc/dhcp/dhclient-exit-hooks.d/03-vyatta-dhclient-hook
index 49bb18372..49bb18372 100644
--- a/src/etc/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook
+++ b/src/etc/dhcp/dhclient-exit-hooks.d/03-vyatta-dhclient-hook
diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/98-run-user-hooks b/src/etc/dhcp/dhclient-exit-hooks.d/98-run-user-hooks
new file mode 100755
index 000000000..442419d79
--- /dev/null
+++ b/src/etc/dhcp/dhclient-exit-hooks.d/98-run-user-hooks
@@ -0,0 +1,5 @@
+#!/bin/bash
+DHCP_POST_HOOKS="/config/scripts/dhcp-client/post-hooks.d/"
+if [ -d "${DHCP_POST_HOOKS}" ] ; then
+ run-parts "${DHCP_POST_HOOKS}"
+fi
diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook b/src/etc/dhcp/dhclient-exit-hooks.d/99-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/99-ipsec-dhclient-hook
@@ -23,7 +23,7 @@ DHCP_HOOK_IFLIST="/tmp/ipsec_dhcp_waiting"
if [ -f $DHCP_HOOK_IFLIST ] && [ "$reason" == "BOUND" ]; then
if grep -qw $interface $DHCP_HOOK_IFLIST; then
sudo rm $DHCP_HOOK_IFLIST
- sudo python3 /usr/libexec/vyos/conf_mode/vpn_ipsec.py
+ sudo /usr/libexec/vyos/conf_mode/vpn_ipsec.py
exit 0
fi
fi
diff --git a/src/etc/modprobe.d/ifb.conf b/src/etc/modprobe.d/ifb.conf
new file mode 100644
index 000000000..2dcfb6af4
--- /dev/null
+++ b/src/etc/modprobe.d/ifb.conf
@@ -0,0 +1 @@
+options ifb numifbs=0
diff --git a/src/etc/opennhrp/opennhrp-script.py b/src/etc/opennhrp/opennhrp-script.py
index bf25a7331..688c7af2a 100755
--- a/src/etc/opennhrp/opennhrp-script.py
+++ b/src/etc/opennhrp/opennhrp-script.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
@@ -17,7 +17,7 @@
import os
import re
import sys
-import vici
+import vyos.ipsec
from json import loads
from pathlib import Path
@@ -51,9 +51,8 @@ def vici_get_ipsec_uniqueid(conn: str, src_nbma: str,
logger.info(
f'Resolving IKE unique ids for: conn: {conn}, '
f'src_nbma: {src_nbma}, dst_nbma: {dst_nbma}')
- session: vici.Session = vici.Session()
list_ikeid: list[str] = []
- list_sa = session.list_sas({'ike': conn})
+ list_sa: list = vyos.ipsec.get_vici_sas_by_name(conn, None)
for sa in list_sa:
if sa[conn]['local-host'].decode('ascii') == src_nbma \
and sa[conn]['remote-host'].decode('ascii') == dst_nbma:
@@ -78,16 +77,7 @@ def vici_ike_terminate(list_ikeid: list[str]) -> bool:
return False
try:
- session = vici.Session()
- for ikeid in list_ikeid:
- logger.info(f'Terminating IKE SA with id {ikeid}')
- session_generator = session.terminate(
- {'ike-id': ikeid, 'timeout': '-1'})
- # a dummy `for` loop is required because of requirements
- # from vici. Without a full iteration on the output, the
- # command to vici may not be executed completely
- for _ in session_generator:
- pass
+ vyos.ipsec.terminate_vici_ikeid_list(list_ikeid)
return True
except Exception as err:
logger.error(f'Failed to terminate SA for IKE ids {list_ikeid}: {err}')
@@ -180,19 +170,7 @@ def vici_initiate(conn: str, child_sa: str, src_addr: str,
f'Trying to initiate connection. Name: {conn}, child sa: {child_sa}, '
f'src_addr: {src_addr}, dst_addr: {dest_addr}')
try:
- session = vici.Session()
- session_generator = session.initiate({
- 'ike': conn,
- 'child': child_sa,
- 'timeout': '-1',
- 'my-host': src_addr,
- 'other-host': dest_addr
- })
- # a dummy `for` loop is required because of requirements
- # from vici. Without a full iteration on the output, the
- # command to vici may not be executed completely
- for _ in session_generator:
- pass
+ vyos.ipsec.vici_initiate(conn, child_sa, src_addr, dest_addr)
return True
except Exception as err:
logger.error(f'Unable to initiate connection {err}')
@@ -218,8 +196,11 @@ def vici_terminate(conn: str, src_addr: str, dest_addr: str) -> None:
f'No active sessions found for IKE profile {conn}, '
f'local NBMA {src_addr}, remote NBMA {dest_addr}')
else:
- vici_ike_terminate(ikeid_list)
-
+ try:
+ vyos.ipsec.terminate_vici_ikeid_list(ikeid_list)
+ except Exception as err:
+ logger.error(
+ f'Failed to terminate SA for IKE ids {ikeid_list}: {err}')
def iface_up(interface: str) -> None:
"""Proceed tunnel interface UP event
diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf
index 411429510..f5d84be4b 100644
--- a/src/etc/sysctl.d/30-vyos-router.conf
+++ b/src/etc/sysctl.d/30-vyos-router.conf
@@ -19,7 +19,7 @@ kernel.core_pattern=/var/core/core-%e-%p-%t
# arp_filter defaults to 1 so set all to 0 so vrrp interfaces can override it.
net.ipv4.conf.all.arp_filter=0
-# https://phabricator.vyos.net/T300
+# https://vyos.dev/T300
net.ipv4.conf.all.arp_ignore=0
net.ipv4.conf.all.arp_announce=2
@@ -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/etc/systemd/system/ddclient.service.d/override.conf b/src/etc/systemd/system/ddclient.service.d/override.conf
index d9c9963b0..09d929d39 100644
--- a/src/etc/systemd/system/ddclient.service.d/override.conf
+++ b/src/etc/systemd/system/ddclient.service.d/override.conf
@@ -8,4 +8,4 @@ WorkingDirectory=/run/ddclient
PIDFile=
PIDFile=/run/ddclient/ddclient.pid
ExecStart=
-ExecStart=/usr/sbin/ddclient -cache /run/ddclient/ddclient.cache -pid /run/ddclient/ddclient.pid -file /run/ddclient/ddclient.conf
+ExecStart=/usr/bin/ddclient -cache /run/ddclient/ddclient.cache -pid /run/ddclient/ddclient.pid -file /run/ddclient/ddclient.conf
diff --git a/src/etc/systemd/system/frr.service.d/override.conf b/src/etc/systemd/system/frr.service.d/override.conf
index 69eb1a86a..2e4b6e295 100644
--- a/src/etc/systemd/system/frr.service.d/override.conf
+++ b/src/etc/systemd/system/frr.service.d/override.conf
@@ -3,6 +3,7 @@ Before=
Before=vyos-router.service
[Service]
+LimitNOFILE=4096
ExecStartPre=/bin/bash -c 'mkdir -p /run/frr/config; \
echo "log syslog" > /run/frr/config/frr.conf; \
echo "log facility local7" >> /run/frr/config/frr.conf; \
diff --git a/src/etc/systemd/system/hostapd@.service.d/override.conf b/src/etc/systemd/system/hostapd@.service.d/override.conf
index bb8e81d7a..926c07f94 100644
--- a/src/etc/systemd/system/hostapd@.service.d/override.conf
+++ b/src/etc/systemd/system/hostapd@.service.d/override.conf
@@ -1,6 +1,8 @@
[Unit]
After=
After=vyos-router.service
+ConditionFileNotEmpty=
+ConditionFileNotEmpty=/run/hostapd/%i.conf
[Service]
WorkingDirectory=/run/hostapd
diff --git a/src/systemd/keepalived.service b/src/etc/systemd/system/keepalived.service.d/override.conf
index a462d8614..d91a824b9 100644
--- a/src/systemd/keepalived.service
+++ b/src/etc/systemd/system/keepalived.service.d/override.conf
@@ -1,13 +1,14 @@
[Unit]
-Description=Keepalive Daemon (LVS and VRRP)
After=vyos-router.service
-# Only start if there is a configuration file
+# Only start if there is our configuration file - remove Debian default
+# config file from the condition list
+ConditionFileNotEmpty=
ConditionFileNotEmpty=/run/keepalived/keepalived.conf
[Service]
KillMode=process
Type=simple
# Read configuration variable file if it is present
+ExecStart=
ExecStart=/usr/sbin/keepalived --use-file /run/keepalived/keepalived.conf --pid /run/keepalived/keepalived.pid --dont-fork --snmp
-ExecReload=/bin/kill -HUP $MAINPID
PIDFile=/run/keepalived/keepalived.pid
diff --git a/src/helpers/vyos-failover.py b/src/helpers/vyos-failover.py
index 1ac193423..ce4cf8fa4 100755
--- a/src/helpers/vyos-failover.py
+++ b/src/helpers/vyos-failover.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -28,6 +28,17 @@ from systemd import journal
my_name = Path(__file__).stem
+def is_route_exists(route, gateway, interface, metric):
+ """Check if route with expected gateway, dev and metric exists"""
+ rc, data = rc_cmd(f'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)
@@ -61,6 +72,7 @@ def get_best_route_options(route, debug=False):
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
@@ -80,32 +92,70 @@ def is_port_open(ip, port):
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
+def is_target_alive(target_list=None,
+ iface='',
+ proto='icmp',
+ port=None,
+ debug=False,
+ policy='any-available') -> bool:
+ """Check the availability of each target in the target_list using
+ the specified protocol ICMP, ARP, TCP
+
+ Args:
+ target_list (list): A list of IP addresses or hostnames to check.
+ iface (str): The name of the network interface to use for the check.
+ proto (str): The protocol to use for the check. Options are 'icmp', 'arp', or 'tcp'.
+ port (int): The port number to use for the TCP check. Only applicable if proto is 'tcp'.
+ debug (bool): If True, print debug information during the check.
+ policy (str): The policy to use for the check. Options are 'any-available' or 'all-available'.
+
+ Returns:
+ bool: True if all targets are reachable according to the policy, False otherwise.
+
+ Example:
+ % is_target_alive(['192.0.2.1', '192.0.2.5'], 'eth1', proto='arp', policy='all-available')
+ 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:
+
+ num_reachable_targets = 0
+ for target in target_list:
+ match proto:
+ case '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:
+ num_reachable_targets += 1
+ if policy == 'any-available':
+ return True
+
+ case '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:
+ num_reachable_targets += 1
+ if policy == 'any-available':
+ return True
+
+ case _ if proto == 'tcp' and port is not None:
+ if is_port_open(target, port):
+ num_reachable_targets += 1
+ if policy == 'any-available':
+ return True
+
+ case _:
+ return False
+
+ if policy == 'all-available' and num_reachable_targets == len(target_list):
return True
- elif proto == 'tcp' and port is not None:
- return True if is_port_open(target, port) else False
- else:
- return False
+
+ return False
if __name__ == '__main__':
@@ -137,22 +187,23 @@ if __name__ == '__main__':
for route, route_config in config.get('route').items():
- exists_route = exists_gateway, exists_iface, exists_metric = get_best_route_options(route, debug=debug)
+ exists_gateway, exists_iface, exists_metric = get_best_route_options(route, debug=debug)
for next_hop, nexthop_config in route_config.get('next_hop').items():
conf_iface = nexthop_config.get('interface')
conf_metric = int(nexthop_config.get('metric'))
port = nexthop_config.get('check').get('port')
port_opt = f'port {port}' if port else ''
+ policy = nexthop_config.get('check').get('policy')
proto = nexthop_config.get('check').get('type')
target = nexthop_config.get('check').get('target')
timeout = nexthop_config.get('check').get('timeout')
- # Best route not fonund in the current routing table
- if exists_route == (None, None, None):
+ # Route not found in the current routing table
+ if not is_route_exists(route, next_hop, conf_iface, conf_metric):
if debug: print(f" [NEW_ROUTE_DETECTED] route: [{route}]")
# Add route if check-target alive
- if is_target_alive(target, conf_iface, proto, port, debug=debug):
+ if is_target_alive(target, conf_iface, proto, port, debug=debug, policy=policy):
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} '
@@ -171,8 +222,8 @@ if __name__ == '__main__':
# Route was added, check if the target is alive
# We should delete route if check fails only if route exists in the routing table
- if not is_target_alive(target, conf_iface, proto, port, debug=debug) and \
- exists_route != (None, None, None):
+ if not is_target_alive(target, conf_iface, proto, port, debug=debug, policy=policy) 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} '
diff --git a/src/migration-scripts/bgp/3-to-4 b/src/migration-scripts/bgp/3-to-4
new file mode 100755
index 000000000..0df2fbec4
--- /dev/null
+++ b/src/migration-scripts/bgp/3-to-4
@@ -0,0 +1,64 @@
+#!/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/>.
+
+# T5150: Rework CLI definitions to apply route-maps between routing daemons
+# and zebra/kernel
+
+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()
+
+config = ConfigTree(config_file)
+
+bgp_base = ['protocols', 'bgp']
+# Check if BGP is configured - if so, migrate the CLI node
+if config.exists(bgp_base):
+ if config.exists(bgp_base + ['route-map']):
+ tmp = config.return_value(bgp_base + ['route-map'])
+
+ config.set(['system', 'ip', 'protocol', 'bgp', 'route-map'], value=tmp)
+ config.set_tag(['system', 'ip', 'protocol'])
+ config.delete(bgp_base + ['route-map'])
+
+
+# Check if vrf names are configured. Check if BGP is configured - if so, migrate
+# the CLI node(s)
+if config.exists(['vrf', 'name']):
+ for vrf in config.list_nodes(['vrf', 'name']):
+ vrf_base = ['vrf', 'name', vrf]
+ if config.exists(vrf_base + ['protocols', 'bgp', 'route-map']):
+ tmp = config.return_value(vrf_base + ['protocols', 'bgp', 'route-map'])
+
+ config.set(vrf_base + ['ip', 'protocol', 'bgp', 'route-map'], value=tmp)
+ config.set_tag(vrf_base + ['ip', 'protocol', 'bgp'])
+ config.delete(vrf_base + ['protocols', 'bgp', 'route-map'])
+
+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/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/dns-forwarding/3-to-4 b/src/migration-scripts/dns-forwarding/3-to-4
new file mode 100755
index 000000000..55165c2c5
--- /dev/null
+++ b/src/migration-scripts/dns-forwarding/3-to-4
@@ -0,0 +1,49 @@
+#!/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/>.
+
+# T5115: migrate "service dns forwarding domain example.com server" to
+# "service dns forwarding domain example.com name-server"
+
+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 = ['service', 'dns', 'forwarding', 'domain']
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+
+for domain in config.list_nodes(base):
+ if config.exists(base + [domain, 'server']):
+ config.copy(base + [domain, 'server'], base + [domain, 'name-server'])
+ config.delete(base + [domain, 'server'])
+
+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/firewall/9-to-10 b/src/migration-scripts/firewall/9-to-10
new file mode 100755
index 000000000..6f67cc512
--- /dev/null
+++ b/src/migration-scripts/firewall/9-to-10
@@ -0,0 +1,80 @@
+#!/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/>.
+
+# T5050: Log options
+# cli changes from:
+# set firewall [name | ipv6-name] <name> rule <number> log-level <log_level>
+# To
+# set firewall [name | ipv6-name] <name> rule <number> log-options level <log_level>
+
+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']):
+ log_options_base = base + ['name', name, 'rule', rule, 'log-options']
+ rule_log_level = base + ['name', name, 'rule', rule, 'log-level']
+
+ if config.exists(rule_log_level):
+ tmp = config.return_value(rule_log_level)
+ config.delete(rule_log_level)
+ config.set(log_options_base + ['level'], 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']):
+ log_options_base = base + ['ipv6-name', name, 'rule', rule, 'log-options']
+ rule_log_level = base + ['ipv6-name', name, 'rule', rule, 'log-level']
+
+ if config.exists(rule_log_level):
+ tmp = config.return_value(rule_log_level)
+ config.delete(rule_log_level)
+ config.set(log_options_base + ['level'], 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/interfaces/0-to-1 b/src/migration-scripts/interfaces/0-to-1
index ee4d6b82c..c7f324661 100755
--- a/src/migration-scripts/interfaces/0-to-1
+++ b/src/migration-scripts/interfaces/0-to-1
@@ -3,7 +3,7 @@
# Change syntax of bridge interface
# - move interface based bridge-group to actual bridge (de-nest)
# - make stp and igmp-snooping nodes valueless
-# https://phabricator.vyos.net/T1556
+# https://vyos.dev/T1556
import sys
from vyos.configtree import ConfigTree
diff --git a/src/migration-scripts/interfaces/1-to-2 b/src/migration-scripts/interfaces/1-to-2
index 050137318..c75404d85 100755
--- a/src/migration-scripts/interfaces/1-to-2
+++ b/src/migration-scripts/interfaces/1-to-2
@@ -2,7 +2,7 @@
# Change syntax of bond interface
# - move interface based bond-group to actual bond (de-nest)
-# https://phabricator.vyos.net/T1614
+# https://vyos.dev/T1614
import sys
from vyos.configtree import ConfigTree
@@ -40,7 +40,7 @@ else:
# some combinations were allowed in the past from a CLI perspective
# but the kernel overwrote them - remove from CLI to not confuse the users.
# In addition new consitency checks are in place so users can't repeat the
- # mistake. One of those nice issues is https://phabricator.vyos.net/T532
+ # mistake. One of those nice issues is https://vyos.dev/T532
for bond in config.list_nodes(base):
if config.exists(base + [bond, 'arp-monitor', 'interval']) and config.exists(base + [bond, 'mode']):
mode = config.return_value(base + [bond, 'mode'])
diff --git a/src/migration-scripts/interfaces/16-to-17 b/src/migration-scripts/interfaces/16-to-17
index a6b4c7663..d123be06f 100755
--- a/src/migration-scripts/interfaces/16-to-17
+++ b/src/migration-scripts/interfaces/16-to-17
@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Command line migration of port mirroring
-# https://phabricator.vyos.net/T3089
+# https://vyos.dev/T3089
import sys
from vyos.configtree import ConfigTree
diff --git a/src/migration-scripts/interfaces/2-to-3 b/src/migration-scripts/interfaces/2-to-3
index a63a54cdf..68d41de39 100755
--- a/src/migration-scripts/interfaces/2-to-3
+++ b/src/migration-scripts/interfaces/2-to-3
@@ -2,7 +2,7 @@
# Change syntax of openvpn encryption settings
# - move cipher from encryption to encryption cipher
-# https://phabricator.vyos.net/T1704
+# https://vyos.dev/T1704
import sys
from vyos.configtree import ConfigTree
diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21
index 0bd858760..cb1c36882 100755
--- a/src/migration-scripts/interfaces/20-to-21
+++ b/src/migration-scripts/interfaces/20-to-21
@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# T3619: mirror Linux Kernel defaults for ethernet offloading options into VyOS
-# CLI. See https://phabricator.vyos.net/T3619#102254 for all the details.
+# CLI. See https://vyos.dev/T3619#102254 for all the details.
# T3787: Remove deprecated UDP fragmentation offloading option
from sys import argv
diff --git a/src/migration-scripts/interfaces/26-to-27 b/src/migration-scripts/interfaces/26-to-27
new file mode 100755
index 000000000..949cc55b6
--- /dev/null
+++ b/src/migration-scripts/interfaces/26-to-27
@@ -0,0 +1,49 @@
+#!/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/>.
+
+# T4995: pppoe, wwan, sstpc-client rename "authentication user" CLI node
+# to "authentication username"
+
+from sys import argv
+
+from vyos.ethtool import Ethtool
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+for type in ['pppoe', 'sstpc-client', 'wwam']:
+ base = ['interfaces', type]
+ if not config.exists(base):
+ continue
+ for interface in config.list_nodes(base):
+ auth_base = base + [interface, 'authentication', 'user']
+ if config.exists(auth_base):
+ config.rename(auth_base, 'username')
+
+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/interfaces/27-to-28 b/src/migration-scripts/interfaces/27-to-28
new file mode 100755
index 000000000..6225d6414
--- /dev/null
+++ b/src/migration-scripts/interfaces/27-to-28
@@ -0,0 +1,55 @@
+#!/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/>.
+
+# T5034: tunnel: rename "multicast enable" CLI node to "enable-multicast"
+# valueless node.
+
+from sys import argv
+
+from vyos.ethtool import Ethtool
+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 = ['interfaces', 'tunnel']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ exit(0)
+
+for ifname in config.list_nodes(base):
+ print(ifname)
+ multicast_base = base + [ifname, 'multicast']
+ if config.exists(multicast_base):
+ tmp = config.return_value(multicast_base)
+ print(tmp)
+ # Delete old Config node
+ config.delete(multicast_base)
+ if tmp == 'enable':
+ config.set(base + [ifname, 'enable-multicast'])
+
+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/interfaces/4-to-5 b/src/migration-scripts/interfaces/4-to-5
index 2a42c60ff..f645c5aeb 100755
--- a/src/migration-scripts/interfaces/4-to-5
+++ b/src/migration-scripts/interfaces/4-to-5
@@ -50,7 +50,7 @@ def migrate_dialer(config, tree, intf):
# Remove IPv6 router-advert nodes as this makes no sense on a
# client diale rinterface to send RAs back into the network
- # https://phabricator.vyos.net/T2055
+ # https://vyos.dev/T2055
ipv6_ra = pppoe_base + ['ipv6', 'router-advert']
if config.exists(ipv6_ra):
config.delete(ipv6_ra)
diff --git a/src/migration-scripts/ipsec/10-to-11 b/src/migration-scripts/ipsec/10-to-11
new file mode 100755
index 000000000..0707a5e3c
--- /dev/null
+++ b/src/migration-scripts/ipsec/10-to-11
@@ -0,0 +1,83 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from sys import argv
+from 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_values(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/11-to-12 b/src/migration-scripts/ipsec/11-to-12
new file mode 100755
index 000000000..8bbde5efa
--- /dev/null
+++ b/src/migration-scripts/ipsec/11-to-12
@@ -0,0 +1,53 @@
+#!/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/>.
+
+# Remove legacy ipsec.conf and ipsec.secrets - Not supported with swanctl
+
+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)
+
+if config.exists(base + ['include-ipsec-conf']):
+ config.delete(base + ['include-ipsec-conf'])
+
+if config.exists(base + ['include-ipsec-secrets']):
+ config.delete(base + ['include-ipsec-secrets'])
+
+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/isis/2-to-3 b/src/migration-scripts/isis/2-to-3
new file mode 100755
index 000000000..4490feb0a
--- /dev/null
+++ b/src/migration-scripts/isis/2-to-3
@@ -0,0 +1,63 @@
+#!/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/>.
+
+# T5150: Rework CLI definitions to apply route-maps between routing daemons
+# and zebra/kernel
+
+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()
+
+config = ConfigTree(config_file)
+
+isis_base = ['protocols', 'isis']
+# Check if IS-IS is configured - if so, migrate the CLI node
+if config.exists(isis_base):
+ if config.exists(isis_base + ['route-map']):
+ tmp = config.return_value(isis_base + ['route-map'])
+
+ config.set(['system', 'ip', 'protocol', 'isis', 'route-map'], value=tmp)
+ config.set_tag(['system', 'ip', 'protocol'])
+ config.delete(isis_base + ['route-map'])
+
+# Check if vrf names are configured. Check if IS-IS is configured - if so,
+# migrate the CLI node(s)
+if config.exists(['vrf', 'name']):
+ for vrf in config.list_nodes(['vrf', 'name']):
+ vrf_base = ['vrf', 'name', vrf]
+ if config.exists(vrf_base + ['protocols', 'isis', 'route-map']):
+ tmp = config.return_value(vrf_base + ['protocols', 'isis', 'route-map'])
+
+ config.set(vrf_base + ['ip', 'protocol', 'isis', 'route-map'], value=tmp)
+ config.set_tag(vrf_base + ['ip', 'protocol', 'isis'])
+ config.delete(vrf_base + ['protocols', 'isis', 'route-map'])
+
+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/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/ospf/1-to-2 b/src/migration-scripts/ospf/1-to-2
new file mode 100755
index 000000000..a6beaf04e
--- /dev/null
+++ b/src/migration-scripts/ospf/1-to-2
@@ -0,0 +1,80 @@
+#!/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/>.
+
+# T5150: Rework CLI definitions to apply route-maps between routing daemons
+# and zebra/kernel
+
+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()
+
+config = ConfigTree(config_file)
+
+ospf_base = ['protocols', 'ospf']
+# Check if OSPF is configured - if so, migrate the CLI node
+if config.exists(ospf_base):
+ if config.exists(ospf_base + ['route-map']):
+ tmp = config.return_value(ospf_base + ['route-map'])
+
+ config.set(['system', 'ip', 'protocol', 'ospf', 'route-map'], value=tmp)
+ config.set_tag(['system', 'ip', 'protocol'])
+ config.delete(ospf_base + ['route-map'])
+
+ospfv3_base = ['protocols', 'ospfv3']
+# Check if OSPFv3 is configured - if so, migrate the CLI node
+if config.exists(ospfv3_base):
+ if config.exists(ospfv3_base + ['route-map']):
+ tmp = config.return_value(ospfv3_base + ['route-map'])
+
+ config.set(['system', 'ipv6', 'protocol', 'ospfv3', 'route-map'], value=tmp)
+ config.set_tag(['system', 'ipv6', 'protocol'])
+ config.delete(ospfv3_base + ['route-map'])
+
+# Check if vrf names are configured. Check if OSPF/OSPFv3 is configured - if so,
+# migrate the CLI node(s)
+if config.exists(['vrf', 'name']):
+ for vrf in config.list_nodes(['vrf', 'name']):
+ vrf_base = ['vrf', 'name', vrf]
+ if config.exists(vrf_base + ['protocols', 'ospf', 'route-map']):
+ tmp = config.return_value(vrf_base + ['protocols', 'ospf', 'route-map'])
+
+ config.set(vrf_base + ['ip', 'protocol', 'ospf', 'route-map'], value=tmp)
+ config.set_tag(vrf_base + ['ip', 'protocol', 'ospf'])
+ config.delete(vrf_base + ['protocols', 'ospf', 'route-map'])
+
+ if config.exists(vrf_base + ['protocols', 'ospfv3', 'route-map']):
+ tmp = config.return_value(vrf_base + ['protocols', 'ospfv3', 'route-map'])
+
+ config.set(vrf_base + ['ipv6', 'protocol', 'ospfv3', 'route-map'], value=tmp)
+ config.set_tag(vrf_base + ['ipv6', 'protocol', 'ospfv6'])
+ config.delete(vrf_base + ['protocols', 'ospfv3', 'route-map'])
+
+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/qos/1-to-2 b/src/migration-scripts/qos/1-to-2
new file mode 100755
index 000000000..14d3a6e0a
--- /dev/null
+++ b/src/migration-scripts/qos/1-to-2
@@ -0,0 +1,148 @@
+#!/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'])
+
+# Change bandwidth unit MBit -> mbit as tc only supports mbit
+base = ['qos', 'policy']
+if config.exists(base):
+ for policy_type in config.list_nodes(base):
+ for policy in config.list_nodes(base + [policy_type]):
+ policy_base = base + [policy_type, policy]
+ if config.exists(policy_base + ['bandwidth']):
+ tmp = config.return_value(policy_base + ['bandwidth'])
+ config.set(policy_base + ['bandwidth'], value=tmp.lower())
+
+ if config.exists(policy_base + ['class']):
+ for cls in config.list_nodes(policy_base + ['class']):
+ cls_base = policy_base + ['class', cls]
+ if config.exists(cls_base + ['bandwidth']):
+ tmp = config.return_value(cls_base + ['bandwidth'])
+ config.set(cls_base + ['bandwidth'], value=tmp.lower())
+
+ if config.exists(policy_base + ['default', 'bandwidth']):
+ if config.exists(policy_base + ['default', 'bandwidth']):
+ tmp = config.return_value(policy_base + ['default', 'bandwidth'])
+ config.set(policy_base + ['default', 'bandwidth'], value=tmp.lower())
+
+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/quagga/10-to-11 b/src/migration-scripts/quagga/10-to-11
new file mode 100755
index 000000000..04fc16f79
--- /dev/null
+++ b/src/migration-scripts/quagga/10-to-11
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# T5150: Rework CLI definitions to apply route-maps between routing daemons
+# and zebra/kernel
+
+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()
+
+config = ConfigTree(config_file)
+
+static_base = ['protocols', 'static']
+# Check if static routes are configured - if so, migrate the CLI node
+if config.exists(static_base):
+ if config.exists(static_base + ['route-map']):
+ tmp = config.return_value(static_base + ['route-map'])
+
+ config.set(['system', 'ip', 'protocol', 'static', 'route-map'], value=tmp)
+ config.set_tag(['system', 'ip', 'protocol'])
+ config.delete(static_base + ['route-map'])
+
+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/rip/0-to-1 b/src/migration-scripts/rip/0-to-1
new file mode 100755
index 000000000..60d510001
--- /dev/null
+++ b/src/migration-scripts/rip/0-to-1
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# T5150: Rework CLI definitions to apply route-maps between routing daemons
+# and zebra/kernel
+
+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()
+
+config = ConfigTree(config_file)
+
+ripng_base = ['protocols', 'ripng']
+# Check if RIPng is configured - if so, migrate the CLI node
+if config.exists(ripng_base):
+ if config.exists(ripng_base + ['route-map']):
+ tmp = config.return_value(ripng_base + ['route-map'])
+
+ config.set(['system', 'ipv6', 'protocol', 'ripng', 'route-map'], value=tmp)
+ config.set_tag(['system', 'ipv6', 'protocol'])
+ config.delete(ripng_base + ['route-map'])
+
+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/snmp/0-to-1 b/src/migration-scripts/snmp/0-to-1
index a836f7011..096ba779d 100755
--- a/src/migration-scripts/snmp/0-to-1
+++ b/src/migration-scripts/snmp/0-to-1
@@ -33,18 +33,18 @@ if not config.exists(config_base):
# Nothing to do
sys.exit(0)
else:
- # we no longer support a per trap target engine ID (https://phabricator.vyos.net/T818)
+ # we no longer support a per trap target engine ID (https://vyos.dev/T818)
if config.exists(config_base + ['v3', 'trap-target']):
for target in config.list_nodes(config_base + ['v3', 'trap-target']):
config.delete(config_base + ['v3', 'trap-target', target, 'engineid'])
- # we no longer support a per user engine ID (https://phabricator.vyos.net/T818)
+ # we no longer support a per user engine ID (https://vyos.dev/T818)
if config.exists(config_base + ['v3', 'user']):
for user in config.list_nodes(config_base + ['v3', 'user']):
config.delete(config_base + ['v3', 'user', user, 'engineid'])
# we drop TSM support as there seem to be no users and this code is untested
- # https://phabricator.vyos.net/T1769
+ # https://vyos.dev/T1769
if config.exists(config_base + ['v3', 'tsm']):
config.delete(config_base + ['v3', 'tsm'])
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
index 2fd045dc3..00de45fc8 100755
--- a/src/op_mode/accelppp.py
+++ b/src/op_mode/accelppp.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
@@ -27,34 +27,56 @@ from vyos.util import rc_cmd
accel_dict = {
'ipoe': {
'port': 2002,
- 'path': 'service ipoe-server'
+ 'path': 'service ipoe-server',
+ 'base_path': 'service ipoe-server'
},
'pppoe': {
'port': 2001,
- 'path': 'service pppoe-server'
+ 'path': 'service pppoe-server',
+ 'base_path': 'service pppoe-server'
},
'pptp': {
'port': 2003,
- 'path': 'vpn pptp'
+ 'path': 'vpn pptp',
+ 'base_path': 'vpn pptp'
},
'l2tp': {
'port': 2004,
- 'path': 'vpn l2tp'
+ 'path': 'vpn l2tp',
+ 'base_path': 'vpn l2tp remote-access'
},
'sstp': {
'port': 2005,
- 'path': 'vpn sstp'
+ 'path': 'vpn sstp',
+ 'base_path': 'vpn sstp'
}
}
-def _get_raw_statistics(accel_output, pattern):
- return vyos.accel_ppp.get_server_statistics(accel_output, pattern, sep=':')
+def _get_config_settings(protocol):
+ '''Get config dict from VyOS configuration'''
+ conf = ConfigTreeQuery()
+ base_path = accel_dict[protocol]['base_path']
+ data = conf.get_config_dict(base_path,
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+ if conf.exists(f'{base_path} authentication local-users'):
+ # Delete sensitive data
+ del data['authentication']['local_users']
+ return {'config_option': data}
+
+
+def _get_raw_statistics(accel_output, pattern, protocol):
+ return {
+ **vyos.accel_ppp.get_server_statistics(accel_output, pattern, sep=':'),
+ **_get_config_settings(protocol)
+ }
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,' \
+ cmd_options = 'show sessions ifname,username,ip,ip6,ip6-dp,type,rate-limit,' \
+ '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(
@@ -103,7 +125,7 @@ def show_statistics(raw: bool, protocol: str):
rc, output = rc_cmd(f'/usr/bin/accel-cmd -p {port} show stat')
if raw:
- return _get_raw_statistics(output, pattern)
+ return _get_raw_statistics(output, pattern, protocol)
return output
diff --git a/src/op_mode/bgp.py b/src/op_mode/bgp.py
index 23001a9d7..3f6d45dd7 100755
--- a/src/op_mode/bgp.py
+++ b/src/op_mode/bgp.py
@@ -30,6 +30,7 @@ from vyos.configquery import ConfigTreeQuery
import vyos.opmode
+ArgFamily = typing.Literal['inet', 'inet6']
frr_command_template = Template("""
{% if family %}
@@ -75,7 +76,7 @@ def _verify(func):
@_verify
def show_neighbors(raw: bool,
- family: str,
+ family: ArgFamily,
peer: typing.Optional[str],
vrf: typing.Optional[str]):
kwargs = dict(locals())
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/conntrack.py b/src/op_mode/conntrack.py
index fff537936..ea7c4c208 100755
--- a/src/op_mode/conntrack.py
+++ b/src/op_mode/conntrack.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
+import typing
import xmltodict
from tabulate import tabulate
@@ -23,6 +24,7 @@ from vyos.util import run
import vyos.opmode
+ArgFamily = typing.Literal['inet', 'inet6']
def _get_xml_data(family):
"""
@@ -116,7 +118,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])
@@ -126,7 +128,7 @@ def get_formatted_output(dict_data):
return output
-def show(raw: bool, family: str):
+def show(raw: bool, family: ArgFamily):
family = 'ipv6' if family == 'inet6' else 'ipv4'
conntrack_data = _get_raw_data(family)
if raw:
diff --git a/src/op_mode/container.py b/src/op_mode/container.py
index ce466ffc1..d48766a0c 100755
--- a/src/op_mode/container.py
+++ b/src/op_mode/container.py
@@ -23,7 +23,6 @@ from vyos.util import cmd
import vyos.opmode
-
def _get_json_data(command: str) -> list:
"""
Get container command format JSON
@@ -36,9 +35,22 @@ def _get_raw_data(command: str) -> list:
data = json.loads(json_data)
return data
+def add_image(name: str):
+ from vyos.util import rc_cmd
+
+ rc, output = rc_cmd(f'podman image pull {name}')
+ if rc != 0:
+ raise vyos.opmode.InternalError(output)
+
+def delete_image(name: str):
+ from vyos.util import rc_cmd
+
+ rc, output = rc_cmd(f'podman image rm --force {name}')
+ if rc != 0:
+ raise vyos.opmode.InternalError(output)
def show_container(raw: bool):
- command = 'sudo podman ps --all'
+ command = 'podman ps --all'
container_data = _get_raw_data(command)
if raw:
return container_data
@@ -47,8 +59,8 @@ def show_container(raw: bool):
def show_image(raw: bool):
- command = 'sudo podman image ls'
- container_data = _get_raw_data('sudo podman image ls')
+ command = 'podman image ls'
+ container_data = _get_raw_data('podman image ls')
if raw:
return container_data
else:
@@ -56,7 +68,7 @@ def show_image(raw: bool):
def show_network(raw: bool):
- command = 'sudo podman network ls'
+ command = 'podman network ls'
container_data = _get_raw_data(command)
if raw:
return container_data
@@ -67,7 +79,7 @@ def show_network(raw: bool):
def restart(name: str):
from vyos.util import rc_cmd
- rc, output = rc_cmd(f'sudo podman restart {name}')
+ rc, output = rc_cmd(f'systemctl restart vyos-container-{name}.service')
if rc != 0:
print(output)
return None
diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py
index 07e9b7d6c..fe7f252ba 100755
--- a/src/op_mode/dhcp.py
+++ b/src/op_mode/dhcp.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
@@ -15,13 +15,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
-from ipaddress import ip_address
import typing
from datetime import datetime
-from sys import exit
-from tabulate import tabulate
+from ipaddress import ip_address
from isc_dhcp_leases import IscDhcpLeases
+from tabulate import tabulate
+
+import vyos.opmode
from vyos.base import Warning
from vyos.configquery import ConfigTreeQuery
@@ -30,19 +31,13 @@ from vyos.util import cmd
from vyos.util import dict_search
from vyos.util import is_systemd_service_running
-import vyos.opmode
-
-
config = ConfigTreeQuery()
-pool_key = "shared-networkname"
-
-
-def _in_pool(lease, pool):
- if pool_key in lease.sets:
- if lease.sets[pool_key] == pool:
- return True
- return False
+lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup']
+sort_valid_inet = ['end', 'mac', 'hostname', 'ip', 'pool', 'remaining', 'start', 'state']
+sort_valid_inet6 = ['end', 'iaid_duid', 'ip', 'last_communication', 'pool', 'remaining', 'state', 'type']
+ArgFamily = typing.Literal['inet', 'inet6']
+ArgState = typing.Literal['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup']
def _utc_to_local(utc_dt):
return datetime.fromtimestamp((datetime.fromtimestamp(utc_dt) - datetime(1970, 1, 1)).total_seconds())
@@ -71,7 +66,7 @@ def _find_list_of_dict_index(lst, key='ip', value='') -> int:
return idx
-def _get_raw_server_leases(family, pool=None) -> list:
+def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[]) -> list:
"""
Get DHCP server leases
:return list
@@ -79,18 +74,21 @@ def _get_raw_server_leases(family, pool=None) -> list:
lease_file = '/config/dhcpdv6.leases' if family == 'inet6' else '/config/dhcpd.leases'
data = []
leases = IscDhcpLeases(lease_file).get()
- if pool is not None:
- if config.exists(f'service dhcp-server shared-network-name {pool}'):
- leases = list(filter(lambda x: _in_pool(x, pool), leases))
+
+ if pool is None:
+ pool = _get_dhcp_pools(family=family)
+ else:
+ pool = [pool]
+
for lease in leases:
data_lease = {}
data_lease['ip'] = lease.ip
data_lease['state'] = lease.binding_state
data_lease['pool'] = lease.sets.get('shared-networkname', '')
- data_lease['end'] = lease.end.timestamp()
+ data_lease['end'] = lease.end.timestamp() if lease.end else None
if family == 'inet':
- data_lease['hardware'] = lease.ethernet
+ data_lease['mac'] = lease.ethernet
data_lease['start'] = lease.start.timestamp()
data_lease['hostname'] = lease.hostname
@@ -100,18 +98,20 @@ def _get_raw_server_leases(family, pool=None) -> list:
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()
+ data_lease['remaining'] = '-'
- 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'] = ''
+ if lease.end:
+ 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]
# Do not add old leases
- if data_lease['remaining'] != '':
- data.append(data_lease)
+ if data_lease['remaining'] != '' and data_lease['pool'] in pool and data_lease['state'] != 'free':
+ if not state or data_lease['state'] in state:
+ data.append(data_lease)
# deduplicate
checked = []
@@ -123,26 +123,31 @@ def _get_raw_server_leases(family, pool=None) -> list:
idx = _find_list_of_dict_index(data, key='ip', value=addr)
data.pop(idx)
+ if sorted:
+ if sorted == 'ip':
+ data.sort(key = lambda x:ip_address(x['ip']))
+ else:
+ data.sort(key = lambda x:x[sorted])
return data
-def _get_formatted_server_leases(raw_data, family):
+def _get_formatted_server_leases(raw_data, family='inet'):
data_entries = []
if family == 'inet':
for lease in raw_data:
ipaddr = lease.get('ip')
- hw_addr = lease.get('hardware')
+ hw_addr = lease.get('mac')
state = lease.get('state')
start = lease.get('start')
start = _utc_to_local(start).strftime('%Y/%m/%d %H:%M:%S')
end = lease.get('end')
- end = _utc_to_local(end).strftime('%Y/%m/%d %H:%M:%S')
+ end = _utc_to_local(end).strftime('%Y/%m/%d %H:%M:%S') if end else '-'
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', 'Hardware address', 'State', 'Lease start', 'Lease expiration', 'Remaining', 'Pool',
+ headers = ['IP Address', 'MAC address', 'State', 'Lease start', 'Lease expiration', 'Remaining', 'Pool',
'Hostname']
if family == 'inet6':
@@ -247,7 +252,7 @@ def _verify(func):
@_verify
-def show_pool_statistics(raw: bool, family: str, pool: typing.Optional[str]):
+def show_pool_statistics(raw: bool, family: ArgFamily, pool: typing.Optional[str]):
pool_data = _get_raw_pool_statistics(family=family, pool=pool)
if raw:
return pool_data
@@ -256,16 +261,30 @@ def show_pool_statistics(raw: bool, family: str, pool: typing.Optional[str]):
@_verify
-def show_server_leases(raw: bool, family: str):
+def show_server_leases(raw: bool, family: ArgFamily, pool: typing.Optional[str],
+ sorted: typing.Optional[str], state: typing.Optional[ArgState]):
# 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 = '6' if family == 'inet6' else ''
+ service_name = 'DHCPv6' if family == 'inet6' else 'DHCP'
+ if not is_systemd_service_running(f'isc-dhcp-server{v}.service'):
+ Warning(f'{service_name} 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!')
- leases = _get_raw_server_leases(family)
+ lease_data = _get_raw_server_leases(family=family, pool=pool, sorted=sorted, state=state)
if raw:
- return leases
+ return lease_data
else:
- return _get_formatted_server_leases(leases, family)
+ return _get_formatted_server_leases(lease_data, family=family)
if __name__ == '__main__':
diff --git a/src/op_mode/dns.py b/src/op_mode/dns.py
index a0e47d7ad..f8863c530 100755
--- a/src/op_mode/dns.py
+++ b/src/op_mode/dns.py
@@ -17,7 +17,6 @@
import sys
-from sys import exit
from tabulate import tabulate
from vyos.configquery import ConfigTreeQuery
@@ -75,8 +74,7 @@ def show_forwarding_statistics(raw: bool):
config = ConfigTreeQuery()
if not config.exists('service dns forwarding'):
- print("DNS forwarding is not configured")
- exit(0)
+ raise vyos.opmode.UnconfiguredSubsystem('DNS forwarding is not configured')
dns_data = _get_raw_forwarding_statistics()
if raw:
diff --git a/src/op_mode/dynamic_dns.py b/src/op_mode/dynamic_dns.py
index 263a3b6a5..d41a74db3 100755
--- a/src/op_mode/dynamic_dns.py
+++ b/src/op_mode/dynamic_dns.py
@@ -16,69 +16,75 @@
import os
import argparse
-import jinja2
import sys
import time
+from tabulate import tabulate
from vyos.config import Config
+from vyos.template import is_ipv4, is_ipv6
from vyos.util import call
cache_file = r'/run/ddclient/ddclient.cache'
-OUT_TMPL_SRC = """
-{% for entry in hosts %}
-ip address : {{ entry.ip }}
-host-name : {{ entry.host }}
-last update : {{ entry.time }}
-update-status: {{ entry.status }}
+columns = {
+ 'host': 'Hostname',
+ 'ipv4': 'IPv4 address',
+ 'status-ipv4': 'IPv4 status',
+ 'ipv6': 'IPv6 address',
+ 'status-ipv6': 'IPv6 status',
+ 'mtime': 'Last update',
+}
+
+
+def _get_formatted_host_records(host_data):
+ data_entries = []
+ for entry in host_data:
+ data_entries.append([entry.get(key) for key in columns.keys()])
+
+ header = columns.values()
+ output = tabulate(data_entries, header, numalign='left')
+ return output
-{% endfor %}
-"""
def show_status():
- # A ddclient status file must not always exist
+ # A ddclient status file might not always exist
if not os.path.exists(cache_file):
sys.exit(0)
- data = {
- 'hosts': []
- }
+ data = []
with open(cache_file, 'r') as f:
for line in f:
if line.startswith('#'):
continue
- outp = {
- 'host': '',
- 'ip': '',
- 'time': ''
- }
-
- if 'host=' in line:
- host = line.split('host=')[1]
- if host:
- outp['host'] = host.split(',')[0]
-
- if 'ip=' in line:
- ip = line.split('ip=')[1]
- if ip:
- outp['ip'] = ip.split(',')[0]
-
- if 'mtime=' in line:
- mtime = line.split('mtime=')[1]
- if mtime:
- outp['time'] = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(int(mtime.split(',')[0], base=10)))
-
- if 'status=' in line:
- status = line.split('status=')[1]
- if status:
- outp['status'] = status.split(',')[0]
-
- data['hosts'].append(outp)
-
- tmpl = jinja2.Template(OUT_TMPL_SRC)
- print(tmpl.render(data))
+ props = {}
+ # ddclient cache rows have properties in 'key=value' format separated by comma
+ # we pick up the ones we are interested in
+ for kvraw in line.split(' ')[0].split(','):
+ k, v = kvraw.split('=')
+ if k in list(columns.keys()) + ['ip', 'status']: # ip and status are legacy keys
+ props[k] = v
+
+ # Extract IPv4 and IPv6 address and status from legacy keys
+ # Dual-stack isn't supported in legacy format, 'ip' and 'status' are for one of IPv4 or IPv6
+ if 'ip' in props:
+ if is_ipv4(props['ip']):
+ props['ipv4'] = props['ip']
+ props['status-ipv4'] = props['status']
+ elif is_ipv6(props['ip']):
+ props['ipv6'] = props['ip']
+ props['status-ipv6'] = props['status']
+ del props['ip']
+
+ # Convert mtime to human readable format
+ if 'mtime' in props:
+ props['mtime'] = time.strftime(
+ "%Y-%m-%d %H:%M:%S", time.localtime(int(props['mtime'], base=10)))
+
+ data.append(props)
+
+ print(_get_formatted_host_records(data))
def update_ddns():
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_public_key_command.py b/src/op_mode/generate_public_key_command.py
index f071ae350..8ba55c901 100755
--- a/src/op_mode/generate_public_key_command.py
+++ b/src/op_mode/generate_public_key_command.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
@@ -19,28 +19,51 @@ import sys
import urllib.parse
import vyos.remote
+from vyos.template import generate_uuid4
-def get_key(path):
+
+def get_key(path) -> list:
+ """Get public keys from a local file or remote URL
+
+ Args:
+ path: Path to the public keys file
+
+ Returns: list of public keys split by new line
+
+ """
url = urllib.parse.urlparse(path)
if url.scheme == 'file' or url.scheme == '':
with open(os.path.expanduser(path), 'r') as f:
key_string = f.read()
else:
key_string = vyos.remote.get_remote_config(path)
- return key_string.split()
-
-try:
- username = sys.argv[1]
- algorithm, key, identifier = get_key(sys.argv[2])
-except Exception as e:
- print("Failed to retrieve the public key: {}".format(e))
- sys.exit(1)
-
-print('# To add this key as an embedded key, run the following commands:')
-print('configure')
-print(f'set system login user {username} authentication public-keys {identifier} key {key}')
-print(f'set system login user {username} authentication public-keys {identifier} type {algorithm}')
-print('commit')
-print('save')
-print('exit')
+ return key_string.split('\n')
+
+
+if __name__ == "__main__":
+ first_loop = True
+
+ for k in get_key(sys.argv[2]):
+ k = k.split()
+ # Skip empty list entry
+ if k == []:
+ continue
+
+ try:
+ username = sys.argv[1]
+ # Github keys don't have identifier for example 'vyos@localhost'
+ # 'ssh-rsa AAAA... vyos@localhost'
+ # Generate uuid4 identifier
+ identifier = f'github@{generate_uuid4("")}' if sys.argv[2].startswith('https://github.com') else k[2]
+ algorithm, key = k[0], k[1]
+ except Exception as e:
+ print("Failed to retrieve the public key: {}".format(e))
+ sys.exit(1)
+
+ if first_loop:
+ print('# To add this key as an embedded key, run the following commands:')
+ print('configure')
+ print(f'set system login user {username} authentication public-keys {identifier} key {key}')
+ print(f'set system login user {username} authentication public-keys {identifier} type {algorithm}')
+ first_loop = False
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..dd87b5901
--- /dev/null
+++ b/src/op_mode/interfaces.py
@@ -0,0 +1,428 @@
+#!/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()
+
+ stats = interface.operational.get_stats()
+ for k in list(stats):
+ stats[k] = _get_counter_val(cache[k], stats[k])
+
+ res_intf['stats'] = 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)
+
+def clear_counters(intf_name: typing.Optional[str],
+ intf_type: typing.Optional[str],
+ vif: bool, vrrp: bool):
+ for interface in filtered_interfaces(intf_name, intf_type, vif, vrrp):
+ interface.operational.clear_counters()
+
+def reset_counters(intf_name: typing.Optional[str],
+ intf_type: typing.Optional[str],
+ vif: bool, vrrp: bool):
+ for interface in filtered_interfaces(intf_name, intf_type, vif, vrrp):
+ interface.operational.reset_counters()
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py
index e0d204a0a..db4948d7a 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
@@ -13,26 +13,21 @@
#
# 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
from vyos.util import seconds_to_human
+from vyos.util import cmd
+from vyos.configquery import ConfigTreeQuery
import vyos.opmode
-
-
-SWANCTL_CONF = '/etc/swanctl/swanctl.conf'
+import vyos.ipsec
def _convert(text):
@@ -43,21 +38,31 @@ def _alphanum_key(key):
return [_convert(c) for c in re_split('([0-9]+)', str(key))]
-def _get_vici_sas():
- from vici import Session as vici_session
-
+def _get_raw_data_sas():
try:
- session = vici_session()
- except Exception:
- raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized")
- sas = list(session.list_sas())
- return sas
+ get_sas = vyos.ipsec.get_vici_sas()
+ sas = convert_data(get_sas)
+ return sas
+ except (vyos.ipsec.ViciInitiateError) as err:
+ raise vyos.opmode.UnconfiguredSubsystem(err)
-def _get_raw_data_sas():
- get_sas = _get_vici_sas()
- sas = convert_data(get_sas)
- return sas
+def _get_output_swanctl_sas_from_list(ra_output_list: list) -> str:
+ """
+ Template for output for VICI
+ Inserts \n after each IKE SA
+ :param ra_output_list: IKE SAs list
+ :type ra_output_list: list
+ :return: formatted string
+ :rtype: str
+ """
+ output = '';
+ for sa_val in ra_output_list:
+ for sa in sa_val.values():
+ swanctl_output: str = cmd(
+ f'sudo swanctl -l --ike-id {sa["uniqueid"]}')
+ output = f'{output}{swanctl_output}\n\n'
+ return output
def _get_formatted_output_sas(sas):
@@ -139,22 +144,14 @@ def _get_formatted_output_sas(sas):
# 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
-
+ try:
+ get_connections = vyos.ipsec.get_vici_connections()
+ connections = convert_data(get_connections)
+ return connections
+ except (vyos.ipsec.ViciInitiateError) as err:
+ raise vyos.opmode.UnconfiguredSubsystem(err)
def _get_parent_sa_proposal(connection_name: str, data: list) -> dict:
"""Get parent SA proposals by connection name
@@ -173,7 +170,7 @@ def _get_parent_sa_proposal(connection_name: str, data: list) -> dict:
for sa in data:
# check if parent SA exist
if connection_name not in sa.keys():
- return {}
+ continue
if 'encr-alg' in sa[connection_name]:
encr_alg = sa.get(connection_name, '').get('encr-alg')
cipher = encr_alg.split('_')[0]
@@ -203,16 +200,17 @@ def _get_parent_sa_state(connection_name: str, data: list) -> str:
Returns:
Parent SA connection state
"""
+ ike_state = 'down'
if not data:
- return 'down'
+ return ike_state
for sa in data:
# check if parent SA exist
- if connection_name not in sa.keys():
- return 'down'
- if sa[connection_name]['state'].lower() == 'established':
- return 'up'
- else:
- return 'down'
+ for connection, connection_conf in sa.items():
+ if connection_name != connection:
+ continue
+ if connection_conf['state'].lower() == 'established':
+ ike_state = 'up'
+ return ike_state
def _get_child_sa_state(connection_name: str, tunnel_name: str,
@@ -227,19 +225,21 @@ def _get_child_sa_state(connection_name: str, tunnel_name: str,
Returns:
str: `up` if child SA state is 'installed' otherwise `down`
"""
+ child_sa = 'down'
if not data:
- return 'down'
+ return child_sa
for sa in data:
# check if parent SA exist
if connection_name not in sa.keys():
- return 'down'
+ continue
child_sas = sa[connection_name]['child-sas']
# Get all child SA states
# there can be multiple SAs per tunnel
child_sa_states = [
- v['state'] for k, v in child_sas.items() if v['name'] == tunnel_name
+ v['state'] for k, v in child_sas.items() if
+ v['name'] == tunnel_name
]
- return 'up' if 'INSTALLED' in child_sa_states else 'down'
+ return 'up' if 'INSTALLED' in child_sa_states else child_sa
def _get_child_sa_info(connection_name: str, tunnel_name: str,
@@ -257,7 +257,7 @@ def _get_child_sa_info(connection_name: str, tunnel_name: str,
for sa in data:
# check if parent SA exist
if connection_name not in sa.keys():
- return {}
+ continue
child_sas = sa[connection_name]['child-sas']
# Get all child SA data
# Skip temp SA name (first key), get only SA values as dict
@@ -404,39 +404,170 @@ def _get_formatted_output_conections(data):
# 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:
- if tunnel is None:
- matches.append(result[1])
+def _get_childsa_id_list(ike_sas: list) -> list:
+ """
+ Generate list of CHILD SA ids based on list of OrderingDict
+ wich is returned by vici
+ :param ike_sas: list of IKE SAs generated by vici
+ :type ike_sas: list
+ :return: list of IKE SAs ids
+ :rtype: list
+ """
+ list_childsa_id: list = []
+ for ike in ike_sas:
+ for ike_sa in ike.values():
+ for child_sa in ike_sa['child-sas'].values():
+ list_childsa_id.append(child_sa['uniqueid'].decode('ascii'))
+ return list_childsa_id
+
+
+def _get_all_sitetosite_peers_name_list() -> list:
+ """
+ Return site-to-site peers configuration
+ :return: site-to-site peers configuration
+ :rtype: list
+ """
+ conf: ConfigTreeQuery = ConfigTreeQuery()
+ config_path = ['vpn', 'ipsec', 'site-to-site', 'peer']
+ peers_config = conf.get_config_dict(config_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+ peers_list: list = []
+ for name in peers_config:
+ peers_list.append(name)
+ return peers_list
+
+
+def reset_peer(peer: str, tunnel: typing.Optional[str] = None):
+ # Convert tunnel to Strongwan format of CHILD_SA
+ tunnel_sw = None
+ if tunnel:
+ if tunnel.isnumeric():
+ tunnel_sw = f'{peer}-tunnel-{tunnel}'
+ elif tunnel == 'vti':
+ tunnel_sw = f'{peer}-vti'
+ try:
+ sa_list: list = vyos.ipsec.get_vici_sas_by_name(peer, tunnel_sw)
+ if not sa_list:
+ raise vyos.opmode.IncorrectValue(
+ f'Peer\'s {peer} SA(s) not found, aborting')
+ if tunnel and sa_list:
+ childsa_id_list: list = _get_childsa_id_list(sa_list)
+ if not childsa_id_list:
+ raise vyos.opmode.IncorrectValue(
+ f'Peer {peer} tunnel {tunnel} SA(s) not found, aborting')
+ vyos.ipsec.terminate_vici_by_name(peer, tunnel_sw)
+ print(f'Peer {peer} reset result: success')
+ except (vyos.ipsec.ViciInitiateError) as err:
+ raise vyos.opmode.UnconfiguredSubsystem(err)
+ except (vyos.ipsec.ViciCommandError) as err:
+ raise vyos.opmode.IncorrectValue(err)
+
+
+def reset_all_peers():
+ sitetosite_list = _get_all_sitetosite_peers_name_list()
+ if sitetosite_list:
+ for peer_name in sitetosite_list:
+ try:
+ reset_peer(peer_name)
+ except (vyos.opmode.IncorrectValue) as err:
+ print(err)
+ print('Peers reset result: success')
+ else:
+ raise vyos.opmode.UnconfiguredSubsystem(
+ 'VPN IPSec site-to-site is not configured, aborting')
+
+
+def _get_ra_session_list_by_username(username: typing.Optional[str] = None):
+ """
+ Return list of remote-access IKE_SAs uniqueids
+ :param username:
+ :type username:
+ :return:
+ :rtype:
+ """
+ list_sa_id = []
+ sa_list = _get_raw_data_sas()
+ for sa_val in sa_list:
+ for sa in sa_val.values():
+ if 'remote-eap-id' in sa:
+ if username:
+ if username == sa['remote-eap-id']:
+ list_sa_id.append(sa['uniqueid'])
else:
- if result[2] == suffix:
- matches.append(result[1])
- return matches
+ list_sa_id.append(sa['uniqueid'])
+ return list_sa_id
-def reset_peer(peer: str, tunnel:typing.Optional[str]):
- conns = get_peer_connections(peer, tunnel)
+def reset_ra(username: typing.Optional[str] = None):
+ #Reset remote-access ipsec sessions
+ if username:
+ list_sa_id = _get_ra_session_list_by_username(username)
+ else:
+ list_sa_id = _get_ra_session_list_by_username()
+ if list_sa_id:
+ vyos.ipsec.terminate_vici_ikeid_list(list_sa_id)
- if not conns:
- raise vyos.opmode.IncorrectValue('Peer or tunnel(s) not found, aborting')
- for conn in conns:
+def reset_profile_dst(profile: str, tunnel: str, nbma_dst: str):
+ if profile and tunnel and nbma_dst:
+ ike_sa_name = f'dmvpn-{profile}-{tunnel}'
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:
- raise vyos.opmode.InternalError(f'Timed out while resetting {conn}')
-
- print('Peer reset result: success')
+ # Get IKE SAs
+ sa_list = convert_data(
+ vyos.ipsec.get_vici_sas_by_name(ike_sa_name, None))
+ if not sa_list:
+ raise vyos.opmode.IncorrectValue(
+ f'SA(s) for profile {profile} tunnel {tunnel} not found, aborting')
+ sa_nbma_list = list([x for x in sa_list if
+ ike_sa_name in x and x[ike_sa_name][
+ 'remote-host'] == nbma_dst])
+ if not sa_nbma_list:
+ raise vyos.opmode.IncorrectValue(
+ f'SA(s) for profile {profile} tunnel {tunnel} remote-host {nbma_dst} not found, aborting')
+ # terminate IKE SAs
+ vyos.ipsec.terminate_vici_ikeid_list(list(
+ [x[ike_sa_name]['uniqueid'] for x in sa_nbma_list if
+ ike_sa_name in x]))
+ # initiate IKE SAs
+ for ike in sa_nbma_list:
+ if ike_sa_name in ike:
+ vyos.ipsec.vici_initiate(ike_sa_name, 'dmvpn',
+ ike[ike_sa_name]['local-host'],
+ ike[ike_sa_name]['remote-host'])
+ print(
+ f'Profile {profile} tunnel {tunnel} remote-host {nbma_dst} reset result: success')
+ except (vyos.ipsec.ViciInitiateError) as err:
+ raise vyos.opmode.UnconfiguredSubsystem(err)
+ except (vyos.ipsec.ViciCommandError) as err:
+ raise vyos.opmode.IncorrectValue(err)
+
+
+def reset_profile_all(profile: str, tunnel: str):
+ if profile and tunnel:
+ ike_sa_name = f'dmvpn-{profile}-{tunnel}'
+ try:
+ # Get IKE SAs
+ sa_list: list = convert_data(
+ vyos.ipsec.get_vici_sas_by_name(ike_sa_name, None))
+ if not sa_list:
+ raise vyos.opmode.IncorrectValue(
+ f'SA(s) for profile {profile} tunnel {tunnel} not found, aborting')
+ # terminate IKE SAs
+ vyos.ipsec.terminate_vici_by_name(ike_sa_name, None)
+ # initiate IKE SAs
+ for ike in sa_list:
+ if ike_sa_name in ike:
+ vyos.ipsec.vici_initiate(ike_sa_name, 'dmvpn',
+ ike[ike_sa_name]['local-host'],
+ ike[ike_sa_name]['remote-host'])
+ print(
+ f'Profile {profile} tunnel {tunnel} remote-host {ike[ike_sa_name]["remote-host"]} reset result: success')
+ print(f'Profile {profile} tunnel {tunnel} reset result: success')
+ except (vyos.ipsec.ViciInitiateError) as err:
+ raise vyos.opmode.UnconfiguredSubsystem(err)
+ except (vyos.ipsec.ViciCommandError) as err:
+ raise vyos.opmode.IncorrectValue(err)
def show_sa(raw: bool):
@@ -446,6 +577,24 @@ def show_sa(raw: bool):
return _get_formatted_output_sas(sa_data)
+def _get_output_sas_detail(ra_output_list: list) -> str:
+ """
+ Formate all IKE SAs detail output
+ :param ra_output_list: IKE SAs list
+ :type ra_output_list: list
+ :return: formatted RA IKE SAs detail output
+ :rtype: str
+ """
+ return _get_output_swanctl_sas_from_list(ra_output_list)
+
+
+def show_sa_detail(raw: bool):
+ sa_data = _get_raw_data_sas()
+ if raw:
+ return sa_data
+ return _get_output_sas_detail(sa_data)
+
+
def show_connections(raw: bool):
list_conns = _get_convert_data_connections()
list_sas = _get_raw_data_sas()
@@ -463,6 +612,173 @@ def show_connections_summary(raw: bool):
return _get_raw_connections_summary(list_conns, list_sas)
+def _get_ra_sessions(username: typing.Optional[str] = None) -> list:
+ """
+ Return list of remote-access IKE_SAs from VICI by username.
+ If username unspecified, return all remote-access IKE_SAs
+ :param username: Username of RA connection
+ :type username: str
+ :return: list of ra remote-access IKE_SAs
+ :rtype: list
+ """
+ list_sa = []
+ sa_list = _get_raw_data_sas()
+ for conn in sa_list:
+ for sa in conn.values():
+ if 'remote-eap-id' in sa:
+ if username:
+ if username == sa['remote-eap-id']:
+ list_sa.append(conn)
+ else:
+ list_sa.append(conn)
+ return list_sa
+
+
+def _filter_ikesas(list_sa: list, filter_key: str, filter_value: str) -> list:
+ """
+ Filter IKE SAs by specifice key
+ :param list_sa: list of IKE SAs
+ :type list_sa: list
+ :param filter_key: Filter Key
+ :type filter_key: str
+ :param filter_value: Filter Value
+ :type filter_value: str
+ :return: Filtered list of IKE SAs
+ :rtype: list
+ """
+ filtered_sa_list = []
+ for conn in list_sa:
+ for sa in conn.values():
+ if sa[filter_key] and sa[filter_key] == filter_value:
+ filtered_sa_list.append(conn)
+ return filtered_sa_list
+
+
+def _get_last_installed_childsa(sa: dict) -> str:
+ """
+ Return name of last installed active Child SA
+ :param sa: Dictionary with Child SAs
+ :type sa: dict
+ :return: Name of the Last installed active Child SA
+ :rtype: str
+ """
+ child_sa_name = None
+ child_sa_id = 0
+ for sa_name, child_sa in sa['child-sas'].items():
+ if child_sa['state'] == 'INSTALLED':
+ if child_sa_id == 0 or int(child_sa['uniqueid']) > child_sa_id:
+ child_sa_id = int(child_sa['uniqueid'])
+ child_sa_name = sa_name
+ return child_sa_name
+
+
+def _get_formatted_ike_proposal(sa: dict) -> str:
+ """
+ Return IKE proposal string in format
+ EncrALG-EncrKeySize/PFR/HASH/DH-GROUP
+ :param sa: IKE SA
+ :type sa: dict
+ :return: IKE proposal string
+ :rtype: str
+ """
+ proposal = ''
+ proposal = f'{proposal}{sa["encr-alg"]}' if 'encr-alg' in sa else proposal
+ proposal = f'{proposal}-{sa["encr-keysize"]}' if 'encr-keysize' in sa else proposal
+ proposal = f'{proposal}/{sa["prf-alg"]}' if 'prf-alg' in sa else proposal
+ proposal = f'{proposal}/{sa["integ-alg"]}' if 'integ-alg' in sa else proposal
+ proposal = f'{proposal}/{sa["dh-group"]}' if 'dh-group' in sa else proposal
+ return proposal
+
+
+def _get_formatted_ipsec_proposal(sa: dict) -> str:
+ """
+ Return IPSec proposal string in format
+ Protocol: EncrALG-EncrKeySize/HASH/PFS
+ :param sa: Child SA
+ :type sa: dict
+ :return: IPSec proposal string
+ :rtype: str
+ """
+ proposal = ''
+ proposal = f'{proposal}{sa["protocol"]}' if 'protocol' in sa else proposal
+ proposal = f'{proposal}:{sa["encr-alg"]}' if 'encr-alg' in sa else proposal
+ proposal = f'{proposal}-{sa["encr-keysize"]}' if 'encr-keysize' in sa else proposal
+ proposal = f'{proposal}/{sa["integ-alg"]}' if 'integ-alg' in sa else proposal
+ proposal = f'{proposal}/{sa["dh-group"]}' if 'dh-group' in sa else proposal
+ return proposal
+
+
+def _get_output_ra_sas_detail(ra_output_list: list) -> str:
+ """
+ Formate RA IKE SAs detail output
+ :param ra_output_list: IKE SAs list
+ :type ra_output_list: list
+ :return: formatted RA IKE SAs detail output
+ :rtype: str
+ """
+ return _get_output_swanctl_sas_from_list(ra_output_list)
+
+
+def _get_formatted_output_ra_summary(ra_output_list: list):
+ sa_data = []
+ for conn in ra_output_list:
+ for sa in conn.values():
+ sa_id = sa['uniqueid'] if 'uniqueid' in sa else ''
+ sa_username = sa['remote-eap-id'] if 'remote-eap-id' in sa else ''
+ sa_protocol = f'IKEv{sa["version"]}' if 'version' in sa else ''
+ sa_remotehost = sa['remote-host'] if 'remote-host' in sa else ''
+ sa_remoteid = sa['remote-id'] if 'remote-id' in sa else ''
+ sa_ike_proposal = _get_formatted_ike_proposal(sa)
+ sa_tunnel_ip = sa['remote-vips']
+ child_sa_key = _get_last_installed_childsa(sa)
+ if child_sa_key:
+ child_sa = sa['child-sas'][child_sa_key]
+ sa_ipsec_proposal = _get_formatted_ipsec_proposal(child_sa)
+ sa_state = "UP"
+ sa_uptime = seconds_to_human(sa['established'])
+ else:
+ sa_ipsec_proposal = ''
+ sa_state = "DOWN"
+ sa_uptime = ''
+ sa_data.append(
+ [sa_id, sa_username, sa_protocol, sa_state, sa_uptime,
+ sa_tunnel_ip,
+ sa_remotehost, sa_remoteid, sa_ike_proposal,
+ sa_ipsec_proposal])
+
+ headers = ["Connection ID", "Username", "Protocol", "State", "Uptime",
+ "Tunnel IP", "Remote Host", "Remote ID", "IKE Proposal",
+ "IPSec Proposal"]
+ sa_data = sorted(sa_data, key=_alphanum_key)
+ output = tabulate(sa_data, headers)
+ return output
+
+
+def show_ra_detail(raw: bool, username: typing.Optional[str] = None,
+ conn_id: typing.Optional[str] = None):
+ list_sa: list = _get_ra_sessions()
+ if username:
+ list_sa = _filter_ikesas(list_sa, 'remote-eap-id', username)
+ elif conn_id:
+ list_sa = _filter_ikesas(list_sa, 'uniqueid', conn_id)
+ if not list_sa:
+ raise vyos.opmode.IncorrectValue(
+ f'No active connections found, aborting')
+ if raw:
+ return list_sa
+ return _get_output_ra_sas_detail(list_sa)
+
+
+def show_ra_summary(raw: bool):
+ list_sa: list = _get_ra_sessions()
+ if not list_sa:
+ raise vyos.opmode.IncorrectValue(
+ f'No active connections found, aborting')
+ if raw:
+ return list_sa
+ return _get_formatted_output_ra_summary(list_sa)
+
+
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/nat.py b/src/op_mode/nat.py
index f899eb3dc..c92795745 100755
--- a/src/op_mode/nat.py
+++ b/src/op_mode/nat.py
@@ -18,23 +18,23 @@ import jmespath
import json
import sys
import xmltodict
+import typing
-from sys import exit
from tabulate import tabulate
-from vyos.configquery import ConfigTreeQuery
+import vyos.opmode
+from vyos.configquery import ConfigTreeQuery
from vyos.util import cmd
from vyos.util import dict_search
-import vyos.opmode
-
-
base = 'nat'
unconf_message = 'NAT is not configured'
+ArgDirection = typing.Literal['source', 'destination']
+ArgFamily = typing.Literal['inet', 'inet6']
-def _get_xml_translation(direction, family):
+def _get_xml_translation(direction, family, address=None):
"""
Get conntrack XML output --src-nat|--dst-nat
"""
@@ -42,7 +42,10 @@ def _get_xml_translation(direction, family):
opt = '--src-nat'
if direction == 'destination':
opt = '--dst-nat'
- return cmd(f'sudo conntrack --dump --family {family} {opt} --output xml')
+ tmp = f'conntrack --dump --family {family} {opt} --output xml'
+ if address:
+ tmp += f' --src {address}'
+ return cmd(tmp)
def _xml_to_dict(xml):
@@ -66,7 +69,7 @@ def _get_json_data(direction, family):
if direction == 'destination':
chain = 'PREROUTING'
family = 'ip6' if family == 'inet6' else 'ip'
- return cmd(f'sudo nft --json list chain {family} vyos_nat {chain}')
+ return cmd(f'nft --json list chain {family} vyos_nat {chain}')
def _get_raw_data_rules(direction, family):
@@ -82,11 +85,11 @@ def _get_raw_data_rules(direction, family):
return rules
-def _get_raw_translation(direction, family):
+def _get_raw_translation(direction, family, address=None):
"""
Return: dictionary
"""
- xml = _get_xml_translation(direction, family)
+ xml = _get_xml_translation(direction, family, address)
if len(xml) == 0:
output = {'conntrack':
{
@@ -231,7 +234,7 @@ def _get_formatted_output_statistics(data, direction):
return output
-def _get_formatted_translation(dict_data, nat_direction, family):
+def _get_formatted_translation(dict_data, nat_direction, family, verbose):
data_entries = []
if 'error' in dict_data['conntrack']:
return 'Entries not found'
@@ -269,14 +272,14 @@ def _get_formatted_translation(dict_data, nat_direction, family):
reply_src = f'{reply_src}:{reply_sport}' if reply_sport else reply_src
reply_dst = f'{reply_dst}:{reply_dport}' if reply_dport else reply_dst
state = meta['state'] if 'state' in meta else ''
- mark = meta['mark']
+ mark = meta.get('mark', '')
zone = meta['zone'] if 'zone' in meta else ''
if nat_direction == 'source':
- data_entries.append(
- [orig_src, reply_dst, proto, timeout, mark, zone])
+ tmp = [orig_src, reply_dst, proto, timeout, mark, zone]
+ data_entries.append(tmp)
elif nat_direction == 'destination':
- data_entries.append(
- [orig_dst, reply_src, proto, timeout, mark, zone])
+ tmp = [orig_dst, reply_src, proto, timeout, mark, zone]
+ data_entries.append(tmp)
headers = ["Pre-NAT", "Post-NAT", "Proto", "Timeout", "Mark", "Zone"]
output = tabulate(data_entries, headers, numalign="left")
@@ -297,7 +300,7 @@ def _verify(func):
@_verify
-def show_rules(raw: bool, direction: str, family: str):
+def show_rules(raw: bool, direction: ArgDirection, family: ArgFamily):
nat_rules = _get_raw_data_rules(direction, family)
if raw:
return nat_rules
@@ -306,7 +309,7 @@ def show_rules(raw: bool, direction: str, family: str):
@_verify
-def show_statistics(raw: bool, direction: str, family: str):
+def show_statistics(raw: bool, direction: ArgDirection, family: ArgFamily):
nat_statistics = _get_raw_data_rules(direction, family)
if raw:
return nat_statistics
@@ -315,13 +318,20 @@ def show_statistics(raw: bool, direction: str, family: str):
@_verify
-def show_translations(raw: bool, direction: str, family: str):
+def show_translations(raw: bool, direction: ArgDirection,
+ family: ArgFamily,
+ 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/neighbor.py b/src/op_mode/neighbor.py
index 264dbdc72..b329ea280 100755
--- a/src/op_mode/neighbor.py
+++ b/src/op_mode/neighbor.py
@@ -32,6 +32,9 @@ import typing
import vyos.opmode
+ArgFamily = typing.Literal['inet', 'inet6']
+ArgState = typing.Literal['reachable', 'stale', 'failed', 'permanent']
+
def interface_exists(interface):
import os
return os.path.exists(f'/sys/class/net/{interface}')
@@ -88,7 +91,8 @@ def format_neighbors(neighs, interface=None):
headers = ["Address", "Interface", "Link layer address", "State"]
return tabulate(neighs, headers)
-def show(raw: bool, family: str, interface: typing.Optional[str], state: typing.Optional[str]):
+def show(raw: bool, family: ArgFamily, interface: typing.Optional[str],
+ state: typing.Optional[ArgState]):
""" Display neighbor table contents """
data = get_raw_data(family, interface, state=state)
@@ -97,7 +101,7 @@ def show(raw: bool, family: str, interface: typing.Optional[str], state: typing.
else:
return format_neighbors(data, interface)
-def reset(family: str, interface: typing.Optional[str], address: typing.Optional[str]):
+def reset(family: ArgFamily, interface: typing.Optional[str], address: typing.Optional[str]):
from vyos.util import run
if address and interface:
diff --git a/src/op_mode/nhrp.py b/src/op_mode/nhrp.py
new file mode 100755
index 000000000..5ff91a59c
--- /dev/null
+++ b/src/op_mode/nhrp.py
@@ -0,0 +1,101 @@
+#!/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 tabulate
+import vyos.opmode
+
+from vyos.util import cmd
+from vyos.util import process_named_running
+from vyos.util import colon_separated_to_dict
+
+
+def _get_formatted_output(output_dict: dict) -> str:
+ """
+ Create formatted table for CLI output
+ :param output_dict: dictionary for API
+ :type output_dict: dict
+ :return: tabulate string
+ :rtype: str
+ """
+ print(f"Status: {output_dict['Status']}")
+ output: str = tabulate.tabulate(output_dict['routes'], headers='keys',
+ numalign="left")
+ return output
+
+
+def _get_formatted_dict(output_string: str) -> dict:
+ """
+ Format string returned from CMD to API list
+ :param output_string: String received by CMD
+ :type output_string: str
+ :return: dictionary for API
+ :rtype: dict
+ """
+ formatted_dict: dict = {
+ 'Status': '',
+ 'routes': []
+ }
+ output_list: list = output_string.split('\n\n')
+ for list_a in output_list:
+ output_dict = colon_separated_to_dict(list_a, True)
+ if 'Status' in output_dict:
+ formatted_dict['Status'] = output_dict['Status']
+ else:
+ formatted_dict['routes'].append(output_dict)
+ return formatted_dict
+
+
+def show_interface(raw: bool):
+ """
+ Command 'show nhrp interface'
+ :param raw: if API
+ :type raw: bool
+ """
+ if not process_named_running('opennhrp'):
+ raise vyos.opmode.UnconfiguredSubsystem('OpenNHRP is not running.')
+ interface_string: str = cmd('sudo opennhrpctl interface show')
+ interface_dict: dict = _get_formatted_dict(interface_string)
+ if raw:
+ return interface_dict
+ else:
+ return _get_formatted_output(interface_dict)
+
+
+def show_tunnel(raw: bool):
+ """
+ Command 'show nhrp tunnel'
+ :param raw: if API
+ :type raw: bool
+ """
+ if not process_named_running('opennhrp'):
+ raise vyos.opmode.UnconfiguredSubsystem('OpenNHRP is not running.')
+ tunnel_string: str = cmd('sudo opennhrpctl show')
+ tunnel_dict: list = _get_formatted_dict(tunnel_string)
+ if raw:
+ return tunnel_dict
+ else:
+ return _get_formatted_output(tunnel_dict)
+
+
+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/openvpn.py b/src/op_mode/openvpn.py
index 3797a7153..d9ae965c5 100755
--- a/src/op_mode/openvpn.py
+++ b/src/op_mode/openvpn.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
@@ -16,16 +16,21 @@
#
#
+import json
import os
import sys
+import typing
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.util import rc_cmd
from vyos.config import Config
+ArgMode = typing.Literal['client', 'server', 'site_to_site']
+
def _get_tunnel_address(peer_host, peer_port, status_file):
peer = peer_host + ':' + peer_port
lst = []
@@ -50,7 +55,7 @@ def _get_tunnel_address(peer_host, peer_port, status_file):
def _get_interface_status(mode: str, interface: str) -> dict:
status_file = f'/run/openvpn/{interface}.status'
- data = {
+ data: dict = {
'mode': mode,
'intf': interface,
'local_host': '',
@@ -60,7 +65,7 @@ def _get_interface_status(mode: str, interface: str) -> dict:
}
if not os.path.exists(status_file):
- raise vyos.opmode.DataUnavailable('No information for interface {interface}')
+ return data
with open(status_file, 'r') as f:
lines = f.readlines()
@@ -139,30 +144,54 @@ def _get_interface_status(mode: str, interface: str) -> dict:
return data
-def _get_raw_data(mode: str) -> dict:
- data = {}
+
+def _get_interface_state(iface):
+ rc, out = rc_cmd(f'ip --json link show dev {iface}')
+ try:
+ data = json.loads(out)
+ except:
+ return 'DOWN'
+ return data[0].get('operstate', 'DOWN')
+
+
+def _get_interface_description(iface):
+ rc, out = rc_cmd(f'ip --json link show dev {iface}')
+ try:
+ data = json.loads(out)
+ except:
+ return ''
+ return data[0].get('ifalias', '')
+
+
+def _get_raw_data(mode: str) -> list:
+ data: list = []
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]
+ interfaces = [x for x in list(conf_dict) if
+ conf_dict[x]['mode'].replace('-', '_') == mode]
for intf in interfaces:
- data[intf] = _get_interface_status(mode, intf)
- d = data[intf]
+ d = _get_interface_status(mode, intf)
+ d['state'] = _get_interface_state(intf)
+ d['description'] = _get_interface_description(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']:
+ if conf.exists(f'interfaces openvpn {intf} server client'):
+ d['configured_clients'] = conf.list_nodes(f'interfaces openvpn {intf} server client')
+ 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')
+ data.append(d)
return data
-def _format_openvpn(data: dict) -> str:
+def _format_openvpn(data: list) -> str:
if not data:
out = 'No OpenVPN interfaces configured'
return out
@@ -171,11 +200,12 @@ def _format_openvpn(data: dict) -> str:
'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']):
+ for d in data:
+ data_out = []
+ intf = d['intf']
+ l_host = d['local_host']
+ l_port = d['local_port']
+ for client in d['clients']:
r_host = client['remote_host']
r_port = client['remote_port']
@@ -190,11 +220,13 @@ def _format_openvpn(data: dict) -> str:
data_out.append([name, remote, tunnel, local, tx_bytes,
rx_bytes, online_since])
- out += tabulate(data_out, headers)
+ if data_out:
+ out += tabulate(data_out, headers)
+ out += "\n"
return out
-def show(raw: bool, mode: str) -> str:
+def show(raw: bool, mode: ArgMode) -> typing.Union[list,str]:
openvpn_data = _get_raw_data(mode)
if raw:
diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py
index 1e78c3a03..b054690b0 100755
--- a/src/op_mode/pki.py
+++ b/src/op_mode/pki.py
@@ -87,6 +87,9 @@ def get_config_certificate(name=None):
def get_certificate_ca(cert, ca_certs):
# Find CA certificate for given certificate
+ if not ca_certs:
+ return None
+
for ca_name, ca_dict in ca_certs.items():
if 'certificate' not in ca_dict:
continue
diff --git a/src/op_mode/reset_vpn.py b/src/op_mode/reset_vpn.py
index 3a0ad941c..46195d6cd 100755
--- a/src/op_mode/reset_vpn.py
+++ b/src/op_mode/reset_vpn.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019 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
@@ -13,60 +13,49 @@
#
# 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 argparse
+import typing
from vyos.util import run
+import vyos.opmode
+
cmd_dict = {
- 'cmd_base' : '/usr/bin/accel-cmd -p {} terminate {} {}',
- 'vpn_types' : {
- 'pptp' : 2003,
- 'l2tp' : 2004,
- 'sstp' : 2005
+ 'cmd_base': '/usr/bin/accel-cmd -p {} terminate {} {}',
+ 'vpn_types': {
+ 'pptp': 2003,
+ 'l2tp': 2004,
+ 'sstp': 2005
}
}
-def terminate_sessions(username='', interface='', protocol=''):
- # Reset vpn connections by username
+def reset_conn(protocol: str, username: typing.Optional[str] = None,
+ interface: typing.Optional[str] = None):
if protocol in cmd_dict['vpn_types']:
- if username == "all_users":
- run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol], 'all', ''))
- else:
- run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol], 'username', username))
-
- # Reset vpn connections by ifname
- elif interface:
- for proto in cmd_dict['vpn_types']:
- run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][proto], 'if', interface))
-
- elif username:
- # Reset all vpn connections
- if username == "all_users":
- for proto in cmd_dict['vpn_types']:
- run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][proto], 'all', ''))
+ # Reset by Interface
+ if interface:
+ run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol],
+ 'if', interface))
+ return
+ # Reset by username
+ if username:
+ run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol],
+ 'username', username))
+ # Reset all
else:
- for proto in cmd_dict['vpn_types']:
- run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][proto], 'username', username))
-
-def main():
- #parese args
- parser = argparse.ArgumentParser()
- parser.add_argument('--username', help='Terminate by username (all_users used for disconnect all users)', required=False)
- parser.add_argument('--interface', help='Terminate by interface', required=False)
- parser.add_argument('--protocol', help='Set protocol (pptp|l2tp|sstp)', required=False)
- args = parser.parse_args()
-
- if args.username or args.interface:
- terminate_sessions(username=args.username, interface=args.interface, protocol=args.protocol)
+ run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol],
+ 'all',
+ ''))
else:
- print("Param --username or --interface required")
- sys.exit(1)
-
- terminate_sessions()
+ vyos.opmode.IncorrectValue('Unknown VPN Protocol, aborting')
if __name__ == '__main__':
- 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/restart_frr.py b/src/op_mode/restart_frr.py
index 91b25567a..680d9f8cc 100755
--- a/src/op_mode/restart_frr.py
+++ b/src/op_mode/restart_frr.py
@@ -139,7 +139,7 @@ def _reload_config(daemon):
# define program arguments
cmd_args_parser = argparse.ArgumentParser(description='restart frr daemons')
cmd_args_parser.add_argument('--action', choices=['restart'], required=True, help='action to frr daemons')
-cmd_args_parser.add_argument('--daemon', choices=['bfdd', 'bgpd', 'ldpd', 'ospfd', 'ospf6d', 'isisd', 'ripd', 'ripngd', 'staticd', 'zebra'], required=False, nargs='*', help='select single or multiple daemons')
+cmd_args_parser.add_argument('--daemon', choices=['bfdd', 'bgpd', 'ldpd', 'ospfd', 'ospf6d', 'isisd', 'ripd', 'ripngd', 'staticd', 'zebra', 'babeld'], required=False, nargs='*', help='select single or multiple daemons')
# parse arguments
cmd_args = cmd_args_parser.parse_args()
diff --git a/src/op_mode/route.py b/src/op_mode/route.py
index d07a34180..d6d6b7d6f 100755
--- a/src/op_mode/route.py
+++ b/src/op_mode/route.py
@@ -54,20 +54,49 @@ frr_command_template = Template("""
{% endif %}
""")
-def show_summary(raw: bool):
+ArgFamily = typing.Literal['inet', 'inet6']
+
+def show_summary(raw: bool, family: ArgFamily, table: typing.Optional[int], vrf: typing.Optional[str]):
from vyos.util import cmd
+ if family == 'inet':
+ family_cmd = 'ip'
+ elif family == 'inet6':
+ family_cmd = 'ipv6'
+ else:
+ raise ValueError(f"Unsupported address family {family}")
+
+ if (table is not None) and (vrf is not None):
+ raise ValueError("table and vrf options are mutually exclusive")
+
+ # Replace with Jinja if it ever starts growing
+ if table:
+ table_cmd = f"table {table}"
+ else:
+ table_cmd = ""
+
+ if vrf:
+ vrf_cmd = f"vrf {vrf}"
+ else:
+ vrf_cmd = ""
+
if raw:
from json import loads
- output = cmd(f"vtysh -c 'show ip route summary json'")
- return loads(output)
+ output = cmd(f"vtysh -c 'show {family_cmd} route {vrf_cmd} summary {table_cmd} json'").strip()
+
+ # If there are no routes in a table, its "JSON" output is an empty string,
+ # as of FRR 8.4.1
+ if output:
+ return loads(output)
+ else:
+ return {}
else:
- output = cmd(f"vtysh -c 'show ip route summary'")
+ output = cmd(f"vtysh -c 'show {family_cmd} route {vrf_cmd} summary {table_cmd}'")
return output
def show(raw: bool,
- family: str,
+ family: ArgFamily,
net: typing.Optional[str],
table: typing.Optional[int],
protocol: typing.Optional[str],
diff --git a/src/op_mode/sflow.py b/src/op_mode/sflow.py
new file mode 100755
index 000000000..88f70d6bd
--- /dev/null
+++ b/src/op_mode/sflow.py
@@ -0,0 +1,108 @@
+#!/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 dbus
+import sys
+
+from tabulate import tabulate
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.util import cmd
+
+import vyos.opmode
+
+
+def _get_raw_sflow():
+ bus = dbus.SystemBus()
+ config = ConfigTreeQuery()
+
+ interfaces = config.values('system sflow interface')
+ servers = config.list_nodes('system sflow server')
+
+ sflow = bus.get_object('net.sflow.hsflowd', '/net/sflow/hsflowd')
+ sflow_telemetry = dbus.Interface(
+ sflow, dbus_interface='net.sflow.hsflowd.telemetry')
+ agent_address = sflow_telemetry.GetAgent()
+ samples_dropped = int(sflow_telemetry.Get('dropped_samples'))
+ packet_drop_sent = int(sflow_telemetry.Get('event_samples'))
+ samples_packet_sent = int(sflow_telemetry.Get('flow_samples'))
+ samples_counter_sent = int(sflow_telemetry.Get('counter_samples'))
+ datagrams_sent = int(sflow_telemetry.Get('datagrams'))
+ rtmetric_samples = int(sflow_telemetry.Get('rtmetric_samples'))
+ event_samples_suppressed = int(sflow_telemetry.Get('event_samples_suppressed'))
+ samples_suppressed = int(sflow_telemetry.Get('flow_samples_suppressed'))
+ counter_samples_suppressed = int(
+ sflow_telemetry.Get("counter_samples_suppressed"))
+ version = sflow_telemetry.GetVersion()
+
+ sflow_dict = {
+ 'agent_address': agent_address,
+ 'sflow_interfaces': interfaces,
+ 'sflow_servers': servers,
+ 'counter_samples_sent': samples_counter_sent,
+ 'datagrams_sent': datagrams_sent,
+ 'packet_drop_sent': packet_drop_sent,
+ 'packet_samples_dropped': samples_dropped,
+ 'packet_samples_sent': samples_packet_sent,
+ 'rtmetric_samples': rtmetric_samples,
+ 'event_samples_suppressed': event_samples_suppressed,
+ 'flow_samples_suppressed': samples_suppressed,
+ 'counter_samples_suppressed': counter_samples_suppressed,
+ 'hsflowd_version': version
+ }
+ return sflow_dict
+
+
+def _get_formatted_sflow(data):
+ table = [
+ ['Agent address', f'{data.get("agent_address")}'],
+ ['sFlow interfaces', f'{data.get("sflow_interfaces", "n/a")}'],
+ ['sFlow servers', f'{data.get("sflow_servers", "n/a")}'],
+ ['Counter samples sent', f'{data.get("counter_samples_sent")}'],
+ ['Datagrams sent', f'{data.get("datagrams_sent")}'],
+ ['Packet samples sent', f'{data.get("packet_samples_sent")}'],
+ ['Packet samples dropped', f'{data.get("packet_samples_dropped")}'],
+ ['Packet drops sent', f'{data.get("packet_drop_sent")}'],
+ ['Packet drops suppressed', f'{data.get("event_samples_suppressed")}'],
+ ['Flow samples suppressed', f'{data.get("flow_samples_suppressed")}'],
+ ['Counter samples suppressed', f'{data.get("counter_samples_suppressed")}']
+ ]
+
+ return tabulate(table)
+
+
+def show(raw: bool):
+
+ config = ConfigTreeQuery()
+ if not config.exists('system sflow'):
+ raise vyos.opmode.UnconfiguredSubsystem(
+ '"system sflow" is not configured!')
+
+ sflow_data = _get_raw_sflow()
+ if raw:
+ return sflow_data
+ else:
+ return _get_formatted_sflow(sflow_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/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_interfaces.py b/src/op_mode/show_interfaces.py
deleted file mode 100755
index eac068274..000000000
--- a/src/op_mode/show_interfaces.py
+++ /dev/null
@@ -1,310 +0,0 @@
-#!/usr/bin/env python3
-
-# Copyright 2017-2021 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 glob
-import argparse
-
-from vyos.ifconfig import Section
-from vyos.ifconfig import Interface
-from vyos.ifconfig import VRRP
-from vyos.util import cmd, call
-
-
-# interfaces = Sections.reserved()
-interfaces = ['eno', 'ens', 'enp', 'enx', 'eth', 'vmnet', 'lo', 'tun', 'wan', 'pppoe']
-glob_ifnames = '/sys/class/net/({})*'.format('|'.join(interfaces))
-
-
-actions = {}
-def register(name):
- """
- Decorator to register a function into actions with a name.
- `actions[name]' can be used to call the registered functions.
- We wrap each function in a SIGPIPE handler as all registered functions
- can be subject to a broken pipe if there are a lot of interfaces.
- """
- def _register(function):
- def handled_function(*args, **kwargs):
- try:
- function(*args, **kwargs)
- except BrokenPipeError:
- # Flush output to /dev/null and bail out.
- os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno())
- sys.exit(1)
- actions[name] = handled_function
- return handled_function
- return _register
-
-
-def filtered_interfaces(ifnames, iftypes, vif, vrrp):
- """
- get all the interfaces from the OS and returns them
- ifnames can be used to filter which interfaces should be considered
-
- ifnames: a list of interfaces names to consider, empty do not filter
- return an instance of the interface class
- """
- 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 ''
- if len(returned) == 2:
- rows, columns = [int(_) for _ in returned]
- else:
- rows, 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(clear, now):
- """
- attempt to correct a counter if it wrapped, copied from perl
-
- clear: previous counter
- now: the current counter
- """
- # This function has to deal with both 32 and 64 bit counters
- if clear == 0:
- return now
-
- # device is using 64 bit values assume they never wrap
- value = now - clear
- if (now >> 32) != 0:
- return value
-
- # The counter has rolled. If the counter has rolled
- # multiple times since the clear value, then this math
- # is meaningless.
- if (value < 0):
- value = (4294967296 - clear) + now
-
- return value
-
-
-@register('help')
-def usage(*args):
- print(f"Usage: {sys.argv[0]} [intf=NAME|intf-type=TYPE|vif|vrrp] action=ACTION")
- print(f" NAME = " + ' | '.join(Section.interfaces()))
- print(f" TYPE = " + ' | '.join(Section.sections()))
- print(f" ACTION = " + ' | '.join(actions))
- sys.exit(1)
-
-
-@register('allowed')
-def run_allowed(**kwarg):
- sys.stdout.write(' '.join(Section.interfaces()))
-
-
-def pppoe(ifname):
- out = cmd(f'ps -C pppd -f')
- if ifname in out:
- return 'C'
- elif ifname in [_.split('/')[-1] for _ in glob.glob('/etc/ppp/peers/pppoe*')]:
- return 'D'
- return ''
-
-
-@register('show')
-def run_show_intf(ifnames, iftypes, vif, vrrp):
- handled = []
- for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
- handled.append(interface.ifname)
- cache = interface.operational.load_counters()
-
- out = cmd(f'ip addr show {interface.ifname}')
- out = re.sub(f'^\d+:\s+','',out)
- if re.search('link/tunnel6', out):
- tunnel = cmd(f'ip -6 tun show {interface.ifname}')
- # tun0: ip/ipv6 remote ::2 local ::1 encaplimit 4 hoplimit 64 tclass inherit flowlabel inherit (flowinfo 0x00000000)
- tunnel = re.sub('.*encap', 'encap', tunnel)
- out = re.sub('(\n\s+)(link/tunnel6)', f'\g<1>{tunnel}\g<1>\g<2>', out)
-
- print(out)
-
- timestamp = int(cache.get('timestamp', 0))
- if timestamp:
- when = interface.operational.strtime(timestamp)
- print(f' Last clear: {when}')
-
- description = interface.get_alias()
- if description:
- print(f' Description: {description}')
-
- print()
- print(interface.operational.formated_stats())
-
- for ifname in ifnames:
- if ifname not in handled and ifname.startswith('pppoe'):
- state = pppoe(ifname)
- if not state:
- continue
- string = {
- 'C': 'Coming up',
- 'D': 'Link down',
- }[state]
- print('{}: {}'.format(ifname, string))
-
-
-@register('show-brief')
-def run_show_intf_brief(ifnames, iftypes, vif, vrrp):
- 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 % ("---------", "----------", "---", "-----------"))
-
- handled = []
- for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
- handled.append(interface.ifname)
-
- oper_state = interface.operational.get_state()
- admin_state = interface.get_admin_state()
-
- intf = [interface.ifname,]
-
- oper = ['u', ] if oper_state in ('up', 'unknown') else ['D', ]
- admin = ['u', ] if admin_state in ('up', 'unknown') else ['A', ]
- addrs = [_ for _ in interface.get_addr() if not _.startswith('fe80::')] or ['-', ]
- descs = list(split_text(interface.get_alias(),0))
-
- while intf or oper or admin or addrs or descs:
- i = intf.pop(0) if intf 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 ifname in ifnames:
- if ifname not in handled and ifname.startswith('pppoe'):
- state = pppoe(ifname)
- if not state:
- continue
- string = {
- 'C': 'u/D',
- 'D': 'A/D',
- }[state]
- print(format1 % (ifname, '', string, ''))
-
-
-@register('show-count')
-def run_show_counters(ifnames, iftypes, vif, vrrp):
- formating = '%-12s %10s %10s %10s %10s'
- print(formating % ('Interface', 'Rx Packets', 'Rx Bytes', 'Tx Packets', 'Tx Bytes'))
-
- for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
- oper = interface.operational.get_state()
-
- if oper not in ('up','unknown'):
- continue
-
- stats = interface.operational.get_stats()
- cache = interface.operational.load_counters()
- print(formating % (
- interface.ifname,
- get_counter_val(cache['rx_packets'], stats['rx_packets']),
- get_counter_val(cache['rx_bytes'], stats['rx_bytes']),
- get_counter_val(cache['tx_packets'], stats['tx_packets']),
- get_counter_val(cache['tx_bytes'], stats['tx_bytes']),
- ))
-
-
-@register('clear')
-def run_clear_intf(ifnames, iftypes, vif, vrrp):
- for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
- print(f'Clearing {interface.ifname}')
- interface.operational.clear_counters()
-
-
-@register('reset')
-def run_reset_intf(ifnames, iftypes, vif, vrrp):
- for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
- interface.operational.reset_counters()
-
-
-if __name__ == '__main__':
- parser = argparse.ArgumentParser(add_help=False, description='Show interface information')
- parser.add_argument('--intf', action="store", type=str, default='', help='only show the specified interface(s)')
- parser.add_argument('--intf-type', action="store", type=str, default='', help='only show the specified interface type')
- parser.add_argument('--action', action="store", type=str, default='show', help='action to perform')
- parser.add_argument('--vif', action='store_true', default=False, help="only show vif interfaces")
- parser.add_argument('--vrrp', action='store_true', default=False, help="only show vrrp interfaces")
- parser.add_argument('--help', action='store_true', default=False, help="show help")
-
- args = parser.parse_args()
-
- def missing(*args):
- print('Invalid action [{args.action}]')
- usage()
-
- actions.get(args.action, missing)(
- [_ for _ in args.intf.split(' ') if _],
- [_ for _ in args.intf_type.split(' ') if _],
- args.vif,
- args.vrrp
- )
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_openconnect_otp.py b/src/op_mode/show_openconnect_otp.py
index ae532ccc9..88982c50b 100755
--- a/src/op_mode/show_openconnect_otp.py
+++ b/src/op_mode/show_openconnect_otp.py
@@ -46,7 +46,7 @@ def get_otp_ocserv(username):
# options which we need to update into the dictionary retrived.
default_values = defaults(base)
ocserv = dict_merge(default_values, ocserv)
- # workaround a "know limitation" - https://phabricator.vyos.net/T2665
+ # workaround a "know limitation" - https://vyos.dev/T2665
del ocserv['authentication']['local_users']['username']['otp']
if not ocserv["authentication"]["local_users"]["username"]:
return None
diff --git a/src/op_mode/show_techsupport_report.py b/src/op_mode/show_techsupport_report.py
new file mode 100644
index 000000000..782004144
--- /dev/null
+++ b/src/op_mode/show_techsupport_report.py
@@ -0,0 +1,303 @@
+#!/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
+
+from typing import List
+from vyos.util import rc_cmd
+from vyos.ifconfig import Section
+from vyos.ifconfig import Interface
+
+
+def print_header(command: str) -> None:
+ """Prints a command with headers '-'.
+
+ Example:
+
+ % print_header('Example command')
+
+ ---------------
+ Example command
+ ---------------
+ """
+ header_length = len(command) * '-'
+ print(f"\n{header_length}\n{command}\n{header_length}")
+
+
+def execute_command(command: str, header_text: str) -> None:
+ """Executes a command and prints the output with a header.
+
+ Example:
+ % execute_command('uptime', "Uptime of the system")
+
+ --------------------
+ Uptime of the system
+ --------------------
+ 20:21:57 up 9:04, 5 users, load average: 0.00, 0.00, 0.0
+
+ """
+ print_header(header_text)
+ try:
+ rc, output = rc_cmd(command)
+ print(output)
+ except Exception as e:
+ print(f"Error executing command: {command}")
+ print(f"Error message: {e}")
+
+
+def op(cmd: str) -> str:
+ """Returns a command with the VyOS operational mode wrapper."""
+ return f'/opt/vyatta/bin/vyatta-op-cmd-wrapper {cmd}'
+
+
+def get_ethernet_interfaces() -> List[Interface]:
+ """Returns a list of Ethernet interfaces."""
+ return Section.interfaces('ethernet')
+
+
+def show_version() -> None:
+ """Prints the VyOS version and package changes."""
+ execute_command(op('show version'), 'VyOS Version and Package Changes')
+
+
+def show_config_file() -> None:
+ """Prints the contents of a configuration file with a header."""
+ execute_command('cat /opt/vyatta/etc/config/config.boot', 'Configuration file')
+
+
+def show_running_config() -> None:
+ """Prints the running configuration."""
+ execute_command(op('show configuration'), 'Running configuration')
+
+
+def show_package_repository_config() -> None:
+ """Prints the package repository configuration file."""
+ execute_command('cat /etc/apt/sources.list', 'Package Repository Configuration File')
+ execute_command('ls -l /etc/apt/sources.list.d/', 'Repositories')
+
+
+def show_user_startup_scripts() -> None:
+ """Prints the user startup scripts."""
+ execute_command('cat /config/scripts/vyos-postconfig-bootup.script', 'User Startup Scripts')
+
+
+def show_frr_config() -> None:
+ """Prints the FRR configuration."""
+ execute_command('vtysh -c "show run"', 'FRR configuration')
+
+
+def show_interfaces() -> None:
+ """Prints the interfaces."""
+ execute_command(op('show interfaces'), 'Interfaces')
+
+
+def show_interface_statistics() -> None:
+ """Prints the interface statistics."""
+ execute_command('ip -s link show', 'Interface statistics')
+
+
+def show_physical_interface_statistics() -> None:
+ """Prints the physical interface statistics."""
+ execute_command('/usr/bin/true', 'Physical Interface statistics')
+ for iface in get_ethernet_interfaces():
+ # Exclude vlans
+ if '.' in iface:
+ continue
+ execute_command(f'ethtool --driver {iface}', f'ethtool --driver {iface}')
+ execute_command(f'ethtool --statistics {iface}', f'ethtool --statistics {iface}')
+ execute_command(f'ethtool --show-ring {iface}', f'ethtool --show-ring {iface}')
+ execute_command(f'ethtool --show-coalesce {iface}', f'ethtool --show-coalesce {iface}')
+ execute_command(f'ethtool --pause {iface}', f'ethtool --pause {iface}')
+ execute_command(f'ethtool --show-features {iface}', f'ethtool --show-features {iface}')
+ execute_command(f'ethtool --phy-statistics {iface}', f'ethtool --phy-statistics {iface}')
+ execute_command('netstat --interfaces', 'netstat --interfaces')
+ execute_command('netstat --listening', 'netstat --listening')
+ execute_command('cat /proc/net/dev', 'cat /proc/net/dev')
+
+
+def show_bridge() -> None:
+ """Show bridge interfaces."""
+ execute_command(op('show bridge'), 'Show bridge')
+
+
+def show_arp() -> None:
+ """Prints ARP entries."""
+ execute_command(op('show arp'), 'ARP Table (Total entries)')
+ execute_command(op('show ipv6 neighbors'), 'show ipv6 neighbors')
+
+
+def show_route() -> None:
+ """Prints routing information."""
+
+ cmd_list_route = [
+ "show ip route bgp | head -108",
+ "show ip route cache",
+ "show ip route connected",
+ "show ip route forward",
+ "show ip route isis | head -108",
+ "show ip route kernel",
+ "show ip route ospf | head -108",
+ "show ip route rip",
+ "show ip route static",
+ "show ip route summary",
+ "show ip route supernets-only",
+ "show ip route table all",
+ "show ip route vrf all",
+ "show ipv6 route bgp | head 108",
+ "show ipv6 route cache",
+ "show ipv6 route connected",
+ "show ipv6 route forward",
+ "show ipv6 route isis",
+ "show ipv6 route kernel",
+ "show ipv6 route ospf",
+ "show ipv6 route rip",
+ "show ipv6 route static",
+ "show ipv6 route summary",
+ "show ipv6 route table all",
+ "show ipv6 route vrf all",
+ ]
+ for command in cmd_list_route:
+ execute_command(op(command), command)
+
+
+def show_firewall() -> None:
+ """Prints firweall information."""
+ execute_command('sudo nft list ruleset', 'nft list ruleset')
+
+
+def show_system() -> None:
+ """Prints system parameters."""
+ execute_command(op('show system image version'), 'Show System Image Version')
+ execute_command(op('show system image storage'), 'Show System Image Storage')
+
+
+def show_date() -> None:
+ """Print the current date."""
+ execute_command('date', 'Current Time')
+
+
+def show_installed_packages() -> None:
+ """Prints installed packages."""
+ execute_command('dpkg --list', 'Installed Packages')
+
+
+def show_loaded_modules() -> None:
+ """Prints loaded modules /proc/modules"""
+ execute_command('cat /proc/modules', 'Loaded Modules')
+
+
+def show_cpu_statistics() -> None:
+ """Prints CPU statistics."""
+ execute_command('/usr/bin/true', 'CPU')
+ execute_command('lscpu', 'Installed CPU\'s')
+ execute_command('top --iterations 1 --batch-mode --accum-time-toggle', 'Cumulative CPU Time Used by Running Processes')
+ execute_command('cat /proc/loadavg', 'Load Average')
+
+
+def show_system_interrupts() -> None:
+ """Prints system interrupts."""
+ execute_command('cat /proc/interrupts', 'Hardware Interrupt Counters')
+
+
+def show_soft_irqs() -> None:
+ """Prints soft IRQ's."""
+ execute_command('cat /proc/softirqs', 'Soft IRQ\'s')
+
+
+def show_softnet_statistics() -> None:
+ """Prints softnet statistics."""
+ execute_command('cat /proc/net/softnet_stat', 'cat /proc/net/softnet_stat')
+
+
+def show_running_processes() -> None:
+ """Prints current running processes"""
+ execute_command('ps -ef', 'Running Processes')
+
+
+def show_memory_usage() -> None:
+ """Prints memory usage"""
+ execute_command('/usr/bin/true', 'Memory')
+ execute_command('cat /proc/meminfo', 'Installed Memory')
+ execute_command('free', 'Memory Usage')
+
+
+def list_disks():
+ disks = set()
+ with open('/proc/partitions') as partitions_file:
+ for line in partitions_file:
+ fields = line.strip().split()
+ if len(fields) == 4 and fields[3].isalpha() and fields[3] != 'name':
+ disks.add(fields[3])
+ return disks
+
+
+def show_storage() -> None:
+ """Prints storage information."""
+ execute_command('cat /proc/devices', 'Devices')
+ execute_command('cat /proc/partitions', 'Partitions')
+
+ for disk in list_disks():
+ execute_command(f'fdisk --list /dev/{disk}', f'Partitioning for disk {disk}')
+
+
+def main():
+ # Configuration data
+ show_version()
+ show_config_file()
+ show_running_config()
+ show_package_repository_config()
+ show_user_startup_scripts()
+ show_frr_config()
+
+ # Interfaces
+ show_interfaces()
+ show_interface_statistics()
+ show_physical_interface_statistics()
+ show_bridge()
+ show_arp()
+
+ # Routing
+ show_route()
+
+ # Firewall
+ show_firewall()
+
+ # System
+ show_system()
+ show_date()
+ show_installed_packages()
+ show_loaded_modules()
+
+ # CPU
+ show_cpu_statistics()
+ show_system_interrupts()
+ show_soft_irqs()
+ show_softnet_statistics()
+
+ # Memory
+ show_memory_usage()
+
+ # Storage
+ show_storage()
+
+ # Processes
+ show_running_processes()
+
+ # TODO: Get information from clouds
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/op_mode/show_vpn_ra.py b/src/op_mode/show_vpn_ra.py
deleted file mode 100755
index 73688c4ea..000000000
--- a/src/op_mode/show_vpn_ra.py
+++ /dev/null
@@ -1,56 +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/>.
-
-import os
-import sys
-import re
-
-from vyos.util import popen
-
-# chech connection to pptp and l2tp daemon
-def get_sessions():
- absent_pptp = False
- absent_l2tp = False
- pptp_cmd = "accel-cmd -p 2003 show sessions"
- l2tp_cmd = "accel-cmd -p 2004 show sessions"
- err_pattern = "^Connection.+failed$"
- # This value for chack only output header without sessions.
- len_def_header = 170
-
- # Check pptp
- output, err = popen(pptp_cmd, decode='utf-8')
- if not err and len(output) > len_def_header and not re.search(err_pattern, output):
- print(output)
- else:
- absent_pptp = True
-
- # Check l2tp
- output, err = popen(l2tp_cmd, decode='utf-8')
- if not err and len(output) > len_def_header and not re.search(err_pattern, output):
- print(output)
- else:
- absent_l2tp = True
-
- if absent_l2tp and absent_pptp:
- print("No active remote access VPN sessions")
-
-
-def main():
- get_sessions()
-
-
-if __name__ == '__main__':
- main()
diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py
index 2392cfe92..b81d1693e 100755
--- a/src/op_mode/vpn_ipsec.py
+++ b/src/op_mode/vpn_ipsec.py
@@ -16,12 +16,12 @@
import re
import argparse
-from subprocess import TimeoutExpired
from vyos.util import call
SWANCTL_CONF = '/etc/swanctl/swanctl.conf'
+
def get_peer_connections(peer, tunnel, return_all = False):
search = rf'^[\s]*(peer_{peer}_(tunnel_[\d]+|vti)).*'
matches = []
@@ -34,57 +34,6 @@ def get_peer_connections(peer, tunnel, return_all = False):
matches.append(result[1])
return matches
-def reset_peer(peer, tunnel):
- if not peer:
- print('Invalid peer, aborting')
- return
-
- conns = get_peer_connections(peer, tunnel, return_all = (not tunnel or tunnel == 'all'))
-
- if not conns:
- print('Tunnel(s) not found, aborting')
- return
-
- result = True
- for conn in conns:
- try:
- 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
-
-
- print('Peer reset result: ' + ('success' if result else 'failed'))
-
-def get_profile_connection(profile, tunnel = None):
- search = rf'(dmvpn-{profile}-[\w]+)' if tunnel == 'all' else rf'(dmvpn-{profile}-{tunnel})'
- with open(SWANCTL_CONF, 'r') as f:
- for line in f.readlines():
- result = re.search(search, line)
- if result:
- return result[1]
- return None
-
-def reset_profile(profile, tunnel):
- if not profile:
- print('Invalid profile, aborting')
- return
-
- if not tunnel:
- print('Invalid tunnel, aborting')
- return
-
- conn = get_profile_connection(profile)
-
- if not conn:
- print('Profile not found, aborting')
- return
-
- 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'))
def debug_peer(peer, tunnel):
peer = peer.replace(':', '-')
@@ -119,6 +68,7 @@ def debug_peer(peer, tunnel):
for conn in conns:
call(f'/usr/sbin/ipsec statusall | grep {conn}')
+
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--action', help='Control action', required=True)
@@ -127,9 +77,6 @@ if __name__ == '__main__':
args = parser.parse_args()
- if args.action == 'reset-peer':
- reset_peer(args.name, args.tunnel)
- elif args.action == "reset-profile":
- reset_profile(args.name, args.tunnel)
- elif args.action == "vpn-debug":
+
+ if args.action == "vpn-debug":
debug_peer(args.name, args.tunnel)
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/bindings.py b/src/services/api/graphql/bindings.py
index aa1ba0eb0..ef4966466 100644
--- a/src/services/api/graphql/bindings.py
+++ b/src/services/api/graphql/bindings.py
@@ -19,9 +19,6 @@ from . graphql.mutations import mutation
from . graphql.directives import directives_dict
from . graphql.errors import op_mode_error
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
@@ -29,10 +26,6 @@ from ariadne import make_executable_schema, load_schema_from_path, snake_case_fa
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()
diff --git a/src/services/api/graphql/generate/config_session_function.py b/src/services/api/graphql/generate/config_session_function.py
index fc0dd7a87..4ebb47a7e 100644
--- a/src/services/api/graphql/generate/config_session_function.py
+++ b/src/services/api/graphql/generate/config_session_function.py
@@ -8,8 +8,12 @@ def show_config(path: list[str], configFormat: typing.Optional[str]):
def show(path: list[str]):
pass
+def show_user_info(user: str):
+ pass
+
queries = {'show_config': show_config,
- 'show': show}
+ 'show': show,
+ 'show_user_info': show_user_info}
def save_config_file(fileName: typing.Optional[str]):
pass
@@ -24,5 +28,3 @@ mutations = {'save_config_file': save_config_file,
'load_config_file': load_config_file,
'add_system_image': add_system_image,
'delete_system_image': delete_system_image}
-
-
diff --git a/src/services/api/graphql/generate/generate_schema.py b/src/services/api/graphql/generate/generate_schema.py
new file mode 100755
index 000000000..dd5e7ea56
--- /dev/null
+++ b/src/services/api/graphql/generate/generate_schema.py
@@ -0,0 +1,26 @@
+#!/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 schema_from_op_mode import generate_op_mode_definitions
+from schema_from_config_session import generate_config_session_definitions
+from schema_from_composite import generate_composite_definitions
+
+if __name__ == '__main__':
+ generate_op_mode_definitions()
+ generate_config_session_definitions()
+ generate_composite_definitions()
diff --git a/src/services/api/graphql/generate/schema_from_composite.py b/src/services/api/graphql/generate/schema_from_composite.py
index 61a08cb2f..06e74032d 100755
--- a/src/services/api/graphql/generate/schema_from_composite.py
+++ b/src/services/api/graphql/generate/schema_from_composite.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
@@ -20,59 +20,31 @@
import os
import sys
-import json
-from inspect import signature, getmembers, isfunction, isclass, getmro
+from inspect import signature
from jinja2 import Template
from vyos.defaults import directories
if __package__ is None or __package__ == '':
- sys.path.append("/usr/libexec/vyos/services/api")
+ sys.path.append(os.path.join(directories['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']
+CLIENT_OP_PATH = directories['api_client_op']
-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_data: dict = {'schema_name': '',
'schema_fields': []}
query_template = """
-{%- if auth_type == 'key' %}
input {{ schema_name }}Input {
- key: String!
+ 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,29 +57,17 @@ type {{ schema_name }}Result {
}
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 {
+ key: String
{%- for field_entry in schema_fields %}
{{ field_entry }}
{%- endfor %}
}
-{%- endif %}
type {{ schema_name }} {
result: Generic
@@ -120,11 +80,31 @@ type {{ schema_name }}Result {
}
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 %}
+}
+"""
+
+op_query_template = """
+query {{ op_name }} ({{ op_sig }}) {
+ {{ op_name }} (data: { {{ op_arg }} }) {
+ success
+ errors
+ data {
+ result
+ }
+ }
+}
+"""
+
+op_mutation_template = """
+mutation {{ op_name }} ({{ op_sig }}) {
+ {{ op_name }} (data: { {{ op_arg }} }) {
+ success
+ errors
+ data {
+ result
+ }
+ }
}
"""
@@ -147,19 +127,52 @@ def create_schema(func_name: str, func: callable, template: str) -> str:
return res
+def create_client_op(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)
+
+ op_sig = ['$key: String']
+ op_arg = ['key: $key']
+ for k,v in field_dict.items():
+ op_sig.append('$'+k+': '+v)
+ op_arg.append(k+': $'+k)
+
+ op_data = {}
+ op_data['op_name'] = snake_to_pascal_case(func_name)
+ op_data['op_sig'] = ', '.join(op_sig)
+ op_data['op_arg'] = ', '.join(op_arg)
+
+ j2_template = Template(template)
+
+ res = j2_template.render(op_data)
+
+ return res
+
def generate_composite_definitions():
- results = []
+ schema = []
+ client_op = []
for name,func in queries.items():
res = create_schema(name, func, query_template)
- results.append(res)
+ schema.append(res)
+ res = create_client_op(name, func, op_query_template)
+ client_op.append(res)
for name,func in mutations.items():
res = create_schema(name, func, mutation_template)
- results.append(res)
+ schema.append(res)
+ res = create_client_op(name, func, op_mutation_template)
+ client_op.append(res)
- out = '\n'.join(results)
+ out = '\n'.join(schema)
with open(f'{SCHEMA_PATH}/composite.graphql', 'w') as f:
f.write(out)
+ out = '\n'.join(client_op)
+ with open(f'{CLIENT_OP_PATH}/composite.graphql', 'w') as f:
+ f.write(out)
+
if __name__ == '__main__':
generate_composite_definitions()
diff --git a/src/services/api/graphql/generate/schema_from_config_session.py b/src/services/api/graphql/generate/schema_from_config_session.py
index 49bf2440e..1d5ff1e53 100755
--- a/src/services/api/graphql/generate/schema_from_config_session.py
+++ b/src/services/api/graphql/generate/schema_from_config_session.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
@@ -20,59 +20,31 @@
import os
import sys
-import json
-from inspect import signature, getmembers, isfunction, isclass, getmro
+from inspect import signature
from jinja2 import Template
from vyos.defaults import directories
if __package__ is None or __package__ == '':
- sys.path.append("/usr/libexec/vyos/services/api")
+ sys.path.append(os.path.join(directories['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 .. 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']
+CLIENT_OP_PATH = directories['api_client_op']
-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_data: dict = {'schema_name': '',
'schema_fields': []}
query_template = """
-{%- if auth_type == 'key' %}
input {{ schema_name }}Input {
- key: String!
+ 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,29 +57,17 @@ 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 {
+ key: String
{%- for field_entry in schema_fields %}
{{ field_entry }}
{%- endfor %}
}
-{%- endif %}
type {{ schema_name }} {
result: Generic
@@ -120,11 +80,31 @@ 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 %}
+}
+"""
+
+op_query_template = """
+query {{ op_name }} ({{ op_sig }}) {
+ {{ op_name }} (data: { {{ op_arg }} }) {
+ success
+ errors
+ data {
+ result
+ }
+ }
+}
+"""
+
+op_mutation_template = """
+mutation {{ op_name }} ({{ op_sig }}) {
+ {{ op_name }} (data: { {{ op_arg }} }) {
+ success
+ errors
+ data {
+ result
+ }
+ }
}
"""
@@ -147,19 +127,52 @@ def create_schema(func_name: str, func: callable, template: str) -> str:
return res
+def create_client_op(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)
+
+ op_sig = ['$key: String']
+ op_arg = ['key: $key']
+ for k,v in field_dict.items():
+ op_sig.append('$'+k+': '+v)
+ op_arg.append(k+': $'+k)
+
+ op_data = {}
+ op_data['op_name'] = snake_to_pascal_case(func_name)
+ op_data['op_sig'] = ', '.join(op_sig)
+ op_data['op_arg'] = ', '.join(op_arg)
+
+ j2_template = Template(template)
+
+ res = j2_template.render(op_data)
+
+ return res
+
def generate_config_session_definitions():
- results = []
+ schema = []
+ client_op = []
for name,func in queries.items():
res = create_schema(name, func, query_template)
- results.append(res)
+ schema.append(res)
+ res = create_client_op(name, func, op_query_template)
+ client_op.append(res)
for name,func in mutations.items():
res = create_schema(name, func, mutation_template)
- results.append(res)
+ schema.append(res)
+ res = create_client_op(name, func, op_mutation_template)
+ client_op.append(res)
- out = '\n'.join(results)
+ out = '\n'.join(schema)
with open(f'{SCHEMA_PATH}/configsession.graphql', 'w') as f:
f.write(out)
+ out = '\n'.join(client_op)
+ with open(f'{CLIENT_OP_PATH}/configsession.graphql', 'w') as f:
+ f.write(out)
+
if __name__ == '__main__':
generate_config_session_definitions()
diff --git a/src/services/api/graphql/generate/schema_from_op_mode.py b/src/services/api/graphql/generate/schema_from_op_mode.py
index fc63b0100..229ccf90f 100755
--- a/src/services/api/graphql/generate/schema_from_op_mode.py
+++ b/src/services/api/graphql/generate/schema_from_op_mode.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
@@ -25,60 +25,36 @@ 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.opmode import _get_literal_values as get_literal_values
from vyos.util import load_as_module
if __package__ is None or __package__ == '':
- sys.path.append("/usr/libexec/vyos/services/api")
- from graphql.libs.op_mode import is_op_mode_function_name, is_show_function_name
+ sys.path.append(os.path.join(directories['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 .. libs.op_mode import is_op_mode_function_name, is_show_function_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']
+CLIENT_OP_PATH = directories['api_client_op']
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'
-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_data: dict = {'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 {
+ key: String
{%- for field_entry in schema_fields %}
{{ field_entry }}
{%- endfor %}
}
-{%- endif %}
type {{ schema_name }} {
result: Generic
@@ -92,29 +68,17 @@ 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 {
+ key: String
{%- for field_entry in schema_fields %}
{{ field_entry }}
{%- endfor %}
}
-{%- endif %}
type {{ schema_name }} {
result: Generic
@@ -128,11 +92,15 @@ 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 %}
+}
+"""
+
+enum_template = """
+enum {{ enum_name }} {
+ {%- for field_entry in enum_fields %}
+ {{ field_entry }}
+ {%- endfor %}
}
"""
@@ -151,12 +119,52 @@ type {{ name }} implements OpModeError {
{%- endfor %}
"""
-def create_schema(func_name: str, base_name: str, func: callable) -> str:
+op_query_template = """
+query {{ op_name }} ({{ op_sig }}) {
+ {{ op_name }} (data: { {{ op_arg }} }) {
+ success
+ errors
+ op_mode_error {
+ name
+ message
+ vyos_code
+ }
+ data {
+ result
+ }
+ }
+}
+"""
+
+op_mutation_template = """
+mutation {{ op_name }} ({{ op_sig }}) {
+ {{ op_name }} (data: { {{ op_arg }} }) {
+ success
+ errors
+ op_mode_error {
+ name
+ message
+ vyos_code
+ }
+ data {
+ result
+ }
+ }
+}
+"""
+
+def create_schema(func_name: str, base_name: str, func: callable,
+ enums: dict) -> str:
sig = signature(func)
+ for k in sig.parameters:
+ t = get_literal_values(sig.parameters[k].annotation)
+ if t:
+ enums[t] = snake_to_pascal_case(sig.parameters[k].name + '_' + base_name)
+
field_dict = {}
for k in sig.parameters:
- field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation)
+ field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation, enums)
# It is assumed that if one is generating a schema for a 'show_*'
# function, that 'get_raw_data' is present and 'raw' is desired.
@@ -179,6 +187,58 @@ def create_schema(func_name: str, base_name: str, func: callable) -> str:
return res
+def create_client_op(func_name: str, base_name: str, func: callable,
+ enums: dict) -> str:
+ sig = signature(func)
+
+ for k in sig.parameters:
+ t = get_literal_values(sig.parameters[k].annotation)
+ if t:
+ enums[t] = snake_to_pascal_case(sig.parameters[k].name + '_' + base_name)
+
+ field_dict = {}
+ for k in sig.parameters:
+ field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation, enums)
+
+ # It is assumed that if one is generating a schema for a 'show_*'
+ # function, that 'get_raw_data' is present and 'raw' is desired.
+ if 'raw' in list(field_dict):
+ del field_dict['raw']
+
+ op_sig = ['$key: String']
+ op_arg = ['key: $key']
+ for k,v in field_dict.items():
+ op_sig.append('$'+k+': '+v)
+ op_arg.append(k+': $'+k)
+
+ op_data = {}
+ op_data['op_name'] = snake_to_pascal_case(func_name + '_' + base_name)
+ op_data['op_sig'] = ', '.join(op_sig)
+ op_data['op_arg'] = ', '.join(op_arg)
+
+ if is_show_function_name(func_name):
+ j2_template = Template(op_query_template)
+ else:
+ j2_template = Template(op_mutation_template)
+
+ res = j2_template.render(op_data)
+
+ return res
+
+def create_enums(enums: dict) -> str:
+ enum_data = []
+ for k, v in enums.items():
+ enum = {'enum_name': v, 'enum_fields': list(k)}
+ enum_data.append(enum)
+
+ out = ''
+ j2_template = Template(enum_template)
+ for el in enum_data:
+ out += j2_template.render(el)
+ out += '\n'
+
+ return out
+
def create_error_schema():
from vyos import opmode
@@ -199,6 +259,8 @@ def create_error_schema():
return res
def generate_op_mode_definitions():
+ os.makedirs(CLIENT_OP_PATH, exist_ok=True)
+
out = create_error_schema()
with open(f'{SCHEMA_PATH}/{op_mode_error_schema}', 'w') as f:
f.write(out)
@@ -217,14 +279,23 @@ def generate_op_mode_definitions():
for (name, thunk) in funcs:
funcs_dict[name] = thunk
- results = []
+ schema = []
+ client_op = []
+ enums = {} # gather enums from function Literal type args
for name,func in funcs_dict.items():
- res = create_schema(name, basename, func)
- results.append(res)
+ res = create_schema(name, basename, func, enums)
+ schema.append(res)
+ res = create_client_op(name, basename, func, enums)
+ client_op.append(res)
- out = '\n'.join(results)
+ out = create_enums(enums)
+ out += '\n'.join(schema)
with open(f'{SCHEMA_PATH}/{basename}.graphql', 'w') as f:
f.write(out)
+ out = '\n'.join(client_op)
+ with open(f'{CLIENT_OP_PATH}/{basename}.graphql', 'w') as f:
+ f.write(out)
+
if __name__ == '__main__':
generate_op_mode_definitions()
diff --git a/src/services/api/graphql/graphql/auth_token_mutation.py b/src/services/api/graphql/graphql/auth_token_mutation.py
index 21ac40094..603a13758 100644
--- a/src/services/api/graphql/graphql/auth_token_mutation.py
+++ b/src/services/api/graphql/graphql/auth_token_mutation.py
@@ -20,6 +20,7 @@ from ariadne import ObjectType, UnionType
from graphql import GraphQLResolveInfo
from .. libs.token_auth import generate_token
+from .. session.session import get_user_info
from .. import state
auth_token_mutation = ObjectType("Mutation")
@@ -36,13 +37,24 @@ def auth_token_resolver(obj: Any, info: GraphQLResolveInfo, data: Dict):
datetime.timedelta(seconds=exp_interval))
res = generate_token(user, passwd, secret, expiration)
- if res:
+ try:
+ res |= get_user_info(user)
+ except ValueError:
+ # non-existent user already caught
+ pass
+ if 'token' in res:
data['result'] = res
return {
"success": True,
"data": data
}
+ if 'errors' in res:
+ return {
+ "success": False,
+ "errors": res['errors']
+ }
+
return {
"success": False,
"errors": ['token generation failed']
diff --git a/src/services/api/graphql/graphql/client_op/auth_token.graphql b/src/services/api/graphql/graphql/client_op/auth_token.graphql
new file mode 100644
index 000000000..5ea2ecc1c
--- /dev/null
+++ b/src/services/api/graphql/graphql/client_op/auth_token.graphql
@@ -0,0 +1,10 @@
+
+mutation AuthToken ($username: String!, $password: String!) {
+ AuthToken (data: { username: $username, password: $password }) {
+ success
+ errors
+ data {
+ result
+ }
+ }
+}
diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py
index 87ea59c43..8254e22b1 100644
--- a/src/services/api/graphql/graphql/mutations.py
+++ b/src/services/api/graphql/graphql/mutations.py
@@ -15,7 +15,7 @@
from importlib import import_module
from typing import Any, Dict, Optional
-from ariadne import ObjectType, convert_kwargs_to_snake_case, convert_camel_case_to_snake
+from ariadne import ObjectType, convert_camel_case_to_snake
from graphql import GraphQLResolveInfo
from makefun import with_signature
@@ -45,7 +45,6 @@ def make_mutation_resolver(mutation_name, class_name, session_func):
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:
diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py
index 1ad586428..daccc19b2 100644
--- a/src/services/api/graphql/graphql/queries.py
+++ b/src/services/api/graphql/graphql/queries.py
@@ -15,7 +15,7 @@
from importlib import import_module
from typing import Any, Dict, Optional
-from ariadne import ObjectType, convert_kwargs_to_snake_case, convert_camel_case_to_snake
+from ariadne import ObjectType, convert_camel_case_to_snake
from graphql import GraphQLResolveInfo
from makefun import with_signature
@@ -45,7 +45,6 @@ def make_query_resolver(query_name, class_name, session_func):
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:
diff --git a/src/services/api/graphql/libs/op_mode.py b/src/services/api/graphql/libs/op_mode.py
index 211f8ce19..e91d8bd0f 100644
--- a/src/services/api/graphql/libs/op_mode.py
+++ b/src/services/api/graphql/libs/op_mode.py
@@ -16,24 +16,19 @@
import os
import re
import typing
-import importlib.util
-from typing import Union
+from typing import Union, Tuple, Optional
from humps import decamelize
from vyos.defaults import directories
from vyos.util import load_as_module
from vyos.opmode import _normalize_field_names
+from vyos.opmode import _is_literal_type, _get_literal_values
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
@@ -78,7 +73,7 @@ def snake_to_pascal_case(name: str) -> str:
res = ''.join(map(str.title, name.split('_')))
return res
-def map_type_name(type_name: type, optional: bool = False) -> str:
+def map_type_name(type_name: type, enums: Optional[dict] = None, optional: bool = False) -> str:
if type_name == str:
return 'String!' if not optional else 'String = null'
if type_name == int:
@@ -87,12 +82,17 @@ def map_type_name(type_name: type, optional: bool = False) -> str:
return 'Boolean = false'
if typing.get_origin(type_name) == list:
if not optional:
- return f'[{map_type_name(typing.get_args(type_name)[0])}]!'
- return f'[{map_type_name(typing.get_args(type_name)[0])}]'
+ return f'[{map_type_name(typing.get_args(type_name)[0], enums=enums)}]!'
+ return f'[{map_type_name(typing.get_args(type_name)[0], enums=enums)}]'
+ if _is_literal_type(type_name):
+ mapped = enums.get(_get_literal_values(type_name), '')
+ if not mapped:
+ raise ValueError(typing.get_args(type_name))
+ return f'{mapped}!' if not optional else mapped
# typing.Optional is typing.Union[_, NoneType]
if (typing.get_origin(type_name) is typing.Union and
typing.get_args(type_name)[1] == type(None)):
- return f'{map_type_name(typing.get_args(type_name)[0], optional=True)}'
+ return f'{map_type_name(typing.get_args(type_name)[0], enums=enums, optional=True)}'
# scalar 'Generic' is defined in schema.graphql
return 'Generic'
diff --git a/src/services/api/graphql/libs/token_auth.py b/src/services/api/graphql/libs/token_auth.py
index 2100eba7f..8585485c9 100644
--- a/src/services/api/graphql/libs/token_auth.py
+++ b/src/services/api/graphql/libs/token_auth.py
@@ -29,14 +29,13 @@ def generate_token(user: str, passwd: str, secret: str, exp: int) -> dict:
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']
- }
+ return {"errors": ['missing secret']}
token = jwt.encode(payload=payload_data, key=secret, algorithm="HS256")
users |= {user_id: user}
return {'token': token}
+ else:
+ return {"errors": ['failed pam authentication']}
def get_user_context(request):
context = {}
diff --git a/src/services/api/graphql/session/errors/op_mode_errors.py b/src/services/api/graphql/session/errors/op_mode_errors.py
index 7bc1d1d81..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,15 +1,17 @@
-
-
op_mode_err_msg = {
"UnconfiguredSubsystem": "subsystem is not configured or not running",
"DataUnavailable": "data currently unavailable",
"PermissionDenied": "client does not have permission",
- "IncorrectValue": "argument value is incorrect"
+ "InsufficientResources": "insufficient system resources",
+ "IncorrectValue": "argument value is incorrect",
+ "UnsupportedOperation": "operation is not supported (yet)",
}
op_mode_err_code = {
"UnconfiguredSubsystem": 2000,
"DataUnavailable": 2001,
+ "InsufficientResources": 2002,
"PermissionDenied": 1003,
- "IncorrectValue": 1002
+ "IncorrectValue": 1002,
+ "UnsupportedOperation": 1004,
}
diff --git a/src/services/api/graphql/session/session.py b/src/services/api/graphql/session/session.py
index 0b77b1433..3c5a062b6 100644
--- a/src/services/api/graphql/session/session.py
+++ b/src/services/api/graphql/session/session.py
@@ -29,6 +29,28 @@ from api.graphql.libs.op_mode import normalize_output
op_mode_include_file = os.path.join(directories['data'], 'op-mode-standardized.json')
+def get_config_dict(path=[], effective=False, key_mangling=None,
+ get_first_key=False, no_multi_convert=False,
+ no_tag_node_value_mangle=False):
+ config = Config()
+ return config.get_config_dict(path=path, effective=effective,
+ key_mangling=key_mangling,
+ get_first_key=get_first_key,
+ no_multi_convert=no_multi_convert,
+ no_tag_node_value_mangle=no_tag_node_value_mangle)
+
+def get_user_info(user):
+ user_info = {}
+ info = get_config_dict(['system', 'login', 'user', user],
+ get_first_key=True)
+ if not info:
+ raise ValueError("No such user")
+
+ user_info['user'] = user
+ user_info['full_name'] = info.get('full-name', '')
+
+ return user_info
+
class Session:
"""
Wrapper for calling configsession functions based on GraphQL requests.
@@ -116,6 +138,19 @@ class Session:
return res
+ def show_user_info(self):
+ session = self._session
+ data = self._data
+
+ user_info = {}
+ user = data['user']
+ try:
+ user_info = get_user_info(user)
+ except Exception as error:
+ raise error
+
+ return user_info
+
def system_status(self):
import api.graphql.session.composite.system_status as system_status
diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd
index a380f2e66..894f9e24d 100755
--- a/src/services/vyos-hostsd
+++ b/src/services/vyos-hostsd
@@ -329,7 +329,7 @@ tag_regex_schema = op_type_schema.extend({
forward_zone_add_schema = op_type_schema.extend({
'data': {
str: {
- 'server': [str],
+ 'name_server': [str],
'addnta': Any({}, None),
'recursion_desired': Any({}, None),
}
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index 60ea9a5ee..acaa383b4 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]
@@ -270,7 +283,7 @@ class MultipartRequest(Request):
return self._headers
async def form(self) -> FormData:
- if not hasattr(self, "_form"):
+ if self._form is None:
assert (
parse_options_header is not None
), "The `python-multipart` library must be installed to use form parsing."
@@ -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\"")
@@ -412,7 +425,7 @@ async def validation_exception_handler(request, exc):
return error(400, str(exc.errors()[0]))
@app.post('/configure')
-def configure_op(data: Union[ConfigureModel, ConfigureListModel]):
+async def configure_op(data: Union[ConfigureModel, ConfigureListModel]):
session = app.state.vyos_session
env = session.get_session_env()
config = vyos.config.Config(session_env=env)
@@ -481,7 +494,7 @@ def configure_op(data: Union[ConfigureModel, ConfigureListModel]):
return success(None)
@app.post("/retrieve")
-def retrieve_op(data: RetrieveModel):
+async def retrieve_op(data: RetrieveModel):
session = app.state.vyos_session
env = session.get_session_env()
config = vyos.config.Config(session_env=env)
@@ -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
diff --git a/src/tests/test_config_diff.py b/src/tests/test_config_diff.py
new file mode 100644
index 000000000..f61cbc4a2
--- /dev/null
+++ b/src/tests/test_config_diff.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/>.
+
+import os
+import vyos.configtree
+
+from unittest import TestCase
+
+class TestConfigDiff(TestCase):
+ def setUp(self):
+ with open('tests/data/config.left', 'r') as f:
+ config_string = f.read()
+ self.config_left = vyos.configtree.ConfigTree(config_string)
+
+ with open('tests/data/config.right', 'r') as f:
+ config_string = f.read()
+ self.config_right = vyos.configtree.ConfigTree(config_string)
+
+ self.config_null = vyos.configtree.ConfigTree('')
+
+ def test_unit(self):
+ diff = vyos.configtree.DiffTree(self.config_left, self.config_null)
+ sub = diff.sub
+ self.assertEqual(sub.to_string(), self.config_left.to_string())
+
+ diff = vyos.configtree.DiffTree(self.config_null, self.config_left)
+ add = diff.add
+ self.assertEqual(add.to_string(), self.config_left.to_string())
+
+ def test_symmetry(self):
+ lr_diff = vyos.configtree.DiffTree(self.config_left,
+ self.config_right)
+ rl_diff = vyos.configtree.DiffTree(self.config_right,
+ self.config_left)
+
+ sub = lr_diff.sub
+ add = rl_diff.add
+ self.assertEqual(sub.to_string(), add.to_string())
+ add = lr_diff.add
+ sub = rl_diff.sub
+ self.assertEqual(add.to_string(), sub.to_string())
+
+ def test_identity(self):
+ lr_diff = vyos.configtree.DiffTree(self.config_left,
+ self.config_right)
+
+ sub = lr_diff.sub
+ inter = lr_diff.inter
+ add = lr_diff.add
+
+ r_union = vyos.configtree.union(add, inter)
+ l_union = vyos.configtree.union(sub, inter)
+
+ self.assertEqual(r_union.to_string(),
+ self.config_right.to_string(ordered_values=True))
+ self.assertEqual(l_union.to_string(),
+ self.config_left.to_string(ordered_values=True))
diff --git a/src/tests/test_config_parser.py b/src/tests/test_config_parser.py
index 6e0a071f8..8148aa79b 100644
--- a/src/tests/test_config_parser.py
+++ b/src/tests/test_config_parser.py
@@ -34,8 +34,8 @@ class TestConfigParser(TestCase):
def test_top_level_tag(self):
self.assertTrue(self.config.exists(["top-level-tag-node"]))
- # No sorting is intentional, child order must be preserved
- self.assertEqual(self.config.list_nodes(["top-level-tag-node"]), ["foo", "bar"])
+ # Sorting is now intentional, during parsing of config
+ self.assertEqual(self.config.list_nodes(["top-level-tag-node"]), ["bar", "foo"])
def test_copy(self):
self.config.copy(["top-level-tag-node", "bar"], ["top-level-tag-node", "baz"])
diff --git a/src/tests/test_configverify.py b/src/tests/test_configverify.py
index ad7e053db..6fb43ece2 100644
--- a/src/tests/test_configverify.py
+++ b/src/tests/test_configverify.py
@@ -27,11 +27,6 @@ class TestDictSearch(TestCase):
def test_dh_key_none(self):
self.assertFalse(verify_diffie_hellman_length('/tmp/non_existing_file', '1024'))
- def test_dh_key_256(self):
- key_len = '256'
- cmd(f'openssl dhparam -out {dh_file} {key_len}')
- self.assertTrue(verify_diffie_hellman_length(dh_file, key_len))
-
def test_dh_key_512(self):
key_len = '512'
cmd(f'openssl dhparam -out {dh_file} {key_len}')
diff --git a/src/validators/timezone b/src/validators/timezone
index baf5abca2..107571181 100755
--- a/src/validators/timezone
+++ b/src/validators/timezone
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019 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
@@ -25,7 +25,7 @@ if __name__ == '__main__':
parser.add_argument("--validate", action="store", required=True, help="Check if timezone is valid")
args = parser.parse_args()
- tz_data = cmd('find /usr/share/zoneinfo/posix -type f -or -type l | sed -e s:/usr/share/zoneinfo/posix/::')
+ tz_data = cmd('timedatectl list-timezones')
tz_data = tz_data.split('\n')
if args.validate not in tz_data:
diff --git a/src/xdp/common/common_libbpf.c b/src/xdp/common/common_libbpf.c
index 5788ecd9e..443ca4c66 100644
--- a/src/xdp/common/common_libbpf.c
+++ b/src/xdp/common/common_libbpf.c
@@ -24,10 +24,6 @@ static inline bool IS_ERR_OR_NULL(const void *ptr)
int bpf_prog_load_xattr_maps(const struct bpf_prog_load_attr_maps *attr,
struct bpf_object **pobj, int *prog_fd)
{
- struct bpf_object_open_attr open_attr = {
- .file = attr->file,
- .prog_type = attr->prog_type,
- };
struct bpf_program *prog, *first_prog = NULL;
enum bpf_attach_type expected_attach_type;
enum bpf_prog_type prog_type;
@@ -41,10 +37,13 @@ int bpf_prog_load_xattr_maps(const struct bpf_prog_load_attr_maps *attr,
if (!attr->file)
return -EINVAL;
+ obj = bpf_object__open_file(attr->file, NULL);
- obj = bpf_object__open_xattr(&open_attr);
- if (IS_ERR_OR_NULL(obj))
- return -ENOENT;
+ if (libbpf_get_error(obj))
+ return -EINVAL;
+
+ prog = bpf_object__next_program(obj, NULL);
+ bpf_program__set_type(prog, attr->prog_type);
bpf_object__for_each_program(prog, obj) {
/*
@@ -82,7 +81,7 @@ int bpf_prog_load_xattr_maps(const struct bpf_prog_load_attr_maps *attr,
bpf_map__for_each(map, obj) {
const char* mapname = bpf_map__name(map);
- if (!bpf_map__is_offload_neutral(map))
+ if (bpf_map__type(map) != BPF_MAP_TYPE_PERF_EVENT_ARRAY)
bpf_map__set_ifindex(map, attr->ifindex);
/* Was: map->map_ifindex = attr->ifindex; */
diff --git a/src/xdp/common/common_user_bpf_xdp.c b/src/xdp/common/common_user_bpf_xdp.c
index faf7f4f91..524f08c9d 100644
--- a/src/xdp/common/common_user_bpf_xdp.c
+++ b/src/xdp/common/common_user_bpf_xdp.c
@@ -21,7 +21,7 @@ int xdp_link_attach(int ifindex, __u32 xdp_flags, int prog_fd)
int err;
/* libbpf provide the XDP net_device link-level hook attach helper */
- err = bpf_set_link_xdp_fd(ifindex, prog_fd, xdp_flags);
+ err = bpf_xdp_attach(ifindex, prog_fd, xdp_flags, NULL);
if (err == -EEXIST && !(xdp_flags & XDP_FLAGS_UPDATE_IF_NOEXIST)) {
/* Force mode didn't work, probably because a program of the
* opposite type is loaded. Let's unload that and try loading
@@ -32,9 +32,9 @@ int xdp_link_attach(int ifindex, __u32 xdp_flags, int prog_fd)
xdp_flags &= ~XDP_FLAGS_MODES;
xdp_flags |= (old_flags & XDP_FLAGS_SKB_MODE) ? XDP_FLAGS_DRV_MODE : XDP_FLAGS_SKB_MODE;
- err = bpf_set_link_xdp_fd(ifindex, -1, xdp_flags);
+ err = bpf_xdp_detach(ifindex, xdp_flags, NULL);
if (!err)
- err = bpf_set_link_xdp_fd(ifindex, prog_fd, old_flags);
+ err = bpf_xdp_attach(ifindex, prog_fd, old_flags, NULL);
}
if (err < 0) {
fprintf(stderr, "ERR: "
@@ -65,7 +65,7 @@ int xdp_link_detach(int ifindex, __u32 xdp_flags, __u32 expected_prog_id)
__u32 curr_prog_id;
int err;
- err = bpf_get_link_xdp_id(ifindex, &curr_prog_id, xdp_flags);
+ err = bpf_xdp_query_id(ifindex, xdp_flags, &curr_prog_id);
if (err) {
fprintf(stderr, "ERR: get link xdp id failed (err=%d): %s\n",
-err, strerror(-err));
@@ -86,7 +86,7 @@ int xdp_link_detach(int ifindex, __u32 xdp_flags, __u32 expected_prog_id)
return EXIT_FAIL;
}
- if ((err = bpf_set_link_xdp_fd(ifindex, -1, xdp_flags)) < 0) {
+ if ((err = bpf_xdp_detach(ifindex, xdp_flags, NULL)) < 0) {
fprintf(stderr, "ERR: %s() link set xdp failed (err=%d): %s\n",
__func__, err, strerror(-err));
return EXIT_FAIL_XDP;
@@ -109,22 +109,28 @@ struct bpf_object *load_bpf_object_file(const char *filename, int ifindex)
* hardware offloading XDP programs (note this sets libbpf
* bpf_program->prog_ifindex and foreach bpf_map->map_ifindex).
*/
- struct bpf_prog_load_attr prog_load_attr = {
- .prog_type = BPF_PROG_TYPE_XDP,
- .ifindex = ifindex,
- };
- prog_load_attr.file = filename;
+ struct bpf_program *prog;
+ obj = bpf_object__open_file(filename, NULL);
+
+ if (libbpf_get_error(obj))
+ return NULL;
+
+ prog = bpf_object__next_program(obj, NULL);
+ bpf_program__set_type(prog, BPF_PROG_TYPE_XDP);
+ bpf_program__set_ifindex(prog, ifindex);
/* Use libbpf for extracting BPF byte-code from BPF-ELF object, and
* loading this into the kernel via bpf-syscall
*/
- err = bpf_prog_load_xattr(&prog_load_attr, &obj, &first_prog_fd);
+ err = bpf_object__load(obj);
if (err) {
fprintf(stderr, "ERR: loading BPF-OBJ file(%s) (%d): %s\n",
filename, err, strerror(-err));
return NULL;
}
+ first_prog_fd = bpf_program__fd(prog);
+
/* Notice how a pointer to a libbpf bpf_object is returned */
return obj;
}
@@ -136,12 +142,15 @@ static struct bpf_object *open_bpf_object(const char *file, int ifindex)
struct bpf_map *map;
struct bpf_program *prog, *first_prog = NULL;
- struct bpf_object_open_attr open_attr = {
- .file = file,
- .prog_type = BPF_PROG_TYPE_XDP,
- };
+ obj = bpf_object__open_file(file, NULL);
- obj = bpf_object__open_xattr(&open_attr);
+ if (libbpf_get_error(obj))
+ return NULL;
+
+ prog = bpf_object__next_program(obj, NULL);
+ bpf_program__set_type(prog, BPF_PROG_TYPE_XDP);
+
+ err = bpf_object__load(obj);
if (IS_ERR_OR_NULL(obj)) {
err = -PTR_ERR(obj);
fprintf(stderr, "ERR: opening BPF-OBJ file(%s) (%d): %s\n",
@@ -157,7 +166,7 @@ static struct bpf_object *open_bpf_object(const char *file, int ifindex)
}
bpf_object__for_each_map(map, obj) {
- if (!bpf_map__is_offload_neutral(map))
+ if (bpf_map__type(map) != BPF_MAP_TYPE_PERF_EVENT_ARRAY)
bpf_map__set_ifindex(map, ifindex);
}
@@ -264,10 +273,10 @@ struct bpf_object *load_bpf_and_xdp_attach(struct config *cfg)
if (cfg->progsec[0])
/* Find a matching BPF prog section name */
- bpf_prog = bpf_object__find_program_by_title(bpf_obj, cfg->progsec);
+ bpf_prog = bpf_object__find_program_by_name(bpf_obj, cfg->progsec);
else
/* Find the first program */
- bpf_prog = bpf_program__next(NULL, bpf_obj);
+ bpf_prog = bpf_object__next_program(bpf_obj, NULL);
if (!bpf_prog) {
fprintf(stderr, "ERR: couldn't find a program in ELF section '%s'\n", cfg->progsec);
diff --git a/src/xdp/common/xdp_stats_kern.h b/src/xdp/common/xdp_stats_kern.h
index 4e08551a0..c061a149d 100644
--- a/src/xdp/common/xdp_stats_kern.h
+++ b/src/xdp/common/xdp_stats_kern.h
@@ -13,12 +13,12 @@
#endif
/* Keeps stats per (enum) xdp_action */
-struct bpf_map_def SEC("maps") xdp_stats_map = {
- .type = BPF_MAP_TYPE_PERCPU_ARRAY,
- .key_size = sizeof(__u32),
- .value_size = sizeof(struct datarec),
- .max_entries = XDP_ACTION_MAX,
-};
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+ __type(key, __u32);
+ __type(value, struct datarec);
+ __uint(max_entries, XDP_ACTION_MAX);
+} xdp_stats_map SEC(".maps");
static __always_inline
__u32 xdp_stats_record_action(struct xdp_md *ctx, __u32 action)
diff --git a/src/xdp/xdp_prog_kern.c b/src/xdp/xdp_prog_kern.c
index a1eb395af..59308325d 100644
--- a/src/xdp/xdp_prog_kern.c
+++ b/src/xdp/xdp_prog_kern.c
@@ -16,19 +16,19 @@
#define memcpy(dest, src, n) __builtin_memcpy((dest), (src), (n))
#endif
-struct bpf_map_def SEC("maps") tx_port = {
- .type = BPF_MAP_TYPE_DEVMAP,
- .key_size = sizeof(int),
- .value_size = sizeof(int),
- .max_entries = 256,
-};
-
-struct bpf_map_def SEC("maps") redirect_params = {
- .type = BPF_MAP_TYPE_HASH,
- .key_size = ETH_ALEN,
- .value_size = ETH_ALEN,
- .max_entries = 1,
-};
+struct {
+ __uint(type, BPF_MAP_TYPE_DEVMAP);
+ __type(key, int);
+ __type(value, int);
+ __uint(max_entries, 256);
+} tx_port SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __type(key, ETH_ALEN);
+ __type(value, ETH_ALEN);
+ __uint(max_entries, 1);
+} redirect_params SEC(".maps");
static __always_inline __u16 csum_fold_helper(__u32 csum)
{
@@ -208,8 +208,12 @@ out:
return xdp_stats_record_action(ctx, action);
}
+#ifndef AF_INET
#define AF_INET 2
+#endif
+#ifndef AF_INET6
#define AF_INET6 10
+#endif
#define IPV6_FLOWINFO_MASK bpf_htonl(0x0FFFFFFF)
/* from include/net/ip.h */
diff --git a/tests/data/config.left b/tests/data/config.left
new file mode 100644
index 000000000..e57c40396
--- /dev/null
+++ b/tests/data/config.left
@@ -0,0 +1,36 @@
+node1 {
+ tag_node foo {
+ valueless
+ multi_node 'v2'
+ multi_node 'v1'
+ single 'left_val'
+ }
+ tag_node bar {
+ node {
+ single 'v0'
+ }
+ }
+ tag_node other {
+ leaf 'leaf_l'
+ }
+}
+
+node3 {
+}
+
+node2 {
+ sub_node_other {
+ single 'val'
+ }
+ sub_node {
+ tag_node other {
+ single 'val'
+ }
+ tag_node bob {
+ valued 'baz'
+ }
+ tag_node duff {
+ valued 'buz'
+ }
+ }
+}
diff --git a/tests/data/config.right b/tests/data/config.right
new file mode 100644
index 000000000..48defeb89
--- /dev/null
+++ b/tests/data/config.right
@@ -0,0 +1,25 @@
+node1 {
+ tag_node baz {
+ other_node {
+ multi_node 'some_val'
+ multe_node 'other_val'
+ }
+ }
+ tag_node foo {
+ valueless
+ multi_node 'v3'
+ multi_node 'v1'
+ single 'right_val'
+ }
+ tag_node other {
+ leaf 'leaf_r'
+ }
+}
+
+node2 {
+ sub_node {
+ tag_node other {
+ multi 'mv'
+ }
+ }
+}