1 # Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
2 # Copyright (C) 2013 Isaku Yamahata <yamahata at private email ne jp>
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
18 router implementation base class
19 a template for router implementation that support VRRP
20 Those routers needs to be created by someone else.
21 sample_manager.routerManager is an example.
23 PYTHONPATH=. ./bin/ryu-manager --verbose \
24 ryu.services.protocols.vrrp.manager \
25 ryu.services.protocols.vrrp.dumper \
26 ryu.services.protocols.vrrp.sample_manager
33 from ryu.base import app_manager
34 from ryu.controller import handler
35 from ryu.controller import ofp_event
36 from ryu.lib import hub
37 from ryu.lib import mac as mac_lib
38 from ryu.lib.packet import arp
39 from ryu.lib.packet import ethernet
40 from ryu.lib.packet import packet
41 from ryu.lib.packet import vlan
42 from ryu.lib.packet import vrrp
43 from ryu.ofproto import ether
44 from ryu.ofproto import ofproto_v1_2
45 from ryu.services.protocols.vrrp import api as vrrp_api
46 from ryu.services.protocols.vrrp import event as vrrp_event
47 from ryu.services.protocols.vrrp import utils
50 class RouterBase(app_manager.RyuApp):
51 def _router_name(self, config, interface):
52 ip_version = 'ipv6' if config.is_ipv6 else 'ipv4'
53 return '%s-%s-%d-%s' % (self.__class__.__name__,
54 str(interface), config.vrid, ip_version)
56 def __init__(self, *args, **kwargs):
57 super(RouterBase, self).__init__(*args, **kwargs)
58 self.instance_name = kwargs['name']
59 self.monitor_name = kwargs['monitor_name']
60 self.config = kwargs['config']
61 self.interface = kwargs['interface']
62 self.name = self._router_name(self.config, self.interface)
64 def _transmit(self, data):
65 vrrp_api.vrrp_transmit(self, self.monitor_name, data)
67 def _initialized(self):
68 self.logger.debug('initialized')
70 def _initialized_to_master(self):
71 self.logger.debug('initialized to master')
73 # o Broadcast a gratuitous ARP request containing the virtual
74 # router MAC address for each IP address associated with the
80 # (115)+ If the protected IPvX address is an IPv4 address, then:
81 # (120) * Broadcast a gratuitous ARP request containing the
82 # virtual router MAC address for each IP address associated
83 # with the virtual router.
84 # (125) + else // IPv6
85 # (130) * For each IPv6 address associated with the virtual
86 # router, send an unsolicited ND Neighbor Advertisement with
87 # the Router Flag (R) set, the Solicited Flag (S) unset, the
88 # Override flag (O) set, the target address set to the IPv6
89 # address of the virtual router, and the target link-layer
90 # address set to the virtual router MAC address.
92 def _become_master(self):
93 self.logger.debug('become master')
95 # o Broadcast a gratuitous ARP request containing the virtual
96 # router MAC address for each IP address associated with the
102 # (375)+ If the protected IPvX address is an IPv4 address, then:
103 # (380)* Broadcast a gratuitous ARP request on that interface
104 # containing the virtual router MAC address for each IPv4
105 # address associated with the virtual router.
106 # (385) + else // ipv6
107 # (390) * Compute and join the Solicited-Node multicast
108 # address [RFC4291] for the IPv6 address(es) associated with
109 # the virtual router.
110 # (395) * For each IPv6 address associated with the virtual
111 # router, send an unsolicited ND Neighbor Advertisement with
112 # the Router Flag (R) set, the Solicited Flag (S) unset, the
113 # Override flag (O) set, the target address set to the IPv6
114 # address of the virtual router, and the target link-layer
115 # address set to the virtual router MAC address.
117 def _become_backup(self):
118 self.logger.debug('become backup')
119 # RFC 3768 6.4.2 Backup
120 # - MUST NOT respond to ARP requests for the IP address(s)
121 # associated with the virtual router.
122 # - MUST discard packets with a destination link layer MAC address
123 # equal to the virtual router MAC address.
124 # - MUST NOT accept packets addressed to the IP address(es)
125 # associated with the virtual router.
129 # RFC 5798 6.4.2 Backup
130 # (305) - If the protected IPvX address is an IPv4 address, then:
131 # (310) + MUST NOT respond to ARP requests for the IPv4
132 # address(es) associated with the virtual router.
133 # (315) - else // protected addr is IPv6
134 # (320) + MUST NOT respond to ND Neighbor Solicitation messages
135 # for the IPv6 address(es) associated with the virtual router.
136 # (325) + MUST NOT send ND Router Advertisement messages for the
138 # (330) -endif // was protected addr IPv4?
139 # (335) - MUST discard packets with a destination link-layer MAC
140 # address equal to the virtual router MAC address.
141 # (340) - MUST NOT accept packets addressed to the IPvX address(es)
142 # associated with the virtual router.
144 def _shutdowned(self):
145 self.logger.debug('shutdowned')
147 @handler.set_ev_handler(vrrp_event.EventVRRPStateChanged)
148 def vrrp_state_changed_handler(self, ev):
149 old_state = ev.old_state
150 new_state = ev.new_state
151 self.logger.debug('sample router %s -> %s', old_state, new_state)
152 if new_state == vrrp_event.VRRP_STATE_MASTER:
153 if old_state == vrrp_event.VRRP_STATE_INITIALIZE:
154 self._initialized_to_master()
155 elif old_state == vrrp_event.VRRP_STATE_BACKUP:
156 self._become_master()
159 # - MUST respond to ARP requests for the IP address(es) associated
160 # with the virtual router.
161 # - MUST forward packets with a destination link layer MAC address
162 # equal to the virtual router MAC address.
163 # - MUST NOT accept packets addressed to the IP address(es)
164 # associated with the virtual router if it is not the IP address
166 # - MUST accept packets addressed to the IP address(es) associated
167 # with the virtual router if it is the IP address owner.
172 # (605) - If the protected IPvX address is an IPv4 address, then:
173 # (610) + MUST respond to ARP requests for the IPv4 address(es)
174 # associated with the virtual router.
175 # (615) - else // ipv6
176 # (620) + MUST be a member of the Solicited-Node multicast
177 # address for the IPv6 address(es) associated with the virtual
179 # (625) + MUST respond to ND Neighbor Solicitation message for
180 # the IPv6 address(es) associated with the virtual router.
181 # (630) ++ MUST send ND Router Advertisements for the virtual
183 # (635) ++ If Accept_Mode is False: MUST NOT drop IPv6 Neighbor
184 # Solicitations and Neighbor Advertisements.
185 # (640) +-endif // ipv4?
186 # (645) - MUST forward packets with a destination link-layer MAC
187 # address equal to the virtual router MAC address.
188 # (650) - MUST accept packets addressed to the IPvX address(es)
189 # associated with the virtual router if it is the IPvX address
190 # owner or if Accept_Mode is True. Otherwise, MUST NOT accept
193 elif new_state == vrrp_event.VRRP_STATE_BACKUP:
194 self._become_backup()
195 elif new_state == vrrp_event.VRRP_STATE_INITIALIZE:
196 if old_state is None:
201 raise ValueError('invalid vrrp state %s' % new_state)
204 class RouterIPV4(RouterBase):
205 def _garp_packet(self, ip_address):
206 # prepare garp packet
207 src_mac = vrrp.vrrp_ipv4_src_mac_address(self.config.vrid)
208 e = ethernet.ethernet(mac_lib.BROADCAST_STR, src_mac,
210 a = arp.arp_ip(arp.ARP_REQUEST, src_mac, ip_address,
211 mac_lib.DONTCARE_STR, ip_address)
215 utils.may_add_vlan(p, self.interface.vlan_id)
220 def __init__(self, *args, **kwargs):
221 super(RouterIPV4, self).__init__(*args, **kwargs)
222 assert not self.config.is_ipv6
224 self.garp_packets = [self._garp_packet(ip_address)
225 for ip_address in self.config.ip_addresses]
227 def _send_garp(self):
228 self.logger.debug('_send_garp')
229 for garp_packet in self.garp_packets:
230 self._transmit(garp_packet.data)
232 def _arp_reply_packet(self, arp_req_sha, arp_req_spa, arp_req_tpa):
233 if not (arp_req_tpa in self.config.ip_addresses or
234 arp_req_tpa == self.config.primary_ip_address):
237 src_mac = vrrp.vrrp_ipv4_src_mac_address(self.config.vrid)
238 e = ethernet.ethernet(arp_req_sha, src_mac, ether.ETH_TYPE_ARP)
239 a = arp.arp_ip(arp.ARP_REPLY, src_mac, arp_req_tpa,
240 arp_req_sha, arp_req_spa)
244 utils.may_add_vlan(p, self.interface.vlan_id)
247 self._transmit(p.data)
249 def _arp_process(self, data):
250 dst_mac = vrrp.vrrp_ipv4_src_mac_address(self.config.vrid)
255 p = packet.Packet(data)
256 for proto in p.protocols:
257 if isinstance(proto, ethernet.ethernet):
258 if proto.dst not in (mac_lib.BROADCAST_STR, dst_mac):
260 ethertype = proto.ethertype
261 if not ((self.interface.vlan_id is None and
262 ethertype == ether.ETH_TYPE_ARP) or
263 (self.interface.vlan_id is not None and
264 ethertype == ether.ETH_TYPE_8021Q)):
266 elif isinstance(proto, vlan.vlan):
267 if (proto.vid != self.interface.vlan_id or
268 proto.ethertype != ether.ETH_TYPE_ARP):
270 elif isinstance(proto, arp.arp):
271 if (proto.hwtype != arp.ARP_HW_TYPE_ETHERNET or
272 proto.proto != ether.ETH_TYPE_IP or
273 proto.hlen != 6 or proto.plen != 4 or
274 proto.opcode != arp.ARP_REQUEST or
275 proto.dst_mac != dst_mac):
277 arp_sha = proto.src_mac
278 arp_spa = proto.src_ip
279 arp_tpa = proto.dst_ip
282 if arp_sha is None or arp_spa is None or arp_tpa is None:
283 self.logger.debug('malformed arp request? arp_sha %s arp_spa %s',
287 self._arp_reply_packet(arp_sha, arp_spa, arp_tpa)
290 class RouterIPV4Linux(RouterIPV4):
291 def __init__(self, *args, **kwargs):
292 super(RouterIPV4Linux, self).__init__(*args, **kwargs)
293 assert isinstance(self.interface,
294 vrrp_event.VRRPInterfaceNetworkDevice)
295 self.__is_master = False
296 self._arp_thread = None
299 self._disable_router()
300 super(RouterIPV4Linux, self).start()
302 def _initialized_to_master(self):
303 self.logger.debug('initialized to master')
306 def _become_master(self):
307 self.logger.debug('become master')
311 self.__is_master = True
312 self._enable_router()
315 def _become_backup(self):
316 self.logger.debug('become backup')
317 self.__is_master = False
318 self._disable_router()
320 def _shutdowned(self):
321 # When VRRP functionality is disabled, what to do?
322 # should we also exit? or continue to route packets?
323 self._disable_router()
325 def _arp_loop_socket(self, packet_socket):
328 buf = packet_socket.recv(1500)
329 except socket.timeout:
332 self._arp_process(buf)
336 with contextlib.closing(
338 socket.AF_PACKET, socket.SOCK_RAW,
339 socket.htons(ether.ETH_TYPE_ARP))) as packet_socket:
340 packet_socket.bind((self.interface.device_name,
341 socket.htons(ether.ETH_TYPE_ARP),
342 socket.PACKET_BROADCAST,
343 arp.ARP_HW_TYPE_ETHERNET,
345 self._arp_loop_socket(packet_socket)
346 except greenlet.GreenletExit:
347 # suppress thread.kill exception
350 def _enable_router(self):
351 if self._arp_thread is None:
352 self._arp_thread = hub.spawn(self._arp_loop)
353 # TODO: implement real routing logic
354 self.logger.debug('TODO:_enable_router')
356 def _disable_router(self):
357 if self._arp_thread is not None:
358 self._arp_thread.kill()
359 hub.joinall([self._arp_thread])
360 self._arp_thread = None
361 # TODO: implement real routing logic
362 self.logger.debug('TODO:_disable_router')
365 class RouterIPV4OpenFlow(RouterIPV4):
366 OFP_VERSIONS = [ofproto_v1_2.OFP_VERSION]
369 # _DROP_PRIORITY < monitor.VRRPInterfaceMonitorOpenFlow._PRIORITY or
370 # _DROP_TABLE > monitor.VRRPInterfaceMonitorOpenFlow._TABLE
371 # to gurantee that VRRP packets are send to controller
373 _DROP_PRIORITY = 0x8000 / 2
376 # _ARP_PRIORITY < _DROP_PRIORITY or
377 # _ARP_TABLE > _DROP_TABLE
378 # to gurantee that responding arp can be disabled
380 _ARP_PRIORITY = _DROP_PRIORITY // 2
383 # _ROUTEING_TABLE < _ARP_TABLE or
384 # _ROUTING_TABLE > _ARP_TABLE
385 # to gurantee that routing can be disabled
387 _ROUTING_PRIORITY = _ARP_PRIORITY // 2
389 def __init__(self, *args, **kwargs):
390 super(RouterIPV4OpenFlow, self).__init__(*args, **kwargs)
391 assert isinstance(self.interface, vrrp_event.VRRPInterfaceOpenFlow)
394 return utils.get_dp(self, self.interface.dpid)
399 self._uninstall_route_rule(dp)
400 self._uninstall_arp_rule(dp)
401 self._uninstall_drop_rule(dp)
402 self._install_drop_rule(dp)
403 self._install_arp_rule(dp)
404 self._install_route_rule(dp)
405 super(RouterIPV4OpenFlow, self).start()
407 def _initialized_to_master(self):
408 self.logger.debug('initialized to master')
411 def _become_master(self):
412 self.logger.debug('become master')
420 self._uninstall_drop_rule(dp)
423 def _become_backup(self):
424 self.logger.debug('become backup')
429 self._install_drop_rule(dp)
431 def _shutdowned(self):
436 # When VRRP functionality is disabled, what to do?
437 # should we also exit? or continue to route packets?
438 self._uninstall_route_rule(dp)
439 self._uninstall_arp_rule(dp)
440 self._uninstall_drop_rule(dp)
442 @handler.set_ev_cls(ofp_event.EventOFPPacketIn, handler.MAIN_DISPATCHER)
443 def packet_in_handler(self, ev):
445 datapath = msg.datapath
446 ofproto = datapath.ofproto
448 # TODO: subscribe only the datapath that we route
450 if dpid != self.interface.dpid:
453 for field in msg.match.fields:
454 header = field.header
455 if header == ofproto.OXM_OF_IN_PORT:
456 if field.value != self.interface.port_no:
460 self._arp_process(msg.data)
462 def _drop_match(self, dp):
464 kwargs['in_port'] = self.interface.port_no
465 kwargs['eth_dst'] = vrrp.vrrp_ipv4_src_mac_address(self.config.vrid)
466 if self.interface.vlan_id is not None:
467 kwargs['vlan_vid'] = self.interface.vlan_id
468 return dp.ofproto_parser.OFPMatch(**kwargs)
470 def _install_drop_rule(self, dp):
471 match = self._drop_match(dp)
472 utils.dp_flow_mod(dp, self._DROP_TABLE, dp.ofproto.OFPFC_ADD,
473 self._DROP_PRIORITY, match, [])
475 def _uninstall_drop_rule(self, dp):
476 match = self._drop_match(dp)
477 utils.dp_flow_mod(dp, self._DROP_TABLE, dp.ofproto.OFPFC_DELETE_STRICT,
478 self._DROP_PRIORITY, match, [])
480 def _arp_match(self, dp):
482 kwargs['in_port'] = self.interface.port_no
483 kwargs['eth_dst'] = mac_lib.BROADCAST_STR
484 kwargs['eth_type'] = ether.ETH_TYPE_ARP
485 if self.interface.vlan_id is not None:
486 kwargs['vlan_vid'] = self.interface.vlan_id
487 kwargs['arp_op'] = arp.ARP_REQUEST
488 kwargs['arp_tpa'] = vrrp.vrrp_ipv4_src_mac_address(self.config.vrid)
489 return dp.ofproto_parser.OFPMatch(**kwargs)
491 def _install_arp_rule(self, dp):
493 ofproto_parser = dp.ofproto_parser
495 match = self._arp_match(dp)
496 actions = [ofproto_parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
497 ofproto.OFPCML_NO_BUFFER)]
498 instructions = [ofproto_parser.OFPInstructionActions(
499 ofproto.OFPIT_APPLY_ACTIONS, actions)]
500 utils.dp_flow_mod(dp, self._ARP_TABLE, dp.fproto.OFPFC_ADD,
501 self._ARP_PRIORITY, match, instructions)
503 def _uninstall_arp_rule(self, dp):
504 match = self._arp_match(dp)
505 utils.dp_flow_mod(dp, self._ARP_TABLE, dp.fproto.OFPFC_DELETE_STRICT,
506 self._ARP_PRIORITY, match, [])
508 def _install_route_rule(self, dp):
509 # TODO: implement real routing logic
510 self.logger.debug('TODO:_install_router_rule')
512 def _uninstall_route_rule(self, dp):
513 # TODO: implement real routing logic
514 self.logger.debug('TODO:_uninstall_router_rule')
517 class RouterIPV6(RouterBase):
518 def __init__(self, *args, **kwargs):
519 super(RouterIPV6, self).__init__(*args, **kwargs)
520 assert self.config.is_ipv6
523 class RouterIPV6Linux(RouterIPV6):
524 def __init__(self, *args, **kwargs):
525 super(RouterIPV6Linux, self).__init__(*args, **kwargs)
526 assert isinstance(self.interface,
527 vrrp_event.VRRPInterfaceNetworkDevice)
529 # TODO: reader's home work
533 class RouterIPV6OpenFlow(RouterIPV6):
534 def __init__(self, *args, **kwargs):
535 super(RouterIPV6OpenFlow, self).__init__(*args, **kwargs)
536 assert isinstance(self.interface, vrrp_event.VRRPInterfaceOpenFlow)
538 # TODO: reader's home work