blob: 1d9a169daaa703775ec0a0c90a3d1117dd43610b [file] [log] [blame]
Hyunsun Moon6ff622e2020-03-11 17:14:48 -07001#!/usr/bin/env python
2# SPDX-License-Identifier: Apache-2.0
Hyunsun Moon6ff622e2020-03-11 17:14:48 -07003# Copyright(c) 2019 Intel Corporation
4
5import argparse
6import signal
7import sys
8import time
9
10# for retrieving neighbor info
11from pyroute2 import IPDB, IPRoute
12
13from scapy.all import *
14
15try:
16 from pybess.bess import *
17except ImportError:
18 print('Cannot import the API module (pybess)')
19 raise
20
21MAX_RETRIES = 5
22SLEEP_S = 2
23
24
25class NeighborEntry:
26 def __init__(self):
27 self.neighbor_ip = None
28 self.iface = None
29 self.iprange = None
30 self.prefix_len = None
31 self.route_count = 0
32 self.gate_idx = 0
33 self.macstr = None
34
35 def __str__(self):
36 return ('{neigh: %s, iface: %s, ip-range: %s/%s}' %
37 (self.neighbor_ip, self.iface, self.iprange, self.prefix_len))
38
39
40def mac2hex(mac):
41 return int(mac.replace(':', ''), 16)
42
43
44def send_ping(neighbor_ip):
45 send(IP(dst=neighbor_ip) / ICMP())
46
47
48def send_arp(neighbor_ip, src_mac, iface):
49 pkt = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=neighbor_ip, hwsrc=src_mac)
50 pkt.show()
51 hexdump(pkt)
52 sendp(pkt, iface=iface)
53
54
55def fetch_mac(dip):
56 ip = ''
57 _mac = ''
58 neighbors = ipr.get_neighbours(dst=dip)
59 for i in range(len(neighbors)):
60 for att in neighbors[i]['attrs']:
61 if 'NDA_DST' in att and dip == att[1]:
62 # ('NDA_DST', dip)
63 ip = att[1]
64 if 'NDA_LLADDR' in att:
65 # ('NDA_LLADDR', _mac)
66 _mac = att[1]
67 return _mac
68
69
70def link_modules(server, module, next_module, ogate=0, igate=0):
71 print('Linking {} module'.format(next_module))
72
73 # Pause bess first
74 bess.pause_all()
75 # Connect module to next_module
76 for _ in range(MAX_RETRIES):
77 try:
78 server.connect_modules(module, next_module, ogate, igate)
79 except BESS.Error as e:
80 bess.resume_all()
81 if e.code == errno.EBUSY:
82 break
83 else:
84 return #raise
85 except Exception as e:
86 print(
87 'Error connecting module {}:{}->{}:{}: {}. Retrying in {} secs...'
88 .format(module, ogate, igate, next_module, e, SLEEP_S))
89 time.sleep(SLEEP_S)
90 else:
91 bess.resume_all()
92 break
93 else:
94 bess.resume_all()
95 print('BESS module connection ({}:{}->{}:{}) failure.'.format(
96 module, ogate, igate, next_module))
97 return
98 #raise Exception('BESS module connection ({}:{}->{}:{}) failure.'.
99 # format(module, ogate, igate, next_module))
100
101
102def link_route_module(server, gateway_mac, item):
103 iprange = item.iprange
104 prefix_len = item.prefix_len
105 route_module = item.iface + 'Routes'
Hyunsun Moon531f42a2020-03-25 20:11:02 -0700106 last_module = item.iface + 'Merge'
Hyunsun Moon6ff622e2020-03-11 17:14:48 -0700107 gateway_mac_str = '{:X}'.format(gateway_mac)
108 print('Adding route entry {}/{} for {}'.format(iprange, prefix_len,
109 route_module))
110
111 print('Trying to retrieve neighbor entry {} from neighbor cache'.format(
112 item.neighbor_ip))
113 neighbor_exists = neighborcache.get(item.neighbor_ip)
114
115 # How many gates does this module have?
116 # If entry does not exist, then initialize it
117 if not modgatecnt.get(route_module):
118 modgatecnt[route_module] = 0
119
120 # Compute likely index
121 if neighbor_exists:
122 # No need to create a new Update module
123 gate_idx = neighbor_exists.gate_idx
124 else:
125 # Need to create a new Update module,
126 # so get gate_idx from gate count
127 gate_idx = modgatecnt[route_module]
128
129 # Pause bess first
130 bess.pause_all()
131 # Pass routing entry to bessd's route module
132 for _ in range(MAX_RETRIES):
133 try:
134 server.run_module_command(route_module, 'add',
135 'IPLookupCommandAddArg', {
136 'prefix': iprange,
137 'prefix_len': int(prefix_len),
138 'gate': gate_idx
139 })
140 except:
141 print('Error adding route entry {}/{} in {}. Retrying in {}sec...'.
142 format(iprange, prefix_len, route_module, SLEEP_S))
143 time.sleep(SLEEP_S)
144 else:
145 bess.resume_all()
146 break
147 else:
148 bess.resume_all()
149 print('BESS route entry ({}/{}) insertion failure in module {}'.format(
150 iprange, prefix_len, route_module))
151 return
152 #raise Exception('BESS route entry ({}/{}) insertion failure in module {}'.
153 # format(iprange, prefix_len, route_module))
154
155 if not neighbor_exists:
156 print('Neighbor does not exist')
157 # Create Update module
158 update_module = route_module + 'DstMAC' + gateway_mac_str
159
160 # Pause bess first
161 bess.pause_all()
162 for _ in range(MAX_RETRIES):
163 try:
164 server.create_module('Update', update_module, {
165 'fields': [{
166 'offset': 0,
167 'size': 6,
168 'value': gateway_mac
169 }]
170 })
171 except BESS.Error as e:
172 bess.resume_all()
173 if e.code == errno.EEXIST:
174 break
175 else:
176 return #raise
177 except Exception as e:
178 print(
179 'Error creating update module {}: {}. Retrying in {} secs...'
180 .format(update_module, e, SLEEP_S))
181 time.sleep(SLEEP_S)
182 else:
183 bess.resume_all()
184 break
185 else:
186 bess.resume_all()
187 print('BESS module {} creation failure.'.format(update_module))
188 return #raise Exception('BESS module {} creation failure.'.
189 # format(update_module))
190
191 print('Update module created')
192
193 # Connect Update module to route module
194 link_modules(server, route_module, update_module, gate_idx, 0)
195
196 # Connect Update module to dpdk_out module
197 link_modules(server, update_module, last_module, 0, 0)
198
199 # Add a new neighbor in neighbor cache
200 neighborcache[item.neighbor_ip] = item
201
202 # Add a record of the affliated gate id
203 item.gate_idx = gate_idx
204
205 # Set the mac str
206 item.macstr = gateway_mac_str
207
208 # Increment global gate count number
209 modgatecnt[route_module] += 1
210
211 neighbor_exists = item
212
213 else:
214 print('Neighbor already exists')
215
216 # Finally increment route count
217 neighborcache[item.neighbor_ip].route_count += 1
218
219
220def del_route_entry(server, item):
221 iprange = item.iprange
222 prefix_len = item.prefix_len
223 route_module = item.iface + 'Routes'
Hyunsun Moon531f42a2020-03-25 20:11:02 -0700224 last_module = item.iface + 'Merge'
Hyunsun Moon6ff622e2020-03-11 17:14:48 -0700225
226 neighbor_exists = neighborcache.get(item.neighbor_ip)
227 if neighbor_exists:
228 # Pause bess first
229 bess.pause_all()
230 # Delete routing entry from bessd's route module
231 for i in range(MAX_RETRIES):
232 try:
233 server.run_module_command(route_module, 'delete',
234 'IPLookupCommandDeleteArg', {
235 'prefix': iprange,
236 'prefix_len': int(prefix_len)
237 })
238 except:
239 print(
240 'Error while deleting route entry for {}. Retrying in {} sec...'
241 .format(route_module, SLEEP_S))
242 time.sleep(SLEEP_S)
243 else:
244 bess.resume_all()
245 break
246 else:
247 bess.resume_all()
248 print('Route entry deletion failure.')
249 return
250 #raise Exception('Route entry deletion failure.')
251
252 print('Route entry {}/{} deleted from {}'.format(
253 iprange, prefix_len, route_module))
254
255 # Decrementing route count for the registered neighbor
256 neighbor_exists.route_count -= 1
257
258 # If route count is 0, then delete the whole module
259 if neighbor_exists.route_count == 0:
260 update_module = route_module + 'DstMAC' + neighbor_exists.macstr
261 # Pause bess first
262 bess.pause_all()
263 for i in range(MAX_RETRIES):
264 try:
265 server.destroy_module(update_module)
266 except:
267 print('Error destroying module {}. Retrying in {}sec...'.
268 format(update_module, SLEEP_S))
269 time.sleep(SLEEP_S)
270 else:
271 bess.resume_all()
272 break
273 else:
274 bess.resume_all()
275 print('Module {} deletion failure.'.format(update_module))
276 return
277 #raise Exception('Module {} deletion failure.'.
278 # format(update_module))
279
280 print('Module {} destroyed'.format(update_module))
281
282 # Delete entry from the neighbor cache
283 del neighborcache[item.neighbor_ip]
284 print('Deleting item from neighborcache')
285 del neighbor_exists
286 else:
287 print('Route count for {} decremented to {}'.format(
288 item.neighbor_ip, neighbor_exists.route_count))
289 neighborcache[item.neighbor_ip] = neighbor_exists
290 else:
291 print('Neighbor {} does not exist'.format(item.neighbor_ip))
292
293
294def probe_addr(item, src_mac):
295 # Store entry if entry does not exist in ARP cache
296 arpcache[item.neighbor_ip] = item
297 print('Adding entry {} in arp probe table'.format(item))
298
299 # Probe ARP request by sending ping
300 send_ping(item.neighbor_ip)
301
302 # Probe ARP request
303 ##send_arp(neighbor_ip, src_mac, item.iface)
304
305
306def parse_new_route(msg):
307 item = NeighborEntry()
308 # Fetch prefix_len
309 item.prefix_len = msg['dst_len']
310 # Default route
311 if item.prefix_len is 0:
312 item.iprange = '0.0.0.0'
313
314 for att in msg['attrs']:
315 if 'RTA_DST' in att:
316 # Fetch IP range
317 # ('RTA_DST', iprange)
318 item.iprange = att[1]
319 if 'RTA_GATEWAY' in att:
320 # Fetch gateway MAC address
321 # ('RTA_GATEWAY', neighbor_ip)
322 item.neighbor_ip = att[1]
323 _mac = fetch_mac(att[1])
324 if not _mac:
325 gateway_mac = 0
326 else:
327 gateway_mac = mac2hex(_mac)
328 if 'RTA_OIF' in att:
329 # Fetch interface name
330 # ('RTA_OIF', iface)
331 item.iface = ipdb.interfaces[int(att[1])].ifname
332
333 if not item.iface in args.i or not item.iprange or not item.neighbor_ip:
334 # Neighbor info is invalid
335 del item
336 return
337
Hyunsun Moon6ff622e2020-03-11 17:14:48 -0700338 # if mac is 0, send ARP request
339 if gateway_mac == 0:
340 print('Adding entry {} in arp probe table'.format(item.iface))
341 probe_addr(item, ipdb.interfaces[item.iface].address)
342
343 else: # if gateway_mac is set
Hyunsun Moon531f42a2020-03-25 20:11:02 -0700344 print('Linking module {}Routes with {}Merge (Dest MAC: {})'.format(
Hyunsun Moon6ff622e2020-03-11 17:14:48 -0700345 item.iface, item.iface, _mac))
346
347 link_route_module(bess, gateway_mac, item)
348
349
350def parse_new_neighbor(msg):
351 for att in msg['attrs']:
352 if 'NDA_DST' in att:
353 # ('NDA_DST', neighbor_ip)
354 neighbor_ip = att[1]
355 if 'NDA_LLADDR' in att:
356 # ('NDA_LLADDR', neighbor_mac)
357 gateway_mac = att[1]
358
359 item = arpcache.get(neighbor_ip)
360 if item:
Hyunsun Moon531f42a2020-03-25 20:11:02 -0700361 print('Linking module {}Routes with {}Merge (Dest MAC: {})'.format(
Hyunsun Moon6ff622e2020-03-11 17:14:48 -0700362 item.iface, item.iface, gateway_mac))
363
364 # Add route entry, and add item in the registered neighbor cache
365 link_route_module(bess, mac2hex(gateway_mac), item)
366
367 # Remove entry from unresolved arp cache
368 del arpcache[neighbor_ip]
369
370
371def parse_del_route(msg):
372 item = NeighborEntry()
373 for att in msg['attrs']:
374 if 'RTA_DST' in att:
375 # Fetch IP range
376 # ('RTA_DST', iprange)
377 item.iprange = att[1]
378 if 'RTA_GATEWAY' in att:
379 # Fetch gateway MAC address
380 # ('RTA_GATEWAY', neighbor_ip)
381 item.neighbor_ip = att[1]
382 if 'RTA_OIF' in att:
383 # Fetch interface name
384 # ('RTA_OIF', iface)
385 item.iface = ipdb.interfaces[int(att[1])].ifname
386
387 if not item.iface in args.i or not item.iprange or not item.neighbor_ip:
388 # Neighbor info is invalid
389 del item
390 return
391
392 # Fetch prefix_len
393 item.prefix_len = msg['dst_len']
394
395 del_route_entry(bess, item)
396
397 # Delete item
398 del item
399
400
401def netlink_event_listener(ipdb, netlink_message, action):
402
403 # If you get a netlink message, parse it
404 msg = netlink_message
405
406 if action == 'RTM_NEWROUTE':
407 parse_new_route(msg)
408
409 if action == 'RTM_NEWNEIGH':
410 parse_new_neighbor(msg)
411
412 if action == 'RTM_DELROUTE':
413 parse_del_route(msg)
414
415
416def bootstrap_routes():
417 routes = ipr.get_routes()
418 for i in routes:
419 if i['event'] == 'RTM_NEWROUTE':
420 parse_new_route(i)
421
422
423def connect_bessd():
424 print('Connecting to BESS daemon...'),
425 # Connect to BESS (assuming host=localhost, port=10514 (default))
426 for i in range(MAX_RETRIES):
427 try:
428 if not bess.is_connected():
429 bess.connect(grpc_url=args.ip + ':' + args.port)
430 except BESS.RPCError:
431 print(
432 'Error connecting to BESS daemon. Retrying in {}sec...'.format(
433 SLEEP_S))
434 time.sleep(SLEEP_S)
435 else:
436 break
437 else:
438 raise Exception('BESS connection failure.')
439
440 print('Done.')
441
442
443def reconfigure(number, frame):
444 print('Received: {} Reloading routes'.format(number))
445 # clear arpcache
446 for ip in list(arpcache):
447 item = arpcache.get(ip)
448 del item
449 arpcache.clear()
450 for ip in list(neighborcache):
451 item = neighborcache.get(ip)
452 del item
453 neighborcache.clear()
454 for modname in list(modgatecnt):
455 item = modgatecnt.get(modname)
456 del item
457 modgatecnt.clear()
458 bootstrap_routes()
459 signal.pause()
460
461
462def cleanup(number, frame):
463 ipdb.unregister_callback(event_callback)
464 print('Received: {} Exiting'.format(number))
465 sys.exit()
466
467
468def main():
469 global arpcache, neighborcache, modgatecnt, ipdb, event_callback, bess, ipr
470 # for holding unresolved ARP queries
471 arpcache = {}
472 # for holding list of registered neighbors
473 neighborcache = {}
474 # for holding gate count per route module
475 modgatecnt = {}
476 # for interacting with kernel
477 ipdb = IPDB()
478 ipr = IPRoute()
479 # for bess client
480 bess = BESS()
481
482 # connect to bessd
483 connect_bessd()
484
485 # program current routes
486 #bootstrap_routes()
487
488 # listen for netlink events
489 print('Registering netlink event listener callback...'),
490 event_callback = ipdb.register_callback(netlink_event_listener)
491 print('Done.')
492
493 signal.signal(signal.SIGHUP, reconfigure)
494 signal.signal(signal.SIGINT, cleanup)
495 signal.signal(signal.SIGTERM, cleanup)
496 signal.pause()
497
498
499if __name__ == '__main__':
500 parser = argparse.ArgumentParser(
501 description='Basic IPv4 Routing Controller')
502 parser.add_argument('-i',
503 type=str,
504 nargs='+',
505 help='interface(s) to control')
506 parser.add_argument('--ip',
507 type=str,
508 default='localhost',
509 help='BESSD address')
510 parser.add_argument('--port', type=str, default='10514', help='BESSD port')
511
512 # for holding command-line arguments
513 global args
514 args = parser.parse_args()
515
516 if args.i:
517 main()
518 # if interface list is empty, print help menu and quit
519 else:
520 print(parser.print_help())