summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ryu/app/rest_router.py1831
1 files changed, 1831 insertions, 0 deletions
diff --git a/ryu/app/rest_router.py b/ryu/app/rest_router.py
new file mode 100644
index 00000000..230fc081
--- /dev/null
+++ b/ryu/app/rest_router.py
@@ -0,0 +1,1831 @@
+# 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 socket
+import struct
+
+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 dpset
+from ryu.controller import ofp_event
+from ryu.controller.handler import set_ev_cls
+from ryu.controller.handler import MAIN_DISPATCHER
+from ryu.exception import OFPUnknownVersion
+from ryu.exception import RyuException
+from ryu.lib import dpid as dpid_lib
+from ryu.lib import hub
+from ryu.lib import mac as mac_lib
+from ryu.lib.packet import arp
+from ryu.lib.packet import ethernet
+from ryu.lib.packet import icmp
+from ryu.lib.packet import ipv4
+from ryu.lib.packet import packet
+from ryu.lib.packet import tcp
+from ryu.lib.packet import udp
+from ryu.lib.packet import vlan
+from ryu.ofproto import ether
+from ryu.ofproto import inet
+from ryu.ofproto import ofproto_v1_0
+from ryu.ofproto import ofproto_v1_2
+
+
+#=============================
+# REST API
+#=============================
+#
+# Note: specify switch and vlan group, as follows.
+# {switch_id} : 'all' or switchID
+# {vlan_id} : 'all' or vlanID
+#
+#
+## 1. get address data and routing data.
+#
+# * get data of no vlan
+# GET /router/{switch_id}
+#
+# * get data of specific vlan group
+# GET /router/{switch_id}/{vlan_id}
+#
+#
+## 2. set address data or routing data.
+#
+# * set data of no vlan
+# POST /router/{switch_id}
+#
+# * set data of specific vlan group
+# POST /router/{switch_id}/{vlan_id}
+#
+# case1: set address data.
+# parameter = {"address": "A.B.C.D/M"}
+# case2-1: set static route.
+# parameter = {"destination": "A.B.C.D/M", "gateway": "E.F.G.H"}
+# case2-2: set default route.
+# parameter = {"gateway": "E.F.G.H"}
+#
+#
+## 3. delete address data or routing data.
+#
+# * delete data of no vlan
+# DELETE /router/{switch_id}
+#
+# * delete data of specific vlan group
+# DELETE /router/{switch_id}/{vlan_id}
+#
+# case1: delete address data.
+# parameter = {"address_id": "<int>"} or {"address_id": "all"}
+# case2: delete routing data.
+# parameter = {"route_id": "<int>"} or {"route_id": "all"}
+#
+#
+
+
+UINT16_MAX = 0xffff
+UINT32_MAX = 0xffffffff
+UINT64_MAX = 0xffffffffffffffff
+
+ETHERNET = ethernet.ethernet.__name__
+VLAN = vlan.vlan.__name__
+IPV4 = ipv4.ipv4.__name__
+ARP = arp.arp.__name__
+ICMP = icmp.icmp.__name__
+TCP = tcp.tcp.__name__
+UDP = udp.udp.__name__
+
+MAX_SUSPENDPACKETS = 50 # Threshold of the packet suspends thread count.
+
+ARP_REPLY_TIMER = 2 # sec
+OFP_REPLY_TIMER = 1.0 # sec
+CHK_ROUTING_TBL_INTERVAL = 1800 # sec
+
+SWITCHID_PATTERN = dpid_lib.DPID_PATTERN + r'|all'
+VLANID_PATTERN = r'[0-9]{1,4}|all'
+
+VLANID_NONE = 0
+VLANID_MIN = 2
+VLANID_MAX = 4094
+
+COOKIE_DEFAULT_ID = 0
+COOKIE_SHIFT_VLANID = 32
+COOKIE_SHIFT_ROUTEID = 16
+
+DEFAULT_ROUTE = '0.0.0.0/0'
+IDLE_TIMEOUT = 1800 # sec
+DEFAULT_TTL = 64
+
+REST_COMMAND_RESULT = 'command_result'
+REST_RESULT = 'result'
+REST_DETAILS = 'details'
+REST_OK = 'success'
+REST_NG = 'failure'
+REST_ALL = 'all'
+REST_SWITCHID = 'switch_id'
+REST_VLANID = 'vlan_id'
+REST_NW = 'internal_network'
+REST_ADDRESSID = 'address_id'
+REST_ADDRESS = 'address'
+REST_ROUTEID = 'route_id'
+REST_ROUTE = 'route'
+REST_DESTINATION = 'destination'
+REST_GATEWAY = 'gateway'
+
+PRIORITY_VLAN_SHIFT = 1000
+PRIORITY_NETMASK_SHIFT = 32
+
+PRIORITY_NORMAL = 0
+PRIORITY_ARP_HANDLING = 1
+PRIORITY_DEFAULT_ROUTING = 1
+PRIORITY_MAC_LEARNING = 2
+PRIORITY_STATIC_ROUTING = 2
+PRIORITY_IMPLICIT_ROUTING = 3
+PRIORITY_L2_SWITCHING = 4
+PRIORITY_IP_HANDLING = 5
+
+PRIORITY_TYPE_ROUTE = 'priority_route'
+
+
+def get_priority(priority_type, vid=0, route=None):
+ log_msg = None
+ priority = priority_type
+
+ if priority_type == PRIORITY_TYPE_ROUTE:
+ assert route is not None
+ if route.dst_ip:
+ priority_type = PRIORITY_STATIC_ROUTING
+ priority = priority_type + route.netmask
+ log_msg = 'static routing'
+ else:
+ priority_type = PRIORITY_DEFAULT_ROUTING
+ priority = priority_type
+ log_msg = 'default routing'
+
+ if vid or priority_type == PRIORITY_IP_HANDLING:
+ priority += PRIORITY_VLAN_SHIFT
+
+ if priority_type > PRIORITY_STATIC_ROUTING:
+ priority += PRIORITY_NETMASK_SHIFT
+
+ if log_msg is None:
+ return priority
+ else:
+ return priority, log_msg
+
+
+def get_priority_type(priority, vid):
+ if vid:
+ priority -= PRIORITY_VLAN_SHIFT
+ return priority
+
+
+class NotFoundError(RyuException):
+ message = 'Router SW is not connected. : switch_id=%(switch_id)s'
+
+
+class CommandFailure(RyuException):
+ pass
+
+
+class RestRouterAPI(app_manager.RyuApp):
+
+ OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION,
+ ofproto_v1_2.OFP_VERSION]
+
+ _CONTEXTS = {'dpset': dpset.DPSet,
+ 'wsgi': WSGIApplication}
+
+ def __init__(self, *args, **kwargs):
+ super(RestRouterAPI, self).__init__(*args, **kwargs)
+
+ # logger configure
+ RouterController.set_logger(self.logger)
+
+ wsgi = kwargs['wsgi']
+ self.waiters = {}
+ self.data = {'waiters': self.waiters}
+
+ mapper = wsgi.mapper
+ wsgi.registory['RouterController'] = self.data
+ requirements = {'switch_id': SWITCHID_PATTERN,
+ 'vlan_id': VLANID_PATTERN}
+
+ # For no vlan data
+ path = '/router/{switch_id}'
+ mapper.connect('router', path, controller=RouterController,
+ requirements=requirements,
+ action='get_data',
+ conditions=dict(method=['GET']))
+ mapper.connect('router', path, controller=RouterController,
+ requirements=requirements,
+ action='set_data',
+ conditions=dict(method=['POST']))
+ mapper.connect('router', path, controller=RouterController,
+ requirements=requirements,
+ action='delete_data',
+ conditions=dict(method=['DELETE']))
+ # For vlan data
+ path = '/router/{switch_id}/{vlan_id}'
+ mapper.connect('router', path, controller=RouterController,
+ requirements=requirements,
+ action='get_vlan_data',
+ conditions=dict(method=['GET']))
+ mapper.connect('router', path, controller=RouterController,
+ requirements=requirements,
+ action='set_vlan_data',
+ conditions=dict(method=['POST']))
+ mapper.connect('router', path, controller=RouterController,
+ requirements=requirements,
+ action='delete_vlan_data',
+ conditions=dict(method=['DELETE']))
+
+ @set_ev_cls(dpset.EventDP, dpset.DPSET_EV_DISPATCHER)
+ def datapath_handler(self, ev):
+ if ev.enter:
+ RouterController.register_router(ev.dp)
+ else:
+ RouterController.unregister_router(ev.dp)
+
+ @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
+ def packet_in_handler(self, ev):
+ RouterController.packet_in_handler(ev.msg)
+
+ def _stats_reply_handler(self, ev):
+ msg = ev.msg
+ dp = msg.datapath
+
+ if (dp.id not in self.waiters
+ or msg.xid not in self.waiters[dp.id]):
+ return
+ event, msgs = self.waiters[dp.id][msg.xid]
+ msgs.append(msg)
+
+ if msg.flags & dp.ofproto.OFPSF_REPLY_MORE:
+ return
+ del self.waiters[dp.id][msg.xid]
+ event.set()
+
+ # 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
+ @set_ev_cls(ofp_event.EventOFPStatsReply, MAIN_DISPATCHER)
+ def stats_reply_handler_v1_2(self, ev):
+ self._stats_reply_handler(ev)
+
+ #TODO: Update routing table when port status is changed.
+
+
+# REST command template
+def rest_command(func):
+ def _rest_command(*args, **kwargs):
+ try:
+ msg = func(*args, **kwargs)
+ return Response(content_type='application/json',
+ body=json.dumps(msg))
+
+ except SyntaxError as e:
+ status = 400
+ details = e.msg
+ except (ValueError, NameError) as e:
+ status = 400
+ details = e.message
+
+ except NotFoundError as msg:
+ status = 404
+ details = str(msg)
+
+ msg = {REST_RESULT: REST_NG,
+ REST_DETAILS: details}
+ return Response(status=status, body=json.dumps(msg))
+
+ return _rest_command
+
+
+class RouterController(ControllerBase):
+
+ _ROUTER_LIST = {}
+ _LOGGER = None
+
+ def __init__(self, req, link, data, **config):
+ super(RouterController, self).__init__(req, link, data, **config)
+ self.waiters = data['waiters']
+
+ @classmethod
+ def set_logger(cls, logger):
+ cls._LOGGER = logger
+ cls._LOGGER.propagate = False
+ hdlr = logging.StreamHandler()
+ fmt_str = '[RT][%(levelname)s] switch_id=%(sw_id)s: %(message)s'
+ hdlr.setFormatter(logging.Formatter(fmt_str))
+ cls._LOGGER.addHandler(hdlr)
+
+ @classmethod
+ def register_router(cls, dp):
+ dpid = {'sw_id': dpid_lib.dpid_to_str(dp.id)}
+ try:
+ router = Router(dp, cls._LOGGER)
+ except OFPUnknownVersion as message:
+ cls._LOGGER.error(str(message), extra=dpid)
+ return
+ cls._ROUTER_LIST.setdefault(dp.id, router)
+ cls._LOGGER.info('Join as router.', extra=dpid)
+
+ @classmethod
+ def unregister_router(cls, dp):
+ if dp.id in cls._ROUTER_LIST:
+ cls._ROUTER_LIST[dp.id].delete()
+ del cls._ROUTER_LIST[dp.id]
+
+ dpid = {'sw_id': dpid_lib.dpid_to_str(dp.id)}
+ cls._LOGGER.info('Leave router.', extra=dpid)
+
+ @classmethod
+ def packet_in_handler(cls, msg):
+ dp_id = msg.datapath.id
+ if dp_id in cls._ROUTER_LIST:
+ router = cls._ROUTER_LIST[dp_id]
+ router.packet_in_handler(msg)
+
+ # GET /router/{switch_id}
+ @rest_command
+ def get_data(self, req, switch_id, **_kwargs):
+ return self._access_router(switch_id, VLANID_NONE,
+ 'get_data', req.body)
+
+ # GET /router/{switch_id}/{vlan_id}
+ @rest_command
+ def get_vlan_data(self, req, switch_id, vlan_id, **_kwargs):
+ return self._access_router(switch_id, vlan_id,
+ 'get_data', req.body)
+
+ # POST /router/{switch_id}
+ @rest_command
+ def set_data(self, req, switch_id, **_kwargs):
+ return self._access_router(switch_id, VLANID_NONE,
+ 'set_data', req.body)
+
+ # POST /router/{switch_id}/{vlan_id}
+ @rest_command
+ def set_vlan_data(self, req, switch_id, vlan_id, **_kwargs):
+ return self._access_router(switch_id, vlan_id,
+ 'set_data', req.body)
+
+ # DELETE /router/{switch_id}
+ @rest_command
+ def delete_data(self, req, switch_id, **_kwargs):
+ return self._access_router(switch_id, VLANID_NONE,
+ 'delete_data', req.body)
+
+ # DELETE /router/{switch_id}/{vlan_id}
+ @rest_command
+ def delete_vlan_data(self, req, switch_id, vlan_id, **_kwargs):
+ return self._access_router(switch_id, vlan_id,
+ 'delete_data', req.body)
+
+ def _access_router(self, switch_id, vlan_id, func, rest_param):
+ rest_message = []
+ routers = self._get_router(switch_id)
+ param = eval(rest_param) if rest_param else {}
+ for router in routers.values():
+ function = getattr(router, func)
+ data = function(vlan_id, param, self.waiters)
+ rest_message.append(data)
+
+ return rest_message
+
+ def _get_router(self, switch_id):
+ routers = {}
+
+ if switch_id == REST_ALL:
+ routers = self._ROUTER_LIST
+ else:
+ sw_id = dpid_lib.str_to_dpid(switch_id)
+ if sw_id in self._ROUTER_LIST:
+ routers = {sw_id: self._ROUTER_LIST[sw_id]}
+
+ if routers:
+ return routers
+ else:
+ raise NotFoundError(switch_id=switch_id)
+
+
+class Router(dict):
+ def __init__(self, dp, logger):
+ super(Router, self).__init__()
+ self.dp = dp
+ self.dpid_str = dpid_lib.dpid_to_str(dp.id)
+ self.sw_id = {'sw_id': self.dpid_str}
+ self.logger = logger
+
+ self.port_data = PortData(dp.ports)
+
+ ofctl = OfCtl.factory(dp, logger)
+ cookie = COOKIE_DEFAULT_ID
+
+ # Set SW config: TTL error packet in (only OFPv1.2)
+ ofctl.set_sw_config_for_ttl()
+
+ # Set flow: ARP handling (packet in)
+ priority = get_priority(PRIORITY_ARP_HANDLING)
+ ofctl.set_packetin_flow(cookie, priority, dl_type=ether.ETH_TYPE_ARP)
+ self.logger.info('Set ARP handling (packet in) flow [cookie=0x%x]',
+ cookie, extra=self.sw_id)
+
+ # Set flow: L2 switching (normal)
+ priority = get_priority(PRIORITY_NORMAL)
+ ofctl.set_normal_flow(cookie, priority)
+ self.logger.info('Set L2 switching (normal) flow [cookie=0x%x]',
+ cookie, extra=self.sw_id)
+
+ # Set VlanRouter for vid=None.
+ vlan_router = VlanRouter(VLANID_NONE, dp, self.port_data, logger)
+ self[VLANID_NONE] = vlan_router
+
+ # Start cyclic routing table check.
+ self.thread = hub.spawn(self._cyclic_update_routing_tbl)
+ self.logger.info('Start cyclic routing table update.',
+ extra=self.sw_id)
+
+ def delete(self):
+ hub.kill(self.thread)
+ self.thread.wait()
+ self.logger.info('Stop cyclic routing table update.',
+ extra=self.sw_id)
+
+ def _get_vlan_router(self, vlan_id):
+ vlan_routers = []
+
+ if vlan_id == REST_ALL:
+ vlan_routers = self.values()
+ else:
+ 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]'
+ raise ValueError(msg % (VLANID_MIN, VLANID_MAX))
+ elif vlan_id in self:
+ vlan_routers = [self[vlan_id]]
+
+ return vlan_routers
+
+ def _add_vlan_router(self, vlan_id):
+ vlan_id = int(vlan_id)
+ if vlan_id not in self:
+ vlan_router = VlanRouter(vlan_id, self.dp, self.port_data,
+ self.logger)
+ self[vlan_id] = vlan_router
+ return self[vlan_id]
+
+ def _del_vlan_router(self, vlan_id, waiters):
+ # Remove unnecessary VlanRouter.
+ if vlan_id == VLANID_NONE:
+ return
+
+ vlan_router = self[vlan_id]
+ if (len(vlan_router.address_data) == 0
+ and len(vlan_router.routing_tbl) == 0):
+ vlan_router.delete(waiters)
+ del self[vlan_id]
+
+ def get_data(self, vlan_id, dummy1, dummy2):
+ vlan_routers = self._get_vlan_router(vlan_id)
+ if vlan_routers:
+ msgs = [vlan_router.get_data() for vlan_router in vlan_routers]
+ else:
+ msgs = [{REST_VLANID: vlan_id}]
+
+ return {REST_SWITCHID: self.dpid_str,
+ REST_NW: msgs}
+
+ def set_data(self, vlan_id, param, waiters):
+ vlan_routers = self._get_vlan_router(vlan_id)
+ if not vlan_routers:
+ vlan_routers = [self._add_vlan_router(vlan_id)]
+
+ msgs = []
+ for vlan_router in vlan_routers:
+ try:
+ msg = vlan_router.set_data(param)
+ msgs.append(msg)
+ if msg[REST_RESULT] == REST_NG:
+ # Data setting is failure.
+ self._del_vlan_router(vlan_router.vlan_id, waiters)
+ except Exception as err_msg:
+ # Data setting is failure.
+ self._del_vlan_router(vlan_router.vlan_id, waiters)
+ raise err_msg
+
+ return {REST_SWITCHID: self.dpid_str,
+ REST_COMMAND_RESULT: msgs}
+
+ def delete_data(self, vlan_id, param, waiters):
+ msgs = []
+ vlan_routers = self._get_vlan_router(vlan_id)
+ if vlan_routers:
+ for vlan_router in vlan_routers:
+ msg = vlan_router.delete_data(param, waiters)
+ if msg:
+ msgs.append(msg)
+ # Check unnecessary VlanRouter.
+ self._del_vlan_router(vlan_router.vlan_id, waiters)
+ if not msgs:
+ msgs = [{REST_RESULT: REST_NG,
+ REST_DETAILS: 'Data is nothing.'}]
+
+ return {REST_SWITCHID: self.dpid_str,
+ REST_COMMAND_RESULT: msgs}
+
+ def packet_in_handler(self, msg):
+ pkt = packet.Packet(msg.data)
+ #TODO: Packet library convert to string
+ #self.logger.debug('Packet in = %s', str(pkt), self.sw_id)
+ header_list = dict((p.protocol_name, p)
+ for p in pkt.protocols if type(p) != str)
+ if header_list:
+ # Check vlan-tag
+ vlan_id = VLANID_NONE
+ if VLAN in header_list:
+ vlan_id = header_list[VLAN].vid
+
+ # Event dispatch
+ if vlan_id in self:
+ self[vlan_id].packet_in_handler(msg, header_list)
+ else:
+ self.logger.debug('Drop unknown vlan packet. [vlan_id=%d]',
+ vlan_id, extra=self.sw_id)
+
+ def _cyclic_update_routing_tbl(self):
+ while True:
+ # send ARP to all gateways.
+ for vlan_router in self.values():
+ vlan_router.send_arp_all_gw()
+ hub.sleep(1)
+
+ hub.sleep(CHK_ROUTING_TBL_INTERVAL)
+
+
+class VlanRouter(object):
+ def __init__(self, vlan_id, dp, port_data, logger):
+ super(VlanRouter, self).__init__()
+ self.vlan_id = vlan_id
+ self.dp = dp
+ self.sw_id = {'sw_id': dpid_lib.dpid_to_str(dp.id)}
+ self.logger = logger
+
+ self.port_data = port_data
+ self.address_data = AddressData()
+ self.routing_tbl = RoutingTable()
+ self.packet_buffer = SuspendPacketList(self.send_icmp_unreach_error)
+ self.ofctl = OfCtl.factory(dp, logger)
+
+ # Set flow: default route (drop)
+ self._set_defaultroute_drop()
+
+ def delete(self, waiters):
+ # Delete flow.
+ msgs = self.ofctl.get_all_flow(waiters)
+ for msg in msgs:
+ for stats in msg.body:
+ vlan_id = VlanRouter._cookie_to_id(REST_VLANID, stats.cookie)
+ if vlan_id == self.vlan_id:
+ self.ofctl.delete_flow(stats)
+
+ assert len(self.packet_buffer) == 0
+
+ @staticmethod
+ def _cookie_to_id(id_type, cookie):
+ if id_type == REST_VLANID:
+ rest_id = cookie >> COOKIE_SHIFT_VLANID
+ elif id_type == REST_ADDRESSID:
+ rest_id = cookie & UINT32_MAX
+ else:
+ assert id_type == REST_ROUTEID
+ rest_id = (cookie & UINT32_MAX) >> COOKIE_SHIFT_ROUTEID
+
+ return rest_id
+
+ def _id_to_cookie(self, id_type, rest_id):
+ vid = self.vlan_id << COOKIE_SHIFT_VLANID
+
+ if id_type == REST_VLANID:
+ cookie = rest_id << COOKIE_SHIFT_VLANID
+ elif id_type == REST_ADDRESSID:
+ cookie = vid + rest_id
+ else:
+ assert id_type == REST_ROUTEID
+ cookie = vid + (rest_id << COOKIE_SHIFT_ROUTEID)
+
+ return cookie
+
+ def _get_priority(self, priority_type, route=None):
+ return get_priority(priority_type, vid=self.vlan_id, route=route)
+
+ def _response(self, msg):
+ if msg and self.vlan_id:
+ msg.setdefault(REST_VLANID, self.vlan_id)
+ return msg
+
+ def get_data(self):
+ address_data = self._get_address_data()
+ routing_data = self._get_routing_data()
+
+ data = {}
+ if address_data[REST_ADDRESS]:
+ data.update(address_data)
+ if routing_data[REST_ROUTE]:
+ data.update(routing_data)
+
+ return self._response(data)
+
+ def _get_address_data(self):
+ address_data = []
+ for value in self.address_data.values():
+ default_gw = ip_addr_ntoa(value.default_gw)
+ address = '%s/%d' % (default_gw, value.netmask)
+ data = {REST_ADDRESSID: value.address_id,
+ REST_ADDRESS: address}
+ address_data.append(data)
+ return {REST_ADDRESS: address_data}
+
+ def _get_routing_data(self):
+ routing_data = []
+ for key, value in self.routing_tbl.items():
+ if value.gateway_mac is not None:
+ gateway = ip_addr_ntoa(value.gateway_ip)
+ data = {REST_ROUTEID: value.route_id,
+ REST_DESTINATION: key,
+ REST_GATEWAY: gateway}
+ routing_data.append(data)
+ return {REST_ROUTE: routing_data}
+
+ def set_data(self, data):
+ details = None
+
+ try:
+ # Set address data
+ if REST_ADDRESS in data:
+ address = data[REST_ADDRESS]
+ address_id = self._set_address_data(address)
+ details = 'Add address [address_id=%d]' % address_id
+ # Set routing data
+ elif REST_GATEWAY in data:
+ gateway = data[REST_GATEWAY]
+ if REST_DESTINATION in data:
+ destination = data[REST_DESTINATION]
+ else:
+ destination = DEFAULT_ROUTE
+ route_id = self._set_routing_data(destination, gateway)
+ details = 'Add route [route_id=%d]' % route_id
+
+ except CommandFailure as err_msg:
+ msg = {REST_RESULT: REST_NG, REST_DETAILS: str(err_msg)}
+ return self._response(msg)
+
+ if details is not None:
+ msg = {REST_RESULT: REST_OK, REST_DETAILS: details}
+ return self._response(msg)
+ else:
+ raise ValueError('Invalid parameter.')
+
+ def _set_address_data(self, address):
+ address = self.address_data.add(address)
+
+ cookie = self._id_to_cookie(REST_ADDRESSID, address.address_id)
+
+ # Set flow: host MAC learning (packet in)
+ priority = self._get_priority(PRIORITY_MAC_LEARNING)
+ self.ofctl.set_packetin_flow(cookie, priority,
+ dl_type=ether.ETH_TYPE_IP,
+ dl_vlan=self.vlan_id,
+ dst_ip=address.nw_addr,
+ dst_mask=address.netmask)
+ log_msg = 'Set host MAC learning (packet in) flow [cookie=0x%x]'
+ self.logger.info(log_msg, cookie, extra=self.sw_id)
+
+ # set Flow: IP handling(PacketIn)
+ priority = self._get_priority(PRIORITY_IP_HANDLING)
+ self.ofctl.set_packetin_flow(cookie, priority,
+ dl_type=ether.ETH_TYPE_IP,
+ dl_vlan=self.vlan_id,
+ dst_ip=address.default_gw)
+ self.logger.info('Set IP handling (packet in) flow [cookie=0x%x]',
+ cookie, extra=self.sw_id)
+
+ # Set flow: L2 switching (normal)
+ outport = self.ofctl.dp.ofproto.OFPP_NORMAL
+ priority = self._get_priority(PRIORITY_L2_SWITCHING)
+ self.ofctl.set_routing_flow(
+ cookie, priority, outport, dl_vlan=self.vlan_id,
+ nw_src=address.nw_addr, src_mask=address.netmask,
+ nw_dst=address.nw_addr, dst_mask=address.netmask)
+ self.logger.info('Set L2 switching (normal) flow [cookie=0x%x]',
+ cookie, extra=self.sw_id)
+
+ # Send GARP
+ self.send_arp_request(address.default_gw, address.default_gw)
+
+ return address.address_id
+
+ def _set_routing_data(self, destination, gateway):
+ err_msg = 'Invalid [%s] value.' % REST_GATEWAY
+ dst_ip = ip_addr_aton(gateway, err_msg=err_msg)
+ address = self.address_data.get_data(ip=dst_ip)
+ if address is None:
+ msg = 'Gateway=%s\'s address is not registered.' % gateway
+ raise CommandFailure(msg=msg)
+ elif dst_ip == address.default_gw:
+ msg = 'Gateway=%s is used as default gateway of address_id=%d'\
+ % (gateway, address.address_id)
+ raise CommandFailure(msg=msg)
+ else:
+ src_ip = address.default_gw
+ route = self.routing_tbl.add(destination, gateway)
+ self._set_route_packetin(route)
+ self.send_arp_request(src_ip, dst_ip)
+ return route.route_id
+
+ def _set_defaultroute_drop(self):
+ cookie = self._id_to_cookie(REST_VLANID, self.vlan_id)
+ priority = self._get_priority(PRIORITY_DEFAULT_ROUTING)
+ outport = None # for drop
+ self.ofctl.set_routing_flow(cookie, priority, outport,
+ dl_vlan=self.vlan_id)
+ self.logger.info('Set default route (drop) flow [cookie=0x%x]',
+ cookie, extra=self.sw_id)
+
+ def _set_route_packetin(self, route):
+ cookie = self._id_to_cookie(REST_ROUTEID, route.route_id)
+ priority, log_msg = self._get_priority(PRIORITY_TYPE_ROUTE,
+ route=route)
+ self.ofctl.set_packetin_flow(cookie, priority,
+ dl_type=ether.ETH_TYPE_IP,
+ dl_vlan=self.vlan_id,
+ dst_ip=route.dst_ip,
+ dst_mask=route.netmask)
+ self.logger.info('Set %s (packet in) flow [cookie=0x%x]', log_msg,
+ cookie, extra=self.sw_id)
+
+ def delete_data(self, data, waiters):
+ if REST_ROUTEID in data:
+ route_id = data[REST_ROUTEID]
+ msg = self._delete_routing_data(route_id, waiters)
+ elif REST_ADDRESSID in data:
+ address_id = data[REST_ADDRESSID]
+ msg = self._delete_address_data(address_id, waiters)
+ else:
+ raise ValueError('Invalid parameter.')
+
+ return self._response(msg)
+
+ def _delete_address_data(self, address_id, waiters):
+ if address_id != REST_ALL:
+ try:
+ address_id = int(address_id)
+ except ValueError as e:
+ err_msg = 'Invalid [%s] value. %s'
+ raise ValueError(err_msg % (REST_ADDRESSID, e.message))
+
+ skip_ids = self._chk_addr_relation_route(address_id)
+
+ # Get all flow.
+ delete_list = []
+ msgs = self.ofctl.get_all_flow(waiters)
+ max_id = UINT16_MAX
+ for msg in msgs:
+ for stats in msg.body:
+ vlan_id = VlanRouter._cookie_to_id(REST_VLANID, stats.cookie)
+ if vlan_id != self.vlan_id:
+ continue
+ addr_id = VlanRouter._cookie_to_id(REST_ADDRESSID,
+ stats.cookie)
+ if addr_id in skip_ids:
+ continue
+ elif address_id == REST_ALL:
+ if addr_id <= COOKIE_DEFAULT_ID or max_id < addr_id:
+ continue
+ elif address_id != addr_id:
+ continue
+ delete_list.append(stats)
+
+ delete_ids = []
+ for flow_stats in delete_list:
+ # Delete flow
+ self.ofctl.delete_flow(flow_stats)
+ address_id = VlanRouter._cookie_to_id(REST_ADDRESSID,
+ flow_stats.cookie)
+
+ del_address = self.address_data.get_data(addr_id=address_id)
+ if del_address is not None:
+ # Clean up suspend packet threads.
+ self.packet_buffer.delete(del_addr=del_address)
+
+ # Delete data.
+ self.address_data.delete(address_id)
+ if address_id not in delete_ids:
+ delete_ids.append(address_id)
+
+ msg = {}
+ if delete_ids:
+ delete_ids = ','.join(str(addr_id) for addr_id in delete_ids)
+ details = 'Delete address [address_id=%s]' % delete_ids
+ msg = {REST_RESULT: REST_OK, REST_DETAILS: details}
+
+ if skip_ids:
+ skip_ids = ','.join(str(addr_id) for addr_id in skip_ids)
+ details = 'Skip delete (related route exist) [address_id=%s]'\
+ % skip_ids
+ if msg:
+ msg[REST_DETAILS] += ', %s' % details
+ else:
+ msg = {REST_RESULT: REST_NG, REST_DETAILS: details}
+
+ return msg
+
+ def _delete_routing_data(self, route_id, waiters):
+ if route_id != REST_ALL:
+ try:
+ route_id = int(route_id)
+ except ValueError as e:
+ err_msg = 'Invalid [%s] value. %s'
+ raise ValueError(err_msg % (REST_ROUTEID, e.message))
+
+ # Get all flow.
+ msgs = self.ofctl.get_all_flow(waiters)
+
+ delete_list = []
+ for msg in msgs:
+ for stats in msg.body:
+ vlan_id = VlanRouter._cookie_to_id(REST_VLANID, stats.cookie)
+ if vlan_id != self.vlan_id:
+ continue
+ rt_id = VlanRouter._cookie_to_id(REST_ROUTEID, stats.cookie)
+ if route_id == REST_ALL:
+ if rt_id == COOKIE_DEFAULT_ID:
+ continue
+ elif route_id != rt_id:
+ continue
+ delete_list.append(stats)
+
+ # Delete flow.
+ delete_ids = []
+ for flow_stats in delete_list:
+ self.ofctl.delete_flow(flow_stats)
+ route_id = VlanRouter._cookie_to_id(REST_ROUTEID,
+ flow_stats.cookie)
+ self.routing_tbl.delete(route_id)
+ if route_id not in delete_ids:
+ delete_ids.append(route_id)
+
+ # case: Default route deleted. -> set flow (drop)
+ route_type = get_priority_type(flow_stats.priority,
+ vid=self.vlan_id)
+ if route_type == PRIORITY_DEFAULT_ROUTING:
+ self._set_defaultroute_drop()
+
+ msg = {}
+ if delete_ids:
+ delete_ids = ','.join(str(route_id) for route_id in delete_ids)
+ details = 'Delete route [route_id=%s]' % delete_ids
+ msg = {REST_RESULT: REST_OK, REST_DETAILS: details}
+
+ return msg
+
+ def _chk_addr_relation_route(self, address_id):
+ # Check exist of related routing data.
+ relate_list = []
+ gateways = self.routing_tbl.get_gateways()
+ for gateway in gateways:
+ address = self.address_data.get_data(ip=gateway)
+ if address is not None:
+ if (address_id == REST_ALL
+ and address.address_id not in relate_list):
+ relate_list.append(address.address_id)
+ elif address.address_id == address_id:
+ relate_list = [address_id]
+ break
+ return relate_list
+
+ def packet_in_handler(self, msg, header_list):
+ # Check invalid TTL (only OpenFlow V1.2)
+ ofproto = self.dp.ofproto
+ if ofproto.OFP_VERSION == ofproto_v1_2.OFP_VERSION:
+ if msg.reason == ofproto.OFPR_INVALID_TTL:
+ self._packetin_invalid_ttl(msg, header_list)
+ return
+
+ # Analyze event type.
+ if ARP in header_list:
+ self._packetin_arp(msg, header_list)
+ return
+
+ if IPV4 in header_list:
+ rt_ports = self.address_data.get_default_gw()
+ if header_list[IPV4].dst in rt_ports:
+ # Packet to router's port.
+ if ICMP in header_list:
+ if header_list[ICMP].type == icmp.ICMP_ECHO_REQUEST:
+ self._packetin_icmp_req(msg, header_list)
+ return
+ elif TCP in header_list or UDP in header_list:
+ self._packetin_tcp_udp(msg, header_list)
+ return
+ else:
+ # Packet to internal host or gateway router.
+ self._packetin_to_node(msg, header_list)
+ return
+
+ def _packetin_arp(self, msg, header_list):
+ src_addr = self.address_data.get_data(ip=header_list[ARP].src_ip)
+ if src_addr is None:
+ return
+
+ # case: Receive ARP from the gateway
+ # Update routing table.
+ # case: Receive ARP from an internal host
+ # Learning host MAC.
+ gw_flg = self._update_routing_tbl(msg, header_list)
+ if gw_flg is False:
+ self._learning_host_mac(msg, header_list)
+
+ # ARP packet handling.
+ in_port = self.ofctl.get_packetin_inport(msg)
+ src_ip = header_list[ARP].src_ip
+ dst_ip = header_list[ARP].dst_ip
+ srcip = ip_addr_ntoa(src_ip)
+ dstip = ip_addr_ntoa(dst_ip)
+ rt_ports = self.address_data.get_default_gw()
+
+ if src_ip == dst_ip:
+ # GARP -> packet forward (normal)
+ output = self.ofctl.dp.ofproto.OFPP_NORMAL
+ self.ofctl.send_packet_out(in_port, output, msg.data)
+
+ self.logger.info('Receive GARP from [%s].', srcip,
+ extra=self.sw_id)
+ self.logger.info('Send GARP (normal).', extra=self.sw_id)
+
+ elif dst_ip not in rt_ports:
+ dst_addr = self.address_data.get_data(ip=dst_ip)
+ if (dst_addr is not None and
+ src_addr.address_id == dst_addr.address_id):
+ # ARP from internal host -> packet forward (normal)
+ output = self.ofctl.dp.ofproto.OFPP_NORMAL
+ self.ofctl.send_packet_out(in_port, output, msg.data)
+
+ self.logger.info('Receive ARP from an internal host [%s].',
+ srcip, extra=self.sw_id)
+ self.logger.info('Send ARP (normal)', extra=self.sw_id)
+ else:
+ if header_list[ARP].opcode == arp.ARP_REQUEST:
+ # ARP request to router port -> send ARP reply
+ src_mac = header_list[ARP].src_mac
+ dst_mac = self.port_data[in_port].mac
+ arp_target_mac = dst_mac
+ output = in_port
+ in_port = self.ofctl.dp.ofproto.OFPP_CONTROLLER
+
+ self.ofctl.send_arp(arp.ARP_REPLY, self.vlan_id,
+ dst_mac, src_mac, dst_ip, src_ip,
+ arp_target_mac, in_port, output)
+
+ log_msg = 'Receive ARP request from [%s] to router port [%s].'
+ self.logger.info(log_msg, srcip, dstip, extra=self.sw_id)
+ self.logger.info('Send ARP reply to [%s]', srcip,
+ extra=self.sw_id)
+
+ elif header_list[ARP].opcode == arp.ARP_REPLY:
+ # ARP reply to router port -> suspend packets forward
+ log_msg = 'Receive ARP reply from [%s] to router port [%s].'
+ self.logger.info(log_msg, srcip, dstip, extra=self.sw_id)
+
+ packet_list = self.packet_buffer.get_data(src_ip)
+ if packet_list:
+ # stop ARP reply wait thread.
+ for suspend_packet in packet_list:
+ self.packet_buffer.delete(pkt=suspend_packet)
+
+ # send suspend packet.
+ output = self.ofctl.dp.ofproto.OFPP_TABLE
+ for suspend_packet in packet_list:
+ self.ofctl.send_packet_out(suspend_packet.in_port,
+ output,
+ suspend_packet.data)
+ self.logger.info('Send suspend packet to [%s].',
+ srcip, extra=self.sw_id)
+
+ def _packetin_icmp_req(self, msg, header_list):
+ # Send ICMP echo reply.
+ in_port = self.ofctl.get_packetin_inport(msg)
+ self.ofctl.send_icmp(in_port, header_list, self.vlan_id,
+ icmp.ICMP_ECHO_REPLY,
+ icmp.ICMP_ECHO_REPLY_CODE,
+ icmp_data=header_list[ICMP].data)
+
+ srcip = ip_addr_ntoa(header_list[IPV4].src)
+ dstip = ip_addr_ntoa(header_list[IPV4].dst)
+ log_msg = 'Receive ICMP echo request from [%s] to router port [%s].'
+ self.logger.info(log_msg, srcip, dstip, extra=self.sw_id)
+ self.logger.info('Send ICMP echo reply to [%s].', srcip,
+ extra=self.sw_id)
+
+ def _packetin_tcp_udp(self, msg, header_list):
+ # Send ICMP port unreach error.
+ in_port = self.ofctl.get_packetin_inport(msg)
+ self.ofctl.send_icmp(in_port, header_list, self.vlan_id,
+ icmp.ICMP_DEST_UNREACH,
+ icmp.ICMP_PORT_UNREACH_CODE,
+ msg_data=msg.data)
+
+ srcip = ip_addr_ntoa(header_list[IPV4].src)
+ dstip = ip_addr_ntoa(header_list[IPV4].dst)
+ self.logger.info('Receive TCP/UDP from [%s] to router port [%s].',
+ srcip, dstip, extra=self.sw_id)
+ self.logger.info('Send ICMP destination unreachable to [%s].', srcip,
+ extra=self.sw_id)
+
+ def _packetin_to_node(self, msg, header_list):
+ if len(self.packet_buffer) >= MAX_SUSPENDPACKETS:
+ self.logger.info('Packet is dropped, MAX_SUSPENDPACKETS exceeded.',
+ extra=self.sw_id)
+ return
+
+ # Send ARP request to get node MAC address.
+ in_port = self.ofctl.get_packetin_inport(msg)
+ src_ip = None
+ dst_ip = header_list[IPV4].dst
+ srcip = ip_addr_ntoa(header_list[IPV4].src)
+ dstip = ip_addr_ntoa(dst_ip)
+
+ address = self.address_data.get_data(ip=dst_ip)
+ if address is not None:
+ log_msg = 'Receive IP packet from [%s] to an internal host [%s].'
+ self.logger.info(log_msg, srcip, dstip, extra=self.sw_id)
+ src_ip = address.default_gw
+ else:
+ route = self.routing_tbl.get_data(dst_ip=dst_ip)
+ if route is not None:
+ log_msg = 'Receive IP packet from [%s] to [%s].'
+ self.logger.info(log_msg, srcip, dstip, extra=self.sw_id)
+ gw_address = self.address_data.get_data(ip=route.gateway_ip)
+ if gw_address is not None:
+ src_ip = gw_address.default_gw
+ dst_ip = route.gateway_ip
+
+ if src_ip is not None:
+ self.packet_buffer.add(in_port, header_list, msg.data)
+ self.send_arp_request(src_ip, dst_ip, in_port=in_port)
+ self.logger.info('Send ARP request (flood)', extra=self.sw_id)
+
+ def _packetin_invalid_ttl(self, msg, header_list):
+ # Send ICMP TTL error.
+ srcip = ip_addr_ntoa(header_list[IPV4].src)
+ self.logger.info('Receive invalid ttl packet from [%s].', srcip,
+ extra=self.sw_id)
+
+ in_port = self.ofctl.get_packetin_inport(msg)
+ src_ip = self._get_send_port_ip(header_list)
+ if src_ip is not None:
+ self.ofctl.send_icmp(in_port, header_list, self.vlan_id,
+ icmp.ICMP_TIME_EXCEEDED,
+ icmp.ICMP_TTL_EXPIRED_CODE,
+ msg_data=msg.data, src_ip=src_ip)
+ self.logger.info('Send ICMP time exceeded to [%s].', srcip,
+ extra=self.sw_id)
+
+ def send_arp_all_gw(self):
+ gateways = self.routing_tbl.get_gateways()
+ for gateway in gateways:
+ address = self.address_data.get_data(ip=gateway)
+ self.send_arp_request(address.default_gw, gateway)
+
+ def send_arp_request(self, src_ip, dst_ip, in_port=None):
+ # Send ARP request from all ports.
+ for send_port in self.port_data.values():
+ if in_port is None or in_port != send_port.port_no:
+ src_mac = send_port.mac
+ dst_mac = mac_lib.BROADCAST
+ arp_target_mac = mac_lib.DONTCARE
+ inport = self.ofctl.dp.ofproto.OFPP_CONTROLLER
+ output = send_port.port_no
+ self.ofctl.send_arp(arp.ARP_REQUEST, self.vlan_id,
+ src_mac, dst_mac, src_ip, dst_ip,
+ arp_target_mac, inport, output)
+
+ def send_icmp_unreach_error(self, packet_buffer):
+ # Send ICMP host unreach error.
+ self.logger.info('ARP reply wait timer was timed out.',
+ extra=self.sw_id)
+ src_ip = self._get_send_port_ip(packet_buffer.header_list)
+ if src_ip is not None:
+ self.ofctl.send_icmp(packet_buffer.in_port,
+ packet_buffer.header_list,
+ self.vlan_id,
+ icmp.ICMP_DEST_UNREACH,
+ icmp.ICMP_HOST_UNREACH_CODE,
+ msg_data=packet_buffer.data,
+ src_ip=src_ip)
+
+ dstip = ip_addr_ntoa(packet_buffer.dst_ip)
+ self.logger.info('Send ICMP destination unreachable to [%s].',
+ dstip, extra=self.sw_id)
+
+ def _update_routing_tbl(self, msg, header_list):
+ # Set flow: routing to gateway.
+ out_port = self.ofctl.get_packetin_inport(msg)
+ src_mac = header_list[ARP].src_mac
+ dst_mac = self.port_data[out_port].mac
+ src_ip = header_list[ARP].src_ip
+
+ gateway_flg = False
+ for key, value in self.routing_tbl.items():
+ if value.gateway_ip == src_ip:
+ gateway_flg = True
+ if value.gateway_mac == src_mac:
+ continue
+ self.routing_tbl[key].gateway_mac = src_mac
+
+ cookie = self._id_to_cookie(REST_ROUTEID, value.route_id)
+ priority, log_msg = self._get_priority(PRIORITY_TYPE_ROUTE,
+ route=value)
+ self.ofctl.set_routing_flow(cookie, priority, out_port,
+ dl_vlan=self.vlan_id,
+ src_mac=dst_mac,
+ dst_mac=src_mac,
+ nw_dst=value.dst_ip,
+ dst_mask=value.netmask,
+ dec_ttl=True)
+ self.logger.info('Set %s flow [cookie=0x%x]', log_msg, cookie,
+ extra=self.sw_id)
+ return gateway_flg
+
+ def _learning_host_mac(self, msg, header_list):
+ # Set flow: routing to internal Host.
+ out_port = self.ofctl.get_packetin_inport(msg)
+ src_mac = header_list[ARP].src_mac
+ dst_mac = self.port_data[out_port].mac
+ src_ip = header_list[ARP].src_ip
+
+ gateways = self.routing_tbl.get_gateways()
+ if src_ip not in gateways:
+ address = self.address_data.get_data(ip=src_ip)
+ if address is not None:
+ cookie = self._id_to_cookie(REST_ADDRESSID, address.address_id)
+ priority = self._get_priority(PRIORITY_IMPLICIT_ROUTING)
+ self.ofctl.set_routing_flow(cookie, priority,
+ out_port, dl_vlan=self.vlan_id,
+ src_mac=dst_mac, dst_mac=src_mac,
+ nw_dst=src_ip,
+ idle_timeout=IDLE_TIMEOUT,
+ dec_ttl=True)
+ self.logger.info('Set implicit routing flow [cookie=0x%x]',
+ cookie, extra=self.sw_id)
+
+ def _get_send_port_ip(self, header_list):
+ try:
+ src_mac = header_list[ETHERNET].src
+ if IPV4 in header_list:
+ src_ip = header_list[IPV4].src
+ else:
+ src_ip = header_list[ARP].src_ip
+ except KeyError:
+ self.logger.debug('Receive unsupported packet.', extra=self.sw_id)
+ return None
+
+ address = self.address_data.get_data(ip=src_ip)
+ if address is not None:
+ return address.default_gw
+ else:
+ route = self.routing_tbl.get_data(gw_mac=src_mac)
+ if route is not None:
+ address = self.address_data.get_data(ip=route.gateway_ip)
+ if address is not None:
+ return address.default_gw
+
+ self.logger.debug('Receive packet from unknown IP[%s].',
+ ip_addr_ntoa(src_ip), extra=self.sw_id)
+ return None
+
+
+class PortData(dict):
+ def __init__(self, ports):
+ super(PortData, self).__init__()
+ for port in ports.values():
+ data = Port(port.port_no, port.hw_addr)
+ self[port.port_no] = data
+
+
+class Port(object):
+ def __init__(self, port_no, hw_addr):
+ super(Port, self).__init__()
+ self.port_no = port_no
+ self.mac = hw_addr
+
+
+class AddressData(dict):
+ def __init__(self):
+ super(AddressData, self).__init__()
+ self.address_id = 1
+
+ def add(self, address):
+ err_msg = 'Invalid [%s] value.' % REST_ADDRESS
+ nw_addr, mask, default_gw = nw_addr_aton(address, err_msg=err_msg)
+
+ # Check overlaps
+ for other in self.values():
+ other_mask = mask_ntob(other.netmask)
+ add_mask = mask_ntob(mask, err_msg=err_msg)
+ if (other.nw_addr == default_gw & other_mask
+ or nw_addr == other.default_gw & add_mask):
+ msg = 'Address overlaps [address_id=%d]' % other.address_id
+ raise CommandFailure(msg=msg)
+
+ address = Address(self.address_id, nw_addr, mask, default_gw)
+ ip_str = ip_addr_ntoa(nw_addr)
+ key = '%s/%d' % (ip_str, mask)
+ self[key] = address
+
+ self.address_id += 1
+ self.address_id &= UINT32_MAX
+ if self.address_id == COOKIE_DEFAULT_ID:
+ self.address_id = 1
+
+ return address
+
+ def delete(self, address_id):
+ for key, value in self.items():
+ if value.address_id == address_id:
+ del self[key]
+ return
+
+ def get_default_gw(self):
+ return [address.default_gw for address in self.values()]
+
+ def get_data(self, addr_id=None, ip=None):
+ for address in self.values():
+ if addr_id is not None:
+ if addr_id == address.address_id:
+ return address
+ else:
+ assert ip is not None
+ if ip & mask_ntob(address.netmask) == address.nw_addr:
+ return address
+ return None
+
+
+class Address(object):
+ def __init__(self, address_id, nw_addr, netmask, default_gw):
+ super(Address, self).__init__()
+ self.address_id = address_id
+ self.nw_addr = nw_addr
+ self.netmask = netmask
+ self.default_gw = default_gw
+
+ def __contains__(self, ip):
+ return bool(ip & mask_ntob(self.netmask) == self.nw_addr)
+
+
+class RoutingTable(dict):
+ def __init__(self):
+ super(RoutingTable, self).__init__()
+ self.route_id = 1
+
+ def add(self, dst_nw_addr, gateway_ip):
+ err_msg = 'Invalid [%s] value.'
+
+ if dst_nw_addr == DEFAULT_ROUTE:
+ dst_ip = 0
+ netmask = 0
+ else:
+ dst_ip, netmask, dummy = nw_addr_aton(
+ dst_nw_addr, err_msg=err_msg % REST_DESTINATION)
+
+ gateway_ip = ip_addr_aton(gateway_ip, err_msg=err_msg % REST_GATEWAY)
+
+ # Check overlaps
+ overlap_route = None
+ if dst_nw_addr == DEFAULT_ROUTE:
+ if DEFAULT_ROUTE in self:
+ overlap_route = self[DEFAULT_ROUTE].route_id
+ elif dst_nw_addr in self:
+ overlap_route = self[dst_nw_addr].route_id
+
+ if overlap_route is not None:
+ msg = 'Destination overlaps [route_id=%d]' % overlap_route
+ raise CommandFailure(msg=msg)
+
+ routing_data = Route(self.route_id, dst_ip, netmask, gateway_ip)
+ ip_str = ip_addr_ntoa(dst_ip)
+ key = '%s/%d' % (ip_str, netmask)
+ self[key] = routing_data
+
+ self.route_id += 1
+ self.route_id &= UINT32_MAX
+ if self.route_id == COOKIE_DEFAULT_ID:
+ self.route_id = 1
+
+ return routing_data
+
+ def delete(self, route_id):
+ for key, value in self.items():
+ if value.route_id == route_id:
+ del self[key]
+ return
+
+ def get_gateways(self):
+ return [routing_data.gateway_ip for routing_data in self.values()]
+
+ def get_data(self, gw_mac=None, dst_ip=None):
+ if gw_mac is not None:
+ for route in self.values():
+ if gw_mac == route.gateway_mac:
+ return route
+ return None
+
+ elif dst_ip is not None:
+ get_route = None
+ mask = 0
+ for route in self.values():
+ if dst_ip & mask_ntob(route.netmask) == route.dst_ip:
+ # For longest match
+ if mask < route.netmask:
+ get_route = route
+ mask = route.netmask
+
+ if get_route is None:
+ get_route = self.get(DEFAULT_ROUTE, None)
+ return get_route
+ else:
+ return None
+
+
+class Route(object):
+ def __init__(self, route_id, dst_ip, netmask, gateway_ip):
+ super(Route, self).__init__()
+ self.route_id = route_id
+ self.dst_ip = dst_ip
+ self.netmask = netmask
+ self.gateway_ip = gateway_ip
+ self.gateway_mac = None
+
+
+class SuspendPacketList(list):
+ def __init__(self, timeout_function):
+ super(SuspendPacketList, self).__init__()
+ self.timeout_function = timeout_function
+
+ def add(self, in_port, header_list, data):
+ suspend_pkt = SuspendPacket(in_port, header_list, data,
+ self.wait_arp_reply_timer)
+ self.append(suspend_pkt)
+
+ def delete(self, pkt=None, del_addr=None):
+ if pkt is not None:
+ del_list = [pkt]
+ else:
+ assert del_addr is not None
+ del_list = [pkt for pkt in self if pkt.dst_ip in del_addr]
+
+ for pkt in del_list:
+ self.remove(pkt)
+ hub.kill(pkt.wait_thread)
+ pkt.wait_thread.wait()
+
+ def get_data(self, dst_ip):
+ return [pkt for pkt in self if pkt.dst_ip == dst_ip]
+
+ def wait_arp_reply_timer(self, suspend_pkt):
+ hub.sleep(ARP_REPLY_TIMER)
+ if suspend_pkt in self:
+ self.timeout_function(suspend_pkt)
+ self.delete(pkt=suspend_pkt)
+
+
+class SuspendPacket(object):
+ def __init__(self, in_port, header_list, data, timer):
+ super(SuspendPacket, self).__init__()
+ self.in_port = in_port
+ self.dst_ip = header_list[IPV4].dst
+ self.header_list = header_list
+ self.data = data
+ # Start ARP reply wait timer.
+ self.wait_thread = hub.spawn(timer, self)
+
+
+class OfCtl(object):
+ _OF_VERSIONS = {}
+
+ @staticmethod
+ def register_of_version(version):
+ def _register_of_version(cls):
+ OfCtl._OF_VERSIONS.setdefault(version, cls)
+ return cls
+ return _register_of_version
+
+ @staticmethod
+ def factory(dp, logger):
+ of_version = dp.ofproto.OFP_VERSION
+ if of_version in OfCtl._OF_VERSIONS:
+ ofctl = OfCtl._OF_VERSIONS[of_version](dp, logger)
+ else:
+ raise OFPUnknownVersion(version=of_version)
+
+ return ofctl
+
+ def __init__(self, dp, logger):
+ super(OfCtl, self).__init__()
+ self.dp = dp
+ self.sw_id = {'sw_id': dpid_lib.dpid_to_str(dp.id)}
+ self.logger = logger
+
+ def set_sw_config_for_ttl(self):
+ # OpenFlow v1_2 only.
+ pass
+
+ def set_flow(self, cookie, priority, dl_type=0, dl_dst=0, dl_vlan=0,
+ nw_src=0, src_mask=32, nw_dst=0, dst_mask=32,
+ nw_proto=0, idle_timeout=0, actions=None):
+ # Abstract method
+ raise NotImplementedError()
+
+ def send_arp(self, arp_opcode, vlan_id, src_mac, dst_mac,
+ src_ip, dst_ip, arp_target_mac, in_port, output):
+ # Generate ARP packet
+ if vlan_id != VLANID_NONE:
+ ether_proto = ether.ETH_TYPE_8021Q
+ pcp = 0
+ cfi = 0
+ vlan_ether = ether.ETH_TYPE_ARP
+ v = vlan.vlan(pcp, cfi, vlan_id, vlan_ether)
+ else:
+ ether_proto = ether.ETH_TYPE_ARP
+ hwtype = 1
+ arp_proto = ether.ETH_TYPE_IP
+ hlen = 6
+ plen = 4
+
+ pkt = packet.Packet()
+ e = ethernet.ethernet(dst_mac, src_mac, ether_proto)
+ a = arp.arp(hwtype, arp_proto, hlen, plen, arp_opcode,
+ src_mac, src_ip, arp_target_mac, dst_ip)
+ pkt.add_protocol(e)
+ if vlan_id != VLANID_NONE:
+ pkt.add_protocol(v)
+ pkt.add_protocol(a)
+ pkt.serialize()
+
+ # Send packet out
+ self.send_packet_out(in_port, output, pkt.data, data_str=str(pkt))
+
+ def send_icmp(self, in_port, protocol_list, vlan_id, icmp_type,
+ icmp_code, icmp_data=None, msg_data=None, src_ip=None):
+ # Generate ICMP reply packet
+ csum = 0
+ offset = ethernet.ethernet._MIN_LEN
+
+ if vlan_id != VLANID_NONE:
+ ether_proto = ether.ETH_TYPE_8021Q
+ pcp = 0
+ cfi = 0
+ vlan_ether = ether.ETH_TYPE_IP
+ v = vlan.vlan(pcp, cfi, vlan_id, vlan_ether)
+ offset += vlan.vlan._MIN_LEN
+ else:
+ ether_proto = ether.ETH_TYPE_IP
+
+ eth = protocol_list[ETHERNET]
+ e = ethernet.ethernet(eth.src, eth.dst, ether_proto)
+
+ if icmp_data is None and msg_data is not None:
+ ip_datagram = msg_data[offset:]
+ if icmp_type == icmp.ICMP_DEST_UNREACH:
+ icmp_data = icmp.dest_unreach(data_len=len(ip_datagram),
+ data=ip_datagram)
+ elif icmp_type == icmp.ICMP_TIME_EXCEEDED:
+ icmp_data = icmp.TimeExceeded(data_len=len(ip_datagram),
+ data=ip_datagram)
+
+ ic = icmp.icmp(icmp_type, icmp_code, csum, data=icmp_data)
+
+ ip = protocol_list[IPV4]
+ if src_ip is None:
+ src_ip = ip.dst
+ ip_total_length = ip.header_length * 4 + ic._MIN_LEN
+ if ic.data is not None:
+ ip_total_length += ic.data._MIN_LEN
+ if ic.data.data is not None:
+ ip_total_length += + len(ic.data.data)
+ i = ipv4.ipv4(ip.version, ip.header_length, ip.tos,
+ ip_total_length, ip.identification, ip.flags,
+ ip.offset, DEFAULT_TTL, inet.IPPROTO_ICMP, csum,
+ src_ip, ip.src)
+
+ pkt = packet.Packet()
+ pkt.add_protocol(e)
+ if vlan_id != VLANID_NONE:
+ pkt.add_protocol(v)
+ pkt.add_protocol(i)
+ pkt.add_protocol(ic)
+ pkt.serialize()
+
+ # Send packet out
+ self.send_packet_out(in_port, self.dp.ofproto.OFPP_IN_PORT,
+ pkt.data, data_str=str(pkt))
+
+ def send_packet_out(self, in_port, output, data, data_str=None):
+ actions = [self.dp.ofproto_parser.OFPActionOutput(output, 0)]
+ self.dp.send_packet_out(buffer_id=UINT32_MAX, in_port=in_port,
+ actions=actions, data=data)
+ #TODO: Packet library convert to string
+ #if data_str is None:
+ # data_str = str(packet.Packet(data))
+ #self.logger.debug('Packet out = %s', data_str, extra=self.sw_id)
+
+ def set_normal_flow(self, cookie, priority):
+ out_port = self.dp.ofproto.OFPP_NORMAL
+ actions = [self.dp.ofproto_parser.OFPActionOutput(out_port, 0)]
+ self.set_flow(cookie, priority, actions=actions)
+
+ def set_packetin_flow(self, cookie, priority, dl_type=0, dl_dst=0,
+ dl_vlan=0, dst_ip=0, dst_mask=32, nw_proto=0):
+ miss_send_len = UINT16_MAX
+ actions = [self.dp.ofproto_parser.OFPActionOutput(
+ self.dp.ofproto.OFPP_CONTROLLER, miss_send_len)]
+ self.set_flow(cookie, priority, dl_type=dl_type, dl_dst=dl_dst,
+ dl_vlan=dl_vlan, nw_dst=dst_ip, dst_mask=dst_mask,
+ nw_proto=nw_proto, actions=actions)
+
+ def send_stats_request(self, stats, waiters):
+ self.dp.set_xid(stats)
+ waiters_per_dp = waiters.setdefault(self.dp.id, {})
+ event = hub.Event()
+ msgs = []
+ waiters_per_dp[stats.xid] = (event, msgs)
+ self.dp.send_msg(stats)
+
+ try:
+ event.wait(timeout=OFP_REPLY_TIMER)
+ except hub.Timeout:
+ del waiters_per_dp[stats.xid]
+
+ return msgs
+
+
+@OfCtl.register_of_version(ofproto_v1_0.OFP_VERSION)
+class OfCtl_v1_0(OfCtl):
+
+ def __init__(self, dp, logger):
+ super(OfCtl_v1_0, self).__init__(dp, logger)
+
+ def get_packetin_inport(self, msg):
+ return msg.in_port
+
+ def get_all_flow(self, waiters):
+ ofp = self.dp.ofproto
+ ofp_parser = self.dp.ofproto_parser
+
+ match = ofp_parser.OFPMatch(ofp.OFPFW_ALL, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0)
+ stats = ofp_parser.OFPFlowStatsRequest(self.dp, 0, match,
+ 0xff, ofp.OFPP_NONE)
+ return self.send_stats_request(stats, waiters)
+
+ def set_flow(self, cookie, priority, dl_type=0, dl_dst=0, dl_vlan=0,
+ nw_src=0, src_mask=32, nw_dst=0, dst_mask=32,
+ nw_proto=0, idle_timeout=0, actions=None):
+ ofp = self.dp.ofproto
+ ofp_parser = self.dp.ofproto_parser
+ cmd = ofp.OFPFC_ADD
+
+ # Match
+ wildcards = ofp.OFPFW_ALL
+ if dl_type:
+ wildcards &= ~ofp.OFPFW_DL_TYPE
+ if dl_dst:
+ wildcards &= ~ofp.OFPFW_DL_DST
+ if dl_vlan:
+ wildcards &= ~ofp.OFPFW_DL_VLAN
+ if nw_src:
+ v = (32 - src_mask) << ofp.OFPFW_NW_SRC_SHIFT | \
+ ~ofp.OFPFW_NW_SRC_MASK
+ wildcards &= v
+ if nw_dst:
+ v = (32 - dst_mask) << ofp.OFPFW_NW_DST_SHIFT | \
+ ~ofp.OFPFW_NW_DST_MASK
+ wildcards &= v
+ if nw_proto:
+ wildcards &= ~ofp.OFPFW_NW_PROTO
+
+ match = ofp_parser.OFPMatch(wildcards, 0, 0, dl_dst, dl_vlan, 0,
+ dl_type, 0, nw_proto, nw_src, nw_dst,
+ 0, 0)
+ actions = actions or []
+
+ m = ofp_parser.OFPFlowMod(self.dp, match, cookie, cmd,
+ idle_timeout=idle_timeout,
+ priority=priority, actions=actions)
+ self.dp.send_msg(m)
+
+ def set_routing_flow(self, cookie, priority, outport, dl_vlan=0,
+ nw_src=0, src_mask=32, nw_dst=0, dst_mask=32,
+ src_mac=0, dst_mac=0, idle_timeout=0, **dummy):
+ ofp_parser = self.dp.ofproto_parser
+
+ dl_type = ether.ETH_TYPE_IP
+
+ # Decrement TTL value is not supported at OpenFlow V1.0
+ actions = []
+ if src_mac:
+ actions.append(ofp_parser.OFPActionSetDlSrc(src_mac))
+ if dst_mac:
+ actions.append(ofp_parser.OFPActionSetDlDst(dst_mac))
+ if outport is not None:
+ actions.append(ofp_parser.OFPActionOutput(outport))
+
+ self.set_flow(cookie, priority, dl_type=dl_type, dl_vlan=dl_vlan,
+ nw_src=nw_src, src_mask=src_mask,
+ nw_dst=nw_dst, dst_mask=dst_mask,
+ idle_timeout=idle_timeout, actions=actions)
+
+ def delete_flow(self, flow_stats):
+ match = flow_stats.match
+ cookie = flow_stats.cookie
+ cmd = self.dp.ofproto.OFPFC_DELETE_STRICT
+ priority = flow_stats.priority
+ actions = []
+
+ flow_mod = self.dp.ofproto_parser.OFPFlowMod(
+ self.dp, match, cookie, cmd, priority=priority, actions=actions)
+ self.dp.send_msg(flow_mod)
+ self.logger.info('Delete flow [cookie=0x%x]', cookie, extra=self.sw_id)
+
+
+@OfCtl.register_of_version(ofproto_v1_2.OFP_VERSION)
+class OfCtl_v1_2(OfCtl):
+
+ def __init__(self, dp, logger):
+ super(OfCtl_v1_2, self).__init__(dp, logger)
+
+ def set_sw_config_for_ttl(self):
+ flags = self.dp.ofproto.OFPC_INVALID_TTL_TO_CONTROLLER
+ miss_send_len = UINT16_MAX
+ m = self.dp.ofproto_parser.OFPSetConfig(self.dp, flags,
+ miss_send_len)
+ self.dp.send_msg(m)
+ self.logger.info('Set SW config for TTL error packet in.',
+ extra=self.sw_id)
+
+ def get_packetin_inport(self, msg):
+ in_port = self.dp.ofproto.OFPP_ANY
+ for match_field in msg.match.fields:
+ if match_field.header == self.dp.ofproto.OXM_OF_IN_PORT:
+ in_port = match_field.value
+ break
+ return in_port
+
+ def get_all_flow(self, waiters):
+ ofp = self.dp.ofproto
+ ofp_parser = self.dp.ofproto_parser
+
+ match = ofp_parser.OFPMatch()
+ stats = ofp_parser.OFPFlowStatsRequest(self.dp, 0, ofp.OFPP_ANY,
+ ofp.OFPG_ANY, 0, 0, match)
+ return self.send_stats_request(stats, waiters)
+
+ def set_flow(self, cookie, priority, dl_type=0, dl_dst=0, dl_vlan=0,
+ nw_src=0, src_mask=32, nw_dst=0, dst_mask=32,
+ nw_proto=0, idle_timeout=0, actions=None):
+ ofp = self.dp.ofproto
+ ofp_parser = self.dp.ofproto_parser
+ cmd = ofp.OFPFC_ADD
+
+ # Match
+ match = ofp_parser.OFPMatch()
+ if dl_type:
+ match.set_dl_type(dl_type)
+ if dl_dst:
+ match.set_dl_dst(dl_dst)
+ if dl_vlan:
+ match.set_vlan_vid(dl_vlan)
+ if nw_src:
+ match.set_ipv4_src_masked(nw_src, mask_ntob(src_mask))
+ if nw_dst:
+ match.set_ipv4_dst_masked(nw_dst, mask_ntob(dst_mask))
+ if nw_proto:
+ if dl_type == ether.ETH_TYPE_IP:
+ match.set_ip_proto(nw_proto)
+ elif dl_type == ether.ETH_TYPE_ARP:
+ match.set_arp_opcode(nw_proto)
+
+ # Instructions
+ actions = actions or []
+ inst = [ofp_parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS,
+ actions)]
+
+ m = ofp_parser.OFPFlowMod(self.dp, cookie, 0, 0, cmd, idle_timeout,
+ 0, priority, UINT32_MAX, ofp.OFPP_ANY,
+ ofp.OFPG_ANY, 0, match, inst)
+ self.dp.send_msg(m)
+
+ def set_routing_flow(self, cookie, priority, outport, dl_vlan=0,
+ nw_src=0, src_mask=32, nw_dst=0, dst_mask=32,
+ src_mac=0, dst_mac=0, idle_timeout=0, dec_ttl=False):
+ ofp = self.dp.ofproto
+ ofp_parser = self.dp.ofproto_parser
+
+ dl_type = ether.ETH_TYPE_IP
+
+ actions = []
+ if dec_ttl:
+ actions.append(ofp_parser.OFPActionDecNwTtl())
+ if src_mac:
+ set_src = ofp_parser.OFPMatchField.make(ofp.OXM_OF_ETH_SRC,
+ src_mac)
+ actions.append(ofp_parser.OFPActionSetField(set_src))
+ if dst_mac:
+ set_dst = ofp_parser.OFPMatchField.make(ofp.OXM_OF_ETH_DST,
+ dst_mac)
+ actions.append(ofp_parser.OFPActionSetField(set_dst))
+ if outport is not None:
+ actions.append(ofp_parser.OFPActionOutput(outport, 0))
+
+ self.set_flow(cookie, priority, dl_type=dl_type, dl_vlan=dl_vlan,
+ nw_src=nw_src, src_mask=src_mask,
+ nw_dst=nw_dst, dst_mask=dst_mask,
+ idle_timeout=idle_timeout, actions=actions)
+
+ def delete_flow(self, flow_stats):
+ ofp = self.dp.ofproto
+ ofp_parser = self.dp.ofproto_parser
+
+ cmd = ofp.OFPFC_DELETE
+ cookie = flow_stats.cookie
+ cookie_mask = UINT64_MAX
+ match = ofp_parser.OFPMatch()
+ inst = []
+
+ flow_mod = ofp_parser.OFPFlowMod(self.dp, cookie, cookie_mask, 0, cmd,
+ 0, 0, 0, UINT32_MAX, ofp.OFPP_ANY,
+ ofp.OFPG_ANY, 0, match, inst)
+ self.dp.send_msg(flow_mod)
+ self.logger.info('Delete flow [cookie=0x%x]', cookie, extra=self.sw_id)
+
+
+def ip_addr_aton(ip_str, err_msg=None):
+ try:
+ return struct.unpack('!I', socket.inet_aton(ip_str))[0]
+ except (struct.error, socket.error) as e:
+ if err_msg is not None:
+ e.message = '%s %s' % (err_msg, e.message)
+ raise ValueError(e.message)
+
+
+def ip_addr_ntoa(ip):
+ return socket.inet_ntoa(struct.pack('!I', ip))
+
+
+def mask_ntob(mask, err_msg=None):
+ try:
+ return (UINT32_MAX << (32 - mask)) & UINT32_MAX
+ except ValueError:
+ msg = 'illegal netmask'
+ if err_msg is not None:
+ msg = '%s %s' % (err_msg, msg)
+ raise ValueError(msg)
+
+
+def nw_addr_aton(nw_addr, err_msg=None):
+ ip_mask = nw_addr.split('/')
+ default_route = ip_addr_aton(ip_mask[0], err_msg=err_msg)
+ netmask = 32
+ if len(ip_mask) == 2:
+ try:
+ netmask = int(ip_mask[1])
+ except ValueError as e:
+ if err_msg is not None:
+ e.message = '%s %s' % (err_msg, e.message)
+ raise ValueError(e.message)
+ if netmask < 0:
+ msg = 'illegal netmask'
+ if err_msg is not None:
+ msg = '%s %s' % (err_msg, msg)
+ raise ValueError(msg)
+ nw_addr = default_route & mask_ntob(netmask, err_msg=err_msg)
+ return nw_addr, netmask, default_route