diff options
-rw-r--r-- | ryu/app/rest_router.py | 1831 |
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 |