1 # Copyright (C) 2014 SDN Hub
3 # Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3.
4 # You may not use this file except in compliance with this License.
5 # You may obtain a copy of the License at
7 # http://www.gnu.org/licenses/gpl-3.0.txt
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
18 from ryu.lib import mac as mac_lib
19 from ryu.lib import ip as ip_lib
20 from ryu.base import app_manager
21 from ryu.controller import ofp_event
22 from ryu.controller.handler import MAIN_DISPATCHER, CONFIG_DISPATCHER
23 from ryu.controller.handler import set_ev_cls
25 from ryu.lib.packet import packet
26 from ryu.lib.packet import ethernet
27 from ryu.lib.packet import ipv4
28 from ryu.lib.packet import tcp
29 from ryu.lib.packet import arp
30 from ryu.ofproto import ether, inet
31 from ryu.ofproto import ofproto_v1_0, ofproto_v1_3
32 from ryu.lib import dpid as dpid_lib
33 import learning_switch
35 UINT32_MAX = 0xffffffff
37 ################ Main ###################
39 # The stateless server load balancer picks a different server for each
40 # request. For making the assignment, it only uses the servers it
41 # already knows the location of. The clients or the gateway sents along
42 # a request for the Virtual IP of the load-balancer. The first switch
43 # intercepting the request will rewrite the headers to match the actual
44 # server picked. So all other switches will only have to do simple
45 # L2 forwarding. It is possible to avoid IP header writing if alias IP
46 # is set on the servers. The call skip_ip_header_rewriting() will handle
47 # the appropriate flag setting.
49 class StatelessLB(app_manager.RyuApp):
51 def __init__(self, *args, **kwargs):
52 super(StatelessLB, self).__init__(*args, **kwargs)
53 self.rewrite_ip_header = True
57 self.virtual_ip = None
58 #self.virtual_ip = "10.0.0.5"
59 self.virtual_mac = "A6:63:DD:D7:C0:C8" # Pick something dummy and
61 #self.servers.append({'ip':"10.0.0.2", 'mac':"00:00:00:00:00:02"})
62 #self.servers.append({'ip':"10.0.0.3", 'mac':"00:00:00:00:00:03"})
63 #self.servers.append({'ip':"10.0.0.4", 'mac':"00:00:00:00:00:04"})
65 #self.learning_switch = kwargs['learning_switch']
66 #self.learning_switch.add_exemption({'dl_type': ether.ETH_TYPE_LLDP})
67 #self.learning_switch.add_exemption({'dl_dst': self.virtual_mac})
69 def set_learning_switch(self, learning_switch):
70 self.learning_switch = learning_switch
71 self.learning_switch.clear_exemption()
72 self.learning_switch.add_exemption({'dl_dst': self.virtual_mac})
74 # Users can skip doing header rewriting by setting the virtual IP
75 # as an alias IP on all the servers. This works well in single subnet
76 def set_rewrite_ip_flag(self, rewrite_ip):
78 self.rewrite_ip_header = True
80 self.rewrite_ip_header = False
82 def set_virtual_ip(self, virtual_ip=None):
83 self.virtual_ip = virtual_ip
85 def set_server_pool(self, servers=None):
86 self.servers = servers
88 def formulate_arp_reply(self, dst_mac, dst_ip):
89 if self.virtual_ip == None:
92 src_mac = self.virtual_mac
93 src_ip = self.virtual_ip
94 arp_opcode = arp.ARP_REPLY
95 arp_target_mac = dst_mac
97 ether_proto = ether.ETH_TYPE_ARP
99 arp_proto = ether.ETH_TYPE_IP
103 pkt = packet.Packet()
104 e = ethernet.ethernet(dst_mac, src_mac, ether_proto)
105 a = arp.arp(hwtype, arp_proto, hlen, plen, arp_opcode,
106 src_mac, src_ip, arp_target_mac, dst_ip)
114 @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
115 def packet_in_handler(self, ev):
116 if self.virtual_ip == None or self.servers == None:
120 datapath = msg.datapath
121 ofp = datapath.ofproto
122 ofp_parser = datapath.ofproto_parser
123 in_port = msg.match['in_port']
126 pkt = packet.Packet(msg.data)
127 eth = pkt.get_protocols(ethernet.ethernet)[0]
129 if eth.ethertype == ether.ETH_TYPE_ARP:
130 arp_hdr = pkt.get_protocols(arp.arp)[0]
132 if arp_hdr.dst_ip == self.virtual_ip and arp_hdr.opcode == arp.ARP_REQUEST:
134 reply_pkt = self.formulate_arp_reply(arp_hdr.src_mac,
137 actions = [ofp_parser.OFPActionOutput(in_port)]
138 out = ofp_parser.OFPPacketOut(datapath=datapath,
139 in_port=ofp.OFPP_ANY, data=reply_pkt.data,
140 actions=actions, buffer_id = UINT32_MAX)
141 datapath.send_msg(out)
145 # Only handle IPv4 traffic going forward
146 elif eth.ethertype != ether.ETH_TYPE_IP:
149 iphdr = pkt.get_protocols(ipv4.ipv4)[0]
151 # Only handle traffic destined to virtual IP
152 if (iphdr.dst != self.virtual_ip):
155 # Only handle TCP traffic
156 if iphdr.proto != inet.IPPROTO_TCP:
159 tcphdr = pkt.get_protocols(tcp.tcp)[0]
162 for server in self.servers:
163 outport = self.learning_switch.get_attachment_port(dpid, server['mac'])
165 server['outport'] = outport
166 valid_servers.append(server)
168 total_servers = len(valid_servers)
170 # If we there are no servers with location known, then skip
171 if total_servers == 0:
174 # Round robin selection of servers
175 index = self.server_index % total_servers
176 selected_server_ip = valid_servers[index]['ip']
177 selected_server_mac = valid_servers[index]['mac']
178 selected_server_outport = valid_servers[index]['outport']
179 self.server_index += 1
180 print "Selected server", selected_server_ip
182 ########### Setup route to server
183 match = ofp_parser.OFPMatch(in_port=in_port,
184 eth_type=eth.ethertype, eth_src=eth.src, eth_dst=eth.dst,
185 ip_proto=iphdr.proto, ipv4_src=iphdr.src, ipv4_dst=iphdr.dst,
186 tcp_src=tcphdr.src_port, tcp_dst=tcphdr.dst_port)
188 if self.rewrite_ip_header:
189 actions = [ofp_parser.OFPActionSetField(eth_dst=selected_server_mac),
190 ofp_parser.OFPActionSetField(ipv4_dst=selected_server_ip),
191 ofp_parser.OFPActionOutput(selected_server_outport) ]
193 actions = [ofp_parser.OFPActionSetField(eth_dst=selected_server_mac),
194 ofp_parser.OFPActionOutput(selected_server_outport) ]
196 inst = [ofp_parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions)]
198 cookie = random.randint(0, 0xffffffffffffffff)
200 mod = ofp_parser.OFPFlowMod(datapath=datapath, match=match, idle_timeout=10,
201 instructions=inst, buffer_id = msg.buffer_id, cookie=cookie)
202 datapath.send_msg(mod)
204 ########### Setup reverse route from server
205 match = ofp_parser.OFPMatch(in_port=selected_server_outport,
206 eth_type=eth.ethertype, eth_src=selected_server_mac, eth_dst=eth.src,
207 ip_proto=iphdr.proto, ipv4_src=selected_server_ip, ipv4_dst=iphdr.src,
208 tcp_src=tcphdr.dst_port, tcp_dst=tcphdr.src_port)
210 if self.rewrite_ip_header:
211 actions = ([ofp_parser.OFPActionSetField(eth_src=self.virtual_mac),
212 ofp_parser.OFPActionSetField(ipv4_src=self.virtual_ip),
213 ofp_parser.OFPActionOutput(in_port) ])
215 actions = ([ofp_parser.OFPActionSetField(eth_src=self.virtual_mac),
216 ofp_parser.OFPActionOutput(in_port) ])
218 inst = [ofp_parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions)]
220 cookie = random.randint(0, 0xffffffffffffffff)
222 mod = ofp_parser.OFPFlowMod(datapath=datapath, match=match, idle_timeout=10,
223 instructions=inst, cookie=cookie)
224 datapath.send_msg(mod)