add REST-API for VLAN configuration of rest_firewall application. it implements handling each vlan groups separately.
Signed-off-by: WATANABE Fumitaka <[email protected]> --- ryu/app/rest_firewall.py | 291 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 229 insertions(+), 62 deletions(-) diff --git a/ryu/app/rest_firewall.py b/ryu/app/rest_firewall.py index e67ccd5..dfa3f36 100644 --- a/ryu/app/rest_firewall.py +++ b/ryu/app/rest_firewall.py @@ -41,7 +41,14 @@ from ryu.ofproto import ofproto_v1_2_parser LOG = logging.getLogger('ryu.app.firewall') -# REST API +#============================= +# REST API +#============================= +# +# Note: specify switch and vlan group, as follows. +# {switch-id} : 'all' or switchID +# {vlan-id} : 'all' or vlanID +# # ## about Firewall status # @@ -50,35 +57,76 @@ LOG = logging.getLogger('ryu.app.firewall') # # set enable the firewall switches # PUT /firewall/module/enable/{switch-id} -# {switch-id} is 'all' or switchID # # set disable the firewall switches # PUT /firewall/module/disable/{switch-id} -# {switch-id} is 'all' or switchID # # ## about Firewall rules # # get rules of the firewall switches +# * for no vlan # GET /firewall/rules/{switch-id} -# {switch-id} is 'all' or switchID +# +# * for specific vlan group +# GET /firewall/rules/{switch-id}/{vlan-id} +# # # set a rule to the firewall switches +# * for no vlan # POST /firewall/rules/{switch-id} -# {switch-id} is 'all' or switchID +# +# * for specific vlan group +# POST /firewall/rules/{switch-id}/{vlan-id} +# +# request body format: +# {"<field1>":"<value1>", "<field2>":"<value2>",...} +# +# <field> : <value> +# "priority": "0 to 65533" +# "in_port" : "<int>" +# "dl_src" : "<xx:xx:xx:xx:xx:xx>" +# "dl_dst" : "<xx:xx:xx:xx:xx:xx>" +# "dl_type" : "<ARP or IPv4>" +# "nw_src" : "<A.B.C.D/M>" +# "nw_dst" : "<A.B.C.D/M>" +# "nw_proto": "<TCP or UDP or ICMP>" +# "tp_src" : "<int>" +# "tp_dst" : "<int>" +# "actions" : "<ALLOW or DENY>" +# +# Note: specifying nw_src/nw_dst +# without specifying dl-type as "ARP" or "IPv4" +# will automatically set dl-type as "IPv4". +# +# Note: When "priority" has not been set up, +# "0" is set to "priority". +# +# Note: When "actions" has not been set up, +# "ALLOW" is set to "actions". +# # # delete a rule of the firewall switches from ruleID +# * for no vlan # DELETE /firewall/rules/{switch-id} -# {switch-id} is 'all' or switchID +# +# * for specific vlan group +# DELETE /firewall/rules/{switch-id}/{vlan-id} +# +# request body format: +# {"<field>":"<value>"} +# +# <field> : <value> +# "rule_id" : "<int>" or "all" # -OK = 0 -NG = -1 SWITCHID_PATTERN = dpid_lib.DPID_PATTERN + r'|all' +VLANID_PATTERN = r'[0-9]{1,4}|all' REST_ALL = 'all' REST_SWITCHID = 'switch_id' +REST_VLANID = 'vlan_id' REST_RULE_ID = 'rule_id' REST_STATUS = 'status' REST_STATUS_ENABLE = 'enable' @@ -92,6 +140,7 @@ REST_DST_MAC = 'dl_dst' REST_DL_TYPE = 'dl_type' REST_DL_TYPE_ARP = 'ARP' REST_DL_TYPE_IPV4 = 'IPv4' +REST_DL_VLAN = 'dl_vlan' REST_SRC_IP = 'nw_src' REST_DST_IP = 'nw_dst' REST_NW_PROTO = 'nw_proto' @@ -109,6 +158,11 @@ STATUS_FLOW_PRIORITY = ofproto_v1_2_parser.UINT16_MAX ARP_FLOW_PRIORITY = ofproto_v1_2_parser.UINT16_MAX - 1 ACL_FLOW_PRIORITY_MAX = ofproto_v1_2_parser.UINT16_MAX - 2 +VLANID_NONE = 0 +VLANID_MIN = 2 +VLANID_MAX = 4094 +COOKIE_SHIFT_VLANID = 32 + class RestFirewallAPI(app_manager.RyuApp): @@ -130,7 +184,8 @@ class RestFirewallAPI(app_manager.RyuApp): mapper = wsgi.mapper wsgi.registory['FirewallController'] = self.data path = '/firewall' - requirements = {'switchid': SWITCHID_PATTERN} + requirements = {'switchid': SWITCHID_PATTERN, + 'vlanid': VLANID_PATTERN} uri = path + '/module/status' mapper.connect('firewall', uri, @@ -149,6 +204,7 @@ class RestFirewallAPI(app_manager.RyuApp): conditions=dict(method=['PUT']), requirements=requirements) + # for no VLAN data uri = path + '/rules/{switchid}' mapper.connect('firewall', uri, controller=FirewallController, action='get_rules', @@ -164,6 +220,22 @@ class RestFirewallAPI(app_manager.RyuApp): controller=FirewallController, action='delete_rule', conditions=dict(method=['DELETE']), requirements=requirements) + # for VLAN data + uri += '/{vlanid}' + mapper.connect('firewall', uri, controller=FirewallController, + action='get_vlan_rules', + conditions=dict(method=['GET']), + requirements=requirements) + + mapper.connect('firewall', uri, controller=FirewallController, + action='set_vlan_rule', + conditions=dict(method=['POST']), + requirements=requirements) + + mapper.connect('firewall', uri, controller=FirewallController, + action='delete_vlan_rule', + conditions=dict(method=['DELETE']), + requirements=requirements) def stats_reply_handler(self, ev): msg = ev.msg @@ -199,19 +271,6 @@ class RestFirewallAPI(app_manager.RyuApp): self.stats_reply_handler(ev) -class FirewallOfs(object): - def __init__(self, dp): - super(FirewallOfs, self).__init__() - self.dp = dp - self.ctl = FirewallOfctl(dp) - self.cookie = 0 - - def get_cookie(self): - self.cookie += 1 - self.cookie &= ofproto_v1_2_parser.UINT64_MAX - return self.cookie - - class FirewallOfsList(dict): def __init__(self): super(FirewallOfsList, self).__init__() @@ -250,7 +309,7 @@ class FirewallController(ControllerBase): @staticmethod def regist_ofs(dp): try: - f_ofs = FirewallOfs(dp) + f_ofs = Firewall(dp) except OFPUnknownVersion, message: mes = 'dpid=%s : %s' % (dpid_lib.dpid_to_str(dp.id), message) LOG.info(mes) @@ -258,8 +317,8 @@ class FirewallController(ControllerBase): FirewallController._OFS_LIST.setdefault(dp.id, f_ofs) - f_ofs.ctl.set_disable_flow() - f_ofs.ctl.set_arp_flow() + f_ofs.set_disable_flow() + f_ofs.set_arp_flow() LOG.info('dpid=%s : Join as firewall switch.' % dpid_lib.dpid_to_str(dp.id)) @@ -271,7 +330,7 @@ class FirewallController(ControllerBase): dpid_lib.dpid_to_str(dp.id)) # GET /firewall/module/status - def get_status(self, req, **_kwargs): + def get_status(self, dummy, **_kwargs): try: dps = self._OFS_LIST.get_ofs(REST_ALL) except ValueError, message: @@ -279,14 +338,14 @@ class FirewallController(ControllerBase): msgs = {} for f_ofs in dps.values(): - status = f_ofs.ctl.get_status(self.waiters) + status = f_ofs.get_status(self.waiters) msgs.update(status) body = json.dumps(msgs) return Response(content_type='application/json', body=body) # POST /firewall/module/enable/{switchid} - def set_enable(self, req, switchid, **_kwargs): + def set_enable(self, dummy, switchid, **_kwargs): try: dps = self._OFS_LIST.get_ofs(switchid) except ValueError, message: @@ -294,14 +353,14 @@ class FirewallController(ControllerBase): msgs = {} for f_ofs in dps.values(): - msg = f_ofs.ctl.set_enable_flow() + msg = f_ofs.set_enable_flow() msgs.update(msg) body = json.dumps(msgs) return Response(content_type='application/json', body=body) # POST /firewall/module/disable/{switchid} - def set_disable(self, req, switchid, **_kwargs): + def set_disable(self, dummy, switchid, **_kwargs): try: dps = self._OFS_LIST.get_ofs(switchid) except ValueError, message: @@ -309,29 +368,52 @@ class FirewallController(ControllerBase): msgs = {} for f_ofs in dps.values(): - msg = f_ofs.ctl.set_disable_flow() + msg = f_ofs.set_disable_flow() msgs.update(msg) body = json.dumps(msgs) return Response(content_type='application/json', body=body) # GET /firewall/rules/{switchid} - def get_rules(self, req, switchid, **_kwargs): + def get_rules(self, dummy, switchid, **_kwargs): + return self._get_rules(switchid) + + # GET /firewall/rules/{switchid}/{vlanid} + def get_vlan_rules(self, dummy, switchid, vlanid, **_kwargs): + return self._get_rules(switchid, vlan_id=vlanid) + + # POST /firewall/rules/{switchid} + def set_rule(self, req, switchid, **_kwargs): + return self._set_rule(req, switchid) + + # POST /firewall/rules/{switchid}/{vlanid} + def set_vlan_rule(self, req, switchid, vlanid, **_kwargs): + return self._set_rule(req, switchid, vlan_id=vlanid) + + # DELETE /firewall/rules/{switchid} + def delete_rule(self, req, switchid, **_kwargs): + return self._delete_rule(req, switchid) + + # DELETE /firewall/rules/{switchid}/{vlanid} + def delete_vlan_rule(self, req, switchid, vlanid, **_kwargs): + return self._delete_rule(req, switchid, vlan_id=vlanid) + + def _get_rules(self, switch_id, vlan_id=VLANID_NONE): try: - dps = self._OFS_LIST.get_ofs(switchid) + dps = self._OFS_LIST.get_ofs(switch_id) + vid = self._conv_toint_vlanid(vlan_id) except ValueError, message: return Response(status=400, body=str(message)) msgs = {} for f_ofs in dps.values(): - rules = f_ofs.ctl.get_rules(self.waiters) + rules = f_ofs.get_rules(self.waiters, vid) msgs.update(rules) body = json.dumps(msgs) return Response(content_type='application/json', body=body) - # POST /firewall/rules/{switchid} - def set_rule(self, req, switchid, **_kwargs): + def _set_rule(self, req, switch_id, vlan_id=VLANID_NONE): try: rule = eval(req.body) except SyntaxError: @@ -339,14 +421,15 @@ class FirewallController(ControllerBase): return Response(status=400) try: - dps = self._OFS_LIST.get_ofs(switchid) + dps = self._OFS_LIST.get_ofs(switch_id) + vid = self._conv_toint_vlanid(vlan_id) except ValueError, message: return Response(status=400, body=str(message)) msgs = {} for f_ofs in dps.values(): try: - msg = f_ofs.ctl.set_rule(f_ofs.get_cookie(), rule) + msg = f_ofs.set_rule(rule, vid) msgs.update(msg) except ValueError, message: return Response(status=400, body=str(message)) @@ -354,8 +437,7 @@ class FirewallController(ControllerBase): body = json.dumps(msgs) return Response(content_type='application/json', body=body) - # DELETE /firewall/rules/{switchid} - def delete_rule(self, req, switchid, **_kwargs): + def _delete_rule(self, req, switch_id, vlan_id=VLANID_NONE): try: ruleid = eval(req.body) except SyntaxError: @@ -363,14 +445,15 @@ class FirewallController(ControllerBase): return Response(status=400) try: - dps = self._OFS_LIST.get_ofs(switchid) + dps = self._OFS_LIST.get_ofs(switch_id) + vid = self._conv_toint_vlanid(vlan_id) except ValueError, message: return Response(status=400, body=str(message)) msgs = {} for f_ofs in dps.values(): try: - msg = f_ofs.ctl.delete_rule(ruleid, self.waiters) + msg = f_ofs.delete_rule(ruleid, self.waiters, vid) msgs.update(msg) except ValueError, message: return Response(status=400, body=str(message)) @@ -378,14 +461,26 @@ class FirewallController(ControllerBase): body = json.dumps(msgs) return Response(content_type='application/json', body=body) + def _conv_toint_vlanid(self, vlan_id): + if vlan_id != REST_ALL: + vlan_id = int(vlan_id) + if (vlan_id != VLANID_NONE and + (vlan_id < VLANID_MIN or VLANID_MAX < vlan_id)): + msg = 'Invalid {vlan_id} value. Set [%d-%d]' % (VLANID_MIN, + VLANID_MAX) + raise ValueError(msg) + return vlan_id + -class FirewallOfctl(object): +class Firewall(object): _OFCTL = {ofproto_v1_0.OFP_VERSION: ofctl_v1_0, ofproto_v1_2.OFP_VERSION: ofctl_v1_2} def __init__(self, dp): - super(FirewallOfctl, self).__init__() + super(Firewall, self).__init__() + self.vlan_list = {} + self.vlan_list[VLANID_NONE] = 0 # for VLAN=None self.dp = dp version = dp.ofproto.OFP_VERSION @@ -394,6 +489,28 @@ class FirewallOfctl(object): self.ofctl = self._OFCTL[version] + def _update_vlan_list(self, vlan_list): + for vlan_id in self.vlan_list.keys(): + if vlan_id is not VLANID_NONE and vlan_id not in vlan_list: + del self.vlan_list[vlan_id] + + def _get_cookie(self, vlan_id): + if vlan_id == REST_ALL: + vlan_ids = self.vlan_list.keys() + else: + vlan_ids = [vlan_id] + + cookie_list = [] + for vlan_id in vlan_ids: + self.vlan_list.setdefault(vlan_id, 0) + self.vlan_list[vlan_id] += 1 + self.vlan_list[vlan_id] &= ofproto_v1_2_parser.UINT32_MAX + cookie = (vlan_id << COOKIE_SHIFT_VLANID) + \ + self.vlan_list[vlan_id] + cookie_list.append([cookie, vlan_id]) + + return cookie_list + def get_status(self, waiters): msgs = self.ofctl.get_flow_stats(self.dp, waiters) @@ -455,13 +572,26 @@ class FirewallOfctl(object): cmd = self.dp.ofproto.OFPFC_ADD self.ofctl.mod_flow_entry(self.dp, flow, cmd) - def set_rule(self, cookie, rest): + def set_rule(self, rest, vlan_id): + msgs = [] + cookie_list = self._get_cookie(vlan_id) + for cookie, vid in cookie_list: + msg = self._set_rule(cookie, rest, vid) + msgs.append(msg) + switch_id = '%s: %s' % (REST_SWITCHID, + dpid_lib.dpid_to_str(self.dp.id)) + return {switch_id: msgs} + + def _set_rule(self, cookie, rest, vlan_id): priority = int(rest.get(REST_PRIORITY, 0)) if priority < 0 or ACL_FLOW_PRIORITY_MAX < priority: raise ValueError('Invalid priority value. Set [0-%d]' % ACL_FLOW_PRIORITY_MAX) + if vlan_id: + rest[REST_DL_VLAN] = vlan_id + match = Match.to_openflow(rest) actions = Action.to_openflow(self.dp, rest) flow = self._to_of_flow(cookie=cookie, priority=priority, @@ -473,14 +603,17 @@ class FirewallOfctl(object): except: raise ValueError('Invalid rule parameter.') + rule_id = cookie & ofproto_v1_2_parser.UINT32_MAX msg = {'result': 'success', - 'details': 'Rule added. : rule_id=%d' % cookie} + 'details': 'Rule added. : rule_id=%d' % rule_id} - switch_id = '%s: %s' % (REST_SWITCHID, - dpid_lib.dpid_to_str(self.dp.id)) - return {switch_id: msg} + if vlan_id == VLANID_NONE: + return msg + else: + vlan_id = '%s: %d' % (REST_VLANID, vlan_id) + return {vlan_id: msg} - def get_rules(self, waiters): + def get_rules(self, waiters, vlan_id): rules = {} msgs = self.ofctl.get_flow_stats(self.dp, waiters) @@ -489,14 +622,25 @@ class FirewallOfctl(object): for flow_stat in flow_stats: if (flow_stat[REST_PRIORITY] != STATUS_FLOW_PRIORITY and flow_stat[REST_PRIORITY] != ARP_FLOW_PRIORITY): - rule = self._to_rest_rule(flow_stat) - rules.update(rule) + vid = flow_stat[REST_MATCH][REST_DL_VLAN] + if vlan_id == REST_ALL or vlan_id == vid: + rule = self._to_rest_rule(flow_stat) + rules.setdefault(vid, {}) + rules[vid].update(rule) + + get_data = [] + for vid, rule in rules.items(): + if vid == VLANID_NONE: + get_data.append(rule) + else: + vid = '%s: %d' % (REST_VLANID, vid) + get_data.append({vid: rule}) switch_id = '%s: %s' % (REST_SWITCHID, dpid_lib.dpid_to_str(self.dp.id)) - return {switch_id: rules} + return {switch_id: get_data} - def delete_rule(self, rest, waiters): + def delete_rule(self, rest, waiters, vlan_id): try: if rest[REST_RULE_ID] == REST_ALL: rule_id = REST_ALL @@ -505,6 +649,7 @@ class FirewallOfctl(object): except: raise ValueError('Invalid ruleID.') + vlan_list = [] delete_list = [] msgs = self.ofctl.get_flow_stats(self.dp, waiters) @@ -512,15 +657,21 @@ class FirewallOfctl(object): flow_stats = msgs[str(self.dp.id)] for flow_stat in flow_stats: cookie = flow_stat[REST_COOKIE] + ruleid = cookie & ofproto_v1_2_parser.UINT32_MAX priority = flow_stat[REST_PRIORITY] + dl_vlan = flow_stat[REST_MATCH][REST_DL_VLAN] if (priority != STATUS_FLOW_PRIORITY and priority != ARP_FLOW_PRIORITY): - if rule_id == REST_ALL or rule_id == cookie: + if ((rule_id == REST_ALL or rule_id == ruleid) and + (vlan_id == dl_vlan or vlan_id == REST_ALL)): match = Match.to_del_openflow(flow_stat[REST_MATCH]) delete_list.append([cookie, priority, match]) - if rule_id == cookie: - break + else: + if dl_vlan not in vlan_list: + vlan_list.append(dl_vlan) + + self._update_vlan_list(vlan_list) if len(delete_list) == 0: msg_details = 'Rule is not exist.' @@ -531,14 +682,29 @@ class FirewallOfctl(object): else: cmd = self.dp.ofproto.OFPFC_DELETE_STRICT actions = [] - msg_details = 'Rule deleted. : ruleID=' + delete_ids = {} for cookie, priority, match in delete_list: flow = self._to_of_flow(cookie=cookie, priority=priority, match=match, actions=actions) self.ofctl.mod_flow_entry(self.dp, flow, cmd) - msg_details += '%d,' % cookie - msg = {'result': 'success', - 'details': msg_details} + + vid = match.get(REST_DL_VLAN, VLANID_NONE) + rule_id = '%d' % (cookie & ofproto_v1_2_parser.UINT32_MAX) + + delete_ids.setdefault(vid, '') + if delete_ids[vid] != '': + delete_ids[vid] += ',' + delete_ids[vid] += rule_id + + msg = [] + for vid, rule_ids in delete_ids.items(): + del_msg = {'result': 'success', + 'details': 'Rule deleted. : ruleID=%s' % rule_ids} + if vid == VLANID_NONE: + msg.append(del_msg) + else: + vid = '%s: %d' % (REST_VLANID, vid) + msg.append({vid: del_msg}) switch_id = '%s: %s' % (REST_SWITCHID, dpid_lib.dpid_to_str(self.dp.id)) @@ -555,7 +721,8 @@ class FirewallOfctl(object): return flow def _to_rest_rule(self, flow): - rule_id = '%s: %d' % (REST_RULE_ID, flow[REST_COOKIE]) + ruleid = flow[REST_COOKIE] & ofproto_v1_2_parser.UINT32_MAX + rule_id = '%s: %d' % (REST_RULE_ID, ruleid) rule = {REST_PRIORITY: flow[REST_PRIORITY]} rule.update(Match.to_rest(flow)) -- 1.7.10.4 ------------------------------------------------------------------------------ Get 100% visibility into Java/.NET code with AppDynamics Lite It's a free troubleshooting tool designed for production Get down to code-level detail for bottlenecks, with <2% overhead. Download for free and get started troubleshooting in minutes. http://p.sf.net/sfu/appdyn_d2d_ap2 _______________________________________________ Ryu-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/ryu-devel
