-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSimpleLoadBalancer.py
320 lines (252 loc) · 12.2 KB
/
SimpleLoadBalancer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
from pox.core import core
from pox.openflow import *
import pox.openflow.libopenflow_01 as of
from pox.lib.packet.arp import arp
from pox.lib.packet.ipv4 import ipv4
from pox.lib.addresses import EthAddr, IPAddr
log = core.getLogger()
import time
import random
import pox.log.color
IDLE_TIMEOUT = 10
LOADBALANCER_MAC = EthAddr("00:00:00:00:00:FE")
ETHERNET_BROADCAST_ADDRESS=EthAddr("ff:ff:ff:ff:ff:ff")
class SimpleLoadBalancer(object):
def __init__(self, service_ip, server_ips = []):
core.openflow.addListeners(self)
self.SERVERS = {} # IPAddr(SERVER_IP)]={'server_mac':EthAddr(SERVER_MAC),'port': PORT_TO_SERVER}
self.CLIENTS = {}
self.LOADBALANCER_MAP = {} # Mapping between clients and servers
self.LOADBALANCER_IP = service_ip
self.SERVER_IPS = server_ips
self.ROBIN_COUNT = 0
def _handle_ConnectionUp(self, event):
self.connection = event.connection
log.debug("FUNCTION: _handle_ConnectionUp")
for ip in self.SERVER_IPS:
selected_server_ip = ip
self.send_arp_request(self.connection, selected_server_ip)
log.debug("Sent ARP Requests to all servers")
def round_robin(self):
log.debug("FUNCTION: round_robin")
a = self.SERVERS.keys()
if self.ROBIN_COUNT == len(self.SERVER_IPS):
self.ROBIN_COUNT = 0
server = a[self.ROBIN_COUNT]
self.ROBIN_COUNT += 1
log.info("Round robin selected: %s" % server)
return server
def update_lb_mapping(self, client_ip):
log.debug("FUNCTION: update_lb_mapping")
if client_ip in self.CLIENTS.keys():
if client_ip not in self.LOADBALANCER_MAP.keys():
selected_server = self.round_robin()
log.info("Server selected %s "%selected_server)
self.LOADBALANCER_MAP[client_ip]=selected_server
def send_arp_reply(self, packet, connection, outport):
log.debug("FUNCTION: send_arp_reply")
# Create an ARP reply
arp_rep= arp()
arp_rep.hwtype = arp_rep.HW_TYPE_ETHERNET
arp_rep.prototype = arp_rep.PROTO_TYPE_IP
arp_rep.hwlen = 6
arp_rep.protolen = arp_rep.protolen
arp_rep.opcode = arp_rep.REPLY
# Set MAC destination and source
arp_rep.hwdst = packet.src
arp_rep.hwsrc = LOADBALANCER_MAC
#Reverse the src, dest to have an answer. Set IP source and destination
arp_rep.protosrc = packet.payload.protodst
arp_rep.protodst = packet.payload.protosrc
# Create ethernet frame, set packet type, dst, src
eth = ethernet()
eth.type = ethernet.ARP_TYPE
eth.dst = packet.src
eth.src = LOADBALANCER_MAC
eth.set_payload(arp_rep)
# Create the necessary Openflow Message to make the switch send the ARP Reply
msg = of.ofp_packet_out()
msg.data = eth.pack()
# Append the output port which the packet should be forwarded to.
msg.actions.append(of.ofp_action_output(port = of.OFPP_IN_PORT))
msg.in_port = outport
connection.send(msg)
def send_arp_request(self, connection, ip):
log.debug("FUNCTION: send_arp_request")
arp_req = arp()
arp_req.hwtype = arp_req.HW_TYPE_ETHERNET
arp_req.prototype = arp_req.PROTO_TYPE_IP
arp_req.hwlen = 6
arp_req.protolen = arp_req.protolen
arp_req.opcode = arp_req.REQUEST # Set the opcode
arp_req.protodst = ip # IP the load balancer is looking for
arp_req.hwsrc = LOADBALANCER_MAC # Set the MAC source of the ARP REQUEST
arp_req.hwdst = ETHERNET_BROADCAST_ADDRESS # Set the MAC address in such a way that the packet is marked as a Broadcast
arp_req.protosrc = self.LOADBALANCER_IP # Set the IP source of the ARP REQUEST
eth = ethernet()
eth.type = ethernet.ARP_TYPE
# eth.src =LOADBALANCER_MAC
eth.dst = ETHERNET_BROADCAST_ADDRESS
eth.set_payload(arp_req)
msg = of.ofp_packet_out()
msg.data = eth.pack()
msg.actions.append(of.ofp_action_nw_addr(of.OFPAT_SET_NW_DST,ip))
# Append an action to the message which makes the switch flood the packet out
msg.actions.append(of.ofp_action_output(port=of.OFPP_FLOOD))
connection.send(msg)
def install_flow_rule_client_to_server(self,event, connection, outport, client_ip, server_ip):
log.debug("FUNCTION: install_flow_rule_client_to_server")
self.install_flow_rule_server_to_client(connection, event.port, server_ip,client_ip)
# Create an instance of the type of Openflow packet you need to install flow table entries
msg = of.ofp_flow_mod()
msg.idle_timeout = IDLE_TIMEOUT
msg.match.dl_type=ethernet.IP_TYPE
# MATCH on destination and source IP
msg.match.nw_src = client_ip
msg.match.nw_dst = self.LOADBALANCER_IP
# SET dl_addr source and destination addresses
msg.actions.append(of.ofp_action_dl_addr.set_dst(self.SERVERS[server_ip].get('server_mac')))
msg.actions.append(of.ofp_action_dl_addr.set_src(LOADBALANCER_MAC))
# SET nw_addr source and destination addresses
msg.actions.append(of.ofp_action_nw_addr.set_src(client_ip))
msg.actions.append(of.ofp_action_nw_addr.set_dst(server_ip))
# Set Port to send matching packets out
msg.actions.append(of.ofp_action_output(port=outport))
self.connection.send(msg)
log.info("Installed flow rule: %s -> %s" % (client_ip,server_ip))
def install_flow_rule_server_to_client(self, connection, outport, server_ip, client_ip):
log.debug("FUNCTION: install_flow_rule_server_to_client")
# Create an instance of the type of Openflow packet you need to install flow table entries
msg = of.ofp_flow_mod()
msg.idle_timeout = IDLE_TIMEOUT
msg.match.dl_type=ethernet.IP_TYPE
# MATCH on destination and source IP
msg.match.nw_src = server_ip
msg.match.nw_dst = client_ip
# SET dl_addr source and destination addresses
msg.actions.append(of.ofp_action_dl_addr.set_dst(self.CLIENTS[client_ip].get('client_mac')))
msg.actions.append(of.ofp_action_dl_addr.set_src(LOADBALANCER_MAC))
# SET nw_addr source and destination addresses
msg.actions.append(of.ofp_action_nw_addr.set_src(self.LOADBALANCER_IP))
msg.actions.append(of.ofp_action_nw_addr.set_dst(client_ip))
# Set Port to send matching packets out
msg.actions.append(of.ofp_action_output(port=outport))
self.connection.send(msg)
log.info("Installed flow rule: %s -> %s" % (server_ip,client_ip))
def _handle_PacketIn(self, event):
log.debug("FUNCTION: _handle_PacketIn")
packet = event.parsed
connection = event.connection
inport = event.port
if packet.type == packet.LLDP_TYPE or packet.type == packet.IPV6_TYPE:
log.info("Received LLDP or IPv6 Packet...")
# Handle ARP Packets
elif packet.type == packet.ARP_TYPE:
log.debug("Received ARP Packet")
response = packet.payload
# Handle ARP replies
if response.opcode == response.REPLY:
log.debug("ARP REPLY Received")
if response.protosrc not in self.SERVERS.keys():
# Add Servers MAC and port to SERVERS dict
self.SERVERS[IPAddr(response.protosrc)] = {'server_mac':EthAddr(packet.payload.hwsrc), 'port': inport}
# Handle ARP requests
elif response.opcode == response.REQUEST:
log.debug("ARP REQUEST Received")
if response.protosrc not in self.SERVERS.keys() and response.protosrc not in self.CLIENTS.keys():
#Insert client's ip mac and port to a forwarding table
self.CLIENTS[response.protosrc]={'client_mac':EthAddr(packet.payload.hwsrc),'port':inport}
if (response.protosrc in self.CLIENTS.keys()and response.protodst == self.LOADBALANCER_IP):
log.info("Client %s sent ARP req to LB %s"%(response.protosrc,response.protodst))
# Load Balancer intercepts ARP Client -> Server
# Send ARP Reply to the client, include the event.connection object
self.send_arp_reply(packet, connection, inport)
elif response.protosrc in self.SERVERS.keys() and response.protodst in self.CLIENTS.keys():
log.info("Server %s sent ARP req to client"%response.protosrc)
# Load Balancer intercepts ARP from Client <- Server
# Send ARP Reply to the Server, include the event.connection object
self.send_arp_reply(packet, connection, inport)
else:
log.info("Invalid ARP request")
# Handle IP Packets
elif packet.type == packet.IP_TYPE:
log.debug("Received IP Packet from %s" % packet.next.srcip)
# Handle Requests from Clients to Servers
# Install flow rule Client -> Server
# Check if the packet is destined for the LB and the source is not a server :
if (packet.next.dstip == self.LOADBALANCER_IP and packet.next.srcip not in self.SERVERS.keys()):
self.update_lb_mapping(packet.next.srcip)
# Get client IP from the packet
client_ip = packet.payload.srcip
server_ip = self.LOADBALANCER_MAP.get(packet.next.srcip)
# Get Port of Server
outport = int(self.SERVERS[server_ip].get('port'))
self.install_flow_rule_client_to_server(event,connection, outport, client_ip,server_ip)
eth = ethernet()
eth.type = ethernet.IP_TYPE
eth.src = LOADBALANCER_MAC
eth.dst = self.SERVERS[server_ip].get('server_mac')
eth.set_payload(packet.next)
# Send the first packet (which was sent to the controller from the switch)
# to the chosen server, so there is no packetloss
msg= of.ofp_packet_out()
msg.data = eth.pack()
msg.in_port = inport
# Add an action which sets the MAC source to the LB's MAC
msg.actions.append(of.ofp_action_dl_addr.set_src(LOADBALANCER_MAC))
# Add an action which sets the MAC destination to the intended destination...
msg.actions.append(of.ofp_action_dl_addr.set_dst(self.SERVERS[server_ip].get('server_mac')))
# Add an action which sets the IP source
msg.actions.append((of.ofp_action_nw_addr.set_src(client_ip)))
# Add an action which sets the IP destination
msg.actions.append(of.ofp_action_nw_addr.set_dst(server_ip))
# Add an action which sets the Outport
msg.actions.append(of.ofp_action_output(port=outport))
connection.send(msg)
# Handle traffic from Server to Client
# Install flow rule Client <- Server
elif packet.next.dstip in self.CLIENTS.keys():
log.info("Installing flow rule from Server -> Client")
if packet.next.srcip in self.SERVERS.keys():
# Get the source IP from the IP Packet
server_ip = packet.next.srcip
client_ip = self.LOADBALANCER_MAP.keys()[list(self.LOADBALANCER_MAP.values()).index(packet.next.srcip)]
outport=int(self.CLIENTS[client_ip].get('port'))
self.install_flow_rule_server_to_client(connection, outport, server_ip,client_ip)
eth = ethernet()
eth.type = ethernet.IP_TYPE
eth.src = LOADBALANCER_MAC
eth.dst = self.CLIENTS[client_ip].get('client_mac')
eth.set_payload(packet.next)
# Send the first packet (which was sent to the controller from the switch)
# to the chosen server, so there is no packetloss
msg = of.ofp_packet_out()
msg.data = eth.pack()
msg.in_port = inport
# Add an action which sets the MAC source to the LB's MAC
msg.actions.append(of.ofp_action_dl_addr.set_src(LOADBALANCER_MAC))
# Add an action which sets the MAC destination to the intended destination...
msg.actions.append(of.ofp_action_dl_addr.set_dst(self.CLIENTS[client_ip].get('client_mac')))
# Add an action which sets the IP source
msg.actions.append(of.ofp_action_nw_addr.set_src(self.LOADBALANCER_IP))
# Add an action which sets the IP destination
msg.actions.append(of.ofp_action_nw_addr.set_dst(client_ip))
# Add an action which sets the Outport
msg.actions.append(of.ofp_action_output(port=outport))
self.connection.send(msg)
else:
log.info("Unknown Packet type: %s" % packet.type)
return
return
def launch(loadbalancer, servers):
# Color-coding and pretty-printing the log output
pox.log.color.launch()
pox.log.launch(format="[@@@bold@@@level%(name)-23s@@@reset] " +
"@@@bold%(message)s@@@normal")
log.info("Loading Simple Load Balancer module:\n\n-----------------------------------CONFIG----------------------------------\n")
server_ips = servers.replace(","," ").split()
server_ips = [IPAddr(x) for x in server_ips]
loadbalancer_ip = IPAddr(loadbalancer)
log.info("Loadbalancer IP: %s" % loadbalancer_ip)
log.info("Backend Server IPs: %s\n\n---------------------------------------\n\n" % ', '.join(str(ip) for ip in server_ips))
core.registerNew(SimpleLoadBalancer, loadbalancer_ip, server_ips)