-
Notifications
You must be signed in to change notification settings - Fork 0
/
OuterZeppelinSynthesiser.py
1327 lines (1071 loc) · 43.1 KB
/
OuterZeppelinSynthesiser.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
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
from Topology import Topology
from PolicyDatabase import PolicyDatabase
from NetworkPredicate import *
import time
import random
import networkx as nx
from subprocess import *
from collections import deque
import math
import copy
from collections import defaultdict
from ZeppelinSynthesiser import ZeppelinSynthesiser
class OuterZeppelinSynthesiser(object) :
def __init__(self, topology, pdb, numDomains=5, timeout=300, configOpt=False, rfOpt=False) :
self.topology = topology
self.pdb = pdb
# Store switch domains for each switch
self.switchDomains = dict()
self.domains = dict()
self.boundarySwitches = dict()
# Domain size variables
self.domainUpperLimit = 40
self.domainLowerLimit = 10
# Domain Assignments
self.bestDomainAssignment = None
self.worstDomainAssigmentRF = None
# BGP compatibility
self.nonBGPCompatibleSwitches = []
# MCMC variables
self.numDomains = numDomains
self.MCMC_MAX_ITER = 10000000
self.MCMC_MAX_TIME = timeout # in seconds
self.beta = 0.011 # Constant
self.configOpt = configOpt
self.rfOpt = rfOpt
# Scoring State variables
self.scoreIter = 0
self.prevConfScore = 0
self.confScoreDiff = 0
self.ASPaths = dict()
self.changedASPaths = dict()
self.ASPositions = dict()
self.changedASPositions = dict()
self.lastNonLoopDownstreamPosition = dict()
self.changedlastNonLoopDownstreamPosition = dict()
self.bgpRouterCounts = defaultdict(lambda:defaultdict(lambda:None))
self.shortestASPaths = defaultdict(lambda:defaultdict(lambda:None))
# Cache neighbours for reduced times.
self.topologyNeighbours = None
# Profiling variables
self.worstConfScore = 1
self.bestConfScore = 1000000000000000000
self.worstSRScore = 1
self.bestSRScore = 1000000000000000000
self.confScoreTime = 0
self.srScoreTime = 0
self.continuityTime = 0
self.domainChangeTime = 0
def enforceDAGs(self, dags, paths, endpoints):
""" Enforce the input destination dags """
self.destinationDAGs = copy.deepcopy(dags)
self.paths = copy.deepcopy(paths)
self.endpoints = copy.deepcopy(endpoints)
self.topologyNeighbours = self.topology.getNeighbours()
self.swCount = self.topology.getSwitchCount()
if not self.topology.checkTopologyContinuity() :
print "Topology not connected"
exit(0)
start_t = time.time()
self.MCMCWalk()
mcmcTime = time.time() - start_t
print "Best Configuration"
self.switchDomains = self.bestDomainAssignment
self.computeBoundaries()
print self.domains
print "Validation", self.checkValidDomainAssignment()
start_t = time.time()
[bestSRCount, bestRScore] = self.synthesizeOSPFConfigurations()
bestSRScore = self.staticSRScore()
self.bestConfScore = self.configurationScore()
bestospfTime = time.time() - start_t
self.zepFile = open("mcmc-timing", 'a')
if not self.configOpt:
print "Worst RF Configuration"
start_t = time.time()
[worstSRCount, worstRScore] = self.synthesizeOSPFConfigurations(self.worstDomainAssigmentRF)
worstospfTime = time.time() - start_t
worstSRScore = 0
if self.worstDomainAssigmentRF != None :
self.switchDomains = self.worstDomainAssigmentRF
self.computeBoundaries()
worstSRScore = self.staticSRScore()
self.zepFile.write(str(self.MCMCIter) + "\t" + str(self.accepts) + "\t" + str(len(paths)))
self.zepFile.write("\t" + str(self.bestConfScore) + "\t" + str(self.worstConfScore))
self.zepFile.write("\t" + str(bestSRCount) + "\t" + str(worstSRCount))
self.zepFile.write("\t" + str(bestRScore) + "\t" + str(worstRScore))
self.zepFile.write("\t" + str(bestospfTime) + "\t" + str(worstospfTime))
self.zepFile.write("\n")
# else :
# self.zepFile.write("Time taken for MCMC is (and iterations), and OSPF time " + str(mcmcTime) + "\t" + str(self.MCMCIter) + "\t" + str(len(dags)) + "\t" + str(len(paths)))
# self.zepFile.write("\n")
# self.zepFile.write("Config Improvement " + str(float(self.bestConfScore)/float(self.worstConfScore)) + "\t" + str(self.bestConfScore) + "\t" + str(self.worstConfScore))
# self.zepFile.write("\n")
self.zepFile.close()
def MCMCWalk(self) :
# Start a MCMC sampling walk with number of domains=self.numDomains.
print "Starting MCMC Walk"
self.MCMCIter = 0
self.accepts = 0
# Find diamonds for route-filter calculations.
self.findDiamonds()
# Find Configuration Score Upper Bound
self.findConfigurationScoreUpperBound()
# MCMC Algorithm initial step: start with a preliminary domain assignment (chosen at random)
self.computeRandomDomainAssignment()
self.computeBoundaries() # boundary bookkeeping for efficiency
worstScore = 0
bestScore = 1000000000000000000
hillClimb = 0
# Perform the Metropolis walk using the score functions.
# We consider solutions with a smaller score to be better.
Score = self.computeDomainAssignmentScore()
print "Started MCMC walk"
MCMCStartTime = time.time()
for self.MCMCIter in range(self.MCMC_MAX_ITER):
# Check if timed out every 10000 iteration
if self.MCMCIter % 10000 == 0 :
if time.time() - MCMCStartTime > self.MCMC_MAX_TIME :
# MCMC timed out.
break
# if self.MCMCIter % 100 == 0 :
# self.computeDomainAssignmentScore()
if Score > worstScore : worstScore = Score
if Score < bestScore :
bestScore = Score
self.bestDomainAssignment = copy.deepcopy(self.switchDomains)
s_t = time.time()
change = self.giveRandomDomainChange()
self.domainChangeTime += time.time() - s_t
# Make the random change to domain assignment.
sw = change[0]
oldDomain = self.switchDomains[sw]
newDomain = change[1]
self.switchDomains[sw] = newDomain
# recompute boundaries
self.recomputeBoundaries(sw, oldDomain, newDomain)
# Check if domain is continuous
s_t = time.time()
if not self.checkDomainContinuity(oldDomain) :
# Do not accept change.
self.switchDomains[sw] = oldDomain
self.recomputeBoundaries(sw, newDomain, oldDomain)
continue
self.continuityTime += time.time() - s_t
# Compute new score.
newScore = self.computeDomainAssignmentScore(False, sw, newDomain, oldDomain)
transitionProbability = math.exp(- self.beta * (float(newScore) - float(Score)))
transition = self.flip(transitionProbability)
if transition :
self.accepts += 1
#print "Score", Score, newScore, " Accept", sw, oldDomain, newDomain, bestScore, self.bestSRScore, self.bestConfScore, transitionProbability, hillClimb, self.MCMCIter
# accept transition to new state
Score = newScore # Update the score as we accept transition
# Change configuration state
self.changeConfigurationState()
continue
else :
# Do not transition. Revert back change.
#print "Score", Score, newScore, " Reject", sw, oldDomain, newDomain, worstScore, bestScore, transitionProbability
self.switchDomains[sw] = oldDomain
self.recomputeBoundaries(sw, newDomain, oldDomain)
print "MCMC Interations", self.MCMCIter
print "Best score", bestScore, "Worst score", worstScore
print "Best configuration score", self.bestConfScore, "Worst configuration score", self.worstConfScore
print "Best RF score", self.bestSRScore, "Worst RF score", self.worstSRScore
def flip(self, p):
# random.random() returns a uniformly distributed pseudo-random floating point number in the range [0, 1).
# This number is less than a given number p in the range [0,1) with probability p.
return (random.random() < p)
def giveRandomDomainChange(self) :
""" Returns a random change of domain for a boundary switch"""
iteration = 0
iterationCount = 5000 # Not assigned based on anything!
for iteration in range(iterationCount) :
sw = random.randint(1, self.swCount)
domain = self.switchDomains[sw]
if len(self.domains[domain]) <= self.domainLowerLimit:
# Domain size will be violated if we change a switch of this domain.
# Dont change it (to preserve number of domains)
continue
if sw not in self.boundarySwitches[domain] :
# sw not a boundary switch, can't change its domain.
continue
else :
# sw is a boundary switch. Check if sw partitions the domain.
neighbours = self.topologyNeighbours[sw]
neighbouringDomains = []
for n in neighbours :
ndomain = self.switchDomains[n]
if ndomain != domain and ndomain not in neighbouringDomains :
neighbouringDomains.append(ndomain)
if len(neighbouringDomains) == 0 :
# ERROR!
print sw, domain, neighbours
exit(0)
# pick one neighbouring domain by random and change sw's domain
newDomain = neighbouringDomains[random.randint(0, len(neighbouringDomains) - 1)]
if len(self.domains[newDomain]) + 1 >= self.domainUpperLimit :
# New domain's size would exceed upper limit. Reject change.
continue
# Return the random domain change picked.
return [sw, newDomain]
# Could not find a random change.
print "ERROR: No change in domain found"
exit(0)
def computeBoundaries(self) :
""" Computes the boundaries of the domains"""
self.boundarySwitches = dict()
for sw in range(1, self.swCount + 1) :
neighbours = self.topologyNeighbours[sw]
for n in neighbours :
if self.switchDomains[sw] != self.switchDomains[n] :
# Boundary
if self.switchDomains[sw] not in self.boundarySwitches :
self.boundarySwitches[self.switchDomains[sw]] = [sw]
else :
self.boundarySwitches[self.switchDomains[sw]].append(sw)
break
for sw in range(1, self.swCount + 1) :
if self.switchDomains[sw] not in self.domains :
self.domains[self.switchDomains[sw]] = [sw]
else :
if sw not in self.domains[self.switchDomains[sw]] :
self.domains[self.switchDomains[sw]].append(sw)
def recomputeBoundaries(self, sw, oldDomain, newDomain):
""" Recompute boundaries for change"""
# Move sw from oldDomain to newDomain
self.domains[oldDomain].remove(sw)
self.domains[newDomain].append(sw)
# Shift boundaries
self.boundarySwitches[oldDomain].remove(sw)
self.boundarySwitches[newDomain].append(sw)
neighbours = self.topologyNeighbours[sw]
for n in neighbours :
# Recompute boundaries for neighbours
if self.isBoundarySwitch(n) :
if n not in self.boundarySwitches[self.switchDomains[n]] :
self.boundarySwitches[self.switchDomains[n]].append(n)
else :
if n in self.boundarySwitches[self.switchDomains[n]] :
self.boundarySwitches[self.switchDomains[n]].remove(n)
def checkDomainContinuity(self, domain) :
domainSwitches = self.domains[domain]
reachableSwitches = dict()
reachableSwitches[domainSwitches[0]] = True
switchQueue = [domainSwitches[0]]
while len(reachableSwitches) < len(domainSwitches):
if len(switchQueue) == 0 :
# Partition in domain. Not valid!
return False
sw = switchQueue.pop(0)
neighbours = self.topologyNeighbours[sw]
for n in neighbours :
if self.switchDomains[n] == domain :
if n not in reachableSwitches and n not in switchQueue :
reachableSwitches[n] = True
switchQueue.append(n)
return True
def isBoundarySwitch(self, sw):
""" Returns if sw is a boundary switch or not"""
neighbours = self.topologyNeighbours[sw]
for n in neighbours :
if self.switchDomains[sw] != self.switchDomains[n] :
return True
return False
def computeRandomDomainAssignment(self):
""" Generate a random domain assignment to start the Metropolis walk"""
switches = range(1, self.swCount + 1)
iterations = 50000
self.switchDomains = dict()
for itr in range(iterations) :
# print "Iteration", itr
currDomain = 0
self.switchDomains = dict()
domainSizes = dict()
for domain in range(self.numDomains) :
domainSizes[domain] = 0
while len(self.switchDomains) != self.swCount:
random.shuffle(switches)
assignedSwitchCount = len(self.switchDomains)
for sw in switches :
if sw in self.switchDomains:
# Switch assigned.
continue
neighbours = self.topologyNeighbours[sw]
neighbouringDomains = dict()
assignedNeighbour = False
for n in neighbours :
if n in self.switchDomains:
# Neighbour assigned
neighbouringDomains[self.switchDomains[n]] = True
assignedNeighbour = True
if currDomain >= self.numDomains :
if len(neighbouringDomains.keys()) == 0 :
# No neighbour assigned
continue
else :
# Pick domain with minimum size
sortedDomains = []
for d in neighbouringDomains.keys() :
sortedDomains.append([d, neighbouringDomains[d]])
sortedDomains = sorted(sortedDomains, key=lambda p: p[1])
for d in sortedDomains :
domain = d[0]
currentSize = domainSizes[domain]
newSize = min(self.domainUpperLimit, currentSize + 2) # Weird constant
switchQueue = [sw]
while len(switchQueue) > 0 and domainSizes[domain] < newSize :
sw1 = switchQueue.pop(0)
neighbours = self.topologyNeighbours[sw1]
if sw1 not in self.switchDomains :
# switch not assigned
self.switchDomains[sw1] = domain
# print sw1, domain
domainSizes[domain] += 1
for n in neighbours :
if n not in self.switchDomains and n not in switchQueue:
switchQueue.append(n)
else :
# Check if any neighbours are assigned to some domain. Then dont assign.
if len(neighbouringDomains.keys()) > 0 :
continue
self.switchDomains[sw] = currDomain
domainSizes[currDomain] += 1
# Do a BFS from this switch till we exceed lower limit
switchQueue = [sw]
while len(switchQueue) > 0 and domainSizes[currDomain] < self.domainLowerLimit :
sw1 = switchQueue.pop(0)
neighbours = self.topologyNeighbours[sw1]
if sw1 not in self.switchDomains :
# switch not assigned
self.switchDomains[sw1] = currDomain
# print sw1, currDomain
domainSizes[currDomain] += 1
for n in neighbours :
if n not in self.switchDomains and n not in switchQueue:
switchQueue.append(n)
if domainSizes[currDomain] < self.domainLowerLimit :
# BFS could not find a domain with size greater than lower limit, Find a new assignment
break
# print "Size", currDomain, domainSizes[currDomain]
currDomain += 1
if len(self.switchDomains) == assignedSwitchCount and len(self.switchDomains) != self.swCount:
# No switch assigned in this pass. Find a new assignment
break
# Check validity
if not self.checkValidDomainAssignment() :
continue
else :
break
if itr >= iterations - 1 :
print "Cannot find a random domain assignment satisfying input policies even after 20k iterations"
exit(0)
def checkValidDomainAssignment(self) :
# Checks the validity of a particular domain assignment.
if len(self.switchDomains) != self.swCount :
# print "Size"
return False
self.domains = dict()
for sw in range(1, self.swCount + 1) :
if self.switchDomains[sw] not in self.domains :
self.domains[self.switchDomains[sw]] = [sw]
else :
if sw not in self.domains[self.switchDomains[sw]] :
self.domains[self.switchDomains[sw]].append(sw)
# A switch must be connected to atleast one switch of the same domain. (Assuming no single switch domains)
neighbours = self.topologyNeighbours[sw]
isConnected = False
for n in neighbours :
if self.switchDomains[sw] == self.switchDomains[n] :
isConnected = True
if not isConnected :
# No neighbour in same domain. Not a valid assignment
# print "No neighbours"
return False
for domain in range(self.numDomains) :
if len(self.domains[domain]) < self.domainLowerLimit or len(self.domains[domain]) > self.domainUpperLimit:
# Domain size violated.
# print "Domain size violated"
return False
# Check if domains are contiguous. We do this by starting at a switch, and
# check if all switches in the domain are reachable inside the domain.
for domain in self.domains.keys() :
domainSwitches = self.domains[domain]
reachableSwitches = [domainSwitches[0]]
switchQueue = [domainSwitches[0]]
while len(reachableSwitches) < len(domainSwitches):
if len(switchQueue) == 0 :
# Partition in domain. Not valid!
# print "Partition"
return False
sw = switchQueue.pop(0)
neighbours = self.topologyNeighbours[sw]
for n in neighbours :
if self.switchDomains[n] == domain :
if n not in reachableSwitches :
reachableSwitches.append(n)
switchQueue.append(n)
return True
def computeDomainAssignmentScore(self, absScore=True, sw=None, newDomain=None, oldDomain=None) :
""" Computes the score of a particular domain assignment """
score = 1
score += self.BGPCompabitityScore()
#score += 2*self.numberBGPRoutersScore()
start_t = time.time()
#print "Prev Conf Score", self.prevConfScore
# Compute score
srScore = self.staticSRScore(sw, newDomain, oldDomain)
if srScore > self.worstSRScore :
self.worstSRScore = srScore
self.worstDomainAssigmentRF = copy.deepcopy(self.switchDomains) # Copy the worst RF domain assignment.
if srScore < self.bestSRScore :
self.bestSRScore = srScore
confScore = self.configurationScore(sw, newDomain, oldDomain)
if confScore > self.worstConfScore :
self.worstConfScore = confScore
if confScore < self.bestConfScore :
self.bestConfScore = confScore
self.confScoreTime += time.time() - start_t
if self.configOpt :
return 4000 * float(confScore)/float(self.worstConfScore)
if self.rfOpt :
return 4000 * float(srScore)/float(self.worstSRScore)
#print float(confScore)/float(self.confScoreUpperBound), float(srScore)/float(self.SRScoreUpperBound), self.confScoreUpperBound
score += 4000 * max(float(confScore)/float(self.worstConfScore), float(srScore)/float(self.worstSRScore))
score += 400 * min(float(confScore)/float(self.worstConfScore), float(srScore)/float(self.worstSRScore))
return score
def domainSizeScore(self) :
# compute domain size (if in range or not).
score = 0
for domain in range(self.numDomains) :
domainSize = len(self.domains[domain])
# if domainSize > self.domainLowerLimit and domainSize < self.domainUpperLimit :
# Within range, score not changed.
score += pow(abs((self.domainLowerLimit + self.domainUpperLimit)/2 - domainSize), 2)
# else :
# score += 100
return score
def BGPCompabitityScore(self) :
score = 0
for sw in self.nonBGPCompatibleSwitches :
domain = self.switchDomains[sw]
if sw in self.boundarySwitches[domain] :
# non BGP-compatible router requires BGP
score += 100
return score
def numberBGPRoutersScore(self) :
score = 0
for domain in range(self.numDomains) :
score += len(self.boundarySwitches[domain])
return score
def findConfigurationScoreUpperBound(self) :
self.confScoreUpperBound = 0
for pc in self.paths.keys() :
path = self.paths[pc]
# Upper bounded by using static routes for all the paths
self.confScoreUpperBound += len(path)
def configurationScore(self, sw=None, newDomain=None, oldDomain=None) :
""" Computes the extra lines of BGP required to enforce policy routing
in the inter-domain setting """
score = 0
# State resets
for subnet in self.destinationDAGs.keys() :
for domain in range(self.numDomains) :
self.bgpRouterCounts[domain][subnet] = 0
# Precompute Shortest AS Paths
for domain1 in range(self.numDomains) :
for domain2 in range(self.numDomains) :
if domain1 == domain2 : continue
[uniqueness, shortestASPath] = self.findShortestASPath(domain1, domain2)
if uniqueness :
self.shortestASPaths[domain1][domain2] = shortestASPath
else :
self.shortestASPaths[domain1][domain2] = []
for domain in range(self.numDomains) :
for subnet in self.destinationDAGs.keys() :
domainpaths = dict()
nexthopAS = dict()
subnetPCs = self.pdb.getDestinationSubnetPacketClasses(subnet)
if len(subnetPCs) == 0 : continue
for pc in subnetPCs :
# Find domain in path
index = -1
path = self.paths[pc]
aspath = self.ASPaths[pc]
aspositions = self.ASPositions[pc]
for j in range(self.lastNonLoopDownstreamPosition[pc] + 1, len(aspath)) :
if aspath[j] == domain :
index = j
break
if index == len(aspath) - 1 or index < 0:
continue # No local preferences required
else :
domainpath = path[aspositions[index]:aspositions[index + 1]] # Extracts the path in the domain.
if domainpath == [] :
print "Something", path, index, aspath, aspositions
exit(0)
domainpaths[pc] = domainpath
nexthopAS[pc] = aspath[index+1]
dstDomain = self.switchDomains[self.pdb.getDestinationSwitch(subnetPCs[0])]
[localPrefScore, bgpRouterCount] = self.findLocalPrefScore(domain, dstDomain, domainpaths, nexthopAS)
self.bgpRouterCounts[domain][subnet] = bgpRouterCount
score += localPrefScore
#print "local prefs", time.time() - s_t
return score
def findStaticConfScore(self) :
""" Find cost of static routes (number of static hops)"""
for pc in self.paths.keys() :
path = self.paths[pc]
src = path[0]
dst = path[len(path) - 1]
aspath = [self.switchDomains[src]]
aspositions = [0] # Store the positions when the AS first starts.
for i in range(1, len(path)) :
domain = self.switchDomains[path[i]]
if domain != aspath[len(aspath) - 1] :
# Next AS. Add to AS path
aspath.append(domain)
aspositions.append(i)
# Store computations in state
self.ASPaths[pc] = aspath
self.ASPositions[pc] = aspositions
# Find largest non-loop path
if len(aspath) == 1 :
# The entire path is the same domain, there is no BGP configuration required for this path
self.lastNonLoopDownstreamPosition[pc] = -1
else :
# Find longest path from destination which does not contain loops
i = len(aspath) - 1
lastNonLoopDownstreamPosition = -1
while i > 0:
domain = aspath[i]
if aspath.count(domain) > 1 :
# Domain part of loop.
for j in range(i - 1, -1, -1) :
if aspath[j] == domain :
# First repitition from the end.
if j > lastNonLoopDownstreamPosition :
lastNonLoopDownstreamPosition = j
i -= 1
self.lastNonLoopDownstreamPosition[pc] = lastNonLoopDownstreamPosition
score = 0
for pc in self.paths.keys() :
score += self.findStaticConfScoreHelper(pc, self.paths[pc], self.ASPaths[pc], self.ASPositions[pc], self.lastNonLoopDownstreamPosition[pc])
return score
def findStaticConfScoreHelper(self, pc, path, aspath, aspositions, lastNonLoopDownstreamPosition) :
""" Find conf score for a single path """
src = path[0]
dst = path[len(path) - 1]
score = 0
if len(aspath) == 1 :
# The entire path is the same domain, there is no BGP configuration required for this path
score += 0
else :
if lastNonLoopDownstreamPosition > -1 :
# A looped path. Add static routing till start of (lastNonLoopDownstreamPosition + 1)th AS
# Routing from (lastNonLoopDownstreamPosition + 1)th AS to destination is loop-free
posSw = aspositions[lastNonLoopDownstreamPosition + 1]
# Static routing cost is the number of rules required till posSw
score += posSw
return score
def findLocalPrefScore(self, domain, dstDomain, domainpaths, nexthopAS) :
if domain == dstDomain :
return [0,0] # Dont need local prefs
shortestASPath = self.shortestASPaths[domain][dstDomain]
if shortestASPath == [] :
uniqueness = False
else :
uniqueness = True
score = 0
bgpRouterCount = 0
if uniqueness and len(domainpaths) == 1 :
for pc in domainpaths.keys() :
if nexthopAS[pc] == shortestASPath[1] :
# next hop AS is on unique shortest path
# Dont need local prefs
score += 0
else :
# Find number of bgp routers = B
bgpRouters = []
for dp in domainpaths.values() :
if dp[len(dp) - 1] not in bgpRouters :
bgpRouters.append(dp[len(dp) - 1])
# Require B local preference entries and B(B-1)/2 number of iBGP filters
# to propagate eBGP routes inside the OSPF domain.
score += len(bgpRouters)*(len(bgpRouters) + 1)*0.5
bgpRouterCount = len(bgpRouters)
return [score, bgpRouterCount]
def findStaticConfScoreDiff(self, sw, newDomain, oldDomain) :
""" Find difference in scores as sw oldDomain -> newDomain """
#Empty changed state
self.changedASPaths.clear()
self.changedASPositions.clear()
self.changedlastNonLoopDownstreamPosition.clear()
scoreDiff = 0
changedSubnets = []
for pc in self.paths.keys() :
path = self.paths[pc]
src = path[0]
dst = path[len(path) - 1]
if sw not in path :
# Not change in score?
continue
# sw is in path, so domain has changed. Check if affected.
oldaspath = self.ASPaths[pc]
oldaspositions = self.ASPositions[pc]
oldlastNonLoopDownstreamPosition = self.lastNonLoopDownstreamPosition[pc]
# Compute old static score.
oldScore = self.findStaticConfScoreHelper(pc, path, oldaspath, oldaspositions, oldlastNonLoopDownstreamPosition)
newaspath = [self.switchDomains[src]]
newaspositions = [0] # Store the positions when the AS first starts.
for i in range(1, len(path)) :
domain = self.switchDomains[path[i]]
if domain != newaspath[len(newaspath) - 1] :
# Next AS. Add to AS path
newaspath.append(domain)
newaspositions.append(i)
# Find largest non-loop path
if len(newaspath) == 1 :
# The entire path is the same domain, there is no BGP configuration required for this path
newlastNonLoopDownstreamPosition = -1
else :
# Find longest path from destination which does not contain loops
i = len(newaspath) - 1
newlastNonLoopDownstreamPosition = -1
while i > 0:
domain = newaspath[i]
if newaspath.count(domain) > 1 :
# Domain part of loop.
for j in range(i - 1, -1, -1) :
if newaspath[j] == domain :
# First repitition from the end.
if j > newlastNonLoopDownstreamPosition :
newlastNonLoopDownstreamPosition = j
i -= 1
self.changedASPaths[pc] = newaspath
self.changedASPositions[pc] = newaspositions
self.changedlastNonLoopDownstreamPosition[pc] = newlastNonLoopDownstreamPosition
newScore = self.findStaticConfScoreHelper(pc, path, newaspath, newaspositions, newlastNonLoopDownstreamPosition)
#print sw, oldDomain, newDomain, pc, path, oldaspath, newaspath, - oldScore + newScore
scoreDiff = scoreDiff - oldScore + newScore
# Check to see if sw is not staticly routed.
# swIndex = path.index(sw)
# if swIndex < oldaspositions[oldlastNonLoopDownstreamPosition + 1] :
# # sw statically routed. No change in local pref scores
# continue
# else :
# subnet = self.pdb.getDestinationSubnet(pc)
# if subnet not in changedSubnets :
# changedSubnets.append(subnet)
# localPrefDiff = 0
# changedDomains = [oldDomain, newDomain]
# for domain in changedDomains :
# for subnet in changedSubnets:
# domainpaths = dict()
# nexthopAS = dict()
# dstpc = -1
# subnetPCs = self.pdb.getDestinationSubnetPacketClasses(subnet)
# if len(subnetPCs) == 0 : continue
# for pc in subnetPCs :
# # Find domain in path
# index = -1
# path = self.paths[pc]
# aspath = self.ASPaths[pc]
# aspositions = self.ASPositions[pc]
# for j in range(self.lastNonLoopDownstreamPosition[pc] + 1, len(aspath)) :
# if aspath[j] == domain :
# index = j
# break
# if index == len(aspath) - 1 or index < 0:
# continue # No local preferences required
# else :
# domainpath = path[aspositions[index]:aspositions[index + 1]] # Extracts the path in the domain.
# if domainpath == [] :
# print "Something", path, index, aspath, aspositions
# exit(0)
# domainpaths[pc] = domainpath
# nexthopAS[pc] = aspath[index+1]
# dstDomain = self.switchDomains[self.pdb.getDestinationSwitch(subnetPCs[0])]
# [oldlocalPrefScore, oldbgpRouterCount] = self.findLocalPrefScore(domain, dstDomain, domainpaths, nexthopAS)
# domainpaths.clear()
# nexthopAS.clear()
# for pc in subnetPCs :
# # Find domain in path
# index = -1
# path = self.paths[pc]
# if pc in self.changedASPaths :
# aspath = self.changedASPaths[pc]
# aspositions = self.changedASPositions[pc]
# lastNonLoopDownstreamPosition = self.changedlastNonLoopDownstreamPosition[pc]
# else :
# aspath = self.ASPaths[pc]
# aspositions = self.ASPositions[pc]
# lastNonLoopDownstreamPosition = self.lastNonLoopDownstreamPosition[pc]
# for j in range(lastNonLoopDownstreamPosition + 1, len(aspath)) :
# if aspath[j] == domain :
# index = j
# break
# if index == len(aspath) - 1 or index < 0:
# continue # No local preferences required
# else :
# domainpath = path[aspositions[index]:aspositions[index + 1]] # Extracts the path in the domain.
# if domainpath == [] :
# print "Something", path, index, aspath, aspositions
# exit(0)
# domainpaths[pc] = domainpath
# nexthopAS[pc] = aspath[index+1]
# [newlocalPrefScore, newbgpRouterCount] = self.findLocalPrefScore(domain, dstDomain, domainpaths, nexthopAS)
# localPrefDiff += newlocalPrefScore - oldlocalPrefScore
# scoreDiff += localPrefDiff
return scoreDiff
def changeConfigurationState(self) :
# Transition accepted, change the original state variables to the changed ones.
for pc in self.changedASPaths.keys() :
self.ASPaths[pc] = self.changedASPaths[pc]
self.ASPositions[pc] = self.changedASPositions[pc]
self.lastNonLoopDownstreamPosition[pc] = self.changedlastNonLoopDownstreamPosition[pc]
# Change config score
self.prevStaticConfScore += self.staticConfScoreDiff
# # Precompute Shortest AS Paths
# for domain1 in range(self.numDomains) :
# for domain2 in range(self.numDomains) :
# if domain1 == domain2 : continue
# [uniqueness, shortestASPath] = self.findShortestASPath(domain1, domain2)
# if uniqueness :
# self.shortestASPaths[domain1][domain2] = shortestASPath
# else :
# self.shortestASPaths[domain1][domain2] = []
def findShortestASPath(self, srcDomain, dstDomain) :
""" Find shortest path from src to dst domains. Return uniqueness of shortest path as well"""
s_t = time.time()
srcBoundary = self.boundarySwitches[srcDomain]
dstBoundary = self.boundarySwitches[dstDomain]
# Do a Breadth-first search from srcDomain to dstDomain
domainQueue1 = [srcDomain]
domainQueue2 = []
visited = dict()
bfstree = dict()
paths = dict()
paths[srcDomain] = 1
while len(domainQueue1) > 0 :
# Explore one level of tree
while len(domainQueue1) > 0:
d = domainQueue1.pop(0)
visited[d] = True
if d == dstDomain :
if paths[d] > 1 :
# Multiple shortest paths to dstDomain.
return [False, None]
else :
# Single Unique shortest path to dstDomain.
# Trace back path to src and return.
aspath = [dstDomain]
nextas = bfstree[dstDomain]
while nextas != srcDomain :
aspath.append(nextas)
nextas = bfstree[nextas]
aspath.append(srcDomain)
# Reverse aspath.
aspath.reverse()
boundary1 = self.boundarySwitches[aspath[0]]
boundary2 = self.boundarySwitches[aspath[1]]
linkCount = 0 # Denotes the number of links connecting domains 1 & 2
for sw in boundary1 :
neighbours = self.topologyNeighbours[sw]
for n in neighbours :
if n in boundary2 :
linkCount += 1 # AS1-AS2 link
if linkCount == 1:
return [True, aspath]
else :
return [False, None]
# Find neighbouring domains
neighbouringDomains = []
boundary = self.boundarySwitches[d]
for sw in boundary :
for n in self.topologyNeighbours[sw] :
nd = self.switchDomains[n]
if nd != d and nd not in neighbouringDomains:
neighbouringDomains.append(nd)
for nd in neighbouringDomains :
if nd not in visited :
if nd not in domainQueue2 :
domainQueue2.append(nd)
paths[nd] = paths[d]
bfstree[nd] = d
else :
paths[nd] += paths[d]
domainQueue1 = domainQueue2
domainQueue2 = []
def findDiamonds(self) :
""" Detecting diamonds in the different dags. A diamond is
defined as a subgraph of the overlay where there are two paths from s to t
such that the two paths belong to different destination Dags. This implies that
there are two shortest paths from s to t which is not enforceable with route filtering """
dsts = self.pdb.getDestinationSubnets()
self.diamondCount = [[0 for x in range(self.swCount + 1)] for x in range(self.swCount + 1)]
self.diamondPaths = [[[] for x in range(self.swCount + 1)] for x in range(self.swCount + 1)]
for dst1 in dsts :
for dst2 in dsts :