Skip to content

Commit

Permalink
Add support for port mirroring CLIs (sonic-net#936)
Browse files Browse the repository at this point in the history
* Add support for port mirroring CLIs

Signed-off-by: Rupesh Kumar <rupesh-k.kumar@broadcom.com>
  • Loading branch information
rupesh-k authored Jul 1, 2020
1 parent fd52e93 commit cfcb766
Show file tree
Hide file tree
Showing 3 changed files with 301 additions and 35 deletions.
29 changes: 19 additions & 10 deletions acl_loader/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
231 changes: 216 additions & 15 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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'})

Expand All @@ -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='<session_name>', required=True)
@click.argument('src_ip', metavar='<src_ip>', required=True)
@click.argument('dst_ip', metavar='<dst_ip>', required=True)
Expand All @@ -1061,46 +1160,144 @@ 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='<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, 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
"""
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:
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='<session_name>', required=True)
def remove(session_name):
@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 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='<session_name>', required=True)
def remove(session_name):
""" Delete mirror session """

"""
For multi-npu platforms we need to program all front asic namespaces
Expand All @@ -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 ...')
#
Expand Down Expand Up @@ -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":
Expand All @@ -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)
Expand Down
Loading

0 comments on commit cfcb766

Please sign in to comment.