From 3c10efe07f6250aa24fb557e622d2064406510fc Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Fri, 22 Nov 2013 16:46:01 +0900 Subject: [PATCH] vrrp service services.protocols.vrrp utils: util functions for vrrp service event: VRRP related events and classes monitor: interface monitor router: VRRP router manager: a class that manages VRRP routers api: API for VRRP service dumper: vrrp event dumper (a sample application) the directory structure (services.protocols.vrrp) was suggested by FUJITA Tomonori. Signed-off-by: Isaku Yamahata Signed-off-by: YAMAMOTO Takashi Signed-off-by: FUJITA Tomonori --- ryu/services/__init__.py | 15 + ryu/services/protocols/__init__.py | 15 + ryu/services/protocols/vrrp/__init__.py | 15 + ryu/services/protocols/vrrp/api.py | 65 ++ ryu/services/protocols/vrrp/dumper.py | 154 ++++ ryu/services/protocols/vrrp/event.py | 265 +++++++ ryu/services/protocols/vrrp/manager.py | 156 ++++ ryu/services/protocols/vrrp/monitor.py | 151 ++++ ryu/services/protocols/vrrp/monitor_linux.py | 234 ++++++ .../protocols/vrrp/monitor_openflow.py | 141 ++++ ryu/services/protocols/vrrp/router.py | 687 ++++++++++++++++++ ryu/services/protocols/vrrp/sample_manager.py | 99 +++ ryu/services/protocols/vrrp/sample_router.py | 539 ++++++++++++++ ryu/services/protocols/vrrp/utils.py | 76 ++ 14 files changed, 2612 insertions(+) create mode 100644 ryu/services/__init__.py create mode 100644 ryu/services/protocols/__init__.py create mode 100644 ryu/services/protocols/vrrp/__init__.py create mode 100644 ryu/services/protocols/vrrp/api.py create mode 100644 ryu/services/protocols/vrrp/dumper.py create mode 100644 ryu/services/protocols/vrrp/event.py create mode 100644 ryu/services/protocols/vrrp/manager.py create mode 100644 ryu/services/protocols/vrrp/monitor.py create mode 100644 ryu/services/protocols/vrrp/monitor_linux.py create mode 100644 ryu/services/protocols/vrrp/monitor_openflow.py create mode 100644 ryu/services/protocols/vrrp/router.py create mode 100644 ryu/services/protocols/vrrp/sample_manager.py create mode 100644 ryu/services/protocols/vrrp/sample_router.py create mode 100644 ryu/services/protocols/vrrp/utils.py diff --git a/ryu/services/__init__.py b/ryu/services/__init__.py new file mode 100644 index 00000000..ca8ef539 --- /dev/null +++ b/ryu/services/__init__.py @@ -0,0 +1,15 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2013 YAMAMOTO Takashi +# +# 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. diff --git a/ryu/services/protocols/__init__.py b/ryu/services/protocols/__init__.py new file mode 100644 index 00000000..340a4230 --- /dev/null +++ b/ryu/services/protocols/__init__.py @@ -0,0 +1,15 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2013 Isaku Yamahata +# +# 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. diff --git a/ryu/services/protocols/vrrp/__init__.py b/ryu/services/protocols/vrrp/__init__.py new file mode 100644 index 00000000..340a4230 --- /dev/null +++ b/ryu/services/protocols/vrrp/__init__.py @@ -0,0 +1,15 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2013 Isaku Yamahata +# +# 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. diff --git a/ryu/services/protocols/vrrp/api.py b/ryu/services/protocols/vrrp/api.py new file mode 100644 index 00000000..c487ec56 --- /dev/null +++ b/ryu/services/protocols/vrrp/api.py @@ -0,0 +1,65 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2013 Isaku Yamahata +# +# 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. + +from ryu.services.protocols.vrrp import event as vrrp_event + + +def vrrp_config(app, interface, config): + """create an instance. + returns EventVRRPConfigReply(instance.name, interface, config) + on success. + returns EventVRRPConfigReply(None, interface, config) + on failure. + """ + config_request = vrrp_event.EventVRRPConfigRequest(interface, config) + config_request.sync = True + return app.send_request(config_request) + + +def vrrp_shutdown(app, instance_name): + """shutdown the instance. + """ + shutdown_request = vrrp_event.EventVRRPShutdownRequest(instance_name) + app.send_event(vrrp_event.VRRP_MANAGER_NAME, shutdown_request) + + +def vrrp_transmit(app, monitor_name, data): + """transmit a packet from the switch. this is internal use only. + data is str-like, a packet to send. + """ + transmit_request = vrrp_event.EventVRRPTransmitRequest(data) + app.send_event(monitor_name, transmit_request) + + +def vrrp_list(app, instance_name=None): + """list instances. + returns EventVRRPListReply([VRRPInstance]). + """ + list_request = vrrp_event.EventVRRPListRequest(instance_name) + list_request.dst = vrrp_event.VRRP_MANAGER_NAME + return app.send_request(list_request) + + +def vrrp_config_change(app, instance_name, + priority=None, advertisement_interval=None, + preempt_mode=None, accept_mode=None): + """change configuration of an instance. + None means no change. + """ + config_change = vrrp_event.EventVRRPConfigChangeRequest( + instance_name, priority, advertisement_interval, + preempt_mode, accept_mode) + return app.send_event(vrrp_event.VRRP_MANAGER_NAME, config_change) diff --git a/ryu/services/protocols/vrrp/dumper.py b/ryu/services/protocols/vrrp/dumper.py new file mode 100644 index 00000000..6f86e1ee --- /dev/null +++ b/ryu/services/protocols/vrrp/dumper.py @@ -0,0 +1,154 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2013 Isaku Yamahata +# +# 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. + +""" +VRRP event dumper +This is also a template for router implementation that support VRRP +""" + +from ryu.base import app_manager +from ryu.controller import handler +from ryu.services.protocols.vrrp import event as vrrp_event + + +class VRRPDumper(app_manager.RyuApp): + def __init__(self, *args, **kwargs): + super(VRRPDumper, self).__init__(*args, **kwargs) + + @handler.set_ev_cls(vrrp_event.EventVRRPStateChanged) + def vrrp_state_changed_handler(self, ev): + old_state = ev.old_state + new_state = ev.new_state + self.logger.info('state change %s: %s -> %s', ev.instance_name, + old_state, new_state) + if new_state == vrrp_event.VRRP_STATE_MASTER: + self.logger.info('becomes master') + if old_state is None: + # 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. + # + pass + elif old_state == vrrp_event.VRRP_STATE_BACKUP: + # 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. + pass + + # 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.logger.info('becomes 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. + elif new_state == vrrp_event.VRRP_STATE_INITIALIZE: + if old_state is None: + self.logger.info('initialized') + else: + self.logger.info('shutdowned') + else: + raise ValueError('invalid vrrp state %s' % new_state) diff --git a/ryu/services/protocols/vrrp/event.py b/ryu/services/protocols/vrrp/event.py new file mode 100644 index 00000000..49d36924 --- /dev/null +++ b/ryu/services/protocols/vrrp/event.py @@ -0,0 +1,265 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2013 Isaku Yamahata +# +# 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. + +""" +Events for VRRP +""" + +from ryu.controller import event +from ryu.lib import dpid as dpid_lib +from ryu.lib import mac as mac_lib +from ryu.lib.packet import vrrp +from ryu.lib import addrconv + + +# When an instance is created, state transition is None -> Initialize +VRRP_STATE_INITIALIZE = 'Initialize' +VRRP_STATE_MASTER = 'Master' +VRRP_STATE_BACKUP = 'Backup' + + +VRRP_MANAGER_NAME = 'VRRPManager' + + +class VRRPInterfaceBase(object): + """ + interface on which VRRP router works + vlan_id = None means no vlan. + NOTE: multiple virtual router can be configured on single port + See RFC 5798 4.2 Sample Configuration 2 + """ + def __init__(self, mac_address, primary_ip_address, vlan_id=None): + super(VRRPInterfaceBase, self).__init__() + self.mac_address = mac_address + self.primary_ip_address = primary_ip_address + self.vlan_id = vlan_id + + def __eq__(self, other): + return (self.__class__ == other.__class__ and + self.mac_address == other.mac_address and + self.primary_ip_address == other.primary_ip_address and + self.vlan_id == other.vlan_id) + + def __hash__(self): + return hash(( + addrconv.mac.text_to_bin(self.mac_address), + vrrp.ip_text_to_bin(self.primary_ip_address), self.vlan_id)) + + +class VRRPInterfaceNetworkDevice(VRRPInterfaceBase): + def __init__(self, mac_address, primary_ip_address, vlan_id, + device_name): + super(VRRPInterfaceNetworkDevice, self).__init__( + mac_address, primary_ip_address, vlan_id) + self.device_name = device_name + + def __str__(self): + return '%s<%s, %s, %s, %s>' % ( + self.__class__.__name__, + self.mac_address, + self.primary_ip_address, self.vlan_id, + self.device_name) + + def __eq__(self, other): + return (super(VRRPInterfaceNetworkDevice, self).__eq__(other) and + self.device_name == other.device_name) + + def __hash__(self): + return hash(( + addrconv.mac.text_to_bin(self.mac_address), + vrrp.ip_text_to_bin(self.primary_ip_address), self.vlan_id, + self.device_name)) + + +class VRRPInterfaceOpenFlow(VRRPInterfaceBase): + def __init__(self, mac_address, primary_ip_address, vlan_id, + dpid, port_no): + super(VRRPInterfaceOpenFlow, self).__init__( + mac_address, primary_ip_address, vlan_id) + self.dpid = dpid + self.port_no = port_no + + def __str__(self): + return '%s<%s, %s, %s, %s, %d>' % ( + self.__class__.__name__, + self.mac_address, + self.primary_ip_address, self.vlan_id, + dpid_lib.dpid_to_str(self.dpid), self.port_no) + + def __eq__(self, other): + return (super(VRRPInterfaceOpenFlow, self).__eq__(other) and + self.dpid == other.dpid and self.port_no == other.port_no) + + def __hash__(self): + return hash(( + addrconv.mac.text_to_bin(self.mac_address), + vrrp.ip_text_to_bin(self.primary_ip_address), self.vlan_id, + self.dpid, self.port_no)) + + +class VRRPConfig(object): + """ + advertmisement_interval is in seconds as float. (Not in centiseconds) + """ + def __init__(self, version=vrrp.VRRP_VERSION_V3, vrid=None, + priority=vrrp.VRRP_PRIORITY_BACKUP_DEFAULT, ip_addresses=None, + advertisement_interval=vrrp.VRRP_MAX_ADVER_INT_DEFAULT_IN_SEC, + preempt_mode=True, preempt_delay=0, accept_mode=False): + # To allow version and priority default + assert vrid is not None + assert ip_addresses is not None + super(VRRPConfig, self).__init__() + + self.version = version + self.vrid = vrid + self.priority = priority + self.ip_addresses = ip_addresses + self.advertisement_interval = advertisement_interval + self.preempt_mode = preempt_mode + self.preempt_delay = preempt_delay + self.accept_mode = accept_mode + + self.is_ipv6 = vrrp.is_ipv6(ip_addresses[0]) + + @property + def address_owner(self): + return self.priority == vrrp.VRRP_PRIORITY_ADDRESS_OWNER + + def __eq__(self, other): + return (self.version == other.version and + self.vrid == other.vrid and + self.priority == other.priority and + self.ip_addresses == other.ip_addresses and + self.advertisement_interval == other.advertisement_interval and + self.preempt_mode == other.preempt_mode and + self.preempt_delay == other.preempt_delay and + self.accept_mode == other.accept_mode and + self.is_ipv6 == other.is_ipv6) + + def __hash__(self): + hash((self.version, self.vrid, self.priority, + map(vrrp.ip_text_to_bin, self.ip_addresses), + self.advertisement_interval, self.preempt_mode, + self.preempt_delay, self.accept_mode, self.is_ipv6)) + + +class EventVRRPConfigRequest(event.EventRequestBase): + """ + Request from management layer to VRRP manager to initialize VRRP Router. + """ + def __init__(self, interface, config): + super(EventVRRPConfigRequest, self).__init__() + self.dst = VRRP_MANAGER_NAME + self.interface = interface + self.config = config + + +class EventVRRPConfigReply(event.EventReplyBase): + def __init__(self, instance_name, interface, config): + # dst = None. dst is filled by app_base.RyuApp.send_reply() + super(EventVRRPConfigReply, self).__init__(None) + self.instance_name = instance_name # None means failure + self.interface = interface + self.config = config + + +class EventVRRPShutdownRequest(event.EventRequestBase): + """ + Request from management layer to VRRP to shutdown VRRP Router. + """ + def __init__(self, instance_name): + super(EventVRRPShutdownRequest, self).__init__() + self.instance_name = instance_name + + +class EventVRRPStateChanged(event.EventBase): + """ + Event that this VRRP Router changed its state. + """ + def __init__(self, instance_name, monitor_name, interface, config, + old_state, new_state): + super(EventVRRPStateChanged, self).__init__() + self.instance_name = instance_name + self.monitor_name = monitor_name + self.interface = interface + self.config = config + self.old_state = old_state + self.new_state = new_state + + +class VRRPInstance(object): + def __init__(self, instance_name, monitor_name, config, interface, state): + super(VRRPInstance, self).__init__() + self.instance_name = instance_name + self.monitor_name = monitor_name + self.config = config + self.interface = interface + self.state = state + + +class EventVRRPListRequest(event.EventRequestBase): + """ + Event that requests list of configured VRRP router + instance_name=None means all instances. + """ + def __init__(self, instance_name=None): + super(EventVRRPListRequest, self).__init__() + self.instance_name = instance_name + + +class EventVRRPListReply(event.EventReplyBase): + def __init__(self, instance_list): + super(EventVRRPListReply, self).__init__(None) + self.instance_list = instance_list + + +class EventVRRPConfigChangeRequest(event.EventRequestBase): + """ + Event that requests to change configuration of a given VRRP router. + None means no-change. + """ + def __init__(self, instance_name, priority=None, + advertisement_interval=None, preempt_mode=None, + preempt_delay=None, accept_mode=None): + super(EventVRRPConfigChangeRequest, self).__init__() + self.instance_name = instance_name + self.priority = priority + self.advertisement_interval = advertisement_interval + self.preempt_mode = preempt_mode + self.preempt_delay = preempt_delay + self.accept_mode = accept_mode + + +# Following classes are internally used by VRRP + +class EventVRRPReceived(event.EventBase): + """ + Event that port manager received valid VRRP packet. + Usually handed by VRRP Router. + """ + def __init__(self, interface, packet): + super(EventVRRPReceived, self).__init__() + self.interface = interface + self.packet = packet + + +class EventVRRPTransmitRequest(event.EventRequestBase): + """ + Request from VRRP router to port manager to transmit VRRP packet. + """ + def __init__(self, data): + super(EventVRRPTransmitRequest, self).__init__() + self.data = data diff --git a/ryu/services/protocols/vrrp/manager.py b/ryu/services/protocols/vrrp/manager.py new file mode 100644 index 00000000..2a25ebc9 --- /dev/null +++ b/ryu/services/protocols/vrrp/manager.py @@ -0,0 +1,156 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2013 Isaku Yamahata +# +# 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. + +""" +VRRP manager that manages VRRP router instances +VRRPManager creates/deletes VRRPRouter, VRRPInterfaceMonitor +dynamically as requested. + +Usage example +PYTHONPATH=. ./bin/ryu-manager --verbose \ + ryu.services.protocols.vrrp.manager \ + ryu.services.protocols.vrrp.dumper +""" + +from ryu.base import app_manager +from ryu.controller import handler +from ryu.lib import hub +from ryu.services.protocols.vrrp import event as vrrp_event +from ryu.services.protocols.vrrp import monitor as vrrp_monitor +from ryu.services.protocols.vrrp import router as vrrp_router + + +class VRRPInstance(object): + def __init__(self, name, monitor_name, config, interface): + super(VRRPInstance, self).__init__() + self.name = name # vrrp_router.name + self.monitor_name = monitor_name # interface_monitor.name + self.config = config + self.interface = interface + self.state = None + + def state_changed(self, new_state): + self.state = new_state + + +class VRRPManager(app_manager.RyuApp): + @staticmethod + def _instance_name(interface, vrid, is_ipv6): + ip_version = 'ipv6' if is_ipv6 else 'ipv4' + return 'VRRP-Router-%s-%d-%s' % (str(interface), vrid, ip_version) + + def __init__(self, *args, **kwargs): + super(VRRPManager, self).__init__(*args, **kwargs) + self._args = args + self._kwargs = kwargs + self.name = vrrp_event.VRRP_MANAGER_NAME + self._instances = {} # name -> VRRPInstance + self.shutdown = hub.Queue() + + def start(self): + self.threads.append(hub.spawn(self._shutdown_loop)) + super(VRRPManager, self).start() + + @handler.set_ev_cls(vrrp_event.EventVRRPConfigRequest) + def config_request_handler(self, ev): + config = ev.config + interface = ev.interface + name = self._instance_name(interface, config.vrid, config.is_ipv6) + if name in self._instances: + rep = vrrp_event.EventVRRPConfigReply(None, interface, config) + self.reply_to_request(ev, rep) + return + + monitor = vrrp_monitor.VRRPInterfaceMonitor.factory( + interface, config, name, *self._args, **self._kwargs) + router = vrrp_router.VRRPRouter.factory( + name, monitor.name, interface, config, *self._args, **self._kwargs) + + # Event piping + # vrrp_router -> vrrp_manager + # EventVRRPStateChanged to vrrp_manager is handled by framework + # vrrp_manager -> vrrp_rouer + self.register_observer(vrrp_event.EventVRRPShutdownRequest, + router.name) + # vrrp_router -> vrrp_monitor + router.register_observer(vrrp_event.EventVRRPStateChanged, + monitor.name) + router.register_observer(vrrp_event.EventVRRPTransmitRequest, + monitor.name) + # vrrp_interface_monitor -> vrrp_router + monitor.register_observer(vrrp_event.EventVRRPReceived, router.name) + + instance = VRRPInstance(name, monitor.name, config, interface) + self._instances[name] = instance + #self.logger.debug('report_bricks') + #app_manager.AppManager.get_instance().report_bricks() # debug + monitor.start() + router.start() + + rep = vrrp_event.EventVRRPConfigReply(instance.name, interface, config) + self.reply_to_request(ev, rep) + + def _proxy_event(self, ev): + name = ev.instance_name + instance = self._instances.get(name, None) + if not instance: + self.logger.info('unknown vrrp router %s', name) + return + self.send_event(instance.name, ev) + + @handler.set_ev_cls(vrrp_event.EventVRRPShutdownRequest) + def shutdown_request_handler(self, ev): + self._proxy_event(ev) + + @handler.set_ev_cls(vrrp_event.EventVRRPConfigChangeRequest) + def config_change_request_handler(self, ev): + self._proxy_event(ev) + + @handler.set_ev_cls(vrrp_event.EventVRRPStateChanged) + def state_change_handler(self, ev): + instance = self._instances.get(ev.instance_name, None) + assert instance is not None + instance.state_changed(ev.new_state) + if ev.old_state and ev.new_state == vrrp_event.VRRP_STATE_INITIALIZE: + self.shutdown.put(instance) + + def _shutdown_loop(self): + app_mgr = app_manager.AppManager.get_instance() + while self.is_active or not self.shutdown.empty(): + instance = self.shutdown.get() + app_mgr.uninstantiate(instance.name) + app_mgr.uninstantiate(instance.monitor_name) + del self._instances[instance.name] + + @handler.set_ev_cls(vrrp_event.EventVRRPListRequest) + def list_request_handler(self, ev): + instance_name = ev.instance_name + if instance_name is None: + instance_list = [vrrp_event.VRRPInstance( + instance.name, instance.monitor_name, + instance.config, instance.interface, instance.state) + for instance in self._instances.values()] + else: + instance = self._instances.get(instance_name, None) + if instance is None: + instance_list = [] + else: + instance_list = [vrrp_event.VRRPInstance( + instance_name, instance.monitor_name, + instance.config, instance.interface, instance.state)] + + vrrp_list = vrrp_event.EventVRRPListReply(instance_list) + self.reply_to_request(ev, vrrp_list) diff --git a/ryu/services/protocols/vrrp/monitor.py b/ryu/services/protocols/vrrp/monitor.py new file mode 100644 index 00000000..8f3bde8f --- /dev/null +++ b/ryu/services/protocols/vrrp/monitor.py @@ -0,0 +1,151 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2013 Isaku Yamahata +# +# 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. +""" +Interface monitor. +Watching packet received on this interface and parse VRRP packet. + +VRRPManager creates/deletes instances of interface monitor dynamically. +""" + +from ryu.base import app_manager +from ryu.controller import handler +from ryu.lib.packet import packet +from ryu.lib.packet import vlan +from ryu.lib.packet import vrrp +from ryu.services.protocols.vrrp import event as vrrp_event + + +class VRRPInterfaceMonitor(app_manager.RyuApp): + # subclass of VRRPInterfaceBase -> subclass of VRRPInterfaceMonitor + _CONSTRUCTORS = {} + + @staticmethod + def register(interface_cls): + def _register(cls): + VRRPInterfaceMonitor._CONSTRUCTORS[interface_cls] = cls + return cls + return _register + + @staticmethod + def factory(interface, config, router_name, *args, **kwargs): + cls = VRRPInterfaceMonitor._CONSTRUCTORS[interface.__class__] + app_mgr = app_manager.AppManager.get_instance() + + kwargs = kwargs.copy() + kwargs['router_name'] = router_name + kwargs['vrrp_config'] = config + kwargs['vrrp_interface'] = interface + app = app_mgr.instantiate(cls, *args, **kwargs) + return app + + @classmethod + def instance_name(cls, interface, vrid): + return '%s-%s-%d' % (cls.__name__, str(interface), vrid) + + def __init__(self, *args, **kwargs): + super(VRRPInterfaceMonitor, self).__init__(*args, **kwargs) + self.config = kwargs['vrrp_config'] + self.interface = kwargs['vrrp_interface'] + self.router_name = kwargs['router_name'] + self.name = self.instance_name(self.interface, self.config.vrid) + + def _send_vrrp_packet_received(self, packet_data): + # OF doesn't support VRRP packet matching, so we have to parse + # it ourselvs. + packet_ = packet.Packet(packet_data) + protocols = packet_.protocols + + # we expect either of + # [ether, vlan, ip, vrrp{, padding}] + # or + # [ether, ip, vrrp{, padding}] + + if len(protocols) < 2: + self.logger.debug('len(protocols) %d', len(protocols)) + return + + vlan_vid = self.interface.vlan_id + may_vlan = protocols[1] + if (vlan_vid is not None) != isinstance(may_vlan, vlan.vlan): + self.logger.debug('vlan_vid: %s %s', vlan_vid, type(may_vlan)) + return + if vlan_vid is not None and vlan_vid != may_vlan.vid: + self.logger.debug('vlan_vid: %s vlan %s', vlan_vid, type(may_vlan)) + return + + # self.logger.debug('%s %s', packet_, packet_.protocols) + may_ip, may_vrrp = vrrp.vrrp.get_payload(packet_) + if not may_ip or not may_vrrp: + # self.logger.debug('may_ip %s may_vrrp %s', may_ip, may_vrrp) + return + if not vrrp.vrrp.is_valid_ttl(may_ip): + self.logger.debug('valid_ttl') + return + if may_vrrp.version != self.config.version: + self.logger.debug('vrrp version %d %d', + may_vrrp.version, self.config.version) + return + if not may_vrrp.is_valid(): + self.logger.debug('valid vrrp') + return + offset = 0 + for proto in packet_.protocols: + if proto == may_vrrp: + break + offset += len(proto) + if not may_vrrp.checksum_ok( + may_ip, packet_.data[offset:offset + len(may_vrrp)]): + self.logger.debug('bad checksum') + return + if may_vrrp.vrid != self.config.vrid: + self.logger.debug('vrid %d %d', may_vrrp.vrid, self.config.vrid) + return + if may_vrrp.is_ipv6 != self.config.is_ipv6: + self.logger.debug('is_ipv6 %s %s', + may_vrrp.is_ipv6, self.config.is_ipv6) + return + + # TODO: Optional check rfc5798 7.1 + # may_vrrp.ip_addresses equals to self.config.ip_addresses + + vrrp_received = vrrp_event.EventVRRPReceived(self.interface, packet_) + self.send_event(self.router_name, vrrp_received) + + @handler.set_ev_handler(vrrp_event.EventVRRPTransmitRequest) + def vrrp_transmit_request_handler(self, ev): + raise NotImplementedError() + + def _initialize(self): + raise NotImplementedError() + + def _shutdown(self): + raise NotImplementedError() + + @handler.set_ev_handler(vrrp_event.EventVRRPStateChanged) + def vrrp_state_changed_handler(self, ev): + assert ev.interface == self.interface + + if ev.new_state == vrrp_event.VRRP_STATE_INITIALIZE: + # add/del packet in rule + if ev.old_state: + self._shutdown() + else: + self._initialize() + elif ev.new_state in [vrrp_event.VRRP_STATE_BACKUP, + vrrp_event.VRRP_STATE_MASTER]: + pass + else: + raise RuntimeError('unknown vrrp state %s' % ev.new_state) diff --git a/ryu/services/protocols/vrrp/monitor_linux.py b/ryu/services/protocols/vrrp/monitor_linux.py new file mode 100644 index 00000000..f1b5efbe --- /dev/null +++ b/ryu/services/protocols/vrrp/monitor_linux.py @@ -0,0 +1,234 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2013 Isaku Yamahata +# +# 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. + +# tested on 64bit linux. +# On other platform like 32bit Linux, the structure can be different +# due to alignment difference. + +import contextlib +import fcntl +import socket +import struct + +from ryu.controller import handler +from ryu.ofproto import ether +from ryu.ofproto import inet +from ryu.lib import addrconv +from ryu.lib import hub +from ryu.lib.packet import arp +from ryu.lib.packet import vrrp +from ryu.services.protocols.vrrp import monitor +from ryu.services.protocols.vrrp import event as vrrp_event +from ryu.services.protocols.vrrp import utils + + +# Those are not defined in socket module +IFNAMSIZ = 16 +SS_MAXSIZE = 128 +SIOCGIFINDEX = 0x8933 # This is for Linux x64. May differ on other Linux +MCAST_JOIN_GROUP = 42 +MCAST_LEAVE_GROUP = 45 +PACKET_ADD_MEMBERSHIP = 1 +PACKET_DROP_MEMBERSHIP = 2 +PACKET_MR_MULTICAST = 0 +SOL_PACKET = 263 + + +def if_nametoindex(ifname): + # can the one defined in libc.so be used? + # + # IFNAMSIZE = 16 + # struct ifreq { + # char ifr_name[IFNAMSIZ]; /* Interface name */ + # union { + # struct sockaddr ifr_addr; + # struct sockaddr ifr_dstaddr; + # struct sockaddr ifr_broadaddr; + # struct sockaddr ifr_netmask; + # struct sockaddr ifr_hwaddr; + # short ifr_flags; + # int ifr_ifindex; + # int ifr_metric; + # int ifr_mtu; + # struct ifmap ifr_map; + # char ifr_slave[IFNAMSIZ]; + # char ifr_newname[IFNAMSIZ]; + # char *ifr_data; + # }; + # }; + PACK_STR = '16sI12x' + + # get ip address of the given interface + with contextlib.closing(socket.socket(socket.AF_INET, + socket.SOCK_DGRAM, 0)) as udp_socket: + ifreq = struct.pack(PACK_STR, ifname, 0) + res = fcntl.ioctl(udp_socket, SIOCGIFINDEX, ifreq) + return struct.unpack(PACK_STR, res)[1] + + +@monitor.VRRPInterfaceMonitor.register(vrrp_event.VRRPInterfaceNetworkDevice) +class VRRPInterfaceMonitorNetworkDevice(monitor.VRRPInterfaceMonitor): + """ + This module uses raw socket so that privilege(CAP_NET_ADMIN capability) + is required. + """ + def __init__(self, *args, **kwargs): + super(VRRPInterfaceMonitorNetworkDevice, self).__init__(*args, + **kwargs) + self.__is_active = True + config = self.config + if config.is_ipv6: + family = socket.AF_INET6 + ether_type = ether.ETH_TYPE_IPV6 + mac_address = vrrp.vrrp_ipv6_src_mac_address(config.vrid) + else: + family = socket.AF_INET + ether_type = ether.ETH_TYPE_IP + mac_address = vrrp.vrrp_ipv4_src_mac_address(config.vrid) + # socket module doesn't define IPPROTO_VRRP + self.ip_socket = socket.socket(family, socket.SOCK_RAW, + inet.IPPROTO_VRRP) + + self.packet_socket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, + socket.htons(ether_type)) + self.packet_socket.bind((self.interface.device_name, ether_type, + socket.PACKET_MULTICAST, + arp.ARP_HW_TYPE_ETHERNET, + addrconv.mac.text_to_bin(mac_address))) + + self.ifindex = if_nametoindex(self.interface.device_name) + + def start(self): + # discard received packets before joining multicast membership + packet_socket = self.packet_socket + packet_socket.setblocking(0) + with hub.Timeout(0.1, False): + while True: + try: + packet_socket.recv(1500) + except socket.error: + break + packet_socket.setblocking(1) + + self._join_multicast_membership(True) + self._join_vrrp_group(True) + super(VRRPInterfaceMonitorNetworkDevice, self).start() + self.threads.append(hub.spawn(self._recv_loop)) + + def stop(self): + self.__is_active = False + super(VRRPInterfaceMonitorNetworkDevice, self).stop() + + def _join_multicast_membership(self, join_leave): + config = self.config + if config.is_ipv6: + mac_address = vrrp.vrrp_ipv6_src_mac_address(config.vrid) + else: + mac_address = vrrp.vrrp_ipv4_src_mac_address(config.vrid) + if join_leave: + add_drop = PACKET_ADD_MEMBERSHIP + else: + add_drop = PACKET_DROP_MEMBERSHIP + packet_mreq = struct.pack('IHH8s', self.ifindex, + PACKET_MR_MULTICAST, 6, + addrconv.mac.text_to_bin(mac_address)) + self.packet_socket.setsockopt(SOL_PACKET, add_drop, packet_mreq) + + def _join_vrrp_group(self, join_leave): + if join_leave: + join_leave = MCAST_JOIN_GROUP + else: + join_leave = MCAST_LEAVE_GROUP + + # struct group_req { + # __u32 gr_interface; /* interface index */ + # struct __kernel_sockaddr_storage gr_group; /* group address */ + # }; + group_req = struct.pack('I', self.ifindex) + # padding to gr_group. This is environment dependent + group_req += '\x00' * (struct.calcsize('P') - struct.calcsize('I')) + if self.config.is_ipv6: + # struct sockaddr_in6 { + # sa_family_t sin6_family; /* AF_INET6 */ + # in_port_t sin6_port; /* port number */ + # uint32_t sin6_flowinfo; /* IPv6 flow information */ + # struct in6_addr sin6_addr; /* IPv6 address */ + # uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */ + # }; + # struct in6_addr { + # unsigned char s6_addr[16]; /* IPv6 address */ + # }; + family = socket.IPPROTO_IPV6 + sockaddr = struct.pack('H', socket.AF_INET6) + sockaddr += struct.pack('!H', 0) + sockaddr += struct.pack('!I', 0) + sockaddr += addrconv.ipv6.text_to_bin(vrrp.VRRP_IPV6_DST_ADDRESS) + sockaddr += struct.pack('I', 0) + else: + # #define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */ + # struct sockaddr_in { + # __kernel_sa_family_t sin_family; /* Address family */ + # __be16 sin_port; /* Port number */ + # struct in_addr sin_addr; /* Internet address */ + # /* Pad to size of `struct sockaddr'. */ + # unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - + # sizeof(unsigned short int) - sizeof(struct in_addr)]; + # }; + # struct in_addr { + # __be32 s_addr; + # }; + family = socket.IPPROTO_IP + sockaddr = struct.pack('H', socket.AF_INET) + sockaddr += struct.pack('!H', 0) + sockaddr += addrconv.ipv4.text_to_bin(vrrp.VRRP_IPV4_DST_ADDRESS) + + sockaddr += '\x00' * (SS_MAXSIZE - len(sockaddr)) + group_req += sockaddr + + self.ip_socket.setsockopt(family, join_leave, group_req) + return + + def _recv_loop(self): + packet_socket = self.packet_socket + packet_socket.settimeout(1.3) # to check activeness periodically + try: + while self.__is_active: + try: + buf = packet_socket.recv(128) + except socket.timeout: + self.logger.debug('timeout') + continue + if len(buf) == 0: + self.__is_active = False + break + + self.logger.debug('recv buf') + self._send_vrrp_packet_received(buf) + finally: + self._join_vrrp_group(False) + self._join_multicast_membership(False) + + @handler.set_ev_handler(vrrp_event.EventVRRPTransmitRequest) + def vrrp_transmit_request_handler(self, ev): + self.logger.debug('send') + self.packet_socket.sendto(ev.data, (self.interface.device_name, 0)) + + def _initialize(self): + # nothing + pass + + def _shutdown(self): + self.__is_active = False diff --git a/ryu/services/protocols/vrrp/monitor_openflow.py b/ryu/services/protocols/vrrp/monitor_openflow.py new file mode 100644 index 00000000..32e691e8 --- /dev/null +++ b/ryu/services/protocols/vrrp/monitor_openflow.py @@ -0,0 +1,141 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2013 Isaku Yamahata +# +# 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. + +from ryu.controller import handler +from ryu.controller import ofp_event +from ryu.lib import dpid as dpid_lib +from ryu.lib.packet import vrrp +from ryu.ofproto import ether +from ryu.ofproto import inet +from ryu.ofproto import ofproto_v1_2 +from ryu.ofproto import ofproto_v1_3 +from ryu.services.protocols.vrrp import monitor +from ryu.services.protocols.vrrp import event as vrrp_event +from ryu.services.protocols.vrrp import utils + + +@monitor.VRRPInterfaceMonitor.register(vrrp_event.VRRPInterfaceOpenFlow) +class VRRPInterfaceMonitorOpenFlow(monitor.VRRPInterfaceMonitor): + # OF1.2 + OFP_VERSIONS = [ofproto_v1_2.OFP_VERSION, + ofproto_v1_3.OFP_VERSION] # probably work with OF1.3 + + _TABLE = 0 # generate packet-in in this table + _PRIORITY = 0x8000 # default priority + + def __init__(self, *args, **kwargs): + super(VRRPInterfaceMonitorOpenFlow, self).__init__(*args, **kwargs) + table = kwargs.get('vrrp_imof_table', None) + if table is not None: + self._TABLE = int(table) + priority = kwargs.get('vrrp_imof_priority', None) + if priority is not None: + self._PRIORITY = int(priority) + + @handler.set_ev_cls(ofp_event.EventOFPPacketIn, handler.MAIN_DISPATCHER) + def packet_in_handler(self, ev): + self.logger.debug('packet_in_handler') + msg = ev.msg + datapath = msg.datapath + ofproto = datapath.ofproto + + # TODO: subscribe only the designated datapath + dpid = datapath.id + if dpid != self.interface.dpid: + self.logger.debug('packet_in_handler dpid %s %s', + dpid_lib.dpid_to_str(dpid), + dpid_lib.dpid_to_str(self.interface.dpid)) + return + + in_port = None + for field in msg.match.fields: + if field.header == ofproto.OXM_OF_IN_PORT: + in_port = field.value + break + + if in_port != self.interface.port_no: + self.logger.debug('packet_in_handler in_port %s %s', + in_port, self.interface.port_no) + return + + self._send_vrrp_packet_received(msg.data) + + def _get_dp(self): + return utils.get_dp(self, self.interface.dpid) + + @handler.set_ev_handler(vrrp_event.EventVRRPTransmitRequest) + def vrrp_transmit_request_handler(self, ev): + dp = self._get_dp() + if not dp: + return + utils.dp_packet_out(dp, self.interface.port_no, ev.data) + + def _ofp_match(self, ofproto_parser): + is_ipv6 = vrrp.is_ipv6(self.config.ip_addresses[0]) + kwargs = {} + kwargs['in_port'] = self.interface.port_no + if is_ipv6: + kwargs['eth_dst'] = vrrp.VRRP_IPV6_DST_MAC_ADDRESS + kwargs['eth_src'] = \ + vrrp.vrrp_ipv6_src_mac_address(self.config.vrid) + kwargs['eth_type'] = ether.ETH_TYPE_IPV6 + kwargs['ipv6_dst'] = vrrp.VRRP_IPV6_DST_ADDRESS + else: + kwargs['eth_dst'] = vrrp.VRRP_IPV4_DST_MAC_ADDRESS + kwargs['eth_src'] = \ + vrrp.vrrp_ipv4_src_mac_address(self.config.vrid) + kwargs['eth_type'] = ether.ETH_TYPE_IP + kwargs['ipv4_dst'] = vrrp.VRRP_IPV4_DST_ADDRESS + + if self.interface.vlan_id is not None: + kwargs['vlan_vid'] = self.interface.vlan_id + kwargs['ip_proto'] = inet.IPPROTO_VRRP + # OF1.2 doesn't support TTL match. + # It needs to be checked by packet in handler + + return ofproto_parser.OFPMatch(**kwargs) + + def _initialize(self): + dp = self._get_dp() + if not dp: + return + + ofproto = dp.ofproto + ofproto_parser = dp.ofproto_parser + + match = self._ofp_match(ofproto_parser) + utils.dp_flow_mod(dp, self._TABLE, ofproto.OFPFC_DELETE_STRICT, + self._PRIORITY, match, [], + out_port=ofproto.OFPP_CONTROLLER) + + match = self._ofp_match(ofproto_parser) + 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._TABLE, ofproto.OFPFC_ADD, self._PRIORITY, + match, instructions) + + def _shutdown(self): + dp = self._get_dp() + if not dp: + return + + ofproto = dp.ofproto + match = self._ofp_match(dp.ofproto_parser) + utils.dp_flow_mod(dp, self._TABLE, ofproto.OFPFC_DELETE_STRICT, + self._PRIORITY, match, [], + out_port=ofproto.OFPP_CONTROLLER) diff --git a/ryu/services/protocols/vrrp/router.py b/ryu/services/protocols/vrrp/router.py new file mode 100644 index 00000000..601c91b5 --- /dev/null +++ b/ryu/services/protocols/vrrp/router.py @@ -0,0 +1,687 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2013 Isaku Yamahata +# +# 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. + +""" +VRRP state machine implementation + +VRRPManager creates/deletes VRRPRounter instances dynamically. +""" + +import abc + +from ryu.base import app_manager +from ryu.controller import event +from ryu.controller import handler +from ryu.lib import hub +from ryu.lib.packet import vrrp +from ryu.services.protocols.vrrp import event as vrrp_event +from ryu.services.protocols.vrrp import api as vrrp_api + + +# TODO: improve Timer service and move it into framework +class Timer(object): + def __init__(self, handler_): + assert callable(handler_) + + super(Timer, self).__init__() + self._handler = handler_ + self._event = hub.Event() + self._thread = None + + def start(self, interval): + """interval is in seconds""" + if self._thread: + self.cancel() + self._event.clear() + self._thread = hub.spawn(self._timer, interval) + + def cancel(self): + if self._thread is None: + return + self._event.set() + hub.joinall([self._thread]) + self._thread = None + + def is_running(self): + return self._thread is not None + + def _timer(self, interval): + # Avoid cancellation during execution of self._callable() + cancel = self._event.wait(interval) + if cancel: + return + + self._handler() + + +class TimerEventSender(Timer): + # timeout handler is called by timer thread context. + # So in order to actual execution context to application's event thread, + # post the event to the application + def __init__(self, app, ev_cls): + super(TimerEventSender, self).__init__(self._timeout) + self._app = app + self._ev_cls = ev_cls + + def _timeout(self): + self._app.send_event(self._app.name, self._ev_cls()) + + +class VRRPParams(object): + def __init__(self, config): + self.config = config + self.master_adver_interval = None # In seconds + + @property + def skew_time(self): + # In seconds + config = self.config + version = config.version + priority = config.priority + if config.version == vrrp.VRRP_VERSION_V2: + return (256.0 - priority) / 256.0 + if config.version == vrrp.VRRP_VERSION_V3: + return (((256.0 - priority) * self.master_adver_interval) / 256.0) + raise ValueError('unknown vrrp version %d' % version) + + @property + def master_down_interval(self): + # In seconds + return (3.0 * self.master_adver_interval) + self.skew_time + + +class VRRPState(object): + __metaclass__ = abc.ABCMeta + + def __init__(self, vrrp_router): + super(VRRPState, self).__init__() + self.vrrp_router = vrrp_router + + @abc.abstractmethod + def master_down(self, ev): + pass + + @abc.abstractmethod + def adver(self, ev): + pass + + @abc.abstractmethod + def preempt_delay(self, ev): + pass + + @abc.abstractmethod + def vrrp_received(self, ev): + pass + + @abc.abstractmethod + def vrrp_shutdown_request(self, ev): + pass + + @abc.abstractmethod + def vrrp_config_change_request(self, ev): + pass + + +class VRRPRouter(app_manager.RyuApp): + _EVENTS = [vrrp_event.EventVRRPStateChanged] + _CONSTRUCTORS = {} + _STATE_MAP = {} # should be overrided by concrete class + + @staticmethod + def register(version): + def _register(cls): + VRRPRouter._CONSTRUCTORS[version] = cls + return cls + return _register + + @staticmethod + def factory(name, monitor_name, interface, config, *args, **kwargs): + cls = VRRPRouter._CONSTRUCTORS[config.version] + app_mgr = app_manager.AppManager.get_instance() + kwargs = kwargs.copy() + kwargs['name'] = name + kwargs['monitor_name'] = monitor_name + kwargs['vrrp_interface'] = interface + kwargs['vrrp_config'] = config + return app_mgr.instantiate(cls, *args, **kwargs) + + class _EventMasterDown(event.EventBase): + pass + + class _EventAdver(event.EventBase): + pass + + class _EventPreemptDelay(event.EventBase): + pass + + def __init__(self, *args, **kwargs): + super(VRRPRouter, self).__init__(*args, **kwargs) + self.name = kwargs['name'] + self.monitor_name = kwargs['monitor_name'] + self.interface = kwargs['vrrp_interface'] + self.config = kwargs['vrrp_config'] + self.params = VRRPParams(self.config) + self.state = None + self.state_impl = None + self.vrrp = None + + self.master_down_timer = TimerEventSender(self, self._EventMasterDown) + self.adver_timer = TimerEventSender(self, self._EventAdver) + self.preempt_delay_timer = TimerEventSender(self, + self._EventPreemptDelay) + self.register_observer(self._EventMasterDown, self.name) + self.register_observer(self._EventAdver, self.name) + + def send_advertisement(self, release=False): + if self.vrrp is None: + config = self.config + max_adver_int = vrrp.vrrp.sec_to_max_adver_int( + config.version, config.advertisement_interval) + self.vrrp = vrrp.vrrp.create_version( + config.version, vrrp.VRRP_TYPE_ADVERTISEMENT, config.vrid, + config.priority, max_adver_int, config.ip_addresses) + + vrrp_ = self.vrrp + if release: + vrrp_ = vrrp_.create(vrrp_.type, vrrp_.vrid, + vrrp.VRRP_PRIORITY_RELEASE_RESPONSIBILITY, + vrrp_.max_adver_int, vrrp_.ip_addresses) + + # create packet frame each time to generate new ip identity + interface = self.interface + packet_ = vrrp_.create_packet(interface.primary_ip_address, + interface.vlan_id) + packet_.serialize() + vrrp_api.vrrp_transmit(self, self.monitor_name, packet_.data) + + def state_change(self, new_state): + old_state = self.state + self.state = new_state + self.state_impl = self._STATE_MAP[new_state](self) + state_changed = vrrp_event.EventVRRPStateChanged( + self.name, self.monitor_name, self.interface, self.config, + old_state, new_state) + self.send_event_to_observers(state_changed) + + @handler.set_ev_handler(_EventMasterDown) + def master_down_handler(self, ev): + self.state_impl.master_down(ev) + + @handler.set_ev_handler(_EventAdver) + def adver_handler(self, ev): + self.state_impl.adver(ev) + + @handler.set_ev_handler(_EventPreemptDelay) + def preempt_delay_handler(self, ev): + self.state_impl.preempt_delay(ev) + + @handler.set_ev_handler(vrrp_event.EventVRRPReceived) + def vrrp_received_handler(self, ev): + self.state_impl.vrrp_received(ev) + + @handler.set_ev_handler(vrrp_event.EventVRRPShutdownRequest) + def vrrp_shutdown_request_handler(self, ev): + assert ev.instance_name == self.name + self.state_impl.vrrp_shutdown_request(ev) + + @handler.set_ev_handler(vrrp_event.EventVRRPConfigChangeRequest) + def vrrp_config_change_request_handler(self, ev): + config = self.config + if ev.priority is not None: + config.priority = ev.priority + if ev.advertisement_interval is not None: + config.advertisement_interval = ev.advertisement_interval + if ev.preempt_mode is not None: + config.preempt_mode = ev.preempt_mode + if ev.preempt_delay is not None: + config.preempt_delay = ev.preempt_delay + if ev.accept_mode is not None: + config.accept_mode = ev.accept_mode + + # force to recreate cached vrrp packet + self.vrrp = None + + self.state_impl.vrrp_config_change_request(ev) + + +# RFC defines that start timer, then change the state. +# This causes the race between state change and event dispatching. +# So our implementation does, state change, then start timer + + +class VRRPV2StateInitialize(VRRPState): + # In theory this shouldn't be called. + def master_down(self, ev): + self.vrrp_router.logger.warn('%s master_down', self.__class__.__name__) + + def adver(self, ev): + self.vrrp_router.logger.warn('%s adver', self.__class__.__name__) + + def preempt_delay(self, ev): + self.vrrp_router.logger.warn('%s preempt_delay', + self.__class__.__name__) + + def vrrp_received(self, ev): + self.vrrp_router.logger.warn('%s vrrp_received', + self.__class__.__name__) + + def vrrp_shutdown_request(self, ev): + self.vrrp_router.logger.warn('%s vrrp_shutdown_request', + self.__class__.__name__) + + def vrrp_config_change_request(self, ev): + self.vrrp_router.logger.warn('%s vrrp_config_change_request', + self.__class__.__name__) + + +class VRRPV2StateMaster(VRRPState): + def master_down(self, ev): + # should not reach here. + # In fact this can be happned due to event scheduling + vrrp_router = self.vrrp_router + vrrp_router.logger.debug('%s master_down %s %s' % ( + self.__class__.__name__, ev.__class__.__name__, vrrp_router.state)) + + def _adver(self): + vrrp_router = self.vrrp_router + vrrp_router.send_advertisement() + vrrp_router.adver_timer.start( + vrrp_router.config.advertisement_interval) + + def adver(self, ev): + self.vrrp_router.logger.debug('%s adver', self.__class__.__name__) + self._adver() + + def preempt_delay(self, ev): + self.vrrp_router.logger.warn('%s preempt_delay', + self.__class__.__name__) + + def vrrp_received(self, ev): + vrrp_router = self.vrrp_router + vrrp_router.logger.debug('%s vrrp_received', self.__class__.__name__) + + ip, vrrp_ = vrrp.vrrp.get_payload(ev.packet) + config = vrrp_router.config + if vrrp_.priority == 0: + vrrp_router.send_advertisement() + vrrp_router.adver_timer.start(config.advertisement_interval) + else: + params = vrrp_router.params + if (config.priority < vrrp_.priority or + (config.priority == vrrp_.priority and + vrrp.ip_address_lt(vrrp_router.interface.primary_ip_address, + ip.src))): + vrrp_router.adver_timer.cancel() + + vrrp_router.state_change(vrrp_event.VRRP_STATE_BACKUP) + vrrp_router.master_down_timer.start( + params.master_down_interval) + + def vrrp_shutdown_request(self, ev): + vrrp_router = self.vrrp_router + vrrp_router.logger.debug('%s vrrp_shutdown_request', + self.__class__.__name__) + + vrrp_router.adver_timer.cancel() + vrrp_router.send_advertisement(True) + vrrp_router.state_change(vrrp_event.VRRP_STATE_INITIALIZE) + + def vrrp_config_change_request(self, ev): + vrrp_router = self.vrrp_router + vrrp_router.logger.warn('%s vrrp_config_change_request', + self.__class__.__name__) + if ev.priority is not None or ev.advertisement_interval is not None: + vrrp_router.adver_timer.cancel() + self._adver() + + +class VRRPV2StateBackup(VRRPState): + def _master_down(self): + vrrp_router = self.vrrp_router + vrrp_router.send_advertisement() + + # This action should be done router on + # EventVRRPStateChanged(VRRP_STATE_BACKUP->VRRP_STATE_MASTER) + # + # RFC3768 6.4.2 Backup + # o Broadcast a gratuitous ARP request containing the virtual + # router MAC address for each IP address associated with the + # virtual router + + # RACE: actual router has the responsiblity to send garp. + # so due to thread scheduling there is a race between + # actual router sending GARP and VRRPRouter becoming + # master/backup + + vrrp_router.preempt_delay_timer.cancel() + vrrp_router.state_change(vrrp_event.VRRP_STATE_MASTER) + vrrp_router.adver_timer.start( + vrrp_router.config.advertisement_interval) + + def master_down(self, ev): + self.vrrp_router.logger.debug('%s master_down', + self.__class__.__name__) + self._master_down() + + def adver(self, ev): + # should not reach here + # In fact this can be happned due to event scheduling + vrrp_router = self.vrrp_router + vrrp_router.logger.debug('%s adver %s %s' % ( + self.__class__.__name__, ev.__class__.__name__, vrrp_router.state)) + + def preempt_delay(self, ev): + self.vrrp_router.logger.warn('%s preempt_delay', + self.__class__.__name__) + self._master_down() + + def vrrp_received(self, ev): + vrrp_router = self.vrrp_router + vrrp_router.logger.debug('%s vrrp_received', self.__class__.__name__) + + _ip, vrrp_ = vrrp.vrrp.get_payload(ev.packet) + if vrrp_.priority == 0: + vrrp_router.master_down_timer.start(vrrp_router.params.skew_time) + else: + config = vrrp_router.config + params = vrrp_router.params + if (not config.preempt_mode or config.priority <= vrrp_.priority): + vrrp_router.preempt_delay_timer.cancel() + vrrp_router.master_down_timer.start( + params.master_down_interval) + elif (config.preempt_mode and config.preempt_delay > 0 and + config.priority > vrrp_.priority): + if not vrrp_router.preempt_delay_timer.is_running(): + vrrp_router.preempt_delay_timer.start(config.preempt_delay) + vrrp_router.master_down_timer.start( + params.master_down_interval) + + def vrrp_shutdown_request(self, ev): + vrrp_router = self.vrrp_router + vrrp_router.logger.debug('%s vrrp_shutdown_request', + self.__class__.__name__) + + vrrp_router.master_down_timer.cancel() + vrrp_router.preempt_delay_timer.cancel() + vrrp_router.state_change(vrrp_event.VRRP_STATE_INITIALIZE) + + def vrrp_config_change_request(self, ev): + vrrp_router = self.vrrp_router + vrrp_router.logger.warn('%s vrrp_config_change_request', + self.__class__.__name__) + if ev.priority is not None and vrrp_router.config.address_owner: + vrrp_router.master_down_timer.cancel() + self._master_down() + if ev.preempt_mode is not None or ev.preempt_delay is not None: + vrrp_router.preempt_delay_timer.cancel() + + +@VRRPRouter.register(vrrp.VRRP_VERSION_V2) +class VRRPRouterV2(VRRPRouter): + _STATE_MAP = { + vrrp_event.VRRP_STATE_INITIALIZE: VRRPV2StateInitialize, + vrrp_event.VRRP_STATE_MASTER: VRRPV2StateMaster, + vrrp_event.VRRP_STATE_BACKUP: VRRPV2StateBackup, + } + + def __init__(self, *args, **kwargs): + super(VRRPRouterV2, self).__init__(*args, **kwargs) + + def start(self): + params = self.params + params.master_adver_interval = self.config.advertisement_interval + self.state_change(vrrp_event.VRRP_STATE_INITIALIZE) + if self.config.address_owner: + self.send_advertisement() + + # This action should be done router on + # EventVRRPStateChanged(None->VRRP_STATE_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. + + self.state_change(vrrp_event.VRRP_STATE_MASTER) + self.adver_timer.start(self.config.advertisement_interval) + else: + self.state_change(vrrp_event.VRRP_STATE_BACKUP) + self.master_down_timer.start(params.master_down_interval) + + super(VRRPRouterV2, self).start() + + +class VRRPV3StateInitialize(VRRPState): + # In theory this shouldn't be called. + def master_down(self, ev): + self.vrrp_router.logger.debug('%s master_down', + self.__class__.__name__) + + def adver(self, ev): + self.vrrp_router.logger.debug('%s adver', self.__class__.__name__) + + def preempt_delay(self, ev): + self.vrrp_router.logger.warn('%s preempt_delay', + self.__class__.__name__) + + def vrrp_received(self, ev): + self.vrrp_router.logger.debug('%s vrrp_received', + self.__class__.__name__) + + def vrrp_shutdown_request(self, ev): + self.vrrp_router.logger.debug('%s vrrp_shutdown_request', + self.__class__.__name__) + + def vrrp_config_change_request(self, ev): + self.vrrp_router.logger.warn('%s vrrp_config_change_request', + self.__class__.__name__) + + +class VRRPV3StateMaster(VRRPState): + def master_down(self, ev): + # should not reach here + # In fact this can be happned due to event scheduling + vrrp_router = self.vrrp_router + vrrp_router.logger.debug('%s master_down %s %s' % ( + self.__class__.__name__, ev.__class__.__name__, vrrp_router.state)) + + def _adver(self): + vrrp_router = self.vrrp_router + vrrp_router.send_advertisement() + vrrp_router.adver_timer.start( + vrrp_router.config.advertisement_interval) + + def adver(self, ev): + self.vrrp_router.logger.debug('%s adver', self.__class__.__name__) + self._adver() + + def preempt_delay(self, ev): + self.vrrp_router.logger.warn('%s preempt_delay', + self.__class__.__name__) + + def vrrp_received(self, ev): + vrrp_router = self.vrrp_router + vrrp_router.logger.debug('%s vrrp_received', self.__class__.__name__) + + ip, vrrp_ = vrrp.vrrp.get_payload(ev.packet) + config = vrrp_router.config + if vrrp_.priority == 0: + vrrp_router.send_advertisement() + vrrp_router.adver_timer.start(config.advertisement_interval) + else: + params = vrrp_router.params + if (config.priority < vrrp_.priority or + (config.priority == vrrp_.priority and + vrrp.ip_address_lt(vrrp_router.interface.primary_ip_address, + ip.src))): + vrrp_router.adver_timer.cancel() + params.master_adver_interval = vrrp_.max_adver_int_in_sec + + vrrp_router.state_change(vrrp_event.VRRP_STATE_BACKUP) + vrrp_router.master_down_timer.start( + params.master_down_interval) + + def vrrp_shutdown_request(self, ev): + vrrp_router = self.vrrp_router + vrrp_router.logger.debug('%s vrrp_shutdown_request', + self.__class__.__name__) + + vrrp_router.adver_timer.cancel() + vrrp_router.send_advertisement(True) + vrrp_router.state_change(vrrp_event.VRRP_STATE_INITIALIZE) + + def vrrp_config_change_request(self, ev): + vrrp_router = self.vrrp_router + vrrp_router.logger.warn('%s vrrp_config_change_request', + self.__class__.__name__) + if ev.priority is not None or ev.advertisement_interval is not None: + vrrp_router.adver_timer.cancel() + self._adver() + + +class VRRPV3StateBackup(VRRPState): + def _master_down(self): + vrrp_router = self.vrrp_router + vrrp_router.send_advertisement() + + # This action should be done by router on + # EventStateChange(VRRP_SATE_BACKUP -> VRRP_STATE_MASTER) + # + # 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. + + # RACE: actual router has the responsiblity to send garp. + # so due to thread scheduling there is a race between + # actual router sending GARP and VRRPRouter becoming + # master/backup + + vrrp_router.preempt_delay_timer.cancel() + vrrp_router.state_change(vrrp_event.VRRP_STATE_MASTER) + vrrp_router.adver_timer.start( + vrrp_router.config.advertisement_interval) + + def master_down(self, ev): + self.vrrp_router.logger.debug('%s master_down', + self.__class__.__name__) + self._master_down() + + def adver(self, ev): + # should not reach here + # In fact this can be happned due to event scheduling + vrrp_router = self.vrrp_router + vrrp_router.logger.debug('adver %s %s %s' % ( + self.__class__.__name__, ev.__class__.__name__, vrrp_router.state)) + + def preempt_delay(self, ev): + self.vrrp_router.logger.warn('%s preempt_delay', + self.__class__.__name__) + self._master_down() + + def vrrp_received(self, ev): + vrrp_router = self.vrrp_router + vrrp_router.logger.debug('%s vrrp_received', self.__class__.__name__) + + _ip, vrrp_ = vrrp.vrrp.get_payload(ev.packet) + if vrrp_.priority == 0: + vrrp_router.master_down_timer.start(vrrp_router.params.skew_time) + else: + params = vrrp_router.params + config = vrrp_router.config + if (not config.preempt_mode or config.priority <= vrrp_.priority): + params.master_adver_interval = vrrp_.max_adver_int_in_sec + vrrp_router.master_down_timer.start( + params.master_down_interval) + elif (config.preempt_mode and config.preempt_delay > 0 and + config.priority > vrrp_.priority): + if not vrrp_router.preempt_delay_timer.is_running(): + vrrp_router.preempt_delay_timer.start(config.preempt_delay) + vrrp_router.master_down_timer.start( + params.master_down_interval) + + def vrrp_shutdown_request(self, ev): + vrrp_router = self.vrrp_router + vrrp_router.logger.debug('%s vrrp_shutdown_request', + self.__class__.__name__) + + vrrp_router.preempt_delay_timer.cancel() + vrrp_router.master_down_timer.cancel() + vrrp_router.state_change(vrrp_event.VRRP_STATE_INITIALIZE) + + def vrrp_config_change_request(self, ev): + vrrp_router = self.vrrp_router + vrrp_router.logger.warn('%s vrrp_config_change_request', + self.__class__.__name__) + if ev.priority is not None and vrrp_router.config.address_owner: + vrrp_router.master_down_timer.cancel() + self._master_down() + if ev.preempt_mode is not None or ev.preempt_delay is not None: + vrrp_router.preempt_delay_timer.cancel() + + +@VRRPRouter.register(vrrp.VRRP_VERSION_V3) +class VRRPRouterV3(VRRPRouter): + _STATE_MAP = { + vrrp_event.VRRP_STATE_INITIALIZE: VRRPV3StateInitialize, + vrrp_event.VRRP_STATE_MASTER: VRRPV3StateMaster, + vrrp_event.VRRP_STATE_BACKUP: VRRPV3StateBackup, + } + + def __init__(self, *args, **kwargs): + super(VRRPRouterV3, self).__init__(*args, **kwargs) + + def start(self): + self.state_change(vrrp_event.VRRP_STATE_INITIALIZE) + if self.config.address_owner: + self.send_advertisement() + + # This action should be done router on + # EventVRRPStateChanged(None->VRRP_STATE_MASTER) + # + # 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. + + self.state_change(vrrp_event.VRRP_STATE_MASTER) + self.adver_timer.start(self.config.advertisement_interval) + else: + params = self.params + params.master_adver_interval = self.config.advertisement_interval + self.state_change(vrrp_event.VRRP_STATE_BACKUP) + self.master_down_timer.start(params.master_down_interval) + + super(VRRPRouterV3, self).start() diff --git a/ryu/services/protocols/vrrp/sample_manager.py b/ryu/services/protocols/vrrp/sample_manager.py new file mode 100644 index 00000000..f55e3d59 --- /dev/null +++ b/ryu/services/protocols/vrrp/sample_manager.py @@ -0,0 +1,99 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2013 Isaku Yamahata +# +# 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. + +""" +sample router manager. +(un-)instantiate routers +Usage example: +PYTHONPATH=. ./bin/ryu-manager --verbose \ + ryu.services.protocols.vrrp.manager \ + ryu.services.protocols.vrrp.dumper \ + ryu.services.protocols.vrrp.sample_manager +""" + +from ryu.base import app_manager +from ryu.controller import handler +from ryu.services.protocols.vrrp import event as vrrp_event +from ryu.services.protocols.vrrp import sample_router + + +class RouterManager(app_manager.RyuApp): + _ROUTER_CLASSES = { + vrrp_event.VRRPInterfaceNetworkDevice: { + 4: sample_router.RouterIPV4Linux, + 6: sample_router.RouterIPV6Linux, + }, + vrrp_event.VRRPInterfaceOpenFlow: { + 4: sample_router.RouterIPV4OpenFlow, + 6: sample_router.RouterIPV6OpenFlow, + }, + } + + def __init__(self, *args, **kwargs): + super(RouterManager, self).__init__(*args, **kwargs) + self._args = args + self._kwargs = kwargs + self.routers = {} # instance name -> router name + + def _router_factory(self, instance_name, monitor_name, interface, config): + cls = None + for interface_cls, router_clses in self._ROUTER_CLASSES.items(): + if isinstance(interface, interface_cls): + if config.is_ipv6: + cls = router_clses[6] + else: + cls = router_clses[4] + break + + self.logger.debug('interface %s %s', type(interface), interface) + self.logger.debug('cls %s', cls) + if cls is None: + raise ValueError('Unknown interface type %s %s' % (type(interface), + interface)) + kwargs = self._kwargs.copy() + kwargs.update({ + 'name': instance_name, + 'monitor_name': monitor_name, + 'config': config, + 'interface': interface, + }) + app_mgr = app_manager.AppManager.get_instance() + return app_mgr.instantiate(cls, *self._args, **kwargs) + + @handler.set_ev_cls(vrrp_event.EventVRRPStateChanged) + def vrrp_state_changed_handler(self, ev): + if ev.new_state == vrrp_event.VRRP_STATE_INITIALIZE: + if ev.old_state: + self._shutdown(ev) + else: + self._initialize(ev) + return + + router_name = self.routers.get(ev.instance_name) + self.send_event(router_name, ev) + + def _initialize(self, ev): + router = self._router_factory(ev.instance_name, ev.monitor_name, + ev.interface, ev.config) + self.routers[ev.instance_name] = router.name + self.send_event(router.name, ev) + router.start() + + def _shutdown(self, ev): + router_name = self.routers.pop(ev.instance_name) + self.send_event(router_name, ev) + app_mgr = app_manager.AppManager.get_instance() + app_mgr.uninstantiate(router_name) diff --git a/ryu/services/protocols/vrrp/sample_router.py b/ryu/services/protocols/vrrp/sample_router.py new file mode 100644 index 00000000..ca834d29 --- /dev/null +++ b/ryu/services/protocols/vrrp/sample_router.py @@ -0,0 +1,539 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2013 Isaku Yamahata +# +# 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 +Those routers needs to be created by someone else. +sample_manager.routerManager is an example. +Usage example: +PYTHONPATH=. ./bin/ryu-manager --verbose \ + ryu.services.protocols.vrrp.manager \ + ryu.services.protocols.vrrp.dumper \ + ryu.services.protocols.vrrp.sample_manager +""" + +import contextlib +import greenlet +import socket + +from ryu.base import app_manager +from ryu.controller import handler +from ryu.controller import ofp_event +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 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.protocols.vrrp import api as vrrp_api +from ryu.services.protocols.vrrp import event as vrrp_event +from ryu.services.protocols.vrrp import utils + + +class RouterBase(app_manager.RyuApp): + def _router_name(self, config, interface): + ip_version = 'ipv6' if config.is_ipv6 else 'ipv4' + return '%s-%s-%d-%s' % (self.__class__.__name__, + str(interface), config.vrid, ip_version) + + 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'] + self.name = self._router_name(self.config, self.interface) + + def _transmit(self, data): + vrrp_api.vrrp_transmit(self, self.monitor_name, data) + + 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 + self.logger.debug('sample router %s -> %s', old_state, new_state) + if new_state == vrrp_event.VRRP_STATE_MASTER: + if old_state == vrrp_event.VRRP_STATE_INITIALIZE: + 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 _garp_packet(self, ip_address): + # prepare garp packet + src_mac = vrrp.vrrp_ipv4_src_mac_address(self.config.vrid) + e = ethernet.ethernet(mac_lib.BROADCAST_STR, src_mac, + ether.ETH_TYPE_ARP) + a = arp.arp_ip(arp.ARP_REQUEST, src_mac, ip_address, + mac_lib.DONTCARE_STR, ip_address) + + p = packet.Packet() + p.add_protocol(e) + utils.may_add_vlan(p, self.interface.vlan_id) + p.add_protocol(a) + p.serialize() + return p + + def __init__(self, *args, **kwargs): + super(RouterIPV4, self).__init__(*args, **kwargs) + assert not self.config.is_ipv6 + + self.garp_packets = [self._garp_packet(ip_address) + for ip_address in self.config.ip_addresses] + + def _send_garp(self): + self.logger.debug('_send_garp') + for garp_packet in self.garp_packets: + self._transmit(garp_packet.data) + + def _arp_reply_packet(self, arp_req_sha, arp_req_spa, arp_req_tpa): + if not (arp_req_tpa in self.config.ip_addresses or + arp_req_tpa == self.config.primary_ip_address): + return None + + 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, arp_req_tpa, + 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() + self._transmit(p.data) + + def _arp_process(self, data): + dst_mac = vrrp.vrrp_ipv4_src_mac_address(self.config.vrid) + arp_sha = None + arp_spa = None + arp_tpa = None + + p = packet.Packet(data) + for proto in p.protocols: + if isinstance(proto, ethernet.ethernet): + if proto.dst not in (mac_lib.BROADCAST_STR, 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.opcode != arp.ARP_REQUEST or + proto.dst_mac != dst_mac): + return None + arp_sha = proto.src_mac + arp_spa = proto.src_ip + arp_tpa = proto.dst_ip + break + + if arp_sha is None or arp_spa is None or arp_tpa is None: + self.logger.debug('malformed arp request? arp_sha %s arp_spa %s', + arp_sha, arp_spa) + return None + + self._arp_reply_packet(arp_sha, arp_spa, arp_tpa) + + +class RouterIPV4Linux(RouterIPV4): + def __init__(self, *args, **kwargs): + super(RouterIPV4Linux, 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(RouterIPV4Linux, 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 _arp_loop_socket(self, packet_socket): + while True: + try: + buf = packet_socket.recv(1500) + except socket.timeout: + continue + + self._arp_process(buf) + + def _arp_loop(self): + try: + with contextlib.closing( + 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_ETHERNET, + mac_lib.BROADCAST)) + self._arp_loop_socket(packet_socket) + except greenlet.GreenletExit: + # suppress thread.kill exception + pass + + def _enable_router(self): + if self._arp_thread is None: + self._arp_thread = hub.spawn(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() + hub.joinall([self._arp_thread]) + 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 + + self._arp_process(msg.data) + + def _drop_match(self, dp): + kwargs = {} + kwargs['in_port'] = self.interface.port_no + kwargs['eth_dst'] = vrrp.vrrp_ipv4_src_mac_address(self.config.vrid) + if self.interface.vlan_id is not None: + kwargs['vlan_vid'] = self.interface.vlan_id + return dp.ofproto_parser.OFPMatch(**kwargs) + + 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): + kwargs = {} + kwargs['in_port'] = self.interface.port_no + kwargs['eth_dst'] = mac_lib.BROADCAST_STR + kwargs['eth_type'] = ether.ETH_TYPE_ARP + if self.interface.vlan_id is not None: + kwargs['vlan_vid'] = self.interface.vlan_id + kwargs['arp_op'] = arp.ARP_REQUEST + kwargs['arp_tpa'] = vrrp.vrrp_ipv4_src_mac_address(self.config.vrid) + return dp.ofproto_parser.OFPMatch(**kwargs) + + 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(RouterIPV6Linux, 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 diff --git a/ryu/services/protocols/vrrp/utils.py b/ryu/services/protocols/vrrp/utils.py new file mode 100644 index 00000000..8cb8fa18 --- /dev/null +++ b/ryu/services/protocols/vrrp/utils.py @@ -0,0 +1,76 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2013 Isaku Yamahata +# +# 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. + +from ryu.lib.packet import ethernet +from ryu.lib.packet import vlan +from ryu.ofproto import ether +from ryu.topology import api as topo_api + + +def may_add_vlan(packet, vlan_id): + """ + :type packet: ryu.lib.packet.packet.Packet + :param packet: + :type vlan_id: int (0 <= vlan_id <= 4095) or None (= No VLAN) + :param vlan_id: + """ + if vlan_id is None: + return + + e = packet.protocols[0] + assert isinstance(e, ethernet.ethernet) + v = vlan.vlan(0, 0, vlan_id, e.ethertype) + e.ethertype = ether.ETH_TYPE_8021Q + packet.add_protocol(v) + + +def get_dp(app, dpid): + """ + :type dpid: datapath id + :param dpid: + :rtype: ryu.controller.controller.Datapatyh + :returns: datapath corresponding to dpid + """ + switches = topo_api.get_switch(app, dpid) + if not switches: + return None + assert len(switches) == 1 + return switches[0].dp + + +def dp_packet_out(dp, port_no, data): + # OF 1.2 + ofproto = dp.ofproto + ofproto_parser = dp.ofproto_parser + actions = [ofproto_parser.OFPActionOutput(port_no, + ofproto.OFPCML_NO_BUFFER)] + packet_out = ofproto_parser.OFPPacketOut( + dp, 0xffffffff, ofproto.OFPP_CONTROLLER, actions, data) + dp.send_msg(packet_out) + + +def dp_flow_mod(dp, table, command, priority, match, instructions, + out_port=None): + # OF 1.2 + ofproto = dp.ofproto + ofproto_parser = dp.ofproto_parser + if out_port is None: + out_port = ofproto.OFPP_ANY + flow_mod = ofproto_parser.OFPFlowMod( + dp, 0, 0, table, command, 0, 0, + priority, 0xffffffff, out_port, ofproto.OFPG_ANY, + ofproto.OFPFF_CHECK_OVERLAP, match, instructions) + dp.send_msg(flow_mod)