Skip to content

Commit cfcb766

Browse files
authored
Add support for port mirroring CLIs (sonic-net#936)
* Add support for port mirroring CLIs Signed-off-by: Rupesh Kumar <[email protected]>
1 parent fd52e93 commit cfcb766

File tree

3 files changed

+301
-35
lines changed

3 files changed

+301
-35
lines changed

acl_loader/main.py

+19-10
Original file line numberDiff line numberDiff line change
@@ -718,21 +718,30 @@ def show_session(self, session_name):
718718
:param session_name: Optional. Mirror session name. Filter sessions by specified name.
719719
:return:
720720
"""
721-
header = ("Name", "Status", "SRC IP", "DST IP", "GRE", "DSCP", "TTL", "Queue", "Policer", "Monitor Port")
721+
erspan_header = ("Name", "Status", "SRC IP", "DST IP", "GRE", "DSCP", "TTL", "Queue",
722+
"Policer", "Monitor Port", "SRC Port", "Direction")
723+
span_header = ("Name", "Status", "DST Port", "SRC Port", "Direction", "Queue", "Policer")
722724

723-
data = []
725+
erspan_data = []
726+
span_data = []
724727
for key, val in self.get_sessions_db_info().iteritems():
725728
if session_name and key != session_name:
726729
continue
727-
# For multi-mpu platform status and monitor port will be dict()
728-
# of 'asic-x':value
729-
data.append([key, val["status"], val["src_ip"], val["dst_ip"],
730-
val.get("gre_type", ""), val.get("dscp", ""),
731-
val.get("ttl", ""), val.get("queue", ""), val.get("policer", ""),
732-
val.get("monitor_port", "")])
733-
734-
print(tabulate.tabulate(data, headers=header, tablefmt="simple", missingval=""))
735730

731+
if val.get("type") == "SPAN":
732+
span_data.append([key, val.get("status", ""), val.get("dst_port", ""),
733+
val.get("src_port", ""), val.get("direction", "").lower(),
734+
val.get("queue", ""), val.get("policer", "")])
735+
else:
736+
erspan_data.append([key, val.get("status", ""), val.get("src_ip", ""),
737+
val.get("dst_ip", ""), val.get("gre_type", ""), val.get("dscp", ""),
738+
val.get("ttl", ""), val.get("queue", ""), val.get("policer", ""),
739+
val.get("monitor_port", ""), val.get("src_port", ""), val.get("direction", "").lower()])
740+
741+
print("ERSPAN Sessions")
742+
print(tabulate.tabulate(erspan_data, headers=erspan_header, tablefmt="simple", missingval=""))
743+
print("\nSPAN Sessions")
744+
print(tabulate.tabulate(span_data, headers=span_header, tablefmt="simple", missingval=""))
736745

737746
def show_policer(self, policer_name):
738747
"""

config/main.py

+216-15
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,99 @@ def is_ipaddress(val):
604604
return False
605605
return True
606606

607+
def interface_is_in_vlan(vlan_member_table, interface_name):
608+
""" Check if an interface is in a vlan """
609+
for _,intf in vlan_member_table.keys():
610+
if intf == interface_name:
611+
return True
612+
613+
return False
614+
615+
def interface_is_in_portchannel(portchannel_member_table, interface_name):
616+
""" Check if an interface is part of portchannel """
617+
for _,intf in portchannel_member_table.keys():
618+
if intf == interface_name:
619+
return True
620+
621+
return False
622+
623+
def interface_is_router_port(interface_table, interface_name):
624+
""" Check if an interface has router config """
625+
for intf in interface_table.keys():
626+
if (interface_name == intf[0]):
627+
return True
628+
629+
return False
630+
631+
def interface_is_mirror_dst_port(config_db, interface_name):
632+
""" Check if port is already configured as mirror destination port """
633+
mirror_table = config_db.get_table('MIRROR_SESSION')
634+
for _,v in mirror_table.items():
635+
if 'dst_port' in v and v['dst_port'] == interface_name:
636+
return True
637+
638+
return False
639+
640+
def interface_has_mirror_config(mirror_table, interface_name):
641+
""" Check if port is already configured with mirror config """
642+
for _,v in mirror_table.items():
643+
if 'src_port' in v and v['src_port'] == interface_name:
644+
return True
645+
if 'dst_port' in v and v['dst_port'] == interface_name:
646+
return True
647+
648+
return False
649+
650+
def validate_mirror_session_config(config_db, session_name, dst_port, src_port, direction):
651+
""" Check if SPAN mirror-session config is valid """
652+
if len(config_db.get_entry('MIRROR_SESSION', session_name)) != 0:
653+
click.echo("Error: {} already exists".format(session_name))
654+
return False
655+
656+
vlan_member_table = config_db.get_table('VLAN_MEMBER')
657+
mirror_table = config_db.get_table('MIRROR_SESSION')
658+
portchannel_member_table = config_db.get_table('PORTCHANNEL_MEMBER')
659+
interface_table = config_db.get_table('INTERFACE')
660+
661+
if dst_port:
662+
if not interface_name_is_valid(dst_port):
663+
click.echo("Error: Destination Interface {} is invalid".format(dst_port))
664+
return False
665+
666+
if interface_is_in_vlan(vlan_member_table, dst_port):
667+
click.echo("Error: Destination Interface {} has vlan config".format(dst_port))
668+
return False
669+
670+
if interface_has_mirror_config(mirror_table, dst_port):
671+
click.echo("Error: Destination Interface {} already has mirror config".format(dst_port))
672+
return False
673+
674+
if interface_is_in_portchannel(portchannel_member_table, dst_port):
675+
click.echo("Error: Destination Interface {} has portchannel config".format(dst_port))
676+
return False
677+
678+
if interface_is_router_port(interface_table, dst_port):
679+
click.echo("Error: Destination Interface {} is a L3 interface".format(dst_port))
680+
return False
681+
682+
if src_port:
683+
for port in src_port.split(","):
684+
if not interface_name_is_valid(port):
685+
click.echo("Error: Source Interface {} is invalid".format(port))
686+
return False
687+
if dst_port and dst_port == port:
688+
click.echo("Error: Destination Interface cant be same as Source Interface")
689+
return False
690+
if interface_has_mirror_config(mirror_table, port):
691+
click.echo("Error: Source Interface {} already has mirror config".format(port))
692+
return False
693+
694+
if direction:
695+
if direction not in ['rx', 'tx', 'both']:
696+
click.echo("Error: Direction {} is invalid".format(direction))
697+
return False
698+
699+
return True
607700

608701
# This is our main entrypoint - the main 'config' command
609702
@click.group(cls=AbbreviationGroup, context_settings=CONTEXT_SETTINGS)
@@ -1030,6 +1123,8 @@ def portchannel_member(ctx):
10301123
def add_portchannel_member(ctx, portchannel_name, port_name):
10311124
"""Add member to port channel"""
10321125
db = ctx.obj['db']
1126+
if interface_is_mirror_dst_port(db, port_name):
1127+
ctx.fail("{} is configured as mirror destination port".format(port_name))
10331128
db.set_entry('PORTCHANNEL_MEMBER', (portchannel_name, port_name),
10341129
{'NULL': 'NULL'})
10351130

@@ -1051,7 +1146,11 @@ def del_portchannel_member(ctx, portchannel_name, port_name):
10511146
def mirror_session():
10521147
pass
10531148

1054-
@mirror_session.command()
1149+
#
1150+
# 'add' subgroup ('config mirror_session add ...')
1151+
#
1152+
1153+
@mirror_session.command('add')
10551154
@click.argument('session_name', metavar='<session_name>', required=True)
10561155
@click.argument('src_ip', metavar='<src_ip>', required=True)
10571156
@click.argument('dst_ip', metavar='<dst_ip>', required=True)
@@ -1061,46 +1160,144 @@ def mirror_session():
10611160
@click.argument('queue', metavar='[queue]', required=False)
10621161
@click.option('--policer')
10631162
def add(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer):
1064-
"""
1065-
Add mirror session
1066-
"""
1163+
""" Add ERSPAN mirror session.(Legacy support) """
1164+
add_erspan(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer)
1165+
1166+
@mirror_session.group(cls=AbbreviationGroup, name='erspan')
1167+
@click.pass_context
1168+
def erspan(ctx):
1169+
""" ERSPAN mirror_session """
1170+
pass
1171+
1172+
1173+
#
1174+
# 'add' subcommand
1175+
#
1176+
1177+
@erspan.command('add')
1178+
@click.argument('session_name', metavar='<session_name>', required=True)
1179+
@click.argument('src_ip', metavar='<src_ip>', required=True)
1180+
@click.argument('dst_ip', metavar='<dst_ip>', required=True)
1181+
@click.argument('dscp', metavar='<dscp>', required=True)
1182+
@click.argument('ttl', metavar='<ttl>', required=True)
1183+
@click.argument('gre_type', metavar='[gre_type]', required=False)
1184+
@click.argument('queue', metavar='[queue]', required=False)
1185+
@click.argument('src_port', metavar='[src_port]', required=False)
1186+
@click.argument('direction', metavar='[direction]', required=False)
1187+
@click.option('--policer')
1188+
def add(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer, src_port, direction):
1189+
""" Add ERSPAN mirror session """
1190+
add_erspan(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer, src_port, direction)
1191+
1192+
def gather_session_info(session_info, policer, queue, src_port, direction):
1193+
if policer:
1194+
session_info['policer'] = policer
1195+
1196+
if queue:
1197+
session_info['queue'] = queue
1198+
1199+
if src_port:
1200+
if get_interface_naming_mode() == "alias":
1201+
src_port_list = []
1202+
for port in src_port.split(","):
1203+
src_port_list.append(interface_alias_to_name(port))
1204+
src_port=",".join(src_port_list)
1205+
1206+
session_info['src_port'] = src_port
1207+
if not direction:
1208+
direction = "both"
1209+
session_info['direction'] = direction.upper()
1210+
1211+
return session_info
1212+
1213+
def add_erspan(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer, src_port=None, direction=None):
10671214
session_info = {
1215+
"type" : "ERSPAN",
10681216
"src_ip": src_ip,
10691217
"dst_ip": dst_ip,
10701218
"dscp": dscp,
10711219
"ttl": ttl
10721220
}
10731221

1074-
if policer is not None:
1075-
session_info['policer'] = policer
1076-
1077-
if gre_type is not None:
1222+
if gre_type:
10781223
session_info['gre_type'] = gre_type
10791224

1080-
if queue is not None:
1081-
session_info['queue'] = queue
1082-
1225+
session_info = gather_session_info(session_info, policer, queue, src_port, direction)
1226+
10831227
"""
10841228
For multi-npu platforms we need to program all front asic namespaces
10851229
"""
10861230
namespaces = sonic_device_util.get_all_namespaces()
10871231
if not namespaces['front_ns']:
10881232
config_db = ConfigDBConnector()
10891233
config_db.connect()
1234+
if validate_mirror_session_config(config_db, session_name, None, src_port, direction) is False:
1235+
return
10901236
config_db.set_entry("MIRROR_SESSION", session_name, session_info)
10911237
else:
10921238
per_npu_configdb = {}
10931239
for front_asic_namespaces in namespaces['front_ns']:
10941240
per_npu_configdb[front_asic_namespaces] = ConfigDBConnector(use_unix_socket_path=True, namespace=front_asic_namespaces)
10951241
per_npu_configdb[front_asic_namespaces].connect()
1242+
if validate_mirror_session_config(per_npu_configdb[front_asic_namespaces], session_name, None, src_port, direction) is False:
1243+
return
10961244
per_npu_configdb[front_asic_namespaces].set_entry("MIRROR_SESSION", session_name, session_info)
10971245

1098-
@mirror_session.command()
1246+
@mirror_session.group(cls=AbbreviationGroup, name='span')
1247+
@click.pass_context
1248+
def span(ctx):
1249+
""" SPAN mirror session """
1250+
pass
1251+
1252+
@span.command('add')
10991253
@click.argument('session_name', metavar='<session_name>', required=True)
1100-
def remove(session_name):
1254+
@click.argument('dst_port', metavar='<dst_port>', required=True)
1255+
@click.argument('src_port', metavar='[src_port]', required=False)
1256+
@click.argument('direction', metavar='[direction]', required=False)
1257+
@click.argument('queue', metavar='[queue]', required=False)
1258+
@click.option('--policer')
1259+
def add(session_name, dst_port, src_port, direction, queue, policer):
1260+
""" Add SPAN mirror session """
1261+
add_span(session_name, dst_port, src_port, direction, queue, policer)
1262+
1263+
def add_span(session_name, dst_port, src_port, direction, queue, policer):
1264+
if get_interface_naming_mode() == "alias":
1265+
dst_port = interface_alias_to_name(dst_port)
1266+
if dst_port is None:
1267+
click.echo("Error: Destination Interface {} is invalid".format(dst_port))
1268+
return
1269+
1270+
session_info = {
1271+
"type" : "SPAN",
1272+
"dst_port": dst_port,
1273+
}
1274+
1275+
session_info = gather_session_info(session_info, policer, queue, src_port, direction)
1276+
11011277
"""
1102-
Delete mirror session
1278+
For multi-npu platforms we need to program all front asic namespaces
11031279
"""
1280+
namespaces = sonic_device_util.get_all_namespaces()
1281+
if not namespaces['front_ns']:
1282+
config_db = ConfigDBConnector()
1283+
config_db.connect()
1284+
if validate_mirror_session_config(config_db, session_name, dst_port, src_port, direction) is False:
1285+
return
1286+
config_db.set_entry("MIRROR_SESSION", session_name, session_info)
1287+
else:
1288+
per_npu_configdb = {}
1289+
for front_asic_namespaces in namespaces['front_ns']:
1290+
per_npu_configdb[front_asic_namespaces] = ConfigDBConnector(use_unix_socket_path=True, namespace=front_asic_namespaces)
1291+
per_npu_configdb[front_asic_namespaces].connect()
1292+
if validate_mirror_session_config(per_npu_configdb[front_asic_namespaces], session_name, dst_port, src_port, direction) is False:
1293+
return
1294+
per_npu_configdb[front_asic_namespaces].set_entry("MIRROR_SESSION", session_name, session_info)
1295+
1296+
1297+
@mirror_session.command()
1298+
@click.argument('session_name', metavar='<session_name>', required=True)
1299+
def remove(session_name):
1300+
""" Delete mirror session """
11041301

11051302
"""
11061303
For multi-npu platforms we need to program all front asic namespaces
@@ -1116,6 +1313,7 @@ def remove(session_name):
11161313
per_npu_configdb[front_asic_namespaces] = ConfigDBConnector(use_unix_socket_path=True, namespace=front_asic_namespaces)
11171314
per_npu_configdb[front_asic_namespaces].connect()
11181315
per_npu_configdb[front_asic_namespaces].set_entry("MIRROR_SESSION", session_name, None)
1316+
11191317
#
11201318
# 'pfcwd' group ('config pfcwd ...')
11211319
#
@@ -1390,6 +1588,9 @@ def add_vlan_member(ctx, vid, interface_name, untagged):
13901588

13911589
if len(vlan) == 0:
13921590
ctx.fail("{} doesn't exist".format(vlan_name))
1591+
if interface_is_mirror_dst_port(db, interface_name):
1592+
ctx.fail("{} is configured as mirror destination port".format(interface_name))
1593+
13931594
members = vlan.get('members', [])
13941595
if interface_name in members:
13951596
if get_interface_naming_mode() == "alias":
@@ -1404,7 +1605,7 @@ def add_vlan_member(ctx, vid, interface_name, untagged):
14041605
for entry in interface_table:
14051606
if (interface_name == entry[0]):
14061607
ctx.fail("{} is a L3 interface!".format(interface_name))
1407-
1608+
14081609
members.append(interface_name)
14091610
vlan['members'] = members
14101611
db.set_entry('VLAN', vlan_name, vlan)

0 commit comments

Comments
 (0)