backing up
[vsorcdistro/.git] / ryu / build / lib.linux-armv7l-2.7 / ryu / app / rest_firewall.py
1 # Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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
12 # implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16
17 import logging
18 import json
19
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
42
43
44 # =============================
45 #          REST API
46 # =============================
47 #
48 #  Note: specify switch and vlan group, as follows.
49 #   {switch-id} : 'all' or switchID
50 #   {vlan-id}   : 'all' or vlanID
51 #
52 #
53
54 # about Firewall status
55 #
56 # get status of all firewall switches
57 # GET /firewall/module/status
58 #
59 # set enable the firewall switches
60 # PUT /firewall/module/enable/{switch-id}
61 #
62 # set disable the firewall switches
63 # PUT /firewall/module/disable/{switch-id}
64 #
65
66 # about Firewall logs
67 #
68 # get log status of all firewall switches
69 # GET /firewall/log/status
70 #
71 # set log enable the firewall switches
72 # PUT /firewall/log/enable/{switch-id}
73 #
74 # set log disable the firewall switches
75 # PUT /firewall/log/disable/{switch-id}
76 #
77
78 # about Firewall rules
79 #
80 # get rules of the firewall switches
81 # * for no vlan
82 # GET /firewall/rules/{switch-id}
83 #
84 # * for specific vlan group
85 # GET /firewall/rules/{switch-id}/{vlan-id}
86 #
87 #
88 # set a rule to the firewall switches
89 # * for no vlan
90 # POST /firewall/rules/{switch-id}
91 #
92 # * for specific vlan group
93 # POST /firewall/rules/{switch-id}/{vlan-id}
94 #
95 #  request body format:
96 #   {"<field1>":"<value1>", "<field2>":"<value2>",...}
97 #
98 #     <field>  : <value>
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>"
109 #    "tp_src"  : "<int>"
110 #    "tp_dst"  : "<int>"
111 #    "actions" : "<ALLOW or DENY>"
112 #
113 #   Note: specifying nw_src/nw_dst
114 #         without specifying dl-type as "ARP" or "IPv4"
115 #         will automatically set dl-type as "IPv4".
116 #
117 #   Note: specifying ipv6_src/ipv6_dst
118 #         without specifying dl-type as "IPv6"
119 #         will automatically set dl-type as "IPv6".
120 #
121 #   Note: When "priority" has not been set up,
122 #         "0" is set to "priority".
123 #
124 #   Note: When "actions" has not been set up,
125 #         "ALLOW" is set to "actions".
126 #
127 #
128 # delete a rule of the firewall switches from ruleID
129 # * for no vlan
130 # DELETE /firewall/rules/{switch-id}
131 #
132 # * for specific vlan group
133 # DELETE /firewall/rules/{switch-id}/{vlan-id}
134 #
135 #  request body format:
136 #   {"<field>":"<value>"}
137 #
138 #     <field>  : <value>
139 #    "rule_id" : "<int>" or "all"
140 #
141
142
143 SWITCHID_PATTERN = dpid_lib.DPID_PATTERN + r'|all'
144 VLANID_PATTERN = r'[0-9]{1,4}|all'
145
146 REST_ALL = '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'
156 REST_RULES = 'rules'
157 REST_COOKIE = 'cookie'
158 REST_PRIORITY = 'priority'
159 REST_MATCH = 'match'
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'
183
184
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
190
191 VLANID_NONE = 0
192 VLANID_MIN = 2
193 VLANID_MAX = 4094
194 COOKIE_SHIFT_VLANID = 32
195
196
197 class RestFirewallAPI(app_manager.RyuApp):
198
199     OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION,
200                     ofproto_v1_2.OFP_VERSION,
201                     ofproto_v1_3.OFP_VERSION]
202
203     _CONTEXTS = {'dpset': dpset.DPSet,
204                  'wsgi': WSGIApplication}
205
206     def __init__(self, *args, **kwargs):
207         super(RestFirewallAPI, self).__init__(*args, **kwargs)
208
209         # logger configure
210         FirewallController.set_logger(self.logger)
211
212         self.dpset = kwargs['dpset']
213         wsgi = kwargs['wsgi']
214         self.waiters = {}
215         self.data = {}
216         self.data['dpset'] = self.dpset
217         self.data['waiters'] = self.waiters
218
219         mapper = wsgi.mapper
220         wsgi.registory['FirewallController'] = self.data
221         path = '/firewall'
222         requirements = {'switchid': SWITCHID_PATTERN,
223                         'vlanid': VLANID_PATTERN}
224
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']))
230
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)
236
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)
242
243         # for firewall logs
244         uri = path + '/log/status'
245         mapper.connect('firewall', uri,
246                        controller=FirewallController, action='get_log_status',
247                        conditions=dict(method=['GET']))
248
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)
254
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)
260
261         # for no VLAN data
262         uri = path + '/rules/{switchid}'
263         mapper.connect('firewall', uri,
264                        controller=FirewallController, action='get_rules',
265                        conditions=dict(method=['GET']),
266                        requirements=requirements)
267
268         mapper.connect('firewall', uri,
269                        controller=FirewallController, action='set_rule',
270                        conditions=dict(method=['POST']),
271                        requirements=requirements)
272
273         mapper.connect('firewall', uri,
274                        controller=FirewallController, action='delete_rule',
275                        conditions=dict(method=['DELETE']),
276                        requirements=requirements)
277
278         # for VLAN data
279         uri += '/{vlanid}'
280         mapper.connect('firewall', uri, controller=FirewallController,
281                        action='get_vlan_rules',
282                        conditions=dict(method=['GET']),
283                        requirements=requirements)
284
285         mapper.connect('firewall', uri, controller=FirewallController,
286                        action='set_vlan_rule',
287                        conditions=dict(method=['POST']),
288                        requirements=requirements)
289
290         mapper.connect('firewall', uri, controller=FirewallController,
291                        action='delete_vlan_rule',
292                        conditions=dict(method=['DELETE']),
293                        requirements=requirements)
294
295     def stats_reply_handler(self, ev):
296         msg = ev.msg
297         dp = msg.datapath
298
299         if dp.id not in self.waiters:
300             return
301         if msg.xid not in self.waiters[dp.id]:
302             return
303         lock, msgs = self.waiters[dp.id][msg.xid]
304         msgs.append(msg)
305
306         flags = 0
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
312
313         if msg.flags & flags:
314             return
315         del self.waiters[dp.id][msg.xid]
316         lock.set()
317
318     @set_ev_cls(dpset.EventDP, dpset.DPSET_EV_DISPATCHER)
319     def handler_datapath(self, ev):
320         if ev.enter:
321             FirewallController.regist_ofs(ev.dp)
322         else:
323             FirewallController.unregist_ofs(ev.dp)
324
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)
329
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)
334
335     @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
336     def packet_in_handler(self, ev):
337         FirewallController.packet_in_handler(ev.msg)
338
339
340 class FirewallOfsList(dict):
341     def __init__(self):
342         super(FirewallOfsList, self).__init__()
343
344     def get_ofs(self, dp_id):
345         if len(self) == 0:
346             raise ValueError('firewall sw is not connected.')
347
348         dps = {}
349         if dp_id == REST_ALL:
350             dps = self
351         else:
352             try:
353                 dpid = dpid_lib.str_to_dpid(dp_id)
354             except:
355                 raise ValueError('Invalid switchID.')
356
357             if dpid in self:
358                 dps = {dpid: self[dpid]}
359             else:
360                 msg = 'firewall sw is not connected. : switchID=%s' % dp_id
361                 raise ValueError(msg)
362
363         return dps
364
365
366 class FirewallController(ControllerBase):
367
368     _OFS_LIST = FirewallOfsList()
369     _LOGGER = None
370
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']
375
376     @classmethod
377     def set_logger(cls, logger):
378         cls._LOGGER = 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)
384
385     @staticmethod
386     def regist_ofs(dp):
387         dpid_str = dpid_lib.dpid_to_str(dp.id)
388         try:
389             f_ofs = Firewall(dp)
390         except OFPUnknownVersion as message:
391             FirewallController._LOGGER.info('dpid=%s: %s',
392                                             dpid_str, message)
393             return
394
395         FirewallController._OFS_LIST.setdefault(dp.id, f_ofs)
396
397         f_ofs.set_disable_flow()
398         f_ofs.set_arp_flow()
399         f_ofs.set_log_enable()
400         FirewallController._LOGGER.info('dpid=%s: Join as firewall.',
401                                         dpid_str)
402
403     @staticmethod
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))
409
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)
414
415     # POST /firewall/module/enable/{switchid}
416     def set_enable(self, req, switchid, **_kwargs):
417         return self._access_module(switchid, 'set_enable_flow')
418
419     # POST /firewall/module/disable/{switchid}
420     def set_disable(self, req, switchid, **_kwargs):
421         return self._access_module(switchid, 'set_disable_flow')
422
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)
427
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)
432
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)
437
438     def _access_module(self, switchid, func, waiters=None):
439         try:
440             dps = self._OFS_LIST.get_ofs(switchid)
441         except ValueError as message:
442             return Response(status=400, body=str(message))
443
444         msgs = []
445         for f_ofs in dps.values():
446             function = getattr(f_ofs, func)
447             msg = function() if waiters is None else function(waiters)
448             msgs.append(msg)
449
450         body = json.dumps(msgs)
451         return Response(content_type='application/json', body=body)
452
453     # GET /firewall/rules/{switchid}
454     def get_rules(self, req, switchid, **_kwargs):
455         return self._get_rules(switchid)
456
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)
460
461     # POST /firewall/rules/{switchid}
462     def set_rule(self, req, switchid, **_kwargs):
463         return self._set_rule(req, switchid)
464
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)
468
469     # DELETE /firewall/rules/{switchid}
470     def delete_rule(self, req, switchid, **_kwargs):
471         return self._delete_rule(req, switchid)
472
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)
476
477     def _get_rules(self, switchid, vlan_id=VLANID_NONE):
478         try:
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))
483
484         msgs = []
485         for f_ofs in dps.values():
486             rules = f_ofs.get_rules(self.waiters, vid)
487             msgs.append(rules)
488
489         body = json.dumps(msgs)
490         return Response(content_type='application/json', body=body)
491
492     def _set_rule(self, req, switchid, vlan_id=VLANID_NONE):
493         try:
494             rule = req.json if req.body else {}
495         except ValueError:
496             FirewallController._LOGGER.debug('invalid syntax %s', req.body)
497             return Response(status=400)
498
499         try:
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))
504
505         msgs = []
506         for f_ofs in dps.values():
507             try:
508                 msg = f_ofs.set_rule(rule, self.waiters, vid)
509                 msgs.append(msg)
510             except ValueError as message:
511                 return Response(status=400, body=str(message))
512
513         body = json.dumps(msgs)
514         return Response(content_type='application/json', body=body)
515
516     def _delete_rule(self, req, switchid, vlan_id=VLANID_NONE):
517         try:
518             ruleid = req.json if req.body else {}
519         except ValueError:
520             FirewallController._LOGGER.debug('invalid syntax %s', req.body)
521             return Response(status=400)
522
523         try:
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))
528
529         msgs = []
530         for f_ofs in dps.values():
531             try:
532                 msg = f_ofs.delete_rule(ruleid, self.waiters, vid)
533                 msgs.append(msg)
534             except ValueError as message:
535                 return Response(status=400, body=str(message))
536
537         body = json.dumps(msgs)
538         return Response(content_type='application/json', body=body)
539
540     @staticmethod
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,
547                                                                 VLANID_MAX)
548                 raise ValueError(msg)
549         return vlan_id
550
551     @staticmethod
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',
556                                         dpid_str, pkt)
557
558
559 class Firewall(object):
560
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}
564
565     def __init__(self, dp):
566         super(Firewall, self).__init__()
567         self.vlan_list = {}
568         self.vlan_list[VLANID_NONE] = 0  # for VLAN=None
569         self.dp = dp
570         version = dp.ofproto.OFP_VERSION
571
572         if version not in self._OFCTL:
573             raise OFPUnknownVersion(version=version)
574
575         self.ofctl = self._OFCTL[version]
576
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]
581
582     def _get_cookie(self, vlan_id):
583         if vlan_id == REST_ALL:
584             vlan_ids = self.vlan_list.keys()
585         else:
586             vlan_ids = [vlan_id]
587
588         cookie_list = []
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])
596
597         return cookie_list
598
599     @staticmethod
600     def _cookie_to_ruleid(cookie):
601         return cookie & ofproto_v1_3_parser.UINT32_MAX
602
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,
609                     key: value}
610         return _rest_command
611
612     @rest_command
613     def get_status(self, waiters):
614         msgs = self.ofctl.get_flow_stats(self.dp, waiters)
615
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
622
623         return REST_STATUS, status
624
625     @rest_command
626     def set_disable_flow(self):
627         cookie = 0
628         priority = STATUS_FLOW_PRIORITY
629         match = {}
630         actions = []
631         flow = self._to_of_flow(cookie=cookie, priority=priority,
632                                 match=match, actions=actions)
633
634         cmd = self.dp.ofproto.OFPFC_ADD
635         self.ofctl.mod_flow_entry(self.dp, flow, cmd)
636
637         msg = {'result': 'success',
638                'details': 'firewall stopped.'}
639         return REST_COMMAND_RESULT, msg
640
641     @rest_command
642     def set_enable_flow(self):
643         cookie = 0
644         priority = STATUS_FLOW_PRIORITY
645         match = {}
646         actions = []
647         flow = self._to_of_flow(cookie=cookie, priority=priority,
648                                 match=match, actions=actions)
649
650         cmd = self.dp.ofproto.OFPFC_DELETE_STRICT
651         self.ofctl.mod_flow_entry(self.dp, flow, cmd)
652
653         msg = {'result': 'success',
654                'details': 'firewall running.'}
655         return REST_COMMAND_RESULT, msg
656
657     @rest_command
658     def get_log_status(self, waiters):
659         msgs = self.ofctl.get_flow_stats(self.dp, waiters)
660
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
668
669         return REST_LOG_STATUS, status
670
671     @rest_command
672     def set_log_disable(self, waiters=None):
673         return self._set_log_status(False, waiters)
674
675     @rest_command
676     def set_log_enable(self, waiters=None):
677         return self._set_log_status(True, waiters)
678
679     def _set_log_status(self, is_enable, waiters):
680         if is_enable:
681             actions = Action.to_openflow({REST_ACTION: REST_ACTION_PACKETIN})
682             details = 'Log collection started.'
683         else:
684             actions = []
685             details = 'Log collection stopped.'
686
687         cmd = self.dp.ofproto.OFPFC_ADD
688
689         if waiters:
690             msgs = self.ofctl.get_flow_stats(self.dp, waiters)
691
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):
698                         continue
699                     action = flow_stat[REST_ACTION]
700                     if action == ['OUTPUT:%d' % self.dp.ofproto.OFPP_NORMAL]:
701                         continue
702
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)
708         else:
709             # Initialize.
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)
713
714         msg = {'result': 'success',
715                'details': details}
716         return REST_COMMAND_RESULT, msg
717
718     def set_arp_flow(self):
719         cookie = 0
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)
726
727         cmd = self.dp.ofproto.OFPFC_ADD
728         self.ofctl.mod_flow_entry(self.dp, flow, cmd)
729
730     @rest_command
731     def set_rule(self, rest, waiters, vlan_id):
732         msgs = []
733         cookie_list = self._get_cookie(vlan_id)
734         for cookie, vid in cookie_list:
735             msg = self._set_rule(cookie, rest, waiters, vid)
736             msgs.append(msg)
737         return REST_COMMAND_RESULT, msgs
738
739     def _set_rule(self, cookie, rest, waiters, vlan_id):
740         priority = int(rest.get(REST_PRIORITY, ACL_FLOW_PRIORITY_MIN))
741
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))
746
747         if vlan_id:
748             rest[REST_DL_VLAN] = vlan_id
749
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)
758
759         cmd = self.dp.ofproto.OFPFC_ADD
760         try:
761             self.ofctl.mod_flow_entry(self.dp, flow, cmd)
762         except:
763             raise ValueError('Invalid rule parameter.')
764
765         rule_id = Firewall._cookie_to_ruleid(cookie)
766         msg = {'result': 'success',
767                'details': 'Rule added. : rule_id=%d' % rule_id}
768
769         if vlan_id != VLANID_NONE:
770             msg.setdefault(REST_VLANID, vlan_id)
771         return msg
772
773     @rest_command
774     def get_rules(self, waiters, vlan_id):
775         rules = {}
776         msgs = self.ofctl.get_flow_stats(self.dp, waiters)
777
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)
790
791         get_data = []
792         for vid, rule in rules.items():
793             if vid == VLANID_NONE:
794                 vid_data = {REST_RULES: rule}
795             else:
796                 vid_data = {REST_VLANID: vid, REST_RULES: rule}
797             get_data.append(vid_data)
798
799         return REST_ACL, get_data
800
801     @rest_command
802     def delete_rule(self, rest, waiters, vlan_id):
803         try:
804             if rest[REST_RULE_ID] == REST_ALL:
805                 rule_id = REST_ALL
806             else:
807                 rule_id = int(rest[REST_RULE_ID])
808         except:
809             raise ValueError('Invalid ruleID.')
810
811         vlan_list = []
812         delete_list = []
813
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)
822
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])
830                     else:
831                         if dl_vlan not in vlan_list:
832                             vlan_list.append(dl_vlan)
833
834         self._update_vlan_list(vlan_list)
835
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}
842         else:
843             cmd = self.dp.ofproto.OFPFC_DELETE_STRICT
844             actions = []
845             delete_ids = {}
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)
850
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)
856
857             msg = []
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)
863                 msg.append(del_msg)
864
865         return REST_COMMAND_RESULT, msg
866
867     def _to_of_flow(self, cookie, priority, match, actions):
868         flow = {'cookie': cookie,
869                 'priority': priority,
870                 'flags': 0,
871                 'idle_timeout': 0,
872                 'hard_timeout': 0,
873                 'match': match,
874                 'actions': actions}
875         return flow
876
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))
883         return rule
884
885
886 class Match(object):
887
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},
892                 REST_NW_PROTO:
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}}
897
898     _MATCHES = [REST_IN_PORT,
899                 REST_SRC_MAC,
900                 REST_DST_MAC,
901                 REST_DL_TYPE,
902                 REST_DL_VLAN,
903                 REST_SRC_IP,
904                 REST_DST_IP,
905                 REST_SRC_IPV6,
906                 REST_DST_IPV6,
907                 REST_NW_PROTO,
908                 REST_TP_SRC,
909                 REST_TP_DST]
910
911     @staticmethod
912     def to_openflow(rest):
913
914         def __inv_combi(msg):
915             raise ValueError('Invalid combination: [%s]' % msg)
916
917         def __inv_2and1(*args):
918             __inv_combi('%s=%s and %s' % (args[0], args[1], args[2]))
919
920         def __inv_2and2(*args):
921             __inv_combi('%s=%s and %s=%s' % (
922                 args[0], args[1], args[2], args[3]))
923
924         def __inv_1and1(*args):
925             __inv_combi('%s and %s' % (args[0], args[1]))
926
927         def __inv_1and2(*args):
928             __inv_combi('%s and %s=%s' % (args[0], args[1], args[2]))
929
930         match = {}
931
932         # error check
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:
938                     __inv_2and1(
939                         REST_DL_TYPE, REST_DL_TYPE_ARP, REST_SRC_IPV6)
940                 if REST_DST_IPV6 in rest:
941                     __inv_2and1(
942                         REST_DL_TYPE, REST_DL_TYPE_ARP, REST_DST_IPV6)
943                 if nw_proto:
944                     __inv_2and1(
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:
948                     __inv_2and1(
949                         REST_DL_TYPE, REST_DL_TYPE_IPV4, REST_SRC_IPV6)
950                 if REST_DST_IPV6 in rest:
951                     __inv_2and1(
952                         REST_DL_TYPE, REST_DL_TYPE_IPV4, REST_DST_IPV6)
953                 if nw_proto == REST_NW_PROTO_ICMPV6:
954                     __inv_2and2(
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:
959                     __inv_2and1(
960                         REST_DL_TYPE, REST_DL_TYPE_IPV6, REST_SRC_IP)
961                 if REST_DST_IP in rest:
962                     __inv_2and1(
963                         REST_DL_TYPE, REST_DL_TYPE_IPV6, REST_DST_IP)
964                 if nw_proto == REST_NW_PROTO_ICMP:
965                     __inv_2and2(
966                         REST_DL_TYPE, REST_DL_TYPE_IPV6,
967                         REST_NW_PROTO, REST_NW_PROTO_ICMP)
968             else:
969                 raise ValueError('Unknown dl_type : %s' % dl_type)
970         else:
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:
977                     __inv_1and2(
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:
986                     __inv_1and2(
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:
991                     __inv_1and2(
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:
996                     __inv_1and2(
997                         REST_DST_IPV6, REST_NW_PROTO, REST_NW_PROTO_ICMP)
998                 rest[REST_DL_TYPE] = REST_DL_TYPE_IPV6
999             else:
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')
1007                 else:
1008                     raise ValueError('Unknown nw_proto: %s' % nw_proto)
1009
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])
1014                 else:
1015                     raise ValueError('Invalid rule parameter. : key=%s' % key)
1016             elif key in Match._MATCHES:
1017                 match.setdefault(key, value)
1018
1019         return match
1020
1021     @staticmethod
1022     def to_rest(openflow):
1023         of_match = openflow[REST_MATCH]
1024
1025         mac_dontcare = mac.haddr_to_str(mac.DONTCARE)
1026         ip_dontcare = '0.0.0.0'
1027         ipv6_dontcare = '::'
1028
1029         match = {}
1030         for key, value in of_match.items():
1031             if key == REST_SRC_MAC or key == REST_DST_MAC:
1032                 if value == mac_dontcare:
1033                     continue
1034             elif key == REST_SRC_IP or key == REST_DST_IP:
1035                 if value == ip_dontcare:
1036                     continue
1037             elif key == REST_SRC_IPV6 or key == REST_DST_IPV6:
1038                 if value == ipv6_dontcare:
1039                     continue
1040             elif value == 0:
1041                 continue
1042
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])
1047             else:
1048                 match.setdefault(key, value)
1049
1050         return match
1051
1052     @staticmethod
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 = '::'
1057
1058         match = {}
1059         for key, value in of_match.items():
1060             if key == REST_SRC_MAC or key == REST_DST_MAC:
1061                 if value == mac_dontcare:
1062                     continue
1063             elif key == REST_SRC_IP or key == REST_DST_IP:
1064                 if value == ip_dontcare:
1065                     continue
1066             elif key == REST_SRC_IPV6 or key == REST_DST_IPV6:
1067                 if value == ipv6_dontcare:
1068                     continue
1069             elif value == 0:
1070                 continue
1071
1072             match.setdefault(key, value)
1073
1074         return match
1075
1076
1077 class Action(object):
1078
1079     @staticmethod
1080     def to_openflow(rest):
1081         value = rest.get(REST_ACTION, REST_ACTION_ALLOW)
1082
1083         if value == REST_ACTION_ALLOW:
1084             action = [{'type': 'OUTPUT',
1085                        'port': 'NORMAL'}]
1086         elif value == REST_ACTION_DENY:
1087             action = []
1088         elif value == REST_ACTION_PACKETIN:
1089             action = [{'type': 'OUTPUT',
1090                        'port': 'CONTROLLER',
1091                        'max_len': 128}]
1092         else:
1093             raise ValueError('Invalid action type.')
1094
1095         return action
1096
1097     @staticmethod
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}
1103             else:
1104                 action = {REST_ACTION: REST_ACTION_DENY}
1105         else:
1106             action = {REST_ACTION: 'Unknown action type.'}
1107
1108         return action