diff options
37 files changed, 437 insertions, 159 deletions
| diff --git a/data/templates/accel-ppp/pptp.config.j2 b/data/templates/accel-ppp/pptp.config.j2 index 442830b6b..78a629d2d 100644 --- a/data/templates/accel-ppp/pptp.config.j2 +++ b/data/templates/accel-ppp/pptp.config.j2 @@ -93,10 +93,15 @@ bind={{ radius_source_address }}  gw-ip-address={{ gw_ip }}  {% endif %} -{% if radius_shaper_attr %} +{% if radius_shaper_enable %}  [shaper]  verbose=1 +{%     if radius_shaper_attr %}  attr={{ radius_shaper_attr }} +{%     endif %} +{%     if radius_shaper_multiplier %} +rate-multiplier={{ radius_shaper_multiplier }} +{%     endif %}  {%     if radius_shaper_vendor %}  vendor={{ radius_shaper_vendor }}  {%     endif %} diff --git a/data/templates/ethernet/wpa_supplicant.conf.j2 b/data/templates/ethernet/wpa_supplicant.conf.j2 index 8f140f6cb..cd35d6d1e 100644 --- a/data/templates/ethernet/wpa_supplicant.conf.j2 +++ b/data/templates/ethernet/wpa_supplicant.conf.j2 @@ -67,6 +67,11 @@ network={      # discards such frames to protect against potential attacks by rogue      # devices, but this option can be used to disable that protection for cases      # where the server/authenticator does not need to be authenticated. -    phase1="allow_canned_success=1" +    # +    # "tls_disable_tlsv1_0=0" is used to allow TLSv1 for compatibility with +    # legacy networks. This follows the behavior of Debian's wpa_supplicant, +    # which includes a custom patch for allowing TLSv1, but the patch currently +    # does not work for VyOS' git builds of wpa_supplicant. +    phase1="allow_canned_success=1 tls_disable_tlsv1_0=0"  } diff --git a/data/templates/frr/isisd.frr.j2 b/data/templates/frr/isisd.frr.j2 index 8df1e9513..3c37e28b9 100644 --- a/data/templates/frr/isisd.frr.j2 +++ b/data/templates/frr/isisd.frr.j2 @@ -25,6 +25,12 @@ interface {{ iface }}  {%         if iface_config.hello_padding is vyos_defined %}   isis hello padding  {%         endif %} +{%         if iface_config.ldp_sync.disable is vyos_defined %} + no isis mpls ldp-sync +{%         elif iface_config.ldp_sync.holddown is vyos_defined %} + isis mpls ldp-sync + isis mpls ldp-sync holddown {{ iface_config.ldp_sync.holddown }} +{%         endif %}  {%         if iface_config.metric is vyos_defined %}   isis metric {{ iface_config.metric }}  {%         endif %} @@ -84,6 +90,11 @@ router isis VyOS {{ 'vrf ' + vrf if vrf is vyos_defined }}  {% if max_lsp_lifetime is vyos_defined %}   max-lsp-lifetime {{ max_lsp_lifetime }}  {% endif %} +{% if ldp_sync.holddown is vyos_defined %} + mpls ldp-sync holddown {{ ldp_sync.holddown }} +{% elif ldp_sync is vyos_defined %} + mpls ldp-sync +{% endif %}  {% if spf_interval is vyos_defined %}   spf-interval {{ spf_interval }}  {% endif %} diff --git a/data/templates/frr/ospfd.frr.j2 b/data/templates/frr/ospfd.frr.j2 index 8c4a81c57..3f97b7325 100644 --- a/data/templates/frr/ospfd.frr.j2 +++ b/data/templates/frr/ospfd.frr.j2 @@ -44,6 +44,12 @@ interface {{ iface }}  {%         if iface_config.bfd.profile is vyos_defined %}   ip ospf bfd profile {{ iface_config.bfd.profile }}  {%         endif %} +{%         if iface_config.ldp_sync.disable is vyos_defined %} + no ip ospf mpls ldp-sync +{%         elif iface_config.ldp_sync.holddown is vyos_defined %} + ip ospf mpls ldp-sync + ip ospf mpls ldp-sync holddown {{ iface_config.ldp_sync.holddown }} +{%         endif %}  {%         if iface_config.mtu_ignore is vyos_defined %}   ip ospf mtu-ignore  {%         endif %} @@ -133,6 +139,11 @@ router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }}  {% if maximum_paths is vyos_defined %}   maximum-paths {{ maximum_paths }}  {% endif %} +{% if ldp_sync.holddown is vyos_defined %} + mpls ldp-sync holddown {{ ldp_sync.holddown }} +{% elif ldp_sync is vyos_defined %} + mpls ldp-sync +{% endif %}  {% if distance.global is vyos_defined %}   distance {{ distance.global }}  {% endif %} diff --git a/data/templates/login/limits.j2 b/data/templates/login/limits.j2 new file mode 100644 index 000000000..5e2c11f35 --- /dev/null +++ b/data/templates/login/limits.j2 @@ -0,0 +1,5 @@ +# Generated by /usr/libexec/vyos/conf_mode/system-login.py + +{% if max_login_session is vyos_defined %} +* - maxsyslogins {{ max_login_session }} +{% endif %} diff --git a/data/templates/telegraf/telegraf.j2 b/data/templates/telegraf/telegraf.j2 index c9f402281..5852d6232 100644 --- a/data/templates/telegraf/telegraf.j2 +++ b/data/templates/telegraf/telegraf.j2 @@ -12,7 +12,7 @@    debug = false    quiet = false    logfile = "" -  hostname = "" +  hostname = "{{ hostname }}"    omit_hostname = false  {% if azure_data_explorer is vyos_defined %}  ### Azure Data Explorer ### diff --git a/debian/control b/debian/control index 8cd49f62a..856f57030 100644 --- a/debian/control +++ b/debian/control @@ -35,6 +35,7 @@ Architecture: amd64 arm64  Depends:    ${python3:Depends},    accel-ppp, +  auditd,    avahi-daemon,    beep,    bmon, @@ -80,6 +81,7 @@ Depends:    lcdproc,    lcdproc-extra-drivers,    libatomic1, +  libauparse0,    libbpf1 [amd64],    libcharon-extra-plugins (>=5.9),    libcharon-extauth-plugins (>=5.9), diff --git a/interface-definitions/include/isis/ldp-sync-holddown.xml.i b/interface-definitions/include/isis/ldp-sync-holddown.xml.i new file mode 100644 index 000000000..15ac26f07 --- /dev/null +++ b/interface-definitions/include/isis/ldp-sync-holddown.xml.i @@ -0,0 +1,14 @@ +<!-- include start from isis/ldp-sync-holddown.xml.i --> +<leafNode name="holddown"> +  <properties> +    <help>Hold down timer for LDP-IGP cost restoration</help> +    <valueHelp> +      <format>u32:0-10000</format> +      <description>Time to wait in seconds for LDP-IGP synchronization to occur before restoring interface cost</description> +    </valueHelp> +    <constraint> +      <validator name="numeric" argument="--range 0-10000"/> +    </constraint> +  </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/isis/ldp-sync-interface.xml.i b/interface-definitions/include/isis/ldp-sync-interface.xml.i new file mode 100644 index 000000000..222a35256 --- /dev/null +++ b/interface-definitions/include/isis/ldp-sync-interface.xml.i @@ -0,0 +1,11 @@ +<!-- include start from isis/ldp-igp-sync.xml.i --> +<node name="ldp-sync"> +  <properties> +    <help>LDP-IGP synchronization configuration for interface</help> +  </properties> +  <children> +    #include <include/generic-disable-node.xml.i> +    #include <include/isis/ldp-sync-holddown.xml.i> +  </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/isis/ldp-sync-protocol.xml.i b/interface-definitions/include/isis/ldp-sync-protocol.xml.i new file mode 100644 index 000000000..b2e696a70 --- /dev/null +++ b/interface-definitions/include/isis/ldp-sync-protocol.xml.i @@ -0,0 +1,10 @@ +<!-- include start from isis/ldp-igp-sync.xml.i --> +<node name="ldp-sync"> +  <properties> +    <help>Protocol wide LDP-IGP synchronization configuration</help> +  </properties> +  <children> +    #include <include/isis/ldp-sync-holddown.xml.i> +  </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/isis/protocol-common-config.xml.i b/interface-definitions/include/isis/protocol-common-config.xml.i index 0e6f19480..8103b5c5d 100644 --- a/interface-definitions/include/isis/protocol-common-config.xml.i +++ b/interface-definitions/include/isis/protocol-common-config.xml.i @@ -152,6 +152,7 @@      </constraint>    </properties>  </leafNode> +#include <include/isis/ldp-sync-protocol.xml.i>  <leafNode name="net">    <properties>      <help>A Network Entity Title for this process (ISO only)</help> @@ -172,7 +173,7 @@  </leafNode>  <node name="traffic-engineering">    <properties> -    <help>Show IS-IS neighbor adjacencies</help> +    <help>IS-IS traffic engineering extensions</help>    </properties>    <children>      <leafNode name="enable"> @@ -631,6 +632,7 @@        </properties>      </leafNode>      #include <include/isis/metric.xml.i> +    #include <include/isis/ldp-sync-interface.xml.i>      <node name="network">        <properties>          <help>Set network type</help> diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i index f39b9c5e3..e400119dd 100644 --- a/interface-definitions/include/ospf/protocol-common-config.xml.i +++ b/interface-definitions/include/ospf/protocol-common-config.xml.i @@ -331,6 +331,7 @@      </constraint>    </properties>  </leafNode> +#include <include/isis/ldp-sync-protocol.xml.i>  <node name="distance">    <properties>      <help>Administrative distance</help> @@ -385,6 +386,7 @@      #include <include/ospf/authentication.xml.i>      #include <include/ospf/intervals.xml.i>      #include <include/ospf/interface-common.xml.i> +    #include <include/isis/ldp-sync-interface.xml.i>      <leafNode name="bandwidth">        <properties>          <help>Interface bandwidth (Mbit/s)</help> @@ -875,4 +877,4 @@      </node>    </children>  </node> -<!-- include end --> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/protocols-failover.xml.in b/interface-definitions/protocols-failover.xml.in index 900c76eab..a8c5c717f 100644 --- a/interface-definitions/protocols-failover.xml.in +++ b/interface-definitions/protocols-failover.xml.in @@ -48,6 +48,7 @@                            <constraint>                              <validator name="ipv4-address"/>                            </constraint> +                          <multi/>                          </properties>                        </leafNode>                        <leafNode name="timeout"> diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in index b00741ffe..258913929 100644 --- a/interface-definitions/system-login.xml.in +++ b/interface-definitions/system-login.xml.in @@ -225,6 +225,19 @@                #include <include/interface/vrf.xml.i>              </children>            </node> +          <leafNode name="max-login-session"> +            <properties> +              <help>Maximum number of all login sessions</help> +              <valueHelp> +                <format>u32:1-65536</format> +                <description>Maximum number of all login sessions</description> +              </valueHelp> +              <constraint> +                <validator name="numeric" argument="--range 1-65536"/> +              </constraint> +              <constraintErrorMessage>Maximum logins must be between 1 and 65536</constraintErrorMessage> +            </properties> +          </leafNode>            <leafNode name="timeout">              <properties>                <help>Session timeout</help> diff --git a/interface-definitions/vpn-pptp.xml.in b/interface-definitions/vpn-pptp.xml.in index 00ffd26f9..5a8b4a78a 100644 --- a/interface-definitions/vpn-pptp.xml.in +++ b/interface-definitions/vpn-pptp.xml.in @@ -108,9 +108,13 @@                        </tagNode>                      </children>                    </node> +                  <node name="radius"> +                    <children> +                      #include <include/accel-ppp/radius-additions-rate-limit.xml.i> +                    </children> +                  </node>                    #include <include/radius-auth-server-ipv4.xml.i>                    #include <include/accel-ppp/radius-additions.xml.i> -                  #include <include/accel-ppp/radius-additions-rate-limit.xml.i>                  </children>                </node>              </children> diff --git a/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i index 7dbc4fde5..820d507fd 100644 --- a/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i +++ b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i @@ -195,6 +195,12 @@          </leafNode>        </children>      </node> +    <leafNode name="filtered-routes"> +      <properties> +        <help>Show filtered routes from BGP neighbor</help> +      </properties> +      <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +    </leafNode>      <leafNode name="received-routes">        <properties>          <help>Show received routes from BGP neighbor</help> diff --git a/op-mode-definitions/include/bgp/show-ip-bgp-common.xml.i b/op-mode-definitions/include/bgp/show-ip-bgp-common.xml.i index 36cc9a3fa..db9021f3e 100644 --- a/op-mode-definitions/include/bgp/show-ip-bgp-common.xml.i +++ b/op-mode-definitions/include/bgp/show-ip-bgp-common.xml.i @@ -93,6 +93,12 @@                </properties>                <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>              </leafNode> +            <leafNode name="filtered-routes"> +              <properties> +                <help>Show the filtered routes from neighbor</help> +              </properties> +              <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +            </leafNode>              <leafNode name="received-routes">                <properties>                  <help>Show the received routes from neighbor</help> diff --git a/op-mode-definitions/include/isis-common.xml.i b/op-mode-definitions/include/isis-common.xml.i index 0e20861c7..e94d868e8 100644 --- a/op-mode-definitions/include/isis-common.xml.i +++ b/op-mode-definitions/include/isis-common.xml.i @@ -4,12 +4,7 @@      <help>Show IS-IS link state database</help>    </properties>    <children> -    <leafNode name="detail"> -      <properties> -        <help>Show detailed information</help> -      </properties> -      <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> -    </leafNode> +    #include <include/vtysh-generic-detail.xml.i>    </children>    <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>  </node> @@ -36,27 +31,22 @@      </completionHelp>    </properties>    <children> -    <leafNode name="detail"> -      <properties> -        <help>Show detailed information</help> -      </properties> -      <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> -    </leafNode> +    #include <include/vtysh-generic-detail.xml.i>    </children>    <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>  </node> -<tagNode name="interface"> +#include <include/vtysh-generic-interface-tagNode.xml.i> +<node name="mpls">    <properties> -    <help>Show specific IS-IS interface</help> -    <completionHelp> -      <script>${vyos_completion_dir}/list_interfaces</script> -    </completionHelp> +    <help>Show MPLS information</help>    </properties> -  <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> -</tagNode> +  <children> +  #include <include/ldp-sync.xml.i> +  </children> +</node>  <node name="mpls-te">    <properties> -    <help>Show IS-IS MPLS traffic engineering information</help> +    <help>Show MPLS traffic engineering information</help>    </properties>    <children>      <leafNode name="router"> @@ -71,15 +61,7 @@        </properties>        <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>      </leafNode> -    <tagNode name="interface"> -      <properties> -        <help>Show specific IS-IS interface</help> -        <completionHelp> -          <script>${vyos_completion_dir}/list_interfaces</script> -        </completionHelp> -      </properties> -      <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> -    </tagNode> +    #include <include/vtysh-generic-interface-tagNode.xml.i>    </children>  </node>  <node name="neighbor"> @@ -87,12 +69,7 @@      <help>Show IS-IS neighbor adjacencies</help>    </properties>    <children> -    <leafNode name="detail"> -      <properties> -        <help>Show detailed information</help> -      </properties> -      <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> -    </leafNode> +    #include <include/vtysh-generic-detail.xml.i>    </children>    <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>  </node> @@ -176,4 +153,4 @@    </children>    <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>  </node> -<!-- included end --> +<!-- included end -->
\ No newline at end of file diff --git a/op-mode-definitions/include/ldp-sync.xml.i b/op-mode-definitions/include/ldp-sync.xml.i new file mode 100644 index 000000000..b7b04e7e5 --- /dev/null +++ b/op-mode-definitions/include/ldp-sync.xml.i @@ -0,0 +1,11 @@ +<!-- included start from ldp-sync.xml.i --> +<node name="ldp-sync"> +  <properties> +    <help>Show LDP-IGP synchronization information</help> +  </properties> +  <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +  <children> +    #include <include/vtysh-generic-interface-tagNode.xml.i> +  </children> +</node> +<!-- included end -->
\ No newline at end of file diff --git a/op-mode-definitions/include/ospf-common.xml.i b/op-mode-definitions/include/ospf-common.xml.i index 098254f4e..aebbae5ff 100644 --- a/op-mode-definitions/include/ospf-common.xml.i +++ b/op-mode-definitions/include/ospf-common.xml.i @@ -508,15 +508,15 @@    </properties>    <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>  </node> -<tagNode name="interface"> +#include <include/vtysh-generic-interface-tagNode.xml.i> +<node name="mpls">    <properties> -    <help>Show IPv4 OSPF information for specified interface</help> -    <completionHelp> -      <script>${vyos_completion_dir}/list_interfaces</script> -    </completionHelp> +    <help>Show MPLS information</help>    </properties> -  <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> -</tagNode> +  <children> +  #include <include/ldp-sync.xml.i> +  </children> +</node>  <node name="neighbor">    <properties>      <help>Show IPv4 OSPF neighbor information</help> @@ -547,4 +547,4 @@    </properties>    <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>  </leafNode> -<!-- included end --> +<!-- included end -->
\ No newline at end of file diff --git a/op-mode-definitions/include/vtysh-generic-interface-tagNode.xml.i b/op-mode-definitions/include/vtysh-generic-interface-tagNode.xml.i new file mode 100644 index 000000000..e95961177 --- /dev/null +++ b/op-mode-definitions/include/vtysh-generic-interface-tagNode.xml.i @@ -0,0 +1,11 @@ +<!-- included start from vtysh-generic-interface.xml.i --> +<tagNode name="interface"> +  <properties> +    <help>Show information about specific interface</help> +    <completionHelp> +      <script>${vyos_completion_dir}/list_interfaces</script> +    </completionHelp> +  </properties> +  <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 7f6469ca9..7663e4c00 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -8,6 +8,12 @@          </properties>          <command>journalctl --no-hostname --boot</command>          <children> +          <leafNode name="audit"> +            <properties> +              <help>Show audit logs</help> +            </properties> +            <command>cat /var/log/audit/audit.log</command> +          </leafNode>            <leafNode name="all">              <properties>                <help>Show contents of all master log files</help> diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py index b2143d16e..c80c7cf80 100755 --- a/smoketest/scripts/cli/test_interfaces_openvpn.py +++ b/smoketest/scripts/cli/test_interfaces_openvpn.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-2023 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -368,6 +368,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):              self.cli_set(path + ['hash', auth_hash])              self.cli_set(path + ['mode', 'server'])              self.cli_set(path + ['local-port', port]) +            self.cli_set(path + ['server', 'mfa', 'totp'])              self.cli_set(path + ['server', 'subnet', subnet])              self.cli_set(path + ['server', 'topology', 'subnet'])              self.cli_set(path + ['keep-alive', 'failure-count', '5']) @@ -388,6 +389,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):          for ii in num_range:              interface = f'vtun{ii}' +            plugin = f'plugin "/usr/lib/openvpn/openvpn-otp.so" "otp_secrets=/config/auth/openvpn/{interface}-otp-secrets otp_slop=180 totp_t0=0 totp_step=30 totp_digits=6 password_is_cr=1"'              subnet = f'192.0.{ii}.0/24'              start_addr = inc_ip(subnet, '2') @@ -411,6 +413,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):              self.assertIn(f'topology subnet', config)              self.assertIn(f'lport {port}', config)              self.assertIn(f'push "redirect-gateway def1"', config) +            self.assertIn(f'{plugin}', config)              self.assertIn(f'keepalive 5 25', config)              # TLS options diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py index d11d80a1f..61e29c449 100755 --- a/smoketest/scripts/cli/test_protocols_isis.py +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -308,5 +308,48 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):          self.assertIn(f' segment-routing prefix {prefix_three} absolute {prefix_three_value} explicit-null', tmp)          self.assertIn(f' segment-routing prefix {prefix_four} absolute {prefix_four_value} no-php-flag', tmp) +    def test_isis_08_ldp_sync(self): +        holddown = "500" +        interface = 'lo' + +        self.cli_set(base_path + ['net', net]) +        self.cli_set(base_path + ['interface', interface]) +        self.cli_set(base_path + ['ldp-sync', 'holddown', holddown]) +         +        # Commit main ISIS changes +        self.cli_commit() +         +        # Verify main ISIS changes +        tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') +        self.assertIn(f' net {net}', tmp) +        self.assertIn(f' mpls ldp-sync', tmp) +        self.assertIn(f' mpls ldp-sync holddown {holddown}', tmp) +         +        for interface in self._interfaces: +            self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'holddown', holddown]) + +            # Commit interface changes for holddown +            self.cli_commit() + +            # Verify interface changes for holddown +            tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') +            self.assertIn(f'interface {interface}', tmp) +            self.assertIn(f' ip router isis {domain}', tmp) +            self.assertIn(f' ipv6 router isis {domain}', tmp) +            self.assertIn(f' isis mpls ldp-sync holddown {holddown}', tmp) +             +        for interface in self._interfaces: +            self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'disable']) +             +            # Commit interface changes for disable +            self.cli_commit() +             +            # Verify interface changes for disable +            tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') +            self.assertIn(f'interface {interface}', tmp) +            self.assertIn(f' ip router isis {domain}', tmp) +            self.assertIn(f' ipv6 router isis {domain}', tmp) +            self.assertIn(f' no isis mpls ldp-sync', tmp) +  if __name__ == '__main__':      unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index 581959b15..d4c85f2b2 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -434,6 +434,47 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):          self.assertIn(f' segment-routing prefix {prefix_one} index {prefix_one_value} explicit-null', frrconfig)          self.assertIn(f' segment-routing prefix {prefix_two} index {prefix_two_value} no-php-flag', frrconfig) +    def test_ospf_15_ldp_sync(self): +        holddown = "500" +        interface = 'lo' +        interfaces = Section.interfaces('ethernet') + +        self.cli_set(base_path + ['interface', interface]) +        self.cli_set(base_path + ['ldp-sync', 'holddown', holddown]) +         +        # Commit main OSPF changes +        self.cli_commit() +         +        # Verify main OSPF changes +        frrconfig = self.getFRRconfig('router ospf') +        self.assertIn(f'router ospf', frrconfig) +        self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) +        self.assertIn(f' mpls ldp-sync holddown {holddown}', frrconfig) +         +        for interface in interfaces: +            self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'holddown', holddown]) + +            # Commit interface changes for holddown +            self.cli_commit() + +            # Verify interface changes for holddown +            config = self.getFRRconfig(f'interface {interface}') +            self.assertIn(f'interface {interface}', config) +            self.assertIn(f' ip ospf dead-interval 40', config) +            self.assertIn(f' ip ospf mpls ldp-sync', config) +            self.assertIn(f' ip ospf mpls ldp-sync holddown {holddown}', config) +             +        for interface in interfaces: +            self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'disable']) +             +            # Commit interface changes for disable +            self.cli_commit() +             +            # Verify interface changes for disable +            config = self.getFRRconfig(f'interface {interface}') +            self.assertIn(f'interface {interface}', config) +            self.assertIn(f' ip ospf dead-interval 40', config) +            self.assertIn(f' no ip ospf mpls ldp-sync', config)  if __name__ == '__main__':      unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_login.py b/smoketest/scripts/cli/test_system_login.py index 6006fe0f6..a1d2ba2ad 100755 --- a/smoketest/scripts/cli/test_system_login.py +++ b/smoketest/scripts/cli/test_system_login.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2019-2022 VyOS maintainers and contributors +# Copyright (C) 2019-2023 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -264,5 +264,26 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase):          tmp = re.findall(r'group:\s+mapname\s+files', nsswitch_conf)          self.assertTrue(tmp) +    def test_system_login_max_login_session(self): +        max_logins = '2' +        timeout = '600' + +        self.cli_set(base_path + ['max-login-session', max_logins]) + +        # 'max-login-session' must be only with 'timeout' option +        with self.assertRaises(ConfigSessionError): +            self.cli_commit() + +        self.cli_set(base_path + ['timeout', timeout]) + +        self.cli_commit() + +        security_limits = read_file('/etc/security/limits.d/10-vyos.conf') +        self.assertIn(f'* - maxsyslogins {max_logins}', security_limits) + +        self.cli_delete(base_path + ['timeout']) +        self.cli_delete(base_path + ['max-login-session']) + +  if __name__ == '__main__':      unittest.main(verbosity=2) diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 05595f86f..4b7ab3444 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -479,8 +479,13 @@ def apply(container):      # the network interface in advance      if 'network' in container:          for network, network_config in container['network'].items(): -            tmp = Interface(f'podman-{network}') -            tmp.set_vrf(network_config.get('vrf', '')) +            network_name = f'podman-{network}' +            # T5147: Networks are started only as soon as there is a consumer. +            # If only a network is created in the first place, no need to assign +            # it to a VRF as there's no consumer, yet. +            if os.path.exists(f'/sys/class/net/{network_name}'): +                tmp = Interface(network_name) +                tmp.set_vrf(network_config.get('vrf', ''))      return None diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index c41a442df..190587980 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -282,6 +282,9 @@ def verify_rule(firewall, rule_conf, ipv6):                  if rule_conf['protocol'] not in ['tcp', 'udp', 'tcp_udp']:                      raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group') +            if 'port' in side_conf and dict_search_args(side_conf, 'group', 'port_group'): +                raise ConfigError(f'{side} port-group and port cannot both be defined') +      if 'log_options' in rule_conf:          if 'log' not in rule_conf or 'enable' not in rule_conf['log']:              raise ConfigError('log-options defined, but log is not enable') diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 13d84a6fe..6f227b0d1 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2019-2022 VyOS maintainers and contributors +# Copyright (C) 2019-2023 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -597,7 +597,7 @@ def generate_pki_files(openvpn):  def generate(openvpn):      interface = openvpn['ifname']      directory = os.path.dirname(cfg_file.format(**openvpn)) -    plugin_dir = '/usr/lib/openvpn' +    openvpn['plugin_dir'] = '/usr/lib/openvpn'      # create base config directory on demand      makedir(directory, user, group)      # enforce proper permissions on /run/openvpn diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py index 363408679..47510ce80 100755 --- a/src/conf_mode/service_monitoring_telegraf.py +++ b/src/conf_mode/service_monitoring_telegraf.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2021-2022 VyOS maintainers and contributors +# Copyright (C) 2021-2023 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -15,6 +15,7 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  import os +import socket  import json  from sys import exit @@ -57,6 +58,13 @@ def get_nft_filter_chains():      return chain_list +def get_hostname() -> str: +    try: +        hostname = socket.getfqdn() +    except socket.gaierror: +        hostname = socket.gethostname() +    return hostname +  def get_config(config=None):      if config:          conf = config @@ -79,6 +87,7 @@ def get_config(config=None):      monitoring = dict_merge(default_values, monitoring)      monitoring['custom_scripts_dir'] = custom_scripts_dir +    monitoring['hostname'] = get_hostname()      monitoring['interfaces_ethernet'] = Section.interfaces('ethernet', vlan=False)      monitoring['nft_chains'] = get_nft_filter_chains() diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index d15fe399d..fbb013cf3 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-2023 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -40,6 +40,7 @@ from vyos import airbag  airbag.enable()  autologout_file = "/etc/profile.d/autologout.sh" +limits_file = "/etc/security/limits.d/10-vyos.conf"  radius_config_file = "/etc/pam_radius_auth.conf"  # LOGIN_TIMEOUT from /etc/loign.defs minus 10 sec @@ -164,6 +165,9 @@ def verify(login):              if ipv6_count > 1:                  raise ConfigError('Only one IPv6 source-address can be set!') +    if 'max_login_session' in login and 'timeout' not in login: +        raise ConfigError('"login timeout" must be configured!') +      return None @@ -226,6 +230,14 @@ def generate(login):          if os.path.isfile(radius_config_file):              os.unlink(radius_config_file) +    # /etc/security/limits.d/10-vyos.conf +    if 'max_login_session' in login: +        render(limits_file, 'login/limits.j2', login, +                   permission=0o644, user='root', group='root') +    else: +        if os.path.isfile(limits_file): +            os.unlink(limits_file) +      if 'timeout' in login:          render(autologout_file, 'login/autologout.j2', login,                     permission=0o755, user='root', group='root') diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py index 7550c411e..986a19972 100755 --- a/src/conf_mode/vpn_pptp.py +++ b/src/conf_mode/vpn_pptp.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018-2020 VyOS maintainers and contributors +# Copyright (C) 2018-2023 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -44,6 +44,8 @@ default_pptp = {      'radius_nas_ip' : '',      'radius_source_address' : '',      'radius_shaper_attr' : '', +    'radius_shaper_enable': False, +    'radius_shaper_multiplier': '',      'radius_shaper_vendor': '',      'radius_dynamic_author' : '',      'chap_secrets_file': pptp_chap_secrets, # used in Jinja2 template @@ -183,15 +185,18 @@ def get_config(config=None):              pptp['radius_dynamic_author'] = dae +        # Rate limit +        if conf.exists(['rate-limit', 'attribute']): +            pptp['radius_shaper_attr'] = conf.return_value(['rate-limit', 'attribute']) +          if conf.exists(['rate-limit', 'enable']): -            pptp['radius_shaper_attr'] = 'Filter-Id' -            c_attr = ['rate-limit', 'enable', 'attribute'] -            if conf.exists(c_attr): -                pptp['radius_shaper_attr'] = conf.return_value(c_attr) - -            c_vendor = ['rate-limit', 'enable', 'vendor'] -            if conf.exists(c_vendor): -                pptp['radius_shaper_vendor'] = conf.return_value(c_vendor) +            pptp['radius_shaper_enable'] = True + +        if conf.exists(['rate-limit', 'multiplier']): +            pptp['radius_shaper_multiplier'] = conf.return_value(['rate-limit', 'multiplier']) + +        if conf.exists(['rate-limit', 'vendor']): +            pptp['radius_shaper_vendor'] = conf.return_value(['rate-limit', 'vendor'])      conf.set_level(base_path)      if conf.exists(['client-ip-pool']): diff --git a/src/etc/opennhrp/opennhrp-script.py b/src/etc/opennhrp/opennhrp-script.py index bf25a7331..688c7af2a 100755 --- a/src/etc/opennhrp/opennhrp-script.py +++ b/src/etc/opennhrp/opennhrp-script.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2023 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -17,7 +17,7 @@  import os  import re  import sys -import vici +import vyos.ipsec  from json import loads  from pathlib import Path @@ -51,9 +51,8 @@ def vici_get_ipsec_uniqueid(conn: str, src_nbma: str,          logger.info(              f'Resolving IKE unique ids for: conn: {conn}, '              f'src_nbma: {src_nbma}, dst_nbma: {dst_nbma}') -        session: vici.Session = vici.Session()          list_ikeid: list[str] = [] -        list_sa = session.list_sas({'ike': conn}) +        list_sa: list = vyos.ipsec.get_vici_sas_by_name(conn, None)          for sa in list_sa:              if sa[conn]['local-host'].decode('ascii') == src_nbma \                      and sa[conn]['remote-host'].decode('ascii') == dst_nbma: @@ -78,16 +77,7 @@ def vici_ike_terminate(list_ikeid: list[str]) -> bool:          return False      try: -        session = vici.Session() -        for ikeid in list_ikeid: -            logger.info(f'Terminating IKE SA with id {ikeid}') -            session_generator = session.terminate( -                {'ike-id': ikeid, 'timeout': '-1'}) -            # a dummy `for` loop is required because of requirements -            # from vici. Without a full iteration on the output, the -            # command to vici may not be executed completely -            for _ in session_generator: -                pass +        vyos.ipsec.terminate_vici_ikeid_list(list_ikeid)          return True      except Exception as err:          logger.error(f'Failed to terminate SA for IKE ids {list_ikeid}: {err}') @@ -180,19 +170,7 @@ def vici_initiate(conn: str, child_sa: str, src_addr: str,          f'Trying to initiate connection. Name: {conn}, child sa: {child_sa}, '          f'src_addr: {src_addr}, dst_addr: {dest_addr}')      try: -        session = vici.Session() -        session_generator = session.initiate({ -            'ike': conn, -            'child': child_sa, -            'timeout': '-1', -            'my-host': src_addr, -            'other-host': dest_addr -        }) -        # a dummy `for` loop is required because of requirements -        # from vici. Without a full iteration on the output, the -        # command to vici may not be executed completely -        for _ in session_generator: -            pass +        vyos.ipsec.vici_initiate(conn, child_sa, src_addr, dest_addr)          return True      except Exception as err:          logger.error(f'Unable to initiate connection {err}') @@ -218,8 +196,11 @@ def vici_terminate(conn: str, src_addr: str, dest_addr: str) -> None:              f'No active sessions found for IKE profile {conn}, '              f'local NBMA {src_addr}, remote NBMA {dest_addr}')      else: -        vici_ike_terminate(ikeid_list) - +        try: +            vyos.ipsec.terminate_vici_ikeid_list(ikeid_list) +        except Exception as err: +            logger.error( +                f'Failed to terminate SA for IKE ids {ikeid_list}: {err}')  def iface_up(interface: str) -> None:      """Proceed tunnel interface UP event diff --git a/src/etc/systemd/system/hostapd@.service.d/override.conf b/src/etc/systemd/system/hostapd@.service.d/override.conf index bb8e81d7a..926c07f94 100644 --- a/src/etc/systemd/system/hostapd@.service.d/override.conf +++ b/src/etc/systemd/system/hostapd@.service.d/override.conf @@ -1,6 +1,8 @@  [Unit]  After=  After=vyos-router.service +ConditionFileNotEmpty= +ConditionFileNotEmpty=/run/hostapd/%i.conf  [Service]  WorkingDirectory=/run/hostapd diff --git a/src/helpers/vyos-failover.py b/src/helpers/vyos-failover.py index 0de945f20..03fb42f57 100755 --- a/src/helpers/vyos-failover.py +++ b/src/helpers/vyos-failover.py @@ -30,7 +30,7 @@ my_name = Path(__file__).stem  def is_route_exists(route, gateway, interface, metric):      """Check if route with expected gateway, dev and metric exists""" -    rc, data = rc_cmd(f'sudo ip --json route show protocol failover {route} ' +    rc, data = rc_cmd(f'ip --json route show protocol failover {route} '                        f'via {gateway} dev {interface} metric {metric}')      if rc == 0:          data = json.loads(data) @@ -72,6 +72,7 @@ def get_best_route_options(route, debug=False):                    f'best_metric: {best_metric}, best_iface: {best_interface}')          return best_gateway, best_interface, best_metric +  def is_port_open(ip, port):      """      Check connection to remote host and port @@ -91,32 +92,54 @@ def is_port_open(ip, port):      finally:          s.close() -def is_target_alive(target=None, iface='', proto='icmp', port=None, debug=False): -    """ -    Host availability check by ICMP, ARP, TCP -    Return True if target checks is successful -    % is_target_alive('192.0.2.1', 'eth1', proto='arp') -    True +def is_target_alive(target_list=None, iface='', proto='icmp', port=None, debug=False): +    """Check the availability of each target in the target_list using +    the specified protocol ICMP, ARP, TCP + +    Args: +        target_list (list): A list of IP addresses or hostnames to check. +        iface (str): The name of the network interface to use for the check. +        proto (str): The protocol to use for the check. Options are 'icmp', 'arp', or 'tcp'. +        port (int): The port number to use for the TCP check. Only applicable if proto is 'tcp'. +        debug (bool): If True, print debug information during the check. + +    Returns: +        bool: True if all targets are reachable, False otherwise. + +    Example: +        % is_target_alive(['192.0.2.1', '192.0.2.5'], 'eth1', proto='arp') +        True      """      if iface != '':          iface = f'-I {iface}' -    if proto == 'icmp': -        command = f'/usr/bin/ping -q {target} {iface} -n -c 2 -W 1' -        rc, response = rc_cmd(command) -        if debug: print(f'    [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]') -        if rc == 0: -            return True -    elif proto == 'arp': -        command = f'/usr/bin/arping -b -c 2 -f -w 1 -i 1 {iface} {target}' -        rc, response = rc_cmd(command) -        if debug: print(f'    [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]') -        if rc == 0: -            return True -    elif proto == 'tcp' and port is not None: -        return True if is_port_open(target, port) else False -    else: -        return False + +    for target in target_list: +        match proto: +            case 'icmp': +                command = f'/usr/bin/ping -q {target} {iface} -n -c 2 -W 1' +                rc, response = rc_cmd(command) +                if debug: +                    print(f'    [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]') +                if rc != 0: +                    return False + +            case 'arp': +                command = f'/usr/bin/arping -b -c 2 -f -w 1 -i 1 {iface} {target}' +                rc, response = rc_cmd(command) +                if debug: +                    print(f'    [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]') +                if rc != 0: +                    return False + +            case _ if proto == 'tcp' and port is not None: +                if not is_port_open(target, port): +                    return False + +            case _: +                return False + +    return True  if __name__ == '__main__': diff --git a/src/op_mode/dynamic_dns.py b/src/op_mode/dynamic_dns.py index 263a3b6a5..2cba33cc8 100755 --- a/src/op_mode/dynamic_dns.py +++ b/src/op_mode/dynamic_dns.py @@ -16,69 +16,63 @@  import os  import argparse -import jinja2  import sys  import time +from tabulate import tabulate  from vyos.config import Config  from vyos.util import call  cache_file = r'/run/ddclient/ddclient.cache' -OUT_TMPL_SRC = """ -{% for entry in hosts %} -ip address   : {{ entry.ip }} -host-name    : {{ entry.host }} -last update  : {{ entry.time }} -update-status: {{ entry.status }} +columns = { +    'host':        'Hostname', +    'ipv4':        'IPv4 address', +    'status-ipv4': 'IPv4 status', +    'ipv6':        'IPv6 address', +    'status-ipv6': 'IPv6 status', +    'mtime':       'Last update', +} + + +def _get_formatted_host_records(host_data): +    data_entries = [] +    for entry in host_data: +        data_entries.append([entry.get(key) for key in columns.keys()]) + +    header = columns.values() +    output = tabulate(data_entries, header, numalign='left') +    return output -{% endfor %} -"""  def show_status():      # A ddclient status file must not always exist      if not os.path.exists(cache_file):          sys.exit(0) -    data = { -        'hosts': [] -    } +    data = []      with open(cache_file, 'r') as f:          for line in f:              if line.startswith('#'):                  continue -            outp = { -                'host': '', -                'ip': '', -                'time': '' -            } - -            if 'host=' in line: -                host = line.split('host=')[1] -                if host: -                    outp['host'] = host.split(',')[0] - -            if 'ip=' in line: -                ip = line.split('ip=')[1] -                if ip: -                    outp['ip'] = ip.split(',')[0] - -            if 'mtime=' in line: -                mtime = line.split('mtime=')[1] -                if mtime: -                    outp['time'] = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(int(mtime.split(',')[0], base=10))) +            props = {} +            # ddclient cache rows have properties in 'key=value' format separated by comma +            # we pick up the ones we are interested in +            for kvraw in line.split(' ')[0].split(','): +                k, v = kvraw.split('=') +                if k in columns.keys(): +                    props[k] = v -            if 'status=' in line: -                status = line.split('status=')[1] -                if status: -                    outp['status'] = status.split(',')[0] +            # Convert mtime to human readable format +            if 'mtime' in props: +                props['mtime'] = time.strftime( +                    "%Y-%m-%d %H:%M:%S", time.localtime(int(props['mtime'], base=10))) -            data['hosts'].append(outp) +            data.append(props) -    tmpl = jinja2.Template(OUT_TMPL_SRC) -    print(tmpl.render(data)) +    print(_get_formatted_host_records(data))  def update_ddns(): diff --git a/src/op_mode/openvpn.py b/src/op_mode/openvpn.py index 37fdbcbeb..d9ae965c5 100755 --- a/src/op_mode/openvpn.py +++ b/src/op_mode/openvpn.py @@ -16,6 +16,7 @@  #  # +import json  import os  import sys  import typing @@ -25,6 +26,7 @@ import vyos.opmode  from vyos.util import bytes_to_human  from vyos.util import commit_in_progress  from vyos.util import call +from vyos.util import rc_cmd  from vyos.config import Config  ArgMode = typing.Literal['client', 'server', 'site_to_site'] @@ -63,7 +65,7 @@ def _get_interface_status(mode: str, interface: str) -> dict:      }      if not os.path.exists(status_file): -        raise vyos.opmode.DataUnavailable('No information for interface {interface}') +        return data      with open(status_file, 'r') as f:          lines = f.readlines() @@ -142,6 +144,25 @@ def _get_interface_status(mode: str, interface: str) -> dict:      return data + +def _get_interface_state(iface): +    rc, out = rc_cmd(f'ip --json link show dev {iface}') +    try: +        data = json.loads(out) +    except: +        return 'DOWN' +    return data[0].get('operstate', 'DOWN') + + +def _get_interface_description(iface): +    rc, out = rc_cmd(f'ip --json link show dev {iface}') +    try: +        data = json.loads(out) +    except: +        return '' +    return data[0].get('ifalias', '') + +  def _get_raw_data(mode: str) -> list:      data: list = []      conf = Config() @@ -154,6 +175,8 @@ def _get_raw_data(mode: str) -> list:                    conf_dict[x]['mode'].replace('-', '_') == mode]      for intf in interfaces:          d = _get_interface_status(mode, intf) +        d['state'] = _get_interface_state(intf) +        d['description'] = _get_interface_description(intf)          d['local_host'] = conf_dict[intf].get('local-host', '')          d['local_port'] = conf_dict[intf].get('local-port', '')          if conf.exists(f'interfaces openvpn {intf} server client'): | 
