Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for port mirroring CLIs #936

Merged
merged 8 commits into from
Jul 1, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions acl_loader/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,21 +718,29 @@ 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":

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the need for policer and queue for SPAN sessions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above. Extending it to SPAN also.

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()])
daall marked this conversation as resolved.
Show resolved Hide resolved

print 'ERSPAN Sessions'
daall marked this conversation as resolved.
Show resolved Hide resolved
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):
"""
Expand Down
180 changes: 175 additions & 5 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,82 @@ def is_ipaddress(val):
return False
return True

#
# Check if an interface_name is in a vlan
#
daall marked this conversation as resolved.
Show resolved Hide resolved
def interface_is_in_vlan(vlan_member_table, interface_name):

for k,v in vlan_member_table:
daall marked this conversation as resolved.
Show resolved Hide resolved
daall marked this conversation as resolved.
Show resolved Hide resolved
if v == interface_name:
return True

return False


#
# Check if port is already configured as mirror destination port
#
def interface_is_mirror_dst_port(config_db, interface_name):
mirror_table = config_db.get_table('MIRROR_SESSION')
for k,v in mirror_table.items():
daall marked this conversation as resolved.
Show resolved Hide resolved
if 'dst_port' in v and v['dst_port'] == interface_name:
return True

return False
#
# Check if port is already configured with mirror config
#
def interface_has_mirror_config(mirror_table, interface_name):
for k,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

#
# Check if SPAN mirror-session config is valid.
#
daall marked this conversation as resolved.
Show resolved Hide resolved
def validate_mirror_session_config(config_db, session_name, dst_port, src_port, direction):
daall marked this conversation as resolved.
Show resolved Hide resolved
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')

if dst_port is not None:
if interface_name_is_valid(dst_port) is False:
click.echo("Error: Destination Interface {} is invalid".format(dst_port))
return False

if interface_is_in_vlan(vlan_member_table, dst_port):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the port is a routed port, SPAN session is supported?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. Not supported. Please check in latest patchset.

click.echo("Error: Destination Interface {} has vlan config".format(dst_port))
return False

if interface_has_mirror_config(mirror_table, dst_port):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I understand this correctly, can a single dst port cannot be a monitor (mirror-to) port
for multiple mirror sessions?
For example
case 1: mirror rx from Ethernet0 to Etherent4
case 2: mirror rx&tx from Ethernet2 to Etherent4
with the above check, case 2 will fail?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This is not supported. We allow only one mirror session per port, The session can have any number of ports.

click.echo("Error: Destination Interface {} already has mirror config".format(dst_port))
return False

if src_port is not None:
daall marked this conversation as resolved.
Show resolved Hide resolved
for port in src_port.split(","):
if interface_name_is_valid(port) is False:
click.echo("Error: Source Interface {} is invalid".format(port))
return False
if dst_port is not None 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 is not None:
if not any ( [direction == 'rx', direction == 'tx', direction == 'both'] ):
daall marked this conversation as resolved.
Show resolved Hide resolved
click.echo("Error: Direction {} is invalid".format(direction))
daall marked this conversation as resolved.
Show resolved Hide resolved
return False

return True

# This is our main entrypoint - the main 'config' command
@click.group(cls=AbbreviationGroup, context_settings=CONTEXT_SETTINGS)
Expand Down Expand Up @@ -1017,20 +1093,37 @@ def del_portchannel_member(ctx, portchannel_name, port_name):
def mirror_session():
pass

@mirror_session.command()
#
# 'add' subgroup ('config mirror_session add ...')
#

@mirror_session.group(cls=AbbreviationGroup, name='add')
@click.pass_context
def add(ctx):
"""Add mirror_session"""
pass

#
# 'add' subcommand
#

@add.command('erspan')
@click.argument('session_name', metavar='<session_name>', required=True)
@click.argument('src_ip', metavar='<src_ip>', required=True)
@click.argument('dst_ip', metavar='<dst_ip>', required=True)
@click.argument('dscp', metavar='<dscp>', required=True)
@click.argument('ttl', metavar='<ttl>', 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):
def erspan(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer, src_port, direction):
"""
Add mirror session
Add ERSPAN mirror session
"""
session_info = {
"type" : "ERSPAN",
"src_ip": src_ip,
"dst_ip": dst_ip,
"dscp": dscp,
Expand All @@ -1045,20 +1138,94 @@ def add(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer):

if queue is not None:
session_info['queue'] = queue


if src_port is not None:
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 direction is not None:
daall marked this conversation as resolved.
Show resolved Hide resolved
session_info['direction'] = direction.upper()

"""
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, None, src_port, direction) is False:
daall marked this conversation as resolved.
Show resolved Hide resolved
return
config_db.set_entry("MIRROR_SESSION", session_name, session_info)
else:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@abdosi could you take a quick look at the multi-NPU logic here and make sure it looks OK to you?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validation need to be updated so that src_port and dst_port are in same asic/namespace.
TO_DO item (not blocking for this PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. will take of this in next PR. How do I validate this ?

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)

