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 <yamahata@valinux.co.jp> Signed-off-by: YAMAMOTO Takashi <yamamoto@valinux.co.jp> Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
This commit is contained in:
parent
f03a9ceb6c
commit
3c10efe07f
15
ryu/services/__init__.py
Normal file
15
ryu/services/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
|
||||||
|
# Copyright (C) 2013 YAMAMOTO Takashi <yamamoto at valinux co 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.
|
15
ryu/services/protocols/__init__.py
Normal file
15
ryu/services/protocols/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# 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.
|
15
ryu/services/protocols/vrrp/__init__.py
Normal file
15
ryu/services/protocols/vrrp/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# 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.
|
65
ryu/services/protocols/vrrp/api.py
Normal file
65
ryu/services/protocols/vrrp/api.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
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)
|
154
ryu/services/protocols/vrrp/dumper.py
Normal file
154
ryu/services/protocols/vrrp/dumper.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
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)
|
265
ryu/services/protocols/vrrp/event.py
Normal file
265
ryu/services/protocols/vrrp/event.py
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
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
|
156
ryu/services/protocols/vrrp/manager.py
Normal file
156
ryu/services/protocols/vrrp/manager.py
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
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)
|
151
ryu/services/protocols/vrrp/monitor.py
Normal file
151
ryu/services/protocols/vrrp/monitor.py
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
# 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.
|
||||||
|
"""
|
||||||
|
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)
|
234
ryu/services/protocols/vrrp/monitor_linux.py
Normal file
234
ryu/services/protocols/vrrp/monitor_linux.py
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
# 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
|
141
ryu/services/protocols/vrrp/monitor_openflow.py
Normal file
141
ryu/services/protocols/vrrp/monitor_openflow.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
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)
|
687
ryu/services/protocols/vrrp/router.py
Normal file
687
ryu/services/protocols/vrrp/router.py
Normal file
@ -0,0 +1,687 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
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()
|
99
ryu/services/protocols/vrrp/sample_manager.py
Normal file
99
ryu/services/protocols/vrrp/sample_manager.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
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)
|
539
ryu/services/protocols/vrrp/sample_router.py
Normal file
539
ryu/services/protocols/vrrp/sample_router.py
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
# 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
|
||||||
|
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
|
76
ryu/services/protocols/vrrp/utils.py
Normal file
76
ryu/services/protocols/vrrp/utils.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
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)
|
Loading…
Reference in New Issue
Block a user