From 4ef110fd2c501b718344c72d495ad7e16d2bd465 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sat, 30 Dec 2023 23:25:20 +0100 Subject: T5474: establish common file name pattern for XML conf mode commands We will use _ as CLI level divider. The XML definition filename and also the Python helper should match the CLI node. Example: set interfaces ethernet -> interfaces_ethernet.xml.in set interfaces bond -> interfaces_bond.xml.in set service dhcp-server -> service_dhcp-server-xml.in --- data/config-mode-dependencies/vyos-1x.json | 64 +- data/configd-include.json | 123 +- data/templates/bcast-relay/udp-broadcast-relay.j2 | 2 +- data/templates/chrony/chrony.conf.j2 | 2 +- data/templates/conntrack/sysctl.conf.j2 | 2 +- data/templates/conntrack/vyos_nf_conntrack.conf.j2 | 2 +- data/templates/conntrackd/conntrackd.conf.j2 | 3 +- data/templates/dhcp-relay/dhcrelay.conf.j2 | 4 +- data/templates/dhcp-relay/dhcrelay6.conf.j2 | 3 +- data/templates/dns-dynamic/ddclient.conf.j2 | 2 +- data/templates/dns-forwarding/recursor.conf.j2 | 3 +- data/templates/dns-forwarding/recursor.conf.lua.j2 | 3 +- .../templates/dns-forwarding/recursor.zone.conf.j2 | 2 +- data/templates/ethernet/wpa_supplicant.conf.j2 | 3 +- data/templates/https/nginx.default.j2 | 2 +- data/templates/igmp-proxy/igmpproxy.conf.j2 | 2 +- data/templates/lldp/lldpd.j2 | 2 +- data/templates/lldp/vyos.conf.j2 | 2 +- data/templates/load-balancing/haproxy.cfg.j2 | 2 +- data/templates/load-balancing/wlb.conf.j2 | 4 +- data/templates/login/authorized_keys.j2 | 3 +- data/templates/login/limits.j2 | 2 +- data/templates/login/nsswitch.conf.j2 | 3 +- data/templates/login/pam_radius_auth.conf.j2 | 2 +- data/templates/login/tacplus_servers.j2 | 3 +- data/templates/macsec/wpa_supplicant.conf.j2 | 2 +- data/templates/mdns-repeater/avahi-daemon.conf.j2 | 2 +- data/templates/openvpn/auth.pw.j2 | 2 +- data/templates/openvpn/client.conf.j2 | 2 +- data/templates/openvpn/server.conf.j2 | 2 +- data/templates/pppoe/peer.j2 | 2 +- data/templates/rsyslog/logrotate.j2 | 2 +- data/templates/rsyslog/rsyslog.conf.j2 | 2 +- data/templates/salt-minion/minion.j2 | 2 +- data/templates/snmp/etc.snmp.conf.j2 | 2 +- data/templates/snmp/etc.snmpd.conf.j2 | 2 +- data/templates/snmp/override.conf.j2 | 1 - data/templates/snmp/usr.snmpd.conf.j2 | 2 +- data/templates/snmp/var.snmpd.conf.j2 | 2 +- data/templates/ssh/sshd_config.j2 | 2 +- data/templates/ssh/sshguard_config.j2 | 2 +- data/templates/ssh/sshguard_whitelist.j2 | 2 +- data/templates/sstp-client/peer.j2 | 2 +- data/templates/system/proxy.j2 | 2 +- data/templates/tftp-server/default.j2 | 2 +- data/templates/wifi/hostapd.conf.j2 | 2 +- data/templates/wifi/wpa_supplicant.conf.j2 | 2 +- interface-definitions/bcast-relay.xml.in | 46 - interface-definitions/cron.xml.in | 72 -- interface-definitions/dhcp-relay.xml.in | 126 --- interface-definitions/dhcp-server.xml.in | 456 -------- interface-definitions/dhcpv6-relay.xml.in | 82 -- interface-definitions/dhcpv6-server.xml.in | 375 ------ interface-definitions/dns-domain-name.xml.in | 107 -- interface-definitions/dns-dynamic.xml.in | 213 ---- interface-definitions/dns-forwarding.xml.in | 703 ------------ interface-definitions/flow-accounting-conf.xml.in | 437 ------- interface-definitions/https.xml.in | 220 ---- interface-definitions/igmp-proxy.xml.in | 97 -- interface-definitions/interfaces-bonding.xml.in | 286 ----- interface-definitions/interfaces-bridge.xml.in | 226 ---- interface-definitions/interfaces-dummy.xml.in | 57 - interface-definitions/interfaces-ethernet.xml.in | 217 ---- interface-definitions/interfaces-geneve.xml.in | 60 - interface-definitions/interfaces-input.xml.in | 27 - interface-definitions/interfaces-l2tpv3.xml.in | 131 --- interface-definitions/interfaces-loopback.xml.in | 35 - interface-definitions/interfaces-macsec.xml.in | 153 --- interface-definitions/interfaces-openvpn.xml.in | 809 ------------- interface-definitions/interfaces-pppoe.xml.in | 153 --- .../interfaces-pseudo-ethernet.xml.in | 68 -- interface-definitions/interfaces-sstpc.xml.in | 47 - interface-definitions/interfaces-tunnel.xml.in | 281 ----- .../interfaces-virtual-ethernet.xml.in | 48 - interface-definitions/interfaces-vti.xml.in | 32 - interface-definitions/interfaces-vxlan.xml.in | 133 --- interface-definitions/interfaces-wireguard.xml.in | 129 --- interface-definitions/interfaces-wireless.xml.in | 832 -------------- interface-definitions/interfaces-wwan.xml.in | 48 - interface-definitions/interfaces_bonding.xml.in | 286 +++++ interface-definitions/interfaces_bridge.xml.in | 226 ++++ interface-definitions/interfaces_dummy.xml.in | 57 + interface-definitions/interfaces_ethernet.xml.in | 217 ++++ interface-definitions/interfaces_geneve.xml.in | 60 + interface-definitions/interfaces_input.xml.in | 27 + interface-definitions/interfaces_l2tpv3.xml.in | 131 +++ interface-definitions/interfaces_loopback.xml.in | 35 + interface-definitions/interfaces_macsec.xml.in | 153 +++ interface-definitions/interfaces_openvpn.xml.in | 809 +++++++++++++ interface-definitions/interfaces_pppoe.xml.in | 153 +++ .../interfaces_pseudo-ethernet.xml.in | 68 ++ interface-definitions/interfaces_sstpc.xml.in | 47 + interface-definitions/interfaces_tunnel.xml.in | 281 +++++ .../interfaces_virtual-ethernet.xml.in | 48 + interface-definitions/interfaces_vti.xml.in | 32 + interface-definitions/interfaces_vxlan.xml.in | 133 +++ interface-definitions/interfaces_wireguard.xml.in | 129 +++ interface-definitions/interfaces_wireless.xml.in | 832 ++++++++++++++ interface-definitions/interfaces_wwan.xml.in | 48 + interface-definitions/lldp.xml.in | 188 --- .../load-balancing-haproxy.xml.in | 254 ----- interface-definitions/load-balancing-wan.xml.in | 399 ------- .../load-balancing_reverse-proxy.xml.in | 254 +++++ interface-definitions/load-balancing_wan.xml.in | 399 +++++++ interface-definitions/ntp.xml.in | 67 -- interface-definitions/policy-local-route.xml.in | 156 --- interface-definitions/policy-route.xml.in | 117 -- interface-definitions/policy_local-route.xml.in | 156 +++ interface-definitions/policy_route.xml.in | 117 ++ interface-definitions/protocols-babel.xml.in | 254 ----- interface-definitions/protocols-bfd.xml.in | 85 -- interface-definitions/protocols-bgp.xml.in | 16 - interface-definitions/protocols-eigrp.xml.in | 17 - interface-definitions/protocols-failover.xml.in | 135 --- interface-definitions/protocols-isis.xml.in | 16 - interface-definitions/protocols-mpls.xml.in | 560 --------- interface-definitions/protocols-multicast.xml.in | 94 -- interface-definitions/protocols-nhrp.xml.in | 138 --- interface-definitions/protocols-ospf.xml.in | 16 - interface-definitions/protocols-ospfv3.xml.in | 16 - interface-definitions/protocols-pim.xml.in | 210 ---- interface-definitions/protocols-pim6.xml.in | 179 --- interface-definitions/protocols-rip.xml.in | 258 ----- interface-definitions/protocols-ripng.xml.in | 155 --- interface-definitions/protocols-rpki.xml.in | 95 -- .../protocols-segment-routing.xml.in | 137 --- interface-definitions/protocols-static-arp.xml.in | 51 - interface-definitions/protocols-static.xml.in | 44 - interface-definitions/protocols_babel.xml.in | 254 +++++ interface-definitions/protocols_bfd.xml.in | 85 ++ interface-definitions/protocols_bgp.xml.in | 16 + interface-definitions/protocols_eigrp.xml.in | 17 + interface-definitions/protocols_failover.xml.in | 135 +++ interface-definitions/protocols_igmp-proxy.xml.in | 97 ++ interface-definitions/protocols_isis.xml.in | 16 + interface-definitions/protocols_mpls.xml.in | 560 +++++++++ interface-definitions/protocols_nhrp.xml.in | 138 +++ interface-definitions/protocols_ospf.xml.in | 16 + interface-definitions/protocols_ospfv3.xml.in | 16 + interface-definitions/protocols_pim.xml.in | 210 ++++ interface-definitions/protocols_pim6.xml.in | 179 +++ interface-definitions/protocols_rip.xml.in | 258 +++++ interface-definitions/protocols_ripng.xml.in | 155 +++ interface-definitions/protocols_rpki.xml.in | 95 ++ .../protocols_segment-routing.xml.in | 137 +++ interface-definitions/protocols_static.xml.in | 44 + interface-definitions/protocols_static_arp.xml.in | 51 + .../protocols_static_multicast.xml.in | 94 ++ interface-definitions/salt-minion.xml.in | 74 -- interface-definitions/service-aws-glb.xml.in | 127 --- interface-definitions/service-config-sync.xml.in | 104 -- .../service-conntrack-sync.xml.in | 173 --- .../service-console-server.xml.in | 100 -- interface-definitions/service-event-handler.xml.in | 70 -- .../service-ids-ddos-protection.xml.in | 167 --- interface-definitions/service-ipoe-server.xml.in | 190 ---- interface-definitions/service-mdns-repeater.xml.in | 82 -- .../service-monitoring-telegraf.xml.in | 284 ----- .../service-monitoring-zabbix-agent.xml.in | 193 ---- interface-definitions/service-pppoe-server.xml.in | 281 ----- interface-definitions/service-router-advert.xml.in | 369 ------ interface-definitions/service-sla.xml.in | 36 - interface-definitions/service-upnp.xml.in | 228 ---- interface-definitions/service-webproxy.xml.in | 654 ----------- interface-definitions/service_aws_glb.xml.in | 127 +++ .../service_broadcast-relay.xml.in | 46 + interface-definitions/service_config-sync.xml.in | 104 ++ .../service_conntrack-sync.xml.in | 173 +++ .../service_console-server.xml.in | 100 ++ interface-definitions/service_dhcp-relay.xml.in | 126 +++ interface-definitions/service_dhcp-server.xml.in | 456 ++++++++ interface-definitions/service_dhcpv6-relay.xml.in | 82 ++ interface-definitions/service_dhcpv6-server.xml.in | 375 ++++++ interface-definitions/service_dns_dynamic.xml.in | 213 ++++ .../service_dns_forwarding.xml.in | 703 ++++++++++++ interface-definitions/service_event-handler.xml.in | 70 ++ interface-definitions/service_https.xml.in | 220 ++++ .../service_ids_ddos-protection.xml.in | 167 +++ interface-definitions/service_ipoe-server.xml.in | 190 ++++ interface-definitions/service_lldp.xml.in | 188 +++ interface-definitions/service_mdns_repeater.xml.in | 82 ++ .../service_monitoring_telegraf.xml.in | 284 +++++ .../service_monitoring_zabbix-agent.xml.in | 193 ++++ interface-definitions/service_ntp.xml.in | 67 ++ interface-definitions/service_pppoe-server.xml.in | 281 +++++ interface-definitions/service_router-advert.xml.in | 369 ++++++ interface-definitions/service_salt-minion.xml.in | 74 ++ interface-definitions/service_sla.xml.in | 36 + interface-definitions/service_snmp.xml.in | 598 ++++++++++ interface-definitions/service_ssh.xml.in | 270 +++++ interface-definitions/service_tftp-server.xml.in | 32 + interface-definitions/service_upnp.xml.in | 228 ++++ interface-definitions/service_webproxy.xml.in | 654 +++++++++++ interface-definitions/snmp.xml.in | 598 ---------- interface-definitions/ssh.xml.in | 270 ----- .../system-acceleration-qat.xml.in | 21 - interface-definitions/system-config-mgmt.xml.in | 82 -- interface-definitions/system-conntrack.xml.in | 513 --------- interface-definitions/system-console.xml.in | 91 -- interface-definitions/system-frr.xml.in | 91 -- interface-definitions/system-ip.xml.in | 114 -- interface-definitions/system-ipv6.xml.in | 50 - interface-definitions/system-lcd.xml.in | 70 -- interface-definitions/system-login-banner.xml.in | 32 - interface-definitions/system-login.xml.in | 302 ----- interface-definitions/system-logs.xml.in | 92 -- interface-definitions/system-option.xml.in | 171 --- interface-definitions/system-proxy.xml.in | 25 - interface-definitions/system-sflow.xml.in | 113 -- interface-definitions/system-sysctl.xml.in | 40 - interface-definitions/system-syslog.xml.in | 155 --- interface-definitions/system-time-zone.xml.in | 19 - interface-definitions/system-update-check.xml.in | 22 - interface-definitions/system_acceleration.xml.in | 21 + .../system_config-management.xml.in | 82 ++ interface-definitions/system_conntrack.xml.in | 513 +++++++++ interface-definitions/system_console.xml.in | 91 ++ interface-definitions/system_domain-name.xml.in | 15 + interface-definitions/system_domain-search.xml.in | 18 + .../system_flow-accounting.xml.in | 437 +++++++ interface-definitions/system_frr.xml.in | 91 ++ interface-definitions/system_host-name.xml.in | 16 + interface-definitions/system_ip.xml.in | 114 ++ interface-definitions/system_ipv6.xml.in | 50 + interface-definitions/system_lcd.xml.in | 70 ++ interface-definitions/system_login.xml.in | 302 +++++ interface-definitions/system_login_banner.xml.in | 32 + interface-definitions/system_logs.xml.in | 92 ++ interface-definitions/system_name-server.xml.in | 33 + interface-definitions/system_option.xml.in | 171 +++ interface-definitions/system_proxy.xml.in | 25 + interface-definitions/system_sflow.xml.in | 113 ++ .../system_static-host-mapping.xml.in | 53 + interface-definitions/system_sysctl.xml.in | 40 + interface-definitions/system_syslog.xml.in | 155 +++ interface-definitions/system_task-scheduler.xml.in | 72 ++ interface-definitions/system_time-zone.xml.in | 19 + interface-definitions/system_update-check.xml.in | 22 + interface-definitions/tftp-server.xml.in | 32 - interface-definitions/vpn-ipsec.xml.in | 1194 -------------------- interface-definitions/vpn-l2tp.xml.in | 163 --- interface-definitions/vpn-openconnect.xml.in | 392 ------- interface-definitions/vpn-pptp.xml.in | 143 --- interface-definitions/vpn-sstp.xml.in | 64 -- interface-definitions/vpn_ipsec.xml.in | 1194 ++++++++++++++++++++ interface-definitions/vpn_l2tp.xml.in | 163 +++ interface-definitions/vpn_openconnect.xml.in | 392 +++++++ interface-definitions/vpn_pptp.xml.in | 143 +++ interface-definitions/vpn_sstp.xml.in | 64 ++ python/vyos/ifconfig/vxlan.py | 2 +- smoketest/scripts/cli/test_ha_virtual_server.py | 152 --- smoketest/scripts/cli/test_ha_vrrp.py | 241 ---- .../cli/test_high-availability_virtual-server.py | 152 +++ .../scripts/cli/test_high-availability_vrrp.py | 241 ++++ .../scripts/cli/test_interfaces_pseudo-ethernet.py | 46 + .../scripts/cli/test_interfaces_pseudo_ethernet.py | 46 - .../cli/test_interfaces_virtual-ethernet.py | 62 + .../cli/test_interfaces_virtual_ethernet.py | 62 - .../cli/test_load-balancing_reverse-proxy.py | 114 ++ smoketest/scripts/cli/test_load-balancing_wan.py | 259 +++++ .../cli/test_load_balancing_reverse_proxy.py | 114 -- smoketest/scripts/cli/test_load_balancing_wan.py | 259 ----- .../scripts/cli/test_protocols_segment-routing.py | 112 ++ .../scripts/cli/test_protocols_segment_routing.py | 112 -- smoketest/scripts/cli/test_service_bcast-relay.py | 68 -- .../scripts/cli/test_service_broadcast-relay.py | 68 ++ smoketest/scripts/cli/test_service_ids.py | 116 -- .../cli/test_service_ids_ddos-protection.py | 116 ++ .../scripts/cli/test_service_mdns-repeater.py | 134 --- .../scripts/cli/test_service_mdns_repeater.py | 134 +++ smoketest/scripts/cli/test_service_salt-minion.py | 105 ++ smoketest/scripts/cli/test_service_salt.py | 105 -- src/conf_mode/arp.py | 74 -- src/conf_mode/bcast_relay.py | 111 -- src/conf_mode/config_mgmt.py | 96 -- src/conf_mode/conntrack.py | 243 ---- src/conf_mode/conntrack_sync.py | 141 --- src/conf_mode/dhcp_relay.py | 104 -- src/conf_mode/dhcp_server.py | 385 ------- src/conf_mode/dhcpv6_relay.py | 106 -- src/conf_mode/dhcpv6_server.py | 222 ---- src/conf_mode/dns_dynamic.py | 187 --- src/conf_mode/dns_forwarding.py | 358 ------ src/conf_mode/firewall.py | 3 - src/conf_mode/flow_accounting_conf.py | 320 ------ src/conf_mode/host_name.py | 188 --- src/conf_mode/https.py | 335 ------ src/conf_mode/igmp_proxy.py | 113 -- src/conf_mode/intel_qat.py | 106 -- src/conf_mode/interfaces-bonding.py | 294 ----- src/conf_mode/interfaces-bridge.py | 186 --- src/conf_mode/interfaces-dummy.py | 76 -- src/conf_mode/interfaces-ethernet.py | 400 ------- src/conf_mode/interfaces-geneve.py | 102 -- src/conf_mode/interfaces-input.py | 70 -- src/conf_mode/interfaces-l2tpv3.py | 112 -- src/conf_mode/interfaces-loopback.py | 66 -- src/conf_mode/interfaces-macsec.py | 207 ---- src/conf_mode/interfaces-openvpn.py | 732 ------------ src/conf_mode/interfaces-pppoe.py | 148 --- src/conf_mode/interfaces-pseudo-ethernet.py | 107 -- src/conf_mode/interfaces-sstpc.py | 145 --- src/conf_mode/interfaces-tunnel.py | 224 ---- src/conf_mode/interfaces-virtual-ethernet.py | 114 -- src/conf_mode/interfaces-vti.py | 68 -- src/conf_mode/interfaces-vxlan.py | 236 ---- src/conf_mode/interfaces-wireguard.py | 133 --- src/conf_mode/interfaces-wireless.py | 275 ----- src/conf_mode/interfaces-wwan.py | 189 ---- src/conf_mode/interfaces_bonding.py | 294 +++++ src/conf_mode/interfaces_bridge.py | 186 +++ src/conf_mode/interfaces_dummy.py | 76 ++ src/conf_mode/interfaces_ethernet.py | 400 +++++++ src/conf_mode/interfaces_geneve.py | 102 ++ src/conf_mode/interfaces_input.py | 70 ++ src/conf_mode/interfaces_l2tpv3.py | 112 ++ src/conf_mode/interfaces_loopback.py | 66 ++ src/conf_mode/interfaces_macsec.py | 207 ++++ src/conf_mode/interfaces_openvpn.py | 732 ++++++++++++ src/conf_mode/interfaces_pppoe.py | 148 +++ src/conf_mode/interfaces_pseudo-ethernet.py | 107 ++ src/conf_mode/interfaces_sstpc.py | 145 +++ src/conf_mode/interfaces_tunnel.py | 224 ++++ src/conf_mode/interfaces_virtual-ethernet.py | 114 ++ src/conf_mode/interfaces_vti.py | 68 ++ src/conf_mode/interfaces_vxlan.py | 236 ++++ src/conf_mode/interfaces_wireguard.py | 133 +++ src/conf_mode/interfaces_wireless.py | 275 +++++ src/conf_mode/interfaces_wwan.py | 189 ++++ src/conf_mode/le_cert.py | 115 -- src/conf_mode/lldp.py | 123 -- src/conf_mode/load-balancing-haproxy.py | 169 --- src/conf_mode/load-balancing-wan.py | 151 --- src/conf_mode/load-balancing_reverse-proxy.py | 169 +++ src/conf_mode/load-balancing_wan.py | 151 +++ src/conf_mode/ntp.py | 136 --- src/conf_mode/pki.py | 8 +- src/conf_mode/policy-local-route.py | 315 ------ src/conf_mode/policy-route.py | 195 ---- src/conf_mode/policy_local-route.py | 315 ++++++ src/conf_mode/policy_route.py | 195 ++++ src/conf_mode/protocols_igmp-proxy.py | 113 ++ src/conf_mode/protocols_segment-routing.py | 118 ++ src/conf_mode/protocols_segment_routing.py | 118 -- src/conf_mode/protocols_static_arp.py | 74 ++ src/conf_mode/salt-minion.py | 118 -- src/conf_mode/service_broadcast-relay.py | 111 ++ src/conf_mode/service_config-sync.py | 105 ++ src/conf_mode/service_config_sync.py | 105 -- src/conf_mode/service_conntrack-sync.py | 141 +++ src/conf_mode/service_dhcp-relay.py | 104 ++ src/conf_mode/service_dhcp-server.py | 385 +++++++ src/conf_mode/service_dhcpv6-relay.py | 106 ++ src/conf_mode/service_dhcpv6-server.py | 222 ++++ src/conf_mode/service_dns_dynamic.py | 187 +++ src/conf_mode/service_dns_forwarding.py | 358 ++++++ src/conf_mode/service_event-handler.py | 92 ++ src/conf_mode/service_event_handler.py | 92 -- src/conf_mode/service_https.py | 335 ++++++ .../service_https_certificates_certbot.py | 114 ++ src/conf_mode/service_ids_ddos-protection.py | 104 ++ src/conf_mode/service_ids_fastnetmon.py | 104 -- src/conf_mode/service_lldp.py | 123 ++ src/conf_mode/service_mdns-repeater.py | 146 --- src/conf_mode/service_mdns_repeater.py | 146 +++ src/conf_mode/service_ntp.py | 136 +++ src/conf_mode/service_salt-minion.py | 118 ++ src/conf_mode/service_snmp.py | 269 +++++ src/conf_mode/service_ssh.py | 142 +++ src/conf_mode/service_tftp-server.py | 142 +++ src/conf_mode/snmp.py | 269 ----- src/conf_mode/ssh.py | 142 --- src/conf_mode/system-ip.py | 143 --- src/conf_mode/system-ipv6.py | 120 -- src/conf_mode/system-login-banner.py | 107 -- src/conf_mode/system-login.py | 423 ------- src/conf_mode/system-logs.py | 79 -- src/conf_mode/system-option.py | 159 --- src/conf_mode/system-proxy.py | 71 -- src/conf_mode/system-syslog.py | 103 -- src/conf_mode/system-timezone.py | 61 - src/conf_mode/system_acceleration.py | 106 ++ src/conf_mode/system_config-management.py | 96 ++ src/conf_mode/system_conntrack.py | 243 ++++ src/conf_mode/system_flow-accounting.py | 320 ++++++ src/conf_mode/system_host-name.py | 188 +++ src/conf_mode/system_ip.py | 143 +++ src/conf_mode/system_ipv6.py | 120 ++ src/conf_mode/system_login.py | 423 +++++++ src/conf_mode/system_login_banner.py | 107 ++ src/conf_mode/system_logs.py | 79 ++ src/conf_mode/system_option.py | 159 +++ src/conf_mode/system_proxy.py | 71 ++ src/conf_mode/system_syslog.py | 103 ++ src/conf_mode/system_task-scheduler.py | 153 +++ src/conf_mode/system_timezone.py | 61 + src/conf_mode/system_update-check.py | 93 ++ src/conf_mode/system_update_check.py | 93 -- src/conf_mode/task_scheduler.py | 153 --- src/conf_mode/tftp_server.py | 142 --- .../ip-down.d/98-vyos-pppoe-cleanup-nameservers | 1 - .../ppp/ip-up.d/98-vyos-pppoe-setup-nameservers | 1 - src/init/vyos-router | 10 +- src/migration-scripts/https/1-to-2 | 2 +- src/op_mode/connect_disconnect.py | 2 +- src/system/keepalived-fifo.py | 2 +- src/tests/test_task_scheduler.py | 8 +- 407 files changed, 30879 insertions(+), 30851 deletions(-) delete mode 100644 interface-definitions/bcast-relay.xml.in delete mode 100644 interface-definitions/cron.xml.in delete mode 100644 interface-definitions/dhcp-relay.xml.in delete mode 100644 interface-definitions/dhcp-server.xml.in delete mode 100644 interface-definitions/dhcpv6-relay.xml.in delete mode 100644 interface-definitions/dhcpv6-server.xml.in delete mode 100644 interface-definitions/dns-domain-name.xml.in delete mode 100644 interface-definitions/dns-dynamic.xml.in delete mode 100644 interface-definitions/dns-forwarding.xml.in delete mode 100644 interface-definitions/flow-accounting-conf.xml.in delete mode 100644 interface-definitions/https.xml.in delete mode 100644 interface-definitions/igmp-proxy.xml.in delete mode 100644 interface-definitions/interfaces-bonding.xml.in delete mode 100644 interface-definitions/interfaces-bridge.xml.in delete mode 100644 interface-definitions/interfaces-dummy.xml.in delete mode 100644 interface-definitions/interfaces-ethernet.xml.in delete mode 100644 interface-definitions/interfaces-geneve.xml.in delete mode 100644 interface-definitions/interfaces-input.xml.in delete mode 100644 interface-definitions/interfaces-l2tpv3.xml.in delete mode 100644 interface-definitions/interfaces-loopback.xml.in delete mode 100644 interface-definitions/interfaces-macsec.xml.in delete mode 100644 interface-definitions/interfaces-openvpn.xml.in delete mode 100644 interface-definitions/interfaces-pppoe.xml.in delete mode 100644 interface-definitions/interfaces-pseudo-ethernet.xml.in delete mode 100644 interface-definitions/interfaces-sstpc.xml.in delete mode 100644 interface-definitions/interfaces-tunnel.xml.in delete mode 100644 interface-definitions/interfaces-virtual-ethernet.xml.in delete mode 100644 interface-definitions/interfaces-vti.xml.in delete mode 100644 interface-definitions/interfaces-vxlan.xml.in delete mode 100644 interface-definitions/interfaces-wireguard.xml.in delete mode 100644 interface-definitions/interfaces-wireless.xml.in delete mode 100644 interface-definitions/interfaces-wwan.xml.in create mode 100644 interface-definitions/interfaces_bonding.xml.in create mode 100644 interface-definitions/interfaces_bridge.xml.in create mode 100644 interface-definitions/interfaces_dummy.xml.in create mode 100644 interface-definitions/interfaces_ethernet.xml.in create mode 100644 interface-definitions/interfaces_geneve.xml.in create mode 100644 interface-definitions/interfaces_input.xml.in create mode 100644 interface-definitions/interfaces_l2tpv3.xml.in create mode 100644 interface-definitions/interfaces_loopback.xml.in create mode 100644 interface-definitions/interfaces_macsec.xml.in create mode 100644 interface-definitions/interfaces_openvpn.xml.in create mode 100644 interface-definitions/interfaces_pppoe.xml.in create mode 100644 interface-definitions/interfaces_pseudo-ethernet.xml.in create mode 100644 interface-definitions/interfaces_sstpc.xml.in create mode 100644 interface-definitions/interfaces_tunnel.xml.in create mode 100644 interface-definitions/interfaces_virtual-ethernet.xml.in create mode 100644 interface-definitions/interfaces_vti.xml.in create mode 100644 interface-definitions/interfaces_vxlan.xml.in create mode 100644 interface-definitions/interfaces_wireguard.xml.in create mode 100644 interface-definitions/interfaces_wireless.xml.in create mode 100644 interface-definitions/interfaces_wwan.xml.in delete mode 100644 interface-definitions/lldp.xml.in delete mode 100644 interface-definitions/load-balancing-haproxy.xml.in delete mode 100644 interface-definitions/load-balancing-wan.xml.in create mode 100644 interface-definitions/load-balancing_reverse-proxy.xml.in create mode 100644 interface-definitions/load-balancing_wan.xml.in delete mode 100644 interface-definitions/ntp.xml.in delete mode 100644 interface-definitions/policy-local-route.xml.in delete mode 100644 interface-definitions/policy-route.xml.in create mode 100644 interface-definitions/policy_local-route.xml.in create mode 100644 interface-definitions/policy_route.xml.in delete mode 100644 interface-definitions/protocols-babel.xml.in delete mode 100644 interface-definitions/protocols-bfd.xml.in delete mode 100644 interface-definitions/protocols-bgp.xml.in delete mode 100644 interface-definitions/protocols-eigrp.xml.in delete mode 100644 interface-definitions/protocols-failover.xml.in delete mode 100644 interface-definitions/protocols-isis.xml.in delete mode 100644 interface-definitions/protocols-mpls.xml.in delete mode 100644 interface-definitions/protocols-multicast.xml.in delete mode 100644 interface-definitions/protocols-nhrp.xml.in delete mode 100644 interface-definitions/protocols-ospf.xml.in delete mode 100644 interface-definitions/protocols-ospfv3.xml.in delete mode 100644 interface-definitions/protocols-pim.xml.in delete mode 100644 interface-definitions/protocols-pim6.xml.in delete mode 100644 interface-definitions/protocols-rip.xml.in delete mode 100644 interface-definitions/protocols-ripng.xml.in delete mode 100644 interface-definitions/protocols-rpki.xml.in delete mode 100644 interface-definitions/protocols-segment-routing.xml.in delete mode 100644 interface-definitions/protocols-static-arp.xml.in delete mode 100644 interface-definitions/protocols-static.xml.in create mode 100644 interface-definitions/protocols_babel.xml.in create mode 100644 interface-definitions/protocols_bfd.xml.in create mode 100644 interface-definitions/protocols_bgp.xml.in create mode 100644 interface-definitions/protocols_eigrp.xml.in create mode 100644 interface-definitions/protocols_failover.xml.in create mode 100644 interface-definitions/protocols_igmp-proxy.xml.in create mode 100644 interface-definitions/protocols_isis.xml.in create mode 100644 interface-definitions/protocols_mpls.xml.in create mode 100644 interface-definitions/protocols_nhrp.xml.in create mode 100644 interface-definitions/protocols_ospf.xml.in create mode 100644 interface-definitions/protocols_ospfv3.xml.in create mode 100644 interface-definitions/protocols_pim.xml.in create mode 100644 interface-definitions/protocols_pim6.xml.in create mode 100644 interface-definitions/protocols_rip.xml.in create mode 100644 interface-definitions/protocols_ripng.xml.in create mode 100644 interface-definitions/protocols_rpki.xml.in create mode 100644 interface-definitions/protocols_segment-routing.xml.in create mode 100644 interface-definitions/protocols_static.xml.in create mode 100644 interface-definitions/protocols_static_arp.xml.in create mode 100644 interface-definitions/protocols_static_multicast.xml.in delete mode 100644 interface-definitions/salt-minion.xml.in delete mode 100644 interface-definitions/service-aws-glb.xml.in delete mode 100644 interface-definitions/service-config-sync.xml.in delete mode 100644 interface-definitions/service-conntrack-sync.xml.in delete mode 100644 interface-definitions/service-console-server.xml.in delete mode 100644 interface-definitions/service-event-handler.xml.in delete mode 100644 interface-definitions/service-ids-ddos-protection.xml.in delete mode 100644 interface-definitions/service-ipoe-server.xml.in delete mode 100644 interface-definitions/service-mdns-repeater.xml.in delete mode 100644 interface-definitions/service-monitoring-telegraf.xml.in delete mode 100644 interface-definitions/service-monitoring-zabbix-agent.xml.in delete mode 100644 interface-definitions/service-pppoe-server.xml.in delete mode 100644 interface-definitions/service-router-advert.xml.in delete mode 100644 interface-definitions/service-sla.xml.in delete mode 100644 interface-definitions/service-upnp.xml.in delete mode 100644 interface-definitions/service-webproxy.xml.in create mode 100644 interface-definitions/service_aws_glb.xml.in create mode 100644 interface-definitions/service_broadcast-relay.xml.in create mode 100644 interface-definitions/service_config-sync.xml.in create mode 100644 interface-definitions/service_conntrack-sync.xml.in create mode 100644 interface-definitions/service_console-server.xml.in create mode 100644 interface-definitions/service_dhcp-relay.xml.in create mode 100644 interface-definitions/service_dhcp-server.xml.in create mode 100644 interface-definitions/service_dhcpv6-relay.xml.in create mode 100644 interface-definitions/service_dhcpv6-server.xml.in create mode 100644 interface-definitions/service_dns_dynamic.xml.in create mode 100644 interface-definitions/service_dns_forwarding.xml.in create mode 100644 interface-definitions/service_event-handler.xml.in create mode 100644 interface-definitions/service_https.xml.in create mode 100644 interface-definitions/service_ids_ddos-protection.xml.in create mode 100644 interface-definitions/service_ipoe-server.xml.in create mode 100644 interface-definitions/service_lldp.xml.in create mode 100644 interface-definitions/service_mdns_repeater.xml.in create mode 100644 interface-definitions/service_monitoring_telegraf.xml.in create mode 100644 interface-definitions/service_monitoring_zabbix-agent.xml.in create mode 100644 interface-definitions/service_ntp.xml.in create mode 100644 interface-definitions/service_pppoe-server.xml.in create mode 100644 interface-definitions/service_router-advert.xml.in create mode 100644 interface-definitions/service_salt-minion.xml.in create mode 100644 interface-definitions/service_sla.xml.in create mode 100644 interface-definitions/service_snmp.xml.in create mode 100644 interface-definitions/service_ssh.xml.in create mode 100644 interface-definitions/service_tftp-server.xml.in create mode 100644 interface-definitions/service_upnp.xml.in create mode 100644 interface-definitions/service_webproxy.xml.in delete mode 100644 interface-definitions/snmp.xml.in delete mode 100644 interface-definitions/ssh.xml.in delete mode 100644 interface-definitions/system-acceleration-qat.xml.in delete mode 100644 interface-definitions/system-config-mgmt.xml.in delete mode 100644 interface-definitions/system-conntrack.xml.in delete mode 100644 interface-definitions/system-console.xml.in delete mode 100644 interface-definitions/system-frr.xml.in delete mode 100644 interface-definitions/system-ip.xml.in delete mode 100644 interface-definitions/system-ipv6.xml.in delete mode 100644 interface-definitions/system-lcd.xml.in delete mode 100644 interface-definitions/system-login-banner.xml.in delete mode 100644 interface-definitions/system-login.xml.in delete mode 100644 interface-definitions/system-logs.xml.in delete mode 100644 interface-definitions/system-option.xml.in delete mode 100644 interface-definitions/system-proxy.xml.in delete mode 100644 interface-definitions/system-sflow.xml.in delete mode 100644 interface-definitions/system-sysctl.xml.in delete mode 100644 interface-definitions/system-syslog.xml.in delete mode 100644 interface-definitions/system-time-zone.xml.in delete mode 100644 interface-definitions/system-update-check.xml.in create mode 100644 interface-definitions/system_acceleration.xml.in create mode 100644 interface-definitions/system_config-management.xml.in create mode 100644 interface-definitions/system_conntrack.xml.in create mode 100644 interface-definitions/system_console.xml.in create mode 100644 interface-definitions/system_domain-name.xml.in create mode 100644 interface-definitions/system_domain-search.xml.in create mode 100644 interface-definitions/system_flow-accounting.xml.in create mode 100644 interface-definitions/system_frr.xml.in create mode 100644 interface-definitions/system_host-name.xml.in create mode 100644 interface-definitions/system_ip.xml.in create mode 100644 interface-definitions/system_ipv6.xml.in create mode 100644 interface-definitions/system_lcd.xml.in create mode 100644 interface-definitions/system_login.xml.in create mode 100644 interface-definitions/system_login_banner.xml.in create mode 100644 interface-definitions/system_logs.xml.in create mode 100644 interface-definitions/system_name-server.xml.in create mode 100644 interface-definitions/system_option.xml.in create mode 100644 interface-definitions/system_proxy.xml.in create mode 100644 interface-definitions/system_sflow.xml.in create mode 100644 interface-definitions/system_static-host-mapping.xml.in create mode 100644 interface-definitions/system_sysctl.xml.in create mode 100644 interface-definitions/system_syslog.xml.in create mode 100644 interface-definitions/system_task-scheduler.xml.in create mode 100644 interface-definitions/system_time-zone.xml.in create mode 100644 interface-definitions/system_update-check.xml.in delete mode 100644 interface-definitions/tftp-server.xml.in delete mode 100644 interface-definitions/vpn-ipsec.xml.in delete mode 100644 interface-definitions/vpn-l2tp.xml.in delete mode 100644 interface-definitions/vpn-openconnect.xml.in delete mode 100644 interface-definitions/vpn-pptp.xml.in delete mode 100644 interface-definitions/vpn-sstp.xml.in create mode 100644 interface-definitions/vpn_ipsec.xml.in create mode 100644 interface-definitions/vpn_l2tp.xml.in create mode 100644 interface-definitions/vpn_openconnect.xml.in create mode 100644 interface-definitions/vpn_pptp.xml.in create mode 100644 interface-definitions/vpn_sstp.xml.in delete mode 100755 smoketest/scripts/cli/test_ha_virtual_server.py delete mode 100755 smoketest/scripts/cli/test_ha_vrrp.py create mode 100755 smoketest/scripts/cli/test_high-availability_virtual-server.py create mode 100755 smoketest/scripts/cli/test_high-availability_vrrp.py create mode 100755 smoketest/scripts/cli/test_interfaces_pseudo-ethernet.py delete mode 100755 smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py create mode 100755 smoketest/scripts/cli/test_interfaces_virtual-ethernet.py delete mode 100755 smoketest/scripts/cli/test_interfaces_virtual_ethernet.py create mode 100755 smoketest/scripts/cli/test_load-balancing_reverse-proxy.py create mode 100755 smoketest/scripts/cli/test_load-balancing_wan.py delete mode 100755 smoketest/scripts/cli/test_load_balancing_reverse_proxy.py delete mode 100755 smoketest/scripts/cli/test_load_balancing_wan.py create mode 100755 smoketest/scripts/cli/test_protocols_segment-routing.py delete mode 100755 smoketest/scripts/cli/test_protocols_segment_routing.py delete mode 100755 smoketest/scripts/cli/test_service_bcast-relay.py create mode 100755 smoketest/scripts/cli/test_service_broadcast-relay.py delete mode 100755 smoketest/scripts/cli/test_service_ids.py create mode 100755 smoketest/scripts/cli/test_service_ids_ddos-protection.py delete mode 100755 smoketest/scripts/cli/test_service_mdns-repeater.py create mode 100755 smoketest/scripts/cli/test_service_mdns_repeater.py create mode 100755 smoketest/scripts/cli/test_service_salt-minion.py delete mode 100755 smoketest/scripts/cli/test_service_salt.py delete mode 100755 src/conf_mode/arp.py delete mode 100755 src/conf_mode/bcast_relay.py delete mode 100755 src/conf_mode/config_mgmt.py delete mode 100755 src/conf_mode/conntrack.py delete mode 100755 src/conf_mode/conntrack_sync.py delete mode 100755 src/conf_mode/dhcp_relay.py delete mode 100755 src/conf_mode/dhcp_server.py delete mode 100755 src/conf_mode/dhcpv6_relay.py delete mode 100755 src/conf_mode/dhcpv6_server.py delete mode 100755 src/conf_mode/dns_dynamic.py delete mode 100755 src/conf_mode/dns_forwarding.py delete mode 100755 src/conf_mode/flow_accounting_conf.py delete mode 100755 src/conf_mode/host_name.py delete mode 100755 src/conf_mode/https.py delete mode 100755 src/conf_mode/igmp_proxy.py delete mode 100755 src/conf_mode/intel_qat.py delete mode 100755 src/conf_mode/interfaces-bonding.py delete mode 100755 src/conf_mode/interfaces-bridge.py delete mode 100755 src/conf_mode/interfaces-dummy.py delete mode 100755 src/conf_mode/interfaces-ethernet.py delete mode 100755 src/conf_mode/interfaces-geneve.py delete mode 100755 src/conf_mode/interfaces-input.py delete mode 100755 src/conf_mode/interfaces-l2tpv3.py delete mode 100755 src/conf_mode/interfaces-loopback.py delete mode 100755 src/conf_mode/interfaces-macsec.py delete mode 100755 src/conf_mode/interfaces-openvpn.py delete mode 100755 src/conf_mode/interfaces-pppoe.py delete mode 100755 src/conf_mode/interfaces-pseudo-ethernet.py delete mode 100755 src/conf_mode/interfaces-sstpc.py delete mode 100755 src/conf_mode/interfaces-tunnel.py delete mode 100755 src/conf_mode/interfaces-virtual-ethernet.py delete mode 100755 src/conf_mode/interfaces-vti.py delete mode 100755 src/conf_mode/interfaces-vxlan.py delete mode 100755 src/conf_mode/interfaces-wireguard.py delete mode 100755 src/conf_mode/interfaces-wireless.py delete mode 100755 src/conf_mode/interfaces-wwan.py create mode 100755 src/conf_mode/interfaces_bonding.py create mode 100755 src/conf_mode/interfaces_bridge.py create mode 100755 src/conf_mode/interfaces_dummy.py create mode 100755 src/conf_mode/interfaces_ethernet.py create mode 100755 src/conf_mode/interfaces_geneve.py create mode 100755 src/conf_mode/interfaces_input.py create mode 100755 src/conf_mode/interfaces_l2tpv3.py create mode 100755 src/conf_mode/interfaces_loopback.py create mode 100755 src/conf_mode/interfaces_macsec.py create mode 100755 src/conf_mode/interfaces_openvpn.py create mode 100755 src/conf_mode/interfaces_pppoe.py create mode 100755 src/conf_mode/interfaces_pseudo-ethernet.py create mode 100755 src/conf_mode/interfaces_sstpc.py create mode 100755 src/conf_mode/interfaces_tunnel.py create mode 100755 src/conf_mode/interfaces_virtual-ethernet.py create mode 100755 src/conf_mode/interfaces_vti.py create mode 100755 src/conf_mode/interfaces_vxlan.py create mode 100755 src/conf_mode/interfaces_wireguard.py create mode 100755 src/conf_mode/interfaces_wireless.py create mode 100755 src/conf_mode/interfaces_wwan.py delete mode 100755 src/conf_mode/le_cert.py delete mode 100755 src/conf_mode/lldp.py delete mode 100755 src/conf_mode/load-balancing-haproxy.py delete mode 100755 src/conf_mode/load-balancing-wan.py create mode 100755 src/conf_mode/load-balancing_reverse-proxy.py create mode 100755 src/conf_mode/load-balancing_wan.py delete mode 100755 src/conf_mode/ntp.py delete mode 100755 src/conf_mode/policy-local-route.py delete mode 100755 src/conf_mode/policy-route.py create mode 100755 src/conf_mode/policy_local-route.py create mode 100755 src/conf_mode/policy_route.py create mode 100755 src/conf_mode/protocols_igmp-proxy.py create mode 100755 src/conf_mode/protocols_segment-routing.py delete mode 100755 src/conf_mode/protocols_segment_routing.py create mode 100755 src/conf_mode/protocols_static_arp.py delete mode 100755 src/conf_mode/salt-minion.py create mode 100755 src/conf_mode/service_broadcast-relay.py create mode 100755 src/conf_mode/service_config-sync.py delete mode 100755 src/conf_mode/service_config_sync.py create mode 100755 src/conf_mode/service_conntrack-sync.py create mode 100755 src/conf_mode/service_dhcp-relay.py create mode 100755 src/conf_mode/service_dhcp-server.py create mode 100755 src/conf_mode/service_dhcpv6-relay.py create mode 100755 src/conf_mode/service_dhcpv6-server.py create mode 100755 src/conf_mode/service_dns_dynamic.py create mode 100755 src/conf_mode/service_dns_forwarding.py create mode 100755 src/conf_mode/service_event-handler.py delete mode 100755 src/conf_mode/service_event_handler.py create mode 100755 src/conf_mode/service_https.py create mode 100755 src/conf_mode/service_https_certificates_certbot.py create mode 100755 src/conf_mode/service_ids_ddos-protection.py delete mode 100755 src/conf_mode/service_ids_fastnetmon.py create mode 100755 src/conf_mode/service_lldp.py delete mode 100755 src/conf_mode/service_mdns-repeater.py create mode 100755 src/conf_mode/service_mdns_repeater.py create mode 100755 src/conf_mode/service_ntp.py create mode 100755 src/conf_mode/service_salt-minion.py create mode 100755 src/conf_mode/service_snmp.py create mode 100755 src/conf_mode/service_ssh.py create mode 100755 src/conf_mode/service_tftp-server.py delete mode 100755 src/conf_mode/snmp.py delete mode 100755 src/conf_mode/ssh.py delete mode 100755 src/conf_mode/system-ip.py delete mode 100755 src/conf_mode/system-ipv6.py delete mode 100755 src/conf_mode/system-login-banner.py delete mode 100755 src/conf_mode/system-login.py delete mode 100755 src/conf_mode/system-logs.py delete mode 100755 src/conf_mode/system-option.py delete mode 100755 src/conf_mode/system-proxy.py delete mode 100755 src/conf_mode/system-syslog.py delete mode 100755 src/conf_mode/system-timezone.py create mode 100755 src/conf_mode/system_acceleration.py create mode 100755 src/conf_mode/system_config-management.py create mode 100755 src/conf_mode/system_conntrack.py create mode 100755 src/conf_mode/system_flow-accounting.py create mode 100755 src/conf_mode/system_host-name.py create mode 100755 src/conf_mode/system_ip.py create mode 100755 src/conf_mode/system_ipv6.py create mode 100755 src/conf_mode/system_login.py create mode 100755 src/conf_mode/system_login_banner.py create mode 100755 src/conf_mode/system_logs.py create mode 100755 src/conf_mode/system_option.py create mode 100755 src/conf_mode/system_proxy.py create mode 100755 src/conf_mode/system_syslog.py create mode 100755 src/conf_mode/system_task-scheduler.py create mode 100755 src/conf_mode/system_timezone.py create mode 100755 src/conf_mode/system_update-check.py delete mode 100755 src/conf_mode/system_update_check.py delete mode 100755 src/conf_mode/task_scheduler.py delete mode 100755 src/conf_mode/tftp_server.py diff --git a/data/config-mode-dependencies/vyos-1x.json b/data/config-mode-dependencies/vyos-1x.json index 4a1bc4011..4fd94d895 100644 --- a/data/config-mode-dependencies/vyos-1x.json +++ b/data/config-mode-dependencies/vyos-1x.json @@ -1,56 +1,52 @@ { - "conntrack": { - "conntrack_sync": ["conntrack_sync"] + "system_conntrack": { + "conntrack_sync": ["service_conntrack-sync"] }, "firewall": { - "conntrack": ["conntrack"], - "conntrack_sync": ["conntrack_sync"], - "group_resync": ["conntrack", "nat", "policy-route"] + "conntrack": ["system_conntrack"], + "group_resync": ["system_conntrack", "nat", "policy_route"] }, "interfaces_bonding": { - "ethernet": ["interfaces-ethernet"] + "ethernet": ["interfaces_ethernet"] }, "interfaces_bridge": { - "vxlan": ["interfaces-vxlan"] + "vxlan": ["interfaces_vxlan"] }, "load_balancing_wan": { - "conntrack": ["conntrack"], - "conntrack_sync": ["conntrack_sync"] + "conntrack": ["system_conntrack"] }, "nat": { - "conntrack": ["conntrack"], - "conntrack_sync": ["conntrack_sync"] + "conntrack": ["system_conntrack"] }, "nat66": { - "conntrack": ["conntrack"], - "conntrack_sync": ["conntrack_sync"] + "conntrack": ["system_conntrack"] }, "pki": { - "ethernet": ["interfaces-ethernet"], - "openvpn": ["interfaces-openvpn"], - "https": ["https"], + "ethernet": ["interfaces_ethernet"], + "openvpn": ["interfaces_openvpn"], + "https": ["service_https"], "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"] + "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 6d7261b73..d1f9db796 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -1,88 +1,107 @@ [ -"arp.py", -"bcast_relay.py", "container.py", -"conntrack.py", -"conntrack_sync.py", -"dhcp_relay.py", -"dhcp_server.py", -"dhcpv6_relay.py", -"dhcpv6_server.py", -"dns_forwarding.py", -"dns_dynamic.py", "firewall.py", -"flow_accounting_conf.py", "high-availability.py", -"host_name.py", -"igmp_proxy.py", -"intel_qat.py", -"interfaces-bonding.py", -"interfaces-bridge.py", -"interfaces-dummy.py", -"interfaces-ethernet.py", -"interfaces-geneve.py", -"interfaces-l2tpv3.py", -"interfaces-loopback.py", -"interfaces-macsec.py", -"interfaces-openvpn.py", -"interfaces-pppoe.py", -"interfaces-pseudo-ethernet.py", -"interfaces-sstpc.py", -"interfaces-tunnel.py", -"interfaces-vti.py", -"interfaces-vxlan.py", -"interfaces-wireguard.py", -"interfaces-wireless.py", -"interfaces-wwan.py", -"lldp.py", +"interfaces_bonding.py", +"interfaces_bridge.py", +"interfaces_dummy.py", +"interfaces_ethernet.py", +"interfaces_geneve.py", +"interfaces_input.py", +"interfaces_l2tpv3.py", +"interfaces_loopback.py", +"interfaces_macsec.py", +"interfaces_openvpn.py", +"interfaces_pppoe.py", +"interfaces_pseudo-ethernet.py", +"interfaces_sstpc.py", +"interfaces_tunnel.py", +"interfaces_virtual-ethernet.py", +"interfaces_vti.py", +"interfaces_vxlan.py", +"interfaces_wireguard.py", +"interfaces_wireless.py", +"interfaces_wwan.py", +"load-balancing_reverse-proxy.py", +"load-balancing_wan.py", "nat.py", +"nat64.py", "nat66.py", "netns.py", -"ntp.py", "pki.py", "policy.py", -"policy-local-route.py", +"policy_route.py", +"policy_local-route.py", +"protocols_babel.py", "protocols_bfd.py", "protocols_bgp.py", +"protocols_eigrp.py", +"protocols_failover.py", +"protocols_igmp-proxy.py", "protocols_isis.py", "protocols_mpls.py", "protocols_nhrp.py", "protocols_ospf.py", "protocols_ospfv3.py", "protocols_pim.py", +"protocols_pim6.py", "protocols_rip.py", "protocols_ripng.py", "protocols_rpki.py", -"protocols_segment_routing.py", +"protocols_segment-routing.py", "protocols_static.py", +"protocols_static_arp.py", "protocols_static_multicast.py", +"protocols_static_neighbor-proxy.py", "qos.py", -"salt-minion.py", +"service_broadcast-relay.py", +"service_config-sync.py", +"service_conntrack-sync.py", "service_console-server.py", -"service_ids_fastnetmon.py", +"service_dhcp-relay.py", +"service_dhcp-server.py", +"service_dhcpv6-relay.py", +"service_dhcpv6-server.py", +"service_dns_dynamic.py", +"service_dns_forwarding.py", +"service_event-handler.py", +"service_https.py", +"service_ids_ddos-protection.py", "service_ipoe-server.py", -"service_mdns-repeater.py", +"service_lldp.py", +"service_mdns_repeater.py", "service_monitoring_telegraf.py", +"service_monitoring_zabbix-agent.py", "service_ndp-proxy.py", +"service_ntp.py", "service_pppoe-server.py", "service_router-advert.py", +"service_salt-minion.py", +"service_ssh.py", +"service_tftp-server.py", "service_upnp.py", -"ssh.py", -"system-ip.py", -"system-ipv6.py", -"system-login-banner.py", -"system-logs.py", -"system-option.py", -"system-proxy.py", -"system_sflow.py", -"system_sysctl.py", -"system-syslog.py", -"system-timezone.py", +"system_acceleration.py", +"system_conntrack.py", "system_console.py", +"system_flow-accounting.py", +"system_frr.py", +"system_host-name.py", +"system_ip.py", +"system_ipv6.py", "system_lcd.py", -"task_scheduler.py", -"tftp_server.py", +"system_login_banner.py", +"system_logs.py", +"system_option.py", +"system_proxy.py", +"system_sflow.py", +"system_sysctl.py", +"system_syslog.py", +"system_task-scheduler.py", +"system_timezone.py", +"system_update-check.py", +"vpn_ipsec.py", "vpn_l2tp.py", +"vpn_openconnect.py", "vpn_pptp.py", "vpn_sstp.py", "vrf.py", diff --git a/data/templates/bcast-relay/udp-broadcast-relay.j2 b/data/templates/bcast-relay/udp-broadcast-relay.j2 index 75740e04c..3f5b5bbe3 100644 --- a/data/templates/bcast-relay/udp-broadcast-relay.j2 +++ b/data/templates/bcast-relay/udp-broadcast-relay.j2 @@ -1,4 +1,4 @@ -### Autogenerated by bcast_relay.py ### +### Autogenerated by service_broadcast-relay.py ### # UDP broadcast relay configuration for instance {{ id }} {{ '# ' ~ description if description is vyos_defined }} diff --git a/data/templates/chrony/chrony.conf.j2 b/data/templates/chrony/chrony.conf.j2 index 0daec8fb8..d02fbf71d 100644 --- a/data/templates/chrony/chrony.conf.j2 +++ b/data/templates/chrony/chrony.conf.j2 @@ -1,4 +1,4 @@ -### Autogenerated by ntp.py ### +### Autogenerated by service_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. diff --git a/data/templates/conntrack/sysctl.conf.j2 b/data/templates/conntrack/sysctl.conf.j2 index 3d6fc43f2..986f75c61 100644 --- a/data/templates/conntrack/sysctl.conf.j2 +++ b/data/templates/conntrack/sysctl.conf.j2 @@ -1,4 +1,4 @@ -# Autogenerated by conntrack.py +# Autogenerated by system_conntrack.py {# all values have defaults - thus no checking required #} net.netfilter.nf_conntrack_expect_max = {{ expect_table_size }} diff --git a/data/templates/conntrack/vyos_nf_conntrack.conf.j2 b/data/templates/conntrack/vyos_nf_conntrack.conf.j2 index 197155d96..1b12fec5f 100644 --- a/data/templates/conntrack/vyos_nf_conntrack.conf.j2 +++ b/data/templates/conntrack/vyos_nf_conntrack.conf.j2 @@ -1,2 +1,2 @@ -# Autogenerated by conntrack.py +# Autogenerated by system_conntrack.py options nf_conntrack hashsize={{ hash_size }} diff --git a/data/templates/conntrackd/conntrackd.conf.j2 b/data/templates/conntrackd/conntrackd.conf.j2 index 808a77759..8f56c8171 100644 --- a/data/templates/conntrackd/conntrackd.conf.j2 +++ b/data/templates/conntrackd/conntrackd.conf.j2 @@ -1,4 +1,4 @@ -# autogenerated by conntrack_sync.py +### autogenerated by service_conntrack-sync.py ### # Synchronizer settings Sync { @@ -111,4 +111,3 @@ General { } {% endif %} } - diff --git a/data/templates/dhcp-relay/dhcrelay.conf.j2 b/data/templates/dhcp-relay/dhcrelay.conf.j2 index c26c263fd..71a395454 100644 --- a/data/templates/dhcp-relay/dhcrelay.conf.j2 +++ b/data/templates/dhcp-relay/dhcrelay.conf.j2 @@ -1,4 +1,4 @@ -### Autogenerated by dhcp_relay.py ### +### Autogenerated by service_dhcp-relay.py ### {% 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 #} @@ -6,4 +6,4 @@ 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 +{% endif %} diff --git a/data/templates/dhcp-relay/dhcrelay6.conf.j2 b/data/templates/dhcp-relay/dhcrelay6.conf.j2 index 6365346b4..25f7671b3 100644 --- a/data/templates/dhcp-relay/dhcrelay6.conf.j2 +++ b/data/templates/dhcp-relay/dhcrelay6.conf.j2 @@ -1,4 +1,4 @@ -### Autogenerated by dhcpv6_relay.py ### +### Autogenerated by service_dhcpv6-relay.py ### {# upstream_interface is mandatory so it's always present #} {% set upstream = namespace(value='') %} @@ -18,4 +18,3 @@ {% endfor %} OPTIONS="{{ listen.value }} {{ upstream.value }} -c {{ max_hop_count }} {{ '-I' if use_interface_id_option is vyos_defined }}" - diff --git a/data/templates/dns-dynamic/ddclient.conf.j2 b/data/templates/dns-dynamic/ddclient.conf.j2 index 30afb9e64..6c0653a55 100644 --- a/data/templates/dns-dynamic/ddclient.conf.j2 +++ b/data/templates/dns-dynamic/ddclient.conf.j2 @@ -20,7 +20,7 @@ if{{ ipv }}={{ address }}, \ {# Actual hostname for the service #} {{ host }} {% endmacro %} -### Autogenerated by dns_dynamic.py ### +### Autogenerated by service_dns_dynamic.py ### daemon={{ interval }} syslog=yes ssl=yes diff --git a/data/templates/dns-forwarding/recursor.conf.j2 b/data/templates/dns-forwarding/recursor.conf.j2 index e02e6c13d..ea700406c 100644 --- a/data/templates/dns-forwarding/recursor.conf.j2 +++ b/data/templates/dns-forwarding/recursor.conf.j2 @@ -1,5 +1,5 @@ {# j2lint: disable=single-statement-per-line #} -### Autogenerated by dns_forwarding.py ### +### Autogenerated by service_dns_forwarding.py ### # XXX: pdns recursor doesn't like whitespace near entry separators, # especially in the semicolon-separated lists of name servers. @@ -47,4 +47,3 @@ serve-rfc1918={{ 'no' if no_serve_rfc1918 is vyos_defined else 'yes' }} auth-zones={% for z in authoritative_zones %}{{ z.name }}={{ z.file }}{{- "," if not loop.last -}}{% endfor %} forward-zones-file=recursor.forward-zones.conf - diff --git a/data/templates/dns-forwarding/recursor.conf.lua.j2 b/data/templates/dns-forwarding/recursor.conf.lua.j2 index e2506238d..816f69160 100644 --- a/data/templates/dns-forwarding/recursor.conf.lua.j2 +++ b/data/templates/dns-forwarding/recursor.conf.lua.j2 @@ -1,4 +1,4 @@ --- Autogenerated by VyOS (dns_forwarding.py) -- +-- Autogenerated by VyOS (service_dns_forwarding.py) -- -- Do not edit, your changes will get overwritten -- -- Load DNSSEC root keys from dns-root-data package. @@ -6,4 +6,3 @@ dofile("/usr/share/pdns-recursor/lua-config/rootkeys.lua") -- Load lua from vyos-hostsd -- dofile("recursor.vyos-hostsd.conf.lua") - diff --git a/data/templates/dns-forwarding/recursor.zone.conf.j2 b/data/templates/dns-forwarding/recursor.zone.conf.j2 index 25193c2ec..797068c49 100644 --- a/data/templates/dns-forwarding/recursor.zone.conf.j2 +++ b/data/templates/dns-forwarding/recursor.zone.conf.j2 @@ -1,5 +1,5 @@ ; -; Autogenerated by dns_forwarding.py +; Autogenerated by service_dns_forwarding.py ; {% for r in records %} {{ r.name }} {{ r.ttl }} {{ r.type }} {{ r.value }} diff --git a/data/templates/ethernet/wpa_supplicant.conf.j2 b/data/templates/ethernet/wpa_supplicant.conf.j2 index cd35d6d1e..6da2fa5e0 100644 --- a/data/templates/ethernet/wpa_supplicant.conf.j2 +++ b/data/templates/ethernet/wpa_supplicant.conf.j2 @@ -1,4 +1,4 @@ -### Autogenerated by interfaces-ethernet.py ### +### Autogenerated by interfaces_ethernet.py ### # see full documentation: # https://w1.fi/cgit/hostap/plain/wpa_supplicant/wpa_supplicant.conf @@ -74,4 +74,3 @@ network={ # 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/https/nginx.default.j2 b/data/templates/https/nginx.default.j2 index dde839e9f..80239ea56 100644 --- a/data/templates/https/nginx.default.j2 +++ b/data/templates/https/nginx.default.j2 @@ -1,4 +1,4 @@ -### Autogenerated by https.py ### +### Autogenerated by service_https.py ### # Default server configuration {% for server in server_block_list %} diff --git a/data/templates/igmp-proxy/igmpproxy.conf.j2 b/data/templates/igmp-proxy/igmpproxy.conf.j2 index ab3c9fd31..85a04de7d 100644 --- a/data/templates/igmp-proxy/igmpproxy.conf.j2 +++ b/data/templates/igmp-proxy/igmpproxy.conf.j2 @@ -1,6 +1,6 @@ ######################################################## # -# autogenerated by igmp_proxy.py +# autogenerated by protocols_igmp-proxy.py # # The configuration file must define one upstream interface, and one or more # downstream interfaces. diff --git a/data/templates/lldp/lldpd.j2 b/data/templates/lldp/lldpd.j2 index 6ae063c4b..2238fe1c4 100644 --- a/data/templates/lldp/lldpd.j2 +++ b/data/templates/lldp/lldpd.j2 @@ -1,2 +1,2 @@ -### Autogenerated by lldp.py ### +### Autogenerated by service_lldp.py ### DAEMON_ARGS="-M 4 {{ '-x' if snmp is vyos_defined }} {{ '-c' if legacy_protocols.cdp is vyos_defined }} {{ '-e' if legacy_protocols.edp is vyos_defined }} {{ '-f' if legacy_protocols.fdp is vyos_defined }} {{ '-s' if legacy_protocols.sonmp is vyos_defined }}" diff --git a/data/templates/lldp/vyos.conf.j2 b/data/templates/lldp/vyos.conf.j2 index dfa422ab8..4b4228cea 100644 --- a/data/templates/lldp/vyos.conf.j2 +++ b/data/templates/lldp/vyos.conf.j2 @@ -1,4 +1,4 @@ -### Autogenerated by lldp.py ### +### Autogenerated by service_lldp.py ### configure system platform VyOS configure system description "VyOS {{ version }}" diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2 index defb76fba..849cef74d 100644 --- a/data/templates/load-balancing/haproxy.cfg.j2 +++ b/data/templates/load-balancing/haproxy.cfg.j2 @@ -1,4 +1,4 @@ -# Generated by ${vyos_conf_scripts_dir}/load-balancing-haproxy.py +### Autogenerated by load-balancing_reverse-proxy.py ### global log /dev/log local0 diff --git a/data/templates/load-balancing/wlb.conf.j2 b/data/templates/load-balancing/wlb.conf.j2 index d3326b6b8..6557b6f4c 100644 --- a/data/templates/load-balancing/wlb.conf.j2 +++ b/data/templates/load-balancing/wlb.conf.j2 @@ -1,4 +1,4 @@ -# Generated by /usr/libexec/vyos/conf_mode/load-balancing-wan.py +### Autogenerated by load-balancing_wan.py ### {% if disable_source_nat is vyos_defined %} disable-source-nat @@ -41,7 +41,7 @@ health { test-script {{ test_config.test_script }} {% endif %} {% if test_config.target is vyos_defined %} - target {{ test_config.target }} + target {{ test_config.target }} {% endif %} resp-time {{ test_config.resp_time | int * 1000 }} } diff --git a/data/templates/login/authorized_keys.j2 b/data/templates/login/authorized_keys.j2 index aabca47cf..695b66abe 100644 --- a/data/templates/login/authorized_keys.j2 +++ b/data/templates/login/authorized_keys.j2 @@ -1,4 +1,4 @@ -### Automatically generated by system-login.py ### +### Automatically generated by system_login.py ### {% if authentication.public_keys is vyos_defined %} {% for key, key_options in authentication.public_keys.items() %} @@ -6,4 +6,3 @@ {{ key_options.options ~ ' ' if key_options.options is vyos_defined }}{{ key_options.type }} {{ key_options.key }} {{ key }} {% endfor %} {% endif %} - diff --git a/data/templates/login/limits.j2 b/data/templates/login/limits.j2 index 5e2c11f35..31abc85dd 100644 --- a/data/templates/login/limits.j2 +++ b/data/templates/login/limits.j2 @@ -1,4 +1,4 @@ -# Generated by /usr/libexec/vyos/conf_mode/system-login.py +# Generated by system_login.py {% if max_login_session is vyos_defined %} * - maxsyslogins {{ max_login_session }} diff --git a/data/templates/login/nsswitch.conf.j2 b/data/templates/login/nsswitch.conf.j2 index 65dc88291..0adfb491c 100644 --- a/data/templates/login/nsswitch.conf.j2 +++ b/data/templates/login/nsswitch.conf.j2 @@ -1,4 +1,4 @@ -# Automatically generated by system-login.py +# automatically generated by system_login.py ### # /etc/nsswitch.conf # # Example configuration of GNU Name Service Switch functionality. @@ -18,4 +18,3 @@ ethers: db files rpc: db files netgroup: nis - diff --git a/data/templates/login/pam_radius_auth.conf.j2 b/data/templates/login/pam_radius_auth.conf.j2 index c61154753..75437ca71 100644 --- a/data/templates/login/pam_radius_auth.conf.j2 +++ b/data/templates/login/pam_radius_auth.conf.j2 @@ -1,4 +1,4 @@ -# Automatically generated by system-login.py +### Automatically generated by system_login.py ### # RADIUS configuration file {% if radius is vyos_defined %} diff --git a/data/templates/login/tacplus_servers.j2 b/data/templates/login/tacplus_servers.j2 index 5a65d6e68..23e8e495e 100644 --- a/data/templates/login/tacplus_servers.j2 +++ b/data/templates/login/tacplus_servers.j2 @@ -1,4 +1,4 @@ -# Automatically generated by system-login.py +# Automatically generated by system_login.py # TACACS+ configuration file # This is a common file used by audisp-tacplus, libpam_tacplus, and @@ -56,4 +56,3 @@ user_homedir=1 service=shell protocol=ssh - diff --git a/data/templates/macsec/wpa_supplicant.conf.j2 b/data/templates/macsec/wpa_supplicant.conf.j2 index 1f7ba16f4..4bb762935 100644 --- a/data/templates/macsec/wpa_supplicant.conf.j2 +++ b/data/templates/macsec/wpa_supplicant.conf.j2 @@ -1,4 +1,4 @@ -### Autogenerated by interfaces-macsec.py ### +### Autogenerated by interfaces_macsec.py ### # see full documentation: # https://w1.fi/cgit/hostap/plain/wpa_supplicant/wpa_supplicant.conf diff --git a/data/templates/mdns-repeater/avahi-daemon.conf.j2 b/data/templates/mdns-repeater/avahi-daemon.conf.j2 index d562c048f..cc6495817 100644 --- a/data/templates/mdns-repeater/avahi-daemon.conf.j2 +++ b/data/templates/mdns-repeater/avahi-daemon.conf.j2 @@ -1,4 +1,4 @@ -### Autogenerated by service_mdns-repeater.py ### +### Autogenerated by service_mdns_repeater.py ### [server] use-ipv4={{ 'yes' if ip_version in ['ipv4', 'both'] else 'no' }} use-ipv6={{ 'yes' if ip_version in ['ipv6', 'both'] else 'no' }} diff --git a/data/templates/openvpn/auth.pw.j2 b/data/templates/openvpn/auth.pw.j2 index 218121062..9f9b31e7a 100644 --- a/data/templates/openvpn/auth.pw.j2 +++ b/data/templates/openvpn/auth.pw.j2 @@ -1,4 +1,4 @@ -{# Autogenerated by interfaces-openvpn.py #} +{# Autogenerated by interfaces_openvpn.py #} {% if authentication is vyos_defined %} {{ authentication.username }} {{ authentication.password }} diff --git a/data/templates/openvpn/client.conf.j2 b/data/templates/openvpn/client.conf.j2 index 2e327e4d3..9edcdc8ae 100644 --- a/data/templates/openvpn/client.conf.j2 +++ b/data/templates/openvpn/client.conf.j2 @@ -1,4 +1,4 @@ -### Autogenerated by interfaces-openvpn.py ### +### Autogenerated by interfaces_openvpn.py ### {% if ip is vyos_defined %} ifconfig-push {{ ip[0] }} {{ server_subnet[0] | netmask_from_cidr }} diff --git a/data/templates/openvpn/server.conf.j2 b/data/templates/openvpn/server.conf.j2 index c02411904..6ac525443 100644 --- a/data/templates/openvpn/server.conf.j2 +++ b/data/templates/openvpn/server.conf.j2 @@ -1,4 +1,4 @@ -### Autogenerated by interfaces-openvpn.py ### +### Autogenerated by interfaces_openvpn.py ### # # See https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage # for individual keyword definition diff --git a/data/templates/pppoe/peer.j2 b/data/templates/pppoe/peer.j2 index 2a99fcb2a..efe47f369 100644 --- a/data/templates/pppoe/peer.j2 +++ b/data/templates/pppoe/peer.j2 @@ -1,4 +1,4 @@ -### Autogenerated by interfaces-pppoe.py ### +### Autogenerated by interfaces_pppoe.py ### {{ '# ' ~ description if description is vyos_defined else '' }} # Require peer to provide the local IP address if it is not diff --git a/data/templates/rsyslog/logrotate.j2 b/data/templates/rsyslog/logrotate.j2 index cc535c48f..ea33fea4f 100644 --- a/data/templates/rsyslog/logrotate.j2 +++ b/data/templates/rsyslog/logrotate.j2 @@ -1,4 +1,4 @@ -### Autogenerated by system-syslog.py ### +### Autogenerated by system_syslog.py ### /var/log/messages { missingok notifempty diff --git a/data/templates/rsyslog/rsyslog.conf.j2 b/data/templates/rsyslog/rsyslog.conf.j2 index 8ca167803..97e0ee0b7 100644 --- a/data/templates/rsyslog/rsyslog.conf.j2 +++ b/data/templates/rsyslog/rsyslog.conf.j2 @@ -1,4 +1,4 @@ -### Autogenerated by system-syslog.py ### +### Autogenerated by system_syslog.py ### {% if global.marker is vyos_defined %} $ModLoad immark diff --git a/data/templates/salt-minion/minion.j2 b/data/templates/salt-minion/minion.j2 index f4001db64..a69438f0b 100644 --- a/data/templates/salt-minion/minion.j2 +++ b/data/templates/salt-minion/minion.j2 @@ -1,4 +1,4 @@ -### Autogenerated by salt-minion.py ### +### Autogenerated by service_salt-minion.py ### ##### Primary configuration settings ##### ########################################## diff --git a/data/templates/snmp/etc.snmp.conf.j2 b/data/templates/snmp/etc.snmp.conf.j2 index 8012cf6bb..c214b2266 100644 --- a/data/templates/snmp/etc.snmp.conf.j2 +++ b/data/templates/snmp/etc.snmp.conf.j2 @@ -1,4 +1,4 @@ -### Autogenerated by snmp.py ### +### Autogenerated by service_snmp.py ### {% if trap_source is vyos_defined %} clientaddr {{ trap_source }} {% endif %} diff --git a/data/templates/snmp/etc.snmpd.conf.j2 b/data/templates/snmp/etc.snmpd.conf.j2 index 3db8c4d7b..b1ceb0451 100644 --- a/data/templates/snmp/etc.snmpd.conf.j2 +++ b/data/templates/snmp/etc.snmpd.conf.j2 @@ -1,4 +1,4 @@ -### Autogenerated by snmp.py ### +### Autogenerated by service_snmp.py ### # non configurable defaults sysObjectID 1.3.6.1.4.1.44641 diff --git a/data/templates/snmp/override.conf.j2 b/data/templates/snmp/override.conf.j2 index 443ee64db..42dc7a9d2 100644 --- a/data/templates/snmp/override.conf.j2 +++ b/data/templates/snmp/override.conf.j2 @@ -10,4 +10,3 @@ ExecStart= 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/snmp/usr.snmpd.conf.j2 b/data/templates/snmp/usr.snmpd.conf.j2 index a713c1cec..189032bb0 100644 --- a/data/templates/snmp/usr.snmpd.conf.j2 +++ b/data/templates/snmp/usr.snmpd.conf.j2 @@ -1,4 +1,4 @@ -### Autogenerated by snmp.py ### +### Autogenerated by service_snmp.py ### {% if v3.user is vyos_defined %} {% for user, user_config in v3.user.items() %} {{ user_config.mode }}user {{ user }} diff --git a/data/templates/snmp/var.snmpd.conf.j2 b/data/templates/snmp/var.snmpd.conf.j2 index 012f33aeb..afab88abc 100644 --- a/data/templates/snmp/var.snmpd.conf.j2 +++ b/data/templates/snmp/var.snmpd.conf.j2 @@ -1,4 +1,4 @@ -### Autogenerated by snmp.py ### +### Autogenerated by service_snmp.py ### # user {% if v3 is vyos_defined %} {% if v3.user is vyos_defined %} diff --git a/data/templates/ssh/sshd_config.j2 b/data/templates/ssh/sshd_config.j2 index 422969ed8..650fd25e6 100644 --- a/data/templates/ssh/sshd_config.j2 +++ b/data/templates/ssh/sshd_config.j2 @@ -1,4 +1,4 @@ -### Autogenerated by ssh.py ### +### Autogenerated by service_ssh.py ### # https://linux.die.net/man/5/sshd_config diff --git a/data/templates/ssh/sshguard_config.j2 b/data/templates/ssh/sshguard_config.j2 index 58c6ad48d..2e7507416 100644 --- a/data/templates/ssh/sshguard_config.j2 +++ b/data/templates/ssh/sshguard_config.j2 @@ -1,4 +1,4 @@ -### Autogenerated by ssh.py ### +### Autogenerated by service_ssh.py ### {% if dynamic_protection is vyos_defined %} # Full path to backend executable (required, no default) diff --git a/data/templates/ssh/sshguard_whitelist.j2 b/data/templates/ssh/sshguard_whitelist.j2 index 47a950a2b..194fa29df 100644 --- a/data/templates/ssh/sshguard_whitelist.j2 +++ b/data/templates/ssh/sshguard_whitelist.j2 @@ -1,4 +1,4 @@ -### Autogenerated by ssh.py ### +### Autogenerated by service_ssh.py ### {% if dynamic_protection.allow_from is vyos_defined %} {% for address in dynamic_protection.allow_from %} diff --git a/data/templates/sstp-client/peer.j2 b/data/templates/sstp-client/peer.j2 index 745a09e14..d38e53f15 100644 --- a/data/templates/sstp-client/peer.j2 +++ b/data/templates/sstp-client/peer.j2 @@ -1,4 +1,4 @@ -### Autogenerated by interfaces-sstpc.py ### +### Autogenerated by interfaces_sstpc.py ### {{ '# ' ~ description if description is vyos_defined else '' }} # Require peer to provide the local IP address if it is not diff --git a/data/templates/system/proxy.j2 b/data/templates/system/proxy.j2 index 215c4c5c2..0737cd3f8 100644 --- a/data/templates/system/proxy.j2 +++ b/data/templates/system/proxy.j2 @@ -1,4 +1,4 @@ -# generated by system-proxy.py +### autogenerated by system_proxy.py ### {% if url is vyos_defined and port is vyos_defined %} {# remove http:// prefix so we can inject a username/password if present #} export http_proxy=http://{{ username ~ ':' ~ password ~ '@' if username is vyos_defined and password is vyos_defined }}{{ url | replace('http://', '') }}:{{ port }} diff --git a/data/templates/tftp-server/default.j2 b/data/templates/tftp-server/default.j2 index b2676e0aa..d9ce847de 100644 --- a/data/templates/tftp-server/default.j2 +++ b/data/templates/tftp-server/default.j2 @@ -1,5 +1,5 @@ {# j2lint: disable=jinja-variable-format #} -### Autogenerated by tftp_server.py ### +### Autogenerated by service_tftp-server.py ### DAEMON_ARGS="--listen --user tftp --address {{ listen_address }} {{ "--create --umask 000" if allow_upload is vyos_defined }} --secure {{ directory }}" {% if vrf is vyos_defined %} VRF_ARGS="ip vrf exec {{ vrf }}" diff --git a/data/templates/wifi/hostapd.conf.j2 b/data/templates/wifi/hostapd.conf.j2 index c3f32da72..83009242b 100644 --- a/data/templates/wifi/hostapd.conf.j2 +++ b/data/templates/wifi/hostapd.conf.j2 @@ -1,5 +1,5 @@ {# j2lint: disable=operator-enclosed-by-spaces #} -### Autogenerated by interfaces-wireless.py ### +### Autogenerated by interfaces_wireless.py ### {% if description is vyos_defined %} # Description: {{ description }} # User-friendly description of device; up to 32 octets encoded in UTF-8 diff --git a/data/templates/wifi/wpa_supplicant.conf.j2 b/data/templates/wifi/wpa_supplicant.conf.j2 index 01e0d632f..ac857a04a 100644 --- a/data/templates/wifi/wpa_supplicant.conf.j2 +++ b/data/templates/wifi/wpa_supplicant.conf.j2 @@ -1,4 +1,4 @@ -### Autogenerated by interfaces-macsec.py ### +### Autogenerated by interfaces_wireless.py ### # see full documentation: # https://w1.fi/cgit/hostap/plain/wpa_supplicant/wpa_supplicant.conf diff --git a/interface-definitions/bcast-relay.xml.in b/interface-definitions/bcast-relay.xml.in deleted file mode 100644 index e2993f3f3..000000000 --- a/interface-definitions/bcast-relay.xml.in +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - UDP broadcast relay service - 990 - - - #include - - - Unique ID for each UDP port to forward - - u32:1-99 - Broadcast relay instance ID - - - - - - - #include - - - Set source IP of forwarded packets, otherwise original senders address is used - - ipv4 - Optional source address for forwarded packets - - - - - - - #include - #include - #include - - - - - - - diff --git a/interface-definitions/cron.xml.in b/interface-definitions/cron.xml.in deleted file mode 100644 index 58dcf64ac..000000000 --- a/interface-definitions/cron.xml.in +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - Task scheduler settings - - - - - Scheduled task - - txt - Task name - - 999 - - - - - UNIX crontab time specification string - - - - - Execution interval - - <minutes> - Execution interval in minutes - - - <minutes>m - Execution interval in minutes - - - <hours>h - Execution interval in hours - - - <days>d - Execution interval in days - - - [1-9]([0-9]*)([mhd]{0,1}) - - - - - - Executable path and arguments - - - - - Path to executable - - - - - Arguments passed to the executable - - - - - - - - - - - diff --git a/interface-definitions/dhcp-relay.xml.in b/interface-definitions/dhcp-relay.xml.in deleted file mode 100644 index 42715c9bb..000000000 --- a/interface-definitions/dhcp-relay.xml.in +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - Host Configuration Protocol (DHCP) relay agent - 910 - - - #include - #include - - - Interface for DHCP Relay Agent to listen for requests - - - - - txt - Interface name - - - #include - - - - - - - Interface for DHCP Relay Agent forward requests out - - - - - txt - Interface name - - - #include - - - - - - - Relay options - - - - - Policy to discard packets that have reached specified hop-count - - u32:1-255 - Hop count - - - - - hop-count must be a value between 1 and 255 - - 10 - - - - Maximum packet size to send to a DHCPv4/BOOTP server - - u32:64-1400 - Maximum packet size - - - - - max-size must be a value between 64 and 1400 - - 576 - - - - Policy to handle incoming DHCPv4 packets which already contain relay agent options - - append replace forward discard - - - append - append own relay options to packet - - - replace - replace existing agent option field - - - forward - forward packet unchanged - - - discard - discard packet (default action if giaddr not set in packet) - - - (append|replace|forward|discard) - - - forward - - - - - - DHCP server address - - ipv4 - DHCP server IPv4 address - - - - - - - - - - - - diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in deleted file mode 100644 index 8aaeeb29d..000000000 --- a/interface-definitions/dhcp-server.xml.in +++ /dev/null @@ -1,456 +0,0 @@ - - - - - - - - Dynamic Host Configuration Protocol (DHCP) for DHCP server - 911 - - - #include - - - Dynamically update Domain Name System (RFC4702) - - - - - - DHCP failover configuration - - - #include - - - IPv4 remote address used for connectio - - ipv4 - IPv4 address of failover peer - - - - - - - - - Peer name used to identify connection - - [-_a-zA-Z0-9.]+ - - Invalid failover peer name. May only contain letters, numbers and .-_ - - - - - Failover hierarchy - - primary secondary - - - primary - Configure this server to be the primary node - - - secondary - Configure this server to be the secondary node - - - (primary|secondary) - - Invalid DHCP failover peer status - - - #include - #include - - - - - Updating /etc/hosts file (per client lease) - - - - #include - - - Name of DHCP shared network - - [-_a-zA-Z0-9.]+ - - Invalid shared network name. May only contain letters, numbers and .-_ - - - - - Option to make DHCP server authoritative for this physical network - - - - #include - #include - #include - #include - #include - #include - - - DHCP subnet for shared network - - ipv4net - IPv4 address and prefix length - - - - - Invalid IPv4 subnet definition - - - - - Bootstrap file name - - [[:ascii:]]{1,253} - - - - - - Server from which the initial boot file is to be loaded - - ipv4 - Bootfile server IPv4 address - - - hostname - Bootfile server FQDN - - - - - - - - - - Bootstrap file size - - u32:1-16 - Bootstrap file size in 512 byte blocks - - - - - - - #include - - - Specifies the clients subnet mask as per RFC 950. If unset, subnet declaration is used. - - u32:0-32 - DHCP client prefix length must be 0 to 32 - - - - - DHCP client prefix length must be 0 to 32 - - - - - IP address of default router - - ipv4 - Default router IPv4 address - - - - - - - #include - #include - #include - #include - - - IP address to exclude from DHCP lease range - - ipv4 - IPv4 address to exclude from lease range - - - - - - - - - - Enable IP forwarding on client - - - - - - Lease timeout in seconds - - u32 - DHCP lease time in seconds - - - - - DHCP lease time must be between 0 and 4294967295 (49 days) - - 86400 - - #include - - - IP address of POP3 server - - ipv4 - POP3 server IPv4 address - - - - - - - - - - Address for DHCP server identifier - - ipv4 - DHCP server identifier IPv4 address - - - - - - - - - IP address of SMTP server - - ipv4 - SMTP server IPv4 address - - - - - - - - - - DHCP lease range - - [-_a-zA-Z0-9.]+ - - Invalid range name, may only be alphanumeric, dot and hyphen - - - - - First IP address for DHCP lease range - - ipv4 - IPv4 start address of pool - - - - - - - - - Last IP address for DHCP lease range - - ipv4 - IPv4 end address of pool - - - - - - - - - - - Hostname for static mapping reservation - - - - Invalid static mapping hostname - - - #include - - - Fixed IP address of static mapping - - ipv4 - IPv4 address used in static mapping - - - - - - - #include - #include - - - - - Classless static route destination subnet - - ipv4net - IPv4 address and prefix length - - - - - - - - - IP address of router to be used to reach the destination subnet - - ipv4 - IPv4 address of router - - - - - - - - - - - Disable IPv4 on IPv6 only hosts (RFC 8925) - - u32 - Seconds - - - - - Seconds must be between 0 and 4294967295 (49 days) - - - - - TFTP server name - - ipv4 - TFTP server IPv4 address - - - hostname - TFTP server FQDN - - - - - - - - - - Client subnet offset in seconds from Coordinated Universal Time (UTC) - - [-]N - Time offset (number, may be negative) - - - -?[0-9]+ - - Invalid time offset value - - - - - IP address of time server - - ipv4 - Time server IPv4 address - - - - - - - - - - Time zone to send to clients. Uses RFC4833 options 100 and 101 - - - - - - - - - - - Vendor Specific Options - - - - - Ubiquiti specific parameters - - - - - Address of UniFi controller - - ipv4 - IP address of UniFi controller - - - - - - - - - - - - - IP address for Windows Internet Name Service (WINS) server - - ipv4 - WINS server IPv4 address - - - - - - - - - - Web Proxy Autodiscovery (WPAD) URL - - - - - - - - - - - diff --git a/interface-definitions/dhcpv6-relay.xml.in b/interface-definitions/dhcpv6-relay.xml.in deleted file mode 100644 index a80317609..000000000 --- a/interface-definitions/dhcpv6-relay.xml.in +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - DHCPv6 Relay Agent parameters - 900 - - - #include - - - Interface for DHCPv6 Relay Agent to listen for requests - - - - - - - - IPv6 address on listen-interface listen for requests on - - ipv6 - IPv6 address on listen interface - - - - - - - - - - - Maximum hop count for which requests will be processed - - u32:1-255 - Hop count - - - - - max-hop-count must be a value between 1 and 255 - - 10 - - - - Interface for DHCPv6 Relay Agent forward requests out - - - - - - - - IPv6 address to forward requests to - - ipv6 - IPv6 address of the DHCP server - - - - - - - - - - - - Option to set DHCPv6 interface-ID option - - - - - - - - diff --git a/interface-definitions/dhcpv6-server.xml.in b/interface-definitions/dhcpv6-server.xml.in deleted file mode 100644 index 10fdbf3f7..000000000 --- a/interface-definitions/dhcpv6-server.xml.in +++ /dev/null @@ -1,375 +0,0 @@ - - - - - - - DHCP for IPv6 (DHCPv6) server - 900 - - - #include - - - Additional global parameters for DHCPv6 server - - - #include - - - - - Preference of this DHCPv6 server compared with others - - u32:0-255 - DHCPv6 server preference (0-255) - - - - - Preference must be between 0 and 255 - - - - - DHCPv6 shared network name - - [-_a-zA-Z0-9.]+ - - Invalid DHCPv6 shared network name. May only contain letters, numbers and .-_ - - - #include - #include - - - Optional interface for this shared network to accept requests from - - - - - txt - Interface name - - - #include - - - - - - Common options to distribute to all clients, including stateless clients - - - - - Time (in seconds) that stateless clients should wait between refreshing the information they were given - - u32:1-4294967295 - DHCPv6 information refresh time - - - - - - - #include - #include - - - - - IPv6 DHCP subnet for this shared network - - ipv6net - IPv6 address and prefix length - - - - - - - - - Parameters setting ranges for assigning IPv6 addresses - - - - - IPv6 prefix defining range of addresses to assign - - ipv6net - IPv6 address and prefix length - - - - - - - - - - First in range of consecutive IPv6 addresses to assign - - ipv6 - IPv6 address - - - - - - - - - Last in range of consecutive IPv6 addresses - - ipv6 - IPv6 address - - - - - - - - - - - #include - #include - - - Parameters relating to the lease time - - - - - Default time (in seconds) that will be assigned to a lease - - u32:1-4294967295 - DHCPv6 valid lifetime - - - - - - - - - Maximum time (in seconds) that will be assigned to a lease - - u32:1-4294967295 - Maximum lease time in seconds - - - - - - - - - Minimum time (in seconds) that will be assigned to a lease - - u32:1-4294967295 - Minimum lease time in seconds - - - - - - - - - #include - - - NIS domain name for client to use - - [-_a-zA-Z0-9.]+ - - Invalid NIS domain name - - - - - IPv6 address of a NIS Server - - ipv6 - IPv6 address of NIS server - - - - - - - - - - NIS+ domain name for client to use - - [-_a-zA-Z0-9.]+ - - Invalid NIS+ domain name. May only contain letters, numbers and .-_ - - - - - IPv6 address of a NIS+ Server - - ipv6 - IPv6 address of NIS+ server - - - - - - - - - - Parameters relating to IPv6 prefix delegation - - - - - IPv6 prefix to be used in prefix delegation - - ipv6 - IPv6 prefix used in prefix delegation - - - - - - - - - Length in bits of prefix - - u32:32-64 - Prefix length (32-64) - - - - - Prefix length must be between 32 and 64 - - - - - Length in bits of prefixes to be delegated - - u32:32-64 - Delegated prefix length (32-64) - - - - - Delegated prefix length must be between 32 and 96 - - - - - - - - - IPv6 address of SIP server - - ipv6 - IPv6 address of SIP server - - - hostname - FQDN of SIP server - - - - - - - - - - - IPv6 address of an SNTP server for client to use - - - - - - - - - Hostname for static mapping reservation - - - - Invalid static mapping hostname - - - #include - #include - #include - - - Client IPv6 address for this static mapping - - ipv6 - IPv6 address for this static mapping - - - - - - - - - Client IPv6 prefix for this static mapping - - ipv6net - IPv6 prefix for this static mapping - - - - - - - - - - - Vendor Specific Options - - - - - Cisco specific parameters - - - - - TFTP server name - - ipv6 - TFTP server IPv6 address - - - - - - - - - - - - - - - - - - - - diff --git a/interface-definitions/dns-domain-name.xml.in b/interface-definitions/dns-domain-name.xml.in deleted file mode 100644 index b5b3692b1..000000000 --- a/interface-definitions/dns-domain-name.xml.in +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - - System Domain Name Servers (DNS) - 400 - - - - - ipv4 - Domain Name Server IPv4 address - - - ipv6 - Domain Name Server IPv6 address - - - txt - Use Domain Name Server from DHCP interface - - - - - #include - - - - - - - System host name (default: vyos) - - #include - - - - - - System domain name - - - - - - - - Domain Name Server (DNS) domain completion order - 400 - - - - Invalid domain name (RFC 1123 section 2).\nMay only contain letters, numbers and period. - - - - - - Map host names to addresses - 400 - - - - - Host name for static address mapping - - #include - - Host-name must be alphanumeric and can contain hyphens - - - - - Alias for this address - - .{1,63} - - invalid alias hostname, needs to be between 1 and 63 charactes - - - - - - IP Address - - ipv4 - IPv4 address - - - ipv6 - IPv6 address - - - - - - - - - - - - - - diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in deleted file mode 100644 index d296a6694..000000000 --- a/interface-definitions/dns-dynamic.xml.in +++ /dev/null @@ -1,213 +0,0 @@ - - - - - - - Domain Name System (DNS) related services - - - - - Dynamic DNS - 990 - - - - - Dynamic DNS configuration - - txt - Dynamic DNS service name - - - #include - - Dynamic DNS service name must be alphanumeric and can contain hyphens and underscores - - - #include - - - ddclient protocol used for Dynamic DNS service - - - - - - - - - - - Obtain IP address to send Dynamic DNS update for - - txt - Use interface to obtain the IP address - - - web - Use HTTP(S) web request to obtain the IP address - - - - web - - - #include - web - - - - - - Options when using HTTP(S) web request to obtain the IP address - - - #include - - - Pattern to skip from the HTTP(S) respose - - txt - Pattern to skip from the HTTP(S) respose to extract the external IP address - - - - - - - - IP address version to use - - _ipv4 - Use only IPv4 address - - - _ipv6 - Use only IPv6 address - - - both - Use both IPv4 and IPv6 address - - - ipv4 ipv6 both - - - (ipv[46]|both) - - IP Version must be literal 'ipv4', 'ipv6' or 'both' - - ipv4 - - - - Hostname to register with Dynamic DNS service - - #include - (\@|\*)[-.A-Za-z0-9]* - - Host-name must be alphanumeric, can contain hyphens and can be prefixed with '@' or '*' - - - - - - Remote Dynamic DNS server to send updates to - - ipv4 - IPv4 address of the remote server - - - ipv6 - IPv6 address of the remote server - - - hostname - Fully qualified domain name of the remote server - - - - - - Remote server must be IP address or fully qualified domain name - - - - - DNS zone to be updated - - txt - Name of DNS zone - - - - - - - #include - #include - - - File containing TSIG authentication key for RFC2136 nsupdate on remote DNS server - - filename - File in /config/auth directory - - - - - - - #include - - - Time in seconds to wait between update attempts - - u32:60-86400 - Time in seconds - - - - - Wait time must be between 60 and 86400 seconds - - - - - Time in seconds for the hostname to be marked expired in cache - - u32:300-2160000 - Time in seconds - - - - - Expiry time must be between 300 and 2160000 seconds - - - - - - - Interval in seconds to wait between Dynamic DNS updates - - u32:60-3600 - Time in seconds - - - - - Interval must be between 60 and 3600 seconds - - 300 - - #include - - - - - - - diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in deleted file mode 100644 index 5ca02acef..000000000 --- a/interface-definitions/dns-forwarding.xml.in +++ /dev/null @@ -1,703 +0,0 @@ - - - - - - - - Domain Name System (DNS) related services - - - - - DNS forwarding - 918 - - - - - DNS forwarding cache size - - u32:0-2147483647 - DNS forwarding cache size - - - - - - 10000 - - - - Interfaces whose DHCP client nameservers to forward requests to - - - - - - - - - Help to communicate between IPv6-only client and IPv4-only server - - ipv6net - IPv6 address and /96 only prefix length - - - - - - - - - DNSSEC mode - - off process-no-validate process log-fail validate - - - off - No DNSSEC processing whatsoever! - - - process-no-validate - Respond with DNSSEC records to clients that ask for it. No validation done at all! - - - process - Respond with DNSSEC records to clients that ask for it. Validation for clients that request it. - - - log-fail - Similar behaviour to process, but validate RRSIGs on responses and log bogus responses. - - - validate - Full blown DNSSEC validation. Send SERVFAIL to clients on bogus responses. - - - (off|process-no-validate|process|log-fail|validate) - - - process-no-validate - - - - Domain to forward to a custom DNS server - - txt - An absolute DNS domain name - - - - - - - #include - - - Add NTA (negative trust anchor) for this domain (must be set if the domain does not support DNSSEC) - - - - - - Set the "recursion desired" bit in requests to the upstream nameserver - - - - - - - - Domain to host authoritative records for - - txt - An absolute DNS domain name - - - - - - - - - DNS zone records - - - - - A record - - txt - A DNS name relative to the root record - - - @ - Root record - - - any - Wildcard record (any subdomain) - - - ([-_a-zA-Z0-9.]{1,63}|@|any)(?<!\.) - - - - - - IPv4 address - - ipv4 - IPv4 address - - - - - - - - #include - - 300 - - #include - - - - - AAAA record - - txt - A DNS name relative to the root record - - - @ - Root record - - - any - Wildcard record (any subdomain) - - - ([-_a-zA-Z0-9.]{1,63}|@|any)(?<!\.) - - - - - - IPv6 address - - ipv6 - IPv6 address - - - - - - - - #include - - 300 - - #include - - - - - CNAME record - - txt - A DNS name relative to the root record - - - @ - Root record - - - ([-_a-zA-Z0-9.]{1,63}|@)(?<!\.) - - - - - - Target DNS name - - name.example.com - Absolute DNS name - - - [-_a-zA-Z0-9.]{1,63}(?<!\.) - - - - #include - - 300 - - #include - - - - - MX record - - txt - A DNS name relative to the root record - - - @ - Root record - - - ([-_a-zA-Z0-9.]{1,63}|@)(?<!\.) - - - - - - Mail server - - name.example.com - Absolute DNS name - - - [-_a-zA-Z0-9.]{1,63}(?<!\.) - - - - - - Server priority - - u32:1-999 - Server priority (lower numbers are higher priority) - - - - - - 10 - - - - #include - - 300 - - #include - - - - - NS record - - txt - A DNS name relative to the root record - - - ([-_a-zA-Z0-9.]{1,63}|@)(?<!\.) - - - - - - Target DNS server authoritative for subdomain - - nsXX.example.com - Absolute DNS name - - - [-_a-zA-Z0-9.]{1,63}(?<!\.) - - - - #include - - 300 - - #include - - - - - PTR record - - txt - A DNS name relative to the root record - - - @ - Root record - - - ([-_a-zA-Z0-9.]{1,63}|@)(?<!\.) - - - - - - Target DNS name - - name.example.com - Absolute DNS name - - - [-_a-zA-Z0-9.]{1,63}(?<!\.) - - - - #include - - 300 - - #include - - - - - TXT record - - txt - A DNS name relative to the root record - - - @ - Root record - - - ([-_a-zA-Z0-9.]{1,63}|@)(?<!\.) - - - - - - Record contents - - txt - Record contents - - - - - #include - - 300 - - #include - - - - - SPF record - - txt - A DNS name relative to the root record - - - @ - Root record - - - ([-_a-zA-Z0-9.]{1,63}|@)(?<!\.) - - - - - - Record contents - - txt - Record contents - - - - #include - - 300 - - #include - - - - - SRV record - - txt - A DNS name relative to the root record - - - @ - Root record - - - ([-_a-zA-Z0-9.]{1,63}|@)(?<!\.) - - - - - - Service entry - - u32:0-65535 - Entry number - - - - - - - - - Server hostname - - name.example.com - Absolute DNS name - - - [-_a-zA-Z0-9.]{1,63}(?<!\.) - - - - - - Port number - - u32:0-65535 - TCP/UDP port number - - - - - - - - - Entry priority - - u32:0-65535 - Entry priority (lower numbers are higher priority) - - - - - - 10 - - - - Entry weight - - u32:0-65535 - Entry weight - - - - - - 0 - - - - #include - - 300 - - #include - - - - - NAPTR record - - txt - A DNS name relative to the root record - - - @ - Root record - - - ([-_a-zA-Z0-9.]{1,63}|@)(?<!\.) - - - - - - NAPTR rule - - u32:0-65535 - Rule number - - - - - - - - - Rule order - - u32:0-65535 - Rule order (lower order is evaluated first) - - - - - - - - - Rule preference - - u32:0-65535 - Rule preference - - - - - - 0 - - - - S flag - - - - - - A flag - - - - - - U flag - - - - - - P flag - - - - - - Service type - - [a-zA-Z][a-zA-Z0-9]{0,31}(\+[a-zA-Z][a-zA-Z0-9]{0,31})? - - - - - - Regular expression - - - - - Replacement DNS name - - name.example.com - Absolute DNS name - - - [-_a-zA-Z0-9.]{1,63}(?<!\.) - - - - - - #include - - 300 - - #include - - - - - #include - - - - - Do not use local /etc/hosts file in name resolution - - - - - - Makes the server authoritatively not aware of RFC1918 addresses - - - - - - Networks allowed to query this server - - ipv4net - IP address and prefix length - - - ipv6net - IPv6 address and prefix length - - - - - - - - #include - #include - - 53 - - - - Maximum amount of time negative entries are cached - - u32:0-7200 - Seconds to cache NXDOMAIN entries - - - - - - 3600 - - - - Number of milliseconds to wait for a remote authoritative server to respond - - u32:10-60000 - Network timeout in milliseconds - - - - - - 1500 - - #include - #include - - 0.0.0.0 :: - - - - Use system name servers - - - - - - - - - - diff --git a/interface-definitions/flow-accounting-conf.xml.in b/interface-definitions/flow-accounting-conf.xml.in deleted file mode 100644 index 40a9bb423..000000000 --- a/interface-definitions/flow-accounting-conf.xml.in +++ /dev/null @@ -1,437 +0,0 @@ - - - - - - - - Flow accounting settings - 990 - - - - - Buffer size - - u32 - Buffer size in MiB - - - - - - 10 - - - - Specifies the maximum number of bytes to capture for each packet - - u32:128-750 - Packet length in bytes - - - - - - 128 - - - - Enable egress flow accounting - - - - - - Disable in memory table plugin - - - - - - Syslog facility for flow-accounting - - auth authpriv cron daemon kern lpr mail mark news protocols security syslog user uucp local0 local1 local2 local3 local4 local5 local6 local7 all - - - auth - Authentication and authorization - - - authpriv - Non-system authorization - - - cron - Cron daemon - - - daemon - System daemons - - - kern - Kernel - - - lpr - Line printer spooler - - - mail - Mail subsystem - - - mark - Timestamp - - - news - USENET subsystem - - - protocols - Routing protocols (local7) - - - security - Authentication and authorization - - - syslog - Authentication and authorization - - - user - Application processes - - - uucp - UUCP subsystem - - - local0 - Local facility 0 - - - local1 - Local facility 1 - - - local2 - Local facility 2 - - - local3 - Local facility 3 - - - local4 - Local facility 4 - - - local5 - Local facility 5 - - - local6 - Local facility 6 - - - local7 - Local facility 7 - - - all - Authentication and authorization - - - (auth|authpriv|cron|daemon|kern|lpr|mail|mark|news|protocols|security|syslog|user|uucp|local0|local1|local2|local3|local4|local5|local6|local7|all) - - - - #include - - - NetFlow settings - - - - - NetFlow engine-id - - 0-255 or 0-255:0-255 - NetFlow engine-id for v5 - - - u32 - NetFlow engine-id for v9 / IPFIX - - - (\d|[1-9]\d{1,8}|[1-3]\d{9}|4[01]\d{8}|42[0-8]\d{7}|429[0-3]\d{6}|4294[0-8]\d{5}|42949[0-5]\d{4}|429496[0-6]\d{3}|4294967[01]\d{2}|42949672[0-8]\d|429496729[0-5])$|^(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]):(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]) - - - - - - NetFlow maximum flows - - u32 - NetFlow maximum flows - - - - - - - - - NetFlow sampling-rate - - u32 - Sampling rate (1 in N packets) - - - - - - - #include - - - NetFlow version to export - - 5 9 10 - - - 5 - NetFlow version 5 - - - 9 - NetFlow version 9 - - - 10 - Internet Protocol Flow Information Export (IPFIX) - - - 9 - - - - NetFlow destination server - - ipv4 - IPv4 server to export NetFlow - - - ipv6 - IPv6 server to export NetFlow - - - - - - - - - NetFlow port number - - u32:1025-65535 - NetFlow port number - - - - - - 2055 - - - - - - NetFlow timeout values - - - - - Expiry scan interval - - u32:0-2147483647 - Expiry scan interval - - - - - - 60 - - - - Generic flow timeout value - - u32:0-2147483647 - Generic flow timeout in seconds - - - - - - 3600 - - - - ICMP timeout value - - u32:0-2147483647 - ICMP timeout in seconds - - - - - - 300 - - - - Max active timeout value - - u32:0-2147483647 - Max active timeout in seconds - - - - - - 604800 - - - - TCP finish timeout value - - u32:0-2147483647 - TCP FIN timeout in seconds - - - - - - 300 - - - - TCP generic timeout value - - u32:0-2147483647 - TCP generic timeout in seconds - - - - - - 3600 - - - - TCP reset timeout value - - u32:0-2147483647 - TCP RST timeout in seconds - - - - - - 120 - - - - UDP timeout value - - u32:0-2147483647 - UDP timeout in seconds - - - - - - 300 - - - - - - - - sFlow settings - - - - - sFlow agent IPv4 address - - auto - - - - ipv4 - sFlow IPv4 agent address - - - - - - - - - sFlow sampling-rate - - u32 - Sampling rate (1 in N packets) - - - - - - - - - sFlow destination server - - ipv4 - IPv4 server to export sFlow - - - ipv6 - IPv6 server to export sFlow - - - - - - - - - sFlow port number - - u32:1025-65535 - sFlow port number - - - - - - 6343 - - - - #include - - - #include - - - - - diff --git a/interface-definitions/https.xml.in b/interface-definitions/https.xml.in deleted file mode 100644 index ca5a5f088..000000000 --- a/interface-definitions/https.xml.in +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - HTTPS configuration - 1001 - - - - - Identifier for virtual host - - [a-zA-Z0-9-_.:]{1,255} - - illegal characters in identifier or identifier longer than 255 characters - - - - - Address to listen for HTTPS requests - - - - - ipv4 - HTTPS IPv4 address - - - ipv6 - HTTPS IPv6 address - - - '*' - any - - - - \* - - - - #include - - 443 - - - - Server names: exact, wildcard, or regex - - - - #include - - - - - VyOS HTTP API configuration - - - - - HTTP API keys - - - - - HTTP API id - - - - - HTTP API plaintext key - - - - - - - - - Enforce strict path checking - - - - - - Debug - - - - - - - GraphQL support - - - - - Schema introspection - - - - - - GraphQL authentication - - - - - Authentication type - - key token - - - key - Use API keys - - - token - Use JWT token - - - (key|token) - - - key - - - - Token time to expire in seconds - - u32:60-31536000 - Token lifetime in seconds - - - - - - 3600 - - - - Length of shared secret in bytes - - u32:16-65535 - Byte length of generated shared secret - - - - - - 32 - - - - - - - - Set CORS options - - - - - Allow resource request from origin - - - - - - - - - - Restrict api proxy to subset of virtual hosts - - - - - Restrict proxy to virtual host(s) - - - - - - - - TLS certificates - - - #include - #include - - - Request or apply a letsencrypt certificate for domain-name - - - - - Domain name(s) for which to obtain certificate - - - - - - Email address to associate with certificate - - - - - - - #include - - - - - diff --git a/interface-definitions/igmp-proxy.xml.in b/interface-definitions/igmp-proxy.xml.in deleted file mode 100644 index 0eea85060..000000000 --- a/interface-definitions/igmp-proxy.xml.in +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - Internet Group Management Protocol (IGMP) proxy parameters - 740 - - - #include - - - Option to disable "quickleave" - - - - - - Interface for IGMP proxy - - - - - - - - Unicast source networks allowed for multicast traffic to be proxyed - - ipv4net - IPv4 network - - - - - - - - - - IGMP interface role - - upstream downstream disabled - - - upstream - Upstream interface (only 1 allowed) - - - downstream - Downstream interface(s) - - - disabled - Disabled interface - - - (upstream|downstream|disabled) - - - downstream - - - - TTL threshold - - u32:1-255 - TTL threshold for the interfaces - - - - - Threshold must be between 1 and 255 - - 1 - - - - Group to whitelist - - ipv4net - IPv4 network - - - - - - - - - - - - - - diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in deleted file mode 100644 index 86c4776b6..000000000 --- a/interface-definitions/interfaces-bonding.xml.in +++ /dev/null @@ -1,286 +0,0 @@ - - - - - - - Bonding Interface/Link Aggregation - 320 - - bond[0-9]+ - - Bonding interface must be named bondN - - bondN - Bonding interface name - - - - #include - - - ARP link monitoring parameters - - - - - ARP link monitoring interval - - u32 - Specifies the ARP link monitoring frequency in milliseconds - - - - - - - - - IP address used for ARP monitoring - - ipv4 - Specify IPv4 address of ARP requests when interval is enabled - - - - - - - - - - #include - #include - #include - #include - #include - #include - #include - - - EVPN Multihoming - - - - - Preference value used for designated forwarder (DF) election - - u32:1-65535 - DF Preference value - - - - - - - - - Ethernet segment identifier - - u32:1-16777215 - Local discriminator - - - txt - 10-byte ID - 00:11:22:33:44:55:AA:BB:CC:DD - - - - ([0-9A-Fa-f][0-9A-Fa-f]:){9}[0-9A-Fa-f][0-9A-Fa-f] - - - - - - Ethernet segment system MAC - - macaddr - MAC address - - - - - - - - - Uplink to the VXLAN core - - - - - - - - Bonding transmit hash policy - - layer2 layer2+3 layer3+4 encap2+3 encap3+4 - - - layer2 - use MAC addresses to generate the hash - - - layer2+3 - combine MAC address and IP address to make hash - - - layer3+4 - combine IP address and port to make hash - - - encap2+3 - combine encapsulated MAC address and IP address to make hash - - - encap3+4 - combine encapsulated IP address and port to make hash - - - (layer2\+3|layer3\+4|layer2|encap2\+3|encap3\+4) - - hash-policy must be layer2 layer2+3 layer3+4 encap2+3 or encap3+4 - - layer2 - - #include - #include - #include - - - Specifies the MII link monitoring frequency in milliseconds - - u32:0 - Disable MII link monitoring - - - u32:50-1000 - MII link monitoring frequency in milliseconds - - - - - - 100 - - - - Minimum number of member interfaces required up before enabling bond - - u32:0-16 - Minimum number of member interfaces required up before enabling bond - - - - - - 0 - - - - Rate in which we will ask our link partner to transmit LACPDU packets - - slow fast - - - slow - Request partner to transmit LACPDUs every 30 seconds - - - fast - Request partner to transmit LACPDUs every 1 second - - - (slow|fast) - - - slow - - - - Bonding mode - - 802.3ad active-backup broadcast round-robin transmit-load-balance adaptive-load-balance xor-hash - - - 802.3ad - IEEE 802.3ad Dynamic link aggregation - - - active-backup - Fault tolerant: only one slave in the bond is active - - - broadcast - Fault tolerant: transmits everything on all slave interfaces - - - round-robin - Load balance: transmit packets in sequential order - - - transmit-load-balance - Load balance: adapts based on transmit load and speed - - - adaptive-load-balance - Load balance: adapts based on transmit and receive plus ARP - - - xor-hash - Distribute based on MAC address - - - (802.3ad|active-backup|broadcast|round-robin|transmit-load-balance|adaptive-load-balance|xor-hash) - - mode must be 802.3ad, active-backup, broadcast, round-robin, transmit-load-balance, adaptive-load-balance, or xor - - 802.3ad - - - - Bridge member interfaces - - - - - Member interface name - - - - - txt - Interface name - - - #include - - - - - - - #include - - - Primary device interface - - - - - txt - Interface name - - - #include - - - - #include - #include - #include - - - - - diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in deleted file mode 100644 index db3762065..000000000 --- a/interface-definitions/interfaces-bridge.xml.in +++ /dev/null @@ -1,226 +0,0 @@ - - - - - - - Bridge Interface - 310 - - br[0-9]+ - - Bridge interface must be named brN - - brN - Bridge interface name - - - - #include - - - MAC address aging interval - - u32:0 - Disable MAC address learning (always flood) - - - u32:10-1000000 - MAC address aging time in seconds - - - - - - 300 - - #include - #include - #include - #include - #include - #include - #include - - - Forwarding delay - - u32:0-200 - Spanning Tree Protocol forwarding delay in seconds - - - - - Forwarding delay must be between 0 and 200 seconds - - 14 - - - - Hello packet advertisement interval - - u32:1-10 - Spanning Tree Protocol hello advertisement interval in seconds - - - - - Bridge Hello interval must be between 1 and 10 seconds - - 2 - - - - Internet Group Management Protocol (IGMP) and Multicast Listener Discovery (MLD) settings - - - - - Enable IGMP/MLD querier - - - - - - Enable IGMP/MLD snooping - - - - - - #include - #include - #include - #include - - - Enable VLAN aware bridge - - - - - - Interval at which neighbor bridges are removed - - u32:1-40 - Bridge maximum aging time in seconds - - - - - Bridge max aging value must be between 1 and 40 seconds - - 20 - - - - Bridge member interfaces - - - - - Member interface name - - - - - #include - - - - - - Specify VLAN id which should natively be present on the link - - u32:1-4094 - Virtual Local Area Network (VLAN) ID - - - - - VLAN ID must be between 1 and 4094 - - - - - Specify VLAN id which is allowed in this trunk interface - - <id> - VLAN id allowed to pass this interface - - - <idN>-<idM> - VLAN id range allowed on this interface (use '-' as delimiter) - - - - - not a valid VLAN ID value or range - - - - - - Bridge port cost - - u32:1-65535 - Path cost value for Spanning Tree Protocol - - - - - Path cost value must be between 1 and 65535 - - 100 - - - - Bridge port priority - - u32:0-63 - Bridge port priority - - - - - Port priority value must be between 0 and 63 - - 32 - - - - Port is isolated (also known as Private-VLAN) - - - - - - - - - - Priority for this bridge - - u32:0-65535 - Bridge priority - - - - - Bridge priority must be between 0 and 65535 (multiples of 4096) - - 32768 - - - - Enable spanning tree protocol - - - - #include - #include - - - - - diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in deleted file mode 100644 index 00784fcdf..000000000 --- a/interface-definitions/interfaces-dummy.xml.in +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - Dummy Interface - 300 - - dum[0-9]+ - - Dummy interface must be named dumN - - dumN - Dummy interface name - - - - #include - #include - #include - - - IPv4 routing parameters - - - #include - #include - - - - - IPv6 routing parameters - - - #include - - - IPv6 address configuration modes - - - #include - #include - - - - - #include - #include - #include - #include - #include - - - - - diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in deleted file mode 100644 index 5aaa7095c..000000000 --- a/interface-definitions/interfaces-ethernet.xml.in +++ /dev/null @@ -1,217 +0,0 @@ - - - - - Network interfaces - - - - - Ethernet Interface - 318 - - ethN - Ethernet interface name - - - ((eth|lan)[0-9]+|(eno|ens|enp|enx).+) - - Invalid Ethernet interface name - - - #include - #include - #include - #include - - - Disable Ethernet flow control (pause frames) - - - - #include - #include - - - Duplex mode - - auto half full - - - auto - Auto negotiation - - - half - Half duplex - - - full - Full duplex - - - (auto|half|full) - - duplex must be auto, half or full - - auto - - #include - #include - #include - #include - #include - #include - #include - - - Configurable offload options - - - - - Enable Generic Receive Offload - - - - - - Enable Generic Segmentation Offload - - - - - - Enable Hardware Flow Offload - - - - - - Enable Large Receive Offload - - - - - - Enable Receive Packet Steering - - - - - - Enable Receive Flow Steering - - - - - - Enable Scatter-Gather - - - - - - Enable TCP Segmentation Offloading - - - - - - - - Link speed - - auto 10 100 1000 2500 5000 10000 25000 40000 50000 100000 - - - auto - Auto negotiation - - - 10 - 10 Mbit/sec - - - 100 - 100 Mbit/sec - - - 1000 - 1 Gbit/sec - - - 2500 - 2.5 Gbit/sec - - - 5000 - 5 Gbit/sec - - - 10000 - 10 Gbit/sec - - - 25000 - 25 Gbit/sec - - - 40000 - 40 Gbit/sec - - - 50000 - 50 Gbit/sec - - - 100000 - 100 Gbit/sec - - - (auto|10|100|1000|2500|5000|10000|25000|40000|50000|100000) - - Speed must be auto, 10, 100, 1000, 2500, 5000, 10000, 25000, 40000, 50000 or 100000 - - auto - - - - Shared buffer between the device driver and NIC - - - - - RX ring buffer - - u32:80-16384 - ring buffer size - - - - - - - - - TX ring buffer - - u32:80-16384 - ring buffer size - - - - - - - - - #include - #include - #include - #include - - - - - diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in deleted file mode 100644 index 29b563a09..000000000 --- a/interface-definitions/interfaces-geneve.xml.in +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - Generic Network Virtualization Encapsulation (GENEVE) Interface - 460 - - gnv[0-9]+ - - GENEVE interface must be named gnvN - - gnvN - GENEVE interface name - - - - #include - #include - #include - #include - #include - #include - #include - - - GENEVE tunnel parameters - - - - - IPv4 specific tunnel parameters - - - #include - #include - #include - #include - - - - - IPv6 specific tunnel parameters - - - #include - - - - - #include - #include - #include - #include - - - - - diff --git a/interface-definitions/interfaces-input.xml.in b/interface-definitions/interfaces-input.xml.in deleted file mode 100644 index d90cf936f..000000000 --- a/interface-definitions/interfaces-input.xml.in +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - Input Functional Block (IFB) interface name - - 310 - - ifb[0-9]+ - - Input interface must be named ifbN - - ifbN - Input interface name - - - - #include - #include - #include - - - - - diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in deleted file mode 100644 index 1f0dd3d19..000000000 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - Layer 2 Tunnel Protocol Version 3 (L2TPv3) Interface - 485 - - l2tpeth[0-9]+ - - L2TPv3 interface must be named l2tpethN - - l2tpethN - L2TPv3 interface name - - - - #include - #include - - - UDP destination port for L2TPv3 tunnel - - u32:1-65535 - Numeric IP port - - - - - - 5000 - - #include - - - Encapsulation type - - udp ip - - - udp - UDP encapsulation - - - ip - IP encapsulation - - - (udp|ip) - - Encapsulation must be UDP or IP - - udp - - #include - #include - #include - #include - #include - - 1488 - - - - Peer session identifier - - u32:1-429496729 - L2TPv3 peer session identifier - - - - - - - - - Peer tunnel identifier - - u32:1-429496729 - L2TPv3 peer tunnel identifier - - - - - - - #include - - - Session identifier - - u32:1-429496729 - L2TPv3 session identifier - - - - - - - - - UDP source port for L2TPv3 tunnel - - u32:1-65535 - Numeric IP port - - - - - - 5000 - - - - Local tunnel identifier - - u32:1-429496729 - L2TPv3 local tunnel identifier - - - - - - - #include - - - - - diff --git a/interface-definitions/interfaces-loopback.xml.in b/interface-definitions/interfaces-loopback.xml.in deleted file mode 100644 index fe0944467..000000000 --- a/interface-definitions/interfaces-loopback.xml.in +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - Loopback Interface - 300 - - lo - - Loopback interface must be named lo - - lo - Loopback interface - - - - #include - #include - - - IPv4 routing parameters - - - #include - - - #include - #include - - - - - diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in deleted file mode 100644 index 766b0bede..000000000 --- a/interface-definitions/interfaces-macsec.xml.in +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - - MACsec Interface (802.1ae) - 461 - - macsec[0-9]+ - - MACsec interface must be named macsecN - - macsecN - MACsec interface name - - - - #include - #include - #include - #include - #include - #include - - - Security/Encryption Settings - - - - - Cipher suite used - - gcm-aes-128 gcm-aes-256 - - - gcm-aes-128 - Galois/Counter Mode of AES cipher with 128-bit key - - - gcm-aes-256 - Galois/Counter Mode of AES cipher with 256-bit key - - - (gcm-aes-128|gcm-aes-256) - - - - - - Enable optional MACsec encryption - - - - - - Use static keys for MACsec [static Secure Authentication Key (SAK) mode] - - - #include - - - MACsec peer name - - [^ ]{1,100} - - MACsec peer name exceeds limit of 100 characters - - - #include - #include - #include - - - - - - - MACsec Key Agreement protocol (MKA) - - - - - Secure Connectivity Association Key - - txt - 16-byte (128-bit) hex-string (32 hex-digits) for gcm-aes-128 or 32-byte (256-bit) hex-string (64 hex-digits) for gcm-aes-256 - - - [A-Fa-f0-9]{32} - [A-Fa-f0-9]{64} - - - - - - Secure Connectivity Association Key Name - - txt - 1..32-bytes (8..256 bit) hex-string (2..64 hex-digits) - - - [A-Fa-f0-9]{2,64} - - - - - - Priority of MACsec Key Agreement protocol (MKA) actor - - u32:0-255 - MACsec Key Agreement protocol (MKA) priority - - - - - - 255 - - - - - - IEEE 802.1X/MACsec replay protection window - - u32:0 - No replay window, strict check - - - u32:1-4294967295 - Number of packets that could be misordered - - - - - - - - - #include - #include - #include - - 1460 - - #include - #include - #include - - - - - diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in deleted file mode 100644 index b8b04334c..000000000 --- a/interface-definitions/interfaces-openvpn.xml.in +++ /dev/null @@ -1,809 +0,0 @@ - - - - - - - OpenVPN Tunnel Interface - 460 - - vtun[0-9]+ - - OpenVPN tunnel interface must be named vtunN - - vtunN - OpenVPN interface name - - - - #include - #include - - - OpenVPN interface device-type - - tun tap - - - tun - TUN device, required for OSI layer 3 - - - tap - TAP device, required for OSI layer 2 - - - (tun|tap) - - - tun - - #include - - - Data Encryption settings - - - - - Standard Data Encryption Algorithm - - none 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm - - - none - Disable encryption - - - 3des - DES algorithm with triple encryption - - - aes128 - AES algorithm with 128-bit key CBC - - - aes128gcm - AES algorithm with 128-bit key GCM - - - aes192 - AES algorithm with 192-bit key CBC - - - aes192gcm - AES algorithm with 192-bit key GCM - - - aes256 - AES algorithm with 256-bit key CBC - - - aes256gcm - AES algorithm with 256-bit key GCM - - - (none|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm) - - - - - - Cipher negotiation list for use in server or client mode - - none 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm - - - none - Disable encryption - - - 3des - DES algorithm with triple encryption - - - aes128 - AES algorithm with 128-bit key CBC - - - aes128gcm - AES algorithm with 128-bit key GCM - - - aes192 - AES algorithm with 192-bit key CBC - - - aes192gcm - AES algorithm with 192-bit key GCM - - - aes256 - AES algorithm with 256-bit key CBC - - - aes256gcm - AES algorithm with 256-bit key GCM - - - (none|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm) - - - - - - - #include - #include - #include - - - Hashing Algorithm - - md5 sha1 sha256 sha384 sha512 - - - md5 - MD5 algorithm - - - sha1 - SHA-1 algorithm - - - sha256 - SHA-256 algorithm - - - sha384 - SHA-384 algorithm - - - sha512 - SHA-512 algorithm - - - (md5|sha1|sha256|sha384|sha512) - - - - - - Keepalive helper options - - - - - Maximum number of keepalive packet failures - - u32:0-1000 - Maximum number of keepalive packet failures - - - - - - 60 - - - - Keepalive packet interval in seconds - - u32:0-600 - Keepalive packet interval (seconds) - - - - - - 10 - - - - - - Local IP address of tunnel (IPv4 or IPv6) - - - - - - - - Subnet-mask for local IP address of tunnel (IPv4 only) - - - - - - - - - - Local IP address to accept connections (all if not set) - - ipv4 - Local IPv4 address - - - ipv6 - Local IPv6 address - - - - - - - - - Local port number to accept connections - - u32:1-65535 - Numeric IP port - - - - - - - - - OpenVPN mode of operation - - site-to-site client server - - - site-to-site - Site-to-site mode - - - client - Client in client-server mode - - - server - Server in client-server mode - - - (site-to-site|client|server) - - - - - - Configurable offload options - - - - - Enable data channel offload on this interface - - - - - - - - Additional OpenVPN options. You must use the syntax of openvpn.conf in this text-field. Using this without proper knowledge may result in a crashed OpenVPN server. Check system log to look for errors. - - - - - - Do not close and reopen interface (TUN/TAP device) on client restarts - - - - - - OpenVPN communication protocol - - udp tcp-passive tcp-active - - - udp - UDP - - - tcp-passive - TCP and accepts connections passively - - - tcp-active - TCP and initiates connections actively - - - (udp|tcp-passive|tcp-active) - - - udp - - - - IP address of remote end of tunnel - - ipv4 - Remote end IPv4 address - - - ipv6 - Remote end IPv6 address - - - - - - - - - - Remote host to connect to (dynamic if not set) - - ipv4 - IPv4 address of remote host - - - ipv6 - IPv6 address of remote host - - - txt - Hostname of remote host - - - - - - - Remote port number to connect to - - u32:1-65535 - Numeric IP port - - - - - - - - - OpenVPN tunnel to be used as the default route - - - - - Tunnel endpoints are on the same subnet - - - - - - - Server-mode options - - - - - Client-specific settings - - name - Client common-name in the certificate - - - - #include - - - IP address of the client - - ipv4 - Client IPv4 address - - - ipv6 - Client IPv6 address - - - - - - - - - - Route to be pushed to the client - - ipv4net - IPv4 network and prefix length - - - ipv6net - IPv6 network and prefix length - - - - - - - - - - Subnet belonging to the client (iroute) - - ipv4net - IPv4 network and prefix length belonging to the client - - - ipv6net - IPv6 network and prefix length belonging to the client - - - - - - - - - - - - Pool of client IPv4 addresses - - - #include - - - First IP address in the pool - - - - - ipv4 - IPv4 address - - - - - - Last IP address in the pool - - - - - ipv4 - IPv4 address - - - - - - Subnet mask pushed to dynamic clients. If not set the server subnet mask will be used. Only used with topology subnet or device type tap. Not used with bridged interfaces. - - - - - ipv4 - IPv4 subnet mask - - - - - - - - Pool of client IPv6 addresses - - - - - Client IPv6 pool base address with optional prefix length - - ipv6net - Client IPv6 pool base address with optional prefix length (defaults: base = server subnet + 0x1000, prefix length = server prefix length) - - - - - - - #include - - - - - DNS suffix to be pushed to all clients - - txt - Domain Name Server suffix - - - - - - Number of maximum client connections - - u32:1-4096 - Number of concurrent clients - - - - - - - #include - - - Route to be pushed to all clients - - ipv4net - IPv4 network and prefix length - - - ipv6net - IPv6 network and prefix length - - - - - - - - - Set metric for this route - - u32:0-4294967295 - Metric for this route - - - - - - 0 - - - - - - Reject connections from clients that are not explicitly configured - - - - - - Server-mode subnet (from which client IPs are allocated) - - ipv4net - IPv4 network and prefix length - - - ipv6net - IPv6 network and prefix length - - - - - - - - - - Topology for clients - - net30 point-to-point subnet - - - net30 - net30 topology - - - point-to-point - Point-to-point topology - - - subnet - Subnet topology - - - (subnet|point-to-point|net30) - - - net30 - - - - multi-factor authentication - - - - - Time-based one-time passwords - - - - - Maximum allowed clock slop in seconds - - 1-65535 - Seconds - - - - - - 180 - - - - Time drift in seconds - - 1-65535 - Seconds - - - - - - 0 - - - - Step value for totp in seconds - - 1-65535 - Seconds - - - - - - 30 - - - - Number of digits to use for totp hash - - 1-65535 - Seconds - - - - - - 6 - - - - Expect password as result of a challenge response protocol - - disable enable - - - disable - Disable challenge-response - - - enable - Enable chalenge-response - - - (disable|enable) - - - enable - - - - - - - - - - Secret key shared with remote end of tunnel - - pki openvpn shared-secret - - - - - - Transport Layer Security (TLS) options - - - - - TLS shared secret key for tls-auth - - pki openvpn shared-secret - - - - #include - #include - - - Diffie Hellman parameters (server only) - - pki dh - - - - - - Static key to use to authenticate control channel - - pki openvpn shared-secret - - - - - - - Peer certificate SHA256 fingerprint - - [0-9a-fA-F]{2}:([0-9a-fA-F]{2}:){30}[0-9a-fA-F]{2} - - Peer certificate fingerprint must be a colon-separated SHA256 hex digest - - - - - Specify the minimum required TLS version - - 1.0 1.1 1.2 1.3 - - - 1.0 - TLS v1.0 - - - 1.1 - TLS v1.1 - - - 1.2 - TLS v1.2 - - - 1.3 - TLS v1.3 - - - (1.0|1.1|1.2|1.3) - - - - - - TLS negotiation role - - active passive - - - active - Initiate TLS negotiation actively - - - passive - Wait for incoming TLS connection - - - (active|passive) - - - - - - - - Use fast LZO compression on this TUN/TAP interface - - - - #include - #include - - - - - diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in deleted file mode 100644 index 4542b8b01..000000000 --- a/interface-definitions/interfaces-pppoe.xml.in +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - - Point-to-Point Protocol over Ethernet (PPPoE) Interface - 322 - - pppoe[0-9]+ - - PPPoE interface must be named pppoeN - - pppoeN - PPPoE dialer interface name - - - - #include - #include - #include - #include - #include - #include - #include - #include - - - Delay before disconnecting idle session (in seconds) - - u32:0-86400 - Idle timeout in seconds - - - - - Timeout must be in range 0 to 86400 - - - - - PPPoE RFC2516 host-uniq tag - - txt - Host-uniq tag as byte string in HEX - - - ([a-fA-F0-9][a-fA-F0-9]){1,18} - - Host-uniq must be specified as hex-adecimal byte-string (even number of HEX characters) - - - - - Delay before re-dial to the access concentrator when PPP session terminated by peer (in seconds) - - u32:0-86400 - Holdoff time in seconds - - - - - Holdoff must be in range 0 to 86400 - - 30 - - - - IPv4 routing parameters - - - #include - #include - #include - - - - - IPv6 routing parameters - - - - - IPv6 address configuration modes - - - #include - - - #include - #include - - - #include - - - IPv4 address of local end of the PPPoE link - - ipv4 - Address of local end of the PPPoE link - - - - - - - #include - #include - - 1492 - - - - Maximum Receive Unit (MRU) (default: MTU value) - - u32:128-16384 - Maximum Receive Unit in byte - - - - - MRU must be between 128 and 16384 - - - #include - - - IPv4 address of remote end of the PPPoE link - - ipv4 - Address of remote end of the PPPoE link - - - - - - - - - Service name, only connect to access concentrators advertising this - - [a-zA-Z0-9]+ - - Service name must be alphanumeric only - - - #include - #include - - - - - diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in deleted file mode 100644 index 5c73825c3..000000000 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - Pseudo Ethernet Interface (Macvlan) - 321 - - peth[0-9]+ - - Pseudo Ethernet interface must be named pethN - - pethN - Pseudo Ethernet interface name - - - - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - - - Receive mode (default: private) - - private vepa bridge passthru - - - private - No communication with other pseudo-devices - - - vepa - Virtual Ethernet Port Aggregator reflective relay - - - bridge - Simple bridge between pseudo-devices - - - passthru - Promicious mode passthrough of underlying device - - - (private|vepa|bridge|passthru) - - mode must be private, vepa, bridge or passthru - - private - - #include - #include - #include - #include - - - - - diff --git a/interface-definitions/interfaces-sstpc.xml.in b/interface-definitions/interfaces-sstpc.xml.in deleted file mode 100644 index b569e9bde..000000000 --- a/interface-definitions/interfaces-sstpc.xml.in +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - Secure Socket Tunneling Protocol (SSTP) client Interface - 460 - - sstpc[0-9]+ - - Secure Socket Tunneling Protocol interface must be named sstpcN - - sstpcN - Secure Socket Tunneling Protocol interface name - - - - #include - #include - #include - #include - #include - #include - #include - - 1452 - - #include - #include - - 443 - - - - Secure Sockets Layer (SSL) configuration - - - #include - - - #include - - - - - diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in deleted file mode 100644 index 58f95dddb..000000000 --- a/interface-definitions/interfaces-tunnel.xml.in +++ /dev/null @@ -1,281 +0,0 @@ - - - - - - - Tunnel interface - 380 - - tun[0-9]+ - - tunnel interface must be named tunN - - tunN - Tunnel interface name - - - - #include - #include - #include - #include - #include - - 1476 - - #include - #include - #include - #include - #include - - - 6rd network prefix - - ipv6 - IPv6 address and prefix length - - - - - - - - - 6rd relay prefix - - ipv4net - IPv4 prefix of interface for 6rd - - - - - - - - - Encapsulation of this tunnel interface - - erspan gre gretap ip6erspan ip6gre ip6gretap ip6ip6 ipip ipip6 sit - - - erspan - Encapsulated Remote Switched Port Analyzer - - - gre - Generic Routing Encapsulation (network layer) - - - gretap - Generic Routing Encapsulation (datalink layer) - - - ip6erspan - Encapsulated Remote Switched Port Analyzer over IPv6 - - - ip6gre - GRE over IPv6 (network layer) - - - ip6gretap - GRE over IPv6 (datalink layer) - - - ip6ip6 - IPv6 in IPv6 encapsulation - - - ipip - IPv4 in IPv4 encapsulation - - - ipip6 - IPv4 in IP6 encapsulation - - - sit - Simple Internet Transition (IPv6 in IPv4) - - - (erspan|gre|gretap|ip6erspan|ip6gre|ip6gretap|ip6ip6|ipip|ipip6|sit) - - Invalid encapsulation, must be one of: erspan, gre, gretap, ip6erspan, ip6gre, ip6gretap, ipip, sit, ipip6 or ip6ip6 - - - #include - - - Enable multicast operation over tunnel - - - - - - Tunnel parameters - - - - - ERSPAN tunnel parameters - - - - - Mirrored traffic direction - - ingress egress - - - ingress - Mirror ingress traffic - - - egress - Mirror egress traffic - - - (ingress|egress) - - - - - - Unique identifier of an ERSPAN engine within a system - - u32:0-1048575 - Unique identifier of an ERSPAN engine - - - - - - - - - ERSPAN version 1 index field - - u32:0-63 - Platform-depedent field for specifying port number and direction - - - - - - - - - Protocol version - - 1 2 - - - 1 - ERSPAN Type II - - - 2 - ERSPAN Type III - - - - - - 1 - - - - - - IPv4-specific tunnel parameters - - - - - Disable path MTU discovery - - - - - - Ignore the DF (don't fragment) bit - - - - #include - #include - #include - - 64 - - - - - - IPv6-specific tunnel parameters - - - - - Set fixed encapsulation limit - - none - - - u32:0-255 - Encapsulation limit - - - none - Disable encapsulation limit - - - (none) - - - Tunnel encaplimit must be 0-255 or none - - 4 - - #include - - - Hoplimit - - u32:0-255 - Hop limit - - - - - hop limit must be between 0-255 - - 64 - - - - Traffic class (Tclass) - - 0x0-0x0fffff - Traffic class, 'inherit' or hex value - - - (0x){0,1}(0?[0-9A-Fa-f]{1,2}) - - Must be 'inherit' or a number - - inherit - - - - - - #include - #include - - - - - diff --git a/interface-definitions/interfaces-virtual-ethernet.xml.in b/interface-definitions/interfaces-virtual-ethernet.xml.in deleted file mode 100644 index 0fc89efc0..000000000 --- a/interface-definitions/interfaces-virtual-ethernet.xml.in +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - Virtual Ethernet (veth) Interface - 300 - - veth[0-9]+ - - Virtual Ethernet interface must be named vethN - - vethN - Virtual Ethernet interface name - - - - #include - #include - #include - #include - #include - #include - #include - #include - #include - - - Virtual ethernet peer interface name - - interfaces virtual-ethernet - - - txt - Name of peer interface - - - veth[0-9]+ - - Virutal Ethernet interface must be named vethN - - - - - - - diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in deleted file mode 100644 index b116f7386..000000000 --- a/interface-definitions/interfaces-vti.xml.in +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - Virtual Tunnel Interface (XFRM) - 381 - - vti[0-9]+ - - VTI interface must be named vtiN - - vtiN - VTI interface name - - - - #include - #include - #include - #include - #include - #include - #include - #include - #include - - - - - diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in deleted file mode 100644 index 4461923d9..000000000 --- a/interface-definitions/interfaces-vxlan.xml.in +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - - Virtual Extensible LAN (VXLAN) Interface - 460 - - vxlan[0-9]+ - - VXLAN interface must be named vxlanN - - vxlanN - VXLAN interface name - - - - #include - #include - #include - - - Enable Generic Protocol extension (VXLAN-GPE) - - - - - - Multicast group address for VXLAN interface - - ipv4 - Multicast IPv4 group address - - - ipv6 - Multicast IPv6 group address - - - - - - Multicast IPv4/IPv6 address required - - - #include - #include - #include - #include - #include - - - VXLAN tunnel parameters - - - - - IPv4 specific tunnel parameters - - - #include - #include - #include - - 16 - - - - - - IPv6 specific tunnel parameters - - - #include - - - - - Use external control plane - - - - - - Do not add unknown addresses into forwarding database - - - - - - Enable neighbor discovery (ARP and ND) suppression - - - - - - Enable VNI filter support - - - - - - #include - - 4789 - - #include - #include - #include - #include - #include - #include - - - Configuring VLAN-to-VNI mappings for EVPN-VXLAN - - u32:0-4094 - Virtual Local Area Network (VLAN) ID - - - - - VLAN ID must be between 0 and 4094 - - - #include - - - - - - - diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in deleted file mode 100644 index 3c79cef28..000000000 --- a/interface-definitions/interfaces-wireguard.xml.in +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - WireGuard Interface - 379 - - wg[0-9]+ - - WireGuard interface must be named wgN - - wgN - WireGuard interface name - - - - #include - #include - #include - #include - #include - #include - - 1420 - - #include - #include - - - A 32-bit fwmark value set on all outgoing packets - - number - value which marks the packet for QoS/shaper - - - - - - 0 - - - - Base64 encoded private key - - [0-9a-zA-Z\+/]{43}= - - Key is not valid 44-character (32-bytes) base64 - - - - - peer alias - - [^ ]{1,100} - - peer alias too long (limit 100 characters) - - - #include - #include - - - base64 encoded public key - - [0-9a-zA-Z\+/]{43}= - - Key is not valid 44-character (32-bytes) base64 - - - - - base64 encoded preshared key - - [0-9a-zA-Z\+/]{43}= - - Key is not valid 44-character (32-bytes) base64 - - - - - IP addresses allowed to traverse the peer - - - - - - - - - IP address of tunnel endpoint - - ipv4 - IPv4 address of remote tunnel endpoint - - - ipv6 - IPv6 address of remote tunnel endpoint - - - - - - - - #include - - - Interval to send keepalive messages - - u32:1-65535 - Interval in seconds - - - - - - - - - #include - #include - #include - - - - - diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in deleted file mode 100644 index 88b858c07..000000000 --- a/interface-definitions/interfaces-wireless.xml.in +++ /dev/null @@ -1,832 +0,0 @@ - - - - - - - Wireless (WiFi/WLAN) Network Interface - 318 - - - - - wlan[0-9]+ - - Wireless interface must be named wlanN - - wlanN - Wireless (WiFi/WLAN) interface name - - - - #include - - - HT and VHT capabilities for your card - - - - - HT (High Throughput) settings - - - - - 40MHz intolerance, use 20MHz only! - - - - - - Enable WMM-PS unscheduled automatic power aave delivery [U-APSD] - - - - - - Supported channel set width - - ht20 ht40+ ht40- - - - ht20 - Supported channel set width both 20 MHz only - - - ht40+ - Supported channel set width both 20 MHz and 40 MHz with secondary channel above primary channel - - - ht40- - Supported channel set width both 20 MHz and 40 MHz with secondary channel below primary channel - - - (ht20|ht40\+|ht40-) - - - - - - - Enable HT-delayed block ack - - - - - - Enable DSSS_CCK-40 - - - - - - Enable HT-greenfield - - - - - - Enable LDPC coding capability - - - - - - Enable L-SIG TXOP protection capability - - - - - - Set maximum A-MSDU length - - 3839 7935 - - - 3839 - Set maximum A-MSDU length to 3839 octets - - - 7935 - Set maximum A-MSDU length to 7935 octets - - - (3839|7935) - - - - - - Short GI capabilities - - 20 40 - - - 20 - Short GI for 20 MHz - - - 40 - Short GI for 40 MHz - - - (20|40) - - - - - - - Spatial Multiplexing Power Save (SMPS) settings - - static dynamic - - - static - STATIC Spatial Multiplexing (SM) Power Save - - - dynamic - DYNAMIC Spatial Multiplexing (SM) Power Save - - - (static|dynamic) - - - - - - Support for sending and receiving PPDU using STBC (Space Time Block Coding) - - - - - Enable receiving PPDU using STBC (Space Time Block Coding) - - [1-3]+ - Number of spacial streams that can use RX STBC - - - [1-3]+ - - Invalid capability item - - - - - Enable sending PPDU using STBC (Space Time Block Coding) - - - - - - - - - - Require stations to support HT PHY (reject association if they do not) - - - - - - - - - Require stations to support VHT PHY (reject association if they do not) - - - - - - - - - VHT (Very High Throughput) settings - - - - - Number of antennas on this card - - u32:1-8 - Number of antennas for this card - - - - - - - - - Set if antenna pattern does not change during the lifetime of an association - - - - - - Beamforming capabilities - - single-user-beamformer single-user-beamformee multi-user-beamformer multi-user-beamformee - - - single-user-beamformer - Support for operation as single user beamformer - - - single-user-beamformee - Support for operation as single user beamformee - - - multi-user-beamformer - Support for operation as multi user beamformer - - - multi-user-beamformee - Support for operation as multi user beamformee - - - (single-user-beamformer|single-user-beamformee|multi-user-beamformer|multi-user-beamformee) - - - - - - - VHT operating channel center frequency - - - - - VHT operating channel center frequency - center freq 1 (for use with 80, 80+80 and 160 modes) - - u32:34-173 - 5Ghz (802.11 a/h/j/n/ac) center channel index (use 42 for primary 80MHz channel 36) - - - - - Channel center value must be between 34 and 173 - - - - - VHT operating channel center frequency - center freq 2 (for use with the 80+80 mode) - - u32:34-173 - 5Ghz (802.11 a/h/j/n/ac) center channel index (use 58 for primary 80MHz channel 52) - - - - - Channel center value must be between 34 and 173 - - - - - - - VHT operating Channel width - - 0 1 2 3 - - - 0 - 20 or 40 MHz channel width - - - 1 - 80 MHz channel width - - - 2 - 160 MHz channel width - - - 3 - 80+80 MHz channel width - - - - - - - - - Enable LDPC (Low Density Parity Check) coding capability - - - - - - VHT link adaptation capabilities - - unsolicited both - - - unsolicited - Station provides only unsolicited VHT MFB - - - both - Station can provide VHT MFB in response to VHT MRQ and unsolicited VHT MFB - - - (unsolicited|both) - - Invalid capability item - - - - - Set the maximum length of A-MPDU pre-EOF padding that the station can receive - - u32:0-7 - Maximum length of A-MPDU pre-EOF padding = 2 pow(13 + x) -1 octets - - - - - - - - - Increase Maximum MPDU length to 7991 or 11454 octets (otherwise: 3895 octets) - - 7991 11454 - - - 7991 - ncrease Maximum MPDU length to 7991 octets - - - 11454 - ncrease Maximum MPDU length to 11454 octets - - - (7991|11454) - - - - - - Short GI capabilities - - 80 160 - - - 80 - Short GI for 80 MHz - - - 160 - Short GI for 160 MHz - - - (80|160) - - - - - - - Support for sending and receiving PPDU using STBC (Space Time Block Coding) - - - - - Enable receiving PPDU using STBC (Space Time Block Coding) - - [1-4]+ - Number of spacial streams that can use RX STBC - - - [1-4]+ - - Invalid capability item - - - - - Enable sending PPDU using STBC (Space Time Block Coding) - - - - - - - - Enable VHT TXOP Power Save Mode - - - - - - Station supports receiving VHT variant HT Control field - - - - - - - - - - Wireless radio channel - - 0 - Automatic Channel Selection (ACS) - - - u32:1-14 - 2.4Ghz (802.11 b/g/n) Channel - - - u32:34-173 - 5Ghz (802.11 a/h/j/n/ac) Channel - - - - - - 0 - - - - Indicate country in which device is operating - - us eu jp de uk cn es fr ru - - - txt - ISO/IEC 3166-1 Country Code - - - [a-z][a-z] - - Invalid ISO/IEC 3166-1 Country Code - - - #include - #include - #include - - - Disable broadcast of SSID from access-point - - - - #include - #include - #include - - - Disassociate stations based on excessive transmission failures - - - - #include - #include - #include - - - Isolate stations on the AP so they cannot see each other - - - - #include - - - Maximum number of wireless radio stations. Excess stations will be rejected upon authentication request. - - u32:1-2007 - Number of allowed stations - - - - - Number of stations must be between 1 and 2007 - - - - - Management Frame Protection (MFP) according to IEEE 802.11w - - disabled optional required - - - disabled - no MFP - - - optional - MFP optional - - - required - MFP enforced - - - (disabled|optional|required) - - - disabled - - - - Wireless radio mode - - a b g n ac - - - a - 802.11a - 54 Mbits/sec - - - b - 802.11b - 11 Mbits/sec - - - g - 802.11g - 54 Mbits/sec - - - n - 802.11n - 600 Mbits/sec - - - ac - 802.11ac - 1300 Mbits/sec - - - (a|b|g|n|ac) - - - g - - #include - - - Wireless physical device - - - - - - - - phy0 - - - - Transmission power reduction in dBm - - u32:0-255 - TX power reduction in dBm - - - - - dBm value must be between 0 and 255 - - - - - Wireless security settings - - - - - Station MAC address based authentication - - - - - Select security operation mode - - accept deny - - - accept - Accept all clients unless found in deny list - - - deny - Deny all clients unless found in accept list - - - (accept|deny) - - - accept - - - - Accept station MAC address - - - #include - - - - - Deny station MAC address - - - #include - - - - - - - Wired Equivalent Privacy (WEP) parameters - - - - - WEP encryption key - - txt - Wired Equivalent Privacy key - - - ([a-fA-F0-9]{10}|[a-fA-F0-9]{26}|[a-fA-F0-9]{32}) - - Invalid WEP key - - - - - - - - Wifi Protected Access (WPA) parameters - - - - - Cipher suite for WPA unicast packets - - GCMP-256 GCMP CCMP-256 CCMP TKIP - - - GCMP-256 - AES in Galois/counter mode with 256-bit key - - - GCMP - AES in Galois/counter mode with 128-bit key - - - CCMP-256 - AES in Counter mode with CBC-MAC with 256-bit key - - - CCMP - AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] (supported on all WPA2 APs) - - - TKIP - Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] - - - (GCMP-256|GCMP|CCMP-256|CCMP|TKIP) - - Invalid cipher selection - - - - - - Cipher suite for WPA multicast and broadcast packets - - GCMP-256 GCMP CCMP-256 CCMP TKIP - - - GCMP-256 - AES in Galois/counter mode with 256-bit key - - - GCMP - AES in Galois/counter mode with 128-bit key - - - CCMP-256 - AES in Counter mode with CBC-MAC with 256-bit key - - - CCMP - AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] (supported on all WPA2 APs) - - - TKIP - Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] - - - (GCMP-256|GCMP|CCMP-256|CCMP|TKIP) - - Invalid group cipher selection - - - - - - WPA mode - - wpa wpa2 wpa+wpa2 wpa3 - - - wpa - WPA (IEEE 802.11i/D3.0) - - - wpa2 - WPA2 (full IEEE 802.11i/RSN) - - - wpa+wpa2 - Allow both WPA and WPA2 - - - (wpa|wpa2|wpa\+wpa2|wpa3) - - Unknown WPA mode - - wpa+wpa2 - - - - WPA personal shared pass phrase. If you are using special characters in the WPA passphrase then single quotes are required. - - txt - Passphrase of at least 8 but not more than 63 printable characters - - - .{8,63} - - Invalid WPA pass phrase, must be 8 to 63 printable characters! - - - #include - - - - - - - Enable RADIUS server to receive accounting info - - - - - - - - - - - - - - Wireless access-point service set identifier (SSID) - - .{1,32} - - Invalid SSID - - - - - Wireless device type for this interface - - access-point station monitor - - - access-point - Access-point forwards packets between other nodes - - - station - Connects to another access point - - - monitor - Passively monitor all packets on the frequency/channel - - - (access-point|station|monitor) - - Type must be access-point, station or monitor - - monitor - - #include - #include - #include - #include - - - - - diff --git a/interface-definitions/interfaces-wwan.xml.in b/interface-definitions/interfaces-wwan.xml.in deleted file mode 100644 index 5fa3be8db..000000000 --- a/interface-definitions/interfaces-wwan.xml.in +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - Wireless Modem (WWAN) Interface - 350 - - - - - wwan[0-9]+ - - Wireless Modem interface must be named wwanN - - wwanN - Wireless Wide Area Network interface name - - - - #include - - - Access Point Name (APN) - - - #include - #include - #include - #include - #include - #include - #include - #include - - 1430 - - #include - #include - #include - #include - #include - - - - - diff --git a/interface-definitions/interfaces_bonding.xml.in b/interface-definitions/interfaces_bonding.xml.in new file mode 100644 index 000000000..62ee0bdc7 --- /dev/null +++ b/interface-definitions/interfaces_bonding.xml.in @@ -0,0 +1,286 @@ + + + + + + + Bonding Interface/Link Aggregation + 320 + + bond[0-9]+ + + Bonding interface must be named bondN + + bondN + Bonding interface name + + + + #include + + + ARP link monitoring parameters + + + + + ARP link monitoring interval + + u32 + Specifies the ARP link monitoring frequency in milliseconds + + + + + + + + + IP address used for ARP monitoring + + ipv4 + Specify IPv4 address of ARP requests when interval is enabled + + + + + + + + + + #include + #include + #include + #include + #include + #include + #include + + + EVPN Multihoming + + + + + Preference value used for designated forwarder (DF) election + + u32:1-65535 + DF Preference value + + + + + + + + + Ethernet segment identifier + + u32:1-16777215 + Local discriminator + + + txt + 10-byte ID - 00:11:22:33:44:55:AA:BB:CC:DD + + + + ([0-9A-Fa-f][0-9A-Fa-f]:){9}[0-9A-Fa-f][0-9A-Fa-f] + + + + + + Ethernet segment system MAC + + macaddr + MAC address + + + + + + + + + Uplink to the VXLAN core + + + + + + + + Bonding transmit hash policy + + layer2 layer2+3 layer3+4 encap2+3 encap3+4 + + + layer2 + use MAC addresses to generate the hash + + + layer2+3 + combine MAC address and IP address to make hash + + + layer3+4 + combine IP address and port to make hash + + + encap2+3 + combine encapsulated MAC address and IP address to make hash + + + encap3+4 + combine encapsulated IP address and port to make hash + + + (layer2\+3|layer3\+4|layer2|encap2\+3|encap3\+4) + + hash-policy must be layer2 layer2+3 layer3+4 encap2+3 or encap3+4 + + layer2 + + #include + #include + #include + + + Specifies the MII link monitoring frequency in milliseconds + + u32:0 + Disable MII link monitoring + + + u32:50-1000 + MII link monitoring frequency in milliseconds + + + + + + 100 + + + + Minimum number of member interfaces required up before enabling bond + + u32:0-16 + Minimum number of member interfaces required up before enabling bond + + + + + + 0 + + + + Rate in which we will ask our link partner to transmit LACPDU packets + + slow fast + + + slow + Request partner to transmit LACPDUs every 30 seconds + + + fast + Request partner to transmit LACPDUs every 1 second + + + (slow|fast) + + + slow + + + + Bonding mode + + 802.3ad active-backup broadcast round-robin transmit-load-balance adaptive-load-balance xor-hash + + + 802.3ad + IEEE 802.3ad Dynamic link aggregation + + + active-backup + Fault tolerant: only one slave in the bond is active + + + broadcast + Fault tolerant: transmits everything on all slave interfaces + + + round-robin + Load balance: transmit packets in sequential order + + + transmit-load-balance + Load balance: adapts based on transmit load and speed + + + adaptive-load-balance + Load balance: adapts based on transmit and receive plus ARP + + + xor-hash + Distribute based on MAC address + + + (802.3ad|active-backup|broadcast|round-robin|transmit-load-balance|adaptive-load-balance|xor-hash) + + mode must be 802.3ad, active-backup, broadcast, round-robin, transmit-load-balance, adaptive-load-balance, or xor + + 802.3ad + + + + Bridge member interfaces + + + + + Member interface name + + + + + txt + Interface name + + + #include + + + + + + + #include + + + Primary device interface + + + + + txt + Interface name + + + #include + + + + #include + #include + #include + + + + + diff --git a/interface-definitions/interfaces_bridge.xml.in b/interface-definitions/interfaces_bridge.xml.in new file mode 100644 index 000000000..d4d277cfc --- /dev/null +++ b/interface-definitions/interfaces_bridge.xml.in @@ -0,0 +1,226 @@ + + + + + + + Bridge Interface + 310 + + br[0-9]+ + + Bridge interface must be named brN + + brN + Bridge interface name + + + + #include + + + MAC address aging interval + + u32:0 + Disable MAC address learning (always flood) + + + u32:10-1000000 + MAC address aging time in seconds + + + + + + 300 + + #include + #include + #include + #include + #include + #include + #include + + + Forwarding delay + + u32:0-200 + Spanning Tree Protocol forwarding delay in seconds + + + + + Forwarding delay must be between 0 and 200 seconds + + 14 + + + + Hello packet advertisement interval + + u32:1-10 + Spanning Tree Protocol hello advertisement interval in seconds + + + + + Bridge Hello interval must be between 1 and 10 seconds + + 2 + + + + Internet Group Management Protocol (IGMP) and Multicast Listener Discovery (MLD) settings + + + + + Enable IGMP/MLD querier + + + + + + Enable IGMP/MLD snooping + + + + + + #include + #include + #include + #include + + + Enable VLAN aware bridge + + + + + + Interval at which neighbor bridges are removed + + u32:1-40 + Bridge maximum aging time in seconds + + + + + Bridge max aging value must be between 1 and 40 seconds + + 20 + + + + Bridge member interfaces + + + + + Member interface name + + + + + #include + + + + + + Specify VLAN id which should natively be present on the link + + u32:1-4094 + Virtual Local Area Network (VLAN) ID + + + + + VLAN ID must be between 1 and 4094 + + + + + Specify VLAN id which is allowed in this trunk interface + + <id> + VLAN id allowed to pass this interface + + + <idN>-<idM> + VLAN id range allowed on this interface (use '-' as delimiter) + + + + + not a valid VLAN ID value or range + + + + + + Bridge port cost + + u32:1-65535 + Path cost value for Spanning Tree Protocol + + + + + Path cost value must be between 1 and 65535 + + 100 + + + + Bridge port priority + + u32:0-63 + Bridge port priority + + + + + Port priority value must be between 0 and 63 + + 32 + + + + Port is isolated (also known as Private-VLAN) + + + + + + + + + + Priority for this bridge + + u32:0-65535 + Bridge priority + + + + + Bridge priority must be between 0 and 65535 (multiples of 4096) + + 32768 + + + + Enable spanning tree protocol + + + + #include + #include + + + + + diff --git a/interface-definitions/interfaces_dummy.xml.in b/interface-definitions/interfaces_dummy.xml.in new file mode 100644 index 000000000..ef8ee78e7 --- /dev/null +++ b/interface-definitions/interfaces_dummy.xml.in @@ -0,0 +1,57 @@ + + + + + + + Dummy Interface + 300 + + dum[0-9]+ + + Dummy interface must be named dumN + + dumN + Dummy interface name + + + + #include + #include + #include + + + IPv4 routing parameters + + + #include + #include + + + + + IPv6 routing parameters + + + #include + + + IPv6 address configuration modes + + + #include + #include + + + + + #include + #include + #include + #include + #include + + + + + diff --git a/interface-definitions/interfaces_ethernet.xml.in b/interface-definitions/interfaces_ethernet.xml.in new file mode 100644 index 000000000..4e55bac7c --- /dev/null +++ b/interface-definitions/interfaces_ethernet.xml.in @@ -0,0 +1,217 @@ + + + + + Network interfaces + + + + + Ethernet Interface + 318 + + ethN + Ethernet interface name + + + ((eth|lan)[0-9]+|(eno|ens|enp|enx).+) + + Invalid Ethernet interface name + + + #include + #include + #include + #include + + + Disable Ethernet flow control (pause frames) + + + + #include + #include + + + Duplex mode + + auto half full + + + auto + Auto negotiation + + + half + Half duplex + + + full + Full duplex + + + (auto|half|full) + + duplex must be auto, half or full + + auto + + #include + #include + #include + #include + #include + #include + #include + + + Configurable offload options + + + + + Enable Generic Receive Offload + + + + + + Enable Generic Segmentation Offload + + + + + + Enable Hardware Flow Offload + + + + + + Enable Large Receive Offload + + + + + + Enable Receive Packet Steering + + + + + + Enable Receive Flow Steering + + + + + + Enable Scatter-Gather + + + + + + Enable TCP Segmentation Offloading + + + + + + + + Link speed + + auto 10 100 1000 2500 5000 10000 25000 40000 50000 100000 + + + auto + Auto negotiation + + + 10 + 10 Mbit/sec + + + 100 + 100 Mbit/sec + + + 1000 + 1 Gbit/sec + + + 2500 + 2.5 Gbit/sec + + + 5000 + 5 Gbit/sec + + + 10000 + 10 Gbit/sec + + + 25000 + 25 Gbit/sec + + + 40000 + 40 Gbit/sec + + + 50000 + 50 Gbit/sec + + + 100000 + 100 Gbit/sec + + + (auto|10|100|1000|2500|5000|10000|25000|40000|50000|100000) + + Speed must be auto, 10, 100, 1000, 2500, 5000, 10000, 25000, 40000, 50000 or 100000 + + auto + + + + Shared buffer between the device driver and NIC + + + + + RX ring buffer + + u32:80-16384 + ring buffer size + + + + + + + + + TX ring buffer + + u32:80-16384 + ring buffer size + + + + + + + + + #include + #include + #include + #include + + + + + diff --git a/interface-definitions/interfaces_geneve.xml.in b/interface-definitions/interfaces_geneve.xml.in new file mode 100644 index 000000000..c94113271 --- /dev/null +++ b/interface-definitions/interfaces_geneve.xml.in @@ -0,0 +1,60 @@ + + + + + + + Generic Network Virtualization Encapsulation (GENEVE) Interface + 460 + + gnv[0-9]+ + + GENEVE interface must be named gnvN + + gnvN + GENEVE interface name + + + + #include + #include + #include + #include + #include + #include + #include + + + GENEVE tunnel parameters + + + + + IPv4 specific tunnel parameters + + + #include + #include + #include + #include + + + + + IPv6 specific tunnel parameters + + + #include + + + + + #include + #include + #include + #include + + + + + diff --git a/interface-definitions/interfaces_input.xml.in b/interface-definitions/interfaces_input.xml.in new file mode 100644 index 000000000..771c47e42 --- /dev/null +++ b/interface-definitions/interfaces_input.xml.in @@ -0,0 +1,27 @@ + + + + + + + Input Functional Block (IFB) interface name + + 310 + + ifb[0-9]+ + + Input interface must be named ifbN + + ifbN + Input interface name + + + + #include + #include + #include + + + + + diff --git a/interface-definitions/interfaces_l2tpv3.xml.in b/interface-definitions/interfaces_l2tpv3.xml.in new file mode 100644 index 000000000..5f816c956 --- /dev/null +++ b/interface-definitions/interfaces_l2tpv3.xml.in @@ -0,0 +1,131 @@ + + + + + + + Layer 2 Tunnel Protocol Version 3 (L2TPv3) Interface + 485 + + l2tpeth[0-9]+ + + L2TPv3 interface must be named l2tpethN + + l2tpethN + L2TPv3 interface name + + + + #include + #include + + + UDP destination port for L2TPv3 tunnel + + u32:1-65535 + Numeric IP port + + + + + + 5000 + + #include + + + Encapsulation type + + udp ip + + + udp + UDP encapsulation + + + ip + IP encapsulation + + + (udp|ip) + + Encapsulation must be UDP or IP + + udp + + #include + #include + #include + #include + #include + + 1488 + + + + Peer session identifier + + u32:1-429496729 + L2TPv3 peer session identifier + + + + + + + + + Peer tunnel identifier + + u32:1-429496729 + L2TPv3 peer tunnel identifier + + + + + + + #include + + + Session identifier + + u32:1-429496729 + L2TPv3 session identifier + + + + + + + + + UDP source port for L2TPv3 tunnel + + u32:1-65535 + Numeric IP port + + + + + + 5000 + + + + Local tunnel identifier + + u32:1-429496729 + L2TPv3 local tunnel identifier + + + + + + + #include + + + + + diff --git a/interface-definitions/interfaces_loopback.xml.in b/interface-definitions/interfaces_loopback.xml.in new file mode 100644 index 000000000..09b4a00cf --- /dev/null +++ b/interface-definitions/interfaces_loopback.xml.in @@ -0,0 +1,35 @@ + + + + + + + Loopback Interface + 300 + + lo + + Loopback interface must be named lo + + lo + Loopback interface + + + + #include + #include + + + IPv4 routing parameters + + + #include + + + #include + #include + + + + + diff --git a/interface-definitions/interfaces_macsec.xml.in b/interface-definitions/interfaces_macsec.xml.in new file mode 100644 index 000000000..d825f8262 --- /dev/null +++ b/interface-definitions/interfaces_macsec.xml.in @@ -0,0 +1,153 @@ + + + + + + + MACsec Interface (802.1ae) + 461 + + macsec[0-9]+ + + MACsec interface must be named macsecN + + macsecN + MACsec interface name + + + + #include + #include + #include + #include + #include + #include + + + Security/Encryption Settings + + + + + Cipher suite used + + gcm-aes-128 gcm-aes-256 + + + gcm-aes-128 + Galois/Counter Mode of AES cipher with 128-bit key + + + gcm-aes-256 + Galois/Counter Mode of AES cipher with 256-bit key + + + (gcm-aes-128|gcm-aes-256) + + + + + + Enable optional MACsec encryption + + + + + + Use static keys for MACsec [static Secure Authentication Key (SAK) mode] + + + #include + + + MACsec peer name + + [^ ]{1,100} + + MACsec peer name exceeds limit of 100 characters + + + #include + #include + #include + + + + + + + MACsec Key Agreement protocol (MKA) + + + + + Secure Connectivity Association Key + + txt + 16-byte (128-bit) hex-string (32 hex-digits) for gcm-aes-128 or 32-byte (256-bit) hex-string (64 hex-digits) for gcm-aes-256 + + + [A-Fa-f0-9]{32} + [A-Fa-f0-9]{64} + + + + + + Secure Connectivity Association Key Name + + txt + 1..32-bytes (8..256 bit) hex-string (2..64 hex-digits) + + + [A-Fa-f0-9]{2,64} + + + + + + Priority of MACsec Key Agreement protocol (MKA) actor + + u32:0-255 + MACsec Key Agreement protocol (MKA) priority + + + + + + 255 + + + + + + IEEE 802.1X/MACsec replay protection window + + u32:0 + No replay window, strict check + + + u32:1-4294967295 + Number of packets that could be misordered + + + + + + + + + #include + #include + #include + + 1460 + + #include + #include + #include + + + + + diff --git a/interface-definitions/interfaces_openvpn.xml.in b/interface-definitions/interfaces_openvpn.xml.in new file mode 100644 index 000000000..addf3c1ab --- /dev/null +++ b/interface-definitions/interfaces_openvpn.xml.in @@ -0,0 +1,809 @@ + + + + + + + OpenVPN Tunnel Interface + 460 + + vtun[0-9]+ + + OpenVPN tunnel interface must be named vtunN + + vtunN + OpenVPN interface name + + + + #include + #include + + + OpenVPN interface device-type + + tun tap + + + tun + TUN device, required for OSI layer 3 + + + tap + TAP device, required for OSI layer 2 + + + (tun|tap) + + + tun + + #include + + + Data Encryption settings + + + + + Standard Data Encryption Algorithm + + none 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm + + + none + Disable encryption + + + 3des + DES algorithm with triple encryption + + + aes128 + AES algorithm with 128-bit key CBC + + + aes128gcm + AES algorithm with 128-bit key GCM + + + aes192 + AES algorithm with 192-bit key CBC + + + aes192gcm + AES algorithm with 192-bit key GCM + + + aes256 + AES algorithm with 256-bit key CBC + + + aes256gcm + AES algorithm with 256-bit key GCM + + + (none|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm) + + + + + + Cipher negotiation list for use in server or client mode + + none 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm + + + none + Disable encryption + + + 3des + DES algorithm with triple encryption + + + aes128 + AES algorithm with 128-bit key CBC + + + aes128gcm + AES algorithm with 128-bit key GCM + + + aes192 + AES algorithm with 192-bit key CBC + + + aes192gcm + AES algorithm with 192-bit key GCM + + + aes256 + AES algorithm with 256-bit key CBC + + + aes256gcm + AES algorithm with 256-bit key GCM + + + (none|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm) + + + + + + + #include + #include + #include + + + Hashing Algorithm + + md5 sha1 sha256 sha384 sha512 + + + md5 + MD5 algorithm + + + sha1 + SHA-1 algorithm + + + sha256 + SHA-256 algorithm + + + sha384 + SHA-384 algorithm + + + sha512 + SHA-512 algorithm + + + (md5|sha1|sha256|sha384|sha512) + + + + + + Keepalive helper options + + + + + Maximum number of keepalive packet failures + + u32:0-1000 + Maximum number of keepalive packet failures + + + + + + 60 + + + + Keepalive packet interval in seconds + + u32:0-600 + Keepalive packet interval (seconds) + + + + + + 10 + + + + + + Local IP address of tunnel (IPv4 or IPv6) + + + + + + + + Subnet-mask for local IP address of tunnel (IPv4 only) + + + + + + + + + + Local IP address to accept connections (all if not set) + + ipv4 + Local IPv4 address + + + ipv6 + Local IPv6 address + + + + + + + + + Local port number to accept connections + + u32:1-65535 + Numeric IP port + + + + + + + + + OpenVPN mode of operation + + site-to-site client server + + + site-to-site + Site-to-site mode + + + client + Client in client-server mode + + + server + Server in client-server mode + + + (site-to-site|client|server) + + + + + + Configurable offload options + + + + + Enable data channel offload on this interface + + + + + + + + Additional OpenVPN options. You must use the syntax of openvpn.conf in this text-field. Using this without proper knowledge may result in a crashed OpenVPN server. Check system log to look for errors. + + + + + + Do not close and reopen interface (TUN/TAP device) on client restarts + + + + + + OpenVPN communication protocol + + udp tcp-passive tcp-active + + + udp + UDP + + + tcp-passive + TCP and accepts connections passively + + + tcp-active + TCP and initiates connections actively + + + (udp|tcp-passive|tcp-active) + + + udp + + + + IP address of remote end of tunnel + + ipv4 + Remote end IPv4 address + + + ipv6 + Remote end IPv6 address + + + + + + + + + + Remote host to connect to (dynamic if not set) + + ipv4 + IPv4 address of remote host + + + ipv6 + IPv6 address of remote host + + + txt + Hostname of remote host + + + + + + + Remote port number to connect to + + u32:1-65535 + Numeric IP port + + + + + + + + + OpenVPN tunnel to be used as the default route + + + + + Tunnel endpoints are on the same subnet + + + + + + + Server-mode options + + + + + Client-specific settings + + name + Client common-name in the certificate + + + + #include + + + IP address of the client + + ipv4 + Client IPv4 address + + + ipv6 + Client IPv6 address + + + + + + + + + + Route to be pushed to the client + + ipv4net + IPv4 network and prefix length + + + ipv6net + IPv6 network and prefix length + + + + + + + + + + Subnet belonging to the client (iroute) + + ipv4net + IPv4 network and prefix length belonging to the client + + + ipv6net + IPv6 network and prefix length belonging to the client + + + + + + + + + + + + Pool of client IPv4 addresses + + + #include + + + First IP address in the pool + + + + + ipv4 + IPv4 address + + + + + + Last IP address in the pool + + + + + ipv4 + IPv4 address + + + + + + Subnet mask pushed to dynamic clients. If not set the server subnet mask will be used. Only used with topology subnet or device type tap. Not used with bridged interfaces. + + + + + ipv4 + IPv4 subnet mask + + + + + + + + Pool of client IPv6 addresses + + + + + Client IPv6 pool base address with optional prefix length + + ipv6net + Client IPv6 pool base address with optional prefix length (defaults: base = server subnet + 0x1000, prefix length = server prefix length) + + + + + + + #include + + + + + DNS suffix to be pushed to all clients + + txt + Domain Name Server suffix + + + + + + Number of maximum client connections + + u32:1-4096 + Number of concurrent clients + + + + + + + #include + + + Route to be pushed to all clients + + ipv4net + IPv4 network and prefix length + + + ipv6net + IPv6 network and prefix length + + + + + + + + + Set metric for this route + + u32:0-4294967295 + Metric for this route + + + + + + 0 + + + + + + Reject connections from clients that are not explicitly configured + + + + + + Server-mode subnet (from which client IPs are allocated) + + ipv4net + IPv4 network and prefix length + + + ipv6net + IPv6 network and prefix length + + + + + + + + + + Topology for clients + + net30 point-to-point subnet + + + net30 + net30 topology + + + point-to-point + Point-to-point topology + + + subnet + Subnet topology + + + (subnet|point-to-point|net30) + + + net30 + + + + multi-factor authentication + + + + + Time-based one-time passwords + + + + + Maximum allowed clock slop in seconds + + 1-65535 + Seconds + + + + + + 180 + + + + Time drift in seconds + + 1-65535 + Seconds + + + + + + 0 + + + + Step value for totp in seconds + + 1-65535 + Seconds + + + + + + 30 + + + + Number of digits to use for totp hash + + 1-65535 + Seconds + + + + + + 6 + + + + Expect password as result of a challenge response protocol + + disable enable + + + disable + Disable challenge-response + + + enable + Enable chalenge-response + + + (disable|enable) + + + enable + + + + + + + + + + Secret key shared with remote end of tunnel + + pki openvpn shared-secret + + + + + + Transport Layer Security (TLS) options + + + + + TLS shared secret key for tls-auth + + pki openvpn shared-secret + + + + #include + #include + + + Diffie Hellman parameters (server only) + + pki dh + + + + + + Static key to use to authenticate control channel + + pki openvpn shared-secret + + + + + + + Peer certificate SHA256 fingerprint + + [0-9a-fA-F]{2}:([0-9a-fA-F]{2}:){30}[0-9a-fA-F]{2} + + Peer certificate fingerprint must be a colon-separated SHA256 hex digest + + + + + Specify the minimum required TLS version + + 1.0 1.1 1.2 1.3 + + + 1.0 + TLS v1.0 + + + 1.1 + TLS v1.1 + + + 1.2 + TLS v1.2 + + + 1.3 + TLS v1.3 + + + (1.0|1.1|1.2|1.3) + + + + + + TLS negotiation role + + active passive + + + active + Initiate TLS negotiation actively + + + passive + Wait for incoming TLS connection + + + (active|passive) + + + + + + + + Use fast LZO compression on this TUN/TAP interface + + + + #include + #include + + + + + diff --git a/interface-definitions/interfaces_pppoe.xml.in b/interface-definitions/interfaces_pppoe.xml.in new file mode 100644 index 000000000..56660bc15 --- /dev/null +++ b/interface-definitions/interfaces_pppoe.xml.in @@ -0,0 +1,153 @@ + + + + + + + Point-to-Point Protocol over Ethernet (PPPoE) Interface + 322 + + pppoe[0-9]+ + + PPPoE interface must be named pppoeN + + pppoeN + PPPoE dialer interface name + + + + #include + #include + #include + #include + #include + #include + #include + #include + + + Delay before disconnecting idle session (in seconds) + + u32:0-86400 + Idle timeout in seconds + + + + + Timeout must be in range 0 to 86400 + + + + + PPPoE RFC2516 host-uniq tag + + txt + Host-uniq tag as byte string in HEX + + + ([a-fA-F0-9][a-fA-F0-9]){1,18} + + Host-uniq must be specified as hex-adecimal byte-string (even number of HEX characters) + + + + + Delay before re-dial to the access concentrator when PPP session terminated by peer (in seconds) + + u32:0-86400 + Holdoff time in seconds + + + + + Holdoff must be in range 0 to 86400 + + 30 + + + + IPv4 routing parameters + + + #include + #include + #include + + + + + IPv6 routing parameters + + + + + IPv6 address configuration modes + + + #include + + + #include + #include + + + #include + + + IPv4 address of local end of the PPPoE link + + ipv4 + Address of local end of the PPPoE link + + + + + + + #include + #include + + 1492 + + + + Maximum Receive Unit (MRU) (default: MTU value) + + u32:128-16384 + Maximum Receive Unit in byte + + + + + MRU must be between 128 and 16384 + + + #include + + + IPv4 address of remote end of the PPPoE link + + ipv4 + Address of remote end of the PPPoE link + + + + + + + + + Service name, only connect to access concentrators advertising this + + [a-zA-Z0-9]+ + + Service name must be alphanumeric only + + + #include + #include + + + + + diff --git a/interface-definitions/interfaces_pseudo-ethernet.xml.in b/interface-definitions/interfaces_pseudo-ethernet.xml.in new file mode 100644 index 000000000..031af3563 --- /dev/null +++ b/interface-definitions/interfaces_pseudo-ethernet.xml.in @@ -0,0 +1,68 @@ + + + + + + + Pseudo Ethernet Interface (Macvlan) + 321 + + peth[0-9]+ + + Pseudo Ethernet interface must be named pethN + + pethN + Pseudo Ethernet interface name + + + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + + Receive mode (default: private) + + private vepa bridge passthru + + + private + No communication with other pseudo-devices + + + vepa + Virtual Ethernet Port Aggregator reflective relay + + + bridge + Simple bridge between pseudo-devices + + + passthru + Promicious mode passthrough of underlying device + + + (private|vepa|bridge|passthru) + + mode must be private, vepa, bridge or passthru + + private + + #include + #include + #include + #include + + + + + diff --git a/interface-definitions/interfaces_sstpc.xml.in b/interface-definitions/interfaces_sstpc.xml.in new file mode 100644 index 000000000..b7c49446f --- /dev/null +++ b/interface-definitions/interfaces_sstpc.xml.in @@ -0,0 +1,47 @@ + + + + + + + Secure Socket Tunneling Protocol (SSTP) client Interface + 460 + + sstpc[0-9]+ + + Secure Socket Tunneling Protocol interface must be named sstpcN + + sstpcN + Secure Socket Tunneling Protocol interface name + + + + #include + #include + #include + #include + #include + #include + #include + + 1452 + + #include + #include + + 443 + + + + Secure Sockets Layer (SSL) configuration + + + #include + + + #include + + + + + diff --git a/interface-definitions/interfaces_tunnel.xml.in b/interface-definitions/interfaces_tunnel.xml.in new file mode 100644 index 000000000..99d9b34c6 --- /dev/null +++ b/interface-definitions/interfaces_tunnel.xml.in @@ -0,0 +1,281 @@ + + + + + + + Tunnel interface + 380 + + tun[0-9]+ + + tunnel interface must be named tunN + + tunN + Tunnel interface name + + + + #include + #include + #include + #include + #include + + 1476 + + #include + #include + #include + #include + #include + + + 6rd network prefix + + ipv6 + IPv6 address and prefix length + + + + + + + + + 6rd relay prefix + + ipv4net + IPv4 prefix of interface for 6rd + + + + + + + + + Encapsulation of this tunnel interface + + erspan gre gretap ip6erspan ip6gre ip6gretap ip6ip6 ipip ipip6 sit + + + erspan + Encapsulated Remote Switched Port Analyzer + + + gre + Generic Routing Encapsulation (network layer) + + + gretap + Generic Routing Encapsulation (datalink layer) + + + ip6erspan + Encapsulated Remote Switched Port Analyzer over IPv6 + + + ip6gre + GRE over IPv6 (network layer) + + + ip6gretap + GRE over IPv6 (datalink layer) + + + ip6ip6 + IPv6 in IPv6 encapsulation + + + ipip + IPv4 in IPv4 encapsulation + + + ipip6 + IPv4 in IP6 encapsulation + + + sit + Simple Internet Transition (IPv6 in IPv4) + + + (erspan|gre|gretap|ip6erspan|ip6gre|ip6gretap|ip6ip6|ipip|ipip6|sit) + + Invalid encapsulation, must be one of: erspan, gre, gretap, ip6erspan, ip6gre, ip6gretap, ipip, sit, ipip6 or ip6ip6 + + + #include + + + Enable multicast operation over tunnel + + + + + + Tunnel parameters + + + + + ERSPAN tunnel parameters + + + + + Mirrored traffic direction + + ingress egress + + + ingress + Mirror ingress traffic + + + egress + Mirror egress traffic + + + (ingress|egress) + + + + + + Unique identifier of an ERSPAN engine within a system + + u32:0-1048575 + Unique identifier of an ERSPAN engine + + + + + + + + + ERSPAN version 1 index field + + u32:0-63 + Platform-depedent field for specifying port number and direction + + + + + + + + + Protocol version + + 1 2 + + + 1 + ERSPAN Type II + + + 2 + ERSPAN Type III + + + + + + 1 + + + + + + IPv4-specific tunnel parameters + + + + + Disable path MTU discovery + + + + + + Ignore the DF (don't fragment) bit + + + + #include + #include + #include + + 64 + + + + + + IPv6-specific tunnel parameters + + + + + Set fixed encapsulation limit + + none + + + u32:0-255 + Encapsulation limit + + + none + Disable encapsulation limit + + + (none) + + + Tunnel encaplimit must be 0-255 or none + + 4 + + #include + + + Hoplimit + + u32:0-255 + Hop limit + + + + + hop limit must be between 0-255 + + 64 + + + + Traffic class (Tclass) + + 0x0-0x0fffff + Traffic class, 'inherit' or hex value + + + (0x){0,1}(0?[0-9A-Fa-f]{1,2}) + + Must be 'inherit' or a number + + inherit + + + + + + #include + #include + + + + + diff --git a/interface-definitions/interfaces_virtual-ethernet.xml.in b/interface-definitions/interfaces_virtual-ethernet.xml.in new file mode 100644 index 000000000..c4610feec --- /dev/null +++ b/interface-definitions/interfaces_virtual-ethernet.xml.in @@ -0,0 +1,48 @@ + + + + + + + Virtual Ethernet (veth) Interface + 300 + + veth[0-9]+ + + Virtual Ethernet interface must be named vethN + + vethN + Virtual Ethernet interface name + + + + #include + #include + #include + #include + #include + #include + #include + #include + #include + + + Virtual ethernet peer interface name + + interfaces virtual-ethernet + + + txt + Name of peer interface + + + veth[0-9]+ + + Virutal Ethernet interface must be named vethN + + + + + + + diff --git a/interface-definitions/interfaces_vti.xml.in b/interface-definitions/interfaces_vti.xml.in new file mode 100644 index 000000000..158d9afd0 --- /dev/null +++ b/interface-definitions/interfaces_vti.xml.in @@ -0,0 +1,32 @@ + + + + + + + Virtual Tunnel Interface (XFRM) + 381 + + vti[0-9]+ + + VTI interface must be named vtiN + + vtiN + VTI interface name + + + + #include + #include + #include + #include + #include + #include + #include + #include + #include + + + + + diff --git a/interface-definitions/interfaces_vxlan.xml.in b/interface-definitions/interfaces_vxlan.xml.in new file mode 100644 index 000000000..504c08e7e --- /dev/null +++ b/interface-definitions/interfaces_vxlan.xml.in @@ -0,0 +1,133 @@ + + + + + + + Virtual Extensible LAN (VXLAN) Interface + 460 + + vxlan[0-9]+ + + VXLAN interface must be named vxlanN + + vxlanN + VXLAN interface name + + + + #include + #include + #include + + + Enable Generic Protocol extension (VXLAN-GPE) + + + + + + Multicast group address for VXLAN interface + + ipv4 + Multicast IPv4 group address + + + ipv6 + Multicast IPv6 group address + + + + + + Multicast IPv4/IPv6 address required + + + #include + #include + #include + #include + #include + + + VXLAN tunnel parameters + + + + + IPv4 specific tunnel parameters + + + #include + #include + #include + + 16 + + + + + + IPv6 specific tunnel parameters + + + #include + + + + + Use external control plane + + + + + + Do not add unknown addresses into forwarding database + + + + + + Enable neighbor discovery (ARP and ND) suppression + + + + + + Enable VNI filter support + + + + + + #include + + 4789 + + #include + #include + #include + #include + #include + #include + + + Configuring VLAN-to-VNI mappings for EVPN-VXLAN + + u32:0-4094 + Virtual Local Area Network (VLAN) ID + + + + + VLAN ID must be between 0 and 4094 + + + #include + + + + + + + diff --git a/interface-definitions/interfaces_wireguard.xml.in b/interface-definitions/interfaces_wireguard.xml.in new file mode 100644 index 000000000..f3fe0f1da --- /dev/null +++ b/interface-definitions/interfaces_wireguard.xml.in @@ -0,0 +1,129 @@ + + + + + + + WireGuard Interface + 379 + + wg[0-9]+ + + WireGuard interface must be named wgN + + wgN + WireGuard interface name + + + + #include + #include + #include + #include + #include + #include + + 1420 + + #include + #include + + + A 32-bit fwmark value set on all outgoing packets + + number + value which marks the packet for QoS/shaper + + + + + + 0 + + + + Base64 encoded private key + + [0-9a-zA-Z\+/]{43}= + + Key is not valid 44-character (32-bytes) base64 + + + + + peer alias + + [^ ]{1,100} + + peer alias too long (limit 100 characters) + + + #include + #include + + + base64 encoded public key + + [0-9a-zA-Z\+/]{43}= + + Key is not valid 44-character (32-bytes) base64 + + + + + base64 encoded preshared key + + [0-9a-zA-Z\+/]{43}= + + Key is not valid 44-character (32-bytes) base64 + + + + + IP addresses allowed to traverse the peer + + + + + + + + + IP address of tunnel endpoint + + ipv4 + IPv4 address of remote tunnel endpoint + + + ipv6 + IPv6 address of remote tunnel endpoint + + + + + + + + #include + + + Interval to send keepalive messages + + u32:1-65535 + Interval in seconds + + + + + + + + + #include + #include + #include + + + + + diff --git a/interface-definitions/interfaces_wireless.xml.in b/interface-definitions/interfaces_wireless.xml.in new file mode 100644 index 000000000..b5da0a556 --- /dev/null +++ b/interface-definitions/interfaces_wireless.xml.in @@ -0,0 +1,832 @@ + + + + + + + Wireless (WiFi/WLAN) Network Interface + 318 + + + + + wlan[0-9]+ + + Wireless interface must be named wlanN + + wlanN + Wireless (WiFi/WLAN) interface name + + + + #include + + + HT and VHT capabilities for your card + + + + + HT (High Throughput) settings + + + + + 40MHz intolerance, use 20MHz only! + + + + + + Enable WMM-PS unscheduled automatic power aave delivery [U-APSD] + + + + + + Supported channel set width + + ht20 ht40+ ht40- + + + ht20 + Supported channel set width both 20 MHz only + + + ht40+ + Supported channel set width both 20 MHz and 40 MHz with secondary channel above primary channel + + + ht40- + Supported channel set width both 20 MHz and 40 MHz with secondary channel below primary channel + + + (ht20|ht40\+|ht40-) + + + + + + + Enable HT-delayed block ack + + + + + + Enable DSSS_CCK-40 + + + + + + Enable HT-greenfield + + + + + + Enable LDPC coding capability + + + + + + Enable L-SIG TXOP protection capability + + + + + + Set maximum A-MSDU length + + 3839 7935 + + + 3839 + Set maximum A-MSDU length to 3839 octets + + + 7935 + Set maximum A-MSDU length to 7935 octets + + + (3839|7935) + + + + + + Short GI capabilities + + 20 40 + + + 20 + Short GI for 20 MHz + + + 40 + Short GI for 40 MHz + + + (20|40) + + + + + + + Spatial Multiplexing Power Save (SMPS) settings + + static dynamic + + + static + STATIC Spatial Multiplexing (SM) Power Save + + + dynamic + DYNAMIC Spatial Multiplexing (SM) Power Save + + + (static|dynamic) + + + + + + Support for sending and receiving PPDU using STBC (Space Time Block Coding) + + + + + Enable receiving PPDU using STBC (Space Time Block Coding) + + [1-3]+ + Number of spacial streams that can use RX STBC + + + [1-3]+ + + Invalid capability item + + + + + Enable sending PPDU using STBC (Space Time Block Coding) + + + + + + + + + + Require stations to support HT PHY (reject association if they do not) + + + + + + + + + Require stations to support VHT PHY (reject association if they do not) + + + + + + + + + VHT (Very High Throughput) settings + + + + + Number of antennas on this card + + u32:1-8 + Number of antennas for this card + + + + + + + + + Set if antenna pattern does not change during the lifetime of an association + + + + + + Beamforming capabilities + + single-user-beamformer single-user-beamformee multi-user-beamformer multi-user-beamformee + + + single-user-beamformer + Support for operation as single user beamformer + + + single-user-beamformee + Support for operation as single user beamformee + + + multi-user-beamformer + Support for operation as multi user beamformer + + + multi-user-beamformee + Support for operation as multi user beamformee + + + (single-user-beamformer|single-user-beamformee|multi-user-beamformer|multi-user-beamformee) + + + + + + + VHT operating channel center frequency + + + + + VHT operating channel center frequency - center freq 1 (for use with 80, 80+80 and 160 modes) + + u32:34-173 + 5Ghz (802.11 a/h/j/n/ac) center channel index (use 42 for primary 80MHz channel 36) + + + + + Channel center value must be between 34 and 173 + + + + + VHT operating channel center frequency - center freq 2 (for use with the 80+80 mode) + + u32:34-173 + 5Ghz (802.11 a/h/j/n/ac) center channel index (use 58 for primary 80MHz channel 52) + + + + + Channel center value must be between 34 and 173 + + + + + + + VHT operating Channel width + + 0 1 2 3 + + + 0 + 20 or 40 MHz channel width + + + 1 + 80 MHz channel width + + + 2 + 160 MHz channel width + + + 3 + 80+80 MHz channel width + + + + + + + + + Enable LDPC (Low Density Parity Check) coding capability + + + + + + VHT link adaptation capabilities + + unsolicited both + + + unsolicited + Station provides only unsolicited VHT MFB + + + both + Station can provide VHT MFB in response to VHT MRQ and unsolicited VHT MFB + + + (unsolicited|both) + + Invalid capability item + + + + + Set the maximum length of A-MPDU pre-EOF padding that the station can receive + + u32:0-7 + Maximum length of A-MPDU pre-EOF padding = 2 pow(13 + x) -1 octets + + + + + + + + + Increase Maximum MPDU length to 7991 or 11454 octets (otherwise: 3895 octets) + + 7991 11454 + + + 7991 + ncrease Maximum MPDU length to 7991 octets + + + 11454 + ncrease Maximum MPDU length to 11454 octets + + + (7991|11454) + + + + + + Short GI capabilities + + 80 160 + + + 80 + Short GI for 80 MHz + + + 160 + Short GI for 160 MHz + + + (80|160) + + + + + + + Support for sending and receiving PPDU using STBC (Space Time Block Coding) + + + + + Enable receiving PPDU using STBC (Space Time Block Coding) + + [1-4]+ + Number of spacial streams that can use RX STBC + + + [1-4]+ + + Invalid capability item + + + + + Enable sending PPDU using STBC (Space Time Block Coding) + + + + + + + + Enable VHT TXOP Power Save Mode + + + + + + Station supports receiving VHT variant HT Control field + + + + + + + + + + Wireless radio channel + + 0 + Automatic Channel Selection (ACS) + + + u32:1-14 + 2.4Ghz (802.11 b/g/n) Channel + + + u32:34-173 + 5Ghz (802.11 a/h/j/n/ac) Channel + + + + + + 0 + + + + Indicate country in which device is operating + + us eu jp de uk cn es fr ru + + + txt + ISO/IEC 3166-1 Country Code + + + [a-z][a-z] + + Invalid ISO/IEC 3166-1 Country Code + + + #include + #include + #include + + + Disable broadcast of SSID from access-point + + + + #include + #include + #include + + + Disassociate stations based on excessive transmission failures + + + + #include + #include + #include + + + Isolate stations on the AP so they cannot see each other + + + + #include + + + Maximum number of wireless radio stations. Excess stations will be rejected upon authentication request. + + u32:1-2007 + Number of allowed stations + + + + + Number of stations must be between 1 and 2007 + + + + + Management Frame Protection (MFP) according to IEEE 802.11w + + disabled optional required + + + disabled + no MFP + + + optional + MFP optional + + + required + MFP enforced + + + (disabled|optional|required) + + + disabled + + + + Wireless radio mode + + a b g n ac + + + a + 802.11a - 54 Mbits/sec + + + b + 802.11b - 11 Mbits/sec + + + g + 802.11g - 54 Mbits/sec + + + n + 802.11n - 600 Mbits/sec + + + ac + 802.11ac - 1300 Mbits/sec + + + (a|b|g|n|ac) + + + g + + #include + + + Wireless physical device + + + + + + + + phy0 + + + + Transmission power reduction in dBm + + u32:0-255 + TX power reduction in dBm + + + + + dBm value must be between 0 and 255 + + + + + Wireless security settings + + + + + Station MAC address based authentication + + + + + Select security operation mode + + accept deny + + + accept + Accept all clients unless found in deny list + + + deny + Deny all clients unless found in accept list + + + (accept|deny) + + + accept + + + + Accept station MAC address + + + #include + + + + + Deny station MAC address + + + #include + + + + + + + Wired Equivalent Privacy (WEP) parameters + + + + + WEP encryption key + + txt + Wired Equivalent Privacy key + + + ([a-fA-F0-9]{10}|[a-fA-F0-9]{26}|[a-fA-F0-9]{32}) + + Invalid WEP key + + + + + + + + Wifi Protected Access (WPA) parameters + + + + + Cipher suite for WPA unicast packets + + GCMP-256 GCMP CCMP-256 CCMP TKIP + + + GCMP-256 + AES in Galois/counter mode with 256-bit key + + + GCMP + AES in Galois/counter mode with 128-bit key + + + CCMP-256 + AES in Counter mode with CBC-MAC with 256-bit key + + + CCMP + AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] (supported on all WPA2 APs) + + + TKIP + Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] + + + (GCMP-256|GCMP|CCMP-256|CCMP|TKIP) + + Invalid cipher selection + + + + + + Cipher suite for WPA multicast and broadcast packets + + GCMP-256 GCMP CCMP-256 CCMP TKIP + + + GCMP-256 + AES in Galois/counter mode with 256-bit key + + + GCMP + AES in Galois/counter mode with 128-bit key + + + CCMP-256 + AES in Counter mode with CBC-MAC with 256-bit key + + + CCMP + AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] (supported on all WPA2 APs) + + + TKIP + Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] + + + (GCMP-256|GCMP|CCMP-256|CCMP|TKIP) + + Invalid group cipher selection + + + + + + WPA mode + + wpa wpa2 wpa+wpa2 wpa3 + + + wpa + WPA (IEEE 802.11i/D3.0) + + + wpa2 + WPA2 (full IEEE 802.11i/RSN) + + + wpa+wpa2 + Allow both WPA and WPA2 + + + (wpa|wpa2|wpa\+wpa2|wpa3) + + Unknown WPA mode + + wpa+wpa2 + + + + WPA personal shared pass phrase. If you are using special characters in the WPA passphrase then single quotes are required. + + txt + Passphrase of at least 8 but not more than 63 printable characters + + + .{8,63} + + Invalid WPA pass phrase, must be 8 to 63 printable characters! + + + #include + + + + + + + Enable RADIUS server to receive accounting info + + + + + + + + + + + + + + Wireless access-point service set identifier (SSID) + + .{1,32} + + Invalid SSID + + + + + Wireless device type for this interface + + access-point station monitor + + + access-point + Access-point forwards packets between other nodes + + + station + Connects to another access point + + + monitor + Passively monitor all packets on the frequency/channel + + + (access-point|station|monitor) + + Type must be access-point, station or monitor + + monitor + + #include + #include + #include + #include + + + + + diff --git a/interface-definitions/interfaces_wwan.xml.in b/interface-definitions/interfaces_wwan.xml.in new file mode 100644 index 000000000..1580c3bcb --- /dev/null +++ b/interface-definitions/interfaces_wwan.xml.in @@ -0,0 +1,48 @@ + + + + + + + Wireless Modem (WWAN) Interface + 350 + + + + + wwan[0-9]+ + + Wireless Modem interface must be named wwanN + + wwanN + Wireless Wide Area Network interface name + + + + #include + + + Access Point Name (APN) + + + #include + #include + #include + #include + #include + #include + #include + #include + + 1430 + + #include + #include + #include + #include + #include + + + + + diff --git a/interface-definitions/lldp.xml.in b/interface-definitions/lldp.xml.in deleted file mode 100644 index 25fb575b6..000000000 --- a/interface-definitions/lldp.xml.in +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - - LLDP settings - 985 - - - - - Location data for interface - - all - Location data all interfaces - - - txt - Location data for a specific interface - - - - all - - - - #include - - - LLDP-MED location data - - - - - Coordinate based location - - - - - Altitude in meters - - 0 - No altitude - - - [+-]<meters> - Altitude in meters - - Altitude should be a positive or negative number - - - - - 0 - - - - Coordinate datum type - - WGS84 - WGS84 - - - NAD83 - NAD83 - - - MLLW - NAD83/MLLW - - - WGS84 NAD83 MLLW - - Datum should be WGS84, NAD83, or MLLW - - (WGS84|NAD83|MLLW) - - - WGS84 - - - - Latitude - - <latitude> - Latitude (example "37.524449N") - - Latitude should be a number followed by S or N - - (\d+)(\.\d+)?[nNsS] - - - - - - Longitude - - <longitude> - Longitude (example "122.267255W") - - Longiture should be a number followed by E or W - - (\d+)(\.\d+)?[eEwW] - - - - - - - - ECS ELIN (Emergency location identifier number) - - u32:0-9999999999 - Emergency Call Service ELIN number (between 10-25 numbers) - - - [0-9]{10,25} - - ELIN number must be between 10-25 numbers - - - - - - - - - Legacy (vendor specific) protocols - - - - - Listen for CDP for Cisco routers/switches - - - - - - Listen for EDP for Extreme routers/switches - - - - - - Listen for FDP for Foundry routers/switches - - - - - - Listen for SONMP for Nortel routers/switches - - - - - - - - Management IP Address - - - - - ipv4 - IPv4 Management Address - - - ipv6 - IPv6 Management Address - - - - - - - - - - Enable SNMP queries of the LLDP database - - - - - - - - diff --git a/interface-definitions/load-balancing-haproxy.xml.in b/interface-definitions/load-balancing-haproxy.xml.in deleted file mode 100644 index 8f6bd3a99..000000000 --- a/interface-definitions/load-balancing-haproxy.xml.in +++ /dev/null @@ -1,254 +0,0 @@ - - - - - - - Configure reverse-proxy - - - - - Frontend service name - - #include - - Server name must be alphanumeric and can contain hyphen and underscores - - - - - Backend member - - #include - - Backend name must be alphanumeric and can contain hyphen and underscores - - txt - Name of reverse-proxy backend system - - - load-balancing reverse-proxy backend - - - - - #include - #include - #include - #include - #include - - - Redirect HTTP to HTTPS - - - - - - SSL Certificate, SSL Key and CA - - - #include - - - - - - - Backend server name - - #include - - Backend name must be alphanumeric and can contain hyphen and underscores - - - - - Load-balancing algorithm - - source-address round-robin least-connection - - - source-address - Based on hash of source IP address - - - round-robin - Round robin - - - least-connection - Least connection - - - (source-address|round-robin|least-connection) - - - round-robin - - #include - #include - - - Backend parameters - - - - - HTTP health check - - - - - - #include - - - Backend server name - - - - - Backend server address - - ipv4 - IPv4 unicast peer address - - - ipv6 - IPv6 unicast peer address - - - - - - - - - Use backup server if other servers are not available - - - - - - Active health check backend server - - - - #include - - - Send a Proxy Protocol version 1 header (text format) - - - - - - Send a Proxy Protocol version 2 header (binary format) - - - - - - - - SSL Certificate, SSL Key and CA - - - #include - - - #include - - - - - Global perfomance parameters and limits - - - - - Maximum allowed connections - - u32:1-2000000 - Maximum allowed connections - - - - - - - - - Cipher algorithms ("cipher suite") used during SSL/TLS handshake for all frontend servers - - ecdhe-ecdsa-aes128-gcm-sha256 ecdhe-rsa-aes128-gcm-sha256 ecdhe-ecdsa-aes256-gcm-sha384 ecdhe-rsa-aes256-gcm-sha384 ecdhe-ecdsa-chacha20-poly1305 ecdhe-rsa-chacha20-poly1305 dhe-rsa-aes128-gcm-sha256 dhe-rsa-aes256-gcm-sha384 - - - ecdhe-ecdsa-aes128-gcm-sha256 - ecdhe-ecdsa-aes128-gcm-sha256 - - - ecdhe-rsa-aes128-gcm-sha256 - ecdhe-rsa-aes128-gcm-sha256 - - - ecdhe-ecdsa-aes256-gcm-sha384 - ecdhe-ecdsa-aes256-gcm-sha384 - - - ecdhe-rsa-aes256-gcm-sha384 - ecdhe-rsa-aes256-gcm-sha384 - - - ecdhe-ecdsa-chacha20-poly1305 - ecdhe-ecdsa-chacha20-poly1305 - - - ecdhe-rsa-chacha20-poly1305 - ecdhe-rsa-chacha20-poly1305 - - - dhe-rsa-aes128-gcm-sha256 - dhe-rsa-aes128-gcm-sha256 - - - dhe-rsa-aes256-gcm-sha384 - dhe-rsa-aes256-gcm-sha384 - - - (ecdhe-ecdsa-aes128-gcm-sha256|ecdhe-rsa-aes128-gcm-sha256|ecdhe-ecdsa-aes256-gcm-sha384|ecdhe-rsa-aes256-gcm-sha384|ecdhe-ecdsa-chacha20-poly1305|ecdhe-rsa-chacha20-poly1305|dhe-rsa-aes128-gcm-sha256|dhe-rsa-aes256-gcm-sha384) - - - - ecdhe-ecdsa-aes128-gcm-sha256 ecdhe-rsa-aes128-gcm-sha256 ecdhe-ecdsa-aes256-gcm-sha384 ecdhe-rsa-aes256-gcm-sha384 ecdhe-ecdsa-chacha20-poly1305 ecdhe-rsa-chacha20-poly1305 dhe-rsa-aes128-gcm-sha256 dhe-rsa-aes256-gcm-sha384 - - - - Specify the minimum required TLS version - - 1.2 1.3 - - - 1.2 - TLS v1.2 - - - 1.3 - TLS v1.3 - - - (1.2|1.3) - - - 1.3 - - - - #include - - - - - diff --git a/interface-definitions/load-balancing-wan.xml.in b/interface-definitions/load-balancing-wan.xml.in deleted file mode 100644 index c12cab22a..000000000 --- a/interface-definitions/load-balancing-wan.xml.in +++ /dev/null @@ -1,399 +0,0 @@ - - - - - Configure load-balancing - 900 - - - - - Configure Wide Area Network (WAN) load-balancing - - - - - Disable source NAT rules from being configured for WAN load balancing - - - - - - Enable WAN load balancing for locally sourced traffic - - - - - - Flush connection tracking tables on connection state change - - - - - - Script to be executed on interface status change - - txt - Script in /config/scripts - - - - - - - - - Interface name - - - - - - - - Failure count - - u32:1-10 - Failure count - - - - - - 1 - - - - Outbound interface nexthop address. Can be 'DHCP or IPv4 address' [REQUIRED] - - dhcp - - - ipv4 - Nexthop IP address - - - dhcp - Set the nexthop via DHCP - - - - (dhcp) - - - - - - Success count - - u32:1-10 - Success count - - - - - - 1 - - - - Rule number - - u32:0-4294967295 - Rule number - - - - - - - - - Ping response time (seconds) - - u32:1-30 - Response time (seconds) - - - - - - 5 - - - - Health target address - - ipv4 - Health target address - - - - - - - - - Path to user-defined script - - txt - Script in /config/scripts - - - - - - - - - TTL limit (hop count) - - u32:1-254 - Number of hops - - - - - - 1 - - - - WLB test type - - ping ttl user-defined - - - ping - Test with ICMP echo response - - - ttl - Test with UDP TTL expired response - - - user-defined - User-defined test script - - - (ping|ttl|user-defined) - - - ping - - - - - - - - Rule number (1-9999) - - u32:1-9999 - Rule number - - - - - - - #include - - - Destination - - - #include - #include - - - - - Exclude packets matching this rule from WAN load balance - - - - - - Enable failover for packets matching this rule from WAN load balance - - - - - - Inbound interface name (e.g., "eth0") [REQUIRED] - - any - - - - - - - Interface name [REQUIRED] - - - - - - - - Load-balance weight - - u32:1-255 - Interface weight - - - - - Weight must be between 1 and 255 - - 1 - - - - - - Enable packet limit for this rule - - - - - Burst limit for matching packets - - u32:0-4294967295 - Burst limit for matching packets - - - - - - 5 - - - - Time window for rate calculation - - hour minute second - - - hour - hour - - - minute - minute - - - second - second - - - (hour|minute|second) - - - second - - - - Number of packets used for rate limit - - u32:0-4294967295 - Number of packets used for rate limit - - - - - - 5 - - - - Threshold behavior for limit - - above below - - - above - Above limit - - - below - Below limit - - - (above|below) - - - below - - - - - - Option to match traffic per-packet instead of the default, per-flow - - - - - - Protocol to match (protocol name, number, or "all") - - - all tcp_udp - - - all - All IP protocols - - - tcp_udp - Both TCP and UDP - - - u32:0-255 - IP protocol number - - - <protocol> - IP protocol name - - - !<protocol> - IP protocol name - - - - - - all - - - - Source information - - - #include - #include - - - - - - - Configure sticky connections - - - - - Enable sticky incoming WAN connections - - - - - - - - - - diff --git a/interface-definitions/load-balancing_reverse-proxy.xml.in b/interface-definitions/load-balancing_reverse-proxy.xml.in new file mode 100644 index 000000000..2c2742dff --- /dev/null +++ b/interface-definitions/load-balancing_reverse-proxy.xml.in @@ -0,0 +1,254 @@ + + + + + + + Configure reverse-proxy + + + + + Frontend service name + + #include + + Server name must be alphanumeric and can contain hyphen and underscores + + + + + Backend member + + #include + + Backend name must be alphanumeric and can contain hyphen and underscores + + txt + Name of reverse-proxy backend system + + + load-balancing reverse-proxy backend + + + + + #include + #include + #include + #include + #include + + + Redirect HTTP to HTTPS + + + + + + SSL Certificate, SSL Key and CA + + + #include + + + + + + + Backend server name + + #include + + Backend name must be alphanumeric and can contain hyphen and underscores + + + + + Load-balancing algorithm + + source-address round-robin least-connection + + + source-address + Based on hash of source IP address + + + round-robin + Round robin + + + least-connection + Least connection + + + (source-address|round-robin|least-connection) + + + round-robin + + #include + #include + + + Backend parameters + + + + + HTTP health check + + + + + + #include + + + Backend server name + + + + + Backend server address + + ipv4 + IPv4 unicast peer address + + + ipv6 + IPv6 unicast peer address + + + + + + + + + Use backup server if other servers are not available + + + + + + Active health check backend server + + + + #include + + + Send a Proxy Protocol version 1 header (text format) + + + + + + Send a Proxy Protocol version 2 header (binary format) + + + + + + + + SSL Certificate, SSL Key and CA + + + #include + + + #include + + + + + Global perfomance parameters and limits + + + + + Maximum allowed connections + + u32:1-2000000 + Maximum allowed connections + + + + + + + + + Cipher algorithms ("cipher suite") used during SSL/TLS handshake for all frontend servers + + ecdhe-ecdsa-aes128-gcm-sha256 ecdhe-rsa-aes128-gcm-sha256 ecdhe-ecdsa-aes256-gcm-sha384 ecdhe-rsa-aes256-gcm-sha384 ecdhe-ecdsa-chacha20-poly1305 ecdhe-rsa-chacha20-poly1305 dhe-rsa-aes128-gcm-sha256 dhe-rsa-aes256-gcm-sha384 + + + ecdhe-ecdsa-aes128-gcm-sha256 + ecdhe-ecdsa-aes128-gcm-sha256 + + + ecdhe-rsa-aes128-gcm-sha256 + ecdhe-rsa-aes128-gcm-sha256 + + + ecdhe-ecdsa-aes256-gcm-sha384 + ecdhe-ecdsa-aes256-gcm-sha384 + + + ecdhe-rsa-aes256-gcm-sha384 + ecdhe-rsa-aes256-gcm-sha384 + + + ecdhe-ecdsa-chacha20-poly1305 + ecdhe-ecdsa-chacha20-poly1305 + + + ecdhe-rsa-chacha20-poly1305 + ecdhe-rsa-chacha20-poly1305 + + + dhe-rsa-aes128-gcm-sha256 + dhe-rsa-aes128-gcm-sha256 + + + dhe-rsa-aes256-gcm-sha384 + dhe-rsa-aes256-gcm-sha384 + + + (ecdhe-ecdsa-aes128-gcm-sha256|ecdhe-rsa-aes128-gcm-sha256|ecdhe-ecdsa-aes256-gcm-sha384|ecdhe-rsa-aes256-gcm-sha384|ecdhe-ecdsa-chacha20-poly1305|ecdhe-rsa-chacha20-poly1305|dhe-rsa-aes128-gcm-sha256|dhe-rsa-aes256-gcm-sha384) + + + + ecdhe-ecdsa-aes128-gcm-sha256 ecdhe-rsa-aes128-gcm-sha256 ecdhe-ecdsa-aes256-gcm-sha384 ecdhe-rsa-aes256-gcm-sha384 ecdhe-ecdsa-chacha20-poly1305 ecdhe-rsa-chacha20-poly1305 dhe-rsa-aes128-gcm-sha256 dhe-rsa-aes256-gcm-sha384 + + + + Specify the minimum required TLS version + + 1.2 1.3 + + + 1.2 + TLS v1.2 + + + 1.3 + TLS v1.3 + + + (1.2|1.3) + + + 1.3 + + + + #include + + + + + diff --git a/interface-definitions/load-balancing_wan.xml.in b/interface-definitions/load-balancing_wan.xml.in new file mode 100644 index 000000000..e117fd1b2 --- /dev/null +++ b/interface-definitions/load-balancing_wan.xml.in @@ -0,0 +1,399 @@ + + + + + Configure load-balancing + 900 + + + + + Configure Wide Area Network (WAN) load-balancing + + + + + Disable source NAT rules from being configured for WAN load balancing + + + + + + Enable WAN load balancing for locally sourced traffic + + + + + + Flush connection tracking tables on connection state change + + + + + + Script to be executed on interface status change + + txt + Script in /config/scripts + + + + + + + + + Interface name + + + + + + + + Failure count + + u32:1-10 + Failure count + + + + + + 1 + + + + Outbound interface nexthop address. Can be 'DHCP or IPv4 address' [REQUIRED] + + dhcp + + + ipv4 + Nexthop IP address + + + dhcp + Set the nexthop via DHCP + + + + (dhcp) + + + + + + Success count + + u32:1-10 + Success count + + + + + + 1 + + + + Rule number + + u32:0-4294967295 + Rule number + + + + + + + + + Ping response time (seconds) + + u32:1-30 + Response time (seconds) + + + + + + 5 + + + + Health target address + + ipv4 + Health target address + + + + + + + + + Path to user-defined script + + txt + Script in /config/scripts + + + + + + + + + TTL limit (hop count) + + u32:1-254 + Number of hops + + + + + + 1 + + + + WLB test type + + ping ttl user-defined + + + ping + Test with ICMP echo response + + + ttl + Test with UDP TTL expired response + + + user-defined + User-defined test script + + + (ping|ttl|user-defined) + + + ping + + + + + + + + Rule number (1-9999) + + u32:1-9999 + Rule number + + + + + + + #include + + + Destination + + + #include + #include + + + + + Exclude packets matching this rule from WAN load balance + + + + + + Enable failover for packets matching this rule from WAN load balance + + + + + + Inbound interface name (e.g., "eth0") [REQUIRED] + + any + + + + + + + Interface name [REQUIRED] + + + + + + + + Load-balance weight + + u32:1-255 + Interface weight + + + + + Weight must be between 1 and 255 + + 1 + + + + + + Enable packet limit for this rule + + + + + Burst limit for matching packets + + u32:0-4294967295 + Burst limit for matching packets + + + + + + 5 + + + + Time window for rate calculation + + hour minute second + + + hour + hour + + + minute + minute + + + second + second + + + (hour|minute|second) + + + second + + + + Number of packets used for rate limit + + u32:0-4294967295 + Number of packets used for rate limit + + + + + + 5 + + + + Threshold behavior for limit + + above below + + + above + Above limit + + + below + Below limit + + + (above|below) + + + below + + + + + + Option to match traffic per-packet instead of the default, per-flow + + + + + + Protocol to match (protocol name, number, or "all") + + + all tcp_udp + + + all + All IP protocols + + + tcp_udp + Both TCP and UDP + + + u32:0-255 + IP protocol number + + + <protocol> + IP protocol name + + + !<protocol> + IP protocol name + + + + + + all + + + + Source information + + + #include + #include + + + + + + + Configure sticky connections + + + + + Enable sticky incoming WAN connections + + + + + + + + + + diff --git a/interface-definitions/ntp.xml.in b/interface-definitions/ntp.xml.in deleted file mode 100644 index 4e874434b..000000000 --- a/interface-definitions/ntp.xml.in +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - Network Time Protocol (NTP) configuration - 900 - - - - - Network Time Protocol (NTP) server - - ipv4 - IP address of NTP server - - - ipv6 - IPv6 address of NTP server - - - hostname - Fully qualified domain name of NTP server - - - - - - - - - - Marks the server as unused - - - - - - Enable Network Time Security (NTS) for the server - - - - - - Associate with a number of remote servers - - - - - - Marks the server as preferred - - - - - - #include - #include - #include - #include - - - - - diff --git a/interface-definitions/policy-local-route.xml.in b/interface-definitions/policy-local-route.xml.in deleted file mode 100644 index 15be099c9..000000000 --- a/interface-definitions/policy-local-route.xml.in +++ /dev/null @@ -1,156 +0,0 @@ - - - - - - - - IPv4 policy route of local traffic - 500 - - - - - Policy local-route rule set number - - - u32:1-32765 - Local-route rule number (1-32765) - - - - - - - - - Packet modifications - - - - - Routing table to forward packet with - - u32:1-200 - Table number - - - main - - - - - - - - Match fwmark value - - u32:1-2147483647 - Address to match against - - - - - - - #include - - - Source parameters - - - #include - #include - - - - - Destination parameters - - - #include - #include - - - #include - - - - - - - IPv6 policy route of local traffic - 500 - - - - - IPv6 policy local-route rule set number - - - u32:1-32765 - Local-route rule number (1-32765) - - - - - - - - - Packet modifications - - - - - Routing table to forward packet with - - u32:1-200 - Table number - - - main - - - - - - - - Match fwmark value - - u32:1-2147483647 - Address to match against - - - - - - - #include - - - Source parameters - - - #include - #include - - - - - Destination parameters - - - #include - #include - - - #include - - - - - - - diff --git a/interface-definitions/policy-route.xml.in b/interface-definitions/policy-route.xml.in deleted file mode 100644 index 92e7a0cb4..000000000 --- a/interface-definitions/policy-route.xml.in +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - Policy route rule set name for IPv6 - - [a-zA-Z0-9][\w\-\.]* - - 201 - - - #include - #include - #include - - - Policy rule number - - u32:1-999999 - Number of policy rule - - - - - Policy rule number must be between 1 and 999999 - - - - - Destination parameters - - - #include - #include - #include - - - - - Source parameters - - - #include - #include - #include - - - #include - #include - #include - #include - #include - #include - - - - - - - Policy route rule set name for IPv4 - - [a-zA-Z0-9][\w\-\.]* - - 201 - - - #include - #include - #include - - - Policy rule number - - u32:1-999999 - Number of policy rule - - - - - Policy rule number must be between 1 and 999999 - - - - - Destination parameters - - - #include - #include - #include - - - - - Source parameters - - - #include - #include - #include - - - #include - #include - #include - #include - #include - #include - - - - - - - diff --git a/interface-definitions/policy_local-route.xml.in b/interface-definitions/policy_local-route.xml.in new file mode 100644 index 000000000..7a019154a --- /dev/null +++ b/interface-definitions/policy_local-route.xml.in @@ -0,0 +1,156 @@ + + + + + + + + IPv4 policy route of local traffic + 500 + + + + + Policy local-route rule set number + + + u32:1-32765 + Local-route rule number (1-32765) + + + + + + + + + Packet modifications + + + + + Routing table to forward packet with + + u32:1-200 + Table number + + + main + + + + + + + + Match fwmark value + + u32:1-2147483647 + Address to match against + + + + + + + #include + + + Source parameters + + + #include + #include + + + + + Destination parameters + + + #include + #include + + + #include + + + + + + + IPv6 policy route of local traffic + 500 + + + + + IPv6 policy local-route rule set number + + + u32:1-32765 + Local-route rule number (1-32765) + + + + + + + + + Packet modifications + + + + + Routing table to forward packet with + + u32:1-200 + Table number + + + main + + + + + + + + Match fwmark value + + u32:1-2147483647 + Address to match against + + + + + + + #include + + + Source parameters + + + #include + #include + + + + + Destination parameters + + + #include + #include + + + #include + + + + + + + diff --git a/interface-definitions/policy_route.xml.in b/interface-definitions/policy_route.xml.in new file mode 100644 index 000000000..9cc22540b --- /dev/null +++ b/interface-definitions/policy_route.xml.in @@ -0,0 +1,117 @@ + + + + + + + Policy route rule set name for IPv6 + + [a-zA-Z0-9][\w\-\.]* + + 201 + + + #include + #include + #include + + + Policy rule number + + u32:1-999999 + Number of policy rule + + + + + Policy rule number must be between 1 and 999999 + + + + + Destination parameters + + + #include + #include + #include + + + + + Source parameters + + + #include + #include + #include + + + #include + #include + #include + #include + #include + #include + + + + + + + Policy route rule set name for IPv4 + + [a-zA-Z0-9][\w\-\.]* + + 201 + + + #include + #include + #include + + + Policy rule number + + u32:1-999999 + Number of policy rule + + + + + Policy rule number must be between 1 and 999999 + + + + + Destination parameters + + + #include + #include + #include + + + + + Source parameters + + + #include + #include + #include + + + #include + #include + #include + #include + #include + #include + + + + + + + diff --git a/interface-definitions/protocols-babel.xml.in b/interface-definitions/protocols-babel.xml.in deleted file mode 100644 index 49fffe230..000000000 --- a/interface-definitions/protocols-babel.xml.in +++ /dev/null @@ -1,254 +0,0 @@ - - - - - - - Babel Routing Protocol - 650 - - - - - Babel-specific parameters - - - - - Enable diversity-aware routing - - - - - - Multiplicative factor used for diversity routing - - u32:1-256 - Multiplicative factor, in units of 1/256 - - - - - - 256 - - - - Time before resending a message - - u32:20-655340 - Milliseconds - - - - - - 2000 - - - - Smoothing half-life - - u32:0-65534 - Seconds - - - - - - 4 - - - - #include - - - Redistribute information from another routing protocol - - - - - Redistribute IPv4 routes - - - - - Redistribute BGP routes - - - - - - Redistribute connected routes - - - - - - Redistribute EIGRP routes - - - - - - Redistribute IS-IS routes - - - - - - Redistribute kernel routes - - - - - - Redistribute NHRP routes - - - - - - Redistribute OSPF routes - - - - - - Redistribute RIP routes - - - - - - Redistribute static routes - - - - - - - - Redistribute IPv6 routes - - - - - Redistribute BGP routes - - - - - - Redistribute connected routes - - - - - - Redistribute IS-IS routes - - - - - - Redistribute kernel routes - - - - - - Redistribute NHRP routes - - - - - - Redistribute OSPFv3 routes - - - - - - Redistribute RIPng routes - - - - - - Redistribute static routes - - - - - - - - - - Filter networks in routing updates - - - - - Filter IPv4 routes - - - #include - - - Apply filtering to an interface - - txt - Apply filtering to an interface - - - - - - #include - - - - #include - #include - - - #include - - - - - Filter IPv6 routes - - - #include - - - Apply filtering to an interface - - txt - Apply filtering to an interface - - - - - - #include - - - - #include - #include - - - #include - - - - - - - - - diff --git a/interface-definitions/protocols-bfd.xml.in b/interface-definitions/protocols-bfd.xml.in deleted file mode 100644 index 9048cf5c2..000000000 --- a/interface-definitions/protocols-bfd.xml.in +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - Bidirectional Forwarding Detection (BFD) - 820 - - - - - Configures BFD peer to listen and talk to - - ipv4 - BFD peer IPv4 address - - - ipv6 - BFD peer IPv6 address - - - - - - - #include - - - Bind listener to specified interface/address, mandatory for IPv6 - - - #include - - - Local address to bind our peer listener to - - - - - ipv4 - Local IPv4 address used to connect to the peer - - - ipv6 - Local IPv6 address used to connect to the peer - - - - - - - - - #include - - - Allow this BFD peer to not be directly connected - - - - #include - - - - - Configure BFD profile used by individual peer - - txt - Name of BFD profile - - - [-_a-zA-Z0-9]{1,32} - - - - #include - - - - - - - diff --git a/interface-definitions/protocols-bgp.xml.in b/interface-definitions/protocols-bgp.xml.in deleted file mode 100644 index e1a822999..000000000 --- a/interface-definitions/protocols-bgp.xml.in +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - Border Gateway Protocol (BGP) - 820 - - - #include - - - - - diff --git a/interface-definitions/protocols-eigrp.xml.in b/interface-definitions/protocols-eigrp.xml.in deleted file mode 100644 index 88a881a1e..000000000 --- a/interface-definitions/protocols-eigrp.xml.in +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - Enhanced Interior Gateway Routing Protocol (EIGRP) - 820 - - - #include - - - - - diff --git a/interface-definitions/protocols-failover.xml.in b/interface-definitions/protocols-failover.xml.in deleted file mode 100644 index c0caec68e..000000000 --- a/interface-definitions/protocols-failover.xml.in +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - Failover Routing - 490 - - - - - Failover IPv4 route - - ipv4net - IPv4 failover route - - - - - - - - - Next-hop IPv4 router address - - ipv4 - Next-hop router address - - - - - - - - - Check target options - - - - - Policy for check targets - - any-available all-available - - - all-available - All targets must be alive - - - any-available - Any target must be alive - - - (all-available|any-available) - - - any-available - - #include - - - Check target address - - ipv4 - Address to check - - - - - - - - - - Timeout between checks - - u32:1-300 - Timeout in seconds between checks - - - - - - 10 - - - - Check type - - arp icmp tcp - - - arp - Check target by ARP - - - icmp - Check target by ICMP - - - tcp - Check target by TCP - - - (arp|icmp|tcp) - - - icmp - - - - #include - - - Route metric for this gateway - - u32:1-255 - Route metric - - - - - - 1 - - - - - - - - - - diff --git a/interface-definitions/protocols-isis.xml.in b/interface-definitions/protocols-isis.xml.in deleted file mode 100644 index e0bc47bb9..000000000 --- a/interface-definitions/protocols-isis.xml.in +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - Intermediate System to Intermediate System (IS-IS) - 610 - - - #include - - - - - diff --git a/interface-definitions/protocols-mpls.xml.in b/interface-definitions/protocols-mpls.xml.in deleted file mode 100644 index 831601fc6..000000000 --- a/interface-definitions/protocols-mpls.xml.in +++ /dev/null @@ -1,560 +0,0 @@ - - - - - - - - Multiprotocol Label Switching (MPLS) - 490 - - - - - Label Distribution Protocol (LDP) - - - #include - - - Forwarding equivalence class allocation from local routes - - - - - IPv4 routes - - - - - Access-list number - - u32:1-2699 - Access list number - - - - - - - - - - - IPv6 routes - - - - - Access-list6 number - - u32:1-2699 - Access list number - - - - - - - - - - - - - LDP neighbor parameters - - ipv4 - Neighbor IPv4 address - - - - - - - - - Neighbor password - - - - - Neighbor TTL security - - disable - - - u32:1-254 - TTL - - - disable - Disable neighbor TTL security - - - - - - Session IPv4 hold time - - u32:15-65535 - Time in seconds - - - - - - - - - - - Discovery parameters - - ipv4 - Discovery parameters - - - - - - Hello IPv4 hold time - - u32:1-65535 - Time in seconds - - - - - - - - - Hello IPv4 interval - - u32:1-65535 - Time in seconds - - - - - - - - - Hello IPv6 hold time - - u32:1-65535 - Time in seconds - - - - - - - - - Hello IPv6 interval - - u32:1-65535 - Time in seconds - - - - - - - - - Session IPv4 hold time - - u32:15-65535 - Time in seconds - - - - - - - - - Session IPv6 hold time - - u32:15-65535 - Time in seconds - - - - - - - - - Transport IPv4 address - - ipv4 - IPv4 bind as transport - - - - - - - - - Transport IPv6 address - - ipv6 - IPv6 bind as transport - - - - - - - - - - - Targeted LDP neighbor/session parameters - - - - - Targeted IPv4 neighbor/session parameters - - - - - Neighbor/session address - - ipv4 - Neighbor/session address - - - - - - - - - - Accept and respond to targeted hellos - - - - - - Hello interval - - u32:1-65535 - Time in seconds - - - - - - - - - Hello hold time - - u32:1-65535 - Time in seconds - - - - - - - - - - - Targeted IPv6 neighbor/session parameters - - - - - Neighbor/session address - - ipv6 - Neighbor/session address - - - - - - - - - - Accept and respond to targeted hellos - - - - - - Hello interval - - u32:1-65535 - Time in seconds - - - - - - - - - Hello hold time - - u32:1-65535 - Time in seconds - - - - - - - - - - - - - Label Distribution Protocol miscellaneous parameters - - - - - Enable Cisco non-compliant format capability TLV - - - - - - Prefer IPv4 for TCP peer transport connection - - - - - - Enable LDP ordered label distribution control mode - - - - - - - - Export parameters - - - - - IPv4 parameters - - - - - Explicit-Null Label - - - - - - Forwarding equivalence class export filter - - - - - Access-list number to apply FEC filtering - - u32:1-2699 - Access list number - - - - - - - - - Access-list number for IPv4 neighbor selection to apply filtering - - u32:1-2699 - Access list number - - - - - - - - - - - - - IPv6 parameters - - - - - Explicit-Null Label - - - - - - Forwarding equivalence class export filter - - - - - Access-list6 number to apply FEC filtering - - u32:1-2699 - Access list number - - - - - - - - - Access-list6 number for IPv6 neighbor selection to apply filtering - - u32:1-2699 - Access list number - - - - - - - - - - - - - - - Import parameters - - - - - IPv4 parameters - - - - - Forwarding equivalence class import filter - - - - - Access-list number to apply FEC filtering - - u32:1-2699 - Access list number - - - - - - - - - Access-list number for IPv4 neighbor selection to apply filtering - - u32:1-2699 - Access list number - - - - - - - - - - - - - IPv6 parameters - - - - - Forwarding equivalence class import filter - - - - - Access-list6 number to apply FEC filtering - - u32:1-2699 - Access list number - - - - - - - - - Access-list6 number for IPv6 neighbor selection to apply filtering - - u32:1-2699 - Access list number - - - - - - - - - - - - - #include - - - - - Multiprotocol Label Switching miscellaneous parameters - - - - - Disable copy of IP TTL to MPLS TTL - - - - - - Maximum TTL for MPLS packets - - u32:1-255 - Maximum hops allowed - - - - - - - - - #include - - - - - diff --git a/interface-definitions/protocols-multicast.xml.in b/interface-definitions/protocols-multicast.xml.in deleted file mode 100644 index c8e28ed35..000000000 --- a/interface-definitions/protocols-multicast.xml.in +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - Multicast static route - - - - - Configure static unicast route into MRIB for multicast RPF lookup - - ipv4net - Network - - - - - - - - - Nexthop IPv4 address - - ipv4 - Nexthop IPv4 address - - - - - - - - - Distance value for this route - - u32:1-255 - Distance for this route - - - - - - - - - - - - - Multicast interface based route - - ipv4net - Network - - - - - - - - - Next-hop interface - - - - - - - - Distance value for this route - - u32:1-255 - Distance for this route - - - - - - - - - - - - - - - - - diff --git a/interface-definitions/protocols-nhrp.xml.in b/interface-definitions/protocols-nhrp.xml.in deleted file mode 100644 index d7663c095..000000000 --- a/interface-definitions/protocols-nhrp.xml.in +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - Next Hop Resolution Protocol (NHRP) parameters - 680 - - - - - Tunnel for NHRP - - tun[0-9]+ - - - tunN - NHRP tunnel name - - - - - - Pass phrase for cisco authentication - - txt - Pass phrase for cisco authentication - - - [^[:space:]]{1,8} - - Password should contain up to eight non-whitespace characters - - - - - Set an HUB tunnel address - - ipv4net - Set the IP address and prefix length - - - - - - Set HUB fqdn (nbma-address - fqdn) - - <fqdn> - Set the external HUB fqdn - - - - - - - - Holding time in seconds - - - - - Set an HUB tunnel address - - - - - If the statically mapped peer is running Cisco IOS, specify this - - - - - - Set HUB address (nbma-address - external hub address or fqdn) - - - - - Specifies that Registration Request should be sent to this peer on startup - - - - - - - - Set multicast for NHRP - - dynamic nhs - - - (dynamic|nhs) - - - - - - This can be used to reduce memory consumption on big NBMA subnets - - - - - - Enable sending of Cisco style NHRP Traffic Indication packets - - - - - - This instructs opennhrp to reply with authorative answers on NHRP Resolution Requests destined to addresses in this interface - - - - - - Defines an off-NBMA network prefix for which the GRE interface will act as a gateway - - - - - Holding time in seconds - - - - - - - Enable creation of shortcut routes. A received NHRP Traffic Indication will trigger the resolution and establishment of a shortcut route - - - - - - - - - - diff --git a/interface-definitions/protocols-ospf.xml.in b/interface-definitions/protocols-ospf.xml.in deleted file mode 100644 index b3c063d0d..000000000 --- a/interface-definitions/protocols-ospf.xml.in +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - Open Shortest Path First (OSPF) - 620 - - - #include - - - - - diff --git a/interface-definitions/protocols-ospfv3.xml.in b/interface-definitions/protocols-ospfv3.xml.in deleted file mode 100644 index 2b98ffa7b..000000000 --- a/interface-definitions/protocols-ospfv3.xml.in +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - Open Shortest Path First (OSPF) for IPv6 - 620 - - - #include - - - - - diff --git a/interface-definitions/protocols-pim.xml.in b/interface-definitions/protocols-pim.xml.in deleted file mode 100644 index 4a20c0d9b..000000000 --- a/interface-definitions/protocols-pim.xml.in +++ /dev/null @@ -1,210 +0,0 @@ - - - - - - - - Protocol Independent Multicast (PIM) and IGMP - 400 - - - - - PIM interface - - - - - #include - - - - #include - #include - #include - #include - #include - #include - - - Internet Group Management Protocol (IGMP) options - - - #include - - - IGMP join multicast group - - ipv4 - Multicast group address - - - - - - - #include - - - - - IGMP host query interval - - u32:1-1800 - Query interval in seconds - - - - - - - - - IGMP max query response time - - u32:10-250 - Query response value in deci-seconds - - - - - - - - - Interface IGMP version - - 2 3 - - - 2 - IGMP version 2 - - - 3 - IGMP version 3 - - - - - - 3 - - - - - - - - Enable PIM ECMP - - - - - Enable PIM ECMP Rebalance - - - - - - - - Internet Group Management Protocol (IGMP) options - - - - - Configure group limit for watermark warning - - u32:1-65535 - Group count to generate watermark warning - - - - - - - - - #include - #include - #include - #include - - - Only accept registers from a specific source prefix list - - - #include - - - - - Rendezvous Point - - - - - Rendezvous Point address - - ipv4 - Rendezvous Point address - - - - - - - - - Group Address range - - ipv4net - Group Address range RFC 3171 - - - - - - - - - - #include - - - - - Disable IPv6 secondary address in hello packets - - - - - - Shortest-path tree (SPT) switchover - - - - - Never switch to SPT Tree - - - #include - - - - - - - Source-Specific Multicast - - - #include - - - - - - - diff --git a/interface-definitions/protocols-pim6.xml.in b/interface-definitions/protocols-pim6.xml.in deleted file mode 100644 index 8bd3f3fee..000000000 --- a/interface-definitions/protocols-pim6.xml.in +++ /dev/null @@ -1,179 +0,0 @@ - - - - - - - - Protocol Independent Multicast for IPv6 (PIMv6) and MLD - 400 - - - - - PIMv6 interface - - - - - #include - - - - #include - #include - #include - #include - - - Multicast Listener Discovery (MLD) - - - #include - - - MLD join multicast group - - ipv6 - Multicast group address - - - - - - - - - Source address - - ipv6 - Source address - - - - - - - - - - - - - - - Last member query count - - u32:1-255 - Count - - - - - - - - - Last member query interval - - u32:100-6553500 - Last member query interval in milliseconds - - - - - - - - - Query interval - - u32:1-65535 - Query interval in seconds - - - - - - - - - Max query response time - - u32:100-6553500 - Query response value in milliseconds - - - - - - - - - MLD version - - 1 2 - - - 1 - MLD version 1 - - - 2 - MLD version 2 - - - - - - 2 - - - - - - #include - #include - #include - #include - - - Rendezvous Point - - - - - Rendezvous Point address - - ipv6 - Rendezvous Point address - - - - - - - - - Group Address range - - ipv6net - Group Address range - - - - - - - - #include - - - #include - - - - - - - diff --git a/interface-definitions/protocols-rip.xml.in b/interface-definitions/protocols-rip.xml.in deleted file mode 100644 index 0edd8f2ce..000000000 --- a/interface-definitions/protocols-rip.xml.in +++ /dev/null @@ -1,258 +0,0 @@ - - - - - - - Routing Information Protocol (RIP) parameters - 650 - - - - - Administrative distance - - u32:1-255 - Administrative distance - - - - - - - #include - #include - - - Filter networks in routing updates - - - #include - - - Apply filtering to an interface - - txt - Apply filtering to an interface - - - - - - #include - - - - #include - #include - - - #include - - - #include - - - - - Authentication - - - - - MD5 key id - - u32:1-255 - OSPF key id - - - - - - - - - Authentication password - - txt - MD5 Key (16 characters or less) - - - [^[:space:]]{1,16} - - Password must be 16 characters or less - - - - - - - Plain text password - - txt - Plain text password (16 characters or less) - - - [^[:space:]]{1,16} - - Password must be 16 characters or less - - - - - - - Advertisement reception - - - #include - - - - - Advertisement transmission - - - #include - - - - - - - Neighbor router - - ipv4 - Neighbor router - - - - - - - - - - RIP network - - ipv4net - RIP network - - - - - - - - - - Source network - - ipv4net - Source network - - - - - - - - - Access list - - txt - Access list - - - policy access-list - - - - #include - - - #include - - - Redistribute information from another routing protocol - - - - - Redistribute BGP routes - - - #include - - - - - Redistribute connected routes - - - #include - - - - - Redistribute IS-IS routes - - - #include - - - - - Redistribute kernel routes - - - #include - - - - - Redistribute OSPF routes - - - #include - - - - - Redistribute static routes - - - #include - - - - - Redistribute Babel routes - - - #include - - - - - - - RIP static route - - ipv4net - RIP static route - - - - - - - - #include - #include - #include - - - - - diff --git a/interface-definitions/protocols-ripng.xml.in b/interface-definitions/protocols-ripng.xml.in deleted file mode 100644 index 9d4d87422..000000000 --- a/interface-definitions/protocols-ripng.xml.in +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - - Routing Information Protocol (RIPng) parameters - 660 - - - - - Aggregate RIPng route announcement - - ipv6net - Aggregate RIPng route announcement - - - - - - - - #include - #include - - - Filter networks in routing updates - - - #include - - - Apply filtering to an interface - - txt - Apply filtering to an interface - - - - - - #include - - - - #include - #include - - - #include - - - #include - - - RIPng network - - ipv6net - RIPng network - - - - - - - - - - Passive interface - - txt - Suppress routing updates on interface - - - - - - - - - - Redistribute information from another routing protocol - - - - - Redistribute BGP routes - - - #include - - - - - Redistribute connected routes - - - #include - - - - - Redistribute kernel routes - - - #include - - - - - Redistribute OSPFv3 routes - - - #include - - - - - Redistribute static routes - - - #include - - - - - Redistribute Babel routes - - - #include - - - - - - - RIPng static route - - ipv6net - RIPng static route - - - - - - - - #include - #include - - - - - diff --git a/interface-definitions/protocols-rpki.xml.in b/interface-definitions/protocols-rpki.xml.in deleted file mode 100644 index e9fd04b5f..000000000 --- a/interface-definitions/protocols-rpki.xml.in +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - BGP prefix origin validation - - - - - RPKI cache server address - - ipv4 - IP address of RPKI server - - - ipv6 - IPv6 address of RPKI server - - - hostname - Fully qualified domain name of RPKI server - - - - - - - - #include - - - Preference of the cache server - - u32:1-255 - Preference of the cache server - - - - - - - - - RPKI SSH connection settings - - - - - RPKI SSH known hosts file - - - - - - - - RPKI SSH private key file - - - - - - - - RPKI SSH public key file path - - - - - - #include - - - - - - - RPKI cache polling period - - u32:1-86400 - Polling period in seconds - - - - - - 300 - - - - - - diff --git a/interface-definitions/protocols-segment-routing.xml.in b/interface-definitions/protocols-segment-routing.xml.in deleted file mode 100644 index 4308f0c91..000000000 --- a/interface-definitions/protocols-segment-routing.xml.in +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - Segment Routing - 900 - - - - - Interface specific Segment Routing options - - - - - txt - Interface name - - - #include - - - - - - Accept SR-enabled IPv6 packets on this interface - - - - - Define HMAC policy for ingress SR-enabled packets on this interface - - accept drop ignore - - - accept - Accept packets without HMAC, validate packets with HMAC - - - drop - Drop packets without HMAC, validate packets with HMAC - - - ignore - Ignore HMAC field. - - - (accept|drop|ignore) - - - accept - - - - - - - - Segment-Routing SRv6 configuration - - - - - Segment Routing SRv6 locator - - #include - - - - - - Set SRv6 behavior uSID - - - - - - SRv6 locator prefix - - ipv6net - SRv6 locator prefix - - - - - - - - - Configure SRv6 locator block length in bits - - u32:16-64 - Specify SRv6 locator block length in bits - - - - - - 40 - - - - Configure SRv6 locator function length in bits - - u32:0-64 - Specify SRv6 locator function length in bits - - - - - - 16 - - - - Configure SRv6 locator node length in bits - - u32:16-64 - Configure SRv6 locator node length in bits - - - - - - 24 - - - - - - - - - - diff --git a/interface-definitions/protocols-static-arp.xml.in b/interface-definitions/protocols-static-arp.xml.in deleted file mode 100644 index 4b338df63..000000000 --- a/interface-definitions/protocols-static-arp.xml.in +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - Static ARP translation - - - - - Interface configuration - - - - - txt - Interface name - - - #include - - - - - - IP address for static ARP entry - - ipv4 - IPv4 destination address - - - - - - - #include - #include - - - - - - - - - - - diff --git a/interface-definitions/protocols-static.xml.in b/interface-definitions/protocols-static.xml.in deleted file mode 100644 index ca4ca2d74..000000000 --- a/interface-definitions/protocols-static.xml.in +++ /dev/null @@ -1,44 +0,0 @@ - - - - - Routing protocols - - - - - Static Routing - 480 - - - #include - #include - #include - - - Policy route table number - - u32:1-200 - Policy route table number - - - - - - - - #include - #include - #include - - - - - - - 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 @@ + + + + + + + Babel Routing Protocol + 650 + + + + + Babel-specific parameters + + + + + Enable diversity-aware routing + + + + + + Multiplicative factor used for diversity routing + + u32:1-256 + Multiplicative factor, in units of 1/256 + + + + + + 256 + + + + Time before resending a message + + u32:20-655340 + Milliseconds + + + + + + 2000 + + + + Smoothing half-life + + u32:0-65534 + Seconds + + + + + + 4 + + + + #include + + + Redistribute information from another routing protocol + + + + + Redistribute IPv4 routes + + + + + Redistribute BGP routes + + + + + + Redistribute connected routes + + + + + + Redistribute EIGRP routes + + + + + + Redistribute IS-IS routes + + + + + + Redistribute kernel routes + + + + + + Redistribute NHRP routes + + + + + + Redistribute OSPF routes + + + + + + Redistribute RIP routes + + + + + + Redistribute static routes + + + + + + + + Redistribute IPv6 routes + + + + + Redistribute BGP routes + + + + + + Redistribute connected routes + + + + + + Redistribute IS-IS routes + + + + + + Redistribute kernel routes + + + + + + Redistribute NHRP routes + + + + + + Redistribute OSPFv3 routes + + + + + + Redistribute RIPng routes + + + + + + Redistribute static routes + + + + + + + + + + Filter networks in routing updates + + + + + Filter IPv4 routes + + + #include + + + Apply filtering to an interface + + txt + Apply filtering to an interface + + + + + + #include + + + + #include + #include + + + #include + + + + + Filter IPv6 routes + + + #include + + + Apply filtering to an interface + + txt + Apply filtering to an interface + + + + + + #include + + + + #include + #include + + + #include + + + + + + + + + diff --git a/interface-definitions/protocols_bfd.xml.in b/interface-definitions/protocols_bfd.xml.in new file mode 100644 index 000000000..9048cf5c2 --- /dev/null +++ b/interface-definitions/protocols_bfd.xml.in @@ -0,0 +1,85 @@ + + + + + + + + Bidirectional Forwarding Detection (BFD) + 820 + + + + + Configures BFD peer to listen and talk to + + ipv4 + BFD peer IPv4 address + + + ipv6 + BFD peer IPv6 address + + + + + + + #include + + + Bind listener to specified interface/address, mandatory for IPv6 + + + #include + + + Local address to bind our peer listener to + + + + + ipv4 + Local IPv4 address used to connect to the peer + + + ipv6 + Local IPv6 address used to connect to the peer + + + + + + + + + #include + + + Allow this BFD peer to not be directly connected + + + + #include + + + + + Configure BFD profile used by individual peer + + txt + Name of BFD profile + + + [-_a-zA-Z0-9]{1,32} + + + + #include + + + + + + + diff --git a/interface-definitions/protocols_bgp.xml.in b/interface-definitions/protocols_bgp.xml.in new file mode 100644 index 000000000..e1a822999 --- /dev/null +++ b/interface-definitions/protocols_bgp.xml.in @@ -0,0 +1,16 @@ + + + + + + + Border Gateway Protocol (BGP) + 820 + + + #include + + + + + diff --git a/interface-definitions/protocols_eigrp.xml.in b/interface-definitions/protocols_eigrp.xml.in new file mode 100644 index 000000000..88a881a1e --- /dev/null +++ b/interface-definitions/protocols_eigrp.xml.in @@ -0,0 +1,17 @@ + + + + + + + + Enhanced Interior Gateway Routing Protocol (EIGRP) + 820 + + + #include + + + + + diff --git a/interface-definitions/protocols_failover.xml.in b/interface-definitions/protocols_failover.xml.in new file mode 100644 index 000000000..c0caec68e --- /dev/null +++ b/interface-definitions/protocols_failover.xml.in @@ -0,0 +1,135 @@ + + + + + + + Failover Routing + 490 + + + + + Failover IPv4 route + + ipv4net + IPv4 failover route + + + + + + + + + Next-hop IPv4 router address + + ipv4 + Next-hop router address + + + + + + + + + Check target options + + + + + Policy for check targets + + any-available all-available + + + all-available + All targets must be alive + + + any-available + Any target must be alive + + + (all-available|any-available) + + + any-available + + #include + + + Check target address + + ipv4 + Address to check + + + + + + + + + + Timeout between checks + + u32:1-300 + Timeout in seconds between checks + + + + + + 10 + + + + Check type + + arp icmp tcp + + + arp + Check target by ARP + + + icmp + Check target by ICMP + + + tcp + Check target by TCP + + + (arp|icmp|tcp) + + + icmp + + + + #include + + + Route metric for this gateway + + u32:1-255 + Route metric + + + + + + 1 + + + + + + + + + + diff --git a/interface-definitions/protocols_igmp-proxy.xml.in b/interface-definitions/protocols_igmp-proxy.xml.in new file mode 100644 index 000000000..5cde484f5 --- /dev/null +++ b/interface-definitions/protocols_igmp-proxy.xml.in @@ -0,0 +1,97 @@ + + + + + + + + Internet Group Management Protocol (IGMP) proxy parameters + 740 + + + #include + + + Option to disable "quickleave" + + + + + + Interface for IGMP proxy + + + + + + + + Unicast source networks allowed for multicast traffic to be proxyed + + ipv4net + IPv4 network + + + + + + + + + + IGMP interface role + + upstream downstream disabled + + + upstream + Upstream interface (only 1 allowed) + + + downstream + Downstream interface(s) + + + disabled + Disabled interface + + + (upstream|downstream|disabled) + + + downstream + + + + TTL threshold + + u32:1-255 + TTL threshold for the interfaces + + + + + Threshold must be between 1 and 255 + + 1 + + + + Group to whitelist + + ipv4net + IPv4 network + + + + + + + + + + + + + + diff --git a/interface-definitions/protocols_isis.xml.in b/interface-definitions/protocols_isis.xml.in new file mode 100644 index 000000000..e0bc47bb9 --- /dev/null +++ b/interface-definitions/protocols_isis.xml.in @@ -0,0 +1,16 @@ + + + + + + + Intermediate System to Intermediate System (IS-IS) + 610 + + + #include + + + + + diff --git a/interface-definitions/protocols_mpls.xml.in b/interface-definitions/protocols_mpls.xml.in new file mode 100644 index 000000000..831601fc6 --- /dev/null +++ b/interface-definitions/protocols_mpls.xml.in @@ -0,0 +1,560 @@ + + + + + + + + Multiprotocol Label Switching (MPLS) + 490 + + + + + Label Distribution Protocol (LDP) + + + #include + + + Forwarding equivalence class allocation from local routes + + + + + IPv4 routes + + + + + Access-list number + + u32:1-2699 + Access list number + + + + + + + + + + + IPv6 routes + + + + + Access-list6 number + + u32:1-2699 + Access list number + + + + + + + + + + + + + LDP neighbor parameters + + ipv4 + Neighbor IPv4 address + + + + + + + + + Neighbor password + + + + + Neighbor TTL security + + disable + + + u32:1-254 + TTL + + + disable + Disable neighbor TTL security + + + + + + Session IPv4 hold time + + u32:15-65535 + Time in seconds + + + + + + + + + + + Discovery parameters + + ipv4 + Discovery parameters + + + + + + Hello IPv4 hold time + + u32:1-65535 + Time in seconds + + + + + + + + + Hello IPv4 interval + + u32:1-65535 + Time in seconds + + + + + + + + + Hello IPv6 hold time + + u32:1-65535 + Time in seconds + + + + + + + + + Hello IPv6 interval + + u32:1-65535 + Time in seconds + + + + + + + + + Session IPv4 hold time + + u32:15-65535 + Time in seconds + + + + + + + + + Session IPv6 hold time + + u32:15-65535 + Time in seconds + + + + + + + + + Transport IPv4 address + + ipv4 + IPv4 bind as transport + + + + + + + + + Transport IPv6 address + + ipv6 + IPv6 bind as transport + + + + + + + + + + + Targeted LDP neighbor/session parameters + + + + + Targeted IPv4 neighbor/session parameters + + + + + Neighbor/session address + + ipv4 + Neighbor/session address + + + + + + + + + + Accept and respond to targeted hellos + + + + + + Hello interval + + u32:1-65535 + Time in seconds + + + + + + + + + Hello hold time + + u32:1-65535 + Time in seconds + + + + + + + + + + + Targeted IPv6 neighbor/session parameters + + + + + Neighbor/session address + + ipv6 + Neighbor/session address + + + + + + + + + + Accept and respond to targeted hellos + + + + + + Hello interval + + u32:1-65535 + Time in seconds + + + + + + + + + Hello hold time + + u32:1-65535 + Time in seconds + + + + + + + + + + + + + Label Distribution Protocol miscellaneous parameters + + + + + Enable Cisco non-compliant format capability TLV + + + + + + Prefer IPv4 for TCP peer transport connection + + + + + + Enable LDP ordered label distribution control mode + + + + + + + + Export parameters + + + + + IPv4 parameters + + + + + Explicit-Null Label + + + + + + Forwarding equivalence class export filter + + + + + Access-list number to apply FEC filtering + + u32:1-2699 + Access list number + + + + + + + + + Access-list number for IPv4 neighbor selection to apply filtering + + u32:1-2699 + Access list number + + + + + + + + + + + + + IPv6 parameters + + + + + Explicit-Null Label + + + + + + Forwarding equivalence class export filter + + + + + Access-list6 number to apply FEC filtering + + u32:1-2699 + Access list number + + + + + + + + + Access-list6 number for IPv6 neighbor selection to apply filtering + + u32:1-2699 + Access list number + + + + + + + + + + + + + + + Import parameters + + + + + IPv4 parameters + + + + + Forwarding equivalence class import filter + + + + + Access-list number to apply FEC filtering + + u32:1-2699 + Access list number + + + + + + + + + Access-list number for IPv4 neighbor selection to apply filtering + + u32:1-2699 + Access list number + + + + + + + + + + + + + IPv6 parameters + + + + + Forwarding equivalence class import filter + + + + + Access-list6 number to apply FEC filtering + + u32:1-2699 + Access list number + + + + + + + + + Access-list6 number for IPv6 neighbor selection to apply filtering + + u32:1-2699 + Access list number + + + + + + + + + + + + + #include + + + + + Multiprotocol Label Switching miscellaneous parameters + + + + + Disable copy of IP TTL to MPLS TTL + + + + + + Maximum TTL for MPLS packets + + u32:1-255 + Maximum hops allowed + + + + + + + + + #include + + + + + diff --git a/interface-definitions/protocols_nhrp.xml.in b/interface-definitions/protocols_nhrp.xml.in new file mode 100644 index 000000000..d7663c095 --- /dev/null +++ b/interface-definitions/protocols_nhrp.xml.in @@ -0,0 +1,138 @@ + + + + + + + Next Hop Resolution Protocol (NHRP) parameters + 680 + + + + + Tunnel for NHRP + + tun[0-9]+ + + + tunN + NHRP tunnel name + + + + + + Pass phrase for cisco authentication + + txt + Pass phrase for cisco authentication + + + [^[:space:]]{1,8} + + Password should contain up to eight non-whitespace characters + + + + + Set an HUB tunnel address + + ipv4net + Set the IP address and prefix length + + + + + + Set HUB fqdn (nbma-address - fqdn) + + <fqdn> + Set the external HUB fqdn + + + + + + + + Holding time in seconds + + + + + Set an HUB tunnel address + + + + + If the statically mapped peer is running Cisco IOS, specify this + + + + + + Set HUB address (nbma-address - external hub address or fqdn) + + + + + Specifies that Registration Request should be sent to this peer on startup + + + + + + + + Set multicast for NHRP + + dynamic nhs + + + (dynamic|nhs) + + + + + + This can be used to reduce memory consumption on big NBMA subnets + + + + + + Enable sending of Cisco style NHRP Traffic Indication packets + + + + + + This instructs opennhrp to reply with authorative answers on NHRP Resolution Requests destined to addresses in this interface + + + + + + Defines an off-NBMA network prefix for which the GRE interface will act as a gateway + + + + + Holding time in seconds + + + + + + + Enable creation of shortcut routes. A received NHRP Traffic Indication will trigger the resolution and establishment of a shortcut route + + + + + + + + + + diff --git a/interface-definitions/protocols_ospf.xml.in b/interface-definitions/protocols_ospf.xml.in new file mode 100644 index 000000000..b3c063d0d --- /dev/null +++ b/interface-definitions/protocols_ospf.xml.in @@ -0,0 +1,16 @@ + + + + + + + Open Shortest Path First (OSPF) + 620 + + + #include + + + + + diff --git a/interface-definitions/protocols_ospfv3.xml.in b/interface-definitions/protocols_ospfv3.xml.in new file mode 100644 index 000000000..2b98ffa7b --- /dev/null +++ b/interface-definitions/protocols_ospfv3.xml.in @@ -0,0 +1,16 @@ + + + + + + + Open Shortest Path First (OSPF) for IPv6 + 620 + + + #include + + + + + diff --git a/interface-definitions/protocols_pim.xml.in b/interface-definitions/protocols_pim.xml.in new file mode 100644 index 000000000..4a20c0d9b --- /dev/null +++ b/interface-definitions/protocols_pim.xml.in @@ -0,0 +1,210 @@ + + + + + + + + Protocol Independent Multicast (PIM) and IGMP + 400 + + + + + PIM interface + + + + + #include + + + + #include + #include + #include + #include + #include + #include + + + Internet Group Management Protocol (IGMP) options + + + #include + + + IGMP join multicast group + + ipv4 + Multicast group address + + + + + + + #include + + + + + IGMP host query interval + + u32:1-1800 + Query interval in seconds + + + + + + + + + IGMP max query response time + + u32:10-250 + Query response value in deci-seconds + + + + + + + + + Interface IGMP version + + 2 3 + + + 2 + IGMP version 2 + + + 3 + IGMP version 3 + + + + + + 3 + + + + + + + + Enable PIM ECMP + + + + + Enable PIM ECMP Rebalance + + + + + + + + Internet Group Management Protocol (IGMP) options + + + + + Configure group limit for watermark warning + + u32:1-65535 + Group count to generate watermark warning + + + + + + + + + #include + #include + #include + #include + + + Only accept registers from a specific source prefix list + + + #include + + + + + Rendezvous Point + + + + + Rendezvous Point address + + ipv4 + Rendezvous Point address + + + + + + + + + Group Address range + + ipv4net + Group Address range RFC 3171 + + + + + + + + + + #include + + + + + Disable IPv6 secondary address in hello packets + + + + + + Shortest-path tree (SPT) switchover + + + + + Never switch to SPT Tree + + + #include + + + + + + + Source-Specific Multicast + + + #include + + + + + + + diff --git a/interface-definitions/protocols_pim6.xml.in b/interface-definitions/protocols_pim6.xml.in new file mode 100644 index 000000000..8bd3f3fee --- /dev/null +++ b/interface-definitions/protocols_pim6.xml.in @@ -0,0 +1,179 @@ + + + + + + + + Protocol Independent Multicast for IPv6 (PIMv6) and MLD + 400 + + + + + PIMv6 interface + + + + + #include + + + + #include + #include + #include + #include + + + Multicast Listener Discovery (MLD) + + + #include + + + MLD join multicast group + + ipv6 + Multicast group address + + + + + + + + + Source address + + ipv6 + Source address + + + + + + + + + + + + + + + Last member query count + + u32:1-255 + Count + + + + + + + + + Last member query interval + + u32:100-6553500 + Last member query interval in milliseconds + + + + + + + + + Query interval + + u32:1-65535 + Query interval in seconds + + + + + + + + + Max query response time + + u32:100-6553500 + Query response value in milliseconds + + + + + + + + + MLD version + + 1 2 + + + 1 + MLD version 1 + + + 2 + MLD version 2 + + + + + + 2 + + + + + + #include + #include + #include + #include + + + Rendezvous Point + + + + + Rendezvous Point address + + ipv6 + Rendezvous Point address + + + + + + + + + Group Address range + + ipv6net + Group Address range + + + + + + + + #include + + + #include + + + + + + + diff --git a/interface-definitions/protocols_rip.xml.in b/interface-definitions/protocols_rip.xml.in new file mode 100644 index 000000000..0edd8f2ce --- /dev/null +++ b/interface-definitions/protocols_rip.xml.in @@ -0,0 +1,258 @@ + + + + + + + Routing Information Protocol (RIP) parameters + 650 + + + + + Administrative distance + + u32:1-255 + Administrative distance + + + + + + + #include + #include + + + Filter networks in routing updates + + + #include + + + Apply filtering to an interface + + txt + Apply filtering to an interface + + + + + + #include + + + + #include + #include + + + #include + + + #include + + + + + Authentication + + + + + MD5 key id + + u32:1-255 + OSPF key id + + + + + + + + + Authentication password + + txt + MD5 Key (16 characters or less) + + + [^[:space:]]{1,16} + + Password must be 16 characters or less + + + + + + + Plain text password + + txt + Plain text password (16 characters or less) + + + [^[:space:]]{1,16} + + Password must be 16 characters or less + + + + + + + Advertisement reception + + + #include + + + + + Advertisement transmission + + + #include + + + + + + + Neighbor router + + ipv4 + Neighbor router + + + + + + + + + + RIP network + + ipv4net + RIP network + + + + + + + + + + Source network + + ipv4net + Source network + + + + + + + + + Access list + + txt + Access list + + + policy access-list + + + + #include + + + #include + + + Redistribute information from another routing protocol + + + + + Redistribute BGP routes + + + #include + + + + + Redistribute connected routes + + + #include + + + + + Redistribute IS-IS routes + + + #include + + + + + Redistribute kernel routes + + + #include + + + + + Redistribute OSPF routes + + + #include + + + + + Redistribute static routes + + + #include + + + + + Redistribute Babel routes + + + #include + + + + + + + RIP static route + + ipv4net + RIP static route + + + + + + + + #include + #include + #include + + + + + diff --git a/interface-definitions/protocols_ripng.xml.in b/interface-definitions/protocols_ripng.xml.in new file mode 100644 index 000000000..9d4d87422 --- /dev/null +++ b/interface-definitions/protocols_ripng.xml.in @@ -0,0 +1,155 @@ + + + + + + + Routing Information Protocol (RIPng) parameters + 660 + + + + + Aggregate RIPng route announcement + + ipv6net + Aggregate RIPng route announcement + + + + + + + + #include + #include + + + Filter networks in routing updates + + + #include + + + Apply filtering to an interface + + txt + Apply filtering to an interface + + + + + + #include + + + + #include + #include + + + #include + + + #include + + + RIPng network + + ipv6net + RIPng network + + + + + + + + + + Passive interface + + txt + Suppress routing updates on interface + + + + + + + + + + Redistribute information from another routing protocol + + + + + Redistribute BGP routes + + + #include + + + + + Redistribute connected routes + + + #include + + + + + Redistribute kernel routes + + + #include + + + + + Redistribute OSPFv3 routes + + + #include + + + + + Redistribute static routes + + + #include + + + + + Redistribute Babel routes + + + #include + + + + + + + RIPng static route + + ipv6net + RIPng static route + + + + + + + + #include + #include + + + + + diff --git a/interface-definitions/protocols_rpki.xml.in b/interface-definitions/protocols_rpki.xml.in new file mode 100644 index 000000000..e9fd04b5f --- /dev/null +++ b/interface-definitions/protocols_rpki.xml.in @@ -0,0 +1,95 @@ + + + + + + + BGP prefix origin validation + + + + + RPKI cache server address + + ipv4 + IP address of RPKI server + + + ipv6 + IPv6 address of RPKI server + + + hostname + Fully qualified domain name of RPKI server + + + + + + + + #include + + + Preference of the cache server + + u32:1-255 + Preference of the cache server + + + + + + + + + RPKI SSH connection settings + + + + + RPKI SSH known hosts file + + + + + + + + RPKI SSH private key file + + + + + + + + RPKI SSH public key file path + + + + + + #include + + + + + + + RPKI cache polling period + + u32:1-86400 + Polling period in seconds + + + + + + 300 + + + + + + diff --git a/interface-definitions/protocols_segment-routing.xml.in b/interface-definitions/protocols_segment-routing.xml.in new file mode 100644 index 000000000..c299f624e --- /dev/null +++ b/interface-definitions/protocols_segment-routing.xml.in @@ -0,0 +1,137 @@ + + + + + + + Segment Routing + 900 + + + + + Interface specific Segment Routing options + + + + + txt + Interface name + + + #include + + + + + + Accept SR-enabled IPv6 packets on this interface + + + + + Define HMAC policy for ingress SR-enabled packets on this interface + + accept drop ignore + + + accept + Accept packets without HMAC, validate packets with HMAC + + + drop + Drop packets without HMAC, validate packets with HMAC + + + ignore + Ignore HMAC field. + + + (accept|drop|ignore) + + + accept + + + + + + + + Segment-Routing SRv6 configuration + + + + + Segment Routing SRv6 locator + + #include + + + + + + Set SRv6 behavior uSID + + + + + + SRv6 locator prefix + + ipv6net + SRv6 locator prefix + + + + + + + + + Configure SRv6 locator block length in bits + + u32:16-64 + Specify SRv6 locator block length in bits + + + + + + 40 + + + + Configure SRv6 locator function length in bits + + u32:0-64 + Specify SRv6 locator function length in bits + + + + + + 16 + + + + Configure SRv6 locator node length in bits + + u32:16-64 + Configure SRv6 locator node length in bits + + + + + + 24 + + + + + + + + + + diff --git a/interface-definitions/protocols_static.xml.in b/interface-definitions/protocols_static.xml.in new file mode 100644 index 000000000..ca4ca2d74 --- /dev/null +++ b/interface-definitions/protocols_static.xml.in @@ -0,0 +1,44 @@ + + + + + Routing protocols + + + + + Static Routing + 480 + + + #include + #include + #include + + + Policy route table number + + u32:1-200 + Policy route table number + + + + + + + + #include + #include + #include + + + + + + + diff --git a/interface-definitions/protocols_static_arp.xml.in b/interface-definitions/protocols_static_arp.xml.in new file mode 100644 index 000000000..05c69f1ed --- /dev/null +++ b/interface-definitions/protocols_static_arp.xml.in @@ -0,0 +1,51 @@ + + + + + + + + + Static ARP translation + + + + + Interface configuration + + + + + txt + Interface name + + + #include + + + + + + IP address for static ARP entry + + ipv4 + IPv4 destination address + + + + + + + #include + #include + + + + + + + + + + + diff --git a/interface-definitions/protocols_static_multicast.xml.in b/interface-definitions/protocols_static_multicast.xml.in new file mode 100644 index 000000000..c8e28ed35 --- /dev/null +++ b/interface-definitions/protocols_static_multicast.xml.in @@ -0,0 +1,94 @@ + + + + + + + + + Multicast static route + + + + + Configure static unicast route into MRIB for multicast RPF lookup + + ipv4net + Network + + + + + + + + + Nexthop IPv4 address + + ipv4 + Nexthop IPv4 address + + + + + + + + + Distance value for this route + + u32:1-255 + Distance for this route + + + + + + + + + + + + + Multicast interface based route + + ipv4net + Network + + + + + + + + + Next-hop interface + + + + + + + + Distance value for this route + + u32:1-255 + Distance for this route + + + + + + + + + + + + + + + + + diff --git a/interface-definitions/salt-minion.xml.in b/interface-definitions/salt-minion.xml.in deleted file mode 100644 index c3219cff3..000000000 --- a/interface-definitions/salt-minion.xml.in +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - Salt Minion - 500 - - - - - Hash used when discovering file on master server (default: sha256) - - md5 sha1 sha224 sha256 sha384 sha512 - - - (md5|sha1|sha224|sha256|sha384|sha512) - - - sha256 - - - - Hostname or IP address of the Salt master server - - ipv4 - Salt server IPv4 address - - - ipv6 - Salt server IPv6 address - - - hostname - Salt server FQDN address - - - - - - Invalid FQDN or IP address - - - - - - Explicitly declare ID for this minion to use (default: hostname) - - - - - Interval in minutes between updates (default: 60) - - u32:1-1440 - Update interval in minutes - - - - - - 60 - - - - URL with signature of master for auth reply verification - - - #include - - - - - diff --git a/interface-definitions/service-aws-glb.xml.in b/interface-definitions/service-aws-glb.xml.in deleted file mode 100644 index c749fd04e..000000000 --- a/interface-definitions/service-aws-glb.xml.in +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - Amazon Web Service - 1280 - - - - - Gateway load-balancer tunnel handler - - - - - Script executed on create or destroy tunnel - - - - - Script to run when interface is created - - - - - - - - Script to run when interface is destroyed - - - - - - - - - - Status - - - - - Statistic format - - simple full - - - simple - Simple format - - - full - Full format - - - (simple|full) - - - - #include - - - - - Threads settings - - - - - Number of threads for each tunnel processor - - u32:1-256 - Number of threads - - - - - - - - - List of cores worker threads - - <idN>-<idM> - CPU core id range (use '-' as delimiter) - - - - - - - - - Number of threads for UDP receiver - - u32:1-256 - Number of threads - - - - - - - - - List of cores worker threads - - <idN>-<idM> - CPU core id range (use '-' as delimiter) - - - - - - - - - - - - - - - diff --git a/interface-definitions/service-config-sync.xml.in b/interface-definitions/service-config-sync.xml.in deleted file mode 100644 index e804e17f7..000000000 --- a/interface-definitions/service-config-sync.xml.in +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - Configuration synchronization - - - - - Secondary server parameters - - - - - IP address - - ipv4 - IPv4 address to match - - - ipv6 - IPv6 address to match - - - hostname - FQDN address to match - - - - - - - - - - - Connection API timeout - - u32:1-300 - Connection API timeout - - - - - - 60 - - - - HTTP API key - - - - - - - Synchronization mode - - load set - - - load - Load and replace configuration section - - - set - Set configuration section - - - (load|set) - - - - - - Section for synchronization - - nat nat66 firewall - - - nat - NAT - - - nat66 - NAT66 - - - firewall - firewall - - - (nat|nat66|firewall) - - - - - - - - - diff --git a/interface-definitions/service-conntrack-sync.xml.in b/interface-definitions/service-conntrack-sync.xml.in deleted file mode 100644 index 50a4bf62f..000000000 --- a/interface-definitions/service-conntrack-sync.xml.in +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - - Connection tracking synchronization - - 799 - - - - - Protocols for which local conntrack entries will be synced - - tcp udp icmp icmp6 sctp dccp - - - tcp - Sync Transmission Control Protocol entries - - - udp - Sync User Datagram Protocol entries - - - icmp - Sync Internet Control Message Protocol entries - - - icmp6 - Sync IPv6 Internet Control Message Protocol entries - - - sctp - Sync Stream Control Transmission Protocol entries - - - dccp - Sync Datagram Congestion Control Protocol entries - - - (tcp|udp|icmp|icmp6|sctp|dccp) - - Allowed protocols: tcp udp icmp or sctp - - - - - - Directly injects the flow-states into the in-kernel Connection Tracking System of the backup firewall. - - - - - - Queue size for local conntrack events - - u32 - Queue size in MB - - - 8 - - - - Protocol for which expect entries need to be synchronized - - all ftp sip h323 nfs sqlnet - - - (all|ftp|sip|h323|nfs|sqlnet) - - Invalid protocol - - - - - - Failover mechanism to use for conntrack-sync - - - - - VRRP as failover-mechanism to use for conntrack-sync - - - - - VRRP sync group - - high-availability vrrp sync-group - - - - - - - - - - IP addresses for which local conntrack entries will not be synced - - ipv4 - IPv4 address to ignore - - - ipv4net - IPv4 prefix to ignore - - - ipv6 - IPv6 address to ignore - - - ipv6net - IPv6 prefix to ignore - - - - - - - - - - - Interface to use for syncing conntrack entries - - - - - - - - IP address of the peer to send the UDP conntrack info too. This disable multicast. - - ipv4 - IP address to listen for incoming connections - - - - - - - #include - - - #include - - - Multicast group to use for syncing conntrack entries - - - - - 225.0.0.50 - - - - Queue size for syncing conntrack entries - - u32 - Queue size in MB - - - 1 - - - - - - diff --git a/interface-definitions/service-console-server.xml.in b/interface-definitions/service-console-server.xml.in deleted file mode 100644 index fc6dbe954..000000000 --- a/interface-definitions/service-console-server.xml.in +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - Serial Console Server - - - - - System serial interface name (ttyS or ttyUSB) - - - - - - ttySxxx - Regular serial interface - - - usbxbxpx - USB based serial interface - - - (ttyS\d+|usb\d+b.*p.*) - - - - #include - - - Human-readable name for this console - - [-_a-zA-Z0-9.]{1,128} - - - - - - Serial port baud rate - - 300 1200 2400 4800 9600 19200 38400 57600 115200 - - - (300|1200|2400|4800|9600|19200|38400|57600|115200) - - - - - - Serial port data bits - - 7 8 - - - - - - 8 - - - - Serial port stop bits - - 1 2 - - - - - - 1 - - - - Parity setting - - even odd none - - - (even|odd|none) - - - none - - - - SSH remote access to this console - - - #include - - - - - - - - - diff --git a/interface-definitions/service-event-handler.xml.in b/interface-definitions/service-event-handler.xml.in deleted file mode 100644 index aef6bc1bc..000000000 --- a/interface-definitions/service-event-handler.xml.in +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - Service event handler - - - - - Event handler name - - - - - Logs filter settings - - - - - Match pattern (regex) - - - - - Identifier of a process in syslog (string) - - - - - - - Event handler script file - - - - - Script arguments - - - - - Script environment arguments - - - - - Environment value - - - - - - - Path to the script - - - - - - - - - - - - - - diff --git a/interface-definitions/service-ids-ddos-protection.xml.in b/interface-definitions/service-ids-ddos-protection.xml.in deleted file mode 100644 index 78463136b..000000000 --- a/interface-definitions/service-ids-ddos-protection.xml.in +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - Intrusion Detection System - - - - - FastNetMon detection and protection parameters - 731 - - - - - Path to fastnetmon alert script - - - - - How long we should keep an IP in blocked state - - u32:1-4294967294 - Time in seconds - - - - - - 1900 - - - - Direction for processing traffic - - in out - - - (in|out) - - - - - - - Specify IPv4 and IPv6 networks which are going to be excluded from protection - - ipv4net - IPv4 prefix(es) to exclude - - - ipv6net - IPv6 prefix(es) to exclude - - - - - - - - - - - Listen interface for mirroring traffic - - - - - - - - - Traffic capture mode - - mirror sflow - - - mirror - Listen to mirrored traffic - - - sflow - Capture sFlow flows - - - (mirror|sflow) - - - - - - Sflow settings - - - #include - #include - - 6343 - - - - - - Specify IPv4 and IPv6 networks which belong to you - - ipv4net - Your IPv4 prefix(es) - - - ipv6net - Your IPv6 prefix(es) - - - - - - - - - - - Attack limits thresholds - - - - - General threshold - - - #include - - - - - TCP threshold - - - #include - - - - - UDP threshold - - - #include - - - - - ICMP threshold - - - #include - - - - - - - - - - - diff --git a/interface-definitions/service-ipoe-server.xml.in b/interface-definitions/service-ipoe-server.xml.in deleted file mode 100644 index edfe6a34c..000000000 --- a/interface-definitions/service-ipoe-server.xml.in +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - Internet Protocol over Ethernet (IPoE) Server - 900 - - - - - Interface to listen dhcp or unclassified packets - - - - - - - - Client connectivity mode - - l2 l3 - - - l2 - Client located on same interface as server - - - l3 - Client located behind a router - - - (l2|l3) - - - l2 - - - - Enables clients to share the same network or each client has its own vlan - - shared vlan - - - (shared|vlan) - - - shared - Multiple clients share the same network - - - vlan - One VLAN per client - - - shared - - - - Client address pool - - ipv4net - IPv4 address and prefix length - - - - - - - - - DHCP requests will be forwarded - - - - - DHCP Server the request will be redirected to. - - ipv4 - IPv4 address of the DHCP Server - - - - - - - - - Relay Agent IPv4 Address - - ipv4 - Gateway IP address - - - - - - - - - #include - - - #include - #include - #include - #include - #include - - - Client authentication methods - - - #include - - - Network interface for client MAC addresses - - - - - - - - Media Access Control (MAC) address - - macaddr - Hardware (MAC) address - - - - - - - - - Upload/Download speed limits - - - - - Upload bandwidth limit in kbits/sec - - - - - - - - Download bandwidth limit in kbits/sec - - - - - - - - - - VLAN monitor for automatic creation of VLAN interfaces - - u32:1-4094 - Client VLAN id - - - - - VLAN IDs need to be in range 1-4094 - - - - - - - - - #include - - - #include - #include - - - #include - - - - - diff --git a/interface-definitions/service-mdns-repeater.xml.in b/interface-definitions/service-mdns-repeater.xml.in deleted file mode 100644 index 67870946c..000000000 --- a/interface-definitions/service-mdns-repeater.xml.in +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - Multicast DNS (mDNS) parameters - - - - - mDNS repeater configuration - 990 - - - #include - #include - - - IP address version to use - - _ipv4 - Use only IPv4 address - - - _ipv6 - Use only IPv6 address - - - both - Use both IPv4 and IPv6 address - - - ipv4 ipv6 both - - - (ipv[46]|both) - - IP Version must be literal 'ipv4', 'ipv6' or 'both' - - both - - - - mDNS browsing domains in addition to the default one - - txt - mDNS browsing domain - - - - - - - - - - Allowed mDNS services to be repeated - - txt - mDNS service - - - [-_.a-zA-Z0-9]+ - - Service name must be alphanumeric and can contain hyphens and underscores - - - - - - Disables mDNS repeater on VRRP interfaces not in MASTER state - - - - - - - - - - diff --git a/interface-definitions/service-monitoring-telegraf.xml.in b/interface-definitions/service-monitoring-telegraf.xml.in deleted file mode 100644 index 4d694114a..000000000 --- a/interface-definitions/service-monitoring-telegraf.xml.in +++ /dev/null @@ -1,284 +0,0 @@ - - - - - - - Monitoring services - 1280 - - - - - Telegraf metric collector - - - - - Output plugin InfluxDB - - - - - Authentication parameters - - - - - Authentication organization for InfluxDB v2 - - [a-zA-Z][1-9a-zA-Z@_\-.]{2,50} - - Organization name must be alphanumeric and can contain hyphens, underscores and at symbol. - - - - - Authentication token for InfluxDB v2 - - txt - Authentication token - - - [a-zA-Z0-9-_]{86}== - - Token must be 88 characters long and must contain only [a-zA-Z0-9-_] and '==' characters. - - - - - - - Remote bucket - - main - - #include - #include - - 8086 - - - - - - Output plugin Azure Data Explorer - - - - - Authentication parameters - - - - - Application client id - - #include - - Client-id is limited to alphanumerical characters and can contain hyphen and underscores - - - - - Application client secret - - #include - - Client-secret is limited to alphanumerical characters and can contain hyphen and underscores - - - - - Set tenant id - - #include - - Tenant-id is limited to alphanumerical characters and can contain hyphen and underscores - - - - - - - Remote database name - - txt - Remote database name - - - #include - - Database is limited to alphanumerical characters and can contain hyphen and underscores - - - - - Type of metrics grouping when push to Azure Data Explorer - - single-table table-per-metric - - - single-table - Metrics stores in one table - - - table-per-metric - One table per gorups of metric by the metric name - - - (single-table|table-per-metric) - - - table-per-metric - - - - Name of the single table [Only if set group-metrics single-table] - - txt - Table name - - - #include - - Table is limited to alphanumerical characters and can contain hyphen and underscores - - - #include - - - - - Source parameters for monitoring - - all hardware-utilization logs network system telegraf - - - all - All parameters - - - hardware-utilization - Hardware-utilization parameters (CPU, disk, memory) - - - logs - Logs parameters - - - network - Network parameters (net, netstat, nftables) - - - system - System parameters (system, processes, interrupts) - - - telegraf - Telegraf internal statistics - - - (all|hardware-utilization|logs|network|system|telegraf) - - - - all - - - - Output plugin Prometheus client - - - - - HTTP basic authentication parameters - - - - - Authentication username - - - - - Authentication password - - txt - Authentication password - - - - - - - - Networks allowed to query this server - - ipv4net - IP address and prefix length - - - ipv6net - IPv6 address and prefix length - - - - - - - - #include - - - Metric version control mapping from Telegraf to Prometheus format - - u32:1-2 - Metric version (default: 2) - - - - - - 2 - - #include - - 9273 - - - - - - Output plugin Splunk - - - - - HTTP basic authentication parameters - - - - - Authorization token - - - - - Use TLS but skip host validation - - - - - - #include - - - #include - - - - - - - diff --git a/interface-definitions/service-monitoring-zabbix-agent.xml.in b/interface-definitions/service-monitoring-zabbix-agent.xml.in deleted file mode 100644 index 40f2df642..000000000 --- a/interface-definitions/service-monitoring-zabbix-agent.xml.in +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - - - - Zabbix-agent settings - - - - - Folder containing individual Zabbix-agent configuration files - - - - - - - - Zabbix agent hostname - - #include - - Host-name must be alphanumeric and can contain hyphens - - - - - Limit settings - - - - - Do not keep data longer than N seconds in buffer - - u32:1-3600 - Seconds - - - - - buffer-flush-interval must be between 1 and 3600 seconds - - 5 - - - - Maximum number of values in a memory buffer - - u32:2-65535 - Maximum number of values in a memory buffer - - - - - Buffer-size must be between 2 and 65535 - - 100 - - - - - - Log settings - - - - - Debug level - - basic critical error warning debug extended-debug - - - basic - Basic information - - - critical - Critical information - - - error - Error information - - - warning - Warnings - - - debug - Debug information - - - extended-debug - Extended debug information - - - (basic|critical|error|warning|debug|extended-debug) - - - warning - - - - Enable logging of executed shell commands as warnings - - - - - - Log file size in megabytes - - u32:0-1024 - Megabytes - - - - - Size must be between 0 and 1024 Megabytes - - 0 - - - - #include - - 0.0.0.0 - - #include - - 10050 - - - - Remote server to connect to - - ipv4 - Server IPv4 address - - - ipv6 - Server IPv6 address - - - hostname - Server hostname/FQDN - - - - - - - Remote server address to get active checks from - - ipv4 - Server IPv4 address - - - ipv6 - Server IPv6 address - - - hostname - Server hostname/FQDN - - - - #include - - - - - Item processing timeout in seconds - - u32:1-30 - Item processing timeout - - - - - Timeout must be between 1 and 30 seconds - - 3 - - - - - - - - diff --git a/interface-definitions/service-pppoe-server.xml.in b/interface-definitions/service-pppoe-server.xml.in deleted file mode 100644 index f1b369936..000000000 --- a/interface-definitions/service-pppoe-server.xml.in +++ /dev/null @@ -1,281 +0,0 @@ - - - - - - - Point to Point over Ethernet (PPPoE) Server - 900 - - - #include - - vyos-ac - - - - Authentication for remote access PPPoE Server - - - #include - #include - #include - #include - #include - - - #include - - - Format of Called-Station-Id attribute - - ifname ifname:mac - - - (ifname|ifname:mac) - - Invalid Called-Station-Id format - - ifname - NAS-Port-Id - should contain root interface name (NAS-Port-Id=eth1) - - - ifname:mac - NAS-Port-Id - should contain root interface name and mac address (NAS-Port-Id=eth1:00:00:00:00:00:00) - - - - - - - - #include - #include - #include - - - interface(s) to listen on - - - - - - #include - - - #include - #include - #include - - - Limits the connection rate from a single source - - - - - Acceptable rate of connections (e.g. 1/min, 60/sec) - - [0-9]+\/(min|sec) - - illegal value - - - - - Burst count - - - - - Timeout in seconds - - - - - - - Service name - - [a-zA-Z0-9\-]{1,100} - - Service-name can contain aplhanumerical characters and dashes only (max. 100) - - - - #include - - - Advanced protocol options - - - - - Minimum acceptable MTU (68-65535) - - - - - 1280 - - - - Preferred MRU (68-65535) - - - - - - - - CCP negotiation (default disabled) - - - - #include - #include - #include - #include - - - IPv4 (IPCP) negotiation algorithm - - (deny|allow|prefer|require) - - invalid value - - deny - Do not negotiate IPv4 - - - allow - Negotiate IPv4 only if client requests - - - prefer - Ask client for IPv4 negotiation, do not fail if it rejects - - - require - Require IPv4 negotiation - - - deny allow prefer require - - - - #include - #include - - - - - PADO delays - - u32:1-999999 - Number in ms - - - - - Invalid PADO delay - - - - - Number of sessions - - u32:1-999999 - Number of sessions - - - - - Invalid number of delayed sessions - - - - - - - control sessions count - - (deny|disable|replace) - - Invalid value - - disable - Disables session control - - - deny - Deny second session authorization - - - replace - Terminate first session when second is authorized - - - deny disable replace - - - replace - - #include - - - Enable SNMP - - - - - enable SNMP master agent mode - - - - - - - - Extended script execution - - - - - Script to run before PPPoE session interface comes up - - - - - - - - Script to run when PPPoE session interface is completely configured and started - - - - - - - - Script to run when PPPoE session interface going to terminate - - - - - - - - Script to run when PPPoE session interface changed by RADIUS CoA handling - - - - - - - - #include - - - - - diff --git a/interface-definitions/service-router-advert.xml.in b/interface-definitions/service-router-advert.xml.in deleted file mode 100644 index 16c29022d..000000000 --- a/interface-definitions/service-router-advert.xml.in +++ /dev/null @@ -1,369 +0,0 @@ - - - - - - - IPv6 Router Advertisements (RAs) service - 900 - - - - - Interface to send RA on - - - - - - - - Set Hop Count field of the IP header for outgoing packets - - u32:0 - Unspecified (by this router) - - - u32:1-255 - Value should represent current diameter of the Internet - - - - - Hop count must be between 0 and 255 - - 64 - - - - Lifetime associated with the default router in units of seconds - - u32:4-9000 - Router Lifetime in seconds - - - 0 - Not a default router - - - - - Default router livetime bust be 0 or between 4 and 9000 - - - - - Preference associated with the default router, - - low medium high - - - low - Default router has low preference - - - medium - Default router has medium preference - - - high - Default router has high preference - - - (low|medium|high) - - Default preference must be low, medium or high - - medium - - - - DNS search list - - - - - - Link MTU value placed in RAs, exluded in RAs if unset - - u32:1280-9000 - Link MTU value in RAs - - - - - Link MTU must be between 1280 and 9000 - - - - - Hosts use the administered (stateful) protocol for address autoconfiguration in addition to any addresses autoconfigured using SLAAC - - - - - - Set interval between unsolicited multicast RAs - - - - - Maximum interval between unsolicited multicast RAs - - u32:4-1800 - Maximum interval in seconds - - - - - Maximum interval must be between 4 and 1800 seconds - - 600 - - - - Minimum interval between unsolicited multicast RAs - - u32:3-1350 - Minimum interval in seconds - - - - - Minimum interval must be between 3 and 1350 seconds - - - - - #include - - - Maximum duration how long the RDNSS entries are used - - u32:0 - Name-servers should no longer be used - - - u32:1-7200 - Maximum interval in seconds - - - - - Maximum interval must be between 1 and 7200 seconds - - - - - Hosts use the administered (stateful) protocol for autoconfiguration of other (non-address) information - - - - - - IPv6 route to be advertised in Router Advertisements (RAs) - - ipv6net - IPv6 route to be advertized - - - - - - - - - Time in seconds that the route will remain valid - - infinity - - - u32:1-4294967295 - Time in seconds that the route will remain valid - - - infinity - Route will remain preferred forever - - - - (infinity) - - - 1800 - - - - Preference associated with the route, - - low medium high - - - low - Route has low preference - - - medium - Route has medium preference - - - high - Route has high preference - - - (low|medium|high) - - Route preference must be low, medium or high - - medium - - - - Do not announce this route with a zero second lifetime upon shutdown - - - - - - - - IPv6 prefix to be advertised in Router Advertisements (RAs) - - ipv6net - IPv6 prefix to be advertized - - - - - - - - - Prefix can not be used for stateless address auto-configuration - - - - - - Prefix can not be used for on-link determination - - - - - - Upon shutdown, this option will deprecate the prefix by announcing it in the shutdown RA - - - - - - Lifetime is decremented by the number of seconds since the last RA - use in conjunction with a DHCPv6-PD prefix - - - - - - Time in seconds that the prefix will remain preferred - - infinity - - - u32 - Time in seconds that the prefix will remain preferred - - - infinity - Prefix will remain preferred forever - - - - (infinity) - - - 14400 - - - - Time in seconds that the prefix will remain valid - - infinity - - - u32:1-4294967295 - Time in seconds that the prefix will remain valid - - - infinity - Prefix will remain preferred forever - - - - (infinity) - - - 2592000 - - - - - - Use IPv6 address as source address. Useful with VRRP. - - ipv6 - IPv6 address to be advertized (must be configured on interface) - - - - - - - - - - Time, in milliseconds, that a node assumes a neighbor is reachable after having received a reachability confirmation - - u32:0 - Reachable Time unspecified by this router - - - u32:1-3600000 - Reachable Time value in RAs (in milliseconds) - - - - - Reachable time must be 0 or between 1 and 3600000 milliseconds - - 0 - - - - Time in milliseconds between retransmitted Neighbor Solicitation messages - - u32:0 - Time, in milliseconds, between retransmitted Neighbor Solicitation messages - - - u32:1-4294967295 - Minimum interval in milliseconds - - - - - Retransmit interval must be 0 or between 1 and 4294967295 milliseconds - - 0 - - - - Do not send router adverts - - - - - - - - - - diff --git a/interface-definitions/service-sla.xml.in b/interface-definitions/service-sla.xml.in deleted file mode 100644 index 0c4f8a591..000000000 --- a/interface-definitions/service-sla.xml.in +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - Service level agreement (SLA) - - - - - One-way active measurement protocol (OWAMP) server - - - #include - - 861 - - - - - - Two-way active measurement protocol (TWAMP) server - - - #include - - 862 - - - - - - - - diff --git a/interface-definitions/service-upnp.xml.in b/interface-definitions/service-upnp.xml.in deleted file mode 100644 index 20e01bfbd..000000000 --- a/interface-definitions/service-upnp.xml.in +++ /dev/null @@ -1,228 +0,0 @@ - - - - - - - Universal Plug and Play (UPnP) service - 900 - - - - - Name of this service - - txt - Friendly name - - - - - - WAN network interface - - - - - #include - - - - - - WAN network IP - - ipv4 - IPv4 address - - - ipv6 - IPv6 address - - - - - - - - - - - Enable NAT-PMP support - - - - - - Enable Secure Mode - - - - - - Presentation Url - - txt - Presentation Url - - - - - - PCP-base lifetime Option - - - - - Max lifetime time - - - - - - - - Min lifetime time - - - - - - - - - - Local IP addresses for service to listen on - - - - - - <interface> - Monitor interface address - - - ipv4 - IPv4 address to listen for incoming connections - - - ipv4net - IPv4 prefix to listen for incoming connections - - - ipv6 - IPv6 address to listen for incoming connections - - - ipv6net - IPv6 prefix to listen for incoming connections - - - - #include - - - - - - - - - Enable STUN probe support (can be used with NAT 1:1 support for WAN interfaces) - - - - - The STUN server address - - txt - The STUN server host address - - - - - - - #include - - - - - UPnP Rule - - u32:0-65535 - Rule number - - - - - - - #include - - - Port range (REQUIRE) - - <port> - single port - - - <portN>-<portM> - Port range (use '-' as delimiter) - - - - - - - - - Port range (REQUIRE) - - <port> - single port - - - <portN>-<portM> - Port range (use '-' as delimiter) - - - - - - - - - The IP to which this rule applies (REQUIRE) - - ipv4 - The IPv4 address to which this rule applies - - - ipv4net - The IPv4 to which this rule applies - - - - - - - - - - Actions against the rule (REQUIRE) - - allow deny - - - (allow|deny) - - - - - - - - - - diff --git a/interface-definitions/service-webproxy.xml.in b/interface-definitions/service-webproxy.xml.in deleted file mode 100644 index 637d57891..000000000 --- a/interface-definitions/service-webproxy.xml.in +++ /dev/null @@ -1,654 +0,0 @@ - - - - - - - Webproxy service settings - 500 - - - - - Safe port ACL - - u32:1-1024 - Port number. Ports included by default: 21,70,80,210,280,443,488,591,777,873,1025-65535 - - - - - - - - - - SSL safe port - - u32:1-65535 - Port number. Ports included by default: 443 - - - - - - - - - - Default domain name - - domain - Domain to use for urls that do not contain a '.' - - - [.][A-Za-z0-9][-.A-Za-z0-9]* - - Must start append-domain with a '.' - - - - - Proxy Authentication Settings - - - - - Number of authentication helper processes - - n - Number of authentication helper processes - - - - - - 5 - - - - Authenticated session time to live in minutes - - n - Authenticated session timeout - - - - - - 60 - - - - LDAP authentication settings - - - - - LDAP Base DN to search - - - - - LDAP DN used to bind to server - - - - - Filter expression to perform LDAP search with - - - - - LDAP password to bind with - - - - - Use persistent LDAP connection - - - - #include - - 389 - - - - LDAP server to use - - - - - Use SSL/TLS for LDAP connection - - - - - - LDAP username attribute - - - - - LDAP protocol version - - 2 3 - - - 2 - LDAP protocol version 2 - - - 3 - LDAP protocol version 2 - - - - - - 3 - - - - - - Authentication Method - - ldap - - - ldap - Lightweight Directory Access Protocol - - - (ldap) - - The only supported method currently is LDAP - - - - - Name of authentication realm (e.g. "My Company proxy server") - - - - - - - Specify other caches in a hierarchy - - hostname - Cache peers FQDN - - - - - - Hostname or IP address of peer - - ipv4 - Squid cache-peer IPv4 address - - - hostname - Squid cache-peer hostname - - - - - - Invalid FQDN or IP address - - - - - Default Proxy Port - - u32:1025-65535 - Default port number - - - - - - 3128 - - - - Cache peer ICP port - - u32:0 - Cache peer disabled - - - u32:1-65535 - Cache peer ICP port - - - - - - 0 - - - - Cache peer options - - txt - Cache peer options - - - no-query default - - - - Squid peer type (default parent) - - parent sibling multicast - - - parent - Peer is a parent - - - sibling - Peer is a sibling - - - multicast - Peer is a member of a multicast group - - - (parent|sibling|multicast) - - - parent - - - - - - Disk cache size in MB - - u32 - Disk cache size in MB - - - 0 - Disable disk caching - - - 100 - - - - Default Proxy Port - - u32:1025-65535 - Default port number - - - - - - 3128 - - - - Disable logging of HTTP accesses - - - - - - Domain name to block - - - - - - Domain name to access without caching - - - - - - IPv4 listen-address for WebProxy - - - - - ipv4 - IPv4 address listen on - - - - - - Default Proxy Port - - u32:1025-65535 - Default port number - - - - - - - - - - Disable transparent mode - - - - - - - - Maximum size of object to be stored in cache in kilobytes - - u32 - Object size in KB - - - - - - - - - Memory cache size in MB - - u32 - Memory cache size in MB - - - - - - 20 - - - - Maximum size of object to be stored in cache in kilobytes - - u32 - Object size in KB - - - - - - - - - Outgoing IP address for webproxy - - - - - MIME type to block - - image/gif www/mime application/macbinary application/oda application/octet-stream application/pdf application/postscript application/postscript application/postscript text/rtf application/octet-stream application/octet-stream application/x-tar application/x-csh application/x-dvi application/x-hdf application/x-latex text/plain application/x-netcdf application/x-netcdf application/x-sh application/x-tcl application/x-tex application/x-texinfo application/x-texinfo application/x-troff application/x-troff application/x-troff application/x-troff-man application/x-troff-me application/x-troff-ms application/x-wais-source application/zip application/x-bcpio application/x-cpio application/x-gtar application/x-rpm application/x-shar application/x-sv4cpio application/x-sv4crc application/x-tar application/x-ustar audio/basic audio/basic audio/mpeg audio/mpeg audio/mpeg audio/x-aiff audio/x-aiff audio/x-aiff audio/x-wav image/bmp image/ief image/jpeg image/jpeg image/jpeg image/tiff image/tiff image/x-cmu-raster image/x-portable-anymap image/x-portable-bitmap image/x-portable-graymap image/x-portable-pixmap image/x-rgb image/x-xbitmap image/x-xpixmap image/x-xwindowdump text/html text/html text/css application/x-javascript text/plain text/plain text/plain text/plain text/plain text/plain text/plain text/plain text/plain text/richtext text/tab-separated-values text/x-setext video/mpeg video/mpeg video/mpeg video/quicktime video/quicktime video/x-msvideo video/x-sgi-movie application/mac-compactpro application/mac-binhex40 application/macwriteii application/msword application/msword application/vnd.ms-excel application/vnd.ms-powerpoint application/vnd.lotus-1-2-3 application/vnd.mif application/x-stuffit application/pict application/pict application/x-arj-compressed application/x-lha-compressed application/x-lha-compressed application/x-deflate text/plain application/octet-stream application/octet-stream image/png application/octet-stream application/x-xpinstall application/octet-stream text/plain application/x-director application/x-director application/x-director image/vnd.djvu image/vnd.djvu application/octet-stream application/octet-stream application/andrew-inset x-conference/x-cooltalk model/iges model/iges audio/midi audio/midi audio/midi model/mesh model/mesh video/vnd.mpegurl chemical/x-pdb application/x-chess-pgn audio/x-realaudio audio/x-pn-realaudio audio/x-pn-realaudio text/sgml text/sgml application/x-koan application/x-koan application/x-koan application/x-koan application/smil application/smil application/octet-stream application/x-futuresplash application/x-shockwave-flash application/x-cdlink model/vrml image/vnd.wap.wbmp application/vnd.wap.wbxml application/vnd.wap.wmlc application/vnd.wap.wmlscriptc application/vnd.wap.wmlscript application/xhtml application/xhtml text/xml text/xml chemical/x-xyz text/plain - - - (image/gif|www/mime|application/macbinary|application/oda|application/octet-stream|application/pdf|application/postscript|application/postscript|application/postscript|text/rtf|application/octet-stream|application/octet-stream|application/x-tar|application/x-csh|application/x-dvi|application/x-hdf|application/x-latex|text/plain|application/x-netcdf|application/x-netcdf|application/x-sh|application/x-tcl|application/x-tex|application/x-texinfo|application/x-texinfo|application/x-troff|application/x-troff|application/x-troff|application/x-troff-man|application/x-troff-me|application/x-troff-ms|application/x-wais-source|application/zip|application/x-bcpio|application/x-cpio|application/x-gtar|application/x-rpm|application/x-shar|application/x-sv4cpio|application/x-sv4crc|application/x-tar|application/x-ustar|audio/basic|audio/basic|audio/mpeg|audio/mpeg|audio/mpeg|audio/x-aiff|audio/x-aiff|audio/x-aiff|audio/x-wav|image/bmp|image/ief|image/jpeg|image/jpeg|image/jpeg|image/tiff|image/tiff|image/x-cmu-raster|image/x-portable-anymap|image/x-portable-bitmap|image/x-portable-graymap|image/x-portable-pixmap|image/x-rgb|image/x-xbitmap|image/x-xpixmap|image/x-xwindowdump|text/html|text/html|text/css|application/x-javascript|text/plain|text/plain|text/plain|text/plain|text/plain|text/plain|text/plain|text/plain|text/plain|text/richtext|text/tab-separated-values|text/x-setext|video/mpeg|video/mpeg|video/mpeg|video/quicktime|video/quicktime|video/x-msvideo|video/x-sgi-movie|application/mac-compactpro|application/mac-binhex40|application/macwriteii|application/msword|application/msword|application/vnd.ms-excel|application/vnd.ms-powerpoint|application/vnd.lotus-1-2-3|application/vnd.mif|application/x-stuffit|application/pict|application/pict|application/x-arj-compressed|application/x-lha-compressed|application/x-lha-compressed|application/x-deflate|text/plain|application/octet-stream|application/octet-stream|image/png|application/octet-stream|application/x-xpinstall|application/octet-stream|text/plain|application/x-director|application/x-director|application/x-director|image/vnd.djvu|image/vnd.djvu|application/octet-stream|application/octet-stream|application/andrew-inset|x-conference/x-cooltalk|model/iges|model/iges|audio/midi|audio/midi|audio/midi|model/mesh|model/mesh|video/vnd.mpegurl|chemical/x-pdb|application/x-chess-pgn|audio/x-realaudio|audio/x-pn-realaudio|audio/x-pn-realaudio|text/sgml|text/sgml|application/x-koan|application/x-koan|application/x-koan|application/x-koan|application/smil|application/smil|application/octet-stream|application/x-futuresplash|application/x-shockwave-flash|application/x-cdlink|model/vrml|image/vnd.wap.wbmp|application/vnd.wap.wbxml|application/vnd.wap.wmlc|application/vnd.wap.wmlscriptc|application/vnd.wap.wmlscript|application/xhtml|application/xhtml|text/xml|text/xml|chemical/x-xyz|text/plain) - - - - - - - Maximum reply body size in KB - - u32 - Reply size in KB - - - - - - - - - URL filtering settings - - - #include - - - URL filtering via squidGuard redirector - - - #include - - - Auto update settings - - - - - Hour of day for database update - - u32:0-23 - Hour for database update - - - - - - 0 - - - - - - Redirect URL for filtered websites - - url - URL for redirect - - - block.vyos.net - - - - URL filter rule for a source-group - - u32:1-1024 - Rule Number - - - - - SquidGuard rule must between 1-1024 - - - #include - - - Redirect URL for filtered websites - - url - URL for redirect - - - - - - Source-group for this rule - - group - Source group identifier for this rule - - - service webproxy url-filtering squidguard source-group - - - - - - Time-period for this rule - - period - Time period for this rule - - - service webproxy url-filtering squidguard time-period - - - - - - - - Source group name - - name - Name of source group - - - [^0-9][a-zA-Z_][a-zA-Z0-9][\w\-\.]* - - URL-filter source-group cannot start with a number! - - - - - Address for source-group - - ipv4 - IPv4 address to match - - - ipv4net - IPv4 prefix to match - - - ipv4range - IPv4 address range to match - - - - - - - - - - #include - - - Domain for source-group - - domain - Domain name for the source-group - - - - - - - LDAP search expression for an IP address list - - - - - - LDAP search expression for a user group - - - - - - List of user names - - - - - - - Time period name - - - - - Time-period days - - Sun Mon Tue Wed Thu Fri Sat weekdays weekend all - - - Sun - Sunday - - - Mon - Monday - - - Tue - Tuesday - - - Wed - Wednesday - - - Thu - Thursday - - - Fri - Friday - - - Sat - Saturday - - - weekdays - Monday through Friday - - - weekend - Saturday and Sunday - - - all - All days of the week - - - (Sun|Mon|Tue|Wed|Thu|Fri|Sat|weekdays|weekend|all) - - - - - - Time for time-period - - <hh:mm - hh:mm> - Time range in 24hr time - - - - (\d\d:\d\d)-(\d\d:\d\d) - - Expected time format hh:mm - hh:mm in 24hr time - - - - - #include - - - - - - - - - - - diff --git a/interface-definitions/service_aws_glb.xml.in b/interface-definitions/service_aws_glb.xml.in new file mode 100644 index 000000000..c749fd04e --- /dev/null +++ b/interface-definitions/service_aws_glb.xml.in @@ -0,0 +1,127 @@ + + + + + + + Amazon Web Service + 1280 + + + + + Gateway load-balancer tunnel handler + + + + + Script executed on create or destroy tunnel + + + + + Script to run when interface is created + + + + + + + + Script to run when interface is destroyed + + + + + + + + + + Status + + + + + Statistic format + + simple full + + + simple + Simple format + + + full + Full format + + + (simple|full) + + + + #include + + + + + Threads settings + + + + + Number of threads for each tunnel processor + + u32:1-256 + Number of threads + + + + + + + + + List of cores worker threads + + <idN>-<idM> + CPU core id range (use '-' as delimiter) + + + + + + + + + Number of threads for UDP receiver + + u32:1-256 + Number of threads + + + + + + + + + List of cores worker threads + + <idN>-<idM> + CPU core id range (use '-' as delimiter) + + + + + + + + + + + + + + + diff --git a/interface-definitions/service_broadcast-relay.xml.in b/interface-definitions/service_broadcast-relay.xml.in new file mode 100644 index 000000000..2e4330e20 --- /dev/null +++ b/interface-definitions/service_broadcast-relay.xml.in @@ -0,0 +1,46 @@ + + + + + + + UDP broadcast relay service + 990 + + + #include + + + Unique ID for each UDP port to forward + + u32:1-99 + Broadcast relay instance ID + + + + + + + #include + + + Set source IP of forwarded packets, otherwise original senders address is used + + ipv4 + Optional source address for forwarded packets + + + + + + + #include + #include + #include + + + + + + + diff --git a/interface-definitions/service_config-sync.xml.in b/interface-definitions/service_config-sync.xml.in new file mode 100644 index 000000000..9955acfee --- /dev/null +++ b/interface-definitions/service_config-sync.xml.in @@ -0,0 +1,104 @@ + + + + + + + Configuration synchronization + + + + + Secondary server parameters + + + + + IP address + + ipv4 + IPv4 address to match + + + ipv6 + IPv6 address to match + + + hostname + FQDN address to match + + + + + + + + + + + Connection API timeout + + u32:1-300 + Connection API timeout + + + + + + 60 + + + + HTTP API key + + + + + + + Synchronization mode + + load set + + + load + Load and replace configuration section + + + set + Set configuration section + + + (load|set) + + + + + + Section for synchronization + + nat nat66 firewall + + + nat + NAT + + + nat66 + NAT66 + + + firewall + firewall + + + (nat|nat66|firewall) + + + + + + + + + diff --git a/interface-definitions/service_conntrack-sync.xml.in b/interface-definitions/service_conntrack-sync.xml.in new file mode 100644 index 000000000..46dc8adc0 --- /dev/null +++ b/interface-definitions/service_conntrack-sync.xml.in @@ -0,0 +1,173 @@ + + + + + + + Connection tracking synchronization + + 799 + + + + + Protocols for which local conntrack entries will be synced + + tcp udp icmp icmp6 sctp dccp + + + tcp + Sync Transmission Control Protocol entries + + + udp + Sync User Datagram Protocol entries + + + icmp + Sync Internet Control Message Protocol entries + + + icmp6 + Sync IPv6 Internet Control Message Protocol entries + + + sctp + Sync Stream Control Transmission Protocol entries + + + dccp + Sync Datagram Congestion Control Protocol entries + + + (tcp|udp|icmp|icmp6|sctp|dccp) + + Allowed protocols: tcp udp icmp or sctp + + + + + + Directly injects the flow-states into the in-kernel Connection Tracking System of the backup firewall. + + + + + + Queue size for local conntrack events + + u32 + Queue size in MB + + + 8 + + + + Protocol for which expect entries need to be synchronized + + all ftp sip h323 nfs sqlnet + + + (all|ftp|sip|h323|nfs|sqlnet) + + Invalid protocol + + + + + + Failover mechanism to use for conntrack-sync + + + + + VRRP as failover-mechanism to use for conntrack-sync + + + + + VRRP sync group + + high-availability vrrp sync-group + + + + + + + + + + IP addresses for which local conntrack entries will not be synced + + ipv4 + IPv4 address to ignore + + + ipv4net + IPv4 prefix to ignore + + + ipv6 + IPv6 address to ignore + + + ipv6net + IPv6 prefix to ignore + + + + + + + + + + + Interface to use for syncing conntrack entries + + + + + + + + IP address of the peer to send the UDP conntrack info too. This disable multicast. + + ipv4 + IP address to listen for incoming connections + + + + + + + #include + + + #include + + + Multicast group to use for syncing conntrack entries + + + + + 225.0.0.50 + + + + Queue size for syncing conntrack entries + + u32 + Queue size in MB + + + 1 + + + + + + diff --git a/interface-definitions/service_console-server.xml.in b/interface-definitions/service_console-server.xml.in new file mode 100644 index 000000000..fc6dbe954 --- /dev/null +++ b/interface-definitions/service_console-server.xml.in @@ -0,0 +1,100 @@ + + + + + + + Serial Console Server + + + + + System serial interface name (ttyS or ttyUSB) + + + + + + ttySxxx + Regular serial interface + + + usbxbxpx + USB based serial interface + + + (ttyS\d+|usb\d+b.*p.*) + + + + #include + + + Human-readable name for this console + + [-_a-zA-Z0-9.]{1,128} + + + + + + Serial port baud rate + + 300 1200 2400 4800 9600 19200 38400 57600 115200 + + + (300|1200|2400|4800|9600|19200|38400|57600|115200) + + + + + + Serial port data bits + + 7 8 + + + + + + 8 + + + + Serial port stop bits + + 1 2 + + + + + + 1 + + + + Parity setting + + even odd none + + + (even|odd|none) + + + none + + + + SSH remote access to this console + + + #include + + + + + + + + + diff --git a/interface-definitions/service_dhcp-relay.xml.in b/interface-definitions/service_dhcp-relay.xml.in new file mode 100644 index 000000000..9fdd9581d --- /dev/null +++ b/interface-definitions/service_dhcp-relay.xml.in @@ -0,0 +1,126 @@ + + + + + + + + Host Configuration Protocol (DHCP) relay agent + 910 + + + #include + #include + + + Interface for DHCP Relay Agent to listen for requests + + + + + txt + Interface name + + + #include + + + + + + + Interface for DHCP Relay Agent forward requests out + + + + + txt + Interface name + + + #include + + + + + + + Relay options + + + + + Policy to discard packets that have reached specified hop-count + + u32:1-255 + Hop count + + + + + hop-count must be a value between 1 and 255 + + 10 + + + + Maximum packet size to send to a DHCPv4/BOOTP server + + u32:64-1400 + Maximum packet size + + + + + max-size must be a value between 64 and 1400 + + 576 + + + + Policy to handle incoming DHCPv4 packets which already contain relay agent options + + append replace forward discard + + + append + append own relay options to packet + + + replace + replace existing agent option field + + + forward + forward packet unchanged + + + discard + discard packet (default action if giaddr not set in packet) + + + (append|replace|forward|discard) + + + forward + + + + + + DHCP server address + + ipv4 + DHCP server IPv4 address + + + + + + + + + + + + diff --git a/interface-definitions/service_dhcp-server.xml.in b/interface-definitions/service_dhcp-server.xml.in new file mode 100644 index 000000000..e35d845f1 --- /dev/null +++ b/interface-definitions/service_dhcp-server.xml.in @@ -0,0 +1,456 @@ + + + + + + + + Dynamic Host Configuration Protocol (DHCP) for DHCP server + 911 + + + #include + + + Dynamically update Domain Name System (RFC4702) + + + + + + DHCP failover configuration + + + #include + + + IPv4 remote address used for connectio + + ipv4 + IPv4 address of failover peer + + + + + + + + + Peer name used to identify connection + + [-_a-zA-Z0-9.]+ + + Invalid failover peer name. May only contain letters, numbers and .-_ + + + + + Failover hierarchy + + primary secondary + + + primary + Configure this server to be the primary node + + + secondary + Configure this server to be the secondary node + + + (primary|secondary) + + Invalid DHCP failover peer status + + + #include + #include + + + + + Updating /etc/hosts file (per client lease) + + + + #include + + + Name of DHCP shared network + + [-_a-zA-Z0-9.]+ + + Invalid shared network name. May only contain letters, numbers and .-_ + + + + + Option to make DHCP server authoritative for this physical network + + + + #include + #include + #include + #include + #include + #include + + + DHCP subnet for shared network + + ipv4net + IPv4 address and prefix length + + + + + Invalid IPv4 subnet definition + + + + + Bootstrap file name + + [[:ascii:]]{1,253} + + + + + + Server from which the initial boot file is to be loaded + + ipv4 + Bootfile server IPv4 address + + + hostname + Bootfile server FQDN + + + + + + + + + + Bootstrap file size + + u32:1-16 + Bootstrap file size in 512 byte blocks + + + + + + + #include + + + Specifies the clients subnet mask as per RFC 950. If unset, subnet declaration is used. + + u32:0-32 + DHCP client prefix length must be 0 to 32 + + + + + DHCP client prefix length must be 0 to 32 + + + + + IP address of default router + + ipv4 + Default router IPv4 address + + + + + + + #include + #include + #include + #include + + + IP address to exclude from DHCP lease range + + ipv4 + IPv4 address to exclude from lease range + + + + + + + + + + Enable IP forwarding on client + + + + + + Lease timeout in seconds + + u32 + DHCP lease time in seconds + + + + + DHCP lease time must be between 0 and 4294967295 (49 days) + + 86400 + + #include + + + IP address of POP3 server + + ipv4 + POP3 server IPv4 address + + + + + + + + + + Address for DHCP server identifier + + ipv4 + DHCP server identifier IPv4 address + + + + + + + + + IP address of SMTP server + + ipv4 + SMTP server IPv4 address + + + + + + + + + + DHCP lease range + + [-_a-zA-Z0-9.]+ + + Invalid range name, may only be alphanumeric, dot and hyphen + + + + + First IP address for DHCP lease range + + ipv4 + IPv4 start address of pool + + + + + + + + + Last IP address for DHCP lease range + + ipv4 + IPv4 end address of pool + + + + + + + + + + + Hostname for static mapping reservation + + + + Invalid static mapping hostname + + + #include + + + Fixed IP address of static mapping + + ipv4 + IPv4 address used in static mapping + + + + + + + #include + #include + + + + + Classless static route destination subnet + + ipv4net + IPv4 address and prefix length + + + + + + + + + IP address of router to be used to reach the destination subnet + + ipv4 + IPv4 address of router + + + + + + + + + + + Disable IPv4 on IPv6 only hosts (RFC 8925) + + u32 + Seconds + + + + + Seconds must be between 0 and 4294967295 (49 days) + + + + + TFTP server name + + ipv4 + TFTP server IPv4 address + + + hostname + TFTP server FQDN + + + + + + + + + + Client subnet offset in seconds from Coordinated Universal Time (UTC) + + [-]N + Time offset (number, may be negative) + + + -?[0-9]+ + + Invalid time offset value + + + + + IP address of time server + + ipv4 + Time server IPv4 address + + + + + + + + + + Time zone to send to clients. Uses RFC4833 options 100 and 101 + + + + + + + + + + + Vendor Specific Options + + + + + Ubiquiti specific parameters + + + + + Address of UniFi controller + + ipv4 + IP address of UniFi controller + + + + + + + + + + + + + IP address for Windows Internet Name Service (WINS) server + + ipv4 + WINS server IPv4 address + + + + + + + + + + Web Proxy Autodiscovery (WPAD) URL + + + + + + + + + + + diff --git a/interface-definitions/service_dhcpv6-relay.xml.in b/interface-definitions/service_dhcpv6-relay.xml.in new file mode 100644 index 000000000..40679d1c2 --- /dev/null +++ b/interface-definitions/service_dhcpv6-relay.xml.in @@ -0,0 +1,82 @@ + + + + + + + + DHCPv6 Relay Agent parameters + 900 + + + #include + + + Interface for DHCPv6 Relay Agent to listen for requests + + + + + + + + IPv6 address on listen-interface listen for requests on + + ipv6 + IPv6 address on listen interface + + + + + + + + + + + Maximum hop count for which requests will be processed + + u32:1-255 + Hop count + + + + + max-hop-count must be a value between 1 and 255 + + 10 + + + + Interface for DHCPv6 Relay Agent forward requests out + + + + + + + + IPv6 address to forward requests to + + ipv6 + IPv6 address of the DHCP server + + + + + + + + + + + + Option to set DHCPv6 interface-ID option + + + + + + + + diff --git a/interface-definitions/service_dhcpv6-server.xml.in b/interface-definitions/service_dhcpv6-server.xml.in new file mode 100644 index 000000000..102c164a6 --- /dev/null +++ b/interface-definitions/service_dhcpv6-server.xml.in @@ -0,0 +1,375 @@ + + + + + + + DHCP for IPv6 (DHCPv6) server + 900 + + + #include + + + Additional global parameters for DHCPv6 server + + + #include + + + + + Preference of this DHCPv6 server compared with others + + u32:0-255 + DHCPv6 server preference (0-255) + + + + + Preference must be between 0 and 255 + + + + + DHCPv6 shared network name + + [-_a-zA-Z0-9.]+ + + Invalid DHCPv6 shared network name. May only contain letters, numbers and .-_ + + + #include + #include + + + Optional interface for this shared network to accept requests from + + + + + txt + Interface name + + + #include + + + + + + Common options to distribute to all clients, including stateless clients + + + + + Time (in seconds) that stateless clients should wait between refreshing the information they were given + + u32:1-4294967295 + DHCPv6 information refresh time + + + + + + + #include + #include + + + + + IPv6 DHCP subnet for this shared network + + ipv6net + IPv6 address and prefix length + + + + + + + + + Parameters setting ranges for assigning IPv6 addresses + + + + + IPv6 prefix defining range of addresses to assign + + ipv6net + IPv6 address and prefix length + + + + + + + + + + First in range of consecutive IPv6 addresses to assign + + ipv6 + IPv6 address + + + + + + + + + Last in range of consecutive IPv6 addresses + + ipv6 + IPv6 address + + + + + + + + + + + #include + #include + + + Parameters relating to the lease time + + + + + Default time (in seconds) that will be assigned to a lease + + u32:1-4294967295 + DHCPv6 valid lifetime + + + + + + + + + Maximum time (in seconds) that will be assigned to a lease + + u32:1-4294967295 + Maximum lease time in seconds + + + + + + + + + Minimum time (in seconds) that will be assigned to a lease + + u32:1-4294967295 + Minimum lease time in seconds + + + + + + + + + #include + + + NIS domain name for client to use + + [-_a-zA-Z0-9.]+ + + Invalid NIS domain name + + + + + IPv6 address of a NIS Server + + ipv6 + IPv6 address of NIS server + + + + + + + + + + NIS+ domain name for client to use + + [-_a-zA-Z0-9.]+ + + Invalid NIS+ domain name. May only contain letters, numbers and .-_ + + + + + IPv6 address of a NIS+ Server + + ipv6 + IPv6 address of NIS+ server + + + + + + + + + + Parameters relating to IPv6 prefix delegation + + + + + IPv6 prefix to be used in prefix delegation + + ipv6 + IPv6 prefix used in prefix delegation + + + + + + + + + Length in bits of prefix + + u32:32-64 + Prefix length (32-64) + + + + + Prefix length must be between 32 and 64 + + + + + Length in bits of prefixes to be delegated + + u32:32-64 + Delegated prefix length (32-64) + + + + + Delegated prefix length must be between 32 and 96 + + + + + + + + + IPv6 address of SIP server + + ipv6 + IPv6 address of SIP server + + + hostname + FQDN of SIP server + + + + + + + + + + + IPv6 address of an SNTP server for client to use + + + + + + + + + Hostname for static mapping reservation + + + + Invalid static mapping hostname + + + #include + #include + #include + + + Client IPv6 address for this static mapping + + ipv6 + IPv6 address for this static mapping + + + + + + + + + Client IPv6 prefix for this static mapping + + ipv6net + IPv6 prefix for this static mapping + + + + + + + + + + + Vendor Specific Options + + + + + Cisco specific parameters + + + + + TFTP server name + + ipv6 + TFTP server IPv6 address + + + + + + + + + + + + + + + + + + + + diff --git a/interface-definitions/service_dns_dynamic.xml.in b/interface-definitions/service_dns_dynamic.xml.in new file mode 100644 index 000000000..d1b0e90bb --- /dev/null +++ b/interface-definitions/service_dns_dynamic.xml.in @@ -0,0 +1,213 @@ + + + + + + + Domain Name System (DNS) related services + + + + + Dynamic DNS + 990 + + + + + Dynamic DNS configuration + + txt + Dynamic DNS service name + + + #include + + Dynamic DNS service name must be alphanumeric and can contain hyphens and underscores + + + #include + + + ddclient protocol used for Dynamic DNS service + + + + + + + + + + + Obtain IP address to send Dynamic DNS update for + + txt + Use interface to obtain the IP address + + + web + Use HTTP(S) web request to obtain the IP address + + + + web + + + #include + web + + + + + + Options when using HTTP(S) web request to obtain the IP address + + + #include + + + Pattern to skip from the HTTP(S) respose + + txt + Pattern to skip from the HTTP(S) respose to extract the external IP address + + + + + + + + IP address version to use + + _ipv4 + Use only IPv4 address + + + _ipv6 + Use only IPv6 address + + + both + Use both IPv4 and IPv6 address + + + ipv4 ipv6 both + + + (ipv[46]|both) + + IP Version must be literal 'ipv4', 'ipv6' or 'both' + + ipv4 + + + + Hostname to register with Dynamic DNS service + + #include + (\@|\*)[-.A-Za-z0-9]* + + Host-name must be alphanumeric, can contain hyphens and can be prefixed with '@' or '*' + + + + + + Remote Dynamic DNS server to send updates to + + ipv4 + IPv4 address of the remote server + + + ipv6 + IPv6 address of the remote server + + + hostname + Fully qualified domain name of the remote server + + + + + + Remote server must be IP address or fully qualified domain name + + + + + DNS zone to be updated + + txt + Name of DNS zone + + + + + + + #include + #include + + + File containing TSIG authentication key for RFC2136 nsupdate on remote DNS server + + filename + File in /config/auth directory + + + + + + + #include + + + Time in seconds to wait between update attempts + + u32:60-86400 + Time in seconds + + + + + Wait time must be between 60 and 86400 seconds + + + + + Time in seconds for the hostname to be marked expired in cache + + u32:300-2160000 + Time in seconds + + + + + Expiry time must be between 300 and 2160000 seconds + + + + + + + Interval in seconds to wait between Dynamic DNS updates + + u32:60-3600 + Time in seconds + + + + + Interval must be between 60 and 3600 seconds + + 300 + + #include + + + + + + + diff --git a/interface-definitions/service_dns_forwarding.xml.in b/interface-definitions/service_dns_forwarding.xml.in new file mode 100644 index 000000000..7dce9b548 --- /dev/null +++ b/interface-definitions/service_dns_forwarding.xml.in @@ -0,0 +1,703 @@ + + + + + + + + Domain Name System (DNS) related services + + + + + DNS forwarding + 918 + + + + + DNS forwarding cache size + + u32:0-2147483647 + DNS forwarding cache size + + + + + + 10000 + + + + Interfaces whose DHCP client nameservers to forward requests to + + + + + + + + + Help to communicate between IPv6-only client and IPv4-only server + + ipv6net + IPv6 address and /96 only prefix length + + + + + + + + + DNSSEC mode + + off process-no-validate process log-fail validate + + + off + No DNSSEC processing whatsoever! + + + process-no-validate + Respond with DNSSEC records to clients that ask for it. No validation done at all! + + + process + Respond with DNSSEC records to clients that ask for it. Validation for clients that request it. + + + log-fail + Similar behaviour to process, but validate RRSIGs on responses and log bogus responses. + + + validate + Full blown DNSSEC validation. Send SERVFAIL to clients on bogus responses. + + + (off|process-no-validate|process|log-fail|validate) + + + process-no-validate + + + + Domain to forward to a custom DNS server + + txt + An absolute DNS domain name + + + + + + + #include + + + Add NTA (negative trust anchor) for this domain (must be set if the domain does not support DNSSEC) + + + + + + Set the "recursion desired" bit in requests to the upstream nameserver + + + + + + + + Domain to host authoritative records for + + txt + An absolute DNS domain name + + + + + + + + + DNS zone records + + + + + A record + + txt + A DNS name relative to the root record + + + @ + Root record + + + any + Wildcard record (any subdomain) + + + ([-_a-zA-Z0-9.]{1,63}|@|any)(?<!\.) + + + + + + IPv4 address + + ipv4 + IPv4 address + + + + + + + + #include + + 300 + + #include + + + + + AAAA record + + txt + A DNS name relative to the root record + + + @ + Root record + + + any + Wildcard record (any subdomain) + + + ([-_a-zA-Z0-9.]{1,63}|@|any)(?<!\.) + + + + + + IPv6 address + + ipv6 + IPv6 address + + + + + + + + #include + + 300 + + #include + + + + + CNAME record + + txt + A DNS name relative to the root record + + + @ + Root record + + + ([-_a-zA-Z0-9.]{1,63}|@)(?<!\.) + + + + + + Target DNS name + + name.example.com + Absolute DNS name + + + [-_a-zA-Z0-9.]{1,63}(?<!\.) + + + + #include + + 300 + + #include + + + + + MX record + + txt + A DNS name relative to the root record + + + @ + Root record + + + ([-_a-zA-Z0-9.]{1,63}|@)(?<!\.) + + + + + + Mail server + + name.example.com + Absolute DNS name + + + [-_a-zA-Z0-9.]{1,63}(?<!\.) + + + + + + Server priority + + u32:1-999 + Server priority (lower numbers are higher priority) + + + + + + 10 + + + + #include + + 300 + + #include + + + + + NS record + + txt + A DNS name relative to the root record + + + ([-_a-zA-Z0-9.]{1,63}|@)(?<!\.) + + + + + + Target DNS server authoritative for subdomain + + nsXX.example.com + Absolute DNS name + + + [-_a-zA-Z0-9.]{1,63}(?<!\.) + + + + #include + + 300 + + #include + + + + + PTR record + + txt + A DNS name relative to the root record + + + @ + Root record + + + ([-_a-zA-Z0-9.]{1,63}|@)(?<!\.) + + + + + + Target DNS name + + name.example.com + Absolute DNS name + + + [-_a-zA-Z0-9.]{1,63}(?<!\.) + + + + #include + + 300 + + #include + + + + + TXT record + + txt + A DNS name relative to the root record + + + @ + Root record + + + ([-_a-zA-Z0-9.]{1,63}|@)(?<!\.) + + + + + + Record contents + + txt + Record contents + + + + + #include + + 300 + + #include + + + + + SPF record + + txt + A DNS name relative to the root record + + + @ + Root record + + + ([-_a-zA-Z0-9.]{1,63}|@)(?<!\.) + + + + + + Record contents + + txt + Record contents + + + + #include + + 300 + + #include + + + + + SRV record + + txt + A DNS name relative to the root record + + + @ + Root record + + + ([-_a-zA-Z0-9.]{1,63}|@)(?<!\.) + + + + + + Service entry + + u32:0-65535 + Entry number + + + + + + + + + Server hostname + + name.example.com + Absolute DNS name + + + [-_a-zA-Z0-9.]{1,63}(?<!\.) + + + + + + Port number + + u32:0-65535 + TCP/UDP port number + + + + + + + + + Entry priority + + u32:0-65535 + Entry priority (lower numbers are higher priority) + + + + + + 10 + + + + Entry weight + + u32:0-65535 + Entry weight + + + + + + 0 + + + + #include + + 300 + + #include + + + + + NAPTR record + + txt + A DNS name relative to the root record + + + @ + Root record + + + ([-_a-zA-Z0-9.]{1,63}|@)(?<!\.) + + + + + + NAPTR rule + + u32:0-65535 + Rule number + + + + + + + + + Rule order + + u32:0-65535 + Rule order (lower order is evaluated first) + + + + + + + + + Rule preference + + u32:0-65535 + Rule preference + + + + + + 0 + + + + S flag + + + + + + A flag + + + + + + U flag + + + + + + P flag + + + + + + Service type + + [a-zA-Z][a-zA-Z0-9]{0,31}(\+[a-zA-Z][a-zA-Z0-9]{0,31})? + + + + + + Regular expression + + + + + Replacement DNS name + + name.example.com + Absolute DNS name + + + [-_a-zA-Z0-9.]{1,63}(?<!\.) + + + + + + #include + + 300 + + #include + + + + + #include + + + + + Do not use local /etc/hosts file in name resolution + + + + + + Makes the server authoritatively not aware of RFC1918 addresses + + + + + + Networks allowed to query this server + + ipv4net + IP address and prefix length + + + ipv6net + IPv6 address and prefix length + + + + + + + + #include + #include + + 53 + + + + Maximum amount of time negative entries are cached + + u32:0-7200 + Seconds to cache NXDOMAIN entries + + + + + + 3600 + + + + Number of milliseconds to wait for a remote authoritative server to respond + + u32:10-60000 + Network timeout in milliseconds + + + + + + 1500 + + #include + #include + + 0.0.0.0 :: + + + + Use system name servers + + + + + + + + + + diff --git a/interface-definitions/service_event-handler.xml.in b/interface-definitions/service_event-handler.xml.in new file mode 100644 index 000000000..2cee4f595 --- /dev/null +++ b/interface-definitions/service_event-handler.xml.in @@ -0,0 +1,70 @@ + + + + + + + Service event handler + + + + + Event handler name + + + + + Logs filter settings + + + + + Match pattern (regex) + + + + + Identifier of a process in syslog (string) + + + + + + + Event handler script file + + + + + Script arguments + + + + + Script environment arguments + + + + + Environment value + + + + + + + Path to the script + + + + + + + + + + + + + + diff --git a/interface-definitions/service_https.xml.in b/interface-definitions/service_https.xml.in new file mode 100644 index 000000000..223f10962 --- /dev/null +++ b/interface-definitions/service_https.xml.in @@ -0,0 +1,220 @@ + + + + + + + HTTPS configuration + 1001 + + + + + Identifier for virtual host + + [a-zA-Z0-9-_.:]{1,255} + + illegal characters in identifier or identifier longer than 255 characters + + + + + Address to listen for HTTPS requests + + + + + ipv4 + HTTPS IPv4 address + + + ipv6 + HTTPS IPv6 address + + + '*' + any + + + + \* + + + + #include + + 443 + + + + Server names: exact, wildcard, or regex + + + + #include + + + + + VyOS HTTP API configuration + + + + + HTTP API keys + + + + + HTTP API id + + + + + HTTP API plaintext key + + + + + + + + + Enforce strict path checking + + + + + + Debug + + + + + + + GraphQL support + + + + + Schema introspection + + + + + + GraphQL authentication + + + + + Authentication type + + key token + + + key + Use API keys + + + token + Use JWT token + + + (key|token) + + + key + + + + Token time to expire in seconds + + u32:60-31536000 + Token lifetime in seconds + + + + + + 3600 + + + + Length of shared secret in bytes + + u32:16-65535 + Byte length of generated shared secret + + + + + + 32 + + + + + + + + Set CORS options + + + + + Allow resource request from origin + + + + + + + + + + Restrict api proxy to subset of virtual hosts + + + + + Restrict proxy to virtual host(s) + + + + + + + + TLS certificates + + + #include + #include + + + Request or apply a letsencrypt certificate for domain-name + + + + + Domain name(s) for which to obtain certificate + + + + + + Email address to associate with certificate + + + + + + + #include + + + + + diff --git a/interface-definitions/service_ids_ddos-protection.xml.in b/interface-definitions/service_ids_ddos-protection.xml.in new file mode 100644 index 000000000..3ef2640b3 --- /dev/null +++ b/interface-definitions/service_ids_ddos-protection.xml.in @@ -0,0 +1,167 @@ + + + + + + + Intrusion Detection System + + + + + FastNetMon detection and protection parameters + 731 + + + + + Path to fastnetmon alert script + + + + + How long we should keep an IP in blocked state + + u32:1-4294967294 + Time in seconds + + + + + + 1900 + + + + Direction for processing traffic + + in out + + + (in|out) + + + + + + + Specify IPv4 and IPv6 networks which are going to be excluded from protection + + ipv4net + IPv4 prefix(es) to exclude + + + ipv6net + IPv6 prefix(es) to exclude + + + + + + + + + + + Listen interface for mirroring traffic + + + + + + + + + Traffic capture mode + + mirror sflow + + + mirror + Listen to mirrored traffic + + + sflow + Capture sFlow flows + + + (mirror|sflow) + + + + + + Sflow settings + + + #include + #include + + 6343 + + + + + + Specify IPv4 and IPv6 networks which belong to you + + ipv4net + Your IPv4 prefix(es) + + + ipv6net + Your IPv6 prefix(es) + + + + + + + + + + + Attack limits thresholds + + + + + General threshold + + + #include + + + + + TCP threshold + + + #include + + + + + UDP threshold + + + #include + + + + + ICMP threshold + + + #include + + + + + + + + + + + diff --git a/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service_ipoe-server.xml.in new file mode 100644 index 000000000..edfe6a34c --- /dev/null +++ b/interface-definitions/service_ipoe-server.xml.in @@ -0,0 +1,190 @@ + + + + + + + Internet Protocol over Ethernet (IPoE) Server + 900 + + + + + Interface to listen dhcp or unclassified packets + + + + + + + + Client connectivity mode + + l2 l3 + + + l2 + Client located on same interface as server + + + l3 + Client located behind a router + + + (l2|l3) + + + l2 + + + + Enables clients to share the same network or each client has its own vlan + + shared vlan + + + (shared|vlan) + + + shared + Multiple clients share the same network + + + vlan + One VLAN per client + + + shared + + + + Client address pool + + ipv4net + IPv4 address and prefix length + + + + + + + + + DHCP requests will be forwarded + + + + + DHCP Server the request will be redirected to. + + ipv4 + IPv4 address of the DHCP Server + + + + + + + + + Relay Agent IPv4 Address + + ipv4 + Gateway IP address + + + + + + + + + #include + + + #include + #include + #include + #include + #include + + + Client authentication methods + + + #include + + + Network interface for client MAC addresses + + + + + + + + Media Access Control (MAC) address + + macaddr + Hardware (MAC) address + + + + + + + + + Upload/Download speed limits + + + + + Upload bandwidth limit in kbits/sec + + + + + + + + Download bandwidth limit in kbits/sec + + + + + + + + + + VLAN monitor for automatic creation of VLAN interfaces + + u32:1-4094 + Client VLAN id + + + + + VLAN IDs need to be in range 1-4094 + + + + + + + + + #include + + + #include + #include + + + #include + + + + + diff --git a/interface-definitions/service_lldp.xml.in b/interface-definitions/service_lldp.xml.in new file mode 100644 index 000000000..1a06e0cb3 --- /dev/null +++ b/interface-definitions/service_lldp.xml.in @@ -0,0 +1,188 @@ + + + + + + + LLDP settings + 985 + + + + + Location data for interface + + all + Location data all interfaces + + + txt + Location data for a specific interface + + + + all + + + + #include + + + LLDP-MED location data + + + + + Coordinate based location + + + + + Altitude in meters + + 0 + No altitude + + + [+-]<meters> + Altitude in meters + + Altitude should be a positive or negative number + + + + + 0 + + + + Coordinate datum type + + WGS84 + WGS84 + + + NAD83 + NAD83 + + + MLLW + NAD83/MLLW + + + WGS84 NAD83 MLLW + + Datum should be WGS84, NAD83, or MLLW + + (WGS84|NAD83|MLLW) + + + WGS84 + + + + Latitude + + <latitude> + Latitude (example "37.524449N") + + Latitude should be a number followed by S or N + + (\d+)(\.\d+)?[nNsS] + + + + + + Longitude + + <longitude> + Longitude (example "122.267255W") + + Longiture should be a number followed by E or W + + (\d+)(\.\d+)?[eEwW] + + + + + + + + ECS ELIN (Emergency location identifier number) + + u32:0-9999999999 + Emergency Call Service ELIN number (between 10-25 numbers) + + + [0-9]{10,25} + + ELIN number must be between 10-25 numbers + + + + + + + + + Legacy (vendor specific) protocols + + + + + Listen for CDP for Cisco routers/switches + + + + + + Listen for EDP for Extreme routers/switches + + + + + + Listen for FDP for Foundry routers/switches + + + + + + Listen for SONMP for Nortel routers/switches + + + + + + + + Management IP Address + + + + + ipv4 + IPv4 Management Address + + + ipv6 + IPv6 Management Address + + + + + + + + + + Enable SNMP queries of the LLDP database + + + + + + + + diff --git a/interface-definitions/service_mdns_repeater.xml.in b/interface-definitions/service_mdns_repeater.xml.in new file mode 100644 index 000000000..5d6f61d74 --- /dev/null +++ b/interface-definitions/service_mdns_repeater.xml.in @@ -0,0 +1,82 @@ + + + + + + + Multicast DNS (mDNS) parameters + + + + + mDNS repeater configuration + 990 + + + #include + #include + + + IP address version to use + + _ipv4 + Use only IPv4 address + + + _ipv6 + Use only IPv6 address + + + both + Use both IPv4 and IPv6 address + + + ipv4 ipv6 both + + + (ipv[46]|both) + + IP Version must be literal 'ipv4', 'ipv6' or 'both' + + both + + + + mDNS browsing domains in addition to the default one + + txt + mDNS browsing domain + + + + + + + + + + Allowed mDNS services to be repeated + + txt + mDNS service + + + [-_.a-zA-Z0-9]+ + + Service name must be alphanumeric and can contain hyphens and underscores + + + + + + Disables mDNS repeater on VRRP interfaces not in MASTER state + + + + + + + + + + diff --git a/interface-definitions/service_monitoring_telegraf.xml.in b/interface-definitions/service_monitoring_telegraf.xml.in new file mode 100644 index 000000000..4d694114a --- /dev/null +++ b/interface-definitions/service_monitoring_telegraf.xml.in @@ -0,0 +1,284 @@ + + + + + + + Monitoring services + 1280 + + + + + Telegraf metric collector + + + + + Output plugin InfluxDB + + + + + Authentication parameters + + + + + Authentication organization for InfluxDB v2 + + [a-zA-Z][1-9a-zA-Z@_\-.]{2,50} + + Organization name must be alphanumeric and can contain hyphens, underscores and at symbol. + + + + + Authentication token for InfluxDB v2 + + txt + Authentication token + + + [a-zA-Z0-9-_]{86}== + + Token must be 88 characters long and must contain only [a-zA-Z0-9-_] and '==' characters. + + + + + + + Remote bucket + + main + + #include + #include + + 8086 + + + + + + Output plugin Azure Data Explorer + + + + + Authentication parameters + + + + + Application client id + + #include + + Client-id is limited to alphanumerical characters and can contain hyphen and underscores + + + + + Application client secret + + #include + + Client-secret is limited to alphanumerical characters and can contain hyphen and underscores + + + + + Set tenant id + + #include + + Tenant-id is limited to alphanumerical characters and can contain hyphen and underscores + + + + + + + Remote database name + + txt + Remote database name + + + #include + + Database is limited to alphanumerical characters and can contain hyphen and underscores + + + + + Type of metrics grouping when push to Azure Data Explorer + + single-table table-per-metric + + + single-table + Metrics stores in one table + + + table-per-metric + One table per gorups of metric by the metric name + + + (single-table|table-per-metric) + + + table-per-metric + + + + Name of the single table [Only if set group-metrics single-table] + + txt + Table name + + + #include + + Table is limited to alphanumerical characters and can contain hyphen and underscores + + + #include + + + + + Source parameters for monitoring + + all hardware-utilization logs network system telegraf + + + all + All parameters + + + hardware-utilization + Hardware-utilization parameters (CPU, disk, memory) + + + logs + Logs parameters + + + network + Network parameters (net, netstat, nftables) + + + system + System parameters (system, processes, interrupts) + + + telegraf + Telegraf internal statistics + + + (all|hardware-utilization|logs|network|system|telegraf) + + + + all + + + + Output plugin Prometheus client + + + + + HTTP basic authentication parameters + + + + + Authentication username + + + + + Authentication password + + txt + Authentication password + + + + + + + + Networks allowed to query this server + + ipv4net + IP address and prefix length + + + ipv6net + IPv6 address and prefix length + + + + + + + + #include + + + Metric version control mapping from Telegraf to Prometheus format + + u32:1-2 + Metric version (default: 2) + + + + + + 2 + + #include + + 9273 + + + + + + Output plugin Splunk + + + + + HTTP basic authentication parameters + + + + + Authorization token + + + + + Use TLS but skip host validation + + + + + + #include + + + #include + + + + + + + diff --git a/interface-definitions/service_monitoring_zabbix-agent.xml.in b/interface-definitions/service_monitoring_zabbix-agent.xml.in new file mode 100644 index 000000000..40f2df642 --- /dev/null +++ b/interface-definitions/service_monitoring_zabbix-agent.xml.in @@ -0,0 +1,193 @@ + + + + + + + + + Zabbix-agent settings + + + + + Folder containing individual Zabbix-agent configuration files + + + + + + + + Zabbix agent hostname + + #include + + Host-name must be alphanumeric and can contain hyphens + + + + + Limit settings + + + + + Do not keep data longer than N seconds in buffer + + u32:1-3600 + Seconds + + + + + buffer-flush-interval must be between 1 and 3600 seconds + + 5 + + + + Maximum number of values in a memory buffer + + u32:2-65535 + Maximum number of values in a memory buffer + + + + + Buffer-size must be between 2 and 65535 + + 100 + + + + + + Log settings + + + + + Debug level + + basic critical error warning debug extended-debug + + + basic + Basic information + + + critical + Critical information + + + error + Error information + + + warning + Warnings + + + debug + Debug information + + + extended-debug + Extended debug information + + + (basic|critical|error|warning|debug|extended-debug) + + + warning + + + + Enable logging of executed shell commands as warnings + + + + + + Log file size in megabytes + + u32:0-1024 + Megabytes + + + + + Size must be between 0 and 1024 Megabytes + + 0 + + + + #include + + 0.0.0.0 + + #include + + 10050 + + + + Remote server to connect to + + ipv4 + Server IPv4 address + + + ipv6 + Server IPv6 address + + + hostname + Server hostname/FQDN + + + + + + + Remote server address to get active checks from + + ipv4 + Server IPv4 address + + + ipv6 + Server IPv6 address + + + hostname + Server hostname/FQDN + + + + #include + + + + + Item processing timeout in seconds + + u32:1-30 + Item processing timeout + + + + + Timeout must be between 1 and 30 seconds + + 3 + + + + + + + + diff --git a/interface-definitions/service_ntp.xml.in b/interface-definitions/service_ntp.xml.in new file mode 100644 index 000000000..65a45d7a1 --- /dev/null +++ b/interface-definitions/service_ntp.xml.in @@ -0,0 +1,67 @@ + + + + + + + + Network Time Protocol (NTP) configuration + 900 + + + + + Network Time Protocol (NTP) server + + ipv4 + IP address of NTP server + + + ipv6 + IPv6 address of NTP server + + + hostname + Fully qualified domain name of NTP server + + + + + + + + + + Marks the server as unused + + + + + + Enable Network Time Security (NTS) for the server + + + + + + Associate with a number of remote servers + + + + + + Marks the server as preferred + + + + + + #include + #include + #include + #include + + + + + diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service_pppoe-server.xml.in new file mode 100644 index 000000000..f1b369936 --- /dev/null +++ b/interface-definitions/service_pppoe-server.xml.in @@ -0,0 +1,281 @@ + + + + + + + Point to Point over Ethernet (PPPoE) Server + 900 + + + #include + + vyos-ac + + + + Authentication for remote access PPPoE Server + + + #include + #include + #include + #include + #include + + + #include + + + Format of Called-Station-Id attribute + + ifname ifname:mac + + + (ifname|ifname:mac) + + Invalid Called-Station-Id format + + ifname + NAS-Port-Id - should contain root interface name (NAS-Port-Id=eth1) + + + ifname:mac + NAS-Port-Id - should contain root interface name and mac address (NAS-Port-Id=eth1:00:00:00:00:00:00) + + + + + + + + #include + #include + #include + + + interface(s) to listen on + + + + + + #include + + + #include + #include + #include + + + Limits the connection rate from a single source + + + + + Acceptable rate of connections (e.g. 1/min, 60/sec) + + [0-9]+\/(min|sec) + + illegal value + + + + + Burst count + + + + + Timeout in seconds + + + + + + + Service name + + [a-zA-Z0-9\-]{1,100} + + Service-name can contain aplhanumerical characters and dashes only (max. 100) + + + + #include + + + Advanced protocol options + + + + + Minimum acceptable MTU (68-65535) + + + + + 1280 + + + + Preferred MRU (68-65535) + + + + + + + + CCP negotiation (default disabled) + + + + #include + #include + #include + #include + + + IPv4 (IPCP) negotiation algorithm + + (deny|allow|prefer|require) + + invalid value + + deny + Do not negotiate IPv4 + + + allow + Negotiate IPv4 only if client requests + + + prefer + Ask client for IPv4 negotiation, do not fail if it rejects + + + require + Require IPv4 negotiation + + + deny allow prefer require + + + + #include + #include + + + + + PADO delays + + u32:1-999999 + Number in ms + + + + + Invalid PADO delay + + + + + Number of sessions + + u32:1-999999 + Number of sessions + + + + + Invalid number of delayed sessions + + + + + + + control sessions count + + (deny|disable|replace) + + Invalid value + + disable + Disables session control + + + deny + Deny second session authorization + + + replace + Terminate first session when second is authorized + + + deny disable replace + + + replace + + #include + + + Enable SNMP + + + + + enable SNMP master agent mode + + + + + + + + Extended script execution + + + + + Script to run before PPPoE session interface comes up + + + + + + + + Script to run when PPPoE session interface is completely configured and started + + + + + + + + Script to run when PPPoE session interface going to terminate + + + + + + + + Script to run when PPPoE session interface changed by RADIUS CoA handling + + + + + + + + #include + + + + + diff --git a/interface-definitions/service_router-advert.xml.in b/interface-definitions/service_router-advert.xml.in new file mode 100644 index 000000000..16c29022d --- /dev/null +++ b/interface-definitions/service_router-advert.xml.in @@ -0,0 +1,369 @@ + + + + + + + IPv6 Router Advertisements (RAs) service + 900 + + + + + Interface to send RA on + + + + + + + + Set Hop Count field of the IP header for outgoing packets + + u32:0 + Unspecified (by this router) + + + u32:1-255 + Value should represent current diameter of the Internet + + + + + Hop count must be between 0 and 255 + + 64 + + + + Lifetime associated with the default router in units of seconds + + u32:4-9000 + Router Lifetime in seconds + + + 0 + Not a default router + + + + + Default router livetime bust be 0 or between 4 and 9000 + + + + + Preference associated with the default router, + + low medium high + + + low + Default router has low preference + + + medium + Default router has medium preference + + + high + Default router has high preference + + + (low|medium|high) + + Default preference must be low, medium or high + + medium + + + + DNS search list + + + + + + Link MTU value placed in RAs, exluded in RAs if unset + + u32:1280-9000 + Link MTU value in RAs + + + + + Link MTU must be between 1280 and 9000 + + + + + Hosts use the administered (stateful) protocol for address autoconfiguration in addition to any addresses autoconfigured using SLAAC + + + + + + Set interval between unsolicited multicast RAs + + + + + Maximum interval between unsolicited multicast RAs + + u32:4-1800 + Maximum interval in seconds + + + + + Maximum interval must be between 4 and 1800 seconds + + 600 + + + + Minimum interval between unsolicited multicast RAs + + u32:3-1350 + Minimum interval in seconds + + + + + Minimum interval must be between 3 and 1350 seconds + + + + + #include + + + Maximum duration how long the RDNSS entries are used + + u32:0 + Name-servers should no longer be used + + + u32:1-7200 + Maximum interval in seconds + + + + + Maximum interval must be between 1 and 7200 seconds + + + + + Hosts use the administered (stateful) protocol for autoconfiguration of other (non-address) information + + + + + + IPv6 route to be advertised in Router Advertisements (RAs) + + ipv6net + IPv6 route to be advertized + + + + + + + + + Time in seconds that the route will remain valid + + infinity + + + u32:1-4294967295 + Time in seconds that the route will remain valid + + + infinity + Route will remain preferred forever + + + + (infinity) + + + 1800 + + + + Preference associated with the route, + + low medium high + + + low + Route has low preference + + + medium + Route has medium preference + + + high + Route has high preference + + + (low|medium|high) + + Route preference must be low, medium or high + + medium + + + + Do not announce this route with a zero second lifetime upon shutdown + + + + + + + + IPv6 prefix to be advertised in Router Advertisements (RAs) + + ipv6net + IPv6 prefix to be advertized + + + + + + + + + Prefix can not be used for stateless address auto-configuration + + + + + + Prefix can not be used for on-link determination + + + + + + Upon shutdown, this option will deprecate the prefix by announcing it in the shutdown RA + + + + + + Lifetime is decremented by the number of seconds since the last RA - use in conjunction with a DHCPv6-PD prefix + + + + + + Time in seconds that the prefix will remain preferred + + infinity + + + u32 + Time in seconds that the prefix will remain preferred + + + infinity + Prefix will remain preferred forever + + + + (infinity) + + + 14400 + + + + Time in seconds that the prefix will remain valid + + infinity + + + u32:1-4294967295 + Time in seconds that the prefix will remain valid + + + infinity + Prefix will remain preferred forever + + + + (infinity) + + + 2592000 + + + + + + Use IPv6 address as source address. Useful with VRRP. + + ipv6 + IPv6 address to be advertized (must be configured on interface) + + + + + + + + + + Time, in milliseconds, that a node assumes a neighbor is reachable after having received a reachability confirmation + + u32:0 + Reachable Time unspecified by this router + + + u32:1-3600000 + Reachable Time value in RAs (in milliseconds) + + + + + Reachable time must be 0 or between 1 and 3600000 milliseconds + + 0 + + + + Time in milliseconds between retransmitted Neighbor Solicitation messages + + u32:0 + Time, in milliseconds, between retransmitted Neighbor Solicitation messages + + + u32:1-4294967295 + Minimum interval in milliseconds + + + + + Retransmit interval must be 0 or between 1 and 4294967295 milliseconds + + 0 + + + + Do not send router adverts + + + + + + + + + + diff --git a/interface-definitions/service_salt-minion.xml.in b/interface-definitions/service_salt-minion.xml.in new file mode 100644 index 000000000..eaa2899f4 --- /dev/null +++ b/interface-definitions/service_salt-minion.xml.in @@ -0,0 +1,74 @@ + + + + + + + Salt Minion + 500 + + + + + Hash used when discovering file on master server (default: sha256) + + md5 sha1 sha224 sha256 sha384 sha512 + + + (md5|sha1|sha224|sha256|sha384|sha512) + + + sha256 + + + + Hostname or IP address of the Salt master server + + ipv4 + Salt server IPv4 address + + + ipv6 + Salt server IPv6 address + + + hostname + Salt server FQDN address + + + + + + Invalid FQDN or IP address + + + + + + Explicitly declare ID for this minion to use (default: hostname) + + + + + Interval in minutes between updates (default: 60) + + u32:1-1440 + Update interval in minutes + + + + + + 60 + + + + URL with signature of master for auth reply verification + + + #include + + + + + diff --git a/interface-definitions/service_sla.xml.in b/interface-definitions/service_sla.xml.in new file mode 100644 index 000000000..0c4f8a591 --- /dev/null +++ b/interface-definitions/service_sla.xml.in @@ -0,0 +1,36 @@ + + + + + + + Service level agreement (SLA) + + + + + One-way active measurement protocol (OWAMP) server + + + #include + + 861 + + + + + + Two-way active measurement protocol (TWAMP) server + + + #include + + 862 + + + + + + + + diff --git a/interface-definitions/service_snmp.xml.in b/interface-definitions/service_snmp.xml.in new file mode 100644 index 000000000..e16e9daa1 --- /dev/null +++ b/interface-definitions/service_snmp.xml.in @@ -0,0 +1,598 @@ + + + + + + + + Simple Network Management Protocol (SNMP) + 900 + + + + + Community name + + [[:alnum:]-_!@*#]{1,100} + + Community string is limited to alphanumerical characters, -, _, !, @, *, and # with a total lenght of 100 + + + + + Authorization type + + ro rw + + + ro + Read-Only + + + rw + Read-Write + + + (ro|rw) + + Authorization type must be either 'rw' or 'ro' + + ro + + + + IP address of SNMP client allowed to contact system + + + + + + + + + + Subnet of SNMP client(s) allowed to contact system + + ipv4net + IP address and prefix length + + + ipv6net + IPv6 address and prefix length + + + + + + + 0.0.0.0/0 ::/0 + + + + + + Contact information + + .{1,255} + + Contact information is limited to 255 characters or less + + + #include + + + Management information base (MIB) + + + + + Sets the maximum number of interfaces included in IF-MIB data collection + + u32:1-4294967295 + Sets the maximum number of interfaces included in IF-MIB data collection + + + + + + + + + Sets the interface name prefix to include in the IF-MIB data collection + + br bond dum eth gnv macsec peth sstpc tun veth vti vtun vxlan wg wlan wwan + + + br + Allow prefix for IF-MIB data collection + + + bond + Allow prefix for IF-MIB data collection + + + dum + Allow prefix for IF-MIB data collection + + + eth + Allow prefix for IF-MIB data collection + + + gnv + Allow prefix for IF-MIB data collection + + + macsec + Allow prefix for IF-MIB data collection + + + peth + Allow prefix for IF-MIB data collection + + + sstpc + Allow prefix for IF-MIB data collection + + + tun + Allow prefix for IF-MIB data collection + + + veth + Allow prefix for IF-MIB data collection + + + vti + Allow prefix for IF-MIB data collection + + + vtun + Allow prefix for IF-MIB data collection + + + vxlan + Allow prefix for IF-MIB data collection + + + wg + Allow prefix for IF-MIB data collection + + + wlan + Allow prefix for IF-MIB data collection + + + wwan + Allow prefix for IF-MIB data collection + + + (br|bond|dum|eth|gnv|macsec|peth|sstpc|tun|veth|vti|vtun|vxlan|wg|wlan|wwan) + + + + + + + + + IP address to listen for incoming SNMP requests + + + + + ipv4 + IPv4 address to listen for incoming SNMP requests + + + ipv6 + IPv6 address to listen for incoming SNMP requests + + + + + + + #include + + 161 + + + + + + Location information + + .{1,255} + + Location is limited to 255 characters or less + + + + + Enable specific OIDs that by default are disable + + ip-forward ip-route-table ip-net-to-media-table ip-net-to-physical-phys-address + + + ip-forward + Enable ipForward: .1.3.6.1.2.1.4.24 + + + ip-route-table + Enable ipRouteTable: .1.3.6.1.2.1.4.21 + + + ip-net-to-media-table + Enable ipNetToMediaTable: .1.3.6.1.2.1.4.22 + + + ip-net-to-physical-phys-address + Enable ipNetToPhysicalPhysAddress: .1.3.6.1.2.1.4.35 + + + (ip-forward|ip-route-table|ip-net-to-media-table|ip-net-to-physical-phys-address) + + OID must be one of the liste options + + + + #include + + + Register a subtree for SMUX-based processing + + txt + SNMP Object Identifier + + + + + + + SNMP trap source address + + ipv4 + IPv4 address + + + ipv6 + IPv6 address + + + + + + + + + Address of trap target + + ipv4 + IPv4 address + + + ipv6 + IPv6 address + + + + + + + + + Community used when sending trap information + + + #include + + 162 + + + + + + Simple Network Management Protocol (SNMP) v3 + + + + + Specifies the EngineID that uniquely identify an agent (e.g. 000000000000000000000002) + + ([0-9a-f][0-9a-f]){1,18} + + ID must contain an even number (from 2 to 36) of hex digits + + + + + + Specifies the group with name groupname + + + #include + + + Security levels + + noauth auth priv + + + noauth + Messages not authenticated and not encrypted (noAuthNoPriv) + + + auth + Messages are authenticated but not encrypted (authNoPriv) + + + priv + Messages are authenticated and encrypted (authPriv) + + + (noauth|auth|priv) + + + auth + + + + Defines the name of view + + service snmp v3 view + + + + + + + + Defines SNMP target for inform or traps for IP + + ipv4 + IP address of trap target + + + ipv6 + IPv6 address of trap target + + + + + + + + + + Defines the privacy + + + + + Defines the encrypted key for authentication + + [0-9a-f]* + + Encrypted key must only contain hex digits + + + + + Defines the clear text key for authentication + + .{8,} + + Key must contain 8 or more characters + + + #include + + + #include + + 162 + + + + Defines the privacy + + + + + Defines the encrypted key for privacy protocol + + [0-9a-f]* + + Encrypted key must only contain hex digits + + + + + Defines the clear text key for privacy protocol + + .{8,} + + Key must contain 8 or more characters + + + #include + + + #include + + + Specifies the type of notification between inform and trap + + inform trap + + + inform + Use INFORM + + + trap + Use TRAP + + + (inform|trap) + + + inform + + + + Defines username for authentication + + service snmp v3 user + + + + + + + + Specifies the user with name username + + [^\(\)\|\-]+ + + Illegal characters in name + + + + + Specifies the auth + + + + + Defines the encrypted key for authentication + + [0-9a-f]* + + Encrypted key must only contain hex digits + + + + + Defines the clear text key for authentication + + .{8,} + + Key must contain 8 or more characters + + + #include + + + + + Specifies group for user name + + service snmp v3 group + + + + #include + + + Defines the privacy + + + + + Defines the encrypted key for privacy protocol + + [0-9a-f]* + + Encrypted key must only contain hex digits + + + + + Defines the clear text key for privacy protocol + + .{8,} + + Key must contain 8 or more characters + + + #include + + + + + + + Specifies the view with name viewname + + [^\(\)\|\-]+ + + Illegal characters in name + + + + + Specifies the oid + + [0-9]+(\.[0-9]+)* + + OID must start from a number + + + + + Exclude is an optional argument + + + + + Defines a bit-mask that is indicating which subidentifiers of the associated subtree OID should be regarded as significant + + [0-9a-f]{2}([\.:][0-9a-f]{2})* + + MASK is a list of hex octets, separated by '.' or ':' + + + + + + + + + + + SNMP script extensions + + + + + Extension name + + [a-z0-9\.\-\_]+ + + Script extension contains invalid characters + + + + + Script location and name + + + + + [a-z0-9\.\-\_\/]+ + + Script extension contains invalid characters + + + + + + + #include + + + + + diff --git a/interface-definitions/service_ssh.xml.in b/interface-definitions/service_ssh.xml.in new file mode 100644 index 000000000..5c893bd35 --- /dev/null +++ b/interface-definitions/service_ssh.xml.in @@ -0,0 +1,270 @@ + + + + + System services + + + + + Secure Shell (SSH) + 1000 + + + + + SSH user/group access controls + + + + + Allow user/group SSH access + + + #include + #include + + + + + Deny user/group SSH access + + + #include + #include + + + + + + + Allowed ciphers + + + 3des-cbc aes128-cbc aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se aes128-ctr aes192-ctr aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com chacha20-poly1305@openssh.com + + + (3des-cbc|aes128-cbc|aes192-cbc|aes256-cbc|rijndael-cbc@lysator.liu.se|aes128-ctr|aes192-ctr|aes256-ctr|aes128-gcm@openssh.com|aes256-gcm@openssh.com|chacha20-poly1305@openssh.com) + + + + + + + Disable IP Address to Hostname lookup + + + + + + Disable password-based authentication + + + + + + Allow dynamic protection + + + + + Block source IP in seconds. Subsequent blocks increase by a factor of 1.5 + + u32:1-65535 + Time interval in seconds for blocking + + + + + + 120 + + + + Remember source IP in seconds before reset their score + + u32:1-65535 + Time interval in seconds + + + + + + 1800 + + + + Block source IP when their cumulative attack score exceeds threshold + + u32:1-65535 + Threshold score + + + + + + 30 + + + + Always allow inbound connections from these systems + + ipv4 + Address to match against + + + ipv4net + IPv4 address and prefix length + + + ipv6 + IPv6 address to match against + + + ipv6net + IPv6 address and prefix length + + + + + + + + + + + + + Allowed host key signature algorithms + + + ssh-ed25519 ssh-ed25519-cert-v01@openssh.com sk-ssh-ed25519@openssh.com sk-ssh-ed25519-cert-v01@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512 ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 sk-ecdsa-sha2-nistp256@openssh.com webauthn-sk-ecdsa-sha2-nistp256@openssh.com ssh-rsa-cert-v01@openssh.com rsa-sha2-256-cert-v01@openssh.com rsa-sha2-512-cert-v01@openssh.com ssh-dss-cert-v01@openssh.com ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp384-cert-v01@openssh.com ecdsa-sha2-nistp521-cert-v01@openssh.com sk-ecdsa-sha2-nistp256-cert-v01@openssh.com + + + + (ssh-ed25519|ssh-ed25519-cert-v01@openssh.com|sk-ssh-ed25519@openssh.com|sk-ssh-ed25519-cert-v01@openssh.com|ssh-rsa|rsa-sha2-256|rsa-sha2-512|ssh-dss|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|sk-ecdsa-sha2-nistp256@openssh.com|webauthn-sk-ecdsa-sha2-nistp256@openssh.com|ssh-rsa-cert-v01@openssh.com|rsa-sha2-256-cert-v01@openssh.com|rsa-sha2-512-cert-v01@openssh.com|ssh-dss-cert-v01@openssh.com|ecdsa-sha2-nistp256-cert-v01@openssh.com|ecdsa-sha2-nistp384-cert-v01@openssh.com|ecdsa-sha2-nistp521-cert-v01@openssh.com|sk-ecdsa-sha2-nistp256-cert-v01@openssh.com) + + + + + + Allowed key exchange (KEX) algorithms + + + diffie-hellman-group1-sha1 diffie-hellman-group14-sha1 diffie-hellman-group14-sha256 diffie-hellman-group16-sha512 diffie-hellman-group18-sha512 diffie-hellman-group-exchange-sha1 diffie-hellman-group-exchange-sha256 ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521 curve25519-sha256 curve25519-sha256@libssh.org + + + + (diffie-hellman-group1-sha1|diffie-hellman-group14-sha1|diffie-hellman-group14-sha256|diffie-hellman-group16-sha512|diffie-hellman-group18-sha512|diffie-hellman-group-exchange-sha1|diffie-hellman-group-exchange-sha256|ecdh-sha2-nistp256|ecdh-sha2-nistp384|ecdh-sha2-nistp521|curve25519-sha256|curve25519-sha256@libssh.org) + + + + #include + + + Log level + + quiet fatal error info verbose + + + quiet + stay silent + + + fatal + log fatals only + + + error + log errors and fatals only + + + info + default log level + + + verbose + enable logging of failed login attempts + + + (quiet|fatal|error|info|verbose) + + + info + + + + Allowed message authentication code (MAC) algorithms + + + hmac-sha1 hmac-sha1-96 hmac-sha2-256 hmac-sha2-512 hmac-md5 hmac-md5-96 umac-64@openssh.com umac-128@openssh.com hmac-sha1-etm@openssh.com hmac-sha1-96-etm@openssh.com hmac-sha2-256-etm@openssh.com hmac-sha2-512-etm@openssh.com hmac-md5-etm@openssh.com hmac-md5-96-etm@openssh.com umac-64-etm@openssh.com umac-128-etm@openssh.com + + + (hmac-sha1|hmac-sha1-96|hmac-sha2-256|hmac-sha2-512|hmac-md5|hmac-md5-96|umac-64@openssh.com|umac-128@openssh.com|hmac-sha1-etm@openssh.com|hmac-sha1-96-etm@openssh.com|hmac-sha2-256-etm@openssh.com|hmac-sha2-512-etm@openssh.com|hmac-md5-etm@openssh.com|hmac-md5-96-etm@openssh.com|umac-64-etm@openssh.com|umac-128-etm@openssh.com) + + + + + + + Port for SSH service + + u32:1-65535 + Numeric IP port + + + + + + + 22 + + + + SSH session rekey limit + + + + + Threshold data in megabytes + + u32:1-65535 + Megabytes + + + + + + + + + Threshold time in minutes + + u32:1-65535 + Minutes + + + + + + + + + + + Enable transmission of keepalives from server to client + + u32:1-65535 + Time interval in seconds for keepalive message + + + + + + + #include + + + + + diff --git a/interface-definitions/service_tftp-server.xml.in b/interface-definitions/service_tftp-server.xml.in new file mode 100644 index 000000000..e48b5a3f0 --- /dev/null +++ b/interface-definitions/service_tftp-server.xml.in @@ -0,0 +1,32 @@ + + + + + + + + Trivial File Transfer Protocol (TFTP) server + 990 + + + + + Folder containing files served by TFTP + + + + + Allow TFTP file uploads + + + + #include + + 69 + + #include + + + + + diff --git a/interface-definitions/service_upnp.xml.in b/interface-definitions/service_upnp.xml.in new file mode 100644 index 000000000..20e01bfbd --- /dev/null +++ b/interface-definitions/service_upnp.xml.in @@ -0,0 +1,228 @@ + + + + + + + Universal Plug and Play (UPnP) service + 900 + + + + + Name of this service + + txt + Friendly name + + + + + + WAN network interface + + + + + #include + + + + + + WAN network IP + + ipv4 + IPv4 address + + + ipv6 + IPv6 address + + + + + + + + + + + Enable NAT-PMP support + + + + + + Enable Secure Mode + + + + + + Presentation Url + + txt + Presentation Url + + + + + + PCP-base lifetime Option + + + + + Max lifetime time + + + + + + + + Min lifetime time + + + + + + + + + + Local IP addresses for service to listen on + + + + + + <interface> + Monitor interface address + + + ipv4 + IPv4 address to listen for incoming connections + + + ipv4net + IPv4 prefix to listen for incoming connections + + + ipv6 + IPv6 address to listen for incoming connections + + + ipv6net + IPv6 prefix to listen for incoming connections + + + + #include + + + + + + + + + Enable STUN probe support (can be used with NAT 1:1 support for WAN interfaces) + + + + + The STUN server address + + txt + The STUN server host address + + + + + + + #include + + + + + UPnP Rule + + u32:0-65535 + Rule number + + + + + + + #include + + + Port range (REQUIRE) + + <port> + single port + + + <portN>-<portM> + Port range (use '-' as delimiter) + + + + + + + + + Port range (REQUIRE) + + <port> + single port + + + <portN>-<portM> + Port range (use '-' as delimiter) + + + + + + + + + The IP to which this rule applies (REQUIRE) + + ipv4 + The IPv4 address to which this rule applies + + + ipv4net + The IPv4 to which this rule applies + + + + + + + + + + Actions against the rule (REQUIRE) + + allow deny + + + (allow|deny) + + + + + + + + + + diff --git a/interface-definitions/service_webproxy.xml.in b/interface-definitions/service_webproxy.xml.in new file mode 100644 index 000000000..637d57891 --- /dev/null +++ b/interface-definitions/service_webproxy.xml.in @@ -0,0 +1,654 @@ + + + + + + + Webproxy service settings + 500 + + + + + Safe port ACL + + u32:1-1024 + Port number. Ports included by default: 21,70,80,210,280,443,488,591,777,873,1025-65535 + + + + + + + + + + SSL safe port + + u32:1-65535 + Port number. Ports included by default: 443 + + + + + + + + + + Default domain name + + domain + Domain to use for urls that do not contain a '.' + + + [.][A-Za-z0-9][-.A-Za-z0-9]* + + Must start append-domain with a '.' + + + + + Proxy Authentication Settings + + + + + Number of authentication helper processes + + n + Number of authentication helper processes + + + + + + 5 + + + + Authenticated session time to live in minutes + + n + Authenticated session timeout + + + + + + 60 + + + + LDAP authentication settings + + + + + LDAP Base DN to search + + + + + LDAP DN used to bind to server + + + + + Filter expression to perform LDAP search with + + + + + LDAP password to bind with + + + + + Use persistent LDAP connection + + + + #include + + 389 + + + + LDAP server to use + + + + + Use SSL/TLS for LDAP connection + + + + + + LDAP username attribute + + + + + LDAP protocol version + + 2 3 + + + 2 + LDAP protocol version 2 + + + 3 + LDAP protocol version 2 + + + + + + 3 + + + + + + Authentication Method + + ldap + + + ldap + Lightweight Directory Access Protocol + + + (ldap) + + The only supported method currently is LDAP + + + + + Name of authentication realm (e.g. "My Company proxy server") + + + + + + + Specify other caches in a hierarchy + + hostname + Cache peers FQDN + + + + + + Hostname or IP address of peer + + ipv4 + Squid cache-peer IPv4 address + + + hostname + Squid cache-peer hostname + + + + + + Invalid FQDN or IP address + + + + + Default Proxy Port + + u32:1025-65535 + Default port number + + + + + + 3128 + + + + Cache peer ICP port + + u32:0 + Cache peer disabled + + + u32:1-65535 + Cache peer ICP port + + + + + + 0 + + + + Cache peer options + + txt + Cache peer options + + + no-query default + + + + Squid peer type (default parent) + + parent sibling multicast + + + parent + Peer is a parent + + + sibling + Peer is a sibling + + + multicast + Peer is a member of a multicast group + + + (parent|sibling|multicast) + + + parent + + + + + + Disk cache size in MB + + u32 + Disk cache size in MB + + + 0 + Disable disk caching + + + 100 + + + + Default Proxy Port + + u32:1025-65535 + Default port number + + + + + + 3128 + + + + Disable logging of HTTP accesses + + + + + + Domain name to block + + + + + + Domain name to access without caching + + + + + + IPv4 listen-address for WebProxy + + + + + ipv4 + IPv4 address listen on + + + + + + Default Proxy Port + + u32:1025-65535 + Default port number + + + + + + + + + + Disable transparent mode + + + + + + + + Maximum size of object to be stored in cache in kilobytes + + u32 + Object size in KB + + + + + + + + + Memory cache size in MB + + u32 + Memory cache size in MB + + + + + + 20 + + + + Maximum size of object to be stored in cache in kilobytes + + u32 + Object size in KB + + + + + + + + + Outgoing IP address for webproxy + + + + + MIME type to block + + image/gif www/mime application/macbinary application/oda application/octet-stream application/pdf application/postscript application/postscript application/postscript text/rtf application/octet-stream application/octet-stream application/x-tar application/x-csh application/x-dvi application/x-hdf application/x-latex text/plain application/x-netcdf application/x-netcdf application/x-sh application/x-tcl application/x-tex application/x-texinfo application/x-texinfo application/x-troff application/x-troff application/x-troff application/x-troff-man application/x-troff-me application/x-troff-ms application/x-wais-source application/zip application/x-bcpio application/x-cpio application/x-gtar application/x-rpm application/x-shar application/x-sv4cpio application/x-sv4crc application/x-tar application/x-ustar audio/basic audio/basic audio/mpeg audio/mpeg audio/mpeg audio/x-aiff audio/x-aiff audio/x-aiff audio/x-wav image/bmp image/ief image/jpeg image/jpeg image/jpeg image/tiff image/tiff image/x-cmu-raster image/x-portable-anymap image/x-portable-bitmap image/x-portable-graymap image/x-portable-pixmap image/x-rgb image/x-xbitmap image/x-xpixmap image/x-xwindowdump text/html text/html text/css application/x-javascript text/plain text/plain text/plain text/plain text/plain text/plain text/plain text/plain text/plain text/richtext text/tab-separated-values text/x-setext video/mpeg video/mpeg video/mpeg video/quicktime video/quicktime video/x-msvideo video/x-sgi-movie application/mac-compactpro application/mac-binhex40 application/macwriteii application/msword application/msword application/vnd.ms-excel application/vnd.ms-powerpoint application/vnd.lotus-1-2-3 application/vnd.mif application/x-stuffit application/pict application/pict application/x-arj-compressed application/x-lha-compressed application/x-lha-compressed application/x-deflate text/plain application/octet-stream application/octet-stream image/png application/octet-stream application/x-xpinstall application/octet-stream text/plain application/x-director application/x-director application/x-director image/vnd.djvu image/vnd.djvu application/octet-stream application/octet-stream application/andrew-inset x-conference/x-cooltalk model/iges model/iges audio/midi audio/midi audio/midi model/mesh model/mesh video/vnd.mpegurl chemical/x-pdb application/x-chess-pgn audio/x-realaudio audio/x-pn-realaudio audio/x-pn-realaudio text/sgml text/sgml application/x-koan application/x-koan application/x-koan application/x-koan application/smil application/smil application/octet-stream application/x-futuresplash application/x-shockwave-flash application/x-cdlink model/vrml image/vnd.wap.wbmp application/vnd.wap.wbxml application/vnd.wap.wmlc application/vnd.wap.wmlscriptc application/vnd.wap.wmlscript application/xhtml application/xhtml text/xml text/xml chemical/x-xyz text/plain + + + (image/gif|www/mime|application/macbinary|application/oda|application/octet-stream|application/pdf|application/postscript|application/postscript|application/postscript|text/rtf|application/octet-stream|application/octet-stream|application/x-tar|application/x-csh|application/x-dvi|application/x-hdf|application/x-latex|text/plain|application/x-netcdf|application/x-netcdf|application/x-sh|application/x-tcl|application/x-tex|application/x-texinfo|application/x-texinfo|application/x-troff|application/x-troff|application/x-troff|application/x-troff-man|application/x-troff-me|application/x-troff-ms|application/x-wais-source|application/zip|application/x-bcpio|application/x-cpio|application/x-gtar|application/x-rpm|application/x-shar|application/x-sv4cpio|application/x-sv4crc|application/x-tar|application/x-ustar|audio/basic|audio/basic|audio/mpeg|audio/mpeg|audio/mpeg|audio/x-aiff|audio/x-aiff|audio/x-aiff|audio/x-wav|image/bmp|image/ief|image/jpeg|image/jpeg|image/jpeg|image/tiff|image/tiff|image/x-cmu-raster|image/x-portable-anymap|image/x-portable-bitmap|image/x-portable-graymap|image/x-portable-pixmap|image/x-rgb|image/x-xbitmap|image/x-xpixmap|image/x-xwindowdump|text/html|text/html|text/css|application/x-javascript|text/plain|text/plain|text/plain|text/plain|text/plain|text/plain|text/plain|text/plain|text/plain|text/richtext|text/tab-separated-values|text/x-setext|video/mpeg|video/mpeg|video/mpeg|video/quicktime|video/quicktime|video/x-msvideo|video/x-sgi-movie|application/mac-compactpro|application/mac-binhex40|application/macwriteii|application/msword|application/msword|application/vnd.ms-excel|application/vnd.ms-powerpoint|application/vnd.lotus-1-2-3|application/vnd.mif|application/x-stuffit|application/pict|application/pict|application/x-arj-compressed|application/x-lha-compressed|application/x-lha-compressed|application/x-deflate|text/plain|application/octet-stream|application/octet-stream|image/png|application/octet-stream|application/x-xpinstall|application/octet-stream|text/plain|application/x-director|application/x-director|application/x-director|image/vnd.djvu|image/vnd.djvu|application/octet-stream|application/octet-stream|application/andrew-inset|x-conference/x-cooltalk|model/iges|model/iges|audio/midi|audio/midi|audio/midi|model/mesh|model/mesh|video/vnd.mpegurl|chemical/x-pdb|application/x-chess-pgn|audio/x-realaudio|audio/x-pn-realaudio|audio/x-pn-realaudio|text/sgml|text/sgml|application/x-koan|application/x-koan|application/x-koan|application/x-koan|application/smil|application/smil|application/octet-stream|application/x-futuresplash|application/x-shockwave-flash|application/x-cdlink|model/vrml|image/vnd.wap.wbmp|application/vnd.wap.wbxml|application/vnd.wap.wmlc|application/vnd.wap.wmlscriptc|application/vnd.wap.wmlscript|application/xhtml|application/xhtml|text/xml|text/xml|chemical/x-xyz|text/plain) + + + + + + + Maximum reply body size in KB + + u32 + Reply size in KB + + + + + + + + + URL filtering settings + + + #include + + + URL filtering via squidGuard redirector + + + #include + + + Auto update settings + + + + + Hour of day for database update + + u32:0-23 + Hour for database update + + + + + + 0 + + + + + + Redirect URL for filtered websites + + url + URL for redirect + + + block.vyos.net + + + + URL filter rule for a source-group + + u32:1-1024 + Rule Number + + + + + SquidGuard rule must between 1-1024 + + + #include + + + Redirect URL for filtered websites + + url + URL for redirect + + + + + + Source-group for this rule + + group + Source group identifier for this rule + + + service webproxy url-filtering squidguard source-group + + + + + + Time-period for this rule + + period + Time period for this rule + + + service webproxy url-filtering squidguard time-period + + + + + + + + Source group name + + name + Name of source group + + + [^0-9][a-zA-Z_][a-zA-Z0-9][\w\-\.]* + + URL-filter source-group cannot start with a number! + + + + + Address for source-group + + ipv4 + IPv4 address to match + + + ipv4net + IPv4 prefix to match + + + ipv4range + IPv4 address range to match + + + + + + + + + + #include + + + Domain for source-group + + domain + Domain name for the source-group + + + + + + + LDAP search expression for an IP address list + + + + + + LDAP search expression for a user group + + + + + + List of user names + + + + + + + Time period name + + + + + Time-period days + + Sun Mon Tue Wed Thu Fri Sat weekdays weekend all + + + Sun + Sunday + + + Mon + Monday + + + Tue + Tuesday + + + Wed + Wednesday + + + Thu + Thursday + + + Fri + Friday + + + Sat + Saturday + + + weekdays + Monday through Friday + + + weekend + Saturday and Sunday + + + all + All days of the week + + + (Sun|Mon|Tue|Wed|Thu|Fri|Sat|weekdays|weekend|all) + + + + + + Time for time-period + + <hh:mm - hh:mm> + Time range in 24hr time + + + + (\d\d:\d\d)-(\d\d:\d\d) + + Expected time format hh:mm - hh:mm in 24hr time + + + + + #include + + + + + + + + + + + diff --git a/interface-definitions/snmp.xml.in b/interface-definitions/snmp.xml.in deleted file mode 100644 index ec2151b98..000000000 --- a/interface-definitions/snmp.xml.in +++ /dev/null @@ -1,598 +0,0 @@ - - - - - - - - Simple Network Management Protocol (SNMP) - 900 - - - - - Community name - - [[:alnum:]-_!@*#]{1,100} - - Community string is limited to alphanumerical characters, -, _, !, @, *, and # with a total lenght of 100 - - - - - Authorization type - - ro rw - - - ro - Read-Only - - - rw - Read-Write - - - (ro|rw) - - Authorization type must be either 'rw' or 'ro' - - ro - - - - IP address of SNMP client allowed to contact system - - - - - - - - - - Subnet of SNMP client(s) allowed to contact system - - ipv4net - IP address and prefix length - - - ipv6net - IPv6 address and prefix length - - - - - - - 0.0.0.0/0 ::/0 - - - - - - Contact information - - .{1,255} - - Contact information is limited to 255 characters or less - - - #include - - - Management information base (MIB) - - - - - Sets the maximum number of interfaces included in IF-MIB data collection - - u32:1-4294967295 - Sets the maximum number of interfaces included in IF-MIB data collection - - - - - - - - - Sets the interface name prefix to include in the IF-MIB data collection - - br bond dum eth gnv macsec peth sstpc tun veth vti vtun vxlan wg wlan wwan - - - br - Allow prefix for IF-MIB data collection - - - bond - Allow prefix for IF-MIB data collection - - - dum - Allow prefix for IF-MIB data collection - - - eth - Allow prefix for IF-MIB data collection - - - gnv - Allow prefix for IF-MIB data collection - - - macsec - Allow prefix for IF-MIB data collection - - - peth - Allow prefix for IF-MIB data collection - - - sstpc - Allow prefix for IF-MIB data collection - - - tun - Allow prefix for IF-MIB data collection - - - veth - Allow prefix for IF-MIB data collection - - - vti - Allow prefix for IF-MIB data collection - - - vtun - Allow prefix for IF-MIB data collection - - - vxlan - Allow prefix for IF-MIB data collection - - - wg - Allow prefix for IF-MIB data collection - - - wlan - Allow prefix for IF-MIB data collection - - - wwan - Allow prefix for IF-MIB data collection - - - (br|bond|dum|eth|gnv|macsec|peth|sstpc|tun|veth|vti|vtun|vxlan|wg|wlan|wwan) - - - - - - - - - IP address to listen for incoming SNMP requests - - - - - ipv4 - IPv4 address to listen for incoming SNMP requests - - - ipv6 - IPv6 address to listen for incoming SNMP requests - - - - - - - #include - - 161 - - - - - - Location information - - .{1,255} - - Location is limited to 255 characters or less - - - - - Enable specific OIDs that by default are disable - - ip-forward ip-route-table ip-net-to-media-table ip-net-to-physical-phys-address - - - ip-forward - Enable ipForward: .1.3.6.1.2.1.4.24 - - - ip-route-table - Enable ipRouteTable: .1.3.6.1.2.1.4.21 - - - ip-net-to-media-table - Enable ipNetToMediaTable: .1.3.6.1.2.1.4.22 - - - ip-net-to-physical-phys-address - Enable ipNetToPhysicalPhysAddress: .1.3.6.1.2.1.4.35 - - - (ip-forward|ip-route-table|ip-net-to-media-table|ip-net-to-physical-phys-address) - - OID must be one of the liste options - - - - #include - - - Register a subtree for SMUX-based processing - - txt - SNMP Object Identifier - - - - - - - SNMP trap source address - - ipv4 - IPv4 address - - - ipv6 - IPv6 address - - - - - - - - - Address of trap target - - ipv4 - IPv4 address - - - ipv6 - IPv6 address - - - - - - - - - Community used when sending trap information - - - #include - - 162 - - - - - - Simple Network Management Protocol (SNMP) v3 - - - - - Specifies the EngineID that uniquely identify an agent (e.g. 000000000000000000000002) - - ([0-9a-f][0-9a-f]){1,18} - - ID must contain an even number (from 2 to 36) of hex digits - - - - - - Specifies the group with name groupname - - - #include - - - Security levels - - noauth auth priv - - - noauth - Messages not authenticated and not encrypted (noAuthNoPriv) - - - auth - Messages are authenticated but not encrypted (authNoPriv) - - - priv - Messages are authenticated and encrypted (authPriv) - - - (noauth|auth|priv) - - - auth - - - - Defines the name of view - - service snmp v3 view - - - - - - - - Defines SNMP target for inform or traps for IP - - ipv4 - IP address of trap target - - - ipv6 - IPv6 address of trap target - - - - - - - - - - Defines the privacy - - - - - Defines the encrypted key for authentication - - [0-9a-f]* - - Encrypted key must only contain hex digits - - - - - Defines the clear text key for authentication - - .{8,} - - Key must contain 8 or more characters - - - #include - - - #include - - 162 - - - - Defines the privacy - - - - - Defines the encrypted key for privacy protocol - - [0-9a-f]* - - Encrypted key must only contain hex digits - - - - - Defines the clear text key for privacy protocol - - .{8,} - - Key must contain 8 or more characters - - - #include - - - #include - - - Specifies the type of notification between inform and trap - - inform trap - - - inform - Use INFORM - - - trap - Use TRAP - - - (inform|trap) - - - inform - - - - Defines username for authentication - - service snmp v3 user - - - - - - - - Specifies the user with name username - - [^\(\)\|\-]+ - - Illegal characters in name - - - - - Specifies the auth - - - - - Defines the encrypted key for authentication - - [0-9a-f]* - - Encrypted key must only contain hex digits - - - - - Defines the clear text key for authentication - - .{8,} - - Key must contain 8 or more characters - - - #include - - - - - Specifies group for user name - - service snmp v3 group - - - - #include - - - Defines the privacy - - - - - Defines the encrypted key for privacy protocol - - [0-9a-f]* - - Encrypted key must only contain hex digits - - - - - Defines the clear text key for privacy protocol - - .{8,} - - Key must contain 8 or more characters - - - #include - - - - - - - Specifies the view with name viewname - - [^\(\)\|\-]+ - - Illegal characters in name - - - - - Specifies the oid - - [0-9]+(\.[0-9]+)* - - OID must start from a number - - - - - Exclude is an optional argument - - - - - Defines a bit-mask that is indicating which subidentifiers of the associated subtree OID should be regarded as significant - - [0-9a-f]{2}([\.:][0-9a-f]{2})* - - MASK is a list of hex octets, separated by '.' or ':' - - - - - - - - - - - SNMP script extensions - - - - - Extension name - - [a-z0-9\.\-\_]+ - - Script extension contains invalid characters - - - - - Script location and name - - - - - [a-z0-9\.\-\_\/]+ - - Script extension contains invalid characters - - - - - - - #include - - - - - diff --git a/interface-definitions/ssh.xml.in b/interface-definitions/ssh.xml.in deleted file mode 100644 index 2bcce2cf0..000000000 --- a/interface-definitions/ssh.xml.in +++ /dev/null @@ -1,270 +0,0 @@ - - - - - System services - - - - - Secure Shell (SSH) - 1000 - - - - - SSH user/group access controls - - - - - Allow user/group SSH access - - - #include - #include - - - - - Deny user/group SSH access - - - #include - #include - - - - - - - Allowed ciphers - - - 3des-cbc aes128-cbc aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se aes128-ctr aes192-ctr aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com chacha20-poly1305@openssh.com - - - (3des-cbc|aes128-cbc|aes192-cbc|aes256-cbc|rijndael-cbc@lysator.liu.se|aes128-ctr|aes192-ctr|aes256-ctr|aes128-gcm@openssh.com|aes256-gcm@openssh.com|chacha20-poly1305@openssh.com) - - - - - - - Disable IP Address to Hostname lookup - - - - - - Disable password-based authentication - - - - - - Allow dynamic protection - - - - - Block source IP in seconds. Subsequent blocks increase by a factor of 1.5 - - u32:1-65535 - Time interval in seconds for blocking - - - - - - 120 - - - - Remember source IP in seconds before reset their score - - u32:1-65535 - Time interval in seconds - - - - - - 1800 - - - - Block source IP when their cumulative attack score exceeds threshold - - u32:1-65535 - Threshold score - - - - - - 30 - - - - Always allow inbound connections from these systems - - ipv4 - Address to match against - - - ipv4net - IPv4 address and prefix length - - - ipv6 - IPv6 address to match against - - - ipv6net - IPv6 address and prefix length - - - - - - - - - - - - - Allowed host key signature algorithms - - - ssh-ed25519 ssh-ed25519-cert-v01@openssh.com sk-ssh-ed25519@openssh.com sk-ssh-ed25519-cert-v01@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512 ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 sk-ecdsa-sha2-nistp256@openssh.com webauthn-sk-ecdsa-sha2-nistp256@openssh.com ssh-rsa-cert-v01@openssh.com rsa-sha2-256-cert-v01@openssh.com rsa-sha2-512-cert-v01@openssh.com ssh-dss-cert-v01@openssh.com ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp384-cert-v01@openssh.com ecdsa-sha2-nistp521-cert-v01@openssh.com sk-ecdsa-sha2-nistp256-cert-v01@openssh.com - - - - (ssh-ed25519|ssh-ed25519-cert-v01@openssh.com|sk-ssh-ed25519@openssh.com|sk-ssh-ed25519-cert-v01@openssh.com|ssh-rsa|rsa-sha2-256|rsa-sha2-512|ssh-dss|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|sk-ecdsa-sha2-nistp256@openssh.com|webauthn-sk-ecdsa-sha2-nistp256@openssh.com|ssh-rsa-cert-v01@openssh.com|rsa-sha2-256-cert-v01@openssh.com|rsa-sha2-512-cert-v01@openssh.com|ssh-dss-cert-v01@openssh.com|ecdsa-sha2-nistp256-cert-v01@openssh.com|ecdsa-sha2-nistp384-cert-v01@openssh.com|ecdsa-sha2-nistp521-cert-v01@openssh.com|sk-ecdsa-sha2-nistp256-cert-v01@openssh.com) - - - - - - Allowed key exchange (KEX) algorithms - - - diffie-hellman-group1-sha1 diffie-hellman-group14-sha1 diffie-hellman-group14-sha256 diffie-hellman-group16-sha512 diffie-hellman-group18-sha512 diffie-hellman-group-exchange-sha1 diffie-hellman-group-exchange-sha256 ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521 curve25519-sha256 curve25519-sha256@libssh.org - - - - (diffie-hellman-group1-sha1|diffie-hellman-group14-sha1|diffie-hellman-group14-sha256|diffie-hellman-group16-sha512|diffie-hellman-group18-sha512|diffie-hellman-group-exchange-sha1|diffie-hellman-group-exchange-sha256|ecdh-sha2-nistp256|ecdh-sha2-nistp384|ecdh-sha2-nistp521|curve25519-sha256|curve25519-sha256@libssh.org) - - - - #include - - - Log level - - quiet fatal error info verbose - - - quiet - stay silent - - - fatal - log fatals only - - - error - log errors and fatals only - - - info - default log level - - - verbose - enable logging of failed login attempts - - - (quiet|fatal|error|info|verbose) - - - info - - - - Allowed message authentication code (MAC) algorithms - - - hmac-sha1 hmac-sha1-96 hmac-sha2-256 hmac-sha2-512 hmac-md5 hmac-md5-96 umac-64@openssh.com umac-128@openssh.com hmac-sha1-etm@openssh.com hmac-sha1-96-etm@openssh.com hmac-sha2-256-etm@openssh.com hmac-sha2-512-etm@openssh.com hmac-md5-etm@openssh.com hmac-md5-96-etm@openssh.com umac-64-etm@openssh.com umac-128-etm@openssh.com - - - (hmac-sha1|hmac-sha1-96|hmac-sha2-256|hmac-sha2-512|hmac-md5|hmac-md5-96|umac-64@openssh.com|umac-128@openssh.com|hmac-sha1-etm@openssh.com|hmac-sha1-96-etm@openssh.com|hmac-sha2-256-etm@openssh.com|hmac-sha2-512-etm@openssh.com|hmac-md5-etm@openssh.com|hmac-md5-96-etm@openssh.com|umac-64-etm@openssh.com|umac-128-etm@openssh.com) - - - - - - - Port for SSH service - - u32:1-65535 - Numeric IP port - - - - - - - 22 - - - - SSH session rekey limit - - - - - Threshold data in megabytes - - u32:1-65535 - Megabytes - - - - - - - - - Threshold time in minutes - - u32:1-65535 - Minutes - - - - - - - - - - - Enable transmission of keepalives from server to client - - u32:1-65535 - Time interval in seconds for keepalive message - - - - - - - #include - - - - - diff --git a/interface-definitions/system-acceleration-qat.xml.in b/interface-definitions/system-acceleration-qat.xml.in deleted file mode 100644 index 812484184..000000000 --- a/interface-definitions/system-acceleration-qat.xml.in +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - Acceleration components - 50 - - - - - Enable Intel QAT (Quick Assist Technology) for cryptographic acceleration - - - - - - - - diff --git a/interface-definitions/system-config-mgmt.xml.in b/interface-definitions/system-config-mgmt.xml.in deleted file mode 100644 index 61089ce34..000000000 --- a/interface-definitions/system-config-mgmt.xml.in +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - Configuration management settings - 400 - - - - - Commit archive settings - - - - - Commit archive location - - http://<user>:<passwd>@<host>/<path> - - - - https://<user>:<passwd>@<host>/<path> - - - - ftp://<user>:<passwd>@<host>/<path> - - - - sftp://<user>:<passwd>@<host>/<path> - - - - scp://<user>:<passwd>@<host>/<path> - - - - tftp://<host>/<path> - - - - git+https://<user>:<passwd>@<host>/<path> - - - - - (ssh|git|git\+(\w+)):\/\/.* - - - - - - - Source address or interface for archive server connections - - - #include - - - - - - - - Commit revisions - - u32:1-65535 - Number of config backups to keep - - - - - Number of revisions must be between 0 and 65535 - - - - - - - diff --git a/interface-definitions/system-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in deleted file mode 100644 index d9504544d..000000000 --- a/interface-definitions/system-conntrack.xml.in +++ /dev/null @@ -1,513 +0,0 @@ - - - - - - - Connection Tracking Engine Options - - 218 - - - - - Enable connection tracking flow accounting - - - - - - Size of connection tracking expect table - - u32:1-50000000 - Number of entries allowed in connection tracking expect table - - - - - - 2048 - - - - Hash size for connection tracking table - - u32:1-50000000 - Size of hash to use for connection tracking table - - - - - - 32768 - - - - Customized rules to ignore selective connection tracking - - - - - IPv4 rules - - - - - Rule number - - u32:1-999999 - Number of conntrack ignore rule - - - - - Ignore rule number must be between 1 and 999999 - - - #include - - - Destination parameters - - - #include - #include - #include - - - - - Interface to ignore connections tracking on - - any - - - - - #include - - - Protocol to match (protocol name, number, or "all") - - - all tcp_udp - - - all - All IP protocols - - - tcp_udp - Both TCP and UDP - - - u32:0-255 - IP protocol number - - - <protocol> - IP protocol name - - - !<protocol> - IP protocol name - - - - - - - - - Source parameters - - - #include - #include - #include - - - #include - - - - - - - IPv6 rules - - - - - Rule number - - u32:1-999999 - Number of conntrack ignore rule - - - - - Ignore rule number must be between 1 and 999999 - - - #include - - - Destination parameters - - - #include - #include - #include - - - - - Interface to ignore connections tracking on - - any - - - - - #include - - - Protocol to match (protocol name, number, or "all") - - - all tcp_udp - - - all - All IP protocols - - - tcp_udp - Both TCP and UDP - - - u32:0-255 - IP protocol number - - - <protocol> - IP protocol name - - - !<protocol> - IP protocol name - - - - - - - - - Source parameters - - - #include - #include - #include - - - #include - - - - - - - - - - Log connection tracking events per protocol - - - - - Log connection tracking events for ICMP - - - #include - - - - - Log connection tracking events for all protocols other than TCP, UDP and ICMP - - - #include - - - - - Log connection tracking events for TCP - - - #include - - - - - Log connection tracking events for UDP - - - #include - - - - - - - Connection tracking modules - - - - - FTP connection tracking - - - - - - H.323 connection tracking - - - - - - NFS connection tracking - - - - - - PPTP connection tracking - - - - - - SIP connection tracking - - - - - - SQLnet connection tracking - - - - - - TFTP connection tracking - - - - - - - - Size of connection tracking table - - u32:1-50000000 - Number of entries allowed in connection tracking table - - - - - - 262144 - - - - TCP options - - - - - Maximum number of TCP half-open connections - - u32:1-2147483647 - Generic connection timeout in seconds - - - - - - 512 - - - - Policy to track previously established connections - - enable disable - - - enable - Allow tracking of previously established connections - - - disable - Do not allow tracking of previously established connections - - - (enable|disable) - - - enable - - - - Maximum number of packets that can be retransmitted without received an ACK - - u32:1-255 - Number of packets to be retransmitted - - - - - - 3 - - - - - - Connection timeout options - - - - - Define custom timeouts per connection - - - - - IPv4 rules - - - - - Rule number - - u32:1-999999 - Number of conntrack rule - - - - - Ignore rule number must be between 1 and 999999 - - - #include - - - Destination parameters - - - #include - #include - - - - - Interface to ignore connections tracking on - - any - - - - - - - Customize protocol specific timers, one protocol configuration per rule - - - #include - - - - - Source parameters - - - #include - #include - - - - - - - - - IPv6 rules - - - - - Rule number - - u32:1-999999 - Number of conntrack rule - - - - - Ignore rule number must be between 1 and 999999 - - - #include - - - Destination parameters - - - #include - #include - - - - - Interface to ignore connections tracking on - - any - - - - - - - Customize protocol specific timers, one protocol configuration per rule - - - #include - - - - - Source parameters - - - #include - #include - - - - - - - - - #include - - - - - - - diff --git a/interface-definitions/system-console.xml.in b/interface-definitions/system-console.xml.in deleted file mode 100644 index 5acd3e90b..000000000 --- a/interface-definitions/system-console.xml.in +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - Serial console configuration - 100 - - - - - Serial console device name - - - - - - ttySN - TTY device name, regular serial port - - - usbNbXpY - TTY device name, USB based - - - hvcN - Xen console - - - (ttyS[0-9]+|hvc[0-9]+|usb[0-9]+b.*) - - - - - - Console baud rate - - 1200 2400 4800 9600 19200 38400 57600 115200 - - - 1200 - 1200 bps - - - 2400 - 2400 bps - - - 4800 - 4800 bps - - - 9600 - 9600 bps - - - 19200 - 19200 bps - - - 38400 - 38400 bps - - - 57600 - 57600 bps - - - 115200 - 115200 bps - - - (1200|2400|4800|9600|19200|38400|57600|115200) - - - 115200 - - - - - - Enable screen blank powersaving on VGA console - - - - - - - - diff --git a/interface-definitions/system-frr.xml.in b/interface-definitions/system-frr.xml.in deleted file mode 100644 index 76001b392..000000000 --- a/interface-definitions/system-frr.xml.in +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - Configure FRR parameters - - 150 - - - - - Enable BGP Monitoring Protocol support - - - - - - Number of open file descriptors a process is allowed to use - - u32:1024-8192 - Number of file descriptors - - - - - Port number must be in range 1024 to 8192 - - 1024 - - - - Enable ICMP Router Discovery Protocol support - - - - - - Enable SNMP integration for next daemons - - - - - BGP - - - - - - IS-IS - - - - - - LDP - - - - - - OSPFv3 - - - - - - OSPFv2 - - - - - - RIP - - - - - - Zebra (IP routing manager) - - - - - - - - - - diff --git a/interface-definitions/system-ip.xml.in b/interface-definitions/system-ip.xml.in deleted file mode 100644 index 6db4dbfc7..000000000 --- a/interface-definitions/system-ip.xml.in +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - IPv4 Settings - - 290 - - - - - Parameters for ARP cache - - - #include - - - - - Disable IPv4 forwarding on all interfaces - - - - - - Disable IPv4 directed broadcast forwarding on all interfaces - - - - - - IPv4 multipath settings - - - - - Ignore next hops that are not in the ARP table - - - - - - Use layer 4 information for ECMP hashing - - - - - - - - IPv4 TCP parameters - - - - - IPv4 TCP MSS probing options - - - - - Attempt to lower the MSS if TCP connections fail to establish - - on-icmp-black-hole force - - - on-icmp-black-hole - Attempt TCP MSS probing when an ICMP black hole is detected - - - force - Attempt TCP MSS probing by default - - - (on-icmp-black-hole|force) - - Must be on-icmp-black-hole or force - - - - - Base MSS to start probing from (applicable to "probing force") - - u32:48-1460 - Base MSS value for probing (default: 1024) - - - - - - - - - Minimum MSS to stop probing at (default: 48) - - u32:48-1460 - Minimum MSS value to probe - - - - - - - - - - - #include - - - - - diff --git a/interface-definitions/system-ipv6.xml.in b/interface-definitions/system-ipv6.xml.in deleted file mode 100644 index e17e1c01c..000000000 --- a/interface-definitions/system-ipv6.xml.in +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - IPv6 Settings - - 290 - - - - - Disable IPv6 forwarding on all interfaces - - - - - - IPv6 multipath settings - - - - - Use layer 4 information for ECMP hashing - - - - - - - - Parameters for neighbor discovery cache - - - #include - - - #include - - - Disable IPv6 operation on interface when DAD fails on LL addr - - - - - - - - diff --git a/interface-definitions/system-lcd.xml.in b/interface-definitions/system-lcd.xml.in deleted file mode 100644 index 0cf4de308..000000000 --- a/interface-definitions/system-lcd.xml.in +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - System LCD display - 100 - - - - - Model of the display attached to this system - - cfa-533 cfa-631 cfa-633 cfa-635 hd44780 sdec - - - cfa-533 - Crystalfontz CFA-533 - - - cfa-631 - Crystalfontz CFA-631 - - - cfa-633 - Crystalfontz CFA-633 - - - cfa-635 - Crystalfontz CFA-635 - - - hd44780 - Hitachi HD44780, Caswell Appliances - - - sdec - Lanner, Watchguard, Nexcom NSA, Sophos UTM appliances - - - (cfa-533|cfa-631|cfa-633|cfa-635|hd44780|sdec) - - - - - - Physical device used by LCD display - - - - - - ttySXX - TTY device name, regular serial port - - - usbNbXpY - TTY device name, USB based - - - (ttyS[0-9]+|usb[0-9]+b.*) - - - - - - - - diff --git a/interface-definitions/system-login-banner.xml.in b/interface-definitions/system-login-banner.xml.in deleted file mode 100644 index bdd0ad96a..000000000 --- a/interface-definitions/system-login-banner.xml.in +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - System User Login Configuration - 400 - - - - - System login banners - - - - - A system banner after the user logs in - - - - - A system banner before the user logs in - - - - - - - - - diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in deleted file mode 100644 index a2f8beead..000000000 --- a/interface-definitions/system-login.xml.in +++ /dev/null @@ -1,302 +0,0 @@ - - - - - - - System User Login Configuration - 400 - - - - - Local user account information - - #include - - Username contains illegal characters or\nexceeds 100 character limitation. - - - - - Authentication settings - - - - - Encrypted password - - (\*|\!) - [a-zA-Z0-9\.\/]{13} - \$1\$[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{22} - \$5\$(rounds=[0-9]+\$)?[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{43} - \$6\$(rounds=[0-9]+\$)?[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{86} - - Invalid encrypted password for $VAR(../../@). - - ! - - - - One-Time-Pad (two-factor) authentication parameters - - - - - Limit number of logins (rate-limit) per rate-time - - u32:1-10 - Number of attempts - - - - - Number of login attempts must me between 1 and 10 - - 3 - - - - Limit number of logins (rate-limit) per rate-time - - u32:15-600 - Time interval - - - - - Rate limit time interval must be between 15 and 600 seconds - - 30 - - - - Set window of concurrently valid codes - - u32:1-21 - Window size - - - - - Window of concurrently valid codes must be between 1 and 21 - - 3 - - - - Key/secret the token algorithm (see RFC4226) - - txt - Base32 encoded key/token - - - [a-zA-Z2-7]{26,10000} - - Key must only include base32 characters and be at least 26 characters long - - - - - - - Plaintext password used for encryption - - - - - Remote access public keys - - txt - Key identifier used by ssh-keygen (usually of form user@host) - - - - - - Public key value (Base64 encoded) - - - - - - - - Optional public key options - - - - - SSH public key type - - ssh-dss ssh-rsa ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 sk-ecdsa-sha2-nistp256@openssh.com sk-ssh-ed25519@openssh.com - - - ssh-dss - Digital Signature Algorithm (DSA) key support - - - ssh-rsa - Key pair based on RSA algorithm - - - ecdsa-sha2-nistp256 - Elliptic Curve DSA with NIST P-256 curve - - - ecdsa-sha2-nistp384 - Elliptic Curve DSA with NIST P-384 curve - - - ecdsa-sha2-nistp521 - Elliptic Curve DSA with NIST P-521 curve - - - ssh-ed25519 - Edwards-curve DSA with elliptic curve 25519 - - - sk-ecdsa-sha2-nistp256@openssh.com - Elliptic Curve DSA security key - - - sk-ssh-ed25519@openssh.com - Elliptic curve 25519 security key - - - (ssh-dss|ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|ssh-ed25519|sk-ecdsa-sha2-nistp256@openssh.com|sk-ssh-ed25519@openssh.com) - - - - - - - - - - Full name of the user (use quotes for names with spaces) - - [^:]* - - Cannot use ':' in full name - - - - - Home directory - - txt - Path to home directory - - - \/$|(\/[a-zA-Z_0-9-.]+)+ - - - - - - #include - - - - - #include - - - Server priority - - u32:1-255 - Server priority - - - - - - 255 - - - - #include - - - - - TACACS+ based user authentication - - - - - TACACS+ server configuration - - ipv4 - TACACS+ server IPv4 address - - - - - - - #include - #include - #include - - 49 - - - - #include - - - Security mode for TACACS+ authentication - - mandatory optional - - - mandatory - Deny access immediately if TACACS+ answers with REJECT - - - optional - Pass to the next authentication method if TACACS+ answers with REJECT - - - (mandatory|optional) - - - optional - - #include - #include - - - - - Maximum number of all login sessions - - u32:1-65536 - Maximum number of all login sessions - - - - - Maximum logins must be between 1 and 65536 - - - - - Session timeout - - u32:5-604800 - Session timeout in seconds - - - - - Timeout must be between 5 and 604800 seconds - - - - - - - diff --git a/interface-definitions/system-logs.xml.in b/interface-definitions/system-logs.xml.in deleted file mode 100644 index 1caa7abb6..000000000 --- a/interface-definitions/system-logs.xml.in +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - Logging options - 9999 - - - - - Logrotate options - - - - - Atop logs options (system resources usage) - - - - - Size of a single log file that triggers rotation - - u32:1-1024 - Size in MB - - - - - The size must be between 1 and 1024 MB - - 10 - - - - Count of rotations before old logs will be deleted - - u32:1-100 - Rotations - - - - - The count must be between 1 and 100 - - 10 - - - - - - The /var/log/messages file rotation - - - - - Size of a single log file that triggers rotation - - u32:1-1024 - Size in MB - - - - - The size must be between 1 and 1024 MB - - 1 - - - - Count of rotations before old logs will be deleted - - u32:1-100 - Rotations - - - - - The count must be between 1 and 100 - - 10 - - - - - - - - - - diff --git a/interface-definitions/system-option.xml.in b/interface-definitions/system-option.xml.in deleted file mode 100644 index b1b5f7fae..000000000 --- a/interface-definitions/system-option.xml.in +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - - System Options - 9999 - - - - - System action on Ctrl-Alt-Delete keystroke - - ignore reboot poweroff - - - ignore - Ignore key sequence - - - reboot - Reboot system - - - poweroff - Poweroff system - - - (ignore|reboot|poweroff) - - Must be ignore, reboot, or poweroff - - - - - System keyboard layout, type ISO2 - - us uk fr de es fi jp106 no dk se-latin1 dvorak - - - us - United States - - - uk - United Kingdom - - - fr - France - - - de - Germany - - - es - Spain - - - fi - Finland - - - jp106 - Japan - - - no - Norway - - - dk - Denmark - - - se-latin1 - Sweden - - - dvorak - Dvorak - - - (us|uk|fr|de|es|fi|jp106|no|dk|se-latin1|dvorak) - - Invalid keyboard layout - - us - - - - Tune system performance - - throughput latency - - - throughput - Tune for maximum network throughput - - - latency - Tune for low network latency - - - (throughput|latency) - - - - - - Global options used for HTTP client - - - #include - #include - - - - - Reboot system on kernel panic - - - - - - Global options used for SSH client - - - #include - #include - - - - - plays sound via system speaker when you can login - - - - - - Enable root partition auto-extention on system boot - - - - - - System time-format - - 12-hour 24-hour - - - 12-hour - 12 hour time format - - - 24-hour - 24 hour time format - - - (12-hour|24-hour) - - - 12-hour - - - - - - diff --git a/interface-definitions/system-proxy.xml.in b/interface-definitions/system-proxy.xml.in deleted file mode 100644 index f7ab31d7e..000000000 --- a/interface-definitions/system-proxy.xml.in +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - Sets a proxy for system wide use - - - - - Proxy URL - - http(s)?:\/\/[a-z0-9-\.]+ - - - - #include - #include - #include - - - - - diff --git a/interface-definitions/system-sflow.xml.in b/interface-definitions/system-sflow.xml.in deleted file mode 100644 index c5152abe9..000000000 --- a/interface-definitions/system-sflow.xml.in +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - sFlow settings - 990 - - - - - sFlow agent IPv4 or IPv6 address - - auto - - - - ipv4 - sFlow IPv4 agent address - - - ipv6 - sFlow IPv6 agent address - - - - - - - - - - IP address associated with this interface - - - - - txt - Interface name - - - #include - - - - - - Export headers of dropped by kernel packets - - u32:1-65535 - Maximum rate limit of N drops per second send out in the sFlow datagrams - - - - - - - #include - - - Schedule counter-polling in seconds - - u32:1-600 - Polling rate in seconds - - - - - - 30 - - - - sFlow sampling-rate - - u32:1-65535 - Sampling rate (1 in N packets) - - - - - - 1000 - - - - sFlow destination server - - ipv4 - IPv4 server to export sFlow - - - ipv6 - IPv6 server to export sFlow - - - - - - - #include - - 6343 - - - - - - - - diff --git a/interface-definitions/system-sysctl.xml.in b/interface-definitions/system-sysctl.xml.in deleted file mode 100644 index bf118c24b..000000000 --- a/interface-definitions/system-sysctl.xml.in +++ /dev/null @@ -1,40 +0,0 @@ - - - - - System parameters - - - - - Configure kernel parameters at runtime - 318 - - - - - Sysctl key name - - - - - txt - Sysctl key name - - - - - - - - - Sysctl configuration value - - - - - - - - - diff --git a/interface-definitions/system-syslog.xml.in b/interface-definitions/system-syslog.xml.in deleted file mode 100644 index cd5c514a8..000000000 --- a/interface-definitions/system-syslog.xml.in +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - - System logging - 400 - - - - - Logging to specific terminal of given user - - system login user - - - txt - Local user account - - - #include - - illegal characters in user - - - #include - - - - - Logging to remote host - - - - - Invalid host (FQDN or IP address) - - ipv4 - Remote syslog server IPv4 address - - - ipv6 - Remote syslog server IPv6 address - - - hostname - Remote syslog server FQDN - - - - #include - - 514 - - #include - #include - - - Logging format - - - - - Allows for the transmission of all characters inside a syslog message - - - - - - - - - - Logging to system standard location - - - #include - - - mark messages sent to syslog - - - - - time interval how often a mark message is being sent in seconds - - - - - 1200 - - - - - - uses FQDN for logging - - - - - - - - Logging to a file - - [a-zA-Z0-9\-_.]{1,255} - - illegal characters in filename or filename longer than 255 characters - - - - - Log file size and rotation characteristics - - - - - Number of saved files - - [0-9]+ - - illegal characters in number of files - - 5 - - - - Size of log files in kbytes - - [0-9]+ - - illegal characters in size - - 256 - - - - #include - - - - - logging to serial console - - - #include - - - #include - - - - - diff --git a/interface-definitions/system-time-zone.xml.in b/interface-definitions/system-time-zone.xml.in deleted file mode 100644 index f6b291984..000000000 --- a/interface-definitions/system-time-zone.xml.in +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - Local time zone (default UTC) - 100 - - - - - - - - - - - diff --git a/interface-definitions/system-update-check.xml.in b/interface-definitions/system-update-check.xml.in deleted file mode 100644 index a7d754003..000000000 --- a/interface-definitions/system-update-check.xml.in +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - Check available update images - 9999 - - - - - Enable auto check for new images - - - - #include - - - - - diff --git a/interface-definitions/system_acceleration.xml.in b/interface-definitions/system_acceleration.xml.in new file mode 100644 index 000000000..fb5c9d4ea --- /dev/null +++ b/interface-definitions/system_acceleration.xml.in @@ -0,0 +1,21 @@ + + + + + + + Acceleration components + 50 + + + + + Enable Intel QAT (Quick Assist Technology) for cryptographic acceleration + + + + + + + + diff --git a/interface-definitions/system_config-management.xml.in b/interface-definitions/system_config-management.xml.in new file mode 100644 index 000000000..7ae347955 --- /dev/null +++ b/interface-definitions/system_config-management.xml.in @@ -0,0 +1,82 @@ + + + + + + + Configuration management settings + 400 + + + + + Commit archive settings + + + + + Commit archive location + + http://<user>:<passwd>@<host>/<path> + + + + https://<user>:<passwd>@<host>/<path> + + + + ftp://<user>:<passwd>@<host>/<path> + + + + sftp://<user>:<passwd>@<host>/<path> + + + + scp://<user>:<passwd>@<host>/<path> + + + + tftp://<host>/<path> + + + + git+https://<user>:<passwd>@<host>/<path> + + + + + (ssh|git|git\+(\w+)):\/\/.* + + + + + + + Source address or interface for archive server connections + + + #include + + + + + + + + Commit revisions + + u32:1-65535 + Number of config backups to keep + + + + + Number of revisions must be between 0 and 65535 + + + + + + + diff --git a/interface-definitions/system_conntrack.xml.in b/interface-definitions/system_conntrack.xml.in new file mode 100644 index 000000000..a348097cc --- /dev/null +++ b/interface-definitions/system_conntrack.xml.in @@ -0,0 +1,513 @@ + + + + + + + Connection Tracking Engine Options + + 218 + + + + + Enable connection tracking flow accounting + + + + + + Size of connection tracking expect table + + u32:1-50000000 + Number of entries allowed in connection tracking expect table + + + + + + 2048 + + + + Hash size for connection tracking table + + u32:1-50000000 + Size of hash to use for connection tracking table + + + + + + 32768 + + + + Customized rules to ignore selective connection tracking + + + + + IPv4 rules + + + + + Rule number + + u32:1-999999 + Number of conntrack ignore rule + + + + + Ignore rule number must be between 1 and 999999 + + + #include + + + Destination parameters + + + #include + #include + #include + + + + + Interface to ignore connections tracking on + + any + + + + + #include + + + Protocol to match (protocol name, number, or "all") + + + all tcp_udp + + + all + All IP protocols + + + tcp_udp + Both TCP and UDP + + + u32:0-255 + IP protocol number + + + <protocol> + IP protocol name + + + !<protocol> + IP protocol name + + + + + + + + + Source parameters + + + #include + #include + #include + + + #include + + + + + + + IPv6 rules + + + + + Rule number + + u32:1-999999 + Number of conntrack ignore rule + + + + + Ignore rule number must be between 1 and 999999 + + + #include + + + Destination parameters + + + #include + #include + #include + + + + + Interface to ignore connections tracking on + + any + + + + + #include + + + Protocol to match (protocol name, number, or "all") + + + all tcp_udp + + + all + All IP protocols + + + tcp_udp + Both TCP and UDP + + + u32:0-255 + IP protocol number + + + <protocol> + IP protocol name + + + !<protocol> + IP protocol name + + + + + + + + + Source parameters + + + #include + #include + #include + + + #include + + + + + + + + + + Log connection tracking events per protocol + + + + + Log connection tracking events for ICMP + + + #include + + + + + Log connection tracking events for all protocols other than TCP, UDP and ICMP + + + #include + + + + + Log connection tracking events for TCP + + + #include + + + + + Log connection tracking events for UDP + + + #include + + + + + + + Connection tracking modules + + + + + FTP connection tracking + + + + + + H.323 connection tracking + + + + + + NFS connection tracking + + + + + + PPTP connection tracking + + + + + + SIP connection tracking + + + + + + SQLnet connection tracking + + + + + + TFTP connection tracking + + + + + + + + Size of connection tracking table + + u32:1-50000000 + Number of entries allowed in connection tracking table + + + + + + 262144 + + + + TCP options + + + + + Maximum number of TCP half-open connections + + u32:1-2147483647 + Generic connection timeout in seconds + + + + + + 512 + + + + Policy to track previously established connections + + enable disable + + + enable + Allow tracking of previously established connections + + + disable + Do not allow tracking of previously established connections + + + (enable|disable) + + + enable + + + + Maximum number of packets that can be retransmitted without received an ACK + + u32:1-255 + Number of packets to be retransmitted + + + + + + 3 + + + + + + Connection timeout options + + + + + Define custom timeouts per connection + + + + + IPv4 rules + + + + + Rule number + + u32:1-999999 + Number of conntrack rule + + + + + Ignore rule number must be between 1 and 999999 + + + #include + + + Destination parameters + + + #include + #include + + + + + Interface to ignore connections tracking on + + any + + + + + + + Customize protocol specific timers, one protocol configuration per rule + + + #include + + + + + Source parameters + + + #include + #include + + + + + + + + + IPv6 rules + + + + + Rule number + + u32:1-999999 + Number of conntrack rule + + + + + Ignore rule number must be between 1 and 999999 + + + #include + + + Destination parameters + + + #include + #include + + + + + Interface to ignore connections tracking on + + any + + + + + + + Customize protocol specific timers, one protocol configuration per rule + + + #include + + + + + Source parameters + + + #include + #include + + + + + + + + + #include + + + + + + + diff --git a/interface-definitions/system_console.xml.in b/interface-definitions/system_console.xml.in new file mode 100644 index 000000000..5acd3e90b --- /dev/null +++ b/interface-definitions/system_console.xml.in @@ -0,0 +1,91 @@ + + + + + + + Serial console configuration + 100 + + + + + Serial console device name + + + + + + ttySN + TTY device name, regular serial port + + + usbNbXpY + TTY device name, USB based + + + hvcN + Xen console + + + (ttyS[0-9]+|hvc[0-9]+|usb[0-9]+b.*) + + + + + + Console baud rate + + 1200 2400 4800 9600 19200 38400 57600 115200 + + + 1200 + 1200 bps + + + 2400 + 2400 bps + + + 4800 + 4800 bps + + + 9600 + 9600 bps + + + 19200 + 19200 bps + + + 38400 + 38400 bps + + + 57600 + 57600 bps + + + 115200 + 115200 bps + + + (1200|2400|4800|9600|19200|38400|57600|115200) + + + 115200 + + + + + + Enable screen blank powersaving on VGA console + + + + + + + + diff --git a/interface-definitions/system_domain-name.xml.in b/interface-definitions/system_domain-name.xml.in new file mode 100644 index 000000000..bfca9b8ce --- /dev/null +++ b/interface-definitions/system_domain-name.xml.in @@ -0,0 +1,15 @@ + + + + + + + System domain name + + + + + + + + diff --git a/interface-definitions/system_domain-search.xml.in b/interface-definitions/system_domain-search.xml.in new file mode 100644 index 000000000..eb6c8a85c --- /dev/null +++ b/interface-definitions/system_domain-search.xml.in @@ -0,0 +1,18 @@ + + + + + + + Domain Name Server (DNS) domain completion order + 400 + + + + Invalid domain name (RFC 1123 section 2).\nMay only contain letters, numbers and period. + + + + + + diff --git a/interface-definitions/system_flow-accounting.xml.in b/interface-definitions/system_flow-accounting.xml.in new file mode 100644 index 000000000..83a2480a3 --- /dev/null +++ b/interface-definitions/system_flow-accounting.xml.in @@ -0,0 +1,437 @@ + + + + + + + + Flow accounting settings + 990 + + + + + Buffer size + + u32 + Buffer size in MiB + + + + + + 10 + + + + Specifies the maximum number of bytes to capture for each packet + + u32:128-750 + Packet length in bytes + + + + + + 128 + + + + Enable egress flow accounting + + + + + + Disable in memory table plugin + + + + + + Syslog facility for flow-accounting + + auth authpriv cron daemon kern lpr mail mark news protocols security syslog user uucp local0 local1 local2 local3 local4 local5 local6 local7 all + + + auth + Authentication and authorization + + + authpriv + Non-system authorization + + + cron + Cron daemon + + + daemon + System daemons + + + kern + Kernel + + + lpr + Line printer spooler + + + mail + Mail subsystem + + + mark + Timestamp + + + news + USENET subsystem + + + protocols + Routing protocols (local7) + + + security + Authentication and authorization + + + syslog + Authentication and authorization + + + user + Application processes + + + uucp + UUCP subsystem + + + local0 + Local facility 0 + + + local1 + Local facility 1 + + + local2 + Local facility 2 + + + local3 + Local facility 3 + + + local4 + Local facility 4 + + + local5 + Local facility 5 + + + local6 + Local facility 6 + + + local7 + Local facility 7 + + + all + Authentication and authorization + + + (auth|authpriv|cron|daemon|kern|lpr|mail|mark|news|protocols|security|syslog|user|uucp|local0|local1|local2|local3|local4|local5|local6|local7|all) + + + + #include + + + NetFlow settings + + + + + NetFlow engine-id + + 0-255 or 0-255:0-255 + NetFlow engine-id for v5 + + + u32 + NetFlow engine-id for v9 / IPFIX + + + (\d|[1-9]\d{1,8}|[1-3]\d{9}|4[01]\d{8}|42[0-8]\d{7}|429[0-3]\d{6}|4294[0-8]\d{5}|42949[0-5]\d{4}|429496[0-6]\d{3}|4294967[01]\d{2}|42949672[0-8]\d|429496729[0-5])$|^(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]):(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]) + + + + + + NetFlow maximum flows + + u32 + NetFlow maximum flows + + + + + + + + + NetFlow sampling-rate + + u32 + Sampling rate (1 in N packets) + + + + + + + #include + + + NetFlow version to export + + 5 9 10 + + + 5 + NetFlow version 5 + + + 9 + NetFlow version 9 + + + 10 + Internet Protocol Flow Information Export (IPFIX) + + + 9 + + + + NetFlow destination server + + ipv4 + IPv4 server to export NetFlow + + + ipv6 + IPv6 server to export NetFlow + + + + + + + + + NetFlow port number + + u32:1025-65535 + NetFlow port number + + + + + + 2055 + + + + + + NetFlow timeout values + + + + + Expiry scan interval + + u32:0-2147483647 + Expiry scan interval + + + + + + 60 + + + + Generic flow timeout value + + u32:0-2147483647 + Generic flow timeout in seconds + + + + + + 3600 + + + + ICMP timeout value + + u32:0-2147483647 + ICMP timeout in seconds + + + + + + 300 + + + + Max active timeout value + + u32:0-2147483647 + Max active timeout in seconds + + + + + + 604800 + + + + TCP finish timeout value + + u32:0-2147483647 + TCP FIN timeout in seconds + + + + + + 300 + + + + TCP generic timeout value + + u32:0-2147483647 + TCP generic timeout in seconds + + + + + + 3600 + + + + TCP reset timeout value + + u32:0-2147483647 + TCP RST timeout in seconds + + + + + + 120 + + + + UDP timeout value + + u32:0-2147483647 + UDP timeout in seconds + + + + + + 300 + + + + + + + + sFlow settings + + + + + sFlow agent IPv4 address + + auto + + + + ipv4 + sFlow IPv4 agent address + + + + + + + + + sFlow sampling-rate + + u32 + Sampling rate (1 in N packets) + + + + + + + + + sFlow destination server + + ipv4 + IPv4 server to export sFlow + + + ipv6 + IPv6 server to export sFlow + + + + + + + + + sFlow port number + + u32:1025-65535 + sFlow port number + + + + + + 6343 + + + + #include + + + #include + + + + + diff --git a/interface-definitions/system_frr.xml.in b/interface-definitions/system_frr.xml.in new file mode 100644 index 000000000..76001b392 --- /dev/null +++ b/interface-definitions/system_frr.xml.in @@ -0,0 +1,91 @@ + + + + + + + Configure FRR parameters + + 150 + + + + + Enable BGP Monitoring Protocol support + + + + + + Number of open file descriptors a process is allowed to use + + u32:1024-8192 + Number of file descriptors + + + + + Port number must be in range 1024 to 8192 + + 1024 + + + + Enable ICMP Router Discovery Protocol support + + + + + + Enable SNMP integration for next daemons + + + + + BGP + + + + + + IS-IS + + + + + + LDP + + + + + + OSPFv3 + + + + + + OSPFv2 + + + + + + RIP + + + + + + Zebra (IP routing manager) + + + + + + + + + + diff --git a/interface-definitions/system_host-name.xml.in b/interface-definitions/system_host-name.xml.in new file mode 100644 index 000000000..423531a68 --- /dev/null +++ b/interface-definitions/system_host-name.xml.in @@ -0,0 +1,16 @@ + + + + + + + + System host name (default: vyos) + + #include + + + + + + diff --git a/interface-definitions/system_ip.xml.in b/interface-definitions/system_ip.xml.in new file mode 100644 index 000000000..6e3b7d5d0 --- /dev/null +++ b/interface-definitions/system_ip.xml.in @@ -0,0 +1,114 @@ + + + + + + + IPv4 Settings + + 290 + + + + + Parameters for ARP cache + + + #include + + + + + Disable IPv4 forwarding on all interfaces + + + + + + Disable IPv4 directed broadcast forwarding on all interfaces + + + + + + IPv4 multipath settings + + + + + Ignore next hops that are not in the ARP table + + + + + + Use layer 4 information for ECMP hashing + + + + + + + + IPv4 TCP parameters + + + + + IPv4 TCP MSS probing options + + + + + Attempt to lower the MSS if TCP connections fail to establish + + on-icmp-black-hole force + + + on-icmp-black-hole + Attempt TCP MSS probing when an ICMP black hole is detected + + + force + Attempt TCP MSS probing by default + + + (on-icmp-black-hole|force) + + Must be on-icmp-black-hole or force + + + + + Base MSS to start probing from (applicable to "probing force") + + u32:48-1460 + Base MSS value for probing (default: 1024) + + + + + + + + + Minimum MSS to stop probing at (default: 48) + + u32:48-1460 + Minimum MSS value to probe + + + + + + + + + + + #include + + + + + diff --git a/interface-definitions/system_ipv6.xml.in b/interface-definitions/system_ipv6.xml.in new file mode 100644 index 000000000..8957cb6a7 --- /dev/null +++ b/interface-definitions/system_ipv6.xml.in @@ -0,0 +1,50 @@ + + + + + + + IPv6 Settings + + 290 + + + + + Disable IPv6 forwarding on all interfaces + + + + + + IPv6 multipath settings + + + + + Use layer 4 information for ECMP hashing + + + + + + + + Parameters for neighbor discovery cache + + + #include + + + #include + + + Disable IPv6 operation on interface when DAD fails on LL addr + + + + + + + + diff --git a/interface-definitions/system_lcd.xml.in b/interface-definitions/system_lcd.xml.in new file mode 100644 index 000000000..0cf4de308 --- /dev/null +++ b/interface-definitions/system_lcd.xml.in @@ -0,0 +1,70 @@ + + + + + + + System LCD display + 100 + + + + + Model of the display attached to this system + + cfa-533 cfa-631 cfa-633 cfa-635 hd44780 sdec + + + cfa-533 + Crystalfontz CFA-533 + + + cfa-631 + Crystalfontz CFA-631 + + + cfa-633 + Crystalfontz CFA-633 + + + cfa-635 + Crystalfontz CFA-635 + + + hd44780 + Hitachi HD44780, Caswell Appliances + + + sdec + Lanner, Watchguard, Nexcom NSA, Sophos UTM appliances + + + (cfa-533|cfa-631|cfa-633|cfa-635|hd44780|sdec) + + + + + + Physical device used by LCD display + + + + + + ttySXX + TTY device name, regular serial port + + + usbNbXpY + TTY device name, USB based + + + (ttyS[0-9]+|usb[0-9]+b.*) + + + + + + + + diff --git a/interface-definitions/system_login.xml.in b/interface-definitions/system_login.xml.in new file mode 100644 index 000000000..44e1a7a92 --- /dev/null +++ b/interface-definitions/system_login.xml.in @@ -0,0 +1,302 @@ + + + + + + + System User Login Configuration + 400 + + + + + Local user account information + + #include + + Username contains illegal characters or\nexceeds 100 character limitation. + + + + + Authentication settings + + + + + Encrypted password + + (\*|\!) + [a-zA-Z0-9\.\/]{13} + \$1\$[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{22} + \$5\$(rounds=[0-9]+\$)?[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{43} + \$6\$(rounds=[0-9]+\$)?[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{86} + + Invalid encrypted password for $VAR(../../@). + + ! + + + + One-Time-Pad (two-factor) authentication parameters + + + + + Limit number of logins (rate-limit) per rate-time + + u32:1-10 + Number of attempts + + + + + Number of login attempts must me between 1 and 10 + + 3 + + + + Limit number of logins (rate-limit) per rate-time + + u32:15-600 + Time interval + + + + + Rate limit time interval must be between 15 and 600 seconds + + 30 + + + + Set window of concurrently valid codes + + u32:1-21 + Window size + + + + + Window of concurrently valid codes must be between 1 and 21 + + 3 + + + + Key/secret the token algorithm (see RFC4226) + + txt + Base32 encoded key/token + + + [a-zA-Z2-7]{26,10000} + + Key must only include base32 characters and be at least 26 characters long + + + + + + + Plaintext password used for encryption + + + + + Remote access public keys + + txt + Key identifier used by ssh-keygen (usually of form user@host) + + + + + + Public key value (Base64 encoded) + + + + + + + + Optional public key options + + + + + SSH public key type + + ssh-dss ssh-rsa ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 sk-ecdsa-sha2-nistp256@openssh.com sk-ssh-ed25519@openssh.com + + + ssh-dss + Digital Signature Algorithm (DSA) key support + + + ssh-rsa + Key pair based on RSA algorithm + + + ecdsa-sha2-nistp256 + Elliptic Curve DSA with NIST P-256 curve + + + ecdsa-sha2-nistp384 + Elliptic Curve DSA with NIST P-384 curve + + + ecdsa-sha2-nistp521 + Elliptic Curve DSA with NIST P-521 curve + + + ssh-ed25519 + Edwards-curve DSA with elliptic curve 25519 + + + sk-ecdsa-sha2-nistp256@openssh.com + Elliptic Curve DSA security key + + + sk-ssh-ed25519@openssh.com + Elliptic curve 25519 security key + + + (ssh-dss|ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|ssh-ed25519|sk-ecdsa-sha2-nistp256@openssh.com|sk-ssh-ed25519@openssh.com) + + + + + + + + + + Full name of the user (use quotes for names with spaces) + + [^:]* + + Cannot use ':' in full name + + + + + Home directory + + txt + Path to home directory + + + \/$|(\/[a-zA-Z_0-9-.]+)+ + + + + + + #include + + + + + #include + + + Server priority + + u32:1-255 + Server priority + + + + + + 255 + + + + #include + + + + + TACACS+ based user authentication + + + + + TACACS+ server configuration + + ipv4 + TACACS+ server IPv4 address + + + + + + + #include + #include + #include + + 49 + + + + #include + + + Security mode for TACACS+ authentication + + mandatory optional + + + mandatory + Deny access immediately if TACACS+ answers with REJECT + + + optional + Pass to the next authentication method if TACACS+ answers with REJECT + + + (mandatory|optional) + + + optional + + #include + #include + + + + + Maximum number of all login sessions + + u32:1-65536 + Maximum number of all login sessions + + + + + Maximum logins must be between 1 and 65536 + + + + + Session timeout + + u32:5-604800 + Session timeout in seconds + + + + + Timeout must be between 5 and 604800 seconds + + + + + + + diff --git a/interface-definitions/system_login_banner.xml.in b/interface-definitions/system_login_banner.xml.in new file mode 100644 index 000000000..211505ae4 --- /dev/null +++ b/interface-definitions/system_login_banner.xml.in @@ -0,0 +1,32 @@ + + + + + + + System User Login Configuration + 400 + + + + + System login banners + + + + + A system banner after the user logs in + + + + + A system banner before the user logs in + + + + + + + + + diff --git a/interface-definitions/system_logs.xml.in b/interface-definitions/system_logs.xml.in new file mode 100644 index 000000000..b34cbdc39 --- /dev/null +++ b/interface-definitions/system_logs.xml.in @@ -0,0 +1,92 @@ + + + + + + + Logging options + 9999 + + + + + Logrotate options + + + + + Atop logs options (system resources usage) + + + + + Size of a single log file that triggers rotation + + u32:1-1024 + Size in MB + + + + + The size must be between 1 and 1024 MB + + 10 + + + + Count of rotations before old logs will be deleted + + u32:1-100 + Rotations + + + + + The count must be between 1 and 100 + + 10 + + + + + + The /var/log/messages file rotation + + + + + Size of a single log file that triggers rotation + + u32:1-1024 + Size in MB + + + + + The size must be between 1 and 1024 MB + + 1 + + + + Count of rotations before old logs will be deleted + + u32:1-100 + Rotations + + + + + The count must be between 1 and 100 + + 10 + + + + + + + + + + diff --git a/interface-definitions/system_name-server.xml.in b/interface-definitions/system_name-server.xml.in new file mode 100644 index 000000000..2f750abfa --- /dev/null +++ b/interface-definitions/system_name-server.xml.in @@ -0,0 +1,33 @@ + + + + + + + System Domain Name Servers (DNS) + 400 + + + + + ipv4 + Domain Name Server IPv4 address + + + ipv6 + Domain Name Server IPv6 address + + + txt + Use Domain Name Server from DHCP interface + + + + + #include + + + + + + diff --git a/interface-definitions/system_option.xml.in b/interface-definitions/system_option.xml.in new file mode 100644 index 000000000..adb45bdcc --- /dev/null +++ b/interface-definitions/system_option.xml.in @@ -0,0 +1,171 @@ + + + + + + + System Options + 9999 + + + + + System action on Ctrl-Alt-Delete keystroke + + ignore reboot poweroff + + + ignore + Ignore key sequence + + + reboot + Reboot system + + + poweroff + Poweroff system + + + (ignore|reboot|poweroff) + + Must be ignore, reboot, or poweroff + + + + + System keyboard layout, type ISO2 + + us uk fr de es fi jp106 no dk se-latin1 dvorak + + + us + United States + + + uk + United Kingdom + + + fr + France + + + de + Germany + + + es + Spain + + + fi + Finland + + + jp106 + Japan + + + no + Norway + + + dk + Denmark + + + se-latin1 + Sweden + + + dvorak + Dvorak + + + (us|uk|fr|de|es|fi|jp106|no|dk|se-latin1|dvorak) + + Invalid keyboard layout + + us + + + + Tune system performance + + throughput latency + + + throughput + Tune for maximum network throughput + + + latency + Tune for low network latency + + + (throughput|latency) + + + + + + Global options used for HTTP client + + + #include + #include + + + + + Reboot system on kernel panic + + + + + + Global options used for SSH client + + + #include + #include + + + + + plays sound via system speaker when you can login + + + + + + Enable root partition auto-extention on system boot + + + + + + System time-format + + 12-hour 24-hour + + + 12-hour + 12 hour time format + + + 24-hour + 24 hour time format + + + (12-hour|24-hour) + + + 12-hour + + + + + + diff --git a/interface-definitions/system_proxy.xml.in b/interface-definitions/system_proxy.xml.in new file mode 100644 index 000000000..214534dbb --- /dev/null +++ b/interface-definitions/system_proxy.xml.in @@ -0,0 +1,25 @@ + + + + + + + Sets a proxy for system wide use + + + + + Proxy URL + + http(s)?:\/\/[a-z0-9-\.]+ + + + + #include + #include + #include + + + + + diff --git a/interface-definitions/system_sflow.xml.in b/interface-definitions/system_sflow.xml.in new file mode 100644 index 000000000..c5152abe9 --- /dev/null +++ b/interface-definitions/system_sflow.xml.in @@ -0,0 +1,113 @@ + + + + + + + + sFlow settings + 990 + + + + + sFlow agent IPv4 or IPv6 address + + auto + + + + ipv4 + sFlow IPv4 agent address + + + ipv6 + sFlow IPv6 agent address + + + + + + + + + + IP address associated with this interface + + + + + txt + Interface name + + + #include + + + + + + Export headers of dropped by kernel packets + + u32:1-65535 + Maximum rate limit of N drops per second send out in the sFlow datagrams + + + + + + + #include + + + Schedule counter-polling in seconds + + u32:1-600 + Polling rate in seconds + + + + + + 30 + + + + sFlow sampling-rate + + u32:1-65535 + Sampling rate (1 in N packets) + + + + + + 1000 + + + + sFlow destination server + + ipv4 + IPv4 server to export sFlow + + + ipv6 + IPv6 server to export sFlow + + + + + + + #include + + 6343 + + + + + + + + diff --git a/interface-definitions/system_static-host-mapping.xml.in b/interface-definitions/system_static-host-mapping.xml.in new file mode 100644 index 000000000..492741f11 --- /dev/null +++ b/interface-definitions/system_static-host-mapping.xml.in @@ -0,0 +1,53 @@ + + + + + + + Map host names to addresses + 400 + + + + + Host name for static address mapping + + #include + + Host-name must be alphanumeric and can contain hyphens + + + + + Alias for this address + + .{1,63} + + invalid alias hostname, needs to be between 1 and 63 charactes + + + + + + IP Address + + ipv4 + IPv4 address + + + ipv6 + IPv6 address + + + + + + + + + + + + + + diff --git a/interface-definitions/system_sysctl.xml.in b/interface-definitions/system_sysctl.xml.in new file mode 100644 index 000000000..bf118c24b --- /dev/null +++ b/interface-definitions/system_sysctl.xml.in @@ -0,0 +1,40 @@ + + + + + System parameters + + + + + Configure kernel parameters at runtime + 318 + + + + + Sysctl key name + + + + + txt + Sysctl key name + + + + + + + + + Sysctl configuration value + + + + + + + + + diff --git a/interface-definitions/system_syslog.xml.in b/interface-definitions/system_syslog.xml.in new file mode 100644 index 000000000..3343e2c59 --- /dev/null +++ b/interface-definitions/system_syslog.xml.in @@ -0,0 +1,155 @@ + + + + + + + System logging + 400 + + + + + Logging to specific terminal of given user + + system login user + + + txt + Local user account + + + #include + + illegal characters in user + + + #include + + + + + Logging to remote host + + + + + Invalid host (FQDN or IP address) + + ipv4 + Remote syslog server IPv4 address + + + ipv6 + Remote syslog server IPv6 address + + + hostname + Remote syslog server FQDN + + + + #include + + 514 + + #include + #include + + + Logging format + + + + + Allows for the transmission of all characters inside a syslog message + + + + + + + + + + Logging to system standard location + + + #include + + + mark messages sent to syslog + + + + + time interval how often a mark message is being sent in seconds + + + + + 1200 + + + + + + uses FQDN for logging + + + + + + + + Logging to a file + + [a-zA-Z0-9\-_.]{1,255} + + illegal characters in filename or filename longer than 255 characters + + + + + Log file size and rotation characteristics + + + + + Number of saved files + + [0-9]+ + + illegal characters in number of files + + 5 + + + + Size of log files in kbytes + + [0-9]+ + + illegal characters in size + + 256 + + + + #include + + + + + logging to serial console + + + #include + + + #include + + + + + diff --git a/interface-definitions/system_task-scheduler.xml.in b/interface-definitions/system_task-scheduler.xml.in new file mode 100644 index 000000000..597d58813 --- /dev/null +++ b/interface-definitions/system_task-scheduler.xml.in @@ -0,0 +1,72 @@ + + + + + + + Task scheduler settings + + + + + Scheduled task + + txt + Task name + + 999 + + + + + UNIX crontab time specification string + + + + + Execution interval + + <minutes> + Execution interval in minutes + + + <minutes>m + Execution interval in minutes + + + <hours>h + Execution interval in hours + + + <days>d + Execution interval in days + + + [1-9]([0-9]*)([mhd]{0,1}) + + + + + + Executable path and arguments + + + + + Path to executable + + + + + Arguments passed to the executable + + + + + + + + + + + diff --git a/interface-definitions/system_time-zone.xml.in b/interface-definitions/system_time-zone.xml.in new file mode 100644 index 000000000..65cce9e95 --- /dev/null +++ b/interface-definitions/system_time-zone.xml.in @@ -0,0 +1,19 @@ + + + + + + + Local time zone (default UTC) + 100 + + + + + + + + + + + diff --git a/interface-definitions/system_update-check.xml.in b/interface-definitions/system_update-check.xml.in new file mode 100644 index 000000000..14570b039 --- /dev/null +++ b/interface-definitions/system_update-check.xml.in @@ -0,0 +1,22 @@ + + + + + + + Check available update images + 9999 + + + + + Enable auto check for new images + + + + #include + + + + + diff --git a/interface-definitions/tftp-server.xml.in b/interface-definitions/tftp-server.xml.in deleted file mode 100644 index 8ca4da883..000000000 --- a/interface-definitions/tftp-server.xml.in +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - Trivial File Transfer Protocol (TFTP) server - 990 - - - - - Folder containing files served by TFTP - - - - - Allow TFTP file uploads - - - - #include - - 69 - - #include - - - - - diff --git a/interface-definitions/vpn-ipsec.xml.in b/interface-definitions/vpn-ipsec.xml.in deleted file mode 100644 index 1847401b5..000000000 --- a/interface-definitions/vpn-ipsec.xml.in +++ /dev/null @@ -1,1194 +0,0 @@ - - - - - Virtual Private Network (VPN) - - - - - VPN IP security (IPsec) parameters - 901 - - - - - Authentication - - - - - Pre-shared key name - - - #include - - - ID for authentication - - txt - ID used for authentication - - - - - - - IKE pre-shared secret key - - txt - IKE pre-shared secret key - - - - - - - - - - Disable requirement for unique IDs in the Security Database - - - - - - Encapsulating Security Payload (ESP) group name - - - - - Enable ESP compression - - - - - - Security Association time to expire - - u32:30-86400 - SA lifetime in seconds - - - - - - 3600 - - - - Security Association byte count to expire - - u32:1024-26843545600000 - SA life in bytes - - - - - - - - - Security Association packet count to expire - - u32:1000-26843545600000 - SA life in packets - - - - - - - - - ESP mode - - tunnel transport - - - tunnel - Tunnel mode - - - transport - Transport mode - - - (tunnel|transport) - - - tunnel - - - - ESP Perfect Forward Secrecy - - enable dh-group1 dh-group2 dh-group5 dh-group14 dh-group15 dh-group16 dh-group17 dh-group18 dh-group19 dh-group20 dh-group21 dh-group22 dh-group23 dh-group24 dh-group25 dh-group26 dh-group27 dh-group28 dh-group29 dh-group30 dh-group31 dh-group32 disable - - - enable - Inherit Diffie-Hellman group from the IKE group - - - dh-group1 - Use Diffie-Hellman group 1 (modp768) - - - dh-group2 - Use Diffie-Hellman group 2 (modp1024) - - - dh-group5 - Use Diffie-Hellman group 5 (modp1536) - - - dh-group14 - Use Diffie-Hellman group 14 (modp2048) - - - dh-group15 - Use Diffie-Hellman group 15 (modp3072) - - - dh-group16 - Use Diffie-Hellman group 16 (modp4096) - - - dh-group17 - Use Diffie-Hellman group 17 (modp6144) - - - dh-group18 - Use Diffie-Hellman group 18 (modp8192) - - - dh-group19 - Use Diffie-Hellman group 19 (ecp256) - - - dh-group20 - Use Diffie-Hellman group 20 (ecp384) - - - dh-group21 - Use Diffie-Hellman group 21 (ecp521) - - - dh-group22 - Use Diffie-Hellman group 22 (modp1024s160) - - - dh-group23 - Use Diffie-Hellman group 23 (modp2048s224) - - - dh-group24 - Use Diffie-Hellman group 24 (modp2048s256) - - - dh-group25 - Use Diffie-Hellman group 25 (ecp192) - - - dh-group26 - Use Diffie-Hellman group 26 (ecp224) - - - dh-group27 - Use Diffie-Hellman group 27 (ecp224bp) - - - dh-group28 - Use Diffie-Hellman group 28 (ecp256bp) - - - dh-group29 - Use Diffie-Hellman group 29 (ecp384bp) - - - dh-group30 - Use Diffie-Hellman group 30 (ecp512bp) - - - dh-group31 - Use Diffie-Hellman group 31 (curve25519) - - - dh-group32 - Use Diffie-Hellman group 32 (curve448) - - - disable - Disable PFS - - - (enable|dh-group1|dh-group2|dh-group5|dh-group14|dh-group15|dh-group16|dh-group17|dh-group18|dh-group19|dh-group20|dh-group21|dh-group22|dh-group23|dh-group24|dh-group25|dh-group26|dh-group27|dh-group28|dh-group29|dh-group30|dh-group31|dh-group32|disable) - - - enable - - - - ESP group proposal - - u32:1-65535 - ESP group proposal number - - - - #include - #include - - - - - - - Internet Key Exchange (IKE) group name - - - - - Action to take if a child SA is unexpectedly closed - - none hold restart - - - none - Do nothing - - - hold - Attempt to re-negotiate when matching traffic is seen - - - restart - Attempt to re-negotiate the connection immediately - - - (none|hold|restart) - - - none - - - - Dead Peer Detection (DPD) - - - - - Keep-alive failure action - - hold clear restart - - - hold - Attempt to re-negotiate the connection when matching traffic is seen - - - clear - Remove the connection immediately - - - restart - Attempt to re-negotiate the connection immediately - - - (hold|clear|restart) - - - clear - - - - Keep-alive interval - - u32:2-86400 - Keep-alive interval in seconds - - - - - - 30 - - - - Dead Peer Detection keep-alive timeout (IKEv1 only) - - u32:2-86400 - Keep-alive timeout in seconds - - - - - - 120 - - - - - - Re-authentication of the remote peer during an IKE re-key (IKEv2 only) - - - - - - IKE version - - ikev1 ikev2 - - - ikev1 - Use IKEv1 for key exchange - - - ikev2 - Use IKEv2 for key exchange - - - (ikev1|ikev2) - - - - - - IKE lifetime - - u32:0-86400 - IKE lifetime in seconds - - - - - - 28800 - - - - Disable MOBIKE Support (IKEv2 only) - - - - - - IKEv1 phase 1 mode - - main aggressive - - - main - Use the main mode (recommended) - - - aggressive - Use the aggressive mode (insecure, not recommended) - - - (main|aggressive) - - - main - - - - IKE proposal - - u32:1-65535 - IKE group proposal - - - - - - dh-grouphelp - - 1 2 5 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 - - - 1 - Diffie-Hellman group 1 (modp768) - - - 2 - Diffie-Hellman group 2 (modp1024) - - - 5 - Diffie-Hellman group 5 (modp1536) - - - 14 - Diffie-Hellman group 14 (modp2048) - - - 15 - Diffie-Hellman group 15 (modp3072) - - - 16 - Diffie-Hellman group 16 (modp4096) - - - 17 - Diffie-Hellman group 17 (modp6144) - - - 18 - Diffie-Hellman group 18 (modp8192) - - - 19 - Diffie-Hellman group 19 (ecp256) - - - 20 - Diffie-Hellman group 20 (ecp384) - - - 21 - Diffie-Hellman group 21 (ecp521) - - - 22 - Diffie-Hellman group 22 (modp1024s160) - - - 23 - Diffie-Hellman group 23 (modp2048s224) - - - 24 - Diffie-Hellman group 24 (modp2048s256) - - - 25 - Diffie-Hellman group 25 (ecp192) - - - 26 - Diffie-Hellman group 26 (ecp224) - - - 27 - Diffie-Hellman group 27 (ecp224bp) - - - 28 - Diffie-Hellman group 28 (ecp256bp) - - - 29 - Diffie-Hellman group 29 (ecp384bp) - - - 30 - Diffie-Hellman group 30 (ecp512bp) - - - 31 - Diffie-Hellman group 31 (curve25519) - - - 32 - Diffie-Hellman group 32 (curve448) - - - (1|2|5|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32) - - - 2 - - - - Pseudo-Random Functions - - prfmd5 prfsha1 prfaesxcbc prfaescmac prfsha256 prfsha384 prfsha512 - - - prfmd5 - MD5 PRF - - - prfsha1 - SHA1 PRF - - - prfaesxcbc - AES XCBC PRF - - - prfaescmac - AES CMAC PRF - - - prfsha256 - SHA2_256 PRF - - - prfsha384 - SHA2_384 PRF - - - prfsha512 - SHA2_512 PRF - - - (prfmd5|prfsha1|prfaesxcbc|prfaescmac|prfsha256|prfsha384|prfsha512) - - - - #include - #include - - - - - #include - - - IPsec logging - - - - - Global IPsec logging Level - - 0 - Very basic auditing logs (e.g., SA up/SA down) - - - 1 - Generic control flow with errors, a good default to see whats going on - - - 2 - More detailed debugging control flow - - - - - - 0 - - - - Subsystem logging levels - - dmn mgr ike chd job cfg knl net asn enc lib esp tls tnc imc imv pts any - - - dmn - Main daemon setup/cleanup/signal handling - - - mgr - IKE_SA manager, handling synchronization for IKE_SA access - - - ike - IKE_SA/ISAKMP SA - - - chd - CHILD_SA/IPsec SA - - - job - Jobs queuing/processing and thread pool management - - - cfg - Configuration management and plugins - - - knl - IPsec/Networking kernel interface - - - net - IKE network communication - - - asn - Low-level encoding/decoding (ASN.1, X.509 etc.) - - - enc - Packet encoding/decoding encryption/decryption operations - - - lib - libstrongswan library messages - - - esp - libipsec library messages - - - tls - libtls library messages - - - tnc - Trusted Network Connect - - - imc - Integrity Measurement Collector - - - imv - Integrity Measurement Verifier - - - pts - Platform Trust Service - - - any - Any subsystem - - - (dmn|mgr|ike|chd|job|cfg|knl|net|asn|enc|lib|esp|tls|tnc|imc|imv|pts|any) - - - - - - - - - Global IPsec settings - - - - - Do not automatically install routes to remote networks - - - - - - Allow FlexVPN vendor ID payload (IKEv2 only) - - - - #include - - - Allow install virtual-ip addresses - - - - - - - - VPN IPsec profile - - txt - Profile name - - - [a-zA-Z][0-9a-zA-Z_-]+ - - Profile name must be alphanumeric and can contain hyphen(s) and underscore(s) - - - #include - - - Authentication - - - - - Authentication mode - - pre-shared-secret - - - pre-shared-secret - Use a pre-shared secret key - - - - #include - - - - - DMVPN tunnel configuration - - - - - Tunnel interface associated with this profile - - interfaces tunnel - - - txt - Associated interface to this profile - - - - - - - #include - #include - - - - - IKEv2 remote access VPN - - - - - IKEv2 VPN connection name - - txt - Connection name - - - [a-zA-Z][0-9a-zA-Z_-]+ - - Profile name must be alphanumeric and can contain hyphen(s) and underscore(s) - - - - - Authentication for remote access - - - #include - #include - - - Client authentication mode - - x509 eap-tls eap-mschapv2 eap-radius - - - x509 - Use IPsec x.509 certificate authentication - - - eap-tls - Use EAP-TLS authentication - - - eap-mschapv2 - Use EAP-MSCHAPv2 authentication - - - eap-radius - Use EAP-RADIUS authentication - - - (x509|eap-tls|eap-mschapv2|eap-radius) - - - eap-mschapv2 - - #include - - - Server authentication mode - - pre-shared-secret x509 - - - pre-shared-secret - Use a pre-shared secret key - - - x509 - Use x.509 certificate - - - (pre-shared-secret|x509) - - - x509 - - #include - - - #include - #include - #include - #include - #include - #include - - - Timeout to close connection if no data is transmitted - - u32:0 - Disable inactivity checks - - - u32:1-86400 - Timeout in seconds - - - - - - 28800 - - - - IP address pool - - vpn ipsec remote-access pool - dhcp radius - - - txt - Predefined IP pool name - - - dhcp - Forward requests for virtual IP addresses to a DHCP server - - - radius - Forward requests for virtual IP addresses to a RADIUS server - - - - - - - Connection uniqueness enforcement policy - - never keep replace - - - never - Never enforce connection uniqueness - - - keep - Reject new connection attempts if the same user already has an active connection - - - replace - Delete any existing connection if a new one for the same user gets established - - - (never|keep|replace) - - - - - - - - DHCP pool options for remote access - - - #include - - - DHCP server address - - ipv4 - DHCP server IPv4 address - - - - - - - - - - - IP address pool for remote access users - - - - - Local IPv4 or IPv6 pool prefix exclusions - - ipv4net - Local IPv4 pool prefix exclusion - - - ipv6net - Local IPv6 pool prefix exclusion - - - - - - - - - - - Local IPv4 or IPv6 pool prefix - - ipv4net - Local IPv4 pool prefix - - - ipv6net - Local IPv6 pool prefix - - - - - - - - #include - - - #include - - - #include - #include - - - #include - - - - - - - - - Site-to-site VPN - - - - - Connection name of the peer - - txt - Connection name of the peer - - - [-_a-zA-Z0-9|@]+ - - Peer connection name must be alphanumeric and can contain hyphen and underscores - - - #include - - - Peer authentication - - - #include - #include - #include - - - Authentication mode - - pre-shared-secret rsa x509 - - - pre-shared-secret - Use pre-shared secret key - - - rsa - Use RSA key - - - x509 - Use x.509 certificate - - - (pre-shared-secret|rsa|x509) - - - - - - ID for remote authentication - - txt - ID used for peer authentication - - - %any - - - - Use certificate common name as ID - - - - - - - - Connection type - - initiate respond none - - - initiate - Bring the connection up immediately - - - respond - Wait for the peer to initiate the connection - - - none - Load the connection only - - - (initiate|respond|none) - - - - - - Defult ESP group name - - vpn ipsec esp-group - - - - #include - #include - - - Force UDP encapsulation - - - - #include - - - Re-authentication of the remote peer during an IKE re-key (IKEv2 only) - - yes no inherit - - - yes - Enable remote host re-autentication during an IKE re-key. Currently broken due to a strong swan bug - - - no - Disable remote host re-authenticaton during an IKE re-key. - - - inherit - Inherit the reauth configuration form your IKE-group - - - (yes|no|inherit) - - - - #include - #include - - - Peer tunnel - - u32 - Peer tunnel - - - - #include - #include - #include - #include - - - Priority for IPsec policy (lowest value more preferable) - - u32:1-100 - Priority for IPsec policy (lowest value more preferable) - - - - - - - - - Match remote addresses - - - #include - - - Remote IPv4 or IPv6 prefix - - ipv4net - Remote IPv4 prefix - - - ipv6net - Remote IPv6 prefix - - - - - - - - - - - - - - - Initiator request virtual-address from peer - - ipv4 - Request IPv4 address from peer - - - ipv6 - Request IPv6 address from peer - - - - - - - Virtual tunnel interface - - - - - VTI tunnel interface associated with this configuration - - interfaces vti - - - - #include - - - - - - - - - - - diff --git a/interface-definitions/vpn-l2tp.xml.in b/interface-definitions/vpn-l2tp.xml.in deleted file mode 100644 index 3e2d00e6b..000000000 --- a/interface-definitions/vpn-l2tp.xml.in +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - - L2TP Virtual Private Network (VPN) - 902 - - - - - Remote access L2TP VPN - - - #include - #include - - 1436 - - - - External IP address to which VPN clients will connect - - - - - - #include - #include - - - L2TP Network Server (LNS) - - - - - Tunnel password used to authenticate the client (LAC) - - - - - Sent to the client (LAC) in the Host-Name attribute - - #include - - Host-name must be alphanumeric and can contain hyphens - - - - - - - Disable Compression Control Protocol (CCP) - - - - - - Internet Protocol Security (IPsec) for remote access L2TP VPN - - - - - IPsec authentication settings - - - - - Authentication mode for IPsec - - pre-shared-secret - Use pre-shared secret for IPsec authentication - - - x509 - Use X.509 certificate for IPsec authentication - - - (pre-shared-secret|x509) - - - pre-shared-secret x509 - - - - #include - #include - - - - - IKE lifetime - - u32:30-86400 - IKE lifetime in seconds - - - - - - 3600 - - - - ESP lifetime - - u32:30-86400 - IKE lifetime in seconds - - - - - - 3600 - - #include - #include - - - #include - #include - #include - #include - #include - - - Authentication for remote access L2TP VPN - - - #include - #include - #include - #include - #include - - - #include - - - - - - - Advanced protocol options - - - #include - #include - #include - #include - #include - #include - - - #include - - - - - - - diff --git a/interface-definitions/vpn-openconnect.xml.in b/interface-definitions/vpn-openconnect.xml.in deleted file mode 100644 index 736084f8b..000000000 --- a/interface-definitions/vpn-openconnect.xml.in +++ /dev/null @@ -1,392 +0,0 @@ - - - - - - - SSL VPN OpenConnect, AnyConnect compatible server - 901 - - - - - Accounting for users OpenConnect VPN Sessions - - - - - Accounting mode used by this server - - - - - Use RADIUS server for accounting - - - - - - #include - - - - - Authentication for remote access SSL VPN Server - - - - - Authentication mode used by this server - - - - - Use local username/password configuration (OTP supported) - - password - Password-only local authentication - - - otp - OTP-only local authentication - - - password-otp - Password (first) + OTP local authentication - - - (password|otp|password-otp) - - Invalid authentication mode. Must be one of: password, otp or password-otp - - otp password password-otp - - - - - - Use RADIUS server for user autentication - - - - - - - - Include configuration file by username or RADIUS group attribute - - - #include - - - Select per user or per group configuration file - ignored if authentication group is configured - - user group - - - user - Match configuration file on username - - - group - Match RADIUS response class attribute as file name - - - (user|group) - - Invalid mode, must be either user or group - - - - - Directory to containing configuration files - - path - Path to configuration directory, must be under /config/auth - - - - - - - - - Default configuration if discrete config could not be found - - filename - Default configuration filename, must be under /config/auth - - - - - - - - - - - Group that a client is allowed to select (from a list). Maps to RADIUS Class attribute. - - txt - Group string. The group may be followed by a user-friendly name in brackets: group1[First Group] - - - - - #include - - - - - - - 2FA OTP authentication parameters - - - - - Token Key Secret key for the token algorithm (see RFC 4226) - - txt - OTP key in hex-encoded format - - - [a-fA-F0-9]{20,10000} - - Key name must only include hex characters and be at least 20 characters long - - - - - Number of digits in OTP code - - u32:6-8 - Number of digits in OTP code - - - - - Number of digits in OTP code must be between 6 and 8 - - 6 - - - - Time tokens interval in seconds - - u32:5-86400 - Time tokens interval in seconds. - - - - - Time token interval must be between 5 and 86400 seconds - - 30 - - - - Token type - - hotp-time - Time-based OTP algorithm - - - hotp-event - Event-based OTP algorithm - - - (hotp-time|hotp-event) - - - hotp-time hotp-event - - - hotp-time - - - - - - - - #include - - - #include - - - If the groupconfig option is set, then config-per-user will be overriden, and all configuration will be read from RADIUS. - - - - - - - #include - - 0.0.0.0 - - - - Specify custom ports to use for client connections - - - - - tcp port number to accept connections - - u32:1-65535 - Numeric IP port - - - - - - 443 - - - - udp port number to accept connections - - u32:1-65535 - Numeric IP port - - - - - - 443 - - - - - - Enable HTTP security headers - - - - - - SSL Certificate, SSL Key and CA - - - #include - #include - - - - - Network settings - - - - - Route to be pushed to the client - - ipv4net - IPv4 network and prefix length - - - ipv6net - IPv6 network and prefix length - - - - - - - - - - Client IP pools settings - - - - - Client IP subnet (CIDR notation) - - ipv4net - IPv4 address and prefix length - - - - - Not a valid CIDR formatted prefix - - - - - - - Pool of client IPv6 addresses - - - - - Pool of addresses used to assign to clients - - ipv6net - IPv6 address and prefix length - - - - - - - - - Prefix length used for individual client - - u32:48-128 - Client prefix length - - - - - - 64 - - - - #include - - - Domains over which the provided DNS should be used - - txt - Client prefix length - - - - - - - - - - If the tunnel-all-dns option is set to yes, tunnel all DNS queries via the VPN. This is the default when a default route is set. - - yes no - - - yes - Enable tunneling of all DNS traffic - - - no - Disable tunneling of all DNS traffic - - - (yes|no) - - - no - - - - - - - - diff --git a/interface-definitions/vpn-pptp.xml.in b/interface-definitions/vpn-pptp.xml.in deleted file mode 100644 index 7bb8db798..000000000 --- a/interface-definitions/vpn-pptp.xml.in +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - - Point to Point Tunneling Protocol (PPTP) Virtual Private Network (VPN) - 901 - - - - - Remote access PPTP VPN - - - #include - #include - - 1436 - - - - External IP address to which VPN clients will connect - - - - - - #include - #include - #include - #include - - - Authentication for remote access PPTP VPN - - - - - Authentication protocol for remote access peer PPTP VPN - - pap chap mschap mschap-v2 - - - pap - Require the peer to authenticate itself using PAP [Password Authentication Protocol]. - - - chap - Require the peer to authenticate itself using CHAP [Challenge Handshake Authentication Protocol]. - - - mschap - Require the peer to authenticate itself using CHAP [Challenge Handshake Authentication Protocol]. - - - mschap-v2 - Require the peer to authenticate itself using MS-CHAPv2 [Microsoft Challenge Handshake Authentication Protocol, Version 2]. - - - (pap|chap|mschap|mschap-v2) - - - mschap-v2 - - - - Specifies mppe negotioation preference. (default require mppe 128-bit stateless - - deny - deny mppe - - - prefer - ask client for mppe, if it rejects do not fail - - - require - ask client for mppe, if it rejects drop connection - - - (deny|prefer|require) - - - deny prefer require - - - prefer - - #include - - - Local user authentication for remote access PPTP VPN - - - - - User name for authentication - - - #include - - - Password for authentication - - - - - Static client IP address - - * - - - - - - - - #include - - - #include - #include - - - - 30 - - - 30 - - - - - - #include - - - - - - - diff --git a/interface-definitions/vpn-sstp.xml.in b/interface-definitions/vpn-sstp.xml.in deleted file mode 100644 index a1b69f990..000000000 --- a/interface-definitions/vpn-sstp.xml.in +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - Secure Socket Tunneling Protocol (SSTP) server - 901 - - - - - Authentication for remote access SSTP Server - - - #include - #include - #include - #include - #include - - - #include - - - - - #include - #include - #include - #include - #include - #include - #include - - 443 - - #include - - - PPP (Point-to-Point Protocol) settings - - - #include - #include - #include - #include - #include - - - - - SSL Certificate, SSL Key and CA - - - #include - #include - - - - - - - diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in new file mode 100644 index 000000000..1847401b5 --- /dev/null +++ b/interface-definitions/vpn_ipsec.xml.in @@ -0,0 +1,1194 @@ + + + + + Virtual Private Network (VPN) + + + + + VPN IP security (IPsec) parameters + 901 + + + + + Authentication + + + + + Pre-shared key name + + + #include + + + ID for authentication + + txt + ID used for authentication + + + + + + + IKE pre-shared secret key + + txt + IKE pre-shared secret key + + + + + + + + + + Disable requirement for unique IDs in the Security Database + + + + + + Encapsulating Security Payload (ESP) group name + + + + + Enable ESP compression + + + + + + Security Association time to expire + + u32:30-86400 + SA lifetime in seconds + + + + + + 3600 + + + + Security Association byte count to expire + + u32:1024-26843545600000 + SA life in bytes + + + + + + + + + Security Association packet count to expire + + u32:1000-26843545600000 + SA life in packets + + + + + + + + + ESP mode + + tunnel transport + + + tunnel + Tunnel mode + + + transport + Transport mode + + + (tunnel|transport) + + + tunnel + + + + ESP Perfect Forward Secrecy + + enable dh-group1 dh-group2 dh-group5 dh-group14 dh-group15 dh-group16 dh-group17 dh-group18 dh-group19 dh-group20 dh-group21 dh-group22 dh-group23 dh-group24 dh-group25 dh-group26 dh-group27 dh-group28 dh-group29 dh-group30 dh-group31 dh-group32 disable + + + enable + Inherit Diffie-Hellman group from the IKE group + + + dh-group1 + Use Diffie-Hellman group 1 (modp768) + + + dh-group2 + Use Diffie-Hellman group 2 (modp1024) + + + dh-group5 + Use Diffie-Hellman group 5 (modp1536) + + + dh-group14 + Use Diffie-Hellman group 14 (modp2048) + + + dh-group15 + Use Diffie-Hellman group 15 (modp3072) + + + dh-group16 + Use Diffie-Hellman group 16 (modp4096) + + + dh-group17 + Use Diffie-Hellman group 17 (modp6144) + + + dh-group18 + Use Diffie-Hellman group 18 (modp8192) + + + dh-group19 + Use Diffie-Hellman group 19 (ecp256) + + + dh-group20 + Use Diffie-Hellman group 20 (ecp384) + + + dh-group21 + Use Diffie-Hellman group 21 (ecp521) + + + dh-group22 + Use Diffie-Hellman group 22 (modp1024s160) + + + dh-group23 + Use Diffie-Hellman group 23 (modp2048s224) + + + dh-group24 + Use Diffie-Hellman group 24 (modp2048s256) + + + dh-group25 + Use Diffie-Hellman group 25 (ecp192) + + + dh-group26 + Use Diffie-Hellman group 26 (ecp224) + + + dh-group27 + Use Diffie-Hellman group 27 (ecp224bp) + + + dh-group28 + Use Diffie-Hellman group 28 (ecp256bp) + + + dh-group29 + Use Diffie-Hellman group 29 (ecp384bp) + + + dh-group30 + Use Diffie-Hellman group 30 (ecp512bp) + + + dh-group31 + Use Diffie-Hellman group 31 (curve25519) + + + dh-group32 + Use Diffie-Hellman group 32 (curve448) + + + disable + Disable PFS + + + (enable|dh-group1|dh-group2|dh-group5|dh-group14|dh-group15|dh-group16|dh-group17|dh-group18|dh-group19|dh-group20|dh-group21|dh-group22|dh-group23|dh-group24|dh-group25|dh-group26|dh-group27|dh-group28|dh-group29|dh-group30|dh-group31|dh-group32|disable) + + + enable + + + + ESP group proposal + + u32:1-65535 + ESP group proposal number + + + + #include + #include + + + + + + + Internet Key Exchange (IKE) group name + + + + + Action to take if a child SA is unexpectedly closed + + none hold restart + + + none + Do nothing + + + hold + Attempt to re-negotiate when matching traffic is seen + + + restart + Attempt to re-negotiate the connection immediately + + + (none|hold|restart) + + + none + + + + Dead Peer Detection (DPD) + + + + + Keep-alive failure action + + hold clear restart + + + hold + Attempt to re-negotiate the connection when matching traffic is seen + + + clear + Remove the connection immediately + + + restart + Attempt to re-negotiate the connection immediately + + + (hold|clear|restart) + + + clear + + + + Keep-alive interval + + u32:2-86400 + Keep-alive interval in seconds + + + + + + 30 + + + + Dead Peer Detection keep-alive timeout (IKEv1 only) + + u32:2-86400 + Keep-alive timeout in seconds + + + + + + 120 + + + + + + Re-authentication of the remote peer during an IKE re-key (IKEv2 only) + + + + + + IKE version + + ikev1 ikev2 + + + ikev1 + Use IKEv1 for key exchange + + + ikev2 + Use IKEv2 for key exchange + + + (ikev1|ikev2) + + + + + + IKE lifetime + + u32:0-86400 + IKE lifetime in seconds + + + + + + 28800 + + + + Disable MOBIKE Support (IKEv2 only) + + + + + + IKEv1 phase 1 mode + + main aggressive + + + main + Use the main mode (recommended) + + + aggressive + Use the aggressive mode (insecure, not recommended) + + + (main|aggressive) + + + main + + + + IKE proposal + + u32:1-65535 + IKE group proposal + + + + + + dh-grouphelp + + 1 2 5 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 + + + 1 + Diffie-Hellman group 1 (modp768) + + + 2 + Diffie-Hellman group 2 (modp1024) + + + 5 + Diffie-Hellman group 5 (modp1536) + + + 14 + Diffie-Hellman group 14 (modp2048) + + + 15 + Diffie-Hellman group 15 (modp3072) + + + 16 + Diffie-Hellman group 16 (modp4096) + + + 17 + Diffie-Hellman group 17 (modp6144) + + + 18 + Diffie-Hellman group 18 (modp8192) + + + 19 + Diffie-Hellman group 19 (ecp256) + + + 20 + Diffie-Hellman group 20 (ecp384) + + + 21 + Diffie-Hellman group 21 (ecp521) + + + 22 + Diffie-Hellman group 22 (modp1024s160) + + + 23 + Diffie-Hellman group 23 (modp2048s224) + + + 24 + Diffie-Hellman group 24 (modp2048s256) + + + 25 + Diffie-Hellman group 25 (ecp192) + + + 26 + Diffie-Hellman group 26 (ecp224) + + + 27 + Diffie-Hellman group 27 (ecp224bp) + + + 28 + Diffie-Hellman group 28 (ecp256bp) + + + 29 + Diffie-Hellman group 29 (ecp384bp) + + + 30 + Diffie-Hellman group 30 (ecp512bp) + + + 31 + Diffie-Hellman group 31 (curve25519) + + + 32 + Diffie-Hellman group 32 (curve448) + + + (1|2|5|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32) + + + 2 + + + + Pseudo-Random Functions + + prfmd5 prfsha1 prfaesxcbc prfaescmac prfsha256 prfsha384 prfsha512 + + + prfmd5 + MD5 PRF + + + prfsha1 + SHA1 PRF + + + prfaesxcbc + AES XCBC PRF + + + prfaescmac + AES CMAC PRF + + + prfsha256 + SHA2_256 PRF + + + prfsha384 + SHA2_384 PRF + + + prfsha512 + SHA2_512 PRF + + + (prfmd5|prfsha1|prfaesxcbc|prfaescmac|prfsha256|prfsha384|prfsha512) + + + + #include + #include + + + + + #include + + + IPsec logging + + + + + Global IPsec logging Level + + 0 + Very basic auditing logs (e.g., SA up/SA down) + + + 1 + Generic control flow with errors, a good default to see whats going on + + + 2 + More detailed debugging control flow + + + + + + 0 + + + + Subsystem logging levels + + dmn mgr ike chd job cfg knl net asn enc lib esp tls tnc imc imv pts any + + + dmn + Main daemon setup/cleanup/signal handling + + + mgr + IKE_SA manager, handling synchronization for IKE_SA access + + + ike + IKE_SA/ISAKMP SA + + + chd + CHILD_SA/IPsec SA + + + job + Jobs queuing/processing and thread pool management + + + cfg + Configuration management and plugins + + + knl + IPsec/Networking kernel interface + + + net + IKE network communication + + + asn + Low-level encoding/decoding (ASN.1, X.509 etc.) + + + enc + Packet encoding/decoding encryption/decryption operations + + + lib + libstrongswan library messages + + + esp + libipsec library messages + + + tls + libtls library messages + + + tnc + Trusted Network Connect + + + imc + Integrity Measurement Collector + + + imv + Integrity Measurement Verifier + + + pts + Platform Trust Service + + + any + Any subsystem + + + (dmn|mgr|ike|chd|job|cfg|knl|net|asn|enc|lib|esp|tls|tnc|imc|imv|pts|any) + + + + + + + + + Global IPsec settings + + + + + Do not automatically install routes to remote networks + + + + + + Allow FlexVPN vendor ID payload (IKEv2 only) + + + + #include + + + Allow install virtual-ip addresses + + + + + + + + VPN IPsec profile + + txt + Profile name + + + [a-zA-Z][0-9a-zA-Z_-]+ + + Profile name must be alphanumeric and can contain hyphen(s) and underscore(s) + + + #include + + + Authentication + + + + + Authentication mode + + pre-shared-secret + + + pre-shared-secret + Use a pre-shared secret key + + + + #include + + + + + DMVPN tunnel configuration + + + + + Tunnel interface associated with this profile + + interfaces tunnel + + + txt + Associated interface to this profile + + + + + + + #include + #include + + + + + IKEv2 remote access VPN + + + + + IKEv2 VPN connection name + + txt + Connection name + + + [a-zA-Z][0-9a-zA-Z_-]+ + + Profile name must be alphanumeric and can contain hyphen(s) and underscore(s) + + + + + Authentication for remote access + + + #include + #include + + + Client authentication mode + + x509 eap-tls eap-mschapv2 eap-radius + + + x509 + Use IPsec x.509 certificate authentication + + + eap-tls + Use EAP-TLS authentication + + + eap-mschapv2 + Use EAP-MSCHAPv2 authentication + + + eap-radius + Use EAP-RADIUS authentication + + + (x509|eap-tls|eap-mschapv2|eap-radius) + + + eap-mschapv2 + + #include + + + Server authentication mode + + pre-shared-secret x509 + + + pre-shared-secret + Use a pre-shared secret key + + + x509 + Use x.509 certificate + + + (pre-shared-secret|x509) + + + x509 + + #include + + + #include + #include + #include + #include + #include + #include + + + Timeout to close connection if no data is transmitted + + u32:0 + Disable inactivity checks + + + u32:1-86400 + Timeout in seconds + + + + + + 28800 + + + + IP address pool + + vpn ipsec remote-access pool + dhcp radius + + + txt + Predefined IP pool name + + + dhcp + Forward requests for virtual IP addresses to a DHCP server + + + radius + Forward requests for virtual IP addresses to a RADIUS server + + + + + + + Connection uniqueness enforcement policy + + never keep replace + + + never + Never enforce connection uniqueness + + + keep + Reject new connection attempts if the same user already has an active connection + + + replace + Delete any existing connection if a new one for the same user gets established + + + (never|keep|replace) + + + + + + + + DHCP pool options for remote access + + + #include + + + DHCP server address + + ipv4 + DHCP server IPv4 address + + + + + + + + + + + IP address pool for remote access users + + + + + Local IPv4 or IPv6 pool prefix exclusions + + ipv4net + Local IPv4 pool prefix exclusion + + + ipv6net + Local IPv6 pool prefix exclusion + + + + + + + + + + + Local IPv4 or IPv6 pool prefix + + ipv4net + Local IPv4 pool prefix + + + ipv6net + Local IPv6 pool prefix + + + + + + + + #include + + + #include + + + #include + #include + + + #include + + + + + + + + + Site-to-site VPN + + + + + Connection name of the peer + + txt + Connection name of the peer + + + [-_a-zA-Z0-9|@]+ + + Peer connection name must be alphanumeric and can contain hyphen and underscores + + + #include + + + Peer authentication + + + #include + #include + #include + + + Authentication mode + + pre-shared-secret rsa x509 + + + pre-shared-secret + Use pre-shared secret key + + + rsa + Use RSA key + + + x509 + Use x.509 certificate + + + (pre-shared-secret|rsa|x509) + + + + + + ID for remote authentication + + txt + ID used for peer authentication + + + %any + + + + Use certificate common name as ID + + + + + + + + Connection type + + initiate respond none + + + initiate + Bring the connection up immediately + + + respond + Wait for the peer to initiate the connection + + + none + Load the connection only + + + (initiate|respond|none) + + + + + + Defult ESP group name + + vpn ipsec esp-group + + + + #include + #include + + + Force UDP encapsulation + + + + #include + + + Re-authentication of the remote peer during an IKE re-key (IKEv2 only) + + yes no inherit + + + yes + Enable remote host re-autentication during an IKE re-key. Currently broken due to a strong swan bug + + + no + Disable remote host re-authenticaton during an IKE re-key. + + + inherit + Inherit the reauth configuration form your IKE-group + + + (yes|no|inherit) + + + + #include + #include + + + Peer tunnel + + u32 + Peer tunnel + + + + #include + #include + #include + #include + + + Priority for IPsec policy (lowest value more preferable) + + u32:1-100 + Priority for IPsec policy (lowest value more preferable) + + + + + + + + + Match remote addresses + + + #include + + + Remote IPv4 or IPv6 prefix + + ipv4net + Remote IPv4 prefix + + + ipv6net + Remote IPv6 prefix + + + + + + + + + + + + + + + Initiator request virtual-address from peer + + ipv4 + Request IPv4 address from peer + + + ipv6 + Request IPv6 address from peer + + + + + + + Virtual tunnel interface + + + + + VTI tunnel interface associated with this configuration + + interfaces vti + + + + #include + + + + + + + + + + + diff --git a/interface-definitions/vpn_l2tp.xml.in b/interface-definitions/vpn_l2tp.xml.in new file mode 100644 index 000000000..3e2d00e6b --- /dev/null +++ b/interface-definitions/vpn_l2tp.xml.in @@ -0,0 +1,163 @@ + + + + + + + L2TP Virtual Private Network (VPN) + 902 + + + + + Remote access L2TP VPN + + + #include + #include + + 1436 + + + + External IP address to which VPN clients will connect + + + + + + #include + #include + + + L2TP Network Server (LNS) + + + + + Tunnel password used to authenticate the client (LAC) + + + + + Sent to the client (LAC) in the Host-Name attribute + + #include + + Host-name must be alphanumeric and can contain hyphens + + + + + + + Disable Compression Control Protocol (CCP) + + + + + + Internet Protocol Security (IPsec) for remote access L2TP VPN + + + + + IPsec authentication settings + + + + + Authentication mode for IPsec + + pre-shared-secret + Use pre-shared secret for IPsec authentication + + + x509 + Use X.509 certificate for IPsec authentication + + + (pre-shared-secret|x509) + + + pre-shared-secret x509 + + + + #include + #include + + + + + IKE lifetime + + u32:30-86400 + IKE lifetime in seconds + + + + + + 3600 + + + + ESP lifetime + + u32:30-86400 + IKE lifetime in seconds + + + + + + 3600 + + #include + #include + + + #include + #include + #include + #include + #include + + + Authentication for remote access L2TP VPN + + + #include + #include + #include + #include + #include + + + #include + + + + + + + Advanced protocol options + + + #include + #include + #include + #include + #include + #include + + + #include + + + + + + + diff --git a/interface-definitions/vpn_openconnect.xml.in b/interface-definitions/vpn_openconnect.xml.in new file mode 100644 index 000000000..736084f8b --- /dev/null +++ b/interface-definitions/vpn_openconnect.xml.in @@ -0,0 +1,392 @@ + + + + + + + SSL VPN OpenConnect, AnyConnect compatible server + 901 + + + + + Accounting for users OpenConnect VPN Sessions + + + + + Accounting mode used by this server + + + + + Use RADIUS server for accounting + + + + + + #include + + + + + Authentication for remote access SSL VPN Server + + + + + Authentication mode used by this server + + + + + Use local username/password configuration (OTP supported) + + password + Password-only local authentication + + + otp + OTP-only local authentication + + + password-otp + Password (first) + OTP local authentication + + + (password|otp|password-otp) + + Invalid authentication mode. Must be one of: password, otp or password-otp + + otp password password-otp + + + + + + Use RADIUS server for user autentication + + + + + + + + Include configuration file by username or RADIUS group attribute + + + #include + + + Select per user or per group configuration file - ignored if authentication group is configured + + user group + + + user + Match configuration file on username + + + group + Match RADIUS response class attribute as file name + + + (user|group) + + Invalid mode, must be either user or group + + + + + Directory to containing configuration files + + path + Path to configuration directory, must be under /config/auth + + + + + + + + + Default configuration if discrete config could not be found + + filename + Default configuration filename, must be under /config/auth + + + + + + + + + + + Group that a client is allowed to select (from a list). Maps to RADIUS Class attribute. + + txt + Group string. The group may be followed by a user-friendly name in brackets: group1[First Group] + + + + + #include + + + + + + + 2FA OTP authentication parameters + + + + + Token Key Secret key for the token algorithm (see RFC 4226) + + txt + OTP key in hex-encoded format + + + [a-fA-F0-9]{20,10000} + + Key name must only include hex characters and be at least 20 characters long + + + + + Number of digits in OTP code + + u32:6-8 + Number of digits in OTP code + + + + + Number of digits in OTP code must be between 6 and 8 + + 6 + + + + Time tokens interval in seconds + + u32:5-86400 + Time tokens interval in seconds. + + + + + Time token interval must be between 5 and 86400 seconds + + 30 + + + + Token type + + hotp-time + Time-based OTP algorithm + + + hotp-event + Event-based OTP algorithm + + + (hotp-time|hotp-event) + + + hotp-time hotp-event + + + hotp-time + + + + + + + + #include + + + #include + + + If the groupconfig option is set, then config-per-user will be overriden, and all configuration will be read from RADIUS. + + + + + + + #include + + 0.0.0.0 + + + + Specify custom ports to use for client connections + + + + + tcp port number to accept connections + + u32:1-65535 + Numeric IP port + + + + + + 443 + + + + udp port number to accept connections + + u32:1-65535 + Numeric IP port + + + + + + 443 + + + + + + Enable HTTP security headers + + + + + + SSL Certificate, SSL Key and CA + + + #include + #include + + + + + Network settings + + + + + Route to be pushed to the client + + ipv4net + IPv4 network and prefix length + + + ipv6net + IPv6 network and prefix length + + + + + + + + + + Client IP pools settings + + + + + Client IP subnet (CIDR notation) + + ipv4net + IPv4 address and prefix length + + + + + Not a valid CIDR formatted prefix + + + + + + + Pool of client IPv6 addresses + + + + + Pool of addresses used to assign to clients + + ipv6net + IPv6 address and prefix length + + + + + + + + + Prefix length used for individual client + + u32:48-128 + Client prefix length + + + + + + 64 + + + + #include + + + Domains over which the provided DNS should be used + + txt + Client prefix length + + + + + + + + + + If the tunnel-all-dns option is set to yes, tunnel all DNS queries via the VPN. This is the default when a default route is set. + + yes no + + + yes + Enable tunneling of all DNS traffic + + + no + Disable tunneling of all DNS traffic + + + (yes|no) + + + no + + + + + + + + diff --git a/interface-definitions/vpn_pptp.xml.in b/interface-definitions/vpn_pptp.xml.in new file mode 100644 index 000000000..7bb8db798 --- /dev/null +++ b/interface-definitions/vpn_pptp.xml.in @@ -0,0 +1,143 @@ + + + + + + + Point to Point Tunneling Protocol (PPTP) Virtual Private Network (VPN) + 901 + + + + + Remote access PPTP VPN + + + #include + #include + + 1436 + + + + External IP address to which VPN clients will connect + + + + + + #include + #include + #include + #include + + + Authentication for remote access PPTP VPN + + + + + Authentication protocol for remote access peer PPTP VPN + + pap chap mschap mschap-v2 + + + pap + Require the peer to authenticate itself using PAP [Password Authentication Protocol]. + + + chap + Require the peer to authenticate itself using CHAP [Challenge Handshake Authentication Protocol]. + + + mschap + Require the peer to authenticate itself using CHAP [Challenge Handshake Authentication Protocol]. + + + mschap-v2 + Require the peer to authenticate itself using MS-CHAPv2 [Microsoft Challenge Handshake Authentication Protocol, Version 2]. + + + (pap|chap|mschap|mschap-v2) + + + mschap-v2 + + + + Specifies mppe negotioation preference. (default require mppe 128-bit stateless + + deny + deny mppe + + + prefer + ask client for mppe, if it rejects do not fail + + + require + ask client for mppe, if it rejects drop connection + + + (deny|prefer|require) + + + deny prefer require + + + prefer + + #include + + + Local user authentication for remote access PPTP VPN + + + + + User name for authentication + + + #include + + + Password for authentication + + + + + Static client IP address + + * + + + + + + + + #include + + + #include + #include + + + + 30 + + + 30 + + + + + + #include + + + + + + + diff --git a/interface-definitions/vpn_sstp.xml.in b/interface-definitions/vpn_sstp.xml.in new file mode 100644 index 000000000..a1b69f990 --- /dev/null +++ b/interface-definitions/vpn_sstp.xml.in @@ -0,0 +1,64 @@ + + + + + + + Secure Socket Tunneling Protocol (SSTP) server + 901 + + + + + Authentication for remote access SSTP Server + + + #include + #include + #include + #include + #include + + + #include + + + + + #include + #include + #include + #include + #include + #include + #include + + 443 + + #include + + + PPP (Point-to-Point Protocol) settings + + + #include + #include + #include + #include + #include + + + + + SSL Certificate, SSL Key and CA + + + #include + #include + + + + + + + diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 23b6daa3a..a2c4aad50 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -112,7 +112,7 @@ class VXLANIf(Interface): # interface is always A/D down. It needs to be enabled explicitly self.set_admin_state('down') - # VXLAN tunnel is always recreated on any change - see interfaces-vxlan.py + # VXLAN tunnel is always recreated on any change - see interfaces_vxlan.py if remote_list: for remote in remote_list: cmd = f'bridge fdb append to 00:00:00:00:00:00 dst {remote} ' \ diff --git a/smoketest/scripts/cli/test_ha_virtual_server.py b/smoketest/scripts/cli/test_ha_virtual_server.py deleted file mode 100755 index 51ccfa4df..000000000 --- a/smoketest/scripts/cli/test_ha_virtual_server.py +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2021-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 . - -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.configsession import ConfigSessionError -from vyos.ifconfig.vrrp import VRRP -from vyos.utils.process import cmd -from vyos.utils.process import process_named_running -from vyos.utils.file import read_file -from vyos.template import inc_ip - -PROCESS_NAME = 'keepalived' -KEEPALIVED_CONF = VRRP.location['config'] -base_path = ['high-availability'] -vrrp_interface = 'eth1' - -class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase): - def tearDown(self): - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - self.cli_delete(['interfaces', 'ethernet', vrrp_interface, 'address']) - self.cli_delete(base_path) - self.cli_commit() - - # Process must be terminated after deleting the config - self.assertFalse(process_named_running(PROCESS_NAME)) - - def test_01_ha_virtual_server(self): - algo = 'least-connection' - delay = '10' - method = 'nat' - persistence_timeout = '600' - vs = 'serv-one' - vip = '203.0.113.111' - vport = '2222' - rservers = ['192.0.2.21', '192.0.2.22', '192.0.2.23'] - rport = '22' - proto = 'tcp' - connection_timeout = '30' - - vserver_base = base_path + ['virtual-server'] - - self.cli_set(vserver_base + [vs, 'address', vip]) - self.cli_set(vserver_base + [vs, 'algorithm', algo]) - self.cli_set(vserver_base + [vs, 'delay-loop', delay]) - self.cli_set(vserver_base + [vs, 'forward-method', method]) - self.cli_set(vserver_base + [vs, 'persistence-timeout', persistence_timeout]) - self.cli_set(vserver_base + [vs, 'port', vport]) - self.cli_set(vserver_base + [vs, 'protocol', proto]) - for rs in rservers: - self.cli_set(vserver_base + [vs, 'real-server', rs, 'connection-timeout', connection_timeout]) - self.cli_set(vserver_base + [vs, 'real-server', rs, 'port', rport]) - - # commit changes - self.cli_commit() - - config = read_file(KEEPALIVED_CONF) - - self.assertIn(f'virtual_server {vip} {vport}', config) - self.assertIn(f'delay_loop {delay}', config) - self.assertIn(f'lb_algo lc', config) - self.assertIn(f'lb_kind {method.upper()}', config) - self.assertIn(f'persistence_timeout {persistence_timeout}', config) - self.assertIn(f'protocol {proto.upper()}', config) - for rs in rservers: - self.assertIn(f'real_server {rs} {rport}', config) - self.assertIn(f'{proto.upper()}_CHECK', config) - self.assertIn(f'connect_timeout {connection_timeout}', config) - - def test_02_ha_virtual_server_and_vrrp(self): - algo = 'least-connection' - delay = '15' - method = 'nat' - persistence_timeout = '300' - vs = 'serv-two' - vip = '203.0.113.222' - vport = '22322' - rservers = ['192.0.2.11', '192.0.2.12'] - rport = '222' - proto = 'tcp' - connection_timeout = '23' - group = 'VyOS' - vrid = '99' - - vrrp_base = base_path + ['vrrp', 'group'] - vserver_base = base_path + ['virtual-server'] - - self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'address', '203.0.113.10/24']) - - # VRRP config - self.cli_set(vrrp_base + [group, 'description', group]) - self.cli_set(vrrp_base + [group, 'interface', vrrp_interface]) - self.cli_set(vrrp_base + [group, 'address', vip + '/24']) - self.cli_set(vrrp_base + [group, 'vrid', vrid]) - - # Virtual-server config - self.cli_set(vserver_base + [vs, 'address', vip]) - self.cli_set(vserver_base + [vs, 'algorithm', algo]) - self.cli_set(vserver_base + [vs, 'delay-loop', delay]) - self.cli_set(vserver_base + [vs, 'forward-method', method]) - self.cli_set(vserver_base + [vs, 'persistence-timeout', persistence_timeout]) - self.cli_set(vserver_base + [vs, 'port', vport]) - self.cli_set(vserver_base + [vs, 'protocol', proto]) - for rs in rservers: - self.cli_set(vserver_base + [vs, 'real-server', rs, 'connection-timeout', connection_timeout]) - self.cli_set(vserver_base + [vs, 'real-server', rs, 'port', rport]) - - # commit changes - self.cli_commit() - - config = read_file(KEEPALIVED_CONF) - - # Keepalived vrrp - self.assertIn(f'# {group}', config) - self.assertIn(f'interface {vrrp_interface}', config) - self.assertIn(f'virtual_router_id {vrid}', config) - self.assertIn(f'priority 100', config) # default value - self.assertIn(f'advert_int 1', config) # default value - self.assertIn(f'preempt_delay 0', config) # default value - - # Keepalived virtual-server - self.assertIn(f'virtual_server {vip} {vport}', config) - self.assertIn(f'delay_loop {delay}', config) - self.assertIn(f'lb_algo lc', config) - self.assertIn(f'lb_kind {method.upper()}', config) - self.assertIn(f'persistence_timeout {persistence_timeout}', config) - self.assertIn(f'protocol {proto.upper()}', config) - for rs in rservers: - self.assertIn(f'real_server {rs} {rport}', config) - self.assertIn(f'{proto.upper()}_CHECK', config) - self.assertIn(f'connect_timeout {connection_timeout}', config) - - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_ha_vrrp.py b/smoketest/scripts/cli/test_ha_vrrp.py deleted file mode 100755 index 98259d830..000000000 --- a/smoketest/scripts/cli/test_ha_vrrp.py +++ /dev/null @@ -1,241 +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 . - -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.configsession import ConfigSessionError -from vyos.ifconfig.vrrp import VRRP -from vyos.utils.process import cmd -from vyos.utils.process import process_named_running -from vyos.utils.file import read_file -from vyos.template import inc_ip - -PROCESS_NAME = 'keepalived' -KEEPALIVED_CONF = VRRP.location['config'] -base_path = ['high-availability'] - -vrrp_interface = 'eth1' -groups = ['VLAN77', 'VLAN78', 'VLAN201'] - -def getConfig(string, end='}'): - command = f'cat {KEEPALIVED_CONF} | sed -n "/^{string}/,/^{end}/p"' - out = cmd(command) - return out - -class TestVRRP(VyOSUnitTestSHIM.TestCase): - def tearDown(self): - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - for group in groups: - vlan_id = group.lstrip('VLAN') - self.cli_delete(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id]) - - self.cli_delete(base_path) - self.cli_commit() - - # Process must be terminated after deleting the config - self.assertFalse(process_named_running(PROCESS_NAME)) - - def test_01_default_values(self): - for group in groups: - vlan_id = group.lstrip('VLAN') - vip = f'100.64.{vlan_id}.1/24' - group_base = base_path + ['vrrp', 'group', group] - - self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) - - self.cli_set(group_base + ['description', group]) - self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) - self.cli_set(group_base + ['address', vip]) - self.cli_set(group_base + ['vrid', vlan_id]) - - # commit changes - self.cli_commit() - - for group in groups: - vlan_id = group.lstrip('VLAN') - vip = f'100.64.{vlan_id}.1/24' - - config = getConfig(f'vrrp_instance {group}') - - self.assertIn(f'# {group}', config) - self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) - self.assertIn(f'virtual_router_id {vlan_id}', config) - self.assertIn(f'priority 100', config) # default value - self.assertIn(f'advert_int 1', config) # default value - self.assertIn(f'preempt_delay 0', config) # default value - self.assertNotIn(f'use_vmac', config) - self.assertIn(f' {vip}', config) - - def test_02_simple_options(self): - 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' - vrrp_version = '3' - - 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]]) - - self.cli_set(group_base + ['description', group]) - self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) - self.cli_set(group_base + ['address', vip]) - self.cli_set(group_base + ['vrid', vlan_id]) - - self.cli_set(group_base + ['advertise-interval', advertise_interval]) - self.cli_set(group_base + ['priority', priority]) - self.cli_set(group_base + ['preempt-delay', preempt_delay]) - - self.cli_set(group_base + ['rfc3768-compatibility']) - - # Authentication - 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}']) - self.cli_set(global_param_base + ['version', vrrp_version]) - - # 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) - self.assertIn(f'vrrp_version {vrrp_version}', config) - - for group in groups: - vlan_id = group.lstrip('VLAN') - vip = f'100.64.{vlan_id}.1/24' - - config = getConfig(f'vrrp_instance {group}') - self.assertIn(f'# {group}', config) - self.assertIn(f'state BACKUP', config) - self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) - self.assertIn(f'virtual_router_id {vlan_id}', config) - self.assertIn(f'priority {priority}', config) - self.assertIn(f'advert_int {advertise_interval}', config) - self.assertIn(f'preempt_delay {preempt_delay}', config) - self.assertIn(f'use_vmac {vrrp_interface}.{vlan_id}v{vlan_id}', config) - self.assertIn(f' {vip}', config) - - # Authentication - 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' - - for group in groups: - vlan_id = group.lstrip('VLAN') - vip = f'100.64.{vlan_id}.1/24' - group_base = base_path + ['vrrp', 'group', group] - - self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) - - self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) - self.cli_set(group_base + ['address', vip]) - self.cli_set(group_base + ['vrid', vlan_id]) - - self.cli_set(base_path + ['vrrp', 'sync-group', sync_group, 'member', group]) - - # commit changes - self.cli_commit() - - for group in groups: - vlan_id = group.lstrip('VLAN') - vip = f'100.64.{vlan_id}.1/24' - config = getConfig(f'vrrp_instance {group}') - - self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) - self.assertIn(f'virtual_router_id {vlan_id}', config) - self.assertNotIn(f'use_vmac', config) - self.assertIn(f' {vip}', config) - - config = getConfig(f'vrrp_sync_group {sync_group}') - self.assertIn(r'group {', config) - for group in groups: - self.assertIn(f'{group}', config) - - def test_04_exclude_vrrp_interface(self): - group = 'VyOS-WAN' - none_vrrp_interface = 'eth2' - vlan_id = '24' - vip = '100.64.24.1/24' - vip_dev = '192.0.2.2/24' - vrid = '150' - group_base = base_path + ['vrrp', 'group', group] - - self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', '100.64.24.11/24']) - self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) - self.cli_set(group_base + ['address', vip]) - self.cli_set(group_base + ['address', vip_dev, 'interface', none_vrrp_interface]) - self.cli_set(group_base + ['track', 'exclude-vrrp-interface']) - self.cli_set(group_base + ['track', 'interface', none_vrrp_interface]) - self.cli_set(group_base + ['vrid', vrid]) - - # commit changes - self.cli_commit() - - config = getConfig(f'vrrp_instance {group}') - - self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) - self.assertIn(f'virtual_router_id {vrid}', config) - self.assertIn(f'dont_track_primary', config) - self.assertIn(f' {vip}', config) - self.assertIn(f' {vip_dev} dev {none_vrrp_interface}', config) - self.assertIn(f'track_interface', config) - self.assertIn(f' {none_vrrp_interface}', config) - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_high-availability_virtual-server.py b/smoketest/scripts/cli/test_high-availability_virtual-server.py new file mode 100755 index 000000000..51ccfa4df --- /dev/null +++ b/smoketest/scripts/cli/test_high-availability_virtual-server.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-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 . + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig.vrrp import VRRP +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.template import inc_ip + +PROCESS_NAME = 'keepalived' +KEEPALIVED_CONF = VRRP.location['config'] +base_path = ['high-availability'] +vrrp_interface = 'eth1' + +class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(['interfaces', 'ethernet', vrrp_interface, 'address']) + self.cli_delete(base_path) + self.cli_commit() + + # Process must be terminated after deleting the config + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_ha_virtual_server(self): + algo = 'least-connection' + delay = '10' + method = 'nat' + persistence_timeout = '600' + vs = 'serv-one' + vip = '203.0.113.111' + vport = '2222' + rservers = ['192.0.2.21', '192.0.2.22', '192.0.2.23'] + rport = '22' + proto = 'tcp' + connection_timeout = '30' + + vserver_base = base_path + ['virtual-server'] + + self.cli_set(vserver_base + [vs, 'address', vip]) + self.cli_set(vserver_base + [vs, 'algorithm', algo]) + self.cli_set(vserver_base + [vs, 'delay-loop', delay]) + self.cli_set(vserver_base + [vs, 'forward-method', method]) + self.cli_set(vserver_base + [vs, 'persistence-timeout', persistence_timeout]) + self.cli_set(vserver_base + [vs, 'port', vport]) + self.cli_set(vserver_base + [vs, 'protocol', proto]) + for rs in rservers: + self.cli_set(vserver_base + [vs, 'real-server', rs, 'connection-timeout', connection_timeout]) + self.cli_set(vserver_base + [vs, 'real-server', rs, 'port', rport]) + + # commit changes + self.cli_commit() + + config = read_file(KEEPALIVED_CONF) + + self.assertIn(f'virtual_server {vip} {vport}', config) + self.assertIn(f'delay_loop {delay}', config) + self.assertIn(f'lb_algo lc', config) + self.assertIn(f'lb_kind {method.upper()}', config) + self.assertIn(f'persistence_timeout {persistence_timeout}', config) + self.assertIn(f'protocol {proto.upper()}', config) + for rs in rservers: + self.assertIn(f'real_server {rs} {rport}', config) + self.assertIn(f'{proto.upper()}_CHECK', config) + self.assertIn(f'connect_timeout {connection_timeout}', config) + + def test_02_ha_virtual_server_and_vrrp(self): + algo = 'least-connection' + delay = '15' + method = 'nat' + persistence_timeout = '300' + vs = 'serv-two' + vip = '203.0.113.222' + vport = '22322' + rservers = ['192.0.2.11', '192.0.2.12'] + rport = '222' + proto = 'tcp' + connection_timeout = '23' + group = 'VyOS' + vrid = '99' + + vrrp_base = base_path + ['vrrp', 'group'] + vserver_base = base_path + ['virtual-server'] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'address', '203.0.113.10/24']) + + # VRRP config + self.cli_set(vrrp_base + [group, 'description', group]) + self.cli_set(vrrp_base + [group, 'interface', vrrp_interface]) + self.cli_set(vrrp_base + [group, 'address', vip + '/24']) + self.cli_set(vrrp_base + [group, 'vrid', vrid]) + + # Virtual-server config + self.cli_set(vserver_base + [vs, 'address', vip]) + self.cli_set(vserver_base + [vs, 'algorithm', algo]) + self.cli_set(vserver_base + [vs, 'delay-loop', delay]) + self.cli_set(vserver_base + [vs, 'forward-method', method]) + self.cli_set(vserver_base + [vs, 'persistence-timeout', persistence_timeout]) + self.cli_set(vserver_base + [vs, 'port', vport]) + self.cli_set(vserver_base + [vs, 'protocol', proto]) + for rs in rservers: + self.cli_set(vserver_base + [vs, 'real-server', rs, 'connection-timeout', connection_timeout]) + self.cli_set(vserver_base + [vs, 'real-server', rs, 'port', rport]) + + # commit changes + self.cli_commit() + + config = read_file(KEEPALIVED_CONF) + + # Keepalived vrrp + self.assertIn(f'# {group}', config) + self.assertIn(f'interface {vrrp_interface}', config) + self.assertIn(f'virtual_router_id {vrid}', config) + self.assertIn(f'priority 100', config) # default value + self.assertIn(f'advert_int 1', config) # default value + self.assertIn(f'preempt_delay 0', config) # default value + + # Keepalived virtual-server + self.assertIn(f'virtual_server {vip} {vport}', config) + self.assertIn(f'delay_loop {delay}', config) + self.assertIn(f'lb_algo lc', config) + self.assertIn(f'lb_kind {method.upper()}', config) + self.assertIn(f'persistence_timeout {persistence_timeout}', config) + self.assertIn(f'protocol {proto.upper()}', config) + for rs in rservers: + self.assertIn(f'real_server {rs} {rport}', config) + self.assertIn(f'{proto.upper()}_CHECK', config) + self.assertIn(f'connect_timeout {connection_timeout}', config) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_high-availability_vrrp.py b/smoketest/scripts/cli/test_high-availability_vrrp.py new file mode 100755 index 000000000..98259d830 --- /dev/null +++ b/smoketest/scripts/cli/test_high-availability_vrrp.py @@ -0,0 +1,241 @@ +#!/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 . + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig.vrrp import VRRP +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.template import inc_ip + +PROCESS_NAME = 'keepalived' +KEEPALIVED_CONF = VRRP.location['config'] +base_path = ['high-availability'] + +vrrp_interface = 'eth1' +groups = ['VLAN77', 'VLAN78', 'VLAN201'] + +def getConfig(string, end='}'): + command = f'cat {KEEPALIVED_CONF} | sed -n "/^{string}/,/^{end}/p"' + out = cmd(command) + return out + +class TestVRRP(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + for group in groups: + vlan_id = group.lstrip('VLAN') + self.cli_delete(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id]) + + self.cli_delete(base_path) + self.cli_commit() + + # Process must be terminated after deleting the config + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_default_values(self): + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + group_base = base_path + ['vrrp', 'group', group] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) + + self.cli_set(group_base + ['description', group]) + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['vrid', vlan_id]) + + # commit changes + self.cli_commit() + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + + config = getConfig(f'vrrp_instance {group}') + + self.assertIn(f'# {group}', config) + self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) + self.assertIn(f'virtual_router_id {vlan_id}', config) + self.assertIn(f'priority 100', config) # default value + self.assertIn(f'advert_int 1', config) # default value + self.assertIn(f'preempt_delay 0', config) # default value + self.assertNotIn(f'use_vmac', config) + self.assertIn(f' {vip}', config) + + def test_02_simple_options(self): + 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' + vrrp_version = '3' + + 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]]) + + self.cli_set(group_base + ['description', group]) + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['vrid', vlan_id]) + + self.cli_set(group_base + ['advertise-interval', advertise_interval]) + self.cli_set(group_base + ['priority', priority]) + self.cli_set(group_base + ['preempt-delay', preempt_delay]) + + self.cli_set(group_base + ['rfc3768-compatibility']) + + # Authentication + 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}']) + self.cli_set(global_param_base + ['version', vrrp_version]) + + # 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) + self.assertIn(f'vrrp_version {vrrp_version}', config) + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + + config = getConfig(f'vrrp_instance {group}') + self.assertIn(f'# {group}', config) + self.assertIn(f'state BACKUP', config) + self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) + self.assertIn(f'virtual_router_id {vlan_id}', config) + self.assertIn(f'priority {priority}', config) + self.assertIn(f'advert_int {advertise_interval}', config) + self.assertIn(f'preempt_delay {preempt_delay}', config) + self.assertIn(f'use_vmac {vrrp_interface}.{vlan_id}v{vlan_id}', config) + self.assertIn(f' {vip}', config) + + # Authentication + 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' + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + group_base = base_path + ['vrrp', 'group', group] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) + + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['vrid', vlan_id]) + + self.cli_set(base_path + ['vrrp', 'sync-group', sync_group, 'member', group]) + + # commit changes + self.cli_commit() + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + config = getConfig(f'vrrp_instance {group}') + + self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) + self.assertIn(f'virtual_router_id {vlan_id}', config) + self.assertNotIn(f'use_vmac', config) + self.assertIn(f' {vip}', config) + + config = getConfig(f'vrrp_sync_group {sync_group}') + self.assertIn(r'group {', config) + for group in groups: + self.assertIn(f'{group}', config) + + def test_04_exclude_vrrp_interface(self): + group = 'VyOS-WAN' + none_vrrp_interface = 'eth2' + vlan_id = '24' + vip = '100.64.24.1/24' + vip_dev = '192.0.2.2/24' + vrid = '150' + group_base = base_path + ['vrrp', 'group', group] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', '100.64.24.11/24']) + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['address', vip_dev, 'interface', none_vrrp_interface]) + self.cli_set(group_base + ['track', 'exclude-vrrp-interface']) + self.cli_set(group_base + ['track', 'interface', none_vrrp_interface]) + self.cli_set(group_base + ['vrid', vrid]) + + # commit changes + self.cli_commit() + + config = getConfig(f'vrrp_instance {group}') + + self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) + self.assertIn(f'virtual_router_id {vrid}', config) + self.assertIn(f'dont_track_primary', config) + self.assertIn(f' {vip}', config) + self.assertIn(f' {vip_dev} dev {none_vrrp_interface}', config) + self.assertIn(f'track_interface', config) + self.assertIn(f' {none_vrrp_interface}', config) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_pseudo-ethernet.py b/smoketest/scripts/cli/test_interfaces_pseudo-ethernet.py new file mode 100755 index 000000000..0d6f5bc1f --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_pseudo-ethernet.py @@ -0,0 +1,46 @@ +#!/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 . + +import os +import unittest + +from vyos.ifconfig import Section +from base_interfaces_test import BasicInterfaceTest + +class PEthInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'pseudo-ethernet'] + + cls._options = {} + # we need to filter out VLAN interfaces identified by a dot (.) + # in their name - just in case! + if 'TEST_ETH' in os.environ: + for tmp in os.environ['TEST_ETH'].split(): + cls._options.update({f'p{tmp}' : [f'source-interface {tmp}']}) + + else: + for tmp in Section.interfaces('ethernet'): + if '.' in tmp: + continue + cls._options.update({f'p{tmp}' : [f'source-interface {tmp}']}) + + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(PEthInterfaceTest, cls).setUpClass() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py b/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py deleted file mode 100755 index 0d6f5bc1f..000000000 --- a/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py +++ /dev/null @@ -1,46 +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 . - -import os -import unittest - -from vyos.ifconfig import Section -from base_interfaces_test import BasicInterfaceTest - -class PEthInterfaceTest(BasicInterfaceTest.TestCase): - @classmethod - def setUpClass(cls): - cls._base_path = ['interfaces', 'pseudo-ethernet'] - - cls._options = {} - # we need to filter out VLAN interfaces identified by a dot (.) - # in their name - just in case! - if 'TEST_ETH' in os.environ: - for tmp in os.environ['TEST_ETH'].split(): - cls._options.update({f'p{tmp}' : [f'source-interface {tmp}']}) - - else: - for tmp in Section.interfaces('ethernet'): - if '.' in tmp: - continue - cls._options.update({f'p{tmp}' : [f'source-interface {tmp}']}) - - cls._interfaces = list(cls._options) - # call base-classes classmethod - super(PEthInterfaceTest, cls).setUpClass() - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_virtual-ethernet.py b/smoketest/scripts/cli/test_interfaces_virtual-ethernet.py new file mode 100755 index 000000000..7874589ca --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_virtual-ethernet.py @@ -0,0 +1,62 @@ +#!/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 . + +import unittest + +from netifaces import interfaces + +from vyos.ifconfig import Section +from vyos.utils.process import process_named_running +from base_interfaces_test import BasicInterfaceTest + +class VEthInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'virtual-ethernet'] + cls._options = { + 'veth0': ['peer-name veth1'], + 'veth1': ['peer-name veth0'], + } + + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(VEthInterfaceTest, cls).setUpClass() + + def test_vif_8021q_mtu_limits(self): + self.skipTest('not supported') + + # As we always need a pair of veth interfaces, we can not rely on the base + # class check to determine if there is a dhcp6c or dhclient instance running. + # This test will always fail as there is an instance running on the peer + # interface. + def tearDown(self): + self.cli_delete(self._base_path) + self.cli_commit() + + # Verify that no previously interface remained on the system + for intf in self._interfaces: + self.assertNotIn(intf, interfaces()) + + @classmethod + def tearDownClass(cls): + # No daemon started during tests should remain running + for daemon in ['dhcp6c', 'dhclient']: + cls.assertFalse(cls, process_named_running(daemon)) + + super(VEthInterfaceTest, cls).tearDownClass() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_virtual_ethernet.py b/smoketest/scripts/cli/test_interfaces_virtual_ethernet.py deleted file mode 100755 index 7874589ca..000000000 --- a/smoketest/scripts/cli/test_interfaces_virtual_ethernet.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/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 . - -import unittest - -from netifaces import interfaces - -from vyos.ifconfig import Section -from vyos.utils.process import process_named_running -from base_interfaces_test import BasicInterfaceTest - -class VEthInterfaceTest(BasicInterfaceTest.TestCase): - @classmethod - def setUpClass(cls): - cls._base_path = ['interfaces', 'virtual-ethernet'] - cls._options = { - 'veth0': ['peer-name veth1'], - 'veth1': ['peer-name veth0'], - } - - cls._interfaces = list(cls._options) - # call base-classes classmethod - super(VEthInterfaceTest, cls).setUpClass() - - def test_vif_8021q_mtu_limits(self): - self.skipTest('not supported') - - # As we always need a pair of veth interfaces, we can not rely on the base - # class check to determine if there is a dhcp6c or dhclient instance running. - # This test will always fail as there is an instance running on the peer - # interface. - def tearDown(self): - self.cli_delete(self._base_path) - self.cli_commit() - - # Verify that no previously interface remained on the system - for intf in self._interfaces: - self.assertNotIn(intf, interfaces()) - - @classmethod - def tearDownClass(cls): - # No daemon started during tests should remain running - for daemon in ['dhcp6c', 'dhclient']: - cls.assertFalse(cls, process_named_running(daemon)) - - super(VEthInterfaceTest, cls).tearDownClass() - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py b/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py new file mode 100755 index 000000000..274b97f22 --- /dev/null +++ b/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py @@ -0,0 +1,114 @@ +#!/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 . + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +PROCESS_NAME = 'haproxy' +HAPROXY_CONF = '/run/haproxy/haproxy.cfg' +base_path = ['load-balancing', 'reverse-proxy'] +proxy_interface = 'eth1' + + +class TestLoadBalancingReverseProxy(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(['interfaces', 'ethernet', proxy_interface, 'address']) + self.cli_delete(base_path) + self.cli_commit() + + # Process must be terminated after deleting the config + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_lb_reverse_proxy_domain(self): + domains_bk_first = ['n1.example.com', 'n2.example.com', 'n3.example.com'] + domain_bk_second = 'n5.example.com' + frontend = 'https_front' + front_port = '4433' + bk_server_first = '192.0.2.11' + bk_server_second = '192.0.2.12' + bk_first_name = 'bk-01' + bk_second_name = 'bk-02' + bk_server_port = '9090' + mode = 'http' + rule_ten = '10' + rule_twenty = '20' + send_proxy = 'send-proxy' + max_connections = '1000' + + back_base = base_path + ['backend'] + + self.cli_set(base_path + ['service', frontend, 'mode', mode]) + self.cli_set(base_path + ['service', frontend, 'port', front_port]) + for domain in domains_bk_first: + self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'domain-name', domain]) + self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'set', 'backend', bk_first_name]) + self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'domain-name', domain_bk_second]) + self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'set', 'backend', bk_second_name]) + + self.cli_set(back_base + [bk_first_name, 'mode', mode]) + self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'address', bk_server_first]) + self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'port', bk_server_port]) + self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, send_proxy]) + + self.cli_set(back_base + [bk_second_name, 'mode', mode]) + self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'address', bk_server_second]) + self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'port', bk_server_port]) + self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'backup']) + + self.cli_set(base_path + ['global-parameters', 'max-connections', max_connections]) + + # commit changes + self.cli_commit() + + config = read_file(HAPROXY_CONF) + + # Global + self.assertIn(f'maxconn {max_connections}', config) + + # Frontend + self.assertIn(f'frontend {frontend}', config) + self.assertIn(f'bind :::{front_port} v4v6', config) + self.assertIn(f'mode {mode}', config) + for domain in domains_bk_first: + self.assertIn(f'acl {rule_ten} hdr(host) -i {domain}', config) + self.assertIn(f'use_backend {bk_first_name} if {rule_ten}', config) + self.assertIn(f'acl {rule_twenty} hdr(host) -i {domain_bk_second}', config) + self.assertIn(f'use_backend {bk_second_name} if {rule_twenty}', config) + + # Backend + self.assertIn(f'backend {bk_first_name}', config) + self.assertIn(f'balance roundrobin', config) + self.assertIn(f'option forwardfor', config) + self.assertIn('http-request add-header X-Forwarded-Proto https if { ssl_fc }', config) + self.assertIn(f'mode {mode}', config) + self.assertIn(f'server {bk_first_name} {bk_server_first}:{bk_server_port} send-proxy', config) + + self.assertIn(f'backend {bk_second_name}', config) + self.assertIn(f'mode {mode}', config) + self.assertIn(f'server {bk_second_name} {bk_server_second}:{bk_server_port}', config) + self.assertIn(f'server {bk_second_name} {bk_server_second}:{bk_server_port} backup', config) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_load-balancing_wan.py b/smoketest/scripts/cli/test_load-balancing_wan.py new file mode 100755 index 000000000..47ca19b27 --- /dev/null +++ b/smoketest/scripts/cli/test_load-balancing_wan.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import unittest +import time + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import call +from vyos.utils.process import cmd + + +base_path = ['load-balancing'] + + +def create_netns(name): + return call(f'sudo ip netns add {name}') + +def create_veth_pair(local='veth0', peer='ceth0'): + return call(f'sudo ip link add {local} type veth peer name {peer}') + +def move_interface_to_netns(iface, netns_name): + return call(f'sudo ip link set {iface} netns {netns_name}') + +def rename_interface(iface, new_name): + return call(f'sudo ip link set {iface} name {new_name}') + +def cmd_in_netns(netns, cmd): + return call(f'sudo ip netns exec {netns} {cmd}') + +def delete_netns(name): + return call(f'sudo ip netns del {name}') + +class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestLoadBalancingWan, 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): + self.cli_delete(base_path) + self.cli_commit() + + def test_table_routes(self): + ns1 = 'ns201' + ns2 = 'ns202' + ns3 = 'ns203' + iface1 = 'eth201' + iface2 = 'eth202' + iface3 = 'eth203' + container_iface1 = 'ceth0' + container_iface2 = 'ceth1' + container_iface3 = 'ceth2' + + # Create network namespeces + create_netns(ns1) + create_netns(ns2) + create_netns(ns3) + 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) + 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') + 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') + cmd_in_netns(ns1, 'ip address add 203.0.113.1/24 dev eth0') + cmd_in_netns(ns2, 'ip address add 192.0.2.1/24 dev eth0') + cmd_in_netns(ns3, 'ip address add 198.51.100.1/24 dev eth0') + cmd_in_netns(ns1, 'ip link set dev eth0 up') + cmd_in_netns(ns2, 'ip link set dev eth0 up') + cmd_in_netns(ns3, 'ip link set dev eth0 up') + + # Set load-balancing configuration + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'failure-count', '2']) + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'nexthop', '203.0.113.1']) + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'success-count', '1']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'failure-count', '2']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'nexthop', '192.0.2.1']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'success-count', '1']) + + self.cli_set(base_path + ['wan', 'rule', '10', 'inbound-interface', iface3]) + self.cli_set(base_path + ['wan', 'rule', '10', 'source', 'address', '198.51.100.0/24']) + + + # commit changes + self.cli_commit() + + time.sleep(5) + # Check default routes in tables 201, 202 + # Expected values + original = 'default via 203.0.113.1 dev eth201' + tmp = cmd('sudo ip route show table 201') + self.assertEqual(tmp, original) + + original = 'default via 192.0.2.1 dev eth202' + tmp = cmd('sudo ip route show table 202') + self.assertEqual(tmp, original) + + # Delete veth interfaces and netns + for iface in [iface1, iface2, iface3]: + call(f'sudo ip link del dev {iface}') + + delete_netns(ns1) + delete_netns(ns2) + delete_netns(ns3) + + def test_check_chains(self): + + ns1 = 'nsA' + ns2 = 'nsB' + ns3 = 'nsC' + iface1 = 'veth1' + iface2 = 'veth2' + iface3 = 'veth3' + container_iface1 = 'ceth0' + container_iface2 = 'ceth1' + container_iface3 = 'ceth2' + mangle_isp1 = """table ip mangle { + chain ISP_veth1 { + 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 accept + } +}""" + mangle_prerouting = """table ip mangle { + chain PREROUTING { + type filter hook prerouting priority mangle; policy accept; + counter jump WANLOADBALANCE_PRE + } +}""" + mangle_wanloadbalance_pre = """table ip mangle { + chain WANLOADBALANCE_PRE { + 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 + } +}""" + nat_wanloadbalance = """table ip nat { + chain WANLOADBALANCE { + ct mark 0xc9 counter snat to 203.0.113.10 + ct mark 0xca counter snat to 192.0.2.10 + } +}""" + nat_vyos_pre_snat_hook = """table ip nat { + chain VYOS_PRE_SNAT_HOOK { + type nat hook postrouting priority srcnat - 1; policy accept; + counter jump WANLOADBALANCE + } +}""" + + # Create network namespeces + create_netns(ns1) + create_netns(ns2) + create_netns(ns3) + 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) + 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}') + + 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') + cmd_in_netns(ns1, 'ip address add 203.0.113.1/24 dev eth0') + cmd_in_netns(ns2, 'ip address add 192.0.2.1/24 dev eth0') + cmd_in_netns(ns3, 'ip address add 198.51.100.1/24 dev eth0') + cmd_in_netns(ns1, 'ip link set dev eth0 up') + cmd_in_netns(ns2, 'ip link set dev eth0 up') + cmd_in_netns(ns3, 'ip link set dev eth0 up') + + # Set load-balancing configuration + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'failure-count', '2']) + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'nexthop', '203.0.113.1']) + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'success-count', '1']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'failure-count', '2']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'nexthop', '192.0.2.1']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'success-count', '1']) + self.cli_set(base_path + ['wan', 'rule', '10', 'inbound-interface', iface3]) + self.cli_set(base_path + ['wan', 'rule', '10', 'source', 'address', '198.51.100.0/24']) + self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface1]) + self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface2]) + + # commit changes + self.cli_commit() + + time.sleep(5) + + # Check mangle chains + tmp = cmd(f'sudo nft -s list chain mangle ISP_{iface1}') + self.assertEqual(tmp, mangle_isp1) + + tmp = cmd(f'sudo nft -s list chain mangle ISP_{iface2}') + self.assertEqual(tmp, mangle_isp2) + + tmp = cmd(f'sudo nft -s list chain mangle PREROUTING') + self.assertEqual(tmp, mangle_prerouting) + + tmp = cmd(f'sudo nft -s list chain mangle WANLOADBALANCE_PRE') + self.assertEqual(tmp, mangle_wanloadbalance_pre) + + # Check nat chains + tmp = cmd(f'sudo nft -s list chain nat WANLOADBALANCE') + self.assertEqual(tmp, nat_wanloadbalance) + + tmp = cmd(f'sudo nft -s list chain nat VYOS_PRE_SNAT_HOOK') + self.assertEqual(tmp, nat_vyos_pre_snat_hook) + + # Delete veth interfaces and netns + for iface in [iface1, iface2, iface3]: + call(f'sudo ip link del dev {iface}') + + delete_netns(ns1) + delete_netns(ns2) + delete_netns(ns3) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_load_balancing_reverse_proxy.py b/smoketest/scripts/cli/test_load_balancing_reverse_proxy.py deleted file mode 100755 index 274b97f22..000000000 --- a/smoketest/scripts/cli/test_load_balancing_reverse_proxy.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/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 . - -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.configsession import ConfigSessionError -from vyos.utils.process import process_named_running -from vyos.utils.file import read_file - -PROCESS_NAME = 'haproxy' -HAPROXY_CONF = '/run/haproxy/haproxy.cfg' -base_path = ['load-balancing', 'reverse-proxy'] -proxy_interface = 'eth1' - - -class TestLoadBalancingReverseProxy(VyOSUnitTestSHIM.TestCase): - def tearDown(self): - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - self.cli_delete(['interfaces', 'ethernet', proxy_interface, 'address']) - self.cli_delete(base_path) - self.cli_commit() - - # Process must be terminated after deleting the config - self.assertFalse(process_named_running(PROCESS_NAME)) - - def test_01_lb_reverse_proxy_domain(self): - domains_bk_first = ['n1.example.com', 'n2.example.com', 'n3.example.com'] - domain_bk_second = 'n5.example.com' - frontend = 'https_front' - front_port = '4433' - bk_server_first = '192.0.2.11' - bk_server_second = '192.0.2.12' - bk_first_name = 'bk-01' - bk_second_name = 'bk-02' - bk_server_port = '9090' - mode = 'http' - rule_ten = '10' - rule_twenty = '20' - send_proxy = 'send-proxy' - max_connections = '1000' - - back_base = base_path + ['backend'] - - self.cli_set(base_path + ['service', frontend, 'mode', mode]) - self.cli_set(base_path + ['service', frontend, 'port', front_port]) - for domain in domains_bk_first: - self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'domain-name', domain]) - self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'set', 'backend', bk_first_name]) - self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'domain-name', domain_bk_second]) - self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'set', 'backend', bk_second_name]) - - self.cli_set(back_base + [bk_first_name, 'mode', mode]) - self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'address', bk_server_first]) - self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'port', bk_server_port]) - self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, send_proxy]) - - self.cli_set(back_base + [bk_second_name, 'mode', mode]) - self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'address', bk_server_second]) - self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'port', bk_server_port]) - self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'backup']) - - self.cli_set(base_path + ['global-parameters', 'max-connections', max_connections]) - - # commit changes - self.cli_commit() - - config = read_file(HAPROXY_CONF) - - # Global - self.assertIn(f'maxconn {max_connections}', config) - - # Frontend - self.assertIn(f'frontend {frontend}', config) - self.assertIn(f'bind :::{front_port} v4v6', config) - self.assertIn(f'mode {mode}', config) - for domain in domains_bk_first: - self.assertIn(f'acl {rule_ten} hdr(host) -i {domain}', config) - self.assertIn(f'use_backend {bk_first_name} if {rule_ten}', config) - self.assertIn(f'acl {rule_twenty} hdr(host) -i {domain_bk_second}', config) - self.assertIn(f'use_backend {bk_second_name} if {rule_twenty}', config) - - # Backend - self.assertIn(f'backend {bk_first_name}', config) - self.assertIn(f'balance roundrobin', config) - self.assertIn(f'option forwardfor', config) - self.assertIn('http-request add-header X-Forwarded-Proto https if { ssl_fc }', config) - self.assertIn(f'mode {mode}', config) - self.assertIn(f'server {bk_first_name} {bk_server_first}:{bk_server_port} send-proxy', config) - - self.assertIn(f'backend {bk_second_name}', config) - self.assertIn(f'mode {mode}', config) - self.assertIn(f'server {bk_second_name} {bk_server_second}:{bk_server_port}', config) - self.assertIn(f'server {bk_second_name} {bk_server_second}:{bk_server_port} backup', config) - - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_load_balancing_wan.py b/smoketest/scripts/cli/test_load_balancing_wan.py deleted file mode 100755 index 47ca19b27..000000000 --- a/smoketest/scripts/cli/test_load_balancing_wan.py +++ /dev/null @@ -1,259 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2022-2023 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import unittest -import time - -from base_vyostest_shim import VyOSUnitTestSHIM -from vyos.configsession import ConfigSessionError -from vyos.ifconfig import Section -from vyos.utils.process import call -from vyos.utils.process import cmd - - -base_path = ['load-balancing'] - - -def create_netns(name): - return call(f'sudo ip netns add {name}') - -def create_veth_pair(local='veth0', peer='ceth0'): - return call(f'sudo ip link add {local} type veth peer name {peer}') - -def move_interface_to_netns(iface, netns_name): - return call(f'sudo ip link set {iface} netns {netns_name}') - -def rename_interface(iface, new_name): - return call(f'sudo ip link set {iface} name {new_name}') - -def cmd_in_netns(netns, cmd): - return call(f'sudo ip netns exec {netns} {cmd}') - -def delete_netns(name): - return call(f'sudo ip netns del {name}') - -class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): - @classmethod - def setUpClass(cls): - super(TestLoadBalancingWan, 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): - self.cli_delete(base_path) - self.cli_commit() - - def test_table_routes(self): - ns1 = 'ns201' - ns2 = 'ns202' - ns3 = 'ns203' - iface1 = 'eth201' - iface2 = 'eth202' - iface3 = 'eth203' - container_iface1 = 'ceth0' - container_iface2 = 'ceth1' - container_iface3 = 'ceth2' - - # Create network namespeces - create_netns(ns1) - create_netns(ns2) - create_netns(ns3) - 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) - 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') - 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') - cmd_in_netns(ns1, 'ip address add 203.0.113.1/24 dev eth0') - cmd_in_netns(ns2, 'ip address add 192.0.2.1/24 dev eth0') - cmd_in_netns(ns3, 'ip address add 198.51.100.1/24 dev eth0') - cmd_in_netns(ns1, 'ip link set dev eth0 up') - cmd_in_netns(ns2, 'ip link set dev eth0 up') - cmd_in_netns(ns3, 'ip link set dev eth0 up') - - # Set load-balancing configuration - self.cli_set(base_path + ['wan', 'interface-health', iface1, 'failure-count', '2']) - self.cli_set(base_path + ['wan', 'interface-health', iface1, 'nexthop', '203.0.113.1']) - self.cli_set(base_path + ['wan', 'interface-health', iface1, 'success-count', '1']) - self.cli_set(base_path + ['wan', 'interface-health', iface2, 'failure-count', '2']) - self.cli_set(base_path + ['wan', 'interface-health', iface2, 'nexthop', '192.0.2.1']) - self.cli_set(base_path + ['wan', 'interface-health', iface2, 'success-count', '1']) - - self.cli_set(base_path + ['wan', 'rule', '10', 'inbound-interface', iface3]) - self.cli_set(base_path + ['wan', 'rule', '10', 'source', 'address', '198.51.100.0/24']) - - - # commit changes - self.cli_commit() - - time.sleep(5) - # Check default routes in tables 201, 202 - # Expected values - original = 'default via 203.0.113.1 dev eth201' - tmp = cmd('sudo ip route show table 201') - self.assertEqual(tmp, original) - - original = 'default via 192.0.2.1 dev eth202' - tmp = cmd('sudo ip route show table 202') - self.assertEqual(tmp, original) - - # Delete veth interfaces and netns - for iface in [iface1, iface2, iface3]: - call(f'sudo ip link del dev {iface}') - - delete_netns(ns1) - delete_netns(ns2) - delete_netns(ns3) - - def test_check_chains(self): - - ns1 = 'nsA' - ns2 = 'nsB' - ns3 = 'nsC' - iface1 = 'veth1' - iface2 = 'veth2' - iface3 = 'veth3' - container_iface1 = 'ceth0' - container_iface2 = 'ceth1' - container_iface3 = 'ceth2' - mangle_isp1 = """table ip mangle { - chain ISP_veth1 { - 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 accept - } -}""" - mangle_prerouting = """table ip mangle { - chain PREROUTING { - type filter hook prerouting priority mangle; policy accept; - counter jump WANLOADBALANCE_PRE - } -}""" - mangle_wanloadbalance_pre = """table ip mangle { - chain WANLOADBALANCE_PRE { - 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 - } -}""" - nat_wanloadbalance = """table ip nat { - chain WANLOADBALANCE { - ct mark 0xc9 counter snat to 203.0.113.10 - ct mark 0xca counter snat to 192.0.2.10 - } -}""" - nat_vyos_pre_snat_hook = """table ip nat { - chain VYOS_PRE_SNAT_HOOK { - type nat hook postrouting priority srcnat - 1; policy accept; - counter jump WANLOADBALANCE - } -}""" - - # Create network namespeces - create_netns(ns1) - create_netns(ns2) - create_netns(ns3) - 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) - 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}') - - 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') - cmd_in_netns(ns1, 'ip address add 203.0.113.1/24 dev eth0') - cmd_in_netns(ns2, 'ip address add 192.0.2.1/24 dev eth0') - cmd_in_netns(ns3, 'ip address add 198.51.100.1/24 dev eth0') - cmd_in_netns(ns1, 'ip link set dev eth0 up') - cmd_in_netns(ns2, 'ip link set dev eth0 up') - cmd_in_netns(ns3, 'ip link set dev eth0 up') - - # Set load-balancing configuration - self.cli_set(base_path + ['wan', 'interface-health', iface1, 'failure-count', '2']) - self.cli_set(base_path + ['wan', 'interface-health', iface1, 'nexthop', '203.0.113.1']) - self.cli_set(base_path + ['wan', 'interface-health', iface1, 'success-count', '1']) - self.cli_set(base_path + ['wan', 'interface-health', iface2, 'failure-count', '2']) - self.cli_set(base_path + ['wan', 'interface-health', iface2, 'nexthop', '192.0.2.1']) - self.cli_set(base_path + ['wan', 'interface-health', iface2, 'success-count', '1']) - self.cli_set(base_path + ['wan', 'rule', '10', 'inbound-interface', iface3]) - self.cli_set(base_path + ['wan', 'rule', '10', 'source', 'address', '198.51.100.0/24']) - self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface1]) - self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface2]) - - # commit changes - self.cli_commit() - - time.sleep(5) - - # Check mangle chains - tmp = cmd(f'sudo nft -s list chain mangle ISP_{iface1}') - self.assertEqual(tmp, mangle_isp1) - - tmp = cmd(f'sudo nft -s list chain mangle ISP_{iface2}') - self.assertEqual(tmp, mangle_isp2) - - tmp = cmd(f'sudo nft -s list chain mangle PREROUTING') - self.assertEqual(tmp, mangle_prerouting) - - tmp = cmd(f'sudo nft -s list chain mangle WANLOADBALANCE_PRE') - self.assertEqual(tmp, mangle_wanloadbalance_pre) - - # Check nat chains - tmp = cmd(f'sudo nft -s list chain nat WANLOADBALANCE') - self.assertEqual(tmp, nat_wanloadbalance) - - tmp = cmd(f'sudo nft -s list chain nat VYOS_PRE_SNAT_HOOK') - self.assertEqual(tmp, nat_vyos_pre_snat_hook) - - # Delete veth interfaces and netns - for iface in [iface1, iface2, iface3]: - call(f'sudo ip link del dev {iface}') - - delete_netns(ns1) - delete_netns(ns2) - delete_netns(ns3) - - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_segment-routing.py b/smoketest/scripts/cli/test_protocols_segment-routing.py new file mode 100755 index 000000000..403c05924 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_segment-routing.py @@ -0,0 +1,112 @@ +#!/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 . + +import os +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.system import sysctl_read + +base_path = ['protocols', 'segment-routing'] +PROCESS_NAME = 'zebra' + +class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + # call base-classes classmethod + super(TestProtocolsSegmentRouting, cls).setUpClass() + # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same + cls.daemon_pid = process_named_running(PROCESS_NAME) + # 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): + self.cli_delete(base_path) + self.cli_commit() + + # check process health and continuity + self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + + def test_srv6(self): + interfaces = Section.interfaces('ethernet', vlan=False) + locators = { + 'foo' : { 'prefix' : '2001:a::/64' }, + 'foo' : { 'prefix' : '2001:b::/64', 'usid' : {} }, + } + + for locator, locator_config in locators.items(): + self.cli_set(base_path + ['srv6', 'locator', locator, 'prefix', locator_config['prefix']]) + if 'usid' in locator_config: + self.cli_set(base_path + ['srv6', 'locator', locator, 'behavior-usid']) + + # verify() - SRv6 should be enabled on at least one interface! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'srv6']) + + self.cli_commit() + + for interface in interfaces: + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '0') # default + + frrconfig = self.getFRRconfig(f'segment-routing', daemon='zebra') + self.assertIn(f'segment-routing', frrconfig) + self.assertIn(f' srv6', frrconfig) + self.assertIn(f' locators', frrconfig) + for locator, locator_config in locators.items(): + self.assertIn(f' locator {locator}', frrconfig) + self.assertIn(f' prefix {locator_config["prefix"]} block-len 40 node-len 24 func-bits 16', frrconfig) + + def test_srv6_sysctl(self): + interfaces = Section.interfaces('ethernet', vlan=False) + + # HMAC accept + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'srv6']) + self.cli_set(base_path + ['interface', interface, 'srv6', 'hmac', 'ignore']) + self.cli_commit() + + for interface in interfaces: + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '-1') # ignore + + # HMAC drop + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'srv6']) + self.cli_set(base_path + ['interface', interface, 'srv6', 'hmac', 'drop']) + self.cli_commit() + + for interface in interfaces: + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '1') # drop + + # Disable SRv6 on first interface + first_if = interfaces[-1] + self.cli_delete(base_path + ['interface', first_if]) + self.cli_commit() + + self.assertEqual(sysctl_read(f'net.ipv6.conf.{first_if}.seg6_enabled'), '0') + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_segment_routing.py b/smoketest/scripts/cli/test_protocols_segment_routing.py deleted file mode 100755 index 403c05924..000000000 --- a/smoketest/scripts/cli/test_protocols_segment_routing.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/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 . - -import os -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.configsession import ConfigSessionError -from vyos.ifconfig import Section -from vyos.utils.process import cmd -from vyos.utils.process import process_named_running -from vyos.utils.system import sysctl_read - -base_path = ['protocols', 'segment-routing'] -PROCESS_NAME = 'zebra' - -class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): - @classmethod - def setUpClass(cls): - # call base-classes classmethod - super(TestProtocolsSegmentRouting, cls).setUpClass() - # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same - cls.daemon_pid = process_named_running(PROCESS_NAME) - # 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): - self.cli_delete(base_path) - self.cli_commit() - - # check process health and continuity - self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) - - def test_srv6(self): - interfaces = Section.interfaces('ethernet', vlan=False) - locators = { - 'foo' : { 'prefix' : '2001:a::/64' }, - 'foo' : { 'prefix' : '2001:b::/64', 'usid' : {} }, - } - - for locator, locator_config in locators.items(): - self.cli_set(base_path + ['srv6', 'locator', locator, 'prefix', locator_config['prefix']]) - if 'usid' in locator_config: - self.cli_set(base_path + ['srv6', 'locator', locator, 'behavior-usid']) - - # verify() - SRv6 should be enabled on at least one interface! - with self.assertRaises(ConfigSessionError): - self.cli_commit() - for interface in interfaces: - self.cli_set(base_path + ['interface', interface, 'srv6']) - - self.cli_commit() - - for interface in interfaces: - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '0') # default - - frrconfig = self.getFRRconfig(f'segment-routing', daemon='zebra') - self.assertIn(f'segment-routing', frrconfig) - self.assertIn(f' srv6', frrconfig) - self.assertIn(f' locators', frrconfig) - for locator, locator_config in locators.items(): - self.assertIn(f' locator {locator}', frrconfig) - self.assertIn(f' prefix {locator_config["prefix"]} block-len 40 node-len 24 func-bits 16', frrconfig) - - def test_srv6_sysctl(self): - interfaces = Section.interfaces('ethernet', vlan=False) - - # HMAC accept - for interface in interfaces: - self.cli_set(base_path + ['interface', interface, 'srv6']) - self.cli_set(base_path + ['interface', interface, 'srv6', 'hmac', 'ignore']) - self.cli_commit() - - for interface in interfaces: - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '-1') # ignore - - # HMAC drop - for interface in interfaces: - self.cli_set(base_path + ['interface', interface, 'srv6']) - self.cli_set(base_path + ['interface', interface, 'srv6', 'hmac', 'drop']) - self.cli_commit() - - for interface in interfaces: - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '1') # drop - - # Disable SRv6 on first interface - first_if = interfaces[-1] - self.cli_delete(base_path + ['interface', first_if]) - self.cli_commit() - - self.assertEqual(sysctl_read(f'net.ipv6.conf.{first_if}.seg6_enabled'), '0') - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_bcast-relay.py b/smoketest/scripts/cli/test_service_bcast-relay.py deleted file mode 100755 index 87901869e..000000000 --- a/smoketest/scripts/cli/test_service_bcast-relay.py +++ /dev/null @@ -1,68 +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 . - -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from psutil import process_iter -from vyos.configsession import ConfigSessionError - -base_path = ['service', 'broadcast-relay'] - -class TestServiceBroadcastRelay(VyOSUnitTestSHIM.TestCase): - _address1 = '192.0.2.1/24' - _address2 = '192.0.2.1/24' - - def setUp(self): - self.cli_set(['interfaces', 'dummy', 'dum1001', 'address', self._address1]) - self.cli_set(['interfaces', 'dummy', 'dum1002', 'address', self._address2]) - - def tearDown(self): - self.cli_delete(['interfaces', 'dummy', 'dum1001']) - self.cli_delete(['interfaces', 'dummy', 'dum1002']) - self.cli_delete(base_path) - self.cli_commit() - - def test_broadcast_relay_service(self): - ids = range(1, 5) - for id in ids: - base = base_path + ['id', str(id)] - self.cli_set(base + ['description', 'vyos']) - self.cli_set(base + ['port', str(10000 + id)]) - - # check validate() - two interfaces must be present - with self.assertRaises(ConfigSessionError): - self.cli_commit() - - self.cli_set(base + ['interface', 'dum1001']) - self.cli_set(base + ['interface', 'dum1002']) - self.cli_set(base + ['address', self._address1.split('/')[0]]) - - self.cli_commit() - - for id in ids: - # check if process is running - running = False - for p in process_iter(): - if "udp-broadcast-relay" in p.name(): - if p.cmdline()[3] == str(id): - running = True - break - self.assertTrue(running) - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_broadcast-relay.py b/smoketest/scripts/cli/test_service_broadcast-relay.py new file mode 100755 index 000000000..87901869e --- /dev/null +++ b/smoketest/scripts/cli/test_service_broadcast-relay.py @@ -0,0 +1,68 @@ +#!/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 . + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from psutil import process_iter +from vyos.configsession import ConfigSessionError + +base_path = ['service', 'broadcast-relay'] + +class TestServiceBroadcastRelay(VyOSUnitTestSHIM.TestCase): + _address1 = '192.0.2.1/24' + _address2 = '192.0.2.1/24' + + def setUp(self): + self.cli_set(['interfaces', 'dummy', 'dum1001', 'address', self._address1]) + self.cli_set(['interfaces', 'dummy', 'dum1002', 'address', self._address2]) + + def tearDown(self): + self.cli_delete(['interfaces', 'dummy', 'dum1001']) + self.cli_delete(['interfaces', 'dummy', 'dum1002']) + self.cli_delete(base_path) + self.cli_commit() + + def test_broadcast_relay_service(self): + ids = range(1, 5) + for id in ids: + base = base_path + ['id', str(id)] + self.cli_set(base + ['description', 'vyos']) + self.cli_set(base + ['port', str(10000 + id)]) + + # check validate() - two interfaces must be present + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base + ['interface', 'dum1001']) + self.cli_set(base + ['interface', 'dum1002']) + self.cli_set(base + ['address', self._address1.split('/')[0]]) + + self.cli_commit() + + for id in ids: + # check if process is running + running = False + for p in process_iter(): + if "udp-broadcast-relay" in p.name(): + if p.cmdline()[3] == str(id): + running = True + break + self.assertTrue(running) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_ids.py b/smoketest/scripts/cli/test_service_ids.py deleted file mode 100755 index 91b056eea..000000000 --- a/smoketest/scripts/cli/test_service_ids.py +++ /dev/null @@ -1,116 +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 . - -import os -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.configsession import ConfigSessionError -from vyos.utils.process import process_named_running -from vyos.utils.file import read_file - -PROCESS_NAME = 'fastnetmon' -FASTNETMON_CONF = '/run/fastnetmon/fastnetmon.conf' -NETWORKS_CONF = '/run/fastnetmon/networks_list' -EXCLUDED_NETWORKS_CONF = '/run/fastnetmon/excluded_networks_list' -base_path = ['service', 'ids', 'ddos-protection'] - -class TestServiceIDS(VyOSUnitTestSHIM.TestCase): - @classmethod - def setUpClass(cls): - super(TestServiceIDS, 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): - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - # delete test config - self.cli_delete(base_path) - self.cli_commit() - - self.assertFalse(os.path.exists(FASTNETMON_CONF)) - self.assertFalse(process_named_running(PROCESS_NAME)) - - def test_fastnetmon(self): - networks = ['10.0.0.0/24', '10.5.5.0/24', '2001:db8:10::/64', '2001:db8:20::/64'] - excluded_networks = ['10.0.0.1/32', '2001:db8:10::1/128'] - interfaces = ['eth0', 'eth1'] - fps = '3500' - mbps = '300' - pps = '60000' - - self.cli_set(base_path + ['mode', 'mirror']) - # Required network! - with self.assertRaises(ConfigSessionError): - self.cli_commit() - for tmp in networks: - self.cli_set(base_path + ['network', tmp]) - - # optional excluded-network! - with self.assertRaises(ConfigSessionError): - self.cli_commit() - for tmp in excluded_networks: - self.cli_set(base_path + ['excluded-network', tmp]) - - # Required interface(s)! - with self.assertRaises(ConfigSessionError): - self.cli_commit() - for tmp in interfaces: - self.cli_set(base_path + ['listen-interface', tmp]) - - self.cli_set(base_path + ['direction', 'in']) - self.cli_set(base_path + ['threshold', 'general', 'fps', fps]) - self.cli_set(base_path + ['threshold', 'general', 'pps', pps]) - self.cli_set(base_path + ['threshold', 'general', 'mbps', mbps]) - - # commit changes - self.cli_commit() - - # Check configured port - config = read_file(FASTNETMON_CONF) - self.assertIn(f'mirror_afpacket = on', config) - self.assertIn(f'process_incoming_traffic = on', config) - self.assertIn(f'process_outgoing_traffic = off', config) - self.assertIn(f'ban_for_flows = on', config) - self.assertIn(f'threshold_flows = {fps}', config) - self.assertIn(f'ban_for_bandwidth = on', config) - self.assertIn(f'threshold_mbps = {mbps}', config) - self.assertIn(f'ban_for_pps = on', config) - self.assertIn(f'threshold_pps = {pps}', config) - # default - self.assertIn(f'enable_ban = on', config) - self.assertIn(f'enable_ban_ipv6 = on', config) - self.assertIn(f'ban_time = 1900', config) - - tmp = ','.join(interfaces) - self.assertIn(f'interfaces = {tmp}', config) - - - network_config = read_file(NETWORKS_CONF) - for tmp in networks: - self.assertIn(f'{tmp}', network_config) - - excluded_network_config = read_file(EXCLUDED_NETWORKS_CONF) - for tmp in excluded_networks: - self.assertIn(f'{tmp}', excluded_network_config) - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_ids_ddos-protection.py b/smoketest/scripts/cli/test_service_ids_ddos-protection.py new file mode 100755 index 000000000..91b056eea --- /dev/null +++ b/smoketest/scripts/cli/test_service_ids_ddos-protection.py @@ -0,0 +1,116 @@ +#!/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 . + +import os +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +PROCESS_NAME = 'fastnetmon' +FASTNETMON_CONF = '/run/fastnetmon/fastnetmon.conf' +NETWORKS_CONF = '/run/fastnetmon/networks_list' +EXCLUDED_NETWORKS_CONF = '/run/fastnetmon/excluded_networks_list' +base_path = ['service', 'ids', 'ddos-protection'] + +class TestServiceIDS(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestServiceIDS, 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): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + # delete test config + self.cli_delete(base_path) + self.cli_commit() + + self.assertFalse(os.path.exists(FASTNETMON_CONF)) + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_fastnetmon(self): + networks = ['10.0.0.0/24', '10.5.5.0/24', '2001:db8:10::/64', '2001:db8:20::/64'] + excluded_networks = ['10.0.0.1/32', '2001:db8:10::1/128'] + interfaces = ['eth0', 'eth1'] + fps = '3500' + mbps = '300' + pps = '60000' + + self.cli_set(base_path + ['mode', 'mirror']) + # Required network! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for tmp in networks: + self.cli_set(base_path + ['network', tmp]) + + # optional excluded-network! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for tmp in excluded_networks: + self.cli_set(base_path + ['excluded-network', tmp]) + + # Required interface(s)! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for tmp in interfaces: + self.cli_set(base_path + ['listen-interface', tmp]) + + self.cli_set(base_path + ['direction', 'in']) + self.cli_set(base_path + ['threshold', 'general', 'fps', fps]) + self.cli_set(base_path + ['threshold', 'general', 'pps', pps]) + self.cli_set(base_path + ['threshold', 'general', 'mbps', mbps]) + + # commit changes + self.cli_commit() + + # Check configured port + config = read_file(FASTNETMON_CONF) + self.assertIn(f'mirror_afpacket = on', config) + self.assertIn(f'process_incoming_traffic = on', config) + self.assertIn(f'process_outgoing_traffic = off', config) + self.assertIn(f'ban_for_flows = on', config) + self.assertIn(f'threshold_flows = {fps}', config) + self.assertIn(f'ban_for_bandwidth = on', config) + self.assertIn(f'threshold_mbps = {mbps}', config) + self.assertIn(f'ban_for_pps = on', config) + self.assertIn(f'threshold_pps = {pps}', config) + # default + self.assertIn(f'enable_ban = on', config) + self.assertIn(f'enable_ban_ipv6 = on', config) + self.assertIn(f'ban_time = 1900', config) + + tmp = ','.join(interfaces) + self.assertIn(f'interfaces = {tmp}', config) + + + network_config = read_file(NETWORKS_CONF) + for tmp in networks: + self.assertIn(f'{tmp}', network_config) + + excluded_network_config = read_file(EXCLUDED_NETWORKS_CONF) + for tmp in excluded_networks: + self.assertIn(f'{tmp}', excluded_network_config) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_mdns-repeater.py b/smoketest/scripts/cli/test_service_mdns-repeater.py deleted file mode 100755 index f2fb3b509..000000000 --- a/smoketest/scripts/cli/test_service_mdns-repeater.py +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from configparser import ConfigParser -from vyos.configsession import ConfigSessionError -from vyos.utils.process import process_named_running - -base_path = ['service', 'mdns', 'repeater'] -intf_base = ['interfaces', 'dummy'] -config_file = '/run/avahi-daemon/avahi-daemon.conf' - - -class TestServiceMDNSrepeater(VyOSUnitTestSHIM.TestCase): - def setUp(self): - # Start with a clean CLI instance - self.cli_delete(base_path) - - # Service required a configured IP address on the interface - self.cli_set(intf_base + ['dum10', 'address', '192.0.2.1/30']) - self.cli_set(intf_base + ['dum10', 'ipv6', 'address', 'no-default-link-local']) - self.cli_set(intf_base + ['dum20', 'address', '192.0.2.5/30']) - self.cli_set(intf_base + ['dum20', 'address', '2001:db8:0:2::5/64']) - self.cli_set(intf_base + ['dum30', 'address', '192.0.2.9/30']) - self.cli_set(intf_base + ['dum30', 'address', '2001:db8:0:2::9/64']) - self.cli_set(intf_base + ['dum40', 'address', '2001:db8:0:2::11/64']) - self.cli_commit() - - def tearDown(self): - # Check for running process - self.assertTrue(process_named_running('avahi-daemon')) - - self.cli_delete(base_path) - self.cli_delete(intf_base + ['dum10']) - self.cli_delete(intf_base + ['dum20']) - self.cli_delete(intf_base + ['dum30']) - self.cli_delete(intf_base + ['dum40']) - self.cli_commit() - - # Check that there is no longer a running process - self.assertFalse(process_named_running('avahi-daemon')) - - def test_service_dual_stack(self): - # mDNS browsing domains in addition to the default one (local) - domains = ['dom1.home.arpa', 'dom2.home.arpa'] - - # mDNS services to be repeated - services = ['_ipp._tcp', '_smb._tcp', '_ssh._tcp'] - - self.cli_set(base_path + ['ip-version', 'both']) - self.cli_set(base_path + ['interface', 'dum20']) - self.cli_set(base_path + ['interface', 'dum30']) - - for domain in domains: - self.cli_set(base_path + ['browse-domain', domain]) - - for service in services: - self.cli_set(base_path + ['allow-service', service]) - - self.cli_commit() - - # Validate configuration values - conf = ConfigParser(delimiters='=') - conf.read(config_file) - - self.assertEqual(conf['server']['use-ipv4'], 'yes') - self.assertEqual(conf['server']['use-ipv6'], 'yes') - self.assertEqual(conf['server']['allow-interfaces'], 'dum20, dum30') - self.assertEqual(conf['server']['browse-domains'], ', '.join(domains)) - self.assertEqual(conf['reflector']['enable-reflector'], 'yes') - self.assertEqual(conf['reflector']['reflect-filters'], ', '.join(services)) - - def test_service_ipv4(self): - # partcipating interfaces should have IPv4 addresses - self.cli_set(base_path + ['ip-version', 'ipv4']) - self.cli_set(base_path + ['interface', 'dum10']) - self.cli_set(base_path + ['interface', 'dum40']) - - # exception is raised if partcipating interfaces do not have IPv4 address - with self.assertRaises(ConfigSessionError): - self.cli_commit() - self.cli_delete(base_path + ['interface', 'dum40']) - self.cli_set(base_path + ['interface', 'dum20']) - self.cli_commit() - - # Validate configuration values - conf = ConfigParser(delimiters='=') - conf.read(config_file) - - self.assertEqual(conf['server']['use-ipv4'], 'yes') - self.assertEqual(conf['server']['use-ipv6'], 'no') - self.assertEqual(conf['server']['allow-interfaces'], 'dum10, dum20') - self.assertEqual(conf['reflector']['enable-reflector'], 'yes') - - def test_service_ipv6(self): - # partcipating interfaces should have IPv6 addresses - self.cli_set(base_path + ['ip-version', 'ipv6']) - self.cli_set(base_path + ['interface', 'dum10']) - self.cli_set(base_path + ['interface', 'dum30']) - - # exception is raised if partcipating interfaces do not have IPv4 address - with self.assertRaises(ConfigSessionError): - self.cli_commit() - self.cli_delete(base_path + ['interface', 'dum10']) - self.cli_set(base_path + ['interface', 'dum40']) - self.cli_commit() - - # Validate configuration values - conf = ConfigParser(delimiters='=') - conf.read(config_file) - - self.assertEqual(conf['server']['use-ipv4'], 'no') - self.assertEqual(conf['server']['use-ipv6'], 'yes') - self.assertEqual(conf['server']['allow-interfaces'], 'dum30, dum40') - self.assertEqual(conf['reflector']['enable-reflector'], 'yes') - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_mdns_repeater.py b/smoketest/scripts/cli/test_service_mdns_repeater.py new file mode 100755 index 000000000..f2fb3b509 --- /dev/null +++ b/smoketest/scripts/cli/test_service_mdns_repeater.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from configparser import ConfigParser +from vyos.configsession import ConfigSessionError +from vyos.utils.process import process_named_running + +base_path = ['service', 'mdns', 'repeater'] +intf_base = ['interfaces', 'dummy'] +config_file = '/run/avahi-daemon/avahi-daemon.conf' + + +class TestServiceMDNSrepeater(VyOSUnitTestSHIM.TestCase): + def setUp(self): + # Start with a clean CLI instance + self.cli_delete(base_path) + + # Service required a configured IP address on the interface + self.cli_set(intf_base + ['dum10', 'address', '192.0.2.1/30']) + self.cli_set(intf_base + ['dum10', 'ipv6', 'address', 'no-default-link-local']) + self.cli_set(intf_base + ['dum20', 'address', '192.0.2.5/30']) + self.cli_set(intf_base + ['dum20', 'address', '2001:db8:0:2::5/64']) + self.cli_set(intf_base + ['dum30', 'address', '192.0.2.9/30']) + self.cli_set(intf_base + ['dum30', 'address', '2001:db8:0:2::9/64']) + self.cli_set(intf_base + ['dum40', 'address', '2001:db8:0:2::11/64']) + self.cli_commit() + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running('avahi-daemon')) + + self.cli_delete(base_path) + self.cli_delete(intf_base + ['dum10']) + self.cli_delete(intf_base + ['dum20']) + self.cli_delete(intf_base + ['dum30']) + self.cli_delete(intf_base + ['dum40']) + self.cli_commit() + + # Check that there is no longer a running process + self.assertFalse(process_named_running('avahi-daemon')) + + def test_service_dual_stack(self): + # mDNS browsing domains in addition to the default one (local) + domains = ['dom1.home.arpa', 'dom2.home.arpa'] + + # mDNS services to be repeated + services = ['_ipp._tcp', '_smb._tcp', '_ssh._tcp'] + + self.cli_set(base_path + ['ip-version', 'both']) + self.cli_set(base_path + ['interface', 'dum20']) + self.cli_set(base_path + ['interface', 'dum30']) + + for domain in domains: + self.cli_set(base_path + ['browse-domain', domain]) + + for service in services: + self.cli_set(base_path + ['allow-service', service]) + + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(delimiters='=') + conf.read(config_file) + + self.assertEqual(conf['server']['use-ipv4'], 'yes') + self.assertEqual(conf['server']['use-ipv6'], 'yes') + self.assertEqual(conf['server']['allow-interfaces'], 'dum20, dum30') + self.assertEqual(conf['server']['browse-domains'], ', '.join(domains)) + self.assertEqual(conf['reflector']['enable-reflector'], 'yes') + self.assertEqual(conf['reflector']['reflect-filters'], ', '.join(services)) + + def test_service_ipv4(self): + # partcipating interfaces should have IPv4 addresses + self.cli_set(base_path + ['ip-version', 'ipv4']) + self.cli_set(base_path + ['interface', 'dum10']) + self.cli_set(base_path + ['interface', 'dum40']) + + # exception is raised if partcipating interfaces do not have IPv4 address + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', 'dum40']) + self.cli_set(base_path + ['interface', 'dum20']) + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(delimiters='=') + conf.read(config_file) + + self.assertEqual(conf['server']['use-ipv4'], 'yes') + self.assertEqual(conf['server']['use-ipv6'], 'no') + self.assertEqual(conf['server']['allow-interfaces'], 'dum10, dum20') + self.assertEqual(conf['reflector']['enable-reflector'], 'yes') + + def test_service_ipv6(self): + # partcipating interfaces should have IPv6 addresses + self.cli_set(base_path + ['ip-version', 'ipv6']) + self.cli_set(base_path + ['interface', 'dum10']) + self.cli_set(base_path + ['interface', 'dum30']) + + # exception is raised if partcipating interfaces do not have IPv4 address + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', 'dum10']) + self.cli_set(base_path + ['interface', 'dum40']) + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(delimiters='=') + conf.read(config_file) + + self.assertEqual(conf['server']['use-ipv4'], 'no') + self.assertEqual(conf['server']['use-ipv6'], 'yes') + self.assertEqual(conf['server']['allow-interfaces'], 'dum30, dum40') + self.assertEqual(conf['reflector']['enable-reflector'], 'yes') + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_salt-minion.py b/smoketest/scripts/cli/test_service_salt-minion.py new file mode 100755 index 000000000..48a588b72 --- /dev/null +++ b/smoketest/scripts/cli/test_service_salt-minion.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import unittest + +from socket import gethostname +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.utils.process import cmd + +PROCESS_NAME = 'salt-minion' +SALT_CONF = '/etc/salt/minion' +base_path = ['service', 'salt-minion'] + +interface = 'dum4456' + +class TestServiceSALT(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestServiceSALT, 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, ['interfaces', 'dummy', interface, 'address', '100.64.0.1/16']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'dummy', interface]) + super(TestServiceSALT, cls).tearDownClass() + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + # delete testing SALT config + self.cli_delete(base_path) + self.cli_commit() + + # For an unknown reason on QEMU systems (e.g. where smoketests are executed + # from the CI) salt-minion process is not killed by systemd. Apparently + # no issue on VMWare. + if cmd('systemd-detect-virt') != 'kvm': + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_default(self): + servers = ['192.0.2.1', '192.0.2.2'] + + for server in servers: + self.cli_set(base_path + ['master', server]) + + self.cli_commit() + + # commiconf = read_file() Check configured port + conf = read_file(SALT_CONF) + self.assertIn(f' - {server}', conf) + + # defaults + hostname = gethostname() + self.assertIn(f'hash_type: sha256', conf) + self.assertIn(f'id: {hostname}', conf) + self.assertIn(f'mine_interval: 60', conf) + + def test_options(self): + server = '192.0.2.3' + hash = 'sha1' + id = 'foo' + interval = '120' + + self.cli_set(base_path + ['master', server]) + self.cli_set(base_path + ['hash', hash]) + self.cli_set(base_path + ['id', id]) + self.cli_set(base_path + ['interval', interval]) + self.cli_set(base_path + ['source-interface', interface]) + + self.cli_commit() + + # commiconf = read_file() Check configured port + conf = read_file(SALT_CONF) + self.assertIn(f'- {server}', conf) + + # defaults + self.assertIn(f'hash_type: {hash}', conf) + self.assertIn(f'id: {id}', conf) + self.assertIn(f'mine_interval: {interval}', conf) + self.assertIn(f'source_interface_name: {interface}', conf) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_salt.py b/smoketest/scripts/cli/test_service_salt.py deleted file mode 100755 index 48a588b72..000000000 --- a/smoketest/scripts/cli/test_service_salt.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2022-2023 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import unittest - -from socket import gethostname -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.utils.process import process_named_running -from vyos.utils.file import read_file -from vyos.utils.process import cmd - -PROCESS_NAME = 'salt-minion' -SALT_CONF = '/etc/salt/minion' -base_path = ['service', 'salt-minion'] - -interface = 'dum4456' - -class TestServiceSALT(VyOSUnitTestSHIM.TestCase): - @classmethod - def setUpClass(cls): - super(TestServiceSALT, 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, ['interfaces', 'dummy', interface, 'address', '100.64.0.1/16']) - - @classmethod - def tearDownClass(cls): - cls.cli_delete(cls, ['interfaces', 'dummy', interface]) - super(TestServiceSALT, cls).tearDownClass() - - def tearDown(self): - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - # delete testing SALT config - self.cli_delete(base_path) - self.cli_commit() - - # For an unknown reason on QEMU systems (e.g. where smoketests are executed - # from the CI) salt-minion process is not killed by systemd. Apparently - # no issue on VMWare. - if cmd('systemd-detect-virt') != 'kvm': - self.assertFalse(process_named_running(PROCESS_NAME)) - - def test_default(self): - servers = ['192.0.2.1', '192.0.2.2'] - - for server in servers: - self.cli_set(base_path + ['master', server]) - - self.cli_commit() - - # commiconf = read_file() Check configured port - conf = read_file(SALT_CONF) - self.assertIn(f' - {server}', conf) - - # defaults - hostname = gethostname() - self.assertIn(f'hash_type: sha256', conf) - self.assertIn(f'id: {hostname}', conf) - self.assertIn(f'mine_interval: 60', conf) - - def test_options(self): - server = '192.0.2.3' - hash = 'sha1' - id = 'foo' - interval = '120' - - self.cli_set(base_path + ['master', server]) - self.cli_set(base_path + ['hash', hash]) - self.cli_set(base_path + ['id', id]) - self.cli_set(base_path + ['interval', interval]) - self.cli_set(base_path + ['source-interface', interface]) - - self.cli_commit() - - # commiconf = read_file() Check configured port - conf = read_file(SALT_CONF) - self.assertIn(f'- {server}', conf) - - # defaults - self.assertIn(f'hash_type: {hash}', conf) - self.assertIn(f'id: {id}', conf) - self.assertIn(f'mine_interval: {interval}', conf) - self.assertIn(f'source_interface_name: {interface}', conf) - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/src/conf_mode/arp.py b/src/conf_mode/arp.py deleted file mode 100755 index b141f1141..000000000 --- a/src/conf_mode/arp.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2022 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from sys import exit - -from vyos.config import Config -from vyos.configdict import node_changed -from vyos.utils.process import call -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - base = ['protocols', 'static', 'arp'] - arp = conf.get_config_dict(base, get_first_key=True) - - if 'interface' in arp: - for interface in arp['interface']: - tmp = node_changed(conf, base + ['interface', interface, 'address'], recursive=True) - if tmp: arp['interface'][interface].update({'address_old' : tmp}) - - return arp - -def verify(arp): - pass - -def generate(arp): - pass - -def apply(arp): - if not arp: - return None - - if 'interface' in arp: - for interface, interface_config in arp['interface'].items(): - # Delete old static ARP assignments first - if 'address_old' in interface_config: - for address in interface_config['address_old']: - call(f'ip neigh del {address} dev {interface}') - - # Add new static ARP entries to interface - if 'address' not in interface_config: - continue - for address, address_config in interface_config['address'].items(): - mac = address_config['mac'] - call(f'ip neigh replace {address} lladdr {mac} dev {interface}') - -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/bcast_relay.py b/src/conf_mode/bcast_relay.py deleted file mode 100755 index 31c552f5a..000000000 --- a/src/conf_mode/bcast_relay.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2017-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 . - -import os - -from glob import glob -from netifaces import AF_INET -from sys import exit - -from vyos.config import Config -from vyos.configverify import verify_interface_exists -from vyos.template import render -from vyos.utils.process import call -from vyos.utils.network import is_afi_configured -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -config_file_base = r'/etc/default/udp-broadcast-relay' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['service', 'broadcast-relay'] - - relay = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - return relay - -def verify(relay): - if not relay or 'disabled' in relay: - return None - - for instance, config in relay.get('id', {}).items(): - # we don't have to check this instance when it's disabled - if 'disabled' in config: - continue - - # we certainly require a UDP port to listen to - if 'port' not in config: - raise ConfigError(f'Port number is mandatory for UDP broadcast relay "{instance}"') - - # Relaying data without two interface is kinda senseless ... - if len(config.get('interface', [])) < 2: - raise ConfigError('At least two interfaces are required for UDP broadcast relay "{instance}"') - - for interface in config.get('interface', []): - verify_interface_exists(interface) - if not is_afi_configured(interface, AF_INET): - raise ConfigError(f'Interface "{interface}" has no IPv4 address configured!') - - return None - -def generate(relay): - if not relay or 'disabled' in relay: - return None - - for config in glob(config_file_base + '*'): - os.remove(config) - - for instance, config in relay.get('id').items(): - # we don't have to check this instance when it's disabled - if 'disabled' in config: - continue - - config['instance'] = instance - render(config_file_base + instance, 'bcast-relay/udp-broadcast-relay.j2', - config) - - return None - -def apply(relay): - # first stop all running services - call('systemctl stop udp-broadcast-relay@*.service') - - if not relay or 'disable' in relay: - return None - - # start only required service instances - for instance, config in relay.get('id').items(): - # we don't have to check this instance when it's disabled - if 'disabled' in config: - continue - - call(f'systemctl start udp-broadcast-relay@{instance}.service') - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/config_mgmt.py b/src/conf_mode/config_mgmt.py deleted file mode 100755 index c681a8405..000000000 --- a/src/conf_mode/config_mgmt.py +++ /dev/null @@ -1,96 +0,0 @@ -#!/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 . - -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/conntrack.py b/src/conf_mode/conntrack.py deleted file mode 100755 index 7f6c71440..000000000 --- a/src/conf_mode/conntrack.py +++ /dev/null @@ -1,243 +0,0 @@ -#!/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 . - -import os -import re - -from sys import exit - -from vyos.config import Config -from vyos.configdep import set_dependents, call_dependents -from vyos.utils.process import process_named_running -from vyos.utils.dict import dict_search -from vyos.utils.dict import dict_search_args -from vyos.utils.dict import dict_search_recursive -from vyos.utils.process import cmd -from vyos.utils.process import rc_cmd -from vyos.utils.process import run -from vyos.template import render -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -conntrack_config = r'/etc/modprobe.d/vyatta_nf_conntrack.conf' -sysctl_file = r'/run/sysctl/10-vyos-conntrack.conf' -nftables_ct_file = r'/run/nftables-ct.conf' - -# Every ALG (Application Layer Gateway) consists of either a Kernel Object -# also called a Kernel Module/Driver or some rules present in iptables -module_map = { - 'ftp': { - 'ko': ['nf_nat_ftp', 'nf_conntrack_ftp'], - 'nftables': ['ct helper set "ftp_tcp" tcp dport {21} return'] - }, - 'h323': { - 'ko': ['nf_nat_h323', 'nf_conntrack_h323'], - 'nftables': ['ct helper set "ras_udp" udp dport {1719} return', - 'ct helper set "q931_tcp" tcp dport {1720} return'] - }, - 'nfs': { - 'nftables': ['ct helper set "rpc_tcp" tcp dport {111} return', - 'ct helper set "rpc_udp" udp dport {111} return'] - }, - 'pptp': { - 'ko': ['nf_nat_pptp', 'nf_conntrack_pptp'], - 'nftables': ['ct helper set "pptp_tcp" tcp dport {1723} return'], - 'ipv4': True - }, - 'sip': { - 'ko': ['nf_nat_sip', 'nf_conntrack_sip'], - 'nftables': ['ct helper set "sip_tcp" tcp dport {5060,5061} return', - 'ct helper set "sip_udp" udp dport {5060,5061} return'] - }, - 'sqlnet': { - 'nftables': ['ct helper set "tns_tcp" tcp dport {1521,1525,1536} return'] - }, - 'tftp': { - 'ko': ['nf_nat_tftp', 'nf_conntrack_tftp'], - 'nftables': ['ct helper set "tftp_udp" udp dport {69} return'] - }, -} - -valid_groups = [ - 'address_group', - 'domain_group', - 'network_group', - 'port_group' -] - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['system', 'conntrack'] - - conntrack = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - - conntrack['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - conntrack['ipv4_nat_action'] = 'accept' if conf.exists(['nat']) else 'return' - conntrack['ipv6_nat_action'] = 'accept' if conf.exists(['nat66']) else 'return' - conntrack['wlb_action'] = 'accept' if conf.exists(['load-balancing', 'wan']) else 'return' - conntrack['wlb_local_action'] = conf.exists(['load-balancing', 'wan', 'enable-local-traffic']) - - conntrack['module_map'] = module_map - - if conf.exists(['service', 'conntrack-sync']): - set_dependents('conntrack_sync', conf) - - return conntrack - -def verify(conntrack): - for inet in ['ipv4', 'ipv6']: - if dict_search_args(conntrack, 'ignore', inet, 'rule') != None: - for rule, rule_config in conntrack['ignore'][inet]['rule'].items(): - if dict_search('destination.port', rule_config) or \ - dict_search('destination.group.port_group', rule_config) or \ - dict_search('source.port', rule_config) or \ - dict_search('source.group.port_group', rule_config): - if 'protocol' not in rule_config or rule_config['protocol'] not in ['tcp', 'udp']: - raise ConfigError(f'Port requires tcp or udp as protocol in rule {rule}') - - tcp_flags = dict_search_args(rule_config, 'tcp', 'flags') - if tcp_flags: - if dict_search_args(rule_config, 'protocol') != 'tcp': - raise ConfigError('Protocol must be tcp when specifying tcp flags') - - not_flags = dict_search_args(rule_config, 'tcp', 'flags', 'not') - if not_flags: - duplicates = [flag for flag in tcp_flags if flag in not_flags] - if duplicates: - raise ConfigError(f'Cannot match a tcp flag as set and not set') - - for side in ['destination', 'source']: - if side in rule_config: - side_conf = rule_config[side] - - if 'group' in side_conf: - if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1: - raise ConfigError('Only one address-group, network-group or domain-group can be specified') - - for group in valid_groups: - if group in side_conf['group']: - group_name = side_conf['group'][group] - error_group = group.replace("_", "-") - - if group in ['address_group', 'network_group', 'domain_group']: - if 'address' in side_conf: - raise ConfigError(f'{error_group} and address cannot both be defined') - - if group_name and group_name[0] == '!': - group_name = group_name[1:] - - if inet == 'ipv6': - group = f'ipv6_{group}' - - group_obj = dict_search_args(conntrack['firewall'], 'group', group, group_name) - - if group_obj is None: - raise ConfigError(f'Invalid {error_group} "{group_name}" on ignore rule') - - if not group_obj: - Warning(f'{error_group} "{group_name}" has no members!') - - if dict_search_args(conntrack, 'timeout', 'custom', inet, 'rule') != None: - for rule, rule_config in conntrack['timeout']['custom'][inet]['rule'].items(): - if 'protocol' not in rule_config: - raise ConfigError(f'Conntrack custom timeout rule {rule} requires protocol tcp or udp') - else: - if 'tcp' in rule_config['protocol'] and 'udp' in rule_config['protocol']: - raise ConfigError(f'conntrack custom timeout rule {rule} - Cant use both tcp and udp protocol') - return None - -def generate(conntrack): - if not os.path.exists(nftables_ct_file): - conntrack['first_install'] = True - - # Determine if conntrack is needed - conntrack['ipv4_firewall_action'] = 'return' - conntrack['ipv6_firewall_action'] = 'return' - - for rules, path in dict_search_recursive(conntrack['firewall'], 'rule'): - if any(('state' in rule_conf or 'connection_status' in rule_conf or 'offload_target' in rule_conf) for rule_conf in rules.values()): - if path[0] == 'ipv4': - conntrack['ipv4_firewall_action'] = 'accept' - elif path[0] == 'ipv6': - conntrack['ipv6_firewall_action'] = 'accept' - - render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.j2', conntrack) - render(sysctl_file, 'conntrack/sysctl.conf.j2', conntrack) - render(nftables_ct_file, 'conntrack/nftables-ct.j2', conntrack) - return None - -def apply(conntrack): - # Depending on the enable/disable state of the ALG (Application Layer Gateway) - # modules we need to either insmod or rmmod the helpers. - - add_modules = [] - rm_modules = [] - - for module, module_config in module_map.items(): - if dict_search_args(conntrack, 'modules', module) is None: - if 'ko' in module_config: - unloaded = [mod for mod in module_config['ko'] if os.path.exists(f'/sys/module/{mod}')] - rm_modules.extend(unloaded) - else: - if 'ko' in module_config: - add_modules.extend(module_config['ko']) - - # Add modules before nftables uses them - if add_modules: - module_str = ' '.join(add_modules) - cmd(f'modprobe -a {module_str}') - - # Load new nftables ruleset - install_result, output = rc_cmd(f'nft -f {nftables_ct_file}') - if install_result == 1: - raise ConfigError(f'Failed to apply configuration: {output}') - - # Remove modules after nftables stops using them - if rm_modules: - module_str = ' '.join(rm_modules) - cmd(f'rmmod {module_str}') - - try: - call_dependents() - except ConfigError: - # Ignore config errors on dependent due to being called too early. Example: - # ConfigError("ConfigError('Interface ethN requires an IP address!')") - pass - - # We silently ignore all errors - # See: https://bugzilla.redhat.com/show_bug.cgi?id=1264080 - cmd(f'sysctl -f {sysctl_file}') - - 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/conntrack_sync.py b/src/conf_mode/conntrack_sync.py deleted file mode 100755 index 4fb2ce27f..000000000 --- a/src/conf_mode/conntrack_sync.py +++ /dev/null @@ -1,141 +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 . - -import os - -from sys import exit -from vyos.config import Config -from vyos.configverify import verify_interface_exists -from vyos.utils.dict import dict_search -from vyos.utils.process import process_named_running -from vyos.utils.file import read_file -from vyos.utils.process import call -from vyos.utils.process import run -from vyos.template import render -from vyos.template import get_ipv4 -from vyos.utils.network import is_addr_assigned -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -config_file = '/run/conntrackd/conntrackd.conf' - -def resync_vrrp(): - tmp = run('/usr/libexec/vyos/conf_mode/high-availability.py') - if tmp > 0: - print('ERROR: error restarting VRRP daemon!') - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['service', 'conntrack-sync'] - if not conf.exists(base): - return None - - conntrack = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, with_defaults=True) - - conntrack['hash_size'] = read_file('/sys/module/nf_conntrack/parameters/hashsize') - conntrack['table_size'] = read_file('/proc/sys/net/netfilter/nf_conntrack_max') - - conntrack['vrrp'] = conf.get_config_dict(['high-availability', 'vrrp', 'sync-group'], - get_first_key=True) - - return conntrack - -def verify(conntrack): - if not conntrack: - return None - - if 'interface' not in conntrack: - raise ConfigError('Interface not defined!') - - has_peer = False - for interface, interface_config in conntrack['interface'].items(): - verify_interface_exists(interface) - # Interface must not only exist, it must also carry an IP address - if len(get_ipv4(interface)) < 1: - raise ConfigError(f'Interface {interface} requires an IP address!') - if 'peer' in interface_config: - has_peer = True - - # If one interface runs in unicast mode instead of multicast, so must all the - # others, else conntrackd will error out with: "cannot use UDP with other - # dedicated link protocols" - if has_peer: - for interface, interface_config in conntrack['interface'].items(): - if 'peer' not in interface_config: - raise ConfigError('Can not mix unicast and multicast mode!') - - if 'expect_sync' in conntrack: - if len(conntrack['expect_sync']) > 1 and 'all' in conntrack['expect_sync']: - raise ConfigError('Can not configure expect-sync "all" with other protocols!') - - if 'listen_address' in conntrack: - for address in conntrack['listen_address']: - if not is_addr_assigned(address): - raise ConfigError(f'Specified listen-address {address} not assigned to any interface!') - - vrrp_group = dict_search('failover_mechanism.vrrp.sync_group', conntrack) - if vrrp_group == None: - raise ConfigError(f'No VRRP sync-group defined!') - if vrrp_group not in conntrack['vrrp']: - raise ConfigError(f'VRRP sync-group {vrrp_group} not configured!') - - return None - -def generate(conntrack): - if not conntrack: - if os.path.isfile(config_file): - os.unlink(config_file) - return None - - render(config_file, 'conntrackd/conntrackd.conf.j2', conntrack) - - return None - -def apply(conntrack): - systemd_service = 'conntrackd.service' - if not conntrack: - # Failover mechanism daemon should be indicated that it no longer needs - # to execute conntrackd actions on transition. This is only required - # once when conntrackd is stopped and taken out of service! - if process_named_running('conntrackd'): - resync_vrrp() - - call(f'systemctl stop {systemd_service}') - return None - - # Failover mechanism daemon should be indicated that it needs to execute - # conntrackd actions on transition. This is only required once when conntrackd - # is started the first time! - if not process_named_running('conntrackd'): - resync_vrrp() - - call(f'systemctl reload-or-restart {systemd_service}') - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py deleted file mode 100755 index 37d708847..000000000 --- a/src/conf_mode/dhcp_relay.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2020 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from sys import exit - -from vyos.base import Warning -from vyos.config import Config -from vyos.template import render -from vyos.base import Warning -from vyos.utils.process import call -from vyos.utils.dict import dict_search -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -config_file = r'/run/dhcp-relay/dhcrelay.conf' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['service', 'dhcp-relay'] - if not conf.exists(base): - return None - - relay = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - - return relay - -def verify(relay): - # bail out early - looks like removal from running config - if not relay or 'disable' in relay: - return None - - if 'lo' in (dict_search('interface', relay) or []): - raise ConfigError('DHCP relay does not support the loopback interface.') - - if 'server' not in 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(' configuration is not compatible with upstream/listen interface') - else: - Warning(' is going to be deprecated.\n' \ - 'Please use and ') - - 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): - # bail out early - looks like removal from running config - if not relay or 'disable' in relay: - return None - - render(config_file, 'dhcp-relay/dhcrelay.conf.j2', relay) - return None - -def apply(relay): - # bail out early - looks like removal from running config - service_name = 'isc-dhcp-relay.service' - if not relay or 'disable' in relay: - call(f'systemctl stop {service_name}') - if os.path.exists(config_file): - os.unlink(config_file) - return None - - call(f'systemctl restart {service_name}') - - 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/dhcp_server.py b/src/conf_mode/dhcp_server.py deleted file mode 100755 index 7ebc560ba..000000000 --- a/src/conf_mode/dhcp_server.py +++ /dev/null @@ -1,385 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from ipaddress import ip_address -from ipaddress import ip_network -from netaddr import IPRange -from sys import exit - -from vyos.config import Config -from vyos.pki import wrap_certificate -from vyos.pki import wrap_private_key -from vyos.template import render -from vyos.utils.dict import dict_search -from vyos.utils.dict import dict_search_args -from vyos.utils.file import chmod_775 -from vyos.utils.file import makedir -from vyos.utils.file import write_file -from vyos.utils.process import call -from vyos.utils.network import is_subnet_connected -from vyos.utils.network import is_addr_assigned -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -ctrl_config_file = '/run/kea/kea-ctrl-agent.conf' -ctrl_socket = '/run/kea/dhcp4-ctrl-socket' -config_file = '/run/kea/kea-dhcp4.conf' -lease_file = '/config/dhcp/dhcp4-leases.csv' -systemd_override = r'/run/systemd/system/kea-ctrl-agent.service.d/10-override.conf' -user_group = '_kea' - -ca_cert_file = '/run/kea/kea-failover-ca.pem' -cert_file = '/run/kea/kea-failover.pem' -cert_key_file = '/run/kea/kea-failover-key.pem' - -def dhcp_slice_range(exclude_list, range_dict): - """ - This function is intended to slice a DHCP range. What does it mean? - - Lets assume we have a DHCP range from '192.0.2.1' to '192.0.2.100' - but want to exclude address '192.0.2.74' and '192.0.2.75'. We will - pass an input 'range_dict' in the format: - {'start' : '192.0.2.1', 'stop' : '192.0.2.100' } - and we will receive an output list of: - [{'start' : '192.0.2.1' , 'stop' : '192.0.2.73' }, - {'start' : '192.0.2.76', 'stop' : '192.0.2.100' }] - The resulting list can then be used in turn to build the proper dhcpd - configuration file. - """ - output = [] - # exclude list must be sorted for this to work - exclude_list = sorted(exclude_list) - range_start = range_dict['start'] - range_stop = range_dict['stop'] - range_last_exclude = '' - - for e in exclude_list: - if (ip_address(e) >= ip_address(range_start)) and \ - (ip_address(e) <= ip_address(range_stop)): - range_last_exclude = e - - for e in exclude_list: - if (ip_address(e) >= ip_address(range_start)) and \ - (ip_address(e) <= ip_address(range_stop)): - - # Build new address range ending one address before exclude address - r = { - 'start' : range_start, - 'stop' : str(ip_address(e) -1) - } - # On the next run our address range will start one address after - # the exclude address - range_start = str(ip_address(e) + 1) - - # on subsequent exclude addresses we can not - # append them to our output - if not (ip_address(r['start']) > ip_address(r['stop'])): - # Everything is fine, add range to result - output.append(r) - - # Take care of last IP address range spanning from the last exclude - # address (+1) to the end of the initial configured range - if ip_address(e) == ip_address(range_last_exclude): - r = { - 'start': str(ip_address(e) + 1), - 'stop': str(range_stop) - } - if not (ip_address(r['start']) > ip_address(r['stop'])): - output.append(r) - else: - # if the excluded address was not part of the range, we simply return - # the entire ranga again - if not range_last_exclude: - if range_dict not in output: - output.append(range_dict) - - return output - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['service', 'dhcp-server'] - if not conf.exists(base): - return None - - dhcp = conf.get_config_dict(base, key_mangling=('-', '_'), - no_tag_node_value_mangle=True, - get_first_key=True, - with_recursive_defaults=True) - - if 'shared_network_name' in dhcp: - for network, network_config in dhcp['shared_network_name'].items(): - if 'subnet' in network_config: - for subnet, subnet_config in network_config['subnet'].items(): - # If exclude IP addresses are defined we need to slice them out of - # the defined ranges - if {'exclude', 'range'} <= set(subnet_config): - new_range_id = 0 - new_range_dict = {} - for r, r_config in subnet_config['range'].items(): - for slice in dhcp_slice_range(subnet_config['exclude'], r_config): - new_range_dict.update({new_range_id : slice}) - new_range_id +=1 - - dhcp['shared_network_name'][network]['subnet'][subnet].update( - {'range' : new_range_dict}) - - if dict_search('failover.certificate', dhcp): - dhcp['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - - return dhcp - -def verify(dhcp): - # bail out early - looks like removal from running config - if not dhcp or 'disable' in dhcp: - return None - - # If DHCP is enabled we need one share-network - if 'shared_network_name' not in dhcp: - raise ConfigError('No DHCP shared networks configured.\n' \ - 'At least one DHCP shared network must be configured.') - - # Inspect shared-network/subnet - listen_ok = False - subnets = [] - failover_ok = False - shared_networks = len(dhcp['shared_network_name']) - disabled_shared_networks = 0 - - - # A shared-network requires a subnet definition - for network, network_config in dhcp['shared_network_name'].items(): - if 'disable' in network_config: - disabled_shared_networks += 1 - - if 'subnet' not in network_config: - raise ConfigError(f'No subnets defined for {network}. At least one\n' \ - 'lease subnet must be configured.') - - for subnet, subnet_config in network_config['subnet'].items(): - # All delivered static routes require a next-hop to be set - if 'static_route' in subnet_config: - for route, route_option in subnet_config['static_route'].items(): - if 'next_hop' not in route_option: - raise ConfigError(f'DHCP static-route "{route}" requires router to be defined!') - - # Check if DHCP address range is inside configured subnet declaration - if 'range' in subnet_config: - networks = [] - for range, range_config in subnet_config['range'].items(): - if not {'start', 'stop'} <= set(range_config): - raise ConfigError(f'DHCP range "{range}" start and stop address must be defined!') - - # Start/Stop address must be inside network - for key in ['start', 'stop']: - if ip_address(range_config[key]) not in ip_network(subnet): - raise ConfigError(f'DHCP range "{range}" {key} address not within shared-network "{network}, {subnet}"!') - - # Stop address must be greater or equal to start address - if ip_address(range_config['stop']) < ip_address(range_config['start']): - raise ConfigError(f'DHCP range "{range}" stop address must be greater or equal\n' \ - 'to the ranges start address!') - - for network in networks: - start = range_config['start'] - stop = range_config['stop'] - if start in network: - raise ConfigError(f'Range "{range}" start address "{start}" already part of another range!') - if stop in network: - raise ConfigError(f'Range "{range}" stop address "{stop}" already part of another range!') - - tmp = IPRange(range_config['start'], range_config['stop']) - networks.append(tmp) - - # Exclude addresses must be in bound - if 'exclude' in subnet_config: - for exclude in subnet_config['exclude']: - if ip_address(exclude) not in ip_network(subnet): - raise ConfigError(f'Excluded IP address "{exclude}" not within shared-network "{network}, {subnet}"!') - - # At least one DHCP address range or static-mapping required - if 'range' not in subnet_config and 'static_mapping' not in subnet_config: - raise ConfigError(f'No DHCP address range or active static-mapping configured\n' \ - f'within shared-network "{network}, {subnet}"!') - - if 'static_mapping' in subnet_config: - # Static mappings require just a MAC address (will use an IP from the dynamic pool if IP is not set) - for mapping, mapping_config in subnet_config['static_mapping'].items(): - if 'ip_address' in mapping_config: - if ip_address(mapping_config['ip_address']) not in ip_network(subnet): - raise ConfigError(f'Configured static lease address for mapping "{mapping}" is\n' \ - f'not within shared-network "{network}, {subnet}"!') - - if ('mac' not in mapping_config and 'duid' not in mapping_config) or \ - ('mac' in mapping_config and 'duid' in mapping_config): - raise ConfigError(f'Either MAC address or Client identifier (DUID) is required for ' - f'static mapping "{mapping}" within shared-network "{network}, {subnet}"!') - - # There must be one subnet connected to a listen interface. - # This only counts if the network itself is not disabled! - if 'disable' not in network_config: - if is_subnet_connected(subnet, primary=False): - listen_ok = True - - # Subnets must be non overlapping - if subnet in subnets: - raise ConfigError(f'Configured subnets must be unique! Subnet "{subnet}"\n' - 'defined multiple times!') - subnets.append(subnet) - - # Check for overlapping subnets - net = ip_network(subnet) - for n in subnets: - net2 = ip_network(n) - if (net != net2): - if 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: - raise ConfigError(f'At least one shared network must be active!') - - if 'failover' in dhcp: - for key in ['name', 'remote', 'source_address', 'status']: - if key not in dhcp['failover']: - tmp = key.replace('_', '-') - raise ConfigError(f'DHCP failover requires "{tmp}" to be specified!') - - if len({'certificate', 'ca_certificate'} & set(dhcp['failover'])) == 1: - raise ConfigError(f'DHCP secured failover requires both certificate and CA certificate') - - if 'certificate' in dhcp['failover']: - cert_name = dhcp['failover']['certificate'] - - if cert_name not in dhcp['pki']['certificate']: - raise ConfigError(f'Invalid certificate specified for DHCP failover') - - if not dict_search_args(dhcp['pki']['certificate'], cert_name, 'certificate'): - raise ConfigError(f'Invalid certificate specified for DHCP failover') - - if not dict_search_args(dhcp['pki']['certificate'], cert_name, 'private', 'key'): - raise ConfigError(f'Missing private key on certificate specified for DHCP failover') - - if 'ca_certificate' in dhcp['failover']: - ca_cert_name = dhcp['failover']['ca_certificate'] - if ca_cert_name not in dhcp['pki']['ca']: - raise ConfigError(f'Invalid CA certificate specified for DHCP failover') - - if not dict_search_args(dhcp['pki']['ca'], ca_cert_name, 'certificate'): - raise ConfigError(f'Invalid CA certificate specified for DHCP failover') - - for address in (dict_search('listen_address', dhcp) or []): - if is_addr_assigned(address): - listen_ok = True - # no need to probe further networks, we have one that is valid - continue - else: - raise ConfigError(f'listen-address "{address}" not configured on any interface') - - - if not listen_ok: - raise ConfigError('None of the configured subnets have an appropriate primary IP address on any\n' - 'broadcast interface configured, nor was there an explicit listen-address\n' - 'configured for serving DHCP relay packets!') - - return None - -def generate(dhcp): - # bail out early - looks like removal from running config - if not dhcp or 'disable' in dhcp: - return None - - dhcp['lease_file'] = lease_file - dhcp['machine'] = os.uname().machine - - # Create directory for lease file if necessary - lease_dir = os.path.dirname(lease_file) - if not os.path.isdir(lease_dir): - makedir(lease_dir, group='vyattacfg') - chmod_775(lease_dir) - - # Create lease file if necessary and let kea own it - 'kea-lfc' expects it that way - if not os.path.exists(lease_file): - write_file(lease_file, '', user=user_group, group=user_group, mode=0o644) - - for f in [cert_file, cert_key_file, ca_cert_file]: - if os.path.exists(f): - os.unlink(f) - - if 'failover' in dhcp: - if 'certificate' in dhcp['failover']: - cert_name = dhcp['failover']['certificate'] - cert_data = dhcp['pki']['certificate'][cert_name]['certificate'] - key_data = dhcp['pki']['certificate'][cert_name]['private']['key'] - write_file(cert_file, wrap_certificate(cert_data), user=user_group, mode=0o600) - write_file(cert_key_file, wrap_private_key(key_data), user=user_group, mode=0o600) - - dhcp['failover']['cert_file'] = cert_file - dhcp['failover']['cert_key_file'] = cert_key_file - - if 'ca_certificate' in dhcp['failover']: - ca_cert_name = dhcp['failover']['ca_certificate'] - ca_cert_data = dhcp['pki']['ca'][ca_cert_name]['certificate'] - write_file(ca_cert_file, wrap_certificate(ca_cert_data), user=user_group, mode=0o600) - - dhcp['failover']['ca_cert_file'] = ca_cert_file - - render(systemd_override, 'dhcp-server/10-override.conf.j2', dhcp) - - render(ctrl_config_file, 'dhcp-server/kea-ctrl-agent.conf.j2', dhcp, user=user_group, group=user_group) - render(config_file, 'dhcp-server/kea-dhcp4.conf.j2', dhcp, user=user_group, group=user_group) - - return None - -def apply(dhcp): - services = ['kea-ctrl-agent', 'kea-dhcp4-server', 'kea-dhcp-ddns-server'] - - if not dhcp or 'disable' in dhcp: - for service in services: - call(f'systemctl stop {service}.service') - - if os.path.exists(config_file): - os.unlink(config_file) - - return None - - for service in services: - action = 'restart' - - if service == 'kea-dhcp-ddns-server' and 'dynamic_dns_update' not in dhcp: - action = 'stop' - - if service == 'kea-ctrl-agent' and 'failover' not in dhcp: - action = 'stop' - - call(f'systemctl {action} {service}.service') - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py deleted file mode 100755 index 6537ca3c2..000000000 --- a/src/conf_mode/dhcpv6_relay.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2020 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from sys import exit - -from vyos.config import Config -from vyos.ifconfig import Interface -from vyos.template import render -from vyos.template import is_ipv6 -from vyos.utils.process import call -from vyos.utils.network import is_ipv6_link_local -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -config_file = '/run/dhcp-relay/dhcrelay6.conf' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['service', 'dhcpv6-relay'] - if not conf.exists(base): - return None - - relay = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - - return relay - -def verify(relay): - # bail out early - looks like removal from running config - if not relay or 'disable' in relay: - return None - - if 'upstream_interface' not in relay: - raise ConfigError('At least one upstream interface required!') - for interface, config in relay['upstream_interface'].items(): - if 'address' not in config: - raise ConfigError('DHCPv6 server required for upstream ' \ - f'interface {interface}!') - - if 'listen_interface' not in relay: - raise ConfigError('At least one listen interface required!') - - # DHCPv6 relay requires at least one global unicat address assigned to the - # interface - for interface in relay['listen_interface']: - has_global = False - for addr in Interface(interface).get_addr(): - if is_ipv6(addr) and not is_ipv6_link_local(addr): - has_global = True - if not has_global: - raise ConfigError(f'Interface {interface} does not have global '\ - 'IPv6 address assigned!') - - return None - -def generate(relay): - # bail out early - looks like removal from running config - if not relay or 'disable' in relay: - return None - - render(config_file, 'dhcp-relay/dhcrelay6.conf.j2', relay) - return None - -def apply(relay): - # bail out early - looks like removal from running config - service_name = 'isc-dhcp-relay6.service' - if not relay or 'disable' in relay: - # DHCPv6 relay support is removed in the commit - call(f'systemctl stop {service_name}') - if os.path.exists(config_file): - os.unlink(config_file) - return None - - call(f'systemctl restart {service_name}') - - 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/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py deleted file mode 100755 index 9cc57dbcf..000000000 --- a/src/conf_mode/dhcpv6_server.py +++ /dev/null @@ -1,222 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from ipaddress import ip_address -from ipaddress import ip_network -from sys import exit - -from vyos.config import Config -from vyos.template import render -from vyos.utils.process import call -from vyos.utils.file import chmod_775 -from vyos.utils.file import makedir -from vyos.utils.file import write_file -from vyos.utils.dict import dict_search -from vyos.utils.network import is_subnet_connected -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -config_file = '/run/kea/kea-dhcp6.conf' -ctrl_socket = '/run/kea/dhcp6-ctrl-socket' -lease_file = '/config/dhcp/dhcp6-leases.csv' -user_group = '_kea' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['service', 'dhcpv6-server'] - if not conf.exists(base): - return None - - dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - return dhcpv6 - -def verify(dhcpv6): - # bail out early - looks like removal from running config - if not dhcpv6 or 'disable' in dhcpv6: - return None - - # If DHCP is enabled we need one share-network - if 'shared_network_name' not in dhcpv6: - raise ConfigError('No DHCPv6 shared networks configured. At least '\ - 'one DHCPv6 shared network must be configured.') - - # Inspect shared-network/subnet - subnets = [] - listen_ok = False - for network, network_config in dhcpv6['shared_network_name'].items(): - # A shared-network requires a subnet definition - if 'subnet' not in network_config: - raise ConfigError(f'No DHCPv6 lease subnets configured for "{network}". '\ - 'At least one lease subnet must be configured for '\ - 'each shared network!') - - for subnet, subnet_config in network_config['subnet'].items(): - if 'address_range' in subnet_config: - if 'start' in subnet_config['address_range']: - range6_start = [] - range6_stop = [] - for start, start_config in subnet_config['address_range']['start'].items(): - if 'stop' not in start_config: - raise ConfigError(f'address-range stop address for start "{start}" is not defined!') - stop = start_config['stop'] - - # Start address must be inside network - if not ip_address(start) in ip_network(subnet): - raise ConfigError(f'address-range start address "{start}" is not in subnet "{subnet}"!') - - # Stop address must be inside network - if not ip_address(stop) in ip_network(subnet): - raise ConfigError(f'address-range stop address "{stop}" is not in subnet "{subnet}"!') - - # Stop address must be greater or equal to start address - if not ip_address(stop) >= ip_address(start): - raise ConfigError(f'address-range stop address "{stop}" must be greater then or equal ' \ - f'to the range start address "{start}"!') - - # DHCPv6 range start address must be unique - two ranges can't - # start with the same address - makes no sense - if start in range6_start: - raise ConfigError(f'Conflicting DHCPv6 lease range: '\ - f'Pool start address "{start}" defined multipe times!') - range6_start.append(start) - - # DHCPv6 range stop address must be unique - two ranges can't - # end with the same address - makes no sense - if stop in range6_stop: - raise ConfigError(f'Conflicting DHCPv6 lease range: '\ - f'Pool stop address "{stop}" defined multipe times!') - range6_stop.append(stop) - - if 'prefix' in subnet_config: - for prefix in subnet_config['prefix']: - if ip_network(prefix) not in ip_network(subnet): - raise ConfigError(f'address-range prefix "{prefix}" is not in subnet "{subnet}""') - - # Prefix delegation sanity checks - if 'prefix_delegation' in subnet_config: - if 'prefix' not in subnet_config['prefix_delegation']: - raise ConfigError('prefix-delegation prefix not defined!') - - for prefix, prefix_config in subnet_config['prefix_delegation']['prefix'].items(): - if 'delegated_length' not in prefix_config: - raise ConfigError(f'Delegated IPv6 prefix length for "{prefix}" '\ - f'must be configured') - - if 'prefix_length' not in prefix_config: - raise ConfigError('Length of delegated IPv6 prefix must be configured') - - if prefix_config['prefix_length'] > prefix_config['delegated_length']: - raise ConfigError('Length of delegated IPv6 prefix must be within parent prefix') - - # Static mappings don't require anything (but check if IP is in subnet if it's set) - if 'static_mapping' in subnet_config: - for mapping, mapping_config in subnet_config['static_mapping'].items(): - if 'ipv6_address' in mapping_config: - # Static address must be in subnet - if ip_address(mapping_config['ipv6_address']) not in ip_network(subnet): - raise ConfigError(f'static-mapping address for mapping "{mapping}" is not in subnet "{subnet}"!') - - if ('mac' not in mapping_config and 'duid' not in mapping_config) or \ - ('mac' in mapping_config and 'duid' in mapping_config): - raise ConfigError(f'Either MAC address or Client identifier (DUID) is required for ' - f'static mapping "{mapping}" within shared-network "{network}, {subnet}"!') - - if 'vendor_option' in subnet_config: - if len(dict_search('vendor_option.cisco.tftp_server', subnet_config)) > 2: - raise ConfigError(f'No more then two Cisco tftp-servers should be defined for subnet "{subnet}"!') - - # Subnets must be unique - if subnet in subnets: - raise ConfigError(f'DHCPv6 subnets must be unique! Subnet {subnet} defined multiple times!') - subnets.append(subnet) - - # DHCPv6 requires at least one configured address range or one static mapping - # (FIXME: is not actually checked right now?) - - # There must be one subnet connected to a listen interface if network is not disabled. - if 'disable' not in network_config: - if is_subnet_connected(subnet): - listen_ok = True - - # DHCPv6 subnet must not overlap. ISC DHCP also complains about overlapping - # subnets: "Warning: subnet 2001:db8::/32 overlaps subnet 2001:db8:1::/32" - net = ip_network(subnet) - for n in subnets: - net2 = ip_network(n) - if (net != net2): - if net.overlaps(net2): - raise ConfigError('DHCPv6 conflicting subnet ranges: {0} overlaps {1}'.format(net, net2)) - - if not listen_ok: - raise ConfigError('None of the DHCPv6 subnets are connected to a subnet6 on '\ - 'this machine. At least one subnet6 must be connected such that '\ - 'DHCPv6 listens on an interface!') - - - return None - -def generate(dhcpv6): - # bail out early - looks like removal from running config - if not dhcpv6 or 'disable' in dhcpv6: - return None - - dhcpv6['lease_file'] = lease_file - dhcpv6['machine'] = os.uname().machine - - # Create directory for lease file if necessary - lease_dir = os.path.dirname(lease_file) - if not os.path.isdir(lease_dir): - makedir(lease_dir, group='vyattacfg') - chmod_775(lease_dir) - - # Create lease file if necessary and let kea own it - 'kea-lfc' expects it that way - if not os.path.exists(lease_file): - write_file(lease_file, '', user=user_group, group=user_group, mode=0o644) - - render(config_file, 'dhcp-server/kea-dhcp6.conf.j2', dhcpv6, user=user_group, group=user_group) - return None - -def apply(dhcpv6): - # bail out early - looks like removal from running config - service_name = 'kea-dhcp6-server.service' - if not dhcpv6 or 'disable' in dhcpv6: - # DHCP server is removed in the commit - call(f'systemctl stop {service_name}') - if os.path.exists(config_file): - os.unlink(config_file) - return None - - call(f'systemctl restart {service_name}') - - 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/dns_dynamic.py b/src/conf_mode/dns_dynamic.py deleted file mode 100755 index 99fa8feee..000000000 --- a/src/conf_mode/dns_dynamic.py +++ /dev/null @@ -1,187 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import re -from sys import exit - -from vyos.base import Warning -from vyos.config import Config -from vyos.configverify import verify_interface_exists -from vyos.template import render -from vyos.utils.process import call -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -config_file = r'/run/ddclient/ddclient.conf' -systemd_override = r'/run/systemd/system/ddclient.service.d/override.conf' - -# Dynamic interfaces that might not exist when the configuration is loaded -dynamic_interfaces = ('pppoe', 'sstpc') - -# Protocols that require zone -zone_necessary = ['cloudflare', 'digitalocean', 'godaddy', 'hetzner', 'gandi', - 'nfsn', 'nsupdate'] -zone_supported = zone_necessary + ['dnsexit2', 'zoneedit1'] - -# Protocols that do not require username -username_unnecessary = ['1984', 'cloudflare', 'cloudns', 'digitalocean', 'dnsexit2', - 'duckdns', 'freemyip', 'hetzner', 'keysystems', 'njalla', - 'nsupdate', 'regfishde'] - -# Protocols that support TTL -ttl_supported = ['cloudflare', 'dnsexit2', 'gandi', 'hetzner', 'godaddy', 'nfsn', - 'nsupdate'] - -# Protocols that support both IPv4 and IPv6 -dualstack_supported = ['cloudflare', 'digitalocean', 'dnsexit2', 'duckdns', - 'dyndns2', 'easydns', 'freedns', 'hetzner', 'infomaniak', - 'njalla'] - -# dyndns2 protocol in ddclient honors dual stack for selective servers -# because of the way it is implemented in ddclient -dyndns_dualstack_servers = ['members.dyndns.org', 'dynv6.com'] - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - base = ['service', 'dns', 'dynamic'] - if not conf.exists(base): - return None - - dyndns = conf.get_config_dict(base, key_mangling=('-', '_'), - no_tag_node_value_mangle=True, - get_first_key=True, - with_recursive_defaults=True) - - dyndns['config_file'] = config_file - return dyndns - -def verify(dyndns): - # bail out early - looks like removal from running config - if not dyndns or 'name' not in dyndns: - return None - - # Dynamic DNS service provider - configuration validation - for service, config in dyndns['name'].items(): - - error_msg_req = f'is required for Dynamic DNS service "{service}"' - error_msg_uns = f'is not supported for Dynamic DNS service "{service}"' - - for field in ['protocol', 'address', 'host_name']: - if field not in config: - raise ConfigError(f'"{field.replace("_", "-")}" {error_msg_req}') - - # If dyndns address is an interface, ensure - # that the interface exists (or just warn if dynamic interface) - # and that web-options are not set - if config['address'] != 'web': - # exclude check interface for dynamic interfaces - if config['address'].startswith(dynamic_interfaces): - Warning(f'Interface "{config["address"]}" does not exist yet and cannot ' - f'be used for Dynamic DNS service "{service}" until it is up!') - else: - verify_interface_exists(config['address']) - if 'web_options' in config: - raise ConfigError(f'"web-options" is applicable only when using HTTP(S) ' - f'web request to obtain the IP address') - - # Warn if using checkip.dyndns.org, as it does not support HTTPS - # See: https://github.com/ddclient/ddclient/issues/597 - if 'web_options' in config: - if 'url' not in config['web_options']: - raise ConfigError(f'"url" in "web-options" {error_msg_req} ' - f'with protocol "{config["protocol"]}"') - elif re.search("^(https?://)?checkip\.dyndns\.org", config['web_options']['url']): - Warning(f'"checkip.dyndns.org" does not support HTTPS requests for IP address ' - f'lookup. Please use a different IP address lookup service.') - - # RFC2136 uses 'key' instead of 'password' - if config['protocol'] != 'nsupdate' and 'password' not in config: - raise ConfigError(f'"password" {error_msg_req}') - - # Other RFC2136 specific configuration validation - if config['protocol'] == 'nsupdate': - if 'password' in config: - raise ConfigError(f'"password" {error_msg_uns} with protocol "{config["protocol"]}"') - for field in ['server', 'key']: - if field not in config: - raise ConfigError(f'"{field}" {error_msg_req} with protocol "{config["protocol"]}"') - - if config['protocol'] in zone_necessary and 'zone' not in config: - raise ConfigError(f'"zone" {error_msg_req} with protocol "{config["protocol"]}"') - - if config['protocol'] not in zone_supported and 'zone' in config: - raise ConfigError(f'"zone" {error_msg_uns} with protocol "{config["protocol"]}"') - - if config['protocol'] not in username_unnecessary and 'username' not in config: - raise ConfigError(f'"username" {error_msg_req} with protocol "{config["protocol"]}"') - - if config['protocol'] not in ttl_supported and 'ttl' in config: - raise ConfigError(f'"ttl" {error_msg_uns} with protocol "{config["protocol"]}"') - - if config['ip_version'] == 'both': - if config['protocol'] not in dualstack_supported: - raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} ' - f'with protocol "{config["protocol"]}"') - # dyndns2 protocol in ddclient honors dual stack only for dyn.com (dyndns.org) - if config['protocol'] == 'dyndns2' and 'server' in config and config['server'] not in dyndns_dualstack_servers: - raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} ' - f'for "{config["server"]}" with protocol "{config["protocol"]}"') - - if {'wait_time', 'expiry_time'} <= config.keys() and int(config['expiry_time']) < int(config['wait_time']): - raise ConfigError(f'"expiry-time" must be greater than "wait-time" for ' - f'Dynamic DNS service "{service}"') - - return None - -def generate(dyndns): - # bail out early - looks like removal from running config - if not dyndns or 'name' not in dyndns: - return None - - render(config_file, 'dns-dynamic/ddclient.conf.j2', dyndns, permission=0o600) - render(systemd_override, 'dns-dynamic/override.conf.j2', dyndns) - return None - -def apply(dyndns): - systemd_service = 'ddclient.service' - # Reload systemd manager configuration - call('systemctl daemon-reload') - - # bail out early - looks like removal from running config - if not dyndns or 'name' not in dyndns: - call(f'systemctl stop {systemd_service}') - if os.path.exists(config_file): - os.unlink(config_file) - else: - call(f'systemctl reload-or-restart {systemd_service}') - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py deleted file mode 100755 index c186f47af..000000000 --- a/src/conf_mode/dns_forwarding.py +++ /dev/null @@ -1,358 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2022 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from netifaces import interfaces -from sys import exit -from glob import glob - -from vyos.config import Config -from vyos.hostsd_client import Client as hostsd_client -from vyos.template import render -from vyos.template import bracketize_ipv6 -from vyos.utils.process import call -from vyos.utils.permission import chown -from vyos.utils.dict import dict_search - -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -pdns_rec_user = pdns_rec_group = 'pdns' -pdns_rec_run_dir = '/run/powerdns' -pdns_rec_lua_conf_file = f'{pdns_rec_run_dir}/recursor.conf.lua' -pdns_rec_hostsd_lua_conf_file = f'{pdns_rec_run_dir}/recursor.vyos-hostsd.conf.lua' -pdns_rec_hostsd_zones_file = f'{pdns_rec_run_dir}/recursor.forward-zones.conf' -pdns_rec_config_file = f'{pdns_rec_run_dir}/recursor.conf' - -hostsd_tag = 'static' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['service', 'dns', 'forwarding'] - if not conf.exists(base): - return None - - dns = conf.get_config_dict(base, key_mangling=('-', '_'), - no_tag_node_value_mangle=True, - get_first_key=True, - with_recursive_defaults=True) - - # some additions to the default dictionary - if 'system' in dns: - base_nameservers = ['system', 'name-server'] - if conf.exists(base_nameservers): - dns.update({'system_name_server': conf.return_values(base_nameservers)}) - - if 'authoritative_domain' in dns: - dns['authoritative_zones'] = [] - dns['authoritative_zone_errors'] = [] - for node in dns['authoritative_domain']: - zonedata = dns['authoritative_domain'][node] - if ('disable' in zonedata) or (not 'records' in zonedata): - continue - zone = { - 'name': node, - 'file': "{}/zone.{}.conf".format(pdns_rec_run_dir, node), - 'records': [], - } - - recorddata = zonedata['records'] - - for rtype in [ 'a', 'aaaa', 'cname', 'mx', 'ns', 'ptr', 'txt', 'spf', 'srv', 'naptr' ]: - if rtype not in recorddata: - continue - for subnode in recorddata[rtype]: - if 'disable' in recorddata[rtype][subnode]: - continue - - rdata = recorddata[rtype][subnode] - - if rtype in [ 'a', 'aaaa' ]: - if not 'address' in rdata: - dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one address is required') - continue - - if subnode == 'any': - subnode = '*' - - for address in rdata['address']: - zone['records'].append({ - 'name': subnode, - 'type': rtype.upper(), - 'ttl': rdata['ttl'], - 'value': address - }) - elif rtype in ['cname', 'ptr', 'ns']: - if not 'target' in rdata: - dns['authoritative_zone_errors'].append(f'{subnode}.{node}: target is required') - continue - - zone['records'].append({ - 'name': subnode, - 'type': rtype.upper(), - 'ttl': rdata['ttl'], - 'value': '{}.'.format(rdata['target']) - }) - elif rtype == 'mx': - if not 'server' in rdata: - dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one server is required') - continue - - for servername in rdata['server']: - serverdata = rdata['server'][servername] - zone['records'].append({ - 'name': subnode, - 'type': rtype.upper(), - 'ttl': rdata['ttl'], - 'value': '{} {}.'.format(serverdata['priority'], servername) - }) - elif rtype == 'txt': - if not 'value' in rdata: - dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one value is required') - continue - - for value in rdata['value']: - zone['records'].append({ - 'name': subnode, - 'type': rtype.upper(), - 'ttl': rdata['ttl'], - 'value': "\"{}\"".format(value.replace("\"", "\\\"")) - }) - elif rtype == 'spf': - if not 'value' in rdata: - dns['authoritative_zone_errors'].append(f'{subnode}.{node}: value is required') - continue - - zone['records'].append({ - 'name': subnode, - 'type': rtype.upper(), - 'ttl': rdata['ttl'], - 'value': '"{}"'.format(rdata['value'].replace("\"", "\\\"")) - }) - elif rtype == 'srv': - if not 'entry' in rdata: - dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one entry is required') - continue - - for entryno in rdata['entry']: - entrydata = rdata['entry'][entryno] - if not 'hostname' in entrydata: - dns['authoritative_zone_errors'].append(f'{subnode}.{node}: hostname is required for entry {entryno}') - continue - - if not 'port' in entrydata: - dns['authoritative_zone_errors'].append(f'{subnode}.{node}: port is required for entry {entryno}') - continue - - zone['records'].append({ - 'name': subnode, - 'type': rtype.upper(), - 'ttl': rdata['ttl'], - 'value': '{} {} {} {}.'.format(entrydata['priority'], entrydata['weight'], entrydata['port'], entrydata['hostname']) - }) - elif rtype == 'naptr': - if not 'rule' in rdata: - dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one rule is required') - continue - - for ruleno in rdata['rule']: - ruledata = rdata['rule'][ruleno] - flags = "" - if 'lookup-srv' in ruledata: - flags += "S" - if 'lookup-a' in ruledata: - flags += "A" - if 'resolve-uri' in ruledata: - flags += "U" - if 'protocol-specific' in ruledata: - flags += "P" - - if 'order' in ruledata: - order = ruledata['order'] - else: - order = ruleno - - if 'regexp' in ruledata: - regexp= ruledata['regexp'].replace("\"", "\\\"") - else: - regexp = '' - - if ruledata['replacement']: - replacement = '{}.'.format(ruledata['replacement']) - else: - replacement = '' - - zone['records'].append({ - 'name': subnode, - 'type': rtype.upper(), - 'ttl': rdata['ttl'], - 'value': '{} {} "{}" "{}" "{}" {}'.format(order, ruledata['preference'], flags, ruledata['service'], regexp, replacement) - }) - - dns['authoritative_zones'].append(zone) - - return dns - -def verify(dns): - # bail out early - looks like removal from running config - if not dns: - return None - - if 'listen_address' not in dns: - raise ConfigError('DNS forwarding requires a listen-address') - - if 'allow_from' not in dns: - raise ConfigError('DNS forwarding requires an allow-from network') - - # we can not use dict_search() when testing for domain servers - # as a domain will contains dot's which is out dictionary delimiter. - if 'domain' in dns: - for domain in dns['domain']: - if 'name_server' not in dns['domain'][domain]: - raise ConfigError(f'No server configured for domain {domain}!') - - if 'dns64_prefix' in dns: - dns_prefix = dns['dns64_prefix'].split('/')[1] - # RFC 6147 requires prefix /96 - if int(dns_prefix) != 96: - raise ConfigError('DNS 6to4 prefix must be of length /96') - - if ('authoritative_zone_errors' in dns) and dns['authoritative_zone_errors']: - for error in dns['authoritative_zone_errors']: - print(error) - raise ConfigError('Invalid authoritative records have been defined') - - if 'system' in dns: - if not 'system_name_server' in dns: - print('Warning: No "system name-server" configured') - - return None - -def generate(dns): - # bail out early - looks like removal from running config - if not dns: - return None - - render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.j2', - dns, user=pdns_rec_user, group=pdns_rec_group) - - render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.j2', - dns, user=pdns_rec_user, group=pdns_rec_group) - - for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'): - os.unlink(zone_filename) - - if 'authoritative_zones' in dns: - for zone in dns['authoritative_zones']: - render(zone['file'], 'dns-forwarding/recursor.zone.conf.j2', - zone, user=pdns_rec_user, group=pdns_rec_group) - - - # if vyos-hostsd didn't create its files yet, create them (empty) - for file in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]: - with open(file, 'a'): - pass - chown(file, user=pdns_rec_user, group=pdns_rec_group) - - return None - -def apply(dns): - if not dns: - # DNS forwarding is removed in the commit - call('systemctl stop pdns-recursor.service') - - if os.path.isfile(pdns_rec_config_file): - os.unlink(pdns_rec_config_file) - - for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'): - os.unlink(zone_filename) - else: - ### first apply vyos-hostsd config - hc = hostsd_client() - - # add static nameservers to hostsd so they can be joined with other - # sources - hc.delete_name_servers([hostsd_tag]) - if 'name_server' in dns: - # '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()) - - ## add nameserver tags - the order determines the nameserver order! - # our own tag (static) - hc.add_name_server_tags_recursor([hostsd_tag]) - - if 'system' in dns: - hc.add_name_server_tags_recursor(['system']) - else: - hc.delete_name_server_tags_recursor(['system']) - - # add dhcp nameserver tags for configured interfaces - if 'system_name_server' in dns: - for interface in dns['system_name_server']: - # system_name_server key contains both IP addresses and interface - # names (DHCP) to use DNS servers. We need to check if the - # value is an interface name - only if this is the case, add the - # interface based DNS forwarder. - if interface in interfaces(): - hc.add_name_server_tags_recursor(['dhcp-' + interface, - 'dhcpv6-' + interface ]) - - # hostsd will generate the forward-zones file - # 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: - 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 - hc.delete_authoritative_zones(list(hc.get_authoritative_zones())) - if 'authoritative_zones' in dns: - hc.add_authoritative_zones(list(map(lambda zone: zone['name'], dns['authoritative_zones']))) - - # call hostsd to generate forward-zones and its lua-config-file - hc.apply() - - ### finally (re)start pdns-recursor - call('systemctl restart pdns-recursor.service') - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index da6724fde..acb7dfa41 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -42,9 +42,6 @@ from vyos import airbag airbag.enable() -nat_conf_script = 'nat.py' -policy_route_conf_script = 'policy-route.py' - nftables_conf = '/run/nftables.conf' sysfs_config = { diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py deleted file mode 100755 index 206f513c8..000000000 --- a/src/conf_mode/flow_accounting_conf.py +++ /dev/null @@ -1,320 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import re - -from sys import exit -from ipaddress import ip_address - -from vyos.base import Warning -from vyos.config import Config -from vyos.config import config_dict_merge -from vyos.configverify import verify_vrf -from vyos.ifconfig import Section -from vyos.template import render -from vyos.utils.process import call -from vyos.utils.process import cmd -from vyos.utils.process import run -from vyos.utils.network import is_addr_assigned -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -uacctd_conf_path = '/run/pmacct/uacctd.conf' -systemd_service = 'uacctd.service' -systemd_override = f'/run/systemd/system/{systemd_service}.d/override.conf' -nftables_nflog_table = 'raw' -nftables_nflog_chain = 'VYOS_PREROUTING_HOOK' -egress_nftables_nflog_table = 'inet mangle' -egress_nftables_nflog_chain = 'FORWARD' - -# get nftables rule dict for chain in table -def _nftables_get_nflog(chain, table): - # define list with rules - rules = [] - - # prepare regex for parsing rules - rule_pattern = '[io]ifname "(?P[\w\.\*\-]+)".*handle (?P[\d]+)' - rule_re = re.compile(rule_pattern) - - # run nftables, save output and split it by lines - nftables_command = f'nft -a list chain {table} {chain}' - tmp = cmd(nftables_command, message='Failed to get flows list') - # parse each line and add information to list - for current_rule in tmp.splitlines(): - if 'FLOW_ACCOUNTING_RULE' not in current_rule: - continue - current_rule_parsed = rule_re.search(current_rule) - if current_rule_parsed: - groups = current_rule_parsed.groupdict() - rules.append({ 'interface': groups["interface"], 'table': table, 'handle': groups["handle"] }) - - # return list with rules - return rules - -def _nftables_config(configured_ifaces, direction, length=None): - # define list of nftables commands to modify settings - nftable_commands = [] - nftables_chain = nftables_nflog_chain - nftables_table = nftables_nflog_table - - if direction == "egress": - nftables_chain = egress_nftables_nflog_chain - nftables_table = egress_nftables_nflog_table - - # prepare extended list with configured interfaces - configured_ifaces_extended = [] - for iface in configured_ifaces: - configured_ifaces_extended.append({ 'iface': iface }) - - # get currently configured interfaces with nftables rules - active_nflog_rules = _nftables_get_nflog(nftables_chain, nftables_table) - - # compare current active list with configured one and delete excessive interfaces, add missed - active_nflog_ifaces = [] - for rule in active_nflog_rules: - interface = rule['interface'] - if interface not in configured_ifaces: - table = rule['table'] - handle = rule['handle'] - nftable_commands.append(f'nft delete rule {table} {nftables_chain} handle {handle}') - else: - active_nflog_ifaces.append({ - 'iface': interface, - }) - - # do not create new rules for already configured interfaces - for iface in active_nflog_ifaces: - if iface in active_nflog_ifaces and iface in configured_ifaces_extended: - configured_ifaces_extended.remove(iface) - - # create missed rules - for iface_extended in configured_ifaces_extended: - iface = iface_extended['iface'] - iface_prefix = "o" if direction == "egress" else "i" - rule_definition = f'{iface_prefix}ifname "{iface}" counter log group 2 snaplen {length} queue-threshold 100 comment "FLOW_ACCOUNTING_RULE"' - nftable_commands.append(f'nft insert rule {nftables_table} {nftables_chain} {rule_definition}') - # Also add IPv6 ingres logging - if nftables_table == nftables_nflog_table: - nftable_commands.append(f'nft insert rule ip6 {nftables_table} {nftables_chain} {rule_definition}') - - # change nftables - for command in nftable_commands: - cmd(command, raising=ConfigError) - - -def _nftables_trigger_setup(operation: str) -> None: - """Add a dummy rule to unlock the main pmacct loop with a packet-trigger - - Args: - operation (str): 'add' or 'delete' a trigger - """ - # check if a chain exists - table_exists = False - if run('nft -snj list table ip pmacct') == 0: - table_exists = True - - if operation == 'delete' and table_exists: - nft_cmd: str = 'nft delete table ip pmacct' - cmd(nft_cmd, raising=ConfigError) - if operation == 'add' and not table_exists: - nft_cmds: list[str] = [ - 'nft add table ip pmacct', - 'nft add chain ip pmacct pmacct_out { type filter hook output priority raw - 50 \\; policy accept \\; }', - 'nft add rule ip pmacct pmacct_out oif lo ip daddr 127.0.254.0 counter log group 2 snaplen 1 queue-threshold 0 comment NFLOG_TRIGGER' - ] - for nft_cmd in nft_cmds: - cmd(nft_cmd, raising=ConfigError) - - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['system', 'flow-accounting'] - if not conf.exists(base): - return None - - flow_accounting = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - - # We have gathered the dict representation of the CLI, but there are - # default values which we need to conditionally update into the - # dictionary retrieved. - default_values = conf.get_config_defaults(**flow_accounting.kwargs, - recursive=True) - - # delete individual flow type defaults - should only be added if user - # sets this feature - for flow_type in ['sflow', 'netflow']: - if flow_type not in flow_accounting and flow_type in default_values: - del default_values[flow_type] - - flow_accounting = config_dict_merge(default_values, flow_accounting) - - return flow_accounting - -def verify(flow_config): - if not flow_config: - return None - - # check if at least one collector is enabled - if 'sflow' not in flow_config and 'netflow' not in flow_config and 'disable_imt' in flow_config: - raise ConfigError('You need to configure at least sFlow or NetFlow, ' \ - 'or not set "disable-imt" for flow-accounting!') - - # Check if at least one interface is configured - if 'interface' not in flow_config: - raise ConfigError('Flow accounting requires at least one interface to ' \ - 'be configured!') - - # check that all configured interfaces exists in the system - for interface in flow_config['interface']: - if interface not in Section.interfaces(): - # Changed from error to warning to allow adding dynamic interfaces - # and interface templates - Warning(f'Interface "{interface}" is not presented in the system') - - # check sFlow configuration - if 'sflow' in flow_config: - # check if at least one sFlow collector is configured - if 'server' not in flow_config['sflow']: - raise ConfigError('You need to configure at least one sFlow server!') - - # check that all sFlow collectors use the same IP protocol version - sflow_collector_ipver = None - for server in flow_config['sflow']['server']: - if sflow_collector_ipver: - if sflow_collector_ipver != ip_address(server).version: - 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 - verify_vrf(flow_config) - sflow_vrf = None - if 'vrf' in flow_config: - sflow_vrf = flow_config['vrf'] - - # check agent-id for sFlow: we should avoid mixing IPv4 agent-id with IPv6 collectors and vice-versa - for server in flow_config['sflow']['server']: - if 'agent_address' in flow_config['sflow']: - if ip_address(server).version != ip_address(flow_config['sflow']['agent_address']).version: - raise ConfigError('IPv4 and IPv6 addresses can not be mixed in "sflow agent-address" and "sflow '\ - 'server". You need to set the same IP version for both "agent-address" and '\ - 'all sFlow servers') - - if 'agent_address' in flow_config['sflow']: - tmp = flow_config['sflow']['agent_address'] - if not is_addr_assigned(tmp, sflow_vrf): - raise ConfigError(f'Configured "sflow agent-address {tmp}" does not exist in the system!') - - # Check if configured sflow source-address exist in the system - if 'source_address' in flow_config['sflow']: - if not is_addr_assigned(flow_config['sflow']['source_address'], sflow_vrf): - tmp = flow_config['sflow']['source_address'] - raise ConfigError(f'Configured "sflow source-address {tmp}" does not exist on the system!') - - # check NetFlow configuration - if 'netflow' in flow_config: - # check if vrf is defined for netflow - netflow_vrf = None - if 'vrf' in flow_config: - netflow_vrf = flow_config['vrf'] - - # check if at least one NetFlow collector is configured if NetFlow configuration is presented - if 'server' not in flow_config['netflow']: - raise ConfigError('You need to configure at least one NetFlow server!') - - # Check if configured netflow source-address exist in the system - if 'source_address' in flow_config['netflow']: - if not is_addr_assigned(flow_config['netflow']['source_address'], netflow_vrf): - tmp = flow_config['netflow']['source_address'] - raise ConfigError(f'Configured "netflow source-address {tmp}" does not exist on the system!') - - # Check if engine-id compatible with selected protocol version - if 'engine_id' in flow_config['netflow']: - v5_filter = '^(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]):(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$' - v9v10_filter = '^(\d|[1-9]\d{1,8}|[1-3]\d{9}|4[01]\d{8}|42[0-8]\d{7}|429[0-3]\d{6}|4294[0-8]\d{5}|42949[0-5]\d{4}|429496[0-6]\d{3}|4294967[01]\d{2}|42949672[0-8]\d|429496729[0-5])$' - engine_id = flow_config['netflow']['engine_id'] - version = flow_config['netflow']['version'] - - if flow_config['netflow']['version'] == '5': - regex_filter = re.compile(v5_filter) - if not regex_filter.search(engine_id): - raise ConfigError(f'You cannot use NetFlow engine-id "{engine_id}" '\ - f'together with NetFlow protocol version "{version}"!') - else: - regex_filter = re.compile(v9v10_filter) - if not regex_filter.search(flow_config['netflow']['engine_id']): - raise ConfigError(f'Can not use NetFlow engine-id "{engine_id}" together '\ - f'with NetFlow protocol version "{version}"!') - - # return True if all checks were passed - return True - -def generate(flow_config): - if not flow_config: - return None - - render(uacctd_conf_path, 'pmacct/uacctd.conf.j2', flow_config) - render(systemd_override, 'pmacct/override.conf.j2', flow_config) - # Reload systemd manager configuration - call('systemctl daemon-reload') - -def apply(flow_config): - # Check if flow-accounting was removed and define command - if not flow_config: - _nftables_config([], 'ingress') - _nftables_config([], 'egress') - - # Stop flow-accounting daemon and remove configuration file - call(f'systemctl stop {systemd_service}') - if os.path.exists(uacctd_conf_path): - os.unlink(uacctd_conf_path) - - # must be done after systemctl - _nftables_trigger_setup('delete') - - return - - # Start/reload flow-accounting daemon - call(f'systemctl restart {systemd_service}') - - # configure nftables rules for defined interfaces - if 'interface' in flow_config: - _nftables_config(flow_config['interface'], 'ingress', flow_config['packet_length']) - - # configure egress the same way if configured otherwise remove it - if 'enable_egress' in flow_config: - _nftables_config(flow_config['interface'], 'egress', flow_config['packet_length']) - else: - _nftables_config([], 'egress') - - # add a trigger for signal processing - _nftables_trigger_setup('add') - - -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/host_name.py b/src/conf_mode/host_name.py deleted file mode 100755 index 6204cf247..000000000 --- a/src/conf_mode/host_name.py +++ /dev/null @@ -1,188 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import re -import sys -import copy - -import vyos.hostsd_client - -from vyos.base import Warning -from vyos.config import Config -from vyos.ifconfig import Section -from vyos.template import is_ip -from vyos.utils.process import cmd -from vyos.utils.process import call -from vyos.utils.process import process_named_running -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -default_config_data = { - 'hostname': 'vyos', - 'domain_name': '', - 'domain_search': [], - 'nameserver': [], - 'nameservers_dhcp_interfaces': {}, - 'static_host_mapping': {} -} - -hostsd_tag = 'system' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - hosts = copy.deepcopy(default_config_data) - - hosts['hostname'] = conf.return_value(['system', 'host-name']) - - # This may happen if the config is not loaded yet, - # e.g. if run by cloud-init - if not hosts['hostname']: - hosts['hostname'] = default_config_data['hostname'] - - if conf.exists(['system', 'domain-name']): - hosts['domain_name'] = conf.return_value(['system', 'domain-name']) - hosts['domain_search'].append(hosts['domain_name']) - - if conf.exists(['system', 'domain-search']): - for search in conf.return_values(['system', 'domain-search']): - hosts['domain_search'].append(search) - - if conf.exists(['system', 'name-server']): - for ns in conf.return_values(['system', 'name-server']): - if is_ip(ns): - hosts['nameserver'].append(ns) - else: - tmp = '' - if_type = Section.section(ns) - if conf.exists(['interfaces', if_type, ns, 'address']): - tmp = conf.return_values(['interfaces', if_type, ns, 'address']) - - hosts['nameservers_dhcp_interfaces'].update({ ns : tmp }) - - # system static-host-mapping - for hn in conf.list_nodes(['system', 'static-host-mapping', 'host-name']): - hosts['static_host_mapping'][hn] = {} - hosts['static_host_mapping'][hn]['address'] = conf.return_values(['system', 'static-host-mapping', 'host-name', hn, 'inet']) - hosts['static_host_mapping'][hn]['aliases'] = conf.return_values(['system', 'static-host-mapping', 'host-name', hn, 'alias']) - - return hosts - - -def verify(hosts): - if hosts is None: - return None - - # pattern $VAR(@) "^[[:alnum:]][-.[:alnum:]]*[[:alnum:]]$" ; "invalid host name $VAR(@)" - hostname_regex = re.compile("^[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]$") - if not hostname_regex.match(hosts['hostname']): - raise ConfigError('Invalid host name ' + hosts["hostname"]) - - # pattern $VAR(@) "^.{1,63}$" ; "invalid host-name length" - length = len(hosts['hostname']) - if length < 1 or length > 63: - raise ConfigError( - 'Invalid host-name length, must be less than 63 characters') - - all_static_host_mapping_addresses = [] - # static mappings alias hostname - for host, hostprops in hosts['static_host_mapping'].items(): - if not hostprops['address']: - raise ConfigError(f'IP address required for static-host-mapping "{host}"') - all_static_host_mapping_addresses.append(hostprops['address']) - for a in hostprops['aliases']: - if not hostname_regex.match(a) and len(a) != 0: - raise ConfigError(f'Invalid alias "{a}" in static-host-mapping "{host}"') - - for interface, interface_config in hosts['nameservers_dhcp_interfaces'].items(): - # Warnin user if interface does not have DHCP or DHCPv6 configured - if not set(interface_config).intersection(['dhcp', 'dhcpv6']): - Warning(f'"{interface}" is not a DHCP interface but uses DHCP name-server option!') - - return None - - -def generate(config): - pass - -def apply(config): - if config is None: - return None - - ## Send the updated data to vyos-hostsd - try: - hc = vyos.hostsd_client.Client() - - hc.set_host_name(config['hostname'], config['domain_name']) - - hc.delete_search_domains([hostsd_tag]) - if config['domain_search']: - hc.add_search_domains({hostsd_tag: config['domain_search']}) - - hc.delete_name_servers([hostsd_tag]) - if config['nameserver']: - hc.add_name_servers({hostsd_tag: config['nameserver']}) - - # add our own tag's (system) nameservers and search to resolv.conf - hc.delete_name_server_tags_system(hc.get_name_server_tags_system()) - hc.add_name_server_tags_system([hostsd_tag]) - - # this will add the dhcp client nameservers to resolv.conf - for intf in config['nameservers_dhcp_interfaces']: - hc.add_name_server_tags_system([f'dhcp-{intf}', f'dhcpv6-{intf}']) - - hc.delete_hosts([hostsd_tag]) - if config['static_host_mapping']: - hc.add_hosts({hostsd_tag: config['static_host_mapping']}) - - hc.apply() - except vyos.hostsd_client.VyOSHostsdError as e: - raise ConfigError(str(e)) - - ## Actually update the hostname -- vyos-hostsd doesn't do that - - # No domain name -- the Debian way. - hostname_new = config['hostname'] - - # rsyslog runs into a race condition at boot time with systemd - # restart rsyslog only if the hostname changed. - hostname_old = cmd('hostnamectl --static') - call(f'hostnamectl set-hostname --static {hostname_new}') - - # Restart services that use the hostname - if hostname_new != hostname_old: - call("systemctl restart rsyslog.service") - - # If SNMP is running, restart it too - if process_named_running('snmpd'): - call('systemctl restart snmpd.service') - - return None - - -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/https.py b/src/conf_mode/https.py deleted file mode 100755 index 3dc5dfc01..000000000 --- a/src/conf_mode/https.py +++ /dev/null @@ -1,335 +0,0 @@ -#!/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 . - -import os -import sys -import json - -from copy import deepcopy -from time import sleep - -import vyos.defaults -import vyos.certbot_util - -from vyos.base import Warning -from vyos.config import Config -from vyos.configdiff import get_config_diff -from vyos.configverify import verify_vrf -from vyos import ConfigError -from vyos.pki import wrap_certificate -from vyos.pki import wrap_private_key -from vyos.template import render -from vyos.utils.process import call -from vyos.utils.process import is_systemd_service_running -from vyos.utils.process import is_systemd_service_active -from vyos.utils.network import check_port_availability -from vyos.utils.network import is_listen_port_bind_service -from vyos.utils.file import write_file - -from vyos import airbag -airbag.enable() - -config_file = '/etc/nginx/sites-available/default' -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'] - -api_config_state = '/run/http-api-state' -systemd_service = '/run/systemd/system/vyos-http-api.service' - -# https config needs to coordinate several subsystems: api, certbot, -# self-signed certificate, as well as the virtual hosts defined within the -# https config definition itself. Consequently, one needs a general dict, -# encompassing the https and other configs, and a list of such virtual hosts -# (server blocks in nginx terminology) to pass to the jinja2 template. -default_server_block = { - 'id' : '', - 'address' : '*', - 'port' : '443', - 'name' : ['_'], - 'api' : False, - 'vyos_cert' : {}, - 'certbot' : False -} - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - base = ['service', 'https'] - if not conf.exists(base): - return None - - diff = get_config_diff(conf) - - https = conf.get_config_dict(base, get_first_key=True) - - if https: - https['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), - no_tag_node_value_mangle=True, - get_first_key=True) - - https['children_changed'] = diff.node_changed_children(base) - https['api_add_or_delete'] = diff.node_changed_presence(base + ['api']) - - if 'api' not in https: - return https - - http_api = conf.get_config_dict(base + ['api'], key_mangling=('-', '_'), - no_tag_node_value_mangle=True, - get_first_key=True, - with_recursive_defaults=True) - - if http_api.from_defaults(['graphql']): - del http_api['graphql'] - - # Do we run inside a VRF context? - vrf_path = ['service', 'https', 'vrf'] - if conf.exists(vrf_path): - http_api['vrf'] = conf.return_value(vrf_path) - - https['api'] = http_api - - return https - -def verify(https): - from vyos.utils.dict import dict_search - - if https is None: - return None - - if 'certificates' in https: - certificates = https['certificates'] - - if 'certificate' in certificates: - if not https['pki']: - raise ConfigError("PKI is not configured") - - cert_name = certificates['certificate'] - - if cert_name not in https['pki']['certificate']: - raise ConfigError("Invalid certificate on https configuration") - - pki_cert = https['pki']['certificate'][cert_name] - - if 'certificate' not in pki_cert: - raise ConfigError("Missing certificate on https configuration") - - if 'private' not in pki_cert or 'key' not in pki_cert['private']: - raise ConfigError("Missing certificate private key on https configuration") - - if 'certbot' in https['certificates']: - vhost_names = [] - for _, vh_conf in https.get('virtual-host', {}).items(): - vhost_names += vh_conf.get('server-name', []) - domains = https['certificates']['certbot'].get('domain-name', []) - domains_found = [domain for domain in domains if domain in vhost_names] - if not domains_found: - raise ConfigError("At least one 'virtual-host server-name' " - "matching the 'certbot domain-name' is required.") - - server_block_list = [] - - # organize by vhosts - vhost_dict = https.get('virtual-host', {}) - - if not vhost_dict: - # no specified virtual hosts (server blocks); use default - server_block_list.append(default_server_block) - else: - for vhost in list(vhost_dict): - server_block = deepcopy(default_server_block) - data = vhost_dict.get(vhost, {}) - server_block['address'] = data.get('listen-address', '*') - server_block['port'] = data.get('port', '443') - server_block_list.append(server_block) - - for entry in server_block_list: - _address = entry.get('address') - _address = '0.0.0.0' if _address == '*' else _address - _port = entry.get('port') - proto = 'tcp' - if check_port_availability(_address, int(_port), proto) is not True and \ - not is_listen_port_bind_service(int(_port), 'nginx'): - raise ConfigError(f'"{proto}" port "{_port}" is used by another service') - - verify_vrf(https) - - # Verify API server settings, if present - if 'api' in https: - keys = dict_search('api.keys.id', https) - gql_auth_type = dict_search('api.graphql.authentication.type', https) - - # If "api graphql" is not defined and `gql_auth_type` is None, - # there's certainly no JWT auth option, and keys are required - jwt_auth = (gql_auth_type == "token") - - # Check for incomplete key configurations in every case - valid_keys_exist = False - if keys: - for k in keys: - if 'key' not in keys[k]: - raise ConfigError(f'Missing HTTPS API key string for key id "{k}"') - else: - valid_keys_exist = True - - # If only key-based methods are enabled, - # fail the commit if no valid key configurations are found - if (not valid_keys_exist) and (not jwt_auth): - raise ConfigError('At least one HTTPS API key is required unless GraphQL token authentication is enabled') - - if (not valid_keys_exist) and jwt_auth: - Warning(f'API keys are not configured: the classic (non-GraphQL) API will be unavailable.') - - return None - -def generate(https): - if https is None: - return None - - if 'api' not in https: - if os.path.exists(systemd_service): - os.unlink(systemd_service) - else: - render(systemd_service, 'https/vyos-http-api.service.j2', https['api']) - with open(api_config_state, 'w') as f: - json.dump(https['api'], f, indent=2) - - server_block_list = [] - - # organize by vhosts - - vhost_dict = https.get('virtual-host', {}) - - if not vhost_dict: - # no specified virtual hosts (server blocks); use default - server_block_list.append(default_server_block) - else: - for vhost in list(vhost_dict): - server_block = deepcopy(default_server_block) - server_block['id'] = vhost - data = vhost_dict.get(vhost, {}) - server_block['address'] = data.get('listen-address', '*') - server_block['port'] = data.get('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 - - cert_dict = https.get('certificates', {}) - - if 'certificate' in cert_dict: - cert_name = cert_dict['certificate'] - pki_cert = https['pki']['certificate'][cert_name] - - cert_path = os.path.join(cert_dir, f'{cert_name}.pem') - key_path = os.path.join(key_dir, f'{cert_name}.pem') - - server_cert = str(wrap_certificate(pki_cert['certificate'])) - if 'ca-certificate' in cert_dict: - ca_cert = cert_dict['ca-certificate'] - server_cert += '\n' + str(wrap_certificate(https['pki']['ca'][ca_cert]['certificate'])) - - write_file(cert_path, server_cert) - write_file(key_path, wrap_private_key(pki_cert['private']['key'])) - - vyos_cert_data = { - 'crt': cert_path, - 'key': key_path - } - - for block in server_block_list: - block['vyos_cert'] = vyos_cert_data - - # letsencrypt certificate using certbot - - certbot = False - cert_domains = cert_dict.get('certbot', {}).get('domain-name', []) - if cert_domains: - certbot = True - for domain in cert_domains: - sub_list = vyos.certbot_util.choose_server_block(server_block_list, - domain) - if sub_list: - for sb in sub_list: - sb['certbot'] = True - sb['certbot_dir'] = certbot_dir - # certbot organizes certificates by first domain - sb['certbot_domain_dir'] = cert_domains[0] - - if 'api' in list(https): - vhost_list = https.get('api-restrict', {}).get('virtual-host', []) - if not vhost_list: - for block in server_block_list: - block['api'] = True - else: - for block in server_block_list: - if block['id'] in vhost_list: - block['api'] = True - - data = { - 'server_block_list': server_block_list, - 'certbot': certbot - } - - render(config_file, 'https/nginx.default.j2', data) - render(systemd_override, 'https/override.conf.j2', https) - return None - -def apply(https): - # Reload systemd manager configuration - call('systemctl daemon-reload') - http_api_service_name = 'vyos-http-api.service' - https_service_name = 'nginx.service' - - if https is None: - if is_systemd_service_active(f'{http_api_service_name}'): - call(f'systemctl stop {http_api_service_name}') - call(f'systemctl stop {https_service_name}') - return - - if 'api' in https['children_changed']: - if 'api' in https: - if is_systemd_service_running(f'{http_api_service_name}'): - call(f'systemctl reload {http_api_service_name}') - else: - call(f'systemctl restart {http_api_service_name}') - # Let uvicorn settle before (possibly) restarting nginx - sleep(1) - else: - if is_systemd_service_active(f'{http_api_service_name}'): - call(f'systemctl stop {http_api_service_name}') - - if (not is_systemd_service_running(f'{https_service_name}') or - https['api_add_or_delete'] or - set(https['children_changed']) - set(['api'])): - call(f'systemctl restart {https_service_name}') - -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/igmp_proxy.py b/src/conf_mode/igmp_proxy.py deleted file mode 100755 index 40db417dd..000000000 --- a/src/conf_mode/igmp_proxy.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2020 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from sys import exit -from netifaces import interfaces - -from vyos.base import Warning -from vyos.config import Config -from vyos.template import render -from vyos.utils.process import call -from vyos.utils.dict import dict_search -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -config_file = r'/etc/igmpproxy.conf' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - base = ['protocols', 'igmp-proxy'] - igmp_proxy = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_defaults=True) - - if conf.exists(['protocols', 'igmp']): - igmp_proxy.update({'igmp_configured': ''}) - - if conf.exists(['protocols', 'pim']): - igmp_proxy.update({'pim_configured': ''}) - - return igmp_proxy - -def verify(igmp_proxy): - # bail out early - looks like removal from running config - if not igmp_proxy or 'disable' in igmp_proxy: - return None - - if 'igmp_configured' in igmp_proxy or 'pim_configured' in igmp_proxy: - raise ConfigError('Can not configure both IGMP proxy and PIM '\ - 'at the same time') - - # at least two interfaces are required, one upstream and one downstream - if 'interface' not in igmp_proxy or len(igmp_proxy['interface']) < 2: - raise ConfigError('Must define exactly one upstream and at least one ' \ - 'downstream interface!') - - upstream = 0 - for interface, config in igmp_proxy['interface'].items(): - if interface not in interfaces(): - raise ConfigError(f'Interface "{interface}" does not exist') - if dict_search('role', config) == 'upstream': - upstream += 1 - - if upstream == 0: - raise ConfigError('At least 1 upstream interface is required!') - elif upstream > 1: - raise ConfigError('Only 1 upstream interface allowed!') - - return None - -def generate(igmp_proxy): - # bail out early - looks like removal from running config - if not igmp_proxy: - return None - - # bail out early - service is disabled, but inform user - if 'disable' in igmp_proxy: - Warning('IGMP Proxy will be deactivated because it is disabled') - return None - - render(config_file, 'igmp-proxy/igmpproxy.conf.j2', igmp_proxy) - - return None - -def apply(igmp_proxy): - if not igmp_proxy or 'disable' in igmp_proxy: - # IGMP Proxy support is removed in the commit - call('systemctl stop igmpproxy.service') - if os.path.exists(config_file): - os.unlink(config_file) - else: - call('systemctl restart igmpproxy.service') - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/intel_qat.py b/src/conf_mode/intel_qat.py deleted file mode 100755 index e4b248675..000000000 --- a/src/conf_mode/intel_qat.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/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 . - -import os -import re - -from sys import exit - -from vyos.config import Config -from vyos.utils.process import popen -from vyos.utils.process import run -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -qat_init_script = '/etc/init.d/qat_service' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - data = {} - - if conf.exists(['system', 'acceleration', 'qat']): - data.update({'qat_enable' : ''}) - - if conf.exists(['vpn', 'ipsec']): - data.update({'ipsec' : ''}) - - if conf.exists(['interfaces', 'openvpn']): - data.update({'openvpn' : ''}) - - return data - - -def vpn_control(action, force_ipsec=False): - # XXX: Should these commands report failure? - if action == 'restore' and force_ipsec: - return run('ipsec start') - - return run(f'ipsec {action}') - - -def verify(qat): - if 'qat_enable' not in qat: - return - - # Check if QAT service installed - if not os.path.exists(qat_init_script): - raise ConfigError('QAT init script not found') - - # Check if QAT device exist - output, err = popen('lspci -nn', decode='utf-8') - if not err: - # PCI id | Chipset - # 19e2 -> C3xx - # 37c8 -> C62x - # 0435 -> DH895 - # 6f54 -> D15xx - # 18ee -> QAT_200XX - data = re.findall( - '(8086:19e2)|(8086:37c8)|(8086:0435)|(8086:6f54)|(8086:18ee)', output) - # If QAT devices found - if not data: - raise ConfigError('No QAT acceleration device found') - -def apply(qat): - # Shutdown VPN service which can use QAT - if 'ipsec' in qat: - vpn_control('stop') - - # Enable/Disable QAT service - if 'qat_enable' in qat: - run(f'{qat_init_script} start') - else: - run(f'{qat_init_script} stop') - - # Recover VPN service - if 'ipsec' in qat: - vpn_control('start') - - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - apply(c) - except ConfigError as e: - print(e) - vpn_control('restore', force_ipsec=('ipsec' in c)) - exit(1) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py deleted file mode 100755 index 8184d8415..000000000 --- a/src/conf_mode/interfaces-bonding.py +++ /dev/null @@ -1,294 +0,0 @@ -#!/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 . - -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 is_node_changed -from vyos.configdict import leaf_node_changed -from vyos.configdict import is_member -from vyos.configdict import is_source_interface -from vyos.configverify import verify_address -from vyos.configverify import verify_bridge_delete -from vyos.configverify import verify_dhcpv6 -from vyos.configverify import verify_mirror_redirect -from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_source_interface -from vyos.configverify import verify_vlan_config -from vyos.configverify import verify_vrf -from vyos.ifconfig import BondIf -from vyos.ifconfig.ethernet import EthernetIf -from vyos.ifconfig import Section -from vyos.template import render_to_string -from vyos.utils.dict import dict_search -from vyos.utils.dict import dict_to_paths_values -from vyos.configdict import has_address_configured -from vyos.configdict import has_vrf_configured -from vyos.configdep import set_dependents, call_dependents -from vyos import ConfigError -from vyos import frr -from vyos import airbag -airbag.enable() - -def get_bond_mode(mode): - if mode == 'round-robin': - return 'balance-rr' - elif mode == 'active-backup': - return 'active-backup' - elif mode == 'xor-hash': - return 'balance-xor' - elif mode == 'broadcast': - return 'broadcast' - elif mode == '802.3ad': - return '802.3ad' - elif mode == 'transmit-load-balance': - return 'balance-tlb' - elif mode == 'adaptive-load-balance': - return 'balance-alb' - else: - raise ConfigError(f'invalid bond mode "{mode}"') - -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', 'bonding'] - ifname, bond = get_interface_dict(conf, base) - - # To make our own life easier transfor the list of member interfaces - # into a dictionary - we will use this to add additional information - # later on for each member - if 'member' in bond and 'interface' in bond['member']: - # convert list of member interfaces to a dictionary - bond['member']['interface'] = {k: {} for k in bond['member']['interface']} - - if 'mode' in bond: - bond['mode'] = get_bond_mode(bond['mode']) - - tmp = is_node_changed(conf, base + [ifname, 'mode']) - if tmp: bond['shutdown_required'] = {} - - tmp = is_node_changed(conf, base + [ifname, 'lacp-rate']) - if tmp: bond['shutdown_required'] = {} - - # determine which members have been removed - interfaces_removed = leaf_node_changed(conf, base + [ifname, 'member', 'interface']) - # Reset config level to interfaces - old_level = conf.get_level() - conf.set_level(['interfaces']) - - if interfaces_removed: - bond['shutdown_required'] = {} - if 'member' not in bond: - bond['member'] = {} - - tmp = {} - for interface in interfaces_removed: - # if member is deleted from bond, add dependencies to call - # ethernet commit again in apply function - # to apply options under ethernet section - set_dependents('ethernet', conf, interface) - section = Section.section(interface) # this will be 'ethernet' for 'eth0' - if conf.exists([section, interface, 'disable']): - tmp[interface] = {'disable': ''} - else: - tmp[interface] = {} - - # also present the interfaces to be removed from the bond as dictionary - bond['member']['interface_remove'] = tmp - - # Restore existing config level - conf.set_level(old_level) - - if dict_search('member.interface', bond): - for interface, interface_config in bond['member']['interface'].items(): - - interface_ethernet_config = conf.get_config_dict( - ['interfaces', 'ethernet', interface], - key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True, - with_defaults=False, - with_recursive_defaults=False) - - interface_config['config_paths'] = dict_to_paths_values(interface_ethernet_config) - - # Check if member interface is a new member - if not conf.exists_effective(base + [ifname, 'member', 'interface', interface]): - bond['shutdown_required'] = {} - interface_config['new_added'] = {} - - # Check if member interface is disabled - conf.set_level(['interfaces']) - - section = Section.section(interface) # this will be 'ethernet' for 'eth0' - if conf.exists([section, interface, 'disable']): - interface_config['disable'] = '' - - conf.set_level(old_level) - - # Check if member interface is already member of another bridge - tmp = is_member(conf, interface, 'bridge') - if tmp: interface_config['is_bridge_member'] = tmp - - # Check if member interface is already member of a bond - tmp = is_member(conf, interface, 'bonding') - for tmp in is_member(conf, interface, 'bonding'): - if bond['ifname'] == tmp: - continue - interface_config['is_bond_member'] = tmp - - # Check if member interface is used as source-interface on another interface - tmp = is_source_interface(conf, interface) - if tmp: interface_config['is_source_interface'] = tmp - - # bond members must not have an assigned address - tmp = has_address_configured(conf, interface) - if tmp: interface_config['has_address'] = {} - - # bond members must not have a VRF attached - tmp = has_vrf_configured(conf, interface) - if tmp: interface_config['has_vrf'] = {} - return bond - - -def verify(bond): - if 'deleted' in bond: - verify_bridge_delete(bond) - return None - - if 'arp_monitor' in bond: - if 'target' in bond['arp_monitor'] and len(bond['arp_monitor']['target']) > 16: - raise ConfigError('The maximum number of arp-monitor targets is 16') - - if 'interval' in bond['arp_monitor'] and int(bond['arp_monitor']['interval']) > 0: - if bond['mode'] in ['802.3ad', 'balance-tlb', 'balance-alb']: - raise ConfigError('ARP link monitoring does not work for mode 802.3ad, ' \ - 'transmit-load-balance or adaptive-load-balance') - - if 'primary' in bond: - if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: - raise ConfigError('Option primary - mode dependency failed, not' - 'supported in mode {mode}!'.format(**bond)) - - verify_mtu_ipv6(bond) - verify_address(bond) - verify_dhcpv6(bond) - verify_vrf(bond) - verify_mirror_redirect(bond) - - # use common function to verify VLAN configuration - verify_vlan_config(bond) - - bond_name = bond['ifname'] - if dict_search('member.interface', bond): - for interface, interface_config in bond['member']['interface'].items(): - error_msg = f'Can not add interface "{interface}" to bond, ' - - if interface == 'lo': - raise ConfigError('Loopback interface "lo" can not be added to a bond') - - if interface not in interfaces(): - raise ConfigError(error_msg + 'it does not exist!') - - if 'is_bridge_member' in interface_config: - tmp = next(iter(interface_config['is_bridge_member'])) - raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') - - if 'is_bond_member' in interface_config: - tmp = next(iter(interface_config['is_bond_member'])) - raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') - - if 'is_source_interface' in interface_config: - tmp = interface_config['is_source_interface'] - raise ConfigError(error_msg + f'it is the source-interface of "{tmp}"!') - - if 'has_address' in interface_config: - raise ConfigError(error_msg + 'it has an address assigned!') - - if 'has_vrf' in interface_config: - raise ConfigError(error_msg + 'it has a VRF assigned!') - - if 'new_added' in interface_config and 'config_paths' in interface_config: - for option_path, option_value in interface_config['config_paths'].items(): - if option_path in EthernetIf.get_bond_member_allowed_options() : - continue - if option_path in BondIf.get_inherit_bond_options(): - continue - raise ConfigError(error_msg + f'it has a "{option_path.replace(".", " ")}" assigned!') - - if 'primary' in bond: - if bond['primary'] not in bond['member']['interface']: - raise ConfigError(f'Primary interface of bond "{bond_name}" must be a member interface') - - if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: - raise ConfigError('primary interface only works for mode active-backup, ' \ - 'transmit-load-balance or adaptive-load-balance') - - return None - -def generate(bond): - bond['frr_zebra_config'] = '' - if 'deleted' not in bond: - bond['frr_zebra_config'] = render_to_string('frr/evpn.mh.frr.j2', bond) - return None - -def apply(bond): - ifname = bond['ifname'] - b = BondIf(ifname) - if 'deleted' in bond: - # delete interface - b.remove() - else: - b.update(bond) - - if dict_search('member.interface_remove', bond): - try: - call_dependents() - except ConfigError: - raise ConfigError('Error in updating ethernet interface ' - 'after deleting it from bond') - - 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'^interface {ifname}', stop_pattern='^exit', remove_stop_mark=True) - if 'frr_zebra_config' in bond: - frr_cfg.add_before(frr.default_add_before, bond['frr_zebra_config']) - frr_cfg.commit_configuration(zebra_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/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py deleted file mode 100755 index 29991e2da..000000000 --- a/src/conf_mode/interfaces-bridge.py +++ /dev/null @@ -1,186 +0,0 @@ -#!/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 . - -from sys import exit - -from vyos.config import Config -from vyos.configdict import get_interface_dict -from vyos.configdict import node_changed -from vyos.configdict import is_member -from vyos.configdict import is_source_interface -from vyos.configdict import has_vlan_subinterface_configured -from vyos.configverify import verify_dhcpv6 -from vyos.configverify import verify_mirror_redirect -from vyos.configverify import verify_vrf -from vyos.ifconfig import BridgeIf -from vyos.configdict import has_address_configured -from vyos.configdict import has_vrf_configured -from vyos.configdep import set_dependents -from vyos.configdep import call_dependents -from vyos.utils.dict import dict_search -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', 'bridge'] - ifname, bridge = get_interface_dict(conf, base) - - # determine which members have been removed - tmp = node_changed(conf, base + [ifname, 'member', 'interface']) - if tmp: - if 'member' in bridge: - bridge['member'].update({'interface_remove' : tmp }) - else: - bridge.update({'member' : {'interface_remove' : tmp }}) - - if dict_search('member.interface', bridge) is not None: - for interface in list(bridge['member']['interface']): - # Check if member interface is already member of another bridge - tmp = is_member(conf, interface, 'bridge') - if tmp and bridge['ifname'] not in tmp: - bridge['member']['interface'][interface].update({'is_bridge_member' : tmp}) - - # Check if member interface is already member of a bond - tmp = is_member(conf, interface, 'bonding') - if tmp: bridge['member']['interface'][interface].update({'is_bond_member' : tmp}) - - # Check if member interface is used as source-interface on another interface - tmp = is_source_interface(conf, interface) - if tmp: bridge['member']['interface'][interface].update({'is_source_interface' : tmp}) - - # Bridge members must not have an assigned address - tmp = has_address_configured(conf, interface) - if tmp: bridge['member']['interface'][interface].update({'has_address' : ''}) - - # Bridge members must not have a VRF attached - tmp = has_vrf_configured(conf, interface) - if tmp: bridge['member']['interface'][interface].update({'has_vrf' : ''}) - - # VLAN-aware bridge members must not have VLAN interface configuration - tmp = has_vlan_subinterface_configured(conf,interface) - if 'enable_vlan' in bridge and tmp: - bridge['member']['interface'][interface].update({'has_vlan' : ''}) - - # When using VXLAN member interfaces that are configured for Single - # VXLAN Device (SVD) we need to call the VXLAN conf-mode script to re-create - # VLAN to VNI mappings if required - if interface.startswith('vxlan'): - set_dependents('vxlan', conf, interface) - - # delete empty dictionary keys - no need to run code paths if nothing is there to do - if 'member' in bridge: - if 'interface' in bridge['member'] and len(bridge['member']['interface']) == 0: - del bridge['member']['interface'] - - if len(bridge['member']) == 0: - del bridge['member'] - - return bridge - -def verify(bridge): - if 'deleted' in bridge: - return None - - verify_dhcpv6(bridge) - verify_vrf(bridge) - verify_mirror_redirect(bridge) - - ifname = bridge['ifname'] - - if dict_search('member.interface', bridge): - for interface, interface_config in bridge['member']['interface'].items(): - error_msg = f'Can not add interface "{interface}" to bridge, ' - - if interface == 'lo': - raise ConfigError('Loopback interface "lo" can not be added to a bridge') - - if 'is_bridge_member' in interface_config: - tmp = next(iter(interface_config['is_bridge_member'])) - raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') - - if 'is_bond_member' in interface_config: - tmp = next(iter(interface_config['is_bond_member'])) - raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') - - if 'is_source_interface' in interface_config: - tmp = interface_config['is_source_interface'] - raise ConfigError(error_msg + f'it is the source-interface of "{tmp}"!') - - if 'has_address' in interface_config: - raise ConfigError(error_msg + 'it has an address assigned!') - - if 'has_vrf' in interface_config: - raise ConfigError(error_msg + 'it has a VRF assigned!') - - if 'enable_vlan' in bridge: - if 'has_vlan' in interface_config: - raise ConfigError(error_msg + 'it has VLAN subinterface(s) assigned!') - - if 'wlan' in interface: - raise ConfigError(error_msg + 'VLAN aware cannot be set!') - else: - for option in ['allowed_vlan', 'native_vlan']: - if option in interface_config: - raise ConfigError('Can not use VLAN options on non VLAN aware bridge') - - if 'enable_vlan' in bridge: - if dict_search('vif.1', bridge): - raise ConfigError(f'VLAN 1 sub interface cannot be set for VLAN aware bridge {ifname}, and VLAN 1 is always the parent interface') - else: - if dict_search('vif', bridge): - raise ConfigError(f'You must first activate "enable-vlan" of {ifname} bridge to use "vif"') - - return None - -def generate(bridge): - return None - -def apply(bridge): - br = BridgeIf(bridge['ifname']) - if 'deleted' in bridge: - # delete interface - br.remove() - else: - br.update(bridge) - - for interface in dict_search('member.interface', bridge) or []: - if interface.startswith('vxlan'): - try: - call_dependents() - except ConfigError: - raise ConfigError('Error in updating VXLAN interface after changing bridge!') - - 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-dummy.py b/src/conf_mode/interfaces-dummy.py deleted file mode 100755 index db768b94d..000000000 --- a/src/conf_mode/interfaces-dummy.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/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 . - -from sys import exit - -from vyos.config import Config -from vyos.configdict import get_interface_dict -from vyos.configverify import verify_vrf -from vyos.configverify import verify_address -from vyos.configverify import verify_bridge_delete -from vyos.configverify import verify_mirror_redirect -from vyos.ifconfig import DummyIf -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', 'dummy'] - _, dummy = get_interface_dict(conf, base) - return dummy - -def verify(dummy): - if 'deleted' in dummy: - verify_bridge_delete(dummy) - return None - - verify_vrf(dummy) - verify_address(dummy) - verify_mirror_redirect(dummy) - - return None - -def generate(dummy): - return None - -def apply(dummy): - d = DummyIf(**dummy) - - # Remove dummy interface - if 'deleted' in dummy: - d.remove() - else: - d.update(dummy) - - 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-ethernet.py b/src/conf_mode/interfaces-ethernet.py deleted file mode 100755 index 7374a29f7..000000000 --- a/src/conf_mode/interfaces-ethernet.py +++ /dev/null @@ -1,400 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2019-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 . - -import os -import pprint - -from glob import glob -from sys import exit - -from vyos.base import Warning -from vyos.config import Config -from vyos.configdict import get_interface_dict -from vyos.configdict import is_node_changed -from vyos.configverify import verify_address -from vyos.configverify import verify_dhcpv6 -from vyos.configverify import verify_eapol -from vyos.configverify import verify_interface_exists -from vyos.configverify import verify_mirror_redirect -from vyos.configverify import verify_mtu -from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_vlan_config -from vyos.configverify import verify_vrf -from vyos.configverify import verify_bond_bridge_member -from vyos.ethtool import Ethtool -from vyos.ifconfig import EthernetIf -from vyos.ifconfig import BondIf -from vyos.pki import find_chain -from vyos.pki import encode_certificate -from vyos.pki import load_certificate -from vyos.pki import wrap_private_key -from vyos.template import render -from vyos.utils.process import call -from vyos.utils.dict import dict_search -from vyos.utils.dict import dict_to_paths_values -from vyos.utils.dict import dict_set -from vyos.utils.dict import dict_delete -from vyos.utils.file import write_file -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -# XXX: wpa_supplicant works on the source interface -cfg_dir = '/run/wpa_supplicant' -wpa_suppl_conf = '/run/wpa_supplicant/{ifname}.conf' - -def update_bond_options(conf: Config, eth_conf: dict) -> list: - """ - Return list of blocked options if interface is a bond member - :param conf: Config object - :type conf: Config - :param eth_conf: Ethernet config dictionary - :type eth_conf: dict - :return: List of blocked options - :rtype: list - """ - blocked_list = [] - bond_name = list(eth_conf['is_bond_member'].keys())[0] - config_without_defaults = conf.get_config_dict( - ['interfaces', 'ethernet', eth_conf['ifname']], - key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True, - with_defaults=False, - with_recursive_defaults=False) - config_with_defaults = conf.get_config_dict( - ['interfaces', 'ethernet', eth_conf['ifname']], - key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True, - with_defaults=True, - with_recursive_defaults=True) - bond_config_with_defaults = conf.get_config_dict( - ['interfaces', 'bonding', bond_name], - key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True, - with_defaults=True, - with_recursive_defaults=True) - eth_dict_paths = dict_to_paths_values(config_without_defaults) - eth_path_base = ['interfaces', 'ethernet', eth_conf['ifname']] - - #if option is configured under ethernet section - for option_path, option_value in eth_dict_paths.items(): - bond_option_value = dict_search(option_path, bond_config_with_defaults) - - #If option is allowed for changing then continue - if option_path in EthernetIf.get_bond_member_allowed_options(): - continue - # if option is inherited from bond then set valued from bond interface - if option_path in BondIf.get_inherit_bond_options(): - # If option equals to bond option then do nothing - if option_value == bond_option_value: - continue - else: - # if ethernet has option and bond interface has - # then copy it from bond - if bond_option_value is not None: - if is_node_changed(conf, eth_path_base + option_path.split('.')): - Warning( - f'Cannot apply "{option_path.replace(".", " ")}" to "{option_value}".' \ - f' Interface "{eth_conf["ifname"]}" is a bond member.' \ - f' Option is inherited from bond "{bond_name}"') - dict_set(option_path, bond_option_value, eth_conf) - continue - # if ethernet has option and bond interface does not have - # then delete it form dict and do not apply it - else: - if is_node_changed(conf, eth_path_base + option_path.split('.')): - Warning( - f'Cannot apply "{option_path.replace(".", " ")}".' \ - f' Interface "{eth_conf["ifname"]}" is a bond member.' \ - f' Option is inherited from bond "{bond_name}"') - dict_delete(option_path, eth_conf) - blocked_list.append(option_path) - - # if inherited option is not configured under ethernet section but configured under bond section - for option_path in BondIf.get_inherit_bond_options(): - bond_option_value = dict_search(option_path, bond_config_with_defaults) - if bond_option_value is not None: - if option_path not in eth_dict_paths: - if is_node_changed(conf, eth_path_base + option_path.split('.')): - Warning( - f'Cannot apply "{option_path.replace(".", " ")}" to "{dict_search(option_path, config_with_defaults)}".' \ - f' Interface "{eth_conf["ifname"]}" is a bond member. ' \ - f'Option is inherited from bond "{bond_name}"') - dict_set(option_path, bond_option_value, eth_conf) - eth_conf['bond_blocked_changes'] = blocked_list - return None - -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() - - # This must be called prior to get_interface_dict(), as this function will - # alter the config level (config.set_level()) - pki = conf.get_config_dict(['pki'], key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - - base = ['interfaces', 'ethernet'] - ifname, ethernet = get_interface_dict(conf, base) - if 'is_bond_member' in ethernet: - update_bond_options(conf, ethernet) - - if 'deleted' not in ethernet: - if pki: ethernet['pki'] = pki - - tmp = is_node_changed(conf, base + [ifname, 'speed']) - if tmp: ethernet.update({'speed_duplex_changed': {}}) - - tmp = is_node_changed(conf, base + [ifname, 'duplex']) - if tmp: ethernet.update({'speed_duplex_changed': {}}) - - return ethernet - - - -def verify_speed_duplex(ethernet: dict, ethtool: Ethtool): - """ - Verify speed and duplex - :param ethernet: dictionary which is received from get_interface_dict - :type ethernet: dict - :param ethtool: Ethernet object - :type ethtool: Ethtool - """ - if ((ethernet['speed'] == 'auto' and ethernet['duplex'] != 'auto') or - (ethernet['speed'] != 'auto' and ethernet['duplex'] == 'auto')): - raise ConfigError( - 'Speed/Duplex missmatch. Must be both auto or manually configured') - - if ethernet['speed'] != 'auto' and ethernet['duplex'] != 'auto': - # We need to verify if the requested speed and duplex setting is - # supported by the underlaying NIC. - speed = ethernet['speed'] - duplex = ethernet['duplex'] - if not ethtool.check_speed_duplex(speed, duplex): - raise ConfigError( - f'Adapter does not support changing speed ' \ - f'and duplex settings to: {speed}/{duplex}!') - - -def verify_flow_control(ethernet: dict, ethtool: Ethtool): - """ - Verify flow control - :param ethernet: dictionary which is received from get_interface_dict - :type ethernet: dict - :param ethtool: Ethernet object - :type ethtool: Ethtool - """ - if 'disable_flow_control' in ethernet: - if not ethtool.check_flow_control(): - raise ConfigError( - 'Adapter does not support changing flow-control settings!') - - -def verify_ring_buffer(ethernet: dict, ethtool: Ethtool): - """ - Verify ring buffer - :param ethernet: dictionary which is received from get_interface_dict - :type ethernet: dict - :param ethtool: Ethernet object - :type ethtool: Ethtool - """ - if 'ring_buffer' in ethernet: - max_rx = ethtool.get_ring_buffer_max('rx') - if not max_rx: - raise ConfigError( - 'Driver does not support RX ring-buffer configuration!') - - max_tx = ethtool.get_ring_buffer_max('tx') - if not max_tx: - raise ConfigError( - 'Driver does not support TX ring-buffer configuration!') - - rx = dict_search('ring_buffer.rx', ethernet) - if rx and int(rx) > int(max_rx): - raise ConfigError(f'Driver only supports a maximum RX ring-buffer ' \ - f'size of "{max_rx}" bytes!') - - tx = dict_search('ring_buffer.tx', ethernet) - if tx and int(tx) > int(max_tx): - raise ConfigError(f'Driver only supports a maximum TX ring-buffer ' \ - f'size of "{max_tx}" bytes!') - - -def verify_offload(ethernet: dict, ethtool: Ethtool): - """ - Verify offloading capabilities - :param ethernet: dictionary which is received from get_interface_dict - :type ethernet: dict - :param ethtool: Ethernet object - :type ethtool: Ethtool - """ - if dict_search('offload.rps', ethernet) != None: - if not os.path.exists(f'/sys/class/net/{ethernet["ifname"]}/queues/rx-0/rps_cpus'): - raise ConfigError('Interface does not suport RPS!') - driver = ethtool.get_driver_name() - # T3342 - Xen driver requires special treatment - if driver == 'vif': - if int(ethernet['mtu']) > 1500 and dict_search('offload.sg', ethernet) == None: - raise ConfigError('Xen netback drivers requires scatter-gatter offloading '\ - 'for MTU size larger then 1500 bytes') - - -def verify_allowedbond_changes(ethernet: dict): - """ - Verify changed options if interface is in bonding - :param ethernet: dictionary which is received from get_interface_dict - :type ethernet: dict - """ - if 'bond_blocked_changes' in ethernet: - for option in ethernet['bond_blocked_changes']: - raise ConfigError(f'Cannot configure "{option.replace(".", " ")}"' \ - f' on interface "{ethernet["ifname"]}".' \ - f' Interface is a bond member') - - -def verify(ethernet): - if 'deleted' in ethernet: - return None - if 'is_bond_member' in ethernet: - verify_bond_member(ethernet) - else: - verify_ethernet(ethernet) - - -def verify_bond_member(ethernet): - """ - Verification function for ethernet interface which is in bonding - :param ethernet: dictionary which is received from get_interface_dict - :type ethernet: dict - """ - ifname = ethernet['ifname'] - verify_interface_exists(ifname) - verify_eapol(ethernet) - verify_mirror_redirect(ethernet) - ethtool = Ethtool(ifname) - verify_speed_duplex(ethernet, ethtool) - verify_flow_control(ethernet, ethtool) - verify_ring_buffer(ethernet, ethtool) - verify_offload(ethernet, ethtool) - verify_allowedbond_changes(ethernet) - -def verify_ethernet(ethernet): - """ - Verification function for simple ethernet interface - :param ethernet: dictionary which is received from get_interface_dict - :type ethernet: dict - """ - ifname = ethernet['ifname'] - verify_interface_exists(ifname) - verify_mtu(ethernet) - verify_mtu_ipv6(ethernet) - verify_dhcpv6(ethernet) - verify_address(ethernet) - verify_vrf(ethernet) - verify_bond_bridge_member(ethernet) - verify_eapol(ethernet) - verify_mirror_redirect(ethernet) - ethtool = Ethtool(ifname) - # No need to check speed and duplex keys as both have default values. - verify_speed_duplex(ethernet, ethtool) - verify_flow_control(ethernet, ethtool) - verify_ring_buffer(ethernet, ethtool) - verify_offload(ethernet, ethtool) - # use common function to verify VLAN configuration - verify_vlan_config(ethernet) - return None - - -def generate(ethernet): - # render real configuration file once - wpa_supplicant_conf = wpa_suppl_conf.format(**ethernet) - - if 'deleted' in ethernet: - # delete configuration on interface removal - if os.path.isfile(wpa_supplicant_conf): - os.unlink(wpa_supplicant_conf) - return None - - if 'eapol' in ethernet: - ifname = ethernet['ifname'] - - render(wpa_supplicant_conf, 'ethernet/wpa_supplicant.conf.j2', ethernet) - - cert_file_path = os.path.join(cfg_dir, f'{ifname}_cert.pem') - cert_key_path = os.path.join(cfg_dir, f'{ifname}_cert.key') - - cert_name = ethernet['eapol']['certificate'] - pki_cert = ethernet['pki']['certificate'][cert_name] - - loaded_pki_cert = load_certificate(pki_cert['certificate']) - loaded_ca_certs = {load_certificate(c['certificate']) - for c in ethernet['pki']['ca'].values()} if 'ca' in ethernet['pki'] else {} - - cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs) - - write_file(cert_file_path, - '\n'.join(encode_certificate(c) for c in cert_full_chain)) - write_file(cert_key_path, wrap_private_key(pki_cert['private']['key'])) - - if 'ca_certificate' in ethernet['eapol']: - ca_cert_file_path = os.path.join(cfg_dir, f'{ifname}_ca.pem') - ca_chains = [] - - for ca_cert_name in ethernet['eapol']['ca_certificate']: - pki_ca_cert = ethernet['pki']['ca'][ca_cert_name] - loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) - ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) - ca_chains.append( - '\n'.join(encode_certificate(c) for c in ca_full_chain)) - - write_file(ca_cert_file_path, '\n'.join(ca_chains)) - - return None - -def apply(ethernet): - ifname = ethernet['ifname'] - # take care about EAPoL supplicant daemon - eapol_action='stop' - - e = EthernetIf(ifname) - if 'deleted' in ethernet: - # delete interface - e.remove() - else: - e.update(ethernet) - if 'eapol' in ethernet: - eapol_action='reload-or-restart' - - call(f'systemctl {eapol_action} wpa_supplicant-wired@{ifname}') - -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-geneve.py b/src/conf_mode/interfaces-geneve.py deleted file mode 100755 index f6694ddde..000000000 --- a/src/conf_mode/interfaces-geneve.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# 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 . - -from sys import exit -from netifaces import interfaces - -from vyos.config import Config -from vyos.configdict import get_interface_dict -from vyos.configdict import is_node_changed -from vyos.configverify import verify_address -from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_bridge_delete -from vyos.configverify import verify_mirror_redirect -from vyos.configverify import verify_bond_bridge_member -from vyos.ifconfig import GeneveIf -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', 'geneve'] - ifname, geneve = get_interface_dict(conf, base) - - # 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', 'parameters']: - if is_node_changed(conf, base + [ifname, cli_option]): - geneve.update({'rebuild_required': {}}) - - return geneve - -def verify(geneve): - if 'deleted' in geneve: - verify_bridge_delete(geneve) - return None - - verify_mtu_ipv6(geneve) - verify_address(geneve) - verify_bond_bridge_member(geneve) - verify_mirror_redirect(geneve) - - if 'remote' not in geneve: - raise ConfigError('Remote side must be configured') - - if 'vni' not in geneve: - raise ConfigError('VNI must be configured') - - return None - - -def generate(geneve): - return None - -def apply(geneve): - # Check if GENEVE interface already exists - if 'rebuild_required' in geneve or 'delete' in geneve: - if geneve['ifname'] in interfaces(): - g = GeneveIf(geneve['ifname']) - # GENEVE is super picky and the tunnel always needs to be recreated, - # thus we can simply always delete it first. - g.remove() - - if 'deleted' not in geneve: - # Finally create the new interface - g = GeneveIf(**geneve) - g.update(geneve) - - 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-input.py b/src/conf_mode/interfaces-input.py deleted file mode 100755 index ad248843d..000000000 --- a/src/conf_mode/interfaces-input.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/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 . - -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-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py deleted file mode 100755 index e1db3206e..000000000 --- a/src/conf_mode/interfaces-l2tpv3.py +++ /dev/null @@ -1,112 +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 . - -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.configverify import verify_address -from vyos.configverify import verify_bridge_delete -from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_mirror_redirect -from vyos.configverify import verify_bond_bridge_member -from vyos.ifconfig import L2TPv3If -from vyos.utils.kernel import check_kmod -from vyos.utils.network import is_addr_assigned -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -k_mod = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6'] - -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', 'l2tpv3'] - ifname, l2tpv3 = get_interface_dict(conf, base) - - # To delete an l2tpv3 interface we need the current tunnel and session-id - if 'deleted' in l2tpv3: - tmp = leaf_node_changed(conf, base + [ifname, 'tunnel-id']) - # leaf_node_changed() returns a list - l2tpv3.update({'tunnel_id': tmp[0]}) - - tmp = leaf_node_changed(conf, base + [ifname, 'session-id']) - l2tpv3.update({'session_id': tmp[0]}) - - return l2tpv3 - -def verify(l2tpv3): - if 'deleted' in l2tpv3: - verify_bridge_delete(l2tpv3) - return None - - interface = l2tpv3['ifname'] - - for key in ['source_address', 'remote', 'tunnel_id', 'peer_tunnel_id', - 'session_id', 'peer_session_id']: - if key not in l2tpv3: - tmp = key.replace('_', '-') - raise ConfigError(f'Missing mandatory L2TPv3 option: "{tmp}"!') - - if not is_addr_assigned(l2tpv3['source_address']): - raise ConfigError('L2TPv3 source-address address "{source_address}" ' - 'not configured on any interface!'.format(**l2tpv3)) - - verify_mtu_ipv6(l2tpv3) - verify_address(l2tpv3) - verify_bond_bridge_member(l2tpv3) - verify_mirror_redirect(l2tpv3) - return None - -def generate(l2tpv3): - return None - -def apply(l2tpv3): - # Check if L2TPv3 interface already exists - if l2tpv3['ifname'] in interfaces(): - # L2TPv3 is picky when changing tunnels/sessions, thus we can simply - # always delete it first. - l = L2TPv3If(**l2tpv3) - l.remove() - - if 'deleted' not in l2tpv3: - # Finally create the new interface - l = L2TPv3If(**l2tpv3) - l.update(l2tpv3) - - return None - -if __name__ == '__main__': - try: - check_kmod(k_mod) - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py deleted file mode 100755 index 08d34477a..000000000 --- a/src/conf_mode/interfaces-loopback.py +++ /dev/null @@ -1,66 +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 . - -import os - -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 LoopbackIf -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', 'loopback'] - _, loopback = get_interface_dict(conf, base) - return loopback - -def verify(loopback): - verify_mirror_redirect(loopback) - return None - -def generate(loopback): - return None - -def apply(loopback): - l = LoopbackIf(**loopback) - if 'deleted' in loopback: - l.remove() - else: - l.update(loopback) - - 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-macsec.py b/src/conf_mode/interfaces-macsec.py deleted file mode 100755 index 0a927ac88..000000000 --- a/src/conf_mode/interfaces-macsec.py +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from netifaces import interfaces -from sys import exit - -from vyos.config import Config -from vyos.configdict import get_interface_dict -from vyos.configdict import is_node_changed -from vyos.configdict import is_source_interface -from vyos.configverify import verify_vrf -from vyos.configverify import verify_address -from vyos.configverify import verify_bridge_delete -from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_mirror_redirect -from vyos.configverify import verify_source_interface -from vyos.configverify import verify_bond_bridge_member -from vyos.ifconfig import MACsecIf -from vyos.ifconfig import Interface -from vyos.template import render -from vyos.utils.process import call -from vyos.utils.dict import dict_search -from vyos.utils.process import is_systemd_service_running -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -# XXX: wpa_supplicant works on the source interface -wpa_suppl_conf = '/run/wpa_supplicant/{source_interface}.conf' - -# Constants -## gcm-aes-128 requires a 128bit long key - 32 characters (string) = 16byte = 128bit -GCM_AES_128_LEN: int = 32 -GCM_128_KEY_ERROR = 'gcm-aes-128 requires a 128bit long key!' -## gcm-aes-256 requires a 256bit long key - 64 characters (string) = 32byte = 256bit -GCM_AES_256_LEN: int = 64 -GCM_256_KEY_ERROR = 'gcm-aes-256 requires a 256bit long key!' - -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', 'macsec'] - ifname, macsec = get_interface_dict(conf, base) - - # Check if interface has been removed - if 'deleted' in macsec: - source_interface = conf.return_effective_value(base + [ifname, 'source-interface']) - macsec.update({'source_interface': source_interface}) - - if is_node_changed(conf, base + [ifname, 'security']): - macsec.update({'shutdown_required': {}}) - - if is_node_changed(conf, base + [ifname, 'source_interface']): - macsec.update({'shutdown_required': {}}) - - if 'source_interface' in macsec: - tmp = is_source_interface(conf, macsec['source_interface'], ['macsec', 'pseudo-ethernet']) - if tmp and tmp != ifname: macsec.update({'is_source_interface' : tmp}) - - return macsec - - -def verify(macsec): - if 'deleted' in macsec: - verify_bridge_delete(macsec) - return None - - verify_source_interface(macsec) - verify_vrf(macsec) - verify_mtu_ipv6(macsec) - verify_address(macsec) - verify_bond_bridge_member(macsec) - verify_mirror_redirect(macsec) - - if dict_search('security.cipher', macsec) == None: - raise ConfigError('Cipher suite must be set for MACsec "{ifname}"'.format(**macsec)) - - if dict_search('security.encrypt', macsec) != None: - # Check that only static or MKA config is present - if dict_search('security.static', macsec) != None and (dict_search('security.mka.cak', macsec) != None or dict_search('security.mka.ckn', macsec) != None): - raise ConfigError('Only static or MKA can be used!') - - # Logic to check static configuration - if dict_search('security.static', macsec) != None: - # tx-key must be defined - if dict_search('security.static.key', macsec) == None: - raise ConfigError('Static MACsec tx-key must be defined.') - - tx_len = len(dict_search('security.static.key', macsec)) - - if dict_search('security.cipher', macsec) == 'gcm-aes-128' and tx_len != GCM_AES_128_LEN: - raise ConfigError(GCM_128_KEY_ERROR) - - if dict_search('security.cipher', macsec) == 'gcm-aes-256' and tx_len != GCM_AES_256_LEN: - raise ConfigError(GCM_256_KEY_ERROR) - - # Make sure at least one peer is defined - if 'peer' not in macsec['security']['static']: - raise ConfigError('Must have at least one peer defined for static MACsec') - - # For every enabled peer, make sure a MAC and rx-key is defined - for peer, peer_config in macsec['security']['static']['peer'].items(): - if 'disable' not in peer_config and ('mac' not in peer_config or 'key' not in peer_config): - raise ConfigError('Every enabled MACsec static peer must have a MAC address and rx-key defined.') - - # check rx-key length against cipher suite - rx_len = len(peer_config['key']) - - if dict_search('security.cipher', macsec) == 'gcm-aes-128' and rx_len != GCM_AES_128_LEN: - raise ConfigError(GCM_128_KEY_ERROR) - - if dict_search('security.cipher', macsec) == 'gcm-aes-256' and rx_len != GCM_AES_256_LEN: - raise ConfigError(GCM_256_KEY_ERROR) - - # Logic to check MKA configuration - else: - if dict_search('security.mka.cak', macsec) == None or dict_search('security.mka.ckn', macsec) == None: - raise ConfigError('Missing mandatory MACsec security keys as encryption is enabled!') - - cak_len = len(dict_search('security.mka.cak', macsec)) - - if dict_search('security.cipher', macsec) == 'gcm-aes-128' and cak_len != GCM_AES_128_LEN: - raise ConfigError(GCM_128_KEY_ERROR) - - elif dict_search('security.cipher', macsec) == 'gcm-aes-256' and cak_len != GCM_AES_256_LEN: - raise ConfigError(GCM_256_KEY_ERROR) - - if 'source_interface' in macsec: - # MACsec adds a 40 byte overhead (32 byte MACsec + 8 bytes VLAN 802.1ad - # and 802.1q) - we need to check the underlaying MTU if our configured - # MTU is at least 40 bytes less then the MTU of our physical interface. - lower_mtu = Interface(macsec['source_interface']).get_mtu() - if lower_mtu < (int(macsec['mtu']) + 40): - raise ConfigError('MACsec overhead does not fit into underlaying device MTU,\n' \ - f'{lower_mtu} bytes is too small!') - - return None - - -def generate(macsec): - # Only generate wpa_supplicant config if using MKA - if dict_search('security.mka.cak', macsec): - render(wpa_suppl_conf.format(**macsec), 'macsec/wpa_supplicant.conf.j2', macsec) - return None - - -def apply(macsec): - systemd_service = 'wpa_supplicant-macsec@{source_interface}'.format(**macsec) - - # Remove macsec interface on deletion or mandatory parameter change - if 'deleted' in macsec or 'shutdown_required' in macsec: - call(f'systemctl stop {systemd_service}') - - if macsec['ifname'] in interfaces(): - tmp = MACsecIf(macsec['ifname']) - tmp.remove() - - if 'deleted' in macsec: - # delete configuration on interface removal - if os.path.isfile(wpa_suppl_conf.format(**macsec)): - os.unlink(wpa_suppl_conf.format(**macsec)) - - return None - - # It is safe to "re-create" the interface always, there is a sanity - # check that the interface will only be create if its non existent - i = MACsecIf(**macsec) - i.update(macsec) - - # Only reload/restart if using MKA - if dict_search('security.mka.cak', macsec): - if not is_systemd_service_running(systemd_service) or 'shutdown_required' in macsec: - call(f'systemctl reload-or-restart {systemd_service}') - - return None - - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py deleted file mode 100755 index bdeb44837..000000000 --- a/src/conf_mode/interfaces-openvpn.py +++ /dev/null @@ -1,732 +0,0 @@ -#!/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 . - -import os -import re -import tempfile - -from cryptography.hazmat.primitives.asymmetric import ec -from glob import glob -from sys import exit -from ipaddress import IPv4Address -from ipaddress import IPv4Network -from ipaddress import IPv6Address -from ipaddress import IPv6Network -from ipaddress import summarize_address_range -from netifaces import interfaces -from secrets import SystemRandom -from shutil import rmtree - -from vyos.base import DeprecationWarning -from vyos.config import Config -from vyos.configdict import get_interface_dict -from vyos.configdict import is_node_changed -from vyos.configverify import verify_vrf -from vyos.configverify import verify_bridge_delete -from vyos.configverify import verify_mirror_redirect -from vyos.configverify import verify_bond_bridge_member -from vyos.ifconfig import VTunIf -from vyos.pki import load_dh_parameters -from vyos.pki import load_private_key -from vyos.pki import sort_ca_chain -from vyos.pki import verify_ca_chain -from vyos.pki import wrap_certificate -from vyos.pki import wrap_crl -from vyos.pki import wrap_dh_parameters -from vyos.pki import wrap_openvpn_key -from vyos.pki import wrap_private_key -from vyos.template import render -from vyos.template import is_ipv4 -from vyos.template import is_ipv6 -from vyos.utils.dict import dict_search -from vyos.utils.dict import dict_search_args -from vyos.utils.list import is_list_equal -from vyos.utils.file import makedir -from vyos.utils.file import read_file -from vyos.utils.file import write_file -from vyos.utils.kernel import check_kmod -from vyos.utils.kernel import unload_kmod -from vyos.utils.process import call -from vyos.utils.permission import chown -from vyos.utils.process import cmd -from vyos.utils.network import is_addr_assigned - -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -user = 'openvpn' -group = 'openvpn' - -cfg_dir = '/run/openvpn' -cfg_file = '/run/openvpn/{ifname}.conf' -otp_path = '/config/auth/openvpn' -otp_file = '/config/auth/openvpn/{ifname}-otp-secrets' -secret_chars = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567') -service_file = '/run/systemd/system/openvpn@{ifname}.service.d/20-override.conf' - -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', 'openvpn'] - - ifname, openvpn = get_interface_dict(conf, base) - openvpn['auth_user_pass_file'] = '/run/openvpn/{ifname}.pw'.format(**openvpn) - - if 'deleted' in openvpn: - return openvpn - - openvpn['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - if is_node_changed(conf, base + [ifname, 'openvpn-option']): - openvpn.update({'restart_required': {}}) - if is_node_changed(conf, base + [ifname, 'enable-dco']): - openvpn.update({'restart_required': {}}) - - # We have to get the dict using 'get_config_dict' instead of 'get_interface_dict' - # as 'get_interface_dict' merges the defaults in, so we can not check for defaults in there. - tmp = conf.get_config_dict(base + [openvpn['ifname']], get_first_key=True) - - # We have to cleanup the config dict, as default values could enable features - # which are not explicitly enabled on the CLI. Example: server mfa totp - # originate comes with defaults, which will enable the - # totp plugin, even when not set via CLI so we - # need to check this first and drop those keys - if dict_search('server.mfa.totp', tmp) == None: - del openvpn['server']['mfa'] - - # OpenVPN Data-Channel-Offload (DCO) is a Kernel module. If loaded it applies to all - # OpenVPN interfaces. Check if DCO is used by any other interface instance. - tmp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - for interface, interface_config in tmp.items(): - # If one interface has DCO configured, enable it. No need to further check - # all other OpenVPN interfaces. We must use a dedicated key to indicate - # the Kernel module must be loaded or not. The per interface "offload.dco" - # key is required per OpenVPN interface instance. - if dict_search('offload.dco', interface_config) != None: - openvpn['module_load_dco'] = {} - break - - return openvpn - -def is_ec_private_key(pki, cert_name): - if not pki or 'certificate' not in pki: - return False - if cert_name not in pki['certificate']: - return False - - pki_cert = pki['certificate'][cert_name] - if 'private' not in pki_cert or 'key' not in pki_cert['private']: - return False - - key = load_private_key(pki_cert['private']['key']) - return isinstance(key, ec.EllipticCurvePrivateKey) - -def verify_pki(openvpn): - pki = openvpn['pki'] - interface = openvpn['ifname'] - mode = openvpn['mode'] - shared_secret_key = dict_search_args(openvpn, 'shared_secret_key') - tls = dict_search_args(openvpn, 'tls') - - if not bool(shared_secret_key) ^ bool(tls): # xor check if only one is set - raise ConfigError('Must specify only one of "shared-secret-key" and "tls"') - - if mode in ['server', 'client'] and not tls: - raise ConfigError('Must specify "tls" for server and client modes') - - if not pki: - raise ConfigError('PKI is not configured') - - if shared_secret_key: - if not dict_search_args(pki, 'openvpn', 'shared_secret'): - raise ConfigError('There are no openvpn shared-secrets in PKI configuration') - - if shared_secret_key not in pki['openvpn']['shared_secret']: - raise ConfigError(f'Invalid shared-secret on openvpn interface {interface}') - - # If PSK settings are correct, warn about its deprecation - DeprecationWarning("OpenVPN shared-secret support will be removed in future VyOS versions.\n\ - Please migrate your site-to-site tunnels to TLS.\n\ - You can use self-signed certificates with peer fingerprint verification, consult the documentation for details.") - - if tls: - if (mode in ['server', 'client']) and ('ca_certificate' not in tls): - raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface},\ - it is required in server and client modes') - else: - if ('ca_certificate' not in tls) and ('peer_fingerprint' not in tls): - raise ConfigError('Either "tls ca-certificate" or "tls peer-fingerprint" is required\ - on openvpn interface {interface} in site-to-site mode') - - if 'ca_certificate' in tls: - for ca_name in tls['ca_certificate']: - if ca_name not in pki['ca']: - raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}') - - if len(tls['ca_certificate']) > 1: - sorted_chain = sort_ca_chain(tls['ca_certificate'], pki['ca']) - if not verify_ca_chain(sorted_chain, pki['ca']): - raise ConfigError(f'CA certificates are not a valid chain') - - if mode != 'client' and 'auth_key' not in tls: - if 'certificate' not in tls: - raise ConfigError(f'Missing "tls certificate" on openvpn interface {interface}') - - if 'certificate' in tls: - if tls['certificate'] not in pki['certificate']: - raise ConfigError(f'Invalid certificate on openvpn interface {interface}') - - if dict_search_args(pki, 'certificate', tls['certificate'], 'private', 'password_protected') is not None: - raise ConfigError(f'Cannot use encrypted private key on openvpn interface {interface}') - - if 'dh_params' in tls: - pki_dh = pki['dh'][tls['dh_params']] - dh_params = load_dh_parameters(pki_dh['parameters']) - dh_numbers = dh_params.parameter_numbers() - dh_bits = dh_numbers.p.bit_length() - - if dh_bits < 2048: - raise ConfigError(f'Minimum DH key-size is 2048 bits') - - - if 'auth_key' in tls or 'crypt_key' in tls: - if not dict_search_args(pki, 'openvpn', 'shared_secret'): - raise ConfigError('There are no openvpn shared-secrets in PKI configuration') - - if 'auth_key' in tls: - if tls['auth_key'] not in pki['openvpn']['shared_secret']: - raise ConfigError(f'Invalid auth-key on openvpn interface {interface}') - - if 'crypt_key' in tls: - if tls['crypt_key'] not in pki['openvpn']['shared_secret']: - raise ConfigError(f'Invalid crypt-key on openvpn interface {interface}') - -def verify(openvpn): - if 'deleted' in openvpn: - # remove totp secrets file if totp is not configured - if os.path.isfile(otp_file.format(**openvpn)): - os.remove(otp_file.format(**openvpn)) - - verify_bridge_delete(openvpn) - return None - - if 'mode' not in openvpn: - raise ConfigError('Must specify OpenVPN operation mode!') - - # - # OpenVPN client mode - VERIFY - # - if openvpn['mode'] == 'client': - if 'local_port' in openvpn: - raise ConfigError('Cannot specify "local-port" in client mode') - - if 'local_host' in openvpn: - raise ConfigError('Cannot specify "local-host" in client mode') - - if 'remote_host' not in openvpn: - raise ConfigError('Must specify "remote-host" in client mode') - - if openvpn['protocol'] == 'tcp-passive': - raise ConfigError('Protocol "tcp-passive" is not valid in client mode') - - if dict_search('tls.dh_params', openvpn): - raise ConfigError('Cannot specify "tls dh-params" in client mode') - - # - # OpenVPN site-to-site - VERIFY - # - elif openvpn['mode'] == 'site-to-site': - if 'local_address' not in openvpn and 'is_bridge_member' not in openvpn: - raise ConfigError('Must specify "local-address" or add interface to bridge') - - if 'local_address' in openvpn: - if len([addr for addr in openvpn['local_address'] if is_ipv4(addr)]) > 1: - raise ConfigError('Only one IPv4 local-address can be specified') - - if len([addr for addr in openvpn['local_address'] if is_ipv6(addr)]) > 1: - raise ConfigError('Only one IPv6 local-address can be specified') - - if openvpn['device_type'] == 'tun': - if 'remote_address' not in openvpn: - raise ConfigError('Must specify "remote-address"') - - if 'remote_address' in openvpn: - if len([addr for addr in openvpn['remote_address'] if is_ipv4(addr)]) > 1: - raise ConfigError('Only one IPv4 remote-address can be specified') - - if len([addr for addr in openvpn['remote_address'] if is_ipv6(addr)]) > 1: - raise ConfigError('Only one IPv6 remote-address can be specified') - - if not 'local_address' in openvpn: - raise ConfigError('"remote-address" requires "local-address"') - - v4loAddr = [addr for addr in openvpn['local_address'] if is_ipv4(addr)] - v4remAddr = [addr for addr in openvpn['remote_address'] if is_ipv4(addr)] - if v4loAddr and not v4remAddr: - raise ConfigError('IPv4 "local-address" requires IPv4 "remote-address"') - elif v4remAddr and not v4loAddr: - raise ConfigError('IPv4 "remote-address" requires IPv4 "local-address"') - - v6remAddr = [addr for addr in openvpn['remote_address'] if is_ipv6(addr)] - v6loAddr = [addr for addr in openvpn['local_address'] if is_ipv6(addr)] - if v6loAddr and not v6remAddr: - raise ConfigError('IPv6 "local-address" requires IPv6 "remote-address"') - elif v6remAddr and not v6loAddr: - raise ConfigError('IPv6 "remote-address" requires IPv6 "local-address"') - - if is_list_equal(v4loAddr, v4remAddr) or is_list_equal(v6loAddr, v6remAddr): - raise ConfigError('"local-address" and "remote-address" cannot be the same') - - if dict_search('local_host', openvpn) in dict_search('local_address', openvpn): - raise ConfigError('"local-address" cannot be the same as "local-host"') - - if dict_search('remote_host', openvpn) in dict_search('remote_address', openvpn): - raise ConfigError('"remote-address" and "remote-host" can not be the same') - - if openvpn['device_type'] == 'tap' and 'local_address' in openvpn: - # we can only have one local_address, this is ensured above - v4addr = None - for laddr in openvpn['local_address']: - if is_ipv4(laddr): - v4addr = laddr - break - - if v4addr in openvpn['local_address'] and 'subnet_mask' not in openvpn['local_address'][v4addr]: - raise ConfigError('Must specify IPv4 "subnet-mask" for local-address') - - if dict_search('encryption.ncp_ciphers', openvpn): - raise ConfigError('NCP ciphers can only be used in client or server mode') - - else: - # checks for client-server or site-to-site bridged - if 'local_address' in openvpn or 'remote_address' in openvpn: - raise ConfigError('Cannot specify "local-address" or "remote-address" ' \ - 'in client/server or bridge mode') - - # - # OpenVPN server mode - VERIFY - # - if openvpn['mode'] == 'server': - if openvpn['protocol'] == 'tcp-active': - raise ConfigError('Protocol "tcp-active" is not valid in server mode') - - if dict_search('authentication.username', openvpn) or dict_search('authentication.password', openvpn): - raise ConfigError('Cannot specify "authentication" in server mode') - - if 'remote_port' in openvpn: - raise ConfigError('Cannot specify "remote-port" in server mode') - - if 'remote_host' in openvpn: - raise ConfigError('Cannot specify "remote-host" in server mode') - - tmp = dict_search('server.subnet', openvpn) - if tmp: - v4_subnets = len([subnet for subnet in tmp if is_ipv4(subnet)]) - v6_subnets = len([subnet for subnet in tmp if is_ipv6(subnet)]) - if v4_subnets > 1: - raise ConfigError('Cannot specify more than 1 IPv4 server subnet') - if v6_subnets > 1: - raise ConfigError('Cannot specify more than 1 IPv6 server subnet') - - for subnet in tmp: - if is_ipv4(subnet): - subnet = IPv4Network(subnet) - - if openvpn['device_type'] == 'tun' and subnet.prefixlen > 29: - raise ConfigError('Server subnets smaller than /29 with device type "tun" are not supported') - elif openvpn['device_type'] == 'tap' and subnet.prefixlen > 30: - raise ConfigError('Server subnets smaller than /30 with device type "tap" are not supported') - - for client in (dict_search('client', openvpn) or []): - if client['ip'] and not IPv4Address(client['ip'][0]) in subnet: - raise ConfigError(f'Client "{client["name"]}" IP {client["ip"][0]} not in server subnet {subnet}') - - else: - if 'is_bridge_member' not in openvpn: - raise ConfigError('Must specify "server subnet" or add interface to bridge in server mode') - - if hasattr(dict_search('server.client', openvpn), '__iter__'): - for client_k, client_v in dict_search('server.client', openvpn).items(): - if (client_v.get('ip') and len(client_v['ip']) > 1) or (client_v.get('ipv6_ip') and len(client_v['ipv6_ip']) > 1): - raise ConfigError(f'Server client "{client_k}": cannot specify more than 1 IPv4 and 1 IPv6 IP') - - if dict_search('server.client_ip_pool', openvpn): - if not (dict_search('server.client_ip_pool.start', openvpn) and dict_search('server.client_ip_pool.stop', openvpn)): - raise ConfigError('Server client-ip-pool requires both start and stop addresses') - else: - v4PoolStart = IPv4Address(dict_search('server.client_ip_pool.start', openvpn)) - v4PoolStop = IPv4Address(dict_search('server.client_ip_pool.stop', openvpn)) - if v4PoolStart > v4PoolStop: - raise ConfigError(f'Server client-ip-pool start address {v4PoolStart} is larger than stop address {v4PoolStop}') - - v4PoolSize = int(v4PoolStop) - int(v4PoolStart) - if v4PoolSize >= 65536: - raise ConfigError(f'Server client-ip-pool is too large [{v4PoolStart} -> {v4PoolStop} = {v4PoolSize}], maximum is 65536 addresses.') - - v4PoolNets = list(summarize_address_range(v4PoolStart, v4PoolStop)) - for client in (dict_search('client', openvpn) or []): - if client['ip']: - for v4PoolNet in v4PoolNets: - if IPv4Address(client['ip'][0]) in v4PoolNet: - print(f'Warning: Client "{client["name"]}" IP {client["ip"][0]} is in server IP pool, it is not reserved for this client.') - # configuring a client_ip_pool will set 'server ... nopool' which is currently incompatible with 'server-ipv6' (probably to be fixed upstream) - for subnet in (dict_search('server.subnet', openvpn) or []): - if is_ipv6(subnet): - raise ConfigError(f'Setting client-ip-pool is incompatible having an IPv6 server subnet.') - - for subnet in (dict_search('server.subnet', openvpn) or []): - if is_ipv6(subnet): - tmp = dict_search('client_ipv6_pool.base', openvpn) - if tmp: - if not dict_search('server.client_ip_pool', openvpn): - raise ConfigError('IPv6 server pool requires an IPv4 server pool') - - if int(tmp.split('/')[1]) >= 112: - raise ConfigError('IPv6 server pool must be larger than /112') - - # - # todo - weird logic - # - v6PoolStart = IPv6Address(tmp) - v6PoolStop = IPv6Network((v6PoolStart, openvpn['server_ipv6_pool_prefixlen']), strict=False)[-1] # don't remove the parentheses, it's a 2-tuple - v6PoolSize = int(v6PoolStop) - int(v6PoolStart) if int(openvpn['server_ipv6_pool_prefixlen']) > 96 else 65536 - if v6PoolSize < v4PoolSize: - raise ConfigError(f'IPv6 server pool must be at least as large as the IPv4 pool (current sizes: IPv6={v6PoolSize} IPv4={v4PoolSize})') - - v6PoolNets = list(summarize_address_range(v6PoolStart, v6PoolStop)) - for client in (dict_search('client', openvpn) or []): - if client['ipv6_ip']: - for v6PoolNet in v6PoolNets: - if IPv6Address(client['ipv6_ip'][0]) in v6PoolNet: - print(f'Warning: Client "{client["name"]}" IP {client["ipv6_ip"][0]} is in server IP pool, it is not reserved for this client.') - - # add mfa users to the file the mfa plugin uses - if dict_search('server.mfa.totp', openvpn): - user_data = '' - if not os.path.isfile(otp_file.format(**openvpn)): - write_file(otp_file.format(**openvpn), user_data, - user=user, group=group, mode=0o644) - - ovpn_users = read_file(otp_file.format(**openvpn)) - for client in (dict_search('server.client', openvpn) or []): - exists = None - for ovpn_user in ovpn_users.split('\n'): - if re.search('^' + client + ' ', ovpn_user): - user_data += f'{ovpn_user}\n' - exists = 'true' - - if not exists: - random = SystemRandom() - totp_secret = ''.join(random.choice(secret_chars) for _ in range(16)) - user_data += f'{client} otp totp:sha1:base32:{totp_secret}::xxx *\n' - - write_file(otp_file.format(**openvpn), user_data, - user=user, group=group, mode=0o644) - - else: - # checks for both client and site-to-site go here - if dict_search('server.reject_unconfigured_clients', openvpn): - raise ConfigError('Option reject-unconfigured-clients only supported in server mode') - - if 'replace_default_route' in openvpn and 'remote_host' not in openvpn: - raise ConfigError('Cannot set "replace-default-route" without "remote-host"') - - # - # OpenVPN common verification section - # not depending on any operation mode - # - - # verify specified IP address is present on any interface on this system - if 'local_host' in openvpn: - if not is_addr_assigned(openvpn['local_host']): - print('local-host IP address "{local_host}" not assigned' \ - ' to any interface'.format(**openvpn)) - - # TCP active - if openvpn['protocol'] == 'tcp-active': - if 'local_port' in openvpn: - raise ConfigError('Cannot specify "local-port" with "tcp-active"') - - if 'remote_host' not in openvpn: - raise ConfigError('Must specify "remote-host" with "tcp-active"') - - # - # TLS/encryption - # - if 'shared_secret_key' in openvpn: - if dict_search('encryption.cipher', openvpn) in ['aes128gcm', 'aes192gcm', 'aes256gcm']: - raise ConfigError('GCM encryption with shared-secret-key not supported') - - if 'tls' in openvpn: - if {'auth_key', 'crypt_key'} <= set(openvpn['tls']): - raise ConfigError('TLS auth and crypt keys are mutually exclusive') - - tmp = dict_search('tls.role', openvpn) - if tmp: - if openvpn['mode'] in ['client', 'server']: - if not dict_search('tls.auth_key', openvpn): - raise ConfigError('Cannot specify "tls role" in client-server mode') - - if tmp == 'active': - if openvpn['protocol'] == 'tcp-passive': - raise ConfigError('Cannot specify "tcp-passive" when "tls role" is "active"') - - if dict_search('tls.dh_params', openvpn): - raise ConfigError('Cannot specify "tls dh-params" when "tls role" is "active"') - - elif tmp == 'passive': - if openvpn['protocol'] == 'tcp-active': - raise ConfigError('Cannot specify "tcp-active" when "tls role" is "passive"') - - if 'certificate' in openvpn['tls'] and is_ec_private_key(openvpn['pki'], openvpn['tls']['certificate']): - if 'dh_params' in openvpn['tls']: - print('Warning: using dh-params and EC keys simultaneously will ' \ - 'lead to DH ciphers being used instead of ECDH') - - if dict_search('encryption.cipher', openvpn) == 'none': - print('Warning: "encryption none" was specified!') - print('No encryption will be performed and data is transmitted in ' \ - 'plain text over the network!') - - verify_pki(openvpn) - - # - # Auth user/pass - # - if (dict_search('authentication.username', openvpn) and not - dict_search('authentication.password', openvpn)): - raise ConfigError('Password for authentication is missing') - - if (dict_search('authentication.password', openvpn) and not - dict_search('authentication.username', openvpn)): - raise ConfigError('Username for authentication is missing') - - verify_vrf(openvpn) - verify_bond_bridge_member(openvpn) - verify_mirror_redirect(openvpn) - - return None - -def generate_pki_files(openvpn): - pki = openvpn['pki'] - if not pki: - return None - - interface = openvpn['ifname'] - shared_secret_key = dict_search_args(openvpn, 'shared_secret_key') - tls = dict_search_args(openvpn, 'tls') - - if shared_secret_key: - pki_key = pki['openvpn']['shared_secret'][shared_secret_key] - key_path = os.path.join(cfg_dir, f'{interface}_shared.key') - write_file(key_path, wrap_openvpn_key(pki_key['key']), - user=user, group=group) - - if tls: - if 'ca_certificate' in tls: - cert_path = os.path.join(cfg_dir, f'{interface}_ca.pem') - crl_path = os.path.join(cfg_dir, f'{interface}_crl.pem') - - if os.path.exists(cert_path): - os.unlink(cert_path) - - if os.path.exists(crl_path): - os.unlink(crl_path) - - for cert_name in sort_ca_chain(tls['ca_certificate'], pki['ca']): - pki_ca = pki['ca'][cert_name] - - if 'certificate' in pki_ca: - write_file(cert_path, wrap_certificate(pki_ca['certificate']) + "\n", - user=user, group=group, mode=0o600, append=True) - - if 'crl' in pki_ca: - for crl in pki_ca['crl']: - write_file(crl_path, wrap_crl(crl) + "\n", user=user, group=group, - mode=0o600, append=True) - - openvpn['tls']['crl'] = True - - if 'certificate' in tls: - cert_name = tls['certificate'] - pki_cert = pki['certificate'][cert_name] - - if 'certificate' in pki_cert: - cert_path = os.path.join(cfg_dir, f'{interface}_cert.pem') - write_file(cert_path, wrap_certificate(pki_cert['certificate']), - user=user, group=group, mode=0o600) - - if 'private' in pki_cert and 'key' in pki_cert['private']: - key_path = os.path.join(cfg_dir, f'{interface}_cert.key') - write_file(key_path, wrap_private_key(pki_cert['private']['key']), - user=user, group=group, mode=0o600) - - openvpn['tls']['private_key'] = True - - if 'dh_params' in tls: - dh_name = tls['dh_params'] - pki_dh = pki['dh'][dh_name] - - if 'parameters' in pki_dh: - dh_path = os.path.join(cfg_dir, f'{interface}_dh.pem') - write_file(dh_path, wrap_dh_parameters(pki_dh['parameters']), - user=user, group=group, mode=0o600) - - if 'auth_key' in tls: - key_name = tls['auth_key'] - pki_key = pki['openvpn']['shared_secret'][key_name] - - if 'key' in pki_key: - key_path = os.path.join(cfg_dir, f'{interface}_auth.key') - write_file(key_path, wrap_openvpn_key(pki_key['key']), - user=user, group=group, mode=0o600) - - if 'crypt_key' in tls: - key_name = tls['crypt_key'] - pki_key = pki['openvpn']['shared_secret'][key_name] - - if 'key' in pki_key: - key_path = os.path.join(cfg_dir, f'{interface}_crypt.key') - write_file(key_path, wrap_openvpn_key(pki_key['key']), - user=user, group=group, mode=0o600) - - -def generate(openvpn): - interface = openvpn['ifname'] - directory = os.path.dirname(cfg_file.format(**openvpn)) - openvpn['plugin_dir'] = '/usr/lib/openvpn' - # create base config directory on demand - makedir(directory, user, group) - # enforce proper permissions on /run/openvpn - chown(directory, user, group) - - # we can't know in advance which clients have been removed, - # thus all client configs will be removed and re-added on demand - ccd_dir = os.path.join(directory, 'ccd', interface) - if os.path.isdir(ccd_dir): - rmtree(ccd_dir, ignore_errors=True) - - # Remove systemd directories with overrides - service_dir = os.path.dirname(service_file.format(**openvpn)) - if os.path.isdir(service_dir): - rmtree(service_dir, ignore_errors=True) - - if 'deleted' in openvpn or 'disable' in openvpn: - return None - - # create client config directory on demand - makedir(ccd_dir, user, group) - - # Fix file permissons for keys - generate_pki_files(openvpn) - - # Generate User/Password authentication file - if 'authentication' in openvpn: - render(openvpn['auth_user_pass_file'], 'openvpn/auth.pw.j2', openvpn, - user=user, group=group, permission=0o600) - else: - # delete old auth file if present - if os.path.isfile(openvpn['auth_user_pass_file']): - os.remove(openvpn['auth_user_pass_file']) - - # Generate client specific configuration - server_client = dict_search_args(openvpn, 'server', 'client') - if server_client: - for client, client_config in server_client.items(): - client_file = os.path.join(ccd_dir, client) - - # Our client need's to know its subnet mask ... - client_config['server_subnet'] = dict_search('server.subnet', openvpn) - - render(client_file, 'openvpn/client.conf.j2', client_config, - user=user, group=group) - - # we need to support quoting of raw parameters from OpenVPN CLI - # see https://vyos.dev/T1632 - render(cfg_file.format(**openvpn), 'openvpn/server.conf.j2', openvpn, - formater=lambda _: _.replace(""", '"'), user=user, group=group) - - # Render 20-override.conf for OpenVPN service - render(service_file.format(**openvpn), 'openvpn/service-override.conf.j2', openvpn, - formater=lambda _: _.replace(""", '"'), user=user, group=group) - # Reload systemd services config to apply an override - call(f'systemctl daemon-reload') - - return None - -def apply(openvpn): - interface = openvpn['ifname'] - - # Do some cleanup when OpenVPN is disabled/deleted - if 'deleted' in openvpn or 'disable' in openvpn: - call(f'systemctl stop openvpn@{interface}.service') - for cleanup_file in glob(f'/run/openvpn/{interface}.*'): - if os.path.isfile(cleanup_file): - os.unlink(cleanup_file) - - if interface in interfaces(): - VTunIf(interface).remove() - - # dynamically load/unload DCO Kernel extension if requested - dco_module = 'ovpn_dco_v2' - if 'module_load_dco' in openvpn: - check_kmod(dco_module) - else: - unload_kmod(dco_module) - - # Now bail out early if interface is disabled or got deleted - if 'deleted' in openvpn or 'disable' in openvpn: - return None - - # verify specified IP address is present on any interface on this system - # Allow to bind service to nonlocal address, if it virtaual-vrrp address - # or if address will be assign later - if 'local_host' in openvpn: - if not is_addr_assigned(openvpn['local_host']): - cmd('sysctl -w net.ipv4.ip_nonlocal_bind=1') - - # No matching OpenVPN process running - maybe it got killed or none - # existed - nevertheless, spawn new OpenVPN process - action = 'reload-or-restart' - if 'restart_required' in openvpn: - action = 'restart' - call(f'systemctl {action} openvpn@{interface}.service') - - o = VTunIf(**openvpn) - o.update(openvpn) - - return None - - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) - diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py deleted file mode 100755 index 42f084309..000000000 --- a/src/conf_mode/interfaces-pppoe.py +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2019-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 . - -import os - -from sys import exit -from copy import deepcopy -from netifaces import interfaces - -from vyos.config import Config -from vyos.configdict import get_interface_dict -from vyos.configdict import is_node_changed -from vyos.configdict import get_pppoe_interfaces -from vyos.configverify import verify_authentication -from vyos.configverify import verify_source_interface -from vyos.configverify import verify_interface_exists -from vyos.configverify import verify_vrf -from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_mirror_redirect -from vyos.ifconfig import PPPoEIf -from vyos.template import render -from vyos.utils.process import call -from vyos.utils.process import is_systemd_service_running -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', 'pppoe'] - ifname, pppoe = get_interface_dict(conf, base) - - # We should only terminate the PPPoE session if critical parameters change. - # All parameters that can be changed on-the-fly (like interface description) - # should not lead to a reconnect! - for options in ['access-concentrator', 'connect-on-demand', 'service-name', - '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 - break - - if 'deleted' not in pppoe: - # We always set the MRU value to the MTU size. This code path only re-creates - # the old behavior if MRU is not set on the CLI. - if 'mru' not in pppoe: - pppoe['mru'] = pppoe['mtu'] - - return pppoe - -def verify(pppoe): - if 'deleted' in pppoe: - # bail out early - return None - - verify_source_interface(pppoe) - verify_authentication(pppoe) - verify_vrf(pppoe) - verify_mtu_ipv6(pppoe) - verify_mirror_redirect(pppoe) - - if {'connect_on_demand', 'vrf'} <= set(pppoe): - raise ConfigError('On-demand dialing and VRF can not be used at the same time') - - # both MTU and MRU have default values, thus we do not need to check - # if the key exists - if int(pppoe['mru']) > int(pppoe['mtu']): - raise ConfigError('PPPoE MRU needs to be lower then MTU!') - - return None - -def generate(pppoe): - # set up configuration file path variables where our templates will be - # rendered into - ifname = pppoe['ifname'] - config_pppoe = f'/etc/ppp/peers/{ifname}' - - if 'deleted' in pppoe or 'disable' in pppoe: - if os.path.exists(config_pppoe): - os.unlink(config_pppoe) - - return None - - # Create PPP configuration files - render(config_pppoe, 'pppoe/peer.j2', pppoe, permission=0o640) - - return None - -def apply(pppoe): - ifname = pppoe['ifname'] - if 'deleted' in pppoe or 'disable' in pppoe: - if os.path.isdir(f'/sys/class/net/{ifname}'): - p = PPPoEIf(ifname) - p.remove() - call(f'systemctl stop ppp@{ifname}.service') - return None - - # reconnect should only be necessary when certain config options change, - # like ACS name, authentication ... (see get_config() for details) - if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or - 'shutdown_required' in pppoe): - - # cleanup system (e.g. FRR routes first) - if os.path.isdir(f'/sys/class/net/{ifname}'): - p = PPPoEIf(ifname) - p.remove() - - call(f'systemctl restart ppp@{ifname}.service') - # When interface comes "live" a hook is called: - # /etc/ppp/ip-up.d/99-vyos-pppoe-callback - # which triggers PPPoEIf.update() - else: - if os.path.isdir(f'/sys/class/net/{ifname}'): - p = PPPoEIf(ifname) - p.update(pppoe) - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py deleted file mode 100755 index dce5c2358..000000000 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# 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 . - -from sys import exit -from netifaces import interfaces - -from vyos.config import Config -from vyos.configdict import get_interface_dict -from vyos.configdict import is_node_changed -from vyos.configdict import is_source_interface -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 -from vyos.configverify import verify_source_interface -from vyos.configverify import verify_vlan_config -from vyos.configverify import verify_mtu_parent -from vyos.configverify import verify_mirror_redirect -from vyos.configverify import verify_bond_bridge_member -from vyos.ifconfig import MACVLANIf -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', 'pseudo-ethernet'] - ifname, peth = get_interface_dict(conf, base) - - mode = is_node_changed(conf, ['mode']) - if mode: peth.update({'shutdown_required' : {}}) - - if is_node_changed(conf, base + [ifname, 'mode']): - peth.update({'rebuild_required': {}}) - - if 'source_interface' in peth: - _, peth['parent'] = get_interface_dict(conf, ['interfaces', 'ethernet'], - peth['source_interface']) - # test if source-interface is maybe already used by another interface - tmp = is_source_interface(conf, peth['source_interface'], ['macsec']) - if tmp and tmp != ifname: peth.update({'is_source_interface' : tmp}) - - return peth - -def verify(peth): - if 'deleted' in peth: - verify_bridge_delete(peth) - return None - - verify_source_interface(peth) - verify_vrf(peth) - verify_address(peth) - verify_mtu_parent(peth, peth['parent']) - verify_mirror_redirect(peth) - # use common function to verify VLAN configuration - verify_vlan_config(peth) - - return None - -def generate(peth): - return None - -def apply(peth): - # Check if the MACVLAN interface already exists - if 'rebuild_required' in peth or 'deleted' in peth: - if peth['ifname'] in interfaces(): - p = MACVLANIf(peth['ifname']) - # MACVLAN is always needs to be recreated, - # thus we can simply always delete it first. - p.remove() - - if 'deleted' not in peth: - p = MACVLANIf(**peth) - p.update(peth) - - 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-sstpc.py b/src/conf_mode/interfaces-sstpc.py deleted file mode 100755 index b588910dc..000000000 --- a/src/conf_mode/interfaces-sstpc.py +++ /dev/null @@ -1,145 +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 . - -import os -from sys import exit - -from vyos.config import Config -from vyos.configdict import get_interface_dict -from vyos.configdict import is_node_changed -from vyos.configverify import verify_authentication -from vyos.configverify import verify_vrf -from vyos.ifconfig import SSTPCIf -from vyos.pki import encode_certificate -from vyos.pki import find_chain -from vyos.pki import load_certificate -from vyos.template import render -from vyos.utils.process import call -from vyos.utils.dict import dict_search -from vyos.utils.process import is_systemd_service_running -from vyos.utils.file import write_file -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -def get_config(config=None): - """ - Retrive CLI config as dictionary. Dictionary can never be empty, as at least the - interface name will be added or a deleted flag - """ - if config: - conf = config - else: - conf = Config() - base = ['interfaces', 'sstpc'] - ifname, sstpc = get_interface_dict(conf, base) - - # We should only terminate the SSTP client session if critical parameters - # change. All parameters that can be changed on-the-fly (like interface - # description) should not lead to a reconnect! - for options in ['authentication', 'no_peer_dns', 'no_default_route', - 'server', 'ssl']: - if is_node_changed(conf, base + [ifname, options]): - sstpc.update({'shutdown_required': {}}) - # bail out early - no need to further process other nodes - break - - # Load PKI certificates for later processing - sstpc['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - return sstpc - -def verify(sstpc): - if 'deleted' in sstpc: - return None - - verify_authentication(sstpc) - verify_vrf(sstpc) - - if not dict_search('server', sstpc): - raise ConfigError('Remote SSTP server must be specified!') - - if not dict_search('ssl.ca_certificate', sstpc): - raise ConfigError('Missing mandatory CA certificate!') - - return None - -def generate(sstpc): - ifname = sstpc['ifname'] - config_sstpc = f'/etc/ppp/peers/{ifname}' - - sstpc['ca_file_path'] = f'/run/sstpc/{ifname}_ca-cert.pem' - - if 'deleted' in sstpc: - for file in [sstpc['ca_file_path'], config_sstpc]: - if os.path.exists(file): - os.unlink(file) - return None - - ca_name = sstpc['ssl']['ca_certificate'] - pki_ca_cert = sstpc['pki']['ca'][ca_name] - - loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) - loaded_ca_certs = {load_certificate(c['certificate']) - for c in sstpc['pki']['ca'].values()} if 'ca' in sstpc['pki'] else {} - - ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) - - write_file(sstpc['ca_file_path'], '\n'.join(encode_certificate(c) for c in ca_full_chain)) - render(config_sstpc, 'sstp-client/peer.j2', sstpc, permission=0o640) - - return None - -def apply(sstpc): - ifname = sstpc['ifname'] - if 'deleted' in sstpc or 'disable' in sstpc: - if os.path.isdir(f'/sys/class/net/{ifname}'): - p = SSTPCIf(ifname) - p.remove() - call(f'systemctl stop ppp@{ifname}.service') - return None - - # reconnect should only be necessary when specific options change, - # like server, authentication ... (see get_config() for details) - if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or - 'shutdown_required' in sstpc): - - # cleanup system (e.g. FRR routes first) - if os.path.isdir(f'/sys/class/net/{ifname}'): - p = SSTPCIf(ifname) - p.remove() - - call(f'systemctl restart ppp@{ifname}.service') - # When interface comes "live" a hook is called: - # /etc/ppp/ip-up.d/96-vyos-sstpc-callback - # which triggers SSTPCIf.update() - else: - if os.path.isdir(f'/sys/class/net/{ifname}'): - p = SSTPCIf(ifname) - p.update(sstpc) - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py deleted file mode 100755 index 91aed9cc3..000000000 --- a/src/conf_mode/interfaces-tunnel.py +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2022 yOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from sys import exit -from netifaces import interfaces - -from vyos.config import Config -from vyos.configdict import get_interface_dict -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 -from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_mirror_redirect -from vyos.configverify import verify_vrf -from vyos.configverify import verify_tunnel -from vyos.configverify import verify_bond_bridge_member -from vyos.ifconfig import Interface -from vyos.ifconfig import Section -from vyos.ifconfig import TunnelIf -from vyos.utils.network import get_interface_config -from vyos.utils.dict import dict_search -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', 'tunnel'] - ifname, tunnel = get_interface_dict(conf, base) - - if 'deleted' not in tunnel: - tmp = is_node_changed(conf, base + [ifname, 'encapsulation']) - if tmp: tunnel.update({'encapsulation_changed': {}}) - - tmp = is_node_changed(conf, base + [ifname, 'parameters', 'ip', 'key']) - if tmp: tunnel.update({'key_changed': {}}) - - # We also need to inspect other configured tunnels as there are Kernel - # restrictions where we need to comply. E.g. GRE tunnel key can't be used - # twice, or with multiple GRE tunnels to the same location we must specify - # a GRE key - conf.set_level(base) - tunnel['other_tunnels'] = conf.get_config_dict([], key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - # delete our own instance from this dict - ifname = tunnel['ifname'] - del tunnel['other_tunnels'][ifname] - # if only one tunnel is present on the system, no need to keep this key - if len(tunnel['other_tunnels']) == 0: - del tunnel['other_tunnels'] - - # We must check if our interface is configured to be a DMVPN member - nhrp_base = ['protocols', 'nhrp', 'tunnel'] - conf.set_level(nhrp_base) - nhrp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) - if nhrp: tunnel.update({'nhrp' : list(nhrp.keys())}) - - if 'encapsulation' in tunnel and tunnel['encapsulation'] not in ['erspan', 'ip6erspan']: - del tunnel['parameters']['erspan'] - - return tunnel - -def verify(tunnel): - if 'deleted' in tunnel: - verify_bridge_delete(tunnel) - - if 'nhrp' in tunnel and tunnel['ifname'] in tunnel['nhrp']: - raise ConfigError('Tunnel used for NHRP, it can not be deleted!') - - return None - - verify_tunnel(tunnel) - - if tunnel['encapsulation'] in ['erspan', 'ip6erspan']: - if dict_search('parameters.ip.key', tunnel) == None: - raise ConfigError('ERSPAN requires ip key parameter!') - - # this is a default field - ver = int(tunnel['parameters']['erspan']['version']) - if ver == 1: - if 'hw_id' in tunnel['parameters']['erspan']: - raise ConfigError('ERSPAN version 1 does not support hw-id!') - if 'direction' in tunnel['parameters']['erspan']: - raise ConfigError('ERSPAN version 1 does not support direction!') - elif ver == 2: - if 'idx' in tunnel['parameters']['erspan']: - raise ConfigError('ERSPAN version 2 does not index parameter!') - if 'direction' not in tunnel['parameters']['erspan']: - raise ConfigError('ERSPAN version 2 requires direction to be set!') - - # If tunnel source is any and gre key is not set - interface = tunnel['ifname'] - if tunnel['encapsulation'] in ['gre'] and \ - dict_search('source_address', tunnel) == '0.0.0.0' and \ - dict_search('parameters.ip.key', tunnel) == None: - raise ConfigError(f'"parameters ip key" must be set for {interface} when '\ - 'encapsulation is GRE!') - - gre_encapsulations = ['gre', 'gretap'] - if tunnel['encapsulation'] in gre_encapsulations and 'other_tunnels' in tunnel: - # Check pairs tunnel source-address/encapsulation/key with exists tunnels. - # Prevent the same key for 2 tunnels with same source-address/encap. T2920 - for o_tunnel, o_tunnel_conf in tunnel['other_tunnels'].items(): - # no match on encapsulation - bail out - our_encapsulation = tunnel['encapsulation'] - their_encapsulation = o_tunnel_conf['encapsulation'] - if our_encapsulation in gre_encapsulations and their_encapsulation \ - not in gre_encapsulations: - continue - - our_address = dict_search('source_address', tunnel) - our_key = dict_search('parameters.ip.key', tunnel) - their_address = dict_search('source_address', o_tunnel_conf) - their_key = dict_search('parameters.ip.key', o_tunnel_conf) - 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 "{o_tunnel}"!') - else: - our_source_if = dict_search('source_interface', tunnel) - their_source_if = dict_search('source_interface', o_tunnel_conf) - our_remote = dict_search('remote', tunnel) - their_remote = dict_search('remote', o_tunnel_conf) - # If no IP GRE key is defined we can not have more then one GRE tunnel - # bound to any one interface/IP address and the same remote. This will - # result in a OS PermissionError: add tunnel "gre0" failed: File exists - if (their_address == our_address or our_source_if == their_source_if) and \ - our_remote == their_remote: - raise ConfigError(f'Missing required "ip key" parameter when '\ - 'running more then one GRE based tunnel on the '\ - 'same source-interface/source-address') - - # Keys are not allowed with ipip and sit tunnels - if tunnel['encapsulation'] in ['ipip', 'sit']: - if dict_search('parameters.ip.key', tunnel) != None: - raise ConfigError('Keys are not allowed with ipip and sit tunnels!') - - verify_mtu_ipv6(tunnel) - verify_address(tunnel) - verify_vrf(tunnel) - verify_bond_bridge_member(tunnel) - verify_mirror_redirect(tunnel) - - if 'source_interface' in tunnel: - verify_interface_exists(tunnel['source_interface']) - - # TTL != 0 and nopmtudisc are incompatible, parameters and ip use default - # values, thus the keys are always present. - if dict_search('parameters.ip.no_pmtu_discovery', tunnel) != None: - if dict_search('parameters.ip.ttl', tunnel) != '0': - raise ConfigError('Disabled PMTU requires TTL set to "0"!') - if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']: - raise ConfigError('Can not disable PMTU discovery for given encapsulation') - - if dict_search('parameters.ip.ignore_df', tunnel) != None: - if tunnel['encapsulation'] not in ['gretap']: - raise ConfigError('Option ignore-df can only be used on GRETAP tunnels!') - - if dict_search('parameters.ip.no_pmtu_discovery', tunnel) == None: - raise ConfigError('Option ignore-df requires path MTU discovery to be disabled!') - - -def generate(tunnel): - return None - -def apply(tunnel): - interface = tunnel['ifname'] - # If a gretap tunnel is already existing we can not "simply" change local or - # remote addresses. This returns "Operation not supported" by the Kernel. - # There is no other solution to destroy and recreate the tunnel. - encap = '' - remote = '' - tmp = get_interface_config(interface) - if tmp: - encap = dict_search('linkinfo.info_kind', tmp) - remote = dict_search('linkinfo.info_data.remote', tmp) - - if ('deleted' in tunnel or 'encapsulation_changed' in tunnel or encap in - ['gretap', 'ip6gretap', 'erspan', 'ip6erspan'] or remote in ['any'] or - 'key_changed' in tunnel): - if interface in interfaces(): - tmp = Interface(interface) - tmp.remove() - if 'deleted' in tunnel: - return None - - tun = TunnelIf(**tunnel) - tun.update(tunnel) - - return None - -if __name__ == '__main__': - try: - c = get_config() - generate(c) - verify(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/interfaces-virtual-ethernet.py b/src/conf_mode/interfaces-virtual-ethernet.py deleted file mode 100755 index 8efe89c41..000000000 --- a/src/conf_mode/interfaces-virtual-ethernet.py +++ /dev/null @@ -1,114 +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 . - -from sys import exit - -from netifaces import interfaces -from vyos import ConfigError -from vyos import airbag -from vyos.config import Config -from vyos.configdict import get_interface_dict -from vyos.configverify import verify_address -from vyos.configverify import verify_bridge_delete -from vyos.configverify import verify_vrf -from vyos.ifconfig import VethIf - -airbag.enable() - -def get_config(config=None): - """ - Retrive CLI config as dictionary. Dictionary can never be empty, as at - least the interface name will be added or a deleted flag - """ - if config: - conf = config - else: - conf = Config() - base = ['interfaces', 'virtual-ethernet'] - ifname, veth = get_interface_dict(conf, base) - - # We need to know all other veth related interfaces as veth requires a 1:1 - # mapping for the peer-names. The Linux kernel automatically creates both - # interfaces, the local one and the peer-name, but VyOS also needs a peer - # interfaces configrued on the CLI so we can assign proper IP addresses etc. - veth['other_interfaces'] = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - - return veth - - -def verify(veth): - if 'deleted' in veth: - verify_bridge_delete(veth) - # Prevent to delete veth interface which used for another "vethX peer-name" - for iface, iface_config in veth['other_interfaces'].items(): - if veth['ifname'] in iface_config['peer_name']: - ifname = veth['ifname'] - raise ConfigError( - f'Cannot delete "{ifname}" used for "interface {iface} peer-name"' - ) - return None - - verify_vrf(veth) - verify_address(veth) - - if 'peer_name' not in veth: - raise ConfigError(f'Remote peer name must be set for "{veth["ifname"]}"!') - - peer_name = veth['peer_name'] - ifname = veth['ifname'] - - if veth['peer_name'] not in veth['other_interfaces']: - raise ConfigError(f'Used peer-name "{peer_name}" on interface "{ifname}" ' \ - 'is not configured!') - - if veth['other_interfaces'][peer_name]['peer_name'] != ifname: - raise ConfigError( - f'Configuration mismatch between "{ifname}" and "{peer_name}"!') - - if peer_name == ifname: - raise ConfigError( - f'Peer-name "{peer_name}" cannot be the same as interface "{ifname}"!') - - return None - - -def generate(peth): - return None - -def apply(veth): - # Check if the Veth interface already exists - if 'rebuild_required' in veth or 'deleted' in veth: - if veth['ifname'] in interfaces(): - p = VethIf(veth['ifname']) - p.remove() - - if 'deleted' not in veth: - p = VethIf(**veth) - p.update(veth) - - return None - - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/interfaces-vti.py b/src/conf_mode/interfaces-vti.py deleted file mode 100755 index 9871810ae..000000000 --- a/src/conf_mode/interfaces-vti.py +++ /dev/null @@ -1,68 +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 . - -from netifaces import interfaces -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 VTIIf -from vyos.utils.dict import dict_search -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', 'vti'] - _, vti = get_interface_dict(conf, base) - return vti - -def verify(vti): - verify_mirror_redirect(vti) - return None - -def generate(vti): - return None - -def apply(vti): - # Remove macsec interface - if 'deleted' in vti: - VTIIf(**vti).remove() - return None - - tmp = VTIIf(**vti) - tmp.update(vti) - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py deleted file mode 100755 index 4251e611b..000000000 --- a/src/conf_mode/interfaces-vxlan.py +++ /dev/null @@ -1,236 +0,0 @@ -#!/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 . - -import os - -from sys import exit -from netifaces import interfaces - -from vyos.base import Warning -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.configdict import node_changed -from vyos.configverify import verify_address -from vyos.configverify import verify_bridge_delete -from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_mirror_redirect -from vyos.configverify import verify_source_interface -from vyos.configverify import verify_bond_bridge_member -from vyos.ifconfig import Interface -from vyos.ifconfig import VXLANIf -from vyos.template import is_ipv6 -from vyos.utils.dict import dict_search -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', 'vxlan'] - ifname, vxlan = get_interface_dict(conf, base) - - # 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 ['parameters', 'gpe', 'group', 'port', 'remote', - 'source-address', 'source-interface', 'vni']: - if is_node_changed(conf, base + [ifname, cli_option]): - vxlan.update({'rebuild_required': {}}) - break - - # When dealing with VNI filtering we need to know what VNI was actually removed, - # so build up a dict matching the vlan_to_vni structure but with removed values. - tmp = node_changed(conf, base + [ifname, 'vlan-to-vni'], recursive=True) - if tmp: - vxlan.update({'vlan_to_vni_removed': {}}) - for vlan in tmp: - vni = leaf_node_changed(conf, base + [ifname, 'vlan-to-vni', vlan, 'vni']) - vxlan['vlan_to_vni_removed'].update({vlan : {'vni' : vni[0]}}) - - # We need to verify that no other VXLAN tunnel is configured when external - # mode is in use - Linux Kernel limitation - conf.set_level(base) - vxlan['other_tunnels'] = conf.get_config_dict([], key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - # This if-clause is just to be sure - it will always evaluate to true - ifname = vxlan['ifname'] - if ifname in vxlan['other_tunnels']: - del vxlan['other_tunnels'][ifname] - if len(vxlan['other_tunnels']) == 0: - del vxlan['other_tunnels'] - - return vxlan - -def verify(vxlan): - if 'deleted' in vxlan: - verify_bridge_delete(vxlan) - return None - - if int(vxlan['mtu']) < 1500: - Warning('RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU') - - if 'group' in vxlan: - if 'source_interface' not in vxlan: - raise ConfigError('Multicast VXLAN requires an underlaying interface') - verify_source_interface(vxlan) - - if not any(tmp in ['group', 'remote', 'source_address', 'source_interface'] for tmp in vxlan): - raise ConfigError('Group, remote, source-address or source-interface must be configured') - - if 'vni' not in vxlan and dict_search('parameters.external', vxlan) == None: - raise ConfigError('Must either configure VXLAN "vni" or use "external" CLI option!') - - if dict_search('parameters.external', vxlan) != None: - if 'vni' in vxlan: - raise ConfigError('Can not specify both "external" and "VNI"!') - - if 'other_tunnels' in vxlan: - # When multiple VXLAN interfaces are defined and "external" is used, - # all VXLAN interfaces need to have vni-filter enabled! - # See Linux Kernel commit f9c4bb0b245cee35ef66f75bf409c9573d934cf9 - other_vni_filter = False - for tunnel, tunnel_config in vxlan['other_tunnels'].items(): - if dict_search('parameters.vni_filter', tunnel_config) != None: - other_vni_filter = True - break - # eqivalent of the C foo ? 'a' : 'b' statement - vni_filter = True and (dict_search('parameters.vni_filter', vxlan) != None) or False - # If either one is enabled, so must be the other. Both can be off and both can be on - if (vni_filter and not other_vni_filter) or (not vni_filter and other_vni_filter): - raise ConfigError(f'Using multiple VXLAN interfaces with "external" '\ - 'requires all VXLAN interfaces to have "vni-filter" configured!') - - if not vni_filter and not other_vni_filter: - other_tunnels = ', '.join(vxlan['other_tunnels']) - raise ConfigError(f'Only one VXLAN tunnel is supported when "external" '\ - f'CLI option is used and "vni-filter" is unset. '\ - f'Additional tunnels: {other_tunnels}') - - if 'gpe' in vxlan and 'external' not in vxlan: - raise ConfigError(f'VXLAN-GPE is only supported when "external" '\ - f'CLI option is used.') - - if 'source_interface' in vxlan: - # VXLAN adds at least an overhead of 50 byte - we need to check the - # underlaying device if our VXLAN package is not going to be fragmented! - vxlan_overhead = 50 - if 'source_address' in vxlan and is_ipv6(vxlan['source_address']): - # IPv6 adds an extra 20 bytes overhead because the IPv6 header is 20 - # bytes larger than the IPv4 header - assuming no extra options are - # in use. - vxlan_overhead += 20 - - # If source_address is not used - check IPv6 'remote' list - elif 'remote' in vxlan: - if any(is_ipv6(a) for a in vxlan['remote']): - vxlan_overhead += 20 - - lower_mtu = Interface(vxlan['source_interface']).get_mtu() - if lower_mtu < (int(vxlan['mtu']) + vxlan_overhead): - raise ConfigError(f'Underlaying device MTU is to small ({lower_mtu} '\ - f'bytes) for VXLAN overhead ({vxlan_overhead} bytes!)') - - # Check for mixed IPv4 and IPv6 addresses - protocol = None - if 'source_address' in vxlan: - if is_ipv6(vxlan['source_address']): - protocol = 'ipv6' - else: - protocol = 'ipv4' - - if 'remote' in vxlan: - error_msg = 'Can not mix both IPv4 and IPv6 for VXLAN underlay' - for remote in vxlan['remote']: - if is_ipv6(remote): - if protocol == 'ipv4': - raise ConfigError(error_msg) - protocol = 'ipv6' - else: - if protocol == 'ipv6': - raise ConfigError(error_msg) - protocol = 'ipv4' - - if 'vlan_to_vni' in vxlan: - if 'is_bridge_member' not in vxlan: - raise ConfigError('VLAN to VNI mapping requires that VXLAN interface '\ - 'is member of a bridge interface!') - - vnis_used = [] - for vif, vif_config in vxlan['vlan_to_vni'].items(): - if 'vni' not in vif_config: - raise ConfigError(f'Must define VNI for VLAN "{vif}"!') - vni = vif_config['vni'] - if vni in vnis_used: - raise ConfigError(f'VNI "{vni}" is already assigned to a different VLAN!') - vnis_used.append(vni) - - if dict_search('parameters.neighbor_suppress', vxlan) != None: - if 'is_bridge_member' not in vxlan: - raise ConfigError('Neighbor suppression requires that VXLAN interface '\ - 'is member of a bridge interface!') - - verify_mtu_ipv6(vxlan) - verify_address(vxlan) - verify_bond_bridge_member(vxlan) - verify_mirror_redirect(vxlan) - - # We use a defaultValue for port, thus it's always safe to use - if vxlan['port'] == '8472': - Warning('Starting from VyOS 1.4, the default port for VXLAN '\ - 'has been changed to 4789. This matches the IANA assigned '\ - 'standard port number!') - - return None - -def generate(vxlan): - return None - -def apply(vxlan): - # Check if the VXLAN interface already exists - if 'rebuild_required' in vxlan or 'delete' in vxlan: - if vxlan['ifname'] in interfaces(): - v = VXLANIf(vxlan['ifname']) - # VXLAN is super picky and the tunnel always needs to be recreated, - # thus we can simply always delete it first. - v.remove() - - if 'deleted' not in vxlan: - # Finally create the new interface - v = VXLANIf(**vxlan) - v.update(vxlan) - - 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-wireguard.py b/src/conf_mode/interfaces-wireguard.py deleted file mode 100755 index 79e5d3f44..000000000 --- a/src/conf_mode/interfaces-wireguard.py +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# 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 . - -from sys import exit - -from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.configdict import get_interface_dict -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 -from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_mirror_redirect -from vyos.configverify import verify_bond_bridge_member -from vyos.ifconfig import WireGuardIf -from vyos.utils.kernel import check_kmod -from vyos.utils.network import check_port_availability -from vyos.utils.network import is_wireguard_key_pair -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', 'wireguard'] - ifname, wireguard = get_interface_dict(conf, base) - - # Check if a port was changed - tmp = is_node_changed(conf, base + [ifname, 'port']) - if tmp: wireguard['port_changed'] = {} - - # T4702: If anything on a peer changes we remove the peer first and re-add it - if is_node_changed(conf, base + [ifname, 'peer']): - wireguard.update({'rebuild_required': {}}) - - return wireguard - -def verify(wireguard): - if 'deleted' in wireguard: - verify_bridge_delete(wireguard) - return None - - verify_mtu_ipv6(wireguard) - verify_address(wireguard) - verify_vrf(wireguard) - verify_bond_bridge_member(wireguard) - verify_mirror_redirect(wireguard) - - if 'private_key' not in wireguard: - raise ConfigError('Wireguard private-key not defined') - - if 'peer' not in wireguard: - raise ConfigError('At least one Wireguard peer is required!') - - if 'port' in wireguard and 'port_changed' in wireguard: - listen_port = int(wireguard['port']) - if check_port_availability('0.0.0.0', listen_port, 'udp') is not True: - raise ConfigError(f'UDP port {listen_port} is busy or unavailable and ' - 'cannot be used for the interface!') - - # run checks on individual configured WireGuard peer - public_keys = [] - for tmp in wireguard['peer']: - peer = wireguard['peer'][tmp] - - if 'allowed_ips' not in peer: - raise ConfigError(f'Wireguard allowed-ips required for peer "{tmp}"!') - - if 'public_key' not in peer: - raise ConfigError(f'Wireguard public-key required for peer "{tmp}"!') - - if ('address' in peer and 'port' not in peer) or ('port' in peer and 'address' not in peer): - raise ConfigError('Both Wireguard port and address must be defined ' - f'for peer "{tmp}" if either one of them is set!') - - if peer['public_key'] in public_keys: - raise ConfigError(f'Duplicate public-key defined on peer "{tmp}"') - - if 'disable' not in peer: - if is_wireguard_key_pair(wireguard['private_key'], peer['public_key']): - raise ConfigError(f'Peer "{tmp}" has the same public key as the interface "{wireguard["ifname"]}"') - - public_keys.append(peer['public_key']) - -def apply(wireguard): - if 'rebuild_required' in wireguard or 'deleted' in wireguard: - wg = WireGuardIf(**wireguard) - # WireGuard only supports peer removal based on the configured public-key, - # by deleting the entire interface this is the shortcut instead of parsing - # out all peers and removing them one by one. - # - # Peer reconfiguration will always come with a short downtime while the - # WireGuard interface is recreated (see below) - wg.remove() - - # Create the new interface if required - if 'deleted' not in wireguard: - wg = WireGuardIf(**wireguard) - wg.update(wireguard) - - return None - -if __name__ == '__main__': - try: - check_kmod('wireguard') - c = get_config() - verify(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py deleted file mode 100755 index 02b4a2500..000000000 --- a/src/conf_mode/interfaces-wireless.py +++ /dev/null @@ -1,275 +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 . - -import os - -from sys import exit -from re import findall -from netaddr import EUI, mac_unix_expanded - -from vyos.config import Config -from vyos.configdict import get_interface_dict -from vyos.configdict import dict_merge -from vyos.configverify import verify_address -from vyos.configverify import verify_bridge_delete -from vyos.configverify import verify_mirror_redirect -from vyos.configverify import verify_vlan_config -from vyos.configverify import verify_vrf -from vyos.configverify import verify_bond_bridge_member -from vyos.ifconfig import WiFiIf -from vyos.template import render -from vyos.utils.process import call -from vyos.utils.dict import dict_search -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -# XXX: wpa_supplicant works on the source interface -wpa_suppl_conf = '/run/wpa_supplicant/{ifname}.conf' -hostapd_conf = '/run/hostapd/{ifname}.conf' -hostapd_accept_station_conf = '/run/hostapd/{ifname}_station_accept.conf' -hostapd_deny_station_conf = '/run/hostapd/{ifname}_station_deny.conf' - -def find_other_stations(conf, base, ifname): - """ - Only one wireless interface per phy can be in station mode - - find all interfaces attached to a phy which run in station mode - """ - old_level = conf.get_level() - conf.set_level(base) - dict = {} - for phy in os.listdir('/sys/class/ieee80211'): - list = [] - for interface in conf.list_nodes([]): - if interface == ifname: - continue - # the following node is mandatory - if conf.exists([interface, 'physical-device', phy]): - tmp = conf.return_value([interface, 'type']) - if tmp == 'station': - list.append(interface) - if list: - dict.update({phy: list}) - conf.set_level(old_level) - return dict - -def get_config(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', 'wireless'] - - ifname, wifi = get_interface_dict(conf, base) - - if 'deleted' not in wifi: - # then get_interface_dict provides default keys - if wifi.from_defaults(['security', 'wep']): # if not set by user - del wifi['security']['wep'] - if wifi.from_defaults(['security', 'wpa']): # if not set by user - del wifi['security']['wpa'] - - if dict_search('security.wpa', wifi) != None: - wpa_cipher = wifi['security']['wpa'].get('cipher') - wpa_mode = wifi['security']['wpa'].get('mode') - if not wpa_cipher: - tmp = None - if wpa_mode == 'wpa': - tmp = {'security': {'wpa': {'cipher' : ['TKIP', 'CCMP']}}} - elif wpa_mode == 'wpa2': - tmp = {'security': {'wpa': {'cipher' : ['CCMP']}}} - elif wpa_mode == 'both': - tmp = {'security': {'wpa': {'cipher' : ['CCMP', 'TKIP']}}} - - if tmp: wifi = dict_merge(tmp, wifi) - - # Only one wireless interface per phy can be in station mode - tmp = find_other_stations(conf, base, wifi['ifname']) - if tmp: wifi['station_interfaces'] = tmp - - # used in hostapt.conf.j2 - wifi['hostapd_accept_station_conf'] = hostapd_accept_station_conf.format(**wifi) - wifi['hostapd_deny_station_conf'] = hostapd_deny_station_conf.format(**wifi) - - return wifi - -def verify(wifi): - if 'deleted' in wifi: - verify_bridge_delete(wifi) - return None - - if 'physical_device' not in wifi: - raise ConfigError('You must specify a physical-device "phy"') - - if 'type' not in wifi: - raise ConfigError('You must specify a WiFi mode') - - if 'ssid' not in wifi and wifi['type'] != 'monitor': - raise ConfigError('SSID must be configured unless type is set to "monitor"!') - - if wifi['type'] == 'access-point': - if 'country_code' not in wifi: - raise ConfigError('Wireless country-code is mandatory') - - if 'channel' not in wifi: - raise ConfigError('Wireless channel must be configured!') - - if 'security' in wifi: - if {'wep', 'wpa'} <= set(wifi.get('security', {})): - raise ConfigError('Must either use WEP or WPA security!') - - if 'wep' in wifi['security']: - if 'key' in wifi['security']['wep'] and len(wifi['security']['wep']) > 4: - raise ConfigError('No more then 4 WEP keys configurable') - elif 'key' not in wifi['security']['wep']: - raise ConfigError('Security WEP configured - missing WEP keys!') - - elif 'wpa' in wifi['security']: - wpa = wifi['security']['wpa'] - if not any(i in ['passphrase', 'radius'] for i in wpa): - raise ConfigError('Misssing WPA key or RADIUS server') - - if 'radius' in wpa: - if 'server' in wpa['radius']: - for server in wpa['radius']['server']: - if 'key' not in wpa['radius']['server'][server]: - raise ConfigError(f'Misssing RADIUS shared secret key for server: {server}') - - if 'capabilities' in wifi: - capabilities = wifi['capabilities'] - if 'vht' in capabilities: - if 'ht' not in capabilities: - raise ConfigError('Specify HT flags if you want to use VHT!') - - if {'beamform', 'antenna_count'} <= set(capabilities.get('vht', {})): - if capabilities['vht']['antenna_count'] == '1': - raise ConfigError('Cannot use beam forming with just one antenna!') - - if capabilities['vht']['beamform'] == 'single-user-beamformer': - if int(capabilities['vht']['antenna_count']) < 3: - # Nasty Gotcha: see https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf lines 692-705 - raise ConfigError('Single-user beam former requires at least 3 antennas!') - - if 'station_interfaces' in wifi and wifi['type'] == 'station': - phy = wifi['physical_device'] - if phy in wifi['station_interfaces']: - if len(wifi['station_interfaces'][phy]) > 0: - raise ConfigError('Only one station per wireless physical interface possible!') - - verify_address(wifi) - verify_vrf(wifi) - verify_bond_bridge_member(wifi) - verify_mirror_redirect(wifi) - - # use common function to verify VLAN configuration - verify_vlan_config(wifi) - - return None - -def generate(wifi): - interface = wifi['ifname'] - - # always stop hostapd service first before reconfiguring it - call(f'systemctl stop hostapd@{interface}.service') - # always stop wpa_supplicant service first before reconfiguring it - call(f'systemctl stop wpa_supplicant@{interface}.service') - - # Delete config files if interface is removed - if 'deleted' in wifi: - if os.path.isfile(hostapd_conf.format(**wifi)): - os.unlink(hostapd_conf.format(**wifi)) - if os.path.isfile(hostapd_accept_station_conf.format(**wifi)): - os.unlink(hostapd_accept_station_conf.format(**wifi)) - if os.path.isfile(hostapd_deny_station_conf.format(**wifi)): - os.unlink(hostapd_deny_station_conf.format(**wifi)) - if os.path.isfile(wpa_suppl_conf.format(**wifi)): - os.unlink(wpa_suppl_conf.format(**wifi)) - - return None - - if 'mac' not in wifi: - # http://wiki.stocksy.co.uk/wiki/Multiple_SSIDs_with_hostapd - # generate locally administered MAC address from used phy interface - with open('/sys/class/ieee80211/{physical_device}/addresses'.format(**wifi), 'r') as f: - # some PHYs tend to have multiple interfaces and thus supply multiple MAC - # addresses - we only need the first one for our calculation - tmp = f.readline().rstrip() - tmp = EUI(tmp).value - # mask last nibble from the MAC address - tmp &= 0xfffffffffff0 - # set locally administered bit in MAC address - tmp |= 0x020000000000 - # we now need to add an offset to our MAC address indicating this - # subinterfaces index - tmp += int(findall(r'\d+', interface)[0]) - - # convert integer to "real" MAC address representation - mac = EUI(hex(tmp).split('x')[-1]) - # change dialect to use : as delimiter instead of - - mac.dialect = mac_unix_expanded - wifi['mac'] = str(mac) - - # XXX: Jinja2 can not operate on a dictionary key when it starts of with a number - if '40mhz_incapable' in (dict_search('capabilities.ht', wifi) or []): - wifi['capabilities']['ht']['fourtymhz_incapable'] = wifi['capabilities']['ht']['40mhz_incapable'] - del wifi['capabilities']['ht']['40mhz_incapable'] - - # render appropriate new config files depending on access-point or station mode - if wifi['type'] == 'access-point': - render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.j2', wifi) - render(hostapd_accept_station_conf.format(**wifi), 'wifi/hostapd_accept_station.conf.j2', wifi) - render(hostapd_deny_station_conf.format(**wifi), 'wifi/hostapd_deny_station.conf.j2', wifi) - - elif wifi['type'] == 'station': - render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.j2', wifi) - - return None - -def apply(wifi): - interface = wifi['ifname'] - if 'deleted' in wifi: - WiFiIf(interface).remove() - else: - # Finally create the new interface - w = WiFiIf(**wifi) - w.update(wifi) - - # Enable/Disable interface - interface is always placed in - # administrative down state in WiFiIf class - if 'disable' not in wifi: - # Physical interface is now configured. Proceed by starting hostapd or - # wpa_supplicant daemon. When type is monitor we can just skip this. - if wifi['type'] == 'access-point': - call(f'systemctl start hostapd@{interface}.service') - - elif wifi['type'] == 'station': - call(f'systemctl start wpa_supplicant@{interface}.service') - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py deleted file mode 100755 index 2515dc838..000000000 --- a/src/conf_mode/interfaces-wwan.py +++ /dev/null @@ -1,189 +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 . - -import os - -from sys import exit -from time import sleep - -from vyos.config import Config -from vyos.configdict import get_interface_dict -from vyos.configdict import is_node_changed -from vyos.configverify import verify_authentication -from vyos.configverify import verify_interface_exists -from vyos.configverify import verify_mirror_redirect -from vyos.configverify import verify_vrf -from vyos.ifconfig import WWANIf -from vyos.utils.dict import dict_search -from vyos.utils.process import cmd -from vyos.utils.process import call -from vyos.utils.process import DEVNULL -from vyos.utils.process import is_systemd_service_active -from vyos.utils.file import write_file -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -service_name = 'ModemManager.service' -cron_script = '/etc/cron.d/vyos-wwan' - -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', 'wwan'] - ifname, wwan = get_interface_dict(conf, base) - - # We should only terminate the WWAN session if critical parameters change. - # All parameters that can be changed on-the-fly (like interface description) - # should not lead to a reconnect! - tmp = is_node_changed(conf, base + [ifname, 'address']) - if tmp: wwan.update({'shutdown_required': {}}) - - tmp = is_node_changed(conf, base + [ifname, 'apn']) - if tmp: wwan.update({'shutdown_required': {}}) - - tmp = is_node_changed(conf, base + [ifname, 'disable']) - if tmp: wwan.update({'shutdown_required': {}}) - - tmp = is_node_changed(conf, base + [ifname, 'vrf']) - if tmp: wwan.update({'shutdown_required': {}}) - - tmp = is_node_changed(conf, base + [ifname, 'authentication']) - if tmp: wwan.update({'shutdown_required': {}}) - - tmp = is_node_changed(conf, base + [ifname, 'ipv6', 'address', 'autoconf']) - if tmp: wwan.update({'shutdown_required': {}}) - - # We need to know the amount of other WWAN interfaces as ModemManager needs - # to be started or stopped. - wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - # This if-clause is just to be sure - it will always evaluate to true - if ifname in wwan['other_interfaces']: - del wwan['other_interfaces'][ifname] - if len(wwan['other_interfaces']) == 0: - del wwan['other_interfaces'] - - return wwan - -def verify(wwan): - if 'deleted' in wwan: - return None - - ifname = wwan['ifname'] - if not 'apn' in wwan: - raise ConfigError(f'No APN configured for "{ifname}"!') - - verify_interface_exists(ifname) - verify_authentication(wwan) - verify_vrf(wwan) - verify_mirror_redirect(wwan) - - return None - -def generate(wwan): - if 'deleted' in wwan: - # We are the last WWAN interface - there are no other ones remaining - # thus the cronjob needs to go away, too - if 'other_interfaces' not in wwan: - if os.path.exists(cron_script): - os.unlink(cron_script) - return None - - # Install cron triggered helper script to re-dial WWAN interfaces on - # disconnect - e.g. happens during RF signal loss. The script watches every - # WWAN interface - so there is only one instance. - if not os.path.exists(cron_script): - write_file(cron_script, '*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py\n') - - return None - -def apply(wwan): - # ModemManager is required to dial WWAN connections - one instance is - # required to serve all modems. Activate ModemManager on first invocation - # of any WWAN interface. - if not is_systemd_service_active(service_name): - cmd(f'systemctl start {service_name}') - - counter = 100 - # Wait until a modem is detected and then we can continue - while counter > 0: - counter -= 1 - tmp = cmd('mmcli -L') - if tmp != 'No modems were found': - break - sleep(0.250) - - if 'shutdown_required' in wwan: - # we only need the modem number. wwan0 -> 0, wwan1 -> 1 - modem = wwan['ifname'].lstrip('wwan') - base_cmd = f'mmcli --modem {modem}' - # Number of bearers is limited - always disconnect first - cmd(f'{base_cmd} --simple-disconnect') - - w = WWANIf(wwan['ifname']) - if 'deleted' in wwan or 'disable' in wwan: - w.remove() - - # We are the last WWAN interface - there are no other WWAN interfaces - # remaining, thus we can stop ModemManager and free resources. - if 'other_interfaces' not in wwan: - cmd(f'systemctl stop {service_name}') - # Clean CRON helper script which is used for to re-connect when - # RF signal is lost - if os.path.exists(cron_script): - os.unlink(cron_script) - - return None - - if 'shutdown_required' in wwan: - ip_type = 'ipv4' - slaac = dict_search('ipv6.address.autoconf', wwan) != None - if 'address' in wwan: - if 'dhcp' in wwan['address'] and ('dhcpv6' in wwan['address'] or slaac): - ip_type = 'ipv4v6' - elif 'dhcpv6' in wwan['address'] or slaac: - ip_type = 'ipv6' - elif 'dhcp' in wwan['address']: - ip_type = 'ipv4' - - options = f'ip-type={ip_type},apn=' + wwan['apn'] - if 'authentication' in wwan: - options += ',user={username},password={password}'.format(**wwan['authentication']) - - command = f'{base_cmd} --simple-connect="{options}"' - call(command, stdout=DEVNULL) - - w.update(wwan) - 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_bonding.py b/src/conf_mode/interfaces_bonding.py new file mode 100755 index 000000000..8184d8415 --- /dev/null +++ b/src/conf_mode/interfaces_bonding.py @@ -0,0 +1,294 @@ +#!/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 . + +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 is_node_changed +from vyos.configdict import leaf_node_changed +from vyos.configdict import is_member +from vyos.configdict import is_source_interface +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_vlan_config +from vyos.configverify import verify_vrf +from vyos.ifconfig import BondIf +from vyos.ifconfig.ethernet import EthernetIf +from vyos.ifconfig import Section +from vyos.template import render_to_string +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_to_paths_values +from vyos.configdict import has_address_configured +from vyos.configdict import has_vrf_configured +from vyos.configdep import set_dependents, call_dependents +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +def get_bond_mode(mode): + if mode == 'round-robin': + return 'balance-rr' + elif mode == 'active-backup': + return 'active-backup' + elif mode == 'xor-hash': + return 'balance-xor' + elif mode == 'broadcast': + return 'broadcast' + elif mode == '802.3ad': + return '802.3ad' + elif mode == 'transmit-load-balance': + return 'balance-tlb' + elif mode == 'adaptive-load-balance': + return 'balance-alb' + else: + raise ConfigError(f'invalid bond mode "{mode}"') + +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', 'bonding'] + ifname, bond = get_interface_dict(conf, base) + + # To make our own life easier transfor the list of member interfaces + # into a dictionary - we will use this to add additional information + # later on for each member + if 'member' in bond and 'interface' in bond['member']: + # convert list of member interfaces to a dictionary + bond['member']['interface'] = {k: {} for k in bond['member']['interface']} + + if 'mode' in bond: + bond['mode'] = get_bond_mode(bond['mode']) + + tmp = is_node_changed(conf, base + [ifname, 'mode']) + if tmp: bond['shutdown_required'] = {} + + tmp = is_node_changed(conf, base + [ifname, 'lacp-rate']) + if tmp: bond['shutdown_required'] = {} + + # determine which members have been removed + interfaces_removed = leaf_node_changed(conf, base + [ifname, 'member', 'interface']) + # Reset config level to interfaces + old_level = conf.get_level() + conf.set_level(['interfaces']) + + if interfaces_removed: + bond['shutdown_required'] = {} + if 'member' not in bond: + bond['member'] = {} + + tmp = {} + for interface in interfaces_removed: + # if member is deleted from bond, add dependencies to call + # ethernet commit again in apply function + # to apply options under ethernet section + set_dependents('ethernet', conf, interface) + section = Section.section(interface) # this will be 'ethernet' for 'eth0' + if conf.exists([section, interface, 'disable']): + tmp[interface] = {'disable': ''} + else: + tmp[interface] = {} + + # also present the interfaces to be removed from the bond as dictionary + bond['member']['interface_remove'] = tmp + + # Restore existing config level + conf.set_level(old_level) + + if dict_search('member.interface', bond): + for interface, interface_config in bond['member']['interface'].items(): + + interface_ethernet_config = conf.get_config_dict( + ['interfaces', 'ethernet', interface], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_defaults=False, + with_recursive_defaults=False) + + interface_config['config_paths'] = dict_to_paths_values(interface_ethernet_config) + + # Check if member interface is a new member + if not conf.exists_effective(base + [ifname, 'member', 'interface', interface]): + bond['shutdown_required'] = {} + interface_config['new_added'] = {} + + # Check if member interface is disabled + conf.set_level(['interfaces']) + + section = Section.section(interface) # this will be 'ethernet' for 'eth0' + if conf.exists([section, interface, 'disable']): + interface_config['disable'] = '' + + conf.set_level(old_level) + + # Check if member interface is already member of another bridge + tmp = is_member(conf, interface, 'bridge') + if tmp: interface_config['is_bridge_member'] = tmp + + # Check if member interface is already member of a bond + tmp = is_member(conf, interface, 'bonding') + for tmp in is_member(conf, interface, 'bonding'): + if bond['ifname'] == tmp: + continue + interface_config['is_bond_member'] = tmp + + # Check if member interface is used as source-interface on another interface + tmp = is_source_interface(conf, interface) + if tmp: interface_config['is_source_interface'] = tmp + + # bond members must not have an assigned address + tmp = has_address_configured(conf, interface) + if tmp: interface_config['has_address'] = {} + + # bond members must not have a VRF attached + tmp = has_vrf_configured(conf, interface) + if tmp: interface_config['has_vrf'] = {} + return bond + + +def verify(bond): + if 'deleted' in bond: + verify_bridge_delete(bond) + return None + + if 'arp_monitor' in bond: + if 'target' in bond['arp_monitor'] and len(bond['arp_monitor']['target']) > 16: + raise ConfigError('The maximum number of arp-monitor targets is 16') + + if 'interval' in bond['arp_monitor'] and int(bond['arp_monitor']['interval']) > 0: + if bond['mode'] in ['802.3ad', 'balance-tlb', 'balance-alb']: + raise ConfigError('ARP link monitoring does not work for mode 802.3ad, ' \ + 'transmit-load-balance or adaptive-load-balance') + + if 'primary' in bond: + if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: + raise ConfigError('Option primary - mode dependency failed, not' + 'supported in mode {mode}!'.format(**bond)) + + verify_mtu_ipv6(bond) + verify_address(bond) + verify_dhcpv6(bond) + verify_vrf(bond) + verify_mirror_redirect(bond) + + # use common function to verify VLAN configuration + verify_vlan_config(bond) + + bond_name = bond['ifname'] + if dict_search('member.interface', bond): + for interface, interface_config in bond['member']['interface'].items(): + error_msg = f'Can not add interface "{interface}" to bond, ' + + if interface == 'lo': + raise ConfigError('Loopback interface "lo" can not be added to a bond') + + if interface not in interfaces(): + raise ConfigError(error_msg + 'it does not exist!') + + if 'is_bridge_member' in interface_config: + tmp = next(iter(interface_config['is_bridge_member'])) + raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') + + if 'is_bond_member' in interface_config: + tmp = next(iter(interface_config['is_bond_member'])) + raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') + + if 'is_source_interface' in interface_config: + tmp = interface_config['is_source_interface'] + raise ConfigError(error_msg + f'it is the source-interface of "{tmp}"!') + + if 'has_address' in interface_config: + raise ConfigError(error_msg + 'it has an address assigned!') + + if 'has_vrf' in interface_config: + raise ConfigError(error_msg + 'it has a VRF assigned!') + + if 'new_added' in interface_config and 'config_paths' in interface_config: + for option_path, option_value in interface_config['config_paths'].items(): + if option_path in EthernetIf.get_bond_member_allowed_options() : + continue + if option_path in BondIf.get_inherit_bond_options(): + continue + raise ConfigError(error_msg + f'it has a "{option_path.replace(".", " ")}" assigned!') + + if 'primary' in bond: + if bond['primary'] not in bond['member']['interface']: + raise ConfigError(f'Primary interface of bond "{bond_name}" must be a member interface') + + if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: + raise ConfigError('primary interface only works for mode active-backup, ' \ + 'transmit-load-balance or adaptive-load-balance') + + return None + +def generate(bond): + bond['frr_zebra_config'] = '' + if 'deleted' not in bond: + bond['frr_zebra_config'] = render_to_string('frr/evpn.mh.frr.j2', bond) + return None + +def apply(bond): + ifname = bond['ifname'] + b = BondIf(ifname) + if 'deleted' in bond: + # delete interface + b.remove() + else: + b.update(bond) + + if dict_search('member.interface_remove', bond): + try: + call_dependents() + except ConfigError: + raise ConfigError('Error in updating ethernet interface ' + 'after deleting it from bond') + + 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'^interface {ifname}', stop_pattern='^exit', remove_stop_mark=True) + if 'frr_zebra_config' in bond: + frr_cfg.add_before(frr.default_add_before, bond['frr_zebra_config']) + frr_cfg.commit_configuration(zebra_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/interfaces_bridge.py b/src/conf_mode/interfaces_bridge.py new file mode 100755 index 000000000..29991e2da --- /dev/null +++ b/src/conf_mode/interfaces_bridge.py @@ -0,0 +1,186 @@ +#!/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 . + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import node_changed +from vyos.configdict import is_member +from vyos.configdict import is_source_interface +from vyos.configdict import has_vlan_subinterface_configured +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_vrf +from vyos.ifconfig import BridgeIf +from vyos.configdict import has_address_configured +from vyos.configdict import has_vrf_configured +from vyos.configdep import set_dependents +from vyos.configdep import call_dependents +from vyos.utils.dict import dict_search +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', 'bridge'] + ifname, bridge = get_interface_dict(conf, base) + + # determine which members have been removed + tmp = node_changed(conf, base + [ifname, 'member', 'interface']) + if tmp: + if 'member' in bridge: + bridge['member'].update({'interface_remove' : tmp }) + else: + bridge.update({'member' : {'interface_remove' : tmp }}) + + if dict_search('member.interface', bridge) is not None: + for interface in list(bridge['member']['interface']): + # Check if member interface is already member of another bridge + tmp = is_member(conf, interface, 'bridge') + if tmp and bridge['ifname'] not in tmp: + bridge['member']['interface'][interface].update({'is_bridge_member' : tmp}) + + # Check if member interface is already member of a bond + tmp = is_member(conf, interface, 'bonding') + if tmp: bridge['member']['interface'][interface].update({'is_bond_member' : tmp}) + + # Check if member interface is used as source-interface on another interface + tmp = is_source_interface(conf, interface) + if tmp: bridge['member']['interface'][interface].update({'is_source_interface' : tmp}) + + # Bridge members must not have an assigned address + tmp = has_address_configured(conf, interface) + if tmp: bridge['member']['interface'][interface].update({'has_address' : ''}) + + # Bridge members must not have a VRF attached + tmp = has_vrf_configured(conf, interface) + if tmp: bridge['member']['interface'][interface].update({'has_vrf' : ''}) + + # VLAN-aware bridge members must not have VLAN interface configuration + tmp = has_vlan_subinterface_configured(conf,interface) + if 'enable_vlan' in bridge and tmp: + bridge['member']['interface'][interface].update({'has_vlan' : ''}) + + # When using VXLAN member interfaces that are configured for Single + # VXLAN Device (SVD) we need to call the VXLAN conf-mode script to re-create + # VLAN to VNI mappings if required + if interface.startswith('vxlan'): + set_dependents('vxlan', conf, interface) + + # delete empty dictionary keys - no need to run code paths if nothing is there to do + if 'member' in bridge: + if 'interface' in bridge['member'] and len(bridge['member']['interface']) == 0: + del bridge['member']['interface'] + + if len(bridge['member']) == 0: + del bridge['member'] + + return bridge + +def verify(bridge): + if 'deleted' in bridge: + return None + + verify_dhcpv6(bridge) + verify_vrf(bridge) + verify_mirror_redirect(bridge) + + ifname = bridge['ifname'] + + if dict_search('member.interface', bridge): + for interface, interface_config in bridge['member']['interface'].items(): + error_msg = f'Can not add interface "{interface}" to bridge, ' + + if interface == 'lo': + raise ConfigError('Loopback interface "lo" can not be added to a bridge') + + if 'is_bridge_member' in interface_config: + tmp = next(iter(interface_config['is_bridge_member'])) + raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') + + if 'is_bond_member' in interface_config: + tmp = next(iter(interface_config['is_bond_member'])) + raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') + + if 'is_source_interface' in interface_config: + tmp = interface_config['is_source_interface'] + raise ConfigError(error_msg + f'it is the source-interface of "{tmp}"!') + + if 'has_address' in interface_config: + raise ConfigError(error_msg + 'it has an address assigned!') + + if 'has_vrf' in interface_config: + raise ConfigError(error_msg + 'it has a VRF assigned!') + + if 'enable_vlan' in bridge: + if 'has_vlan' in interface_config: + raise ConfigError(error_msg + 'it has VLAN subinterface(s) assigned!') + + if 'wlan' in interface: + raise ConfigError(error_msg + 'VLAN aware cannot be set!') + else: + for option in ['allowed_vlan', 'native_vlan']: + if option in interface_config: + raise ConfigError('Can not use VLAN options on non VLAN aware bridge') + + if 'enable_vlan' in bridge: + if dict_search('vif.1', bridge): + raise ConfigError(f'VLAN 1 sub interface cannot be set for VLAN aware bridge {ifname}, and VLAN 1 is always the parent interface') + else: + if dict_search('vif', bridge): + raise ConfigError(f'You must first activate "enable-vlan" of {ifname} bridge to use "vif"') + + return None + +def generate(bridge): + return None + +def apply(bridge): + br = BridgeIf(bridge['ifname']) + if 'deleted' in bridge: + # delete interface + br.remove() + else: + br.update(bridge) + + for interface in dict_search('member.interface', bridge) or []: + if interface.startswith('vxlan'): + try: + call_dependents() + except ConfigError: + raise ConfigError('Error in updating VXLAN interface after changing bridge!') + + 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_dummy.py b/src/conf_mode/interfaces_dummy.py new file mode 100755 index 000000000..db768b94d --- /dev/null +++ b/src/conf_mode/interfaces_dummy.py @@ -0,0 +1,76 @@ +#!/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 . + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_vrf +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mirror_redirect +from vyos.ifconfig import DummyIf +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', 'dummy'] + _, dummy = get_interface_dict(conf, base) + return dummy + +def verify(dummy): + if 'deleted' in dummy: + verify_bridge_delete(dummy) + return None + + verify_vrf(dummy) + verify_address(dummy) + verify_mirror_redirect(dummy) + + return None + +def generate(dummy): + return None + +def apply(dummy): + d = DummyIf(**dummy) + + # Remove dummy interface + if 'deleted' in dummy: + d.remove() + else: + d.update(dummy) + + 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_ethernet.py b/src/conf_mode/interfaces_ethernet.py new file mode 100755 index 000000000..7374a29f7 --- /dev/null +++ b/src/conf_mode/interfaces_ethernet.py @@ -0,0 +1,400 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-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 . + +import os +import pprint + +from glob import glob +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configverify import verify_address +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_eapol +from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_mtu +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_vlan_config +from vyos.configverify import verify_vrf +from vyos.configverify import verify_bond_bridge_member +from vyos.ethtool import Ethtool +from vyos.ifconfig import EthernetIf +from vyos.ifconfig import BondIf +from vyos.pki import find_chain +from vyos.pki import encode_certificate +from vyos.pki import load_certificate +from vyos.pki import wrap_private_key +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_to_paths_values +from vyos.utils.dict import dict_set +from vyos.utils.dict import dict_delete +from vyos.utils.file import write_file +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +# XXX: wpa_supplicant works on the source interface +cfg_dir = '/run/wpa_supplicant' +wpa_suppl_conf = '/run/wpa_supplicant/{ifname}.conf' + +def update_bond_options(conf: Config, eth_conf: dict) -> list: + """ + Return list of blocked options if interface is a bond member + :param conf: Config object + :type conf: Config + :param eth_conf: Ethernet config dictionary + :type eth_conf: dict + :return: List of blocked options + :rtype: list + """ + blocked_list = [] + bond_name = list(eth_conf['is_bond_member'].keys())[0] + config_without_defaults = conf.get_config_dict( + ['interfaces', 'ethernet', eth_conf['ifname']], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_defaults=False, + with_recursive_defaults=False) + config_with_defaults = conf.get_config_dict( + ['interfaces', 'ethernet', eth_conf['ifname']], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_defaults=True, + with_recursive_defaults=True) + bond_config_with_defaults = conf.get_config_dict( + ['interfaces', 'bonding', bond_name], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_defaults=True, + with_recursive_defaults=True) + eth_dict_paths = dict_to_paths_values(config_without_defaults) + eth_path_base = ['interfaces', 'ethernet', eth_conf['ifname']] + + #if option is configured under ethernet section + for option_path, option_value in eth_dict_paths.items(): + bond_option_value = dict_search(option_path, bond_config_with_defaults) + + #If option is allowed for changing then continue + if option_path in EthernetIf.get_bond_member_allowed_options(): + continue + # if option is inherited from bond then set valued from bond interface + if option_path in BondIf.get_inherit_bond_options(): + # If option equals to bond option then do nothing + if option_value == bond_option_value: + continue + else: + # if ethernet has option and bond interface has + # then copy it from bond + if bond_option_value is not None: + if is_node_changed(conf, eth_path_base + option_path.split('.')): + Warning( + f'Cannot apply "{option_path.replace(".", " ")}" to "{option_value}".' \ + f' Interface "{eth_conf["ifname"]}" is a bond member.' \ + f' Option is inherited from bond "{bond_name}"') + dict_set(option_path, bond_option_value, eth_conf) + continue + # if ethernet has option and bond interface does not have + # then delete it form dict and do not apply it + else: + if is_node_changed(conf, eth_path_base + option_path.split('.')): + Warning( + f'Cannot apply "{option_path.replace(".", " ")}".' \ + f' Interface "{eth_conf["ifname"]}" is a bond member.' \ + f' Option is inherited from bond "{bond_name}"') + dict_delete(option_path, eth_conf) + blocked_list.append(option_path) + + # if inherited option is not configured under ethernet section but configured under bond section + for option_path in BondIf.get_inherit_bond_options(): + bond_option_value = dict_search(option_path, bond_config_with_defaults) + if bond_option_value is not None: + if option_path not in eth_dict_paths: + if is_node_changed(conf, eth_path_base + option_path.split('.')): + Warning( + f'Cannot apply "{option_path.replace(".", " ")}" to "{dict_search(option_path, config_with_defaults)}".' \ + f' Interface "{eth_conf["ifname"]}" is a bond member. ' \ + f'Option is inherited from bond "{bond_name}"') + dict_set(option_path, bond_option_value, eth_conf) + eth_conf['bond_blocked_changes'] = blocked_list + return None + +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() + + # This must be called prior to get_interface_dict(), as this function will + # alter the config level (config.set_level()) + pki = conf.get_config_dict(['pki'], key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + base = ['interfaces', 'ethernet'] + ifname, ethernet = get_interface_dict(conf, base) + if 'is_bond_member' in ethernet: + update_bond_options(conf, ethernet) + + if 'deleted' not in ethernet: + if pki: ethernet['pki'] = pki + + tmp = is_node_changed(conf, base + [ifname, 'speed']) + if tmp: ethernet.update({'speed_duplex_changed': {}}) + + tmp = is_node_changed(conf, base + [ifname, 'duplex']) + if tmp: ethernet.update({'speed_duplex_changed': {}}) + + return ethernet + + + +def verify_speed_duplex(ethernet: dict, ethtool: Ethtool): + """ + Verify speed and duplex + :param ethernet: dictionary which is received from get_interface_dict + :type ethernet: dict + :param ethtool: Ethernet object + :type ethtool: Ethtool + """ + if ((ethernet['speed'] == 'auto' and ethernet['duplex'] != 'auto') or + (ethernet['speed'] != 'auto' and ethernet['duplex'] == 'auto')): + raise ConfigError( + 'Speed/Duplex missmatch. Must be both auto or manually configured') + + if ethernet['speed'] != 'auto' and ethernet['duplex'] != 'auto': + # We need to verify if the requested speed and duplex setting is + # supported by the underlaying NIC. + speed = ethernet['speed'] + duplex = ethernet['duplex'] + if not ethtool.check_speed_duplex(speed, duplex): + raise ConfigError( + f'Adapter does not support changing speed ' \ + f'and duplex settings to: {speed}/{duplex}!') + + +def verify_flow_control(ethernet: dict, ethtool: Ethtool): + """ + Verify flow control + :param ethernet: dictionary which is received from get_interface_dict + :type ethernet: dict + :param ethtool: Ethernet object + :type ethtool: Ethtool + """ + if 'disable_flow_control' in ethernet: + if not ethtool.check_flow_control(): + raise ConfigError( + 'Adapter does not support changing flow-control settings!') + + +def verify_ring_buffer(ethernet: dict, ethtool: Ethtool): + """ + Verify ring buffer + :param ethernet: dictionary which is received from get_interface_dict + :type ethernet: dict + :param ethtool: Ethernet object + :type ethtool: Ethtool + """ + if 'ring_buffer' in ethernet: + max_rx = ethtool.get_ring_buffer_max('rx') + if not max_rx: + raise ConfigError( + 'Driver does not support RX ring-buffer configuration!') + + max_tx = ethtool.get_ring_buffer_max('tx') + if not max_tx: + raise ConfigError( + 'Driver does not support TX ring-buffer configuration!') + + rx = dict_search('ring_buffer.rx', ethernet) + if rx and int(rx) > int(max_rx): + raise ConfigError(f'Driver only supports a maximum RX ring-buffer ' \ + f'size of "{max_rx}" bytes!') + + tx = dict_search('ring_buffer.tx', ethernet) + if tx and int(tx) > int(max_tx): + raise ConfigError(f'Driver only supports a maximum TX ring-buffer ' \ + f'size of "{max_tx}" bytes!') + + +def verify_offload(ethernet: dict, ethtool: Ethtool): + """ + Verify offloading capabilities + :param ethernet: dictionary which is received from get_interface_dict + :type ethernet: dict + :param ethtool: Ethernet object + :type ethtool: Ethtool + """ + if dict_search('offload.rps', ethernet) != None: + if not os.path.exists(f'/sys/class/net/{ethernet["ifname"]}/queues/rx-0/rps_cpus'): + raise ConfigError('Interface does not suport RPS!') + driver = ethtool.get_driver_name() + # T3342 - Xen driver requires special treatment + if driver == 'vif': + if int(ethernet['mtu']) > 1500 and dict_search('offload.sg', ethernet) == None: + raise ConfigError('Xen netback drivers requires scatter-gatter offloading '\ + 'for MTU size larger then 1500 bytes') + + +def verify_allowedbond_changes(ethernet: dict): + """ + Verify changed options if interface is in bonding + :param ethernet: dictionary which is received from get_interface_dict + :type ethernet: dict + """ + if 'bond_blocked_changes' in ethernet: + for option in ethernet['bond_blocked_changes']: + raise ConfigError(f'Cannot configure "{option.replace(".", " ")}"' \ + f' on interface "{ethernet["ifname"]}".' \ + f' Interface is a bond member') + + +def verify(ethernet): + if 'deleted' in ethernet: + return None + if 'is_bond_member' in ethernet: + verify_bond_member(ethernet) + else: + verify_ethernet(ethernet) + + +def verify_bond_member(ethernet): + """ + Verification function for ethernet interface which is in bonding + :param ethernet: dictionary which is received from get_interface_dict + :type ethernet: dict + """ + ifname = ethernet['ifname'] + verify_interface_exists(ifname) + verify_eapol(ethernet) + verify_mirror_redirect(ethernet) + ethtool = Ethtool(ifname) + verify_speed_duplex(ethernet, ethtool) + verify_flow_control(ethernet, ethtool) + verify_ring_buffer(ethernet, ethtool) + verify_offload(ethernet, ethtool) + verify_allowedbond_changes(ethernet) + +def verify_ethernet(ethernet): + """ + Verification function for simple ethernet interface + :param ethernet: dictionary which is received from get_interface_dict + :type ethernet: dict + """ + ifname = ethernet['ifname'] + verify_interface_exists(ifname) + verify_mtu(ethernet) + verify_mtu_ipv6(ethernet) + verify_dhcpv6(ethernet) + verify_address(ethernet) + verify_vrf(ethernet) + verify_bond_bridge_member(ethernet) + verify_eapol(ethernet) + verify_mirror_redirect(ethernet) + ethtool = Ethtool(ifname) + # No need to check speed and duplex keys as both have default values. + verify_speed_duplex(ethernet, ethtool) + verify_flow_control(ethernet, ethtool) + verify_ring_buffer(ethernet, ethtool) + verify_offload(ethernet, ethtool) + # use common function to verify VLAN configuration + verify_vlan_config(ethernet) + return None + + +def generate(ethernet): + # render real configuration file once + wpa_supplicant_conf = wpa_suppl_conf.format(**ethernet) + + if 'deleted' in ethernet: + # delete configuration on interface removal + if os.path.isfile(wpa_supplicant_conf): + os.unlink(wpa_supplicant_conf) + return None + + if 'eapol' in ethernet: + ifname = ethernet['ifname'] + + render(wpa_supplicant_conf, 'ethernet/wpa_supplicant.conf.j2', ethernet) + + cert_file_path = os.path.join(cfg_dir, f'{ifname}_cert.pem') + cert_key_path = os.path.join(cfg_dir, f'{ifname}_cert.key') + + cert_name = ethernet['eapol']['certificate'] + pki_cert = ethernet['pki']['certificate'][cert_name] + + loaded_pki_cert = load_certificate(pki_cert['certificate']) + loaded_ca_certs = {load_certificate(c['certificate']) + for c in ethernet['pki']['ca'].values()} if 'ca' in ethernet['pki'] else {} + + cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs) + + write_file(cert_file_path, + '\n'.join(encode_certificate(c) for c in cert_full_chain)) + write_file(cert_key_path, wrap_private_key(pki_cert['private']['key'])) + + if 'ca_certificate' in ethernet['eapol']: + ca_cert_file_path = os.path.join(cfg_dir, f'{ifname}_ca.pem') + ca_chains = [] + + for ca_cert_name in ethernet['eapol']['ca_certificate']: + pki_ca_cert = ethernet['pki']['ca'][ca_cert_name] + loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) + ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + ca_chains.append( + '\n'.join(encode_certificate(c) for c in ca_full_chain)) + + write_file(ca_cert_file_path, '\n'.join(ca_chains)) + + return None + +def apply(ethernet): + ifname = ethernet['ifname'] + # take care about EAPoL supplicant daemon + eapol_action='stop' + + e = EthernetIf(ifname) + if 'deleted' in ethernet: + # delete interface + e.remove() + else: + e.update(ethernet) + if 'eapol' in ethernet: + eapol_action='reload-or-restart' + + call(f'systemctl {eapol_action} wpa_supplicant-wired@{ifname}') + +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_geneve.py b/src/conf_mode/interfaces_geneve.py new file mode 100755 index 000000000..f6694ddde --- /dev/null +++ b/src/conf_mode/interfaces_geneve.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# +# 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 +# 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 . + +from sys import exit +from netifaces import interfaces + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configverify import verify_address +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_bond_bridge_member +from vyos.ifconfig import GeneveIf +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', 'geneve'] + ifname, geneve = get_interface_dict(conf, base) + + # 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', 'parameters']: + if is_node_changed(conf, base + [ifname, cli_option]): + geneve.update({'rebuild_required': {}}) + + return geneve + +def verify(geneve): + if 'deleted' in geneve: + verify_bridge_delete(geneve) + return None + + verify_mtu_ipv6(geneve) + verify_address(geneve) + verify_bond_bridge_member(geneve) + verify_mirror_redirect(geneve) + + if 'remote' not in geneve: + raise ConfigError('Remote side must be configured') + + if 'vni' not in geneve: + raise ConfigError('VNI must be configured') + + return None + + +def generate(geneve): + return None + +def apply(geneve): + # Check if GENEVE interface already exists + if 'rebuild_required' in geneve or 'delete' in geneve: + if geneve['ifname'] in interfaces(): + g = GeneveIf(geneve['ifname']) + # GENEVE is super picky and the tunnel always needs to be recreated, + # thus we can simply always delete it first. + g.remove() + + if 'deleted' not in geneve: + # Finally create the new interface + g = GeneveIf(**geneve) + g.update(geneve) + + 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_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 . + +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_l2tpv3.py b/src/conf_mode/interfaces_l2tpv3.py new file mode 100755 index 000000000..e1db3206e --- /dev/null +++ b/src/conf_mode/interfaces_l2tpv3.py @@ -0,0 +1,112 @@ +#!/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 . + +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.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_bond_bridge_member +from vyos.ifconfig import L2TPv3If +from vyos.utils.kernel import check_kmod +from vyos.utils.network import is_addr_assigned +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +k_mod = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6'] + +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', 'l2tpv3'] + ifname, l2tpv3 = get_interface_dict(conf, base) + + # To delete an l2tpv3 interface we need the current tunnel and session-id + if 'deleted' in l2tpv3: + tmp = leaf_node_changed(conf, base + [ifname, 'tunnel-id']) + # leaf_node_changed() returns a list + l2tpv3.update({'tunnel_id': tmp[0]}) + + tmp = leaf_node_changed(conf, base + [ifname, 'session-id']) + l2tpv3.update({'session_id': tmp[0]}) + + return l2tpv3 + +def verify(l2tpv3): + if 'deleted' in l2tpv3: + verify_bridge_delete(l2tpv3) + return None + + interface = l2tpv3['ifname'] + + for key in ['source_address', 'remote', 'tunnel_id', 'peer_tunnel_id', + 'session_id', 'peer_session_id']: + if key not in l2tpv3: + tmp = key.replace('_', '-') + raise ConfigError(f'Missing mandatory L2TPv3 option: "{tmp}"!') + + if not is_addr_assigned(l2tpv3['source_address']): + raise ConfigError('L2TPv3 source-address address "{source_address}" ' + 'not configured on any interface!'.format(**l2tpv3)) + + verify_mtu_ipv6(l2tpv3) + verify_address(l2tpv3) + verify_bond_bridge_member(l2tpv3) + verify_mirror_redirect(l2tpv3) + return None + +def generate(l2tpv3): + return None + +def apply(l2tpv3): + # Check if L2TPv3 interface already exists + if l2tpv3['ifname'] in interfaces(): + # L2TPv3 is picky when changing tunnels/sessions, thus we can simply + # always delete it first. + l = L2TPv3If(**l2tpv3) + l.remove() + + if 'deleted' not in l2tpv3: + # Finally create the new interface + l = L2TPv3If(**l2tpv3) + l.update(l2tpv3) + + return None + +if __name__ == '__main__': + try: + check_kmod(k_mod) + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_loopback.py b/src/conf_mode/interfaces_loopback.py new file mode 100755 index 000000000..08d34477a --- /dev/null +++ b/src/conf_mode/interfaces_loopback.py @@ -0,0 +1,66 @@ +#!/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 . + +import os + +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 LoopbackIf +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', 'loopback'] + _, loopback = get_interface_dict(conf, base) + return loopback + +def verify(loopback): + verify_mirror_redirect(loopback) + return None + +def generate(loopback): + return None + +def apply(loopback): + l = LoopbackIf(**loopback) + if 'deleted' in loopback: + l.remove() + else: + l.update(loopback) + + 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_macsec.py b/src/conf_mode/interfaces_macsec.py new file mode 100755 index 000000000..0a927ac88 --- /dev/null +++ b/src/conf_mode/interfaces_macsec.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from netifaces import interfaces +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configdict import is_source_interface +from vyos.configverify import verify_vrf +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_bond_bridge_member +from vyos.ifconfig import MACsecIf +from vyos.ifconfig import Interface +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.utils.process import is_systemd_service_running +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +# XXX: wpa_supplicant works on the source interface +wpa_suppl_conf = '/run/wpa_supplicant/{source_interface}.conf' + +# Constants +## gcm-aes-128 requires a 128bit long key - 32 characters (string) = 16byte = 128bit +GCM_AES_128_LEN: int = 32 +GCM_128_KEY_ERROR = 'gcm-aes-128 requires a 128bit long key!' +## gcm-aes-256 requires a 256bit long key - 64 characters (string) = 32byte = 256bit +GCM_AES_256_LEN: int = 64 +GCM_256_KEY_ERROR = 'gcm-aes-256 requires a 256bit long key!' + +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', 'macsec'] + ifname, macsec = get_interface_dict(conf, base) + + # Check if interface has been removed + if 'deleted' in macsec: + source_interface = conf.return_effective_value(base + [ifname, 'source-interface']) + macsec.update({'source_interface': source_interface}) + + if is_node_changed(conf, base + [ifname, 'security']): + macsec.update({'shutdown_required': {}}) + + if is_node_changed(conf, base + [ifname, 'source_interface']): + macsec.update({'shutdown_required': {}}) + + if 'source_interface' in macsec: + tmp = is_source_interface(conf, macsec['source_interface'], ['macsec', 'pseudo-ethernet']) + if tmp and tmp != ifname: macsec.update({'is_source_interface' : tmp}) + + return macsec + + +def verify(macsec): + if 'deleted' in macsec: + verify_bridge_delete(macsec) + return None + + verify_source_interface(macsec) + verify_vrf(macsec) + verify_mtu_ipv6(macsec) + verify_address(macsec) + verify_bond_bridge_member(macsec) + verify_mirror_redirect(macsec) + + if dict_search('security.cipher', macsec) == None: + raise ConfigError('Cipher suite must be set for MACsec "{ifname}"'.format(**macsec)) + + if dict_search('security.encrypt', macsec) != None: + # Check that only static or MKA config is present + if dict_search('security.static', macsec) != None and (dict_search('security.mka.cak', macsec) != None or dict_search('security.mka.ckn', macsec) != None): + raise ConfigError('Only static or MKA can be used!') + + # Logic to check static configuration + if dict_search('security.static', macsec) != None: + # tx-key must be defined + if dict_search('security.static.key', macsec) == None: + raise ConfigError('Static MACsec tx-key must be defined.') + + tx_len = len(dict_search('security.static.key', macsec)) + + if dict_search('security.cipher', macsec) == 'gcm-aes-128' and tx_len != GCM_AES_128_LEN: + raise ConfigError(GCM_128_KEY_ERROR) + + if dict_search('security.cipher', macsec) == 'gcm-aes-256' and tx_len != GCM_AES_256_LEN: + raise ConfigError(GCM_256_KEY_ERROR) + + # Make sure at least one peer is defined + if 'peer' not in macsec['security']['static']: + raise ConfigError('Must have at least one peer defined for static MACsec') + + # For every enabled peer, make sure a MAC and rx-key is defined + for peer, peer_config in macsec['security']['static']['peer'].items(): + if 'disable' not in peer_config and ('mac' not in peer_config or 'key' not in peer_config): + raise ConfigError('Every enabled MACsec static peer must have a MAC address and rx-key defined.') + + # check rx-key length against cipher suite + rx_len = len(peer_config['key']) + + if dict_search('security.cipher', macsec) == 'gcm-aes-128' and rx_len != GCM_AES_128_LEN: + raise ConfigError(GCM_128_KEY_ERROR) + + if dict_search('security.cipher', macsec) == 'gcm-aes-256' and rx_len != GCM_AES_256_LEN: + raise ConfigError(GCM_256_KEY_ERROR) + + # Logic to check MKA configuration + else: + if dict_search('security.mka.cak', macsec) == None or dict_search('security.mka.ckn', macsec) == None: + raise ConfigError('Missing mandatory MACsec security keys as encryption is enabled!') + + cak_len = len(dict_search('security.mka.cak', macsec)) + + if dict_search('security.cipher', macsec) == 'gcm-aes-128' and cak_len != GCM_AES_128_LEN: + raise ConfigError(GCM_128_KEY_ERROR) + + elif dict_search('security.cipher', macsec) == 'gcm-aes-256' and cak_len != GCM_AES_256_LEN: + raise ConfigError(GCM_256_KEY_ERROR) + + if 'source_interface' in macsec: + # MACsec adds a 40 byte overhead (32 byte MACsec + 8 bytes VLAN 802.1ad + # and 802.1q) - we need to check the underlaying MTU if our configured + # MTU is at least 40 bytes less then the MTU of our physical interface. + lower_mtu = Interface(macsec['source_interface']).get_mtu() + if lower_mtu < (int(macsec['mtu']) + 40): + raise ConfigError('MACsec overhead does not fit into underlaying device MTU,\n' \ + f'{lower_mtu} bytes is too small!') + + return None + + +def generate(macsec): + # Only generate wpa_supplicant config if using MKA + if dict_search('security.mka.cak', macsec): + render(wpa_suppl_conf.format(**macsec), 'macsec/wpa_supplicant.conf.j2', macsec) + return None + + +def apply(macsec): + systemd_service = 'wpa_supplicant-macsec@{source_interface}'.format(**macsec) + + # Remove macsec interface on deletion or mandatory parameter change + if 'deleted' in macsec or 'shutdown_required' in macsec: + call(f'systemctl stop {systemd_service}') + + if macsec['ifname'] in interfaces(): + tmp = MACsecIf(macsec['ifname']) + tmp.remove() + + if 'deleted' in macsec: + # delete configuration on interface removal + if os.path.isfile(wpa_suppl_conf.format(**macsec)): + os.unlink(wpa_suppl_conf.format(**macsec)) + + return None + + # It is safe to "re-create" the interface always, there is a sanity + # check that the interface will only be create if its non existent + i = MACsecIf(**macsec) + i.update(macsec) + + # Only reload/restart if using MKA + if dict_search('security.mka.cak', macsec): + if not is_systemd_service_running(systemd_service) or 'shutdown_required' in macsec: + call(f'systemctl reload-or-restart {systemd_service}') + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_openvpn.py b/src/conf_mode/interfaces_openvpn.py new file mode 100755 index 000000000..bdeb44837 --- /dev/null +++ b/src/conf_mode/interfaces_openvpn.py @@ -0,0 +1,732 @@ +#!/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 . + +import os +import re +import tempfile + +from cryptography.hazmat.primitives.asymmetric import ec +from glob import glob +from sys import exit +from ipaddress import IPv4Address +from ipaddress import IPv4Network +from ipaddress import IPv6Address +from ipaddress import IPv6Network +from ipaddress import summarize_address_range +from netifaces import interfaces +from secrets import SystemRandom +from shutil import rmtree + +from vyos.base import DeprecationWarning +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configverify import verify_vrf +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_bond_bridge_member +from vyos.ifconfig import VTunIf +from vyos.pki import load_dh_parameters +from vyos.pki import load_private_key +from vyos.pki import sort_ca_chain +from vyos.pki import verify_ca_chain +from vyos.pki import wrap_certificate +from vyos.pki import wrap_crl +from vyos.pki import wrap_dh_parameters +from vyos.pki import wrap_openvpn_key +from vyos.pki import wrap_private_key +from vyos.template import render +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_args +from vyos.utils.list import is_list_equal +from vyos.utils.file import makedir +from vyos.utils.file import read_file +from vyos.utils.file import write_file +from vyos.utils.kernel import check_kmod +from vyos.utils.kernel import unload_kmod +from vyos.utils.process import call +from vyos.utils.permission import chown +from vyos.utils.process import cmd +from vyos.utils.network import is_addr_assigned + +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +user = 'openvpn' +group = 'openvpn' + +cfg_dir = '/run/openvpn' +cfg_file = '/run/openvpn/{ifname}.conf' +otp_path = '/config/auth/openvpn' +otp_file = '/config/auth/openvpn/{ifname}-otp-secrets' +secret_chars = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567') +service_file = '/run/systemd/system/openvpn@{ifname}.service.d/20-override.conf' + +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', 'openvpn'] + + ifname, openvpn = get_interface_dict(conf, base) + openvpn['auth_user_pass_file'] = '/run/openvpn/{ifname}.pw'.format(**openvpn) + + if 'deleted' in openvpn: + return openvpn + + openvpn['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + if is_node_changed(conf, base + [ifname, 'openvpn-option']): + openvpn.update({'restart_required': {}}) + if is_node_changed(conf, base + [ifname, 'enable-dco']): + openvpn.update({'restart_required': {}}) + + # We have to get the dict using 'get_config_dict' instead of 'get_interface_dict' + # as 'get_interface_dict' merges the defaults in, so we can not check for defaults in there. + tmp = conf.get_config_dict(base + [openvpn['ifname']], get_first_key=True) + + # We have to cleanup the config dict, as default values could enable features + # which are not explicitly enabled on the CLI. Example: server mfa totp + # originate comes with defaults, which will enable the + # totp plugin, even when not set via CLI so we + # need to check this first and drop those keys + if dict_search('server.mfa.totp', tmp) == None: + del openvpn['server']['mfa'] + + # OpenVPN Data-Channel-Offload (DCO) is a Kernel module. If loaded it applies to all + # OpenVPN interfaces. Check if DCO is used by any other interface instance. + tmp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + for interface, interface_config in tmp.items(): + # If one interface has DCO configured, enable it. No need to further check + # all other OpenVPN interfaces. We must use a dedicated key to indicate + # the Kernel module must be loaded or not. The per interface "offload.dco" + # key is required per OpenVPN interface instance. + if dict_search('offload.dco', interface_config) != None: + openvpn['module_load_dco'] = {} + break + + return openvpn + +def is_ec_private_key(pki, cert_name): + if not pki or 'certificate' not in pki: + return False + if cert_name not in pki['certificate']: + return False + + pki_cert = pki['certificate'][cert_name] + if 'private' not in pki_cert or 'key' not in pki_cert['private']: + return False + + key = load_private_key(pki_cert['private']['key']) + return isinstance(key, ec.EllipticCurvePrivateKey) + +def verify_pki(openvpn): + pki = openvpn['pki'] + interface = openvpn['ifname'] + mode = openvpn['mode'] + shared_secret_key = dict_search_args(openvpn, 'shared_secret_key') + tls = dict_search_args(openvpn, 'tls') + + if not bool(shared_secret_key) ^ bool(tls): # xor check if only one is set + raise ConfigError('Must specify only one of "shared-secret-key" and "tls"') + + if mode in ['server', 'client'] and not tls: + raise ConfigError('Must specify "tls" for server and client modes') + + if not pki: + raise ConfigError('PKI is not configured') + + if shared_secret_key: + if not dict_search_args(pki, 'openvpn', 'shared_secret'): + raise ConfigError('There are no openvpn shared-secrets in PKI configuration') + + if shared_secret_key not in pki['openvpn']['shared_secret']: + raise ConfigError(f'Invalid shared-secret on openvpn interface {interface}') + + # If PSK settings are correct, warn about its deprecation + DeprecationWarning("OpenVPN shared-secret support will be removed in future VyOS versions.\n\ + Please migrate your site-to-site tunnels to TLS.\n\ + You can use self-signed certificates with peer fingerprint verification, consult the documentation for details.") + + if tls: + if (mode in ['server', 'client']) and ('ca_certificate' not in tls): + raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface},\ + it is required in server and client modes') + else: + if ('ca_certificate' not in tls) and ('peer_fingerprint' not in tls): + raise ConfigError('Either "tls ca-certificate" or "tls peer-fingerprint" is required\ + on openvpn interface {interface} in site-to-site mode') + + if 'ca_certificate' in tls: + for ca_name in tls['ca_certificate']: + if ca_name not in pki['ca']: + raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}') + + if len(tls['ca_certificate']) > 1: + sorted_chain = sort_ca_chain(tls['ca_certificate'], pki['ca']) + if not verify_ca_chain(sorted_chain, pki['ca']): + raise ConfigError(f'CA certificates are not a valid chain') + + if mode != 'client' and 'auth_key' not in tls: + if 'certificate' not in tls: + raise ConfigError(f'Missing "tls certificate" on openvpn interface {interface}') + + if 'certificate' in tls: + if tls['certificate'] not in pki['certificate']: + raise ConfigError(f'Invalid certificate on openvpn interface {interface}') + + if dict_search_args(pki, 'certificate', tls['certificate'], 'private', 'password_protected') is not None: + raise ConfigError(f'Cannot use encrypted private key on openvpn interface {interface}') + + if 'dh_params' in tls: + pki_dh = pki['dh'][tls['dh_params']] + dh_params = load_dh_parameters(pki_dh['parameters']) + dh_numbers = dh_params.parameter_numbers() + dh_bits = dh_numbers.p.bit_length() + + if dh_bits < 2048: + raise ConfigError(f'Minimum DH key-size is 2048 bits') + + + if 'auth_key' in tls or 'crypt_key' in tls: + if not dict_search_args(pki, 'openvpn', 'shared_secret'): + raise ConfigError('There are no openvpn shared-secrets in PKI configuration') + + if 'auth_key' in tls: + if tls['auth_key'] not in pki['openvpn']['shared_secret']: + raise ConfigError(f'Invalid auth-key on openvpn interface {interface}') + + if 'crypt_key' in tls: + if tls['crypt_key'] not in pki['openvpn']['shared_secret']: + raise ConfigError(f'Invalid crypt-key on openvpn interface {interface}') + +def verify(openvpn): + if 'deleted' in openvpn: + # remove totp secrets file if totp is not configured + if os.path.isfile(otp_file.format(**openvpn)): + os.remove(otp_file.format(**openvpn)) + + verify_bridge_delete(openvpn) + return None + + if 'mode' not in openvpn: + raise ConfigError('Must specify OpenVPN operation mode!') + + # + # OpenVPN client mode - VERIFY + # + if openvpn['mode'] == 'client': + if 'local_port' in openvpn: + raise ConfigError('Cannot specify "local-port" in client mode') + + if 'local_host' in openvpn: + raise ConfigError('Cannot specify "local-host" in client mode') + + if 'remote_host' not in openvpn: + raise ConfigError('Must specify "remote-host" in client mode') + + if openvpn['protocol'] == 'tcp-passive': + raise ConfigError('Protocol "tcp-passive" is not valid in client mode') + + if dict_search('tls.dh_params', openvpn): + raise ConfigError('Cannot specify "tls dh-params" in client mode') + + # + # OpenVPN site-to-site - VERIFY + # + elif openvpn['mode'] == 'site-to-site': + if 'local_address' not in openvpn and 'is_bridge_member' not in openvpn: + raise ConfigError('Must specify "local-address" or add interface to bridge') + + if 'local_address' in openvpn: + if len([addr for addr in openvpn['local_address'] if is_ipv4(addr)]) > 1: + raise ConfigError('Only one IPv4 local-address can be specified') + + if len([addr for addr in openvpn['local_address'] if is_ipv6(addr)]) > 1: + raise ConfigError('Only one IPv6 local-address can be specified') + + if openvpn['device_type'] == 'tun': + if 'remote_address' not in openvpn: + raise ConfigError('Must specify "remote-address"') + + if 'remote_address' in openvpn: + if len([addr for addr in openvpn['remote_address'] if is_ipv4(addr)]) > 1: + raise ConfigError('Only one IPv4 remote-address can be specified') + + if len([addr for addr in openvpn['remote_address'] if is_ipv6(addr)]) > 1: + raise ConfigError('Only one IPv6 remote-address can be specified') + + if not 'local_address' in openvpn: + raise ConfigError('"remote-address" requires "local-address"') + + v4loAddr = [addr for addr in openvpn['local_address'] if is_ipv4(addr)] + v4remAddr = [addr for addr in openvpn['remote_address'] if is_ipv4(addr)] + if v4loAddr and not v4remAddr: + raise ConfigError('IPv4 "local-address" requires IPv4 "remote-address"') + elif v4remAddr and not v4loAddr: + raise ConfigError('IPv4 "remote-address" requires IPv4 "local-address"') + + v6remAddr = [addr for addr in openvpn['remote_address'] if is_ipv6(addr)] + v6loAddr = [addr for addr in openvpn['local_address'] if is_ipv6(addr)] + if v6loAddr and not v6remAddr: + raise ConfigError('IPv6 "local-address" requires IPv6 "remote-address"') + elif v6remAddr and not v6loAddr: + raise ConfigError('IPv6 "remote-address" requires IPv6 "local-address"') + + if is_list_equal(v4loAddr, v4remAddr) or is_list_equal(v6loAddr, v6remAddr): + raise ConfigError('"local-address" and "remote-address" cannot be the same') + + if dict_search('local_host', openvpn) in dict_search('local_address', openvpn): + raise ConfigError('"local-address" cannot be the same as "local-host"') + + if dict_search('remote_host', openvpn) in dict_search('remote_address', openvpn): + raise ConfigError('"remote-address" and "remote-host" can not be the same') + + if openvpn['device_type'] == 'tap' and 'local_address' in openvpn: + # we can only have one local_address, this is ensured above + v4addr = None + for laddr in openvpn['local_address']: + if is_ipv4(laddr): + v4addr = laddr + break + + if v4addr in openvpn['local_address'] and 'subnet_mask' not in openvpn['local_address'][v4addr]: + raise ConfigError('Must specify IPv4 "subnet-mask" for local-address') + + if dict_search('encryption.ncp_ciphers', openvpn): + raise ConfigError('NCP ciphers can only be used in client or server mode') + + else: + # checks for client-server or site-to-site bridged + if 'local_address' in openvpn or 'remote_address' in openvpn: + raise ConfigError('Cannot specify "local-address" or "remote-address" ' \ + 'in client/server or bridge mode') + + # + # OpenVPN server mode - VERIFY + # + if openvpn['mode'] == 'server': + if openvpn['protocol'] == 'tcp-active': + raise ConfigError('Protocol "tcp-active" is not valid in server mode') + + if dict_search('authentication.username', openvpn) or dict_search('authentication.password', openvpn): + raise ConfigError('Cannot specify "authentication" in server mode') + + if 'remote_port' in openvpn: + raise ConfigError('Cannot specify "remote-port" in server mode') + + if 'remote_host' in openvpn: + raise ConfigError('Cannot specify "remote-host" in server mode') + + tmp = dict_search('server.subnet', openvpn) + if tmp: + v4_subnets = len([subnet for subnet in tmp if is_ipv4(subnet)]) + v6_subnets = len([subnet for subnet in tmp if is_ipv6(subnet)]) + if v4_subnets > 1: + raise ConfigError('Cannot specify more than 1 IPv4 server subnet') + if v6_subnets > 1: + raise ConfigError('Cannot specify more than 1 IPv6 server subnet') + + for subnet in tmp: + if is_ipv4(subnet): + subnet = IPv4Network(subnet) + + if openvpn['device_type'] == 'tun' and subnet.prefixlen > 29: + raise ConfigError('Server subnets smaller than /29 with device type "tun" are not supported') + elif openvpn['device_type'] == 'tap' and subnet.prefixlen > 30: + raise ConfigError('Server subnets smaller than /30 with device type "tap" are not supported') + + for client in (dict_search('client', openvpn) or []): + if client['ip'] and not IPv4Address(client['ip'][0]) in subnet: + raise ConfigError(f'Client "{client["name"]}" IP {client["ip"][0]} not in server subnet {subnet}') + + else: + if 'is_bridge_member' not in openvpn: + raise ConfigError('Must specify "server subnet" or add interface to bridge in server mode') + + if hasattr(dict_search('server.client', openvpn), '__iter__'): + for client_k, client_v in dict_search('server.client', openvpn).items(): + if (client_v.get('ip') and len(client_v['ip']) > 1) or (client_v.get('ipv6_ip') and len(client_v['ipv6_ip']) > 1): + raise ConfigError(f'Server client "{client_k}": cannot specify more than 1 IPv4 and 1 IPv6 IP') + + if dict_search('server.client_ip_pool', openvpn): + if not (dict_search('server.client_ip_pool.start', openvpn) and dict_search('server.client_ip_pool.stop', openvpn)): + raise ConfigError('Server client-ip-pool requires both start and stop addresses') + else: + v4PoolStart = IPv4Address(dict_search('server.client_ip_pool.start', openvpn)) + v4PoolStop = IPv4Address(dict_search('server.client_ip_pool.stop', openvpn)) + if v4PoolStart > v4PoolStop: + raise ConfigError(f'Server client-ip-pool start address {v4PoolStart} is larger than stop address {v4PoolStop}') + + v4PoolSize = int(v4PoolStop) - int(v4PoolStart) + if v4PoolSize >= 65536: + raise ConfigError(f'Server client-ip-pool is too large [{v4PoolStart} -> {v4PoolStop} = {v4PoolSize}], maximum is 65536 addresses.') + + v4PoolNets = list(summarize_address_range(v4PoolStart, v4PoolStop)) + for client in (dict_search('client', openvpn) or []): + if client['ip']: + for v4PoolNet in v4PoolNets: + if IPv4Address(client['ip'][0]) in v4PoolNet: + print(f'Warning: Client "{client["name"]}" IP {client["ip"][0]} is in server IP pool, it is not reserved for this client.') + # configuring a client_ip_pool will set 'server ... nopool' which is currently incompatible with 'server-ipv6' (probably to be fixed upstream) + for subnet in (dict_search('server.subnet', openvpn) or []): + if is_ipv6(subnet): + raise ConfigError(f'Setting client-ip-pool is incompatible having an IPv6 server subnet.') + + for subnet in (dict_search('server.subnet', openvpn) or []): + if is_ipv6(subnet): + tmp = dict_search('client_ipv6_pool.base', openvpn) + if tmp: + if not dict_search('server.client_ip_pool', openvpn): + raise ConfigError('IPv6 server pool requires an IPv4 server pool') + + if int(tmp.split('/')[1]) >= 112: + raise ConfigError('IPv6 server pool must be larger than /112') + + # + # todo - weird logic + # + v6PoolStart = IPv6Address(tmp) + v6PoolStop = IPv6Network((v6PoolStart, openvpn['server_ipv6_pool_prefixlen']), strict=False)[-1] # don't remove the parentheses, it's a 2-tuple + v6PoolSize = int(v6PoolStop) - int(v6PoolStart) if int(openvpn['server_ipv6_pool_prefixlen']) > 96 else 65536 + if v6PoolSize < v4PoolSize: + raise ConfigError(f'IPv6 server pool must be at least as large as the IPv4 pool (current sizes: IPv6={v6PoolSize} IPv4={v4PoolSize})') + + v6PoolNets = list(summarize_address_range(v6PoolStart, v6PoolStop)) + for client in (dict_search('client', openvpn) or []): + if client['ipv6_ip']: + for v6PoolNet in v6PoolNets: + if IPv6Address(client['ipv6_ip'][0]) in v6PoolNet: + print(f'Warning: Client "{client["name"]}" IP {client["ipv6_ip"][0]} is in server IP pool, it is not reserved for this client.') + + # add mfa users to the file the mfa plugin uses + if dict_search('server.mfa.totp', openvpn): + user_data = '' + if not os.path.isfile(otp_file.format(**openvpn)): + write_file(otp_file.format(**openvpn), user_data, + user=user, group=group, mode=0o644) + + ovpn_users = read_file(otp_file.format(**openvpn)) + for client in (dict_search('server.client', openvpn) or []): + exists = None + for ovpn_user in ovpn_users.split('\n'): + if re.search('^' + client + ' ', ovpn_user): + user_data += f'{ovpn_user}\n' + exists = 'true' + + if not exists: + random = SystemRandom() + totp_secret = ''.join(random.choice(secret_chars) for _ in range(16)) + user_data += f'{client} otp totp:sha1:base32:{totp_secret}::xxx *\n' + + write_file(otp_file.format(**openvpn), user_data, + user=user, group=group, mode=0o644) + + else: + # checks for both client and site-to-site go here + if dict_search('server.reject_unconfigured_clients', openvpn): + raise ConfigError('Option reject-unconfigured-clients only supported in server mode') + + if 'replace_default_route' in openvpn and 'remote_host' not in openvpn: + raise ConfigError('Cannot set "replace-default-route" without "remote-host"') + + # + # OpenVPN common verification section + # not depending on any operation mode + # + + # verify specified IP address is present on any interface on this system + if 'local_host' in openvpn: + if not is_addr_assigned(openvpn['local_host']): + print('local-host IP address "{local_host}" not assigned' \ + ' to any interface'.format(**openvpn)) + + # TCP active + if openvpn['protocol'] == 'tcp-active': + if 'local_port' in openvpn: + raise ConfigError('Cannot specify "local-port" with "tcp-active"') + + if 'remote_host' not in openvpn: + raise ConfigError('Must specify "remote-host" with "tcp-active"') + + # + # TLS/encryption + # + if 'shared_secret_key' in openvpn: + if dict_search('encryption.cipher', openvpn) in ['aes128gcm', 'aes192gcm', 'aes256gcm']: + raise ConfigError('GCM encryption with shared-secret-key not supported') + + if 'tls' in openvpn: + if {'auth_key', 'crypt_key'} <= set(openvpn['tls']): + raise ConfigError('TLS auth and crypt keys are mutually exclusive') + + tmp = dict_search('tls.role', openvpn) + if tmp: + if openvpn['mode'] in ['client', 'server']: + if not dict_search('tls.auth_key', openvpn): + raise ConfigError('Cannot specify "tls role" in client-server mode') + + if tmp == 'active': + if openvpn['protocol'] == 'tcp-passive': + raise ConfigError('Cannot specify "tcp-passive" when "tls role" is "active"') + + if dict_search('tls.dh_params', openvpn): + raise ConfigError('Cannot specify "tls dh-params" when "tls role" is "active"') + + elif tmp == 'passive': + if openvpn['protocol'] == 'tcp-active': + raise ConfigError('Cannot specify "tcp-active" when "tls role" is "passive"') + + if 'certificate' in openvpn['tls'] and is_ec_private_key(openvpn['pki'], openvpn['tls']['certificate']): + if 'dh_params' in openvpn['tls']: + print('Warning: using dh-params and EC keys simultaneously will ' \ + 'lead to DH ciphers being used instead of ECDH') + + if dict_search('encryption.cipher', openvpn) == 'none': + print('Warning: "encryption none" was specified!') + print('No encryption will be performed and data is transmitted in ' \ + 'plain text over the network!') + + verify_pki(openvpn) + + # + # Auth user/pass + # + if (dict_search('authentication.username', openvpn) and not + dict_search('authentication.password', openvpn)): + raise ConfigError('Password for authentication is missing') + + if (dict_search('authentication.password', openvpn) and not + dict_search('authentication.username', openvpn)): + raise ConfigError('Username for authentication is missing') + + verify_vrf(openvpn) + verify_bond_bridge_member(openvpn) + verify_mirror_redirect(openvpn) + + return None + +def generate_pki_files(openvpn): + pki = openvpn['pki'] + if not pki: + return None + + interface = openvpn['ifname'] + shared_secret_key = dict_search_args(openvpn, 'shared_secret_key') + tls = dict_search_args(openvpn, 'tls') + + if shared_secret_key: + pki_key = pki['openvpn']['shared_secret'][shared_secret_key] + key_path = os.path.join(cfg_dir, f'{interface}_shared.key') + write_file(key_path, wrap_openvpn_key(pki_key['key']), + user=user, group=group) + + if tls: + if 'ca_certificate' in tls: + cert_path = os.path.join(cfg_dir, f'{interface}_ca.pem') + crl_path = os.path.join(cfg_dir, f'{interface}_crl.pem') + + if os.path.exists(cert_path): + os.unlink(cert_path) + + if os.path.exists(crl_path): + os.unlink(crl_path) + + for cert_name in sort_ca_chain(tls['ca_certificate'], pki['ca']): + pki_ca = pki['ca'][cert_name] + + if 'certificate' in pki_ca: + write_file(cert_path, wrap_certificate(pki_ca['certificate']) + "\n", + user=user, group=group, mode=0o600, append=True) + + if 'crl' in pki_ca: + for crl in pki_ca['crl']: + write_file(crl_path, wrap_crl(crl) + "\n", user=user, group=group, + mode=0o600, append=True) + + openvpn['tls']['crl'] = True + + if 'certificate' in tls: + cert_name = tls['certificate'] + pki_cert = pki['certificate'][cert_name] + + if 'certificate' in pki_cert: + cert_path = os.path.join(cfg_dir, f'{interface}_cert.pem') + write_file(cert_path, wrap_certificate(pki_cert['certificate']), + user=user, group=group, mode=0o600) + + if 'private' in pki_cert and 'key' in pki_cert['private']: + key_path = os.path.join(cfg_dir, f'{interface}_cert.key') + write_file(key_path, wrap_private_key(pki_cert['private']['key']), + user=user, group=group, mode=0o600) + + openvpn['tls']['private_key'] = True + + if 'dh_params' in tls: + dh_name = tls['dh_params'] + pki_dh = pki['dh'][dh_name] + + if 'parameters' in pki_dh: + dh_path = os.path.join(cfg_dir, f'{interface}_dh.pem') + write_file(dh_path, wrap_dh_parameters(pki_dh['parameters']), + user=user, group=group, mode=0o600) + + if 'auth_key' in tls: + key_name = tls['auth_key'] + pki_key = pki['openvpn']['shared_secret'][key_name] + + if 'key' in pki_key: + key_path = os.path.join(cfg_dir, f'{interface}_auth.key') + write_file(key_path, wrap_openvpn_key(pki_key['key']), + user=user, group=group, mode=0o600) + + if 'crypt_key' in tls: + key_name = tls['crypt_key'] + pki_key = pki['openvpn']['shared_secret'][key_name] + + if 'key' in pki_key: + key_path = os.path.join(cfg_dir, f'{interface}_crypt.key') + write_file(key_path, wrap_openvpn_key(pki_key['key']), + user=user, group=group, mode=0o600) + + +def generate(openvpn): + interface = openvpn['ifname'] + directory = os.path.dirname(cfg_file.format(**openvpn)) + openvpn['plugin_dir'] = '/usr/lib/openvpn' + # create base config directory on demand + makedir(directory, user, group) + # enforce proper permissions on /run/openvpn + chown(directory, user, group) + + # we can't know in advance which clients have been removed, + # thus all client configs will be removed and re-added on demand + ccd_dir = os.path.join(directory, 'ccd', interface) + if os.path.isdir(ccd_dir): + rmtree(ccd_dir, ignore_errors=True) + + # Remove systemd directories with overrides + service_dir = os.path.dirname(service_file.format(**openvpn)) + if os.path.isdir(service_dir): + rmtree(service_dir, ignore_errors=True) + + if 'deleted' in openvpn or 'disable' in openvpn: + return None + + # create client config directory on demand + makedir(ccd_dir, user, group) + + # Fix file permissons for keys + generate_pki_files(openvpn) + + # Generate User/Password authentication file + if 'authentication' in openvpn: + render(openvpn['auth_user_pass_file'], 'openvpn/auth.pw.j2', openvpn, + user=user, group=group, permission=0o600) + else: + # delete old auth file if present + if os.path.isfile(openvpn['auth_user_pass_file']): + os.remove(openvpn['auth_user_pass_file']) + + # Generate client specific configuration + server_client = dict_search_args(openvpn, 'server', 'client') + if server_client: + for client, client_config in server_client.items(): + client_file = os.path.join(ccd_dir, client) + + # Our client need's to know its subnet mask ... + client_config['server_subnet'] = dict_search('server.subnet', openvpn) + + render(client_file, 'openvpn/client.conf.j2', client_config, + user=user, group=group) + + # we need to support quoting of raw parameters from OpenVPN CLI + # see https://vyos.dev/T1632 + render(cfg_file.format(**openvpn), 'openvpn/server.conf.j2', openvpn, + formater=lambda _: _.replace(""", '"'), user=user, group=group) + + # Render 20-override.conf for OpenVPN service + render(service_file.format(**openvpn), 'openvpn/service-override.conf.j2', openvpn, + formater=lambda _: _.replace(""", '"'), user=user, group=group) + # Reload systemd services config to apply an override + call(f'systemctl daemon-reload') + + return None + +def apply(openvpn): + interface = openvpn['ifname'] + + # Do some cleanup when OpenVPN is disabled/deleted + if 'deleted' in openvpn or 'disable' in openvpn: + call(f'systemctl stop openvpn@{interface}.service') + for cleanup_file in glob(f'/run/openvpn/{interface}.*'): + if os.path.isfile(cleanup_file): + os.unlink(cleanup_file) + + if interface in interfaces(): + VTunIf(interface).remove() + + # dynamically load/unload DCO Kernel extension if requested + dco_module = 'ovpn_dco_v2' + if 'module_load_dco' in openvpn: + check_kmod(dco_module) + else: + unload_kmod(dco_module) + + # Now bail out early if interface is disabled or got deleted + if 'deleted' in openvpn or 'disable' in openvpn: + return None + + # verify specified IP address is present on any interface on this system + # Allow to bind service to nonlocal address, if it virtaual-vrrp address + # or if address will be assign later + if 'local_host' in openvpn: + if not is_addr_assigned(openvpn['local_host']): + cmd('sysctl -w net.ipv4.ip_nonlocal_bind=1') + + # No matching OpenVPN process running - maybe it got killed or none + # existed - nevertheless, spawn new OpenVPN process + action = 'reload-or-restart' + if 'restart_required' in openvpn: + action = 'restart' + call(f'systemctl {action} openvpn@{interface}.service') + + o = VTunIf(**openvpn) + o.update(openvpn) + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) + diff --git a/src/conf_mode/interfaces_pppoe.py b/src/conf_mode/interfaces_pppoe.py new file mode 100755 index 000000000..42f084309 --- /dev/null +++ b/src/conf_mode/interfaces_pppoe.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-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 . + +import os + +from sys import exit +from copy import deepcopy +from netifaces import interfaces + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configdict import get_pppoe_interfaces +from vyos.configverify import verify_authentication +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_vrf +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_mirror_redirect +from vyos.ifconfig import PPPoEIf +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.process import is_systemd_service_running +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', 'pppoe'] + ifname, pppoe = get_interface_dict(conf, base) + + # We should only terminate the PPPoE session if critical parameters change. + # All parameters that can be changed on-the-fly (like interface description) + # should not lead to a reconnect! + for options in ['access-concentrator', 'connect-on-demand', 'service-name', + '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 + break + + if 'deleted' not in pppoe: + # We always set the MRU value to the MTU size. This code path only re-creates + # the old behavior if MRU is not set on the CLI. + if 'mru' not in pppoe: + pppoe['mru'] = pppoe['mtu'] + + return pppoe + +def verify(pppoe): + if 'deleted' in pppoe: + # bail out early + return None + + verify_source_interface(pppoe) + verify_authentication(pppoe) + verify_vrf(pppoe) + verify_mtu_ipv6(pppoe) + verify_mirror_redirect(pppoe) + + if {'connect_on_demand', 'vrf'} <= set(pppoe): + raise ConfigError('On-demand dialing and VRF can not be used at the same time') + + # both MTU and MRU have default values, thus we do not need to check + # if the key exists + if int(pppoe['mru']) > int(pppoe['mtu']): + raise ConfigError('PPPoE MRU needs to be lower then MTU!') + + return None + +def generate(pppoe): + # set up configuration file path variables where our templates will be + # rendered into + ifname = pppoe['ifname'] + config_pppoe = f'/etc/ppp/peers/{ifname}' + + if 'deleted' in pppoe or 'disable' in pppoe: + if os.path.exists(config_pppoe): + os.unlink(config_pppoe) + + return None + + # Create PPP configuration files + render(config_pppoe, 'pppoe/peer.j2', pppoe, permission=0o640) + + return None + +def apply(pppoe): + ifname = pppoe['ifname'] + if 'deleted' in pppoe or 'disable' in pppoe: + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = PPPoEIf(ifname) + p.remove() + call(f'systemctl stop ppp@{ifname}.service') + return None + + # reconnect should only be necessary when certain config options change, + # like ACS name, authentication ... (see get_config() for details) + if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or + 'shutdown_required' in pppoe): + + # cleanup system (e.g. FRR routes first) + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = PPPoEIf(ifname) + p.remove() + + call(f'systemctl restart ppp@{ifname}.service') + # When interface comes "live" a hook is called: + # /etc/ppp/ip-up.d/99-vyos-pppoe-callback + # which triggers PPPoEIf.update() + else: + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = PPPoEIf(ifname) + p.update(pppoe) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_pseudo-ethernet.py b/src/conf_mode/interfaces_pseudo-ethernet.py new file mode 100755 index 000000000..dce5c2358 --- /dev/null +++ b/src/conf_mode/interfaces_pseudo-ethernet.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# +# 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 +# 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 . + +from sys import exit +from netifaces import interfaces + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configdict import is_source_interface +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 +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_vlan_config +from vyos.configverify import verify_mtu_parent +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_bond_bridge_member +from vyos.ifconfig import MACVLANIf +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', 'pseudo-ethernet'] + ifname, peth = get_interface_dict(conf, base) + + mode = is_node_changed(conf, ['mode']) + if mode: peth.update({'shutdown_required' : {}}) + + if is_node_changed(conf, base + [ifname, 'mode']): + peth.update({'rebuild_required': {}}) + + if 'source_interface' in peth: + _, peth['parent'] = get_interface_dict(conf, ['interfaces', 'ethernet'], + peth['source_interface']) + # test if source-interface is maybe already used by another interface + tmp = is_source_interface(conf, peth['source_interface'], ['macsec']) + if tmp and tmp != ifname: peth.update({'is_source_interface' : tmp}) + + return peth + +def verify(peth): + if 'deleted' in peth: + verify_bridge_delete(peth) + return None + + verify_source_interface(peth) + verify_vrf(peth) + verify_address(peth) + verify_mtu_parent(peth, peth['parent']) + verify_mirror_redirect(peth) + # use common function to verify VLAN configuration + verify_vlan_config(peth) + + return None + +def generate(peth): + return None + +def apply(peth): + # Check if the MACVLAN interface already exists + if 'rebuild_required' in peth or 'deleted' in peth: + if peth['ifname'] in interfaces(): + p = MACVLANIf(peth['ifname']) + # MACVLAN is always needs to be recreated, + # thus we can simply always delete it first. + p.remove() + + if 'deleted' not in peth: + p = MACVLANIf(**peth) + p.update(peth) + + 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_sstpc.py b/src/conf_mode/interfaces_sstpc.py new file mode 100755 index 000000000..b588910dc --- /dev/null +++ b/src/conf_mode/interfaces_sstpc.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configverify import verify_authentication +from vyos.configverify import verify_vrf +from vyos.ifconfig import SSTPCIf +from vyos.pki import encode_certificate +from vyos.pki import find_chain +from vyos.pki import load_certificate +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.utils.process import is_systemd_service_running +from vyos.utils.file import write_file +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'sstpc'] + ifname, sstpc = get_interface_dict(conf, base) + + # We should only terminate the SSTP client session if critical parameters + # change. All parameters that can be changed on-the-fly (like interface + # description) should not lead to a reconnect! + for options in ['authentication', 'no_peer_dns', 'no_default_route', + 'server', 'ssl']: + if is_node_changed(conf, base + [ifname, options]): + sstpc.update({'shutdown_required': {}}) + # bail out early - no need to further process other nodes + break + + # Load PKI certificates for later processing + sstpc['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + return sstpc + +def verify(sstpc): + if 'deleted' in sstpc: + return None + + verify_authentication(sstpc) + verify_vrf(sstpc) + + if not dict_search('server', sstpc): + raise ConfigError('Remote SSTP server must be specified!') + + if not dict_search('ssl.ca_certificate', sstpc): + raise ConfigError('Missing mandatory CA certificate!') + + return None + +def generate(sstpc): + ifname = sstpc['ifname'] + config_sstpc = f'/etc/ppp/peers/{ifname}' + + sstpc['ca_file_path'] = f'/run/sstpc/{ifname}_ca-cert.pem' + + if 'deleted' in sstpc: + for file in [sstpc['ca_file_path'], config_sstpc]: + if os.path.exists(file): + os.unlink(file) + return None + + ca_name = sstpc['ssl']['ca_certificate'] + pki_ca_cert = sstpc['pki']['ca'][ca_name] + + loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) + loaded_ca_certs = {load_certificate(c['certificate']) + for c in sstpc['pki']['ca'].values()} if 'ca' in sstpc['pki'] else {} + + ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + + write_file(sstpc['ca_file_path'], '\n'.join(encode_certificate(c) for c in ca_full_chain)) + render(config_sstpc, 'sstp-client/peer.j2', sstpc, permission=0o640) + + return None + +def apply(sstpc): + ifname = sstpc['ifname'] + if 'deleted' in sstpc or 'disable' in sstpc: + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = SSTPCIf(ifname) + p.remove() + call(f'systemctl stop ppp@{ifname}.service') + return None + + # reconnect should only be necessary when specific options change, + # like server, authentication ... (see get_config() for details) + if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or + 'shutdown_required' in sstpc): + + # cleanup system (e.g. FRR routes first) + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = SSTPCIf(ifname) + p.remove() + + call(f'systemctl restart ppp@{ifname}.service') + # When interface comes "live" a hook is called: + # /etc/ppp/ip-up.d/96-vyos-sstpc-callback + # which triggers SSTPCIf.update() + else: + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = SSTPCIf(ifname) + p.update(sstpc) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_tunnel.py b/src/conf_mode/interfaces_tunnel.py new file mode 100755 index 000000000..91aed9cc3 --- /dev/null +++ b/src/conf_mode/interfaces_tunnel.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2022 yOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from sys import exit +from netifaces import interfaces + +from vyos.config import Config +from vyos.configdict import get_interface_dict +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 +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_vrf +from vyos.configverify import verify_tunnel +from vyos.configverify import verify_bond_bridge_member +from vyos.ifconfig import Interface +from vyos.ifconfig import Section +from vyos.ifconfig import TunnelIf +from vyos.utils.network import get_interface_config +from vyos.utils.dict import dict_search +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', 'tunnel'] + ifname, tunnel = get_interface_dict(conf, base) + + if 'deleted' not in tunnel: + tmp = is_node_changed(conf, base + [ifname, 'encapsulation']) + if tmp: tunnel.update({'encapsulation_changed': {}}) + + tmp = is_node_changed(conf, base + [ifname, 'parameters', 'ip', 'key']) + if tmp: tunnel.update({'key_changed': {}}) + + # We also need to inspect other configured tunnels as there are Kernel + # restrictions where we need to comply. E.g. GRE tunnel key can't be used + # twice, or with multiple GRE tunnels to the same location we must specify + # a GRE key + conf.set_level(base) + tunnel['other_tunnels'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + # delete our own instance from this dict + ifname = tunnel['ifname'] + del tunnel['other_tunnels'][ifname] + # if only one tunnel is present on the system, no need to keep this key + if len(tunnel['other_tunnels']) == 0: + del tunnel['other_tunnels'] + + # We must check if our interface is configured to be a DMVPN member + nhrp_base = ['protocols', 'nhrp', 'tunnel'] + conf.set_level(nhrp_base) + nhrp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if nhrp: tunnel.update({'nhrp' : list(nhrp.keys())}) + + if 'encapsulation' in tunnel and tunnel['encapsulation'] not in ['erspan', 'ip6erspan']: + del tunnel['parameters']['erspan'] + + return tunnel + +def verify(tunnel): + if 'deleted' in tunnel: + verify_bridge_delete(tunnel) + + if 'nhrp' in tunnel and tunnel['ifname'] in tunnel['nhrp']: + raise ConfigError('Tunnel used for NHRP, it can not be deleted!') + + return None + + verify_tunnel(tunnel) + + if tunnel['encapsulation'] in ['erspan', 'ip6erspan']: + if dict_search('parameters.ip.key', tunnel) == None: + raise ConfigError('ERSPAN requires ip key parameter!') + + # this is a default field + ver = int(tunnel['parameters']['erspan']['version']) + if ver == 1: + if 'hw_id' in tunnel['parameters']['erspan']: + raise ConfigError('ERSPAN version 1 does not support hw-id!') + if 'direction' in tunnel['parameters']['erspan']: + raise ConfigError('ERSPAN version 1 does not support direction!') + elif ver == 2: + if 'idx' in tunnel['parameters']['erspan']: + raise ConfigError('ERSPAN version 2 does not index parameter!') + if 'direction' not in tunnel['parameters']['erspan']: + raise ConfigError('ERSPAN version 2 requires direction to be set!') + + # If tunnel source is any and gre key is not set + interface = tunnel['ifname'] + if tunnel['encapsulation'] in ['gre'] and \ + dict_search('source_address', tunnel) == '0.0.0.0' and \ + dict_search('parameters.ip.key', tunnel) == None: + raise ConfigError(f'"parameters ip key" must be set for {interface} when '\ + 'encapsulation is GRE!') + + gre_encapsulations = ['gre', 'gretap'] + if tunnel['encapsulation'] in gre_encapsulations and 'other_tunnels' in tunnel: + # Check pairs tunnel source-address/encapsulation/key with exists tunnels. + # Prevent the same key for 2 tunnels with same source-address/encap. T2920 + for o_tunnel, o_tunnel_conf in tunnel['other_tunnels'].items(): + # no match on encapsulation - bail out + our_encapsulation = tunnel['encapsulation'] + their_encapsulation = o_tunnel_conf['encapsulation'] + if our_encapsulation in gre_encapsulations and their_encapsulation \ + not in gre_encapsulations: + continue + + our_address = dict_search('source_address', tunnel) + our_key = dict_search('parameters.ip.key', tunnel) + their_address = dict_search('source_address', o_tunnel_conf) + their_key = dict_search('parameters.ip.key', o_tunnel_conf) + 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 "{o_tunnel}"!') + else: + our_source_if = dict_search('source_interface', tunnel) + their_source_if = dict_search('source_interface', o_tunnel_conf) + our_remote = dict_search('remote', tunnel) + their_remote = dict_search('remote', o_tunnel_conf) + # If no IP GRE key is defined we can not have more then one GRE tunnel + # bound to any one interface/IP address and the same remote. This will + # result in a OS PermissionError: add tunnel "gre0" failed: File exists + if (their_address == our_address or our_source_if == their_source_if) and \ + our_remote == their_remote: + raise ConfigError(f'Missing required "ip key" parameter when '\ + 'running more then one GRE based tunnel on the '\ + 'same source-interface/source-address') + + # Keys are not allowed with ipip and sit tunnels + if tunnel['encapsulation'] in ['ipip', 'sit']: + if dict_search('parameters.ip.key', tunnel) != None: + raise ConfigError('Keys are not allowed with ipip and sit tunnels!') + + verify_mtu_ipv6(tunnel) + verify_address(tunnel) + verify_vrf(tunnel) + verify_bond_bridge_member(tunnel) + verify_mirror_redirect(tunnel) + + if 'source_interface' in tunnel: + verify_interface_exists(tunnel['source_interface']) + + # TTL != 0 and nopmtudisc are incompatible, parameters and ip use default + # values, thus the keys are always present. + if dict_search('parameters.ip.no_pmtu_discovery', tunnel) != None: + if dict_search('parameters.ip.ttl', tunnel) != '0': + raise ConfigError('Disabled PMTU requires TTL set to "0"!') + if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']: + raise ConfigError('Can not disable PMTU discovery for given encapsulation') + + if dict_search('parameters.ip.ignore_df', tunnel) != None: + if tunnel['encapsulation'] not in ['gretap']: + raise ConfigError('Option ignore-df can only be used on GRETAP tunnels!') + + if dict_search('parameters.ip.no_pmtu_discovery', tunnel) == None: + raise ConfigError('Option ignore-df requires path MTU discovery to be disabled!') + + +def generate(tunnel): + return None + +def apply(tunnel): + interface = tunnel['ifname'] + # If a gretap tunnel is already existing we can not "simply" change local or + # remote addresses. This returns "Operation not supported" by the Kernel. + # There is no other solution to destroy and recreate the tunnel. + encap = '' + remote = '' + tmp = get_interface_config(interface) + if tmp: + encap = dict_search('linkinfo.info_kind', tmp) + remote = dict_search('linkinfo.info_data.remote', tmp) + + if ('deleted' in tunnel or 'encapsulation_changed' in tunnel or encap in + ['gretap', 'ip6gretap', 'erspan', 'ip6erspan'] or remote in ['any'] or + 'key_changed' in tunnel): + if interface in interfaces(): + tmp = Interface(interface) + tmp.remove() + if 'deleted' in tunnel: + return None + + tun = TunnelIf(**tunnel) + tun.update(tunnel) + + return None + +if __name__ == '__main__': + try: + c = get_config() + generate(c) + verify(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_virtual-ethernet.py b/src/conf_mode/interfaces_virtual-ethernet.py new file mode 100755 index 000000000..8efe89c41 --- /dev/null +++ b/src/conf_mode/interfaces_virtual-ethernet.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from sys import exit + +from netifaces import interfaces +from vyos import ConfigError +from vyos import airbag +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_vrf +from vyos.ifconfig import VethIf + +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at + least the interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'virtual-ethernet'] + ifname, veth = get_interface_dict(conf, base) + + # We need to know all other veth related interfaces as veth requires a 1:1 + # mapping for the peer-names. The Linux kernel automatically creates both + # interfaces, the local one and the peer-name, but VyOS also needs a peer + # interfaces configrued on the CLI so we can assign proper IP addresses etc. + veth['other_interfaces'] = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + return veth + + +def verify(veth): + if 'deleted' in veth: + verify_bridge_delete(veth) + # Prevent to delete veth interface which used for another "vethX peer-name" + for iface, iface_config in veth['other_interfaces'].items(): + if veth['ifname'] in iface_config['peer_name']: + ifname = veth['ifname'] + raise ConfigError( + f'Cannot delete "{ifname}" used for "interface {iface} peer-name"' + ) + return None + + verify_vrf(veth) + verify_address(veth) + + if 'peer_name' not in veth: + raise ConfigError(f'Remote peer name must be set for "{veth["ifname"]}"!') + + peer_name = veth['peer_name'] + ifname = veth['ifname'] + + if veth['peer_name'] not in veth['other_interfaces']: + raise ConfigError(f'Used peer-name "{peer_name}" on interface "{ifname}" ' \ + 'is not configured!') + + if veth['other_interfaces'][peer_name]['peer_name'] != ifname: + raise ConfigError( + f'Configuration mismatch between "{ifname}" and "{peer_name}"!') + + if peer_name == ifname: + raise ConfigError( + f'Peer-name "{peer_name}" cannot be the same as interface "{ifname}"!') + + return None + + +def generate(peth): + return None + +def apply(veth): + # Check if the Veth interface already exists + if 'rebuild_required' in veth or 'deleted' in veth: + if veth['ifname'] in interfaces(): + p = VethIf(veth['ifname']) + p.remove() + + if 'deleted' not in veth: + p = VethIf(**veth) + p.update(veth) + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_vti.py b/src/conf_mode/interfaces_vti.py new file mode 100755 index 000000000..9871810ae --- /dev/null +++ b/src/conf_mode/interfaces_vti.py @@ -0,0 +1,68 @@ +#!/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 . + +from netifaces import interfaces +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 VTIIf +from vyos.utils.dict import dict_search +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', 'vti'] + _, vti = get_interface_dict(conf, base) + return vti + +def verify(vti): + verify_mirror_redirect(vti) + return None + +def generate(vti): + return None + +def apply(vti): + # Remove macsec interface + if 'deleted' in vti: + VTIIf(**vti).remove() + return None + + tmp = VTIIf(**vti) + tmp.update(vti) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_vxlan.py b/src/conf_mode/interfaces_vxlan.py new file mode 100755 index 000000000..4251e611b --- /dev/null +++ b/src/conf_mode/interfaces_vxlan.py @@ -0,0 +1,236 @@ +#!/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 . + +import os + +from sys import exit +from netifaces import interfaces + +from vyos.base import Warning +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.configdict import node_changed +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_bond_bridge_member +from vyos.ifconfig import Interface +from vyos.ifconfig import VXLANIf +from vyos.template import is_ipv6 +from vyos.utils.dict import dict_search +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', 'vxlan'] + ifname, vxlan = get_interface_dict(conf, base) + + # 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 ['parameters', 'gpe', 'group', 'port', 'remote', + 'source-address', 'source-interface', 'vni']: + if is_node_changed(conf, base + [ifname, cli_option]): + vxlan.update({'rebuild_required': {}}) + break + + # When dealing with VNI filtering we need to know what VNI was actually removed, + # so build up a dict matching the vlan_to_vni structure but with removed values. + tmp = node_changed(conf, base + [ifname, 'vlan-to-vni'], recursive=True) + if tmp: + vxlan.update({'vlan_to_vni_removed': {}}) + for vlan in tmp: + vni = leaf_node_changed(conf, base + [ifname, 'vlan-to-vni', vlan, 'vni']) + vxlan['vlan_to_vni_removed'].update({vlan : {'vni' : vni[0]}}) + + # We need to verify that no other VXLAN tunnel is configured when external + # mode is in use - Linux Kernel limitation + conf.set_level(base) + vxlan['other_tunnels'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # This if-clause is just to be sure - it will always evaluate to true + ifname = vxlan['ifname'] + if ifname in vxlan['other_tunnels']: + del vxlan['other_tunnels'][ifname] + if len(vxlan['other_tunnels']) == 0: + del vxlan['other_tunnels'] + + return vxlan + +def verify(vxlan): + if 'deleted' in vxlan: + verify_bridge_delete(vxlan) + return None + + if int(vxlan['mtu']) < 1500: + Warning('RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU') + + if 'group' in vxlan: + if 'source_interface' not in vxlan: + raise ConfigError('Multicast VXLAN requires an underlaying interface') + verify_source_interface(vxlan) + + if not any(tmp in ['group', 'remote', 'source_address', 'source_interface'] for tmp in vxlan): + raise ConfigError('Group, remote, source-address or source-interface must be configured') + + if 'vni' not in vxlan and dict_search('parameters.external', vxlan) == None: + raise ConfigError('Must either configure VXLAN "vni" or use "external" CLI option!') + + if dict_search('parameters.external', vxlan) != None: + if 'vni' in vxlan: + raise ConfigError('Can not specify both "external" and "VNI"!') + + if 'other_tunnels' in vxlan: + # When multiple VXLAN interfaces are defined and "external" is used, + # all VXLAN interfaces need to have vni-filter enabled! + # See Linux Kernel commit f9c4bb0b245cee35ef66f75bf409c9573d934cf9 + other_vni_filter = False + for tunnel, tunnel_config in vxlan['other_tunnels'].items(): + if dict_search('parameters.vni_filter', tunnel_config) != None: + other_vni_filter = True + break + # eqivalent of the C foo ? 'a' : 'b' statement + vni_filter = True and (dict_search('parameters.vni_filter', vxlan) != None) or False + # If either one is enabled, so must be the other. Both can be off and both can be on + if (vni_filter and not other_vni_filter) or (not vni_filter and other_vni_filter): + raise ConfigError(f'Using multiple VXLAN interfaces with "external" '\ + 'requires all VXLAN interfaces to have "vni-filter" configured!') + + if not vni_filter and not other_vni_filter: + other_tunnels = ', '.join(vxlan['other_tunnels']) + raise ConfigError(f'Only one VXLAN tunnel is supported when "external" '\ + f'CLI option is used and "vni-filter" is unset. '\ + f'Additional tunnels: {other_tunnels}') + + if 'gpe' in vxlan and 'external' not in vxlan: + raise ConfigError(f'VXLAN-GPE is only supported when "external" '\ + f'CLI option is used.') + + if 'source_interface' in vxlan: + # VXLAN adds at least an overhead of 50 byte - we need to check the + # underlaying device if our VXLAN package is not going to be fragmented! + vxlan_overhead = 50 + if 'source_address' in vxlan and is_ipv6(vxlan['source_address']): + # IPv6 adds an extra 20 bytes overhead because the IPv6 header is 20 + # bytes larger than the IPv4 header - assuming no extra options are + # in use. + vxlan_overhead += 20 + + # If source_address is not used - check IPv6 'remote' list + elif 'remote' in vxlan: + if any(is_ipv6(a) for a in vxlan['remote']): + vxlan_overhead += 20 + + lower_mtu = Interface(vxlan['source_interface']).get_mtu() + if lower_mtu < (int(vxlan['mtu']) + vxlan_overhead): + raise ConfigError(f'Underlaying device MTU is to small ({lower_mtu} '\ + f'bytes) for VXLAN overhead ({vxlan_overhead} bytes!)') + + # Check for mixed IPv4 and IPv6 addresses + protocol = None + if 'source_address' in vxlan: + if is_ipv6(vxlan['source_address']): + protocol = 'ipv6' + else: + protocol = 'ipv4' + + if 'remote' in vxlan: + error_msg = 'Can not mix both IPv4 and IPv6 for VXLAN underlay' + for remote in vxlan['remote']: + if is_ipv6(remote): + if protocol == 'ipv4': + raise ConfigError(error_msg) + protocol = 'ipv6' + else: + if protocol == 'ipv6': + raise ConfigError(error_msg) + protocol = 'ipv4' + + if 'vlan_to_vni' in vxlan: + if 'is_bridge_member' not in vxlan: + raise ConfigError('VLAN to VNI mapping requires that VXLAN interface '\ + 'is member of a bridge interface!') + + vnis_used = [] + for vif, vif_config in vxlan['vlan_to_vni'].items(): + if 'vni' not in vif_config: + raise ConfigError(f'Must define VNI for VLAN "{vif}"!') + vni = vif_config['vni'] + if vni in vnis_used: + raise ConfigError(f'VNI "{vni}" is already assigned to a different VLAN!') + vnis_used.append(vni) + + if dict_search('parameters.neighbor_suppress', vxlan) != None: + if 'is_bridge_member' not in vxlan: + raise ConfigError('Neighbor suppression requires that VXLAN interface '\ + 'is member of a bridge interface!') + + verify_mtu_ipv6(vxlan) + verify_address(vxlan) + verify_bond_bridge_member(vxlan) + verify_mirror_redirect(vxlan) + + # We use a defaultValue for port, thus it's always safe to use + if vxlan['port'] == '8472': + Warning('Starting from VyOS 1.4, the default port for VXLAN '\ + 'has been changed to 4789. This matches the IANA assigned '\ + 'standard port number!') + + return None + +def generate(vxlan): + return None + +def apply(vxlan): + # Check if the VXLAN interface already exists + if 'rebuild_required' in vxlan or 'delete' in vxlan: + if vxlan['ifname'] in interfaces(): + v = VXLANIf(vxlan['ifname']) + # VXLAN is super picky and the tunnel always needs to be recreated, + # thus we can simply always delete it first. + v.remove() + + if 'deleted' not in vxlan: + # Finally create the new interface + v = VXLANIf(**vxlan) + v.update(vxlan) + + 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_wireguard.py b/src/conf_mode/interfaces_wireguard.py new file mode 100755 index 000000000..79e5d3f44 --- /dev/null +++ b/src/conf_mode/interfaces_wireguard.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# +# 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 +# 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 . + +from sys import exit + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configdict import get_interface_dict +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 +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_bond_bridge_member +from vyos.ifconfig import WireGuardIf +from vyos.utils.kernel import check_kmod +from vyos.utils.network import check_port_availability +from vyos.utils.network import is_wireguard_key_pair +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', 'wireguard'] + ifname, wireguard = get_interface_dict(conf, base) + + # Check if a port was changed + tmp = is_node_changed(conf, base + [ifname, 'port']) + if tmp: wireguard['port_changed'] = {} + + # T4702: If anything on a peer changes we remove the peer first and re-add it + if is_node_changed(conf, base + [ifname, 'peer']): + wireguard.update({'rebuild_required': {}}) + + return wireguard + +def verify(wireguard): + if 'deleted' in wireguard: + verify_bridge_delete(wireguard) + return None + + verify_mtu_ipv6(wireguard) + verify_address(wireguard) + verify_vrf(wireguard) + verify_bond_bridge_member(wireguard) + verify_mirror_redirect(wireguard) + + if 'private_key' not in wireguard: + raise ConfigError('Wireguard private-key not defined') + + if 'peer' not in wireguard: + raise ConfigError('At least one Wireguard peer is required!') + + if 'port' in wireguard and 'port_changed' in wireguard: + listen_port = int(wireguard['port']) + if check_port_availability('0.0.0.0', listen_port, 'udp') is not True: + raise ConfigError(f'UDP port {listen_port} is busy or unavailable and ' + 'cannot be used for the interface!') + + # run checks on individual configured WireGuard peer + public_keys = [] + for tmp in wireguard['peer']: + peer = wireguard['peer'][tmp] + + if 'allowed_ips' not in peer: + raise ConfigError(f'Wireguard allowed-ips required for peer "{tmp}"!') + + if 'public_key' not in peer: + raise ConfigError(f'Wireguard public-key required for peer "{tmp}"!') + + if ('address' in peer and 'port' not in peer) or ('port' in peer and 'address' not in peer): + raise ConfigError('Both Wireguard port and address must be defined ' + f'for peer "{tmp}" if either one of them is set!') + + if peer['public_key'] in public_keys: + raise ConfigError(f'Duplicate public-key defined on peer "{tmp}"') + + if 'disable' not in peer: + if is_wireguard_key_pair(wireguard['private_key'], peer['public_key']): + raise ConfigError(f'Peer "{tmp}" has the same public key as the interface "{wireguard["ifname"]}"') + + public_keys.append(peer['public_key']) + +def apply(wireguard): + if 'rebuild_required' in wireguard or 'deleted' in wireguard: + wg = WireGuardIf(**wireguard) + # WireGuard only supports peer removal based on the configured public-key, + # by deleting the entire interface this is the shortcut instead of parsing + # out all peers and removing them one by one. + # + # Peer reconfiguration will always come with a short downtime while the + # WireGuard interface is recreated (see below) + wg.remove() + + # Create the new interface if required + if 'deleted' not in wireguard: + wg = WireGuardIf(**wireguard) + wg.update(wireguard) + + return None + +if __name__ == '__main__': + try: + check_kmod('wireguard') + c = get_config() + verify(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_wireless.py b/src/conf_mode/interfaces_wireless.py new file mode 100755 index 000000000..02b4a2500 --- /dev/null +++ b/src/conf_mode/interfaces_wireless.py @@ -0,0 +1,275 @@ +#!/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 . + +import os + +from sys import exit +from re import findall +from netaddr import EUI, mac_unix_expanded + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import dict_merge +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_vlan_config +from vyos.configverify import verify_vrf +from vyos.configverify import verify_bond_bridge_member +from vyos.ifconfig import WiFiIf +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +# XXX: wpa_supplicant works on the source interface +wpa_suppl_conf = '/run/wpa_supplicant/{ifname}.conf' +hostapd_conf = '/run/hostapd/{ifname}.conf' +hostapd_accept_station_conf = '/run/hostapd/{ifname}_station_accept.conf' +hostapd_deny_station_conf = '/run/hostapd/{ifname}_station_deny.conf' + +def find_other_stations(conf, base, ifname): + """ + Only one wireless interface per phy can be in station mode - + find all interfaces attached to a phy which run in station mode + """ + old_level = conf.get_level() + conf.set_level(base) + dict = {} + for phy in os.listdir('/sys/class/ieee80211'): + list = [] + for interface in conf.list_nodes([]): + if interface == ifname: + continue + # the following node is mandatory + if conf.exists([interface, 'physical-device', phy]): + tmp = conf.return_value([interface, 'type']) + if tmp == 'station': + list.append(interface) + if list: + dict.update({phy: list}) + conf.set_level(old_level) + return dict + +def get_config(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', 'wireless'] + + ifname, wifi = get_interface_dict(conf, base) + + if 'deleted' not in wifi: + # then get_interface_dict provides default keys + if wifi.from_defaults(['security', 'wep']): # if not set by user + del wifi['security']['wep'] + if wifi.from_defaults(['security', 'wpa']): # if not set by user + del wifi['security']['wpa'] + + if dict_search('security.wpa', wifi) != None: + wpa_cipher = wifi['security']['wpa'].get('cipher') + wpa_mode = wifi['security']['wpa'].get('mode') + if not wpa_cipher: + tmp = None + if wpa_mode == 'wpa': + tmp = {'security': {'wpa': {'cipher' : ['TKIP', 'CCMP']}}} + elif wpa_mode == 'wpa2': + tmp = {'security': {'wpa': {'cipher' : ['CCMP']}}} + elif wpa_mode == 'both': + tmp = {'security': {'wpa': {'cipher' : ['CCMP', 'TKIP']}}} + + if tmp: wifi = dict_merge(tmp, wifi) + + # Only one wireless interface per phy can be in station mode + tmp = find_other_stations(conf, base, wifi['ifname']) + if tmp: wifi['station_interfaces'] = tmp + + # used in hostapt.conf.j2 + wifi['hostapd_accept_station_conf'] = hostapd_accept_station_conf.format(**wifi) + wifi['hostapd_deny_station_conf'] = hostapd_deny_station_conf.format(**wifi) + + return wifi + +def verify(wifi): + if 'deleted' in wifi: + verify_bridge_delete(wifi) + return None + + if 'physical_device' not in wifi: + raise ConfigError('You must specify a physical-device "phy"') + + if 'type' not in wifi: + raise ConfigError('You must specify a WiFi mode') + + if 'ssid' not in wifi and wifi['type'] != 'monitor': + raise ConfigError('SSID must be configured unless type is set to "monitor"!') + + if wifi['type'] == 'access-point': + if 'country_code' not in wifi: + raise ConfigError('Wireless country-code is mandatory') + + if 'channel' not in wifi: + raise ConfigError('Wireless channel must be configured!') + + if 'security' in wifi: + if {'wep', 'wpa'} <= set(wifi.get('security', {})): + raise ConfigError('Must either use WEP or WPA security!') + + if 'wep' in wifi['security']: + if 'key' in wifi['security']['wep'] and len(wifi['security']['wep']) > 4: + raise ConfigError('No more then 4 WEP keys configurable') + elif 'key' not in wifi['security']['wep']: + raise ConfigError('Security WEP configured - missing WEP keys!') + + elif 'wpa' in wifi['security']: + wpa = wifi['security']['wpa'] + if not any(i in ['passphrase', 'radius'] for i in wpa): + raise ConfigError('Misssing WPA key or RADIUS server') + + if 'radius' in wpa: + if 'server' in wpa['radius']: + for server in wpa['radius']['server']: + if 'key' not in wpa['radius']['server'][server]: + raise ConfigError(f'Misssing RADIUS shared secret key for server: {server}') + + if 'capabilities' in wifi: + capabilities = wifi['capabilities'] + if 'vht' in capabilities: + if 'ht' not in capabilities: + raise ConfigError('Specify HT flags if you want to use VHT!') + + if {'beamform', 'antenna_count'} <= set(capabilities.get('vht', {})): + if capabilities['vht']['antenna_count'] == '1': + raise ConfigError('Cannot use beam forming with just one antenna!') + + if capabilities['vht']['beamform'] == 'single-user-beamformer': + if int(capabilities['vht']['antenna_count']) < 3: + # Nasty Gotcha: see https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf lines 692-705 + raise ConfigError('Single-user beam former requires at least 3 antennas!') + + if 'station_interfaces' in wifi and wifi['type'] == 'station': + phy = wifi['physical_device'] + if phy in wifi['station_interfaces']: + if len(wifi['station_interfaces'][phy]) > 0: + raise ConfigError('Only one station per wireless physical interface possible!') + + verify_address(wifi) + verify_vrf(wifi) + verify_bond_bridge_member(wifi) + verify_mirror_redirect(wifi) + + # use common function to verify VLAN configuration + verify_vlan_config(wifi) + + return None + +def generate(wifi): + interface = wifi['ifname'] + + # always stop hostapd service first before reconfiguring it + call(f'systemctl stop hostapd@{interface}.service') + # always stop wpa_supplicant service first before reconfiguring it + call(f'systemctl stop wpa_supplicant@{interface}.service') + + # Delete config files if interface is removed + if 'deleted' in wifi: + if os.path.isfile(hostapd_conf.format(**wifi)): + os.unlink(hostapd_conf.format(**wifi)) + if os.path.isfile(hostapd_accept_station_conf.format(**wifi)): + os.unlink(hostapd_accept_station_conf.format(**wifi)) + if os.path.isfile(hostapd_deny_station_conf.format(**wifi)): + os.unlink(hostapd_deny_station_conf.format(**wifi)) + if os.path.isfile(wpa_suppl_conf.format(**wifi)): + os.unlink(wpa_suppl_conf.format(**wifi)) + + return None + + if 'mac' not in wifi: + # http://wiki.stocksy.co.uk/wiki/Multiple_SSIDs_with_hostapd + # generate locally administered MAC address from used phy interface + with open('/sys/class/ieee80211/{physical_device}/addresses'.format(**wifi), 'r') as f: + # some PHYs tend to have multiple interfaces and thus supply multiple MAC + # addresses - we only need the first one for our calculation + tmp = f.readline().rstrip() + tmp = EUI(tmp).value + # mask last nibble from the MAC address + tmp &= 0xfffffffffff0 + # set locally administered bit in MAC address + tmp |= 0x020000000000 + # we now need to add an offset to our MAC address indicating this + # subinterfaces index + tmp += int(findall(r'\d+', interface)[0]) + + # convert integer to "real" MAC address representation + mac = EUI(hex(tmp).split('x')[-1]) + # change dialect to use : as delimiter instead of - + mac.dialect = mac_unix_expanded + wifi['mac'] = str(mac) + + # XXX: Jinja2 can not operate on a dictionary key when it starts of with a number + if '40mhz_incapable' in (dict_search('capabilities.ht', wifi) or []): + wifi['capabilities']['ht']['fourtymhz_incapable'] = wifi['capabilities']['ht']['40mhz_incapable'] + del wifi['capabilities']['ht']['40mhz_incapable'] + + # render appropriate new config files depending on access-point or station mode + if wifi['type'] == 'access-point': + render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.j2', wifi) + render(hostapd_accept_station_conf.format(**wifi), 'wifi/hostapd_accept_station.conf.j2', wifi) + render(hostapd_deny_station_conf.format(**wifi), 'wifi/hostapd_deny_station.conf.j2', wifi) + + elif wifi['type'] == 'station': + render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.j2', wifi) + + return None + +def apply(wifi): + interface = wifi['ifname'] + if 'deleted' in wifi: + WiFiIf(interface).remove() + else: + # Finally create the new interface + w = WiFiIf(**wifi) + w.update(wifi) + + # Enable/Disable interface - interface is always placed in + # administrative down state in WiFiIf class + if 'disable' not in wifi: + # Physical interface is now configured. Proceed by starting hostapd or + # wpa_supplicant daemon. When type is monitor we can just skip this. + if wifi['type'] == 'access-point': + call(f'systemctl start hostapd@{interface}.service') + + elif wifi['type'] == 'station': + call(f'systemctl start wpa_supplicant@{interface}.service') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_wwan.py b/src/conf_mode/interfaces_wwan.py new file mode 100755 index 000000000..2515dc838 --- /dev/null +++ b/src/conf_mode/interfaces_wwan.py @@ -0,0 +1,189 @@ +#!/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 . + +import os + +from sys import exit +from time import sleep + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configverify import verify_authentication +from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_vrf +from vyos.ifconfig import WWANIf +from vyos.utils.dict import dict_search +from vyos.utils.process import cmd +from vyos.utils.process import call +from vyos.utils.process import DEVNULL +from vyos.utils.process import is_systemd_service_active +from vyos.utils.file import write_file +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +service_name = 'ModemManager.service' +cron_script = '/etc/cron.d/vyos-wwan' + +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', 'wwan'] + ifname, wwan = get_interface_dict(conf, base) + + # We should only terminate the WWAN session if critical parameters change. + # All parameters that can be changed on-the-fly (like interface description) + # should not lead to a reconnect! + tmp = is_node_changed(conf, base + [ifname, 'address']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = is_node_changed(conf, base + [ifname, 'apn']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = is_node_changed(conf, base + [ifname, 'disable']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = is_node_changed(conf, base + [ifname, 'vrf']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = is_node_changed(conf, base + [ifname, 'authentication']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = is_node_changed(conf, base + [ifname, 'ipv6', 'address', 'autoconf']) + if tmp: wwan.update({'shutdown_required': {}}) + + # We need to know the amount of other WWAN interfaces as ModemManager needs + # to be started or stopped. + wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # This if-clause is just to be sure - it will always evaluate to true + if ifname in wwan['other_interfaces']: + del wwan['other_interfaces'][ifname] + if len(wwan['other_interfaces']) == 0: + del wwan['other_interfaces'] + + return wwan + +def verify(wwan): + if 'deleted' in wwan: + return None + + ifname = wwan['ifname'] + if not 'apn' in wwan: + raise ConfigError(f'No APN configured for "{ifname}"!') + + verify_interface_exists(ifname) + verify_authentication(wwan) + verify_vrf(wwan) + verify_mirror_redirect(wwan) + + return None + +def generate(wwan): + if 'deleted' in wwan: + # We are the last WWAN interface - there are no other ones remaining + # thus the cronjob needs to go away, too + if 'other_interfaces' not in wwan: + if os.path.exists(cron_script): + os.unlink(cron_script) + return None + + # Install cron triggered helper script to re-dial WWAN interfaces on + # disconnect - e.g. happens during RF signal loss. The script watches every + # WWAN interface - so there is only one instance. + if not os.path.exists(cron_script): + write_file(cron_script, '*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py\n') + + return None + +def apply(wwan): + # ModemManager is required to dial WWAN connections - one instance is + # required to serve all modems. Activate ModemManager on first invocation + # of any WWAN interface. + if not is_systemd_service_active(service_name): + cmd(f'systemctl start {service_name}') + + counter = 100 + # Wait until a modem is detected and then we can continue + while counter > 0: + counter -= 1 + tmp = cmd('mmcli -L') + if tmp != 'No modems were found': + break + sleep(0.250) + + if 'shutdown_required' in wwan: + # we only need the modem number. wwan0 -> 0, wwan1 -> 1 + modem = wwan['ifname'].lstrip('wwan') + base_cmd = f'mmcli --modem {modem}' + # Number of bearers is limited - always disconnect first + cmd(f'{base_cmd} --simple-disconnect') + + w = WWANIf(wwan['ifname']) + if 'deleted' in wwan or 'disable' in wwan: + w.remove() + + # We are the last WWAN interface - there are no other WWAN interfaces + # remaining, thus we can stop ModemManager and free resources. + if 'other_interfaces' not in wwan: + cmd(f'systemctl stop {service_name}') + # Clean CRON helper script which is used for to re-connect when + # RF signal is lost + if os.path.exists(cron_script): + os.unlink(cron_script) + + return None + + if 'shutdown_required' in wwan: + ip_type = 'ipv4' + slaac = dict_search('ipv6.address.autoconf', wwan) != None + if 'address' in wwan: + if 'dhcp' in wwan['address'] and ('dhcpv6' in wwan['address'] or slaac): + ip_type = 'ipv4v6' + elif 'dhcpv6' in wwan['address'] or slaac: + ip_type = 'ipv6' + elif 'dhcp' in wwan['address']: + ip_type = 'ipv4' + + options = f'ip-type={ip_type},apn=' + wwan['apn'] + if 'authentication' in wwan: + options += ',user={username},password={password}'.format(**wwan['authentication']) + + command = f'{base_cmd} --simple-connect="{options}"' + call(command, stdout=DEVNULL) + + w.update(wwan) + 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/le_cert.py b/src/conf_mode/le_cert.py deleted file mode 100755 index 06c7e7b72..000000000 --- a/src/conf_mode/le_cert.py +++ /dev/null @@ -1,115 +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 . - -import sys -import os - -import vyos.defaults -from vyos.config import Config -from vyos import ConfigError -from vyos.utils.process import cmd -from vyos.utils.process import call -from vyos.utils.process import is_systemd_service_running - -from vyos import airbag -airbag.enable() - -vyos_conf_scripts_dir = vyos.defaults.directories['conf_mode'] -vyos_certbot_dir = vyos.defaults.directories['certbot'] - -dependencies = [ - 'https.py', -] - -def request_certbot(cert): - email = cert.get('email') - if email is not None: - email_flag = '-m {0}'.format(email) - else: - email_flag = '' - - domains = cert.get('domains') - if domains is not None: - domain_flag = '-d ' + ' -d '.join(domains) - else: - domain_flag = '' - - certbot_cmd = f'certbot certonly --config-dir {vyos_certbot_dir} -n --nginx --agree-tos --no-eff-email --expand {email_flag} {domain_flag}' - - cmd(certbot_cmd, - raising=ConfigError, - message="The certbot request failed for the specified domains.") - -def get_config(): - conf = Config() - if not conf.exists('service https certificates certbot'): - return None - else: - conf.set_level('service https certificates certbot') - - cert = {} - - if conf.exists('domain-name'): - cert['domains'] = conf.return_values('domain-name') - - if conf.exists('email'): - cert['email'] = conf.return_value('email') - - return cert - -def verify(cert): - if cert is None: - return None - - if 'domains' not in cert: - raise ConfigError("At least one domain name is required to" - " request a letsencrypt certificate.") - - if 'email' not in cert: - raise ConfigError("An email address is required to request" - " a letsencrypt certificate.") - -def generate(cert): - if cert is None: - return None - - # certbot will attempt to reload nginx, even with 'certonly'; - # start nginx if not active - if not is_systemd_service_running('nginx.service'): - call('systemctl start nginx.service') - - request_certbot(cert) - -def apply(cert): - if cert is not None: - call('systemctl restart certbot.timer') - else: - call('systemctl stop certbot.timer') - return None - - for dep in dependencies: - cmd(f'{vyos_conf_scripts_dir}/{dep}', raising=ConfigError) - -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/lldp.py b/src/conf_mode/lldp.py deleted file mode 100755 index 3c647a0e8..000000000 --- a/src/conf_mode/lldp.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2017-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 . - -import os - -from sys import exit - -from vyos.base import Warning -from vyos.config import Config -from vyos.utils.network import is_addr_assigned -from vyos.utils.network import is_loopback_addr -from vyos.version import get_version_data -from vyos.utils.process import call -from vyos.utils.dict import dict_search -from vyos.template import render -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -config_file = "/etc/default/lldpd" -vyos_config_file = "/etc/lldpd.d/01-vyos.conf" -base = ['service', 'lldp'] - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - if not conf.exists(base): - return {} - - lldp = conf.get_config_dict(base, key_mangling=('-', '_'), - no_tag_node_value_mangle=True, - get_first_key=True, - with_recursive_defaults=True) - - if conf.exists(['service', 'snmp']): - lldp['system_snmp_enabled'] = '' - - version_data = get_version_data() - lldp['version'] = version_data['version'] - - # prune location information if not set by user - for interface in lldp.get('interface', []): - if lldp.from_defaults(['interface', interface, 'location']): - del lldp['interface'][interface]['location'] - elif lldp.from_defaults(['interface', interface, 'location','coordinate_based']): - del lldp['interface'][interface]['location']['coordinate_based'] - - return lldp - -def verify(lldp): - # bail out early - looks like removal from running config - if lldp is None: - return - - if 'management_address' in lldp: - for address in lldp['management_address']: - message = f'LLDP management address "{address}" is invalid' - if is_loopback_addr(address): - Warning(f'{message} - loopback address') - elif not is_addr_assigned(address): - Warning(f'{message} - not assigned to any interface') - - if 'interface' in lldp: - for interface, interface_config in lldp['interface'].items(): - # bail out early if no location info present in interface config - if 'location' not in interface_config: - continue - if 'coordinate_based' in interface_config['location']: - if not {'latitude', 'latitude'} <= set(interface_config['location']['coordinate_based']): - raise ConfigError(f'Must define both longitude and latitude for "{interface}" location!') - - # check options - if 'snmp' in lldp: - if 'system_snmp_enabled' not in lldp: - raise ConfigError('SNMP must be configured to enable LLDP SNMP!') - - -def generate(lldp): - # bail out early - looks like removal from running config - if lldp is None: - return - - render(config_file, 'lldp/lldpd.j2', lldp) - render(vyos_config_file, 'lldp/vyos.conf.j2', lldp) - -def apply(lldp): - systemd_service = 'lldpd.service' - if lldp: - # start/restart lldp service - call(f'systemctl restart {systemd_service}') - else: - # LLDP service has been terminated - call(f'systemctl stop {systemd_service}') - if os.path.isfile(config_file): - os.unlink(config_file) - if os.path.isfile(vyos_config_file): - os.unlink(vyos_config_file) - -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/load-balancing-haproxy.py b/src/conf_mode/load-balancing-haproxy.py deleted file mode 100755 index 333ebc66c..000000000 --- a/src/conf_mode/load-balancing-haproxy.py +++ /dev/null @@ -1,169 +0,0 @@ -#!/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 . - -import os - -from sys import exit -from shutil import rmtree - -from vyos.config import Config -from vyos.utils.process import call -from vyos.utils.network import check_port_availability -from vyos.utils.network import is_listen_port_bind_service -from vyos.pki import wrap_certificate -from vyos.pki import wrap_private_key -from vyos.template import render -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -load_balancing_dir = '/run/haproxy' -load_balancing_conf_file = f'{load_balancing_dir}/haproxy.cfg' -systemd_service = 'haproxy.service' -systemd_override = r'/run/systemd/system/haproxy.service.d/10-override.conf' - - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - base = ['load-balancing', 'reverse-proxy'] - lb = conf.get_config_dict(base, - get_first_key=True, - key_mangling=('-', '_'), - no_tag_node_value_mangle=True) - - if lb: - lb['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - - if lb: - lb = conf.merge_defaults(lb, recursive=True) - - return lb - - -def verify(lb): - if not lb: - return None - - if 'backend' not in lb or 'service' not in lb: - raise ConfigError(f'"service" and "backend" must be configured!') - - for front, front_config in lb['service'].items(): - if 'port' not in front_config: - raise ConfigError(f'"{front} service port" must be configured!') - - # Check if bind address:port are used by another service - tmp_address = front_config.get('address', '0.0.0.0') - tmp_port = front_config['port'] - if check_port_availability(tmp_address, int(tmp_port), 'tcp') is not True and \ - not is_listen_port_bind_service(int(tmp_port), 'haproxy'): - raise ConfigError(f'"TCP" port "{tmp_port}" is used by another service') - - for back, back_config in lb['backend'].items(): - if 'server' not in back_config: - raise ConfigError(f'"{back} server" must be configured!') - for bk_server, bk_server_conf in back_config['server'].items(): - if 'address' not in bk_server_conf or 'port' not in bk_server_conf: - raise ConfigError(f'"backend {back} server {bk_server} address and port" must be configured!') - - if {'send_proxy', 'send_proxy_v2'} <= set(bk_server_conf): - raise ConfigError(f'Cannot use both "send-proxy" and "send-proxy-v2" for server "{bk_server}"') - -def generate(lb): - if not lb: - # Delete /run/haproxy/haproxy.cfg - config_files = [load_balancing_conf_file, systemd_override] - for file in config_files: - if os.path.isfile(file): - os.unlink(file) - # Delete old directories - if os.path.isdir(load_balancing_dir): - rmtree(load_balancing_dir, ignore_errors=True) - - return None - - # Create load-balance dir - if not os.path.isdir(load_balancing_dir): - os.mkdir(load_balancing_dir) - - # SSL Certificates for frontend - for front, front_config in lb['service'].items(): - if 'ssl' in front_config: - - if 'certificate' in front_config['ssl']: - cert_names = front_config['ssl']['certificate'] - - for cert_name in cert_names: - pki_cert = lb['pki']['certificate'][cert_name] - cert_file_path = os.path.join(load_balancing_dir, f'{cert_name}.pem') - cert_key_path = os.path.join(load_balancing_dir, f'{cert_name}.pem.key') - - with open(cert_file_path, 'w') as f: - f.write(wrap_certificate(pki_cert['certificate'])) - - if 'private' in pki_cert and 'key' in pki_cert['private']: - with open(cert_key_path, 'w') as f: - f.write(wrap_private_key(pki_cert['private']['key'])) - - if 'ca_certificate' in front_config['ssl']: - ca_name = front_config['ssl']['ca_certificate'] - pki_ca_cert = lb['pki']['ca'][ca_name] - ca_cert_file_path = os.path.join(load_balancing_dir, f'{ca_name}.pem') - - with open(ca_cert_file_path, 'w') as f: - f.write(wrap_certificate(pki_ca_cert['certificate'])) - - # SSL Certificates for backend - for back, back_config in lb['backend'].items(): - if 'ssl' in back_config: - - if 'ca_certificate' in back_config['ssl']: - ca_name = back_config['ssl']['ca_certificate'] - pki_ca_cert = lb['pki']['ca'][ca_name] - ca_cert_file_path = os.path.join(load_balancing_dir, f'{ca_name}.pem') - - with open(ca_cert_file_path, 'w') as f: - f.write(wrap_certificate(pki_ca_cert['certificate'])) - - render(load_balancing_conf_file, 'load-balancing/haproxy.cfg.j2', lb) - render(systemd_override, 'load-balancing/override_haproxy.conf.j2', lb) - - return None - - -def apply(lb): - call('systemctl daemon-reload') - if not lb: - call(f'systemctl stop {systemd_service}') - else: - call(f'systemctl reload-or-restart {systemd_service}') - - return None - - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/load-balancing-wan.py b/src/conf_mode/load-balancing-wan.py deleted file mode 100755 index 5da0b906b..000000000 --- a/src/conf_mode/load-balancing-wan.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/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 . - -import os - -from sys import exit -from shutil import rmtree - -from vyos.base import Warning -from vyos.config import Config -from vyos.configdep import set_dependents, call_dependents -from vyos.utils.process import cmd -from vyos.template import render -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -load_balancing_dir = '/run/load-balance' -load_balancing_conf_file = f'{load_balancing_dir}/wlb.conf' -systemd_service = 'vyos-wan-load-balance.service' - - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - base = ['load-balancing', 'wan'] - lb = conf.get_config_dict(base, key_mangling=('-', '_'), - no_tag_node_value_mangle=True, - get_first_key=True, - with_recursive_defaults=True) - - # prune limit key if not set by user - for rule in lb.get('rule', []): - if lb.from_defaults(['rule', rule, 'limit']): - del lb['rule'][rule]['limit'] - - set_dependents('conntrack', conf) - - return lb - - -def verify(lb): - if not lb: - return None - - if 'interface_health' not in lb: - raise ConfigError( - 'A valid WAN load-balance configuration requires an interface with a nexthop!' - ) - - for interface, interface_config in lb['interface_health'].items(): - if 'nexthop' not in interface_config: - raise ConfigError( - f'interface-health {interface} nexthop must be specified!') - - if 'test' in interface_config: - for test_rule, test_config in interface_config['test'].items(): - if 'type' in test_config: - if test_config['type'] == 'user-defined' and 'test_script' not in test_config: - raise ConfigError( - f'test {test_rule} script must be defined for test-script!' - ) - - if 'rule' not in lb: - Warning( - 'At least one rule with an (outbound) interface must be defined for WAN load balancing to be active!' - ) - else: - for rule, rule_config in lb['rule'].items(): - if 'inbound_interface' not in rule_config: - raise ConfigError(f'rule {rule} inbound-interface must be specified!') - if {'failover', 'exclude'} <= set(rule_config): - raise ConfigError(f'rule {rule} failover cannot be configured with exclude!') - if {'limit', 'exclude'} <= set(rule_config): - raise ConfigError(f'rule {rule} limit cannot be used with exclude!') - if 'interface' not in rule_config: - if 'exclude' not in rule_config: - Warning( - f'rule {rule} will be inactive because no (outbound) interfaces have been defined for this rule' - ) - for direction in {'source', 'destination'}: - if direction in rule_config: - if 'protocol' in rule_config and 'port' in rule_config[ - direction]: - if rule_config['protocol'] not in {'tcp', 'udp'}: - raise ConfigError('ports can only be specified when protocol is "tcp" or "udp"') - - -def generate(lb): - if not lb: - # Delete /run/load-balance/wlb.conf - if os.path.isfile(load_balancing_conf_file): - os.unlink(load_balancing_conf_file) - # Delete old directories - if os.path.isdir(load_balancing_dir): - rmtree(load_balancing_dir, ignore_errors=True) - if os.path.exists('/var/run/load-balance/wlb.out'): - os.unlink('/var/run/load-balance/wlb.out') - - return None - - # Create load-balance dir - if not os.path.isdir(load_balancing_dir): - os.mkdir(load_balancing_dir) - - render(load_balancing_conf_file, 'load-balancing/wlb.conf.j2', lb) - - return None - - -def apply(lb): - if not lb: - try: - cmd(f'systemctl stop {systemd_service}') - except Exception as e: - print(f"Error message: {e}") - - else: - cmd('sudo sysctl -w net.netfilter.nf_conntrack_acct=1') - cmd(f'systemctl restart {systemd_service}') - - call_dependents() - - 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/load-balancing_reverse-proxy.py b/src/conf_mode/load-balancing_reverse-proxy.py new file mode 100755 index 000000000..333ebc66c --- /dev/null +++ b/src/conf_mode/load-balancing_reverse-proxy.py @@ -0,0 +1,169 @@ +#!/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 . + +import os + +from sys import exit +from shutil import rmtree + +from vyos.config import Config +from vyos.utils.process import call +from vyos.utils.network import check_port_availability +from vyos.utils.network import is_listen_port_bind_service +from vyos.pki import wrap_certificate +from vyos.pki import wrap_private_key +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +load_balancing_dir = '/run/haproxy' +load_balancing_conf_file = f'{load_balancing_dir}/haproxy.cfg' +systemd_service = 'haproxy.service' +systemd_override = r'/run/systemd/system/haproxy.service.d/10-override.conf' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['load-balancing', 'reverse-proxy'] + lb = conf.get_config_dict(base, + get_first_key=True, + key_mangling=('-', '_'), + no_tag_node_value_mangle=True) + + if lb: + lb['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + if lb: + lb = conf.merge_defaults(lb, recursive=True) + + return lb + + +def verify(lb): + if not lb: + return None + + if 'backend' not in lb or 'service' not in lb: + raise ConfigError(f'"service" and "backend" must be configured!') + + for front, front_config in lb['service'].items(): + if 'port' not in front_config: + raise ConfigError(f'"{front} service port" must be configured!') + + # Check if bind address:port are used by another service + tmp_address = front_config.get('address', '0.0.0.0') + tmp_port = front_config['port'] + if check_port_availability(tmp_address, int(tmp_port), 'tcp') is not True and \ + not is_listen_port_bind_service(int(tmp_port), 'haproxy'): + raise ConfigError(f'"TCP" port "{tmp_port}" is used by another service') + + for back, back_config in lb['backend'].items(): + if 'server' not in back_config: + raise ConfigError(f'"{back} server" must be configured!') + for bk_server, bk_server_conf in back_config['server'].items(): + if 'address' not in bk_server_conf or 'port' not in bk_server_conf: + raise ConfigError(f'"backend {back} server {bk_server} address and port" must be configured!') + + if {'send_proxy', 'send_proxy_v2'} <= set(bk_server_conf): + raise ConfigError(f'Cannot use both "send-proxy" and "send-proxy-v2" for server "{bk_server}"') + +def generate(lb): + if not lb: + # Delete /run/haproxy/haproxy.cfg + config_files = [load_balancing_conf_file, systemd_override] + for file in config_files: + if os.path.isfile(file): + os.unlink(file) + # Delete old directories + if os.path.isdir(load_balancing_dir): + rmtree(load_balancing_dir, ignore_errors=True) + + return None + + # Create load-balance dir + if not os.path.isdir(load_balancing_dir): + os.mkdir(load_balancing_dir) + + # SSL Certificates for frontend + for front, front_config in lb['service'].items(): + if 'ssl' in front_config: + + if 'certificate' in front_config['ssl']: + cert_names = front_config['ssl']['certificate'] + + for cert_name in cert_names: + pki_cert = lb['pki']['certificate'][cert_name] + cert_file_path = os.path.join(load_balancing_dir, f'{cert_name}.pem') + cert_key_path = os.path.join(load_balancing_dir, f'{cert_name}.pem.key') + + with open(cert_file_path, 'w') as f: + f.write(wrap_certificate(pki_cert['certificate'])) + + if 'private' in pki_cert and 'key' in pki_cert['private']: + with open(cert_key_path, 'w') as f: + f.write(wrap_private_key(pki_cert['private']['key'])) + + if 'ca_certificate' in front_config['ssl']: + ca_name = front_config['ssl']['ca_certificate'] + pki_ca_cert = lb['pki']['ca'][ca_name] + ca_cert_file_path = os.path.join(load_balancing_dir, f'{ca_name}.pem') + + with open(ca_cert_file_path, 'w') as f: + f.write(wrap_certificate(pki_ca_cert['certificate'])) + + # SSL Certificates for backend + for back, back_config in lb['backend'].items(): + if 'ssl' in back_config: + + if 'ca_certificate' in back_config['ssl']: + ca_name = back_config['ssl']['ca_certificate'] + pki_ca_cert = lb['pki']['ca'][ca_name] + ca_cert_file_path = os.path.join(load_balancing_dir, f'{ca_name}.pem') + + with open(ca_cert_file_path, 'w') as f: + f.write(wrap_certificate(pki_ca_cert['certificate'])) + + render(load_balancing_conf_file, 'load-balancing/haproxy.cfg.j2', lb) + render(systemd_override, 'load-balancing/override_haproxy.conf.j2', lb) + + return None + + +def apply(lb): + call('systemctl daemon-reload') + if not lb: + call(f'systemctl stop {systemd_service}') + else: + call(f'systemctl reload-or-restart {systemd_service}') + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/load-balancing_wan.py b/src/conf_mode/load-balancing_wan.py new file mode 100755 index 000000000..5da0b906b --- /dev/null +++ b/src/conf_mode/load-balancing_wan.py @@ -0,0 +1,151 @@ +#!/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 . + +import os + +from sys import exit +from shutil import rmtree + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdep import set_dependents, call_dependents +from vyos.utils.process import cmd +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +load_balancing_dir = '/run/load-balance' +load_balancing_conf_file = f'{load_balancing_dir}/wlb.conf' +systemd_service = 'vyos-wan-load-balance.service' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['load-balancing', 'wan'] + lb = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + # prune limit key if not set by user + for rule in lb.get('rule', []): + if lb.from_defaults(['rule', rule, 'limit']): + del lb['rule'][rule]['limit'] + + set_dependents('conntrack', conf) + + return lb + + +def verify(lb): + if not lb: + return None + + if 'interface_health' not in lb: + raise ConfigError( + 'A valid WAN load-balance configuration requires an interface with a nexthop!' + ) + + for interface, interface_config in lb['interface_health'].items(): + if 'nexthop' not in interface_config: + raise ConfigError( + f'interface-health {interface} nexthop must be specified!') + + if 'test' in interface_config: + for test_rule, test_config in interface_config['test'].items(): + if 'type' in test_config: + if test_config['type'] == 'user-defined' and 'test_script' not in test_config: + raise ConfigError( + f'test {test_rule} script must be defined for test-script!' + ) + + if 'rule' not in lb: + Warning( + 'At least one rule with an (outbound) interface must be defined for WAN load balancing to be active!' + ) + else: + for rule, rule_config in lb['rule'].items(): + if 'inbound_interface' not in rule_config: + raise ConfigError(f'rule {rule} inbound-interface must be specified!') + if {'failover', 'exclude'} <= set(rule_config): + raise ConfigError(f'rule {rule} failover cannot be configured with exclude!') + if {'limit', 'exclude'} <= set(rule_config): + raise ConfigError(f'rule {rule} limit cannot be used with exclude!') + if 'interface' not in rule_config: + if 'exclude' not in rule_config: + Warning( + f'rule {rule} will be inactive because no (outbound) interfaces have been defined for this rule' + ) + for direction in {'source', 'destination'}: + if direction in rule_config: + if 'protocol' in rule_config and 'port' in rule_config[ + direction]: + if rule_config['protocol'] not in {'tcp', 'udp'}: + raise ConfigError('ports can only be specified when protocol is "tcp" or "udp"') + + +def generate(lb): + if not lb: + # Delete /run/load-balance/wlb.conf + if os.path.isfile(load_balancing_conf_file): + os.unlink(load_balancing_conf_file) + # Delete old directories + if os.path.isdir(load_balancing_dir): + rmtree(load_balancing_dir, ignore_errors=True) + if os.path.exists('/var/run/load-balance/wlb.out'): + os.unlink('/var/run/load-balance/wlb.out') + + return None + + # Create load-balance dir + if not os.path.isdir(load_balancing_dir): + os.mkdir(load_balancing_dir) + + render(load_balancing_conf_file, 'load-balancing/wlb.conf.j2', lb) + + return None + + +def apply(lb): + if not lb: + try: + cmd(f'systemctl stop {systemd_service}') + except Exception as e: + print(f"Error message: {e}") + + else: + cmd('sudo sysctl -w net.netfilter.nf_conntrack_acct=1') + cmd(f'systemctl restart {systemd_service}') + + call_dependents() + + 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/ntp.py b/src/conf_mode/ntp.py deleted file mode 100755 index 1cc23a7df..000000000 --- a/src/conf_mode/ntp.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from vyos.config import Config -from vyos.configdict import is_node_changed -from vyos.configverify import verify_vrf -from vyos.configverify import verify_interface_exists -from vyos.utils.process import call -from vyos.utils.permission import chmod_750 -from vyos.utils.network import get_interface_config -from vyos.template import render -from vyos.template import is_ipv4 -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -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 = ['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': {}}) - - return ntp - -def verify(ntp): - # bail out early - looks like removal from running config - if not ntp: - return None - - if 'server' not in ntp: - raise ConfigError('NTP server not configured') - - verify_vrf(ntp) - - if 'interface' in ntp: - # If ntpd should listen on a given interface, ensure it exists - interface = ntp['interface'] - verify_interface_exists(interface) - - # If we run in a VRF, our interface must belong to this VRF, too - if 'vrf' in ntp: - tmp = get_interface_config(interface) - vrf_name = ntp['vrf'] - if 'master' not in tmp or tmp['master'] != vrf_name: - raise ConfigError(f'NTP runs in VRF "{vrf_name}" - "{interface}" '\ - f'does not belong to this VRF!') - - if 'listen_address' in ntp: - ipv4_addresses = 0 - ipv6_addresses = 0 - for address in ntp['listen_address']: - if is_ipv4(address): - ipv4_addresses += 1 - else: - ipv6_addresses += 1 - if ipv4_addresses > 1: - raise ConfigError(f'NTP Only admits one ipv4 value for listen-address parameter ') - if ipv6_addresses > 1: - raise ConfigError(f'NTP Only admits one ipv6 value for listen-address parameter ') - - return None - -def generate(ntp): - # bail out early - looks like removal from running config - if not ntp: - return None - - 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 = 'chrony.service' - # Reload systemd manager configuration - call('systemctl daemon-reload') - - if not ntp: - # NTP support is removed in the commit - call(f'systemctl stop {systemd_service}') - if os.path.exists(config_file): - os.unlink(config_file) - if os.path.isfile(systemd_override): - os.unlink(systemd_override) - return - - # we need to restart the service if e.g. the VRF name changed - systemd_action = 'reload-or-restart' - if 'restart_required' in ntp: - systemd_action = 'restart' - - call(f'systemctl {systemd_action} {systemd_service}') - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py index 34ba2fe69..f7e14aa16 100755 --- a/src/conf_mode/pki.py +++ b/src/conf_mode/pki.py @@ -36,22 +36,22 @@ sync_search = [ { 'keys': ['certificate'], 'path': ['service', 'https'], - 'script': '/usr/libexec/vyos/conf_mode/https.py' + 'script': '/usr/libexec/vyos/conf_mode/service_https.py' }, { 'keys': ['certificate', 'ca_certificate'], 'path': ['interfaces', 'ethernet'], - 'script': '/usr/libexec/vyos/conf_mode/interfaces-ethernet.py' + 'script': '/usr/libexec/vyos/conf_mode/interfaces_ethernet.py' }, { 'keys': ['certificate', 'ca_certificate', 'dh_params', 'shared_secret_key', 'auth_key', 'crypt_key'], 'path': ['interfaces', 'openvpn'], - 'script': '/usr/libexec/vyos/conf_mode/interfaces-openvpn.py' + 'script': '/usr/libexec/vyos/conf_mode/interfaces_openvpn.py' }, { 'keys': ['ca_certificate'], 'path': ['interfaces', 'sstpc'], - 'script': '/usr/libexec/vyos/conf_mode/interfaces-sstpc.py' + 'script': '/usr/libexec/vyos/conf_mode/interfaces_sstpc.py' }, { 'keys': ['certificate', 'ca_certificate', 'local_key', 'remote_key'], diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py deleted file mode 100755 index 91e4fce2c..000000000 --- a/src/conf_mode/policy-local-route.py +++ /dev/null @@ -1,315 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from itertools import product -from sys import exit - -from netifaces import interfaces -from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.configdict import node_changed -from vyos.configdict import leaf_node_changed -from vyos.template import render -from vyos.utils.process import call -from vyos import ConfigError -from vyos import airbag -airbag.enable() - - -def get_config(config=None): - - if config: - conf = config - else: - conf = Config() - base = ['policy'] - - pbr = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - - for route in ['local_route', 'local_route6']: - dict_id = 'rule_remove' if route == 'local_route' else 'rule6_remove' - route_key = 'local-route' if route == 'local_route' else 'local-route6' - base_rule = base + [route_key, 'rule'] - - # delete policy local-route - dict = {} - tmp = node_changed(conf, base_rule, key_mangling=('-', '_')) - if tmp: - for rule in (tmp or []): - src = leaf_node_changed(conf, base_rule + [rule, 'source', 'address']) - src_port = leaf_node_changed(conf, base_rule + [rule, 'source', 'port']) - fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) - iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface']) - dst = leaf_node_changed(conf, base_rule + [rule, 'destination', 'address']) - dst_port = leaf_node_changed(conf, base_rule + [rule, 'destination', 'port']) - table = leaf_node_changed(conf, base_rule + [rule, 'set', 'table']) - proto = leaf_node_changed(conf, base_rule + [rule, 'protocol']) - rule_def = {} - if src: - rule_def = dict_merge({'source': {'address': src}}, rule_def) - if src_port: - rule_def = dict_merge({'source': {'port': src_port}}, rule_def) - if fwmk: - rule_def = dict_merge({'fwmark' : fwmk}, rule_def) - if iif: - rule_def = dict_merge({'inbound_interface' : iif}, rule_def) - if dst: - rule_def = dict_merge({'destination': {'address': dst}}, rule_def) - if dst_port: - rule_def = dict_merge({'destination': {'port': dst_port}}, rule_def) - if table: - rule_def = dict_merge({'table' : table}, rule_def) - if proto: - rule_def = dict_merge({'protocol' : proto}, rule_def) - dict = dict_merge({dict_id : {rule : rule_def}}, dict) - pbr.update(dict) - - if not route in pbr: - continue - - # delete policy local-route rule x source x.x.x.x - # delete policy local-route rule x fwmark x - # delete policy local-route rule x destination x.x.x.x - if 'rule' in pbr[route]: - for rule, rule_config in pbr[route]['rule'].items(): - src = leaf_node_changed(conf, base_rule + [rule, 'source', 'address']) - src_port = leaf_node_changed(conf, base_rule + [rule, 'source', 'port']) - fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) - iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface']) - dst = leaf_node_changed(conf, base_rule + [rule, 'destination', 'address']) - dst_port = leaf_node_changed(conf, base_rule + [rule, 'destination', 'port']) - table = leaf_node_changed(conf, base_rule + [rule, 'set', 'table']) - proto = leaf_node_changed(conf, base_rule + [rule, 'protocol']) - # keep track of changes in configuration - # otherwise we might remove an existing node although nothing else has changed - changed = False - - rule_def = {} - # src is None if there are no changes to src - if src is None: - # if src hasn't changed, include it in the removal selector - # if a new selector is added, we have to remove all previous rules without this selector - # to make sure we remove all previous rules with this source(s), it will be included - if 'source' in rule_config: - if 'address' in rule_config['source']: - rule_def = dict_merge({'source': {'address': rule_config['source']['address']}}, rule_def) - else: - # if src is not None, it's previous content will be returned - # this can be an empty array if it's just being set, or the previous value - # either way, something has to be changed and we only want to remove previous values - changed = True - # set the old value for removal if it's not empty - if len(src) > 0: - rule_def = dict_merge({'source': {'address': src}}, rule_def) - - # source port - if src_port is None: - if 'source' in rule_config: - if 'port' in rule_config['source']: - tmp = rule_config['source']['port'] - if isinstance(tmp, str): - tmp = [tmp] - rule_def = dict_merge({'source': {'port': tmp}}, rule_def) - else: - changed = True - if len(src_port) > 0: - rule_def = dict_merge({'source': {'port': src_port}}, rule_def) - - # fwmark - if fwmk is None: - if 'fwmark' in rule_config: - tmp = rule_config['fwmark'] - if isinstance(tmp, str): - tmp = [tmp] - rule_def = dict_merge({'fwmark': tmp}, rule_def) - else: - changed = True - if len(fwmk) > 0: - rule_def = dict_merge({'fwmark' : fwmk}, rule_def) - - # inbound-interface - if iif is None: - if 'inbound_interface' in rule_config: - rule_def = dict_merge({'inbound_interface': rule_config['inbound_interface']}, rule_def) - else: - changed = True - if len(iif) > 0: - rule_def = dict_merge({'inbound_interface' : iif}, rule_def) - - # destination address - if dst is None: - if 'destination' in rule_config: - if 'address' in rule_config['destination']: - rule_def = dict_merge({'destination': {'address': rule_config['destination']['address']}}, rule_def) - else: - changed = True - if len(dst) > 0: - rule_def = dict_merge({'destination': {'address': dst}}, rule_def) - - # destination port - if dst_port is None: - if 'destination' in rule_config: - if 'port' in rule_config['destination']: - tmp = rule_config['destination']['port'] - if isinstance(tmp, str): - tmp = [tmp] - rule_def = dict_merge({'destination': {'port': tmp}}, rule_def) - else: - changed = True - if len(dst_port) > 0: - rule_def = dict_merge({'destination': {'port': dst_port}}, rule_def) - - # table - if table is None: - if 'set' in rule_config and 'table' in rule_config['set']: - rule_def = dict_merge({'table': [rule_config['set']['table']]}, rule_def) - else: - changed = True - if len(table) > 0: - rule_def = dict_merge({'table' : table}, rule_def) - - # protocol - if proto is None: - if 'protocol' in rule_config: - tmp = rule_config['protocol'] - if isinstance(tmp, str): - tmp = [tmp] - rule_def = dict_merge({'protocol': tmp}, rule_def) - else: - changed = True - if len(proto) > 0: - rule_def = dict_merge({'protocol' : proto}, rule_def) - - if changed: - dict = dict_merge({dict_id : {rule : rule_def}}, dict) - pbr.update(dict) - - return pbr - -def verify(pbr): - # bail out early - looks like removal from running config - if not pbr: - return None - - for route in ['local_route', 'local_route6']: - if not route in pbr: - continue - - pbr_route = pbr[route] - if 'rule' in pbr_route: - for rule in pbr_route['rule']: - if ( - 'source' not in pbr_route['rule'][rule] and - 'destination' not in pbr_route['rule'][rule] and - 'fwmark' not in pbr_route['rule'][rule] and - 'inbound_interface' not in pbr_route['rule'][rule] and - 'protocol' not in pbr_route['rule'][rule] - ): - raise ConfigError('Source or destination address or fwmark or inbound-interface or protocol is required!') - - if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']: - raise ConfigError('Table set is required!') - - if 'inbound_interface' in pbr_route['rule'][rule]: - interface = pbr_route['rule'][rule]['inbound_interface'] - if interface not in interfaces(): - raise ConfigError(f'Interface "{interface}" does not exist') - - return None - -def generate(pbr): - if not pbr: - return None - - return None - -def apply(pbr): - if not pbr: - return None - - # Delete old rule if needed - for rule_rm in ['rule_remove', 'rule6_remove']: - if rule_rm in pbr: - v6 = " -6" if rule_rm == 'rule6_remove' else "" - - for rule, rule_config in pbr[rule_rm].items(): - source = rule_config.get('source', {}).get('address', ['']) - source_port = rule_config.get('source', {}).get('port', ['']) - destination = rule_config.get('destination', {}).get('address', ['']) - destination_port = rule_config.get('destination', {}).get('port', ['']) - fwmark = rule_config.get('fwmark', ['']) - inbound_interface = rule_config.get('inbound_interface', ['']) - protocol = rule_config.get('protocol', ['']) - table = rule_config.get('table', ['']) - - for src, dst, src_port, dst_port, fwmk, iif, proto, table in product( - source, destination, source_port, destination_port, - fwmark, inbound_interface, protocol, table): - f_src = '' if src == '' else f' from {src} ' - f_src_port = '' if src_port == '' else f' sport {src_port} ' - f_dst = '' if dst == '' else f' to {dst} ' - f_dst_port = '' if dst_port == '' else f' dport {dst_port} ' - f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} ' - f_iif = '' if iif == '' else f' iif {iif} ' - f_proto = '' if proto == '' else f' ipproto {proto} ' - f_table = '' if table == '' else f' lookup {table} ' - - call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_proto}{f_src_port}{f_dst_port}{f_fwmk}{f_iif}{f_table}') - - # Generate new config - for route in ['local_route', 'local_route6']: - if not route in pbr: - continue - - v6 = " -6" if route == 'local_route6' else "" - pbr_route = pbr[route] - - if 'rule' in pbr_route: - for rule, rule_config in pbr_route['rule'].items(): - table = rule_config['set'].get('table', '') - source = rule_config.get('source', {}).get('address', ['all']) - source_port = rule_config.get('source', {}).get('port', '') - destination = rule_config.get('destination', {}).get('address', ['all']) - destination_port = rule_config.get('destination', {}).get('port', '') - fwmark = rule_config.get('fwmark', '') - inbound_interface = rule_config.get('inbound_interface', '') - protocol = rule_config.get('protocol', '') - - for src in source: - f_src = f' from {src} ' if src else '' - for dst in destination: - f_dst = f' to {dst} ' if dst else '' - f_src_port = f' sport {source_port} ' if source_port else '' - f_dst_port = f' dport {destination_port} ' if destination_port else '' - f_fwmk = f' fwmark {fwmark} ' if fwmark else '' - f_iif = f' iif {inbound_interface} ' if inbound_interface else '' - f_proto = f' ipproto {protocol} ' if protocol else '' - - call(f'ip{v6} rule add prio {rule}{f_src}{f_dst}{f_proto}{f_src_port}{f_dst_port}{f_fwmk}{f_iif} lookup {table}') - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py deleted file mode 100755 index adad012de..000000000 --- a/src/conf_mode/policy-route.py +++ /dev/null @@ -1,195 +0,0 @@ -#!/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 . - -import os - -from json import loads -from sys import exit - -from vyos.base import Warning -from vyos.config import Config -from vyos.template import render -from vyos.utils.dict import dict_search_args -from vyos.utils.process import cmd -from vyos.utils.process import run -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -mark_offset = 0x7FFFFFFF -nftables_conf = '/run/nftables_policy.conf' - -valid_groups = [ - 'address_group', - 'domain_group', - 'network_group', - 'port_group', - 'interface_group' -] - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['policy'] - - policy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - - policy['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - - return policy - -def verify_rule(policy, name, rule_conf, ipv6, rule_id): - icmp = 'icmp' if not ipv6 else 'icmpv6' - if icmp in rule_conf: - icmp_defined = False - if 'type_name' in rule_conf[icmp]: - icmp_defined = True - if 'code' in rule_conf[icmp] or 'type' in rule_conf[icmp]: - raise ConfigError(f'{name} rule {rule_id}: Cannot use ICMP type/code with ICMP type-name') - if 'code' in rule_conf[icmp]: - icmp_defined = True - if 'type' not in rule_conf[icmp]: - raise ConfigError(f'{name} rule {rule_id}: ICMP code can only be defined if ICMP type is defined') - if 'type' in rule_conf[icmp]: - icmp_defined = True - - if icmp_defined and 'protocol' not in rule_conf or rule_conf['protocol'] != icmp: - raise ConfigError(f'{name} rule {rule_id}: ICMP type/code or type-name can only be defined if protocol is ICMP') - - if 'set' in rule_conf: - if 'tcp_mss' in rule_conf['set']: - tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') - if not tcp_flags or 'syn' not in tcp_flags: - raise ConfigError(f'{name} rule {rule_id}: TCP SYN flag must be set to modify TCP-MSS') - - tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') - if tcp_flags: - if dict_search_args(rule_conf, 'protocol') != 'tcp': - raise ConfigError('Protocol must be tcp when specifying tcp flags') - - not_flags = dict_search_args(rule_conf, 'tcp', 'flags', 'not') - if not_flags: - duplicates = [flag for flag in tcp_flags if flag in not_flags] - if duplicates: - raise ConfigError(f'Cannot match a tcp flag as set and not set') - - for side in ['destination', 'source']: - if side in rule_conf: - side_conf = rule_conf[side] - - if 'group' in side_conf: - if len({'address_group', 'domain_group', 'network_group'} & set(side_conf['group'])) > 1: - raise ConfigError('Only one address-group, domain-group or network-group can be specified') - - for group in valid_groups: - if group in side_conf['group']: - group_name = side_conf['group'][group] - - if group_name.startswith('!'): - group_name = group_name[1:] - - fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group - error_group = fw_group.replace("_", "-") - group_obj = dict_search_args(policy['firewall_group'], fw_group, group_name) - - if group_obj is None: - raise ConfigError(f'Invalid {error_group} "{group_name}" on policy route rule') - - if not group_obj: - Warning(f'{error_group} "{group_name}" has no members') - - if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'): - if 'protocol' not in rule_conf: - raise ConfigError('Protocol must be defined if specifying a port or port-group') - - 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') - -def verify(policy): - for route in ['route', 'route6']: - ipv6 = route == 'route6' - if route in policy: - for name, pol_conf in policy[route].items(): - if 'rule' in pol_conf: - for rule_id, rule_conf in pol_conf['rule'].items(): - verify_rule(policy, name, rule_conf, ipv6, rule_id) - - return None - -def generate(policy): - if not os.path.exists(nftables_conf): - policy['first_install'] = True - - render(nftables_conf, 'firewall/nftables-policy.j2', policy) - return None - -def apply_table_marks(policy): - for route in ['route', 'route6']: - if route in policy: - cmd_str = 'ip' if route == 'route' else 'ip -6' - tables = [] - for name, pol_conf in policy[route].items(): - if 'rule' in pol_conf: - for rule_id, rule_conf in pol_conf['rule'].items(): - set_table = dict_search_args(rule_conf, 'set', 'table') - if set_table: - if set_table == 'main': - set_table = '254' - if set_table in tables: - continue - tables.append(set_table) - table_mark = mark_offset - int(set_table) - cmd(f'{cmd_str} rule add pref {set_table} fwmark {table_mark} table {set_table}') - -def cleanup_table_marks(): - for cmd_str in ['ip', 'ip -6']: - json_rules = cmd(f'{cmd_str} -j -N rule list') - rules = loads(json_rules) - for rule in rules: - if 'fwmark' not in rule or 'table' not in rule: - continue - fwmark = rule['fwmark'] - table = int(rule['table']) - if fwmark[:2] == '0x': - fwmark = int(fwmark, 16) - if (int(fwmark) == (mark_offset - table)): - cmd(f'{cmd_str} rule del fwmark {fwmark} table {table}') - -def apply(policy): - install_result = run(f'nft -f {nftables_conf}') - if install_result == 1: - raise ConfigError('Failed to apply policy based routing') - - if 'first_install' not in policy: - cleanup_table_marks() - - apply_table_marks(policy) - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/policy_local-route.py b/src/conf_mode/policy_local-route.py new file mode 100755 index 000000000..91e4fce2c --- /dev/null +++ b/src/conf_mode/policy_local-route.py @@ -0,0 +1,315 @@ +#!/usr/bin/env python3 +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from itertools import product +from sys import exit + +from netifaces import interfaces +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configdict import node_changed +from vyos.configdict import leaf_node_changed +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + + +def get_config(config=None): + + if config: + conf = config + else: + conf = Config() + base = ['policy'] + + pbr = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + for route in ['local_route', 'local_route6']: + dict_id = 'rule_remove' if route == 'local_route' else 'rule6_remove' + route_key = 'local-route' if route == 'local_route' else 'local-route6' + base_rule = base + [route_key, 'rule'] + + # delete policy local-route + dict = {} + tmp = node_changed(conf, base_rule, key_mangling=('-', '_')) + if tmp: + for rule in (tmp or []): + src = leaf_node_changed(conf, base_rule + [rule, 'source', 'address']) + src_port = leaf_node_changed(conf, base_rule + [rule, 'source', 'port']) + fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) + iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface']) + dst = leaf_node_changed(conf, base_rule + [rule, 'destination', 'address']) + dst_port = leaf_node_changed(conf, base_rule + [rule, 'destination', 'port']) + table = leaf_node_changed(conf, base_rule + [rule, 'set', 'table']) + proto = leaf_node_changed(conf, base_rule + [rule, 'protocol']) + rule_def = {} + if src: + rule_def = dict_merge({'source': {'address': src}}, rule_def) + if src_port: + rule_def = dict_merge({'source': {'port': src_port}}, rule_def) + if fwmk: + rule_def = dict_merge({'fwmark' : fwmk}, rule_def) + if iif: + rule_def = dict_merge({'inbound_interface' : iif}, rule_def) + if dst: + rule_def = dict_merge({'destination': {'address': dst}}, rule_def) + if dst_port: + rule_def = dict_merge({'destination': {'port': dst_port}}, rule_def) + if table: + rule_def = dict_merge({'table' : table}, rule_def) + if proto: + rule_def = dict_merge({'protocol' : proto}, rule_def) + dict = dict_merge({dict_id : {rule : rule_def}}, dict) + pbr.update(dict) + + if not route in pbr: + continue + + # delete policy local-route rule x source x.x.x.x + # delete policy local-route rule x fwmark x + # delete policy local-route rule x destination x.x.x.x + if 'rule' in pbr[route]: + for rule, rule_config in pbr[route]['rule'].items(): + src = leaf_node_changed(conf, base_rule + [rule, 'source', 'address']) + src_port = leaf_node_changed(conf, base_rule + [rule, 'source', 'port']) + fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) + iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface']) + dst = leaf_node_changed(conf, base_rule + [rule, 'destination', 'address']) + dst_port = leaf_node_changed(conf, base_rule + [rule, 'destination', 'port']) + table = leaf_node_changed(conf, base_rule + [rule, 'set', 'table']) + proto = leaf_node_changed(conf, base_rule + [rule, 'protocol']) + # keep track of changes in configuration + # otherwise we might remove an existing node although nothing else has changed + changed = False + + rule_def = {} + # src is None if there are no changes to src + if src is None: + # if src hasn't changed, include it in the removal selector + # if a new selector is added, we have to remove all previous rules without this selector + # to make sure we remove all previous rules with this source(s), it will be included + if 'source' in rule_config: + if 'address' in rule_config['source']: + rule_def = dict_merge({'source': {'address': rule_config['source']['address']}}, rule_def) + else: + # if src is not None, it's previous content will be returned + # this can be an empty array if it's just being set, or the previous value + # either way, something has to be changed and we only want to remove previous values + changed = True + # set the old value for removal if it's not empty + if len(src) > 0: + rule_def = dict_merge({'source': {'address': src}}, rule_def) + + # source port + if src_port is None: + if 'source' in rule_config: + if 'port' in rule_config['source']: + tmp = rule_config['source']['port'] + if isinstance(tmp, str): + tmp = [tmp] + rule_def = dict_merge({'source': {'port': tmp}}, rule_def) + else: + changed = True + if len(src_port) > 0: + rule_def = dict_merge({'source': {'port': src_port}}, rule_def) + + # fwmark + if fwmk is None: + if 'fwmark' in rule_config: + tmp = rule_config['fwmark'] + if isinstance(tmp, str): + tmp = [tmp] + rule_def = dict_merge({'fwmark': tmp}, rule_def) + else: + changed = True + if len(fwmk) > 0: + rule_def = dict_merge({'fwmark' : fwmk}, rule_def) + + # inbound-interface + if iif is None: + if 'inbound_interface' in rule_config: + rule_def = dict_merge({'inbound_interface': rule_config['inbound_interface']}, rule_def) + else: + changed = True + if len(iif) > 0: + rule_def = dict_merge({'inbound_interface' : iif}, rule_def) + + # destination address + if dst is None: + if 'destination' in rule_config: + if 'address' in rule_config['destination']: + rule_def = dict_merge({'destination': {'address': rule_config['destination']['address']}}, rule_def) + else: + changed = True + if len(dst) > 0: + rule_def = dict_merge({'destination': {'address': dst}}, rule_def) + + # destination port + if dst_port is None: + if 'destination' in rule_config: + if 'port' in rule_config['destination']: + tmp = rule_config['destination']['port'] + if isinstance(tmp, str): + tmp = [tmp] + rule_def = dict_merge({'destination': {'port': tmp}}, rule_def) + else: + changed = True + if len(dst_port) > 0: + rule_def = dict_merge({'destination': {'port': dst_port}}, rule_def) + + # table + if table is None: + if 'set' in rule_config and 'table' in rule_config['set']: + rule_def = dict_merge({'table': [rule_config['set']['table']]}, rule_def) + else: + changed = True + if len(table) > 0: + rule_def = dict_merge({'table' : table}, rule_def) + + # protocol + if proto is None: + if 'protocol' in rule_config: + tmp = rule_config['protocol'] + if isinstance(tmp, str): + tmp = [tmp] + rule_def = dict_merge({'protocol': tmp}, rule_def) + else: + changed = True + if len(proto) > 0: + rule_def = dict_merge({'protocol' : proto}, rule_def) + + if changed: + dict = dict_merge({dict_id : {rule : rule_def}}, dict) + pbr.update(dict) + + return pbr + +def verify(pbr): + # bail out early - looks like removal from running config + if not pbr: + return None + + for route in ['local_route', 'local_route6']: + if not route in pbr: + continue + + pbr_route = pbr[route] + if 'rule' in pbr_route: + for rule in pbr_route['rule']: + if ( + 'source' not in pbr_route['rule'][rule] and + 'destination' not in pbr_route['rule'][rule] and + 'fwmark' not in pbr_route['rule'][rule] and + 'inbound_interface' not in pbr_route['rule'][rule] and + 'protocol' not in pbr_route['rule'][rule] + ): + raise ConfigError('Source or destination address or fwmark or inbound-interface or protocol is required!') + + if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']: + raise ConfigError('Table set is required!') + + if 'inbound_interface' in pbr_route['rule'][rule]: + interface = pbr_route['rule'][rule]['inbound_interface'] + if interface not in interfaces(): + raise ConfigError(f'Interface "{interface}" does not exist') + + return None + +def generate(pbr): + if not pbr: + return None + + return None + +def apply(pbr): + if not pbr: + return None + + # Delete old rule if needed + for rule_rm in ['rule_remove', 'rule6_remove']: + if rule_rm in pbr: + v6 = " -6" if rule_rm == 'rule6_remove' else "" + + for rule, rule_config in pbr[rule_rm].items(): + source = rule_config.get('source', {}).get('address', ['']) + source_port = rule_config.get('source', {}).get('port', ['']) + destination = rule_config.get('destination', {}).get('address', ['']) + destination_port = rule_config.get('destination', {}).get('port', ['']) + fwmark = rule_config.get('fwmark', ['']) + inbound_interface = rule_config.get('inbound_interface', ['']) + protocol = rule_config.get('protocol', ['']) + table = rule_config.get('table', ['']) + + for src, dst, src_port, dst_port, fwmk, iif, proto, table in product( + source, destination, source_port, destination_port, + fwmark, inbound_interface, protocol, table): + f_src = '' if src == '' else f' from {src} ' + f_src_port = '' if src_port == '' else f' sport {src_port} ' + f_dst = '' if dst == '' else f' to {dst} ' + f_dst_port = '' if dst_port == '' else f' dport {dst_port} ' + f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} ' + f_iif = '' if iif == '' else f' iif {iif} ' + f_proto = '' if proto == '' else f' ipproto {proto} ' + f_table = '' if table == '' else f' lookup {table} ' + + call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_proto}{f_src_port}{f_dst_port}{f_fwmk}{f_iif}{f_table}') + + # Generate new config + for route in ['local_route', 'local_route6']: + if not route in pbr: + continue + + v6 = " -6" if route == 'local_route6' else "" + pbr_route = pbr[route] + + if 'rule' in pbr_route: + for rule, rule_config in pbr_route['rule'].items(): + table = rule_config['set'].get('table', '') + source = rule_config.get('source', {}).get('address', ['all']) + source_port = rule_config.get('source', {}).get('port', '') + destination = rule_config.get('destination', {}).get('address', ['all']) + destination_port = rule_config.get('destination', {}).get('port', '') + fwmark = rule_config.get('fwmark', '') + inbound_interface = rule_config.get('inbound_interface', '') + protocol = rule_config.get('protocol', '') + + for src in source: + f_src = f' from {src} ' if src else '' + for dst in destination: + f_dst = f' to {dst} ' if dst else '' + f_src_port = f' sport {source_port} ' if source_port else '' + f_dst_port = f' dport {destination_port} ' if destination_port else '' + f_fwmk = f' fwmark {fwmark} ' if fwmark else '' + f_iif = f' iif {inbound_interface} ' if inbound_interface else '' + f_proto = f' ipproto {protocol} ' if protocol else '' + + call(f'ip{v6} rule add prio {rule}{f_src}{f_dst}{f_proto}{f_src_port}{f_dst_port}{f_fwmk}{f_iif} lookup {table}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/policy_route.py b/src/conf_mode/policy_route.py new file mode 100755 index 000000000..adad012de --- /dev/null +++ b/src/conf_mode/policy_route.py @@ -0,0 +1,195 @@ +#!/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 . + +import os + +from json import loads +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.template import render +from vyos.utils.dict import dict_search_args +from vyos.utils.process import cmd +from vyos.utils.process import run +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +mark_offset = 0x7FFFFFFF +nftables_conf = '/run/nftables_policy.conf' + +valid_groups = [ + 'address_group', + 'domain_group', + 'network_group', + 'port_group', + 'interface_group' +] + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['policy'] + + policy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + policy['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + return policy + +def verify_rule(policy, name, rule_conf, ipv6, rule_id): + icmp = 'icmp' if not ipv6 else 'icmpv6' + if icmp in rule_conf: + icmp_defined = False + if 'type_name' in rule_conf[icmp]: + icmp_defined = True + if 'code' in rule_conf[icmp] or 'type' in rule_conf[icmp]: + raise ConfigError(f'{name} rule {rule_id}: Cannot use ICMP type/code with ICMP type-name') + if 'code' in rule_conf[icmp]: + icmp_defined = True + if 'type' not in rule_conf[icmp]: + raise ConfigError(f'{name} rule {rule_id}: ICMP code can only be defined if ICMP type is defined') + if 'type' in rule_conf[icmp]: + icmp_defined = True + + if icmp_defined and 'protocol' not in rule_conf or rule_conf['protocol'] != icmp: + raise ConfigError(f'{name} rule {rule_id}: ICMP type/code or type-name can only be defined if protocol is ICMP') + + if 'set' in rule_conf: + if 'tcp_mss' in rule_conf['set']: + tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') + if not tcp_flags or 'syn' not in tcp_flags: + raise ConfigError(f'{name} rule {rule_id}: TCP SYN flag must be set to modify TCP-MSS') + + tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') + if tcp_flags: + if dict_search_args(rule_conf, 'protocol') != 'tcp': + raise ConfigError('Protocol must be tcp when specifying tcp flags') + + not_flags = dict_search_args(rule_conf, 'tcp', 'flags', 'not') + if not_flags: + duplicates = [flag for flag in tcp_flags if flag in not_flags] + if duplicates: + raise ConfigError(f'Cannot match a tcp flag as set and not set') + + for side in ['destination', 'source']: + if side in rule_conf: + side_conf = rule_conf[side] + + if 'group' in side_conf: + if len({'address_group', 'domain_group', 'network_group'} & set(side_conf['group'])) > 1: + raise ConfigError('Only one address-group, domain-group or network-group can be specified') + + for group in valid_groups: + if group in side_conf['group']: + group_name = side_conf['group'][group] + + if group_name.startswith('!'): + group_name = group_name[1:] + + fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group + error_group = fw_group.replace("_", "-") + group_obj = dict_search_args(policy['firewall_group'], fw_group, group_name) + + if group_obj is None: + raise ConfigError(f'Invalid {error_group} "{group_name}" on policy route rule') + + if not group_obj: + Warning(f'{error_group} "{group_name}" has no members') + + if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'): + if 'protocol' not in rule_conf: + raise ConfigError('Protocol must be defined if specifying a port or port-group') + + 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') + +def verify(policy): + for route in ['route', 'route6']: + ipv6 = route == 'route6' + if route in policy: + for name, pol_conf in policy[route].items(): + if 'rule' in pol_conf: + for rule_id, rule_conf in pol_conf['rule'].items(): + verify_rule(policy, name, rule_conf, ipv6, rule_id) + + return None + +def generate(policy): + if not os.path.exists(nftables_conf): + policy['first_install'] = True + + render(nftables_conf, 'firewall/nftables-policy.j2', policy) + return None + +def apply_table_marks(policy): + for route in ['route', 'route6']: + if route in policy: + cmd_str = 'ip' if route == 'route' else 'ip -6' + tables = [] + for name, pol_conf in policy[route].items(): + if 'rule' in pol_conf: + for rule_id, rule_conf in pol_conf['rule'].items(): + set_table = dict_search_args(rule_conf, 'set', 'table') + if set_table: + if set_table == 'main': + set_table = '254' + if set_table in tables: + continue + tables.append(set_table) + table_mark = mark_offset - int(set_table) + cmd(f'{cmd_str} rule add pref {set_table} fwmark {table_mark} table {set_table}') + +def cleanup_table_marks(): + for cmd_str in ['ip', 'ip -6']: + json_rules = cmd(f'{cmd_str} -j -N rule list') + rules = loads(json_rules) + for rule in rules: + if 'fwmark' not in rule or 'table' not in rule: + continue + fwmark = rule['fwmark'] + table = int(rule['table']) + if fwmark[:2] == '0x': + fwmark = int(fwmark, 16) + if (int(fwmark) == (mark_offset - table)): + cmd(f'{cmd_str} rule del fwmark {fwmark} table {table}') + +def apply(policy): + install_result = run(f'nft -f {nftables_conf}') + if install_result == 1: + raise ConfigError('Failed to apply policy based routing') + + if 'first_install' not in policy: + cleanup_table_marks() + + apply_table_marks(policy) + + 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_igmp-proxy.py b/src/conf_mode/protocols_igmp-proxy.py new file mode 100755 index 000000000..40db417dd --- /dev/null +++ b/src/conf_mode/protocols_igmp-proxy.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from sys import exit +from netifaces import interfaces + +from vyos.base import Warning +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/etc/igmpproxy.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['protocols', 'igmp-proxy'] + igmp_proxy = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_defaults=True) + + if conf.exists(['protocols', 'igmp']): + igmp_proxy.update({'igmp_configured': ''}) + + if conf.exists(['protocols', 'pim']): + igmp_proxy.update({'pim_configured': ''}) + + return igmp_proxy + +def verify(igmp_proxy): + # bail out early - looks like removal from running config + if not igmp_proxy or 'disable' in igmp_proxy: + return None + + if 'igmp_configured' in igmp_proxy or 'pim_configured' in igmp_proxy: + raise ConfigError('Can not configure both IGMP proxy and PIM '\ + 'at the same time') + + # at least two interfaces are required, one upstream and one downstream + if 'interface' not in igmp_proxy or len(igmp_proxy['interface']) < 2: + raise ConfigError('Must define exactly one upstream and at least one ' \ + 'downstream interface!') + + upstream = 0 + for interface, config in igmp_proxy['interface'].items(): + if interface not in interfaces(): + raise ConfigError(f'Interface "{interface}" does not exist') + if dict_search('role', config) == 'upstream': + upstream += 1 + + if upstream == 0: + raise ConfigError('At least 1 upstream interface is required!') + elif upstream > 1: + raise ConfigError('Only 1 upstream interface allowed!') + + return None + +def generate(igmp_proxy): + # bail out early - looks like removal from running config + if not igmp_proxy: + return None + + # bail out early - service is disabled, but inform user + if 'disable' in igmp_proxy: + Warning('IGMP Proxy will be deactivated because it is disabled') + return None + + render(config_file, 'igmp-proxy/igmpproxy.conf.j2', igmp_proxy) + + return None + +def apply(igmp_proxy): + if not igmp_proxy or 'disable' in igmp_proxy: + # IGMP Proxy support is removed in the commit + call('systemctl stop igmpproxy.service') + if os.path.exists(config_file): + os.unlink(config_file) + else: + call('systemctl restart igmpproxy.service') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_segment-routing.py b/src/conf_mode/protocols_segment-routing.py new file mode 100755 index 000000000..d865c2ac0 --- /dev/null +++ b/src/conf_mode/protocols_segment-routing.py @@ -0,0 +1,118 @@ +#!/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 . + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configdict import node_changed +from vyos.template import render_to_string +from vyos.utils.dict import dict_search +from vyos.utils.system import sysctl_write +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', 'segment-routing'] + sr = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=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: + sr['interface_removed'] = list(interfaces_removed) + + import pprint + pprint.pprint(sr) + return sr + +def verify(sr): + if 'srv6' in sr: + srv6_enable = False + if 'interface' in sr: + for interface, interface_config in sr['interface'].items(): + if 'srv6' in interface_config: + srv6_enable = True + break + if not srv6_enable: + raise ConfigError('SRv6 should be enabled on at least one interface!') + return None + +def generate(sr): + if not sr: + return None + + sr['new_frr_config'] = render_to_string('frr/zebra.segment_routing.frr.j2', sr) + return None + +def apply(sr): + zebra_daemon = 'zebra' + + if 'interface_removed' in sr: + for interface in sr['interface_removed']: + # Disable processing of IPv6-SR packets + sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0') + + if 'interface' in sr: + for interface, interface_config in sr['interface'].items(): + # Accept or drop SR-enabled IPv6 packets on this interface + if 'srv6' in interface_config: + sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '1') + # Define HMAC policy for ingress SR-enabled packets on this interface + # It's a redundant check as HMAC has a default value - but better safe + # then sorry + tmp = dict_search('srv6.hmac', interface_config) + if tmp == 'accept': + sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '0') + elif tmp == 'drop': + sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '1') + elif tmp == 'ignore': + sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '-1') + else: + sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0') + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + frr_cfg.load_configuration(zebra_daemon) + frr_cfg.modify_section(r'^segment-routing') + if 'new_frr_config' in sr: + frr_cfg.add_before(frr.default_add_before, sr['new_frr_config']) + frr_cfg.commit_configuration(zebra_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_segment_routing.py b/src/conf_mode/protocols_segment_routing.py deleted file mode 100755 index d865c2ac0..000000000 --- a/src/conf_mode/protocols_segment_routing.py +++ /dev/null @@ -1,118 +0,0 @@ -#!/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 . - -import os - -from sys import exit - -from vyos.config import Config -from vyos.configdict import node_changed -from vyos.template import render_to_string -from vyos.utils.dict import dict_search -from vyos.utils.system import sysctl_write -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', 'segment-routing'] - sr = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True, - with_recursive_defaults=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: - sr['interface_removed'] = list(interfaces_removed) - - import pprint - pprint.pprint(sr) - return sr - -def verify(sr): - if 'srv6' in sr: - srv6_enable = False - if 'interface' in sr: - for interface, interface_config in sr['interface'].items(): - if 'srv6' in interface_config: - srv6_enable = True - break - if not srv6_enable: - raise ConfigError('SRv6 should be enabled on at least one interface!') - return None - -def generate(sr): - if not sr: - return None - - sr['new_frr_config'] = render_to_string('frr/zebra.segment_routing.frr.j2', sr) - return None - -def apply(sr): - zebra_daemon = 'zebra' - - if 'interface_removed' in sr: - for interface in sr['interface_removed']: - # Disable processing of IPv6-SR packets - sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0') - - if 'interface' in sr: - for interface, interface_config in sr['interface'].items(): - # Accept or drop SR-enabled IPv6 packets on this interface - if 'srv6' in interface_config: - sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '1') - # Define HMAC policy for ingress SR-enabled packets on this interface - # It's a redundant check as HMAC has a default value - but better safe - # then sorry - tmp = dict_search('srv6.hmac', interface_config) - if tmp == 'accept': - sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '0') - elif tmp == 'drop': - sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '1') - elif tmp == 'ignore': - sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '-1') - else: - sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0') - - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(zebra_daemon) - frr_cfg.modify_section(r'^segment-routing') - if 'new_frr_config' in sr: - frr_cfg.add_before(frr.default_add_before, sr['new_frr_config']) - frr_cfg.commit_configuration(zebra_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_static_arp.py b/src/conf_mode/protocols_static_arp.py new file mode 100755 index 000000000..b141f1141 --- /dev/null +++ b/src/conf_mode/protocols_static_arp.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from sys import exit + +from vyos.config import Config +from vyos.configdict import node_changed +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['protocols', 'static', 'arp'] + arp = conf.get_config_dict(base, get_first_key=True) + + if 'interface' in arp: + for interface in arp['interface']: + tmp = node_changed(conf, base + ['interface', interface, 'address'], recursive=True) + if tmp: arp['interface'][interface].update({'address_old' : tmp}) + + return arp + +def verify(arp): + pass + +def generate(arp): + pass + +def apply(arp): + if not arp: + return None + + if 'interface' in arp: + for interface, interface_config in arp['interface'].items(): + # Delete old static ARP assignments first + if 'address_old' in interface_config: + for address in interface_config['address_old']: + call(f'ip neigh del {address} dev {interface}') + + # Add new static ARP entries to interface + if 'address' not in interface_config: + continue + for address, address_config in interface_config['address'].items(): + mac = address_config['mac'] + call(f'ip neigh replace {address} lladdr {mac} dev {interface}') + +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/salt-minion.py b/src/conf_mode/salt-minion.py deleted file mode 100755 index a8fce8e01..000000000 --- a/src/conf_mode/salt-minion.py +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2022 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from socket import gethostname -from sys import exit -from urllib3 import PoolManager - -from vyos.base import Warning -from vyos.config import Config -from vyos.configverify import verify_interface_exists -from vyos.template import render -from vyos.utils.process import call -from vyos.utils.permission import chown -from vyos import ConfigError - -from vyos import airbag -airbag.enable() - -config_file = r'/etc/salt/minion' -master_keyfile = r'/opt/vyatta/etc/config/salt/pki/minion/master_sign.pub' - -user='minion' -group='vyattacfg' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['service', 'salt-minion'] - - if not conf.exists(base): - return None - - salt = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # ID default is dynamic thus we can not use defaults() - if 'id' not in salt: - salt['id'] = gethostname() - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - salt = conf.merge_defaults(salt, recursive=True) - - if not conf.exists(base): - return None - else: - conf.set_level(base) - - return salt - -def verify(salt): - if not salt: - return None - - if 'hash' in salt and salt['hash'] == 'sha1': - Warning('Do not use sha1 hashing algorithm, upgrade to sha256 or later!') - - if 'source_interface' in salt: - verify_interface_exists(salt['source_interface']) - - return None - -def generate(salt): - if not salt: - return None - - render(config_file, 'salt-minion/minion.j2', salt, user=user, group=group) - - if not os.path.exists(master_keyfile): - if 'master_key' in salt: - req = PoolManager().request('GET', salt['master_key'], preload_content=False) - with open(master_keyfile, 'wb') as f: - while True: - data = req.read(1024) - if not data: - break - f.write(data) - - req.release_conn() - chown(master_keyfile, user, group) - - return None - -def apply(salt): - service_name = 'salt-minion.service' - if not salt: - # Salt removed from running config - call(f'systemctl stop {service_name}') - if os.path.exists(config_file): - os.unlink(config_file) - else: - call(f'systemctl restart {service_name}') - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/service_broadcast-relay.py b/src/conf_mode/service_broadcast-relay.py new file mode 100755 index 000000000..31c552f5a --- /dev/null +++ b/src/conf_mode/service_broadcast-relay.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2017-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 . + +import os + +from glob import glob +from netifaces import AF_INET +from sys import exit + +from vyos.config import Config +from vyos.configverify import verify_interface_exists +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.network import is_afi_configured +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file_base = r'/etc/default/udp-broadcast-relay' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'broadcast-relay'] + + relay = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + return relay + +def verify(relay): + if not relay or 'disabled' in relay: + return None + + for instance, config in relay.get('id', {}).items(): + # we don't have to check this instance when it's disabled + if 'disabled' in config: + continue + + # we certainly require a UDP port to listen to + if 'port' not in config: + raise ConfigError(f'Port number is mandatory for UDP broadcast relay "{instance}"') + + # Relaying data without two interface is kinda senseless ... + if len(config.get('interface', [])) < 2: + raise ConfigError('At least two interfaces are required for UDP broadcast relay "{instance}"') + + for interface in config.get('interface', []): + verify_interface_exists(interface) + if not is_afi_configured(interface, AF_INET): + raise ConfigError(f'Interface "{interface}" has no IPv4 address configured!') + + return None + +def generate(relay): + if not relay or 'disabled' in relay: + return None + + for config in glob(config_file_base + '*'): + os.remove(config) + + for instance, config in relay.get('id').items(): + # we don't have to check this instance when it's disabled + if 'disabled' in config: + continue + + config['instance'] = instance + render(config_file_base + instance, 'bcast-relay/udp-broadcast-relay.j2', + config) + + return None + +def apply(relay): + # first stop all running services + call('systemctl stop udp-broadcast-relay@*.service') + + if not relay or 'disable' in relay: + return None + + # start only required service instances + for instance, config in relay.get('id').items(): + # we don't have to check this instance when it's disabled + if 'disabled' in config: + continue + + call(f'systemctl start udp-broadcast-relay@{instance}.service') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_config-sync.py b/src/conf_mode/service_config-sync.py new file mode 100755 index 000000000..4b8a7f6ee --- /dev/null +++ b/src/conf_mode/service_config-sync.py @@ -0,0 +1,105 @@ +#!/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 . + +import os +import json +from pathlib import Path + +from vyos.config import Config +from vyos import ConfigError +from vyos import airbag + +airbag.enable() + + +service_conf = Path(f'/run/config_sync_conf.conf') +post_commit_dir = '/run/scripts/commit/post-hooks.d' +post_commit_file_src = '/usr/libexec/vyos/vyos_config_sync.py' +post_commit_file = f'{post_commit_dir}/vyos_config_sync' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'config-sync'] + if not conf.exists(base): + return None + config = conf.get_config_dict(base, get_first_key=True, + with_recursive_defaults=True) + + return config + + +def verify(config): + # bail out early - looks like removal from running config + if not config: + return None + + if 'mode' not in config: + raise ConfigError(f'config-sync mode is mandatory!') + + for option in ['secondary', 'section']: + if option not in config: + raise ConfigError(f"config-sync '{option}' is not configured!") + + if 'address' not in config['secondary']: + raise ConfigError(f'secondary address is mandatory!') + if 'key' not in config['secondary']: + raise ConfigError(f'secondary key is mandatory!') + + +def generate(config): + if not config: + + if os.path.exists(post_commit_file): + os.unlink(post_commit_file) + + if service_conf.exists(): + service_conf.unlink() + + return None + + # Write configuration file + conf_json = json.dumps(config, indent=4) + service_conf.write_text(conf_json) + + # Create post commit dir + if not os.path.isdir(post_commit_dir): + os.makedirs(post_commit_dir) + + # Symlink from helpers to post-commit + if not os.path.exists(post_commit_file): + os.symlink(post_commit_file_src, post_commit_file) + + return None + + +def apply(config): + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_config_sync.py b/src/conf_mode/service_config_sync.py deleted file mode 100755 index 4b8a7f6ee..000000000 --- a/src/conf_mode/service_config_sync.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/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 . - -import os -import json -from pathlib import Path - -from vyos.config import Config -from vyos import ConfigError -from vyos import airbag - -airbag.enable() - - -service_conf = Path(f'/run/config_sync_conf.conf') -post_commit_dir = '/run/scripts/commit/post-hooks.d' -post_commit_file_src = '/usr/libexec/vyos/vyos_config_sync.py' -post_commit_file = f'{post_commit_dir}/vyos_config_sync' - - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - base = ['service', 'config-sync'] - if not conf.exists(base): - return None - config = conf.get_config_dict(base, get_first_key=True, - with_recursive_defaults=True) - - return config - - -def verify(config): - # bail out early - looks like removal from running config - if not config: - return None - - if 'mode' not in config: - raise ConfigError(f'config-sync mode is mandatory!') - - for option in ['secondary', 'section']: - if option not in config: - raise ConfigError(f"config-sync '{option}' is not configured!") - - if 'address' not in config['secondary']: - raise ConfigError(f'secondary address is mandatory!') - if 'key' not in config['secondary']: - raise ConfigError(f'secondary key is mandatory!') - - -def generate(config): - if not config: - - if os.path.exists(post_commit_file): - os.unlink(post_commit_file) - - if service_conf.exists(): - service_conf.unlink() - - return None - - # Write configuration file - conf_json = json.dumps(config, indent=4) - service_conf.write_text(conf_json) - - # Create post commit dir - if not os.path.isdir(post_commit_dir): - os.makedirs(post_commit_dir) - - # Symlink from helpers to post-commit - if not os.path.exists(post_commit_file): - os.symlink(post_commit_file_src, post_commit_file) - - return None - - -def apply(config): - return None - - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/service_conntrack-sync.py b/src/conf_mode/service_conntrack-sync.py new file mode 100755 index 000000000..4fb2ce27f --- /dev/null +++ b/src/conf_mode/service_conntrack-sync.py @@ -0,0 +1,141 @@ +#!/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 . + +import os + +from sys import exit +from vyos.config import Config +from vyos.configverify import verify_interface_exists +from vyos.utils.dict import dict_search +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.utils.process import call +from vyos.utils.process import run +from vyos.template import render +from vyos.template import get_ipv4 +from vyos.utils.network import is_addr_assigned +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = '/run/conntrackd/conntrackd.conf' + +def resync_vrrp(): + tmp = run('/usr/libexec/vyos/conf_mode/high-availability.py') + if tmp > 0: + print('ERROR: error restarting VRRP daemon!') + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'conntrack-sync'] + if not conf.exists(base): + return None + + conntrack = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, with_defaults=True) + + conntrack['hash_size'] = read_file('/sys/module/nf_conntrack/parameters/hashsize') + conntrack['table_size'] = read_file('/proc/sys/net/netfilter/nf_conntrack_max') + + conntrack['vrrp'] = conf.get_config_dict(['high-availability', 'vrrp', 'sync-group'], + get_first_key=True) + + return conntrack + +def verify(conntrack): + if not conntrack: + return None + + if 'interface' not in conntrack: + raise ConfigError('Interface not defined!') + + has_peer = False + for interface, interface_config in conntrack['interface'].items(): + verify_interface_exists(interface) + # Interface must not only exist, it must also carry an IP address + if len(get_ipv4(interface)) < 1: + raise ConfigError(f'Interface {interface} requires an IP address!') + if 'peer' in interface_config: + has_peer = True + + # If one interface runs in unicast mode instead of multicast, so must all the + # others, else conntrackd will error out with: "cannot use UDP with other + # dedicated link protocols" + if has_peer: + for interface, interface_config in conntrack['interface'].items(): + if 'peer' not in interface_config: + raise ConfigError('Can not mix unicast and multicast mode!') + + if 'expect_sync' in conntrack: + if len(conntrack['expect_sync']) > 1 and 'all' in conntrack['expect_sync']: + raise ConfigError('Can not configure expect-sync "all" with other protocols!') + + if 'listen_address' in conntrack: + for address in conntrack['listen_address']: + if not is_addr_assigned(address): + raise ConfigError(f'Specified listen-address {address} not assigned to any interface!') + + vrrp_group = dict_search('failover_mechanism.vrrp.sync_group', conntrack) + if vrrp_group == None: + raise ConfigError(f'No VRRP sync-group defined!') + if vrrp_group not in conntrack['vrrp']: + raise ConfigError(f'VRRP sync-group {vrrp_group} not configured!') + + return None + +def generate(conntrack): + if not conntrack: + if os.path.isfile(config_file): + os.unlink(config_file) + return None + + render(config_file, 'conntrackd/conntrackd.conf.j2', conntrack) + + return None + +def apply(conntrack): + systemd_service = 'conntrackd.service' + if not conntrack: + # Failover mechanism daemon should be indicated that it no longer needs + # to execute conntrackd actions on transition. This is only required + # once when conntrackd is stopped and taken out of service! + if process_named_running('conntrackd'): + resync_vrrp() + + call(f'systemctl stop {systemd_service}') + return None + + # Failover mechanism daemon should be indicated that it needs to execute + # conntrackd actions on transition. This is only required once when conntrackd + # is started the first time! + if not process_named_running('conntrackd'): + resync_vrrp() + + call(f'systemctl reload-or-restart {systemd_service}') + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_dhcp-relay.py b/src/conf_mode/service_dhcp-relay.py new file mode 100755 index 000000000..37d708847 --- /dev/null +++ b/src/conf_mode/service_dhcp-relay.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.template import render +from vyos.base import Warning +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/run/dhcp-relay/dhcrelay.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'dhcp-relay'] + if not conf.exists(base): + return None + + relay = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + return relay + +def verify(relay): + # bail out early - looks like removal from running config + if not relay or 'disable' in relay: + return None + + if 'lo' in (dict_search('interface', relay) or []): + raise ConfigError('DHCP relay does not support the loopback interface.') + + if 'server' not in 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(' configuration is not compatible with upstream/listen interface') + else: + Warning(' is going to be deprecated.\n' \ + 'Please use and ') + + 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): + # bail out early - looks like removal from running config + if not relay or 'disable' in relay: + return None + + render(config_file, 'dhcp-relay/dhcrelay.conf.j2', relay) + return None + +def apply(relay): + # bail out early - looks like removal from running config + service_name = 'isc-dhcp-relay.service' + if not relay or 'disable' in relay: + call(f'systemctl stop {service_name}') + if os.path.exists(config_file): + os.unlink(config_file) + return None + + call(f'systemctl restart {service_name}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_dhcp-server.py b/src/conf_mode/service_dhcp-server.py new file mode 100755 index 000000000..7ebc560ba --- /dev/null +++ b/src/conf_mode/service_dhcp-server.py @@ -0,0 +1,385 @@ +#!/usr/bin/env python3 +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from ipaddress import ip_address +from ipaddress import ip_network +from netaddr import IPRange +from sys import exit + +from vyos.config import Config +from vyos.pki import wrap_certificate +from vyos.pki import wrap_private_key +from vyos.template import render +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_args +from vyos.utils.file import chmod_775 +from vyos.utils.file import makedir +from vyos.utils.file import write_file +from vyos.utils.process import call +from vyos.utils.network import is_subnet_connected +from vyos.utils.network import is_addr_assigned +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +ctrl_config_file = '/run/kea/kea-ctrl-agent.conf' +ctrl_socket = '/run/kea/dhcp4-ctrl-socket' +config_file = '/run/kea/kea-dhcp4.conf' +lease_file = '/config/dhcp/dhcp4-leases.csv' +systemd_override = r'/run/systemd/system/kea-ctrl-agent.service.d/10-override.conf' +user_group = '_kea' + +ca_cert_file = '/run/kea/kea-failover-ca.pem' +cert_file = '/run/kea/kea-failover.pem' +cert_key_file = '/run/kea/kea-failover-key.pem' + +def dhcp_slice_range(exclude_list, range_dict): + """ + This function is intended to slice a DHCP range. What does it mean? + + Lets assume we have a DHCP range from '192.0.2.1' to '192.0.2.100' + but want to exclude address '192.0.2.74' and '192.0.2.75'. We will + pass an input 'range_dict' in the format: + {'start' : '192.0.2.1', 'stop' : '192.0.2.100' } + and we will receive an output list of: + [{'start' : '192.0.2.1' , 'stop' : '192.0.2.73' }, + {'start' : '192.0.2.76', 'stop' : '192.0.2.100' }] + The resulting list can then be used in turn to build the proper dhcpd + configuration file. + """ + output = [] + # exclude list must be sorted for this to work + exclude_list = sorted(exclude_list) + range_start = range_dict['start'] + range_stop = range_dict['stop'] + range_last_exclude = '' + + for e in exclude_list: + if (ip_address(e) >= ip_address(range_start)) and \ + (ip_address(e) <= ip_address(range_stop)): + range_last_exclude = e + + for e in exclude_list: + if (ip_address(e) >= ip_address(range_start)) and \ + (ip_address(e) <= ip_address(range_stop)): + + # Build new address range ending one address before exclude address + r = { + 'start' : range_start, + 'stop' : str(ip_address(e) -1) + } + # On the next run our address range will start one address after + # the exclude address + range_start = str(ip_address(e) + 1) + + # on subsequent exclude addresses we can not + # append them to our output + if not (ip_address(r['start']) > ip_address(r['stop'])): + # Everything is fine, add range to result + output.append(r) + + # Take care of last IP address range spanning from the last exclude + # address (+1) to the end of the initial configured range + if ip_address(e) == ip_address(range_last_exclude): + r = { + 'start': str(ip_address(e) + 1), + 'stop': str(range_stop) + } + if not (ip_address(r['start']) > ip_address(r['stop'])): + output.append(r) + else: + # if the excluded address was not part of the range, we simply return + # the entire ranga again + if not range_last_exclude: + if range_dict not in output: + output.append(range_dict) + + return output + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'dhcp-server'] + if not conf.exists(base): + return None + + dhcp = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + if 'shared_network_name' in dhcp: + for network, network_config in dhcp['shared_network_name'].items(): + if 'subnet' in network_config: + for subnet, subnet_config in network_config['subnet'].items(): + # If exclude IP addresses are defined we need to slice them out of + # the defined ranges + if {'exclude', 'range'} <= set(subnet_config): + new_range_id = 0 + new_range_dict = {} + for r, r_config in subnet_config['range'].items(): + for slice in dhcp_slice_range(subnet_config['exclude'], r_config): + new_range_dict.update({new_range_id : slice}) + new_range_id +=1 + + dhcp['shared_network_name'][network]['subnet'][subnet].update( + {'range' : new_range_dict}) + + if dict_search('failover.certificate', dhcp): + dhcp['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) + + return dhcp + +def verify(dhcp): + # bail out early - looks like removal from running config + if not dhcp or 'disable' in dhcp: + return None + + # If DHCP is enabled we need one share-network + if 'shared_network_name' not in dhcp: + raise ConfigError('No DHCP shared networks configured.\n' \ + 'At least one DHCP shared network must be configured.') + + # Inspect shared-network/subnet + listen_ok = False + subnets = [] + failover_ok = False + shared_networks = len(dhcp['shared_network_name']) + disabled_shared_networks = 0 + + + # A shared-network requires a subnet definition + for network, network_config in dhcp['shared_network_name'].items(): + if 'disable' in network_config: + disabled_shared_networks += 1 + + if 'subnet' not in network_config: + raise ConfigError(f'No subnets defined for {network}. At least one\n' \ + 'lease subnet must be configured.') + + for subnet, subnet_config in network_config['subnet'].items(): + # All delivered static routes require a next-hop to be set + if 'static_route' in subnet_config: + for route, route_option in subnet_config['static_route'].items(): + if 'next_hop' not in route_option: + raise ConfigError(f'DHCP static-route "{route}" requires router to be defined!') + + # Check if DHCP address range is inside configured subnet declaration + if 'range' in subnet_config: + networks = [] + for range, range_config in subnet_config['range'].items(): + if not {'start', 'stop'} <= set(range_config): + raise ConfigError(f'DHCP range "{range}" start and stop address must be defined!') + + # Start/Stop address must be inside network + for key in ['start', 'stop']: + if ip_address(range_config[key]) not in ip_network(subnet): + raise ConfigError(f'DHCP range "{range}" {key} address not within shared-network "{network}, {subnet}"!') + + # Stop address must be greater or equal to start address + if ip_address(range_config['stop']) < ip_address(range_config['start']): + raise ConfigError(f'DHCP range "{range}" stop address must be greater or equal\n' \ + 'to the ranges start address!') + + for network in networks: + start = range_config['start'] + stop = range_config['stop'] + if start in network: + raise ConfigError(f'Range "{range}" start address "{start}" already part of another range!') + if stop in network: + raise ConfigError(f'Range "{range}" stop address "{stop}" already part of another range!') + + tmp = IPRange(range_config['start'], range_config['stop']) + networks.append(tmp) + + # Exclude addresses must be in bound + if 'exclude' in subnet_config: + for exclude in subnet_config['exclude']: + if ip_address(exclude) not in ip_network(subnet): + raise ConfigError(f'Excluded IP address "{exclude}" not within shared-network "{network}, {subnet}"!') + + # At least one DHCP address range or static-mapping required + if 'range' not in subnet_config and 'static_mapping' not in subnet_config: + raise ConfigError(f'No DHCP address range or active static-mapping configured\n' \ + f'within shared-network "{network}, {subnet}"!') + + if 'static_mapping' in subnet_config: + # Static mappings require just a MAC address (will use an IP from the dynamic pool if IP is not set) + for mapping, mapping_config in subnet_config['static_mapping'].items(): + if 'ip_address' in mapping_config: + if ip_address(mapping_config['ip_address']) not in ip_network(subnet): + raise ConfigError(f'Configured static lease address for mapping "{mapping}" is\n' \ + f'not within shared-network "{network}, {subnet}"!') + + if ('mac' not in mapping_config and 'duid' not in mapping_config) or \ + ('mac' in mapping_config and 'duid' in mapping_config): + raise ConfigError(f'Either MAC address or Client identifier (DUID) is required for ' + f'static mapping "{mapping}" within shared-network "{network}, {subnet}"!') + + # There must be one subnet connected to a listen interface. + # This only counts if the network itself is not disabled! + if 'disable' not in network_config: + if is_subnet_connected(subnet, primary=False): + listen_ok = True + + # Subnets must be non overlapping + if subnet in subnets: + raise ConfigError(f'Configured subnets must be unique! Subnet "{subnet}"\n' + 'defined multiple times!') + subnets.append(subnet) + + # Check for overlapping subnets + net = ip_network(subnet) + for n in subnets: + net2 = ip_network(n) + if (net != net2): + if 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: + raise ConfigError(f'At least one shared network must be active!') + + if 'failover' in dhcp: + for key in ['name', 'remote', 'source_address', 'status']: + if key not in dhcp['failover']: + tmp = key.replace('_', '-') + raise ConfigError(f'DHCP failover requires "{tmp}" to be specified!') + + if len({'certificate', 'ca_certificate'} & set(dhcp['failover'])) == 1: + raise ConfigError(f'DHCP secured failover requires both certificate and CA certificate') + + if 'certificate' in dhcp['failover']: + cert_name = dhcp['failover']['certificate'] + + if cert_name not in dhcp['pki']['certificate']: + raise ConfigError(f'Invalid certificate specified for DHCP failover') + + if not dict_search_args(dhcp['pki']['certificate'], cert_name, 'certificate'): + raise ConfigError(f'Invalid certificate specified for DHCP failover') + + if not dict_search_args(dhcp['pki']['certificate'], cert_name, 'private', 'key'): + raise ConfigError(f'Missing private key on certificate specified for DHCP failover') + + if 'ca_certificate' in dhcp['failover']: + ca_cert_name = dhcp['failover']['ca_certificate'] + if ca_cert_name not in dhcp['pki']['ca']: + raise ConfigError(f'Invalid CA certificate specified for DHCP failover') + + if not dict_search_args(dhcp['pki']['ca'], ca_cert_name, 'certificate'): + raise ConfigError(f'Invalid CA certificate specified for DHCP failover') + + for address in (dict_search('listen_address', dhcp) or []): + if is_addr_assigned(address): + listen_ok = True + # no need to probe further networks, we have one that is valid + continue + else: + raise ConfigError(f'listen-address "{address}" not configured on any interface') + + + if not listen_ok: + raise ConfigError('None of the configured subnets have an appropriate primary IP address on any\n' + 'broadcast interface configured, nor was there an explicit listen-address\n' + 'configured for serving DHCP relay packets!') + + return None + +def generate(dhcp): + # bail out early - looks like removal from running config + if not dhcp or 'disable' in dhcp: + return None + + dhcp['lease_file'] = lease_file + dhcp['machine'] = os.uname().machine + + # Create directory for lease file if necessary + lease_dir = os.path.dirname(lease_file) + if not os.path.isdir(lease_dir): + makedir(lease_dir, group='vyattacfg') + chmod_775(lease_dir) + + # Create lease file if necessary and let kea own it - 'kea-lfc' expects it that way + if not os.path.exists(lease_file): + write_file(lease_file, '', user=user_group, group=user_group, mode=0o644) + + for f in [cert_file, cert_key_file, ca_cert_file]: + if os.path.exists(f): + os.unlink(f) + + if 'failover' in dhcp: + if 'certificate' in dhcp['failover']: + cert_name = dhcp['failover']['certificate'] + cert_data = dhcp['pki']['certificate'][cert_name]['certificate'] + key_data = dhcp['pki']['certificate'][cert_name]['private']['key'] + write_file(cert_file, wrap_certificate(cert_data), user=user_group, mode=0o600) + write_file(cert_key_file, wrap_private_key(key_data), user=user_group, mode=0o600) + + dhcp['failover']['cert_file'] = cert_file + dhcp['failover']['cert_key_file'] = cert_key_file + + if 'ca_certificate' in dhcp['failover']: + ca_cert_name = dhcp['failover']['ca_certificate'] + ca_cert_data = dhcp['pki']['ca'][ca_cert_name]['certificate'] + write_file(ca_cert_file, wrap_certificate(ca_cert_data), user=user_group, mode=0o600) + + dhcp['failover']['ca_cert_file'] = ca_cert_file + + render(systemd_override, 'dhcp-server/10-override.conf.j2', dhcp) + + render(ctrl_config_file, 'dhcp-server/kea-ctrl-agent.conf.j2', dhcp, user=user_group, group=user_group) + render(config_file, 'dhcp-server/kea-dhcp4.conf.j2', dhcp, user=user_group, group=user_group) + + return None + +def apply(dhcp): + services = ['kea-ctrl-agent', 'kea-dhcp4-server', 'kea-dhcp-ddns-server'] + + if not dhcp or 'disable' in dhcp: + for service in services: + call(f'systemctl stop {service}.service') + + if os.path.exists(config_file): + os.unlink(config_file) + + return None + + for service in services: + action = 'restart' + + if service == 'kea-dhcp-ddns-server' and 'dynamic_dns_update' not in dhcp: + action = 'stop' + + if service == 'kea-ctrl-agent' and 'failover' not in dhcp: + action = 'stop' + + call(f'systemctl {action} {service}.service') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_dhcpv6-relay.py b/src/conf_mode/service_dhcpv6-relay.py new file mode 100755 index 000000000..6537ca3c2 --- /dev/null +++ b/src/conf_mode/service_dhcpv6-relay.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from sys import exit + +from vyos.config import Config +from vyos.ifconfig import Interface +from vyos.template import render +from vyos.template import is_ipv6 +from vyos.utils.process import call +from vyos.utils.network import is_ipv6_link_local +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = '/run/dhcp-relay/dhcrelay6.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'dhcpv6-relay'] + if not conf.exists(base): + return None + + relay = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + return relay + +def verify(relay): + # bail out early - looks like removal from running config + if not relay or 'disable' in relay: + return None + + if 'upstream_interface' not in relay: + raise ConfigError('At least one upstream interface required!') + for interface, config in relay['upstream_interface'].items(): + if 'address' not in config: + raise ConfigError('DHCPv6 server required for upstream ' \ + f'interface {interface}!') + + if 'listen_interface' not in relay: + raise ConfigError('At least one listen interface required!') + + # DHCPv6 relay requires at least one global unicat address assigned to the + # interface + for interface in relay['listen_interface']: + has_global = False + for addr in Interface(interface).get_addr(): + if is_ipv6(addr) and not is_ipv6_link_local(addr): + has_global = True + if not has_global: + raise ConfigError(f'Interface {interface} does not have global '\ + 'IPv6 address assigned!') + + return None + +def generate(relay): + # bail out early - looks like removal from running config + if not relay or 'disable' in relay: + return None + + render(config_file, 'dhcp-relay/dhcrelay6.conf.j2', relay) + return None + +def apply(relay): + # bail out early - looks like removal from running config + service_name = 'isc-dhcp-relay6.service' + if not relay or 'disable' in relay: + # DHCPv6 relay support is removed in the commit + call(f'systemctl stop {service_name}') + if os.path.exists(config_file): + os.unlink(config_file) + return None + + call(f'systemctl restart {service_name}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_dhcpv6-server.py b/src/conf_mode/service_dhcpv6-server.py new file mode 100755 index 000000000..9cc57dbcf --- /dev/null +++ b/src/conf_mode/service_dhcpv6-server.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from ipaddress import ip_address +from ipaddress import ip_network +from sys import exit + +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.file import chmod_775 +from vyos.utils.file import makedir +from vyos.utils.file import write_file +from vyos.utils.dict import dict_search +from vyos.utils.network import is_subnet_connected +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = '/run/kea/kea-dhcp6.conf' +ctrl_socket = '/run/kea/dhcp6-ctrl-socket' +lease_file = '/config/dhcp/dhcp6-leases.csv' +user_group = '_kea' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'dhcpv6-server'] + if not conf.exists(base): + return None + + dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + return dhcpv6 + +def verify(dhcpv6): + # bail out early - looks like removal from running config + if not dhcpv6 or 'disable' in dhcpv6: + return None + + # If DHCP is enabled we need one share-network + if 'shared_network_name' not in dhcpv6: + raise ConfigError('No DHCPv6 shared networks configured. At least '\ + 'one DHCPv6 shared network must be configured.') + + # Inspect shared-network/subnet + subnets = [] + listen_ok = False + for network, network_config in dhcpv6['shared_network_name'].items(): + # A shared-network requires a subnet definition + if 'subnet' not in network_config: + raise ConfigError(f'No DHCPv6 lease subnets configured for "{network}". '\ + 'At least one lease subnet must be configured for '\ + 'each shared network!') + + for subnet, subnet_config in network_config['subnet'].items(): + if 'address_range' in subnet_config: + if 'start' in subnet_config['address_range']: + range6_start = [] + range6_stop = [] + for start, start_config in subnet_config['address_range']['start'].items(): + if 'stop' not in start_config: + raise ConfigError(f'address-range stop address for start "{start}" is not defined!') + stop = start_config['stop'] + + # Start address must be inside network + if not ip_address(start) in ip_network(subnet): + raise ConfigError(f'address-range start address "{start}" is not in subnet "{subnet}"!') + + # Stop address must be inside network + if not ip_address(stop) in ip_network(subnet): + raise ConfigError(f'address-range stop address "{stop}" is not in subnet "{subnet}"!') + + # Stop address must be greater or equal to start address + if not ip_address(stop) >= ip_address(start): + raise ConfigError(f'address-range stop address "{stop}" must be greater then or equal ' \ + f'to the range start address "{start}"!') + + # DHCPv6 range start address must be unique - two ranges can't + # start with the same address - makes no sense + if start in range6_start: + raise ConfigError(f'Conflicting DHCPv6 lease range: '\ + f'Pool start address "{start}" defined multipe times!') + range6_start.append(start) + + # DHCPv6 range stop address must be unique - two ranges can't + # end with the same address - makes no sense + if stop in range6_stop: + raise ConfigError(f'Conflicting DHCPv6 lease range: '\ + f'Pool stop address "{stop}" defined multipe times!') + range6_stop.append(stop) + + if 'prefix' in subnet_config: + for prefix in subnet_config['prefix']: + if ip_network(prefix) not in ip_network(subnet): + raise ConfigError(f'address-range prefix "{prefix}" is not in subnet "{subnet}""') + + # Prefix delegation sanity checks + if 'prefix_delegation' in subnet_config: + if 'prefix' not in subnet_config['prefix_delegation']: + raise ConfigError('prefix-delegation prefix not defined!') + + for prefix, prefix_config in subnet_config['prefix_delegation']['prefix'].items(): + if 'delegated_length' not in prefix_config: + raise ConfigError(f'Delegated IPv6 prefix length for "{prefix}" '\ + f'must be configured') + + if 'prefix_length' not in prefix_config: + raise ConfigError('Length of delegated IPv6 prefix must be configured') + + if prefix_config['prefix_length'] > prefix_config['delegated_length']: + raise ConfigError('Length of delegated IPv6 prefix must be within parent prefix') + + # Static mappings don't require anything (but check if IP is in subnet if it's set) + if 'static_mapping' in subnet_config: + for mapping, mapping_config in subnet_config['static_mapping'].items(): + if 'ipv6_address' in mapping_config: + # Static address must be in subnet + if ip_address(mapping_config['ipv6_address']) not in ip_network(subnet): + raise ConfigError(f'static-mapping address for mapping "{mapping}" is not in subnet "{subnet}"!') + + if ('mac' not in mapping_config and 'duid' not in mapping_config) or \ + ('mac' in mapping_config and 'duid' in mapping_config): + raise ConfigError(f'Either MAC address or Client identifier (DUID) is required for ' + f'static mapping "{mapping}" within shared-network "{network}, {subnet}"!') + + if 'vendor_option' in subnet_config: + if len(dict_search('vendor_option.cisco.tftp_server', subnet_config)) > 2: + raise ConfigError(f'No more then two Cisco tftp-servers should be defined for subnet "{subnet}"!') + + # Subnets must be unique + if subnet in subnets: + raise ConfigError(f'DHCPv6 subnets must be unique! Subnet {subnet} defined multiple times!') + subnets.append(subnet) + + # DHCPv6 requires at least one configured address range or one static mapping + # (FIXME: is not actually checked right now?) + + # There must be one subnet connected to a listen interface if network is not disabled. + if 'disable' not in network_config: + if is_subnet_connected(subnet): + listen_ok = True + + # DHCPv6 subnet must not overlap. ISC DHCP also complains about overlapping + # subnets: "Warning: subnet 2001:db8::/32 overlaps subnet 2001:db8:1::/32" + net = ip_network(subnet) + for n in subnets: + net2 = ip_network(n) + if (net != net2): + if net.overlaps(net2): + raise ConfigError('DHCPv6 conflicting subnet ranges: {0} overlaps {1}'.format(net, net2)) + + if not listen_ok: + raise ConfigError('None of the DHCPv6 subnets are connected to a subnet6 on '\ + 'this machine. At least one subnet6 must be connected such that '\ + 'DHCPv6 listens on an interface!') + + + return None + +def generate(dhcpv6): + # bail out early - looks like removal from running config + if not dhcpv6 or 'disable' in dhcpv6: + return None + + dhcpv6['lease_file'] = lease_file + dhcpv6['machine'] = os.uname().machine + + # Create directory for lease file if necessary + lease_dir = os.path.dirname(lease_file) + if not os.path.isdir(lease_dir): + makedir(lease_dir, group='vyattacfg') + chmod_775(lease_dir) + + # Create lease file if necessary and let kea own it - 'kea-lfc' expects it that way + if not os.path.exists(lease_file): + write_file(lease_file, '', user=user_group, group=user_group, mode=0o644) + + render(config_file, 'dhcp-server/kea-dhcp6.conf.j2', dhcpv6, user=user_group, group=user_group) + return None + +def apply(dhcpv6): + # bail out early - looks like removal from running config + service_name = 'kea-dhcp6-server.service' + if not dhcpv6 or 'disable' in dhcpv6: + # DHCP server is removed in the commit + call(f'systemctl stop {service_name}') + if os.path.exists(config_file): + os.unlink(config_file) + return None + + call(f'systemctl restart {service_name}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_dns_dynamic.py b/src/conf_mode/service_dns_dynamic.py new file mode 100755 index 000000000..99fa8feee --- /dev/null +++ b/src/conf_mode/service_dns_dynamic.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import re +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configverify import verify_interface_exists +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/run/ddclient/ddclient.conf' +systemd_override = r'/run/systemd/system/ddclient.service.d/override.conf' + +# Dynamic interfaces that might not exist when the configuration is loaded +dynamic_interfaces = ('pppoe', 'sstpc') + +# Protocols that require zone +zone_necessary = ['cloudflare', 'digitalocean', 'godaddy', 'hetzner', 'gandi', + 'nfsn', 'nsupdate'] +zone_supported = zone_necessary + ['dnsexit2', 'zoneedit1'] + +# Protocols that do not require username +username_unnecessary = ['1984', 'cloudflare', 'cloudns', 'digitalocean', 'dnsexit2', + 'duckdns', 'freemyip', 'hetzner', 'keysystems', 'njalla', + 'nsupdate', 'regfishde'] + +# Protocols that support TTL +ttl_supported = ['cloudflare', 'dnsexit2', 'gandi', 'hetzner', 'godaddy', 'nfsn', + 'nsupdate'] + +# Protocols that support both IPv4 and IPv6 +dualstack_supported = ['cloudflare', 'digitalocean', 'dnsexit2', 'duckdns', + 'dyndns2', 'easydns', 'freedns', 'hetzner', 'infomaniak', + 'njalla'] + +# dyndns2 protocol in ddclient honors dual stack for selective servers +# because of the way it is implemented in ddclient +dyndns_dualstack_servers = ['members.dyndns.org', 'dynv6.com'] + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'dns', 'dynamic'] + if not conf.exists(base): + return None + + dyndns = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + dyndns['config_file'] = config_file + return dyndns + +def verify(dyndns): + # bail out early - looks like removal from running config + if not dyndns or 'name' not in dyndns: + return None + + # Dynamic DNS service provider - configuration validation + for service, config in dyndns['name'].items(): + + error_msg_req = f'is required for Dynamic DNS service "{service}"' + error_msg_uns = f'is not supported for Dynamic DNS service "{service}"' + + for field in ['protocol', 'address', 'host_name']: + if field not in config: + raise ConfigError(f'"{field.replace("_", "-")}" {error_msg_req}') + + # If dyndns address is an interface, ensure + # that the interface exists (or just warn if dynamic interface) + # and that web-options are not set + if config['address'] != 'web': + # exclude check interface for dynamic interfaces + if config['address'].startswith(dynamic_interfaces): + Warning(f'Interface "{config["address"]}" does not exist yet and cannot ' + f'be used for Dynamic DNS service "{service}" until it is up!') + else: + verify_interface_exists(config['address']) + if 'web_options' in config: + raise ConfigError(f'"web-options" is applicable only when using HTTP(S) ' + f'web request to obtain the IP address') + + # Warn if using checkip.dyndns.org, as it does not support HTTPS + # See: https://github.com/ddclient/ddclient/issues/597 + if 'web_options' in config: + if 'url' not in config['web_options']: + raise ConfigError(f'"url" in "web-options" {error_msg_req} ' + f'with protocol "{config["protocol"]}"') + elif re.search("^(https?://)?checkip\.dyndns\.org", config['web_options']['url']): + Warning(f'"checkip.dyndns.org" does not support HTTPS requests for IP address ' + f'lookup. Please use a different IP address lookup service.') + + # RFC2136 uses 'key' instead of 'password' + if config['protocol'] != 'nsupdate' and 'password' not in config: + raise ConfigError(f'"password" {error_msg_req}') + + # Other RFC2136 specific configuration validation + if config['protocol'] == 'nsupdate': + if 'password' in config: + raise ConfigError(f'"password" {error_msg_uns} with protocol "{config["protocol"]}"') + for field in ['server', 'key']: + if field not in config: + raise ConfigError(f'"{field}" {error_msg_req} with protocol "{config["protocol"]}"') + + if config['protocol'] in zone_necessary and 'zone' not in config: + raise ConfigError(f'"zone" {error_msg_req} with protocol "{config["protocol"]}"') + + if config['protocol'] not in zone_supported and 'zone' in config: + raise ConfigError(f'"zone" {error_msg_uns} with protocol "{config["protocol"]}"') + + if config['protocol'] not in username_unnecessary and 'username' not in config: + raise ConfigError(f'"username" {error_msg_req} with protocol "{config["protocol"]}"') + + if config['protocol'] not in ttl_supported and 'ttl' in config: + raise ConfigError(f'"ttl" {error_msg_uns} with protocol "{config["protocol"]}"') + + if config['ip_version'] == 'both': + if config['protocol'] not in dualstack_supported: + raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} ' + f'with protocol "{config["protocol"]}"') + # dyndns2 protocol in ddclient honors dual stack only for dyn.com (dyndns.org) + if config['protocol'] == 'dyndns2' and 'server' in config and config['server'] not in dyndns_dualstack_servers: + raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} ' + f'for "{config["server"]}" with protocol "{config["protocol"]}"') + + if {'wait_time', 'expiry_time'} <= config.keys() and int(config['expiry_time']) < int(config['wait_time']): + raise ConfigError(f'"expiry-time" must be greater than "wait-time" for ' + f'Dynamic DNS service "{service}"') + + return None + +def generate(dyndns): + # bail out early - looks like removal from running config + if not dyndns or 'name' not in dyndns: + return None + + render(config_file, 'dns-dynamic/ddclient.conf.j2', dyndns, permission=0o600) + render(systemd_override, 'dns-dynamic/override.conf.j2', dyndns) + return None + +def apply(dyndns): + systemd_service = 'ddclient.service' + # Reload systemd manager configuration + call('systemctl daemon-reload') + + # bail out early - looks like removal from running config + if not dyndns or 'name' not in dyndns: + call(f'systemctl stop {systemd_service}') + if os.path.exists(config_file): + os.unlink(config_file) + else: + call(f'systemctl reload-or-restart {systemd_service}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_dns_forwarding.py b/src/conf_mode/service_dns_forwarding.py new file mode 100755 index 000000000..c186f47af --- /dev/null +++ b/src/conf_mode/service_dns_forwarding.py @@ -0,0 +1,358 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from netifaces import interfaces +from sys import exit +from glob import glob + +from vyos.config import Config +from vyos.hostsd_client import Client as hostsd_client +from vyos.template import render +from vyos.template import bracketize_ipv6 +from vyos.utils.process import call +from vyos.utils.permission import chown +from vyos.utils.dict import dict_search + +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +pdns_rec_user = pdns_rec_group = 'pdns' +pdns_rec_run_dir = '/run/powerdns' +pdns_rec_lua_conf_file = f'{pdns_rec_run_dir}/recursor.conf.lua' +pdns_rec_hostsd_lua_conf_file = f'{pdns_rec_run_dir}/recursor.vyos-hostsd.conf.lua' +pdns_rec_hostsd_zones_file = f'{pdns_rec_run_dir}/recursor.forward-zones.conf' +pdns_rec_config_file = f'{pdns_rec_run_dir}/recursor.conf' + +hostsd_tag = 'static' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'dns', 'forwarding'] + if not conf.exists(base): + return None + + dns = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + # some additions to the default dictionary + if 'system' in dns: + base_nameservers = ['system', 'name-server'] + if conf.exists(base_nameservers): + dns.update({'system_name_server': conf.return_values(base_nameservers)}) + + if 'authoritative_domain' in dns: + dns['authoritative_zones'] = [] + dns['authoritative_zone_errors'] = [] + for node in dns['authoritative_domain']: + zonedata = dns['authoritative_domain'][node] + if ('disable' in zonedata) or (not 'records' in zonedata): + continue + zone = { + 'name': node, + 'file': "{}/zone.{}.conf".format(pdns_rec_run_dir, node), + 'records': [], + } + + recorddata = zonedata['records'] + + for rtype in [ 'a', 'aaaa', 'cname', 'mx', 'ns', 'ptr', 'txt', 'spf', 'srv', 'naptr' ]: + if rtype not in recorddata: + continue + for subnode in recorddata[rtype]: + if 'disable' in recorddata[rtype][subnode]: + continue + + rdata = recorddata[rtype][subnode] + + if rtype in [ 'a', 'aaaa' ]: + if not 'address' in rdata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one address is required') + continue + + if subnode == 'any': + subnode = '*' + + for address in rdata['address']: + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': address + }) + elif rtype in ['cname', 'ptr', 'ns']: + if not 'target' in rdata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: target is required') + continue + + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '{}.'.format(rdata['target']) + }) + elif rtype == 'mx': + if not 'server' in rdata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one server is required') + continue + + for servername in rdata['server']: + serverdata = rdata['server'][servername] + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '{} {}.'.format(serverdata['priority'], servername) + }) + elif rtype == 'txt': + if not 'value' in rdata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one value is required') + continue + + for value in rdata['value']: + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': "\"{}\"".format(value.replace("\"", "\\\"")) + }) + elif rtype == 'spf': + if not 'value' in rdata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: value is required') + continue + + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '"{}"'.format(rdata['value'].replace("\"", "\\\"")) + }) + elif rtype == 'srv': + if not 'entry' in rdata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one entry is required') + continue + + for entryno in rdata['entry']: + entrydata = rdata['entry'][entryno] + if not 'hostname' in entrydata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: hostname is required for entry {entryno}') + continue + + if not 'port' in entrydata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: port is required for entry {entryno}') + continue + + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '{} {} {} {}.'.format(entrydata['priority'], entrydata['weight'], entrydata['port'], entrydata['hostname']) + }) + elif rtype == 'naptr': + if not 'rule' in rdata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one rule is required') + continue + + for ruleno in rdata['rule']: + ruledata = rdata['rule'][ruleno] + flags = "" + if 'lookup-srv' in ruledata: + flags += "S" + if 'lookup-a' in ruledata: + flags += "A" + if 'resolve-uri' in ruledata: + flags += "U" + if 'protocol-specific' in ruledata: + flags += "P" + + if 'order' in ruledata: + order = ruledata['order'] + else: + order = ruleno + + if 'regexp' in ruledata: + regexp= ruledata['regexp'].replace("\"", "\\\"") + else: + regexp = '' + + if ruledata['replacement']: + replacement = '{}.'.format(ruledata['replacement']) + else: + replacement = '' + + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '{} {} "{}" "{}" "{}" {}'.format(order, ruledata['preference'], flags, ruledata['service'], regexp, replacement) + }) + + dns['authoritative_zones'].append(zone) + + return dns + +def verify(dns): + # bail out early - looks like removal from running config + if not dns: + return None + + if 'listen_address' not in dns: + raise ConfigError('DNS forwarding requires a listen-address') + + if 'allow_from' not in dns: + raise ConfigError('DNS forwarding requires an allow-from network') + + # we can not use dict_search() when testing for domain servers + # as a domain will contains dot's which is out dictionary delimiter. + if 'domain' in dns: + for domain in dns['domain']: + if 'name_server' not in dns['domain'][domain]: + raise ConfigError(f'No server configured for domain {domain}!') + + if 'dns64_prefix' in dns: + dns_prefix = dns['dns64_prefix'].split('/')[1] + # RFC 6147 requires prefix /96 + if int(dns_prefix) != 96: + raise ConfigError('DNS 6to4 prefix must be of length /96') + + if ('authoritative_zone_errors' in dns) and dns['authoritative_zone_errors']: + for error in dns['authoritative_zone_errors']: + print(error) + raise ConfigError('Invalid authoritative records have been defined') + + if 'system' in dns: + if not 'system_name_server' in dns: + print('Warning: No "system name-server" configured') + + return None + +def generate(dns): + # bail out early - looks like removal from running config + if not dns: + return None + + render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.j2', + dns, user=pdns_rec_user, group=pdns_rec_group) + + render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.j2', + dns, user=pdns_rec_user, group=pdns_rec_group) + + for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'): + os.unlink(zone_filename) + + if 'authoritative_zones' in dns: + for zone in dns['authoritative_zones']: + render(zone['file'], 'dns-forwarding/recursor.zone.conf.j2', + zone, user=pdns_rec_user, group=pdns_rec_group) + + + # if vyos-hostsd didn't create its files yet, create them (empty) + for file in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]: + with open(file, 'a'): + pass + chown(file, user=pdns_rec_user, group=pdns_rec_group) + + return None + +def apply(dns): + if not dns: + # DNS forwarding is removed in the commit + call('systemctl stop pdns-recursor.service') + + if os.path.isfile(pdns_rec_config_file): + os.unlink(pdns_rec_config_file) + + for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'): + os.unlink(zone_filename) + else: + ### first apply vyos-hostsd config + hc = hostsd_client() + + # add static nameservers to hostsd so they can be joined with other + # sources + hc.delete_name_servers([hostsd_tag]) + if 'name_server' in dns: + # '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()) + + ## add nameserver tags - the order determines the nameserver order! + # our own tag (static) + hc.add_name_server_tags_recursor([hostsd_tag]) + + if 'system' in dns: + hc.add_name_server_tags_recursor(['system']) + else: + hc.delete_name_server_tags_recursor(['system']) + + # add dhcp nameserver tags for configured interfaces + if 'system_name_server' in dns: + for interface in dns['system_name_server']: + # system_name_server key contains both IP addresses and interface + # names (DHCP) to use DNS servers. We need to check if the + # value is an interface name - only if this is the case, add the + # interface based DNS forwarder. + if interface in interfaces(): + hc.add_name_server_tags_recursor(['dhcp-' + interface, + 'dhcpv6-' + interface ]) + + # hostsd will generate the forward-zones file + # 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: + 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 + hc.delete_authoritative_zones(list(hc.get_authoritative_zones())) + if 'authoritative_zones' in dns: + hc.add_authoritative_zones(list(map(lambda zone: zone['name'], dns['authoritative_zones']))) + + # call hostsd to generate forward-zones and its lua-config-file + hc.apply() + + ### finally (re)start pdns-recursor + call('systemctl restart pdns-recursor.service') + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_event-handler.py b/src/conf_mode/service_event-handler.py new file mode 100755 index 000000000..5028ef52f --- /dev/null +++ b/src/conf_mode/service_event-handler.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import json +from pathlib import Path + +from vyos.config import Config +from vyos.utils.dict import dict_search +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag + +airbag.enable() + +service_name = 'vyos-event-handler' +service_conf = Path(f'/run/{service_name}.conf') + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'event-handler', 'event'] + config = conf.get_config_dict(base, + get_first_key=True, + no_tag_node_value_mangle=True) + + return config + + +def verify(config): + # bail out early - looks like removal from running config + if not config: + return None + + for name, event_config in config.items(): + if not dict_search('filter.pattern', event_config) or not dict_search( + 'script.path', event_config): + raise ConfigError( + 'Event-handler: both pattern and script path items are mandatory' + ) + + if dict_search('script.environment.message', event_config): + raise ConfigError( + 'Event-handler: "message" environment variable is reserved for log message text' + ) + + +def generate(config): + if not config: + # Remove old config and return + service_conf.unlink(missing_ok=True) + return None + + # Write configuration file + conf_json = json.dumps(config, indent=4) + service_conf.write_text(conf_json) + + return None + + +def apply(config): + if config: + call(f'systemctl restart {service_name}.service') + else: + call(f'systemctl stop {service_name}.service') + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_event_handler.py b/src/conf_mode/service_event_handler.py deleted file mode 100755 index 5028ef52f..000000000 --- a/src/conf_mode/service_event_handler.py +++ /dev/null @@ -1,92 +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 . - -import json -from pathlib import Path - -from vyos.config import Config -from vyos.utils.dict import dict_search -from vyos.utils.process import call -from vyos import ConfigError -from vyos import airbag - -airbag.enable() - -service_name = 'vyos-event-handler' -service_conf = Path(f'/run/{service_name}.conf') - - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - base = ['service', 'event-handler', 'event'] - config = conf.get_config_dict(base, - get_first_key=True, - no_tag_node_value_mangle=True) - - return config - - -def verify(config): - # bail out early - looks like removal from running config - if not config: - return None - - for name, event_config in config.items(): - if not dict_search('filter.pattern', event_config) or not dict_search( - 'script.path', event_config): - raise ConfigError( - 'Event-handler: both pattern and script path items are mandatory' - ) - - if dict_search('script.environment.message', event_config): - raise ConfigError( - 'Event-handler: "message" environment variable is reserved for log message text' - ) - - -def generate(config): - if not config: - # Remove old config and return - service_conf.unlink(missing_ok=True) - return None - - # Write configuration file - conf_json = json.dumps(config, indent=4) - service_conf.write_text(conf_json) - - return None - - -def apply(config): - if config: - call(f'systemctl restart {service_name}.service') - else: - call(f'systemctl stop {service_name}.service') - - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/service_https.py b/src/conf_mode/service_https.py new file mode 100755 index 000000000..3dc5dfc01 --- /dev/null +++ b/src/conf_mode/service_https.py @@ -0,0 +1,335 @@ +#!/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 . + +import os +import sys +import json + +from copy import deepcopy +from time import sleep + +import vyos.defaults +import vyos.certbot_util + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdiff import get_config_diff +from vyos.configverify import verify_vrf +from vyos import ConfigError +from vyos.pki import wrap_certificate +from vyos.pki import wrap_private_key +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.process import is_systemd_service_running +from vyos.utils.process import is_systemd_service_active +from vyos.utils.network import check_port_availability +from vyos.utils.network import is_listen_port_bind_service +from vyos.utils.file import write_file + +from vyos import airbag +airbag.enable() + +config_file = '/etc/nginx/sites-available/default' +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'] + +api_config_state = '/run/http-api-state' +systemd_service = '/run/systemd/system/vyos-http-api.service' + +# https config needs to coordinate several subsystems: api, certbot, +# self-signed certificate, as well as the virtual hosts defined within the +# https config definition itself. Consequently, one needs a general dict, +# encompassing the https and other configs, and a list of such virtual hosts +# (server blocks in nginx terminology) to pass to the jinja2 template. +default_server_block = { + 'id' : '', + 'address' : '*', + 'port' : '443', + 'name' : ['_'], + 'api' : False, + 'vyos_cert' : {}, + 'certbot' : False +} + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'https'] + if not conf.exists(base): + return None + + diff = get_config_diff(conf) + + https = conf.get_config_dict(base, get_first_key=True) + + if https: + https['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True) + + https['children_changed'] = diff.node_changed_children(base) + https['api_add_or_delete'] = diff.node_changed_presence(base + ['api']) + + if 'api' not in https: + return https + + http_api = conf.get_config_dict(base + ['api'], key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + if http_api.from_defaults(['graphql']): + del http_api['graphql'] + + # Do we run inside a VRF context? + vrf_path = ['service', 'https', 'vrf'] + if conf.exists(vrf_path): + http_api['vrf'] = conf.return_value(vrf_path) + + https['api'] = http_api + + return https + +def verify(https): + from vyos.utils.dict import dict_search + + if https is None: + return None + + if 'certificates' in https: + certificates = https['certificates'] + + if 'certificate' in certificates: + if not https['pki']: + raise ConfigError("PKI is not configured") + + cert_name = certificates['certificate'] + + if cert_name not in https['pki']['certificate']: + raise ConfigError("Invalid certificate on https configuration") + + pki_cert = https['pki']['certificate'][cert_name] + + if 'certificate' not in pki_cert: + raise ConfigError("Missing certificate on https configuration") + + if 'private' not in pki_cert or 'key' not in pki_cert['private']: + raise ConfigError("Missing certificate private key on https configuration") + + if 'certbot' in https['certificates']: + vhost_names = [] + for _, vh_conf in https.get('virtual-host', {}).items(): + vhost_names += vh_conf.get('server-name', []) + domains = https['certificates']['certbot'].get('domain-name', []) + domains_found = [domain for domain in domains if domain in vhost_names] + if not domains_found: + raise ConfigError("At least one 'virtual-host server-name' " + "matching the 'certbot domain-name' is required.") + + server_block_list = [] + + # organize by vhosts + vhost_dict = https.get('virtual-host', {}) + + if not vhost_dict: + # no specified virtual hosts (server blocks); use default + server_block_list.append(default_server_block) + else: + for vhost in list(vhost_dict): + server_block = deepcopy(default_server_block) + data = vhost_dict.get(vhost, {}) + server_block['address'] = data.get('listen-address', '*') + server_block['port'] = data.get('port', '443') + server_block_list.append(server_block) + + for entry in server_block_list: + _address = entry.get('address') + _address = '0.0.0.0' if _address == '*' else _address + _port = entry.get('port') + proto = 'tcp' + if check_port_availability(_address, int(_port), proto) is not True and \ + not is_listen_port_bind_service(int(_port), 'nginx'): + raise ConfigError(f'"{proto}" port "{_port}" is used by another service') + + verify_vrf(https) + + # Verify API server settings, if present + if 'api' in https: + keys = dict_search('api.keys.id', https) + gql_auth_type = dict_search('api.graphql.authentication.type', https) + + # If "api graphql" is not defined and `gql_auth_type` is None, + # there's certainly no JWT auth option, and keys are required + jwt_auth = (gql_auth_type == "token") + + # Check for incomplete key configurations in every case + valid_keys_exist = False + if keys: + for k in keys: + if 'key' not in keys[k]: + raise ConfigError(f'Missing HTTPS API key string for key id "{k}"') + else: + valid_keys_exist = True + + # If only key-based methods are enabled, + # fail the commit if no valid key configurations are found + if (not valid_keys_exist) and (not jwt_auth): + raise ConfigError('At least one HTTPS API key is required unless GraphQL token authentication is enabled') + + if (not valid_keys_exist) and jwt_auth: + Warning(f'API keys are not configured: the classic (non-GraphQL) API will be unavailable.') + + return None + +def generate(https): + if https is None: + return None + + if 'api' not in https: + if os.path.exists(systemd_service): + os.unlink(systemd_service) + else: + render(systemd_service, 'https/vyos-http-api.service.j2', https['api']) + with open(api_config_state, 'w') as f: + json.dump(https['api'], f, indent=2) + + server_block_list = [] + + # organize by vhosts + + vhost_dict = https.get('virtual-host', {}) + + if not vhost_dict: + # no specified virtual hosts (server blocks); use default + server_block_list.append(default_server_block) + else: + for vhost in list(vhost_dict): + server_block = deepcopy(default_server_block) + server_block['id'] = vhost + data = vhost_dict.get(vhost, {}) + server_block['address'] = data.get('listen-address', '*') + server_block['port'] = data.get('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 + + cert_dict = https.get('certificates', {}) + + if 'certificate' in cert_dict: + cert_name = cert_dict['certificate'] + pki_cert = https['pki']['certificate'][cert_name] + + cert_path = os.path.join(cert_dir, f'{cert_name}.pem') + key_path = os.path.join(key_dir, f'{cert_name}.pem') + + server_cert = str(wrap_certificate(pki_cert['certificate'])) + if 'ca-certificate' in cert_dict: + ca_cert = cert_dict['ca-certificate'] + server_cert += '\n' + str(wrap_certificate(https['pki']['ca'][ca_cert]['certificate'])) + + write_file(cert_path, server_cert) + write_file(key_path, wrap_private_key(pki_cert['private']['key'])) + + vyos_cert_data = { + 'crt': cert_path, + 'key': key_path + } + + for block in server_block_list: + block['vyos_cert'] = vyos_cert_data + + # letsencrypt certificate using certbot + + certbot = False + cert_domains = cert_dict.get('certbot', {}).get('domain-name', []) + if cert_domains: + certbot = True + for domain in cert_domains: + sub_list = vyos.certbot_util.choose_server_block(server_block_list, + domain) + if sub_list: + for sb in sub_list: + sb['certbot'] = True + sb['certbot_dir'] = certbot_dir + # certbot organizes certificates by first domain + sb['certbot_domain_dir'] = cert_domains[0] + + if 'api' in list(https): + vhost_list = https.get('api-restrict', {}).get('virtual-host', []) + if not vhost_list: + for block in server_block_list: + block['api'] = True + else: + for block in server_block_list: + if block['id'] in vhost_list: + block['api'] = True + + data = { + 'server_block_list': server_block_list, + 'certbot': certbot + } + + render(config_file, 'https/nginx.default.j2', data) + render(systemd_override, 'https/override.conf.j2', https) + return None + +def apply(https): + # Reload systemd manager configuration + call('systemctl daemon-reload') + http_api_service_name = 'vyos-http-api.service' + https_service_name = 'nginx.service' + + if https is None: + if is_systemd_service_active(f'{http_api_service_name}'): + call(f'systemctl stop {http_api_service_name}') + call(f'systemctl stop {https_service_name}') + return + + if 'api' in https['children_changed']: + if 'api' in https: + if is_systemd_service_running(f'{http_api_service_name}'): + call(f'systemctl reload {http_api_service_name}') + else: + call(f'systemctl restart {http_api_service_name}') + # Let uvicorn settle before (possibly) restarting nginx + sleep(1) + else: + if is_systemd_service_active(f'{http_api_service_name}'): + call(f'systemctl stop {http_api_service_name}') + + if (not is_systemd_service_running(f'{https_service_name}') or + https['api_add_or_delete'] or + set(https['children_changed']) - set(['api'])): + call(f'systemctl restart {https_service_name}') + +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/service_https_certificates_certbot.py b/src/conf_mode/service_https_certificates_certbot.py new file mode 100755 index 000000000..1a6a498de --- /dev/null +++ b/src/conf_mode/service_https_certificates_certbot.py @@ -0,0 +1,114 @@ +#!/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 . + +import sys +import os + +import vyos.defaults +from vyos.config import Config +from vyos import ConfigError +from vyos.utils.process import cmd +from vyos.utils.process import call +from vyos.utils.process import is_systemd_service_running + +from vyos import airbag +airbag.enable() + +vyos_conf_scripts_dir = vyos.defaults.directories['conf_mode'] +vyos_certbot_dir = vyos.defaults.directories['certbot'] + +dependencies = [ + 'service_https.py', +] + +def request_certbot(cert): + email = cert.get('email') + if email is not None: + email_flag = '-m {0}'.format(email) + else: + email_flag = '' + + domains = cert.get('domains') + if domains is not None: + domain_flag = '-d ' + ' -d '.join(domains) + else: + domain_flag = '' + + certbot_cmd = f'certbot certonly --config-dir {vyos_certbot_dir} -n --nginx --agree-tos --no-eff-email --expand {email_flag} {domain_flag}' + + cmd(certbot_cmd, + raising=ConfigError, + message="The certbot request failed for the specified domains.") + +def get_config(): + conf = Config() + if not conf.exists('service https certificates certbot'): + return None + else: + conf.set_level('service https certificates certbot') + + cert = {} + + if conf.exists('domain-name'): + cert['domains'] = conf.return_values('domain-name') + + if conf.exists('email'): + cert['email'] = conf.return_value('email') + + return cert + +def verify(cert): + if cert is None: + return None + + if 'domains' not in cert: + raise ConfigError("At least one domain name is required to" + " request a letsencrypt certificate.") + + if 'email' not in cert: + raise ConfigError("An email address is required to request" + " a letsencrypt certificate.") + +def generate(cert): + if cert is None: + return None + + # certbot will attempt to reload nginx, even with 'certonly'; + # start nginx if not active + if not is_systemd_service_running('nginx.service'): + call('systemctl start nginx.service') + + request_certbot(cert) + +def apply(cert): + if cert is not None: + call('systemctl restart certbot.timer') + else: + call('systemctl stop certbot.timer') + return None + + for dep in dependencies: + cmd(f'{vyos_conf_scripts_dir}/{dep}', raising=ConfigError) + +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/service_ids_ddos-protection.py b/src/conf_mode/service_ids_ddos-protection.py new file mode 100755 index 000000000..276a71fcb --- /dev/null +++ b/src/conf_mode/service_ids_ddos-protection.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from sys import exit + +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/run/fastnetmon/fastnetmon.conf' +networks_list = r'/run/fastnetmon/networks_list' +excluded_networks_list = r'/run/fastnetmon/excluded_networks_list' +attack_dir = '/var/log/fastnetmon_attacks' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'ids', 'ddos-protection'] + if not conf.exists(base): + return None + + fastnetmon = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + return fastnetmon + +def verify(fastnetmon): + if not fastnetmon: + return None + + if 'mode' not in fastnetmon: + raise ConfigError('Specify operating mode!') + + if fastnetmon.get('mode') == 'mirror' and 'listen_interface' not in fastnetmon: + raise ConfigError("Incorrect settings for 'mode mirror': must specify interface(s) for traffic mirroring") + + if fastnetmon.get('mode') == 'sflow' and 'listen_address' not in fastnetmon.get('sflow', {}): + raise ConfigError("Incorrect settings for 'mode sflow': must specify sFlow 'listen-address'") + + if 'alert_script' in fastnetmon: + if os.path.isfile(fastnetmon['alert_script']): + # Check script permissions + if not os.access(fastnetmon['alert_script'], os.X_OK): + raise ConfigError('Script "{alert_script}" is not executable!'.format(fastnetmon['alert_script'])) + else: + raise ConfigError('File "{alert_script}" does not exists!'.format(fastnetmon)) + +def generate(fastnetmon): + if not fastnetmon: + for file in [config_file, networks_list]: + if os.path.isfile(file): + os.unlink(file) + + return None + + # Create dir for log attack details + if not os.path.exists(attack_dir): + os.mkdir(attack_dir) + + render(config_file, 'ids/fastnetmon.j2', fastnetmon) + render(networks_list, 'ids/fastnetmon_networks_list.j2', fastnetmon) + render(excluded_networks_list, 'ids/fastnetmon_excluded_networks_list.j2', fastnetmon) + return None + +def apply(fastnetmon): + systemd_service = 'fastnetmon.service' + if not fastnetmon: + # Stop fastnetmon service if removed + call(f'systemctl stop {systemd_service}') + else: + call(f'systemctl reload-or-restart {systemd_service}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_ids_fastnetmon.py b/src/conf_mode/service_ids_fastnetmon.py deleted file mode 100755 index 276a71fcb..000000000 --- a/src/conf_mode/service_ids_fastnetmon.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from sys import exit - -from vyos.config import Config -from vyos.template import render -from vyos.utils.process import call -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -config_file = r'/run/fastnetmon/fastnetmon.conf' -networks_list = r'/run/fastnetmon/networks_list' -excluded_networks_list = r'/run/fastnetmon/excluded_networks_list' -attack_dir = '/var/log/fastnetmon_attacks' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['service', 'ids', 'ddos-protection'] - if not conf.exists(base): - return None - - fastnetmon = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - - return fastnetmon - -def verify(fastnetmon): - if not fastnetmon: - return None - - if 'mode' not in fastnetmon: - raise ConfigError('Specify operating mode!') - - if fastnetmon.get('mode') == 'mirror' and 'listen_interface' not in fastnetmon: - raise ConfigError("Incorrect settings for 'mode mirror': must specify interface(s) for traffic mirroring") - - if fastnetmon.get('mode') == 'sflow' and 'listen_address' not in fastnetmon.get('sflow', {}): - raise ConfigError("Incorrect settings for 'mode sflow': must specify sFlow 'listen-address'") - - if 'alert_script' in fastnetmon: - if os.path.isfile(fastnetmon['alert_script']): - # Check script permissions - if not os.access(fastnetmon['alert_script'], os.X_OK): - raise ConfigError('Script "{alert_script}" is not executable!'.format(fastnetmon['alert_script'])) - else: - raise ConfigError('File "{alert_script}" does not exists!'.format(fastnetmon)) - -def generate(fastnetmon): - if not fastnetmon: - for file in [config_file, networks_list]: - if os.path.isfile(file): - os.unlink(file) - - return None - - # Create dir for log attack details - if not os.path.exists(attack_dir): - os.mkdir(attack_dir) - - render(config_file, 'ids/fastnetmon.j2', fastnetmon) - render(networks_list, 'ids/fastnetmon_networks_list.j2', fastnetmon) - render(excluded_networks_list, 'ids/fastnetmon_excluded_networks_list.j2', fastnetmon) - return None - -def apply(fastnetmon): - systemd_service = 'fastnetmon.service' - if not fastnetmon: - # Stop fastnetmon service if removed - call(f'systemctl stop {systemd_service}') - else: - call(f'systemctl reload-or-restart {systemd_service}') - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/service_lldp.py b/src/conf_mode/service_lldp.py new file mode 100755 index 000000000..3c647a0e8 --- /dev/null +++ b/src/conf_mode/service_lldp.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2017-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 . + +import os + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.utils.network import is_addr_assigned +from vyos.utils.network import is_loopback_addr +from vyos.version import get_version_data +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = "/etc/default/lldpd" +vyos_config_file = "/etc/lldpd.d/01-vyos.conf" +base = ['service', 'lldp'] + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + if not conf.exists(base): + return {} + + lldp = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + if conf.exists(['service', 'snmp']): + lldp['system_snmp_enabled'] = '' + + version_data = get_version_data() + lldp['version'] = version_data['version'] + + # prune location information if not set by user + for interface in lldp.get('interface', []): + if lldp.from_defaults(['interface', interface, 'location']): + del lldp['interface'][interface]['location'] + elif lldp.from_defaults(['interface', interface, 'location','coordinate_based']): + del lldp['interface'][interface]['location']['coordinate_based'] + + return lldp + +def verify(lldp): + # bail out early - looks like removal from running config + if lldp is None: + return + + if 'management_address' in lldp: + for address in lldp['management_address']: + message = f'LLDP management address "{address}" is invalid' + if is_loopback_addr(address): + Warning(f'{message} - loopback address') + elif not is_addr_assigned(address): + Warning(f'{message} - not assigned to any interface') + + if 'interface' in lldp: + for interface, interface_config in lldp['interface'].items(): + # bail out early if no location info present in interface config + if 'location' not in interface_config: + continue + if 'coordinate_based' in interface_config['location']: + if not {'latitude', 'latitude'} <= set(interface_config['location']['coordinate_based']): + raise ConfigError(f'Must define both longitude and latitude for "{interface}" location!') + + # check options + if 'snmp' in lldp: + if 'system_snmp_enabled' not in lldp: + raise ConfigError('SNMP must be configured to enable LLDP SNMP!') + + +def generate(lldp): + # bail out early - looks like removal from running config + if lldp is None: + return + + render(config_file, 'lldp/lldpd.j2', lldp) + render(vyos_config_file, 'lldp/vyos.conf.j2', lldp) + +def apply(lldp): + systemd_service = 'lldpd.service' + if lldp: + # start/restart lldp service + call(f'systemctl restart {systemd_service}') + else: + # LLDP service has been terminated + call(f'systemctl stop {systemd_service}') + if os.path.isfile(config_file): + os.unlink(config_file) + if os.path.isfile(vyos_config_file): + os.unlink(vyos_config_file) + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_mdns-repeater.py b/src/conf_mode/service_mdns-repeater.py deleted file mode 100755 index 6526c23d1..000000000 --- a/src/conf_mode/service_mdns-repeater.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2017-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 . - -import os - -from json import loads -from sys import exit -from netifaces import ifaddresses, interfaces, AF_INET, AF_INET6 - -from vyos.config import Config -from vyos.ifconfig.vrrp import VRRP -from vyos.template import render -from vyos.utils.process import call -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -config_file = '/run/avahi-daemon/avahi-daemon.conf' -systemd_override = r'/run/systemd/system/avahi-daemon.service.d/override.conf' -vrrp_running_file = '/run/mdns_vrrp_active' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - base = ['service', 'mdns', 'repeater'] - if not conf.exists(base): - return None - - mdns = conf.get_config_dict(base, key_mangling=('-', '_'), - no_tag_node_value_mangle=True, - get_first_key=True, - with_recursive_defaults=True) - - if mdns: - mdns['vrrp_exists'] = conf.exists('high-availability vrrp') - mdns['config_file'] = config_file - - return mdns - -def verify(mdns): - if not mdns or 'disable' in mdns: - return None - - # We need at least two interfaces to repeat mDNS advertisments - if 'interface' not in mdns or len(mdns['interface']) < 2: - raise ConfigError('mDNS repeater requires at least 2 configured interfaces!') - - # For mdns-repeater to work it is essential that the interfaces has - # an IPv4 address assigned - for interface in mdns['interface']: - if interface not in interfaces(): - raise ConfigError(f'Interface "{interface}" does not exist!') - - if mdns['ip_version'] in ['ipv4', 'both'] and AF_INET not in ifaddresses(interface): - raise ConfigError('mDNS repeater requires an IPv4 address to be ' - f'configured on interface "{interface}"') - - if mdns['ip_version'] in ['ipv6', 'both'] and AF_INET6 not in ifaddresses(interface): - raise ConfigError('mDNS repeater requires an IPv6 address to be ' - f'configured on interface "{interface}"') - - return None - -# Get VRRP states from interfaces, returns only interfaces where state is MASTER -def get_vrrp_master(interfaces): - json_data = loads(VRRP.collect('json')) - for group in json_data: - if 'data' in group: - if 'ifp_ifname' in group['data']: - iface = group['data']['ifp_ifname'] - state = group['data']['state'] # 2 = Master - if iface in interfaces and state != 2: - interfaces.remove(iface) - return interfaces - -def generate(mdns): - if not mdns: - return None - - if 'disable' in mdns: - print('Warning: mDNS repeater will be deactivated because it is disabled') - return None - - if mdns['vrrp_exists'] and 'vrrp_disable' in mdns: - mdns['interface'] = get_vrrp_master(mdns['interface']) - - if len(mdns['interface']) < 2: - return None - - render(config_file, 'mdns-repeater/avahi-daemon.conf.j2', mdns) - render(systemd_override, 'mdns-repeater/override.conf.j2', mdns) - return None - -def apply(mdns): - systemd_service = 'avahi-daemon.service' - # Reload systemd manager configuration - call('systemctl daemon-reload') - - if not mdns or 'disable' in mdns: - call(f'systemctl stop {systemd_service}') - if os.path.exists(config_file): - os.unlink(config_file) - - if os.path.exists(vrrp_running_file): - os.unlink(vrrp_running_file) - else: - if 'vrrp_disable' not in mdns and os.path.exists(vrrp_running_file): - os.unlink(vrrp_running_file) - - if mdns['vrrp_exists'] and 'vrrp_disable' in mdns: - if not os.path.exists(vrrp_running_file): - os.mknod(vrrp_running_file) # vrrp script looks for this file to update mdns repeater - - if len(mdns['interface']) < 2: - call(f'systemctl stop {systemd_service}') - return None - - call(f'systemctl restart {systemd_service}') - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/service_mdns_repeater.py b/src/conf_mode/service_mdns_repeater.py new file mode 100755 index 000000000..6526c23d1 --- /dev/null +++ b/src/conf_mode/service_mdns_repeater.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2017-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 . + +import os + +from json import loads +from sys import exit +from netifaces import ifaddresses, interfaces, AF_INET, AF_INET6 + +from vyos.config import Config +from vyos.ifconfig.vrrp import VRRP +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = '/run/avahi-daemon/avahi-daemon.conf' +systemd_override = r'/run/systemd/system/avahi-daemon.service.d/override.conf' +vrrp_running_file = '/run/mdns_vrrp_active' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'mdns', 'repeater'] + if not conf.exists(base): + return None + + mdns = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + if mdns: + mdns['vrrp_exists'] = conf.exists('high-availability vrrp') + mdns['config_file'] = config_file + + return mdns + +def verify(mdns): + if not mdns or 'disable' in mdns: + return None + + # We need at least two interfaces to repeat mDNS advertisments + if 'interface' not in mdns or len(mdns['interface']) < 2: + raise ConfigError('mDNS repeater requires at least 2 configured interfaces!') + + # For mdns-repeater to work it is essential that the interfaces has + # an IPv4 address assigned + for interface in mdns['interface']: + if interface not in interfaces(): + raise ConfigError(f'Interface "{interface}" does not exist!') + + if mdns['ip_version'] in ['ipv4', 'both'] and AF_INET not in ifaddresses(interface): + raise ConfigError('mDNS repeater requires an IPv4 address to be ' + f'configured on interface "{interface}"') + + if mdns['ip_version'] in ['ipv6', 'both'] and AF_INET6 not in ifaddresses(interface): + raise ConfigError('mDNS repeater requires an IPv6 address to be ' + f'configured on interface "{interface}"') + + return None + +# Get VRRP states from interfaces, returns only interfaces where state is MASTER +def get_vrrp_master(interfaces): + json_data = loads(VRRP.collect('json')) + for group in json_data: + if 'data' in group: + if 'ifp_ifname' in group['data']: + iface = group['data']['ifp_ifname'] + state = group['data']['state'] # 2 = Master + if iface in interfaces and state != 2: + interfaces.remove(iface) + return interfaces + +def generate(mdns): + if not mdns: + return None + + if 'disable' in mdns: + print('Warning: mDNS repeater will be deactivated because it is disabled') + return None + + if mdns['vrrp_exists'] and 'vrrp_disable' in mdns: + mdns['interface'] = get_vrrp_master(mdns['interface']) + + if len(mdns['interface']) < 2: + return None + + render(config_file, 'mdns-repeater/avahi-daemon.conf.j2', mdns) + render(systemd_override, 'mdns-repeater/override.conf.j2', mdns) + return None + +def apply(mdns): + systemd_service = 'avahi-daemon.service' + # Reload systemd manager configuration + call('systemctl daemon-reload') + + if not mdns or 'disable' in mdns: + call(f'systemctl stop {systemd_service}') + if os.path.exists(config_file): + os.unlink(config_file) + + if os.path.exists(vrrp_running_file): + os.unlink(vrrp_running_file) + else: + if 'vrrp_disable' not in mdns and os.path.exists(vrrp_running_file): + os.unlink(vrrp_running_file) + + if mdns['vrrp_exists'] and 'vrrp_disable' in mdns: + if not os.path.exists(vrrp_running_file): + os.mknod(vrrp_running_file) # vrrp script looks for this file to update mdns repeater + + if len(mdns['interface']) < 2: + call(f'systemctl stop {systemd_service}') + return None + + call(f'systemctl restart {systemd_service}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_ntp.py b/src/conf_mode/service_ntp.py new file mode 100755 index 000000000..1cc23a7df --- /dev/null +++ b/src/conf_mode/service_ntp.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from vyos.config import Config +from vyos.configdict import is_node_changed +from vyos.configverify import verify_vrf +from vyos.configverify import verify_interface_exists +from vyos.utils.process import call +from vyos.utils.permission import chmod_750 +from vyos.utils.network import get_interface_config +from vyos.template import render +from vyos.template import is_ipv4 +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +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 = ['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': {}}) + + return ntp + +def verify(ntp): + # bail out early - looks like removal from running config + if not ntp: + return None + + if 'server' not in ntp: + raise ConfigError('NTP server not configured') + + verify_vrf(ntp) + + if 'interface' in ntp: + # If ntpd should listen on a given interface, ensure it exists + interface = ntp['interface'] + verify_interface_exists(interface) + + # If we run in a VRF, our interface must belong to this VRF, too + if 'vrf' in ntp: + tmp = get_interface_config(interface) + vrf_name = ntp['vrf'] + if 'master' not in tmp or tmp['master'] != vrf_name: + raise ConfigError(f'NTP runs in VRF "{vrf_name}" - "{interface}" '\ + f'does not belong to this VRF!') + + if 'listen_address' in ntp: + ipv4_addresses = 0 + ipv6_addresses = 0 + for address in ntp['listen_address']: + if is_ipv4(address): + ipv4_addresses += 1 + else: + ipv6_addresses += 1 + if ipv4_addresses > 1: + raise ConfigError(f'NTP Only admits one ipv4 value for listen-address parameter ') + if ipv6_addresses > 1: + raise ConfigError(f'NTP Only admits one ipv6 value for listen-address parameter ') + + return None + +def generate(ntp): + # bail out early - looks like removal from running config + if not ntp: + return None + + 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 = 'chrony.service' + # Reload systemd manager configuration + call('systemctl daemon-reload') + + if not ntp: + # NTP support is removed in the commit + call(f'systemctl stop {systemd_service}') + if os.path.exists(config_file): + os.unlink(config_file) + if os.path.isfile(systemd_override): + os.unlink(systemd_override) + return + + # we need to restart the service if e.g. the VRF name changed + systemd_action = 'reload-or-restart' + if 'restart_required' in ntp: + systemd_action = 'restart' + + call(f'systemctl {systemd_action} {systemd_service}') + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_salt-minion.py b/src/conf_mode/service_salt-minion.py new file mode 100755 index 000000000..a8fce8e01 --- /dev/null +++ b/src/conf_mode/service_salt-minion.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from socket import gethostname +from sys import exit +from urllib3 import PoolManager + +from vyos.base import Warning +from vyos.config import Config +from vyos.configverify import verify_interface_exists +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.permission import chown +from vyos import ConfigError + +from vyos import airbag +airbag.enable() + +config_file = r'/etc/salt/minion' +master_keyfile = r'/opt/vyatta/etc/config/salt/pki/minion/master_sign.pub' + +user='minion' +group='vyattacfg' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'salt-minion'] + + if not conf.exists(base): + return None + + salt = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + # ID default is dynamic thus we can not use defaults() + if 'id' not in salt: + salt['id'] = gethostname() + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + salt = conf.merge_defaults(salt, recursive=True) + + if not conf.exists(base): + return None + else: + conf.set_level(base) + + return salt + +def verify(salt): + if not salt: + return None + + if 'hash' in salt and salt['hash'] == 'sha1': + Warning('Do not use sha1 hashing algorithm, upgrade to sha256 or later!') + + if 'source_interface' in salt: + verify_interface_exists(salt['source_interface']) + + return None + +def generate(salt): + if not salt: + return None + + render(config_file, 'salt-minion/minion.j2', salt, user=user, group=group) + + if not os.path.exists(master_keyfile): + if 'master_key' in salt: + req = PoolManager().request('GET', salt['master_key'], preload_content=False) + with open(master_keyfile, 'wb') as f: + while True: + data = req.read(1024) + if not data: + break + f.write(data) + + req.release_conn() + chown(master_keyfile, user, group) + + return None + +def apply(salt): + service_name = 'salt-minion.service' + if not salt: + # Salt removed from running config + call(f'systemctl stop {service_name}') + if os.path.exists(config_file): + os.unlink(config_file) + else: + call(f'systemctl restart {service_name}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_snmp.py b/src/conf_mode/service_snmp.py new file mode 100755 index 000000000..6565ffd60 --- /dev/null +++ b/src/conf_mode/service_snmp.py @@ -0,0 +1,269 @@ +#!/usr/bin/env python3 +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configverify import verify_vrf +from vyos.snmpv3_hashgen import plaintext_to_md5 +from vyos.snmpv3_hashgen import plaintext_to_sha1 +from vyos.snmpv3_hashgen import random +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.permission import chmod_755 +from vyos.utils.dict import dict_search +from vyos.utils.network import is_addr_assigned +from vyos.version import get_version_data +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +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'/run/systemd/system/snmpd.service.d/override.conf' +systemd_service = 'snmpd.service' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'snmp'] + + snmp = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + if not conf.exists(base): + snmp.update({'deleted' : ''}) + + if conf.exists(['service', 'lldp', 'snmp']): + snmp.update({'lldp_snmp' : ''}) + + if 'deleted' in snmp: + return snmp + + version_data = get_version_data() + snmp['version'] = version_data['version'] + + # create an internal snmpv3 user of the form 'vyosxxxxxxxxxxxxxxxx' + snmp['vyos_user'] = 'vyos' + random(8) + snmp['vyos_user_pass'] = random(16) + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + snmp = conf.merge_defaults(snmp, recursive=True) + + if 'listen_address' in snmp: + # 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://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']) + + if '::1' not in snmp['listen_address']: + tmp = {'::1': {'port': '161'}} + snmp['listen_address'] = dict_merge(tmp, snmp['listen_address']) + + return snmp + +def verify(snmp): + if 'deleted' in snmp: + return None + + if {'deleted', 'lldp_snmp'} <= set(snmp): + raise ConfigError('Can not delete SNMP service, as LLDP still uses SNMP!') + + ### check if the configured script actually exist + if 'script_extensions' in snmp and 'extension_name' in snmp['script_extensions']: + for extension, extension_opt in snmp['script_extensions']['extension_name'].items(): + if 'script' not in extension_opt: + raise ConfigError(f'Script extension "{extension}" requires an actual script to be configured!') + + tmp = extension_opt['script'] + if not os.path.isfile(tmp): + Warning(f'script "{tmp}" does not exist!') + else: + chmod_755(extension_opt['script']) + + if 'listen_address' in snmp: + for address in snmp['listen_address']: + # We only wan't to configure addresses that exist on the system. + # Hint the user if they don't exist + if 'vrf' in snmp: + vrf_name = snmp['vrf'] + if not is_addr_assigned(address, vrf_name) and address not in ['::1','127.0.0.1']: + raise ConfigError(f'SNMP listen address "{address}" not configured in vrf "{vrf_name}"!') + elif not is_addr_assigned(address): + raise ConfigError(f'SNMP listen address "{address}" not configured in default vrf!') + + if 'trap_target' in snmp: + for trap, trap_config in snmp['trap_target'].items(): + 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 + if 'v3' not in snmp: + return None + + if 'user' in snmp['v3']: + for user, user_config in snmp['v3']['user'].items(): + if 'group' not in user_config: + raise ConfigError(f'Group membership required for user "{user}"!') + + if 'plaintext_password' not in user_config['auth'] and 'encrypted_password' not in user_config['auth']: + raise ConfigError(f'Must specify authentication encrypted-password or plaintext-password for user "{user}"!') + + if 'plaintext_password' not in user_config['privacy'] and 'encrypted_password' not in user_config['privacy']: + raise ConfigError(f'Must specify privacy encrypted-password or plaintext-password for user "{user}"!') + + if 'group' in snmp['v3']: + for group, group_config in snmp['v3']['group'].items(): + if 'seclevel' not in group_config: + raise ConfigError(f'Must configure "seclevel" for group "{group}"!') + if 'view' not in group_config: + raise ConfigError(f'Must configure "view" for group "{group}"!') + + # Check if 'view' exists + view = group_config['view'] + if 'view' not in snmp['v3'] or view not in snmp['v3']['view']: + raise ConfigError(f'You must create view "{view}" first!') + + if 'view' in snmp['v3']: + for view, view_config in snmp['v3']['view'].items(): + if 'oid' not in view_config: + raise ConfigError(f'Must configure an "oid" for view "{view}"!') + + if 'trap_target' in snmp['v3']: + for trap, trap_config in snmp['v3']['trap_target'].items(): + if 'plaintext_password' not in trap_config['auth'] and 'encrypted_password' not in trap_config['auth']: + raise ConfigError(f'Must specify one of authentication encrypted-password or plaintext-password for trap "{trap}"!') + + if {'plaintext_password', 'encrypted_password'} <= set(trap_config['auth']): + raise ConfigError(f'Can not specify both authentication encrypted-password and plaintext-password for trap "{trap}"!') + + if 'plaintext_password' not in trap_config['privacy'] and 'encrypted_password' not in trap_config['privacy']: + raise ConfigError(f'Must specify one of privacy encrypted-password or plaintext-password for trap "{trap}"!') + + if {'plaintext_password', 'encrypted_password'} <= set(trap_config['privacy']): + raise ConfigError(f'Can not specify both privacy encrypted-password and plaintext-password for trap "{trap}"!') + + if 'type' not in trap_config: + raise ConfigError('SNMP v3 trap "type" must be specified!') + + return None + +def generate(snmp): + # As we are manipulating the snmpd user database we have to stop it first! + # This is even save if service is going to be removed + call(f'systemctl stop {systemd_service}') + # Clean config files + config_files = [config_file_client, config_file_daemon, + config_file_access, config_file_user, systemd_override] + for file in config_files: + if os.path.isfile(file): + os.unlink(file) + + if 'deleted' in snmp: + return None + + if 'v3' in snmp: + # net-snmp is now regenerating the configuration file in the background + # thus we need to re-open and re-read the file as the content changed. + # After that we can no read the encrypted password from the config and + # replace the CLI plaintext password with its encrypted version. + os.environ['vyos_libexec_dir'] = '/usr/libexec/vyos' + + if 'user' in snmp['v3']: + for user, user_config in snmp['v3']['user'].items(): + if dict_search('auth.type', user_config) == 'sha': + hash = plaintext_to_sha1 + else: + hash = plaintext_to_md5 + + if dict_search('auth.plaintext_password', user_config) is not None: + tmp = hash(dict_search('auth.plaintext_password', user_config), + dict_search('v3.engineid', snmp)) + + snmp['v3']['user'][user]['auth']['encrypted_password'] = tmp + del snmp['v3']['user'][user]['auth']['plaintext_password'] + + call(f'/opt/vyatta/sbin/my_set service snmp v3 user "{user}" auth encrypted-password "{tmp}" > /dev/null') + call(f'/opt/vyatta/sbin/my_delete service snmp v3 user "{user}" auth plaintext-password > /dev/null') + + if dict_search('privacy.plaintext_password', user_config) is not None: + tmp = hash(dict_search('privacy.plaintext_password', user_config), + dict_search('v3.engineid', snmp)) + + snmp['v3']['user'][user]['privacy']['encrypted_password'] = tmp + del snmp['v3']['user'][user]['privacy']['plaintext_password'] + + call(f'/opt/vyatta/sbin/my_set service snmp v3 user "{user}" privacy encrypted-password "{tmp}" > /dev/null') + call(f'/opt/vyatta/sbin/my_delete service snmp v3 user "{user}" privacy plaintext-password > /dev/null') + + # Write client config file + render(config_file_client, 'snmp/etc.snmp.conf.j2', snmp) + # Write server config file + render(config_file_daemon, 'snmp/etc.snmpd.conf.j2', snmp) + # Write access rights config file + render(config_file_access, 'snmp/usr.snmpd.conf.j2', snmp) + # Write access rights config file + render(config_file_user, 'snmp/var.snmpd.conf.j2', snmp) + # Write daemon configuration file + render(systemd_override, 'snmp/override.conf.j2', snmp) + + return None + +def apply(snmp): + # Always reload systemd manager configuration + call('systemctl daemon-reload') + + if 'deleted' in snmp: + return None + + # start SNMP daemon + call(f'systemctl restart {systemd_service}') + + # Enable AgentX in FRR + # This should be done for each daemon individually because common command + # works only if all the daemons started with SNMP support + # Following daemons from FRR 9.0/stable have SNMP module compiled in VyOS + frr_daemons_list = ['zebra', 'bgpd', 'ospf6d', 'ospfd', 'ripd', 'isisd', 'ldpd'] + for frr_daemon in frr_daemons_list: + call(f'vtysh -c "configure terminal" -d {frr_daemon} -c "agentx" >/dev/null') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_ssh.py b/src/conf_mode/service_ssh.py new file mode 100755 index 000000000..ee5e1eca2 --- /dev/null +++ b/src/conf_mode/service_ssh.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from sys import exit +from syslog import syslog +from syslog import LOG_INFO + +from vyos.config import Config +from vyos.configdict import is_node_changed +from vyos.configverify import verify_vrf +from vyos.utils.process import call +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/run/sshd/sshd_config' +systemd_override = r'/run/systemd/system/ssh.service.d/override.conf' + +sshguard_config_file = '/etc/sshguard/sshguard.conf' +sshguard_whitelist = '/etc/sshguard/whitelist' + +key_rsa = '/etc/ssh/ssh_host_rsa_key' +key_dsa = '/etc/ssh/ssh_host_dsa_key' +key_ed25519 = '/etc/ssh/ssh_host_ed25519_key' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'ssh'] + if not conf.exists(base): + return None + + ssh = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + tmp = is_node_changed(conf, base + ['vrf']) + if tmp: ssh.update({'restart_required': {}}) + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + ssh = conf.merge_defaults(ssh, recursive=True) + + # pass config file path - used in override template + ssh['config_file'] = config_file + + # Ignore default XML values if config doesn't exists + # Delete key from dict + if not conf.exists(base + ['dynamic-protection']): + del ssh['dynamic_protection'] + + return ssh + +def verify(ssh): + if not ssh: + return None + + if 'rekey' in ssh and 'data' not in ssh['rekey']: + raise ConfigError(f'Rekey data is required!') + + verify_vrf(ssh) + return None + +def generate(ssh): + if not ssh: + if os.path.isfile(config_file): + os.unlink(config_file) + if os.path.isfile(systemd_override): + os.unlink(systemd_override) + + return None + + # This usually happens only once on a fresh system, SSH keys need to be + # freshly generted, one per every system! + if not os.path.isfile(key_rsa): + syslog(LOG_INFO, 'SSH RSA host key not found, generating new key!') + call(f'ssh-keygen -q -N "" -t rsa -f {key_rsa}') + if not os.path.isfile(key_dsa): + syslog(LOG_INFO, 'SSH DSA host key not found, generating new key!') + call(f'ssh-keygen -q -N "" -t dsa -f {key_dsa}') + if not os.path.isfile(key_ed25519): + syslog(LOG_INFO, 'SSH ed25519 host key not found, generating new key!') + call(f'ssh-keygen -q -N "" -t ed25519 -f {key_ed25519}') + + render(config_file, 'ssh/sshd_config.j2', ssh) + render(systemd_override, 'ssh/override.conf.j2', ssh) + + if 'dynamic_protection' in ssh: + render(sshguard_config_file, 'ssh/sshguard_config.j2', ssh) + render(sshguard_whitelist, 'ssh/sshguard_whitelist.j2', ssh) + # Reload systemd manager configuration + call('systemctl daemon-reload') + + return None + +def apply(ssh): + systemd_service_ssh = 'ssh.service' + systemd_service_sshguard = 'sshguard.service' + if not ssh: + # SSH access is removed in the commit + call(f'systemctl stop {systemd_service_ssh}') + call(f'systemctl stop {systemd_service_sshguard}') + return None + + if 'dynamic_protection' not in ssh: + call(f'systemctl stop {systemd_service_sshguard}') + else: + call(f'systemctl reload-or-restart {systemd_service_sshguard}') + + # we need to restart the service if e.g. the VRF name changed + systemd_action = 'reload-or-restart' + if 'restart_required' in ssh: + systemd_action = 'restart' + + call(f'systemctl {systemd_action} {systemd_service_ssh}') + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_tftp-server.py b/src/conf_mode/service_tftp-server.py new file mode 100755 index 000000000..3ad346e2e --- /dev/null +++ b/src/conf_mode/service_tftp-server.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import stat +import pwd + +from copy import deepcopy +from glob import glob +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configverify import verify_vrf +from vyos.template import render +from vyos.template import is_ipv4 +from vyos.utils.process import call +from vyos.utils.permission import chmod_755 +from vyos.utils.network import is_addr_assigned +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/etc/default/tftpd' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'tftp-server'] + if not conf.exists(base): + return None + + tftpd = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + return tftpd + +def verify(tftpd): + # bail out early - looks like removal from running config + if not tftpd: + return None + + # Configuring allowed clients without a server makes no sense + if 'directory' not in tftpd: + raise ConfigError('TFTP root directory must be configured!') + + if 'listen_address' not in tftpd: + raise ConfigError('TFTP server listen address must be configured!') + + for address, address_config in tftpd['listen_address'].items(): + if not is_addr_assigned(address): + Warning(f'TFTP server listen address "{address}" not ' \ + 'assigned to any interface!') + verify_vrf(address_config) + + return None + +def generate(tftpd): + # cleanup any available configuration file + # files will be recreated on demand + for i in glob(config_file + '*'): + os.unlink(i) + + # bail out early - looks like removal from running config + if tftpd is None: + return None + + idx = 0 + for address, address_config in tftpd['listen_address'].items(): + config = deepcopy(tftpd) + port = tftpd['port'] + if is_ipv4(address): + config['listen_address'] = f'{address}:{port} -4' + else: + config['listen_address'] = f'[{address}]:{port} -6' + + if 'vrf' in address_config: + config['vrf'] = address_config['vrf'] + + file = config_file + str(idx) + render(file, 'tftp-server/default.j2', config) + idx = idx + 1 + + return None + +def apply(tftpd): + # stop all services first - then we will decide + call('systemctl stop tftpd@*.service') + + # bail out early - e.g. service deletion + if tftpd is None: + return None + + tftp_root = tftpd['directory'] + if not os.path.exists(tftp_root): + os.makedirs(tftp_root) + chmod_755(tftp_root) + + # get UNIX uid for user 'tftp' + tftp_uid = pwd.getpwnam('tftp').pw_uid + tftp_gid = pwd.getpwnam('tftp').pw_gid + + # get UNIX uid for tftproot directory + dir_uid = os.stat(tftp_root).st_uid + dir_gid = os.stat(tftp_root).st_gid + + # adjust uid/gid of tftproot directory if files don't belong to user tftp + if (tftp_uid != dir_uid) or (tftp_gid != dir_gid): + os.chown(tftp_root, tftp_uid, tftp_gid) + + idx = 0 + for address in tftpd['listen_address']: + call(f'systemctl restart tftpd@{idx}.service') + idx = idx + 1 + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py deleted file mode 100755 index 6565ffd60..000000000 --- a/src/conf_mode/snmp.py +++ /dev/null @@ -1,269 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from sys import exit - -from vyos.base import Warning -from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.configverify import verify_vrf -from vyos.snmpv3_hashgen import plaintext_to_md5 -from vyos.snmpv3_hashgen import plaintext_to_sha1 -from vyos.snmpv3_hashgen import random -from vyos.template import render -from vyos.utils.process import call -from vyos.utils.permission import chmod_755 -from vyos.utils.dict import dict_search -from vyos.utils.network import is_addr_assigned -from vyos.version import get_version_data -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -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'/run/systemd/system/snmpd.service.d/override.conf' -systemd_service = 'snmpd.service' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['service', 'snmp'] - - snmp = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - if not conf.exists(base): - snmp.update({'deleted' : ''}) - - if conf.exists(['service', 'lldp', 'snmp']): - snmp.update({'lldp_snmp' : ''}) - - if 'deleted' in snmp: - return snmp - - version_data = get_version_data() - snmp['version'] = version_data['version'] - - # create an internal snmpv3 user of the form 'vyosxxxxxxxxxxxxxxxx' - snmp['vyos_user'] = 'vyos' + random(8) - snmp['vyos_user_pass'] = random(16) - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - snmp = conf.merge_defaults(snmp, recursive=True) - - if 'listen_address' in snmp: - # 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://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']) - - if '::1' not in snmp['listen_address']: - tmp = {'::1': {'port': '161'}} - snmp['listen_address'] = dict_merge(tmp, snmp['listen_address']) - - return snmp - -def verify(snmp): - if 'deleted' in snmp: - return None - - if {'deleted', 'lldp_snmp'} <= set(snmp): - raise ConfigError('Can not delete SNMP service, as LLDP still uses SNMP!') - - ### check if the configured script actually exist - if 'script_extensions' in snmp and 'extension_name' in snmp['script_extensions']: - for extension, extension_opt in snmp['script_extensions']['extension_name'].items(): - if 'script' not in extension_opt: - raise ConfigError(f'Script extension "{extension}" requires an actual script to be configured!') - - tmp = extension_opt['script'] - if not os.path.isfile(tmp): - Warning(f'script "{tmp}" does not exist!') - else: - chmod_755(extension_opt['script']) - - if 'listen_address' in snmp: - for address in snmp['listen_address']: - # We only wan't to configure addresses that exist on the system. - # Hint the user if they don't exist - if 'vrf' in snmp: - vrf_name = snmp['vrf'] - if not is_addr_assigned(address, vrf_name) and address not in ['::1','127.0.0.1']: - raise ConfigError(f'SNMP listen address "{address}" not configured in vrf "{vrf_name}"!') - elif not is_addr_assigned(address): - raise ConfigError(f'SNMP listen address "{address}" not configured in default vrf!') - - if 'trap_target' in snmp: - for trap, trap_config in snmp['trap_target'].items(): - 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 - if 'v3' not in snmp: - return None - - if 'user' in snmp['v3']: - for user, user_config in snmp['v3']['user'].items(): - if 'group' not in user_config: - raise ConfigError(f'Group membership required for user "{user}"!') - - if 'plaintext_password' not in user_config['auth'] and 'encrypted_password' not in user_config['auth']: - raise ConfigError(f'Must specify authentication encrypted-password or plaintext-password for user "{user}"!') - - if 'plaintext_password' not in user_config['privacy'] and 'encrypted_password' not in user_config['privacy']: - raise ConfigError(f'Must specify privacy encrypted-password or plaintext-password for user "{user}"!') - - if 'group' in snmp['v3']: - for group, group_config in snmp['v3']['group'].items(): - if 'seclevel' not in group_config: - raise ConfigError(f'Must configure "seclevel" for group "{group}"!') - if 'view' not in group_config: - raise ConfigError(f'Must configure "view" for group "{group}"!') - - # Check if 'view' exists - view = group_config['view'] - if 'view' not in snmp['v3'] or view not in snmp['v3']['view']: - raise ConfigError(f'You must create view "{view}" first!') - - if 'view' in snmp['v3']: - for view, view_config in snmp['v3']['view'].items(): - if 'oid' not in view_config: - raise ConfigError(f'Must configure an "oid" for view "{view}"!') - - if 'trap_target' in snmp['v3']: - for trap, trap_config in snmp['v3']['trap_target'].items(): - if 'plaintext_password' not in trap_config['auth'] and 'encrypted_password' not in trap_config['auth']: - raise ConfigError(f'Must specify one of authentication encrypted-password or plaintext-password for trap "{trap}"!') - - if {'plaintext_password', 'encrypted_password'} <= set(trap_config['auth']): - raise ConfigError(f'Can not specify both authentication encrypted-password and plaintext-password for trap "{trap}"!') - - if 'plaintext_password' not in trap_config['privacy'] and 'encrypted_password' not in trap_config['privacy']: - raise ConfigError(f'Must specify one of privacy encrypted-password or plaintext-password for trap "{trap}"!') - - if {'plaintext_password', 'encrypted_password'} <= set(trap_config['privacy']): - raise ConfigError(f'Can not specify both privacy encrypted-password and plaintext-password for trap "{trap}"!') - - if 'type' not in trap_config: - raise ConfigError('SNMP v3 trap "type" must be specified!') - - return None - -def generate(snmp): - # As we are manipulating the snmpd user database we have to stop it first! - # This is even save if service is going to be removed - call(f'systemctl stop {systemd_service}') - # Clean config files - config_files = [config_file_client, config_file_daemon, - config_file_access, config_file_user, systemd_override] - for file in config_files: - if os.path.isfile(file): - os.unlink(file) - - if 'deleted' in snmp: - return None - - if 'v3' in snmp: - # net-snmp is now regenerating the configuration file in the background - # thus we need to re-open and re-read the file as the content changed. - # After that we can no read the encrypted password from the config and - # replace the CLI plaintext password with its encrypted version. - os.environ['vyos_libexec_dir'] = '/usr/libexec/vyos' - - if 'user' in snmp['v3']: - for user, user_config in snmp['v3']['user'].items(): - if dict_search('auth.type', user_config) == 'sha': - hash = plaintext_to_sha1 - else: - hash = plaintext_to_md5 - - if dict_search('auth.plaintext_password', user_config) is not None: - tmp = hash(dict_search('auth.plaintext_password', user_config), - dict_search('v3.engineid', snmp)) - - snmp['v3']['user'][user]['auth']['encrypted_password'] = tmp - del snmp['v3']['user'][user]['auth']['plaintext_password'] - - call(f'/opt/vyatta/sbin/my_set service snmp v3 user "{user}" auth encrypted-password "{tmp}" > /dev/null') - call(f'/opt/vyatta/sbin/my_delete service snmp v3 user "{user}" auth plaintext-password > /dev/null') - - if dict_search('privacy.plaintext_password', user_config) is not None: - tmp = hash(dict_search('privacy.plaintext_password', user_config), - dict_search('v3.engineid', snmp)) - - snmp['v3']['user'][user]['privacy']['encrypted_password'] = tmp - del snmp['v3']['user'][user]['privacy']['plaintext_password'] - - call(f'/opt/vyatta/sbin/my_set service snmp v3 user "{user}" privacy encrypted-password "{tmp}" > /dev/null') - call(f'/opt/vyatta/sbin/my_delete service snmp v3 user "{user}" privacy plaintext-password > /dev/null') - - # Write client config file - render(config_file_client, 'snmp/etc.snmp.conf.j2', snmp) - # Write server config file - render(config_file_daemon, 'snmp/etc.snmpd.conf.j2', snmp) - # Write access rights config file - render(config_file_access, 'snmp/usr.snmpd.conf.j2', snmp) - # Write access rights config file - render(config_file_user, 'snmp/var.snmpd.conf.j2', snmp) - # Write daemon configuration file - render(systemd_override, 'snmp/override.conf.j2', snmp) - - return None - -def apply(snmp): - # Always reload systemd manager configuration - call('systemctl daemon-reload') - - if 'deleted' in snmp: - return None - - # start SNMP daemon - call(f'systemctl restart {systemd_service}') - - # Enable AgentX in FRR - # This should be done for each daemon individually because common command - # works only if all the daemons started with SNMP support - # Following daemons from FRR 9.0/stable have SNMP module compiled in VyOS - frr_daemons_list = ['zebra', 'bgpd', 'ospf6d', 'ospfd', 'ripd', 'isisd', 'ldpd'] - for frr_daemon in frr_daemons_list: - call(f'vtysh -c "configure terminal" -d {frr_daemon} -c "agentx" >/dev/null') - - 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/ssh.py b/src/conf_mode/ssh.py deleted file mode 100755 index ee5e1eca2..000000000 --- a/src/conf_mode/ssh.py +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2022 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from sys import exit -from syslog import syslog -from syslog import LOG_INFO - -from vyos.config import Config -from vyos.configdict import is_node_changed -from vyos.configverify import verify_vrf -from vyos.utils.process import call -from vyos.template import render -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -config_file = r'/run/sshd/sshd_config' -systemd_override = r'/run/systemd/system/ssh.service.d/override.conf' - -sshguard_config_file = '/etc/sshguard/sshguard.conf' -sshguard_whitelist = '/etc/sshguard/whitelist' - -key_rsa = '/etc/ssh/ssh_host_rsa_key' -key_dsa = '/etc/ssh/ssh_host_dsa_key' -key_ed25519 = '/etc/ssh/ssh_host_ed25519_key' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['service', 'ssh'] - if not conf.exists(base): - return None - - ssh = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - - tmp = is_node_changed(conf, base + ['vrf']) - if tmp: ssh.update({'restart_required': {}}) - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - ssh = conf.merge_defaults(ssh, recursive=True) - - # pass config file path - used in override template - ssh['config_file'] = config_file - - # Ignore default XML values if config doesn't exists - # Delete key from dict - if not conf.exists(base + ['dynamic-protection']): - del ssh['dynamic_protection'] - - return ssh - -def verify(ssh): - if not ssh: - return None - - if 'rekey' in ssh and 'data' not in ssh['rekey']: - raise ConfigError(f'Rekey data is required!') - - verify_vrf(ssh) - return None - -def generate(ssh): - if not ssh: - if os.path.isfile(config_file): - os.unlink(config_file) - if os.path.isfile(systemd_override): - os.unlink(systemd_override) - - return None - - # This usually happens only once on a fresh system, SSH keys need to be - # freshly generted, one per every system! - if not os.path.isfile(key_rsa): - syslog(LOG_INFO, 'SSH RSA host key not found, generating new key!') - call(f'ssh-keygen -q -N "" -t rsa -f {key_rsa}') - if not os.path.isfile(key_dsa): - syslog(LOG_INFO, 'SSH DSA host key not found, generating new key!') - call(f'ssh-keygen -q -N "" -t dsa -f {key_dsa}') - if not os.path.isfile(key_ed25519): - syslog(LOG_INFO, 'SSH ed25519 host key not found, generating new key!') - call(f'ssh-keygen -q -N "" -t ed25519 -f {key_ed25519}') - - render(config_file, 'ssh/sshd_config.j2', ssh) - render(systemd_override, 'ssh/override.conf.j2', ssh) - - if 'dynamic_protection' in ssh: - render(sshguard_config_file, 'ssh/sshguard_config.j2', ssh) - render(sshguard_whitelist, 'ssh/sshguard_whitelist.j2', ssh) - # Reload systemd manager configuration - call('systemctl daemon-reload') - - return None - -def apply(ssh): - systemd_service_ssh = 'ssh.service' - systemd_service_sshguard = 'sshguard.service' - if not ssh: - # SSH access is removed in the commit - call(f'systemctl stop {systemd_service_ssh}') - call(f'systemctl stop {systemd_service_sshguard}') - return None - - if 'dynamic_protection' not in ssh: - call(f'systemctl stop {systemd_service_sshguard}') - else: - call(f'systemctl reload-or-restart {systemd_service_sshguard}') - - # we need to restart the service if e.g. the VRF name changed - systemd_action = 'reload-or-restart' - if 'restart_required' in ssh: - systemd_action = 'restart' - - call(f'systemctl {systemd_action} {systemd_service_ssh}') - 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/system-ip.py b/src/conf_mode/system-ip.py deleted file mode 100755 index 7612e2c0d..000000000 --- a/src/conf_mode/system-ip.py +++ /dev/null @@ -1,143 +0,0 @@ -#!/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 . - -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.utils.dict import dict_search -from vyos.utils.file import write_file -from vyos.utils.process import call -from vyos.utils.process import is_systemd_service_active -from vyos.utils.system import sysctl_write - -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 = ['system', 'ip'] - - opt = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - - # 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): - 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): - opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt) - return - -def apply(opt): - # Apply ARP threshold values - # table_size has a default value - thus the key always exists - size = int(dict_search('arp.table_size', opt)) - # Amount upon reaching which the records begin to be cleared immediately - sysctl_write('net.ipv4.neigh.default.gc_thresh3', size) - # Amount after which the records begin to be cleaned after 5 seconds - sysctl_write('net.ipv4.neigh.default.gc_thresh2', size // 2) - # Minimum number of stored records is indicated which is not cleared - sysctl_write('net.ipv4.neigh.default.gc_thresh1', size // 8) - - # enable/disable IPv4 forwarding - tmp = dict_search('disable_forwarding', opt) - value = '0' if (tmp != None) else '1' - write_file('/proc/sys/net/ipv4/conf/all/forwarding', value) - - # enable/disable IPv4 directed broadcast forwarding - tmp = dict_search('disable_directed_broadcast', opt) - value = '0' if (tmp != None) else '1' - write_file('/proc/sys/net/ipv4/conf/all/bc_forwarding', value) - - # configure multipath - tmp = dict_search('multipath.ignore_unreachable_nexthops', opt) - value = '1' if (tmp != None) else '0' - sysctl_write('net.ipv4.fib_multipath_use_neigh', value) - - tmp = dict_search('multipath.layer4_hashing', opt) - value = '1' if (tmp != None) else '0' - sysctl_write('net.ipv4.fib_multipath_hash_policy', value) - - # configure TCP options (defaults as of Linux 6.4) - tmp = dict_search('tcp.mss.probing', opt) - if tmp is None: - value = 0 - elif tmp == 'on-icmp-black-hole': - value = 1 - elif tmp == 'force': - value = 2 - else: - # Shouldn't happen - raise ValueError("TCP MSS probing is neither 'on-icmp-black-hole' nor 'force'!") - sysctl_write('net.ipv4.tcp_mtu_probing', value) - - tmp = dict_search('tcp.mss.base', opt) - value = '1024' if (tmp is None) else tmp - sysctl_write('net.ipv4.tcp_base_mss', value) - - tmp = dict_search('tcp.mss.floor', opt) - value = '48' if (tmp is None) else tmp - sysctl_write('net.ipv4.tcp_mtu_probe_floor', value) - - # During startup of vyos-router that brings up FRR, the service is not yet - # running when this script is called first. Skip this part and wait for initial - # commit of the configuration to trigger this statement - if is_systemd_service_active('frr.service'): - 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() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py deleted file mode 100755 index 90a1a8087..000000000 --- a/src/conf_mode/system-ipv6.py +++ /dev/null @@ -1,120 +0,0 @@ -#!/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 . - -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.utils.dict import dict_search -from vyos.utils.file import write_file -from vyos.utils.process import is_systemd_service_active -from vyos.utils.system import sysctl_write -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 = ['system', 'ipv6'] - - opt = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - - # 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): - 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): - opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt) - return - -def apply(opt): - # configure multipath - tmp = dict_search('multipath.layer4_hashing', opt) - value = '1' if (tmp != None) else '0' - sysctl_write('net.ipv6.fib_multipath_hash_policy', value) - - # Apply ND threshold values - # table_size has a default value - thus the key always exists - size = int(dict_search('neighbor.table_size', opt)) - # Amount upon reaching which the records begin to be cleared immediately - sysctl_write('net.ipv6.neigh.default.gc_thresh3', size) - # Amount after which the records begin to be cleaned after 5 seconds - sysctl_write('net.ipv6.neigh.default.gc_thresh2', size // 2) - # Minimum number of stored records is indicated which is not cleared - sysctl_write('net.ipv6.neigh.default.gc_thresh1', size // 8) - - # enable/disable IPv6 forwarding - tmp = dict_search('disable_forwarding', opt) - value = '0' if (tmp != None) else '1' - write_file('/proc/sys/net/ipv6/conf/all/forwarding', value) - - # configure IPv6 strict-dad - tmp = dict_search('strict_dad', opt) - value = '2' if (tmp != None) else '1' - for root, dirs, files in os.walk('/proc/sys/net/ipv6/conf'): - for name in files: - if name == 'accept_dad': - write_file(os.path.join(root, name), value) - - # During startup of vyos-router that brings up FRR, the service is not yet - # running when this script is called first. Skip this part and wait for initial - # commit of the configuration to trigger this statement - if is_systemd_service_active('frr.service'): - 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() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/system-login-banner.py b/src/conf_mode/system-login-banner.py deleted file mode 100755 index 65fa04417..000000000 --- a/src/conf_mode/system-login-banner.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020-2021 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from sys import exit -from copy import deepcopy - -from vyos.config import Config -from vyos.utils.file import write_file -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -try: - with open('/usr/share/vyos/default_motd') as f: - motd = f.read() -except: - # Use an empty banner if the default banner file cannot be read - motd = "\n" - -PRELOGIN_FILE = r'/etc/issue' -PRELOGIN_NET_FILE = r'/etc/issue.net' -POSTLOGIN_FILE = r'/etc/motd' - -default_config_data = { - 'issue': 'Welcome to VyOS - \\n \\l\n\n', - 'issue_net': '', - 'motd': motd -} - -def get_config(config=None): - banner = deepcopy(default_config_data) - if config: - conf = config - else: - conf = Config() - base_level = ['system', 'login', 'banner'] - - if not conf.exists(base_level): - return banner - else: - conf.set_level(base_level) - - # Post-Login banner - if conf.exists(['post-login']): - tmp = conf.return_value(['post-login']) - # post-login banner can be empty as well - if tmp: - tmp = tmp.replace('\\n','\n') - tmp = tmp.replace('\\t','\t') - # always add newline character - tmp += '\n' - else: - tmp = '' - - banner['motd'] = tmp - - # Pre-Login banner - if conf.exists(['pre-login']): - tmp = conf.return_value(['pre-login']) - # pre-login banner can be empty as well - if tmp: - tmp = tmp.replace('\\n','\n') - tmp = tmp.replace('\\t','\t') - # always add newline character - tmp += '\n' - else: - tmp = '' - - banner['issue'] = banner['issue_net'] = tmp - - return banner - -def verify(banner): - pass - -def generate(banner): - pass - -def apply(banner): - write_file(PRELOGIN_FILE, banner['issue']) - write_file(PRELOGIN_NET_FILE, banner['issue_net']) - write_file(POSTLOGIN_FILE, banner['motd']) - - 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/system-login.py b/src/conf_mode/system-login.py deleted file mode 100755 index f34575aff..000000000 --- a/src/conf_mode/system-login.py +++ /dev/null @@ -1,423 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from passlib.hosts import linux_context -from psutil import users -from pwd import getpwall -from pwd import getpwnam -from sys import exit -from time import sleep - -from vyos.config import Config -from vyos.configverify import verify_vrf -from vyos.defaults import directories -from vyos.template import render -from vyos.template import is_ipv4 -from vyos.utils.dict import dict_search -from vyos.utils.file import chown -from vyos.utils.process import cmd -from vyos.utils.process import call -from vyos.utils.process import rc_cmd -from vyos.utils.process import run -from vyos.utils.process import DEVNULL -from vyos import ConfigError -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" -tacacs_pam_config_file = "/etc/tacplus_servers" -tacacs_nss_config_file = "/etc/tacplus_nss.conf" -nss_config_file = "/etc/nsswitch.conf" - -# Minimum UID used when adding system users -MIN_USER_UID: int = 1000 -# Maximim UID used when adding system users -MAX_USER_UID: int = 59999 -# 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 = 8 -# Maximum number of supported TACACS servers -MAX_TACACS_COUNT: int = 8 - -# List of local user accounts that must be preserved -SYSTEM_USER_SKIP_LIST: list = ['radius_user', 'radius_priv_user', 'tacacs0', 'tacacs1', - 'tacacs2', 'tacacs3', 'tacacs4', 'tacacs5', 'tacacs6', - 'tacacs7', 'tacacs8', 'tacacs9', 'tacacs10',' tacacs11', - 'tacacs12', 'tacacs13', 'tacacs14', 'tacacs15'] - -def get_local_users(): - """Return list of dynamically allocated users (see Debian Policy Manual)""" - local_users = [] - for s_user in getpwall(): - if getpwnam(s_user.pw_name).pw_uid < MIN_USER_UID: - continue - if getpwnam(s_user.pw_name).pw_uid > MAX_USER_UID: - continue - if s_user.pw_name in SYSTEM_USER_SKIP_LIST: - continue - local_users.append(s_user.pw_name) - - 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: - conf = config - else: - conf = Config() - base = ['system', 'login'] - login = conf.get_config_dict(base, key_mangling=('-', '_'), - no_tag_node_value_mangle=True, - get_first_key=True, - with_recursive_defaults=True) - - # users no longer existing in the running configuration need to be deleted - local_users = get_local_users() - cli_users = [] - if 'user' in login: - cli_users = list(login['user']) - - # prune TACACS global defaults if not set by user - if login.from_defaults(['tacacs']): - del login['tacacs'] - # same for RADIUS - if login.from_defaults(['radius']): - del login['radius'] - - # create a list of all users, cli and users - all_users = list(set(local_users + cli_users)) - # We will remove any normal users that dos not exist in the current - # configuration. This can happen if user is added but configuration was not - # saved and system is rebooted. - rm_users = [tmp for tmp in all_users if tmp not in cli_users] - if rm_users: login.update({'rm_users' : rm_users}) - - return login - -def verify(login): - if 'rm_users' in login: - # This check is required as the script is also executed from vyos-router - # init script and there is no SUDO_USER environment variable available - # during system boot. - if 'SUDO_USER' in os.environ: - cur_user = os.environ['SUDO_USER'] - if cur_user in login['rm_users']: - raise ConfigError(f'Attempting to delete current user: {cur_user}') - - if 'user' in login: - system_users = getpwall() - for user, user_config in login['user'].items(): - # Linux system users range up until UID 1000, we can not create a - # VyOS CLI user which already exists as system user - for s_user in system_users: - if s_user.pw_name == user and s_user.pw_uid < MIN_USER_UID: - raise ConfigError(f'User "{user}" can not be created, conflict with local system account!') - - for pubkey, pubkey_options in (dict_search('authentication.public_keys', user_config) or {}).items(): - if 'type' not in pubkey_options: - raise ConfigError(f'Missing type for public-key "{pubkey}"!') - if 'key' not in pubkey_options: - raise ConfigError(f'Missing key for public-key "{pubkey}"!') - - if {'radius', 'tacacs'} <= set(login): - raise ConfigError('Using both RADIUS and TACACS at the same time is not supported!') - - # At lease one RADIUS server must not be disabled - 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 'disable' not in server_config: - sum_timeout += int(server_config['timeout']) - radius_servers_count += 1 - fail = False - - if fail: - raise ConfigError('All RADIUS servers are disabled') - - if radius_servers_count > MAX_RADIUS_COUNT: - raise ConfigError(f'Number of RADIUS servers exceeded maximum of {MAX_RADIUS_COUNT}!') - - 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']: - ipv4_count = 0 - ipv6_count = 0 - for address in login['radius']['source_address']: - if is_ipv4(address): ipv4_count += 1 - else: ipv6_count += 1 - - if ipv4_count > 1: - raise ConfigError('Only one IPv4 source-address can be set!') - if ipv6_count > 1: - raise ConfigError('Only one IPv6 source-address can be set!') - - if 'tacacs' in login: - tacacs_servers_count: int = 0 - fail = True - for server, server_config in dict_search('tacacs.server', login).items(): - if 'key' not in server_config: - raise ConfigError(f'TACACS server "{server}" requires key!') - if 'disable' not in server_config: - tacacs_servers_count += 1 - fail = False - - if fail: - raise ConfigError('All RADIUS servers are disabled') - - if tacacs_servers_count > MAX_TACACS_COUNT: - raise ConfigError(f'Number of TACACS servers exceeded maximum of {MAX_TACACS_COUNT}!') - - verify_vrf(login['tacacs']) - - if 'max_login_session' in login and 'timeout' not in login: - raise ConfigError('"login timeout" must be configured!') - - return None - - -def generate(login): - # calculate users encrypted password - if 'user' in login: - for user, user_config in login['user'].items(): - tmp = dict_search('authentication.plaintext_password', user_config) - if tmp: - 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'] = directories['base'] - - # Set default commands for re-adding user with encrypted password - del_user_plain = f"system login user {user} authentication plaintext-password" - add_user_encrypt = f"system login user {user} authentication encrypted-password '{encrypted_password}'" - - lvl = env['VYATTA_EDIT_LEVEL'] - # We're in config edit level, for example "edit system login" - # Change default commands for re-adding user with encrypted password - if lvl != '/': - # Replace '/system/login' to 'system login' - lvl = lvl.strip('/').split('/') - # Convert command str to list - del_user_plain = del_user_plain.split() - # New command exclude level, for example "edit system login" - del_user_plain = del_user_plain[len(lvl):] - # Convert string to list - del_user_plain = " ".join(del_user_plain) - - add_user_encrypt = add_user_encrypt.split() - add_user_encrypt = add_user_encrypt[len(lvl):] - add_user_encrypt = " ".join(add_user_encrypt) - - ret, out = rc_cmd(f"/opt/vyatta/sbin/my_delete {del_user_plain}", env=env) - if ret: raise ConfigError(out) - ret, out = rc_cmd(f"/opt/vyatta/sbin/my_set {add_user_encrypt}", env=env) - if ret: raise ConfigError(out) - else: - try: - 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. - # - # The encrypted password will be set only once during the first boot - # after an image upgrade. - del login['user'][user]['authentication']['encrypted_password'] - except: - pass - - ### RADIUS based user authentication - if 'radius' in login: - render(radius_config_file, 'login/pam_radius_auth.conf.j2', login, - permission=0o600, user='root', group='root') - else: - if os.path.isfile(radius_config_file): - os.unlink(radius_config_file) - - ### TACACS+ based user authentication - if 'tacacs' in login: - render(tacacs_pam_config_file, 'login/tacplus_servers.j2', login, - permission=0o644, user='root', group='root') - render(tacacs_nss_config_file, 'login/tacplus_nss.conf.j2', login, - permission=0o644, user='root', group='root') - else: - if os.path.isfile(tacacs_pam_config_file): - os.unlink(tacacs_pam_config_file) - if os.path.isfile(tacacs_nss_config_file): - os.unlink(tacacs_nss_config_file) - - - - # NSS must always be present on the system - render(nss_config_file, 'login/nsswitch.conf.j2', login, - permission=0o644, user='root', group='root') - - # /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') - else: - if os.path.isfile(autologout_file): - os.unlink(autologout_file) - - return None - - -def apply(login): - enable_otp = False - if 'user' in login: - for user, user_config in login['user'].items(): - # make new user using vyatta shell and make home directory (-m), - # default group of 100 (users) - command = 'useradd --create-home --no-user-group ' - # check if user already exists: - if user in get_local_users(): - # update existing account - command = 'usermod' - - # all accounts use /bin/vbash - command += ' --shell /bin/vbash' - # we need to use '' quotes when passing formatted data to the shell - # else it will not work as some data parts are lost in translation - tmp = dict_search('authentication.encrypted_password', user_config) - if tmp: command += f" --password '{tmp}'" - - tmp = dict_search('full_name', user_config) - if tmp: command += f" --comment '{tmp}'" - - tmp = dict_search('home_directory', user_config) - if tmp: command += f" --home '{tmp}'" - else: command += f" --home '/home/{user}'" - - command += f' --groups frr,frrvty,vyattacfg,sudo,adm,dip,disk,_kea {user}' - try: - cmd(command) - # we should not rely on the value stored in - # user_config['home_directory'], as a crazy user will choose - # username root or any other system user which will fail. - # - # XXX: Should we deny using root at all? - home_dir = getpwnam(user).pw_dir - # T5875: ensure UID is properly set on home directory if user is re-added - if os.path.exists(home_dir): - chown(home_dir, user=user, recursive=True) - - render(f'{home_dir}/.ssh/authorized_keys', 'login/authorized_keys.j2', - user_config, permission=0o600, - formater=lambda _: _.replace(""", '"'), - user=user, group='users') - - except Exception as e: - raise ConfigError(f'Adding user "{user}" raised exception: "{e}"') - - # Generate 2FA/MFA One-Time-Pad configuration - if dict_search('authentication.otp.key', user_config): - enable_otp = True - render(f'{home_dir}/.google_authenticator', 'login/pam_otp_ga.conf.j2', - user_config, permission=0o400, user=user, group='users') - else: - # delete configuration as it's not enabled for the user - if os.path.exists(f'{home_dir}/.google_authenticator'): - os.remove(f'{home_dir}/.google_authenticator') - - if 'rm_users' in login: - for user in login['rm_users']: - try: - # Disable user to prevent re-login - call(f'usermod -s /sbin/nologin {user}') - - # Logout user if he is still logged in - if user in list(set([tmp[0] for tmp in users()])): - print(f'{user} is logged in, forcing logout!') - # re-run command until user is logged out - while run(f'pkill -HUP -u {user}'): - sleep(0.250) - - # Remove user account but leave home directory in place. Re-run - # command until user is removed - userdel might return 8 as - # SSH sessions are not all yet properly cleaned away, thus we - # simply re-run the command until the account wen't away - while run(f'userdel {user}', stderr=DEVNULL): - sleep(0.250) - - except Exception as e: - raise ConfigError(f'Deleting user "{user}" raised exception: {e}') - - # Enable/disable RADIUS in PAM configuration - cmd('pam-auth-update --disable radius-mandatory radius-optional') - if 'radius' in login: - if login['radius'].get('security_mode', '') == 'mandatory': - pam_profile = 'radius-mandatory' - else: - pam_profile = 'radius-optional' - cmd(f'pam-auth-update --enable {pam_profile}') - - # Enable/disable TACACS+ in PAM configuration - cmd('pam-auth-update --disable tacplus-mandatory tacplus-optional') - if 'tacacs' in login: - if login['tacacs'].get('security_mode', '') == 'mandatory': - pam_profile = 'tacplus-mandatory' - else: - pam_profile = 'tacplus-optional' - cmd(f'pam-auth-update --enable {pam_profile}') - - # Enable/disable Google authenticator - cmd('pam-auth-update --disable mfa-google-authenticator') - if enable_otp: - cmd(f'pam-auth-update --enable mfa-google-authenticator') - - 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/system-logs.py b/src/conf_mode/system-logs.py deleted file mode 100755 index 8ad4875d4..000000000 --- a/src/conf_mode/system-logs.py +++ /dev/null @@ -1,79 +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 . - -from sys import exit - -from vyos import ConfigError -from vyos import airbag -from vyos.config import Config -from vyos.logger import syslog -from vyos.template import render -from vyos.utils.dict import dict_search -airbag.enable() - -# path to logrotate configs -logrotate_atop_file = '/etc/logrotate.d/vyos-atop' -logrotate_rsyslog_file = '/etc/logrotate.d/vyos-rsyslog' - - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - base = ['system', 'logs'] - logs_config = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - - return logs_config - - -def verify(logs_config): - # Nothing to verify here - pass - - -def generate(logs_config): - # get configuration for logrotate atop - logrotate_atop = dict_search('logrotate.atop', logs_config) - # generate new config file for atop - syslog.debug('Adding logrotate config for atop') - render(logrotate_atop_file, 'logs/logrotate/vyos-atop.j2', logrotate_atop) - - # get configuration for logrotate rsyslog - logrotate_rsyslog = dict_search('logrotate.messages', logs_config) - # generate new config file for rsyslog - syslog.debug('Adding logrotate config for rsyslog') - render(logrotate_rsyslog_file, 'logs/logrotate/vyos-rsyslog.j2', - logrotate_rsyslog) - - -def apply(logs_config): - # No further actions needed - pass - - -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/system-option.py b/src/conf_mode/system-option.py deleted file mode 100755 index d92121b3d..000000000 --- a/src/conf_mode/system-option.py +++ /dev/null @@ -1,159 +0,0 @@ -#!/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 . - -import os - -from netifaces import interfaces -from sys import exit -from time import sleep - -from vyos.config import Config -from vyos.configverify import verify_source_interface -from vyos.template import render -from vyos.utils.process import cmd -from vyos.utils.process import is_systemd_service_running -from vyos.utils.network import is_addr_assigned -from vyos.utils.network import is_intf_addr_assigned -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -curlrc_config = r'/etc/curlrc' -ssh_config = r'/etc/ssh/ssh_config.d/91-vyos-ssh-client-options.conf' -systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target' -time_format_to_locale = { - '12-hour': 'en_US.UTF-8', - '24-hour': 'en_GB.UTF-8' -} - - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['system', 'option'] - options = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - - return options - -def verify(options): - if 'http_client' in options: - config = options['http_client'] - if 'source_interface' in config: - if not config['source_interface'] in interfaces(): - raise ConfigError(f'Source interface {source_interface} does not ' - f'exist'.format(**config)) - - if {'source_address', 'source_interface'} <= set(config): - raise ConfigError('Can not define both HTTP source-interface and source-address') - - if 'source_address' in config: - if not is_addr_assigned(config['source_address']): - raise ConfigError('No interface with give address specified!') - - if 'ssh_client' in options: - config = options['ssh_client'] - if 'source_address' in config: - address = config['source_address'] - if not is_addr_assigned(config['source_address']): - 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 - -def generate(options): - render(curlrc_config, 'system/curlrc.j2', options) - render(ssh_config, 'system/ssh_config.j2', options) - return None - -def apply(options): - # System bootup beep - if 'startup_beep' in options: - cmd('systemctl enable vyos-beep.service') - else: - cmd('systemctl disable vyos-beep.service') - - # Ctrl-Alt-Delete action - if os.path.exists(systemd_action_file): - os.unlink(systemd_action_file) - if 'ctrl_alt_delete' in options: - if options['ctrl_alt_delete'] == 'reboot': - os.symlink('/lib/systemd/system/reboot.target', systemd_action_file) - elif options['ctrl_alt_delete'] == 'poweroff': - os.symlink('/lib/systemd/system/poweroff.target', systemd_action_file) - - # Configure HTTP client - if 'http_client' not in options: - if os.path.exists(curlrc_config): - os.unlink(curlrc_config) - - # Configure SSH client - if 'ssh_client' not in options: - if os.path.exists(ssh_config): - os.unlink(ssh_config) - - # Reboot system on kernel panic - timeout = '0' - if 'reboot_on_panic' in options: - timeout = '60' - with open('/proc/sys/kernel/panic', 'w') as f: - f.write(timeout) - - # tuned - performance tuning - if 'performance' in options: - cmd('systemctl restart tuned.service') - # wait until daemon has started before sending configuration - while (not is_systemd_service_running('tuned.service')): - sleep(0.250) - cmd('tuned-adm profile network-{performance}'.format(**options)) - else: - cmd('systemctl stop tuned.service') - - # Keyboard layout - there will be always the default key inside the dict - # but we check for key existence anyway - if 'keyboard_layout' in options: - cmd('loadkeys {keyboard_layout}'.format(**options)) - - # Enable/diable root-partition-auto-resize SystemD service - if 'root_partition_auto_resize' in options: - cmd('systemctl enable root-partition-auto-resize.service') - else: - cmd('systemctl disable root-partition-auto-resize.service') - - # Time format 12|24-hour - if 'time_format' in options: - time_format = time_format_to_locale.get(options['time_format']) - cmd(f'localectl set-locale LC_TIME={time_format}') - -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/system-proxy.py b/src/conf_mode/system-proxy.py deleted file mode 100755 index 079c43e7e..000000000 --- a/src/conf_mode/system-proxy.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2022 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from sys import exit - -from vyos.config import Config -from vyos.template import render -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -proxy_def = r'/etc/profile.d/vyos-system-proxy.sh' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['system', 'proxy'] - if not conf.exists(base): - return None - - proxy = conf.get_config_dict(base, get_first_key=True) - return proxy - -def verify(proxy): - if not proxy: - return - - if 'url' not in proxy or 'port' not in proxy: - raise ConfigError('Proxy URL and port require a value') - - if ('username' in proxy and 'password' not in proxy) or \ - ('username' not in proxy and 'password' in proxy): - raise ConfigError('Both username and password need to be defined!') - -def generate(proxy): - if not proxy: - if os.path.isfile(proxy_def): - os.unlink(proxy_def) - return - - render(proxy_def, 'system/proxy.j2', proxy, permission=0o755) - -def apply(proxy): - pass - -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/system-syslog.py b/src/conf_mode/system-syslog.py deleted file mode 100755 index 07fbb0734..000000000 --- a/src/conf_mode/system-syslog.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os - -from sys import exit - -from vyos.config import Config -from vyos.configdict import is_node_changed -from vyos.configverify import verify_vrf -from vyos.utils.process import call -from vyos.template import render -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -rsyslog_conf = '/etc/rsyslog.d/00-vyos.conf' -logrotate_conf = '/etc/logrotate.d/vyos-rsyslog' -systemd_override = r'/run/systemd/system/rsyslog.service.d/override.conf' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['system', 'syslog'] - if not conf.exists(base): - return None - - syslog = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - - syslog.update({ 'logrotate' : logrotate_conf }) - - tmp = is_node_changed(conf, base + ['vrf']) - if tmp: syslog.update({'restart_required': {}}) - - syslog = conf.merge_defaults(syslog, recursive=True) - if syslog.from_defaults(['global']): - del syslog['global'] - - return syslog - -def verify(syslog): - if not syslog: - return None - - verify_vrf(syslog) - -def generate(syslog): - if not syslog: - if os.path.exists(rsyslog_conf): - os.unlink(rsyslog_conf) - if os.path.exists(logrotate_conf): - os.unlink(logrotate_conf) - - return None - - render(rsyslog_conf, 'rsyslog/rsyslog.conf.j2', syslog) - render(systemd_override, 'rsyslog/override.conf.j2', syslog) - render(logrotate_conf, 'rsyslog/logrotate.j2', syslog) - - # Reload systemd manager configuration - call('systemctl daemon-reload') - return None - -def apply(syslog): - systemd_socket = 'syslog.socket' - systemd_service = 'syslog.service' - if not syslog: - call(f'systemctl stop {systemd_service} {systemd_socket}') - return None - - # we need to restart the service if e.g. the VRF name changed - systemd_action = 'reload-or-restart' - if 'restart_required' in syslog: - systemd_action = 'restart' - - call(f'systemctl {systemd_action} {systemd_service}') - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/system-timezone.py b/src/conf_mode/system-timezone.py deleted file mode 100755 index cd3d4b229..000000000 --- a/src/conf_mode/system-timezone.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2019 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import sys -import os - -from copy import deepcopy -from vyos.config import Config -from vyos import ConfigError -from vyos.utils.process import call - -from vyos import airbag -airbag.enable() - -default_config_data = { - 'name': 'UTC' -} - -def get_config(config=None): - tz = deepcopy(default_config_data) - if config: - conf = config - else: - conf = Config() - if conf.exists('system time-zone'): - tz['name'] = conf.return_value('system time-zone') - - return tz - -def verify(tz): - pass - -def generate(tz): - pass - -def apply(tz): - call('/usr/bin/timedatectl set-timezone {}'.format(tz['name'])) - call('systemctl restart rsyslog') - -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/system_acceleration.py b/src/conf_mode/system_acceleration.py new file mode 100755 index 000000000..e4b248675 --- /dev/null +++ b/src/conf_mode/system_acceleration.py @@ -0,0 +1,106 @@ +#!/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 . + +import os +import re + +from sys import exit + +from vyos.config import Config +from vyos.utils.process import popen +from vyos.utils.process import run +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +qat_init_script = '/etc/init.d/qat_service' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + data = {} + + if conf.exists(['system', 'acceleration', 'qat']): + data.update({'qat_enable' : ''}) + + if conf.exists(['vpn', 'ipsec']): + data.update({'ipsec' : ''}) + + if conf.exists(['interfaces', 'openvpn']): + data.update({'openvpn' : ''}) + + return data + + +def vpn_control(action, force_ipsec=False): + # XXX: Should these commands report failure? + if action == 'restore' and force_ipsec: + return run('ipsec start') + + return run(f'ipsec {action}') + + +def verify(qat): + if 'qat_enable' not in qat: + return + + # Check if QAT service installed + if not os.path.exists(qat_init_script): + raise ConfigError('QAT init script not found') + + # Check if QAT device exist + output, err = popen('lspci -nn', decode='utf-8') + if not err: + # PCI id | Chipset + # 19e2 -> C3xx + # 37c8 -> C62x + # 0435 -> DH895 + # 6f54 -> D15xx + # 18ee -> QAT_200XX + data = re.findall( + '(8086:19e2)|(8086:37c8)|(8086:0435)|(8086:6f54)|(8086:18ee)', output) + # If QAT devices found + if not data: + raise ConfigError('No QAT acceleration device found') + +def apply(qat): + # Shutdown VPN service which can use QAT + if 'ipsec' in qat: + vpn_control('stop') + + # Enable/Disable QAT service + if 'qat_enable' in qat: + run(f'{qat_init_script} start') + else: + run(f'{qat_init_script} stop') + + # Recover VPN service + if 'ipsec' in qat: + vpn_control('start') + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + apply(c) + except ConfigError as e: + print(e) + vpn_control('restore', force_ipsec=('ipsec' in c)) + exit(1) diff --git a/src/conf_mode/system_config-management.py b/src/conf_mode/system_config-management.py new file mode 100755 index 000000000..c681a8405 --- /dev/null +++ b/src/conf_mode/system_config-management.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 . + +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/system_conntrack.py b/src/conf_mode/system_conntrack.py new file mode 100755 index 000000000..7f6c71440 --- /dev/null +++ b/src/conf_mode/system_conntrack.py @@ -0,0 +1,243 @@ +#!/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 . + +import os +import re + +from sys import exit + +from vyos.config import Config +from vyos.configdep import set_dependents, call_dependents +from vyos.utils.process import process_named_running +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_args +from vyos.utils.dict import dict_search_recursive +from vyos.utils.process import cmd +from vyos.utils.process import rc_cmd +from vyos.utils.process import run +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +conntrack_config = r'/etc/modprobe.d/vyatta_nf_conntrack.conf' +sysctl_file = r'/run/sysctl/10-vyos-conntrack.conf' +nftables_ct_file = r'/run/nftables-ct.conf' + +# Every ALG (Application Layer Gateway) consists of either a Kernel Object +# also called a Kernel Module/Driver or some rules present in iptables +module_map = { + 'ftp': { + 'ko': ['nf_nat_ftp', 'nf_conntrack_ftp'], + 'nftables': ['ct helper set "ftp_tcp" tcp dport {21} return'] + }, + 'h323': { + 'ko': ['nf_nat_h323', 'nf_conntrack_h323'], + 'nftables': ['ct helper set "ras_udp" udp dport {1719} return', + 'ct helper set "q931_tcp" tcp dport {1720} return'] + }, + 'nfs': { + 'nftables': ['ct helper set "rpc_tcp" tcp dport {111} return', + 'ct helper set "rpc_udp" udp dport {111} return'] + }, + 'pptp': { + 'ko': ['nf_nat_pptp', 'nf_conntrack_pptp'], + 'nftables': ['ct helper set "pptp_tcp" tcp dport {1723} return'], + 'ipv4': True + }, + 'sip': { + 'ko': ['nf_nat_sip', 'nf_conntrack_sip'], + 'nftables': ['ct helper set "sip_tcp" tcp dport {5060,5061} return', + 'ct helper set "sip_udp" udp dport {5060,5061} return'] + }, + 'sqlnet': { + 'nftables': ['ct helper set "tns_tcp" tcp dport {1521,1525,1536} return'] + }, + 'tftp': { + 'ko': ['nf_nat_tftp', 'nf_conntrack_tftp'], + 'nftables': ['ct helper set "tftp_udp" udp dport {69} return'] + }, +} + +valid_groups = [ + 'address_group', + 'domain_group', + 'network_group', + 'port_group' +] + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'conntrack'] + + conntrack = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + conntrack['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + conntrack['ipv4_nat_action'] = 'accept' if conf.exists(['nat']) else 'return' + conntrack['ipv6_nat_action'] = 'accept' if conf.exists(['nat66']) else 'return' + conntrack['wlb_action'] = 'accept' if conf.exists(['load-balancing', 'wan']) else 'return' + conntrack['wlb_local_action'] = conf.exists(['load-balancing', 'wan', 'enable-local-traffic']) + + conntrack['module_map'] = module_map + + if conf.exists(['service', 'conntrack-sync']): + set_dependents('conntrack_sync', conf) + + return conntrack + +def verify(conntrack): + for inet in ['ipv4', 'ipv6']: + if dict_search_args(conntrack, 'ignore', inet, 'rule') != None: + for rule, rule_config in conntrack['ignore'][inet]['rule'].items(): + if dict_search('destination.port', rule_config) or \ + dict_search('destination.group.port_group', rule_config) or \ + dict_search('source.port', rule_config) or \ + dict_search('source.group.port_group', rule_config): + if 'protocol' not in rule_config or rule_config['protocol'] not in ['tcp', 'udp']: + raise ConfigError(f'Port requires tcp or udp as protocol in rule {rule}') + + tcp_flags = dict_search_args(rule_config, 'tcp', 'flags') + if tcp_flags: + if dict_search_args(rule_config, 'protocol') != 'tcp': + raise ConfigError('Protocol must be tcp when specifying tcp flags') + + not_flags = dict_search_args(rule_config, 'tcp', 'flags', 'not') + if not_flags: + duplicates = [flag for flag in tcp_flags if flag in not_flags] + if duplicates: + raise ConfigError(f'Cannot match a tcp flag as set and not set') + + for side in ['destination', 'source']: + if side in rule_config: + side_conf = rule_config[side] + + if 'group' in side_conf: + if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1: + raise ConfigError('Only one address-group, network-group or domain-group can be specified') + + for group in valid_groups: + if group in side_conf['group']: + group_name = side_conf['group'][group] + error_group = group.replace("_", "-") + + if group in ['address_group', 'network_group', 'domain_group']: + if 'address' in side_conf: + raise ConfigError(f'{error_group} and address cannot both be defined') + + if group_name and group_name[0] == '!': + group_name = group_name[1:] + + if inet == 'ipv6': + group = f'ipv6_{group}' + + group_obj = dict_search_args(conntrack['firewall'], 'group', group, group_name) + + if group_obj is None: + raise ConfigError(f'Invalid {error_group} "{group_name}" on ignore rule') + + if not group_obj: + Warning(f'{error_group} "{group_name}" has no members!') + + if dict_search_args(conntrack, 'timeout', 'custom', inet, 'rule') != None: + for rule, rule_config in conntrack['timeout']['custom'][inet]['rule'].items(): + if 'protocol' not in rule_config: + raise ConfigError(f'Conntrack custom timeout rule {rule} requires protocol tcp or udp') + else: + if 'tcp' in rule_config['protocol'] and 'udp' in rule_config['protocol']: + raise ConfigError(f'conntrack custom timeout rule {rule} - Cant use both tcp and udp protocol') + return None + +def generate(conntrack): + if not os.path.exists(nftables_ct_file): + conntrack['first_install'] = True + + # Determine if conntrack is needed + conntrack['ipv4_firewall_action'] = 'return' + conntrack['ipv6_firewall_action'] = 'return' + + for rules, path in dict_search_recursive(conntrack['firewall'], 'rule'): + if any(('state' in rule_conf or 'connection_status' in rule_conf or 'offload_target' in rule_conf) for rule_conf in rules.values()): + if path[0] == 'ipv4': + conntrack['ipv4_firewall_action'] = 'accept' + elif path[0] == 'ipv6': + conntrack['ipv6_firewall_action'] = 'accept' + + render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.j2', conntrack) + render(sysctl_file, 'conntrack/sysctl.conf.j2', conntrack) + render(nftables_ct_file, 'conntrack/nftables-ct.j2', conntrack) + return None + +def apply(conntrack): + # Depending on the enable/disable state of the ALG (Application Layer Gateway) + # modules we need to either insmod or rmmod the helpers. + + add_modules = [] + rm_modules = [] + + for module, module_config in module_map.items(): + if dict_search_args(conntrack, 'modules', module) is None: + if 'ko' in module_config: + unloaded = [mod for mod in module_config['ko'] if os.path.exists(f'/sys/module/{mod}')] + rm_modules.extend(unloaded) + else: + if 'ko' in module_config: + add_modules.extend(module_config['ko']) + + # Add modules before nftables uses them + if add_modules: + module_str = ' '.join(add_modules) + cmd(f'modprobe -a {module_str}') + + # Load new nftables ruleset + install_result, output = rc_cmd(f'nft -f {nftables_ct_file}') + if install_result == 1: + raise ConfigError(f'Failed to apply configuration: {output}') + + # Remove modules after nftables stops using them + if rm_modules: + module_str = ' '.join(rm_modules) + cmd(f'rmmod {module_str}') + + try: + call_dependents() + except ConfigError: + # Ignore config errors on dependent due to being called too early. Example: + # ConfigError("ConfigError('Interface ethN requires an IP address!')") + pass + + # We silently ignore all errors + # See: https://bugzilla.redhat.com/show_bug.cgi?id=1264080 + cmd(f'sysctl -f {sysctl_file}') + + 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/system_flow-accounting.py b/src/conf_mode/system_flow-accounting.py new file mode 100755 index 000000000..206f513c8 --- /dev/null +++ b/src/conf_mode/system_flow-accounting.py @@ -0,0 +1,320 @@ +#!/usr/bin/env python3 +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import re + +from sys import exit +from ipaddress import ip_address + +from vyos.base import Warning +from vyos.config import Config +from vyos.config import config_dict_merge +from vyos.configverify import verify_vrf +from vyos.ifconfig import Section +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.process import cmd +from vyos.utils.process import run +from vyos.utils.network import is_addr_assigned +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +uacctd_conf_path = '/run/pmacct/uacctd.conf' +systemd_service = 'uacctd.service' +systemd_override = f'/run/systemd/system/{systemd_service}.d/override.conf' +nftables_nflog_table = 'raw' +nftables_nflog_chain = 'VYOS_PREROUTING_HOOK' +egress_nftables_nflog_table = 'inet mangle' +egress_nftables_nflog_chain = 'FORWARD' + +# get nftables rule dict for chain in table +def _nftables_get_nflog(chain, table): + # define list with rules + rules = [] + + # prepare regex for parsing rules + rule_pattern = '[io]ifname "(?P[\w\.\*\-]+)".*handle (?P[\d]+)' + rule_re = re.compile(rule_pattern) + + # run nftables, save output and split it by lines + nftables_command = f'nft -a list chain {table} {chain}' + tmp = cmd(nftables_command, message='Failed to get flows list') + # parse each line and add information to list + for current_rule in tmp.splitlines(): + if 'FLOW_ACCOUNTING_RULE' not in current_rule: + continue + current_rule_parsed = rule_re.search(current_rule) + if current_rule_parsed: + groups = current_rule_parsed.groupdict() + rules.append({ 'interface': groups["interface"], 'table': table, 'handle': groups["handle"] }) + + # return list with rules + return rules + +def _nftables_config(configured_ifaces, direction, length=None): + # define list of nftables commands to modify settings + nftable_commands = [] + nftables_chain = nftables_nflog_chain + nftables_table = nftables_nflog_table + + if direction == "egress": + nftables_chain = egress_nftables_nflog_chain + nftables_table = egress_nftables_nflog_table + + # prepare extended list with configured interfaces + configured_ifaces_extended = [] + for iface in configured_ifaces: + configured_ifaces_extended.append({ 'iface': iface }) + + # get currently configured interfaces with nftables rules + active_nflog_rules = _nftables_get_nflog(nftables_chain, nftables_table) + + # compare current active list with configured one and delete excessive interfaces, add missed + active_nflog_ifaces = [] + for rule in active_nflog_rules: + interface = rule['interface'] + if interface not in configured_ifaces: + table = rule['table'] + handle = rule['handle'] + nftable_commands.append(f'nft delete rule {table} {nftables_chain} handle {handle}') + else: + active_nflog_ifaces.append({ + 'iface': interface, + }) + + # do not create new rules for already configured interfaces + for iface in active_nflog_ifaces: + if iface in active_nflog_ifaces and iface in configured_ifaces_extended: + configured_ifaces_extended.remove(iface) + + # create missed rules + for iface_extended in configured_ifaces_extended: + iface = iface_extended['iface'] + iface_prefix = "o" if direction == "egress" else "i" + rule_definition = f'{iface_prefix}ifname "{iface}" counter log group 2 snaplen {length} queue-threshold 100 comment "FLOW_ACCOUNTING_RULE"' + nftable_commands.append(f'nft insert rule {nftables_table} {nftables_chain} {rule_definition}') + # Also add IPv6 ingres logging + if nftables_table == nftables_nflog_table: + nftable_commands.append(f'nft insert rule ip6 {nftables_table} {nftables_chain} {rule_definition}') + + # change nftables + for command in nftable_commands: + cmd(command, raising=ConfigError) + + +def _nftables_trigger_setup(operation: str) -> None: + """Add a dummy rule to unlock the main pmacct loop with a packet-trigger + + Args: + operation (str): 'add' or 'delete' a trigger + """ + # check if a chain exists + table_exists = False + if run('nft -snj list table ip pmacct') == 0: + table_exists = True + + if operation == 'delete' and table_exists: + nft_cmd: str = 'nft delete table ip pmacct' + cmd(nft_cmd, raising=ConfigError) + if operation == 'add' and not table_exists: + nft_cmds: list[str] = [ + 'nft add table ip pmacct', + 'nft add chain ip pmacct pmacct_out { type filter hook output priority raw - 50 \\; policy accept \\; }', + 'nft add rule ip pmacct pmacct_out oif lo ip daddr 127.0.254.0 counter log group 2 snaplen 1 queue-threshold 0 comment NFLOG_TRIGGER' + ] + for nft_cmd in nft_cmds: + cmd(nft_cmd, raising=ConfigError) + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'flow-accounting'] + if not conf.exists(base): + return None + + flow_accounting = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + # We have gathered the dict representation of the CLI, but there are + # default values which we need to conditionally update into the + # dictionary retrieved. + default_values = conf.get_config_defaults(**flow_accounting.kwargs, + recursive=True) + + # delete individual flow type defaults - should only be added if user + # sets this feature + for flow_type in ['sflow', 'netflow']: + if flow_type not in flow_accounting and flow_type in default_values: + del default_values[flow_type] + + flow_accounting = config_dict_merge(default_values, flow_accounting) + + return flow_accounting + +def verify(flow_config): + if not flow_config: + return None + + # check if at least one collector is enabled + if 'sflow' not in flow_config and 'netflow' not in flow_config and 'disable_imt' in flow_config: + raise ConfigError('You need to configure at least sFlow or NetFlow, ' \ + 'or not set "disable-imt" for flow-accounting!') + + # Check if at least one interface is configured + if 'interface' not in flow_config: + raise ConfigError('Flow accounting requires at least one interface to ' \ + 'be configured!') + + # check that all configured interfaces exists in the system + for interface in flow_config['interface']: + if interface not in Section.interfaces(): + # Changed from error to warning to allow adding dynamic interfaces + # and interface templates + Warning(f'Interface "{interface}" is not presented in the system') + + # check sFlow configuration + if 'sflow' in flow_config: + # check if at least one sFlow collector is configured + if 'server' not in flow_config['sflow']: + raise ConfigError('You need to configure at least one sFlow server!') + + # check that all sFlow collectors use the same IP protocol version + sflow_collector_ipver = None + for server in flow_config['sflow']['server']: + if sflow_collector_ipver: + if sflow_collector_ipver != ip_address(server).version: + 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 + verify_vrf(flow_config) + sflow_vrf = None + if 'vrf' in flow_config: + sflow_vrf = flow_config['vrf'] + + # check agent-id for sFlow: we should avoid mixing IPv4 agent-id with IPv6 collectors and vice-versa + for server in flow_config['sflow']['server']: + if 'agent_address' in flow_config['sflow']: + if ip_address(server).version != ip_address(flow_config['sflow']['agent_address']).version: + raise ConfigError('IPv4 and IPv6 addresses can not be mixed in "sflow agent-address" and "sflow '\ + 'server". You need to set the same IP version for both "agent-address" and '\ + 'all sFlow servers') + + if 'agent_address' in flow_config['sflow']: + tmp = flow_config['sflow']['agent_address'] + if not is_addr_assigned(tmp, sflow_vrf): + raise ConfigError(f'Configured "sflow agent-address {tmp}" does not exist in the system!') + + # Check if configured sflow source-address exist in the system + if 'source_address' in flow_config['sflow']: + if not is_addr_assigned(flow_config['sflow']['source_address'], sflow_vrf): + tmp = flow_config['sflow']['source_address'] + raise ConfigError(f'Configured "sflow source-address {tmp}" does not exist on the system!') + + # check NetFlow configuration + if 'netflow' in flow_config: + # check if vrf is defined for netflow + netflow_vrf = None + if 'vrf' in flow_config: + netflow_vrf = flow_config['vrf'] + + # check if at least one NetFlow collector is configured if NetFlow configuration is presented + if 'server' not in flow_config['netflow']: + raise ConfigError('You need to configure at least one NetFlow server!') + + # Check if configured netflow source-address exist in the system + if 'source_address' in flow_config['netflow']: + if not is_addr_assigned(flow_config['netflow']['source_address'], netflow_vrf): + tmp = flow_config['netflow']['source_address'] + raise ConfigError(f'Configured "netflow source-address {tmp}" does not exist on the system!') + + # Check if engine-id compatible with selected protocol version + if 'engine_id' in flow_config['netflow']: + v5_filter = '^(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]):(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$' + v9v10_filter = '^(\d|[1-9]\d{1,8}|[1-3]\d{9}|4[01]\d{8}|42[0-8]\d{7}|429[0-3]\d{6}|4294[0-8]\d{5}|42949[0-5]\d{4}|429496[0-6]\d{3}|4294967[01]\d{2}|42949672[0-8]\d|429496729[0-5])$' + engine_id = flow_config['netflow']['engine_id'] + version = flow_config['netflow']['version'] + + if flow_config['netflow']['version'] == '5': + regex_filter = re.compile(v5_filter) + if not regex_filter.search(engine_id): + raise ConfigError(f'You cannot use NetFlow engine-id "{engine_id}" '\ + f'together with NetFlow protocol version "{version}"!') + else: + regex_filter = re.compile(v9v10_filter) + if not regex_filter.search(flow_config['netflow']['engine_id']): + raise ConfigError(f'Can not use NetFlow engine-id "{engine_id}" together '\ + f'with NetFlow protocol version "{version}"!') + + # return True if all checks were passed + return True + +def generate(flow_config): + if not flow_config: + return None + + render(uacctd_conf_path, 'pmacct/uacctd.conf.j2', flow_config) + render(systemd_override, 'pmacct/override.conf.j2', flow_config) + # Reload systemd manager configuration + call('systemctl daemon-reload') + +def apply(flow_config): + # Check if flow-accounting was removed and define command + if not flow_config: + _nftables_config([], 'ingress') + _nftables_config([], 'egress') + + # Stop flow-accounting daemon and remove configuration file + call(f'systemctl stop {systemd_service}') + if os.path.exists(uacctd_conf_path): + os.unlink(uacctd_conf_path) + + # must be done after systemctl + _nftables_trigger_setup('delete') + + return + + # Start/reload flow-accounting daemon + call(f'systemctl restart {systemd_service}') + + # configure nftables rules for defined interfaces + if 'interface' in flow_config: + _nftables_config(flow_config['interface'], 'ingress', flow_config['packet_length']) + + # configure egress the same way if configured otherwise remove it + if 'enable_egress' in flow_config: + _nftables_config(flow_config['interface'], 'egress', flow_config['packet_length']) + else: + _nftables_config([], 'egress') + + # add a trigger for signal processing + _nftables_trigger_setup('add') + + +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/system_host-name.py b/src/conf_mode/system_host-name.py new file mode 100755 index 000000000..6204cf247 --- /dev/null +++ b/src/conf_mode/system_host-name.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import re +import sys +import copy + +import vyos.hostsd_client + +from vyos.base import Warning +from vyos.config import Config +from vyos.ifconfig import Section +from vyos.template import is_ip +from vyos.utils.process import cmd +from vyos.utils.process import call +from vyos.utils.process import process_named_running +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +default_config_data = { + 'hostname': 'vyos', + 'domain_name': '', + 'domain_search': [], + 'nameserver': [], + 'nameservers_dhcp_interfaces': {}, + 'static_host_mapping': {} +} + +hostsd_tag = 'system' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + hosts = copy.deepcopy(default_config_data) + + hosts['hostname'] = conf.return_value(['system', 'host-name']) + + # This may happen if the config is not loaded yet, + # e.g. if run by cloud-init + if not hosts['hostname']: + hosts['hostname'] = default_config_data['hostname'] + + if conf.exists(['system', 'domain-name']): + hosts['domain_name'] = conf.return_value(['system', 'domain-name']) + hosts['domain_search'].append(hosts['domain_name']) + + if conf.exists(['system', 'domain-search']): + for search in conf.return_values(['system', 'domain-search']): + hosts['domain_search'].append(search) + + if conf.exists(['system', 'name-server']): + for ns in conf.return_values(['system', 'name-server']): + if is_ip(ns): + hosts['nameserver'].append(ns) + else: + tmp = '' + if_type = Section.section(ns) + if conf.exists(['interfaces', if_type, ns, 'address']): + tmp = conf.return_values(['interfaces', if_type, ns, 'address']) + + hosts['nameservers_dhcp_interfaces'].update({ ns : tmp }) + + # system static-host-mapping + for hn in conf.list_nodes(['system', 'static-host-mapping', 'host-name']): + hosts['static_host_mapping'][hn] = {} + hosts['static_host_mapping'][hn]['address'] = conf.return_values(['system', 'static-host-mapping', 'host-name', hn, 'inet']) + hosts['static_host_mapping'][hn]['aliases'] = conf.return_values(['system', 'static-host-mapping', 'host-name', hn, 'alias']) + + return hosts + + +def verify(hosts): + if hosts is None: + return None + + # pattern $VAR(@) "^[[:alnum:]][-.[:alnum:]]*[[:alnum:]]$" ; "invalid host name $VAR(@)" + hostname_regex = re.compile("^[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]$") + if not hostname_regex.match(hosts['hostname']): + raise ConfigError('Invalid host name ' + hosts["hostname"]) + + # pattern $VAR(@) "^.{1,63}$" ; "invalid host-name length" + length = len(hosts['hostname']) + if length < 1 or length > 63: + raise ConfigError( + 'Invalid host-name length, must be less than 63 characters') + + all_static_host_mapping_addresses = [] + # static mappings alias hostname + for host, hostprops in hosts['static_host_mapping'].items(): + if not hostprops['address']: + raise ConfigError(f'IP address required for static-host-mapping "{host}"') + all_static_host_mapping_addresses.append(hostprops['address']) + for a in hostprops['aliases']: + if not hostname_regex.match(a) and len(a) != 0: + raise ConfigError(f'Invalid alias "{a}" in static-host-mapping "{host}"') + + for interface, interface_config in hosts['nameservers_dhcp_interfaces'].items(): + # Warnin user if interface does not have DHCP or DHCPv6 configured + if not set(interface_config).intersection(['dhcp', 'dhcpv6']): + Warning(f'"{interface}" is not a DHCP interface but uses DHCP name-server option!') + + return None + + +def generate(config): + pass + +def apply(config): + if config is None: + return None + + ## Send the updated data to vyos-hostsd + try: + hc = vyos.hostsd_client.Client() + + hc.set_host_name(config['hostname'], config['domain_name']) + + hc.delete_search_domains([hostsd_tag]) + if config['domain_search']: + hc.add_search_domains({hostsd_tag: config['domain_search']}) + + hc.delete_name_servers([hostsd_tag]) + if config['nameserver']: + hc.add_name_servers({hostsd_tag: config['nameserver']}) + + # add our own tag's (system) nameservers and search to resolv.conf + hc.delete_name_server_tags_system(hc.get_name_server_tags_system()) + hc.add_name_server_tags_system([hostsd_tag]) + + # this will add the dhcp client nameservers to resolv.conf + for intf in config['nameservers_dhcp_interfaces']: + hc.add_name_server_tags_system([f'dhcp-{intf}', f'dhcpv6-{intf}']) + + hc.delete_hosts([hostsd_tag]) + if config['static_host_mapping']: + hc.add_hosts({hostsd_tag: config['static_host_mapping']}) + + hc.apply() + except vyos.hostsd_client.VyOSHostsdError as e: + raise ConfigError(str(e)) + + ## Actually update the hostname -- vyos-hostsd doesn't do that + + # No domain name -- the Debian way. + hostname_new = config['hostname'] + + # rsyslog runs into a race condition at boot time with systemd + # restart rsyslog only if the hostname changed. + hostname_old = cmd('hostnamectl --static') + call(f'hostnamectl set-hostname --static {hostname_new}') + + # Restart services that use the hostname + if hostname_new != hostname_old: + call("systemctl restart rsyslog.service") + + # If SNMP is running, restart it too + if process_named_running('snmpd'): + call('systemctl restart snmpd.service') + + return None + + +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/system_ip.py b/src/conf_mode/system_ip.py new file mode 100755 index 000000000..7612e2c0d --- /dev/null +++ b/src/conf_mode/system_ip.py @@ -0,0 +1,143 @@ +#!/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 . + +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.utils.dict import dict_search +from vyos.utils.file import write_file +from vyos.utils.process import call +from vyos.utils.process import is_systemd_service_active +from vyos.utils.system import sysctl_write + +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 = ['system', 'ip'] + + opt = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + # 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): + 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): + opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt) + return + +def apply(opt): + # Apply ARP threshold values + # table_size has a default value - thus the key always exists + size = int(dict_search('arp.table_size', opt)) + # Amount upon reaching which the records begin to be cleared immediately + sysctl_write('net.ipv4.neigh.default.gc_thresh3', size) + # Amount after which the records begin to be cleaned after 5 seconds + sysctl_write('net.ipv4.neigh.default.gc_thresh2', size // 2) + # Minimum number of stored records is indicated which is not cleared + sysctl_write('net.ipv4.neigh.default.gc_thresh1', size // 8) + + # enable/disable IPv4 forwarding + tmp = dict_search('disable_forwarding', opt) + value = '0' if (tmp != None) else '1' + write_file('/proc/sys/net/ipv4/conf/all/forwarding', value) + + # enable/disable IPv4 directed broadcast forwarding + tmp = dict_search('disable_directed_broadcast', opt) + value = '0' if (tmp != None) else '1' + write_file('/proc/sys/net/ipv4/conf/all/bc_forwarding', value) + + # configure multipath + tmp = dict_search('multipath.ignore_unreachable_nexthops', opt) + value = '1' if (tmp != None) else '0' + sysctl_write('net.ipv4.fib_multipath_use_neigh', value) + + tmp = dict_search('multipath.layer4_hashing', opt) + value = '1' if (tmp != None) else '0' + sysctl_write('net.ipv4.fib_multipath_hash_policy', value) + + # configure TCP options (defaults as of Linux 6.4) + tmp = dict_search('tcp.mss.probing', opt) + if tmp is None: + value = 0 + elif tmp == 'on-icmp-black-hole': + value = 1 + elif tmp == 'force': + value = 2 + else: + # Shouldn't happen + raise ValueError("TCP MSS probing is neither 'on-icmp-black-hole' nor 'force'!") + sysctl_write('net.ipv4.tcp_mtu_probing', value) + + tmp = dict_search('tcp.mss.base', opt) + value = '1024' if (tmp is None) else tmp + sysctl_write('net.ipv4.tcp_base_mss', value) + + tmp = dict_search('tcp.mss.floor', opt) + value = '48' if (tmp is None) else tmp + sysctl_write('net.ipv4.tcp_mtu_probe_floor', value) + + # During startup of vyos-router that brings up FRR, the service is not yet + # running when this script is called first. Skip this part and wait for initial + # commit of the configuration to trigger this statement + if is_systemd_service_active('frr.service'): + 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() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_ipv6.py b/src/conf_mode/system_ipv6.py new file mode 100755 index 000000000..90a1a8087 --- /dev/null +++ b/src/conf_mode/system_ipv6.py @@ -0,0 +1,120 @@ +#!/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 . + +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.utils.dict import dict_search +from vyos.utils.file import write_file +from vyos.utils.process import is_systemd_service_active +from vyos.utils.system import sysctl_write +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 = ['system', 'ipv6'] + + opt = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + # 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): + 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): + opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt) + return + +def apply(opt): + # configure multipath + tmp = dict_search('multipath.layer4_hashing', opt) + value = '1' if (tmp != None) else '0' + sysctl_write('net.ipv6.fib_multipath_hash_policy', value) + + # Apply ND threshold values + # table_size has a default value - thus the key always exists + size = int(dict_search('neighbor.table_size', opt)) + # Amount upon reaching which the records begin to be cleared immediately + sysctl_write('net.ipv6.neigh.default.gc_thresh3', size) + # Amount after which the records begin to be cleaned after 5 seconds + sysctl_write('net.ipv6.neigh.default.gc_thresh2', size // 2) + # Minimum number of stored records is indicated which is not cleared + sysctl_write('net.ipv6.neigh.default.gc_thresh1', size // 8) + + # enable/disable IPv6 forwarding + tmp = dict_search('disable_forwarding', opt) + value = '0' if (tmp != None) else '1' + write_file('/proc/sys/net/ipv6/conf/all/forwarding', value) + + # configure IPv6 strict-dad + tmp = dict_search('strict_dad', opt) + value = '2' if (tmp != None) else '1' + for root, dirs, files in os.walk('/proc/sys/net/ipv6/conf'): + for name in files: + if name == 'accept_dad': + write_file(os.path.join(root, name), value) + + # During startup of vyos-router that brings up FRR, the service is not yet + # running when this script is called first. Skip this part and wait for initial + # commit of the configuration to trigger this statement + if is_systemd_service_active('frr.service'): + 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() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_login.py b/src/conf_mode/system_login.py new file mode 100755 index 000000000..f34575aff --- /dev/null +++ b/src/conf_mode/system_login.py @@ -0,0 +1,423 @@ +#!/usr/bin/env python3 +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from passlib.hosts import linux_context +from psutil import users +from pwd import getpwall +from pwd import getpwnam +from sys import exit +from time import sleep + +from vyos.config import Config +from vyos.configverify import verify_vrf +from vyos.defaults import directories +from vyos.template import render +from vyos.template import is_ipv4 +from vyos.utils.dict import dict_search +from vyos.utils.file import chown +from vyos.utils.process import cmd +from vyos.utils.process import call +from vyos.utils.process import rc_cmd +from vyos.utils.process import run +from vyos.utils.process import DEVNULL +from vyos import ConfigError +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" +tacacs_pam_config_file = "/etc/tacplus_servers" +tacacs_nss_config_file = "/etc/tacplus_nss.conf" +nss_config_file = "/etc/nsswitch.conf" + +# Minimum UID used when adding system users +MIN_USER_UID: int = 1000 +# Maximim UID used when adding system users +MAX_USER_UID: int = 59999 +# 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 = 8 +# Maximum number of supported TACACS servers +MAX_TACACS_COUNT: int = 8 + +# List of local user accounts that must be preserved +SYSTEM_USER_SKIP_LIST: list = ['radius_user', 'radius_priv_user', 'tacacs0', 'tacacs1', + 'tacacs2', 'tacacs3', 'tacacs4', 'tacacs5', 'tacacs6', + 'tacacs7', 'tacacs8', 'tacacs9', 'tacacs10',' tacacs11', + 'tacacs12', 'tacacs13', 'tacacs14', 'tacacs15'] + +def get_local_users(): + """Return list of dynamically allocated users (see Debian Policy Manual)""" + local_users = [] + for s_user in getpwall(): + if getpwnam(s_user.pw_name).pw_uid < MIN_USER_UID: + continue + if getpwnam(s_user.pw_name).pw_uid > MAX_USER_UID: + continue + if s_user.pw_name in SYSTEM_USER_SKIP_LIST: + continue + local_users.append(s_user.pw_name) + + 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: + conf = config + else: + conf = Config() + base = ['system', 'login'] + login = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + # users no longer existing in the running configuration need to be deleted + local_users = get_local_users() + cli_users = [] + if 'user' in login: + cli_users = list(login['user']) + + # prune TACACS global defaults if not set by user + if login.from_defaults(['tacacs']): + del login['tacacs'] + # same for RADIUS + if login.from_defaults(['radius']): + del login['radius'] + + # create a list of all users, cli and users + all_users = list(set(local_users + cli_users)) + # We will remove any normal users that dos not exist in the current + # configuration. This can happen if user is added but configuration was not + # saved and system is rebooted. + rm_users = [tmp for tmp in all_users if tmp not in cli_users] + if rm_users: login.update({'rm_users' : rm_users}) + + return login + +def verify(login): + if 'rm_users' in login: + # This check is required as the script is also executed from vyos-router + # init script and there is no SUDO_USER environment variable available + # during system boot. + if 'SUDO_USER' in os.environ: + cur_user = os.environ['SUDO_USER'] + if cur_user in login['rm_users']: + raise ConfigError(f'Attempting to delete current user: {cur_user}') + + if 'user' in login: + system_users = getpwall() + for user, user_config in login['user'].items(): + # Linux system users range up until UID 1000, we can not create a + # VyOS CLI user which already exists as system user + for s_user in system_users: + if s_user.pw_name == user and s_user.pw_uid < MIN_USER_UID: + raise ConfigError(f'User "{user}" can not be created, conflict with local system account!') + + for pubkey, pubkey_options in (dict_search('authentication.public_keys', user_config) or {}).items(): + if 'type' not in pubkey_options: + raise ConfigError(f'Missing type for public-key "{pubkey}"!') + if 'key' not in pubkey_options: + raise ConfigError(f'Missing key for public-key "{pubkey}"!') + + if {'radius', 'tacacs'} <= set(login): + raise ConfigError('Using both RADIUS and TACACS at the same time is not supported!') + + # At lease one RADIUS server must not be disabled + 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 'disable' not in server_config: + sum_timeout += int(server_config['timeout']) + radius_servers_count += 1 + fail = False + + if fail: + raise ConfigError('All RADIUS servers are disabled') + + if radius_servers_count > MAX_RADIUS_COUNT: + raise ConfigError(f'Number of RADIUS servers exceeded maximum of {MAX_RADIUS_COUNT}!') + + 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']: + ipv4_count = 0 + ipv6_count = 0 + for address in login['radius']['source_address']: + if is_ipv4(address): ipv4_count += 1 + else: ipv6_count += 1 + + if ipv4_count > 1: + raise ConfigError('Only one IPv4 source-address can be set!') + if ipv6_count > 1: + raise ConfigError('Only one IPv6 source-address can be set!') + + if 'tacacs' in login: + tacacs_servers_count: int = 0 + fail = True + for server, server_config in dict_search('tacacs.server', login).items(): + if 'key' not in server_config: + raise ConfigError(f'TACACS server "{server}" requires key!') + if 'disable' not in server_config: + tacacs_servers_count += 1 + fail = False + + if fail: + raise ConfigError('All RADIUS servers are disabled') + + if tacacs_servers_count > MAX_TACACS_COUNT: + raise ConfigError(f'Number of TACACS servers exceeded maximum of {MAX_TACACS_COUNT}!') + + verify_vrf(login['tacacs']) + + if 'max_login_session' in login and 'timeout' not in login: + raise ConfigError('"login timeout" must be configured!') + + return None + + +def generate(login): + # calculate users encrypted password + if 'user' in login: + for user, user_config in login['user'].items(): + tmp = dict_search('authentication.plaintext_password', user_config) + if tmp: + 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'] = directories['base'] + + # Set default commands for re-adding user with encrypted password + del_user_plain = f"system login user {user} authentication plaintext-password" + add_user_encrypt = f"system login user {user} authentication encrypted-password '{encrypted_password}'" + + lvl = env['VYATTA_EDIT_LEVEL'] + # We're in config edit level, for example "edit system login" + # Change default commands for re-adding user with encrypted password + if lvl != '/': + # Replace '/system/login' to 'system login' + lvl = lvl.strip('/').split('/') + # Convert command str to list + del_user_plain = del_user_plain.split() + # New command exclude level, for example "edit system login" + del_user_plain = del_user_plain[len(lvl):] + # Convert string to list + del_user_plain = " ".join(del_user_plain) + + add_user_encrypt = add_user_encrypt.split() + add_user_encrypt = add_user_encrypt[len(lvl):] + add_user_encrypt = " ".join(add_user_encrypt) + + ret, out = rc_cmd(f"/opt/vyatta/sbin/my_delete {del_user_plain}", env=env) + if ret: raise ConfigError(out) + ret, out = rc_cmd(f"/opt/vyatta/sbin/my_set {add_user_encrypt}", env=env) + if ret: raise ConfigError(out) + else: + try: + 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. + # + # The encrypted password will be set only once during the first boot + # after an image upgrade. + del login['user'][user]['authentication']['encrypted_password'] + except: + pass + + ### RADIUS based user authentication + if 'radius' in login: + render(radius_config_file, 'login/pam_radius_auth.conf.j2', login, + permission=0o600, user='root', group='root') + else: + if os.path.isfile(radius_config_file): + os.unlink(radius_config_file) + + ### TACACS+ based user authentication + if 'tacacs' in login: + render(tacacs_pam_config_file, 'login/tacplus_servers.j2', login, + permission=0o644, user='root', group='root') + render(tacacs_nss_config_file, 'login/tacplus_nss.conf.j2', login, + permission=0o644, user='root', group='root') + else: + if os.path.isfile(tacacs_pam_config_file): + os.unlink(tacacs_pam_config_file) + if os.path.isfile(tacacs_nss_config_file): + os.unlink(tacacs_nss_config_file) + + + + # NSS must always be present on the system + render(nss_config_file, 'login/nsswitch.conf.j2', login, + permission=0o644, user='root', group='root') + + # /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') + else: + if os.path.isfile(autologout_file): + os.unlink(autologout_file) + + return None + + +def apply(login): + enable_otp = False + if 'user' in login: + for user, user_config in login['user'].items(): + # make new user using vyatta shell and make home directory (-m), + # default group of 100 (users) + command = 'useradd --create-home --no-user-group ' + # check if user already exists: + if user in get_local_users(): + # update existing account + command = 'usermod' + + # all accounts use /bin/vbash + command += ' --shell /bin/vbash' + # we need to use '' quotes when passing formatted data to the shell + # else it will not work as some data parts are lost in translation + tmp = dict_search('authentication.encrypted_password', user_config) + if tmp: command += f" --password '{tmp}'" + + tmp = dict_search('full_name', user_config) + if tmp: command += f" --comment '{tmp}'" + + tmp = dict_search('home_directory', user_config) + if tmp: command += f" --home '{tmp}'" + else: command += f" --home '/home/{user}'" + + command += f' --groups frr,frrvty,vyattacfg,sudo,adm,dip,disk,_kea {user}' + try: + cmd(command) + # we should not rely on the value stored in + # user_config['home_directory'], as a crazy user will choose + # username root or any other system user which will fail. + # + # XXX: Should we deny using root at all? + home_dir = getpwnam(user).pw_dir + # T5875: ensure UID is properly set on home directory if user is re-added + if os.path.exists(home_dir): + chown(home_dir, user=user, recursive=True) + + render(f'{home_dir}/.ssh/authorized_keys', 'login/authorized_keys.j2', + user_config, permission=0o600, + formater=lambda _: _.replace(""", '"'), + user=user, group='users') + + except Exception as e: + raise ConfigError(f'Adding user "{user}" raised exception: "{e}"') + + # Generate 2FA/MFA One-Time-Pad configuration + if dict_search('authentication.otp.key', user_config): + enable_otp = True + render(f'{home_dir}/.google_authenticator', 'login/pam_otp_ga.conf.j2', + user_config, permission=0o400, user=user, group='users') + else: + # delete configuration as it's not enabled for the user + if os.path.exists(f'{home_dir}/.google_authenticator'): + os.remove(f'{home_dir}/.google_authenticator') + + if 'rm_users' in login: + for user in login['rm_users']: + try: + # Disable user to prevent re-login + call(f'usermod -s /sbin/nologin {user}') + + # Logout user if he is still logged in + if user in list(set([tmp[0] for tmp in users()])): + print(f'{user} is logged in, forcing logout!') + # re-run command until user is logged out + while run(f'pkill -HUP -u {user}'): + sleep(0.250) + + # Remove user account but leave home directory in place. Re-run + # command until user is removed - userdel might return 8 as + # SSH sessions are not all yet properly cleaned away, thus we + # simply re-run the command until the account wen't away + while run(f'userdel {user}', stderr=DEVNULL): + sleep(0.250) + + except Exception as e: + raise ConfigError(f'Deleting user "{user}" raised exception: {e}') + + # Enable/disable RADIUS in PAM configuration + cmd('pam-auth-update --disable radius-mandatory radius-optional') + if 'radius' in login: + if login['radius'].get('security_mode', '') == 'mandatory': + pam_profile = 'radius-mandatory' + else: + pam_profile = 'radius-optional' + cmd(f'pam-auth-update --enable {pam_profile}') + + # Enable/disable TACACS+ in PAM configuration + cmd('pam-auth-update --disable tacplus-mandatory tacplus-optional') + if 'tacacs' in login: + if login['tacacs'].get('security_mode', '') == 'mandatory': + pam_profile = 'tacplus-mandatory' + else: + pam_profile = 'tacplus-optional' + cmd(f'pam-auth-update --enable {pam_profile}') + + # Enable/disable Google authenticator + cmd('pam-auth-update --disable mfa-google-authenticator') + if enable_otp: + cmd(f'pam-auth-update --enable mfa-google-authenticator') + + 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/system_login_banner.py b/src/conf_mode/system_login_banner.py new file mode 100755 index 000000000..65fa04417 --- /dev/null +++ b/src/conf_mode/system_login_banner.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from sys import exit +from copy import deepcopy + +from vyos.config import Config +from vyos.utils.file import write_file +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +try: + with open('/usr/share/vyos/default_motd') as f: + motd = f.read() +except: + # Use an empty banner if the default banner file cannot be read + motd = "\n" + +PRELOGIN_FILE = r'/etc/issue' +PRELOGIN_NET_FILE = r'/etc/issue.net' +POSTLOGIN_FILE = r'/etc/motd' + +default_config_data = { + 'issue': 'Welcome to VyOS - \\n \\l\n\n', + 'issue_net': '', + 'motd': motd +} + +def get_config(config=None): + banner = deepcopy(default_config_data) + if config: + conf = config + else: + conf = Config() + base_level = ['system', 'login', 'banner'] + + if not conf.exists(base_level): + return banner + else: + conf.set_level(base_level) + + # Post-Login banner + if conf.exists(['post-login']): + tmp = conf.return_value(['post-login']) + # post-login banner can be empty as well + if tmp: + tmp = tmp.replace('\\n','\n') + tmp = tmp.replace('\\t','\t') + # always add newline character + tmp += '\n' + else: + tmp = '' + + banner['motd'] = tmp + + # Pre-Login banner + if conf.exists(['pre-login']): + tmp = conf.return_value(['pre-login']) + # pre-login banner can be empty as well + if tmp: + tmp = tmp.replace('\\n','\n') + tmp = tmp.replace('\\t','\t') + # always add newline character + tmp += '\n' + else: + tmp = '' + + banner['issue'] = banner['issue_net'] = tmp + + return banner + +def verify(banner): + pass + +def generate(banner): + pass + +def apply(banner): + write_file(PRELOGIN_FILE, banner['issue']) + write_file(PRELOGIN_NET_FILE, banner['issue_net']) + write_file(POSTLOGIN_FILE, banner['motd']) + + 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/system_logs.py b/src/conf_mode/system_logs.py new file mode 100755 index 000000000..8ad4875d4 --- /dev/null +++ b/src/conf_mode/system_logs.py @@ -0,0 +1,79 @@ +#!/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 . + +from sys import exit + +from vyos import ConfigError +from vyos import airbag +from vyos.config import Config +from vyos.logger import syslog +from vyos.template import render +from vyos.utils.dict import dict_search +airbag.enable() + +# path to logrotate configs +logrotate_atop_file = '/etc/logrotate.d/vyos-atop' +logrotate_rsyslog_file = '/etc/logrotate.d/vyos-rsyslog' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['system', 'logs'] + logs_config = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + return logs_config + + +def verify(logs_config): + # Nothing to verify here + pass + + +def generate(logs_config): + # get configuration for logrotate atop + logrotate_atop = dict_search('logrotate.atop', logs_config) + # generate new config file for atop + syslog.debug('Adding logrotate config for atop') + render(logrotate_atop_file, 'logs/logrotate/vyos-atop.j2', logrotate_atop) + + # get configuration for logrotate rsyslog + logrotate_rsyslog = dict_search('logrotate.messages', logs_config) + # generate new config file for rsyslog + syslog.debug('Adding logrotate config for rsyslog') + render(logrotate_rsyslog_file, 'logs/logrotate/vyos-rsyslog.j2', + logrotate_rsyslog) + + +def apply(logs_config): + # No further actions needed + pass + + +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/system_option.py b/src/conf_mode/system_option.py new file mode 100755 index 000000000..d92121b3d --- /dev/null +++ b/src/conf_mode/system_option.py @@ -0,0 +1,159 @@ +#!/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 . + +import os + +from netifaces import interfaces +from sys import exit +from time import sleep + +from vyos.config import Config +from vyos.configverify import verify_source_interface +from vyos.template import render +from vyos.utils.process import cmd +from vyos.utils.process import is_systemd_service_running +from vyos.utils.network import is_addr_assigned +from vyos.utils.network import is_intf_addr_assigned +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +curlrc_config = r'/etc/curlrc' +ssh_config = r'/etc/ssh/ssh_config.d/91-vyos-ssh-client-options.conf' +systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target' +time_format_to_locale = { + '12-hour': 'en_US.UTF-8', + '24-hour': 'en_GB.UTF-8' +} + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'option'] + options = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + return options + +def verify(options): + if 'http_client' in options: + config = options['http_client'] + if 'source_interface' in config: + if not config['source_interface'] in interfaces(): + raise ConfigError(f'Source interface {source_interface} does not ' + f'exist'.format(**config)) + + if {'source_address', 'source_interface'} <= set(config): + raise ConfigError('Can not define both HTTP source-interface and source-address') + + if 'source_address' in config: + if not is_addr_assigned(config['source_address']): + raise ConfigError('No interface with give address specified!') + + if 'ssh_client' in options: + config = options['ssh_client'] + if 'source_address' in config: + address = config['source_address'] + if not is_addr_assigned(config['source_address']): + 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 + +def generate(options): + render(curlrc_config, 'system/curlrc.j2', options) + render(ssh_config, 'system/ssh_config.j2', options) + return None + +def apply(options): + # System bootup beep + if 'startup_beep' in options: + cmd('systemctl enable vyos-beep.service') + else: + cmd('systemctl disable vyos-beep.service') + + # Ctrl-Alt-Delete action + if os.path.exists(systemd_action_file): + os.unlink(systemd_action_file) + if 'ctrl_alt_delete' in options: + if options['ctrl_alt_delete'] == 'reboot': + os.symlink('/lib/systemd/system/reboot.target', systemd_action_file) + elif options['ctrl_alt_delete'] == 'poweroff': + os.symlink('/lib/systemd/system/poweroff.target', systemd_action_file) + + # Configure HTTP client + if 'http_client' not in options: + if os.path.exists(curlrc_config): + os.unlink(curlrc_config) + + # Configure SSH client + if 'ssh_client' not in options: + if os.path.exists(ssh_config): + os.unlink(ssh_config) + + # Reboot system on kernel panic + timeout = '0' + if 'reboot_on_panic' in options: + timeout = '60' + with open('/proc/sys/kernel/panic', 'w') as f: + f.write(timeout) + + # tuned - performance tuning + if 'performance' in options: + cmd('systemctl restart tuned.service') + # wait until daemon has started before sending configuration + while (not is_systemd_service_running('tuned.service')): + sleep(0.250) + cmd('tuned-adm profile network-{performance}'.format(**options)) + else: + cmd('systemctl stop tuned.service') + + # Keyboard layout - there will be always the default key inside the dict + # but we check for key existence anyway + if 'keyboard_layout' in options: + cmd('loadkeys {keyboard_layout}'.format(**options)) + + # Enable/diable root-partition-auto-resize SystemD service + if 'root_partition_auto_resize' in options: + cmd('systemctl enable root-partition-auto-resize.service') + else: + cmd('systemctl disable root-partition-auto-resize.service') + + # Time format 12|24-hour + if 'time_format' in options: + time_format = time_format_to_locale.get(options['time_format']) + cmd(f'localectl set-locale LC_TIME={time_format}') + +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/system_proxy.py b/src/conf_mode/system_proxy.py new file mode 100755 index 000000000..079c43e7e --- /dev/null +++ b/src/conf_mode/system_proxy.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from sys import exit + +from vyos.config import Config +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +proxy_def = r'/etc/profile.d/vyos-system-proxy.sh' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'proxy'] + if not conf.exists(base): + return None + + proxy = conf.get_config_dict(base, get_first_key=True) + return proxy + +def verify(proxy): + if not proxy: + return + + if 'url' not in proxy or 'port' not in proxy: + raise ConfigError('Proxy URL and port require a value') + + if ('username' in proxy and 'password' not in proxy) or \ + ('username' not in proxy and 'password' in proxy): + raise ConfigError('Both username and password need to be defined!') + +def generate(proxy): + if not proxy: + if os.path.isfile(proxy_def): + os.unlink(proxy_def) + return + + render(proxy_def, 'system/proxy.j2', proxy, permission=0o755) + +def apply(proxy): + pass + +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/system_syslog.py b/src/conf_mode/system_syslog.py new file mode 100755 index 000000000..07fbb0734 --- /dev/null +++ b/src/conf_mode/system_syslog.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configdict import is_node_changed +from vyos.configverify import verify_vrf +from vyos.utils.process import call +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +rsyslog_conf = '/etc/rsyslog.d/00-vyos.conf' +logrotate_conf = '/etc/logrotate.d/vyos-rsyslog' +systemd_override = r'/run/systemd/system/rsyslog.service.d/override.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'syslog'] + if not conf.exists(base): + return None + + syslog = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + syslog.update({ 'logrotate' : logrotate_conf }) + + tmp = is_node_changed(conf, base + ['vrf']) + if tmp: syslog.update({'restart_required': {}}) + + syslog = conf.merge_defaults(syslog, recursive=True) + if syslog.from_defaults(['global']): + del syslog['global'] + + return syslog + +def verify(syslog): + if not syslog: + return None + + verify_vrf(syslog) + +def generate(syslog): + if not syslog: + if os.path.exists(rsyslog_conf): + os.unlink(rsyslog_conf) + if os.path.exists(logrotate_conf): + os.unlink(logrotate_conf) + + return None + + render(rsyslog_conf, 'rsyslog/rsyslog.conf.j2', syslog) + render(systemd_override, 'rsyslog/override.conf.j2', syslog) + render(logrotate_conf, 'rsyslog/logrotate.j2', syslog) + + # Reload systemd manager configuration + call('systemctl daemon-reload') + return None + +def apply(syslog): + systemd_socket = 'syslog.socket' + systemd_service = 'syslog.service' + if not syslog: + call(f'systemctl stop {systemd_service} {systemd_socket}') + return None + + # we need to restart the service if e.g. the VRF name changed + systemd_action = 'reload-or-restart' + if 'restart_required' in syslog: + systemd_action = 'restart' + + call(f'systemctl {systemd_action} {systemd_service}') + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_task-scheduler.py b/src/conf_mode/system_task-scheduler.py new file mode 100755 index 000000000..129be5d3c --- /dev/null +++ b/src/conf_mode/system_task-scheduler.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2017 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# + +import os +import re +import sys + +from vyos.config import Config +from vyos import ConfigError + +from vyos import airbag +airbag.enable() + +crontab_file = "/etc/cron.d/vyos-crontab" + + +def format_task(minute="*", hour="*", day="*", dayofweek="*", month="*", user="root", rawspec=None, command=""): + fmt_full = "{minute} {hour} {day} {month} {dayofweek} {user} {command}\n" + fmt_raw = "{spec} {user} {command}\n" + + if rawspec is None: + s = fmt_full.format(minute=minute, hour=hour, day=day, + dayofweek=dayofweek, month=month, command=command, user=user) + else: + s = fmt_raw.format(spec=rawspec, user=user, command=command) + + return s + +def split_interval(s): + result = re.search(r"(\d+)([mdh]?)", s) + value = int(result.group(1)) + suffix = result.group(2) + return( (value, suffix) ) + +def make_command(executable, arguments): + if arguments: + return("sg vyattacfg \"{0} {1}\"".format(executable, arguments)) + else: + return("sg vyattacfg \"{0}\"".format(executable)) + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + conf.set_level("system task-scheduler task") + task_names = conf.list_nodes("") + tasks = [] + + for name in task_names: + interval = conf.return_value("{0} interval".format(name)) + spec = conf.return_value("{0} crontab-spec".format(name)) + executable = conf.return_value("{0} executable path".format(name)) + args = conf.return_value("{0} executable arguments".format(name)) + task = { + "name": name, + "interval": interval, + "spec": spec, + "executable": executable, + "args": args + } + tasks.append(task) + + return tasks + +def verify(tasks): + for task in tasks: + if not task["interval"] and not task["spec"]: + raise ConfigError("Invalid task {0}: must define either interval or crontab-spec".format(task["name"])) + + if task["interval"]: + if task["spec"]: + raise ConfigError("Invalid task {0}: cannot use interval and crontab-spec at the same time".format(task["name"])) + + if not re.match(r"^\d+[mdh]?$", task["interval"]): + raise(ConfigError("Invalid interval {0} in task {1}: interval should be a number optionally followed by m, h, or d".format(task["name"], task["interval"]))) + else: + # Check if values are within allowed range + value, suffix = split_interval(task["interval"]) + + if not suffix or suffix == "m": + if value > 60: + raise ConfigError("Invalid task {0}: interval in minutes must not exceed 60".format(task["name"])) + elif suffix == "h": + if value > 24: + raise ConfigError("Invalid task {0}: interval in hours must not exceed 24".format(task["name"])) + elif suffix == "d": + if value > 31: + raise ConfigError("Invalid task {0}: interval in days must not exceed 31".format(task["name"])) + + if not task["executable"]: + raise ConfigError("Invalid task {0}: executable is not defined".format(task["name"])) + else: + # Check if executable exists and is executable + if not (os.path.isfile(task["executable"]) and os.access(task["executable"], os.X_OK)): + raise ConfigError("Invalid task {0}: file {1} does not exist or is not executable".format(task["name"], task["executable"])) + +def generate(tasks): + crontab_header = "### Generated by vyos-update-crontab.py ###\n" + if len(tasks) == 0: + if os.path.exists(crontab_file): + os.remove(crontab_file) + else: + pass + else: + crontab_lines = [] + for task in tasks: + command = make_command(task["executable"], task["args"]) + if task["spec"]: + line = format_task(command=command, rawspec=task["spec"]) + else: + value, suffix = split_interval(task["interval"]) + if not suffix or suffix == "m": + line = format_task(command=command, minute="*/{0}".format(value)) + elif suffix == "h": + line = format_task(command=command, minute="0", hour="*/{0}".format(value)) + elif suffix == "d": + line = format_task(command=command, minute="0", hour="0", day="*/{0}".format(value)) + crontab_lines.append(line) + + with open(crontab_file, 'w') as f: + f.write(crontab_header) + f.writelines(crontab_lines) + +def apply(config): + # No daemon restarts etc. needed for cron + pass + + +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/system_timezone.py b/src/conf_mode/system_timezone.py new file mode 100755 index 000000000..cd3d4b229 --- /dev/null +++ b/src/conf_mode/system_timezone.py @@ -0,0 +1,61 @@ +#!/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 . + +import sys +import os + +from copy import deepcopy +from vyos.config import Config +from vyos import ConfigError +from vyos.utils.process import call + +from vyos import airbag +airbag.enable() + +default_config_data = { + 'name': 'UTC' +} + +def get_config(config=None): + tz = deepcopy(default_config_data) + if config: + conf = config + else: + conf = Config() + if conf.exists('system time-zone'): + tz['name'] = conf.return_value('system time-zone') + + return tz + +def verify(tz): + pass + +def generate(tz): + pass + +def apply(tz): + call('/usr/bin/timedatectl set-timezone {}'.format(tz['name'])) + call('systemctl restart rsyslog') + +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/system_update-check.py b/src/conf_mode/system_update-check.py new file mode 100755 index 000000000..8d641a97d --- /dev/null +++ b/src/conf_mode/system_update-check.py @@ -0,0 +1,93 @@ +#!/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 . + +import os +import json +import jmespath + +from pathlib import Path +from sys import exit + +from vyos.config import Config +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + + +base = ['system', 'update-check'] +service_name = 'vyos-system-update' +service_conf = Path(f'/run/{service_name}.conf') +motd_file = Path('/run/motd.d/10-vyos-update') + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + if not conf.exists(base): + return None + + config = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + return config + + +def verify(config): + # bail out early - looks like removal from running config + if config is None: + return + + if 'url' not in config: + raise ConfigError('URL is required!') + + +def generate(config): + # bail out early - looks like removal from running config + if config is None: + # Remove old config and return + service_conf.unlink(missing_ok=True) + # MOTD used in /run/motd.d/10-update + motd_file.unlink(missing_ok=True) + return None + + # Write configuration file + conf_json = json.dumps(config, indent=4) + service_conf.write_text(conf_json) + + return None + + +def apply(config): + if config: + if 'auto_check' in config: + call(f'systemctl restart {service_name}.service') + else: + call(f'systemctl stop {service_name}.service') + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_update_check.py b/src/conf_mode/system_update_check.py deleted file mode 100755 index 8d641a97d..000000000 --- a/src/conf_mode/system_update_check.py +++ /dev/null @@ -1,93 +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 . - -import os -import json -import jmespath - -from pathlib import Path -from sys import exit - -from vyos.config import Config -from vyos.utils.process import call -from vyos import ConfigError -from vyos import airbag -airbag.enable() - - -base = ['system', 'update-check'] -service_name = 'vyos-system-update' -service_conf = Path(f'/run/{service_name}.conf') -motd_file = Path('/run/motd.d/10-vyos-update') - - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - if not conf.exists(base): - return None - - config = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - - return config - - -def verify(config): - # bail out early - looks like removal from running config - if config is None: - return - - if 'url' not in config: - raise ConfigError('URL is required!') - - -def generate(config): - # bail out early - looks like removal from running config - if config is None: - # Remove old config and return - service_conf.unlink(missing_ok=True) - # MOTD used in /run/motd.d/10-update - motd_file.unlink(missing_ok=True) - return None - - # Write configuration file - conf_json = json.dumps(config, indent=4) - service_conf.write_text(conf_json) - - return None - - -def apply(config): - if config: - if 'auto_check' in config: - call(f'systemctl restart {service_name}.service') - else: - call(f'systemctl stop {service_name}.service') - - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/task_scheduler.py b/src/conf_mode/task_scheduler.py deleted file mode 100755 index 129be5d3c..000000000 --- a/src/conf_mode/task_scheduler.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2017 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# - -import os -import re -import sys - -from vyos.config import Config -from vyos import ConfigError - -from vyos import airbag -airbag.enable() - -crontab_file = "/etc/cron.d/vyos-crontab" - - -def format_task(minute="*", hour="*", day="*", dayofweek="*", month="*", user="root", rawspec=None, command=""): - fmt_full = "{minute} {hour} {day} {month} {dayofweek} {user} {command}\n" - fmt_raw = "{spec} {user} {command}\n" - - if rawspec is None: - s = fmt_full.format(minute=minute, hour=hour, day=day, - dayofweek=dayofweek, month=month, command=command, user=user) - else: - s = fmt_raw.format(spec=rawspec, user=user, command=command) - - return s - -def split_interval(s): - result = re.search(r"(\d+)([mdh]?)", s) - value = int(result.group(1)) - suffix = result.group(2) - return( (value, suffix) ) - -def make_command(executable, arguments): - if arguments: - return("sg vyattacfg \"{0} {1}\"".format(executable, arguments)) - else: - return("sg vyattacfg \"{0}\"".format(executable)) - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - conf.set_level("system task-scheduler task") - task_names = conf.list_nodes("") - tasks = [] - - for name in task_names: - interval = conf.return_value("{0} interval".format(name)) - spec = conf.return_value("{0} crontab-spec".format(name)) - executable = conf.return_value("{0} executable path".format(name)) - args = conf.return_value("{0} executable arguments".format(name)) - task = { - "name": name, - "interval": interval, - "spec": spec, - "executable": executable, - "args": args - } - tasks.append(task) - - return tasks - -def verify(tasks): - for task in tasks: - if not task["interval"] and not task["spec"]: - raise ConfigError("Invalid task {0}: must define either interval or crontab-spec".format(task["name"])) - - if task["interval"]: - if task["spec"]: - raise ConfigError("Invalid task {0}: cannot use interval and crontab-spec at the same time".format(task["name"])) - - if not re.match(r"^\d+[mdh]?$", task["interval"]): - raise(ConfigError("Invalid interval {0} in task {1}: interval should be a number optionally followed by m, h, or d".format(task["name"], task["interval"]))) - else: - # Check if values are within allowed range - value, suffix = split_interval(task["interval"]) - - if not suffix or suffix == "m": - if value > 60: - raise ConfigError("Invalid task {0}: interval in minutes must not exceed 60".format(task["name"])) - elif suffix == "h": - if value > 24: - raise ConfigError("Invalid task {0}: interval in hours must not exceed 24".format(task["name"])) - elif suffix == "d": - if value > 31: - raise ConfigError("Invalid task {0}: interval in days must not exceed 31".format(task["name"])) - - if not task["executable"]: - raise ConfigError("Invalid task {0}: executable is not defined".format(task["name"])) - else: - # Check if executable exists and is executable - if not (os.path.isfile(task["executable"]) and os.access(task["executable"], os.X_OK)): - raise ConfigError("Invalid task {0}: file {1} does not exist or is not executable".format(task["name"], task["executable"])) - -def generate(tasks): - crontab_header = "### Generated by vyos-update-crontab.py ###\n" - if len(tasks) == 0: - if os.path.exists(crontab_file): - os.remove(crontab_file) - else: - pass - else: - crontab_lines = [] - for task in tasks: - command = make_command(task["executable"], task["args"]) - if task["spec"]: - line = format_task(command=command, rawspec=task["spec"]) - else: - value, suffix = split_interval(task["interval"]) - if not suffix or suffix == "m": - line = format_task(command=command, minute="*/{0}".format(value)) - elif suffix == "h": - line = format_task(command=command, minute="0", hour="*/{0}".format(value)) - elif suffix == "d": - line = format_task(command=command, minute="0", hour="0", day="*/{0}".format(value)) - crontab_lines.append(line) - - with open(crontab_file, 'w') as f: - f.write(crontab_header) - f.writelines(crontab_lines) - -def apply(config): - # No daemon restarts etc. needed for cron - pass - - -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/tftp_server.py b/src/conf_mode/tftp_server.py deleted file mode 100755 index 3ad346e2e..000000000 --- a/src/conf_mode/tftp_server.py +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2020 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import stat -import pwd - -from copy import deepcopy -from glob import glob -from sys import exit - -from vyos.base import Warning -from vyos.config import Config -from vyos.configverify import verify_vrf -from vyos.template import render -from vyos.template import is_ipv4 -from vyos.utils.process import call -from vyos.utils.permission import chmod_755 -from vyos.utils.network import is_addr_assigned -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -config_file = r'/etc/default/tftpd' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - base = ['service', 'tftp-server'] - if not conf.exists(base): - return None - - tftpd = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - return tftpd - -def verify(tftpd): - # bail out early - looks like removal from running config - if not tftpd: - return None - - # Configuring allowed clients without a server makes no sense - if 'directory' not in tftpd: - raise ConfigError('TFTP root directory must be configured!') - - if 'listen_address' not in tftpd: - raise ConfigError('TFTP server listen address must be configured!') - - for address, address_config in tftpd['listen_address'].items(): - if not is_addr_assigned(address): - Warning(f'TFTP server listen address "{address}" not ' \ - 'assigned to any interface!') - verify_vrf(address_config) - - return None - -def generate(tftpd): - # cleanup any available configuration file - # files will be recreated on demand - for i in glob(config_file + '*'): - os.unlink(i) - - # bail out early - looks like removal from running config - if tftpd is None: - return None - - idx = 0 - for address, address_config in tftpd['listen_address'].items(): - config = deepcopy(tftpd) - port = tftpd['port'] - if is_ipv4(address): - config['listen_address'] = f'{address}:{port} -4' - else: - config['listen_address'] = f'[{address}]:{port} -6' - - if 'vrf' in address_config: - config['vrf'] = address_config['vrf'] - - file = config_file + str(idx) - render(file, 'tftp-server/default.j2', config) - idx = idx + 1 - - return None - -def apply(tftpd): - # stop all services first - then we will decide - call('systemctl stop tftpd@*.service') - - # bail out early - e.g. service deletion - if tftpd is None: - return None - - tftp_root = tftpd['directory'] - if not os.path.exists(tftp_root): - os.makedirs(tftp_root) - chmod_755(tftp_root) - - # get UNIX uid for user 'tftp' - tftp_uid = pwd.getpwnam('tftp').pw_uid - tftp_gid = pwd.getpwnam('tftp').pw_gid - - # get UNIX uid for tftproot directory - dir_uid = os.stat(tftp_root).st_uid - dir_gid = os.stat(tftp_root).st_gid - - # adjust uid/gid of tftproot directory if files don't belong to user tftp - if (tftp_uid != dir_uid) or (tftp_gid != dir_gid): - os.chown(tftp_root, tftp_uid, tftp_gid) - - idx = 0 - for address in tftpd['listen_address']: - call(f'systemctl restart tftpd@{idx}.service') - idx = idx + 1 - - 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/etc/ppp/ip-down.d/98-vyos-pppoe-cleanup-nameservers b/src/etc/ppp/ip-down.d/98-vyos-pppoe-cleanup-nameservers index 222c75f21..5157469f4 100755 --- a/src/etc/ppp/ip-down.d/98-vyos-pppoe-cleanup-nameservers +++ b/src/etc/ppp/ip-down.d/98-vyos-pppoe-cleanup-nameservers @@ -1,5 +1,4 @@ #!/bin/bash -### Autogenerated by interfaces-pppoe.py ### interface=$6 if [ -z "$interface" ]; then diff --git a/src/etc/ppp/ip-up.d/98-vyos-pppoe-setup-nameservers b/src/etc/ppp/ip-up.d/98-vyos-pppoe-setup-nameservers index 0fcedbedc..4affaeb5c 100755 --- a/src/etc/ppp/ip-up.d/98-vyos-pppoe-setup-nameservers +++ b/src/etc/ppp/ip-up.d/98-vyos-pppoe-setup-nameservers @@ -1,5 +1,4 @@ #!/bin/bash -### Autogenerated by interfaces-pppoe.py ### interface=$6 if [ -z "$interface" ]; then diff --git a/src/init/vyos-router b/src/init/vyos-router index 711681a8e..aaecbf2a1 100755 --- a/src/init/vyos-router +++ b/src/init/vyos-router @@ -372,11 +372,11 @@ start () # As VyOS does not execute commands that are not present in the CLI we call # the script by hand to have a single source for the login banner and MOTD ${vyos_conf_scripts_dir}/system_console.py || log_failure_msg "could not reset serial console" - ${vyos_conf_scripts_dir}/system-login-banner.py || log_failure_msg "could not reset motd and issue files" - ${vyos_conf_scripts_dir}/system-option.py || log_failure_msg "could not reset system option files" - ${vyos_conf_scripts_dir}/system-ip.py || log_failure_msg "could not reset system IPv4 options" - ${vyos_conf_scripts_dir}/system-ipv6.py || log_failure_msg "could not reset system IPv6 options" - ${vyos_conf_scripts_dir}/conntrack.py || log_failure_msg "could not reset conntrack subsystem" + ${vyos_conf_scripts_dir}/system_login_banner.py || log_failure_msg "could not reset motd and issue files" + ${vyos_conf_scripts_dir}/system_option.py || log_failure_msg "could not reset system option files" + ${vyos_conf_scripts_dir}/system_ip.py || log_failure_msg "could not reset system IPv4 options" + ${vyos_conf_scripts_dir}/system_ipv6.py || log_failure_msg "could not reset system IPv6 options" + ${vyos_conf_scripts_dir}/system_conntrack.py || log_failure_msg "could not reset conntrack subsystem" ${vyos_conf_scripts_dir}/container.py || log_failure_msg "could not reset container subsystem" clear_or_override_config_files || log_failure_msg "could not reset config files" diff --git a/src/migration-scripts/https/1-to-2 b/src/migration-scripts/https/1-to-2 index b1cf37ea6..1a2cdc1e7 100755 --- a/src/migration-scripts/https/1-to-2 +++ b/src/migration-scripts/https/1-to-2 @@ -15,7 +15,7 @@ # along with this program. If not, see . # * Move 'api virtual-host' list to 'api-restrict virtual-host' so it -# is owned by https.py instead of http-api.py +# is owned by service_https.py import sys diff --git a/src/op_mode/connect_disconnect.py b/src/op_mode/connect_disconnect.py index 89f929be7..10034e499 100755 --- a/src/op_mode/connect_disconnect.py +++ b/src/op_mode/connect_disconnect.py @@ -55,7 +55,7 @@ def connect(interface): if is_wwan_connected(interface): print(f'Interface {interface}: already connected!') else: - call(f'VYOS_TAGNODE_VALUE={interface} /usr/libexec/vyos/conf_mode/interfaces-wwan.py') + call(f'VYOS_TAGNODE_VALUE={interface} /usr/libexec/vyos/conf_mode/interfaces_wwan.py') else: print(f'Unknown interface {interface}, can not connect. Aborting!') diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py index 5e19bdbad..6d33e372d 100755 --- a/src/system/keepalived-fifo.py +++ b/src/system/keepalived-fifo.py @@ -41,7 +41,7 @@ logger.addHandler(logs_handler_syslog) logger.setLevel(logging.DEBUG) mdns_running_file = '/run/mdns_vrrp_active' -mdns_update_command = 'sudo /usr/libexec/vyos/conf_mode/service_mdns-repeater.py' +mdns_update_command = 'sudo /usr/libexec/vyos/conf_mode/service_mdns_repeater.py' # class for all operations class KeepalivedFifo: diff --git a/src/tests/test_task_scheduler.py b/src/tests/test_task_scheduler.py index f15fcde88..130f825e6 100644 --- a/src/tests/test_task_scheduler.py +++ b/src/tests/test_task_scheduler.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 @@ -17,16 +17,16 @@ import os import tempfile import unittest +import importlib from vyos import ConfigError try: - from src.conf_mode import task_scheduler + task_scheduler = importlib.import_module("src.conf_mode.system_task-scheduler") except ModuleNotFoundError: # for unittest.main() import sys sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) - from src.conf_mode import task_scheduler - + task_scheduler = importlib.import_module("src.conf_mode.system_task-scheduler") class TestUpdateCrontab(unittest.TestCase): -- cgit v1.2.3