diff options
| -rw-r--r-- | data/templates/ids/fastnetmon.j2 | 3 | ||||
| -rw-r--r-- | data/templates/ids/fastnetmon_excluded_networks_list.j2 | 5 | ||||
| -rw-r--r-- | interface-definitions/service-ids-ddos-protection.xml.in | 18 | ||||
| -rw-r--r-- | op-mode-definitions/nat.xml.in | 2 | ||||
| -rw-r--r-- | op-mode-definitions/show-vrf.xml.in | 2 | ||||
| -rw-r--r-- | op-mode-definitions/vpn-ipsec.xml.in | 3 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_protocols_bgp.py | 26 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_service_ids.py | 12 | ||||
| -rwxr-xr-x | src/conf_mode/flow_accounting_conf.py | 9 | ||||
| -rwxr-xr-x | src/conf_mode/interfaces-vxlan.py | 5 | ||||
| -rwxr-xr-x | src/conf_mode/interfaces-wwan.py | 2 | ||||
| -rwxr-xr-x | src/conf_mode/protocols_bgp.py | 6 | ||||
| -rwxr-xr-x | src/conf_mode/service_ids_fastnetmon.py | 2 | ||||
| -rwxr-xr-x | src/etc/opennhrp/opennhrp-script.py | 2 | ||||
| -rw-r--r-- | src/etc/systemd/system/fastnetmon.service.d/override.conf | 2 | ||||
| -rwxr-xr-x | src/op_mode/nat.py | 29 | ||||
| -rwxr-xr-x | src/op_mode/show_vrf.py | 66 | ||||
| -rwxr-xr-x | src/op_mode/vpn_ipsec.py | 5 | ||||
| -rwxr-xr-x | src/op_mode/vrf.py | 20 | 
19 files changed, 140 insertions, 79 deletions
| diff --git a/data/templates/ids/fastnetmon.j2 b/data/templates/ids/fastnetmon.j2 index 005338836..b9f77a257 100644 --- a/data/templates/ids/fastnetmon.j2 +++ b/data/templates/ids/fastnetmon.j2 @@ -5,6 +5,9 @@ logging:local_syslog_logging = on  # list of all your networks in CIDR format  networks_list_path = /run/fastnetmon/networks_list +# list networks in CIDR format which will be not monitored for attacks +white_list_path = /run/fastnetmon/excluded_networks_list +  # Enable/Disable any actions in case of attack  enable_ban = on  enable_ban_ipv6 = on diff --git a/data/templates/ids/fastnetmon_excluded_networks_list.j2 b/data/templates/ids/fastnetmon_excluded_networks_list.j2 new file mode 100644 index 000000000..c88a1c527 --- /dev/null +++ b/data/templates/ids/fastnetmon_excluded_networks_list.j2 @@ -0,0 +1,5 @@ +{% if excluded_network is vyos_defined %} +{%     for net in excluded_network %} +{{ net }} +{%     endfor %} +{% endif %} diff --git a/interface-definitions/service-ids-ddos-protection.xml.in b/interface-definitions/service-ids-ddos-protection.xml.in index a176d6fff..86fc4dffa 100644 --- a/interface-definitions/service-ids-ddos-protection.xml.in +++ b/interface-definitions/service-ids-ddos-protection.xml.in @@ -43,6 +43,24 @@                    <multi/>                  </properties>                </leafNode> +              <leafNode name="excluded-network"> +                <properties> +                  <help>Specify IPv4 and IPv6 networks which are going to be excluded from protection</help> +                  <valueHelp> +                    <format>ipv4net</format> +                    <description>IPv4 prefix(es) to exclude</description> +                  </valueHelp> +                  <valueHelp> +                    <format>ipv6net</format> +                    <description>IPv6 prefix(es) to exclude</description> +                  </valueHelp> +                  <constraint> +                    <validator name="ipv4-prefix"/> +                    <validator name="ipv6-prefix"/> +                  </constraint> +                  <multi/> +                </properties> +              </leafNode>                <leafNode name="listen-interface">                  <properties>                    <help>Listen interface for mirroring traffic</help> diff --git a/op-mode-definitions/nat.xml.in b/op-mode-definitions/nat.xml.in index 84e999995..b0ec8989f 100644 --- a/op-mode-definitions/nat.xml.in +++ b/op-mode-definitions/nat.xml.in @@ -22,7 +22,7 @@                  <properties>                    <help>Show statistics for configured source NAT rules</help>                  </properties> -                <command>${vyos_op_scripts_dir}/show_nat_statistics.py --source</command> +                <command>${vyos_op_scripts_dir}/nat.py show_statistics --direction source</command>                </node>                <node name="translations">                  <properties> diff --git a/op-mode-definitions/show-vrf.xml.in b/op-mode-definitions/show-vrf.xml.in index d8d5284d7..0e0370445 100644 --- a/op-mode-definitions/show-vrf.xml.in +++ b/op-mode-definitions/show-vrf.xml.in @@ -15,7 +15,7 @@              <path>vrf name</path>            </completionHelp>          </properties> -        <command>${vyos_op_scripts_dir}/show_vrf.py -e "$3"</command> +        <command>${vyos_op_scripts_dir}/vrf.py show --name="$3"</command>          <children>            <leafNode name="processes">              <properties> diff --git a/op-mode-definitions/vpn-ipsec.xml.in b/op-mode-definitions/vpn-ipsec.xml.in index f1f43755b..a98cf8ff2 100644 --- a/op-mode-definitions/vpn-ipsec.xml.in +++ b/op-mode-definitions/vpn-ipsec.xml.in @@ -76,6 +76,9 @@                <tagNode name="peer">                  <properties>                    <help>Show debugging information for a peer</help> +                  <completionHelp> +                    <path>vpn ipsec site-to-site peer</path> +                  </completionHelp>                  </properties>                  <children>                    <tagNode name="tunnel"> diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 9c0c93779..009dbc803 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -921,5 +921,31 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):          self.assertIn(f' neighbor {peer_group} peer-group', frrconfig)          self.assertIn(f' neighbor {peer_group} remote-as {remote_asn}', frrconfig) +    def test_bgp_15_local_as_ebgp(self): +        # https://phabricator.vyos.net/T4560 +        # local-as allowed only for ebgp peers + +        neighbor = '192.0.2.99' +        remote_asn = '500' +        local_asn = '400' + +        self.cli_set(base_path + ['local-as', ASN]) +        self.cli_set(base_path + ['neighbor', neighbor, 'remote-as', ASN]) +        self.cli_set(base_path + ['neighbor', neighbor, 'local-as', local_asn]) + +        # check validate() - local-as allowed only for ebgp peers +        with self.assertRaises(ConfigSessionError): +            self.cli_commit() + +        self.cli_set(base_path + ['neighbor', neighbor, 'remote-as', remote_asn]) + +        self.cli_commit() + +        frrconfig = self.getFRRconfig(f'router bgp {ASN}') +        self.assertIn(f'router bgp {ASN}', frrconfig) +        self.assertIn(f' neighbor {neighbor} remote-as {remote_asn}', frrconfig) +        self.assertIn(f' neighbor {neighbor} local-as {local_asn}', frrconfig) + +  if __name__ == '__main__':      unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_ids.py b/smoketest/scripts/cli/test_service_ids.py index 8720362ba..d471eeaed 100755 --- a/smoketest/scripts/cli/test_service_ids.py +++ b/smoketest/scripts/cli/test_service_ids.py @@ -26,6 +26,7 @@ from vyos.util import read_file  PROCESS_NAME = 'fastnetmon'  FASTNETMON_CONF = '/run/fastnetmon/fastnetmon.conf'  NETWORKS_CONF = '/run/fastnetmon/networks_list' +EXCLUDED_NETWORKS_CONF = '/run/fastnetmon/excluded_networks_list'  base_path = ['service', 'ids', 'ddos-protection']  class TestServiceIDS(VyOSUnitTestSHIM.TestCase): @@ -50,6 +51,7 @@ class TestServiceIDS(VyOSUnitTestSHIM.TestCase):      def test_fastnetmon(self):          networks = ['10.0.0.0/24', '10.5.5.0/24', '2001:db8:10::/64', '2001:db8:20::/64'] +        excluded_networks = ['10.0.0.1/32', '2001:db8:10::1/128']          interfaces = ['eth0', 'eth1']          fps = '3500'          mbps = '300' @@ -62,6 +64,12 @@ class TestServiceIDS(VyOSUnitTestSHIM.TestCase):          for tmp in networks:              self.cli_set(base_path + ['network', tmp]) +        # optional excluded-network! +        with self.assertRaises(ConfigSessionError): +            self.cli_commit() +        for tmp in excluded_networks: +            self.cli_set(base_path + ['excluded-network', tmp]) +          # Required interface(s)!          with self.assertRaises(ConfigSessionError):              self.cli_commit() @@ -100,5 +108,9 @@ class TestServiceIDS(VyOSUnitTestSHIM.TestCase):          for tmp in networks:              self.assertIn(f'{tmp}', network_config) +        excluded_network_config = read_file(EXCLUDED_NETWORKS_CONF) +        for tmp in excluded_networks: +            self.assertIn(f'{tmp}', excluded_network_config) +  if __name__ == '__main__':      unittest.main(verbosity=2) diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py index 7750c1247..7e16235c1 100755 --- a/src/conf_mode/flow_accounting_conf.py +++ b/src/conf_mode/flow_accounting_conf.py @@ -192,6 +192,11 @@ def verify(flow_config):                      raise ConfigError("All sFlow servers must use the same IP protocol")              else:                  sflow_collector_ipver = ip_address(server).version +	 +        # check if vrf is defined for Sflow +        sflow_vrf = None +        if 'vrf' in flow_config: +            sflow_vrf = flow_config['vrf']          # check agent-id for sFlow: we should avoid mixing IPv4 agent-id with IPv6 collectors and vice-versa          for server in flow_config['sflow']['server']: @@ -203,12 +208,12 @@ def verify(flow_config):          if 'agent_address' in flow_config['sflow']:              tmp = flow_config['sflow']['agent_address'] -            if not is_addr_assigned(tmp): +            if not is_addr_assigned(tmp, sflow_vrf):                  raise ConfigError(f'Configured "sflow agent-address {tmp}" does not exist in the system!')          # Check if configured netflow source-address exist in the system          if 'source_address' in flow_config['sflow']: -            if not is_addr_assigned(flow_config['sflow']['source_address']): +            if not is_addr_assigned(flow_config['sflow']['source_address'], sflow_vrf):                  tmp = flow_config['sflow']['source_address']                  raise ConfigError(f'Configured "sflow source-address {tmp}" does not exist on the system!') diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index bf0f6840d..af2d0588d 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -118,6 +118,11 @@ def verify(vxlan):              # in use.              vxlan_overhead += 20 +        # If source_address is not used - check IPv6 'remote' list +        elif 'remote' in vxlan: +            if any(is_ipv6(a) for a in vxlan['remote']): +                vxlan_overhead += 20 +          lower_mtu = Interface(vxlan['source_interface']).get_mtu()          if lower_mtu < (int(vxlan['mtu']) + vxlan_overhead):              raise ConfigError(f'Underlaying device MTU is to small ({lower_mtu} '\ diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index e275ace84..97b3a6396 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -76,7 +76,7 @@ def get_config(config=None):      # We need to know the amount of other WWAN interfaces as ModemManager needs      # to be started or stopped.      conf.set_level(base) -    _, wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'), +    wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'),                                                         get_first_key=True,                                                         no_tag_node_value_mangle=True) diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 5aa643476..7d3687094 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -213,6 +213,12 @@ def verify(bgp):                      if 'source_interface' in peer_config['interface']:                          raise ConfigError(f'"source-interface" option not allowed for neighbor "{peer}"') +            # Local-AS allowed only for EBGP peers +            if 'local_as' in peer_config: +                remote_as = verify_remote_as(peer_config, bgp) +                if remote_as == bgp['local_as']: +                    raise ConfigError(f'local-as configured for "{peer}", allowed only for eBGP peers!') +              for afi in ['ipv4_unicast', 'ipv4_multicast', 'ipv4_labeled_unicast', 'ipv4_flowspec',                          'ipv6_unicast', 'ipv6_multicast', 'ipv6_labeled_unicast', 'ipv6_flowspec',                          'l2vpn_evpn']: diff --git a/src/conf_mode/service_ids_fastnetmon.py b/src/conf_mode/service_ids_fastnetmon.py index 615658c84..c58f8db9a 100755 --- a/src/conf_mode/service_ids_fastnetmon.py +++ b/src/conf_mode/service_ids_fastnetmon.py @@ -29,6 +29,7 @@ airbag.enable()  config_file = r'/run/fastnetmon/fastnetmon.conf'  networks_list = r'/run/fastnetmon/networks_list' +excluded_networks_list = r'/run/fastnetmon/excluded_networks_list'  def get_config(config=None):      if config: @@ -75,6 +76,7 @@ def generate(fastnetmon):      render(config_file, 'ids/fastnetmon.j2', fastnetmon)      render(networks_list, 'ids/fastnetmon_networks_list.j2', fastnetmon) +    render(excluded_networks_list, 'ids/fastnetmon_excluded_networks_list.j2', fastnetmon)      return None  def apply(fastnetmon): diff --git a/src/etc/opennhrp/opennhrp-script.py b/src/etc/opennhrp/opennhrp-script.py index 5a64dade8..8274e6564 100755 --- a/src/etc/opennhrp/opennhrp-script.py +++ b/src/etc/opennhrp/opennhrp-script.py @@ -62,7 +62,7 @@ def add_peer_route(nbma_src: str, nbma_dst: str, mtu: str) -> None:      for route_item in route_info_data:          route_dev = route_item.get('dev')          route_dst = route_item.get('dst') -        route_gateway = route_item.get('route_gateway') +        route_gateway = route_item.get('gateway')          # Prepare a command to add a route          route_add_cmd = 'sudo ip route add'          if route_dst: diff --git a/src/etc/systemd/system/fastnetmon.service.d/override.conf b/src/etc/systemd/system/fastnetmon.service.d/override.conf index 8f7f3774f..841666070 100644 --- a/src/etc/systemd/system/fastnetmon.service.d/override.conf +++ b/src/etc/systemd/system/fastnetmon.service.d/override.conf @@ -7,6 +7,6 @@ After=vyos-router.service  [Service]  Type=simple  WorkingDirectory=/run/fastnetmon -PIDFile=/run/fastnetmon/fastnetmon.pid +PIDFile=/run/fastnetmon.pid  ExecStart=  ExecStart=/usr/sbin/fastnetmon --configuration_file /run/fastnetmon/fastnetmon.conf diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py index f3f79f863..4b54ecf31 100755 --- a/src/op_mode/nat.py +++ b/src/op_mode/nat.py @@ -52,6 +52,9 @@ def _get_raw_data_rules(direction):  def _get_formatted_output_rules(data, direction): +    # Add default values before loop +    sport, dport, proto = 'any', 'any', 'any' +    saddr, daddr = '0.0.0.0/0', '0.0.0.0/0'      data_entries = []      for rule in data:          if 'comment' in rule['rule']: @@ -144,6 +147,24 @@ port {port}'''      return output +def _get_formatted_output_statistics(data, direction): +    data_entries = [] +    for rule in data: +        if 'comment' in rule['rule']: +            comment = rule.get('rule').get('comment') +            rule_number = comment.split('-')[-1] +            rule_number = rule_number.split(' ')[0] +        if 'expr' in rule['rule']: +            interface = rule.get('rule').get('expr')[0].get('match').get('right') \ +                if jmespath.search('rule.expr[*].match.left.meta', rule) else 'any' +            packets = jmespath.search('rule.expr[*].counter.packets | [0]', rule) +            _bytes = jmespath.search('rule.expr[*].counter.bytes | [0]', rule) +        data_entries.append([rule_number, packets, _bytes, interface]) +    headers = ["Rule", "Packets", "Bytes", "Interface"] +    output = tabulate(data_entries, headers, numalign="left") +    return output + +  def show_rules(raw: bool, direction: str):      nat_rules = _get_raw_data_rules(direction)      if raw: @@ -152,6 +173,14 @@ def show_rules(raw: bool, direction: str):          return _get_formatted_output_rules(nat_rules, direction) +def show_statistics(raw: bool, direction: str): +    nat_statistics = _get_raw_data_rules(direction) +    if raw: +        return nat_statistics +    else: +        return _get_formatted_output_statistics(nat_statistics, direction) + +  if __name__ == '__main__':      try:          res = vyos.opmode.run(sys.modules[__name__]) diff --git a/src/op_mode/show_vrf.py b/src/op_mode/show_vrf.py deleted file mode 100755 index 3c7a90205..000000000 --- a/src/op_mode/show_vrf.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program.  If not, see <http://www.gnu.org/licenses/>. - -import argparse -import jinja2 -from json import loads - -from vyos.util import cmd - -vrf_out_tmpl = """VRF name          state     mac address        flags                     interfaces ---------          -----     -----------        -----                     ---------- -{%- for v in vrf %} -{{"%-16s"|format(v.ifname)}}  {{ "%-8s"|format(v.operstate | lower())}}  {{"%-17s"|format(v.address | lower())}}  {{ v.flags|join(',')|lower()}}  {{v.members|join(',')|lower()}} -{%- endfor %} - -""" - -def list_vrfs(): -    command = 'ip -j -br link show type vrf' -    answer = loads(cmd(command)) -    return [_ for _ in answer if _] - -def list_vrf_members(vrf): -    command = f'ip -j -br link show master {vrf}' -    answer = loads(cmd(command)) -    return [_ for _ in answer if _] - -parser = argparse.ArgumentParser() -group = parser.add_mutually_exclusive_group() -group.add_argument("-e", "--extensive", action="store_true", -                   help="provide detailed vrf informatio") -parser.add_argument('interface', metavar='I', type=str, nargs='?', -                    help='interface to display') - -args = parser.parse_args() - -if args.extensive: -    data = { 'vrf': [] } -    for vrf in list_vrfs(): -        name = vrf['ifname'] -        if args.interface and name != args.interface: -            continue - -        vrf['members'] = [] -        for member in list_vrf_members(name): -            vrf['members'].append(member['ifname']) -        data['vrf'].append(vrf) - -    tmpl = jinja2.Template(vrf_out_tmpl) -    print(tmpl.render(data)) - -else: -    print(" ".join([vrf['ifname'] for vrf in list_vrfs()])) diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py index 8955e5a59..68dc5bc45 100755 --- a/src/op_mode/vpn_ipsec.py +++ b/src/op_mode/vpn_ipsec.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -87,6 +87,7 @@ def reset_profile(profile, tunnel):      print('Profile reset result: ' + ('success' if result == 0 else 'failed'))  def debug_peer(peer, tunnel): +    peer = peer.replace(':', '-')      if not peer or peer == "all":          debug_commands = [              "sudo ipsec statusall", @@ -109,7 +110,7 @@ def debug_peer(peer, tunnel):      if not tunnel or tunnel == 'all':          tunnel = '' -    conn = get_peer_connections(peer, tunnel) +    conns = get_peer_connections(peer, tunnel, return_all = (tunnel == '' or tunnel == 'all'))      if not conns:          print('Peer not found, aborting') diff --git a/src/op_mode/vrf.py b/src/op_mode/vrf.py index 63d9b5ee5..f86516786 100755 --- a/src/op_mode/vrf.py +++ b/src/op_mode/vrf.py @@ -16,6 +16,7 @@  import json  import sys +import typing  from tabulate import tabulate  from vyos.util import cmd @@ -23,12 +24,23 @@ from vyos.util import cmd  import vyos.opmode -def _get_raw_data(): +def _get_raw_data(name=None):      """ -    :return: list +    If vrf name is not set - get all VRFs +    If vrf name is set - get only this name data +    If vrf name set and not found - return []      """      output = cmd('sudo ip --json --brief link show type vrf')      data = json.loads(output) +    if not data: +        return [] +    if name: +        is_vrf_exists = True if [vrf for vrf in data if vrf.get('ifname') == name] else False +        if is_vrf_exists: +            output = cmd(f'sudo ip --json --brief link show dev {name}') +            data = json.loads(output) +            return data +        return []      return data @@ -62,8 +74,8 @@ def _get_formatted_output(raw_data):      return output -def show(raw: bool): -    vrf_data = _get_raw_data() +def show(raw: bool, name: typing.Optional[str]): +    vrf_data = _get_raw_data(name=name)      if raw:          return vrf_data      else: | 
