Skip to content

Commit 5564d87

Browse files
stepanblyschaklguohan
authored andcommitted
[acl-loader] egress mirror action support and action ASIC support check (sonic-net#575)
- support egress mirror action - support action check via state DB
1 parent 3247828 commit 5564d87

8 files changed

Lines changed: 236 additions & 62 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@ build/
22
deb_dist/
33
dist/
44
*.egg-info/
5+
*.pyc
6+
.cache
7+
*.tar.gz

acl_loader/main.py

Lines changed: 136 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,31 @@ def deep_update(dst, src):
3838
return dst
3939

4040

41+
class AclAction:
42+
""" namespace for ACL action keys """
43+
44+
PACKET = "PACKET_ACTION"
45+
REDIRECT = "REDIRECT_ACTION"
46+
MIRROR = "MIRROR_ACTION"
47+
MIRROR_INGRESS = "MIRROR_INGRESS_ACTION"
48+
MIRROR_EGRESS = "MIRROR_EGRESS_ACTION"
49+
50+
51+
class PacketAction:
52+
""" namespace for ACL packet actions """
53+
54+
DROP = "DROP"
55+
FORWARD = "FORWARD"
56+
ACCEPT = "ACCEPT"
57+
58+
59+
class Stage:
60+
""" namespace for ACL stages """
61+
62+
INGRESS = "INGRESS"
63+
EGRESS = "EGRESS"
64+
65+
4166
class AclLoaderException(Exception):
4267
pass
4368

@@ -52,6 +77,9 @@ class AclLoader(object):
5277
STATE_MIRROR_SESSION_TABLE = "MIRROR_SESSION_TABLE"
5378
POLICER = "POLICER"
5479
SESSION_PREFIX = "everflow"
80+
SWITCH_CAPABILITY_TABLE = "SWITCH_CAPABILITY"
81+
ACL_ACTIONS_CAPABILITY_FIELD = "ACL_ACTIONS"
82+
ACL_ACTION_CAPABILITY_FIELD = "ACL_ACTION"
5583

5684
min_priority = 1
5785
max_priority = 10000
@@ -81,6 +109,7 @@ class AclLoader(object):
81109
def __init__(self):
82110
self.yang_acl = None
83111
self.requested_session = None
112+
self.mirror_stage = None
84113
self.current_table = None
85114
self.tables_db_info = {}
86115
self.rules_db_info = {}
@@ -179,6 +208,14 @@ def set_session_name(self, session_name):
179208

180209
self.requested_session = session_name
181210

211+
def set_mirror_stage(self, stage):
212+
"""
213+
Set mirror stage to be used in ACL mirror rule action
214+
:param session_name: stage 'ingress'/'egress'
215+
:return:
216+
"""
217+
self.mirror_stage = stage.upper()
218+
182219
def set_max_priority(self, priority):
183220
"""
184221
Set rules max priority
@@ -232,25 +269,72 @@ def convert_action(self, table_name, rule_idx, rule):
232269

233270
if rule.actions.config.forwarding_action == "ACCEPT":
234271
if self.is_table_control_plane(table_name):
235-
rule_props["PACKET_ACTION"] = "ACCEPT"
272+
rule_props[AclAction.PACKET] = PacketAction.ACCEPT
236273
elif self.is_table_mirror(table_name):
237274
session_name = self.get_session_name()
238275
if not session_name:
239276
raise AclLoaderException("Mirroring session does not exist")
240277

241-
rule_props["MIRROR_ACTION"] = session_name
278+
if self.mirror_stage == Stage.INGRESS:
279+
mirror_action = AclAction.MIRROR_INGRESS
280+
elif self.mirror_stage == Stage.EGRESS:
281+
mirror_action = AclAction.MIRROR_EGRESS
282+
else:
283+
raise AclLoaderException("Invalid mirror stage passed {}".format(self.mirror_stage))
284+
285+
rule_props[mirror_action] = session_name
242286
else:
243-
rule_props["PACKET_ACTION"] = "FORWARD"
287+
rule_props[AclAction.PACKET] = PacketAction.FORWARD
244288
elif rule.actions.config.forwarding_action == "DROP":
245-
rule_props["PACKET_ACTION"] = "DROP"
289+
rule_props[AclAction.PACKET] = PacketAction.DROP
246290
elif rule.actions.config.forwarding_action == "REJECT":
247-
rule_props["PACKET_ACTION"] = "DROP"
291+
rule_props[AclAction.PACKET] = PacketAction.DROP
248292
else:
249-
raise AclLoaderException("Unknown rule action %s in table %s, rule %d" % (
293+
raise AclLoaderException("Unknown rule action {} in table {}, rule {}".format(
294+
rule.actions.config.forwarding_action, table_name, rule_idx))
295+
296+
if not self.validate_actions(table_name, rule_props):
297+
raise AclLoaderException("Rule action {} is not supported in table {}, rule {}".format(
250298
rule.actions.config.forwarding_action, table_name, rule_idx))
251299

252300
return rule_props
253301

302+
def validate_actions(self, table_name, action_props):
303+
if self.is_table_control_plane(table_name):
304+
return True
305+
306+
action_count = len(action_props)
307+
308+
if table_name not in self.tables_db_info:
309+
raise AclLoaderException("Table {} does not exist".format(table_name))
310+
311+
stage = self.tables_db_info[table_name].get("stage", Stage.INGRESS)
312+
capability = self.statedb.get_all(self.statedb.STATE_DB, "{}|switch".format(self.SWITCH_CAPABILITY_TABLE))
313+
for action_key in dict(action_props):
314+
key = "{}|{}".format(self.ACL_ACTIONS_CAPABILITY_FIELD, stage.upper())
315+
if key not in capability:
316+
del action_props[action_key]
317+
continue
318+
319+
values = capability[key].split(",")
320+
if action_key.upper() not in values:
321+
del action_props[action_key]
322+
continue
323+
324+
if action_key == AclAction.PACKET:
325+
# Check if action_value is supported
326+
action_value = action_props[action_key]
327+
key = "{}|{}".format(self.ACL_ACTION_CAPABILITY_FIELD, action_key.upper())
328+
if key not in capability:
329+
del action_props[action_key]
330+
continue
331+
332+
if action_value not in capability[key]:
333+
del action_props[action_key]
334+
continue
335+
336+
return action_count == len(action_props)
337+
254338
def convert_l2(self, table_name, rule_idx, rule):
255339
rule_props = {}
256340

@@ -510,30 +594,32 @@ def show_table(self, table_name):
510594
:param table_name: Optional. ACL table name. Filter tables by specified name.
511595
:return:
512596
"""
513-
header = ("Name", "Type", "Binding", "Description")
597+
header = ("Name", "Type", "Binding", "Description", "Stage")
514598

515599
data = []
516600
for key, val in self.get_tables_db_info().iteritems():
517601
if table_name and key != table_name:
518602
continue
519603

604+
stage = val.get("stage", Stage.INGRESS).lower()
605+
520606
if val["type"] == AclLoader.ACL_TABLE_TYPE_CTRLPLANE:
521607
services = natsorted(val["services"])
522-
data.append([key, val["type"], services[0], val["policy_desc"]])
608+
data.append([key, val["type"], services[0], val["policy_desc"], stage])
523609

524610
if len(services) > 1:
525611
for service in services[1:]:
526-
data.append(["", "", service, ""])
612+
data.append(["", "", service, "", ""])
527613
else:
528614
if not val["ports"]:
529-
data.append([key, val["type"], "", val["policy_desc"]])
615+
data.append([key, val["type"], "", val["policy_desc"], stage])
530616
else:
531617
ports = natsorted(val["ports"])
532-
data.append([key, val["type"], ports[0], val["policy_desc"]])
618+
data.append([key, val["type"], ports[0], val["policy_desc"], stage])
533619

534620
if len(ports) > 1:
535621
for port in ports[1:]:
536-
data.append(["", "", port, ""])
622+
data.append(["", "", port, "", ""])
537623

538624
print(tabulate.tabulate(data, headers=header, tablefmt="simple", missingval=""))
539625

@@ -586,7 +672,33 @@ def show_rule(self, table_name, rule_id):
586672
"""
587673
header = ("Table", "Rule", "Priority", "Action", "Match")
588674

589-
ignore_list = ["PRIORITY", "PACKET_ACTION", "MIRROR_ACTION"]
675+
def pop_priority(val):
676+
priority = val.pop("PRIORITY")
677+
return priority
678+
679+
def pop_action(val):
680+
action = ""
681+
682+
for key in dict(val):
683+
key = key.upper()
684+
if key == AclAction.PACKET:
685+
action = val.pop(key)
686+
elif key == AclAction.REDIRECT:
687+
action = "REDIRECT: {}".format(val.pop(key))
688+
elif key in (AclAction.MIRROR, AclAction.MIRROR_INGRESS):
689+
action = "MIRROR INGRESS: {}".format(val.pop(key))
690+
elif key == AclAction.MIRROR_EGRESS:
691+
action = "MIRROR EGRESS: {}".format(val.pop(key))
692+
else:
693+
continue
694+
695+
return action
696+
697+
def pop_matches(val):
698+
matches = list(sorted(["%s: %s" % (k, val[k]) for k in val]))
699+
if len(matches) == 0:
700+
matches.append("N/A")
701+
return matches
590702

591703
raw_data = []
592704
for (tname, rid), val in self.get_rules_db_info().iteritems():
@@ -597,22 +709,9 @@ def show_rule(self, table_name, rule_id):
597709
if rule_id and rule_id != rid:
598710
continue
599711

600-
priority = val["PRIORITY"]
601-
602-
action = ""
603-
if "PACKET_ACTION" in val:
604-
action = val["PACKET_ACTION"]
605-
elif "MIRROR_ACTION" in val:
606-
action = "MIRROR: %s" % val["MIRROR_ACTION"]
607-
else:
608-
continue
609-
610-
matches = ["%s: %s" % (k, v) for k, v in val.iteritems() if k not in ignore_list]
611-
612-
matches.sort()
613-
614-
if len(matches) == 0:
615-
matches.append("N/A")
712+
priority = pop_priority(val)
713+
action = pop_action(val)
714+
matches = pop_matches(val)
616715

617716
rule_data = [[tname, rid, priority, action, matches[0]]]
618717
if len(matches) > 1:
@@ -718,9 +817,10 @@ def update(ctx):
718817
@click.argument('filename', type=click.Path(exists=True))
719818
@click.option('--table_name', type=click.STRING, required=False)
720819
@click.option('--session_name', type=click.STRING, required=False)
820+
@click.option('--mirror_stage', type=click.Choice(["ingress", "egress"]), default="ingress")
721821
@click.option('--max_priority', type=click.INT, required=False)
722822
@click.pass_context
723-
def full(ctx, filename, table_name, session_name, max_priority):
823+
def full(ctx, filename, table_name, session_name, mirror_stage, max_priority):
724824
"""
725825
Full update of ACL rules configuration.
726826
If a table_name is provided, the operation will be restricted in the specified table.
@@ -733,6 +833,8 @@ def full(ctx, filename, table_name, session_name, max_priority):
733833
if session_name:
734834
acl_loader.set_session_name(session_name)
735835

836+
acl_loader.set_mirror_stage(mirror_stage)
837+
736838
if max_priority:
737839
acl_loader.set_max_priority(max_priority)
738840

@@ -743,9 +845,10 @@ def full(ctx, filename, table_name, session_name, max_priority):
743845
@update.command()
744846
@click.argument('filename', type=click.Path(exists=True))
745847
@click.option('--session_name', type=click.STRING, required=False)
848+
@click.option('--mirror_stage', type=click.Choice(["ingress", "egress"]), default="ingress")
746849
@click.option('--max_priority', type=click.INT, required=False)
747850
@click.pass_context
748-
def incremental(ctx, filename, session_name, max_priority):
851+
def incremental(ctx, filename, session_name, mirror_stage, max_priority):
749852
"""
750853
Incremental update of ACL rule configuration.
751854
"""
@@ -754,6 +857,8 @@ def incremental(ctx, filename, session_name, max_priority):
754857
if session_name:
755858
acl_loader.set_session_name(session_name)
756859

860+
acl_loader.set_mirror_stage(mirror_stage)
861+
757862
if max_priority:
758863
acl_loader.set_max_priority(max_priority)
759864

config/main.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1407,7 +1407,8 @@ def get_acl_bound_ports():
14071407
@click.argument("table_type", metavar="<table_type>")
14081408
@click.option("-d", "--description")
14091409
@click.option("-p", "--ports")
1410-
def table(table_name, table_type, description, ports):
1410+
@click.option("-s", "--stage", type=click.Choice(["ingress", "egress"]), default="ingress")
1411+
def table(table_name, table_type, description, ports, stage):
14111412
"""
14121413
Add ACL table
14131414
"""
@@ -1426,6 +1427,8 @@ def table(table_name, table_type, description, ports):
14261427
else:
14271428
table_info["ports@"] = ",".join(get_acl_bound_ports())
14281429

1430+
table_info["stage"] = stage
1431+
14291432
config_db.set_entry("ACL_TABLE", table_name, table_info)
14301433

14311434
#

0 commit comments

Comments
 (0)