@add.command('span')
@click.argument('session_name', metavar='<session_name>', required=True)
@click.argument('dst_port', metavar='<dst_port>', 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 span(session_name, dst_port, src_port, direction, queue, policer):
"""
Add port mirror session
"""
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
if src_port is not None:
src_port_list = []
for port in src_port.split(","):
src_port_list.append(interface_alias_to_name(port))
src_port=",".join(src_port_list)
daall marked this conversation as resolved.
Show resolved Hide resolved

session_info = {
"type" : "SPAN",
"dst_port": dst_port,
}

if src_port is not None:
session_info['src_port'] = src_port

if direction is not None:
session_info['direction'] = direction.upper()

if policer is not None:
session_info['policer'] = policer

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the need for policer and queue for SPAN sessions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is existing support in community. This is to police the mirrored traffic.


if queue is not None:
session_info['queue'] = queue

"""
For multi-npu platforms we need to program all front asic namespaces
"""
namespaces = sonic_device_util.get_all_namespaces()
abdosi marked this conversation as resolved.
Show resolved Hide resolved
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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validation need to be updated so that src_port and dst_port are in same asic/namespace.
TO_DO item (not blocking for this PR)

return
per_npu_configdb[front_asic_namespaces].set_entry("MIRROR_SESSION", session_name, session_info)

@mirror_session.command()
Expand Down Expand Up @@ -1356,6 +1523,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":
Expand All @@ -1370,7 +1540,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)
Expand Down
64 changes: 53 additions & 11 deletions doc/Command-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -3747,7 +3747,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
Expand All @@ -3763,39 +3762,82 @@ 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

**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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we have Everflow ACL table with everflow mirror session and Data ACL Table with port based mirror session ?


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,
3) DSCP (QoS) value with which mirrored packets are forwarded
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.

- Usage:
```
config mirror_session add <session_name> <src_ip> <dst_ip> <dscp> <ttl> [gre_type] [queue]
config mirror_session add erspan <session_name> <src_ip> <dst_ip> <dscp> <ttl> [gre_type] [queue] [policer <policer_name>] [source-port-list] [direction]
daall marked this conversation as resolved.
Show resolved Hide resolved
```

- 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 erspan 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 add erspan mrr_port 1.2.3.4 20.21.22.23 8 100 0x6558 0 Ethernet0 both
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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a note that atleast one port/LAG is required for local span?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can have local span session with only destination port, which can be used in ACL mirroring.
Direction is optional in this case. Based on ingress/egress ACL, the corresponding flow gets mirrored.

3) optional - Direction - Mirror session direction when configured along with Source port.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you specify the default direction as this is optional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When source port is specified, we can make direction default as both. Let me push that change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. taken care of this. Thanks.

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 add span <session_name> <dst_port> [source-port-list] [direction] [queue] [policer <policer_name>]
```

- Example:
```
root@T1-2:~# config mirror_session add span port0 Ethernet0 Ethernet4,PortChannel001,Ethernet8 both
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)

Expand Down