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