Skip to content

Commit 139d110

Browse files
authored
Merge pull request #3959 from gizmoguy/conntrack
Add support for using openvswitch conntrack in faucet ACLs
2 parents 90fc115 + 160b031 commit 139d110

File tree

6 files changed

+365
-3
lines changed

6 files changed

+365
-3
lines changed

docs/configuration.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,10 @@ Actions is a dictionary of actions to apply upon match.
959959
- dictionary or list
960960
- None
961961
- Used to apply more specific output actions for an ACL
962+
* - ct
963+
- dictionary
964+
- None
965+
- Used to apply connection tracking to the specified flow.
962966

963967
The output action contains a dictionary with the following elements:
964968

faucet/acl.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class ACL(Conf):
8787
'output': (dict, list),
8888
'allow': int,
8989
'force_port_vlan': int,
90+
'ct': dict,
9091
}
9192
output_actions_types = {
9293
'tunnel': dict,
@@ -99,6 +100,24 @@ class ACL(Conf):
99100
'vlan_vid': int,
100101
'vlan_vids': list,
101102
}
103+
ct_action_types = {
104+
'flags': int,
105+
'alg': int,
106+
'table': int,
107+
'zone': int,
108+
'zone_src': int,
109+
'clear': bool,
110+
'nat': dict,
111+
}
112+
ct_action_nat_types = {
113+
'flags': int,
114+
'range_ipv4_min': str,
115+
'range_ipv4_max': str,
116+
'range_ipv6_min': str,
117+
'range_ipv6_max': str,
118+
'range_proto_min': int,
119+
'range_proto_max': int
120+
}
102121
tunnel_types = {
103122
'type': (str, None),
104123
'tunnel_id': (str, int, None),
@@ -186,6 +205,23 @@ def check_config(self):
186205
# Old format
187206
self._check_conf_types(
188207
action_conf, self.output_actions_types)
208+
elif action_name == 'ct':
209+
self._check_conf_types(action_conf, self.ct_action_types)
210+
# if clear set, make sure nothing else is
211+
if 'clear' in action_conf and action_conf['clear']:
212+
test_config_condition(
213+
len(action_conf) != 1,
214+
"no other parameters can be set when 'clear' set on "
215+
"conntrack ACL")
216+
else:
217+
test_config_condition(
218+
'table' not in action_conf,
219+
"required parameter 'table' not set for conntrack ACL")
220+
test_config_condition(
221+
'zone' not in action_conf,
222+
"required parameter 'zone' not set for conntrack ACL")
223+
if 'nat' in action_conf:
224+
self._check_conf_types(action_conf['nat'], self.ct_action_nat_types)
189225

190226
def build(self, meters, vid, port_num):
191227
"""Check that ACL can be built from config."""

faucet/valve_acl.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,38 @@ def rewrite_vlan(acl_table, output_dict):
105105
return vlan_actions
106106

107107

108+
def build_ct_actions(acl_table, ct_dict):
109+
"""Build conntrack action from ACL rule"""
110+
111+
if 'clear' in ct_dict and ct_dict['clear']:
112+
return valve_of.ct_clear()
113+
114+
ct_actions = []
115+
116+
if 'nat' in ct_dict:
117+
nat_args = {
118+
'flags': ct_dict['nat'].get('flags', 0),
119+
'range_ipv4_min': ct_dict['nat'].get('range_ipv4_min', ''),
120+
'range_ipv4_max': ct_dict['nat'].get('range_ipv4_max', ''),
121+
'range_ipv6_min': ct_dict['nat'].get('range_ipv6_min', ''),
122+
'range_ipv6_max': ct_dict['nat'].get('range_ipv6_max', ''),
123+
'range_proto_min': ct_dict['nat'].get('range_proto_min', None),
124+
'range_proto_max': ct_dict['nat'].get('range_proto_max', None),
125+
}
126+
ct_actions.append(valve_of.ct_nat(**nat_args))
127+
128+
ct_args = {
129+
'flags': ct_dict.get('flags', 0),
130+
'actions': ct_actions,
131+
'alg': ct_dict.get('alg', 0),
132+
'recirc_table': ct_dict.get('table'),
133+
'zone_ofs_nbits': ct_dict.get('zone'),
134+
'zone_src': ct_dict.get('zone_src', None),
135+
}
136+
137+
return valve_of.ct(**ct_args)
138+
139+
108140
def build_output_actions(acl_table, output_dict, tunnel_rules=None, source_id=None):
109141
"""Implement actions to alter packet/output."""
110142
if isinstance(output_dict, (list, tuple)):
@@ -195,6 +227,9 @@ def build_acl_entry( # pylint: disable=too-many-arguments,too-many-branches,too
195227
# if port specified, output packet now and exit pipeline.
196228
if not allow and output_port is not None:
197229
continue
230+
if 'ct' in attrib_value:
231+
ct_action = build_ct_actions(acl_table, attrib_value['ct'])
232+
acl_act.append(ct_action)
198233

199234
if allow:
200235
acl_inst.extend(allow_inst)

faucet/valve_of.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,10 @@ def is_set_field(action):
384384
return isinstance(action, parser.OFPActionSetField)
385385

386386

387+
def is_ct(action):
388+
return isinstance(action, parser.NXActionCT)
389+
390+
387391
def apply_meter(meter_id):
388392
"""Return instruction to apply a meter."""
389393
return parser.OFPInstructionMeter(meter_id, ofp.OFPIT_METER)
@@ -513,6 +517,39 @@ def pop_vlan():
513517
return parser.OFPActionPopVlan()
514518

515519

520+
def ct(**kwds): # pylint: disable=invalid-name
521+
"""Return connection tracker action.
522+
523+
Args:
524+
kwds (dict): exactly one connection tracker action.
525+
Returns:
526+
ryu.ofproto.nx_actions.NXActionCT: connection tracker action.
527+
"""
528+
return parser.NXActionCT(**kwds) # pylint: disable=no-member
529+
530+
531+
def ct_clear():
532+
"""Return clear connection tracker state action.
533+
534+
Args:
535+
kwds (dict): exactly one clear connection tracker state action.
536+
Returns:
537+
ryu.ofproto.nx_actions.NXActionCTClear: clear connection tracker state action.
538+
"""
539+
return parser.NXActionCTClear() # pylint: disable=no-member
540+
541+
542+
def ct_nat(**kwds):
543+
"""Return network address translation connection tracker action.
544+
545+
Args:
546+
kwds (dict): exactly one network address translation connection tracker action.
547+
Returns:
548+
ryu.ofproto.nx_actions.NXActionNAT: network address translation connection tracker action.
549+
"""
550+
return parser.NXActionNAT(**kwds) # pylint: disable=no-member
551+
552+
516553
@functools.lru_cache(maxsize=1024)
517554
def output_port(port_num, max_len=0):
518555
"""Return OpenFlow action to output to a port.
@@ -662,8 +699,8 @@ def valve_match_vid(value):
662699
return to_match_vid(value, ofp.OFPVID_PRESENT)
663700

664701

665-
# See 7.2.3.7 Flow Match Fields (OF 1.3.5)
666702
MATCH_FIELDS = {
703+
# See 7.2.3.7 Flow Match Fields (OF 1.3.5)
667704
'in_port': OFCtlUtil(ofp).ofp_port_from_user,
668705
'in_phy_port': str_to_int,
669706
'metadata': to_match_masked_int,
@@ -703,7 +740,13 @@ def valve_match_vid(value):
703740
'mpls_bos': str_to_int,
704741
'pbb_isid': to_match_masked_int,
705742
'tunnel_id': to_match_masked_int,
706-
'ipv6_exthdr': to_match_masked_int
743+
'ipv6_exthdr': to_match_masked_int,
744+
745+
# Nicira extensions, see ovs-fields(7)
746+
'ct_state': to_match_masked_int,
747+
'ct_zone': str_to_int,
748+
'ct_mark': to_match_masked_int,
749+
'ct_label': to_match_masked_int
707750
}
708751

709752

faucet/valve_table.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,12 @@ def _trim_inst(self, inst):
157157
new_inst = []
158158
for instruction in inst:
159159
if instruction.type == valve_of.ofp.OFPIT_APPLY_ACTIONS:
160+
recirc_present = any((
161+
True for action in instruction.actions
162+
if valve_of.is_ct(action) and hasattr(action, 'recirc_table')
163+
))
160164
# If no goto present, this is the last set of actions that can take place
161-
if not goto_present:
165+
if not goto_present and not recirc_present:
162166
instruction.actions = self._trim_actions(instruction.actions)
163167
# Always drop an apply actions instruction with no actions.
164168
if not instruction.actions:

0 commit comments

Comments
 (0)