-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathBGPnode.py
638 lines (522 loc) · 28 KB
/
BGPnode.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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
#!/usr/bin/env python3
#
#
# Author: Pavlos Sermpezis
# Institute of Computer Science, Foundation for Research and Technology - Hellas (FORTH), Greece
#
# E-mail: [email protected]
#
#
# This file is part of the BGPsimulator
#
import random
from collections import defaultdict
from copy import deepcopy
class BGPnode:
'''
Class for BGP nodes (i.e., ASes are represented as single nodes).
A BGPnode can own IP prefixes, have connections to other BGPnodes/ASes.
In this class, there exist methods related to the IPprefixes, AS connections, and methods for exchanging BGP messages (announcing, receiving, propagating, hijacking).
class variables:
(a) ASN: integer (mandatory, given upon creation) - id of the node corresponding to its AS number
(b) Topology: object of type BGPtopology (mandatory, given upon creation) - object/topology to which the node belongs
(c) IPprefix: set (initially empty) - set of prefixes owned by the BGPnode
(d) hijacked_IPprefix: dictionary (initially empty) - dictionary with (i) keys the hijacked IP prefixes and (ii) values the hijack_type as int (0==origin_AS hijack, 1== 1st hop hijack, etc. )
(e) ASneighbors: dictionary (initially empty) - dictionary with (i) keys the ASNs of neighbors and (ii) values {1,0,-1} if the neighbor is {provider,peer,customer} respectively
(f) ASneighbors_preference: dictionary (initially empty) - dictionary with (i) keys the ASNs of neighbors and (ii) values the preferences of the neighbors for selection of BGP paths / tie breaker - values are float in [0,1]
(g) paths: dictionary (initially empty) corresponding to the best paths per prefix - dictionary with (i) keys the IP prefixes and (ii) values the corresponding AS path given as a list (e.g., [ASNx, ASNy, ASNz, origin_ASN])
(h) all_paths: dictionary of dictionaries (initially empty) representing the local FIB of BGP - dictionary with (i) keys the IP prefixes, (ii) keys (for each prefix) the ASN of the neighbor that sent the path, and (iii) values the corresponding AS path given as a list (e.g., [ASNx, ASNy, ASNz, origin_ASN])
(i) filters: dictionary (initially empty) - dictionary with (i) keys the IPprefixes, and (ii) values sets of ASNs; if an ASN exists in the set, then the every path for the prefix that contains this ASN need to be filtered/discarded
'''
'''
Contructor for object of the class BGPnode. Creates the class variables from the given arguments, and defines the variable types for the initially epmty class variables.
Input arguments:
(a) ASN: integer
(b) Topology: object of type BGPtopology
'''
def __init__(self, ASN, Topology):
self.ASN = ASN
self.Topology = Topology
self.IPprefix = set()
self.hijacked_IPprefix = {}
self.ASneighbors = {}
self.ASneighbors_preference = {}
self.paths = {}
self.all_paths = defaultdict(dict)
self.filters = {}
### methods for owned prefixes ###
'''
Adds the given prefix to the dictionary ("IPprefix" class variable) for the owned prefixes.
IF the given prefix does not exist in the "IPprefix" dictionary,
THEN (i) add the prefix,
(ii) set an empty path for this prefix in the "paths" dictionary, and
(iii) announce the prefix
Input argument:
(a) IPprefix: the (owned) prefix to be added
'''
def add_prefix(self,IPprefix,forbidden_neighbors=None):
if not self.has_prefix(IPprefix):
self.IPprefix.add(IPprefix)
self.paths[IPprefix] = []
if forbidden_neighbors is not None:
neighb_to_announce = set(self.ASneighbors.keys()).difference(set(forbidden_neighbors))
self.announce_path(IPprefix,list(neighb_to_announce))
else:
self.announce_path(IPprefix,list(self.ASneighbors.keys()))
'''
Re-announces the given prefix, if it is an owned or hijacked prefix.
IF the given prefix is an owned or hijacked prefix,
THEN announce the prefix (as owned, or the fake/hijacked path) to all neighbors
Input argument:
(a) IPprefix: the prefix to be re-announced
'''
def re_announce_prefix(self,IPprefix):
if self.has_prefix(IPprefix) or self.has_hijacked_prefix(IPprefix):
self.announce_path(IPprefix,list(self.ASneighbors.keys()))
'''
Checks if the given prefix exists in the "IPprefix" dictionary.
Returns:
TRUE if it exists, FALSE otherwise
'''
def has_prefix(self,IPprefix):
if IPprefix in self.IPprefix:
return True
else:
return False
'''
Returns:
The dictionary "IPprefix"
'''
def get_prefixes(self):
return self.IPprefix
'''
Clears the routing tables, i.e. the dictionaries "paths" and "all_paths"
'''
def clear_routing_tables(self):
self.paths.clear() #self.paths = {}
self.all_paths.clear() #self.all_paths = defaultdict(dict)
self.IPprefix.clear()
self.hijacked_IPprefix.clear()
self.filters.clear()
### methods for hijacked IP prefixes ###
'''
Adds the given prefix and the corresponding hijack type to the dictionary ("hijacked_IPprefix" class variable) for the hijacked prefixes.
IF the given prefix does not exist in the "hijacked_IPprefix" dictionary,
THEN (i) add the prefix as the key to the "hijacked_IPprefix" dictionary, and
(ii) set the corresponding value of the dictionary equal to the hijack type
Input argument:
(a) IPprefix: the (hijacked) prefix to be added
(b) hijack_type: the type of the hijack for this prefix
'''
def add_hijacked_prefix(self,IPprefix,hijack_type):
if not self.has_prefix(IPprefix):
self.hijacked_IPprefix[IPprefix] = hijack_type
# the best path of the hijacker remains as before the hijacking (i.e., the legitimate path); however, the hijacker does not propagate legitiamte paths for her own hijacked prefixes.
# I.e., do NOT add the following line (it creates a problem in the method "get_path_poisoning_hijack(...)" )
# the self.paths[IPprefix] = []
'''
Checks if the given prefix exists in the "hijacked_IPprefix" dictionary.
Input argument:
(a) IPprefix: the prefix to be checked
Returns:
TRUE if it exists, FALSE otherwise
'''
def has_hijacked_prefix(self,IPprefix):
if IPprefix in self.hijacked_IPprefix.keys():
return True
else:
return False
'''
Returns:
The dictionary "hijacked_IPprefix"
'''
def get_hijacked_prefixes(self):
return self.hijacked_IPprefix
### methods for AS neighbors ###
'''
Adds the given ASN and the corresponding peering relation type to the dictionary ("ASneighbors" class variable) for the AS neighbors, and sets a random value for the preference for this AS neighbor.
IF the given ASN does not exist in the "ASneighbors" dictionary,
THEN (i) add the ASN as the key to the "hijacked_IPprefix" dictionary,
(ii) set the corresponding value of the dictionary equal to the given peering relation type, and
(iii) set a random value (float in the range [0,1]) for the preference for this AS neighbor (to be used for BGP tie breaker)
Input arguments:
(a) ASN: the AS number of the AS neighbor to be added
(b) relation: a string that denotes the peering relation type with the AS neighbor; IF ""
'''
def add_ASneighbor(self,ASN,relation):
if not self.has_ASneighbor(ASN):
if relation == 'customer':
self.ASneighbors[ASN] = -1
elif relation == 'peer':
self.ASneighbors[ASN] = 0
elif relation == 'provider':
self.ASneighbors[ASN] = 1
else:
print('ERROR: Not valid peering relation')
self.ASneighbors_preference[ASN] = random.random() # add a random preference to neighbor
def remove_ASneighbor(self,ASN):
if self.has_ASneighbor(ASN):
del self.ASneighbors[ASN]
del self.ASneighbors_preference[ASN]
# for prefix in self.all_paths.keys():
# self.withdraw_path(prefix,ASN)
'''
Checks if the AS neighbor exists in the "ASneighbors" dictionary.
Returns:
TRUE if it exists, FALSE otherwise
'''
def has_ASneighbor(self,ASN):
if ASN in self.ASneighbors.keys():
return True
else:
return False
'''
Returns the number of neighbors, grouped by type.
Create a new dictionary with keys the type of neighbor, i.e., {1,0,-1} for {providers, peers, customers} respectively, and values the list of neighbors of this type.
Returns:
A tuple {nb_of_providers, nb_of_peers, nb_of_customers}
'''
def get_nb_of_neighbors(self):
neighbor_type_dict = defaultdict(list)
for ASN, type in self.ASneighbors.items():
neighbor_type_dict[type].append(ASN)
return list([len(neighbor_type_dict[1]), len(neighbor_type_dict[0]), len(neighbor_type_dict[-1])])
'''
Returns the list of neighbors, grouped by type.
Create a new dictionary with keys the type of neighbor, i.e., {1,0,-1} for {providers, peers, customers} respectively,
and values the list of neighbors of this type.
Returns:
A dictionary {'providers' : list_of_providers, 'peers' : list_of_peers, 'customers' : list_of_customers}
'''
def get_neighbors(self):
neighbor_type_dict = defaultdict(list)
for ASN, type in self.ASneighbors.items():
neighbor_type_dict[type].append(ASN)
return {'providers': neighbor_type_dict[1],
'peers': neighbor_type_dict[0],
'customers': neighbor_type_dict[-1]}
'''
Returns the best path (i.e. value in the "paths" dictionary) for the given prefix
Input argument:
(a) IPprefix: the prefix the path to which is requested
Returns:
A path, i.e., a list of ASNs, for the given prefix
'''
def get_path(self,IPprefix):
return self.paths.get(IPprefix)
### methods for BGP paths (reception, announcement, updating, propagation, etc.) ###
'''
Receives a BGP announcement for a BGP path (i.e., a prefix and the corresponding AS path), and -if necessary- changes the entry for the path and propagates the path.
Calls the method "conditions_to_add_received_path(...)" to decide if the received announcement must trigger further actions.
IF "conditions_to_add_received_path(...)" returns TRUE,
THEN IF the announcement must be filtered (i.e., illegitimate path)
THEN calls the method "withdraw_path(...)" to discard all the illegitimate paths and withraw (if needed) all the needed paths
ELSE (i) calls the method "add_received_path(...)" to add the given path in the "all_paths" dictionary (i.e. local BGP FIB), and update the entry in the dictionary "paths" for the given prefix, and
(ii) IF the method "add_received_path(...)" returns TRUE (i.e., the given path is selected as the best path)
THEN call the method "export_path(...)" to export the given path
Input arguments:
(a) IPprefix: the prefix of the received BGP announcement
(b) new_path: the path (i.e., list of ASNs) for the prefix, of the received BGP announcement
'''
def receive_path(self,IPprefix, new_path):
if self.conditions_to_add_received_path(IPprefix,new_path):
if self.must_filter_path(IPprefix,new_path) or (self.ASN in new_path):
self.withdraw_path(IPprefix,new_path[0])
else:
bool_export_path = self.add_received_path(IPprefix,new_path)
if bool_export_path:
self.export_path(IPprefix)
'''
Decides if the given path is should be considered to for further actions (e.g., to be added in the local FIB / "all_paths" dictionary, or selected as the best path) or discarded.
IF the given prefix is not (i) an owned or (ii) hijacked prefix, and (iii) the new path does not contain my ASN (for loop avoidance),
THEN return TRUE
ELSE return FALSE (i.e., the received path for the prefix must be discarded)
Input arguments:
(a) IPprefix: the prefix for which the new path is received and needs to be checked
(b) new_path: the path (i.e., list of ASNs) for the prefix that needs to be checked
Returns:
TRUE if the path for the prefix should be considered for further actions, FALSE otherwise (i.e. discard it)
'''
def conditions_to_add_received_path(self,IPprefix,new_path):
if (not self.has_prefix(IPprefix)) and (not self.has_hijacked_prefix(IPprefix)): # if it's (a) not my prefix, AND (b) not a prefix I have hijacked, ADN (c) the new path does not contain my ASN (i.e., for loop avoidance)
return True
return False
'''
Adds the given path for the given prefix in the "all_paths" dictionary (i.e., local BGP FIB), and compares the given path with the with the stored best path (if any) for the given prefix, and decides if the new path needs to replace the stored path (or added if no stored path exists).
Adds the given path in the "all_paths" dictionary (for the keys: given prefix and neighbor that sent the path "new_path[0]"); in case a stored path for this prefix from this neighbor exists, replaces the stored path with the given path.
IF a path for the given prefix exists,
THEN IF the existing best path is from the same neighbor as the given path,
THEN set as the existing best path the given path, and select the best path among all the stored paths in the "all_paths" dictionary (i.e., select from all paths in the local BGP FIB),
and return TRUE
ELSE IF the given path is preferred than the existing best path, set the given path as the best path,
and return TRUE
ELSE set the given path as the best path (i.e., in the "paths" dictionary),
and return TRUE
Input arguments:
(a) IPprefix: the prefix for which the new path is received and is to be added in the local FIB and considered for best path
(b) new_path: the path (i.e., list of ASNs) that is received and is to be added in the local FIB and considered for best path
Returns:
TRUE if the path for the prefix in the "paths" dictionary needs to be changed, FALSE otherwise
'''
def add_received_path(self,IPprefix,new_path):
self.all_paths[IPprefix][new_path[0]] = new_path # replace or add the path from this neighbor (new_path[0])
if self.paths.get(IPprefix):
my_best_path = list(self.paths[IPprefix])
if new_path[0] == my_best_path[0]:
self.paths[IPprefix] = new_path
self.select_best_path(IPprefix)
return True
else:
if self.conditions_to_change_existing_path(IPprefix,new_path):
self.paths[IPprefix] = new_path
return True
else:
self.paths[IPprefix] = new_path
return True
return False
'''
Selects the best path for the given prefix among all the paths in the "all_paths" dictionary, and sets this path as best path in the "paths" dictionary
IF no best path exists for the given prefix,
THEN IF there exists at least one path in the "all_paths" dictionary
THEN select randomly one of the paths in the "all_paths" dictionary as best path (i.e., put it in the "paths" dictionary)
ELSE set the best path equal to an empty path/list
FOR EACH path in the "all_paths" dictionary, compare the path with the current best path, and select the best path among the two.
In the end, the "paths" dictionary contains the best path among the paths in the "all_paths" dictionary.
Input argument:
(a) IPprefix: the prefix for which a best path is to be selected
'''
def select_best_path(self,IPprefix):
if not self.paths.get(IPprefix):
if self.all_paths.get(IPprefix):
self.paths[IPprefix] = random.choice(list(self.all_paths[IPprefix].values()))
else:
self.paths[IPprefix] = []
for search_path in self.all_paths[IPprefix].values(): # finds the best path and adds it to the self.paths[IPprefix]
if self.conditions_to_change_existing_path(IPprefix,search_path):
self.paths[IPprefix] = search_path
'''
Adds a filter for the received announcements for the given prefix that contain (in the path) the given ASN, removes all stored paths ("all_paths" dictionary) that do not conform to the new filter, and withdraws any previous announcements affected by the new filter.
Adds the given filter in the "filter" dictionary.
IF a path for the given prefix exists
THEN (i) FOR EACH stored path in the "all_paths" dictionary, remove the path if it must be filtered (using the method "must_filter_path(...)")
(ii) IF the existing best path must be filtered
THEN call the method "withdraw_path(...)" to witdraw all previous announcements that do not conform to the filter.
Input arguments:
(a) IPprefix: the prefix for which the filtering will be enabled
(b) ASN: the filter, i.e., the ASN whose announcements for the given prefix will be filtered out
'''
def filter_path(self,IPprefix,ASN):
self.add_filter(IPprefix,ASN)
if self.paths.get(IPprefix):
copy_of_all_paths = deepcopy(self.all_paths[IPprefix])
for search_key,search_path in copy_of_all_paths.items():
if self.must_filter_path(IPprefix,search_path):
del self.all_paths[IPprefix][search_key]
my_best_path = list(self.paths[IPprefix])
if self.must_filter_path(IPprefix,my_best_path):
self.withdraw_path(IPprefix,my_best_path[0])
'''
Removes all the stored paths in the "all_paths" dictionary from the given AS for the given prefix, and if any of them is the best path, then (i) withdraws also the previous announcement of the existing best path, and (ii) selects and announces the new best path.
IF a path exists in the "paths" dictionary for the given prefix
THEN (i) IF a path exists in the "all_paths" dictionary for the given prefix from the given AS
THEN remove this path from the "all_paths" dictionary
(ii) IF the existing path in the "paths" dictionary (i.e., the best path) is from the given AS
THEN (a) withdraw the announced paths from all neighbors
(b) select a new best path
(c) export the new best path to the neighbors
Input arguments:
(a) IPprefix: the prefix for which the path will be withdrawn
(b) w_ASN: the neighbor AS (i.e., the last AS in the path; not necessarily the origin-AS) that announced the path which will be withdrawn
'''
def withdraw_path(self,IPprefix,w_ASN): # withdraws path without announcing a new path for this prefix
if self.paths.get(IPprefix): # if a path for this prefix exists in my FIB
if self.all_paths[IPprefix].get(w_ASN):
del self.all_paths[IPprefix][w_ASN] # remove it from local FIB
if w_ASN == list(self.paths[IPprefix])[0]: # if the withdrawn path is my current best path
for neighbor in self.ASneighbors.keys(): # make all my neighbors to withdraw the path (in case I have announced it to them)
self.Topology.get_node(neighbor).withdraw_path(IPprefix,self.ASN) # do withdrawal to neighbor
self.paths[IPprefix] = [] # remove it from my best path
self.select_best_path(IPprefix) # select a new best path
if self.paths.get(IPprefix):
self.export_path(IPprefix) # export the new best path
'''
Adds a filter for the given prefix.
IF there are filters for the given prefix
THEN add the given ASN in the set of filters for the given prefix (if the filter exists, there will be no change, since the filters are stored in a set)
ELSE add to the dictionary a filter for the given prefix and add to it the given ASN
Input arguments:
(a) IPprefix: the prefix for which the filter will be added
(b) ASN: the filter, i.e., the ASN whose announcements for the given prefix will be filtered out
'''
def add_filter(self,IPprefix,ASN):
if self.filters.get(IPprefix):
self.filters[IPprefix].add(ASN)
else:
self.filters[IPprefix] = set([ASN])
'''
Checks if the received path must be filtered, based on the stored filters.
IF a filter for the given prefix exists
THEN FOR EACH ASN in the given path
IF the ASN exists in the filter for the given prefix (i.e. the dictionary "filters")
THEN return True (i.e., filter the path)
Input arguments:
(a) IPprefix: the prefix for which the path has to be checked if it will be filtered
(b) path: the path (i.e., list of ASNs) which will be checked if it will be filtered
Returns:
TRUE if the path must be filtered, FALSE otherwise
'''
def must_filter_path(self,IPprefix,path):
if self.filters.get(IPprefix):
for ASN in path:
if ASN in self.filters[IPprefix]:
return True
return False
'''
### NOT USED METHOD ###
Compares the given path for the given prefix with the stored path (if any) for the given prefix, and decides if the new path needs to replace the stored path (or added if no stored path exists).
IF the given prefix is not an owned or hijacked prefix, and the new path does not contain my ASN (for loop avoidance),
THEN IF there exists a stored path in the dictionary "paths" for the given prefix,
THEN call method "conditions_to_change_existing_path(...)" to check if the existing paths needs to be changed,
ELSE return TRUE (i.e., the path for the prefix in the "paths" dictionary needs to be added/changed)
ELSE return FALSE (i.e., the path for the prefix in the "paths" dictionary must not be added/changed)
Input arguments:
(a) IPprefix: the prefix for which there exists a new path
(b) new_path: the path (i.e., list of ASNs) for the prefix
Returns:
TRUE if the path for the prefix in the "paths" dictionary needs to be added/changed, FALSE otherwise
'''
def change_path(self,IPprefix,new_path):
if (not self.has_prefix(IPprefix)) and (not self.has_hijacked_prefix(IPprefix)) and (self.ASN not in new_path): # if it's (a) not my prefix, AND (b) not a prefix I have hijacked, ADN (c) the new path does not contain my ASN (i.e., for loop avoidance)
if self.paths.get(IPprefix): # (if) I already have a path for this prefix in my table
if self.conditions_to_change_existing_path(IPprefix, new_path):
return True
else: # (else) I do NOT have a path for this prefix in my table
return True
return False
'''
Defines and checks the conditions that must be satisfied in order to replace an existing path for a prefix.
IF the given path is from the same neighbor as the existing path,
THEN change the path (because this neighbor would withdraw the old path in practice)
IF the given path is from a customer (or, peer) AS neighbor, and the existing path is from a peer/provider (or, provider) AS neighbor,
THEN return TRUE (i.e., prefer the given path)
ELIF the given and existing paths are from same type of AS neighbors,
THEN (i) prefer the shorter path, or
(ii) if both paths are of equal length, prefer the path from the neighbor with the higher preference
ELSE return FALSE (i.e., prefer the existing path)
Input arguments:
(a) IPprefix: the prefix for which there exists a new path
(b) new_path: the path (i.e., list of ASNs) for the prefix
Returns:
TRUE if the path for the prefix in the "paths" dictionary needs to be replaced, FALSE otherwise
'''
def conditions_to_change_existing_path(self,IPprefix, new_path):
my_path = list(self.paths[IPprefix])
if new_path[0] == my_path[0]: # if the new path is from the same neighbor as the old path, then change the path (because this neighbor would withdraw the old path in practice)
return True
if self.ASneighbors[new_path[0]] < self.ASneighbors[my_path[0]]: # Prefer customer (-1) > peer (0) > provider (1)
return True
elif self.ASneighbors[new_path[0]] == self.ASneighbors[my_path[0]]: # if both paths from equal neighbors ...
if len(new_path) < len(my_path): # if new path is shorter ...prefer it
return True
elif len(new_path) == len(my_path): # if new path is of equal length ...
return self.ASneighbors_preference[new_path[0]] > self.ASneighbors_preference[my_path[0]] # ... return neighbor with highest preference
return False
'''
Exports the path for the given prefix, i.e., (i) selects to which neighbors the path for the given prefix needs to be announced, and (ii) sends them the announcement.
IF the path for the given prefix is from a customer AS neighbor,
THEN the path must be announced to all AS neighbors (expect for the neighbor that sent it)
ELSE the path must be announced to all customer AS neighbors
Input arguments:
(a) IPprefix: the prefix whose the path needs to be exported
'''
def export_path(self,IPprefix):
neighbors_to_announce = set()
#neighbor_who_sent_the_announcement = self.ASneighbors[self.paths[IPprefix][0]]
neighbor_who_sent_the_announcement = self.paths[IPprefix][0]
if self.ASneighbors[neighbor_who_sent_the_announcement] == -1: # (if) path received from customer .... announce to all
neighbors_to_announce = set(self.ASneighbors.keys())
else: # (else) path received from peer/provider ... announce to all customers
for asn,peer_type in self.ASneighbors.items():
if peer_type == -1:
neighbors_to_announce.add(asn)
neighbors_to_announce.discard(neighbor_who_sent_the_announcement) # do NOT announce to the neighbor from which the path is received
if neighbors_to_announce:
self.announce_path(IPprefix, neighbors_to_announce)
'''
Announces the path for the given prefix to the given set of AS neighbors.
IF a certain path is not given
THEN (i) get the path that is stored in the "paths" dictionary, and
(ii) add to it the self.ASN
Announce the (given/stored) path to the given AS neighbors
'''
def announce_path(self,IPprefix, neighbors_to_announce, path_to_announce=None):
if path_to_announce is None:
path_to_announce = list(self.paths[IPprefix])
path_to_announce.insert(0,self.ASN)
for neighbor in neighbors_to_announce:
self.Topology.get_node(neighbor).receive_path(IPprefix,path_to_announce) # do announcement to neighbor
### methods for hijacking ###
'''
Hijacks the given prefix (i.e., announces a path that -illegitimately- includes the self.ASN).
IF the given prefix is NOT an owned prefix or a prefix previously hijacked,
THEN (i) add the given prefix to the dictionary of the hijacked prefixes,
(ii) IF the hijack type is 0 (i.e. origin AS hijack)
THEN set the path to be announced equal to [self.ASN]
ELSE call the method "get_path_poisoning_hijack(...)" to set the path
(iii) set the path in the "paths" dictionary equal to the announced/hijacked/fake path (without the self.ASN)
(iv) announce the path to all neighbors
Input arguments:
(a) IPprefix: the prefix to be hijacked
(b) hijack_type: the type of the hijack attack; the default value is 0, which corresponds to an origin-AS hijack
'''
def do_hijack(self, IPprefix, hijack_type=0):
if (not self.has_prefix(IPprefix)) and (not self.has_hijacked_prefix(IPprefix)):
self.add_hijacked_prefix(IPprefix,hijack_type)
neighbors_to_announce = set(self.ASneighbors.keys()) # announce the hijack to all neighbors
if hijack_type == 0: # origin-AS
path_to_announce = [self.ASN]
else: # 1st, 2nd, 3rd, etc. hop hijack, where hijack_type = 1,2,3,etc.
path_to_announce = self.get_path_poisoning_hijack(IPprefix, hijack_type)
self.paths[IPprefix] = path_to_announce[1:]
if len(path_to_announce): # check for the case that the "self.get_path_poisoning_hijack(...)"" function returns an empty list
self.announce_path(IPprefix, neighbors_to_announce, path_to_announce)
'''
Returns the path to be announced for a hijack of type {1,2,3,...} (i.e., a not origin-AS hijack).
(For the moment) this method does path poisoning as follows:
IF the hijacker AS has an existing/stored path for the given prefix,
THEN IF the length of the stored path is less than the attack type
THEN use the first hops in the stored AS path, e.g., if the hijacker has the path [AS3 AS2 AS1 origin_AS] and wants to do 2nd hop hijack, she will announce the path [self.ASN AS1 origin_AS]
ELSE complete first hops with origin ASN, e.g., for 2nd hop hijack announce [hijacker_AS origin_AS origin_AS]
ELSE do nothing (TODO: create a hijack-path for the given prefix when there is no stored legitimate-path)
Input arguments:
(a) IPprefix: the prefix to be hijacked
(b) hijack_type: the type of the hijack attack
Returns:
An AS-path, i.e., a list of ASNs (integers)
'''
def get_path_poisoning_hijack(self, IPprefix, hijack_type):
if self.paths.get(IPprefix):
original_path = list(self.paths.get(IPprefix))
if hijack_type <= len(original_path):
path_to_announce = [self.ASN] + original_path[-hijack_type:]
else:
path_to_announce = [self.ASN] + [original_path[-1]]*hijack_type
else:
# TODO: create a hijack-path for the given prefix when there is no stored legitimate-path
path_to_announce = []
return path_to_announce
### methods for printing information ###
def print_info(self):
print('***ASN***: '+str(self.ASN))
if self.IPprefix:
print('IP prefixes: '+str(self.IPprefix))
if self.hijacked_IPprefix:
print('hijacked IP prefixes: '+str(set(self.hijacked_IPprefix.keys())))
if self.ASneighbors:
print('AS neigbors')
for asn,peer_type in self.ASneighbors.items():
print(str(asn)+':'+str(peer_type))
if self.paths:
for ip,p in self.paths.items():
print('IP: '+str(ip)+' path: '+str(p))
print(' ')