diff --git a/src/apps/socket/unix.lua b/src/apps/socket/unix.lua index 234e6bede1..784008b894 100644 --- a/src/apps/socket/unix.lua +++ b/src/apps/socket/unix.lua @@ -83,7 +83,7 @@ function UnixSocket:new (arg) local sa = S.t.sockaddr_un(file) local ok, err = sock:connect(sa) if not ok then - if err.CONNREFUSED or err.AGAIN then return end + if err.CONNREFUSED or err.AGAIN or err.NOENT then return end assert(nil, err) end return sock diff --git a/src/pp.lua b/src/pp.lua new file mode 100644 index 0000000000..f07cf67126 --- /dev/null +++ b/src/pp.lua @@ -0,0 +1,228 @@ + +--fast, small recursive pretty printer with optional indentation and cycle detection. +--Written by Cosmin Apreutesei. Public Domain. + +--pretty-printing of non-structured types + +local type, tostring = type, tostring +local string_format, string_dump = string.format, string.dump +local math_huge, math_floor = math.huge, math.floor + +local escapes = { --don't add unpopular escapes here + ['\\'] = '\\\\', + ['\t'] = '\\t', + ['\n'] = '\\n', + ['\r'] = '\\r', +} + +local function escape_byte_long(c1, c2) + return string_format('\\%03d%s', c1:byte(), c2) +end +local function escape_byte_short(c) + return string_format('\\%d', c:byte()) +end +local function quote_string(s, quote) + s = s:gsub('[\\\t\n\r]', escapes) + s = s:gsub(quote, '\\%1') + s = s:gsub('([^\32-\126])([0-9])', escape_byte_long) + s = s:gsub('[^\32-\126]', escape_byte_short) + return s +end + +local function format_string(s, quote) + return string_format('%s%s%s', quote, quote_string(s, quote), quote) +end + +local function write_string(s, write, quote) + write(quote); write(quote_string(s, quote)); write(quote) +end + +local keywords = {} +for i,k in ipairs{ + 'and', 'break', 'do', 'else', 'elseif', 'end', + 'false', 'for', 'function', 'goto', 'if', 'in', + 'local', 'nil', 'not', 'or', 'repeat', 'return', + 'then', 'true', 'until', 'while', +} do + keywords[k] = true +end + +local function is_identifier(v) + return type(v) == 'string' and not keywords[v] + and v:find('^[a-zA-Z_][a-zA-Z_0-9]*$') ~= nil +end + +local hasinf = math_huge == math_huge - 1 +local function format_number(v) + if v ~= v then + return '0/0' --NaN + elseif hasinf and v == math_huge then + return '1/0' --writing 'math.huge' would not make it portable, just wrong + elseif hasinf and v == -math_huge then + return '-1/0' + elseif v == math_floor(v) and v >= -2^31 and v <= 2^31-1 then + return string_format('%d', v) --printing with %d is faster + else + return string_format('%0.17g', v) + end +end + +local function write_number(v, write) + write(format_number(v)) +end + +local function is_dumpable(f) + return type(f) == 'function' and debug.getinfo(f, 'Su').what ~= 'C' +end + +local function format_function(f) + return string_format('loadstring(%s)', format_string(string_dump(f, true))) +end + +local function write_function(f, write, quote) + write'loadstring('; write_string(string_dump(f, true), write, quote); write')' +end + +local ffi, int64, uint64 +local function is_int64(v) + if type(v) ~= 'cdata' then return false end + if not int64 then + ffi = require'ffi' + int64 = ffi.new'int64_t' + uint64 = ffi.new'uint64_t' + end + return ffi.istype(v, int64) or ffi.istype(v, uint64) +end + +local function format_int64(v) + return tostring(v) +end + +local function write_int64(v, write) + write(format_int64(v)) +end + +local function format(v, quote) + quote = quote or "'" + if v == nil or type(v) == 'boolean' then + return tostring(v) + elseif type(v) == 'number' then + return format_number(v) + elseif type(v) == 'string' then + return format_string(v, quote) + elseif is_dumpable(v) then + return format_function(v) + elseif is_int64(v) then + return format_int64(v) + else + error('unserializable', 0) + end +end + +local function is_serializable(v) + return type(v) == 'nil' or type(v) == 'boolean' or type(v) == 'string' + or type(v) == 'number' or is_dumpable(v) or is_int64(v) +end + +local function pf_write(v, write, quote) + quote = quote or "'" + if v == nil or type(v) == 'boolean' then + write(tostring(v)) + elseif type(v) == 'number' then + write_number(v, write) + elseif type(v) == 'string' then + write_string(v, write, quote) + elseif is_dumpable(v) then + write_function(v, write, quote) + elseif is_int64(v) then + write_int64(v, write) + else + error('unserializable', 0) + end +end + +local function pretty(v, write, indent, parents, quote, onerror, depth, wwrapper) + if is_serializable(v) then + pf_write(v, write, quote) + elseif getmetatable(v) and getmetatable(v).__pwrite then + wwrapper = wwrapper or function(v) + pretty(v, write, nil, parents, quote, onerror, -1, wwrapper) + end + getmetatable(v).__pwrite(v, write, wwrapper) + elseif type(v) == 'table' then + if parents then + if parents[v] then + write(onerror and onerror('cycle', v, depth) or 'nil --[[cycle]]') + return + end + parents[v] = true + end + + write'{' + local maxn = 0; while rawget(v, maxn+1) ~= nil do maxn = maxn+1 end + + local first = true + for k,v in pairs(v) do + if not (maxn > 0 and type(k) == 'number' and k == math.floor(k) and k >= 1 and k <= maxn) then + if first then first = false else write',' end + if indent then write'\n'; write(indent:rep(depth)) end + if is_identifier(k) then + write(k); write'=' + else + write'['; pretty(k, write, indent, parents, quote, onerror, depth + 1, wwrapper); write']=' + end + pretty(v, write, indent, parents, quote, onerror, depth + 1, wwrapper) + end + end + + for k,v in ipairs(v) do + if first then first = false else write',' end + if indent then write'\n'; write(indent:rep(depth)) end + pretty(v, write, indent, parents, quote, onerror, depth + 1, wwrapper) + end + + if indent then write'\n'; write(indent:rep(depth-1)) end + write'}' + + if parents then parents[v] = nil end + else + write(onerror and onerror('unserializable', v, depth) or + string.format('nil --[[unserializable %s]]', type(v))) + end +end + +local function to_sink(write, v, indent, parents, quote, onerror, depth) + return pretty(v, write, indent, parents, quote, onerror, depth or 1) +end + +local function to_string(v, indent, parents, quote, onerror, depth) + local buf = {} + pretty(v, function(s) buf[#buf+1] = s end, indent, parents, quote, onerror, depth or 1) + return table.concat(buf) +end + +local function to_file(file, v, indent, parents, quote, onerror, depth) + local f = assert(io.open(file, 'wb')) + f:write'return ' + pretty(v, function(s) f:write(s) end, indent, parents, quote, onerror, depth or 1) + f:close() +end + +local function pp(...) + local t = {} + for i=1,select('#',...) do + t[i] = to_string(select(i,...), ' ', {}) + end + print(unpack(t)) + return ... +end + +return setmetatable({ + write = to_sink, + format = to_string, + save = to_file, + print = pp, + pp = pp, --old API +}, {__call = function(self, ...) + return pp(...) +end}) diff --git a/src/program/lisper/dev-env/lisp.fib b/src/program/lisper/dev-env/lisp.fib index 867b3eab87..bf36a1f96d 100644 --- a/src/program/lisper/dev-env/lisp.fib +++ b/src/program/lisper/dev-env/lisp.fib @@ -1,4 +1,10 @@ -[4660] 00:00:00:00:aa:01 rloc fd80:1::2 1 50 -[4660] 00:00:00:00:aa:02 rloc fd80:2::2 1 50 -[4660] 00:00:00:00:aa:03 rloc fd80:3::2 1 50 -[4660] FF:FF:FF:FF:FF:FF rloc fd80:1::2 1 50, fd80:2::2 1 50, fd80:3::2 1 50 +{ "instance-id": 1, "eid-prefix": "000000-00bb01/48", "rlocs": [{ "rloc": "fd80:8::2", "priority": 1, "weight": 50 }] } + +{ "instance-id": 1, "eid-prefix": "000000-00bb02/48", "rlocs": [{ "rloc": "fd80:8::2", "priority": 1, "weight": 50 }] } + +{ "instance-id": 1, "eid-prefix": "000000-00bb03/48", "rlocs": [{ "rloc": "fd80:8::2", "priority": 1, "weight": 50 }] } + +{ "instance-id": 4660, "eid-prefix": "ffffff-ffffff/48", "rles": [ + { "rle": "fd80:8::2", "priority": 1, "weight": 50 } +]} + diff --git a/src/program/lisper/dev-env/lisp.lua b/src/program/lisper/dev-env/lisp.lua index 32910087be..e1aa8645c8 100644 --- a/src/program/lisper/dev-env/lisp.lua +++ b/src/program/lisper/dev-env/lisp.lua @@ -5,8 +5,8 @@ io.stderr:setvbuf'no' --LISP controller mock-up program for testing. local function assert(v, ...) - if v then return v, ... end - error(tostring((...)), 2) + if v then return v, ... end + error(tostring((...)), 2) end local ffi = require("ffi") @@ -18,33 +18,39 @@ local PUNT_SOCK = "/var/tmp/lispers.net-itr" S.signal('pipe', 'ign') --I 💔 Linux ::retry:: -sock = sock or assert(S.socket("unix", "stream, nonblock")) +sock = sock or assert(S.socket("unix", "dgram, nonblock")) local sa = S.t.sockaddr_un(CONTROL_SOCK) local ok, err = sock:connect(sa) if not ok then - if err.CONNREFUSED or err.AGAIN then - S.sleep(1) - print'retrying...' - goto retry - end - assert(nil, err) + if err.CONNREFUSED or err.AGAIN then + S.sleep(1) + print'retrying...' + goto retry + end + assert(nil, err) end print'connected' while true do - if assert(S.select({writefds = {sock}}, 0)).count == 1 then - - local f = assert(io.open'lisp.fib') - local data = assert(f:read'*a') - f:close() - - print'sending...' - if not S.write(sock, data, #data) then - print'write error' - sock:close() - sock = nil - goto retry - end - end - S.sleep(1) + if assert(S.select({writefds = {sock}}, 0)).count == 1 then + + local f = assert(io.open'lisp.fib') + local s = f:read'*a' + f:close() + local t = {} + for s in s:gmatch'(.-)[\r?\n][\r?\n]' do + table.insert(t, s) + end + + print'sending...' + for i,s in ipairs(t) do + if not S.write(sock, s, #s) then + print'write error' + sock:close() + sock = nil + goto retry + end + end + end + S.sleep(1) end diff --git a/src/program/lisper/dev-env/lisper.conf b/src/program/lisper/dev-env/lisper.conf index 91c175db2a..b8d2d4c1fd 100644 --- a/src/program/lisper/dev-env/lisper.conf +++ b/src/program/lisper/dev-env/lisper.conf @@ -1,23 +1,33 @@ { "control_sock" : "/var/tmp/lisp-ipc-map-cache", "punt_sock" : "/var/tmp/lispers.net-itr", + "arp_timeout" : 60, // seconds + "interfaces": [ - //{ "name": "eth0", "mac": "00:00:00:00:01:04", "pci": "01:00.0" }, - //{ "name": "eth1", "mac": "00:00:00:00:01:05", "pci": "02:00.0" }, - { "name": "eth0", "mac": "00:00:00:00:01:05", "ip": "fd80:1::1", "gateway": "fd80:1::1" } + { "name": "e0", "mac": "00:00:00:00:01:04" } ], - "vlans": [ - { "name": "vlan1", "interface": "eth0", "id": 1 }, - { "name": "vlan2", "interface": "eth0", "id": 2, "mac": "00:00:00:00:01:05", - "ip": "fd80:2::1", "gateway": "fd80:1::1" } + + //"vlans": [ + // { "name": "vlan1", "interface": "eth0", "id": 1 }, + // { "name": "vlan2", "interface": "eth0", "id": 2, "mac": "00:00:00:00:01:05" } + //], + + "exits": [ + { "name": "e0", "ip": "fd80:4::2", "interface": "e0", "next_hop": "fd80:4::1" } ], + "lispers": [ - { "ip": "fd:80:1::1", "interface": "eth0" }, - { "ip": "fd:80:2::1", "interface": "eth1_1" } + { "ip": "fd80:8::2", "exit": "e0" } ], + "local_networks": [ - { "iid": 1, "type": "L2TPv3", "ip": "fd80:1::2", "interface": "eth0", "cookie": "" }, - { "iid": 2, "type": "L2TPv3", "ip": "fd80:2::2", "interface": "vlan1", "cookie": "" }, - { "iid": 2, "interface": "eth1" } + { "iid": 1, "type": "L2TPv3", "ip": "fd80:1::2", "exit": "e0", + "session_id": 4660, "cookie": "0" }, + { "iid": 1, "type": "L2TPv3", "ip": "fd80:2::2", "exit": "e0", + "session_id": 4660, "cookie": "0" }, + { "iid": 1, "type": "L2TPv3", "ip": "fd80:3::2", "exit": "e0", + "session_id": 4660, "cookie": "0" } + + //{ "iid": 2, "interface": "eth1" } ] } diff --git a/src/program/lisper/lisper.lua b/src/program/lisper/lisper.lua index 89d706ad8b..02afe22be8 100644 --- a/src/program/lisper/lisper.lua +++ b/src/program/lisper/lisper.lua @@ -1,6 +1,7 @@ module(..., package.seeall) local ffi = require("ffi") +local app = require("core.app") local lib = require("core.lib") local packet = require("core.packet") local usage = require("program.lisper.README_inc") @@ -23,96 +24,233 @@ local function assert(v, ...) --assert overload because end local function parsehex(s) - return s and s ~= "" and (s:gsub("[0-9a-fA-F][0-9a-fA-F]", function(cc) - return char(tonumber(cc, 16)) + return (s:gsub("[0-9a-fA-F][0-9a-fA-F]", function(cc) + return string.char(tonumber(cc, 16)) end)) end +local function parsemac(s) + return ffi.string(ethernet:pton(s:gsub('[%:%s%-]', '') + :gsub('..', function(d) return d..':' end):gsub('%:$', '')), 6) +end + +local function parseip6(s) + return ffi.string(ipv6:pton(s), 16) +end + local function macstr(mac) local mac = ffi.string(mac, 6) return lib.hexdump(mac):gsub(" ", ":") end local function ip6str(ip6) - return ipv6:ntop(ip6) + assert(ip6, "ip missing") + return + --type(ip6) == "string" and lib.hexdump(ip6):gsub(" ", ":") or + ipv6:ntop(ip6) +end + +local function padhex(s, n) --pad a hex string to a fixed number of bytes + return ("0"):rep(n*2 - #s)..s +end + +local function parsesessid(sid) --4-byte L2TPV3 session id given as a number + return lib.htonl(tonumber(sid)) +end + +local function parsecookie(cookie) --8-byte L2TPv3 cookie given in hex + return parsehex(padhex(cookie, 8)) end local _ = string.format ---config --------------------------------------------------------------------- +--get the value of a table field, and if the field is not present in the +--table, create it as an empty table, and return it. +local function attr(t, k) + local v = t[k] + if v == nil then + v = {} + t[k] = v + end + return v +end -local DEBUG = os.getenv"LISPER_DEBUG" --if set, print packets to stdout -local MODE = os.getenv"LISPER_MODE" --if set to "record" then record packets to pcap files +local broadcast_mac = parsemac("ffffff-ffffff") -local conf +--config --------------------------------------------------------------------- +local DEBUG = lib.getenv"LISPER_DEBUG" --if set, print packets to stdout +local MODE = lib.getenv"LISPER_MODE" --if set to "record" then record packets to pcap files + +--phy_t: {name=s, mac=mac, pci=s, vlans={vlan1_t, ...}, exits={exit1_t,...}} +--vlan_t: {name=s, mac=mac, id=n, interface=phy_t, exits={exit1_t,...}} +--if_t: phy_t|vlan_t +--exit_t: {ip=ipv6, interface=if_t, next_hop=ip6} +--loc_t: eth_loc_t|l2tp_loc_t|lisper_loc_t +--eth_loc_t: {type="ethernet", interface=if_t} +--l2tp_loc_t: {type="l2tpv3", ip=ip6, session_id=n, cookie=s, exit=exit_t} +--lisper_loc_t: {type="lisper", ip=ip6, p=n, w=n, key=s, exit=exit_t} +local conf --{control_sock=s, punt_sock=s} +local phys --{ifname -> phy_t} +local vlans --{id -> vlan_t} +local ifs --{ifname -> if_t} +local exits --{exitname -> exit_t} +local eths --{ifname -> {iid=n, loc=eth_loc_t}} +local l2tps --{sesson_id -> {cookie -> {iid=n, loc=l2tp_loc_t}}} +local locs --{iid -> {dest_mac -> {loc1_t, ...}}} +local lispers --{ipv6 -> exit_t} + +--see dev-env/lisper.conf for the format of s. local function update_config(s) - local t = assert(json.decode(s)) --see dev-env/lisper.conf for the format - t.interfaces = t.interfaces or {} - --map interfaces by name - for i,iface in ipairs(t.interfaces) do - t.interfaces[iface.name] = iface + local t = assert(json.decode(s)) + + conf = {} + phys = {} + vlans = {} + ifs = {} + exits = {} + eths = {} + l2tps = {} + locs = {} + lispers = {} + + --globals + conf.control_sock = t.control_sock + conf.punt_sock = t.punt_sock + + --map physical interfaces + if t.interfaces then + for i,iface in ipairs(t.interfaces) do + assert(not ifs[iface.name], "duplicate interface name: "..iface.name) + local phy_t = { + name = iface.name, + pci = iface.pci, + mac = parsemac(iface.mac), + vlans = {}, + exits = {}, + } + phys[iface.name] = phy_t + ifs[iface.name] = phy_t + end end - --associate vlans to interfaces + + --map 802.1Q interfaces if t.vlans then for i,vlan in ipairs(t.vlans) do - local iface = assert(t.interfaces[vlan.interface], + local iface = assert(phys[vlan.interface], "invalid interface "..vlan.interface.." for vlan "..vlan.name) - iface.vlans = iface.vlans or {} - table.insert(iface.vlans, vlan) - vlan.mac = vlan.mac or iface.mac - vlan.ip = vlan.ip or iface.ip - vlan.gateway = vlan.gateway or iface.gateway + assert(not ifs[iface.name], "duplicate interface name: "..iface.name) + local vlan_t = { + name = vlan.name, + mac = parsemac(vlan.mac or iface.mac), + id = vlan.id, + interface = iface, + exits = {}, + } + table.insert(iface.vlans, vlan_t) + vlans[vlan.id] = vlan_t + ifs[vlan.name] = vlan_t end end - --associate lispers with interfaces - if t.lispers then - for i,t in ipairs(t.lispers) do + + --map ipv6 exit points + if t.exits then + for i,t in ipairs(t.exits) do + local ip = parseip6(t.ip) + local iface = assert(ifs[t.interface], "invalid interface "..t.interface) + local exit_t = { + name = t.name, + ip = ip, + interface = iface, + next_hop = t.next_hop and parseip6(t.next_hop), + } + exits[t.name] = exit_t + table.insert(iface.exits, exit_t) end end + + --map local L2 networks and l2tp-tunneled networks if t.local_networks then - for i,t in ipairs(t.local_networks) do + for i,net in ipairs(t.local_networks) do + local context = "local network #"..i + if net.type and net.type:lower() == "l2tpv3" then + assert(net.session_id, "session_id missing on "..context) + local sid = assert(parsesessid(net.session_id), "invalid session id") + local cookie = parsecookie(net.cookie) + local ip = parseip6(net.ip) + local exit = exits[net.exit] + assert(exit, "invalid exit "..net.exit) + local loc = { + type = "l2tpv3", + ip = ip, + session_id = sid, + cookie = cookie, + exit = exit, + } + attr(l2tps, sid)[cookie] = {iid = net.iid, loc = loc} + local blocs = attr(attr(locs, net.iid), broadcast_mac) + table.insert(blocs, loc) + else + assert(ifs[net.interface], "invalid interface "..net.interface) + local loc = { + type = "ethernet", + interface = net.interface, + } + eths[net.interface] = {iid = net.iid, loc = loc} + local blocs = attr(attr(locs, net.iid), broadcast_mac) + table.insert(blocs, loc) + end end end - conf = t -end - ---fib ------------------------------------------------------------------------ -local fib = {} --{iid = {dest_mac = {rloc1, ...}}} + --map lispers + if t.lispers then + for i,t in ipairs(t.lispers) do + local ip = parseip6(t.ip) + local exit = ifs[t.exit] + assert(exit, "invalid exit "..t.exit) + lispers[ip] = exit + end + end +end +--see "Map-Cache Population IPC Interface" section in dt-l2-overlay.txt +--for the format of s. local function update_fib(s) local t = assert(json.decode(s)) - for k,v in pairs(t) do - local iid = assert(k["instance-id"]) - local dt = {} - fib[iid] = dt - local eid_prefix = assert(k["eid-prefix"]) - local mac = eid_prefix:gsub("/d+$", "") --MAC/48 - local mac = ethernet:pton(mac) - local rt = {} - dt[mac] = rt - local rlocs = k.rlocs or k.rles - if #rlocs > 0 then - for i,t in ipairs(rlocs) do - local dt = {} - table.insert(rt, dt) - dt.ip = assert(t.rloc or t.rle) - dt.priority = tonumber(t.priority) - dt.weight = tonumber(t.weight) - dt.key = parsehex(t.key) - end + local iid = assert(t["instance-id"]) + local dt = attr(locs, iid) + local eid_prefix = assert(t["eid-prefix"]) + local mac = eid_prefix:gsub("/%d+$", "") --MAC/48 + local mac = parsemac(mac) + local rt = {} + dt[mac] = rt + local rlocs = t.rlocs or t.rles + if rlocs and #rlocs > 0 then + for i,t in ipairs(rlocs) do + local rloc = assert(t.rloc or t.rle) + local ip = parseip6(rloc) + local exit = lispers[ip] + assert(exit, "invalid rloc "..rloc) + local key = t.key and t.key ~= "" and parsehex(t.key) + local p = tonumber(t.priority) + local w = tonumber(t.weight) + local loc = { + type = "lisper", + ip = ip, + p = p, + w = w, + key = key, + exit = exit, + } + table.insert(rt, loc) end end end -local function lookup_fib(iid, mac) - return fib[iid] and fib[id][mac] -end - ---punting -------------------------------------------------------------------- +--punting queue -------------------------------------------------------------- -local punt = {} --{{mac=,name=,},...} +local punt = {} --{{mac=,name=}, ...} local function punt_mac(mac, ifname) table.insert(punt, {mac = mac, ifname = ifname}) @@ -124,7 +262,7 @@ local function get_punt_message() return _('{"eid-prefix" : "%s", "interface" : "%s"}', macstr(t.mac), t.ifname) end ---L2TPv3/IPv6 frame format --------------------------------------------------- +--data plane ----------------------------------------------------------------- local l2tp_ct = ffi.typeof[[ struct { @@ -154,23 +292,59 @@ local l2tp_ct = ffi.typeof[[ local l2tp_ct_size = ffi.sizeof(l2tp_ct) local l2tp_ctp = ffi.typeof("$*", l2tp_ct) +local function eth_parse(p) + if p.length < 12 then return end + local p = ffi.cast(l2tp_ctp, p.data) + local smac = ffi.string(p.smac, 6) + local dmac = ffi.string(p.dmac, 6) + return smac, dmac, 0 +end + local function l2tp_parse(p) if p.length < l2tp_ct_size then return end local p = ffi.cast(l2tp_ctp, p.data) if p.ethertype ~= 0xdd86 then return end --not ipv6 if p.next_header ~= 115 then return end --not l2tpv3 - local sessid = lib.ntohl(p.session_id) + local src_ip = ffi.string(p.src_ip, 16) + local sid = p.session_id + local cookie = ffi.string(p.cookie, 8) + local l2tp_smac = ffi.string(p.l2tp_smac, 6) local l2tp_dmac = ffi.string(p.l2tp_dmac, 6) - return sessid, l2tp_dmac + return src_ip, sid, cookie, l2tp_smac, l2tp_dmac, 66 +end + +local function eth_format(srcp, payload_offset, smac, dmac) + local dstp = packet.clone(srcp) + local len = srcp.length - payload_offset + ffi.copy(dstp.data, srcp.data + payload_offset, len) + dstp.length = len + local p = ffi.cast(l2tp_ctp, dstp.data) + ffi.copy(p.smac, smac, 6) + ffi.copy(p.dmac, dmac, 6) + return dstp, p +end + +local function ip6_format(srcp, payload_offset, smac, dmac, src_ip, dst_ip) + local dstp, p = eth_format(srcp, payload_offset, smac, dmac) + ffi.copy(p.src_ip, src_ip, 16) + ffi.copy(p.dst_ip, dst_ip, 16) + return dstp, p end +local function l2tp_format(srcp, payload_offset, smac, dmac, src_ip, dst_ip, sid, cookie) + local dstp, p = ip6_format(srcp, payload_offset, smac, dmac, src_ip, dst_ip) + p.session_id = sid + ffi.copy(p.cookie, cookie, 8) + return dstp +end + +--[[ local function l2tp_update(p, dest_ip, local_ip) local p = ffi.cast(l2tp_ctp, p.data) ffi.copy(p.src_ip, local_ip, 16) ffi.copy(p.dst_ip, dest_ip, 16) end - ---frame dumper --------------------------------------------------------------- +]] local function l2tp_dump(p, text) local p = ffi.cast(l2tp_ctp, p.data) @@ -187,24 +361,73 @@ local function l2tp_dump(p, text) " ["..macstr(p.l2tp_smac).." -> "..macstr(p.l2tp_dmac).."]") end -local L2TP_Dump = {} +local function route_packet(p, rxname, txports) -function L2TP_Dump:new(name) - return setmetatable({text = name}, {__index = self}) -end + --step #1: find the iid and source location of the packet + local iid, sloc, smac, dmac, payload_offset + local t = eths[rxname] + if t then --packet came from a local ethernet + iid, sloc = t.iid, t.loc + smac, dmac, payload_offset = eth_parse(p) + if not smac then return end --invalid packet + else --packet came from a l2tp tunnel or a lisper + local src_ip, session_id, cookie + src_ip, session_id, cookie, smac, dmac, payload_offset = l2tp_parse(p) + if not src_ip then return end --invalid packet + if lispers[src_ip] then --packet came from a lisper + assert(false) + iid = session_id --iid comes in the session_id field, cookie is ignored + else --packet came from a l2tp tunnel + local t = l2tps[session_id] and l2tps[session_id][cookie] + if not t then return end --invalid packet: bad l2tp config + iid, sloc = t.iid, t.loc + end + end + local locs = locs[iid] --contextualize locations + + --step #2: remember the location of the smac and punt it if it's new + if sloc then --didn't come from a lisper + local slocs = locs[smac] + if not slocs or slocs[1] ~= sloc then + locs[smac] = {sloc} + punt_mac(smac, rxname) + end + end -function L2TP_Dump:push() - local rx = self.input.rx - local tx = self.output.tx - if rx == nil or tx == nil then return end - while not link.empty(rx) do - local p = link.receive(rx) - l2tp_dump(p, self.text) - link.transmit(tx, p) + --step #3: find the location(s) of the dest. mac and send the payload + --to it/them. We can have multiple locations only if they're all of + --type "lisper" (i.e. multihoming), or if the dmac is the broadcast mac, + --or if the dmac is unknown (in which case we use the broadcast mac). + local dlocs = locs[dmac] or locs[broadcast_mac] + for i=1,#dlocs do + local loc = dlocs[i] + local dp, tx + if loc.type == "ethernet" then + dp = eth_format(p, payload_offset, smac, dmac) + tx = txports[loc.interface.name] + elseif loc.type == "l2tpv3" then + dp = l2tp_format(p, payload_offset, smac, dmac, + loc.exit.ip, + loc.ip, + loc.session_id, + loc.cookie) + tx = txports[loc.exit.interface.name] + --print(ip6str(loc.exit.ip), ip6str(loc.ip), lib.ntohl(loc.session_id), + -- lib.hexdump(loc.cookie), loc.exit.interface.name) + elseif loc.type == "lisper" then + dp = l2tp_format(p, payload_offset, smac, dmac, + loc.exit.ip, + loc.ip, + iid, + "\0\0\0\0\0\0\0\0") + tx = txports[loc.exit.interface.name] + end + if link.full(tx) then return end + link.transmit(tx, dp) end end ---control plane -------------------------------------------------------------- +--data processing apps ------------------------------------------------------- local Ctl = {} @@ -234,38 +457,11 @@ function Punt:pull() while not link.full(tx) do local s = get_punt_message() if not s then break end - link.transmit(rx, s) - end -end - ---data plane ----------------------------------------------------------------- - -local function rloc_interface(rloc) - --TODO -end - -local function route_packet(p, rxname, txports) - local iid, dmac = l2tp_parse(p) - if not idd then return true end --invalid packet - local rlocs = lookup_fib(iid, dmac) - if rlocs then - --check if all rloc interfaces have room for the packet - for i=1,#rlocs do - local txname = rloc_interface(rlocs[i]) - local tx = txports[txname] - if link.full(tx) then return end --dest. buffer full - end - for i=1,#rlocs do - local txname, local_ip = rloc_interface(rlocs[i]) - local tx = txports[txname] - local p = packet.clone(p) - l2tp_update(p, rlocs[i].ip, local_ip) - link.transmit(tx, p) - end - else - punt_mac(dmac, rxname) + local p = packet.allocate() + p.length = #s + ffi.copy(p.data, s) + link.transmit(tx, p) end - return true end local Lisper = {} @@ -277,17 +473,30 @@ end function Lisper:push() for rxname, rx in pairs(self.input) do while not link.empty(rx) do - local p = link.peek(rx) - if route_packet(p, rxname, self.output) then - packet.free(link.receive(rx)) - else - --dest. ringbuffer full, we'll try again on the next push - break - end + local p = link.receive(rx) + route_packet(p, rxname, self.output) + packet.free(p) end end end +local Dumper = {} + +function Dumper:new(text) + return setmetatable({text = text}, {__index = self}) +end + +function Dumper:push() + local rx = self.input.rx + local tx = self.output.tx + if rx == nil or tx == nil then return end + while not link.empty(rx) do + local p = link.receive(rx) + l2tp_dump(p, self.text) + link.transmit(tx, p) + end +end + --program args --------------------------------------------------------------- local long_opts = { @@ -342,20 +551,18 @@ function run(args) config.app(c, "lisper", Lisper) - for i,iface in ipairs(conf.interfaces) do - - local ifname = iface.name + for ifname,iface in pairs(phys) do if iface.pci then config.app(c, "if_"..ifname, intel.Intel82599, { pciaddr = iface.pci, - macaddr = iface.mac, + macaddr = macstr(iface.mac), }) else config.app(c, "if_"..ifname, raw.RawSocket, ifname) end - if iface.vlans then + if #iface.vlans > 0 then local ports = {} for i,vlan in ipairs(iface.vlans) do @@ -370,12 +577,15 @@ function run(args) local vname = vlan.name - if vlan.gateway then -- if -> trunk -> nd -> lisper + if #vlan.exits > 0 then + + assert(#vlan.exits == 1, "multiple exits per interface not supported") + local exit = vlan.exits[1] config.app(c, "nd_"..vname, nd.nd_light, { - local_mac = vlan.mac, - local_ip = vlan.ip, - next_hop = vlan.gateway, + local_mac = macstr(vlan.mac), + local_ip = ip6str(exit.ip), + next_hop = ip6str(exit.next_hop), }) config.link(c, _("nd_%s.south -> trunk_%s.%s", vname, ifname, vname)) config.link(c, _("trunk_%s.%s -> nd_%s.south", ifname, vname, vname)) @@ -383,7 +593,7 @@ function run(args) config.link(c, _("lisper.%s -> nd_%s.north", vname, vname)) config.link(c, _("nd_%s.north -> lisper.%s", vname, vname)) - else -- if -> trunk -> lisper + else -- phy -> trunk -> lisper config.link(c, _("lisper.%s -> trunk_%s.%s", vname, ifname, vname)) config.link(c, _("trunk_%s.%s -> lisper.%s", ifname, vname, vname)) @@ -392,20 +602,24 @@ function run(args) end - elseif iface.gateway then -- if -> nd -> lisper + elseif #iface.exits > 0 then -- phy -> nd -> lisper + + assert(#iface.exits == 1, "multiple exits per interface not supported") + local exit = iface.exits[1] config.app(c, "nd_"..ifname, nd.nd_light, { - local_mac = iface.mac, - local_ip = iface.ip, - next_hop = iface.gateway, + local_mac = macstr(iface.mac), + local_ip = ip6str(exit.ip), + next_hop = ip6str(exit.next_hop), }) + config.link(c, _("nd_%s.south -> if_%s.rx", ifname, ifname)) config.link(c, _("if_%s.tx -> nd_%s.south", ifname, ifname)) config.link(c, _("lisper.%s -> nd_%s.north", ifname, ifname)) config.link(c, _("nd_%s.north -> lisper.%s", ifname, ifname)) - else -- if -> lisper + else -- phy -> lisper config.link(c, _("lisper.%s -> if_%s.rx", ifname, ifname)) config.link(c, _("if_%s.tx -> lisper.%s", ifname, ifname)) @@ -416,7 +630,26 @@ function run(args) engine.configure(c) - print(config.graphviz(c)) + print("Links:") + for linkspec in pairs(c.links) do + print(" "..linkspec) + end + print("Params:") + for appname, app in pairs(app.app_table) do + local s = "" + local arg = c.apps[appname].arg + if arg == "nil" then arg = nil end --TODO: fix core.config + if type(arg) == "string" then + s = arg + elseif type(arg) == "table" then + local t = {} + for k,v in pairs(arg) do + table.insert(t, _("\n %-10s: %s", k, tostring(v))) + end + s = table.concat(t) + end + print(_(" %-12s: %s", appname, s)) + end engine.main({report = {showlinks=true}})