1 # Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
20 from ryu.app.wsgi import ControllerBase
21 from ryu.app.wsgi import Response
22 from ryu.app.wsgi import WSGIApplication
23 from ryu.base import app_manager
24 from ryu.controller import ofp_event
25 from ryu.controller import dpset
26 from ryu.controller.handler import MAIN_DISPATCHER
27 from ryu.controller.handler import set_ev_cls
28 from ryu.exception import OFPUnknownVersion
29 from ryu.lib import mac
30 from ryu.lib import dpid as dpid_lib
31 from ryu.lib import ofctl_v1_0
32 from ryu.lib import ofctl_v1_2
33 from ryu.lib import ofctl_v1_3
34 from ryu.lib.packet import packet
35 from ryu.ofproto import ether
36 from ryu.ofproto import inet
37 from ryu.ofproto import ofproto_v1_0
38 from ryu.ofproto import ofproto_v1_2
39 from ryu.ofproto import ofproto_v1_2_parser
40 from ryu.ofproto import ofproto_v1_3
41 from ryu.ofproto import ofproto_v1_3_parser
44 # =============================
46 # =============================
48 # Note: specify switch and vlan group, as follows.
49 # {switch-id} : 'all' or switchID
50 # {vlan-id} : 'all' or vlanID
54 # about Firewall status
56 # get status of all firewall switches
57 # GET /firewall/module/status
59 # set enable the firewall switches
60 # PUT /firewall/module/enable/{switch-id}
62 # set disable the firewall switches
63 # PUT /firewall/module/disable/{switch-id}
68 # get log status of all firewall switches
69 # GET /firewall/log/status
71 # set log enable the firewall switches
72 # PUT /firewall/log/enable/{switch-id}
74 # set log disable the firewall switches
75 # PUT /firewall/log/disable/{switch-id}
78 # about Firewall rules
80 # get rules of the firewall switches
82 # GET /firewall/rules/{switch-id}
84 # * for specific vlan group
85 # GET /firewall/rules/{switch-id}/{vlan-id}
88 # set a rule to the firewall switches
90 # POST /firewall/rules/{switch-id}
92 # * for specific vlan group
93 # POST /firewall/rules/{switch-id}/{vlan-id}
95 # request body format:
96 # {"<field1>":"<value1>", "<field2>":"<value2>",...}
99 # "priority": "0 to 65533"
100 # "in_port" : "<int>"
101 # "dl_src" : "<xx:xx:xx:xx:xx:xx>"
102 # "dl_dst" : "<xx:xx:xx:xx:xx:xx>"
103 # "dl_type" : "<ARP or IPv4 or IPv6>"
104 # "nw_src" : "<A.B.C.D/M>"
105 # "nw_dst" : "<A.B.C.D/M>"
106 # "ipv6_src": "<xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/M>"
107 # "ipv6_dst": "<xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/M>"
108 # "nw_proto": "<TCP or UDP or ICMP or ICMPv6>"
111 # "actions" : "<ALLOW or DENY>"
113 # Note: specifying nw_src/nw_dst
114 # without specifying dl-type as "ARP" or "IPv4"
115 # will automatically set dl-type as "IPv4".
117 # Note: specifying ipv6_src/ipv6_dst
118 # without specifying dl-type as "IPv6"
119 # will automatically set dl-type as "IPv6".
121 # Note: When "priority" has not been set up,
122 # "0" is set to "priority".
124 # Note: When "actions" has not been set up,
125 # "ALLOW" is set to "actions".
128 # delete a rule of the firewall switches from ruleID
130 # DELETE /firewall/rules/{switch-id}
132 # * for specific vlan group
133 # DELETE /firewall/rules/{switch-id}/{vlan-id}
135 # request body format:
136 # {"<field>":"<value>"}
139 # "rule_id" : "<int>" or "all"
143 SWITCHID_PATTERN = dpid_lib.DPID_PATTERN + r'|all'
144 VLANID_PATTERN = r'[0-9]{1,4}|all'
147 REST_SWITCHID = 'switch_id'
148 REST_VLANID = 'vlan_id'
149 REST_RULE_ID = 'rule_id'
150 REST_STATUS = 'status'
151 REST_LOG_STATUS = 'log_status'
152 REST_STATUS_ENABLE = 'enable'
153 REST_STATUS_DISABLE = 'disable'
154 REST_COMMAND_RESULT = 'command_result'
155 REST_ACL = 'access_control_list'
157 REST_COOKIE = 'cookie'
158 REST_PRIORITY = 'priority'
160 REST_IN_PORT = 'in_port'
161 REST_SRC_MAC = 'dl_src'
162 REST_DST_MAC = 'dl_dst'
163 REST_DL_TYPE = 'dl_type'
164 REST_DL_TYPE_ARP = 'ARP'
165 REST_DL_TYPE_IPV4 = 'IPv4'
166 REST_DL_TYPE_IPV6 = 'IPv6'
167 REST_DL_VLAN = 'dl_vlan'
168 REST_SRC_IP = 'nw_src'
169 REST_DST_IP = 'nw_dst'
170 REST_SRC_IPV6 = 'ipv6_src'
171 REST_DST_IPV6 = 'ipv6_dst'
172 REST_NW_PROTO = 'nw_proto'
173 REST_NW_PROTO_TCP = 'TCP'
174 REST_NW_PROTO_UDP = 'UDP'
175 REST_NW_PROTO_ICMP = 'ICMP'
176 REST_NW_PROTO_ICMPV6 = 'ICMPv6'
177 REST_TP_SRC = 'tp_src'
178 REST_TP_DST = 'tp_dst'
179 REST_ACTION = 'actions'
180 REST_ACTION_ALLOW = 'ALLOW'
181 REST_ACTION_DENY = 'DENY'
182 REST_ACTION_PACKETIN = 'PACKETIN'
185 STATUS_FLOW_PRIORITY = ofproto_v1_3_parser.UINT16_MAX
186 ARP_FLOW_PRIORITY = ofproto_v1_3_parser.UINT16_MAX - 1
187 LOG_FLOW_PRIORITY = 0
188 ACL_FLOW_PRIORITY_MIN = LOG_FLOW_PRIORITY + 1
189 ACL_FLOW_PRIORITY_MAX = ofproto_v1_3_parser.UINT16_MAX - 2
194 COOKIE_SHIFT_VLANID = 32
197 class RestFirewallAPI(app_manager.RyuApp):
199 OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION,
200 ofproto_v1_2.OFP_VERSION,
201 ofproto_v1_3.OFP_VERSION]
203 _CONTEXTS = {'dpset': dpset.DPSet,
204 'wsgi': WSGIApplication}
206 def __init__(self, *args, **kwargs):
207 super(RestFirewallAPI, self).__init__(*args, **kwargs)
210 FirewallController.set_logger(self.logger)
212 self.dpset = kwargs['dpset']
213 wsgi = kwargs['wsgi']
216 self.data['dpset'] = self.dpset
217 self.data['waiters'] = self.waiters
220 wsgi.registory['FirewallController'] = self.data
222 requirements = {'switchid': SWITCHID_PATTERN,
223 'vlanid': VLANID_PATTERN}
225 # for firewall status
226 uri = path + '/module/status'
227 mapper.connect('firewall', uri,
228 controller=FirewallController, action='get_status',
229 conditions=dict(method=['GET']))
231 uri = path + '/module/enable/{switchid}'
232 mapper.connect('firewall', uri,
233 controller=FirewallController, action='set_enable',
234 conditions=dict(method=['PUT']),
235 requirements=requirements)
237 uri = path + '/module/disable/{switchid}'
238 mapper.connect('firewall', uri,
239 controller=FirewallController, action='set_disable',
240 conditions=dict(method=['PUT']),
241 requirements=requirements)
244 uri = path + '/log/status'
245 mapper.connect('firewall', uri,
246 controller=FirewallController, action='get_log_status',
247 conditions=dict(method=['GET']))
249 uri = path + '/log/enable/{switchid}'
250 mapper.connect('firewall', uri,
251 controller=FirewallController, action='set_log_enable',
252 conditions=dict(method=['PUT']),
253 requirements=requirements)
255 uri = path + '/log/disable/{switchid}'
256 mapper.connect('firewall', uri,
257 controller=FirewallController, action='set_log_disable',
258 conditions=dict(method=['PUT']),
259 requirements=requirements)
262 uri = path + '/rules/{switchid}'
263 mapper.connect('firewall', uri,
264 controller=FirewallController, action='get_rules',
265 conditions=dict(method=['GET']),
266 requirements=requirements)
268 mapper.connect('firewall', uri,
269 controller=FirewallController, action='set_rule',
270 conditions=dict(method=['POST']),
271 requirements=requirements)
273 mapper.connect('firewall', uri,
274 controller=FirewallController, action='delete_rule',
275 conditions=dict(method=['DELETE']),
276 requirements=requirements)
280 mapper.connect('firewall', uri, controller=FirewallController,
281 action='get_vlan_rules',
282 conditions=dict(method=['GET']),
283 requirements=requirements)
285 mapper.connect('firewall', uri, controller=FirewallController,
286 action='set_vlan_rule',
287 conditions=dict(method=['POST']),
288 requirements=requirements)
290 mapper.connect('firewall', uri, controller=FirewallController,
291 action='delete_vlan_rule',
292 conditions=dict(method=['DELETE']),
293 requirements=requirements)
295 def stats_reply_handler(self, ev):
299 if dp.id not in self.waiters:
301 if msg.xid not in self.waiters[dp.id]:
303 lock, msgs = self.waiters[dp.id][msg.xid]
307 if dp.ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION or \
308 dp.ofproto.OFP_VERSION == ofproto_v1_2.OFP_VERSION:
309 flags = dp.ofproto.OFPSF_REPLY_MORE
310 elif dp.ofproto.OFP_VERSION == ofproto_v1_3.OFP_VERSION:
311 flags = dp.ofproto.OFPMPF_REPLY_MORE
313 if msg.flags & flags:
315 del self.waiters[dp.id][msg.xid]
318 @set_ev_cls(dpset.EventDP, dpset.DPSET_EV_DISPATCHER)
319 def handler_datapath(self, ev):
321 FirewallController.regist_ofs(ev.dp)
323 FirewallController.unregist_ofs(ev.dp)
325 # for OpenFlow version1.0
326 @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER)
327 def stats_reply_handler_v1_0(self, ev):
328 self.stats_reply_handler(ev)
330 # for OpenFlow version1.2 or later
331 @set_ev_cls(ofp_event.EventOFPStatsReply, MAIN_DISPATCHER)
332 def stats_reply_handler_v1_2(self, ev):
333 self.stats_reply_handler(ev)
335 @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
336 def packet_in_handler(self, ev):
337 FirewallController.packet_in_handler(ev.msg)
340 class FirewallOfsList(dict):
342 super(FirewallOfsList, self).__init__()
344 def get_ofs(self, dp_id):
346 raise ValueError('firewall sw is not connected.')
349 if dp_id == REST_ALL:
353 dpid = dpid_lib.str_to_dpid(dp_id)
355 raise ValueError('Invalid switchID.')
358 dps = {dpid: self[dpid]}
360 msg = 'firewall sw is not connected. : switchID=%s' % dp_id
361 raise ValueError(msg)
366 class FirewallController(ControllerBase):
368 _OFS_LIST = FirewallOfsList()
371 def __init__(self, req, link, data, **config):
372 super(FirewallController, self).__init__(req, link, data, **config)
373 self.dpset = data['dpset']
374 self.waiters = data['waiters']
377 def set_logger(cls, logger):
379 cls._LOGGER.propagate = False
380 hdlr = logging.StreamHandler()
381 fmt_str = '[FW][%(levelname)s] %(message)s'
382 hdlr.setFormatter(logging.Formatter(fmt_str))
383 cls._LOGGER.addHandler(hdlr)
387 dpid_str = dpid_lib.dpid_to_str(dp.id)
390 except OFPUnknownVersion as message:
391 FirewallController._LOGGER.info('dpid=%s: %s',
395 FirewallController._OFS_LIST.setdefault(dp.id, f_ofs)
397 f_ofs.set_disable_flow()
399 f_ofs.set_log_enable()
400 FirewallController._LOGGER.info('dpid=%s: Join as firewall.',
404 def unregist_ofs(dp):
405 if dp.id in FirewallController._OFS_LIST:
406 del FirewallController._OFS_LIST[dp.id]
407 FirewallController._LOGGER.info('dpid=%s: Leave firewall.',
408 dpid_lib.dpid_to_str(dp.id))
410 # GET /firewall/module/status
411 def get_status(self, req, **_kwargs):
412 return self._access_module(REST_ALL, 'get_status',
413 waiters=self.waiters)
415 # POST /firewall/module/enable/{switchid}
416 def set_enable(self, req, switchid, **_kwargs):
417 return self._access_module(switchid, 'set_enable_flow')
419 # POST /firewall/module/disable/{switchid}
420 def set_disable(self, req, switchid, **_kwargs):
421 return self._access_module(switchid, 'set_disable_flow')
423 # GET /firewall/log/status
424 def get_log_status(self, dummy, **_kwargs):
425 return self._access_module(REST_ALL, 'get_log_status',
426 waiters=self.waiters)
428 # PUT /firewall/log/enable/{switchid}
429 def set_log_enable(self, dummy, switchid, **_kwargs):
430 return self._access_module(switchid, 'set_log_enable',
431 waiters=self.waiters)
433 # PUT /firewall/log/disable/{switchid}
434 def set_log_disable(self, dummy, switchid, **_kwargs):
435 return self._access_module(switchid, 'set_log_disable',
436 waiters=self.waiters)
438 def _access_module(self, switchid, func, waiters=None):
440 dps = self._OFS_LIST.get_ofs(switchid)
441 except ValueError as message:
442 return Response(status=400, body=str(message))
445 for f_ofs in dps.values():
446 function = getattr(f_ofs, func)
447 msg = function() if waiters is None else function(waiters)
450 body = json.dumps(msgs)
451 return Response(content_type='application/json', body=body)
453 # GET /firewall/rules/{switchid}
454 def get_rules(self, req, switchid, **_kwargs):
455 return self._get_rules(switchid)
457 # GET /firewall/rules/{switchid}/{vlanid}
458 def get_vlan_rules(self, req, switchid, vlanid, **_kwargs):
459 return self._get_rules(switchid, vlan_id=vlanid)
461 # POST /firewall/rules/{switchid}
462 def set_rule(self, req, switchid, **_kwargs):
463 return self._set_rule(req, switchid)
465 # POST /firewall/rules/{switchid}/{vlanid}
466 def set_vlan_rule(self, req, switchid, vlanid, **_kwargs):
467 return self._set_rule(req, switchid, vlan_id=vlanid)
469 # DELETE /firewall/rules/{switchid}
470 def delete_rule(self, req, switchid, **_kwargs):
471 return self._delete_rule(req, switchid)
473 # DELETE /firewall/rules/{switchid}/{vlanid}
474 def delete_vlan_rule(self, req, switchid, vlanid, **_kwargs):
475 return self._delete_rule(req, switchid, vlan_id=vlanid)
477 def _get_rules(self, switchid, vlan_id=VLANID_NONE):
479 dps = self._OFS_LIST.get_ofs(switchid)
480 vid = FirewallController._conv_toint_vlanid(vlan_id)
481 except ValueError as message:
482 return Response(status=400, body=str(message))
485 for f_ofs in dps.values():
486 rules = f_ofs.get_rules(self.waiters, vid)
489 body = json.dumps(msgs)
490 return Response(content_type='application/json', body=body)
492 def _set_rule(self, req, switchid, vlan_id=VLANID_NONE):
494 rule = req.json if req.body else {}
496 FirewallController._LOGGER.debug('invalid syntax %s', req.body)
497 return Response(status=400)
500 dps = self._OFS_LIST.get_ofs(switchid)
501 vid = FirewallController._conv_toint_vlanid(vlan_id)
502 except ValueError as message:
503 return Response(status=400, body=str(message))
506 for f_ofs in dps.values():
508 msg = f_ofs.set_rule(rule, self.waiters, vid)
510 except ValueError as message:
511 return Response(status=400, body=str(message))
513 body = json.dumps(msgs)
514 return Response(content_type='application/json', body=body)
516 def _delete_rule(self, req, switchid, vlan_id=VLANID_NONE):
518 ruleid = req.json if req.body else {}
520 FirewallController._LOGGER.debug('invalid syntax %s', req.body)
521 return Response(status=400)
524 dps = self._OFS_LIST.get_ofs(switchid)
525 vid = FirewallController._conv_toint_vlanid(vlan_id)
526 except ValueError as message:
527 return Response(status=400, body=str(message))
530 for f_ofs in dps.values():
532 msg = f_ofs.delete_rule(ruleid, self.waiters, vid)
534 except ValueError as message:
535 return Response(status=400, body=str(message))
537 body = json.dumps(msgs)
538 return Response(content_type='application/json', body=body)
541 def _conv_toint_vlanid(vlan_id):
542 if vlan_id != REST_ALL:
543 vlan_id = int(vlan_id)
544 if (vlan_id != VLANID_NONE and
545 (vlan_id < VLANID_MIN or VLANID_MAX < vlan_id)):
546 msg = 'Invalid {vlan_id} value. Set [%d-%d]' % (VLANID_MIN,
548 raise ValueError(msg)
552 def packet_in_handler(msg):
553 pkt = packet.Packet(msg.data)
554 dpid_str = dpid_lib.dpid_to_str(msg.datapath.id)
555 FirewallController._LOGGER.info('dpid=%s: Blocked packet = %s',
559 class Firewall(object):
561 _OFCTL = {ofproto_v1_0.OFP_VERSION: ofctl_v1_0,
562 ofproto_v1_2.OFP_VERSION: ofctl_v1_2,
563 ofproto_v1_3.OFP_VERSION: ofctl_v1_3}
565 def __init__(self, dp):
566 super(Firewall, self).__init__()
568 self.vlan_list[VLANID_NONE] = 0 # for VLAN=None
570 version = dp.ofproto.OFP_VERSION
572 if version not in self._OFCTL:
573 raise OFPUnknownVersion(version=version)
575 self.ofctl = self._OFCTL[version]
577 def _update_vlan_list(self, vlan_list):
578 for vlan_id in self.vlan_list.keys():
579 if vlan_id is not VLANID_NONE and vlan_id not in vlan_list:
580 del self.vlan_list[vlan_id]
582 def _get_cookie(self, vlan_id):
583 if vlan_id == REST_ALL:
584 vlan_ids = self.vlan_list.keys()
589 for vlan_id in vlan_ids:
590 self.vlan_list.setdefault(vlan_id, 0)
591 self.vlan_list[vlan_id] += 1
592 self.vlan_list[vlan_id] &= ofproto_v1_3_parser.UINT32_MAX
593 cookie = (vlan_id << COOKIE_SHIFT_VLANID) + \
594 self.vlan_list[vlan_id]
595 cookie_list.append([cookie, vlan_id])
600 def _cookie_to_ruleid(cookie):
601 return cookie & ofproto_v1_3_parser.UINT32_MAX
603 # REST command template
604 def rest_command(func):
605 def _rest_command(*args, **kwargs):
606 key, value = func(*args, **kwargs)
607 switch_id = dpid_lib.dpid_to_str(args[0].dp.id)
608 return {REST_SWITCHID: switch_id,
613 def get_status(self, waiters):
614 msgs = self.ofctl.get_flow_stats(self.dp, waiters)
616 status = REST_STATUS_ENABLE
617 if str(self.dp.id) in msgs:
618 flow_stats = msgs[str(self.dp.id)]
619 for flow_stat in flow_stats:
620 if flow_stat['priority'] == STATUS_FLOW_PRIORITY:
621 status = REST_STATUS_DISABLE
623 return REST_STATUS, status
626 def set_disable_flow(self):
628 priority = STATUS_FLOW_PRIORITY
631 flow = self._to_of_flow(cookie=cookie, priority=priority,
632 match=match, actions=actions)
634 cmd = self.dp.ofproto.OFPFC_ADD
635 self.ofctl.mod_flow_entry(self.dp, flow, cmd)
637 msg = {'result': 'success',
638 'details': 'firewall stopped.'}
639 return REST_COMMAND_RESULT, msg
642 def set_enable_flow(self):
644 priority = STATUS_FLOW_PRIORITY
647 flow = self._to_of_flow(cookie=cookie, priority=priority,
648 match=match, actions=actions)
650 cmd = self.dp.ofproto.OFPFC_DELETE_STRICT
651 self.ofctl.mod_flow_entry(self.dp, flow, cmd)
653 msg = {'result': 'success',
654 'details': 'firewall running.'}
655 return REST_COMMAND_RESULT, msg
658 def get_log_status(self, waiters):
659 msgs = self.ofctl.get_flow_stats(self.dp, waiters)
661 status = REST_STATUS_DISABLE
662 if str(self.dp.id) in msgs:
663 flow_stats = msgs[str(self.dp.id)]
664 for flow_stat in flow_stats:
665 if flow_stat['priority'] == LOG_FLOW_PRIORITY:
666 if flow_stat['actions']:
667 status = REST_STATUS_ENABLE
669 return REST_LOG_STATUS, status
672 def set_log_disable(self, waiters=None):
673 return self._set_log_status(False, waiters)
676 def set_log_enable(self, waiters=None):
677 return self._set_log_status(True, waiters)
679 def _set_log_status(self, is_enable, waiters):
681 actions = Action.to_openflow({REST_ACTION: REST_ACTION_PACKETIN})
682 details = 'Log collection started.'
685 details = 'Log collection stopped.'
687 cmd = self.dp.ofproto.OFPFC_ADD
690 msgs = self.ofctl.get_flow_stats(self.dp, waiters)
692 if str(self.dp.id) in msgs:
693 flow_stats = msgs[str(self.dp.id)]
694 for flow_stat in flow_stats:
695 priority = flow_stat[REST_PRIORITY]
696 if (priority == STATUS_FLOW_PRIORITY
697 or priority == ARP_FLOW_PRIORITY):
699 action = flow_stat[REST_ACTION]
700 if action == ['OUTPUT:%d' % self.dp.ofproto.OFPP_NORMAL]:
703 cookie = flow_stat[REST_COOKIE]
704 match = Match.to_mod_openflow(flow_stat[REST_MATCH])
705 flow = self._to_of_flow(cookie=cookie, priority=priority,
706 match=match, actions=actions)
707 self.ofctl.mod_flow_entry(self.dp, flow, cmd)
710 flow = self._to_of_flow(cookie=0, priority=LOG_FLOW_PRIORITY,
711 match={}, actions=actions)
712 self.ofctl.mod_flow_entry(self.dp, flow, cmd)
714 msg = {'result': 'success',
716 return REST_COMMAND_RESULT, msg
718 def set_arp_flow(self):
720 priority = ARP_FLOW_PRIORITY
721 match = {REST_DL_TYPE: ether.ETH_TYPE_ARP}
722 action = {REST_ACTION: REST_ACTION_ALLOW}
723 actions = Action.to_openflow(action)
724 flow = self._to_of_flow(cookie=cookie, priority=priority,
725 match=match, actions=actions)
727 cmd = self.dp.ofproto.OFPFC_ADD
728 self.ofctl.mod_flow_entry(self.dp, flow, cmd)
731 def set_rule(self, rest, waiters, vlan_id):
733 cookie_list = self._get_cookie(vlan_id)
734 for cookie, vid in cookie_list:
735 msg = self._set_rule(cookie, rest, waiters, vid)
737 return REST_COMMAND_RESULT, msgs
739 def _set_rule(self, cookie, rest, waiters, vlan_id):
740 priority = int(rest.get(REST_PRIORITY, ACL_FLOW_PRIORITY_MIN))
742 if (priority < ACL_FLOW_PRIORITY_MIN
743 or ACL_FLOW_PRIORITY_MAX < priority):
744 raise ValueError('Invalid priority value. Set [%d-%d]'
745 % (ACL_FLOW_PRIORITY_MIN, ACL_FLOW_PRIORITY_MAX))
748 rest[REST_DL_VLAN] = vlan_id
750 match = Match.to_openflow(rest)
751 if rest.get(REST_ACTION) == REST_ACTION_DENY:
752 result = self.get_log_status(waiters)
753 if result[REST_LOG_STATUS] == REST_STATUS_ENABLE:
754 rest[REST_ACTION] = REST_ACTION_PACKETIN
755 actions = Action.to_openflow(rest)
756 flow = self._to_of_flow(cookie=cookie, priority=priority,
757 match=match, actions=actions)
759 cmd = self.dp.ofproto.OFPFC_ADD
761 self.ofctl.mod_flow_entry(self.dp, flow, cmd)
763 raise ValueError('Invalid rule parameter.')
765 rule_id = Firewall._cookie_to_ruleid(cookie)
766 msg = {'result': 'success',
767 'details': 'Rule added. : rule_id=%d' % rule_id}
769 if vlan_id != VLANID_NONE:
770 msg.setdefault(REST_VLANID, vlan_id)
774 def get_rules(self, waiters, vlan_id):
776 msgs = self.ofctl.get_flow_stats(self.dp, waiters)
778 if str(self.dp.id) in msgs:
779 flow_stats = msgs[str(self.dp.id)]
780 for flow_stat in flow_stats:
781 priority = flow_stat[REST_PRIORITY]
782 if (priority != STATUS_FLOW_PRIORITY
783 and priority != ARP_FLOW_PRIORITY
784 and priority != LOG_FLOW_PRIORITY):
785 vid = flow_stat[REST_MATCH].get(REST_DL_VLAN, VLANID_NONE)
786 if vlan_id == REST_ALL or vlan_id == vid:
787 rule = self._to_rest_rule(flow_stat)
788 rules.setdefault(vid, [])
789 rules[vid].append(rule)
792 for vid, rule in rules.items():
793 if vid == VLANID_NONE:
794 vid_data = {REST_RULES: rule}
796 vid_data = {REST_VLANID: vid, REST_RULES: rule}
797 get_data.append(vid_data)
799 return REST_ACL, get_data
802 def delete_rule(self, rest, waiters, vlan_id):
804 if rest[REST_RULE_ID] == REST_ALL:
807 rule_id = int(rest[REST_RULE_ID])
809 raise ValueError('Invalid ruleID.')
814 msgs = self.ofctl.get_flow_stats(self.dp, waiters)
815 if str(self.dp.id) in msgs:
816 flow_stats = msgs[str(self.dp.id)]
817 for flow_stat in flow_stats:
818 cookie = flow_stat[REST_COOKIE]
819 ruleid = Firewall._cookie_to_ruleid(cookie)
820 priority = flow_stat[REST_PRIORITY]
821 dl_vlan = flow_stat[REST_MATCH].get(REST_DL_VLAN, VLANID_NONE)
823 if (priority != STATUS_FLOW_PRIORITY
824 and priority != ARP_FLOW_PRIORITY
825 and priority != LOG_FLOW_PRIORITY):
826 if ((rule_id == REST_ALL or rule_id == ruleid) and
827 (vlan_id == dl_vlan or vlan_id == REST_ALL)):
828 match = Match.to_mod_openflow(flow_stat[REST_MATCH])
829 delete_list.append([cookie, priority, match])
831 if dl_vlan not in vlan_list:
832 vlan_list.append(dl_vlan)
834 self._update_vlan_list(vlan_list)
836 if len(delete_list) == 0:
837 msg_details = 'Rule is not exist.'
838 if rule_id != REST_ALL:
839 msg_details += ' : ruleID=%d' % rule_id
840 msg = {'result': 'failure',
841 'details': msg_details}
843 cmd = self.dp.ofproto.OFPFC_DELETE_STRICT
846 for cookie, priority, match in delete_list:
847 flow = self._to_of_flow(cookie=cookie, priority=priority,
848 match=match, actions=actions)
849 self.ofctl.mod_flow_entry(self.dp, flow, cmd)
851 vid = match.get(REST_DL_VLAN, VLANID_NONE)
852 rule_id = Firewall._cookie_to_ruleid(cookie)
853 delete_ids.setdefault(vid, '')
854 delete_ids[vid] += (('%d' if delete_ids[vid] == ''
855 else ',%d') % rule_id)
858 for vid, rule_ids in delete_ids.items():
859 del_msg = {'result': 'success',
860 'details': 'Rule deleted. : ruleID=%s' % rule_ids}
861 if vid != VLANID_NONE:
862 del_msg.setdefault(REST_VLANID, vid)
865 return REST_COMMAND_RESULT, msg
867 def _to_of_flow(self, cookie, priority, match, actions):
868 flow = {'cookie': cookie,
869 'priority': priority,
877 def _to_rest_rule(self, flow):
878 ruleid = Firewall._cookie_to_ruleid(flow[REST_COOKIE])
879 rule = {REST_RULE_ID: ruleid}
880 rule.update({REST_PRIORITY: flow[REST_PRIORITY]})
881 rule.update(Match.to_rest(flow))
882 rule.update(Action.to_rest(flow))
888 _CONVERT = {REST_DL_TYPE:
889 {REST_DL_TYPE_ARP: ether.ETH_TYPE_ARP,
890 REST_DL_TYPE_IPV4: ether.ETH_TYPE_IP,
891 REST_DL_TYPE_IPV6: ether.ETH_TYPE_IPV6},
893 {REST_NW_PROTO_TCP: inet.IPPROTO_TCP,
894 REST_NW_PROTO_UDP: inet.IPPROTO_UDP,
895 REST_NW_PROTO_ICMP: inet.IPPROTO_ICMP,
896 REST_NW_PROTO_ICMPV6: inet.IPPROTO_ICMPV6}}
898 _MATCHES = [REST_IN_PORT,
912 def to_openflow(rest):
914 def __inv_combi(msg):
915 raise ValueError('Invalid combination: [%s]' % msg)
917 def __inv_2and1(*args):
918 __inv_combi('%s=%s and %s' % (args[0], args[1], args[2]))
920 def __inv_2and2(*args):
921 __inv_combi('%s=%s and %s=%s' % (
922 args[0], args[1], args[2], args[3]))
924 def __inv_1and1(*args):
925 __inv_combi('%s and %s' % (args[0], args[1]))
927 def __inv_1and2(*args):
928 __inv_combi('%s and %s=%s' % (args[0], args[1], args[2]))
933 dl_type = rest.get(REST_DL_TYPE)
934 nw_proto = rest.get(REST_NW_PROTO)
935 if dl_type is not None:
936 if dl_type == REST_DL_TYPE_ARP:
937 if REST_SRC_IPV6 in rest:
939 REST_DL_TYPE, REST_DL_TYPE_ARP, REST_SRC_IPV6)
940 if REST_DST_IPV6 in rest:
942 REST_DL_TYPE, REST_DL_TYPE_ARP, REST_DST_IPV6)
945 REST_DL_TYPE, REST_DL_TYPE_ARP, REST_NW_PROTO)
946 elif dl_type == REST_DL_TYPE_IPV4:
947 if REST_SRC_IPV6 in rest:
949 REST_DL_TYPE, REST_DL_TYPE_IPV4, REST_SRC_IPV6)
950 if REST_DST_IPV6 in rest:
952 REST_DL_TYPE, REST_DL_TYPE_IPV4, REST_DST_IPV6)
953 if nw_proto == REST_NW_PROTO_ICMPV6:
955 REST_DL_TYPE, REST_DL_TYPE_IPV4,
956 REST_NW_PROTO, REST_NW_PROTO_ICMPV6)
957 elif dl_type == REST_DL_TYPE_IPV6:
958 if REST_SRC_IP in rest:
960 REST_DL_TYPE, REST_DL_TYPE_IPV6, REST_SRC_IP)
961 if REST_DST_IP in rest:
963 REST_DL_TYPE, REST_DL_TYPE_IPV6, REST_DST_IP)
964 if nw_proto == REST_NW_PROTO_ICMP:
966 REST_DL_TYPE, REST_DL_TYPE_IPV6,
967 REST_NW_PROTO, REST_NW_PROTO_ICMP)
969 raise ValueError('Unknown dl_type : %s' % dl_type)
971 if REST_SRC_IP in rest:
972 if REST_SRC_IPV6 in rest:
973 __inv_1and1(REST_SRC_IP, REST_SRC_IPV6)
974 if REST_DST_IPV6 in rest:
975 __inv_1and1(REST_SRC_IP, REST_DST_IPV6)
976 if nw_proto == REST_NW_PROTO_ICMPV6:
978 REST_SRC_IP, REST_NW_PROTO, REST_NW_PROTO_ICMPV6)
979 rest[REST_DL_TYPE] = REST_DL_TYPE_IPV4
980 elif REST_DST_IP in rest:
981 if REST_SRC_IPV6 in rest:
982 __inv_1and1(REST_DST_IP, REST_SRC_IPV6)
983 if REST_DST_IPV6 in rest:
984 __inv_1and1(REST_DST_IP, REST_DST_IPV6)
985 if nw_proto == REST_NW_PROTO_ICMPV6:
987 REST_DST_IP, REST_NW_PROTO, REST_NW_PROTO_ICMPV6)
988 rest[REST_DL_TYPE] = REST_DL_TYPE_IPV4
989 elif REST_SRC_IPV6 in rest:
990 if nw_proto == REST_NW_PROTO_ICMP:
992 REST_SRC_IPV6, REST_NW_PROTO, REST_NW_PROTO_ICMP)
993 rest[REST_DL_TYPE] = REST_DL_TYPE_IPV6
994 elif REST_DST_IPV6 in rest:
995 if nw_proto == REST_NW_PROTO_ICMP:
997 REST_DST_IPV6, REST_NW_PROTO, REST_NW_PROTO_ICMP)
998 rest[REST_DL_TYPE] = REST_DL_TYPE_IPV6
1000 if nw_proto == REST_NW_PROTO_ICMP:
1001 rest[REST_DL_TYPE] = REST_DL_TYPE_IPV4
1002 elif nw_proto == REST_NW_PROTO_ICMPV6:
1003 rest[REST_DL_TYPE] = REST_DL_TYPE_IPV6
1004 elif nw_proto == REST_NW_PROTO_TCP or \
1005 nw_proto == REST_NW_PROTO_UDP:
1006 raise ValueError('no dl_type was specified')
1008 raise ValueError('Unknown nw_proto: %s' % nw_proto)
1010 for key, value in rest.items():
1011 if key in Match._CONVERT:
1012 if value in Match._CONVERT[key]:
1013 match.setdefault(key, Match._CONVERT[key][value])
1015 raise ValueError('Invalid rule parameter. : key=%s' % key)
1016 elif key in Match._MATCHES:
1017 match.setdefault(key, value)
1022 def to_rest(openflow):
1023 of_match = openflow[REST_MATCH]
1025 mac_dontcare = mac.haddr_to_str(mac.DONTCARE)
1026 ip_dontcare = '0.0.0.0'
1027 ipv6_dontcare = '::'
1030 for key, value in of_match.items():
1031 if key == REST_SRC_MAC or key == REST_DST_MAC:
1032 if value == mac_dontcare:
1034 elif key == REST_SRC_IP or key == REST_DST_IP:
1035 if value == ip_dontcare:
1037 elif key == REST_SRC_IPV6 or key == REST_DST_IPV6:
1038 if value == ipv6_dontcare:
1043 if key in Match._CONVERT:
1044 conv = Match._CONVERT[key]
1045 conv = dict((value, key) for key, value in conv.items())
1046 match.setdefault(key, conv[value])
1048 match.setdefault(key, value)
1053 def to_mod_openflow(of_match):
1054 mac_dontcare = mac.haddr_to_str(mac.DONTCARE)
1055 ip_dontcare = '0.0.0.0'
1056 ipv6_dontcare = '::'
1059 for key, value in of_match.items():
1060 if key == REST_SRC_MAC or key == REST_DST_MAC:
1061 if value == mac_dontcare:
1063 elif key == REST_SRC_IP or key == REST_DST_IP:
1064 if value == ip_dontcare:
1066 elif key == REST_SRC_IPV6 or key == REST_DST_IPV6:
1067 if value == ipv6_dontcare:
1072 match.setdefault(key, value)
1077 class Action(object):
1080 def to_openflow(rest):
1081 value = rest.get(REST_ACTION, REST_ACTION_ALLOW)
1083 if value == REST_ACTION_ALLOW:
1084 action = [{'type': 'OUTPUT',
1086 elif value == REST_ACTION_DENY:
1088 elif value == REST_ACTION_PACKETIN:
1089 action = [{'type': 'OUTPUT',
1090 'port': 'CONTROLLER',
1093 raise ValueError('Invalid action type.')
1098 def to_rest(openflow):
1099 if REST_ACTION in openflow:
1100 action_allow = 'OUTPUT:NORMAL'
1101 if openflow[REST_ACTION] == [action_allow]:
1102 action = {REST_ACTION: REST_ACTION_ALLOW}
1104 action = {REST_ACTION: REST_ACTION_DENY}
1106 action = {REST_ACTION: 'Unknown action type.'}