summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorwatanabe.fumitaka <watanabe.fumitaka@nttcom.co.jp>2013-07-09 10:42:14 +0900
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2013-07-18 23:33:35 +0900
commit7099838c3fbb37abd7634eb832c030c1c6f5fa1e (patch)
tree5425c038711da546da147ca17985cb04f3b09126
parent66b0c290be07ef48d8c892d3555458f688342495 (diff)
add router Ryu application
This patch implements router Ryu application. packet is forwarded on the static routing table. The routing table can be set up by the REST commands. and, Router can handle each vlan groups separately. -------------------------------------------- * Example case: Set static route on vlanID=10 From: address(172.16.10.0/24) belongs to the rest_router(switch_id=0000000000000001) To : address(10.0.255.0/24) belongs to the gateway router(192.168.10.20) - Following is the rest_router(switch_id=0000000000000001)'s setting. If the gateway router operates by the rest_router Ryu applicaion too, same setting is required. 1. add Address data(172.16.10.0/24, default_route=172.16.10.1) $ curl -X POST -d '{"address":"172.16.10.1/24"}' http://localhost:8080/router/0000000000000001/10 2. add Address data(192.168.10.0/24, default_route=192.168.10.1) $ curl -X POST -d '{"address":"192.168.10.1/24"}' http://localhost:8080/router/0000000000000001/10 3. add Static route(destination=10.0.255.0/24, gateway=192.168.10.20) $ curl -X POST -d '{"destination":"10.0.255.0/24","gateway":"192.168.10.20"}' http://localhost:8080/router/0000000000000001/10 - route can not be registered, if gateway's address is not registered as 'address data' 4. check of the registered address data and routing data. $ curl http://localhost:8080/router/0000000000000001/10 5. you can send packets from host(172.16.10.0/24) to gateway router(192.168.10.20), and send to host(10.0.255.0/24) if gateway router is set up correctly. (packet needs to contain vlan-tag=10) -------------------------------------------- Please refer to rest_router.py comments, for other RESTcommand interfaces. Signed-off-by: WATANABE Fumitaka <watanabe.fumitaka@nttcom.co.jp> Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-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