-
Notifications
You must be signed in to change notification settings - Fork 298
/
binding_table.lua
373 lines (337 loc) · 14.5 KB
/
binding_table.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
-- AFTR Binding Table
--
-- A binding table is a collection of softwires (tunnels). One endpoint
-- of the softwire is in the AFTR and the other is in the B4. A
-- softwire provisions an IPv4 address (or a part of an IPv4 address) to
-- a customer behind a B4. The B4 arranges for all IPv4 traffic to be
-- encapsulated in IPv6 and sent to the AFTR; the AFTR does the reverse.
-- The binding table is how the AFTR knows which B4 is associated with
-- an incoming packet.
--
-- There are three parts of a binding table: the PSID info map, the
-- border router (BR) address table, and the softwire map.
--
-- The PSID info map facilitates IPv4 address sharing. The lightweight
-- 4-over-6 architecture supports sharing of IPv4 addresses by
-- partitioning the space of TCP/UDP/ICMP ports into disjoint "port
-- sets". Each softwire associated with an IPv4 address corresponds to
-- a different set of ports on that address. The way that the ports are
-- partitioned is specified in RFC 7597: each address has an associated
-- set of parameters that specifies how to compute a "port set
-- identifier" (PSID) from a given port.
--
-- 0 1
-- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
-- +-----------+-----------+-------+
-- Ports in | A | PSID | j |
-- the CE port set | > 0 | | |
-- +-----------+-----------+-------+
-- | a bits | k bits |m bits |
--
-- Figure 2: Structure of a Port-Restricted Port Field
--
-- Source: http://tools.ietf.org/html/rfc7597#section-5.1
--
-- We find the specification's names to be a bit obtuse, so we refer to
-- them using the following names:
--
-- a bits = reserved_ports_bit_count.
-- k bits = psid_length.
-- m bits = shift.
--
-- When a packet comes in, we take the IPv4 address and look up the PSID
-- parameters from the PSID info table. We use those parameters to
-- compute the PSID. Together, the IPv4 address and PSID are used as a
-- key into the softwire table, which determines if the packet
-- corresponds to a known softwire, and if so the IPv6 address of the B4.
--
-- A successful lookup into the softwire table will also indicate the
-- IPv6 address of the AFTR itself. As described in
-- https://www.ietf.org/id/draft-farrer-softwire-br-multiendpoints-01.txt,
-- an AFTR may have multiple configured addresses.
--
-- Note that if reserved_ports_bit_count is nonzero, the lwAFTR must
-- drop a packet whose port is less than 2^reserved_ports_bit_count. In
-- practice though we just return a PSID that is out of range (greater
-- or equal to 2^psid_length), which will cause the softwire lookup to
-- fail. Likewise if we get a packet to an IPv4 address that's not
-- under our control, we return 0 for the PSID, knowing that the
-- subsequent softwire lookup will fail.
--
module(..., package.seeall)
local bit = require('bit')
local ffi = require("ffi")
local rangemap = require("apps.lwaftr.rangemap")
local ctable = require("lib.ctable")
local ipv6 = require("lib.protocol.ipv6")
local ipv4_ntop = require("lib.yang.util").ipv4_ntop
local band, lshift, rshift = bit.band, bit.lshift, bit.rshift
softwire_key_t = ffi.typeof[[
struct { uint32_t ipv4; uint16_t psid; }
]]
softwire_value_t = ffi.typeof[[
struct { uint8_t b4_ipv6[16], br_address[16]; }
]]
psid_map_key_t = ffi.typeof[[
struct { uint32_t addr; }
]]
psid_map_value_t = ffi.typeof[[
struct { uint16_t psid_length; uint16_t shift; }
]]
BTLookupQueue = {}
local BTLookupQueue_size = 128
assert(BTLookupQueue_size >= engine.pull_npackets)
-- BTLookupQueue needs a binding table to get softwires and PSID lookup.
function BTLookupQueue.new(binding_table)
local ret = {
binding_table = assert(binding_table),
}
ret.streamer = binding_table.softwires:make_lookup_streamer(BTLookupQueue_size)
ret.packet_queue = ffi.new("struct packet * [?]", BTLookupQueue_size)
ret.length = 0
return setmetatable(ret, {__index=BTLookupQueue})
end
function BTLookupQueue:enqueue_lookup(pkt, ipv4, port)
assert(self.length < BTLookupQueue_size, "BTLookupQueue overflow")
local n = self.length
local streamer = self.streamer
streamer.entries[n].key.ipv4 = ipv4
streamer.entries[n].key.psid = port
self.packet_queue[n] = pkt
n = n + 1
self.length = n
end
function BTLookupQueue:process_queue()
if self.length > 0 then
local streamer = self.streamer
for n = 0, self.length-1 do
local ipv4 = streamer.entries[n].key.ipv4
local port = streamer.entries[n].key.psid
streamer.entries[n].key.psid = self.binding_table:lookup_psid(ipv4, port)
end
streamer:stream()
end
return self.length
end
function BTLookupQueue:get_lookup(n)
if n < self.length then
local streamer = self.streamer
local pkt, b4_ipv6, br_ipv6
pkt = self.packet_queue[n]
if not streamer:is_empty(n) then
b4_ipv6 = streamer.entries[n].value.b4_ipv6
br_ipv6 = streamer.entries[n].value.br_address
end
return pkt, b4_ipv6, br_ipv6
end
end
function BTLookupQueue:reset_queue()
self.length = 0
end
local BindingTable = {}
function BindingTable.new(psid_map, softwires)
local ret = {
psid_map = assert(psid_map),
softwires = assert(softwires),
entry = softwires.entry_type()
}
return setmetatable(ret, {__index=BindingTable})
end
function BindingTable:add_softwire_entry(entry_blob)
local entry = self.entry
assert(ffi.sizeof(entry) == ffi.sizeof(entry_blob))
ffi.copy(entry, entry_blob, ffi.sizeof(entry))
self.softwires:add(entry.key, entry.value)
end
function BindingTable:remove_softwire_entry(entry_key_blob)
local entry = self.entry
assert(ffi.sizeof(entry.key) == ffi.sizeof(entry_key_blob))
ffi.copy(entry.key, entry_key_blob, ffi.sizeof(entry.key))
self.softwires:remove(entry.key)
end
function BindingTable:lookup(ipv4, port)
local lookup_key = self.entry.key
local psid = self:lookup_psid(ipv4, port)
lookup_key.ipv4 = ipv4
lookup_key.psid = psid
local entry = self.softwires:lookup_ptr(lookup_key)
if entry then return entry.value end
return nil
end
function BindingTable:is_managed_ipv4_address(ipv4)
-- The PSID info map covers only the addresses that are declared in
-- the binding table. Other addresses are recorded as having
-- psid_length == shift == 0.
local psid_info = self.psid_map:lookup(ipv4).value
return psid_info.psid_length + psid_info.shift > 0
end
function BindingTable:lookup_psid(ipv4, port)
local psid_info = self.psid_map:lookup(ipv4).value
local psid_len, shift = psid_info.psid_length, psid_info.shift
local psid_mask = lshift(1, psid_len) - 1
local psid = band(rshift(port, shift), psid_mask)
-- Are there any restricted ports for this address?
if psid_len + shift < 16 then
local reserved_ports_bit_count = 16 - psid_len - shift
local first_allocated_port = lshift(1, reserved_ports_bit_count)
-- The port is within the range of restricted ports. Assign a
-- bogus PSID so that lookup will fail.
if port < first_allocated_port then psid = psid_mask + 1 end
end
return psid
end
-- Iterate over the set of IPv4 addresses managed by a binding
-- table. Invoke like:
--
-- for ipv4_lo, ipv4_hi, psid_info in bt:iterate_psid_map() do ... end
--
-- The IPv4 values are host-endianness uint32 values, and are an
-- inclusive range to which the psid_info applies. The psid_info is a
-- psid_map_value_t pointer, which has psid_length and shift members.
function BindingTable:iterate_psid_map()
local f, state, lo = self.psid_map:iterate()
local function next_entry()
local hi, value
repeat
lo, hi, value = f(state, lo)
if lo == nil then return end
until value.psid_length > 0 or value.shift > 0
return lo, hi, value
end
return next_entry
end
-- Iterate over the softwires in a binding table. Invoke like:
--
-- for entry in bt:iterate_softwires() do ... end
--
-- Each entry is a pointer with two members, "key" and "value". They
-- key is a softwire_key_t and has "ipv4" and "psid" members. The value
-- is a softwire_value_t and has "br_address" and "b4_ipv6" members. Both
-- members are a uint8_t[16].
function BindingTable:iterate_softwires()
return self.softwires:iterate()
end
function pack_psid_map_entry (softwire)
local port_set = assert(softwire.port_set)
local psid_length = port_set.psid_length
local shift = 16 - psid_length - (port_set.reserved_ports_bit_count or 0)
assert(psid_length + shift <= 16,
("psid_length %s + shift %s should not exceed 16"):
format(psid_length, shift))
local key = softwire.ipv4
local value = {psid_length = psid_length, shift = shift}
return key, value
end
function load (conf)
local psid_builder = rangemap.RangeMapBuilder.new(psid_map_value_t)
-- Lets create an intermediatory PSID map to verify if we've added
-- a PSID entry yet, if we have we need to verify that the values
-- are the same, if not we need to error.
local inter_psid_map = {
keys = {}
}
function inter_psid_map:exists (key, value)
local v = self.keys[key]
if not v then return false end
if v.psid_length ~= v.psid_length or v.shift ~= v.shift then
error("Port set already added with different values: "..key)
end
return true
end
function inter_psid_map:add (key, value)
self.keys[key] = value
end
local softwires = ctable.new{
key_type = softwire_key_t,
value_type = softwire_value_t,
max_occupancy_rate = 0.4
}
local key, value = softwire_key_t(), softwire_value_t()
for _, entry in ipairs(conf.softwire) do
-- Add entry to binding table
key.ipv4 = entry.ipv4
key.psid = entry.psid
value.b4_ipv6 = entry.b4_ipv6
value.br_address = entry.br_address
softwires:add(key, value)
-- Check that the map either hasn't been added or that
-- it's the same value as one which has.
local psid_key, psid_value = pack_psid_map_entry(entry)
if not inter_psid_map:exists(psid_key, psid_value) then
inter_psid_map:add(psid_key, psid_value)
psid_builder:add(entry.ipv4, psid_value)
end
end
local psid_map = psid_builder:build(psid_map_value_t(), true)
return BindingTable.new(psid_map, softwires)
end
function selftest()
print('selftest: binding_table')
local function load_str(str)
local mem = require("lib.stream.mem")
local yang = require('lib.yang.yang')
local data = require('lib.yang.data')
local schema = yang.load_schema_by_name('snabb-softwire-v3')
local grammar = data.config_grammar_from_schema(schema)
local subgrammar = assert(grammar.members['softwire-config'])
local subgrammar = assert(subgrammar.members['binding-table'])
local parse = data.data_parser_from_grammar(subgrammar)
return load(parse(mem.open_input_string(str)))
end
local map = load_str([[
softwire { ipv4 178.79.150.233; psid 80; b4-ipv6 127:2:3:4:5:6:7:128; br-address 8:9:a:b:c:d:e:f; port-set { psid-length 16; }}
softwire { ipv4 178.79.150.233; psid 2300; b4-ipv6 127:11:12:13:14:15:16:128; br-address 8:9:a:b:c:d:e:f; port-set { psid-length 16; }}
softwire { ipv4 178.79.150.233; psid 2700; b4-ipv6 127:11:12:13:14:15:16:128; br-address 8:9:a:b:c:d:e:f; port-set { psid-length 16; }}
softwire { ipv4 178.79.150.233; psid 4660; b4-ipv6 127:11:12:13:14:15:16:128; br-address 8:9:a:b:c:d:e:f; port-set { psid-length 16; }}
softwire { ipv4 178.79.150.233; psid 7850; b4-ipv6 127:11:12:13:14:15:16:128; br-address 8:9:a:b:c:d:e:f; port-set { psid-length 16; }}
softwire { ipv4 178.79.150.233; psid 22788; b4-ipv6 127:11:12:13:14:15:16:128; br-address 8:9:a:b:c:d:e:f; port-set { psid-length 16; }}
softwire { ipv4 178.79.150.233; psid 54192; b4-ipv6 127:11:12:13:14:15:16:128; br-address 8:9:a:b:c:d:e:f; port-set { psid-length 16; }}
softwire { ipv4 178.79.150.15; psid 0; b4-ipv6 127:22:33:44:55:66:77:128; br-address 8:9:a:b:c:d:e:f; port-set { psid-length 4; }}
softwire { ipv4 178.79.150.15; psid 1; b4-ipv6 127:22:33:44:55:66:77:128; br-address 8:9:a:b:c:d:e:f; port-set { psid-length 4; }}
softwire { ipv4 178.79.150.2; psid 7850; b4-ipv6 127:24:35:46:57:68:79:128; br-address 1E:1:1:1:1:1:1:af; port-set { psid-length 16; }}
softwire { ipv4 178.79.150.3; psid 4; b4-ipv6 127:14:25:36:47:58:69:128; br-address 1E:2:2:2:2:2:2:af; port-set { psid-length 6; }}
]])
local ipv4_pton = require('lib.yang.util').ipv4_pton
local ipv6_protocol = require("lib.protocol.ipv6")
local function lookup(ipv4, port)
return map:lookup(ipv4_pton(ipv4), port)
end
local function assert_lookup(ipv4, port, ipv6, br)
local val = assert(lookup(ipv4, port))
assert(ffi.C.memcmp(ipv6_protocol:pton(ipv6), val.b4_ipv6, 16) == 0)
assert(ffi.C.memcmp(ipv6_protocol:pton(br), val.br_address, 16) == 0)
end
assert_lookup('178.79.150.233', 80, '127:2:3:4:5:6:7:128', '8:9:a:b:c:d:e:f')
assert(lookup('178.79.150.233', 79) == nil)
assert(lookup('178.79.150.233', 81) == nil)
assert_lookup('178.79.150.15', 80, '127:22:33:44:55:66:77:128', '8:9:a:b:c:d:e:f')
assert_lookup('178.79.150.15', 4095, '127:22:33:44:55:66:77:128', '8:9:a:b:c:d:e:f')
assert_lookup('178.79.150.15', 4096, '127:22:33:44:55:66:77:128', '8:9:a:b:c:d:e:f')
assert_lookup('178.79.150.15', 8191, '127:22:33:44:55:66:77:128', '8:9:a:b:c:d:e:f')
assert(lookup('178.79.150.15', 8192) == nil)
assert_lookup('178.79.150.2', 7850, '127:24:35:46:57:68:79:128', '1E:1:1:1:1:1:1:af')
assert(lookup('178.79.150.3', 4095) == nil)
assert_lookup('178.79.150.3', 4096, '127:14:25:36:47:58:69:128', '1E:2:2:2:2:2:2:af')
assert_lookup('178.79.150.3', 5119, '127:14:25:36:47:58:69:128', '1E:2:2:2:2:2:2:af')
assert(lookup('178.79.150.3', 5120) == nil)
assert(lookup('178.79.150.4', 7850) == nil)
do
local psid_map_iter = {
{ ipv4_pton('178.79.150.2'), { psid_length=16, shift=0 } },
{ ipv4_pton('178.79.150.3'), { psid_length=6, shift=10 } },
{ ipv4_pton('178.79.150.15'), { psid_length=4, shift=12 } },
{ ipv4_pton('178.79.150.233'), { psid_length=16, shift=0 } }
}
local i = 1
for lo, hi, value in map:iterate_psid_map() do
local ipv4, expected = unpack(psid_map_iter[i])
assert(lo == ipv4)
assert(hi == ipv4)
assert(value.psid_length == expected.psid_length)
assert(value.shift == expected.shift)
i = i + 1
end
assert(i == #psid_map_iter + 1)
end
print('ok')
end