os-ken/ryu/app/rest_firewall.py
Satoshi Kobayashi 325c9ae9c3 Syntax level compatibility with Python 3
We use syntaxes which can be used only in Python2 at many locations. But we should reduce the use of the syntaxes. This patch serves syntax level compatibility with Python3. However, for full compatibility is required still a lot of work because many API changes are made between 2 to 3.

Signed-off-by: Satoshi Kobayashi <satoshi-k@stratosphere.co.jp>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
2015-04-08 12:57:45 +09:00

1113 lines
38 KiB
Python

# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import json
from webob import Response
from ryu.app.wsgi import ControllerBase
from ryu.app.wsgi import WSGIApplication
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller import dpset
from ryu.controller.handler import MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.exception import OFPUnknownVersion
from ryu.lib import mac
from ryu.lib import dpid as dpid_lib
from ryu.lib import ofctl_v1_0
from ryu.lib import ofctl_v1_2
from ryu.lib import ofctl_v1_3
from ryu.lib.packet import packet
from ryu.ofproto import ether
from ryu.ofproto import inet
from ryu.ofproto import ofproto_v1_0
from ryu.ofproto import ofproto_v1_2
from ryu.ofproto import ofproto_v1_2_parser
from ryu.ofproto import ofproto_v1_3
from ryu.ofproto import ofproto_v1_3_parser
# =============================
# REST API
# =============================
#
# Note: specify switch and vlan group, as follows.
# {switch-id} : 'all' or switchID
# {vlan-id} : 'all' or vlanID
#
#
# about Firewall status
#
# get status of all firewall switches
# GET /firewall/module/status
#
# set enable the firewall switches
# PUT /firewall/module/enable/{switch-id}
#
# set disable the firewall switches
# PUT /firewall/module/disable/{switch-id}
#
# about Firewall logs
#
# get log status of all firewall switches
# GET /firewall/log/status
#
# set log enable the firewall switches
# PUT /firewall/log/enable/{switch-id}
#
# set log disable the firewall switches
# PUT /firewall/log/disable/{switch-id}
#
# about Firewall rules
#
# get rules of the firewall switches
# * for no vlan
# GET /firewall/rules/{switch-id}
#
# * 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}
#
# * 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 or IPv6>"
# "nw_src" : "<A.B.C.D/M>"
# "nw_dst" : "<A.B.C.D/M>"
# "ipv6_src": "<xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/M>"
# "ipv6_dst": "<xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/M>"
# "nw_proto": "<TCP or UDP or ICMP or ICMPv6>"
# "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: specifying ipv6_src/ipv6_dst
# without specifying dl-type as "IPv6"
# will automatically set dl-type as "IPv6".
#
# 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}
#
# * for specific vlan group
# DELETE /firewall/rules/{switch-id}/{vlan-id}
#
# request body format:
# {"<field>":"<value>"}
#
# <field> : <value>
# "rule_id" : "<int>" or "all"
#
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_LOG_STATUS = 'log_status'
REST_STATUS_ENABLE = 'enable'
REST_STATUS_DISABLE = 'disable'
REST_COMMAND_RESULT = 'command_result'
REST_ACL = 'access_control_list'
REST_RULES = 'rules'
REST_COOKIE = 'cookie'
REST_PRIORITY = 'priority'
REST_MATCH = 'match'
REST_IN_PORT = 'in_port'
REST_SRC_MAC = 'dl_src'
REST_DST_MAC = 'dl_dst'
REST_DL_TYPE = 'dl_type'
REST_DL_TYPE_ARP = 'ARP'
REST_DL_TYPE_IPV4 = 'IPv4'
REST_DL_TYPE_IPV6 = 'IPv6'
REST_DL_VLAN = 'dl_vlan'
REST_SRC_IP = 'nw_src'
REST_DST_IP = 'nw_dst'
REST_SRC_IPV6 = 'ipv6_src'
REST_DST_IPV6 = 'ipv6_dst'
REST_NW_PROTO = 'nw_proto'
REST_NW_PROTO_TCP = 'TCP'
REST_NW_PROTO_UDP = 'UDP'
REST_NW_PROTO_ICMP = 'ICMP'
REST_NW_PROTO_ICMPV6 = 'ICMPv6'
REST_TP_SRC = 'tp_src'
REST_TP_DST = 'tp_dst'
REST_ACTION = 'actions'
REST_ACTION_ALLOW = 'ALLOW'
REST_ACTION_DENY = 'DENY'
REST_ACTION_PACKETIN = 'PACKETIN'
STATUS_FLOW_PRIORITY = ofproto_v1_3_parser.UINT16_MAX
ARP_FLOW_PRIORITY = ofproto_v1_3_parser.UINT16_MAX - 1
LOG_FLOW_PRIORITY = 0
ACL_FLOW_PRIORITY_MIN = LOG_FLOW_PRIORITY + 1
ACL_FLOW_PRIORITY_MAX = ofproto_v1_3_parser.UINT16_MAX - 2
VLANID_NONE = 0
VLANID_MIN = 2
VLANID_MAX = 4094
COOKIE_SHIFT_VLANID = 32
class RestFirewallAPI(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION,
ofproto_v1_2.OFP_VERSION,
ofproto_v1_3.OFP_VERSION]
_CONTEXTS = {'dpset': dpset.DPSet,
'wsgi': WSGIApplication}
def __init__(self, *args, **kwargs):
super(RestFirewallAPI, self).__init__(*args, **kwargs)
# logger configure
FirewallController.set_logger(self.logger)
self.dpset = kwargs['dpset']
wsgi = kwargs['wsgi']
self.waiters = {}
self.data = {}
self.data['dpset'] = self.dpset
self.data['waiters'] = self.waiters
mapper = wsgi.mapper
wsgi.registory['FirewallController'] = self.data
path = '/firewall'
requirements = {'switchid': SWITCHID_PATTERN,
'vlanid': VLANID_PATTERN}
# for firewall status
uri = path + '/module/status'
mapper.connect('firewall', uri,
controller=FirewallController, action='get_status',
conditions=dict(method=['GET']))
uri = path + '/module/enable/{switchid}'
mapper.connect('firewall', uri,
controller=FirewallController, action='set_enable',
conditions=dict(method=['PUT']),
requirements=requirements)
uri = path + '/module/disable/{switchid}'
mapper.connect('firewall', uri,
controller=FirewallController, action='set_disable',
conditions=dict(method=['PUT']),
requirements=requirements)
# for firewall logs
uri = path + '/log/status'
mapper.connect('firewall', uri,
controller=FirewallController, action='get_log_status',
conditions=dict(method=['GET']))
uri = path + '/log/enable/{switchid}'
mapper.connect('firewall', uri,
controller=FirewallController, action='set_log_enable',
conditions=dict(method=['PUT']),
requirements=requirements)
uri = path + '/log/disable/{switchid}'
mapper.connect('firewall', uri,
controller=FirewallController, action='set_log_disable',
conditions=dict(method=['PUT']),
requirements=requirements)
# for no VLAN data
uri = path + '/rules/{switchid}'
mapper.connect('firewall', uri,
controller=FirewallController, action='get_rules',
conditions=dict(method=['GET']),
requirements=requirements)
mapper.connect('firewall', uri,
controller=FirewallController, action='set_rule',
conditions=dict(method=['POST']),
requirements=requirements)
mapper.connect('firewall', uri,
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
dp = msg.datapath
if dp.id not in self.waiters:
return
if msg.xid not in self.waiters[dp.id]:
return
lock, msgs = self.waiters[dp.id][msg.xid]
msgs.append(msg)
flags = 0
if dp.ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION or \
dp.ofproto.OFP_VERSION == ofproto_v1_2.OFP_VERSION:
flags = dp.ofproto.OFPSF_REPLY_MORE
elif dp.ofproto.OFP_VERSION == ofproto_v1_3.OFP_VERSION:
flags = dp.ofproto.OFPMPF_REPLY_MORE
if msg.flags & flags:
return
del self.waiters[dp.id][msg.xid]
lock.set()
@set_ev_cls(dpset.EventDP, dpset.DPSET_EV_DISPATCHER)
def handler_datapath(self, ev):
if ev.enter:
FirewallController.regist_ofs(ev.dp)
else:
FirewallController.unregist_ofs(ev.dp)
# for OpenFlow version1.0
@set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER)
def stats_reply_handler_v1_0(self, ev):
self.stats_reply_handler(ev)
# for OpenFlow version1.2 or later
@set_ev_cls(ofp_event.EventOFPStatsReply, MAIN_DISPATCHER)
def stats_reply_handler_v1_2(self, ev):
self.stats_reply_handler(ev)
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def packet_in_handler(self, ev):
FirewallController.packet_in_handler(ev.msg)
class FirewallOfsList(dict):
def __init__(self):
super(FirewallOfsList, self).__init__()
def get_ofs(self, dp_id):
if len(self) == 0:
raise ValueError('firewall sw is not connected.')
dps = {}
if dp_id == REST_ALL:
dps = self
else:
try:
dpid = dpid_lib.str_to_dpid(dp_id)
except:
raise ValueError('Invalid switchID.')
if dpid in self:
dps = {dpid: self[dpid]}
else:
msg = 'firewall sw is not connected. : switchID=%s' % dp_id
raise ValueError(msg)
return dps
class FirewallController(ControllerBase):
_OFS_LIST = FirewallOfsList()
_LOGGER = None
def __init__(self, req, link, data, **config):
super(FirewallController, self).__init__(req, link, data, **config)
self.dpset = data['dpset']
self.waiters = data['waiters']
@classmethod
def set_logger(cls, logger):
cls._LOGGER = logger
cls._LOGGER.propagate = False
hdlr = logging.StreamHandler()
fmt_str = '[FW][%(levelname)s] %(message)s'
hdlr.setFormatter(logging.Formatter(fmt_str))
cls._LOGGER.addHandler(hdlr)
@staticmethod
def regist_ofs(dp):
dpid_str = dpid_lib.dpid_to_str(dp.id)
try:
f_ofs = Firewall(dp)
except OFPUnknownVersion as message:
FirewallController._LOGGER.info('dpid=%s: %s',
dpid_str, message)
return
FirewallController._OFS_LIST.setdefault(dp.id, f_ofs)
f_ofs.set_disable_flow()
f_ofs.set_arp_flow()
f_ofs.set_log_enable()
FirewallController._LOGGER.info('dpid=%s: Join as firewall.',
dpid_str)
@staticmethod
def unregist_ofs(dp):
if dp.id in FirewallController._OFS_LIST:
del FirewallController._OFS_LIST[dp.id]
FirewallController._LOGGER.info('dpid=%s: Leave firewall.',
dpid_lib.dpid_to_str(dp.id))
# GET /firewall/module/status
def get_status(self, req, **_kwargs):
return self._access_module(REST_ALL, 'get_status',
waiters=self.waiters)
# POST /firewall/module/enable/{switchid}
def set_enable(self, req, switchid, **_kwargs):
return self._access_module(switchid, 'set_enable_flow')
# POST /firewall/module/disable/{switchid}
def set_disable(self, req, switchid, **_kwargs):
return self._access_module(switchid, 'set_disable_flow')
# GET /firewall/log/status
def get_log_status(self, dummy, **_kwargs):
return self._access_module(REST_ALL, 'get_log_status',
waiters=self.waiters)
# PUT /firewall/log/enable/{switchid}
def set_log_enable(self, dummy, switchid, **_kwargs):
return self._access_module(switchid, 'set_log_enable',
waiters=self.waiters)
# PUT /firewall/log/disable/{switchid}
def set_log_disable(self, dummy, switchid, **_kwargs):
return self._access_module(switchid, 'set_log_disable',
waiters=self.waiters)
def _access_module(self, switchid, func, waiters=None):
try:
dps = self._OFS_LIST.get_ofs(switchid)
except ValueError as message:
return Response(status=400, body=str(message))
msgs = []
for f_ofs in dps.values():
function = getattr(f_ofs, func)
msg = function() if waiters is None else function(waiters)
msgs.append(msg)
body = json.dumps(msgs)
return Response(content_type='application/json', body=body)
# GET /firewall/rules/{switchid}
def get_rules(self, req, switchid, **_kwargs):
return self._get_rules(switchid)
# GET /firewall/rules/{switchid}/{vlanid}
def get_vlan_rules(self, req, 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, switchid, vlan_id=VLANID_NONE):
try:
dps = self._OFS_LIST.get_ofs(switchid)
vid = FirewallController._conv_toint_vlanid(vlan_id)
except ValueError as message:
return Response(status=400, body=str(message))
msgs = []
for f_ofs in dps.values():
rules = f_ofs.get_rules(self.waiters, vid)
msgs.append(rules)
body = json.dumps(msgs)
return Response(content_type='application/json', body=body)
def _set_rule(self, req, switchid, vlan_id=VLANID_NONE):
try:
rule = json.loads(req.body)
except SyntaxError:
FirewallController._LOGGER.debug('invalid syntax %s', req.body)
return Response(status=400)
try:
dps = self._OFS_LIST.get_ofs(switchid)
vid = FirewallController._conv_toint_vlanid(vlan_id)
except ValueError as message:
return Response(status=400, body=str(message))
msgs = []
for f_ofs in dps.values():
try:
msg = f_ofs.set_rule(rule, self.waiters, vid)
msgs.append(msg)
except ValueError as message:
return Response(status=400, body=str(message))
body = json.dumps(msgs)
return Response(content_type='application/json', body=body)
def _delete_rule(self, req, switchid, vlan_id=VLANID_NONE):
try:
ruleid = json.loads(req.body)
except SyntaxError:
FirewallController._LOGGER.debug('invalid syntax %s', req.body)
return Response(status=400)
try:
dps = self._OFS_LIST.get_ofs(switchid)
vid = FirewallController._conv_toint_vlanid(vlan_id)
except ValueError as message:
return Response(status=400, body=str(message))
msgs = []
for f_ofs in dps.values():
try:
msg = f_ofs.delete_rule(ruleid, self.waiters, vid)
msgs.append(msg)
except ValueError as message:
return Response(status=400, body=str(message))
body = json.dumps(msgs)
return Response(content_type='application/json', body=body)
@staticmethod
def _conv_toint_vlanid(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
@staticmethod
def packet_in_handler(msg):
pkt = packet.Packet(msg.data)
dpid_str = dpid_lib.dpid_to_str(msg.datapath.id)
FirewallController._LOGGER.info('dpid=%s: Blocked packet = %s',
dpid_str, pkt)
class Firewall(object):
_OFCTL = {ofproto_v1_0.OFP_VERSION: ofctl_v1_0,
ofproto_v1_2.OFP_VERSION: ofctl_v1_2,
ofproto_v1_3.OFP_VERSION: ofctl_v1_3}
def __init__(self, dp):
super(Firewall, self).__init__()
self.vlan_list = {}
self.vlan_list[VLANID_NONE] = 0 # for VLAN=None
self.dp = dp
version = dp.ofproto.OFP_VERSION
if version not in self._OFCTL:
raise OFPUnknownVersion(version=version)
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_3_parser.UINT32_MAX
cookie = (vlan_id << COOKIE_SHIFT_VLANID) + \
self.vlan_list[vlan_id]
cookie_list.append([cookie, vlan_id])
return cookie_list
@staticmethod
def _cookie_to_ruleid(cookie):
return cookie & ofproto_v1_3_parser.UINT32_MAX
# REST command template
def rest_command(func):
def _rest_command(*args, **kwargs):
key, value = func(*args, **kwargs)
switch_id = dpid_lib.dpid_to_str(args[0].dp.id)
return {REST_SWITCHID: switch_id,
key: value}
return _rest_command
@rest_command
def get_status(self, waiters):
msgs = self.ofctl.get_flow_stats(self.dp, waiters)
status = REST_STATUS_ENABLE
if str(self.dp.id) in msgs:
flow_stats = msgs[str(self.dp.id)]
for flow_stat in flow_stats:
if flow_stat['priority'] == STATUS_FLOW_PRIORITY:
status = REST_STATUS_DISABLE
return REST_STATUS, status
@rest_command
def set_disable_flow(self):
cookie = 0
priority = STATUS_FLOW_PRIORITY
match = {}
actions = []
flow = self._to_of_flow(cookie=cookie, priority=priority,
match=match, actions=actions)
cmd = self.dp.ofproto.OFPFC_ADD
self.ofctl.mod_flow_entry(self.dp, flow, cmd)
msg = {'result': 'success',
'details': 'firewall stopped.'}
return REST_COMMAND_RESULT, msg
@rest_command
def set_enable_flow(self):
cookie = 0
priority = STATUS_FLOW_PRIORITY
match = {}
actions = []
flow = self._to_of_flow(cookie=cookie, priority=priority,
match=match, actions=actions)
cmd = self.dp.ofproto.OFPFC_DELETE_STRICT
self.ofctl.mod_flow_entry(self.dp, flow, cmd)
msg = {'result': 'success',
'details': 'firewall running.'}
return REST_COMMAND_RESULT, msg
@rest_command
def get_log_status(self, waiters):
msgs = self.ofctl.get_flow_stats(self.dp, waiters)
status = REST_STATUS_DISABLE
if str(self.dp.id) in msgs:
flow_stats = msgs[str(self.dp.id)]
for flow_stat in flow_stats:
if flow_stat['priority'] == LOG_FLOW_PRIORITY:
if flow_stat['actions']:
status = REST_STATUS_ENABLE
return REST_LOG_STATUS, status
@rest_command
def set_log_disable(self, waiters=None):
return self._set_log_status(False, waiters)
@rest_command
def set_log_enable(self, waiters=None):
return self._set_log_status(True, waiters)
def _set_log_status(self, is_enable, waiters):
if is_enable:
actions = Action.to_openflow(self.dp,
{REST_ACTION: REST_ACTION_PACKETIN})
details = 'Log collection started.'
else:
actions = []
details = 'Log collection stopped.'
cmd = self.dp.ofproto.OFPFC_ADD
if waiters:
msgs = self.ofctl.get_flow_stats(self.dp, waiters)
if str(self.dp.id) in msgs:
flow_stats = msgs[str(self.dp.id)]
for flow_stat in flow_stats:
priority = flow_stat[REST_PRIORITY]
if (priority == STATUS_FLOW_PRIORITY
or priority == ARP_FLOW_PRIORITY):
continue
action = flow_stat[REST_ACTION]
if action == ['OUTPUT:%d' % self.dp.ofproto.OFPP_NORMAL]:
continue
cookie = flow_stat[REST_COOKIE]
match = Match.to_mod_openflow(flow_stat[REST_MATCH])
flow = self._to_of_flow(cookie=cookie, priority=priority,
match=match, actions=actions)
self.ofctl.mod_flow_entry(self.dp, flow, cmd)
else:
# Initialize.
flow = self._to_of_flow(cookie=0, priority=LOG_FLOW_PRIORITY,
match={}, actions=actions)
self.ofctl.mod_flow_entry(self.dp, flow, cmd)
msg = {'result': 'success',
'details': details}
return REST_COMMAND_RESULT, msg
def set_arp_flow(self):
cookie = 0
priority = ARP_FLOW_PRIORITY
match = {REST_DL_TYPE: ether.ETH_TYPE_ARP}
action = {REST_ACTION: REST_ACTION_ALLOW}
actions = Action.to_openflow(self.dp, action)
flow = self._to_of_flow(cookie=cookie, priority=priority,
match=match, actions=actions)
cmd = self.dp.ofproto.OFPFC_ADD
self.ofctl.mod_flow_entry(self.dp, flow, cmd)
@rest_command
def set_rule(self, rest, waiters, vlan_id):
msgs = []
cookie_list = self._get_cookie(vlan_id)
for cookie, vid in cookie_list:
msg = self._set_rule(cookie, rest, waiters, vid)
msgs.append(msg)
return REST_COMMAND_RESULT, msgs
def _set_rule(self, cookie, rest, waiters, vlan_id):
priority = int(rest.get(REST_PRIORITY, ACL_FLOW_PRIORITY_MIN))
if (priority < ACL_FLOW_PRIORITY_MIN
or ACL_FLOW_PRIORITY_MAX < priority):
raise ValueError('Invalid priority value. Set [%d-%d]'
% (ACL_FLOW_PRIORITY_MIN, ACL_FLOW_PRIORITY_MAX))
if vlan_id:
rest[REST_DL_VLAN] = vlan_id
match = Match.to_openflow(rest)
if rest.get(REST_ACTION) == REST_ACTION_DENY:
result = self.get_log_status(waiters)
if result[REST_LOG_STATUS] == REST_STATUS_ENABLE:
rest[REST_ACTION] = REST_ACTION_PACKETIN
actions = Action.to_openflow(self.dp, rest)
flow = self._to_of_flow(cookie=cookie, priority=priority,
match=match, actions=actions)
cmd = self.dp.ofproto.OFPFC_ADD
try:
self.ofctl.mod_flow_entry(self.dp, flow, cmd)
except:
raise ValueError('Invalid rule parameter.')
rule_id = Firewall._cookie_to_ruleid(cookie)
msg = {'result': 'success',
'details': 'Rule added. : rule_id=%d' % rule_id}
if vlan_id != VLANID_NONE:
msg.setdefault(REST_VLANID, vlan_id)
return msg
@rest_command
def get_rules(self, waiters, vlan_id):
rules = {}
msgs = self.ofctl.get_flow_stats(self.dp, waiters)
if str(self.dp.id) in msgs:
flow_stats = msgs[str(self.dp.id)]
for flow_stat in flow_stats:
priority = flow_stat[REST_PRIORITY]
if (priority != STATUS_FLOW_PRIORITY
and priority != ARP_FLOW_PRIORITY
and priority != LOG_FLOW_PRIORITY):
vid = flow_stat[REST_MATCH].get(REST_DL_VLAN, VLANID_NONE)
if vlan_id == REST_ALL or vlan_id == vid:
rule = self._to_rest_rule(flow_stat)
rules.setdefault(vid, [])
rules[vid].append(rule)
get_data = []
for vid, rule in rules.items():
if vid == VLANID_NONE:
vid_data = {REST_RULES: rule}
else:
vid_data = {REST_VLANID: vid, REST_RULES: rule}
get_data.append(vid_data)
return REST_ACL, get_data
@rest_command
def delete_rule(self, rest, waiters, vlan_id):
try:
if rest[REST_RULE_ID] == REST_ALL:
rule_id = REST_ALL
else:
rule_id = int(rest[REST_RULE_ID])
except:
raise ValueError('Invalid ruleID.')
vlan_list = []
delete_list = []
msgs = self.ofctl.get_flow_stats(self.dp, waiters)
if str(self.dp.id) in msgs:
flow_stats = msgs[str(self.dp.id)]
for flow_stat in flow_stats:
cookie = flow_stat[REST_COOKIE]
ruleid = Firewall._cookie_to_ruleid(cookie)
priority = flow_stat[REST_PRIORITY]
dl_vlan = flow_stat[REST_MATCH].get(REST_DL_VLAN, VLANID_NONE)
if (priority != STATUS_FLOW_PRIORITY
and priority != ARP_FLOW_PRIORITY
and priority != LOG_FLOW_PRIORITY):
if ((rule_id == REST_ALL or rule_id == ruleid) and
(vlan_id == dl_vlan or vlan_id == REST_ALL)):
match = Match.to_mod_openflow(flow_stat[REST_MATCH])
delete_list.append([cookie, priority, match])
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.'
if rule_id != REST_ALL:
msg_details += ' : ruleID=%d' % rule_id
msg = {'result': 'failure',
'details': msg_details}
else:
cmd = self.dp.ofproto.OFPFC_DELETE_STRICT
actions = []
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)
vid = match.get(REST_DL_VLAN, VLANID_NONE)
rule_id = Firewall._cookie_to_ruleid(cookie)
delete_ids.setdefault(vid, '')
delete_ids[vid] += (('%d' if delete_ids[vid] == ''
else ',%d') % 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:
del_msg.setdefault(REST_VLANID, vid)
msg.append(del_msg)
return REST_COMMAND_RESULT, msg
def _to_of_flow(self, cookie, priority, match, actions):
flow = {'cookie': cookie,
'priority': priority,
'flags': 0,
'idle_timeout': 0,
'hard_timeout': 0,
'match': match,
'actions': actions}
return flow
def _to_rest_rule(self, flow):
ruleid = Firewall._cookie_to_ruleid(flow[REST_COOKIE])
rule = {REST_RULE_ID: ruleid}
rule.update({REST_PRIORITY: flow[REST_PRIORITY]})
rule.update(Match.to_rest(flow))
rule.update(Action.to_rest(self.dp, flow))
return rule
class Match(object):
_CONVERT = {REST_DL_TYPE:
{REST_DL_TYPE_ARP: ether.ETH_TYPE_ARP,
REST_DL_TYPE_IPV4: ether.ETH_TYPE_IP,
REST_DL_TYPE_IPV6: ether.ETH_TYPE_IPV6},
REST_NW_PROTO:
{REST_NW_PROTO_TCP: inet.IPPROTO_TCP,
REST_NW_PROTO_UDP: inet.IPPROTO_UDP,
REST_NW_PROTO_ICMP: inet.IPPROTO_ICMP,
REST_NW_PROTO_ICMPV6: inet.IPPROTO_ICMPV6}}
_MATCHES = [REST_IN_PORT,
REST_SRC_MAC,
REST_DST_MAC,
REST_DL_TYPE,
REST_DL_VLAN,
REST_SRC_IP,
REST_DST_IP,
REST_SRC_IPV6,
REST_DST_IPV6,
REST_NW_PROTO,
REST_TP_SRC,
REST_TP_DST]
@staticmethod
def to_openflow(rest):
def __inv_combi(msg):
raise ValueError('Invalid combination: [%s]' % msg)
def __inv_2and1(*args):
__inv_combi('%s=%s and %s' % (args[0], args[1], args[2]))
def __inv_2and2(*args):
__inv_combi('%s=%s and %s=%s' % (
args[0], args[1], args[2], args[3]))
def __inv_1and1(*args):
__inv_combi('%s and %s' % (args[0], args[1]))
def __inv_1and2(*args):
__inv_combi('%s and %s=%s' % (args[0], args[1], args[2]))
match = {}
# error check
dl_type = rest.get(REST_DL_TYPE)
nw_proto = rest.get(REST_NW_PROTO)
if dl_type is not None:
if dl_type == REST_DL_TYPE_ARP:
if REST_SRC_IPV6 in rest:
__inv_2and1(
REST_DL_TYPE, REST_DL_TYPE_ARP, REST_SRC_IPV6)
if REST_DST_IPV6 in rest:
__inv_2and1(
REST_DL_TYPE, REST_DL_TYPE_ARP, REST_DST_IPV6)
if nw_proto:
__inv_2and1(
REST_DL_TYPE, REST_DL_TYPE_ARP, REST_NW_PROTO)
elif dl_type == REST_DL_TYPE_IPV4:
if REST_SRC_IPV6 in rest:
__inv_2and1(
REST_DL_TYPE, REST_DL_TYPE_IPV4, REST_SRC_IPV6)
if REST_DST_IPV6 in rest:
__inv_2and1(
REST_DL_TYPE, REST_DL_TYPE_IPV4, REST_DST_IPV6)
if nw_proto == REST_NW_PROTO_ICMPV6:
__inv_2and2(
REST_DL_TYPE, REST_DL_TYPE_IPV4,
REST_NW_PROTO, REST_NW_PROTO_ICMPV6)
elif dl_type == REST_DL_TYPE_IPV6:
if REST_SRC_IP in rest:
__inv_2and1(
REST_DL_TYPE, REST_DL_TYPE_IPV6, REST_SRC_IP)
if REST_DST_IP in rest:
__inv_2and1(
REST_DL_TYPE, REST_DL_TYPE_IPV6, REST_DST_IP)
if nw_proto == REST_NW_PROTO_ICMP:
__inv_2and2(
REST_DL_TYPE, REST_DL_TYPE_IPV6,
REST_NW_PROTO, REST_NW_PROTO_ICMP)
else:
raise ValueError('Unknown dl_type : %s' % dl_type)
else:
if REST_SRC_IP in rest:
if REST_SRC_IPV6 in rest:
__inv_1and1(REST_SRC_IP, REST_SRC_IPV6)
if REST_DST_IPV6 in rest:
__inv_1and1(REST_SRC_IP, REST_DST_IPV6)
if nw_proto == REST_NW_PROTO_ICMPV6:
__inv_1and2(
REST_SRC_IP, REST_NW_PROTO, REST_NW_PROTO_ICMPV6)
rest[REST_DL_TYPE] = REST_DL_TYPE_IPV4
elif REST_DST_IP in rest:
if REST_SRC_IPV6 in rest:
__inv_1and1(REST_DST_IP, REST_SRC_IPV6)
if REST_DST_IPV6 in rest:
__inv_1and1(REST_DST_IP, REST_DST_IPV6)
if nw_proto == REST_NW_PROTO_ICMPV6:
__inv_1and2(
REST_DST_IP, REST_NW_PROTO, REST_NW_PROTO_ICMPV6)
rest[REST_DL_TYPE] = REST_DL_TYPE_IPV4
elif REST_SRC_IPV6 in rest:
if nw_proto == REST_NW_PROTO_ICMP:
__inv_1and2(
REST_SRC_IPV6, REST_NW_PROTO, REST_NW_PROTO_ICMP)
rest[REST_DL_TYPE] = REST_DL_TYPE_IPV6
elif REST_DST_IPV6 in rest:
if nw_proto == REST_NW_PROTO_ICMP:
__inv_1and2(
REST_DST_IPV6, REST_NW_PROTO, REST_NW_PROTO_ICMP)
rest[REST_DL_TYPE] = REST_DL_TYPE_IPV6
else:
if nw_proto == REST_NW_PROTO_ICMP:
rest[REST_DL_TYPE] = REST_DL_TYPE_IPV4
elif nw_proto == REST_NW_PROTO_ICMPV6:
rest[REST_DL_TYPE] = REST_DL_TYPE_IPV6
elif nw_proto == REST_NW_PROTO_TCP or \
nw_proto == REST_NW_PROTO_UDP:
raise ValueError('no dl_type was specified')
else:
raise ValueError('Unknown nw_proto: %s' % nw_proto)
for key, value in rest.items():
if key in Match._CONVERT:
if value in Match._CONVERT[key]:
match.setdefault(key, Match._CONVERT[key][value])
else:
raise ValueError('Invalid rule parameter. : key=%s' % key)
elif key in Match._MATCHES:
match.setdefault(key, value)
return match
@staticmethod
def to_rest(openflow):
of_match = openflow[REST_MATCH]
mac_dontcare = mac.haddr_to_str(mac.DONTCARE)
ip_dontcare = '0.0.0.0'
ipv6_dontcare = '::'
match = {}
for key, value in of_match.items():
if key == REST_SRC_MAC or key == REST_DST_MAC:
if value == mac_dontcare:
continue
elif key == REST_SRC_IP or key == REST_DST_IP:
if value == ip_dontcare:
continue
elif key == REST_SRC_IPV6 or key == REST_DST_IPV6:
if value == ipv6_dontcare:
continue
elif value == 0:
continue
if key in Match._CONVERT:
conv = Match._CONVERT[key]
conv = dict((value, key) for key, value in conv.items())
match.setdefault(key, conv[value])
else:
match.setdefault(key, value)
return match
@staticmethod
def to_mod_openflow(of_match):
mac_dontcare = mac.haddr_to_str(mac.DONTCARE)
ip_dontcare = '0.0.0.0'
ipv6_dontcare = '::'
match = {}
for key, value in of_match.items():
if key == REST_SRC_MAC or key == REST_DST_MAC:
if value == mac_dontcare:
continue
elif key == REST_SRC_IP or key == REST_DST_IP:
if value == ip_dontcare:
continue
elif key == REST_SRC_IPV6 or key == REST_DST_IPV6:
if value == ipv6_dontcare:
continue
elif value == 0:
continue
match.setdefault(key, value)
return match
class Action(object):
@staticmethod
def to_openflow(dp, rest):
value = rest.get(REST_ACTION, REST_ACTION_ALLOW)
if value == REST_ACTION_ALLOW:
out_port = dp.ofproto.OFPP_NORMAL
action = [{'type': 'OUTPUT',
'port': out_port}]
elif value == REST_ACTION_DENY:
action = []
elif value == REST_ACTION_PACKETIN:
out_port = dp.ofproto.OFPP_CONTROLLER
action = [{'type': 'OUTPUT',
'port': out_port,
'max_len': 128}]
else:
raise ValueError('Invalid action type.')
return action
@staticmethod
def to_rest(dp, openflow):
if REST_ACTION in openflow:
action_allow = 'OUTPUT:%d' % dp.ofproto.OFPP_NORMAL
if openflow[REST_ACTION] == [action_allow]:
action = {REST_ACTION: REST_ACTION_ALLOW}
else:
action = {REST_ACTION: REST_ACTION_DENY}
else:
action = {REST_ACTION: 'Unknown action type.'}
return action