diff options
120 files changed, 3542 insertions, 776 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index d77275d38..000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Build -on: - push: - branches: - - current - pull_request: - types: [opened, synchronize, reopened] -jobs: - sonarcloud: - name: SonarCloud - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: SonarCloud Scan - uses: SonarSource/sonarcloud-github-action@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/mergifyio_backport.yml b/.github/workflows/mergifyio_backport.yml new file mode 100644 index 000000000..f1f4312c4 --- /dev/null +++ b/.github/workflows/mergifyio_backport.yml @@ -0,0 +1,22 @@ +name: Mergifyio backport + +on: [issue_comment] + +jobs: + mergifyio_backport: + if: github.repository == 'vyos/vyos-1x' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions-ecosystem/action-regex-match@v2 + id: regex-match + with: + text: ${{ github.event.comment.body }} + regex: '[Mm]ergifyio backport ' + + - uses: actions-ecosystem/action-add-labels@v1 + if: ${{ steps.regex-match.outputs.match != '' }} + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + labels: backport @@ -62,7 +62,6 @@ op_mode_definitions: $(op_xml_obj) rm -f $(OP_TMPL_DIR)/delete/node.def rm -f $(OP_TMPL_DIR)/generate/node.def rm -f $(OP_TMPL_DIR)/set/node.def - rm -f $(OP_TMPL_DIR)/show/tech-support/node.def # XXX: ping and traceroute must be able to recursivly call itself as the # options are provided from the script itself diff --git a/data/config-mode-dependencies.json b/data/config-mode-dependencies/vyos-1x.json index 91a757c16..08732bd4c 100644 --- a/data/config-mode-dependencies.json +++ b/data/config-mode-dependencies/vyos-1x.json @@ -1,5 +1,5 @@ { - "firewall": {"group_resync": ["nat", "policy-route"]}, + "firewall": {"group_resync": ["conntrack", "nat", "policy-route"]}, "http_api": {"https": ["https"]}, "pki": { "ethernet": ["interfaces-ethernet"], diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json index 042c466ab..ded934bff 100644 --- a/data/op-mode-standardized.json +++ b/data/op-mode-standardized.json @@ -16,6 +16,7 @@ "neighbor.py", "nhrp.py", "openconnect.py", +"otp.py", "openvpn.py", "reset_vpn.py", "reverseproxy.py", diff --git a/data/templates/accel-ppp/ipoe.config.j2 b/data/templates/accel-ppp/ipoe.config.j2 index add3dc7e4..f59428509 100644 --- a/data/templates/accel-ppp/ipoe.config.j2 +++ b/data/templates/accel-ppp/ipoe.config.j2 @@ -36,7 +36,9 @@ verbose=1 {% set shared = 'shared=0,' %} {% endif %} {% set range = 'range=' ~ iface_config.client_subnet ~ ',' if iface_config.client_subnet is vyos_defined else '' %} -{{ tmp }},{{ shared }}mode={{ iface_config.mode | upper }},ifcfg=1,{{ range }}start=dhcpv4,ipv6=1 +{% set relay = ',' ~ 'relay=' ~ iface_config.external_dhcp.dhcp_relay if iface_config.external_dhcp.dhcp_relay is vyos_defined else '' %} +{% set giaddr = ',' ~ 'giaddr=' ~ iface_config.external_dhcp.giaddr if iface_config.external_dhcp.giaddr is vyos_defined else '' %} +{{ tmp }},{{ shared }}mode={{ iface_config.mode | upper }},ifcfg=1,{{ range }}start=dhcpv4,ipv6=1{{ relay }}{{ giaddr }} {% if iface_config.vlan is vyos_defined %} vlan-mon={{ iface }},{{ iface_config.vlan | join(',') }} {% endif %} diff --git a/data/templates/conntrack/nftables-ct.j2 b/data/templates/conntrack/nftables-ct.j2 index 16a03fc6e..3a5b5a87c 100644 --- a/data/templates/conntrack/nftables-ct.j2 +++ b/data/templates/conntrack/nftables-ct.j2 @@ -1,5 +1,7 @@ #!/usr/sbin/nft -f +{% import 'firewall/nftables-defines.j2' as group_tmpl %} + {% set nft_ct_ignore_name = 'VYOS_CT_IGNORE' %} {% set nft_ct_timeout_name = 'VYOS_CT_TIMEOUT' %} @@ -10,29 +12,35 @@ flush chain raw {{ nft_ct_timeout_name }} table raw { chain {{ nft_ct_ignore_name }} { -{% if ignore.rule is vyos_defined %} -{% for rule, rule_config in ignore.rule.items() %} +{% if ignore.ipv4.rule is vyos_defined %} +{% for rule, rule_config in ignore.ipv4.rule.items() %} + # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }} + {{ rule_config | conntrack_ignore_rule(rule, ipv6=False) }} +{% endfor %} +{% endif %} + return + } + chain {{ nft_ct_timeout_name }} { +{% if timeout.custom.rule is vyos_defined %} +{% for rule, rule_config in timeout.custom.rule.items() %} + # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }} +{% endfor %} +{% endif %} + return + } + +{{ group_tmpl.groups(firewall_group, False, True) }} +} + +flush chain ip6 raw {{ nft_ct_ignore_name }} +flush chain ip6 raw {{ nft_ct_timeout_name }} + +table ip6 raw { + chain {{ nft_ct_ignore_name }} { +{% if ignore.ipv6.rule is vyos_defined %} +{% for rule, rule_config in ignore.ipv6.rule.items() %} # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }} -{% set nft_command = '' %} -{% if rule_config.inbound_interface is vyos_defined %} -{% set nft_command = nft_command ~ ' iifname ' ~ rule_config.inbound_interface %} -{% endif %} -{% if rule_config.protocol is vyos_defined %} -{% set nft_command = nft_command ~ ' ip protocol ' ~ rule_config.protocol %} -{% endif %} -{% if rule_config.destination.address is vyos_defined %} -{% set nft_command = nft_command ~ ' ip daddr ' ~ rule_config.destination.address %} -{% endif %} -{% if rule_config.destination.port is vyos_defined %} -{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' dport { ' ~ rule_config.destination.port ~ ' }' %} -{% endif %} -{% if rule_config.source.address is vyos_defined %} -{% set nft_command = nft_command ~ ' ip saddr ' ~ rule_config.source.address %} -{% endif %} -{% if rule_config.source.port is vyos_defined %} -{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' sport { ' ~ rule_config.source.port ~ ' }' %} -{% endif %} - {{ nft_command }} counter notrack comment ignore-{{ rule }} + {{ rule_config | conntrack_ignore_rule(rule, ipv6=True) }} {% endfor %} {% endif %} return @@ -45,4 +53,6 @@ table raw { {% endif %} return } + +{{ group_tmpl.groups(firewall_group, True, True) }} } diff --git a/data/templates/conntrack/sysctl.conf.j2 b/data/templates/conntrack/sysctl.conf.j2 index 075402c04..3d6fc43f2 100644 --- a/data/templates/conntrack/sysctl.conf.j2 +++ b/data/templates/conntrack/sysctl.conf.j2 @@ -24,3 +24,4 @@ net.netfilter.nf_conntrack_tcp_timeout_time_wait = {{ timeout.tcp.time_wait }} net.netfilter.nf_conntrack_udp_timeout = {{ timeout.udp.other }} net.netfilter.nf_conntrack_udp_timeout_stream = {{ timeout.udp.stream }} +net.netfilter.nf_conntrack_acct = {{ '1' if flow_accounting is vyos_defined else '0' }} diff --git a/src/systemd/isc-dhcp-server.service b/data/templates/dhcp-server/10-override.conf.j2 index a7d86e69c..1504b6808 100644 --- a/src/systemd/isc-dhcp-server.service +++ b/data/templates/dhcp-server/10-override.conf.j2 @@ -1,22 +1,28 @@ +### Autogenerated by dhcp_server.py ### +{% set lease_file = '/config/dhcpd.leases' %} [Unit] Description=ISC DHCP IPv4 server Documentation=man:dhcpd(8) RequiresMountsFor=/run +ConditionPathExists= ConditionPathExists=/run/dhcp-server/dhcpd.conf +After= After=vyos-router.service [Service] Type=forking +WorkingDirectory= WorkingDirectory=/run/dhcp-server RuntimeDirectory=dhcp-server RuntimeDirectoryPreserve=yes -Environment=PID_FILE=/run/dhcp-server/dhcpd.pid CONFIG_FILE=/run/dhcp-server/dhcpd.conf LEASE_FILE=/config/dhcpd.leases +Environment=PID_FILE=/run/dhcp-server/dhcpd.pid CONFIG_FILE=/run/dhcp-server/dhcpd.conf LEASE_FILE={{ lease_file }} PIDFile=/run/dhcp-server/dhcpd.pid ExecStartPre=/bin/sh -ec '\ touch ${LEASE_FILE}; \ chown dhcpd:vyattacfg ${LEASE_FILE}* ; \ chmod 664 ${LEASE_FILE}* ; \ /usr/sbin/dhcpd -4 -t -T -q -user dhcpd -group vyattacfg -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE} ' +ExecStart= ExecStart=/usr/sbin/dhcpd -4 -q -user dhcpd -group vyattacfg -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE} Restart=always diff --git a/data/templates/firewall/nftables-bridge.j2 b/data/templates/firewall/nftables-bridge.j2 new file mode 100644 index 000000000..7f94e10d6 --- /dev/null +++ b/data/templates/firewall/nftables-bridge.j2 @@ -0,0 +1,35 @@ +{% macro bridge(bridge) %} +{% set ns = namespace(sets=[]) %} +{% if bridge.forward is vyos_defined %} +{% for prior, conf in bridge.forward.items() %} +{% set def_action = conf.default_action %} + chain VYOS_FORWARD_{{ prior }} { + type filter hook forward priority {{ prior }}; policy {{ def_action }}; +{% if conf.rule is vyos_defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} + {{ rule_conf | nft_rule('FWD', prior, rule_id, 'bri') }} +{% if rule_conf.recent is vyos_defined %} +{% set ns.sets = ns.sets + ['FWD_' + prior + '_' + rule_id] %} +{% endif %} +{% endfor %} +{% endif %} + } +{% endfor %} +{% endif %} + +{% if bridge.name is vyos_defined %} +{% for name_text, conf in bridge.name.items() %} + chain NAME_{{ name_text }} { +{% if conf.rule is vyos_defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} + {{ rule_conf | nft_rule('NAM', name_text, rule_id, 'bri') }} +{% if rule_conf.recent is vyos_defined %} +{% set ns.sets = ns.sets + ['NAM_' + name_text + '_' + rule_id] %} +{% endif %} +{% endfor %} +{% endif %} + {{ conf | nft_default_rule(name_text) }} + } +{% endfor %} +{% endif %} +{% endmacro %} diff --git a/data/templates/firewall/nftables-defines.j2 b/data/templates/firewall/nftables-defines.j2 index 0a7e79edd..a20c399ae 100644 --- a/data/templates/firewall/nftables-defines.j2 +++ b/data/templates/firewall/nftables-defines.j2 @@ -1,7 +1,7 @@ -{% macro groups(group, is_ipv6) %} +{% macro groups(group, is_ipv6, is_l3) %} {% if group is vyos_defined %} {% set ip_type = 'ipv6_addr' if is_ipv6 else 'ipv4_addr' %} -{% if group.address_group is vyos_defined and not is_ipv6 %} +{% if group.address_group is vyos_defined and not is_ipv6 and is_l3 %} {% for group_name, group_conf in group.address_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set A_{{ group_name }} { @@ -14,7 +14,7 @@ } {% endfor %} {% endif %} -{% if group.ipv6_address_group is vyos_defined and is_ipv6 %} +{% if group.ipv6_address_group is vyos_defined and is_ipv6 and is_l3 %} {% for group_name, group_conf in group.ipv6_address_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set A6_{{ group_name }} { @@ -27,7 +27,7 @@ } {% endfor %} {% endif %} -{% if group.domain_group is vyos_defined %} +{% if group.domain_group is vyos_defined and is_l3 %} {% for name, name_config in group.domain_group.items() %} set D_{{ name }} { type {{ ip_type }} @@ -46,7 +46,7 @@ } {% endfor %} {% endif %} -{% if group.network_group is vyos_defined and not is_ipv6 %} +{% if group.network_group is vyos_defined and not is_ipv6 and is_l3 %} {% for group_name, group_conf in group.network_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set N_{{ group_name }} { @@ -59,7 +59,7 @@ } {% endfor %} {% endif %} -{% if group.ipv6_network_group is vyos_defined and is_ipv6 %} +{% if group.ipv6_network_group is vyos_defined and is_ipv6 and is_l3 %} {% for group_name, group_conf in group.ipv6_network_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set N6_{{ group_name }} { @@ -72,7 +72,7 @@ } {% endfor %} {% endif %} -{% if group.port_group is vyos_defined %} +{% if group.port_group is vyos_defined and is_l3 %} {% for group_name, group_conf in group.port_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set P_{{ group_name }} { diff --git a/data/templates/firewall/nftables-nat.j2 b/data/templates/firewall/nftables-nat.j2 index f0be3cf5d..dcf28da88 100644 --- a/data/templates/firewall/nftables-nat.j2 +++ b/data/templates/firewall/nftables-nat.j2 @@ -62,6 +62,6 @@ table ip vyos_nat { return } -{{ group_tmpl.groups(firewall_group, False) }} +{{ group_tmpl.groups(firewall_group, False, True) }} } {% endif %} diff --git a/data/templates/firewall/nftables-offload.j2 b/data/templates/firewall/nftables-offload.j2 new file mode 100644 index 000000000..6afcd79f7 --- /dev/null +++ b/data/templates/firewall/nftables-offload.j2 @@ -0,0 +1,11 @@ +{% macro render_flowtable(name, devices, priority='filter', hardware_offload=false, with_counter=true) %} +flowtable {{ name }} { + hook ingress priority {{ priority }}; devices = { {{ devices | join(', ') }} }; +{% if hardware_offload %} + flags offload; +{% endif %} +{% if with_counter %} + counter +{% endif %} +} +{% endmacro %} diff --git a/data/templates/firewall/nftables-policy.j2 b/data/templates/firewall/nftables-policy.j2 index 699349e2b..d77e3f6e9 100644 --- a/data/templates/firewall/nftables-policy.j2 +++ b/data/templates/firewall/nftables-policy.j2 @@ -32,7 +32,7 @@ table ip vyos_mangle { {% endfor %} {% endif %} -{{ group_tmpl.groups(firewall_group, False) }} +{{ group_tmpl.groups(firewall_group, False, True) }} } table ip6 vyos_mangle { @@ -61,5 +61,5 @@ table ip6 vyos_mangle { {% endfor %} {% endif %} -{{ group_tmpl.groups(firewall_group, True) }} +{{ group_tmpl.groups(firewall_group, True, True) }} } diff --git a/data/templates/firewall/nftables-vrf-zones.j2 b/data/templates/firewall/nftables-vrf-zones.j2 index eecf47b78..3bce7312d 100644 --- a/data/templates/firewall/nftables-vrf-zones.j2 +++ b/data/templates/firewall/nftables-vrf-zones.j2 @@ -7,11 +7,11 @@ table inet vrf_zones { # Chain for inbound traffic chain vrf_zones_ct_in { type filter hook prerouting priority raw; policy accept; - counter ct zone set iifname map @ct_iface_map + counter ct original zone set iifname map @ct_iface_map } # Chain for locally-generated traffic chain vrf_zones_ct_out { type filter hook output priority raw; policy accept; - counter ct zone set oifname map @ct_iface_map + counter ct original zone set oifname map @ct_iface_map } } diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2 index 0fbddfaa9..db010257d 100644 --- a/data/templates/firewall/nftables.j2 +++ b/data/templates/firewall/nftables.j2 @@ -1,33 +1,42 @@ #!/usr/sbin/nft -f {% import 'firewall/nftables-defines.j2' as group_tmpl %} +{% import 'firewall/nftables-bridge.j2' as bridge_tmpl %} +{% import 'firewall/nftables-offload.j2' as offload %} flush chain raw FW_CONNTRACK flush chain ip6 raw FW_CONNTRACK +flush chain raw vyos_global_rpfilter +flush chain ip6 raw vyos_global_rpfilter + table raw { chain FW_CONNTRACK { {{ ipv4_conntrack_action }} } + + chain vyos_global_rpfilter { +{% if global_options.source_validation is vyos_defined('loose') %} + fib saddr oif 0 counter drop +{% elif global_options.source_validation is vyos_defined('strict') %} + fib saddr . iif oif 0 counter drop +{% endif %} + return + } } table ip6 raw { chain FW_CONNTRACK { {{ ipv6_conntrack_action }} } -} -{% if first_install is not vyos_defined %} -delete table inet vyos_global_rpfilter -{% endif %} -table inet vyos_global_rpfilter { - chain PREROUTING { - type filter hook prerouting priority -300; policy accept; -{% if global_options.source_validation is vyos_defined('loose') %} + chain vyos_global_rpfilter { +{% if global_options.ipv6_source_validation is vyos_defined('loose') %} fib saddr oif 0 counter drop -{% elif global_options.source_validation is vyos_defined('strict') %} +{% elif global_options.ipv6_source_validation is vyos_defined('strict') %} fib saddr . iif oif 0 counter drop {% endif %} + return } } @@ -147,7 +156,7 @@ table ip vyos_filter { {% endfor %} {% endif %} {% endif %} -{{ group_tmpl.groups(group, False) }} +{{ group_tmpl.groups(group, False, True) }} } {% if first_install is not vyos_defined %} @@ -250,5 +259,37 @@ table ip6 vyos_filter { {% endfor %} {% endif %} {% endif %} -{{ group_tmpl.groups(group, True) }} -}
\ No newline at end of file +{{ group_tmpl.groups(group, True, True) }} +} + +## Bridge Firewall +{% if first_install is not vyos_defined %} +delete table bridge vyos_filter +{% endif %} +{% if bridge is vyos_defined %} +table bridge vyos_filter { +{{ bridge_tmpl.bridge(bridge) }} +{{ group_tmpl.groups(group, False, False) }} +} +{% endif %} + +table inet vyos_offload +delete table inet vyos_offload +table inet vyos_offload { +{% if flowtable_enabled %} +{% if global_options.flow_offload.hardware.interface is vyos_defined %} + {{- offload.render_flowtable('VYOS_FLOWTABLE_hardware', global_options.flow_offload.hardware.interface | list, priority='filter - 2', hardware_offload=true) }} + chain VYOS_OFFLOAD_hardware { + type filter hook forward priority filter - 2; policy accept; + ct state { established, related } meta l4proto { tcp, udp } flow add @VYOS_FLOWTABLE_hardware + } +{% endif %} +{% if global_options.flow_offload.software.interface is vyos_defined %} + {{- offload.render_flowtable('VYOS_FLOWTABLE_software', global_options.flow_offload.software.interface | list, priority='filter - 1') }} + chain VYOS_OFFLOAD_software { + type filter hook forward priority filter - 1; policy accept; + ct state { established, related } meta l4proto { tcp, udp } flow add @VYOS_FLOWTABLE_software + } +{% endif %} +{% endif %} +} diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2 index 7fa974254..e1c102e16 100644 --- a/data/templates/frr/bgpd.frr.j2 +++ b/data/templates/frr/bgpd.frr.j2 @@ -170,7 +170,7 @@ {% endif %} {% endif %} {% if afi_config.remove_private_as is vyos_defined %} - neighbor {{ neighbor }} remove-private-AS + neighbor {{ neighbor }} remove-private-AS {{ 'all' if afi_config.remove_private_as.all is vyos_defined }} {% endif %} {% if afi_config.route_reflector_client is vyos_defined %} neighbor {{ neighbor }} route-reflector-client diff --git a/data/templates/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl index 3aad8e8dd..fe2610724 100644 --- a/data/templates/frr/daemons.frr.tmpl +++ b/data/templates/frr/daemons.frr.tmpl @@ -6,6 +6,7 @@ ripd=yes ripngd=yes isisd=yes pimd=no +pim6d=yes ldpd=yes nhrpd=no eigrpd=yes @@ -16,39 +17,41 @@ bfdd=yes staticd=yes vtysh_enable=yes -zebra_options=" -s 90000000 --daemon -A 127.0.0.1 +zebra_options=" --daemon -A 127.0.0.1 -s 90000000 {%- if irdp is defined %} -M irdp{% endif -%} {%- if snmp is defined and snmp.zebra is defined %} -M snmp{% endif -%} " -bgpd_options=" --daemon -A 127.0.0.1 +bgpd_options=" --daemon -A 127.0.0.1 -M rpki {%- if bmp is defined %} -M bmp{% endif -%} {%- if snmp is defined and snmp.bgpd is defined %} -M snmp{% endif -%} " -ospfd_options=" --daemon -A 127.0.0.1 +ospfd_options=" --daemon -A 127.0.0.1 {%- if snmp is defined and snmp.ospfd is defined %} -M snmp{% endif -%} " -ospf6d_options=" --daemon -A ::1 +ospf6d_options=" --daemon -A ::1 {%- if snmp is defined and snmp.ospf6d is defined %} -M snmp{% endif -%} " -ripd_options=" --daemon -A 127.0.0.1 +ripd_options=" --daemon -A 127.0.0.1 {%- if snmp is defined and snmp.ripd is defined %} -M snmp{% endif -%} " -ripngd_options=" --daemon -A ::1" -isisd_options=" --daemon -A 127.0.0.1 +ripngd_options=" --daemon -A ::1" +isisd_options=" --daemon -A 127.0.0.1 {%- if snmp is defined and snmp.isisd is defined %} -M snmp{% endif -%} " -pimd_options=" --daemon -A 127.0.0.1" -ldpd_options=" --daemon -A 127.0.0.1 +pimd_options=" --daemon -A 127.0.0.1" +pim6d_options=" --daemon -A ::1" +ldpd_options=" --daemon -A 127.0.0.1 {%- if snmp is defined and snmp.ldpd is defined %} -M snmp{% endif -%} " -mgmtd_options=" --daemon -A 127.0.0.1" -nhrpd_options=" --daemon -A 127.0.0.1" +mgmtd_options=" --daemon -A 127.0.0.1" +nhrpd_options=" --daemon -A 127.0.0.1" eigrpd_options=" --daemon -A 127.0.0.1" babeld_options=" --daemon -A 127.0.0.1" sharpd_options=" --daemon -A 127.0.0.1" -pbrd_options=" --daemon -A 127.0.0.1" -staticd_options=" --daemon -A 127.0.0.1" -bfdd_options=" --daemon -A 127.0.0.1" +pbrd_options=" --daemon -A 127.0.0.1" +staticd_options=" --daemon -A 127.0.0.1" +bfdd_options=" --daemon -A 127.0.0.1" watchfrr_enable=no valgrind_enable=no + diff --git a/data/templates/frr/igmp.frr.j2 b/data/templates/frr/igmp.frr.j2 index ce1f8fdda..b75884484 100644 --- a/data/templates/frr/igmp.frr.j2 +++ b/data/templates/frr/igmp.frr.j2 @@ -27,9 +27,9 @@ interface {{ interface }} {% if interface_config.query_max_resp_time %} ip igmp query-max-response-time {{ interface_config.query_max_resp_time }} {% endif %} -{% for group in interface_config.gr_join %} -{% if ifaces[iface].gr_join[group] %} -{% for source in ifaces[iface].gr_join[group] %} +{% for group, sources in interface_config.gr_join.items() %} +{% if sources is vyos_defined %} +{% for source in sources %} ip igmp join {{ group }} {{ source }} {% endfor %} {% else %} diff --git a/data/templates/frr/pim6d.frr.j2 b/data/templates/frr/pim6d.frr.j2 new file mode 100644 index 000000000..8e430541d --- /dev/null +++ b/data/templates/frr/pim6d.frr.j2 @@ -0,0 +1,38 @@ +! +{% if interface is vyos_defined %} +{% for iface, iface_config in interface.items() %} +interface {{ iface }} +{% if iface_config.mld is vyos_defined and iface_config.mld.disable is not vyos_defined %} + ipv6 mld +{% if iface_config.mld.version is vyos_defined %} + ipv6 mld version {{ iface_config.mld.version }} +{% endif %} +{% if iface_config.mld.interval is vyos_defined %} + ipv6 mld query-interval {{ iface_config.mld.interval }} +{% endif %} +{% if iface_config.mld.max_response_time is vyos_defined %} + ipv6 mld query-max-response-time {{ iface_config.mld.max_response_time // 100 }} +{% endif %} +{% if iface_config.mld.last_member_query_count is vyos_defined %} + ipv6 mld last-member-query-count {{ iface_config.mld.last_member_query_count }} +{% endif %} +{% if iface_config.mld.last_member_query_interval is vyos_defined %} + ipv6 mld last-member-query-interval {{ iface_config.mld.last_member_query_interval // 100 }} +{% endif %} +{% if iface_config.mld.join is vyos_defined %} +{% for group, group_config in iface_config.mld.join.items() %} +{% if group_config.source is vyos_defined %} +{% for source in group_config.source %} + ipv6 mld join {{ group }} {{ source }} +{% endfor %} +{% else %} + ipv6 mld join {{ group }} +{% endif %} +{% endfor %} +{% endif %} +{% endif %} +exit +! +{% endfor %} +! +{% endif %} diff --git a/src/etc/systemd/system/keepalived.service.d/override.conf b/data/templates/high-availability/10-override.conf.j2 index d91a824b9..d1cb25581 100644 --- a/src/etc/systemd/system/keepalived.service.d/override.conf +++ b/data/templates/high-availability/10-override.conf.j2 @@ -1,3 +1,5 @@ +### Autogenerated by ${vyos_conf_scripts_dir}/high-availability.py ### +{% set snmp = '' if vrrp.disable_snmp is vyos_defined else '--snmp' %} [Unit] After=vyos-router.service # Only start if there is our configuration file - remove Debian default @@ -10,5 +12,5 @@ KillMode=process Type=simple # Read configuration variable file if it is present ExecStart= -ExecStart=/usr/sbin/keepalived --use-file /run/keepalived/keepalived.conf --pid /run/keepalived/keepalived.pid --dont-fork --snmp +ExecStart=/usr/sbin/keepalived --use-file /run/keepalived/keepalived.conf --pid /run/keepalived/keepalived.pid --dont-fork {{ snmp }} PIDFile=/run/keepalived/keepalived.pid diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2 index f8e1587f8..0a40e1ecf 100644 --- a/data/templates/load-balancing/haproxy.cfg.j2 +++ b/data/templates/load-balancing/haproxy.cfg.j2 @@ -150,13 +150,13 @@ backend {{ back }} {% endfor %} {% endif %} {% if back_config.timeout.check is vyos_defined %} - timeout check {{ back_config.timeout.check }} + timeout check {{ back_config.timeout.check }}s {% endif %} {% if back_config.timeout.connect is vyos_defined %} - timeout connect {{ back_config.timeout.connect }} + timeout connect {{ back_config.timeout.connect }}s {% endif %} {% if back_config.timeout.server is vyos_defined %} - timeout server {{ back_config.timeout.server }} + timeout server {{ back_config.timeout.server }}s {% endif %} {% endfor %} diff --git a/data/templates/openvpn/server.conf.j2 b/data/templates/openvpn/server.conf.j2 index f76fbbe79..2eb9416fe 100644 --- a/data/templates/openvpn/server.conf.j2 +++ b/data/templates/openvpn/server.conf.j2 @@ -74,7 +74,7 @@ topology {{ server.topology }} {% endif %} {% for subnet in server.subnet %} {% if subnet | is_ipv4 %} -server {{ subnet | address_from_cidr }} {{ subnet | netmask_from_cidr }} nopool +server {{ subnet | address_from_cidr }} {{ subnet | netmask_from_cidr }} {{ 'nopool' if server.client_ip_pool is vyos_defined and server.client_ip_pool.disable is not vyos_defined else '' }} {# First ip address is used as gateway. It's allows to use metrics #} {% if server.push_route is vyos_defined %} {% for route, route_config in server.push_route.items() %} @@ -85,15 +85,6 @@ push "route-ipv6 {{ route }}" {% endif %} {% endfor %} {% endif %} -{# OpenVPN assigns the first IP address to its local interface so the pool used #} -{# in net30 topology - where each client receives a /30 must start from the second subnet #} -{% if server.topology is vyos_defined('net30') %} -ifconfig-pool {{ subnet | inc_ip('4') }} {{ subnet | last_host_address | dec_ip('1') }} {{ subnet | netmask_from_cidr if device_type == 'tap' else '' }} -{% else %} -{# OpenVPN assigns the first IP address to its local interface so the pool must #} -{# start from the second address and end on the last address #} -ifconfig-pool {{ subnet | first_host_address | inc_ip('1') }} {{ subnet | last_host_address | dec_ip('1') }} {{ subnet | netmask_from_cidr if device_type == 'tun' else '' }} -{% endif %} {% elif subnet | is_ipv6 %} server-ipv6 {{ subnet }} {% endif %} diff --git a/data/templates/wifi/hostapd.conf.j2 b/data/templates/wifi/hostapd.conf.j2 index 613038597..c3f32da72 100644 --- a/data/templates/wifi/hostapd.conf.j2 +++ b/data/templates/wifi/hostapd.conf.j2 @@ -340,6 +340,11 @@ vht_oper_chwidth={{ capabilities.vht.channel_set_width }} {% endif %} {% set output = namespace(value='') %} +{% if capabilities.vht.channel_set_width is vyos_defined('2') %} +{% set output.value = output.value ~ '[VHT160]' %} +{% elif capabilities.vht.channel_set_width is vyos_defined('3') %} +{% set output.value = output.value ~ '[VHT160-80PLUS80]' %} +{% endif %} {% if capabilities.vht.stbc.tx is vyos_defined %} {% set output.value = output.value ~ '[TX-STBC-2BY1]' %} {% endif %} @@ -363,30 +368,21 @@ vht_oper_chwidth={{ capabilities.vht.channel_set_width }} {% endif %} {% if capabilities.vht.max_mpdu_exp is vyos_defined %} {% set output.value = output.value ~ '[MAX-A-MPDU-LEN-EXP-' ~ capabilities.vht.max_mpdu_exp ~ ']' %} -{% if capabilities.vht.max_mpdu_exp is vyos_defined('2') %} -{% set output.value = output.value ~ '[VHT160]' %} -{% endif %} -{% if capabilities.vht.max_mpdu_exp is vyos_defined('3') %} -{% set output.value = output.value ~ '[VHT160-80PLUS80]' %} -{% endif %} {% endif %} {% if capabilities.vht.link_adaptation is vyos_defined('unsolicited') %} {% set output.value = output.value ~ '[VHT-LINK-ADAPT2]' %} {% elif capabilities.vht.link_adaptation is vyos_defined('both') %} {% set output.value = output.value ~ '[VHT-LINK-ADAPT3]' %} {% endif %} - {% for short_gi in capabilities.vht.short_gi if capabilities.vht.short_gi is vyos_defined %} {% set output.value = output.value ~ '[SHORT-GI-' ~ short_gi | upper ~ ']' %} {% endfor %} - {% for beamform in capabilities.vht.beamform if capabilities.vht.beamform is vyos_defined %} {% set output.value = output.value ~ '[SU-BEAMFORMER]' if beamform is vyos_defined('single-user-beamformer') else '' %} {% set output.value = output.value ~ '[SU-BEAMFORMEE]' if beamform is vyos_defined('single-user-beamformee') else '' %} {% set output.value = output.value ~ '[MU-BEAMFORMER]' if beamform is vyos_defined('multi-user-beamformer') else '' %} {% set output.value = output.value ~ '[MU-BEAMFORMEE]' if beamform is vyos_defined('multi-user-beamformee') else '' %} {% endfor %} - {% if capabilities.vht.antenna_count is vyos_defined and capabilities.vht.antenna_count | int > 1 %} {% if capabilities.vht.beamform is vyos_defined %} {% if capabilities.vht.beamform == 'single-user-beamformer' %} diff --git a/data/vyos-firewall-init.conf b/data/vyos-firewall-init.conf index 41e7627f5..7e258e6f1 100644 --- a/data/vyos-firewall-init.conf +++ b/data/vyos-firewall-init.conf @@ -19,6 +19,15 @@ table raw { type filter hook forward priority -300; policy accept; } + chain vyos_global_rpfilter { + return + } + + chain vyos_rpfilter { + type filter hook prerouting priority -300; policy accept; + counter jump vyos_global_rpfilter + } + chain PREROUTING { type filter hook prerouting priority -300; policy accept; counter jump VYOS_CT_IGNORE @@ -82,12 +91,19 @@ table ip6 raw { type filter hook forward priority -300; policy accept; } + chain vyos_global_rpfilter { + return + } + chain vyos_rpfilter { type filter hook prerouting priority -300; policy accept; + counter jump vyos_global_rpfilter } chain PREROUTING { type filter hook prerouting priority -300; policy accept; + counter jump VYOS_CT_IGNORE + counter jump VYOS_CT_TIMEOUT counter jump VYOS_CT_PREROUTING_HOOK counter jump FW_CONNTRACK notrack @@ -95,11 +111,40 @@ table ip6 raw { chain OUTPUT { type filter hook output priority -300; policy accept; + counter jump VYOS_CT_IGNORE + counter jump VYOS_CT_TIMEOUT counter jump VYOS_CT_OUTPUT_HOOK counter jump FW_CONNTRACK notrack } + ct helper rpc_tcp { + type "rpc" protocol tcp; + } + + ct helper rpc_udp { + type "rpc" protocol udp; + } + + ct helper tns_tcp { + type "tns" protocol tcp; + } + + chain VYOS_CT_HELPER { + ct helper set "rpc_tcp" tcp dport {111} return + ct helper set "rpc_udp" udp dport {111} return + ct helper set "tns_tcp" tcp dport {1521,1525,1536} return + return + } + + chain VYOS_CT_IGNORE { + return + } + + chain VYOS_CT_TIMEOUT { + return + } + chain VYOS_CT_PREROUTING_HOOK { return } diff --git a/debian/changelog b/debian/changelog index c9d925253..d64c66818 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -vyos-1x (1.4dev0) unstable; urgency=medium +vyos-1x (1.5dev0) unstable; urgency=medium * Dummy changelog entry for vyos-1x repository This is a internal VyOS package and the VyOS package process does not use @@ -7,4 +7,4 @@ vyos-1x (1.4dev0) unstable; urgency=medium The correct verion number of this package is auto-generated by GIT on build-time - -- VyOS maintainers and contributors <maintainers@vyos.io> Mon, 11 Jan 2021 19:02:53 +0100 + -- VyOS maintainers and contributors <maintainers@vyos.io> Sun, 10 Sep 2023 15:42:53 +0200 diff --git a/debian/rules b/debian/rules index e6bbeeafb..9a6ab2996 100755 --- a/debian/rules +++ b/debian/rules @@ -117,6 +117,10 @@ override_dh_auto_install: mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config/ cp -r smoketest/configs/* $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config + # Install smoke test config tests + mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config-tests/ + cp -r smoketest/config-tests/* $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config-tests + # Install system programs mkdir -p $(DIR)/$(VYOS_BIN_DIR) cp -r smoketest/bin/* $(DIR)/$(VYOS_BIN_DIR) diff --git a/debian/vyos-1x-smoketest.install b/debian/vyos-1x-smoketest.install index 406fef4be..739cb189b 100644 --- a/debian/vyos-1x-smoketest.install +++ b/debian/vyos-1x-smoketest.install @@ -3,3 +3,4 @@ usr/bin/vyos-configtest usr/bin/vyos-configtest-pki usr/libexec/vyos/tests/smoke usr/libexec/vyos/tests/config +usr/libexec/vyos/tests/config-tests diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index cff533e0a..b43416152 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -29,6 +29,11 @@ do sed -i "/^# Standard Un\*x authentication\./i${PAM_CONFIG}" $file done +# We do not make use of a TACACS UNIX group - drop it +if grep -q '^tacacs' /etc/group; then + delgroup tacacs +fi + # Both RADIUS and TACACS users belong to aaa group - this must be added first if ! grep -q '^aaa' /etc/group; then addgroup --firstgid 1000 --quiet aaa @@ -42,6 +47,7 @@ if grep -q '^tacacs' /etc/passwd; then vyos_group=vyattaop while [ $level -lt 16 ]; do userdel tacacs${level} || true + rm -rf /home/tacacs${level} || true level=$(( level+1 )) done 2>&1 fi diff --git a/debian/vyos-1x.preinst b/debian/vyos-1x.preinst index e355ffa84..16c118cb7 100644 --- a/debian/vyos-1x.preinst +++ b/debian/vyos-1x.preinst @@ -10,3 +10,4 @@ dpkg-divert --package vyos-1x --add --no-rename /etc/skel/.profile dpkg-divert --package vyos-1x --add --no-rename /etc/sysctl.d/80-vpp.conf dpkg-divert --package vyos-1x --add --no-rename /etc/netplug/netplugd.conf dpkg-divert --package vyos-1x --add --no-rename /etc/netplug/netplug +dpkg-divert --package vyos-1x --add --no-rename /etc/rsyslog.d/45-frr.conf diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in index baab6104f..b35ba8d1c 100644 --- a/interface-definitions/container.xml.in +++ b/interface-definitions/container.xml.in @@ -25,7 +25,7 @@ <properties> <help>Container capabilities/permissions</help> <completionHelp> - <list>net-admin net-bind-service net-raw setpcap sys-admin sys-time</list> + <list>net-admin net-bind-service net-raw setpcap sys-admin sys-module sys-time</list> </completionHelp> <valueHelp> <format>net-admin</format> @@ -48,11 +48,15 @@ <description>Administation operations (quotactl, mount, sethostname, setdomainame)</description> </valueHelp> <valueHelp> + <format>sys-module</format> + <description>Load, unload and delete kernel modules</description> + </valueHelp> + <valueHelp> <format>sys-time</format> <description>Permission to set system clock</description> </valueHelp> <constraint> - <regex>(net-admin|net-bind-service|net-raw|setpcap|sys-admin|sys-time)</regex> + <regex>(net-admin|net-bind-service|net-raw|setpcap|sys-admin|sys-module|sys-time)</regex> </constraint> <multi/> </properties> @@ -110,7 +114,7 @@ <constraint> <regex>[ !#-%&(-~]+</regex> </constraint> - <constraintErrorMessage>Entrypoint must be ascii characters, use &quot; and &apos for double and single quotes respectively</constraintErrorMessage> + <constraintErrorMessage>Entrypoint must be ASCII characters, use &quot; and &apos for double and single quotes respectively</constraintErrorMessage> </properties> </leafNode> <leafNode name="host-name"> @@ -133,7 +137,7 @@ <constraint> <regex>[ !#-%&(-~]+</regex> </constraint> - <constraintErrorMessage>Command must be ascii characters, use &quot; and &apos for double and single quotes respectively</constraintErrorMessage> + <constraintErrorMessage>Command must be ASCII characters, use &quot; and &apos for double and single quotes respectively</constraintErrorMessage> </properties> </leafNode> <leafNode name="arguments"> @@ -142,9 +146,29 @@ <constraint> <regex>[ !#-%&(-~]+</regex> </constraint> - <constraintErrorMessage>The command's arguments must be ascii characters, use &quot; and &apos for double and single quotes respectively</constraintErrorMessage> + <constraintErrorMessage>The command's arguments must be ASCII characters, use &quot; and &apos for double and single quotes respectively</constraintErrorMessage> </properties> </leafNode> + <tagNode name="label"> + <properties> + <help>Add label variables</help> + <constraint> + <regex>[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?</regex> + </constraint> + <constraintErrorMessage>Label variable name must be alphanumeric and can contain hyphen, dots and underscores</constraintErrorMessage> + </properties> + <children> + <leafNode name="value"> + <properties> + <help>Set label option value</help> + <valueHelp> + <format>txt</format> + <description>Set label option value</description> + </valueHelp> + </properties> + </leafNode> + </children> + </tagNode> <leafNode name="memory"> <properties> <help>Memory (RAM) available to this container</help> diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index 127f4b7e7..8e462f3eb 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -284,6 +284,15 @@ </tagNode> </children> </node> + <node name="bridge"> + <properties> + <help>Bridge firewall</help> + </properties> + <children> + #include <include/firewall/bridge-hook-forward.xml.i> + #include <include/firewall/bridge-custom-name.xml.i> + </children> + </node> <node name="ipv4"> <properties> <help>IPv4 firewall</help> diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in index 4f55916fa..47a772d04 100644 --- a/interface-definitions/high-availability.xml.in +++ b/interface-definitions/high-availability.xml.in @@ -12,6 +12,12 @@ <help>Virtual Router Redundancy Protocol settings</help> </properties> <children> + <leafNode name="disable-snmp"> + <properties> + <valueless/> + <help>Disable SNMP</help> + </properties> + </leafNode> <node name="global-parameters"> <properties> <help>VRRP global parameters</help> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i index 75221a348..9ec513da9 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i @@ -1,4 +1,5 @@ <!-- include start from bgp/neighbor-afi-ipv4-ipv6-common.xml.i --> + <leafNode name="addpath-tx-all"> <properties> <help>Use addpath to advertise all paths to a neighbor</help> @@ -156,12 +157,19 @@ </properties> </leafNode> #include <include/bgp/afi-nexthop-self.xml.i> -<leafNode name="remove-private-as"> +<node name="remove-private-as"> <properties> <help>Remove private AS numbers from AS path in outbound route updates</help> - <valueless/> </properties> -</leafNode> + <children> + <leafNode name="all"> + <properties> + <help>Remove private AS numbers to all AS numbers in outbound route updates</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> #include <include/bgp/afi-route-map.xml.i> #include <include/bgp/afi-route-reflector-client.xml.i> #include <include/bgp/afi-route-server-client.xml.i> diff --git a/interface-definitions/include/firewall/action-l2.xml.i b/interface-definitions/include/firewall/action-l2.xml.i new file mode 100644 index 000000000..84af576c8 --- /dev/null +++ b/interface-definitions/include/firewall/action-l2.xml.i @@ -0,0 +1,37 @@ +<!-- include start from firewall/action.xml.i --> +<leafNode name="action"> + <properties> + <help>Rule action</help> + <completionHelp> + <list>accept continue jump return drop queue</list> + </completionHelp> + <valueHelp> + <format>accept</format> + <description>Accept matching entries</description> + </valueHelp> + <valueHelp> + <format>continue</format> + <description>Continue parsing next rule</description> + </valueHelp> + <valueHelp> + <format>jump</format> + <description>Jump to another chain</description> + </valueHelp> + <valueHelp> + <format>return</format> + <description>Return from the current chain and continue at the next rule of the last chain</description> + </valueHelp> + <valueHelp> + <format>drop</format> + <description>Drop matching entries</description> + </valueHelp> + <valueHelp> + <format>queue</format> + <description>Enqueue packet to userspace</description> + </valueHelp> + <constraint> + <regex>(accept|continue|jump|return|drop|queue)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/action.xml.i b/interface-definitions/include/firewall/action.xml.i index 7c6e33839..9391a7bee 100644 --- a/interface-definitions/include/firewall/action.xml.i +++ b/interface-definitions/include/firewall/action.xml.i @@ -3,13 +3,17 @@ <properties> <help>Rule action</help> <completionHelp> - <list>accept jump reject return drop queue</list> + <list>accept continue jump reject return drop queue</list> </completionHelp> <valueHelp> <format>accept</format> <description>Accept matching entries</description> </valueHelp> <valueHelp> + <format>continue</format> + <description>Continue parsing next rule</description> + </valueHelp> + <valueHelp> <format>jump</format> <description>Jump to another chain</description> </valueHelp> @@ -30,7 +34,7 @@ <description>Enqueue packet to userspace</description> </valueHelp> <constraint> - <regex>(accept|jump|reject|return|drop|queue)</regex> + <regex>(accept|continue|jump|reject|return|drop|queue)</regex> </constraint> </properties> </leafNode> diff --git a/interface-definitions/include/firewall/bridge-custom-name.xml.i b/interface-definitions/include/firewall/bridge-custom-name.xml.i new file mode 100644 index 000000000..a85fd5a19 --- /dev/null +++ b/interface-definitions/include/firewall/bridge-custom-name.xml.i @@ -0,0 +1,39 @@ +<!-- include start from firewall/bridge-custom-name.xml.i --> +<tagNode name="name"> + <properties> + <help>Bridge custom firewall</help> + <constraint> + <regex>[a-zA-Z0-9][\w\-\.]*</regex> + </constraint> + </properties> + <children> + #include <include/firewall/default-action.xml.i> + #include <include/firewall/enable-default-log.xml.i> + #include <include/generic-description.xml.i> + <leafNode name="default-jump-target"> + <properties> + <help>Set jump target. Action jump must be defined in default-action to use this setting</help> + <completionHelp> + <path>firewall bridge name</path> + </completionHelp> + </properties> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Bridge Firewall forward filter rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-bridge.xml.i> + </children> + </tagNode> + </children> +</tagNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/bridge-hook-forward.xml.i b/interface-definitions/include/firewall/bridge-hook-forward.xml.i new file mode 100644 index 000000000..23d757070 --- /dev/null +++ b/interface-definitions/include/firewall/bridge-hook-forward.xml.i @@ -0,0 +1,34 @@ +<!-- include start from firewall/bridge-hook-forward.xml.i --> +<node name="forward"> + <properties> + <help>Bridge forward firewall</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Bridge firewall forward filter</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Bridge Firewall forward filter rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-bridge.xml.i> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/common-rule-bridge.xml.i b/interface-definitions/include/firewall/common-rule-bridge.xml.i new file mode 100644 index 000000000..381e04b1e --- /dev/null +++ b/interface-definitions/include/firewall/common-rule-bridge.xml.i @@ -0,0 +1,57 @@ +<!-- include start from firewall/common-rule-bridge.xml.i --> +#include <include/firewall/action-l2.xml.i> +#include <include/firewall/nft-queue.xml.i> +<node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/mac-address.xml.i> + </children> +</node> +<leafNode name="disable"> + <properties> + <help>Option to disable firewall rule</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="jump-target"> + <properties> + <help>Set jump target. Action jump must be defined to use this setting</help> + <completionHelp> + <path>firewall bridge name</path> + </completionHelp> + </properties> +</leafNode> +<leafNode name="log"> + <properties> + <help>Option to log packets matching rule</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable log</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable log</description> + </valueHelp> + <constraint> + <regex>(enable|disable)</regex> + </constraint> + </properties> +</leafNode> +#include <include/firewall/rule-log-options.xml.i> +<node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/mac-address.xml.i> + </children> +</node> +#include <include/firewall/inbound-interface.xml.i> +#include <include/firewall/outbound-interface.xml.i> +#include <include/firewall/match-vlan.xml.i> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/default-action-bridge.xml.i b/interface-definitions/include/firewall/default-action-bridge.xml.i new file mode 100644 index 000000000..858c7aeeb --- /dev/null +++ b/interface-definitions/include/firewall/default-action-bridge.xml.i @@ -0,0 +1,34 @@ +<!-- include start from firewall/default-action.xml.i --> +<leafNode name="default-action"> + <properties> + <help>Default-action for rule-set</help> + <completionHelp> + <list>drop jump return accept continue</list> + </completionHelp> + <valueHelp> + <format>drop</format> + <description>Drop if no prior rules are hit</description> + </valueHelp> + <valueHelp> + <format>jump</format> + <description>Jump to another chain if no prior rules are hit</description> + </valueHelp> + <valueHelp> + <format>return</format> + <description>Return from the current chain and continue at the next rule of the last chain</description> + </valueHelp> + <valueHelp> + <format>accept</format> + <description>Accept if no prior rules are hit</description> + </valueHelp> + <valueHelp> + <format>continue</format> + <description>Continue parsing next rule</description> + </valueHelp> + <constraint> + <regex>(drop|jump|return|accept|continue)</regex> + </constraint> + </properties> + <defaultValue>drop</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/default-action.xml.i b/interface-definitions/include/firewall/default-action.xml.i index 80efaf335..53a161495 100644 --- a/interface-definitions/include/firewall/default-action.xml.i +++ b/interface-definitions/include/firewall/default-action.xml.i @@ -3,7 +3,7 @@ <properties> <help>Default-action for rule-set</help> <completionHelp> - <list>drop jump reject return accept</list> + <list>drop jump reject return accept continue</list> </completionHelp> <valueHelp> <format>drop</format> @@ -25,8 +25,12 @@ <format>accept</format> <description>Accept if no prior rules are hit</description> </valueHelp> + <valueHelp> + <format>continue</format> + <description>Continue parsing next rule</description> + </valueHelp> <constraint> - <regex>(drop|jump|reject|return|accept)</regex> + <regex>(drop|jump|reject|return|accept|continue)</regex> </constraint> </properties> <defaultValue>drop</defaultValue> diff --git a/interface-definitions/include/firewall/flow-offload.xml.i b/interface-definitions/include/firewall/flow-offload.xml.i new file mode 100644 index 000000000..706836362 --- /dev/null +++ b/interface-definitions/include/firewall/flow-offload.xml.i @@ -0,0 +1,47 @@ +<!-- include start from firewall/flow-offload.xml.i --> +<node name="flow-offload"> + <properties> + <help>Configurable flow offload options</help> + </properties> + <children> + <leafNode name="disable"> + <properties> + <help>Disable flow offload</help> + <valueless/> + </properties> + </leafNode> + <node name="software"> + <properties> + <help>Software offload</help> + </properties> + <children> + <leafNode name="interface"> + <properties> + <help>Interfaces to enable</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <multi/> + </properties> + </leafNode> + </children> + </node> + <node name="hardware"> + <properties> + <help>Hardware offload</help> + </properties> + <children> + <leafNode name="interface"> + <properties> + <help>Interfaces to enable</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <multi/> + </properties> + </leafNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i index a63874cb0..03c07e657 100644 --- a/interface-definitions/include/firewall/global-options.xml.i +++ b/interface-definitions/include/firewall/global-options.xml.i @@ -145,21 +145,21 @@ </leafNode> <leafNode name="source-validation"> <properties> - <help>Policy for source validation by reversed path, as specified in RFC3704</help> + <help>Policy for IPv4 source validation by reversed path, as specified in RFC3704</help> <completionHelp> <list>strict loose disable</list> </completionHelp> <valueHelp> <format>strict</format> - <description>Enable Strict Reverse Path Forwarding as defined in RFC3704</description> + <description>Enable IPv4 Strict Reverse Path Forwarding as defined in RFC3704</description> </valueHelp> <valueHelp> <format>loose</format> - <description>Enable Loose Reverse Path Forwarding as defined in RFC3704</description> + <description>Enable IPv4 Loose Reverse Path Forwarding as defined in RFC3704</description> </valueHelp> <valueHelp> <format>disable</format> - <description>No source validation</description> + <description>No IPv4 source validation</description> </valueHelp> <constraint> <regex>(strict|loose|disable)</regex> @@ -227,6 +227,30 @@ </properties> <defaultValue>disable</defaultValue> </leafNode> + <leafNode name="ipv6-source-validation"> + <properties> + <help>Policy for IPv6 source validation by reversed path, as specified in RFC3704</help> + <completionHelp> + <list>strict loose disable</list> + </completionHelp> + <valueHelp> + <format>strict</format> + <description>Enable IPv6 Strict Reverse Path Forwarding as defined in RFC3704</description> + </valueHelp> + <valueHelp> + <format>loose</format> + <description>Enable IPv6 Loose Reverse Path Forwarding as defined in RFC3704</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>No IPv6 source validation</description> + </valueHelp> + <constraint> + <regex>(strict|loose|disable)</regex> + </constraint> + </properties> + <defaultValue>disable</defaultValue> + </leafNode> <leafNode name="ipv6-src-route"> <properties> <help>Policy for handling IPv6 packets with routing extension header</help> @@ -247,6 +271,7 @@ </properties> <defaultValue>disable</defaultValue> </leafNode> + #include <include/firewall/flow-offload.xml.i> </children> </node> <!-- include end --> diff --git a/interface-definitions/include/firewall/match-vlan.xml.i b/interface-definitions/include/firewall/match-vlan.xml.i new file mode 100644 index 000000000..44ad02c99 --- /dev/null +++ b/interface-definitions/include/firewall/match-vlan.xml.i @@ -0,0 +1,41 @@ +<!-- include start from firewall/match-vlan.xml.i --> +<node name="vlan"> + <properties> + <help>VLAN parameters</help> + </properties> + <children> + <leafNode name="id"> + <properties> + <help>Vlan id</help> + <valueHelp> + <format>u32:0-4096</format> + <description>Vlan id</description> + </valueHelp> + <valueHelp> + <format><start-end></format> + <description>Vlan id range to match</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 0-4095"/> + </constraint> + </properties> + </leafNode> + <leafNode name="priority"> + <properties> + <help>Vlan priority(pcp)</help> + <valueHelp> + <format>u32:0-7</format> + <description>Vlan priority</description> + </valueHelp> + <valueHelp> + <format><start-end></format> + <description>Vlan priority range to match</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 0-7"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/source-destination-group-ipv4.xml.i b/interface-definitions/include/firewall/source-destination-group-ipv4.xml.i new file mode 100644 index 000000000..8c34fb933 --- /dev/null +++ b/interface-definitions/include/firewall/source-destination-group-ipv4.xml.i @@ -0,0 +1,41 @@ +<!-- include start from firewall/source-destination-group-ipv4.xml.i --> +<node name="group"> + <properties> + <help>Group</help> + </properties> + <children> + <leafNode name="address-group"> + <properties> + <help>Group of addresses</help> + <completionHelp> + <path>firewall group address-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="domain-group"> + <properties> + <help>Group of domains</help> + <completionHelp> + <path>firewall group domain-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="network-group"> + <properties> + <help>Group of networks</help> + <completionHelp> + <path>firewall group network-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="port-group"> + <properties> + <help>Group of ports</help> + <completionHelp> + <path>firewall group port-group</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/interface/eapol.xml.i b/interface-definitions/include/interface/eapol.xml.i index c4cdeae0c..a3206f2c7 100644 --- a/interface-definitions/include/interface/eapol.xml.i +++ b/interface-definitions/include/interface/eapol.xml.i @@ -4,7 +4,7 @@ <help>Extensible Authentication Protocol over Local Area Network</help> </properties> <children> - #include <include/pki/ca-certificate.xml.i> + #include <include/pki/ca-certificate-multi.xml.i> #include <include/pki/certificate-key.xml.i> </children> </node> diff --git a/interface-definitions/include/version/conntrack-version.xml.i b/interface-definitions/include/version/conntrack-version.xml.i index 696f76362..c0f632c70 100644 --- a/interface-definitions/include/version/conntrack-version.xml.i +++ b/interface-definitions/include/version/conntrack-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/conntrack-version.xml.i --> -<syntaxVersion component='conntrack' version='3'></syntaxVersion> +<syntaxVersion component='conntrack' version='4'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/interfaces-virtual-ethernet.xml.in b/interface-definitions/interfaces-virtual-ethernet.xml.in index 1daa764d4..5f205f354 100644 --- a/interface-definitions/interfaces-virtual-ethernet.xml.in +++ b/interface-definitions/interfaces-virtual-ethernet.xml.in @@ -21,6 +21,7 @@ #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> #include <include/interface/disable.xml.i> + #include <include/interface/netns.xml.i> #include <include/interface/vif-s.xml.i> #include <include/interface/vif.xml.i> #include <include/interface/vrf.xml.i> diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index fb60c93d0..b246d9a09 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -101,6 +101,22 @@ #include <include/interface/redirect.xml.i> #include <include/interface/vrf.xml.i> #include <include/vni.xml.i> + <tagNode name="vlan-to-vni"> + <properties> + <help>Configuring VLAN-to-VNI mappings for EVPN-VXLAN</help> + <valueHelp> + <format>u32:0-4094</format> + <description>Virtual Local Area Network (VLAN) ID</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4094"/> + </constraint> + <constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage> + </properties> + <children> + #include <include/vni.xml.i> + </children> + </tagNode> </children> </tagNode> </children> diff --git a/interface-definitions/protocols-pim6.xml.in b/interface-definitions/protocols-pim6.xml.in new file mode 100644 index 000000000..58ef5a1e3 --- /dev/null +++ b/interface-definitions/protocols-pim6.xml.in @@ -0,0 +1,132 @@ +<?xml version="1.0"?> +<!-- Protocol Independent Multicast for IPv6 (PIMv6) configuration --> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="pim6" owner="${vyos_conf_scripts_dir}/protocols_pim6.py"> + <properties> + <help>Protocol Independent Multicast for IPv6 (PIMv6)</help> + <priority>400</priority> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>PIMv6 interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + <node name="mld"> + <properties> + <help>Multicast Listener Discovery (MLD)</help> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <tagNode name="join"> + <properties> + <help>MLD join multicast group</help> + <valueHelp> + <format>ipv6</format> + <description>Multicast group address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + <children> + <leafNode name="source"> + <properties> + <help>Source address</help> + <valueHelp> + <format>ipv6</format> + <description>Source address</description> + </valueHelp> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --ipv6</script> + </completionHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="version"> + <properties> + <help>MLD version</help> + <completionHelp> + <list>1 2</list> + </completionHelp> + <valueHelp> + <format>1</format> + <description>MLD version 1</description> + </valueHelp> + <valueHelp> + <format>2</format> + <description>MLD version 2</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2"/> + </constraint> + </properties> + <defaultValue>2</defaultValue> + </leafNode> + <leafNode name="interval"> + <properties> + <help>Query interval</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Query interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="max-response-time"> + <properties> + <help>Max query response time</help> + <valueHelp> + <format>u32:100-6553500</format> + <description>Query response value in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 100-6553500"/> + </constraint> + </properties> + </leafNode> + <leafNode name="last-member-query-count"> + <properties> + <help>Last member query count</help> + <valueHelp> + <format>u32:1-255</format> + <description>Count</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="last-member-query-interval"> + <properties> + <help>Last member query interval</help> + <valueHelp> + <format>u32:100-6553500</format> + <description>Last member query interval in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 100-6553500"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service-webproxy.xml.in b/interface-definitions/service-webproxy.xml.in index b24997816..637d57891 100644 --- a/interface-definitions/service-webproxy.xml.in +++ b/interface-definitions/service-webproxy.xml.in @@ -353,7 +353,7 @@ <description>Object size in KB</description> </valueHelp> <constraint> - <validator name="numeric" argument="--range 1-100000"/> + <validator name="numeric" argument="--range 1-1000000"/> </constraint> </properties> </leafNode> diff --git a/interface-definitions/system-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in index 8dad048b8..78d19090c 100644 --- a/interface-definitions/system-conntrack.xml.in +++ b/interface-definitions/system-conntrack.xml.in @@ -9,6 +9,12 @@ <priority>218</priority> </properties> <children> + <leafNode name="flow-accounting"> + <properties> + <help>Enable connection tracking flow accounting</help> + <valueless/> + </properties> + </leafNode> <leafNode name="expect-table-size"> <properties> <help>Size of connection tracking expect table</help> @@ -40,82 +46,177 @@ <help>Customized rules to ignore selective connection tracking</help> </properties> <children> - <tagNode name="rule"> + <node name="ipv4"> <properties> - <help>Rule number</help> - <valueHelp> - <format>u32:1-999999</format> - <description>Number of conntrack ignore rule</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-999999"/> - </constraint> - <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage> + <help>IPv4 rules</help> </properties> <children> - #include <include/generic-description.xml.i> - <node name="destination"> + <tagNode name="rule"> <properties> - <help>Destination parameters</help> + <help>Rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number of conntrack ignore rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage> </properties> <children> - #include <include/nat-address.xml.i> - #include <include/nat-port.xml.i> + #include <include/generic-description.xml.i> + <node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/source-destination-group-ipv4.xml.i> + #include <include/nat-address.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> + <leafNode name="inbound-interface"> + <properties> + <help>Interface to ignore connections tracking on</help> + <completionHelp> + <list>any</list> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + </leafNode> + #include <include/ip-protocol.xml.i> + <leafNode name="protocol"> + <properties> + <help>Protocol to match (protocol name, number, or "all")</help> + <completionHelp> + <script>${vyos_completion_dir}/list_protocols.sh</script> + <list>all tcp_udp</list> + </completionHelp> + <valueHelp> + <format>all</format> + <description>All IP protocols</description> + </valueHelp> + <valueHelp> + <format>tcp_udp</format> + <description>Both TCP and UDP</description> + </valueHelp> + <valueHelp> + <format>u32:0-255</format> + <description>IP protocol number</description> + </valueHelp> + <valueHelp> + <format><protocol></format> + <description>IP protocol name</description> + </valueHelp> + <valueHelp> + <format>!<protocol></format> + <description>IP protocol name</description> + </valueHelp> + <constraint> + <validator name="ip-protocol"/> + </constraint> + </properties> + </leafNode> + <node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/source-destination-group-ipv4.xml.i> + #include <include/nat-address.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> </children> - </node> - <leafNode name="inbound-interface"> - <properties> - <help>Interface to ignore connections tracking on</help> - <completionHelp> - <list>any</list> - <script>${vyos_completion_dir}/list_interfaces</script> - </completionHelp> - </properties> - </leafNode> - #include <include/ip-protocol.xml.i> - <leafNode name="protocol"> + </tagNode> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 rules</help> + </properties> + <children> + <tagNode name="rule"> <properties> - <help>Protocol to match (protocol name, number, or "all")</help> - <completionHelp> - <script>${vyos_completion_dir}/list_protocols.sh</script> - <list>all tcp_udp</list> - </completionHelp> - <valueHelp> - <format>all</format> - <description>All IP protocols</description> - </valueHelp> - <valueHelp> - <format>tcp_udp</format> - <description>Both TCP and UDP</description> - </valueHelp> - <valueHelp> - <format>u32:0-255</format> - <description>IP protocol number</description> - </valueHelp> - <valueHelp> - <format><protocol></format> - <description>IP protocol name</description> - </valueHelp> + <help>Rule number</help> <valueHelp> - <format>!<protocol></format> - <description>IP protocol name</description> + <format>u32:1-999999</format> + <description>Number of conntrack ignore rule</description> </valueHelp> <constraint> - <validator name="ip-protocol"/> + <validator name="numeric" argument="--range 1-999999"/> </constraint> - </properties> - </leafNode> - <node name="source"> - <properties> - <help>Source parameters</help> + <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage> </properties> <children> - #include <include/nat-address.xml.i> - #include <include/nat-port.xml.i> + #include <include/generic-description.xml.i> + <node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/firewall/source-destination-group-ipv6.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> + <leafNode name="inbound-interface"> + <properties> + <help>Interface to ignore connections tracking on</help> + <completionHelp> + <list>any</list> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + </leafNode> + #include <include/ip-protocol.xml.i> + <leafNode name="protocol"> + <properties> + <help>Protocol to match (protocol name, number, or "all")</help> + <completionHelp> + <script>${vyos_completion_dir}/list_protocols.sh</script> + <list>all tcp_udp</list> + </completionHelp> + <valueHelp> + <format>all</format> + <description>All IP protocols</description> + </valueHelp> + <valueHelp> + <format>tcp_udp</format> + <description>Both TCP and UDP</description> + </valueHelp> + <valueHelp> + <format>u32:0-255</format> + <description>IP protocol number</description> + </valueHelp> + <valueHelp> + <format><protocol></format> + <description>IP protocol name</description> + </valueHelp> + <valueHelp> + <format>!<protocol></format> + <description>IP protocol name</description> + </valueHelp> + <constraint> + <validator name="ip-protocol"/> + </constraint> + </properties> + </leafNode> + <node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/firewall/source-destination-group-ipv6.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> </children> - </node> + </tagNode> </children> - </tagNode> + </node> + </children> </node> <node name="log"> diff --git a/op-mode-definitions/firewall.xml.in b/op-mode-definitions/firewall.xml.in index 164ce6b60..4a7ffbb66 100644 --- a/op-mode-definitions/firewall.xml.in +++ b/op-mode-definitions/firewall.xml.in @@ -119,6 +119,7 @@ <path>firewall group address-group</path> <path>firewall group network-group</path> <path>firewall group port-group</path> + <path>firewall group interface-group</path> <path>firewall group ipv6-address-group</path> <path>firewall group ipv6-network-group</path> </completionHelp> @@ -131,6 +132,58 @@ </properties> <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_group</command> </leafNode> + <node name="bridge"> + <properties> + <help>Show bridge firewall</help> + </properties> + <children> + <node name="forward"> + <properties> + <help>Show bridge forward firewall ruleset</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Show bridge forward filter firewall ruleset</help> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>Show summary of bridge forward filter firewall rules</help> + <completionHelp> + <path>firewall bridge forward filter rule</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </node> + </children> + </node> + <tagNode name="name"> + <properties> + <help>Show bridge custom firewall chains</help> + <completionHelp> + <path>firewall bridge name</path> + </completionHelp> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>Show summary of bridge custom firewall ruleset</help> + <completionHelp> + <path>firewall bridge name ${COMP_WORDS[6]} rule</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_family --family $3</command> + </node> <node name="ipv6"> <properties> <help>Show IPv6 firewall</help> @@ -153,10 +206,10 @@ <path>firewall ipv6 forward filter rule</path> </completionHelp> </properties> - <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --rule $7 --ipv6</command> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> </tagNode> </children> - <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --ipv6</command> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> </node> </children> </node> @@ -177,10 +230,10 @@ <path>firewall ipv6 input filter rule</path> </completionHelp> </properties> - <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --rule $7 --ipv6</command> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> </tagNode> </children> - <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --ipv6</command> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> </node> </children> </node> @@ -201,10 +254,10 @@ <path>firewall ipv6 output filter rule</path> </completionHelp> </properties> - <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --rule $7 --ipv6</command> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> </tagNode> </children> - <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --ipv6</command> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> </node> </children> </node> @@ -223,10 +276,10 @@ <path>firewall ipv6 ipv6-name ${COMP_WORDS[6]} rule</path> </completionHelp> </properties> - <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --rule $7 --ipv6</command> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> </tagNode> </children> - <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --ipv6</command> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> </tagNode> </children> <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_family --family $3</command> @@ -253,10 +306,10 @@ <path>firewall ipv4 forward filter rule</path> </completionHelp> </properties> - <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --rule $7</command> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> </tagNode> </children> - <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5</command> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> </node> </children> </node> @@ -277,10 +330,10 @@ <path>firewall ipv4 input filter rule</path> </completionHelp> </properties> - <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --rule $7</command> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> </tagNode> </children> - <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5</command> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> </node> </children> </node> @@ -301,10 +354,10 @@ <path>firewall ipv4 output filter rule</path> </completionHelp> </properties> - <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --rule $7</command> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> </tagNode> </children> - <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5</command> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> </node> </children> </node> @@ -323,10 +376,10 @@ <path>firewall ipv4 name ${COMP_WORDS[6]} rule</path> </completionHelp> </properties> - <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --rule $7</command> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> </tagNode> </children> - <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5</command> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> </tagNode> </children> <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_family --family $3</command> diff --git a/op-mode-definitions/monitor-command.xml.in b/op-mode-definitions/monitor-command.xml.in new file mode 100644 index 000000000..31c68f029 --- /dev/null +++ b/op-mode-definitions/monitor-command.xml.in @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="monitor"> + <children> + <tagNode name="command"> + <properties> + <help>Monitor operational mode command (refreshes every 2 seconds)</help> + </properties> + <command>watch --no-title ${vyos_op_scripts_dir}/vyos-op-cmd-wrapper.sh ${@:3}</command> + </tagNode> + <node name="command"> + <children> + <node name="diff"> + <properties> + <help>Show differences during each run</help> + </properties> + </node> + <tagNode name="diff"> + <properties> + <help>Monitor operational mode command (refreshes every 2 seconds)</help> + </properties> + <command>watch --no-title --differences ${vyos_op_scripts_dir}/vyos-op-cmd-wrapper.sh ${@:4}</command> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/ntp.xml.in b/op-mode-definitions/ntp.xml.in new file mode 100644 index 000000000..b8d0c43ec --- /dev/null +++ b/op-mode-definitions/ntp.xml.in @@ -0,0 +1,49 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ntp"> + <properties> + <help>Show peer status of NTP daemon</help> + </properties> + <command>${vyos_op_scripts_dir}/show_ntp.sh --sourcestats</command> + <children> + <node name="system"> + <properties> + <help>Show parameters about the system clock performance</help> + </properties> + <command>${vyos_op_scripts_dir}/show_ntp.sh --tracking</command> + </node> + </children> + </node> + </children> + </node> + <node name="force"> + <children> + <node name="ntp"> + <properties> + <help>NTP (Network Time Protocol) operations</help> + </properties> + <children> + <node name="synchronization"> + <properties> + <help>Force NTP time synchronization</help> + </properties> + <children> + <tagNode name="vrf"> + <properties> + <help>Force NTP time synchronization in given VRF</help> + <completionHelp> + <path>vrf name</path> + </completionHelp> + </properties> + <command>sudo ip vrf exec $5 chronyc makestep</command> + </tagNode> + </children> + <command>sudo chronyc makestep</command> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-hardware.xml.in b/op-mode-definitions/show-hardware.xml.in index ebd806ba5..21079765a 100644 --- a/op-mode-definitions/show-hardware.xml.in +++ b/op-mode-definitions/show-hardware.xml.in @@ -31,7 +31,7 @@ <properties> <help>Show system DMI details</help> </properties> - <command>${vyatta_bindir}/vyatta-show-dmi</command> + <command>sudo dmidecode</command> </node> <node name="mem"> <properties> diff --git a/op-mode-definitions/show-ip.xml.in b/op-mode-definitions/show-ip.xml.in index d5dbb7850..3caf1f1ea 100644 --- a/op-mode-definitions/show-ip.xml.in +++ b/op-mode-definitions/show-ip.xml.in @@ -33,6 +33,12 @@ </tagNode> </children> </node> + <leafNode name="nht"> + <properties> + <help>Show IPv4 nexthop tracking table</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> </children> </node> </children> diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 747622db6..a2a210543 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -133,47 +133,267 @@ <properties> <help>Show log for Firewall</help> </properties> + <command>journalctl --no-hostname --boot -k | egrep "(ipv[46]|bri)-(FWD|INP|OUT|NAM)"</command> <children> - <tagNode name="ipv6-name"> + <node name="bridge"> <properties> - <help>Show log for a specified firewall (IPv6)</help> - <completionHelp> - <path>firewall ipv6-name</path> - </completionHelp> + <help>Show firewall bridge log</help> </properties> - <command>cat $(printf "%s\n" /var/log/messages* | sort -nr ) | egrep "\[$5-([0-9]+|default)-[ADR]\]"</command> + <command>journalctl --no-hostname --boot -k | egrep "bri-(FWD|INP|OUT|NAM)"</command> <children> - <tagNode name="rule"> + <node name="forward"> + <properties> + <help>Show Bridge forward firewall log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep bri-FWD</command> + <children> + <node name="filter"> + <properties> + <help>Show Bridge firewall forward filter</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep bri-FWD-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall bridge forward filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[bri-FWD-filter-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + <tagNode name="name"> <properties> - <help>Show log for a rule in the specified firewall</help> + <help>Show custom Bridge firewall log</help> <completionHelp> - <path>firewall ipv6-name ${COMP_WORDS[4]} rule</path> + <path>firewall bridge name</path> </completionHelp> </properties> - <command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e "\[$5-$7-[ADR]\]"</command> + <command>journalctl --no-hostname --boot -k | grep bri-NAM-$6</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall bridge name ${COMP_WORDS[5]} rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[bri-NAM-$6-$8-[ADRJC]\]"</command> + </tagNode> + </children> </tagNode> </children> - </tagNode> - <tagNode name="name"> + </node> + <node name="ipv4"> <properties> - <help>Show log for a specified firewall (IPv4)</help> - <completionHelp> - <path>firewall name</path> - </completionHelp> + <help>Show firewall IPv4 log</help> </properties> - <command>cat $(printf "%s\n" /var/log/messages* | sort -nr ) | egrep "\[$5-([0-9]+|default)-[ADR]\]"</command> + <command>journalctl --no-hostname --boot -k | egrep "ipv4-(FWD|INP|OUT|NAM)"</command> <children> - <tagNode name="rule"> + <node name="forward"> <properties> - <help>Show log for a rule in the specified firewall</help> + <help>Show firewall IPv4 forward log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv4-FWD</command> + <children> + <node name="filter"> + <properties> + <help>Show firewall IPv4 forward filter log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv4-FWD-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv4 forward filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv4-FWD-filter-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="input"> + <properties> + <help>Show firewall IPv4 input log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv4-INP</command> + <children> + <node name="filter"> + <properties> + <help>Show firewall IPv4 input filter log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv4-INP-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv4 input filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv4-INP-filter-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + <tagNode name="name"> + <properties> + <help>Show custom IPv4 firewall log</help> <completionHelp> - <path>firewall name ${COMP_WORDS[4]} rule</path> + <path>firewall ipv4 name</path> </completionHelp> </properties> - <command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | egrep "\[$5-$7-[ADR]\]"</command> + <command>journalctl --no-hostname --boot -k | grep ipv4-NAM-$6</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv4 name ${COMP_WORDS[5]} rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv4-NAM-$6-$8-[ADRJC]\]"</command> + </tagNode> + </children> </tagNode> + <node name="output"> + <properties> + <help>Show firewall IPv4 output log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv4-OUT</command> + <children> + <node name="filter"> + <properties> + <help>Show firewall IPv4 output filter log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv4-OUT-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv4 output filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv4-OUT-filter-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> </children> - </tagNode> + </node> + <node name="ipv6"> + <properties> + <help>Show firewall IPv6 log</help> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "ipv6-(FWD|INP|OUT|NAM)"</command> + <children> + <node name="forward"> + <properties> + <help>Show firewall IPv6 forward log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv6-FWD</command> + <children> + <node name="filter"> + <properties> + <help>Show firewall IPv6 forward filter log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv6-FWD-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv6 forward filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv6-FWD-filter-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="input"> + <properties> + <help>Show firewall IPv6 input log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv6-INP</command> + <children> + <node name="filter"> + <properties> + <help>Show firewall IPv6 input filter log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv6-INP-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv6 input filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv6-INP-filter-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + <tagNode name="name"> + <properties> + <help>Show custom IPv6 firewall log</help> + <completionHelp> + <path>firewall ipv6 name</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv6-NAM-$6</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv6 name ${COMP_WORDS[5]} rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv6-NAM-$6-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </tagNode> + <node name="output"> + <properties> + <help>Show firewall IPv6 output log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv6-OUT</command> + <children> + <node name="filter"> + <properties> + <help>Show firewall IPv6 output filter log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv6-OUT-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv6 output filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv6-OUT-filter-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> </children> </node> <leafNode name="flow-accounting"> diff --git a/op-mode-definitions/show-ntp.xml.in b/op-mode-definitions/show-ntp.xml.in deleted file mode 100644 index 0907722af..000000000 --- a/op-mode-definitions/show-ntp.xml.in +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0"?> -<interfaceDefinition> - <node name="show"> - <children> - <node name="ntp"> - <properties> - <help>Show peer status of NTP daemon</help> - </properties> - <command>${vyos_op_scripts_dir}/show_ntp.sh --sourcestats</command> - <children> - <node name="system"> - <properties> - <help>Show parameters about the system clock performance</help> - </properties> - <command>${vyos_op_scripts_dir}/show_ntp.sh --tracking</command> - </node> - </children> - </node> - </children> - </node> -</interfaceDefinition> diff --git a/op-mode-definitions/show-system.xml.in b/op-mode-definitions/show-system.xml.in index 85bfdcdba..116c7460f 100644 --- a/op-mode-definitions/show-system.xml.in +++ b/op-mode-definitions/show-system.xml.in @@ -102,6 +102,55 @@ <help>Show user accounts</help> </properties> <children> + <node name="authentication"> + <properties> + <help>Show user account authentication information</help> + </properties> + <children> + <tagNode name="user"> + <properties> + <help>Show configured user</help> + <completionHelp> + <path>system login user</path> + </completionHelp> + </properties> + <children> + <node name="otp"> + <properties> + <help>Show OTP key information</help> + </properties> + <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="full"</command> + <children> + <leafNode name="full"> + <properties> + <help>Show full settings, including QR code and commands for VyOS</help> + </properties> + <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="full"</command> + </leafNode> + <leafNode name="key-b32"> + <properties> + <help>Show OTP authentication secret in Base32 (used in mobile apps)</help> + </properties> + <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="key-b32"</command> + </leafNode> + <leafNode name="qrcode"> + <properties> + <help>Show OTP authentication QR code</help> + </properties> + <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="qrcode"</command> + </leafNode> + <leafNode name="uri"> + <properties> + <help>Show OTP authentication otpauth URI</help> + </properties> + <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="uri"</command> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> + </node> <node name="users"> <properties> <help>Show user account information</help> diff --git a/op-mode-definitions/show-techsupport_report.xml.in b/op-mode-definitions/show-techsupport_report.xml.in index aa51eacd9..ef051e940 100644 --- a/op-mode-definitions/show-techsupport_report.xml.in +++ b/op-mode-definitions/show-techsupport_report.xml.in @@ -3,6 +3,9 @@ <node name="show"> <children> <node name="tech-support"> + <properties> + <help>Show tech-support report</help> + </properties> <children> <node name="report"> <properties> diff --git a/op-mode-definitions/vpn-ipsec.xml.in b/op-mode-definitions/vpn-ipsec.xml.in index c7ba780a3..b551af2be 100644 --- a/op-mode-definitions/vpn-ipsec.xml.in +++ b/op-mode-definitions/vpn-ipsec.xml.in @@ -177,7 +177,7 @@ <properties> <help>Show all the pre-shared key secrets</help> </properties> - <command>sudo cat /etc/ipsec.secrets | sed 's/#.*//'</command> + <command>${vyos_op_scripts_dir}/ipsec.py show_psk</command> </node> <node name="status"> <properties> diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py index 0fc72e660..dbf17ade4 100644 --- a/python/vyos/config_mgmt.py +++ b/python/vyos/config_mgmt.py @@ -25,12 +25,14 @@ from datetime import datetime from textwrap import dedent from pathlib import Path from tabulate import tabulate +from shutil import copy from vyos.config import Config from vyos.configtree import ConfigTree, ConfigTreeError, show_diff from vyos.defaults import directories from vyos.version import get_full_version_data from vyos.utils.io import ask_yes_no +from vyos.utils.boot import boot_configuration_complete from vyos.utils.process import is_systemd_service_active from vyos.utils.process import rc_cmd @@ -200,9 +202,9 @@ Proceed ?''' raise ConfigMgmtError(out) entry = self._read_tmp_log_entry() - self._add_log_entry(**entry) if self._archive_active_config(): + self._add_log_entry(**entry) self._update_archive() msg = 'Reboot timer stopped' @@ -223,8 +225,6 @@ Proceed ?''' def rollback(self, rev: int, no_prompt: bool=False) -> Tuple[str,int]: """Reboot to config revision 'rev'. """ - from shutil import copy - msg = '' if not self._check_revision_number(rev): @@ -334,10 +334,10 @@ Proceed ?''' user = self._get_user() via = 'init' comment = '' - self._add_log_entry(user, via, comment) # add empty init config before boot-config load for revision # and diff consistency if self._archive_active_config(): + self._add_log_entry(user, via, comment) self._update_archive() os.umask(mask) @@ -352,9 +352,8 @@ Proceed ?''' self._new_log_entry(tmp_file=tmp_log_entry) return - self._add_log_entry() - if self._archive_active_config(): + self._add_log_entry() self._update_archive() def commit_archive(self): @@ -475,22 +474,26 @@ Proceed ?''' conf_file.chmod(0o644) def _archive_active_config(self) -> bool: + save_to_tmp = (boot_configuration_complete() or not + os.path.isfile(archive_config_file)) mask = os.umask(0o113) ext = os.getpid() - tmp_save = f'/tmp/config.boot.{ext}' - save_config(tmp_save) + cmp_saved = f'/tmp/config.boot.{ext}' + if save_to_tmp: + save_config(cmp_saved) + else: + copy(config_file, cmp_saved) try: - if cmp(tmp_save, archive_config_file, shallow=False): - # this will be the case on boot, as well as certain - # re-initialiation instances after delete/set - os.unlink(tmp_save) + if cmp(cmp_saved, archive_config_file, shallow=False): + os.unlink(cmp_saved) + os.umask(mask) return False except FileNotFoundError: pass - rc, out = rc_cmd(f'sudo mv {tmp_save} {archive_config_file}') + rc, out = rc_cmd(f'sudo mv {cmp_saved} {archive_config_file}') os.umask(mask) if rc != 0: @@ -522,9 +525,8 @@ Proceed ?''' return len(l) def _check_revision_number(self, rev: int) -> bool: - # exclude init revision: maxrev = self._get_number_of_revisions() - if not 0 <= rev < maxrev - 1: + if not 0 <= rev < maxrev: return False return True diff --git a/python/vyos/configdep.py b/python/vyos/configdep.py index 7a8559839..05d9a3fa3 100644 --- a/python/vyos/configdep.py +++ b/python/vyos/configdep.py @@ -1,4 +1,4 @@ -# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -17,8 +17,10 @@ import os import json import typing from inspect import stack +from graphlib import TopologicalSorter, CycleError from vyos.utils.system import load_as_module +from vyos.configdict import dict_merge from vyos.defaults import directories from vyos.configsource import VyOSError from vyos import ConfigError @@ -28,6 +30,9 @@ from vyos import ConfigError if typing.TYPE_CHECKING: from vyos.config import Config +dependency_dir = os.path.join(directories['data'], + 'config-mode-dependencies') + dependent_func: dict[str, list[typing.Callable]] = {} def canon_name(name: str) -> str: @@ -40,12 +45,20 @@ def canon_name_of_path(path: str) -> str: def caller_name() -> str: return stack()[-1].filename -def read_dependency_dict() -> dict: - path = os.path.join(directories['data'], - 'config-mode-dependencies.json') - with open(path) as f: - d = json.load(f) - return d +def read_dependency_dict(dependency_dir: str = dependency_dir) -> dict: + res = {} + for dep_file in os.listdir(dependency_dir): + if not dep_file.endswith('.json'): + continue + path = os.path.join(dependency_dir, dep_file) + with open(path) as f: + d = json.load(f) + if dep_file == 'vyos-1x.json': + res = dict_merge(res, d) + else: + res = dict_merge(d, res) + + return res def get_dependency_dict(config: 'Config') -> dict: if hasattr(config, 'cached_dependency_dict'): @@ -93,3 +106,37 @@ def call_dependents(): while l: f = l.pop(0) f() + +def graph_from_dependency_dict(d: dict) -> dict: + g = {} + for k in list(d): + g[k] = set() + # add the dependencies for every sub-case; should there be cases + # that are mutally exclusive in the future, the graphs will be + # distinguished + for el in list(d[k]): + g[k] |= set(d[k][el]) + + return g + +def is_acyclic(d: dict) -> bool: + g = graph_from_dependency_dict(d) + ts = TopologicalSorter(g) + try: + # get node iterator + order = ts.static_order() + # try iteration + _ = [*order] + except CycleError: + return False + + return True + +def check_dependency_graph(dependency_dir: str = dependency_dir, + supplement: str = None) -> bool: + d = read_dependency_dict(dependency_dir=dependency_dir) + if supplement is not None: + with open(supplement) as f: + d = dict_merge(json.load(f), d) + + return is_acyclic(d) diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 5b94bd98b..52f9238b8 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -187,15 +187,14 @@ def verify_eapol(config): if 'ca' not in config['pki']: raise ConfigError('Invalid CA certificate specified for EAPoL') - ca_cert_name = config['eapol']['ca_certificate'] + for ca_cert_name in config['eapol']['ca_certificate']: + if ca_cert_name not in config['pki']['ca']: + raise ConfigError('Invalid CA certificate specified for EAPoL') - if ca_cert_name not in config['pki']['ca']: - raise ConfigError('Invalid CA certificate specified for EAPoL') - - ca_cert = config['pki']['ca'][ca_cert_name] + ca_cert = config['pki']['ca'][ca_cert_name] - if 'certificate' not in ca_cert: - raise ConfigError('Invalid CA certificate specified for EAPoL') + if 'certificate' not in ca_cert: + raise ConfigError('Invalid CA certificate specified for EAPoL') def verify_mirror_redirect(config): """ diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 53ff8259e..3305eb269 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -87,7 +87,14 @@ def nft_action(vyos_action): def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name): output = [] - def_suffix = '6' if ip_name == 'ip6' else '' + #def_suffix = '6' if ip_name == 'ip6' else '' + + if ip_name == 'ip6': + def_suffix = '6' + family = 'ipv6' + else: + def_suffix = '' + family = 'bri' if ip_name == 'bri' else 'ipv4' if 'state' in rule_conf and rule_conf['state']: states = ",".join([s for s, v in rule_conf['state'].items() if v == 'enable']) @@ -244,8 +251,9 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name): if 'log' in rule_conf and rule_conf['log'] == 'enable': action = rule_conf['action'] if 'action' in rule_conf else 'accept' - output.append(f'log prefix "[{fw_name[:19]}-{rule_id}-{action[:1].upper()}]"') - + #output.append(f'log prefix "[{fw_name[:19]}-{rule_id}-{action[:1].upper()}]"') + output.append(f'log prefix "[{family}-{hook}-{fw_name}-{rule_id}-{action[:1].upper()}]"') + ##{family}-{hook}-{fw_name}-{rule_id} if 'log_options' in rule_conf: if 'level' in rule_conf['log_options']: @@ -379,6 +387,13 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name): conn_mark_str = ','.join(rule_conf['connection_mark']) output.append(f'ct mark {{{conn_mark_str}}}') + if 'vlan' in rule_conf: + if 'id' in rule_conf['vlan']: + output.append(f'vlan id {rule_conf["vlan"]["id"]}') + if 'priority' in rule_conf['vlan']: + output.append(f'vlan pcp {rule_conf["vlan"]["priority"]}') + + output.append('counter') if 'set' in rule_conf: @@ -404,7 +419,7 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name): else: output.append('return') - output.append(f'comment "{hook}-{fw_name}-{rule_id}"') + output.append(f'comment "{family}-{hook}-{fw_name}-{rule_id}"') return " ".join(output) def parse_tcp_flags(flags): diff --git a/python/vyos/frr.py b/python/vyos/frr.py index 2e3c8a271..9c9e50ff7 100644 --- a/python/vyos/frr.py +++ b/python/vyos/frr.py @@ -88,7 +88,7 @@ LOG.addHandler(ch2) _frr_daemons = ['zebra', 'bgpd', 'fabricd', 'isisd', 'ospf6d', 'ospfd', 'pbrd', 'pimd', 'ripd', 'ripngd', 'sharpd', 'staticd', 'vrrpd', 'ldpd', - 'bfdd', 'eigrpd', 'babeld'] + 'bfdd', 'eigrpd', 'babeld' ,'pim6d'] path_vtysh = '/usr/bin/vtysh' path_frr_reload = '/usr/lib/frr/frr-reload.py' diff --git a/python/vyos/ifconfig/control.py b/python/vyos/ifconfig/control.py index c8366cb58..7402da55a 100644 --- a/python/vyos/ifconfig/control.py +++ b/python/vyos/ifconfig/control.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-2023 VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -49,6 +49,18 @@ class Control(Section): return popen(command, self.debug) def _cmd(self, command): + import re + if 'netns' in self.config: + # This command must be executed from default netns 'ip link set dev X netns X' + # exclude set netns cmd from netns to avoid: + # failed to run command: ip netns exec ns01 ip link set dev veth20 netns ns01 + pattern = r'ip link set dev (\S+) netns (\S+)' + matches = re.search(pattern, command) + if matches and matches.group(2) == self.config['netns']: + # Command already includes netns and matches desired namespace: + command = command + else: + command = f'ip netns exec {self.config["netns"]} {command}' return cmd(command, self.debug) def _get_command(self, config, name): diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 41ce352ad..050095364 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -38,7 +38,9 @@ from vyos.utils.dict import dict_search from vyos.utils.file import read_file from vyos.utils.network import get_interface_config from vyos.utils.network import get_interface_namespace +from vyos.utils.network import is_netns_interface from vyos.utils.process import is_systemd_service_active +from vyos.utils.process import run from vyos.template import is_ipv4 from vyos.template import is_ipv6 from vyos.utils.network import is_intf_addr_assigned @@ -138,9 +140,6 @@ class Interface(Control): 'validate': assert_mtu, 'shellcmd': 'ip link set dev {ifname} mtu {value}', }, - 'netns': { - 'shellcmd': 'ip link set dev {ifname} netns {value}', - }, 'vrf': { 'convert': lambda v: f'master {v}' if v else 'nomaster', 'shellcmd': 'ip link set dev {ifname} {value}', @@ -175,10 +174,6 @@ class Interface(Control): 'validate': assert_boolean, 'location': '/proc/sys/net/ipv4/conf/{ifname}/bc_forwarding', }, - 'rp_filter': { - 'validate': lambda flt: assert_range(flt,0,3), - 'location': '/proc/sys/net/ipv4/conf/{ifname}/rp_filter', - }, 'ipv6_accept_ra': { 'validate': lambda ara: assert_range(ara,0,3), 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra', @@ -252,9 +247,6 @@ class Interface(Control): 'ipv4_directed_broadcast': { 'location': '/proc/sys/net/ipv4/conf/{ifname}/bc_forwarding', }, - 'rp_filter': { - 'location': '/proc/sys/net/ipv4/conf/{ifname}/rp_filter', - }, 'ipv6_accept_ra': { 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra', }, @@ -286,8 +278,11 @@ class Interface(Control): } @classmethod - def exists(cls, ifname): - return os.path.exists(f'/sys/class/net/{ifname}') + def exists(cls, ifname: str, netns: str=None) -> bool: + cmd = f'ip link show dev {ifname}' + if netns: + cmd = f'ip netns exec {netns} {cmd}' + return run(cmd) == 0 @classmethod def get_config(cls): @@ -355,7 +350,13 @@ class Interface(Control): self.vrrp = VRRP(ifname) def _create(self): + # Do not create interface that already exist or exists in netns + netns = self.config.get('netns', None) + if self.exists(f'{self.ifname}', netns=netns): + return + cmd = 'ip link add dev {ifname} type {type}'.format(**self.config) + if 'netns' in self.config: cmd = f'ip netns exec {netns} {cmd}' self._cmd(cmd) def remove(self): @@ -390,6 +391,9 @@ class Interface(Control): # after interface removal no other commands should be allowed # to be called and instead should raise an Exception: cmd = 'ip link del dev {ifname}'.format(**self.config) + # for delete we can't get data from self.config{'netns'} + netns = get_interface_namespace(self.ifname) + if netns: cmd = f'ip netns exec {netns} {cmd}' return self._cmd(cmd) def _set_vrf_ct_zone(self, vrf): @@ -397,6 +401,10 @@ class Interface(Control): Add/Remove rules in nftables to associate traffic in VRF to an individual conntack zone """ + # Don't allow for netns yet + if 'netns' in self.config: + return None + if vrf: # Get routing table ID for VRF vrf_table_id = get_interface_config(vrf).get('linkinfo', {}).get( @@ -540,36 +548,30 @@ class Interface(Control): if prev_state == 'up': self.set_admin_state('up') - def del_netns(self, netns): - """ - Remove interface from given NETNS. - """ - - # If NETNS does not exist then there is nothing to delete + def del_netns(self, netns: str) -> bool: + """ Remove interface from given network namespace """ + # If network namespace does not exist then there is nothing to delete if not os.path.exists(f'/run/netns/{netns}'): - return None - - # As a PoC we only allow 'dummy' interfaces - if 'dum' not in self.ifname: - return None + return False - # Check if interface realy exists in namespace - if get_interface_namespace(self.ifname) != None: - self._cmd(f'ip netns exec {get_interface_namespace(self.ifname)} ip link del dev {self.ifname}') - return + # Check if interface exists in network namespace + if is_netns_interface(self.ifname, netns): + self._cmd(f'ip netns exec {netns} ip link del dev {self.ifname}') + return True + return False - def set_netns(self, netns): + def set_netns(self, netns: str) -> bool: """ - Add interface from given NETNS. + Add interface from given network namespace Example: >>> from vyos.ifconfig import Interface >>> Interface('dum0').set_netns('foo') """ + self._cmd(f'ip link set dev {self.ifname} netns {netns}') + return True - self.set_interface('netns', netns) - - def set_vrf(self, vrf): + def set_vrf(self, vrf: str) -> bool: """ Add/Remove interface from given VRF instance. @@ -581,10 +583,11 @@ class Interface(Control): tmp = self.get_interface('vrf') if tmp == vrf: - return None + return False self.set_interface('vrf', vrf) self._set_vrf_ct_zone(vrf) + return True def set_arp_cache_tmo(self, tmo): """ @@ -621,6 +624,10 @@ class Interface(Control): >>> from vyos.ifconfig import Interface >>> Interface('eth0').set_tcp_ipv4_mss(1340) """ + # Don't allow for netns yet + if 'netns' in self.config: + return None + self._cleanup_mss_rules('raw', self.ifname) nft_prefix = 'nft add rule raw VYOS_TCP_MSS' base_cmd = f'oifname "{self.ifname}" tcp flags & (syn|rst) == syn' @@ -641,6 +648,10 @@ class Interface(Control): >>> from vyos.ifconfig import Interface >>> Interface('eth0').set_tcp_mss(1320) """ + # Don't allow for netns yet + if 'netns' in self.config: + return None + self._cleanup_mss_rules('ip6 raw', self.ifname) nft_prefix = 'nft add rule ip6 raw VYOS_TCP_MSS' base_cmd = f'oifname "{self.ifname}" tcp flags & (syn|rst) == syn' @@ -745,40 +756,36 @@ class Interface(Control): return None return self.set_interface('ipv4_directed_broadcast', forwarding) - def set_ipv4_source_validation(self, value): - """ - Help prevent attacks used by Spoofing IP Addresses. Reverse path - filtering is a Kernel feature that, when enabled, is designed to ensure - packets that are not routable to be dropped. The easiest example of this - would be and IP Address of the range 10.0.0.0/8, a private IP Address, - being received on the Internet facing interface of the router. + def _cleanup_ipv4_source_validation_rules(self, ifname): + results = self._cmd(f'nft -a list chain ip raw vyos_rpfilter').split("\n") + for line in results: + if f'iifname "{ifname}"' in line: + handle_search = re.search('handle (\d+)', line) + if handle_search: + self._cmd(f'nft delete rule ip raw vyos_rpfilter handle {handle_search[1]}') - As per RFC3074. + def set_ipv4_source_validation(self, mode): """ - if value == 'strict': - value = 1 - elif value == 'loose': - value = 2 - else: - value = 0 - - all_rp_filter = int(read_file('/proc/sys/net/ipv4/conf/all/rp_filter')) - if all_rp_filter > value: - global_setting = 'disable' - if all_rp_filter == 1: global_setting = 'strict' - elif all_rp_filter == 2: global_setting = 'loose' - - from vyos.base import Warning - Warning(f'Global source-validation is set to "{global_setting}", this '\ - f'overrides per interface setting on "{self.ifname}"!') + Set IPv4 reverse path validation - tmp = self.get_interface('rp_filter') - if int(tmp) == value: + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_ipv4_source_validation('strict') + """ + # Don't allow for netns yet + if 'netns' in self.config: return None - return self.set_interface('rp_filter', value) + + self._cleanup_ipv4_source_validation_rules(self.ifname) + nft_prefix = f'nft insert rule ip raw vyos_rpfilter iifname "{self.ifname}"' + if mode in ['strict', 'loose']: + self._cmd(f"{nft_prefix} counter return") + if mode == 'strict': + self._cmd(f"{nft_prefix} fib saddr . iif oif 0 counter drop") + elif mode == 'loose': + self._cmd(f"{nft_prefix} fib saddr oif 0 counter drop") def _cleanup_ipv6_source_validation_rules(self, ifname): - commands = [] results = self._cmd(f'nft -a list chain ip6 raw vyos_rpfilter').split("\n") for line in results: if f'iifname "{ifname}"' in line: @@ -794,8 +801,14 @@ class Interface(Control): >>> from vyos.ifconfig import Interface >>> Interface('eth0').set_ipv6_source_validation('strict') """ + # Don't allow for netns yet + if 'netns' in self.config: + return None + self._cleanup_ipv6_source_validation_rules(self.ifname) - nft_prefix = f'nft add rule ip6 raw vyos_rpfilter iifname "{self.ifname}"' + nft_prefix = f'nft insert rule ip6 raw vyos_rpfilter iifname "{self.ifname}"' + if mode in ['strict', 'loose']: + self._cmd(f"{nft_prefix} counter return") if mode == 'strict': self._cmd(f"{nft_prefix} fib saddr . iif oif 0 counter drop") elif mode == 'loose': @@ -1143,13 +1156,17 @@ class Interface(Control): if addr in self._addr: return False + # get interface network namespace if specified + netns = self.config.get('netns', None) + # add to interface if addr == 'dhcp': self.set_dhcp(True) elif addr == 'dhcpv6': self.set_dhcpv6(True) - elif not is_intf_addr_assigned(self.ifname, addr): - tmp = f'ip addr add {addr} dev {self.ifname}' + elif not is_intf_addr_assigned(self.ifname, addr, netns=netns): + netns_cmd = f'ip netns exec {netns}' if netns else '' + tmp = f'{netns_cmd} ip addr add {addr} dev {self.ifname}' # Add broadcast address for IPv4 if is_ipv4(addr): tmp += ' brd +' @@ -1189,13 +1206,17 @@ class Interface(Control): if not addr: raise ValueError() + # get interface network namespace if specified + netns = self.config.get('netns', None) + # remove from interface if addr == 'dhcp': self.set_dhcp(False) elif addr == 'dhcpv6': self.set_dhcpv6(False) - elif is_intf_addr_assigned(self.ifname, addr): - self._cmd(f'ip addr del "{addr}" dev "{self.ifname}"') + elif is_intf_addr_assigned(self.ifname, addr, netns=netns): + netns_cmd = f'ip netns exec {netns}' if netns else '' + self._cmd(f'{netns_cmd} ip addr del {addr} dev {self.ifname}') else: return False @@ -1215,8 +1236,11 @@ class Interface(Control): self.set_dhcp(False) self.set_dhcpv6(False) + netns = get_interface_namespace(self.ifname) + netns_cmd = f'ip netns exec {netns}' if netns else '' + cmd = f'{netns_cmd} ip addr flush dev {self.ifname}' # flush all addresses - self._cmd(f'ip addr flush dev "{self.ifname}"') + self._cmd(cmd) def add_to_bridge(self, bridge_dict): """ @@ -1371,6 +1395,11 @@ class Interface(Control): # - https://man7.org/linux/man-pages/man8/tc-mirred.8.html # Depening if we are the source or the target interface of the port # mirror we need to setup some variables. + + # Don't allow for netns yet + if 'netns' in self.config: + return None + source_if = self.config['ifname'] mirror_config = None @@ -1471,8 +1500,8 @@ class Interface(Control): # Since the interface is pushed onto a separate logical stack # Configure NETNS if dict_search('netns', config) != None: - self.set_netns(config.get('netns', '')) - return + if not is_netns_interface(self.ifname, self.config['netns']): + self.set_netns(config.get('netns', '')) else: self.del_netns(config.get('netns', '')) diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 6a9911588..1fe5db7cd 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -1,4 +1,4 @@ -# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-2023 VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -13,9 +13,15 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see <http://www.gnu.org/licenses/>. +from json import loads + from vyos import ConfigError +from vyos.configdict import list_diff from vyos.ifconfig import Interface +from vyos.utils.assertion import assert_list from vyos.utils.dict import dict_search +from vyos.utils.network import get_interface_config +from vyos.utils.network import get_vxlan_vlan_tunnels @Interface.register class VXLANIf(Interface): @@ -49,6 +55,13 @@ class VXLANIf(Interface): } } + _command_set = {**Interface._command_set, **{ + 'vlan_tunnel': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'shellcmd': 'bridge link set dev {ifname} vlan_tunnel {value}', + }, + }} + def _create(self): # This table represents a mapping from VyOS internal config dict to # arguments used by iproute2. For more information please refer to: @@ -99,3 +112,54 @@ class VXLANIf(Interface): cmd = f'bridge fdb append to 00:00:00:00:00:00 dst {remote} ' \ 'port {port} dev {ifname}' self._cmd(cmd.format(**self.config)) + + def set_vlan_vni_mapping(self, state): + """ + Controls whether vlan to tunnel mapping is enabled on the port. + By default this flag is off. + """ + if not isinstance(state, bool): + raise ValueError('Value out of range') + + cur_vlan_ids = [] + if 'vlan_to_vni_removed' in self.config: + cur_vlan_ids = self.config['vlan_to_vni_removed'] + for vlan in cur_vlan_ids: + self._cmd(f'bridge vlan del dev {self.ifname} vid {vlan}') + + # Determine current OS Kernel vlan_tunnel setting - only adjust when needed + tmp = get_interface_config(self.ifname) + cur_state = 'on' if dict_search(f'linkinfo.info_slave_data.vlan_tunnel', tmp) == True else 'off' + new_state = 'on' if state else 'off' + if cur_state != new_state: + self.set_interface('vlan_tunnel', new_state) + + # Determine current OS Kernel configured VLANs + os_configured_vlan_ids = get_vxlan_vlan_tunnels(self.ifname) + + if 'vlan_to_vni' in self.config: + add_vlan = list_diff(list(self.config['vlan_to_vni'].keys()), os_configured_vlan_ids) + + for vlan, vlan_config in self.config['vlan_to_vni'].items(): + # VLAN mapping already exists - skip + if vlan not in add_vlan: + continue + + vni = vlan_config['vni'] + # The following commands must be run one after another, + # they can not be combined with linux 6.1 and iproute2 6.1 + self._cmd(f'bridge vlan add dev {self.ifname} vid {vlan}') + self._cmd(f'bridge vlan add dev {self.ifname} vid {vlan} tunnel_info id {vni}') + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # call base class last + super().update(config) + + # Enable/Disable VLAN tunnel mapping + # This is only possible after the interface was assigned to the bridge + self.set_vlan_vni_mapping(dict_search('vlan_to_vni', config) != None) diff --git a/python/vyos/template.py b/python/vyos/template.py index e167488c6..add4d3ce5 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -663,6 +663,85 @@ def nat_static_rule(rule_conf, rule_id, nat_type): from vyos.nat import parse_nat_static_rule return parse_nat_static_rule(rule_conf, rule_id, nat_type) +@register_filter('conntrack_ignore_rule') +def conntrack_ignore_rule(rule_conf, rule_id, ipv6=False): + ip_prefix = 'ip6' if ipv6 else 'ip' + def_suffix = '6' if ipv6 else '' + output = [] + + if 'inbound_interface' in rule_conf: + ifname = rule_conf['inbound_interface'] + if ifname != 'any': + output.append(f'iifname {ifname}') + + if 'protocol' in rule_conf: + proto = rule_conf['protocol'] + output.append(f'meta l4proto {proto}') + + for side in ['source', 'destination']: + if side in rule_conf: + side_conf = rule_conf[side] + prefix = side[0] + + if 'address' in side_conf: + address = side_conf['address'] + operator = '' + if address[0] == '!': + operator = '!=' + address = address[1:] + output.append(f'{ip_prefix} {prefix}addr {operator} {address}') + + if 'port' in side_conf: + port = side_conf['port'] + operator = '' + if port[0] == '!': + operator = '!=' + port = port[1:] + output.append(f'th {prefix}port {operator} {port}') + + if 'group' in side_conf: + group = side_conf['group'] + + if 'address_group' in group: + group_name = group['address_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_prefix} {prefix}addr {operator} @A{def_suffix}_{group_name}') + # Generate firewall group domain-group + elif 'domain_group' in group: + group_name = group['domain_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_prefix} {prefix}addr {operator} @D_{group_name}') + elif 'network_group' in group: + group_name = group['network_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_prefix} {prefix}addr {operator} @N{def_suffix}_{group_name}') + if 'port_group' in group: + group_name = group['port_group'] + + if proto == 'tcp_udp': + proto = 'th' + + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + + output.append(f'{proto} {prefix}port {operator} @P_{group_name}') + + output.append('counter notrack') + output.append(f'comment "ignore-{rule_id}"') + + return " ".join(output) + @register_filter('range_to_regex') def range_to_regex(num_range): """Convert range of numbers or list of ranges diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py index 2f181d8d9..4c579c760 100644 --- a/python/vyos/utils/network.py +++ b/python/vyos/utils/network.py @@ -40,13 +40,19 @@ def interface_exists(interface) -> bool: import os return os.path.exists(f'/sys/class/net/{interface}') -def interface_exists_in_netns(interface_name, netns): +def is_netns_interface(interface, netns): from vyos.utils.process import rc_cmd - rc, out = rc_cmd(f'ip netns exec {netns} ip link show dev {interface_name}') + rc, out = rc_cmd(f'sudo ip netns exec {netns} ip link show dev {interface}') if rc == 0: return True return False +def get_netns_all() -> list: + from json import loads + from vyos.utils.process import cmd + tmp = loads(cmd('ip --json netns ls')) + return [ netns['name'] for netns in tmp ] + def get_vrf_members(vrf: str) -> list: """ Get list of interface VRF members @@ -78,8 +84,7 @@ def get_interface_config(interface): """ Returns the used encapsulation protocol for given interface. If interface does not exist, None is returned. """ - import os - if not os.path.exists(f'/sys/class/net/{interface}'): + if not interface_exists(interface): return None from json import loads from vyos.utils.process import cmd @@ -90,33 +95,63 @@ def get_interface_address(interface): """ Returns the used encapsulation protocol for given interface. If interface does not exist, None is returned. """ - import os - if not os.path.exists(f'/sys/class/net/{interface}'): + if not interface_exists(interface): return None from json import loads from vyos.utils.process import cmd tmp = loads(cmd(f'ip --detail --json addr show dev {interface}'))[0] return tmp -def get_interface_namespace(iface): +def get_interface_namespace(interface: str): """ Returns wich netns the interface belongs to """ from json import loads from vyos.utils.process import cmd - # Check if netns exist - tmp = loads(cmd(f'ip --json netns ls')) - if len(tmp) == 0: - return None - for ns in tmp: + # Bail out early if netns does not exist + tmp = cmd(f'ip --json netns ls') + if not tmp: return None + + for ns in loads(tmp): netns = f'{ns["name"]}' # Search interface in each netns data = loads(cmd(f'ip netns exec {netns} ip --json link show')) for tmp in data: - if iface == tmp["ifname"]: + if interface == tmp["ifname"]: return netns +def is_ipv6_tentative(iface: str, ipv6_address: str) -> bool: + """Check if IPv6 address is in tentative state. + + This function checks if an IPv6 address on a specific network interface is + in the tentative state. IPv6 tentative addresses are not fully configured + and are undergoing Duplicate Address Detection (DAD) to ensure they are + unique on the network. + + Args: + iface (str): The name of the network interface. + ipv6_address (str): The IPv6 address to check. + + Returns: + bool: True if the IPv6 address is tentative, False otherwise. + """ + import json + from vyos.utils.process import rc_cmd + + rc, out = rc_cmd(f'ip -6 --json address show dev {iface} scope global') + if rc: + return False + + data = json.loads(out) + for addr_info in data[0]['addr_info']: + if ( + addr_info.get('local') == ipv6_address and + addr_info.get('tentative', False) + ): + return True + return False + def is_wwan_connected(interface): """ Determine if a given WWAN interface, e.g. wwan0 is connected to the carrier network or not """ @@ -141,8 +176,7 @@ def is_wwan_connected(interface): def get_bridge_fdb(interface): """ Returns the forwarding database entries for a given interface """ - import os - if not os.path.exists(f'/sys/class/net/{interface}'): + if not interface_exists(interface): return None from json import loads from vyos.utils.process import cmd @@ -274,57 +308,33 @@ def is_addr_assigned(ip_address, vrf=None) -> bool: return False -def is_intf_addr_assigned(intf, address) -> bool: +def is_intf_addr_assigned(ifname: str, addr: str, netns: str=None) -> bool: """ Verify if the given IPv4/IPv6 address is assigned to specific interface. It can check both a single IP address (e.g. 192.0.2.1 or a assigned CIDR address 192.0.2.1/24. """ - from vyos.template import is_ipv4 - - from netifaces import ifaddresses - from netifaces import AF_INET - from netifaces import AF_INET6 - - # check if the requested address type is configured at all - # { - # 17: [{'addr': '08:00:27:d9:5b:04', 'broadcast': 'ff:ff:ff:ff:ff:ff'}], - # 2: [{'addr': '10.0.2.15', 'netmask': '255.255.255.0', 'broadcast': '10.0.2.255'}], - # 10: [{'addr': 'fe80::a00:27ff:fed9:5b04%eth0', 'netmask': 'ffff:ffff:ffff:ffff::'}] - # } - try: - addresses = ifaddresses(intf) - except ValueError as e: - print(e) - return False - - # determine IP version (AF_INET or AF_INET6) depending on passed address - addr_type = AF_INET if is_ipv4(address) else AF_INET6 - - # Check every IP address on this interface for a match - netmask = None - if '/' in address: - address, netmask = address.split('/') - for ip in addresses.get(addr_type, []): - # ip can have the interface name in the 'addr' field, we need to remove it - # {'addr': 'fe80::a00:27ff:fec5:f821%eth2', 'netmask': 'ffff:ffff:ffff:ffff::'} - ip_addr = ip['addr'].split('%')[0] - - if not _are_same_ip(address, ip_addr): - continue - - # we do not have a netmask to compare against, they are the same - if not netmask: - return True + import json + import jmespath - prefixlen = '' - if is_ipv4(ip_addr): - prefixlen = sum([bin(int(_)).count('1') for _ in ip['netmask'].split('.')]) - else: - prefixlen = sum([bin(int(_,16)).count('1') for _ in ip['netmask'].split('/')[0].split(':') if _]) + from vyos.utils.process import rc_cmd + from ipaddress import ip_interface - if str(prefixlen) == netmask: - return True + netns_cmd = f'ip netns exec {netns}' if netns else '' + rc, out = rc_cmd(f'{netns_cmd} ip --json address show dev {ifname}') + if rc == 0: + json_out = json.loads(out) + addresses = jmespath.search("[].addr_info[].{family: family, address: local, prefixlen: prefixlen}", json_out) + for address_info in addresses: + family = address_info['family'] + address = address_info['address'] + prefixlen = address_info['prefixlen'] + # Remove the interface name if present in the given address + if '%' in addr: + addr = addr.split('%')[0] + interface = ip_interface(f"{address}/{prefixlen}") + if ip_interface(addr) == interface or address == addr: + return True return False @@ -398,7 +408,7 @@ def is_subnet_connected(subnet, primary=False): return False -def is_afi_configured(interface, afi): +def is_afi_configured(interface: str, afi): """ Check if given address family is configured, or in other words - an IP address is assigned to the interface. """ from netifaces import ifaddresses @@ -415,3 +425,46 @@ def is_afi_configured(interface, afi): return False return afi in addresses + +def get_vxlan_vlan_tunnels(interface: str) -> list: + """ Return a list of strings with VLAN IDs configured in the Kernel """ + from json import loads + from vyos.utils.process import cmd + + if not interface.startswith('vxlan'): + raise ValueError('Only applicable for VXLAN interfaces!') + + # Determine current OS Kernel configured VLANs + # + # $ bridge -j -p vlan tunnelshow dev vxlan0 + # [ { + # "ifname": "vxlan0", + # "tunnels": [ { + # "vlan": 10, + # "vlanEnd": 11, + # "tunid": 10010, + # "tunidEnd": 10011 + # },{ + # "vlan": 20, + # "tunid": 10020 + # } ] + # } ] + # + os_configured_vlan_ids = [] + tmp = loads(cmd(f'bridge --json vlan tunnelshow dev {interface}')) + if tmp: + for tunnel in tmp[0].get('tunnels', {}): + vlanStart = tunnel['vlan'] + if 'vlanEnd' in tunnel: + vlanEnd = tunnel['vlanEnd'] + # Build a real list for user VLAN IDs + vlan_list = list(range(vlanStart, vlanEnd +1)) + # Convert list of integers to list or strings + os_configured_vlan_ids.extend(map(str, vlan_list)) + # Proceed with next tunnel - this one is complete + continue + + # Add single tunel id - not part of a range + os_configured_vlan_ids.append(str(vlanStart)) + + return os_configured_vlan_ids diff --git a/smoketest/bin/vyos-configtest b/smoketest/bin/vyos-configtest index 3e42b0380..c1b602737 100755 --- a/smoketest/bin/vyos-configtest +++ b/smoketest/bin/vyos-configtest @@ -24,6 +24,7 @@ from vyos.configsession import ConfigSession, ConfigSessionError from vyos import ConfigError config_dir = '/usr/libexec/vyos/tests/config' +config_test_dir = '/usr/libexec/vyos/tests/config-tests' save_config = '/tmp/vyos-configtest-save' class DynamicClassBase(unittest.TestCase): @@ -42,7 +43,7 @@ class DynamicClassBase(unittest.TestCase): except OSError: pass -def make_test_function(filename): +def make_test_function(filename, test_path=None): def test_config_load(self): config_path = os.path.join(config_dir, filename) self.session.migrate_and_load_config(config_path) @@ -51,6 +52,16 @@ def make_test_function(filename): except (ConfigError, ConfigSessionError): self.session.discard() self.fail() + + if test_path: + config_commands = self.session.show(['configuration', 'commands']) + + with open(test_path, 'r') as f: + for line in f.readlines(): + if not line or line.startswith("#"): + continue + + self.assertIn(line, config_commands) return test_config_load def class_name_from_func_name(s): @@ -69,10 +80,18 @@ if __name__ == '__main__': config_list.sort() for config in config_list: - test_func = make_test_function(config) + test_path = os.path.join(config_test_dir, config) + + if not os.path.exists(test_path): + test_path = None + else: + log.info(f'Loaded migration result test for config "{config}"') + + test_func = make_test_function(config, test_path) func_name = config.replace('-', '_') klassname = f'TestConfig{class_name_from_func_name(func_name)}' + globals()[klassname] = type(klassname, (DynamicClassBase,), {f'test_{func_name}': test_func}) diff --git a/smoketest/config-tests/basic-vyos b/smoketest/config-tests/basic-vyos new file mode 100644 index 000000000..ef8bf374a --- /dev/null +++ b/smoketest/config-tests/basic-vyos @@ -0,0 +1,62 @@ +set interfaces ethernet eth0 address '192.168.0.1/24' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 speed 'auto' +set interfaces ethernet eth2 duplex 'auto' +set interfaces ethernet eth2 speed 'auto' +set interfaces ethernet eth2 vif 100 address '100.100.0.1/24' +set interfaces ethernet eth2 vif-s 200 address '100.64.200.254/24' +set interfaces ethernet eth2 vif-s 200 vif-c 201 address '100.64.201.254/24' +set interfaces ethernet eth2 vif-s 200 vif-c 202 address '100.64.202.254/24' +set interfaces loopback lo +set protocols static arp interface eth0 address 192.168.0.20 mac '00:50:00:00:00:20' +set protocols static arp interface eth0 address 192.168.0.30 mac '00:50:00:00:00:30' +set protocols static arp interface eth0 address 192.168.0.40 mac '00:50:00:00:00:40' +set protocols static arp interface eth2.100 address 100.100.0.2 mac '00:50:00:00:02:02' +set protocols static arp interface eth2.100 address 100.100.0.3 mac '00:50:00:00:02:03' +set protocols static arp interface eth2.100 address 100.100.0.4 mac '00:50:00:00:02:04' +set protocols static arp interface eth2.200 address 100.64.200.1 mac '00:50:00:00:00:01' +set protocols static arp interface eth2.200 address 100.64.200.2 mac '00:50:00:00:00:02' +set protocols static arp interface eth2.200.201 address 100.64.201.10 mac '00:50:00:00:00:10' +set protocols static arp interface eth2.200.201 address 100.64.201.20 mac '00:50:00:00:00:20' +set protocols static arp interface eth2.200.202 address 100.64.202.30 mac '00:50:00:00:00:30' +set protocols static arp interface eth2.200.202 address 100.64.202.40 mac '00:50:00:00:00:40' +set protocols static route 0.0.0.0/0 next-hop 100.64.0.1 +set service dhcp-server shared-network-name LAN authoritative +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 default-router '192.168.0.1' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-name 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-search 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 name-server '192.168.0.1' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic start '192.168.0.20' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic stop '192.168.0.240' +set service dns forwarding allow-from '192.168.0.0/16' +set service dns forwarding cache-size '10000' +set service dns forwarding dnssec 'off' +set service dns forwarding listen-address '192.168.0.1' +set service ssh ciphers 'aes128-ctr' +set service ssh ciphers 'aes192-ctr' +set service ssh ciphers 'aes256-ctr' +set service ssh ciphers 'chacha20-poly1305@openssh.com' +set service ssh ciphers 'rijndael-cbc@lysator.liu.se' +set service ssh key-exchange 'curve25519-sha256@libssh.org' +set service ssh key-exchange 'diffie-hellman-group1-sha1' +set service ssh key-exchange 'diffie-hellman-group-exchange-sha1' +set service ssh key-exchange 'diffie-hellman-group-exchange-sha256' +set service ssh listen-address '192.168.0.1' +set service ssh port '22' +set system config-management commit-revisions '100' +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system name-server '192.168.0.1' +set system syslog console facility all level 'emerg' +set system syslog console facility mail level 'info' +set system syslog global facility all level 'info' +set system syslog global facility auth level 'info' +set system syslog global facility local7 level 'debug' +set system syslog global preserve-fqdn +set system syslog host syslog.vyos.net facility auth level 'warning' +set system syslog host syslog.vyos.net facility local7 level 'notice' +set system syslog host syslog.vyos.net format octet-counted +set system syslog host syslog.vyos.net port '8000' +set system time-zone 'Europe/Berlin' diff --git a/smoketest/config-tests/dialup-router-medium-vpn b/smoketest/config-tests/dialup-router-medium-vpn new file mode 100644 index 000000000..37baee0fd --- /dev/null +++ b/smoketest/config-tests/dialup-router-medium-vpn @@ -0,0 +1,321 @@ +set firewall global-options all-ping 'enable' +set firewall global-options broadcast-ping 'disable' +set firewall global-options ip-src-route 'disable' +set firewall global-options ipv6-receive-redirects 'disable' +set firewall global-options ipv6-src-route 'disable' +set firewall global-options log-martians 'enable' +set firewall global-options receive-redirects 'disable' +set firewall global-options send-redirects 'enable' +set firewall global-options source-validation 'disable' +set firewall global-options syn-cookies 'disable' +set firewall global-options twa-hazards-protection 'enable' +set firewall ipv4 name test_tcp_flags rule 1 action 'drop' +set firewall ipv4 name test_tcp_flags rule 1 protocol 'tcp' +set firewall ipv4 name test_tcp_flags rule 1 tcp flags ack +set firewall ipv4 name test_tcp_flags rule 1 tcp flags not fin +set firewall ipv4 name test_tcp_flags rule 1 tcp flags not rst +set firewall ipv4 name test_tcp_flags rule 1 tcp flags syn +set high-availability vrrp group LAN address 192.168.0.1/24 +set high-availability vrrp group LAN hello-source-address '192.168.0.250' +set high-availability vrrp group LAN interface 'eth1' +set high-availability vrrp group LAN peer-address '192.168.0.251' +set high-availability vrrp group LAN priority '200' +set high-availability vrrp group LAN vrid '1' +set high-availability vrrp sync-group failover-group member 'LAN' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 mtu '9000' +set interfaces ethernet eth0 offload gro +set interfaces ethernet eth0 offload gso +set interfaces ethernet eth0 offload sg +set interfaces ethernet eth0 offload tso +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth1 address '192.168.0.250/24' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 ip source-validation 'strict' +set interfaces ethernet eth1 mtu '9000' +set interfaces ethernet eth1 offload gro +set interfaces ethernet eth1 offload gso +set interfaces ethernet eth1 offload sg +set interfaces ethernet eth1 offload tso +set interfaces ethernet eth1 speed 'auto' +set interfaces loopback lo +set interfaces openvpn vtun0 encryption cipher 'aes256' +set interfaces openvpn vtun0 hash 'sha512' +set interfaces openvpn vtun0 ip adjust-mss '1380' +set interfaces openvpn vtun0 ip source-validation 'strict' +set interfaces openvpn vtun0 keep-alive failure-count '3' +set interfaces openvpn vtun0 keep-alive interval '30' +set interfaces openvpn vtun0 mode 'client' +set interfaces openvpn vtun0 openvpn-option 'comp-lzo adaptive' +set interfaces openvpn vtun0 openvpn-option 'fast-io' +set interfaces openvpn vtun0 openvpn-option 'persist-key' +set interfaces openvpn vtun0 openvpn-option 'reneg-sec 86400' +set interfaces openvpn vtun0 persistent-tunnel +set interfaces openvpn vtun0 remote-host '192.0.2.10' +set interfaces openvpn vtun0 tls auth-key 'openvpn_vtun0_auth' +set interfaces openvpn vtun0 tls ca-certificate 'openvpn_vtun0_1' +set interfaces openvpn vtun0 tls ca-certificate 'openvpn_vtun0_2' +set interfaces openvpn vtun0 tls certificate 'openvpn_vtun0' +set interfaces openvpn vtun1 authentication password 'vyos1' +set interfaces openvpn vtun1 authentication username 'vyos1' +set interfaces openvpn vtun1 encryption cipher 'aes256' +set interfaces openvpn vtun1 hash 'sha1' +set interfaces openvpn vtun1 ip adjust-mss '1380' +set interfaces openvpn vtun1 keep-alive failure-count '3' +set interfaces openvpn vtun1 keep-alive interval '30' +set interfaces openvpn vtun1 mode 'client' +set interfaces openvpn vtun1 openvpn-option 'comp-lzo adaptive' +set interfaces openvpn vtun1 openvpn-option 'tun-mtu 1500' +set interfaces openvpn vtun1 openvpn-option 'tun-mtu-extra 32' +set interfaces openvpn vtun1 openvpn-option 'mssfix 1300' +set interfaces openvpn vtun1 openvpn-option 'persist-key' +set interfaces openvpn vtun1 openvpn-option 'mute 10' +set interfaces openvpn vtun1 openvpn-option 'route-nopull' +set interfaces openvpn vtun1 openvpn-option 'fast-io' +set interfaces openvpn vtun1 openvpn-option 'reneg-sec 86400' +set interfaces openvpn vtun1 persistent-tunnel +set interfaces openvpn vtun1 protocol 'udp' +set interfaces openvpn vtun1 remote-host '01.foo.com' +set interfaces openvpn vtun1 remote-port '1194' +set interfaces openvpn vtun1 tls auth-key 'openvpn_vtun1_auth' +set interfaces openvpn vtun1 tls ca-certificate 'openvpn_vtun1_1' +set interfaces openvpn vtun1 tls ca-certificate 'openvpn_vtun1_2' +set interfaces openvpn vtun2 authentication password 'vyos2' +set interfaces openvpn vtun2 authentication username 'vyos2' +set interfaces openvpn vtun2 disable +set interfaces openvpn vtun2 encryption cipher 'aes256' +set interfaces openvpn vtun2 hash 'sha512' +set interfaces openvpn vtun2 ip adjust-mss '1380' +set interfaces openvpn vtun2 keep-alive failure-count '3' +set interfaces openvpn vtun2 keep-alive interval '30' +set interfaces openvpn vtun2 mode 'client' +set interfaces openvpn vtun2 openvpn-option 'tun-mtu 1500' +set interfaces openvpn vtun2 openvpn-option 'tun-mtu-extra 32' +set interfaces openvpn vtun2 openvpn-option 'mssfix 1300' +set interfaces openvpn vtun2 openvpn-option 'persist-key' +set interfaces openvpn vtun2 openvpn-option 'mute 10' +set interfaces openvpn vtun2 openvpn-option 'route-nopull' +set interfaces openvpn vtun2 openvpn-option 'fast-io' +set interfaces openvpn vtun2 openvpn-option 'remote-random' +set interfaces openvpn vtun2 openvpn-option 'reneg-sec 86400' +set interfaces openvpn vtun2 persistent-tunnel +set interfaces openvpn vtun2 protocol 'udp' +set interfaces openvpn vtun2 remote-host '01.myvpn.com' +set interfaces openvpn vtun2 remote-host '02.myvpn.com' +set interfaces openvpn vtun2 remote-host '03.myvpn.com' +set interfaces openvpn vtun2 remote-port '1194' +set interfaces openvpn vtun2 tls auth-key 'openvpn_vtun2_auth' +set interfaces openvpn vtun2 tls ca-certificate 'openvpn_vtun2_1' +set interfaces pppoe pppoe0 authentication password 'password' +set interfaces pppoe pppoe0 authentication username 'vyos' +set interfaces pppoe pppoe0 mtu '1500' +set interfaces pppoe pppoe0 source-interface 'eth0' +set interfaces wireguard wg0 address '192.168.10.1/24' +set interfaces wireguard wg0 ip adjust-mss '1380' +set interfaces wireguard wg0 peer blue allowed-ips '192.168.10.3/32' +set interfaces wireguard wg0 peer blue persistent-keepalive '20' +set interfaces wireguard wg0 peer blue preshared-key 'ztFDOY9UyaDvn8N3X97SFMDwIfv7EEfuUIPP2yab6UI=' +set interfaces wireguard wg0 peer blue public-key 'G4pZishpMRrLmd96Kr6V7LIuNGdcUb81gWaYZ+FWkG0=' +set interfaces wireguard wg0 peer green allowed-ips '192.168.10.21/32' +set interfaces wireguard wg0 peer green persistent-keepalive '25' +set interfaces wireguard wg0 peer green preshared-key 'LQ9qmlTh9G4nZu4UgElxRUwg7JB/qoV799aADJOijnY=' +set interfaces wireguard wg0 peer green public-key '5iQUD3VoCDBTPXAPHOwUJ0p7xzKGHEY/wQmgvBVmaFI=' +set interfaces wireguard wg0 peer pink allowed-ips '192.168.10.14/32' +set interfaces wireguard wg0 peer pink allowed-ips '192.168.10.16/32' +set interfaces wireguard wg0 peer pink persistent-keepalive '25' +set interfaces wireguard wg0 peer pink preshared-key 'Qi9Odyx0/5itLPN5C5bEy3uMX+tmdl15QbakxpKlWqQ=' +set interfaces wireguard wg0 peer pink public-key 'i4qNPmxyy9EETL4tIoZOLKJF4p7IlVmpAE15gglnAk4=' +set interfaces wireguard wg0 peer red allowed-ips '192.168.10.4/32' +set interfaces wireguard wg0 peer red persistent-keepalive '20' +set interfaces wireguard wg0 peer red preshared-key 'CumyXX7osvUT9AwnS+m2TEfCaL0Ptc2LfuZ78Sujuk8=' +set interfaces wireguard wg0 peer red public-key 'ALGWvMJCKpHF2tVH3hEIHqUe9iFfAmZATUUok/WQzks=' +set interfaces wireguard wg0 port '7777' +set interfaces wireguard wg1 address '10.89.90.2/30' +set interfaces wireguard wg1 ip adjust-mss '1380' +set interfaces wireguard wg1 peer sam address '192.0.2.45' +set interfaces wireguard wg1 peer sam allowed-ips '10.1.1.0/24' +set interfaces wireguard wg1 peer sam allowed-ips '10.89.90.1/32' +set interfaces wireguard wg1 peer sam persistent-keepalive '20' +set interfaces wireguard wg1 peer sam port '1200' +set interfaces wireguard wg1 peer sam preshared-key 'XpFtzx2Z+nR8pBv9/sSf7I94OkZkVYTz0AeU5Q/QQUE=' +set interfaces wireguard wg1 peer sam public-key 'v5zfKGvH6W/lfDXJ0en96lvKo1gfFxMUWxe02+Fj5BU=' +set interfaces wireguard wg1 port '7778' +set nat destination rule 50 destination port '49371' +set nat destination rule 50 inbound-interface 'pppoe0' +set nat destination rule 50 protocol 'tcp_udp' +set nat destination rule 50 translation address '192.168.0.5' +set nat destination rule 51 destination port '58050-58051' +set nat destination rule 51 inbound-interface 'pppoe0' +set nat destination rule 51 protocol 'tcp' +set nat destination rule 51 translation address '192.168.0.5' +set nat destination rule 52 destination port '22067-22070' +set nat destination rule 52 inbound-interface 'pppoe0' +set nat destination rule 52 protocol 'tcp' +set nat destination rule 52 translation address '192.168.0.5' +set nat destination rule 53 destination port '34342' +set nat destination rule 53 inbound-interface 'pppoe0' +set nat destination rule 53 protocol 'tcp_udp' +set nat destination rule 53 translation address '192.168.0.121' +set nat destination rule 54 destination port '45459' +set nat destination rule 54 inbound-interface 'pppoe0' +set nat destination rule 54 protocol 'tcp_udp' +set nat destination rule 54 translation address '192.168.0.120' +set nat destination rule 55 destination port '22' +set nat destination rule 55 inbound-interface 'pppoe0' +set nat destination rule 55 protocol 'tcp' +set nat destination rule 55 translation address '192.168.0.5' +set nat destination rule 56 destination port '8920' +set nat destination rule 56 inbound-interface 'pppoe0' +set nat destination rule 56 protocol 'tcp' +set nat destination rule 56 translation address '192.168.0.5' +set nat destination rule 60 destination port '80,443' +set nat destination rule 60 inbound-interface 'pppoe0' +set nat destination rule 60 protocol 'tcp' +set nat destination rule 60 translation address '192.168.0.5' +set nat destination rule 70 destination port '5001' +set nat destination rule 70 inbound-interface 'pppoe0' +set nat destination rule 70 protocol 'tcp' +set nat destination rule 70 translation address '192.168.0.5' +set nat destination rule 80 destination port '25' +set nat destination rule 80 inbound-interface 'pppoe0' +set nat destination rule 80 protocol 'tcp' +set nat destination rule 80 translation address '192.168.0.5' +set nat destination rule 90 destination port '8123' +set nat destination rule 90 inbound-interface 'pppoe0' +set nat destination rule 90 protocol 'tcp' +set nat destination rule 90 translation address '192.168.0.7' +set nat destination rule 91 destination port '1880' +set nat destination rule 91 inbound-interface 'pppoe0' +set nat destination rule 91 protocol 'tcp' +set nat destination rule 91 translation address '192.168.0.7' +set nat destination rule 500 destination address '!192.168.0.0/24' +set nat destination rule 500 destination port '53' +set nat destination rule 500 inbound-interface 'eth1' +set nat destination rule 500 protocol 'tcp_udp' +set nat destination rule 500 source address '!192.168.0.1-192.168.0.5' +set nat destination rule 500 translation address '192.168.0.1' +set nat source rule 1000 outbound-interface 'pppoe0' +set nat source rule 1000 translation address 'masquerade' +set nat source rule 2000 outbound-interface 'vtun0' +set nat source rule 2000 source address '192.168.0.0/16' +set nat source rule 2000 translation address 'masquerade' +set nat source rule 3000 outbound-interface 'vtun1' +set nat source rule 3000 translation address 'masquerade' +set policy prefix-list user1-routes rule 1 action 'permit' +set policy prefix-list user1-routes rule 1 prefix '192.168.0.0/24' +set policy prefix-list user2-routes rule 1 action 'permit' +set policy prefix-list user2-routes rule 1 prefix '10.1.1.0/24' +set policy route LAN-POLICY-BASED-ROUTING interface 'eth1' +set policy route LAN-POLICY-BASED-ROUTING rule 10 destination +set policy route LAN-POLICY-BASED-ROUTING rule 10 disable +set policy route LAN-POLICY-BASED-ROUTING rule 10 set table '10' +set policy route LAN-POLICY-BASED-ROUTING rule 10 source address '192.168.0.119/32' +set policy route LAN-POLICY-BASED-ROUTING rule 20 destination +set policy route LAN-POLICY-BASED-ROUTING rule 20 set table '100' +set policy route LAN-POLICY-BASED-ROUTING rule 20 source address '192.168.0.240' +set policy route-map rm-static-to-bgp rule 10 action 'permit' +set policy route-map rm-static-to-bgp rule 10 match ip address prefix-list 'user1-routes' +set policy route-map rm-static-to-bgp rule 100 action 'deny' +set policy route6 LAN6-POLICY-BASED-ROUTING interface 'eth1' +set policy route6 LAN6-POLICY-BASED-ROUTING rule 10 destination +set policy route6 LAN6-POLICY-BASED-ROUTING rule 10 disable +set policy route6 LAN6-POLICY-BASED-ROUTING rule 10 set table '10' +set policy route6 LAN6-POLICY-BASED-ROUTING rule 10 source address '2002::1' +set policy route6 LAN6-POLICY-BASED-ROUTING rule 20 destination +set policy route6 LAN6-POLICY-BASED-ROUTING rule 20 set table '100' +set policy route6 LAN6-POLICY-BASED-ROUTING rule 20 source address '2008::f' +set protocols bgp address-family ipv4-unicast redistribute connected route-map 'rm-static-to-bgp' +set protocols bgp neighbor 10.89.90.1 address-family ipv4-unicast nexthop-self +set protocols bgp neighbor 10.89.90.1 address-family ipv4-unicast prefix-list export 'user1-routes' +set protocols bgp neighbor 10.89.90.1 address-family ipv4-unicast prefix-list import 'user2-routes' +set protocols bgp neighbor 10.89.90.1 address-family ipv4-unicast soft-reconfiguration inbound +set protocols bgp neighbor 10.89.90.1 password 'ericandre2020' +set protocols bgp neighbor 10.89.90.1 remote-as '64589' +set protocols bgp parameters log-neighbor-changes +set protocols bgp parameters router-id '10.89.90.2' +set protocols bgp system-as '64590' +set protocols static route 100.64.160.23/32 interface pppoe0 +set protocols static route 100.64.165.25/32 interface pppoe0 +set protocols static route 100.64.165.26/32 interface pppoe0 +set protocols static route 100.64.198.0/24 interface vtun0 +set protocols static table 10 route 0.0.0.0/0 interface vtun1 +set protocols static table 100 route 0.0.0.0/0 next-hop 192.168.10.5 +set service conntrack-sync accept-protocol 'tcp' +set service conntrack-sync accept-protocol 'udp' +set service conntrack-sync accept-protocol 'icmp' +set service conntrack-sync disable-external-cache +set service conntrack-sync event-listen-queue-size '8' +set service conntrack-sync expect-sync 'all' +set service conntrack-sync failover-mechanism vrrp sync-group 'failover-group' +set service conntrack-sync interface eth1 peer '192.168.0.251' +set service conntrack-sync sync-queue-size '8' +set service dhcp-server failover name 'DHCP02' +set service dhcp-server failover remote '192.168.0.251' +set service dhcp-server failover source-address '192.168.0.250' +set service dhcp-server failover status 'primary' +set service dhcp-server shared-network-name LAN authoritative +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 default-router '192.168.0.1' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-name 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-search 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 enable-failover +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 lease '86400' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 name-server '192.168.0.1' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic start '192.168.0.200' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic stop '192.168.0.240' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Audio ip-address '192.168.0.107' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Audio mac-address '00:50:01:dc:91:14' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping IPTV ip-address '192.168.0.104' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping IPTV mac-address '00:50:01:31:b5:f6' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus ip-address '192.168.0.60' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus mac-address '00:50:01:58:ac:95' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus static-mapping-parameters 'option domain-name-servers 192.168.0.6,192.168.0.17;' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 ip-address '192.168.0.109' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 mac-address '00:50:01:bc:ac:51' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 static-mapping-parameters 'option domain-name-servers 192.168.0.6,192.168.0.17;' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 ip-address '192.168.0.11' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 mac-address '00:50:01:70:b9:4d' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 static-mapping-parameters 'option domain-name-servers 192.168.0.6,192.168.0.17;' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 ip-address '192.168.0.12' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 mac-address '00:50:01:70:b7:4f' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 static-mapping-parameters 'option domain-name-servers 192.168.0.6,192.168.0.17;' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping pearTV ip-address '192.168.0.101' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping pearTV mac-address '00:50:01:ba:62:79' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping sand ip-address '192.168.0.110' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping sand mac-address '00:50:01:af:c5:d2' +set service dns forwarding allow-from '192.168.0.0/16' +set service dns forwarding cache-size '8192' +set service dns forwarding dnssec 'off' +set service dns forwarding listen-address '192.168.0.1' +set service dns forwarding name-server 100.64.0.1 +set service dns forwarding name-server 100.64.0.2 +set service ntp allow-client address '192.168.0.0/16' +set service ntp server nz.pool.ntp.org prefer +set service snmp community AwesomeCommunity authorization 'ro' +set service snmp community AwesomeCommunity client '127.0.0.1' +set service snmp community AwesomeCommunity network '192.168.0.0/24' +set service ssh access-control allow user 'vyos' +set service ssh client-keepalive-interval '60' +set service ssh listen-address '192.168.0.1' +set service ssh listen-address '192.168.10.1' +set service ssh listen-address '192.168.0.250' +set system config-management commit-revisions '100' +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system ip arp table-size '1024' +set system name-server '192.168.0.1' +set system name-server 'pppoe0' +set system option ctrl-alt-delete 'ignore' +set system option reboot-on-panic +set system option startup-beep +set system static-host-mapping host-name host60.vyos.net inet '192.168.0.60' +set system static-host-mapping host-name host104.vyos.net inet '192.168.0.104' +set system static-host-mapping host-name host107.vyos.net inet '192.168.0.107' +set system static-host-mapping host-name host109.vyos.net inet '192.168.0.109' +set system sysctl parameter net.core.default_qdisc value 'fq' +set system sysctl parameter net.ipv4.tcp_congestion_control value 'bbr' +set system syslog global facility all level 'info' +set system syslog host 192.168.0.252 facility all level 'debug' +set system syslog host 192.168.0.252 protocol 'udp' +set system task-scheduler task Update-Blacklists executable path '/config/scripts/vyos-foo-update.script' +set system task-scheduler task Update-Blacklists interval '3h' +set system time-zone 'Pacific/Auckland' diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos index 033c1a518..78dba3ee2 100644 --- a/smoketest/configs/basic-vyos +++ b/smoketest/configs/basic-vyos @@ -116,6 +116,18 @@ system { speed 115200 } } + conntrack { + ignore { + rule 1 { + destination { + address 192.0.2.2 + } + source { + address 192.0.2.1 + } + } + } + } host-name vyos login { user vyos { diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 820024dc9..51ccbc9e6 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -834,8 +834,12 @@ class BasicInterfaceTest: self.assertEqual('1', tmp) if cli_defined(self._base_path + ['ip'], 'source-validation'): - tmp = read_file(f'{proc_base}/rp_filter') - self.assertEqual('2', tmp) + base_options = f'iifname "{interface}"' + out = cmd('sudo nft list chain ip raw vyos_rpfilter') + for line in out.splitlines(): + if line.startswith(base_options): + self.assertIn('fib saddr oif 0', line) + self.assertIn('drop', line) def test_interface_ipv6_options(self): if not self._test_ipv6: diff --git a/smoketest/scripts/cli/test_dependency_graph.py b/smoketest/scripts/cli/test_dependency_graph.py deleted file mode 100755 index 45a40acc4..000000000 --- a/smoketest/scripts/cli/test_dependency_graph.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2022 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import json -import unittest -from graphlib import TopologicalSorter, CycleError - -DEP_FILE = '/usr/share/vyos/config-mode-dependencies.json' - -def graph_from_dict(d): - g = {} - for k in list(d): - g[k] = set() - # add the dependencies for every sub-case; should there be cases - # that are mutally exclusive in the future, the graphs will be - # distinguished - for el in list(d[k]): - g[k] |= set(d[k][el]) - return g - -class TestDependencyGraph(unittest.TestCase): - def setUp(self): - with open(DEP_FILE) as f: - dd = json.load(f) - self.dependency_graph = graph_from_dict(dd) - - def test_cycles(self): - ts = TopologicalSorter(self.dependency_graph) - out = None - try: - # get node iterator - order = ts.static_order() - # try iteration - _ = [*order] - except CycleError as e: - out = e.args - - self.assertIsNone(out) - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index ee6ccb710..391ef03ff 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -274,8 +274,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): ['meta l4proto gre', f'oifname != "{interface}"', 'drop'], ['meta l4proto icmp', f'ct mark {mark_hex}', 'return'], ['chain NAME_smoketest'], - ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[smoketest-1-A]" log level debug', 'ip ttl 15', 'accept'], - ['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[smoketest-2-R]" log level err', 'ip ttl > 102', 'reject'], + ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[ipv4-NAM-smoketest-1-A]" log level debug', 'ip ttl 15', 'accept'], + ['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[ipv4-NAM-smoketest-2-R]" log level err', 'ip ttl > 102', 'reject'], ['log prefix "[smoketest-default-D]"','smoketest default-action', 'drop'] ] @@ -331,7 +331,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): [f'meta l4proto tcp','queue to 3'], [f'meta l4proto udp','queue flags bypass,fanout to 0-15'], [f'chain NAME_{name}'], - ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', f'log prefix "[{name}-6-A]" log group 66 snaplen 6666 queue-threshold 32000', 'accept'], + ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', f'log prefix "[ipv4-NAM-{name}-6-A]" log group 66 snaplen 6666 queue-threshold 32000', 'accept'], ['ip length 1-30000', 'ip length != 60000-65535', 'ip dscp 0x03-0x0b', 'ip dscp != 0x15-0x19', 'accept'], [f'log prefix "[{name}-default-D]"', 'drop'] ] @@ -412,7 +412,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): ['type filter hook output priority filter; policy drop;'], ['meta l4proto gre', f'oifname "{interface}"', 'return'], [f'chain NAME6_{name}'], - ['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[v6-smoketest-1-A]" log level crit', 'accept'], + ['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[ipv6-NAM-v6-smoketest-1-A]" log level crit', 'accept'], [f'"{name} default-action drop"', f'log prefix "[{name}-default-D]"', 'drop'] ] @@ -526,26 +526,65 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.verify_nftables_chain([['accept']], 'raw', 'FW_CONNTRACK') self.verify_nftables_chain([['return']], 'ip6 raw', 'FW_CONNTRACK') + def test_bridge_basic_rules(self): + name = 'smoketest' + interface_in = 'eth0' + mac_address = '00:53:00:00:00:01' + vlan_id = '12' + vlan_prior = '3' + + self.cli_set(['firewall', 'bridge', 'name', name, 'default-action', 'accept']) + self.cli_set(['firewall', 'bridge', 'name', name, 'enable-default-log']) + self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'source', 'mac-address', mac_address]) + self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'inbound-interface', 'interface-name', interface_in]) + self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'log', 'enable']) + self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'log-options', 'level', 'crit']) + + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'default-action', 'drop']) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'vlan', 'id', vlan_id]) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'action', 'jump']) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'jump-target', name]) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'vlan', 'priority', vlan_prior]) + + self.cli_commit() + + nftables_search = [ + ['chain VYOS_FORWARD_filter'], + ['type filter hook forward priority filter; policy drop;'], + [f'vlan id {vlan_id}', 'accept'], + [f'vlan pcp {vlan_prior}', f'jump NAME_{name}'], + [f'chain NAME_{name}'], + [f'ether saddr {mac_address}', f'iifname "{interface_in}"', f'log prefix "[bri-NAM-{name}-1-A]" log level crit', 'accept'] + ] + + self.verify_nftables(nftables_search, 'bridge vyos_filter') + def test_source_validation(self): # Strict self.cli_set(['firewall', 'global-options', 'source-validation', 'strict']) + self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'strict']) self.cli_commit() nftables_strict_search = [ ['fib saddr . iif oif 0', 'drop'] ] - self.verify_nftables(nftables_strict_search, 'inet vyos_global_rpfilter') + self.verify_nftables_chain(nftables_strict_search, 'ip raw', 'vyos_global_rpfilter') + self.verify_nftables_chain(nftables_strict_search, 'ip6 raw', 'vyos_global_rpfilter') # Loose self.cli_set(['firewall', 'global-options', 'source-validation', 'loose']) + self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'loose']) self.cli_commit() nftables_loose_search = [ ['fib saddr oif 0', 'drop'] ] - self.verify_nftables(nftables_loose_search, 'inet vyos_global_rpfilter') + self.verify_nftables_chain(nftables_loose_search, 'ip raw', 'vyos_global_rpfilter') + self.verify_nftables_chain(nftables_loose_search, 'ip6 raw', 'vyos_global_rpfilter') def test_sysfs(self): for name, conf in sysfs_config.items(): @@ -564,5 +603,17 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): with open(path, 'r') as f: self.assertNotEqual(f.read().strip(), conf['default'], msg=path) + def test_flow_offload_software(self): + self.cli_set(['firewall', 'global-options', 'flow-offload', 'software', 'interface', 'eth0']) + self.cli_commit() + nftables_search = [ + ['flowtable VYOS_FLOWTABLE_software'], + ['hook ingress priority filter - 1'], + ['devices = { eth0 }'], + ['flow add @VYOS_FLOWTABLE_software'], + ] + self.verify_nftables(nftables_search, 'inet vyos_offload') + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index 5ea21fea8..a39b81348 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -250,10 +250,19 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): for interface in self._interfaces: # Enable EAPoL self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-server-ca-intermediate']) + self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-client-ca-intermediate']) self.cli_set(self._base_path + [interface, 'eapol', 'certificate', cert_name]) self.cli_commit() + # Test multiple CA chains + self.assertEqual(get_certificate_count(interface, 'ca'), 4) + + for interface in self._interfaces: + self.cli_delete(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-client-ca-intermediate']) + + self.cli_commit() + # Check for running process self.assertTrue(process_named_running('wpa_supplicant')) diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py index d1ece84d6..4a7e2418c 100755 --- a/smoketest/scripts/cli/test_interfaces_openvpn.py +++ b/smoketest/scripts/cli/test_interfaces_openvpn.py @@ -421,7 +421,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase): # IP pool configuration netmask = IPv4Network(subnet).netmask network = IPv4Network(subnet).network_address - self.assertIn(f'server {network} {netmask} nopool', config) + self.assertIn(f'server {network} {netmask}', config) # Verify client client_config = read_file(client_config_file) @@ -442,80 +442,6 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase): interface = f'vtun{ii}' self.assertNotIn(interface, interfaces()) - def test_openvpn_server_net30_topology(self): - # Create OpenVPN server interfaces (net30) using different client - # subnets. Validate configuration afterwards. - auth_hash = 'sha256' - num_range = range(20, 25) - port = '' - for ii in num_range: - interface = f'vtun{ii}' - subnet = f'192.0.{ii}.0/24' - path = base_path + [interface] - port = str(2000 + ii) - - self.cli_set(path + ['device-type', 'tun']) - self.cli_set(path + ['encryption', 'cipher', 'aes192']) - self.cli_set(path + ['hash', auth_hash]) - self.cli_set(path + ['mode', 'server']) - self.cli_set(path + ['local-port', port]) - self.cli_set(path + ['server', 'subnet', subnet]) - self.cli_set(path + ['server', 'topology', 'net30']) - self.cli_set(path + ['replace-default-route']) - self.cli_set(path + ['keep-alive', 'failure-count', '10']) - self.cli_set(path + ['keep-alive', 'interval', '5']) - self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test']) - self.cli_set(path + ['tls', 'certificate', 'ovpn_test']) - self.cli_set(path + ['tls', 'dh-params', 'ovpn_test']) - self.cli_set(path + ['vrf', vrf_name]) - - self.cli_commit() - - for ii in num_range: - interface = f'vtun{ii}' - subnet = f'192.0.{ii}.0/24' - start_addr = inc_ip(subnet, '4') - stop_addr = dec_ip(last_host_address(subnet), '1') - port = str(2000 + ii) - - config_file = f'/run/openvpn/{interface}.conf' - config = read_file(config_file) - - self.assertIn(f'dev {interface}', config) - self.assertIn(f'dev-type tun', config) - self.assertIn(f'persist-key', config) - self.assertIn(f'proto udp', config) # default protocol - self.assertIn(f'auth {auth_hash}', config) - self.assertIn(f'cipher AES-192-CBC', config) - self.assertIn(f'topology net30', config) - self.assertIn(f'lport {port}', config) - self.assertIn(f'push "redirect-gateway def1"', config) - self.assertIn(f'keepalive 5 50', config) - - # TLS options - self.assertIn(f'ca /run/openvpn/{interface}_ca.pem', config) - self.assertIn(f'cert /run/openvpn/{interface}_cert.pem', config) - self.assertIn(f'key /run/openvpn/{interface}_cert.key', config) - self.assertIn(f'dh /run/openvpn/{interface}_dh.pem', config) - - # IP pool configuration - netmask = IPv4Network(subnet).netmask - network = IPv4Network(subnet).network_address - self.assertIn(f'server {network} {netmask} nopool', config) - self.assertIn(f'ifconfig-pool {start_addr} {stop_addr}', config) - - self.assertTrue(process_named_running(PROCESS_NAME)) - self.assertEqual(get_vrf(interface), vrf_name) - self.assertIn(interface, interfaces()) - - # check that no interface remained after deleting them - self.cli_delete(base_path) - self.cli_commit() - - for ii in num_range: - interface = f'vtun{ii}' - self.assertNotIn(interface, interfaces()) - def test_openvpn_site2site_verify(self): # Create one OpenVPN site2site interface and check required # verify() stages diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py index f6b203de4..e9c9e68fd 100755 --- a/smoketest/scripts/cli/test_interfaces_vxlan.py +++ b/smoketest/scripts/cli/test_interfaces_vxlan.py @@ -20,6 +20,8 @@ from vyos.configsession import ConfigSessionError from vyos.ifconfig import Interface from vyos.utils.network import get_bridge_fdb from vyos.utils.network import get_interface_config +from vyos.utils.network import interface_exists +from vyos.utils.network import get_vxlan_vlan_tunnels from vyos.template import is_ipv6 from base_interfaces_test import BasicInterfaceTest @@ -133,5 +135,53 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): self.assertTrue(options['linkinfo']['info_data']['external']) self.assertEqual('vxlan', options['linkinfo']['info_kind']) + def test_vxlan_vlan_vni_mapping(self): + bridge = 'br0' + interface = 'vxlan0' + source_interface = 'eth0' + + vlan_to_vni = { + '10': '10010', + '11': '10011', + '12': '10012', + '13': '10013', + '20': '10020', + '30': '10030', + '31': '10031', + } + + self.cli_set(self._base_path + [interface, 'external']) + self.cli_set(self._base_path + [interface, 'source-interface', source_interface]) + + for vlan, vni in vlan_to_vni.items(): + self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni]) + + # This must fail as this VXLAN interface is not associated with any bridge + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(['interfaces', 'bridge', bridge, 'member', 'interface', interface]) + + # It is not allowed to use duplicate VNIs + self.cli_set(self._base_path + [interface, 'vlan-to-vni', '11', 'vni', vlan_to_vni['10']]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + # restore VLAN - VNI mappings + for vlan, vni in vlan_to_vni.items(): + self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni]) + + # commit configuration + self.cli_commit() + + self.assertTrue(interface_exists(bridge)) + self.assertTrue(interface_exists(interface)) + + tmp = get_interface_config(interface) + self.assertEqual(tmp['master'], bridge) + + tmp = get_vxlan_vlan_tunnels('vxlan0') + self.assertEqual(tmp, list(vlan_to_vni)) + + self.cli_delete(['interfaces', 'bridge', bridge]) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py index f8686edd8..95246a7b9 100755 --- a/smoketest/scripts/cli/test_interfaces_wireless.py +++ b/smoketest/scripts/cli/test_interfaces_wireless.py @@ -97,6 +97,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase): vht_opt = { # VyOS CLI option hostapd - ht_capab setting + 'channel-set-width 3' : '[VHT160-80PLUS80]', 'stbc tx' : '[TX-STBC-2BY1]', 'stbc rx 12' : '[RX-STBC-12]', 'ldpc' : '[RXLDPC]', @@ -104,7 +105,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase): 'vht-cf' : '[HTC-VHT]', 'antenna-pattern-fixed' : '[RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]', 'max-mpdu 11454' : '[MAX-MPDU-11454]', - 'max-mpdu-exp 2' : '[MAX-A-MPDU-LEN-EXP-2][VHT160]', + 'max-mpdu-exp 2' : '[MAX-A-MPDU-LEN-EXP-2]', 'link-adaptation both' : '[VHT-LINK-ADAPT3]', 'short-gi 80' : '[SHORT-GI-80]', 'short-gi 160' : '[SHORT-GI-160]', diff --git a/smoketest/scripts/cli/test_load_balancing_wan.py b/smoketest/scripts/cli/test_load_balancing_wan.py index 9b2cb0fac..47ca19b27 100755 --- a/smoketest/scripts/cli/test_load_balancing_wan.py +++ b/smoketest/scripts/cli/test_load_balancing_wan.py @@ -124,11 +124,12 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): self.assertEqual(tmp, original) # Delete veth interfaces and netns - for iface in [iface1, iface2, iface3, container_iface1, container_iface2, container_iface3]: + 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): @@ -246,11 +247,13 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): self.assertEqual(tmp, nat_vyos_pre_snat_hook) # Delete veth interfaces and netns - for iface in [iface1, iface2, iface3, container_iface1, container_iface2, container_iface3]: + 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_nat.py b/smoketest/scripts/cli/test_nat.py index 31dfcef87..703e5ab28 100755 --- a/smoketest/scripts/cli/test_nat.py +++ b/smoketest/scripts/cli/test_nat.py @@ -155,11 +155,6 @@ class TestNAT(VyOSUnitTestSHIM.TestCase): rule = '5' self.cli_set(src_path + ['rule', rule, 'source', 'address', '192.0.2.0/24']) - # check validate() - outbound-interface must be defined - with self.assertRaises(ConfigSessionError): - self.cli_commit() - self.cli_set(src_path + ['rule', rule, 'outbound-interface', 'eth0']) - # check validate() - translation address not specified with self.assertRaises(ConfigSessionError): self.cli_commit() diff --git a/smoketest/scripts/cli/test_interfaces_netns.py b/smoketest/scripts/cli/test_netns.py index b8bebb221..fd04dd520 100755 --- a/smoketest/scripts/cli/test_interfaces_netns.py +++ b/smoketest/scripts/cli/test_netns.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -16,7 +16,6 @@ import unittest -from netifaces import interfaces from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSession @@ -24,56 +23,61 @@ from vyos.configsession import ConfigSessionError from vyos.ifconfig import Interface from vyos.ifconfig import Section from vyos.utils.process import cmd +from vyos.utils.network import is_netns_interface +from vyos.utils.network import get_netns_all base_path = ['netns'] -namespaces = ['mgmt', 'front', 'back', 'ams-ix'] +interfaces = ['dum10', 'dum12', 'dum50'] -class NETNSTest(VyOSUnitTestSHIM.TestCase): - def setUp(self): - self._interfaces = ['dum10', 'dum12', 'dum50'] +class NetNSTest(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + self.cli_delete(base_path) + # commit changes + self.cli_commit() + + # There should be no network namespace remaining + tmp = cmd('ip netns ls') + self.assertFalse(tmp) + + super(NetNSTest, self).tearDown() - def test_create_netns(self): + def test_netns_create(self): + namespaces = ['mgmt', 'front', 'back'] for netns in namespaces: - base = base_path + ['name', netns] - self.cli_set(base) + self.cli_set(base_path + ['name', netns]) # commit changes self.cli_commit() - netns_list = cmd('ip netns ls') - # Verify NETNS configuration for netns in namespaces: - self.assertTrue(netns in netns_list) - + self.assertIn(netns, get_netns_all()) - def test_netns_assign_interface(self): + def test_netns_interface(self): netns = 'foo' - self.cli_set(['netns', 'name', netns]) + self.cli_set(base_path + ['name', netns]) # Set - for iface in self._interfaces: + for iface in interfaces: self.cli_set(['interfaces', 'dummy', iface, 'netns', netns]) # commit changes self.cli_commit() - netns_iface_list = cmd(f'sudo ip netns exec {netns} ip link show') - - for iface in self._interfaces: - self.assertTrue(iface in netns_iface_list) + for interface in interfaces: + self.assertTrue(is_netns_interface(interface, netns)) # Delete - for iface in self._interfaces: - self.cli_delete(['interfaces', 'dummy', iface, 'netns', netns]) + for interface in interfaces: + self.cli_delete(['interfaces', 'dummy', interface]) # commit changes self.cli_commit() netns_iface_list = cmd(f'sudo ip netns exec {netns} ip link show') - for iface in self._interfaces: - self.assertNotIn(iface, netns_iface_list) + for interface in interfaces: + self.assertFalse(is_netns_interface(interface, netns)) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py index d9b64544a..118b1d3a2 100755 --- a/smoketest/scripts/cli/test_policy_route.py +++ b/smoketest/scripts/cli/test_policy_route.py @@ -250,7 +250,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): ['meta l4proto udp', 'drop'], ['tcp flags syn / syn,ack', 'meta mark set ' + mark_hex], ['ct state new', 'tcp dport 22', 'ip saddr 198.51.100.0/24', 'ip ttl > 2', 'meta mark set ' + mark_hex], - ['meta l4proto icmp', 'log prefix "[smoketest-4-A]"', 'icmp type echo-request', 'ip length { 128, 1024-2048 }', 'meta pkttype other', 'meta mark set ' + mark_hex], + ['meta l4proto icmp', 'log prefix "[ipv4-route-smoketest-4-A]"', 'icmp type echo-request', 'ip length { 128, 1024-2048 }', 'meta pkttype other', 'meta mark set ' + mark_hex], ['ip dscp { 0x29, 0x39-0x3b }', 'meta mark set ' + mark_hex] ] @@ -262,7 +262,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): ['meta l4proto udp', 'drop'], ['tcp flags syn / syn,ack', 'meta mark set ' + mark_hex], ['ct state new', 'tcp dport 22', 'ip6 saddr 2001:db8::/64', 'ip6 hoplimit > 2', 'meta mark set ' + mark_hex], - ['meta l4proto ipv6-icmp', 'log prefix "[smoketest6-4-A]"', 'icmpv6 type echo-request', 'ip6 length != { 128, 1024-2048 }', 'meta pkttype multicast', 'meta mark set ' + mark_hex], + ['meta l4proto ipv6-icmp', 'log prefix "[ipv6-route6-smoketest6-4-A]"', 'icmpv6 type echo-request', 'ip6 length != { 128, 1024-2048 }', 'meta pkttype multicast', 'meta mark set ' + mark_hex], ['ip6 dscp != { 0x0e-0x13, 0x3d }', 'meta mark set ' + mark_hex] ] diff --git a/smoketest/scripts/cli/test_protocols_pim6.py b/smoketest/scripts/cli/test_protocols_pim6.py new file mode 100755 index 000000000..1be12836d --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_pim6.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'pim6d' +base_path = ['protocols', 'pim6'] + + +class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + self.cli_delete(base_path) + self.cli_commit() + + def test_pim6_01_mld_simple(self): + # commit changes + interfaces = Section.interfaces('ethernet') + + for interface in interfaces: + self.cli_set(base_path + ['interface', interface]) + + self.cli_commit() + + # Verify FRR pim6d configuration + for interface in interfaces: + config = self.getFRRconfig( + f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ipv6 mld', config) + self.assertNotIn(f' ipv6 mld version 1', config) + + # Change to MLD version 1 + for interface in interfaces: + self.cli_set(base_path + ['interface', + interface, 'mld', 'version', '1']) + + self.cli_commit() + + # Verify FRR pim6d configuration + for interface in interfaces: + config = self.getFRRconfig( + f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ipv6 mld', config) + self.assertIn(f' ipv6 mld version 1', config) + + def test_pim6_02_mld_join(self): + # commit changes + interfaces = Section.interfaces('ethernet') + + # Use an invalid multiple group address + for interface in interfaces: + self.cli_set(base_path + ['interface', + interface, 'mld', 'join', 'fd00::1234']) + + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface']) + + # Use a valid multiple group address + for interface in interfaces: + self.cli_set(base_path + ['interface', + interface, 'mld', 'join', 'ff18::1234']) + + self.cli_commit() + + # Verify FRR pim6d configuration + for interface in interfaces: + config = self.getFRRconfig( + f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ipv6 mld join ff18::1234', config) + + # Join a source-specific multicast group + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, + 'mld', 'join', 'ff38::5678', 'source', '2001:db8::5678']) + + self.cli_commit() + + # Verify FRR pim6d configuration + for interface in interfaces: + config = self.getFRRconfig( + f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ipv6 mld join ff38::5678 2001:db8::5678', config) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py index 2a89aa98b..ea304783d 100755 --- a/smoketest/scripts/cli/test_system_conntrack.py +++ b/smoketest/scripts/cli/test_system_conntrack.py @@ -35,6 +35,17 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase): self.cli_delete(base_path) self.cli_commit() + def verify_nftables(self, nftables_search, table, inverse=False, args=''): + nftables_output = cmd(f'sudo nft {args} list table {table}') + + for search in nftables_search: + matched = False + for line in nftables_output.split("\n"): + if all(item in line for item in search): + matched = True + break + self.assertTrue(not matched if inverse else matched, msg=search) + def test_conntrack_options(self): conntrack_config = { 'net.netfilter.nf_conntrack_expect_max' : { @@ -232,5 +243,51 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase): tmp = read_file('/etc/modprobe.d/vyatta_nf_conntrack.conf') self.assertIn(hash_size_default, tmp) + def test_conntrack_ignore(self): + address_group = 'conntracktest' + address_group_member = '192.168.0.1' + ipv6_address_group = 'conntracktest6' + ipv6_address_group_member = 'dead:beef::1' + + self.cli_set(['firewall', 'group', 'address-group', address_group, 'address', address_group_member]) + self.cli_set(['firewall', 'group', 'ipv6-address-group', ipv6_address_group, 'address', ipv6_address_group_member]) + + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'source', 'address', '192.0.2.1']) + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'destination', 'address', '192.0.2.2']) + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'destination', 'port', '22']) + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'protocol', 'tcp']) + + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'source', 'address', '192.0.2.1']) + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'destination', 'group', 'address-group', address_group]) + + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'source', 'address', 'fe80::1']) + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'address', 'fe80::2']) + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'port', '22']) + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'protocol', 'tcp']) + + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '12', 'source', 'address', 'fe80::1']) + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '12', 'destination', 'group', 'address-group', ipv6_address_group]) + + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '13', 'source', 'address', 'fe80::1']) + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '13', 'destination', 'address', '!fe80::3']) + + self.cli_commit() + + nftables_search = [ + ['ip saddr 192.0.2.1', 'ip daddr 192.0.2.2', 'tcp dport 22', 'notrack'], + ['ip saddr 192.0.2.1', 'ip daddr @A_conntracktest', 'notrack'] + ] + + nftables6_search = [ + ['ip6 saddr fe80::1', 'ip6 daddr fe80::2', 'tcp dport 22', 'notrack'], + ['ip6 saddr fe80::1', 'ip6 daddr @A6_conntracktest6', 'notrack'], + ['ip6 saddr fe80::1', 'ip6 daddr != fe80::3', 'notrack'] + ] + + self.verify_nftables(nftables_search, 'raw') + self.verify_nftables(nftables6_search, 'ip6 raw') + + self.cli_delete(['firewall']) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py index 9c43640a9..a0de914bc 100755 --- a/src/conf_mode/conntrack.py +++ b/src/conf_mode/conntrack.py @@ -24,7 +24,9 @@ from vyos.firewall import find_nftables_rule from vyos.firewall import remove_nftables_rule 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.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 @@ -62,6 +64,13 @@ module_map = { }, } +valid_groups = [ + 'address_group', + 'domain_group', + 'network_group', + 'port_group' +] + def resync_conntrackd(): tmp = run('/usr/libexec/vyos/conf_mode/conntrack_sync.py') if tmp > 0: @@ -78,15 +87,53 @@ def get_config(config=None): get_first_key=True, with_recursive_defaults=True) + conntrack['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + return conntrack def verify(conntrack): - if dict_search('ignore.rule', conntrack) != None: - for rule, rule_config in conntrack['ignore']['rule'].items(): - if dict_search('destination.port', rule_config) or \ - dict_search('source.port', 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}') + 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}') + + 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!') return None @@ -94,26 +141,18 @@ def generate(conntrack): 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) - - # dry-run newly generated configuration - tmp = run(f'nft -c -f {nftables_ct_file}') - if tmp > 0: - if os.path.exists(nftables_ct_file): - os.unlink(nftables_ct_file) - raise ConfigError('Configuration file errors encountered!') - return None -def find_nftables_ct_rule(rule): +def find_nftables_ct_rule(table, chain, rule): helper_search = re.search('ct helper set "(\w+)"', rule) if helper_search: rule = helper_search[1] - return find_nftables_rule('raw', 'VYOS_CT_HELPER', [rule]) + return find_nftables_rule(table, chain, [rule]) -def find_remove_rule(rule): - handle = find_nftables_ct_rule(rule) +def find_remove_rule(table, chain, rule): + handle = find_nftables_ct_rule(table, chain, rule) if handle: - remove_nftables_rule('raw', 'VYOS_CT_HELPER', handle) + remove_nftables_rule(table, chain, handle) def apply(conntrack): # Depending on the enable/disable state of the ALG (Application Layer Gateway) @@ -127,18 +166,24 @@ def apply(conntrack): cmd(f'rmmod {mod}') if 'nftables' in module_config: for rule in module_config['nftables']: - find_remove_rule(rule) + find_remove_rule('raw', 'VYOS_CT_HELPER', rule) + find_remove_rule('ip6 raw', 'VYOS_CT_HELPER', rule) else: if 'ko' in module_config: for mod in module_config['ko']: cmd(f'modprobe {mod}') if 'nftables' in module_config: for rule in module_config['nftables']: - if not find_nftables_ct_rule(rule): - cmd(f'nft insert rule ip raw VYOS_CT_HELPER {rule}') + if not find_nftables_ct_rule('raw', 'VYOS_CT_HELPER', rule): + cmd(f'nft insert rule raw VYOS_CT_HELPER {rule}') + + if not find_nftables_ct_rule('ip6 raw', 'VYOS_CT_HELPER', rule): + cmd(f'nft insert rule ip6 raw VYOS_CT_HELPER {rule}') # Load new nftables ruleset - cmd(f'nft -f {nftables_ct_file}') + install_result, output = rc_cmd(f'nft -f {nftables_ct_file}') + if install_result == 1: + raise ConfigError(f'Failed to apply configuration: {output}') if process_named_running('conntrackd'): # Reload conntrack-sync daemon to fetch new sysctl values diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 79b605ffb..daad9186e 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -178,6 +178,11 @@ def verify(container): if 'value' not in cfg: raise ConfigError(f'Environment variable {var} has no value assigned!') + if 'label' in container_config: + for var, cfg in container_config['label'].items(): + if 'value' not in cfg: + raise ConfigError(f'Label variable {var} has no value assigned!') + if 'volume' in container_config: for volume, volume_config in container_config['volume'].items(): if 'source' not in volume_config: @@ -268,6 +273,12 @@ def generate_run_arguments(name, container_config): for k, v in container_config['environment'].items(): env_opt += f" --env \"{k}={v['value']}\"" + # Check/set label options "--label foo=bar" + label = '' + if 'label' in container_config: + for k, v in container_config['label'].items(): + label += f" --label \"{k}={v['value']}\"" + hostname = '' if 'host_name' in container_config: hostname = container_config['host_name'] @@ -303,7 +314,7 @@ def generate_run_arguments(name, container_config): container_base_cmd = f'--detach --interactive --tty --replace {cap_add} ' \ f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \ - f'--name {name} {hostname} {device} {port} {volume} {env_opt}' + f'--name {name} {hostname} {device} {port} {volume} {env_opt} {label}' entrypoint = '' if 'entrypoint' in container_config: diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index c4c72aae9..ac7d95632 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2022 VyOS maintainers and contributors +# Copyright (C) 2018-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -34,6 +34,7 @@ from vyos import airbag airbag.enable() config_file = '/run/dhcp-server/dhcpd.conf' +systemd_override = r'/run/systemd/system/isc-dhcp-server.service.d/10-override.conf' def dhcp_slice_range(exclude_list, range_dict): """ @@ -295,6 +296,7 @@ def generate(dhcp): # render the "real" configuration render(config_file, 'dhcp-server/dhcpd.conf.j2', dhcp, formater=lambda _: _.replace(""", '"')) + render(systemd_override, 'dhcp-server/10-override.conf.j2', dhcp) # Clean up configuration test file if os.path.exists(tmp_file): @@ -303,6 +305,7 @@ def generate(dhcp): return None def apply(dhcp): + call('systemctl daemon-reload') # bail out early - looks like removal from running config if not dhcp or 'disable' in dhcp: call('systemctl stop isc-dhcp-server.service') diff --git a/src/conf_mode/dns_dynamic.py b/src/conf_mode/dns_dynamic.py index ab80defe8..4b1aed742 100755 --- a/src/conf_mode/dns_dynamic.py +++ b/src/conf_mode/dns_dynamic.py @@ -104,7 +104,7 @@ def generate(dyndns): if not dyndns or 'address' not in dyndns: return None - render(config_file, 'dns-dynamic/ddclient.conf.j2', dyndns) + render(config_file, 'dns-dynamic/ddclient.conf.j2', dyndns, permission=0o600) render(systemd_override, 'dns-dynamic/override.conf.j2', dyndns) return None diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index c3b1ee015..769cc598f 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -26,7 +26,7 @@ from vyos.config import Config from vyos.configdict import node_changed from vyos.configdiff import get_config_diff, Diff from vyos.configdep import set_dependents, call_dependents -# from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_interface_exists from vyos.firewall import fqdn_config_parse from vyos.firewall import geoip_update from vyos.template import render @@ -38,6 +38,7 @@ from vyos.utils.process import process_named_running from vyos.utils.process import rc_cmd from vyos import ConfigError from vyos import airbag + airbag.enable() nat_conf_script = 'nat.py' @@ -100,7 +101,7 @@ def geoip_updated(conf, firewall): elif (path[0] == 'ipv6'): set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}' out['ipv6_name'].append(set_name) - + updated = True if 'delete' in node_diff: @@ -140,6 +141,14 @@ def get_config(config=None): fqdn_config_parse(firewall) + firewall['flowtable_enabled'] = False + flow_offload = dict_search_args(firewall, 'global_options', 'flow_offload') + if flow_offload and 'disable' not in flow_offload: + for offload_type in ('software', 'hardware'): + if dict_search_args(flow_offload, offload_type, 'interface'): + firewall['flowtable_enabled'] = True + break + return firewall def verify_rule(firewall, rule_conf, ipv6): @@ -327,6 +336,14 @@ def verify(firewall): for rule_id, rule_conf in name_conf['rule'].items(): verify_rule(firewall, rule_conf, True) + # Verify flow offload options + flow_offload = dict_search_args(firewall, 'global_options', 'flow_offload') + for offload_type in ('software', 'hardware'): + interfaces = dict_search_args(flow_offload, offload_type, 'interface') or [] + for interface in interfaces: + # nft will raise an error when adding a non-existent interface to a flowtable + verify_interface_exists(interface) + return None def generate(firewall): @@ -336,13 +353,15 @@ def generate(firewall): # Determine if conntrack is needed firewall['ipv4_conntrack_action'] = 'return' firewall['ipv6_conntrack_action'] = 'return' - - for rules, path in dict_search_recursive(firewall, 'rule'): - if any(('state' in rule_conf or 'connection_status' in rule_conf) for rule_conf in rules.values()): - if path[0] == 'ipv4': - firewall['ipv4_conntrack_action'] = 'accept' - elif path[0] == 'ipv6': - firewall['ipv6_conntrack_action'] = 'accept' + if firewall['flowtable_enabled']: # Netfilter's flowtable offload requires conntrack + firewall['ipv4_conntrack_action'] = 'accept' + firewall['ipv6_conntrack_action'] = 'accept' + else: # Check if conntrack is needed by firewall rules + for proto in ('ipv4', 'ipv6'): + for rules, _ in dict_search_recursive(firewall.get(proto, {}), 'rule'): + if any(('state' in rule_conf or 'connection_status' in rule_conf) for rule_conf in rules.values()): + firewall[f'{proto}_conntrack_action'] = 'accept' + break render(nftables_conf, 'firewall/nftables.j2', firewall) return None diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py index 626a3757e..70f43ab52 100755 --- a/src/conf_mode/high-availability.py +++ b/src/conf_mode/high-availability.py @@ -15,6 +15,9 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. +import os +import time + from sys import exit from ipaddress import ip_interface from ipaddress import IPv4Interface @@ -22,15 +25,21 @@ from ipaddress import IPv6Interface from vyos.base import Warning from vyos.config import Config +from vyos.configdict import leaf_node_changed from vyos.ifconfig.vrrp import VRRP from vyos.template import render from vyos.template import is_ipv4 from vyos.template import is_ipv6 +from vyos.utils.network import is_ipv6_tentative from vyos.utils.process import call from vyos import ConfigError from vyos import airbag airbag.enable() + +systemd_override = r'/run/systemd/system/keepalived.service.d/10-override.conf' + + def get_config(config=None): if config: conf = config @@ -50,6 +59,9 @@ def get_config(config=None): if conf.exists(conntrack_path): ha['conntrack_sync_group'] = conf.return_value(conntrack_path) + if leaf_node_changed(conf, base + ['vrrp', 'disable-snmp']): + ha.update({'restart_required': {}}) + return ha def verify(ha): @@ -160,18 +172,38 @@ def verify(ha): def generate(ha): if not ha or 'disable' in ha: + if os.path.isfile(systemd_override): + os.unlink(systemd_override) return None render(VRRP.location['config'], 'high-availability/keepalived.conf.j2', ha) + render(systemd_override, 'high-availability/10-override.conf.j2', ha) return None def apply(ha): service_name = 'keepalived.service' + call('systemctl daemon-reload') if not ha or 'disable' in ha: call(f'systemctl stop {service_name}') return None - call(f'systemctl reload-or-restart {service_name}') + # Check if IPv6 address is tentative T5533 + for group, group_config in ha.get('vrrp', {}).get('group', {}).items(): + if 'hello_source_address' in group_config: + if is_ipv6(group_config['hello_source_address']): + ipv6_address = group_config['hello_source_address'] + interface = group_config['interface'] + checks = 20 + interval = 0.1 + for _ in range(checks): + if is_ipv6_tentative(interface, ipv6_address): + time.sleep(interval) + + systemd_action = 'reload-or-restart' + if 'restart_required' in ha: + systemd_action = 'restart' + + call(f'systemctl {systemd_action} {service_name}') return None if __name__ == '__main__': diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index e771581e1..db768b94d 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2021 VyOS maintainers and contributors +# Copyright (C) 2019-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -55,7 +55,7 @@ def generate(dummy): return None def apply(dummy): - d = DummyIf(dummy['ifname']) + d = DummyIf(**dummy) # Remove dummy interface if 'deleted' in dummy: diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index b015bba88..f3e65ad5e 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -186,14 +186,15 @@ def generate(ethernet): if 'ca_certificate' in ethernet['eapol']: ca_cert_file_path = os.path.join(cfg_dir, f'{ifname}_ca.pem') - ca_cert_name = ethernet['eapol']['ca_certificate'] - pki_ca_cert = ethernet['pki']['ca'][ca_cert_name] + ca_chains = [] - loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) - ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + 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(encode_certificate(c) for c in ca_full_chain)) + write_file(ca_cert_file_path, '\n'.join(ca_chains)) return None diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 1d0feb56f..9f4de990c 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -344,9 +344,6 @@ def verify(openvpn): if v6_subnets > 1: raise ConfigError('Cannot specify more than 1 IPv6 server subnet') - if v6_subnets > 0 and v4_subnets == 0: - raise ConfigError('IPv6 server requires an IPv4 server subnet') - for subnet in tmp: if is_ipv4(subnet): subnet = IPv4Network(subnet) @@ -388,6 +385,10 @@ def verify(openvpn): 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): diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index a3b0867e0..05f68112a 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2022 VyOS maintainers and contributors +# Copyright (C) 2019-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -24,6 +24,7 @@ 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 @@ -58,6 +59,9 @@ def get_config(config=None): vxlan.update({'rebuild_required': {}}) break + tmp = node_changed(conf, base + [ifname, 'vlan-to-vni'], recursive=True) + if tmp: vxlan.update({'vlan_to_vni_removed': tmp}) + # We need to verify that no other VXLAN tunnel is configured when external # mode is in use - Linux Kernel limitation conf.set_level(base) @@ -146,6 +150,20 @@ def verify(vxlan): 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) + verify_mtu_ipv6(vxlan) verify_address(vxlan) verify_bond_bridge_member(vxlan) diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 9da7fbe80..e37a7011c 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -112,7 +112,7 @@ def verify_rule(config, err_msg, groups_dict): group_obj = dict_search_args(groups_dict, group, group_name) if group_obj is None: - raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule') + raise ConfigError(f'Invalid {error_group} "{group_name}" on nat rule') if not group_obj: Warning(f'{error_group} "{group_name}" has no members!') @@ -195,11 +195,10 @@ def verify(nat): if dict_search('source.rule', nat): for rule, config in dict_search('source.rule', nat).items(): err_msg = f'Source NAT configuration error in rule {rule}:' - if 'outbound_interface' not in config: - raise ConfigError(f'{err_msg} outbound-interface not specified') - if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces(): - Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') + if 'outbound_interface' in config: + if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces(): + Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') if not dict_search('translation.address', config) and not dict_search('translation.port', config): if 'exclude' not in config and 'backend' not in config['load_balance']: @@ -218,11 +217,9 @@ def verify(nat): for rule, config in dict_search('destination.rule', nat).items(): err_msg = f'Destination NAT configuration error in rule {rule}:' - if 'inbound_interface' not in config: - raise ConfigError(f'{err_msg}\n' \ - 'inbound-interface not specified') - elif config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces(): - Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system') + if 'inbound_interface' in config: + if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces(): + Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system') if not dict_search('translation.address', config) and not dict_search('translation.port', config) and 'redirect' not in config['translation']: if 'exclude' not in config and 'backend' not in config['load_balance']: diff --git a/src/conf_mode/protocols_igmp.py b/src/conf_mode/protocols_igmp.py index f6097e282..435189025 100755 --- a/src/conf_mode/protocols_igmp.py +++ b/src/conf_mode/protocols_igmp.py @@ -102,7 +102,7 @@ def verify(igmp): # Check, is this multicast group for intfc in igmp['ifaces']: for gr_addr in igmp['ifaces'][intfc]['gr_join']: - if IPv4Address(gr_addr) < IPv4Address('224.0.0.0'): + if not IPv4Address(gr_addr).is_multicast: raise ConfigError(gr_addr + " not a multicast group") def generate(igmp): diff --git a/src/conf_mode/protocols_pim6.py b/src/conf_mode/protocols_pim6.py new file mode 100755 index 000000000..6a1235ba5 --- /dev/null +++ b/src/conf_mode/protocols_pim6.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from ipaddress import IPv6Address +from sys import exit +from typing import Optional + +from vyos import ConfigError, airbag, frr +from vyos.config import Config, ConfigDict +from vyos.configdict import node_changed +from vyos.configverify import verify_interface_exists +from vyos.template import render_to_string + +airbag.enable() + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['protocols', 'pim6'] + pim6 = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=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: + pim6['interface_removed'] = list(interfaces_removed) + + return pim6 + + +def verify(pim6): + if pim6 is None: + return + + for interface, interface_config in pim6.get('interface', {}).items(): + verify_interface_exists(interface) + if 'mld' in interface_config: + mld = interface_config['mld'] + for group in mld.get('join', {}).keys(): + # Validate multicast group address + if not IPv6Address(group).is_multicast: + raise ConfigError(f"{group} is not a multicast group") + + +def generate(pim6): + if pim6 is None: + return + + pim6['new_frr_config'] = render_to_string('frr/pim6d.frr.j2', pim6) + + +def apply(pim6): + if pim6 is None: + return + + pim6_daemon = 'pim6d' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + frr_cfg.load_configuration(pim6_daemon) + + for key in ['interface', 'interface_removed']: + if key not in pim6: + continue + for interface in pim6[key]: + frr_cfg.modify_section( + f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) + + if 'new_frr_config' in pim6: + frr_cfg.add_before(frr.default_add_before, pim6['new_frr_config']) + frr_cfg.commit_configuration(pim6_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-ip.py b/src/conf_mode/system-ip.py index 5e4e5ec28..7612e2c0d 100755 --- a/src/conf_mode/system-ip.py +++ b/src/conf_mode/system-ip.py @@ -20,10 +20,12 @@ 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.process import call 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 @@ -115,16 +117,20 @@ def apply(opt): value = '48' if (tmp is None) else tmp sysctl_write('net.ipv4.tcp_mtu_probe_floor', value) - 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) + # 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: diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py index e40ed38e2..90a1a8087 100755 --- a/src/conf_mode/system-ipv6.py +++ b/src/conf_mode/system-ipv6.py @@ -22,8 +22,9 @@ 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.system import sysctl_write 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 @@ -93,16 +94,20 @@ def apply(opt): if name == 'accept_dad': write_file(os.path.join(root, name), value) - zebra_daemon = 'zebra' - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() + # 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) + # 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: diff --git a/src/conf_mode/system_frr.py b/src/conf_mode/system_frr.py index fb252238a..d8224b3c3 100755 --- a/src/conf_mode/system_frr.py +++ b/src/conf_mode/system_frr.py @@ -22,17 +22,14 @@ from vyos import airbag from vyos.config import Config from vyos.logger import syslog from vyos.template import render_to_string +from vyos.utils.boot import boot_configuration_complete from vyos.utils.file import read_file from vyos.utils.file import write_file -from vyos.utils.process import run +from vyos.utils.process import call airbag.enable() # path to daemons config and config status files config_file = '/etc/frr/daemons' -vyos_status_file = '/tmp/vyos-config-status' -# path to watchfrr for FRR control -watchfrr = '/usr/lib/frr/watchfrr.sh' - def get_config(config=None): if config: @@ -45,12 +42,10 @@ def get_config(config=None): return frr_config - def verify(frr_config): # Nothing to verify here pass - def generate(frr_config): # read daemons config file daemons_config_current = read_file(config_file) @@ -62,25 +57,21 @@ def generate(frr_config): write_file(config_file, daemons_config_new) frr_config['config_file_changed'] = True - def apply(frr_config): - # check if this is initial commit during boot or intiated by CLI - # if the file exists, this must be CLI commit - commit_type_cli = Path(vyos_status_file).exists() # display warning to user - if commit_type_cli and frr_config.get('config_file_changed'): + if boot_configuration_complete() and frr_config.get('config_file_changed'): # Since FRR restart is not safe thing, better to give # control over this to users print(''' You need to reboot a router (preferred) or restart FRR to apply changes in modules settings ''') - # restart FRR automatically. DUring the initial boot this should be - # safe in most cases - if not commit_type_cli and frr_config.get('config_file_changed'): - syslog.warning('Restarting FRR to apply changes in modules') - run(f'{watchfrr} restart') + # restart FRR automatically + # During initial boot this should be safe in most cases + if not boot_configuration_complete() and frr_config.get('config_file_changed'): + syslog.warning('Restarting FRR to apply changes in modules') + call(f'systemctl restart frr.service') if __name__ == '__main__': try: diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf index f5d84be4b..fcdc1b21d 100644 --- a/src/etc/sysctl.d/30-vyos-router.conf +++ b/src/etc/sysctl.d/30-vyos-router.conf @@ -98,15 +98,11 @@ net.ipv6.route.skip_notify_on_dev_down=1 # Default value of 20 seems to interfere with larger OSPF and VRRP setups net.ipv4.igmp_max_memberships = 512 -# Increase default garbage collection thresholds -net.ipv4.neigh.default.gc_thresh1 = 1024 -net.ipv4.neigh.default.gc_thresh2 = 4096 -net.ipv4.neigh.default.gc_thresh3 = 8192 -# -net.ipv6.neigh.default.gc_thresh1 = 1024 -net.ipv6.neigh.default.gc_thresh2 = 4096 -net.ipv6.neigh.default.gc_thresh3 = 8192 - # Enable global RFS (Receive Flow Steering) configuration. RFS is inactive # until explicitly configured at the interface level net.core.rps_sock_flow_entries = 32768 + +# Congestion control +net.core.default_qdisc=fq +net.ipv4.tcp_congestion_control=bbr + diff --git a/src/helpers/config_dependency.py b/src/helpers/config_dependency.py new file mode 100755 index 000000000..50c72956e --- /dev/null +++ b/src/helpers/config_dependency.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +import os +import sys +from argparse import ArgumentParser +from argparse import ArgumentTypeError + +try: + from vyos.configdep import check_dependency_graph + from vyos.defaults import directories +except ImportError: + # allow running during addon package build + _here = os.path.dirname(__file__) + sys.path.append(os.path.join(_here, '../../python/vyos')) + from configdep import check_dependency_graph + from defaults import directories + +# addon packages will need to specify the dependency directory +dependency_dir = os.path.join(directories['data'], + 'config-mode-dependencies') + +def path_exists(s): + if not os.path.exists(s): + raise ArgumentTypeError("Must specify a valid vyos-1x dependency directory") + return s + +def main(): + parser = ArgumentParser(description='generate and save dict from xml defintions') + parser.add_argument('--dependency-dir', type=path_exists, + default=dependency_dir, + help='location of vyos-1x dependency directory') + parser.add_argument('--supplement', type=str, + help='supplemental dependency file') + args = vars(parser.parse_args()) + + if not check_dependency_graph(**args): + sys.exit(1) + + sys.exit(0) + +if __name__ == '__main__': + main() diff --git a/src/helpers/vyos-domain-resolver.py b/src/helpers/vyos-domain-resolver.py index 7e2fe2462..eac3d37af 100755 --- a/src/helpers/vyos-domain-resolver.py +++ b/src/helpers/vyos-domain-resolver.py @@ -37,12 +37,14 @@ domain_state = {} ipv4_tables = { 'ip vyos_mangle', 'ip vyos_filter', - 'ip vyos_nat' + 'ip vyos_nat', + 'ip raw' } ipv6_tables = { 'ip6 vyos_mangle', - 'ip6 vyos_filter' + 'ip6 vyos_filter', + 'ip6 raw' } def get_config(conf): diff --git a/src/helpers/vyos-save-config.py b/src/helpers/vyos-save-config.py index 2812155e8..8af4a7916 100755 --- a/src/helpers/vyos-save-config.py +++ b/src/helpers/vyos-save-config.py @@ -44,7 +44,10 @@ ct = config.get_config_tree(effective=True) write_file = save_file if remote_save is None else NamedTemporaryFile(delete=False).name with open(write_file, 'w') as f: - f.write(ct.to_string()) + # config_tree is None before boot configuration is complete; + # automated saves should check boot_configuration_complete + if ct is not None: + f.write(ct.to_string()) f.write("\n") f.write(system_footer()) diff --git a/src/init/vyos-router b/src/init/vyos-router index 96f163213..9ef1fa335 100755 --- a/src/init/vyos-router +++ b/src/init/vyos-router @@ -335,18 +335,19 @@ start () nfct helper add rpc inet tcp nfct helper add rpc inet udp nfct helper add tns inet tcp + nfct helper add rpc inet6 tcp + nfct helper add rpc inet6 udp + nfct helper add tns inet6 tcp nft -f /usr/share/vyos/vyos-firewall-init.conf || log_failure_msg "could not initiate firewall rules" - rm -f /etc/hostname - ${vyos_conf_scripts_dir}/host_name.py || log_failure_msg "could not reset host-name" - systemctl start frr.service - # As VyOS does not execute commands that are not present in the CLI we call # the script by hand to have a single source for the login banner and MOTD ${vyos_conf_scripts_dir}/system_console.py || log_failure_msg "could not reset serial console" ${vyos_conf_scripts_dir}/system-login.py || log_failure_msg "could not reset system login" ${vyos_conf_scripts_dir}/system-login-banner.py || log_failure_msg "could not reset motd and issue files" ${vyos_conf_scripts_dir}/system-option.py || log_failure_msg "could not reset system option files" + ${vyos_conf_scripts_dir}/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}/container.py || log_failure_msg "could not reset container subsystem" @@ -373,6 +374,13 @@ start () && chgrp ${GROUP} ${vyatta_configdir} log_action_end_msg $? + rm -f /etc/hostname + ${vyos_conf_scripts_dir}/host_name.py || log_failure_msg "could not reset host-name" + ${vyos_conf_scripts_dir}/system_frr.py || log_failure_msg "could not reset FRR config" + # If for any reason FRR was not started by system_frr.py - start it anyways. + # This is a safety net! + systemctl start frr.service + disabled bootfile || init_bootfile cleanup_post_commit_hooks diff --git a/src/migration-scripts/conntrack/3-to-4 b/src/migration-scripts/conntrack/3-to-4 new file mode 100755 index 000000000..e90c383af --- /dev/null +++ b/src/migration-scripts/conntrack/3-to-4 @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Add support for IPv6 conntrack ignore, move existing nodes to `system conntrack ignore ipv4` + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if len(argv) < 2: + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['system', 'conntrack'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +if config.exists(base + ['ignore', 'rule']): + config.set(base + ['ignore', 'ipv4']) + config.copy(base + ['ignore', 'rule'], base + ['ignore', 'ipv4', 'rule']) + config.delete(base + ['ignore', 'rule']) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) diff --git a/src/migration-scripts/system/13-to-14 b/src/migration-scripts/system/13-to-14 index 1fa781869..5b781158b 100755 --- a/src/migration-scripts/system/13-to-14 +++ b/src/migration-scripts/system/13-to-14 @@ -34,7 +34,7 @@ else: # retrieve all valid timezones try: - tz_datas = cmd('find /usr/share/zoneinfo/posix -type f -or -type l | sed -e s:/usr/share/zoneinfo/posix/::') + tz_datas = cmd('timedatectl list-timezones') except OSError: tz_datas = '' tz_data = tz_datas.split('\n') diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py index f558c18b7..77f38992b 100755 --- a/src/op_mode/dhcp.py +++ b/src/op_mode/dhcp.py @@ -338,10 +338,12 @@ def _get_formatted_client_leases(lease_data, family): from time import localtime from time import strftime - from vyos.validate import is_intf_addr_assigned + from vyos.utils.network import is_intf_addr_assigned data_entries = [] for lease in lease_data: + if not lease.get('new_ip_address'): + continue data_entries.append(["Interface", lease['interface']]) if 'new_ip_address' in lease: tmp = '[Active]' if is_intf_addr_assigned(lease['interface'], lease['new_ip_address']) else '[Inactive]' diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py index 852a7248a..3434707ec 100755 --- a/src/op_mode/firewall.py +++ b/src/op_mode/firewall.py @@ -24,26 +24,39 @@ from vyos.config import Config from vyos.utils.process import cmd from vyos.utils.dict import dict_search_args -def get_config_firewall(conf, hook=None, priority=None, ipv6=False, interfaces=True): +def get_config_firewall(conf, family=None, hook=None, priority=None): config_path = ['firewall'] - if hook: - config_path += ['ipv6' if ipv6 else 'ipv4', hook] - if priority: - config_path += [priority] + if family: + config_path += [family] + if hook: + config_path += [hook] + if priority: + config_path += [priority] firewall = conf.get_config_dict(config_path, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) return firewall -def get_nftables_details(hook, priority, ipv6=False): - suffix = '6' if ipv6 else '' - name_prefix = 'NAME6_' if ipv6 else 'NAME_' +def get_nftables_details(family, hook, priority): + if family == 'ipv6': + suffix = 'ip6' + name_prefix = 'NAME6_' + aux='IPV6_' + elif family == 'ipv4': + suffix = 'ip' + name_prefix = 'NAME_' + aux='' + else: + suffix = 'bridge' + name_prefix = 'NAME_' + aux='' + if hook == 'name' or hook == 'ipv6-name': - command = f'sudo nft list chain ip{suffix} vyos_filter {name_prefix}{priority}' + command = f'sudo nft list chain {suffix} vyos_filter {name_prefix}{priority}' else: up_hook = hook.upper() - command = f'sudo nft list chain ip{suffix} vyos_filter VYOS_{up_hook}_{priority}' + command = f'sudo nft list chain {suffix} vyos_filter VYOS_{aux}{up_hook}_{priority}' try: results = cmd(command) @@ -67,11 +80,10 @@ def get_nftables_details(hook, priority, ipv6=False): out[rule_id] = rule return out -def output_firewall_name(hook, priority, firewall_conf, ipv6=False, single_rule_id=None): - ip_str = 'IPv6' if ipv6 else 'IPv4' - print(f'\n---------------------------------\n{ip_str} Firewall "{hook} {priority}"\n') +def output_firewall_name(family, hook, priority, firewall_conf, single_rule_id=None): + print(f'\n---------------------------------\n{family} Firewall "{hook} {priority}"\n') - details = get_nftables_details(hook, priority, ipv6) + details = get_nftables_details(family, hook, priority) rows = [] if 'rule' in firewall_conf: @@ -102,11 +114,10 @@ def output_firewall_name(hook, priority, firewall_conf, ipv6=False, single_rule_ header = ['Rule', 'Action', 'Protocol', 'Packets', 'Bytes', 'Conditions'] print(tabulate.tabulate(rows, header) + '\n') -def output_firewall_name_statistics(hook, prior, prior_conf, ipv6=False, single_rule_id=None): - ip_str = 'IPv6' if ipv6 else 'IPv4' - print(f'\n---------------------------------\n{ip_str} Firewall "{hook} {prior}"\n') +def output_firewall_name_statistics(family, hook, prior, prior_conf, single_rule_id=None): + print(f'\n---------------------------------\n{family} Firewall "{hook} {prior}"\n') - details = get_nftables_details(prior, ipv6) + details = get_nftables_details(family, hook, prior) rows = [] if 'rule' in prior_conf: @@ -117,8 +128,57 @@ def output_firewall_name_statistics(hook, prior, prior_conf, ipv6=False, single_ if 'disable' in rule_conf: continue - source_addr = dict_search_args(rule_conf, 'source', 'address') or '0.0.0.0/0' - dest_addr = dict_search_args(rule_conf, 'destination', 'address') or '0.0.0.0/0' + # Get source + source_addr = dict_search_args(rule_conf, 'source', 'address') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'group', 'address_group') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'group', 'network_group') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'group', 'domain_group') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'fqdn') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'geoip', 'country_code') + if source_addr: + source_addr = str(source_addr)[1:-1].replace('\'','') + if 'inverse_match' in dict_search_args(rule_conf, 'source', 'geoip'): + source_addr = 'NOT ' + str(source_addr) + if not source_addr: + source_addr = 'any' + + # Get destination + dest_addr = dict_search_args(rule_conf, 'destination', 'address') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'group', 'address_group') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'group', 'network_group') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'group', 'domain_group') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'fqdn') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'geoip', 'country_code') + if dest_addr: + dest_addr = str(dest_addr)[1:-1].replace('\'','') + if 'inverse_match' in dict_search_args(rule_conf, 'destination', 'geoip'): + dest_addr = 'NOT ' + str(dest_addr) + if not dest_addr: + dest_addr = 'any' + + # Get inbound interface + iiface = dict_search_args(rule_conf, 'inbound_interface', 'interface_name') + if not iiface: + iiface = dict_search_args(rule_conf, 'inbound_interface', 'interface_group') + if not iiface: + iiface = 'any' + + # Get outbound interface + oiface = dict_search_args(rule_conf, 'outbound_interface', 'interface_name') + if not oiface: + oiface = dict_search_args(rule_conf, 'outbound_interface', 'interface_group') + if not oiface: + oiface = 'any' row = [rule_id] if rule_id in details: @@ -131,9 +191,26 @@ def output_firewall_name_statistics(hook, prior, prior_conf, ipv6=False, single_ row.append(rule_conf['action']) row.append(source_addr) row.append(dest_addr) + row.append(iiface) + row.append(oiface) rows.append(row) - if 'default_action' in prior_conf and not single_rule_id: + + if hook in ['input', 'forward', 'output']: + row = ['default'] + row.append('N/A') + row.append('N/A') + if 'default_action' in prior_conf: + row.append(prior_conf['default_action']) + else: + row.append('accept') + row.append('any') + row.append('any') + row.append('any') + row.append('any') + rows.append(row) + + elif 'default_action' in prior_conf and not single_rule_id: row = ['default'] if 'default-action' in details: rule_details = details['default-action'] @@ -143,12 +220,14 @@ def output_firewall_name_statistics(hook, prior, prior_conf, ipv6=False, single_ row.append('0') row.append('0') row.append(prior_conf['default_action']) - row.append('0.0.0.0/0') # Source - row.append('0.0.0.0/0') # Dest + row.append('any') # Source + row.append('any') # Dest + row.append('any') # inbound-interface + row.append('any') # outbound-interface rows.append(row) if rows: - header = ['Rule', 'Packets', 'Bytes', 'Action', 'Source', 'Destination'] + header = ['Rule', 'Packets', 'Bytes', 'Action', 'Source', 'Destination', 'Inbound-Interface', 'Outbound-interface'] print(tabulate.tabulate(rows, header) + '\n') def show_firewall(): @@ -160,15 +239,11 @@ def show_firewall(): if not firewall: return - if 'ipv4' in firewall: - for hook, hook_conf in firewall['ipv4'].items(): - for prior, prior_conf in firewall['ipv4'][hook].items(): - output_firewall_name(hook, prior, prior_conf, ipv6=False) - - if 'ipv6' in firewall: - for hook, hook_conf in firewall['ipv6'].items(): - for prior, prior_conf in firewall['ipv6'][hook].items(): - output_firewall_name(hook, prior, prior_conf, ipv6=True) + for family in ['ipv4', 'ipv6', 'bridge']: + if family in firewall: + for hook, hook_conf in firewall[family].items(): + for prior, prior_conf in firewall[family][hook].items(): + output_firewall_name(family, hook, prior, prior_conf) def show_firewall_family(family): print(f'Rulesets {family} Information') @@ -176,35 +251,32 @@ def show_firewall_family(family): conf = Config() firewall = get_config_firewall(conf) - if not firewall: + if not firewall or family not in firewall: return for hook, hook_conf in firewall[family].items(): for prior, prior_conf in firewall[family][hook].items(): - if family == 'ipv6': - output_firewall_name(hook, prior, prior_conf, ipv6=True) - else: - output_firewall_name(hook, prior, prior_conf, ipv6=False) + output_firewall_name(family, hook, prior, prior_conf) -def show_firewall_name(hook, priority, ipv6=False): +def show_firewall_name(family, hook, priority): print('Ruleset Information') conf = Config() - firewall = get_config_firewall(conf, hook, priority, ipv6) + firewall = get_config_firewall(conf, family, hook, priority) if firewall: - output_firewall_name(hook, priority, firewall, ipv6) + output_firewall_name(family, hook, priority, firewall) -def show_firewall_rule(hook, priority, rule_id, ipv6=False): +def show_firewall_rule(family, hook, priority, rule_id): print('Rule Information') conf = Config() - firewall = get_config_firewall(conf, hook, priority, ipv6) + firewall = get_config_firewall(conf, family, hook, priority) if firewall: - output_firewall_name(hook, priority, firewall, ipv6, rule_id) + output_firewall_name(family, hook, priority, firewall, rule_id) def show_firewall_group(name=None): conf = Config() - firewall = get_config_firewall(conf, interfaces=False) + firewall = get_config_firewall(conf) if 'group' not in firewall: return @@ -225,18 +297,39 @@ def show_firewall_group(name=None): for item in family: for name_type in ['name', 'ipv6_name', 'forward', 'input', 'output']: - if name_type not in firewall[item]: - continue - for name, name_conf in firewall[item][name_type].items(): - if 'rule' not in name_conf: + if item in firewall: + if name_type not in firewall[item]: continue - for rule_id, rule_conf in name_conf['rule'].items(): - source_group = dict_search_args(rule_conf, 'source', 'group', group_type) - dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type) - if source_group and group_name == source_group: - out.append(f'{name}-{rule_id}') - elif dest_group and group_name == dest_group: - out.append(f'{name}-{rule_id}') + for priority, priority_conf in firewall[item][name_type].items(): + if priority not in firewall[item][name_type]: + continue + if 'rule' not in priority_conf: + continue + for rule_id, rule_conf in priority_conf['rule'].items(): + source_group = dict_search_args(rule_conf, 'source', 'group', group_type) + dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type) + in_interface = dict_search_args(rule_conf, 'inbound_interface', 'interface_group') + out_interface = dict_search_args(rule_conf, 'outbound_interface', 'interface_group') + if source_group: + if source_group[0] == "!": + source_group = source_group[1:] + if group_name == source_group: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') + if dest_group: + if dest_group[0] == "!": + dest_group = dest_group[1:] + if group_name == dest_group: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') + if in_interface: + if in_interface[0] == "!": + in_interface = in_interface[1:] + if group_name == in_interface: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') + if out_interface: + if out_interface[0] == "!": + out_interface = out_interface[1:] + if group_name == out_interface: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') return out header = ['Name', 'Type', 'References', 'Members'] @@ -248,7 +341,7 @@ def show_firewall_group(name=None): continue references = find_references(group_type, group_name) - row = [group_name, group_type, '\n'.join(references) or 'N/A'] + row = [group_name, group_type, '\n'.join(references) or 'N/D'] if 'address' in group_conf: row.append("\n".join(sorted(group_conf['address']))) elif 'network' in group_conf: @@ -257,8 +350,10 @@ def show_firewall_group(name=None): row.append("\n".join(sorted(group_conf['mac_address']))) elif 'port' in group_conf: row.append("\n".join(sorted(group_conf['port']))) + elif 'interface' in group_conf: + row.append("\n".join(sorted(group_conf['interface']))) else: - row.append('N/A') + row.append('N/D') rows.append(row) if rows: @@ -277,6 +372,7 @@ def show_summary(): header = ['Ruleset Hook', 'Ruleset Priority', 'Description', 'References'] v4_out = [] v6_out = [] + br_out = [] if 'ipv4' in firewall: for hook, hook_conf in firewall['ipv4'].items(): @@ -290,6 +386,12 @@ def show_summary(): description = prior_conf.get('description', '') v6_out.append([hook, prior, description]) + if 'bridge' in firewall: + for hook, hook_conf in firewall['bridge'].items(): + for prior, prior_conf in firewall['bridge'][hook].items(): + description = prior_conf.get('description', '') + br_out.append([hook, prior, description]) + if v6_out: print('\nIPv6 Ruleset:\n') print(tabulate.tabulate(v6_out, header) + '\n') @@ -298,6 +400,10 @@ def show_summary(): print('\nIPv4 Ruleset:\n') print(tabulate.tabulate(v4_out, header) + '\n') + if br_out: + print('\nBridge Ruleset:\n') + print(tabulate.tabulate(br_out, header) + '\n') + show_firewall_group() def show_statistics(): @@ -309,15 +415,11 @@ def show_statistics(): if not firewall: return - if 'ipv4' in firewall: - for hook, hook_conf in firewall['ipv4'].items(): - for prior, prior_conf in firewall['ipv4'][hook].items(): - output_firewall_name_statistics(hook,prior, prior_conf, ipv6=False) - - if 'ipv6' in firewall: - for hook, hook_conf in firewall['ipv6'].items(): - for prior, prior_conf in firewall['ipv6'][hook].items(): - output_firewall_name_statistics(hook,prior, prior_conf, ipv6=True) + for family in ['ipv4', 'ipv6', 'bridge']: + if family in firewall: + for hook, hook_conf in firewall[family].items(): + for prior, prior_conf in firewall[family][hook].items(): + output_firewall_name_statistics(family, hook,prior, prior_conf) if __name__ == '__main__': parser = argparse.ArgumentParser() @@ -333,9 +435,9 @@ if __name__ == '__main__': if args.action == 'show': if not args.rule: - show_firewall_name(args.hook, args.priority, args.ipv6) + show_firewall_name(args.family, args.hook, args.priority) else: - show_firewall_rule(args.hook, args.priority, args.rule, args.ipv6) + show_firewall_rule(args.family, args.hook, args.priority, args.rule) elif args.action == 'show_all': show_firewall() elif args.action == 'show_family': diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py index 57d3cfed9..44d41219e 100755 --- a/src/op_mode/ipsec.py +++ b/src/op_mode/ipsec.py @@ -779,6 +779,45 @@ def show_ra_summary(raw: bool): return _get_formatted_output_ra_summary(list_sa) +# PSK block +def _get_raw_psk(): + conf: ConfigTreeQuery = ConfigTreeQuery() + config_path = ['vpn', 'ipsec', 'authentication', 'psk'] + psk_config = conf.get_config_dict(config_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + psk_list = [] + for psk, psk_data in psk_config.items(): + psk_data['psk'] = psk + psk_list.append(psk_data) + + return psk_list + + +def _get_formatted_psk(psk_list): + headers = ["PSK", "Id", "Secret"] + formatted_data = [] + + for psk_data in psk_list: + formatted_data.append([psk_data["psk"], "\n".join(psk_data["id"]), psk_data["secret"]]) + + return tabulate(formatted_data, headers=headers) + + +def show_psk(raw: bool): + config = ConfigTreeQuery() + if not config.exists('vpn ipsec authentication psk'): + raise vyos.opmode.UnconfiguredSubsystem('VPN ipsec psk authentication is not configured') + + psk = _get_raw_psk() + if raw: + return psk + return _get_formatted_psk(psk) + +# PSK block end + + if __name__ == '__main__': try: res = vyos.opmode.run(sys.modules[__name__]) diff --git a/src/op_mode/otp.py b/src/op_mode/otp.py new file mode 100755 index 000000000..6d4298894 --- /dev/null +++ b/src/op_mode/otp.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 + +# Copyright 2017, 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + + +import sys +import os +import vyos.opmode +from jinja2 import Template +from vyos.configquery import ConfigTreeQuery +from vyos.xml import defaults +from vyos.configdict import dict_merge +from vyos.utils.process import popen + + +users_otp_template = Template(""" +{% if info == "full" %} +# You can share it with the user, he just needs to scan the QR in his OTP app +# username: {{username}} +# OTP KEY: {{key_base32}} +# OTP URL: {{otp_url}} +{{qrcode}} +# To add this OTP key to configuration, run the following commands: +set system login user {{username}} authentication otp key '{{key_base32}}' +{% if rate_limit != "3" %} +set system login user {{username}} authentication otp rate-limit '{{rate_limit}}' +{% endif %} +{% if rate_time != "30" %} +set system login user {{username}} authentication otp rate-time '{{rate_time}}' +{% endif %} +{% if window_size != "3" %} +set system login user {{username}} authentication otp window-size '{{window_size}}' +{% endif %} +{% elif info == "key-b32" %} +# OTP key in Base32 for system user {{username}}: +{{key_base32}} +{% elif info == "qrcode" %} +# QR code for system user '{{username}}' +{{qrcode}} +{% elif info == "uri" %} +# URI for system user '{{username}}' +{{otp_url}} +{% endif %} +""", trim_blocks=True, lstrip_blocks=True) + + +def _check_uname_otp(username:str): + """ + Check if "username" exists and have an OTP key + """ + config = ConfigTreeQuery() + base_key = ['system', 'login', 'user', username, 'authentication', 'otp', 'key'] + if not config.exists(base_key): + return None + return True + +def _get_login_otp(username: str, info:str): + """ + Retrieve user settings from configuration and set some defaults + """ + config = ConfigTreeQuery() + base = ['system', 'login', 'user', username] + if not config.exists(base): + return None + user_otp = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = defaults(['system', 'login', 'user']) + user_otp = dict_merge(default_values, user_otp) + result = user_otp['authentication']['otp'] + # Filling in the system and default options + result['info'] = info + result['hostname'] = os.uname()[1] + result['username'] = username + result['key_base32'] = result['key'] + result['otp_length'] = '6' + result['interval'] = '30' + result['token_type'] = 'hotp-time' + if result['token_type'] == 'hotp-time': + token_type_acrn = 'totp' + result['otp_url'] = ''.join(["otpauth://",token_type_acrn,"/",username,"@",\ + result['hostname'],"?secret=",result['key_base32'],"&digits=",\ + result['otp_length'],"&period=",result['interval']]) + result['qrcode'],err = popen('qrencode -t ansiutf8', input=result['otp_url']) + return result + +def show_login(raw: bool, username: str, info:str): + ''' + Display OTP parameters for <username> + ''' + check_otp = _check_uname_otp(username) + if check_otp: + user_otp_params = _get_login_otp(username, info) + else: + print(f'There is no such user ("{username}") with an OTP key configured') + print('You can use the following command to generate a key for a user:\n') + print(f'generate system login username {username} otp-key hotp-time') + sys.exit(0) + if raw: + return user_otp_params + return users_otp_template.render(user_otp_params) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/vyos-op-cmd-wrapper.sh b/src/op_mode/vyos-op-cmd-wrapper.sh new file mode 100755 index 000000000..a89211b2b --- /dev/null +++ b/src/op_mode/vyos-op-cmd-wrapper.sh @@ -0,0 +1,6 @@ +#!/bin/vbash +shopt -s expand_aliases +source /etc/default/vyatta +source /etc/bash_completion.d/vyatta-op +_vyatta_op_init +_vyatta_op_run "$@" diff --git a/src/pam-configs/radius b/src/pam-configs/radius index 08247f77c..eee9cb93e 100644 --- a/src/pam-configs/radius +++ b/src/pam-configs/radius @@ -3,15 +3,18 @@ Default: no Priority: 257 Auth-Type: Primary Auth: + [default=ignore success=2] pam_succeed_if.so service = sudo [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet [authinfo_unavail=ignore success=end default=ignore] pam_radius_auth.so Account-Type: Primary Account: + [default=ignore success=2] pam_succeed_if.so service = sudo [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet [authinfo_unavail=ignore success=end perm_denied=bad default=ignore] pam_radius_auth.so Session-Type: Additional Session: + [default=ignore success=2] pam_succeed_if.so service = sudo [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet [authinfo_unavail=ignore success=ok default=ignore] pam_radius_auth.so diff --git a/src/systemd/vyos-router.service b/src/systemd/vyos-router.service index 6f683cebb..7a1638f11 100644 --- a/src/systemd/vyos-router.service +++ b/src/systemd/vyos-router.service @@ -1,7 +1,6 @@ [Unit] Description=VyOS Router After=systemd-journald-dev-log.socket time-sync.target local-fs.target cloud-config.service -Requires=frr.service Conflicts=shutdown.target Before=systemd-user-sessions.service diff --git a/src/tests/test_dependency_graph.py b/src/tests/test_dependency_graph.py new file mode 100644 index 000000000..f682e87bb --- /dev/null +++ b/src/tests/test_dependency_graph.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +from vyos.configdep import check_dependency_graph + +_here = os.path.dirname(__file__) +ddir = os.path.join(_here, '../../data/config-mode-dependencies') + +from unittest import TestCase + +class TestDependencyGraph(TestCase): + def setUp(self): + pass + + def test_acyclic(self): + res = check_dependency_graph(dependency_dir=ddir) + self.assertTrue(res) |