-
Notifications
You must be signed in to change notification settings - Fork 1
/
pa.lua
1515 lines (1275 loc) · 40.8 KB
/
pa.lua
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
#!/usr/bin/env lua
-- -*-lua-*-
--
-- $Id: pa.lua $
--
-- Author: Markus Stenberg <[email protected]>
--
-- Copyright (c) 2012 cisco Systems, Inc.
--
-- Created: Mon Oct 1 11:08:04 2012 mstenber
-- Last modified: Thu Oct 24 15:56:54 2013 mstenber
-- Edit time: 1001 min
--
-- This is homenet prefix assignment algorithm, written using fairly
-- abstract data structures. The network abstraction object should
-- provide a way of doing the required operations, and should call us
-- whenever it's state changes.
-- client is expected to provide:
-- get_hwf(rid) => hardware fingerprint in string
-- get_hwaddr(ifname) => hardware MAC address
-- iterate_rid(rid, f) => callback f with {rid=[, ifname=, nh=][, rname=]}
-- iterate_usp(rid, f) => callback f with {prefix=, rid=}
-- iterate_asp(rid, f) => callback f with {prefix=, iid=, rid=}
-- iterate_asa(rid, f) => callback f with {prefix=, rid=}
-- iterate_if(rid, f) => callback f with if-object
-- iterate_ifo_neigh(rid, if-object, f) => callback with {iid=, rid=}
-- client can also override/subclass the lap class here within pa, to
-- provide the real assign/unassign/deprecation (by providing do_*
-- methods). once done, they should call Done/Error. if asynchronous,
-- they should also provide stop_* methods.
-- configuration parameters of note:
-- disable_ula - disable ALL ULA generation (we will use it if it is
-- provided by the network, though)
-- disable_ipv4 - disable IPv4 handling (both origination of IPv4 USP,
-- and assignment of local addresses)
-- disable_always_ula - disable constant ULA generation/use (if
-- disabled, ULA will be available if and only if no global prefixes
-- are available)
-- rid (or given to constructor)
require 'mst'
require 'ipv6s'
local ssloop = require 'ssloop'
-- SMC-generated state machine
-- fix braindeath of using pcall in a state machine in general..
-- and not returning errors in particular
local orig_pcall = pcall
function pcall(f)
-- errors, huh?
f()
end
local lap_sm = require 'pa_lap_sm'
pcall = orig_pcall
module('pa', package.seeall)
-- which is the last ip# on a /24 reserved for routers; assumption is
-- that there WON'T be that many nodes on a link (although wireless
-- may surprise us at some point)
IPV4_PA_LAST_ROUTER=64
-- how many bits of the IPv4 prefix are dedicated to routers
-- IPV4_PA_LAST_ROUTER overrides this, but this is used as initial
-- search space; therefore, they should be ~correct - that is,
-- 2^IPV4_PA_ROUTER_BITS =~ IPV4_PA_LAST_ROUTER
IPV4_PA_ROUTER_BITS=math.floor(math.log(IPV4_PA_LAST_ROUTER) / math.log(2))
CONFIG_DISABLE_ULA='disable_ula'
CONFIG_DISABLE_ALWAYS_ULA='disable_always_ula'
CONFIG_DISABLE_IPV4='disable_ipv4'
CONFIGS={CONFIG_DISABLE_IPV4, CONFIG_DISABLE_ULA, CONFIG_DISABLE_ALWAYS_ULA}
local _null = string.char(0)
-- sanity checking
function _valid_rid(s)
-- we use strings internally for test purposes
return type(s) == 'string' or type(s) == 'number'
end
function _valid_iid(s)
return type(s) == 'number'
end
function _valid_local_iid(pa, s)
mst.a(pa and s, 'pa/s not specified', pa, s)
return pa.ifs[s]
end
-- prefix handler base (used in lap, asp, usp)
ph = mst.create_class{class='ph'}
function ph:init()
self:a(self.prefix)
if type(self.prefix) == 'string'
then
self.prefix = ipv6s.new_prefix_from_ascii(self.prefix)
end
self.ascii_prefix = self.prefix:get_ascii()
self.binary_prefix = self.prefix:get_binary()
self.ipv4 = self.prefix:is_ipv4() and true or nil
end
-- local assigned prefix
lap = ph:new_subclass{class='lap', mandatory={'prefix', 'iid', 'pa'},
assigned=false,
depracated=false}
function lap:init()
-- superclass init
ph.init(self)
-- set up the local fields
local ifo = self.pa.ifs[self.iid]
self:a(ifo, 'non-existent interface iid', self.iid)
self.ifname = ifo.name
if not self.ipv4
then
-- generate IPv6 address to be assigned here; that way it's
-- available elsewhere as required
local hwaddr = self.pa:get_hwaddr(self.ifname)
self.address = ipv6s.new_prefix_from_ascii(ipv6s.prefix_hwaddr_to_eui64(self.ascii_prefix, hwaddr))
end
-- insert to pa's lap list
self.pa.lap:insert(self.iid, self)
-- start the state machine
self.sm = lap_sm:new{owner=self}
self.sm.debugFlag = true
self.sm.debugStream = {write=function (f, s)
self:d(mst.string_strip(s))
end}
self.sm:enterStartState()
self:assign()
self.pa:changed()
end
function lap:uninit()
self:d('uninit')
-- get rid of timeouts, if any
self.sm:UnInit()
-- remove from parent, mark that parent changed
self.pa.lap:remove(self.iid, self)
self.pa:changed()
end
function lap:repr_data()
return mst.repr{prefix=self.ascii_prefix,
iid=self.iid,
address=self.address,
za=self.assigned,
zd=self.depracated,
}
end
function lap:start_depracate_timeout()
-- child responsibility - should someday result in a Timeout()
end
function lap:stop_depracate_timeout()
end
function lap:start_expire_timeout()
-- child responsibility - should someday result in a Timeout()
end
function lap:stop_expire_timeout()
end
function lap:error(s)
self:d('got error', s)
end
function lap:find_usp()
-- this might be worth caching, perhaps..?
-- find the shortest (bit-mask-wise) usp which contains our prefix =>
-- that one is the one we belong to
local found, found_bits, bits
self.pa.usp:foreach(function (x, usp)
if usp.prefix:contains(self.prefix)
then
bits = usp.prefix:get_binary_bits()
if not found or found_bits > bits
then
found = usp
found_bits = bits
end
end
end)
return found
end
-- external API (which is just forwarded to the state machine)
function lap:assign()
self:d('assign', self.assigned)
self.sm:Assign()
end
function lap:unassign()
self:d('unassign')
self.sm:Unassign()
end
function lap:depracate()
self:d('depracate')
self.sm:Depracate()
end
-- these are subclass responsibility (optionally - it can also just
-- use the assigned/depracated flags these set)
function lap:do_assign()
self:a(not self._is_done, 'called when done')
self.pa:changed()
self.assigned = true
self.depracated = false
self.sm:Done()
end
function lap:do_depracate()
self:a(not self._is_done, 'called when done')
self.pa:changed()
self.assigned = false
self.depracated = true
if self.ipv4
then
self.address = nil -- clear ipv4 address, if any
end
self.sm:Done()
end
function lap:do_unassign()
self:a(not self._is_done, 'called when done')
self.pa:changed()
self.assigned = false
if self.ipv4
then
self.address = nil -- clear ipv4 address, if any
end
-- (hopefully we have something better to replace it with)
self.sm:Done()
end
-- assigned prefix
asp = ph:new_subclass{class='asp', mandatory={'prefix',
'iid',
'rid',
'pa'}}
function asp:init()
-- superclass init
ph.init(self)
local added = self.pa.asp:insert(self.rid, self)
self:a(added, "already existed?", self)
self:d('init')
self.pa:changed()
end
function asp:uninit()
self:d('uninit')
-- unassign is better - it has some built-in tolerance
-- (depracate = instantly offline)
self:unassign_lap()
self.pa.asp:remove(self.rid, self)
self.pa:changed()
end
function asp:repr_data()
return mst.repr{prefix=self.ascii_prefix, iid=self.iid, rid=self.rid}
end
function asp:get_liid()
return self.liid or self.iid
end
function asp:find_lap()
local iid = self:get_liid()
self:d('find_lap')
self:a(_valid_local_iid(self.pa, iid))
self:a(self.class)
self:a(self.pa, 'no pa?!?')
local t = self.pa.lap[iid]
for i, v in ipairs(t or {})
do
--self:d(' considering', v)
if v.ascii_prefix == self.ascii_prefix
then
return v
end
end
end
function asp:find_or_create_lap()
local iid = self:get_liid()
self:d('find_or_create_lap')
self:a(_valid_local_iid(self.pa, iid))
local o = self:find_lap(iid)
if o then return o end
self:d(' not found => creating')
-- mark that something changed so pa knows it too
-- (some branches of the logic, e.g. assign_other, won't, otherwise)
self.pa:changed()
return self.pa.lap_class:new{prefix=self.prefix,
iid=iid,
pa=self.pa}
end
function asp:assign_lap()
local iid = self:get_liid()
self:a(_valid_local_iid(self.pa, iid))
self:a(self.class == 'asp')
local lap = self:find_or_create_lap()
lap:assign()
lap.owner = self.rid == self.pa.rid and true or nil
end
function asp:depracate_lap()
-- look up locally assigned prefixes (if any)
local lap = self:find_lap()
if not lap then return end
lap:depracate()
end
function asp:unassign_lap()
local iid = self:get_liid()
-- look up locally assigned prefixes (if any)
-- brute-force through the lap - if prefix is same, we're good
for _, lap in ipairs(self.pa.lap[iid] or {})
do
if lap.ascii_prefix == self.ascii_prefix
then
lap:unassign()
return
end
end
end
function asp:is_remote()
return self.rid ~= self.pa.rid
end
-- subprefix sources can generate random prefixes within (using
-- self:get_desired_bits() as guidance to how long they should be,
-- provide their own freelist abstraction (which can be cleared as
-- needed), and some other niceties)
sps = ph:new_subclass{class='sps', mandatory={'prefix', 'pa'}}
function sps:repr_data()
return mst.repr{prefix=self.ascii_prefix, desired_bits=self.desired_bits}
end
function sps:get_desired_bits()
local bits = self.desired_bits
self:a(bits, 'desired_bits not set')
return bits
end
function sps:get_random_binary_prefix(iid, i)
local b = self.binary_prefix
local bl = self.prefix:get_binary_bits()
local desired_bits = self:get_desired_bits()
mst.a(bl > (#b-1) * 8 and bl <= #b * 8, "weird b<>bl", #b, bl)
mst.a(bl <= desired_bits, 'invalid prefix length prefix<>wanted', self, desired_bits)
i = i or 0
-- get the rest of the bytes from md5
local s = string.format("%s-%s-%s-%d",
self.pa:get_hwf(), self.pa.ifs[iid].name, self.ascii_prefix, i)
local sb = mst.create_hash(s)
-- sub-byte handling of the bits available
local v = string.byte(b, #b, #b)
local v2 = string.byte(sb, 1, 1)
for i=1,(8-bl%8)%8
do
if mst.bitv_is_set_bit(v2, i)
then
v = mst.bitv_xor_bit(v, i)
end
end
local c = string.char(v)
p = string.sub(b, 1, #b-1) .. c .. string.sub(sb, #b+1, desired_bits / 8)
local got_bits = #p * 8
self:a(got_bits == desired_bits, 'mismatch', got_bits, desired_bits)
return p
end
function sps:create_prefix_freelist(assigned)
if self.freelist then return self.freelist end
local b = self.binary_prefix
local t = mst.array:new()
local desired_bits = self:get_desired_bits()
local usp_bits = self.prefix:get_binary_bits()
mst.a(desired_bits > 0)
self.freelist = t
if #b >= desired_bits/8
then
return t
end
-- use the last prefix as base, iterate through the whole usable prefix
local p = b .. string.rep(string.char(0), desired_bits/8-#b)
local sp = p
while true
do
p = ipv6s.binary_prefix_next_from_usp(b, usp_bits, p, desired_bits)
self:a(#p == desired_bits/8, "binary_prefix_next_from_usp bugs?")
if not assigned[p]
then
local np = ipv6s.new_prefix_from_binary(p)
self:a(self.prefix:contains(np))
t:insert(np)
end
-- we're done once we're back at start
if sp == p
then
self:d('created freelist', #t)
return t
end
end
-- never reached
end
function sps:find_new_from(iid, assigned)
local b = self.binary_prefix
local p
self:a(assigned, 'assigned missing')
self:a(b)
-- if we're in freelist mode, just use it. otherwise, try to
-- pick randomly first
local t = self.freelist
if not t
then
-- initially, try the specified number times (completely
-- arbitrary number) to figure a randomish prefix (based on the
-- router id)
-- (note: it should be big enough to make it unlikely that we
-- have to produce a freelist, which in and of itself is
-- expensive)
for i=1,self.random_prefix_tries
do
local p = self:get_random_binary_prefix(iid, i)
if not assigned[p]
then
local np = ipv6s.new_prefix_from_binary(p)
self:d('find_new_from random worked iteration', i, self.pa.rid, iid, np)
self:a(self.prefix:contains(np))
return np
end
end
-- ok, lookup failed; create freelist
t = self:create_prefix_freelist(assigned)
end
-- Now handle freelist; pick random item from there. We _could_
-- try some sort of md5-seeded logic here too; however, I'm not
-- convinced the freelist looks same in exhaustion cases anyway, so
-- random choice is as good as any?
self:a(t)
local idx = mst.array_randindex(t)
if not idx
then
self:d('not found in freelist', self.prefix, #t)
return
end
local v = t[idx]
t:remove_index(idx)
self:d('find_new_from picked index', idx, v)
return v
end
-- usable prefix, can be either local or remote (no behavioral
-- difference though?)
usp = sps:new_subclass{class='usp', mandatory={'prefix', 'rid', 'pa'}}
function usp:init()
-- superclass init
sps.init(self)
local added = self.pa.usp:insert(self.rid, self)
self:a(added, 'already existed?', self)
self.pa:changed()
end
function usp:uninit()
self:d('uninit')
self.pa.usp:remove(self.rid, self)
self.pa:changed()
end
function usp:repr_data()
return mst.repr{prefix=self.ascii_prefix, rid=self.rid}
end
function usp:get_desired_bits()
if self.ipv4
then
return 96 + 24 -- /24
else
return 64
end
end
-- main prefix assignment class
pa = mst.create_class{class='pa',
lap_class=lap,
mandatory={'rid'},
-- tunable parameters
new_prefix_assignment=0,
new_ula_prefix=0,
random_prefix_tries=5,
time=ssloop.time,
}
function pa:init()
-- set the sps subclasses' random prefix #
-- TODO - this isn't very pretty (but mostly works)
sps.random_prefix_tries = self.random_prefix_tries
-- locally assigned prefixes - iid => list
self.lap = mst.multimap:new()
-- rid reachability => neighbor object (keys: rid, rname) if reachable
self.ridr = mst.map:new()
-- all asp data, ordered by prefix
self.asp = mst.multimap:new()
self.vsa = mst.validity_sync:new{t=self.asp, single=true}
-- all usp data, ordered by prefix
self.usp = mst.multimap:new()
self.vsu = mst.validity_sync:new{t=self.usp, single=true}
-- init changes to 0 here (it's cleared at _end_ of each pa:run,
-- but timeouts may cause it to become non-zero before next pa:run)
self.changes = 0
-- store when we started, for hysteresis calculations
self.start_time = self.time()
end
function pa:uninit()
self:d('uninit')
-- just kill the contents of all datastructures
self:filtered_values_done(self.lap)
self:filtered_values_done(self.asp)
self:filtered_values_done(self.usp)
end
function pa:filtered_values_done(h, f)
self:a(h.class == 'multimap')
local todo
h:foreach(function (k, o)
if not f or f(o)
then
todo = todo or {}
table.insert(todo, o)
end
end)
if todo
then
for i, o in ipairs(todo)
do
self:d('done with', o)
o:done()
end
end
end
function pa:get_local_asp_values()
self:a(self)
self:a(self.class=='pa')
local l = self.asp[self.rid]
if l then l = mst.table_copy(l) end
return l or {}
end
function pa:get_local_usp_values()
self:a(self)
self:a(self.class=='pa')
local l = self.usp[self.rid]
if l then l = mst.table_copy(l) end
return l or {}
end
function pa:repr_data()
if not self.asp
then
return '?'
end
local asps = self.asp:values()
local lasp = self:get_local_asp_values()
return string.format('rid:%s #lap:%d #ridr:%d #asp:%d[%d] #usp:%d #if:%d',
self.rid,
#self.lap:values(),
#self.ridr:values(),
#asps,
#lasp,
#self.usp:values(),
self.ifs and #self.ifs:values() or -1)
end
function pa:run_if_usp(iid, neigh, usp)
local rid = self.rid
self:a(_valid_rid(rid))
self:a(rid, 'no rid')
self:d('run_if_usp', iid, usp.prefix, neigh)
local ifo = self.ifs[iid]
if ifo.disable
then
self:d(' PA disabled')
return
end
if ifo.external
then
self:d(' external (according to RFC6204, should be just host W-1)')
return
end
-- skip if it's IPv4 prefix + interface is disabled for v4
if usp.ipv4 and ifo.disable_v4
then
self:d(' v4 PA disabled')
return
end
-- Alg from 6.3.. steps noted
-- 1. if some shorter prefix contains this usp, skip
local containing_prefix_found
self.usp:foreach(function (k, v)
-- TODO-DRAFT - complain that this seems broken
-- (BCP38 stuff might make it not-so-working?)
if v.ascii_prefix ~= usp.ascii_prefix
and v.prefix:contains(usp.prefix)
then
containing_prefix_found = true
end
end)
if containing_prefix_found
then
self:d('skipped, containing prefix found')
return
end
-- (skip 2. - we don't really care about neighbors)
-- 3. determine highest rid of already assigned prefix on the link
local own
local highest
self.asp:foreach(function (k, asp)
--self:d(' considering', asp)
if ((asp.rid == rid and iid == asp.iid)
or neigh[asp.rid] == asp.iid)
and usp.prefix:contains(asp.prefix)
then
self:d(' fitting', asp)
if not highest or highest.rid < asp.rid
then
highest = asp
end
if asp.rid == rid
then
own = asp
end
end
end)
-- 4.
-- (i) - router made assignment, highest router id
if own and own == highest
then
own.usp = usp
self:check_asp_conflicts(iid, own)
return
end
-- (ii) - assignment by neighbor
if highest
then
highest.usp = usp
-- note the local interface identifier so it can be used
-- appropriately by the asp class's methods
highest.liid = iid
-- zap anything else we might have
self:eliminate_other_lap(iid, usp, highest)
self:assign_other(iid, highest)
return
end
-- (iii) no assignment by anyone, highest rid?
-- TODO-DRAFT - should we check for AC-enabled neighbors here only?
local neigh_rids = neigh:keys()
local highest_rid = mst.max(unpack(neigh_rids))
self:a(not highest_rid or _valid_rid(highest_rid), 'invalid highest_rid', highest_rid)
if not highest_rid or highest_rid <= rid
then
self:assign_own(iid, usp)
return
end
-- (iv) no assignment by anyone, not highest rid
-- nop (do nothing)
self:d('no assignments, lower rid than', highest_rid)
end
-- 6.3.1
function pa:assign_own(iid, usp)
local lap
self:d('6.3.1 assign_own', iid, usp)
self:a(_valid_local_iid(self, iid))
-- 1. find already assigned prefixes
assigned = self:find_assigned(usp)
usp.freelist = nil -- clear old freelist, if any
-- 2. try to find 'old one'
local p
for i, v in ipairs(self.lap[iid] or {})
do
if usp.prefix:contains(v.prefix)
then
if v.assigned or not assigned[v.binary_prefix]
then
self:d('found old lap', v)
p = v.prefix
lap = v
end
end
end
if not p
then
local old = self:get_old_assignments()
if old
then
self:d('considering old assignments', usp.prefix)
for i, v in ipairs(old[usp.ascii_prefix] or {})
do
--self:d('got', v)
local oiid, oprefix = unpack(v)
self:d(' ', oiid, oprefix)
if oiid == iid
then
if not assigned[oprefix]
then
self:d(' found in old assignments', oprefix)
p = oprefix
else
self:d(' found in old assignments, but reserved', oprefix)
end
end
end
end
end
-- 3. hysteresis (sigh)
-- first off, apply it only if within 'short enough' period of time from the start of the router
if not p
and self.new_prefix_assignment > 0
and self:time_since_start() < self.new_prefix_assignment
and self:is_only_visible_router()
then
self:busy_until(self.new_prefix_assignment)
self:d('hysteresis criteria filled - not assigning anything yet')
return
end
-- 4. assign /64 if possible
if not p
then
p = usp:find_new_from(iid, assigned)
end
-- 5. if none available, skip
-- (TODO-DRAFT in draft, the order is assign new + hysteresis + this)
if not p
then
return
end
-- get rid of potentially duplicate lap's, if any
self:eliminate_other_lap(iid, usp, nil, lap)
-- 6. if assigned, mark as valid + send AC LSA
local o = asp:new{prefix=p,
pa=self,
iid=iid,
rid=self.rid}
o:assign_lap()
end
function pa:eliminate_other_lap(iid, usp, asp, lap)
self:a(iid, 'iid missing', iid, usp, asp)
-- depracate immediately any other prefix that is on this iid,
-- with same USP as us
local is_ipv4 = not usp or usp.ipv4
local lap = lap or (asp and asp:find_lap())
-- (nondeterministic) test case for this in elsa_pa_stress.lua
for i, lap2 in ipairs(self.lap[iid] or {})
do
self:d('eliminate_other_lap considering', lap2)
-- Only one IPv4 prefix can be active per-interface =>
-- when adding something new, old one disappears off the map immediately
-- anyway regardless of USP match
if ((is_ipv4 and lap2.ipv4 and lap2.address)
or (usp and usp.prefix:contains(lap2.prefix))) and lap2 ~= lap
then
self:d(' matched -> depracate')
lap2:depracate()
end
end
end
function pa:time_since_start()
return self.time() - self.start_time
end
-- child responsibility - return old assignment multimap, with
-- usp-prefix => {{iid, asp-prefix}, ...}
function pa:get_old_assignments()
return self.old_assignments
end
function pa:find_assigned(usp)
local t = mst.set:new()
self.asp:foreach(function (k, asp)
local ab = asp.binary_prefix
self:a(ab, 'no asp.binary_prefix')
if usp.prefix:contains(asp.prefix)
then
t:insert(ab)
end
end)
return t
end
-- 6.3.2
function pa:check_asp_conflicts(iid, asp)
self:d('6.3.2 check_asp_conflicts', asp)
local t
local found_conflict
self.asp:foreach(function (k, asp2)
-- if conflict, with overriding rid is found,
-- depracate prefix
if asp2.ascii_prefix == asp.ascii_prefix and asp2.rid > asp.rid
then
found_conflict = asp2
end
end)
if found_conflict
then
-- as described in 6.3.3
self:d(' found conflict', found_conflict)
self:a(asp:get_liid() == iid)
asp:depracate_lap()
return
end
-- otherise mark it as valid
self.vsa:set_valid(asp)
end
-- 6.3.4
function pa:assign_other(iid, asp)
self:d('6.3.4 assign_other', asp)
-- if we get here, it's valid asp.. just question of what we need
-- to do with lap
self.vsa:set_valid(asp)
-- So we just fire up the assign_lap, it will ignore duplicate calls anyway
-- Note: the verbiage about locally converted interfaces etc seems
-- excessively strict in the draft.
local liid = asp:get_liid()
self:a(liid == iid, 'asp iid mismatch', liid, iid)
asp:assign_lap()
end
function pa:find_usp_matching(filter)
local found
self.usp:foreach(function (k, usp)
if filter(usp) then found = usp end
end)
return found
end
function pa:is_only_visible_router()
for rid, o in pairs(self.ridr)
do
if o and o.rid ~= self.rid
then
return false
end
end
return true
end
function pa:get_highest_rid()
local highest_rid
for rid, o in pairs(self.ridr)
do
if o
then
if not highest_rid or highest_rid < rid
then
highest_rid = rid
end
end
end
return highest_rid
end
function pa:generate_ulaish(filter, filter_own, generate_prefix, desc)
-- i) first off, if we _do_ have usable prefixes from someone else,
-- use them
if self:find_usp_matching(filter)
then
self:d('something exists, generate_ulaish skipped', desc)
return
end
self:d('generate_ulaish', desc)
-- ii) do we have highest rid? if not, generation isn't our job
local my_rid = self.rid
if self.rid < self:get_highest_rid()
then
self:d(' higher rid exists, skipping')
return
end
-- iii) 'assignments'.. vague. skipped. XXX
-- we should either create or maintain ULA-USP