From cfcb766cc3a7155ece5e7e2539dfbd25099283a5 Mon Sep 17 00:00:00 2001 From: rupesh-k <53595165+rupesh-k@users.noreply.github.com> Date: Thu, 2 Jul 2020 00:56:31 +0530 Subject: [PATCH] Add support for port mirroring CLIs (#936) * Add support for port mirroring CLIs Signed-off-by: Rupesh Kumar --- acl_loader/main.py | 29 +++-- config/main.py | 231 ++++++++++++++++++++++++++++++++++++--- doc/Command-Reference.md | 76 +++++++++++-- 3 files changed, 301 insertions(+), 35 deletions(-) diff --git a/acl_loader/main.py b/acl_loader/main.py index 7b7e480f6b..c719100fd9 100644 --- a/acl_loader/main.py +++ b/acl_loader/main.py @@ -718,21 +718,30 @@ def show_session(self, session_name): :param session_name: Optional. Mirror session name. Filter sessions by specified name. :return: """ - header = ("Name", "Status", "SRC IP", "DST IP", "GRE", "DSCP", "TTL", "Queue", "Policer", "Monitor Port") + erspan_header = ("Name", "Status", "SRC IP", "DST IP", "GRE", "DSCP", "TTL", "Queue", + "Policer", "Monitor Port", "SRC Port", "Direction") + span_header = ("Name", "Status", "DST Port", "SRC Port", "Direction", "Queue", "Policer") - data = [] + erspan_data = [] + span_data = [] for key, val in self.get_sessions_db_info().iteritems(): if session_name and key != session_name: continue - # For multi-mpu platform status and monitor port will be dict() - # of 'asic-x':value - data.append([key, val["status"], val["src_ip"], val["dst_ip"], - val.get("gre_type", ""), val.get("dscp", ""), - val.get("ttl", ""), val.get("queue", ""), val.get("policer", ""), - val.get("monitor_port", "")]) - - print(tabulate.tabulate(data, headers=header, tablefmt="simple", missingval="")) + if val.get("type") == "SPAN": + span_data.append([key, val.get("status", ""), val.get("dst_port", ""), + val.get("src_port", ""), val.get("direction", "").lower(), + val.get("queue", ""), val.get("policer", "")]) + else: + erspan_data.append([key, val.get("status", ""), val.get("src_ip", ""), + val.get("dst_ip", ""), val.get("gre_type", ""), val.get("dscp", ""), + val.get("ttl", ""), val.get("queue", ""), val.get("policer", ""), + val.get("monitor_port", ""), val.get("src_port", ""), val.get("direction", "").lower()]) + + print("ERSPAN Sessions") + print(tabulate.tabulate(erspan_data, headers=erspan_header, tablefmt="simple", missingval="")) + print("\nSPAN Sessions") + print(tabulate.tabulate(span_data, headers=span_header, tablefmt="simple", missingval="")) def show_policer(self, policer_name): """ diff --git a/config/main.py b/config/main.py index 9786ba9e4d..bd37962005 100755 --- a/config/main.py +++ b/config/main.py @@ -604,6 +604,99 @@ def is_ipaddress(val): return False return True +def interface_is_in_vlan(vlan_member_table, interface_name): + """ Check if an interface is in a vlan """ + for _,intf in vlan_member_table.keys(): + if intf == interface_name: + return True + + return False + +def interface_is_in_portchannel(portchannel_member_table, interface_name): + """ Check if an interface is part of portchannel """ + for _,intf in portchannel_member_table.keys(): + if intf == interface_name: + return True + + return False + +def interface_is_router_port(interface_table, interface_name): + """ Check if an interface has router config """ + for intf in interface_table.keys(): + if (interface_name == intf[0]): + return True + + return False + +def interface_is_mirror_dst_port(config_db, interface_name): + """ Check if port is already configured as mirror destination port """ + mirror_table = config_db.get_table('MIRROR_SESSION') + for _,v in mirror_table.items(): + if 'dst_port' in v and v['dst_port'] == interface_name: + return True + + return False + +def interface_has_mirror_config(mirror_table, interface_name): + """ Check if port is already configured with mirror config """ + for _,v in mirror_table.items(): + if 'src_port' in v and v['src_port'] == interface_name: + return True + if 'dst_port' in v and v['dst_port'] == interface_name: + return True + + return False + +def validate_mirror_session_config(config_db, session_name, dst_port, src_port, direction): + """ Check if SPAN mirror-session config is valid """ + if len(config_db.get_entry('MIRROR_SESSION', session_name)) != 0: + click.echo("Error: {} already exists".format(session_name)) + return False + + vlan_member_table = config_db.get_table('VLAN_MEMBER') + mirror_table = config_db.get_table('MIRROR_SESSION') + portchannel_member_table = config_db.get_table('PORTCHANNEL_MEMBER') + interface_table = config_db.get_table('INTERFACE') + + if dst_port: + if not interface_name_is_valid(dst_port): + click.echo("Error: Destination Interface {} is invalid".format(dst_port)) + return False + + if interface_is_in_vlan(vlan_member_table, dst_port): + click.echo("Error: Destination Interface {} has vlan config".format(dst_port)) + return False + + if interface_has_mirror_config(mirror_table, dst_port): + click.echo("Error: Destination Interface {} already has mirror config".format(dst_port)) + return False + + if interface_is_in_portchannel(portchannel_member_table, dst_port): + click.echo("Error: Destination Interface {} has portchannel config".format(dst_port)) + return False + + if interface_is_router_port(interface_table, dst_port): + click.echo("Error: Destination Interface {} is a L3 interface".format(dst_port)) + return False + + if src_port: + for port in src_port.split(","): + if not interface_name_is_valid(port): + click.echo("Error: Source Interface {} is invalid".format(port)) + return False + if dst_port and dst_port == port: + click.echo("Error: Destination Interface cant be same as Source Interface") + return False + if interface_has_mirror_config(mirror_table, port): + click.echo("Error: Source Interface {} already has mirror config".format(port)) + return False + + if direction: + if direction not in ['rx', 'tx', 'both']: + click.echo("Error: Direction {} is invalid".format(direction)) + return False + + return True # This is our main entrypoint - the main 'config' command @click.group(cls=AbbreviationGroup, context_settings=CONTEXT_SETTINGS) @@ -1030,6 +1123,8 @@ def portchannel_member(ctx): def add_portchannel_member(ctx, portchannel_name, port_name): """Add member to port channel""" db = ctx.obj['db'] + if interface_is_mirror_dst_port(db, port_name): + ctx.fail("{} is configured as mirror destination port".format(port_name)) db.set_entry('PORTCHANNEL_MEMBER', (portchannel_name, port_name), {'NULL': 'NULL'}) @@ -1051,7 +1146,11 @@ def del_portchannel_member(ctx, portchannel_name, port_name): def mirror_session(): pass -@mirror_session.command() +# +# 'add' subgroup ('config mirror_session add ...') +# + +@mirror_session.command('add') @click.argument('session_name', metavar='', required=True) @click.argument('src_ip', metavar='', required=True) @click.argument('dst_ip', metavar='', required=True) @@ -1061,25 +1160,70 @@ def mirror_session(): @click.argument('queue', metavar='[queue]', required=False) @click.option('--policer') def add(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer): - """ - Add mirror session - """ + """ Add ERSPAN mirror session.(Legacy support) """ + add_erspan(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer) + +@mirror_session.group(cls=AbbreviationGroup, name='erspan') +@click.pass_context +def erspan(ctx): + """ ERSPAN mirror_session """ + pass + + +# +# 'add' subcommand +# + +@erspan.command('add') +@click.argument('session_name', metavar='', required=True) +@click.argument('src_ip', metavar='', required=True) +@click.argument('dst_ip', metavar='', required=True) +@click.argument('dscp', metavar='', required=True) +@click.argument('ttl', metavar='', required=True) +@click.argument('gre_type', metavar='[gre_type]', required=False) +@click.argument('queue', metavar='[queue]', required=False) +@click.argument('src_port', metavar='[src_port]', required=False) +@click.argument('direction', metavar='[direction]', required=False) +@click.option('--policer') +def add(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer, src_port, direction): + """ Add ERSPAN mirror session """ + add_erspan(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer, src_port, direction) + +def gather_session_info(session_info, policer, queue, src_port, direction): + if policer: + session_info['policer'] = policer + + if queue: + session_info['queue'] = queue + + if src_port: + if get_interface_naming_mode() == "alias": + src_port_list = [] + for port in src_port.split(","): + src_port_list.append(interface_alias_to_name(port)) + src_port=",".join(src_port_list) + + session_info['src_port'] = src_port + if not direction: + direction = "both" + session_info['direction'] = direction.upper() + + return session_info + +def add_erspan(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer, src_port=None, direction=None): session_info = { + "type" : "ERSPAN", "src_ip": src_ip, "dst_ip": dst_ip, "dscp": dscp, "ttl": ttl } - if policer is not None: - session_info['policer'] = policer - - if gre_type is not None: + if gre_type: session_info['gre_type'] = gre_type - if queue is not None: - session_info['queue'] = queue - + session_info = gather_session_info(session_info, policer, queue, src_port, direction) + """ For multi-npu platforms we need to program all front asic namespaces """ @@ -1087,20 +1231,73 @@ def add(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer): if not namespaces['front_ns']: config_db = ConfigDBConnector() config_db.connect() + if validate_mirror_session_config(config_db, session_name, None, src_port, direction) is False: + return config_db.set_entry("MIRROR_SESSION", session_name, session_info) else: per_npu_configdb = {} for front_asic_namespaces in namespaces['front_ns']: per_npu_configdb[front_asic_namespaces] = ConfigDBConnector(use_unix_socket_path=True, namespace=front_asic_namespaces) per_npu_configdb[front_asic_namespaces].connect() + if validate_mirror_session_config(per_npu_configdb[front_asic_namespaces], session_name, None, src_port, direction) is False: + return per_npu_configdb[front_asic_namespaces].set_entry("MIRROR_SESSION", session_name, session_info) -@mirror_session.command() +@mirror_session.group(cls=AbbreviationGroup, name='span') +@click.pass_context +def span(ctx): + """ SPAN mirror session """ + pass + +@span.command('add') @click.argument('session_name', metavar='', required=True) -def remove(session_name): +@click.argument('dst_port', metavar='', required=True) +@click.argument('src_port', metavar='[src_port]', required=False) +@click.argument('direction', metavar='[direction]', required=False) +@click.argument('queue', metavar='[queue]', required=False) +@click.option('--policer') +def add(session_name, dst_port, src_port, direction, queue, policer): + """ Add SPAN mirror session """ + add_span(session_name, dst_port, src_port, direction, queue, policer) + +def add_span(session_name, dst_port, src_port, direction, queue, policer): + if get_interface_naming_mode() == "alias": + dst_port = interface_alias_to_name(dst_port) + if dst_port is None: + click.echo("Error: Destination Interface {} is invalid".format(dst_port)) + return + + session_info = { + "type" : "SPAN", + "dst_port": dst_port, + } + + session_info = gather_session_info(session_info, policer, queue, src_port, direction) + """ - Delete mirror session + For multi-npu platforms we need to program all front asic namespaces """ + namespaces = sonic_device_util.get_all_namespaces() + if not namespaces['front_ns']: + config_db = ConfigDBConnector() + config_db.connect() + if validate_mirror_session_config(config_db, session_name, dst_port, src_port, direction) is False: + return + config_db.set_entry("MIRROR_SESSION", session_name, session_info) + else: + per_npu_configdb = {} + for front_asic_namespaces in namespaces['front_ns']: + per_npu_configdb[front_asic_namespaces] = ConfigDBConnector(use_unix_socket_path=True, namespace=front_asic_namespaces) + per_npu_configdb[front_asic_namespaces].connect() + if validate_mirror_session_config(per_npu_configdb[front_asic_namespaces], session_name, dst_port, src_port, direction) is False: + return + per_npu_configdb[front_asic_namespaces].set_entry("MIRROR_SESSION", session_name, session_info) + + +@mirror_session.command() +@click.argument('session_name', metavar='', required=True) +def remove(session_name): + """ Delete mirror session """ """ For multi-npu platforms we need to program all front asic namespaces @@ -1116,6 +1313,7 @@ def remove(session_name): per_npu_configdb[front_asic_namespaces] = ConfigDBConnector(use_unix_socket_path=True, namespace=front_asic_namespaces) per_npu_configdb[front_asic_namespaces].connect() per_npu_configdb[front_asic_namespaces].set_entry("MIRROR_SESSION", session_name, None) + # # 'pfcwd' group ('config pfcwd ...') # @@ -1390,6 +1588,9 @@ def add_vlan_member(ctx, vid, interface_name, untagged): if len(vlan) == 0: ctx.fail("{} doesn't exist".format(vlan_name)) + if interface_is_mirror_dst_port(db, interface_name): + ctx.fail("{} is configured as mirror destination port".format(interface_name)) + members = vlan.get('members', []) if interface_name in members: if get_interface_naming_mode() == "alias": @@ -1404,7 +1605,7 @@ def add_vlan_member(ctx, vid, interface_name, untagged): for entry in interface_table: if (interface_name == entry[0]): ctx.fail("{} is a L3 interface!".format(interface_name)) - + members.append(interface_name) vlan['members'] = members db.set_entry('VLAN', vlan_name, vlan) diff --git a/doc/Command-Reference.md b/doc/Command-Reference.md index 55dfaff599..8971445418 100644 --- a/doc/Command-Reference.md +++ b/doc/Command-Reference.md @@ -3879,7 +3879,6 @@ This command deletes the SNMP Trap server IP address to which SNMP agent is expe Go Back To [Beginning of the document](#) or [Beginning of this section](#management-vrf) - ## Mirroring ### Mirroring Show commands @@ -3895,10 +3894,16 @@ This command displays all the mirror sessions that are configured. - Example: ``` - admin@sonic:~$ show mirror session - Name Status SRC IP DST IP GRE DSCP TTL Queue - --------- -------- --------- -------- ----- ------ ----- ------- + admin@sonic:~$ show mirror_session + ERSPAN Sessions + Name Status SRC IP DST IP GRE DSCP TTL Queue Policer Monitor Port SRC Port Direction + ------ -------- -------- -------- ----- ------ ----- ------- --------- -------------- ---------- ----------- everflow0 active 10.1.0.32 10.0.0.7 + + SPAN Sessions + Name Status DST Port SRC Port Direction + ------ -------- ---------- ------------- ----------- + port0 active Ethernet0 PortChannel10 rx ``` ### Mirroring Config commands @@ -3906,7 +3911,12 @@ This command displays all the mirror sessions that are configured. **config mirror_session** This command is used to add or remove mirroring sessions. Mirror session is identified by "session_name". -While adding a new session, users need to configure the following fields that are used while forwarding the mirrored packets. +This command supports configuring both SPAN/ERSPAN sessions. +In SPAN user can configure mirroring of list of source ports/LAG to destination port in ingress/egress/both directions. +In ERSPAN user can configure mirroring of list of source ports/LAG to a destination IP. +Both SPAN/ERSPAN support ACL based mirroring and can be used in ACL configurations. + +While adding a new ERSPAN session, users need to configure the following fields that are used while forwarding the mirrored packets. 1) source IP address, 2) destination IP address, @@ -3914,19 +3924,65 @@ While adding a new session, users need to configure the following fields that ar 4) TTL value 5) optional - GRE Type in case if user wants to send the packet via GRE tunnel. GRE type could be anything; it could also be left as empty; by default, it is 0x8949 for Mellanox; and 0x88be for the rest of the chips. 6) optional - Queue in which packets shall be sent out of the device. Valid values 0 to 7 for most of the devices. Users need to know their device and the number of queues supported in that device. +7) optional - Policer which will be used to control the rate at which frames are mirrored. +8) optional - List of source ports which can have both Ethernet and LAG ports. +9) optional - Direction - Mirror session direction when configured along with Source port. (Supported rx/tx/both. default direction is both) - Usage: + ``` + config mirror_session erspan add [gre_type] [queue] [policer ] [source-port-list] [direction] + ``` + + The following command is also supported to be backward compatible. + This command will be deprecated in future releases. ``` config mirror_session add [gre_type] [queue] ``` - Example: ``` - admin@sonic:~$ sudo config mirror_session add mrr_abcd 1.2.3.4 20.21.22.23 8 100 0x6558 0 - admin@sonic:~$ show mirror_session - Name Status SRC IP DST IP GRE DSCP TTL Queue - --------- -------- ----------- ----------- ------ ------ ----- ------- - mrr_abcd inactive 1.2.3.4 20.21.22.23 0x6558 8 100 0 + root@T1-2:~# config mirror_session add mrr_legacy 1.2.3.4 20.21.22.23 8 100 0x6558 0 + root@T1-2:~# show mirror_session + Name Status SRC IP DST IP GRE DSCP TTL Queue Policer Monitor Port SRC Port Direction + --------- -------- -------- ----------- ------ ------ ----- ------- --------- -------------- ---------- ----------- + mrr_legacy inactive 1.2.3.4 20.21.22.23 0x6558 8 100 0 + + + root@T1-2:~# config mirror_session erspan add mrr_abcd 1.2.3.4 20.21.22.23 8 100 0x6558 0 + root@T1-2:~# show mirror_session + Name Status SRC IP DST IP GRE DSCP TTL Queue Policer Monitor Port SRC Port Direction + --------- -------- -------- ----------- ------ ------ ----- ------- --------- -------------- ---------- ----------- + mrr_abcd inactive 1.2.3.4 20.21.22.23 0x6558 8 100 0 + root@T1-2:~# + + root@T1-2:~# config mirror_session erspan add mrr_port 1.2.3.4 20.21.22.23 8 100 0x6558 0 Ethernet0 + root@T1-2:~# show mirror_session + Name Status SRC IP DST IP GRE DSCP TTL Queue Policer Monitor Port SRC Port Direction + --------- -------- -------- ----------- ------ ------ ----- ------- --------- -------------- ---------- ----------- + mrr_port inactive 1.2.3.4 20.21.22.23 0x6558 8 100 0 Ethernet0 both + root@T1-2:~# + ``` + +While adding a new SPAN session, users need to configure the following fields that are used while forwarding the mirrored packets. +1) destination port, +2) optional - List of source ports- List of source ports which can have both Ethernet and LAG ports. +3) optional - Direction - Mirror session direction when configured along with Source port. (Supported rx/tx/both. default direction is both) +4) optional - Queue in which packets shall be sent out of the device. Valid values 0 to 7 for most of the devices. Users need to know their device and the number of queues supported in that device. +5) optional - Policer which will be used to control the rate at which frames are mirrored. + +- Usage: + ``` + config mirror_session span add [source-port-list] [direction] [queue] [policer ] + ``` + +- Example: + ``` + root@T1-2:~# config mirror_session span add port0 Ethernet0 Ethernet4,PortChannel001,Ethernet8 + root@T1-2:~# show mirror_session + Name Status DST Port SRC Port Direction + ------ -------- ---------- --------------------------------- ----------- + port0 active Ethernet0 Ethernet4,PortChannel10,Ethernet8 both + root@T1-2:~# ``` Go Back To [Beginning of the document](#) or [Beginning of this section](#mirroring)