Sample router code.
Real router app can use this code as template in order to support VRRP.

Signed-off-by: Isaku Yamahata <yamah...@valinux.co.jp>
---
Changes v1 -> v2:
- sample for Linux network device
- trivial update
  arp_ether_ip -> arp_ip
- don't rely on packet-in match field for packet parsing
---
 ryu/services/vrrp/sample_router.py |  517 ++++++++++++++++++++++++++++++++++++
 1 file changed, 517 insertions(+)
 create mode 100644 ryu/services/vrrp/sample_router.py

diff --git a/ryu/services/vrrp/sample_router.py 
b/ryu/services/vrrp/sample_router.py
new file mode 100644
index 0000000..03b42a7
--- /dev/null
+++ b/ryu/services/vrrp/sample_router.py
@@ -0,0 +1,517 @@
+# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
+# Copyright (C) 2013 Isaku Yamahata <yamahata at private email ne jp>
+#
+# 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.
+
+"""
+router implementation base class
+a template for router implementation that support VRRP
+"""
+
+import gevent
+import socket
+
+from ryu.base import app_manager
+from ryu.controller import handler
+from ryu.controller import ofp_event
+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 packet
+from ryu.lib.packet import vlan
+from ryu.lib.packet import vrrp
+from ryu.ofproto import ether
+from ryu.ofproto import ofproto_v1_2
+from ryu.services.vrrp import api as vrrp_api
+from ryu.services.vrrp import event as vrrp_event
+from ryu.services.vrrp import utils
+
+
+class RouterBase(app_manager.RyuApp):
+    def __init__(self, *args, **kwargs):
+        super(RouterBase, self).__init__(*args, **kwargs)
+        self.instance_name = kwargs['name']
+        self.monitor_name = kwargs['monitor_name']
+        self.config = kwargs['config']
+        self.interface = kwargs['interface']
+
+        # TODO:XXX race
+        vrrp_api.vrrp_register(self, self.instance_name, self.name)
+
+    def _initialized(self):
+        self.logger.debug('initialized')
+
+    def _initialized_to_master(self):
+        self.logger.debug('initialized to master')
+        # RFC3768 6.4.1
+        # o  Broadcast a gratuitous ARP request containing the virtual
+        # router MAC address for each IP address associated with the
+        # virtual router.
+        #
+        # or
+        #
+        # RFC 5795 6.4.1
+        #(115)+ If the protected IPvX address is an IPv4 address, then:
+        #   (120) * Broadcast a gratuitous ARP request containing the
+        #   virtual router MAC address for each IP address associated
+        #   with the virtual router.
+        #(125) + else // IPv6
+        #   (130) * For each IPv6 address associated with the virtual
+        #   router, send an unsolicited ND Neighbor Advertisement with
+        #   the Router Flag (R) set, the Solicited Flag (S) unset, the
+        #   Override flag (O) set, the target address set to the IPv6
+        #   address of the virtual router, and the target link-layer
+        #   address set to the virtual router MAC address.
+
+    def _become_master(self):
+        self.logger.debug('become master')
+        # RFC3768 6.4.2
+        # o  Broadcast a gratuitous ARP request containing the virtual
+        #    router MAC address for each IP address associated with the
+        #    virtual router
+        #
+        # or
+        #
+        # RFC 5795 6.4.2
+        #(375)+ If the protected IPvX address is an IPv4 address, then:
+        #   (380)* Broadcast a gratuitous ARP request on that interface
+        #   containing the virtual router MAC address for each IPv4
+        #   address associated with the virtual router.
+        #(385) + else // ipv6
+        #   (390) * Compute and join the Solicited-Node multicast
+        #   address [RFC4291] for the IPv6 address(es) associated with
+        #   the virtual router.
+        #   (395) * For each IPv6 address associated with the virtual
+        #   router, send an unsolicited ND Neighbor Advertisement with
+        #   the Router Flag (R) set, the Solicited Flag (S) unset, the
+        #   Override flag (O) set, the target address set to the IPv6
+        #   address of the virtual router, and the target link-layer
+        #   address set to the virtual router MAC address.
+
+    def _become_backup(self):
+        self.logger.debug('become backup')
+        # RFC 3768 6.4.2 Backup
+        # -  MUST NOT respond to ARP requests for the IP address(s)
+        #    associated with the virtual router.
+        # -  MUST discard packets with a destination link layer MAC address
+        #    equal to the virtual router MAC address.
+        # -  MUST NOT accept packets addressed to the IP address(es)
+        #    associated with the virtual router.
+        #
+        # or
+        #
+        # RFC 5798 6.4.2 Backup
+        #(305) - If the protected IPvX address is an IPv4 address, then:
+        #   (310) + MUST NOT respond to ARP requests for the IPv4
+        #   address(es) associated with the virtual router.
+        #(315) - else // protected addr is IPv6
+        #   (320) + MUST NOT respond to ND Neighbor Solicitation messages
+        #   for the IPv6 address(es) associated with the virtual router.
+        #   (325) + MUST NOT send ND Router Advertisement messages for the
+        #   virtual router.
+        #(330) -endif // was protected addr IPv4?
+        #(335) - MUST discard packets with a destination link-layer MAC
+        #address equal to the virtual router MAC address.
+        #(340) - MUST NOT accept packets addressed to the IPvX address(es)
+        #associated with the virtual router.
+
+    def _shutdowned(self):
+        self.logger.debug('shutdowned')
+
+    @handler.set_ev_handler(vrrp_event.EventVRRPStateChanged)
+    def vrrp_state_changed_handler(self, ev):
+        old_state = ev.old_state
+        new_state = ev.new_state
+        if new_state == vrrp_event.VRRP_STATE_MASTER:
+            if old_state is None:
+                self._initialized_to_master()
+            elif old_state == vrrp_event.VRRP_STATE_BACKUP:
+                self._become_master()
+
+            # RFC 3768 6.4.3
+            # -  MUST respond to ARP requests for the IP address(es) associated
+            #    with the virtual router.
+            # -  MUST forward packets with a destination link layer MAC address
+            #    equal to the virtual router MAC address.
+            # -  MUST NOT accept packets addressed to the IP address(es)
+            #    associated with the virtual router if it is not the IP address
+            #    owner.
+            # -  MUST accept packets addressed to the IP address(es) associated
+            #    with the virtual router if it is the IP address owner.
+            #
+            # or
+            #
+            # RFC5798 6.4.3
+            #(605) - If the protected IPvX address is an IPv4 address, then:
+            #   (610) + MUST respond to ARP requests for the IPv4 address(es)
+            #   associated with the virtual router.
+            #(615) - else // ipv6
+            #   (620) + MUST be a member of the Solicited-Node multicast
+            #   address for the IPv6 address(es) associated with the virtual
+            #   router.
+            #   (625) + MUST respond to ND Neighbor Solicitation message for
+            #   the IPv6 address(es) associated with the virtual router.
+            #   (630) ++ MUST send ND Router Advertisements for the virtual
+            #   router.
+            #   (635) ++ If Accept_Mode is False:  MUST NOT drop IPv6 Neighbor
+            #   Solicitations and Neighbor Advertisements.
+            #(640) +-endif // ipv4?
+            #(645) - MUST forward packets with a destination link-layer MAC
+            #address equal to the virtual router MAC address.
+            #(650) - MUST accept packets addressed to the IPvX address(es)
+            #associated with the virtual router if it is the IPvX address owner
+            #or if Accept_Mode is True.  Otherwise, MUST NOT accept these
+            #packets.
+
+        elif new_state == vrrp_event.VRRP_STATE_BACKUP:
+            self._become_backup()
+        elif new_state == vrrp_event.VRRP_STATE_INITIALIZE:
+            if old_state is None:
+                self._initialized()
+            else:
+                self._shutdowned()
+        else:
+            raise ValueError('invalid vrrp state %s' % new_state)
+
+
+class RouterIPV4(RouterBase):
+    def __init__(self, *args, **kwargs):
+        super(RouterIPV4, self).__init__(*args, **kwargs)
+        assert not self.config.is_ipv6
+
+        # prepare garp packet
+        src_mac = vrrp.vrrp_ipv4_src_mac_address(self.config.vrid)
+        e = ethernet.ethernet(mac_lib.BROADCAST, src_mac, ether.ETH_TYPE_ARP)
+        a = arp.arp_ip(arp.ARP_REQUEST, src_mac,
+                       self.config.primary_ip_address, src_mac, 0)
+
+        p = packet.Packet()
+        p.add_protocol(e)
+        utils.may_add_vlan(p, self.interface.vlan_id)
+        p.add_protocol(a)
+        p.serialize()
+        self.garp_packet = p
+
+    def _arp_reply_packet(self, arp_req_sha, arp_req_spa):
+        src_mac = vrrp.vrrp_ipv4_src_mac_address(self.config.vrid)
+        e = ethernet.ethernet(arp_req_sha, src_mac, ether.ETH_TYPE_ARP)
+        a = arp.arp_ip(arp.ARP_REPLY, src_mac, self.config.primary_ip_address,
+                       arp_req_sha, arp_req_spa)
+
+        p = packet.Packet()
+        p.add_protocol(e)
+        utils.may_add_vlan(p, self.interface.vlan_id)
+        p.add_protocol(a)
+        p.serialize()
+        return p.data
+
+    def _arp_process(self, data):
+        dst_mac = vrrp.vrrp_ipv4_src_mac_address(self.config.vrid)
+        arp_sha = None
+        arp_spa = None
+
+        p = packet.packet(data)
+        for proto in p.protocols:
+            if isinstance(proto, ethernet.ethernet):
+                if proto.dst != dst_mac:
+                    return None
+                ethertype = proto.ethertype
+                if not ((self.interface.vlan_id is None and
+                         ethertype == ether.ETH_TYPE_ARP) or
+                        (self.interface.vlan_id is not None and
+                         ethertype == ether.ETH_TYPE_8021Q)):
+                    return None
+            elif isinstance(proto, vlan.vlan):
+                if (proto.vid != self.interface.vlan_id or
+                        proto.ethertype != ether.ETH_TYPE_ARP):
+                    return None
+            elif isinstance(proto, arp.arp):
+                if (proto.hwtype != arp.ARP_HW_TYPE_ETHERNET or
+                    proto.proto != ether.ETH_TYPE_IP or
+                    proto.hlen != 6 or proto.plen != 4 or
+                        proto.dst_mac != dst_mac):
+                    return None
+                arp_sha = proto.src_mac
+                arp_spa = proto.src_ip
+                break
+
+        if arp_sha is None or arp_spa is None:
+            self.logger.debug('malformed arp request? arp_sha %s arp_spa %s',
+                              arp_sha, arp_spa)
+            return None
+
+        return self._arp_reply_packet(arp_sha, arp_spa)
+
+
+class RouterIPV4Linux(RouterIPV4):
+    def __init__(self, *args, **kwargs):
+        super(RouterIPV4OpenFlow, self).__init__(*args, **kwargs)
+        assert isinstance(self.interface,
+                          vrrp_event.VRRPInterfaceNetworkDevice)
+        self.__is_master = False
+        self._arp_thread = None
+
+    def start(self):
+        self._disable_router()
+        super(RouterIPV4OpenFlow, self).start()
+
+    def _initialized_to_master(self):
+        self.logger.debug('initialized to master')
+        self._master()
+
+    def _become_master(self):
+        self.logger.debug('become master')
+        self._master()
+
+    def _master(self):
+        self.__is_master = True
+        self._enable_router()
+        self._send_garp()
+
+    def _become_backup(self):
+        self.logger.debug('become backup')
+        self.__is_master = False
+        self._disable_router()
+
+    def _shutdowned(self):
+        # When VRRP functionality is disabled, what to do?
+        #  should we also exit? or continue to route packets?
+        self._disable_router()
+
+    def _send_garp(self):
+        packet_socket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
+                                      socket.htons(ether.ETH_TYPE_ARP))
+        packet_socket.sendto(self.garp_packet.data,
+                             (self.interface.device_name, 0))
+        packet_socket.close()
+
+    def _arp_loop(self):
+        with socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
+                           socket.htons(ether.ETH_TYPE_ARP)) as packet_socket:
+            packet_socket.bind((self.interface.device_name,
+                                socket.htons(ether.ETH_TYPE_ARP),
+                                socket.PACKET_BROADCAST,
+                                arp.ARP_HW_TYPE_ETHER_NET, mac_lib.BROADCAST))
+
+            while True:
+                try:
+                    buf = packet_socket.recv(1500)
+                except socket.timeout:
+                    continue
+
+                data = self._arp_process(buf)
+                packet_socket.sendto(data, (self.interface.device_name, 0))
+
+    def _enable_router(self):
+        if self._arp_thread is None:
+            self._arp_thread = gevent.spawn_later(0, self._arp_loop)
+        # TODO: implement real routing logic
+        self.logger.debug('TODO:_enable_router')
+
+    def _disable_router(self):
+        if self._arp_thread is not None:
+            self._arp_thread.kill()
+            self._arp_thread.join()
+            self._arp_thread = None
+        # TODO: implement real routing logic
+        self.logger.debug('TODO:_disable_router')
+
+
+class RouterIPV4OpenFlow(RouterIPV4):
+    OFP_VERSIONS = [ofproto_v1_2.OFP_VERSION]
+
+    # it must be that
+    # _DROP_PRIORITY < monitor.VRRPInterfaceMonitorOpenFlow._PRIORITY or
+    # _DROP_TABLE > monitor.VRRPInterfaceMonitorOpenFlow._TABLE
+    # to gurantee that VRRP packets are send to controller
+    _DROP_TABLE = 0
+    _DROP_PRIORITY = 0x8000 / 2
+
+    # it must be that
+    # _ARP_PRIORITY < _DROP_PRIORITY or
+    # _ARP_TABLE > _DROP_TABLE
+    # to gurantee that responding arp can be disabled
+    _ARP_TABLE = 0
+    _ARP_PRIORITY = _DROP_PRIORITY / 2
+
+    # it must be that
+    # _ROUTEING_TABLE < _ARP_TABLE or
+    # _ROUTING_TABLE > _ARP_TABLE
+    # to gurantee that routing can be disabled
+    _ROUTING_TABLE = 0
+    _ROUTING_PRIORITY = _ARP_PRIORITY / 2
+
+    def __init__(self, *args, **kwargs):
+        super(RouterIPV4OpenFlow, self).__init__(*args, **kwargs)
+        assert isinstance(self.interface, vrrp_event.VRRPInterfaceOpenFlow)
+
+    def _get_dp(self):
+        return utils.get_dp(self, self.interface.dpid)
+
+    def start(self):
+        dp = self._get_dp()
+        assert dp
+        self._uninstall_route_rule(dp)
+        self._uninstall_arp_rule(dp)
+        self._uninstall_drop_rule(dp)
+        self._install_drop_rule(dp)
+        self._install_arp_rule(dp)
+        self._install_route_rule(dp)
+        super(RouterIPV4OpenFlow, self).start()
+
+    def _initialized_to_master(self):
+        self.logger.debug('initialized to master')
+        self._master()
+
+    def _become_master(self):
+        self.logger.debug('become master')
+        self._master()
+
+    def _master(self):
+        dp = self._get_dp()
+        if dp is None:
+            return
+
+        self._uninstall_drop_rule(dp)
+        self._send_garp(dp)
+
+    def _become_backup(self):
+        self.logger.debug('become backup')
+        dp = self._get_dp()
+        if dp is None:
+            return
+
+        self._install_drop_rule(dp)
+
+    def _shutdowned(self):
+        dp = self._get_dp()
+        if dp is None:
+            return
+
+        # When VRRP functionality is disabled, what to do?
+        #  should we also exit? or continue to route packets?
+        self._uninstall_route_rule(dp)
+        self._uninstall_arp_rule(dp)
+        self._uninstall_drop_rule(dp)
+
+    @handler.set_ev_cls(ofp_event.EventOFPPacketIn, handler.MAIN_DISPATCHER)
+    def packet_in_handler(self, ev):
+        msg = ev.msg
+        datapath = msg.datapath
+        ofproto = datapath.ofproto
+
+        # TODO: subscribe only the datapath that we route
+        dpid = datapath.dpid
+        if dpid != self.interface.dpid:
+            return
+
+        for field in msg.match.fields:
+            header = field.header
+            if header == ofproto.OXM_OF_IN_PORT:
+                if field.value != self.interface.port_no:
+                    return
+                break
+
+        data = self._arp_process(msg.data)
+
+        dp = self._get_dp()
+        if dp is None:
+            return
+        utils.dp_packet_out(dp, self.interface.port_no, data)
+
+    def _send_garp(self, dp):
+        dp = self._get_dp()
+        if dp is None:
+            return
+        utils.dp_packet_out(dp, self.interface.port_no, self.garp_packet.data)
+
+    def _drop_match(self, dp):
+        match = dp.ofproto_parser.OFPMatch()
+        match.set_in_port(self.interface.port_no)
+        match.set_dl_dst(vrrp.vrrp_ipv4_src_mac_address(self.config.vrid))
+        if self.interface.vlan_id is not None:
+            match.set_vlan_vid(self.interface.vlan_id)
+        return match
+
+    def _install_drop_rule(self, dp):
+        match = self._drop_match(dp)
+        utils.dp_flow_mod(dp, self._DROP_TABLE, dp.ofproto.OFPFC_ADD,
+                          self._DROP_PRIORITY, match, [])
+
+    def _uninstall_drop_rule(self, dp):
+        match = self._drop_match(dp)
+        utils.dp_flow_mod(dp, self._DROP_TABLE, dp.ofproto.OFPFC_DELETE_STRICT,
+                          self._DROP_PRIORITY, match, [])
+
+    def _arp_match(self, dp):
+        match = dp.ofproto_parser.OFPMatch()
+        match.set_in_port(self.interface.port_no)
+        match.set_dl_dst(mac_lib.BROADCAST)
+        match.set_dl_type(ether.ETH_TYPE_ARP)
+        if self.interface.vlan_id is not None:
+            match.set_vlan_vid(self.interface.vlan_id)
+        match.set_arp_opcode(arp.ARP_REQUEST)
+        match.set_arp_tpa(self,
+                          vrrp.vrrp_ipv4_src_mac_address(self.config.vrid))
+        return match
+
+    def _install_arp_rule(self, dp):
+        ofproto = dp.ofproto
+        ofproto_parser = dp.ofproto_parser
+
+        match = self._arp_match(dp)
+        actions = [ofproto_parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
+                                                  ofproto.OFPCML_NO_BUFFER)]
+        instructions = [ofproto_parser.OFPInstructionActions(
+            ofproto.OFPIT_APPLY_ACTIONS, actions)]
+        utils.dp_flow_mod(dp, self._ARP_TABLE, dp.fproto.OFPFC_ADD,
+                          self._ARP_PRIORITY, match, instructions)
+
+    def _uninstall_arp_rule(self, dp):
+        match = self._arp_match(dp)
+        utils.dp_flow_mod(dp, self._ARP_TABLE, dp.fproto.OFPFC_DELETE_STRICT,
+                          self._ARP_PRIORITY, match, [])
+
+    def _install_route_rule(self, dp):
+        # TODO: implement real routing logic
+        self.logger.debug('TODO:_install_router_rule')
+
+    def _uninstall_route_rule(self, dp):
+        # TODO: implement real routing logic
+        self.logger.debug('TODO:_uninstall_router_rule')
+
+
+class RouterIPV6(RouterBase):
+    def __init__(self, *args, **kwargs):
+        super(RouterIPV6, self).__init__(*args, **kwargs)
+        assert self.config.is_ipv6
+
+
+class RouterIPV6Linux(RouterIPV6):
+    def __init__(self, *args, **kwargs):
+        super(RouterIPV6OpenFlow, self).__init__(*args, **kwargs)
+        assert isinstance(self.interface,
+                          vrrp_event.VRRPInterfaceNetworkDevice)
+
+    # TODO: reader's home work
+    pass
+
+
+class RouterIPV6OpenFlow(RouterIPV6):
+    def __init__(self, *args, **kwargs):
+        super(RouterIPV6OpenFlow, self).__init__(*args, **kwargs)
+        assert isinstance(self.interface, vrrp_event.VRRPInterfaceOpenFlow)
+
+    # TODO: reader's home work
+    pass
-- 
1.7.10.4


------------------------------------------------------------------------------
Precog is a next-generation analytics platform capable of advanced
analytics on semi-structured data. The platform includes APIs for building
apps and a phenomenal toolset for data science. Developers can use
our toolset for easy data analysis & visualization. Get a free account!
http://www2.precog.com/precogplatform/slashdotnewsletter
_______________________________________________
Ryu-devel mailing list
Ryu-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/ryu-devel

Reply via email to