-
Notifications
You must be signed in to change notification settings - Fork 684
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
Changes from all commits
f66a8cc
c2dab24
47f4a93
cd23175
9588c94
d492005
8696423
8b332bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -570,6 +570,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): | ||
daall marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" 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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the port is a routed port, SPAN session is supported? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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)) | ||
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) | ||
|
@@ -996,6 +1089,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'}) | ||
|
||
|
@@ -1017,7 +1112,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) | ||
|
@@ -1027,46 +1126,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: | ||
daall marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return | ||
config_db.set_entry("MIRROR_SESSION", session_name, session_info) | ||
else: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
||
@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() | ||
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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
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 | ||
|
@@ -1082,6 +1279,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 ...') | ||
# | ||
|
@@ -1356,6 +1554,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": | ||
|
@@ -1370,7 +1571,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) | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.