From 8f273c66c9e07c5542337e9a93e1a714b3b3da3c Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Fri, 18 May 2018 23:39:57 +0200 Subject: [PATCH 01/21] lib.protocol.ipv4: add ttl_decrement method (borrowed from lwAFTR) --- src/lib/protocol/ipv4.lua | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/lib/protocol/ipv4.lua b/src/lib/protocol/ipv4.lua index c89f5940fb..85265af0a7 100644 --- a/src/lib/protocol/ipv4.lua +++ b/src/lib/protocol/ipv4.lua @@ -8,6 +8,8 @@ local header = require("lib.protocol.header") local ipsum = require("lib.checksum").ipsum local htons, ntohs, htonl, ntohl = lib.htons, lib.ntohs, lib.htonl, lib.ntohl +local band, lshift, rshift, bnot = + bit.band, bit.lshift, bit.rshift, bit.bnot -- TODO: generalize local AF_INET = 2 @@ -147,6 +149,25 @@ function ipv4:ttl (ttl) end end +-- Adopted from lwAFTR with love +function ipv4:ttl_decrement () + local old_ttl = self:ttl() + local new_ttl = band(old_ttl - 1, 0xff) + self:ttl(new_ttl) + local chksum = bnot(ntohs(self:header().checksum)) + -- Now fix up the checksum. The ttl field is the first byte in the + -- 16-bit big-endian word, so the difference to the overall sum is + -- multiplied by 0xff. + chksum = chksum + lshift(new_ttl - old_ttl, 8) + -- Now do the one's complement 16-bit addition of the 16-bit words of + -- the checksum, which necessarily is a 32-bit value. Two carry + -- iterations will suffice. + chksum = band(chksum, 0xffff) + rshift(chksum, 16) + chksum = band(chksum, 0xffff) + rshift(chksum, 16) + self:header().checksum = htons(bnot(chksum)) + return new_ttl +end + function ipv4:protocol (protocol) if protocol ~= nil then self:header().protocol = protocol From d183536fe1b8ecae7ff3541fc35bde56ab496ef0 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Sun, 20 May 2018 23:45:16 +0200 Subject: [PATCH 02/21] lib.protocol.ipv4: accept total_length initialization parameter --- src/lib/protocol/ipv4.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/protocol/ipv4.lua b/src/lib/protocol/ipv4.lua index 85265af0a7..e008e9f5db 100644 --- a/src/lib/protocol/ipv4.lua +++ b/src/lib/protocol/ipv4.lua @@ -64,7 +64,7 @@ function ipv4:new (config) o:ihl(o:sizeof() / 4) o:dscp(config.dscp or 0) o:ecn(config.ecn or 0) - o:total_length(o:sizeof()) -- default to header only + o:total_length(config.total_length or o:sizeof()) -- default to header only o:id(config.id or 0) o:flags(config.flags or 0) o:frag_off(config.frag_off or 0) From 7a370e82bb9d3716a74ed6792c88d29023421c7a Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Sun, 20 May 2018 23:46:05 +0200 Subject: [PATCH 03/21] vita gentest: add Ethernet padding for test packets < 60 bytes --- src/program/vita/gentest.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/program/vita/gentest.lua b/src/program/vita/gentest.lua index e10e6ac45f..8c6c47a803 100644 --- a/src/program/vita/gentest.lua +++ b/src/program/vita/gentest.lua @@ -38,10 +38,13 @@ function gen_packet (conf, route, size) local d = datagram:new(packet.resize(packet.allocate(), payload_size)) d:push(ipv4:new{ src = ipv4:pton(conf.private_nexthop_ip4), dst = ipv4:pton(conf.route_prefix.."."..route..".1"), + total_length = ipv4:sizeof() + payload_size, ttl = 64 }) d:push(ethernet:new{ dst = ethernet:pton(conf.private_mac), type = 0x0800 }) - return d:packet() + local p = d:packet() + -- Pad to minimum Ethernet frame size (excluding four octet CRC) + return packet.resize(p, math.max(60, p.length)) end function gen_packets (conf) From bdc926a836fa8d9676f9fe9b3f2e7a1da75518a6 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Fri, 18 May 2018 23:41:56 +0200 Subject: [PATCH 04/21] vita: factor dispatch out of routers, fix ttl handing This factors dedicated protocol dispatch (using pf.match <3) apps out from PublicRouter and PrivateRouter. This much nicer design allows for the proper handling of IP TTLs, and hopefully will also ease someday supporting IPv6. Using pf.match in this capacity requires a bit of a hack (dispatch.lua:17) to get a pointer to the packet structure from the payload pointer passed by pf.match to the handlers using a negative known offset. *shrug* --- src/program/vita/dispatch.lua | 137 ++++++++++++++++++++++++++++++ src/program/vita/nexthop.lua | 11 +-- src/program/vita/route.lua | 151 +++++----------------------------- src/program/vita/ttl.lua | 40 +++++++++ src/program/vita/vita.lua | 22 +++-- 5 files changed, 216 insertions(+), 145 deletions(-) create mode 100644 src/program/vita/dispatch.lua create mode 100644 src/program/vita/ttl.lua diff --git a/src/program/vita/dispatch.lua b/src/program/vita/dispatch.lua new file mode 100644 index 0000000000..db9f58d62e --- /dev/null +++ b/src/program/vita/dispatch.lua @@ -0,0 +1,137 @@ +-- Use of this source code is governed by the GNU AGPL license; see COPYING. + +module(...,package.seeall) + +local counter = require("core.counter") +local ethernet = require("lib.protocol.ethernet") +local ipv4 = require("lib.protocol.ipv4") +local arp = require("lib.protocol.arp") +local esp_header = require("lib.protocol.esp") +local esp = require("lib.ipsec.esp") +local exchange = require("program.vita.exchange") +local pf_match = require("pf.match") +local ffi = require("ffi") + +-- Ugly hack: given a packet.data pointer we just happen to know the struct +-- packet pointer is two bytes behind. +local function payload_packet (ptr) + return ffi.cast("struct packet *", ptr - 2) +end + + +PrivateDispatch = { + name = "PrivateDispatch", + shm = { + rxerrors = {counter}, + ethertype_errors = {counter}, + checksum_errors = {counter} + } +} + +function PrivateDispatch:new () + local o = { + ip4 = ipv4:new({}), + dispatch = pf_match.compile([[match { + ip => forward4 + arp => arp + otherwise => reject_ethertype + }]]) + } + return setmetatable(o, {__index=PrivateDispatch}) +end + +function PrivateDispatch:forward4 (data, length) + local p = packet.shiftleft(payload_packet(data), ethernet:sizeof()) + assert(self.ip4:new_from_mem(p.data, p.length)) + if self.ip4:checksum_ok() then + -- Strip datagram of any Ethernet frame padding before encapsulation. + local d = packet.resize(p, math.min(self.ip4:total_length(), p.length)) + link.transmit(self.output.forward4, d) + else + packet.free(p) + counter.add(self.shm.rxerrors) + counter.add(self.shm.checksum_errors) + end +end + +function PrivateDispatch:arp (data) + local p = packet.shiftleft(payload_packet(data), ethernet:sizeof()) + link.transmit(self.output.arp, p) +end + +function PrivateDispatch:reject_ethertype (data) + packet.free(payload_packet(data)) + counter.add(self.shm.rxerrors) + counter.add(self.shm.ethertype_errors) +end + +function PrivateDispatch:push () + local input = self.input.input + while not link.empty(input) do + local p = link.receive(input) + self:dispatch(p.data, p.length) + end +end + + +PublicDispatch = { + name = "PublicDispatch", + shm = { + rxerrors = {counter}, + ethertype_errors = {counter}, + protocol_errors = {counter} + } +} + +function PublicDispatch:new () + local o = { + ip4 = ipv4:new({}), + dispatch = pf_match.compile([[match { + ip proto esp => forward4 + ip proto 99 => protocol + ip => reject_protocol + arp => arp + otherwise => reject_ethertype + }]]) + } + return setmetatable(o, {__index=PublicDispatch}) +end + +function PublicDispatch:forward4 (data) + local p = packet.shiftleft(payload_packet(data), + ethernet:sizeof() + ipv4:sizeof()) + -- NB: Ignore potential differences between IP datagram and Ethernet size + -- since the minimum ESP packet exceeds 60 bytes in payload. + link.transmit(self.output.forward4, p) +end + +function PublicDispatch:protocol (data) + local p = packet.shiftleft(payload_packet(data), + ethernet:sizeof() + ipv4:sizeof()) + link.transmit(self.output.protocol, p) +end + +function PublicDispatch:arp (data) + local p = packet.shiftleft(payload_packet(data), ethernet:sizeof()) + link.transmit(self.output.arp, p) +end + +function PublicDispatch:reject_protocol (data) + packet.free(payload_packet(data)) + counter.add(self.shm.rxerrors) + counter.add(self.shm.protocol_errors) +end + +function PublicDispatch:reject_ethertype (data) + packet.free(payload_packet(data)) + counter.add(self.shm.rxerrors) + counter.add(self.shm.ethertype_errors) +end + +function PublicDispatch:push () + local input = self.input.input + while not link.empty(input) do + local p = link.receive(input) + self:dispatch(p.data, p.length) + end +end diff --git a/src/program/vita/nexthop.lua b/src/program/vita/nexthop.lua index e31d6f2292..f2343fbffc 100644 --- a/src/program/vita/nexthop.lua +++ b/src/program/vita/nexthop.lua @@ -22,7 +22,6 @@ NextHop4 = { nexthop_ip4 = {required=true} }, shm = { - protocol_errors = {counter}, arp_requests = {counter}, arp_replies = {counter}, arp_errors = {counter}, @@ -68,7 +67,6 @@ function NextHop4:new (conf) -- Headers to parse o.arp = arp:new{} o.arp_ipv4 = arp_ipv4:new{} - o.ip4 = ipv4:new{} -- Initially, we don’t know the hardware address of our next hop o.connected = false @@ -100,14 +98,7 @@ function NextHop4:push () for _, input in ipairs(self.forward) do while not link.empty(input) do local p = link.receive(input) - local ip4 = self.ip4:new_from_mem(p.data, p.length) - if ip4 and ip4:ttl() > 0 then - ip4:ttl(ip4:ttl() - 1) - ip4:checksum() - link.transmit(output, self:encapsulate(p, 0x0800)) - else - counter.add(self.shm.protocol_errors) - end + link.transmit(output, self:encapsulate(p, 0x0800)) end end diff --git a/src/program/vita/route.lua b/src/program/vita/route.lua index 9a7b9f517c..61e489889e 100644 --- a/src/program/vita/route.lua +++ b/src/program/vita/route.lua @@ -12,7 +12,6 @@ local exchange = require("program.vita.exchange") local lpm = require("lib.lpm.lpm4_248").LPM4_248 local ctable = require("lib.ctable") local ffi = require("ffi") -local packet_buffer -- route := { net_cidr4=(CIDR4), gw_ip4=(IPv4), preshared_key=(KEY) } @@ -24,8 +23,6 @@ PrivateRouter = { }, shm = { rxerrors = {counter}, - ethertype_errors = {counter}, - protocol_errors = {counter}, route_errors = {counter} } } @@ -33,10 +30,7 @@ PrivateRouter = { function PrivateRouter:new (conf) local o = { routes = {}, - eth = ethernet:new({}), - ip4 = ipv4:new({}), - fwd4_packets = packet_buffer(), - arp_packets = packet_buffer() + ip4 = ipv4:new({}) } for id, route in pairs(conf.routes) do o.routes[#o.routes+1] = { @@ -67,65 +61,25 @@ function PrivateRouter:link () self.routing_table4:build() end +function PrivateRouter:find_route4 (dst) + return self.routes[self.routing_table4:search_bytes(dst)] +end + function PrivateRouter:push () local input = self.input.input - local fwd4_packets, fwd4_cursor = self.fwd4_packets, 0 - local arp_packets, arp_cursor = self.arp_packets, 0 while not link.empty(input) do local p = link.receive(input) - local eth = self.eth:new_from_mem(p.data, p.length) - if eth and eth:type() == 0x0800 then -- IPv4 - fwd4_packets[fwd4_cursor] = packet.shiftleft(p, ethernet:sizeof()) - fwd4_cursor = fwd4_cursor + 1 - elseif eth and eth:type() == arp.ETHERTYPE then - arp_packets[arp_cursor] = packet.shiftleft(p, ethernet:sizeof()) - arp_cursor = arp_cursor + 1 + assert(self.ip4:new_from_mem(p.data, p.length)) + local route = self:find_route4(self.ip4:dst()) + if route then + link.transmit(route.link, p) else packet.free(p) counter.add(self.shm.rxerrors) - counter.add(self.shm.ethertype_errors) + counter.add(self.shm.route_errors) end end - - local new_cursor = 0 - for i = 0, fwd4_cursor - 1 do - local p = fwd4_packets[i] - local ip4 = self.ip4:new_from_mem(p.data, ipv4:sizeof()) - if ip4 and ip4:checksum_ok() then - fwd4_packets[new_cursor] = p - new_cursor = new_cursor + 1 - else - packet.free(p) - counter.add(self.shm.rxerrors) - counter.add(self.shm.protocol_errors) - end - end - fwd4_cursor = new_cursor - - for i = 0, fwd4_cursor - 1 do - self:forward4(fwd4_packets[i]) - end - - for i = 0, arp_cursor - 1 do - link.transmit(self.output.arp, arp_packets[i]) - end -end - -function PrivateRouter:find_route4 (dst) - return self.routes[self.routing_table4:search_bytes(dst)] -end - -function PrivateRouter:forward4 (p) - self.ip4:new_from_mem(p.data, p.length) - local route = self:find_route4(self.ip4:dst()) - if route then - link.transmit(route.link, p) - else - packet.free(p) - counter.add(self.shm.rxerrors) - counter.add(self.shm.route_errors) - end end @@ -137,22 +91,14 @@ PublicRouter = { }, shm = { rxerrors = {counter}, - ethertype_errors = {counter}, - protocol_errors = {counter}, - route_errors = {counter}, + route_errors = {counter} } } function PublicRouter:new (conf) local o = { routes = {}, - eth = ethernet:new({}), - ip4 = ipv4:new({}), - esp = esp_header:new({}), - ip4_packets = packet_buffer(), - fwd4_packets = packet_buffer(), - protocol_packets = packet_buffer(), - arp_packets = packet_buffer() + esp = esp_header:new({}) } for id, route in pairs(conf.routes) do o.routes[#o.routes+1] = { @@ -179,78 +125,25 @@ function PublicRouter:link () end end +function PublicRouter:find_route4 (spi) + local entry = self.routing_table4:lookup_ptr(spi) + return entry and self.routes[entry.value] +end + function PublicRouter:push () local input = self.input.input - local ip4_packets, ip4_cursor = self.ip4_packets, 0 - local arp_packets, arp_cursor = self.arp_packets, 0 while not link.empty(input) do local p = link.receive(input) - local eth = self.eth:new_from_mem(p.data, p.length) - if eth and eth:type() == 0x0800 then -- IPv4 - ip4_packets[ip4_cursor] = packet.shiftleft(p, ethernet:sizeof()) - ip4_cursor = ip4_cursor + 1 - elseif eth and eth:type() == arp.ETHERTYPE then - arp_packets[arp_cursor] = packet.shiftleft(p, ethernet:sizeof()) - arp_cursor = arp_cursor + 1 - else - packet.free(p) - counter.add(self.shm.rxerrors) - counter.add(self.shm.ethertype_errors) - end - end - - local fwd4_packets, fwd4_cursor = self.fwd4_packets, 0 - local protocol_packets, protocol_cursor = self.protocol_packets, 0 - for i = 0, ip4_cursor - 1 do - local p = ip4_packets[i] - local ip4 = self.ip4:new_from_mem(p.data, p.length) - and self.ip4:checksum_ok() - and self.ip4 - if ip4 and ip4:protocol() == esp.PROTOCOL then - fwd4_packets[fwd4_cursor] = packet.shiftleft(p, ipv4:sizeof()) - fwd4_cursor = fwd4_cursor + 1 - elseif ip4 and ip4:protocol() == exchange.PROTOCOL then - protocol_packets[protocol_cursor] = packet.shiftleft(p, ipv4:sizeof()) - protocol_cursor = protocol_cursor + 1 + assert(self.esp:new_from_mem(p.data, p.length)) + local route = self:find_route4(self.esp:spi()) + if route then + link.transmit(route.link, p) else packet.free(p) counter.add(self.shm.rxerrors) - counter.add(self.shm.protocol_errors) + counter.add(self.shm.route_errors) end end - - for i = 0, fwd4_cursor - 1 do - self:forward4(fwd4_packets[i]) - end - - for i = 0, protocol_cursor - 1 do - link.transmit(self.output.protocol, protocol_packets[i]) - end - - for i = 0, arp_cursor - 1 do - link.transmit(self.output.arp, arp_packets[i]) - end -end - -function PublicRouter:find_route4 (spi) - local entry = self.routing_table4:lookup_ptr(spi) - return entry and self.routes[entry.value] -end - -function PublicRouter:forward4 (p) - local route = self.esp:new_from_mem(p.data, p.length) - and self:find_route4(self.esp:spi()) - if route then - link.transmit(route.link, p) - else - packet.free(p) - counter.add(self.shm.rxerrors) - counter.add(self.shm.route_errors) - end end - -function packet_buffer () - return ffi.new("struct packet *[?]", link.max) -end diff --git a/src/program/vita/ttl.lua b/src/program/vita/ttl.lua new file mode 100644 index 0000000000..f06c7a6d94 --- /dev/null +++ b/src/program/vita/ttl.lua @@ -0,0 +1,40 @@ +-- Use of this source code is governed by the GNU AGPL license; see COPYING. + +module(...,package.seeall) + +local counter = require("core.counter") +local ipv4 = require("lib.protocol.ipv4") + +DecrementTTL = { + name = "DecrementTTL", + shm = { + rxerrors = {counter}, + ttl_errors = {counter}, + protocol_errors = {counter} + } +} + +function DecrementTTL:new () + return setmetatable({ip4 = ipv4:new({})}, {__index=DecrementTTL}) +end + +function DecrementTTL:push () + for _, input in ipairs(self.input) do + while not link.empty(input) do + local p = link.receive(input) + local ip4 = self.ip4:new_from_mem(p.data, p.length) + if ip4 and ip4:ttl() > 0 then + ip4:ttl_decrement() + link.transmit(self.output.output, p) + elseif ip4 then + packet.free(p) + counter.add(self.shm.rxerrors) + counter.add(self.shm.ttl_errors) + else + packet.free(p) + counter.add(self.shm.rxerrors) + counter.add(self.shm.protocol_errors) + end + end + end +end diff --git a/src/program/vita/vita.lua b/src/program/vita/vita.lua index 1c5397ae62..4d5e25efbf 100644 --- a/src/program/vita/vita.lua +++ b/src/program/vita/vita.lua @@ -5,6 +5,8 @@ module(...,package.seeall) local lib = require("core.lib") local shm = require("core.shm") local worker = require("core.worker") +local dispatch = require("program.vita.dispatch") +local ttl = require("program.vita.ttl") local route = require("program.vita.route") local tunnel = require("program.vita.tunnel") local nexthop = require("program.vita.nexthop") @@ -97,13 +99,19 @@ function configure_private_router (conf, append) conf = lib.parse(conf, confspec) local c = append or config.new() + config.app(c, "PrivateDispatch", dispatch.PrivateDispatch) + config.app(c, "OutboundTTL", ttl.DecrementTTL) config.app(c, "PrivateRouter", route.PrivateRouter, {routes=conf.route}) + config.app(c, "InboundTTL", ttl.DecrementTTL) config.app(c, "PrivateNextHop", nexthop.NextHop4, { node_mac = conf.private_interface.macaddr, node_ip4 = conf.private_ip4, nexthop_ip4 = conf.private_nexthop_ip4 }) - config.link(c, "PrivateRouter.arp -> PrivateNextHop.arp") + config.link(c, "PrivateDispatch.forward4 -> OutboundTTL.input") + config.link(c, "PrivateDispatch.arp -> PrivateNextHop.arp") + config.link(c, "OutboundTTL.output -> PrivateRouter.input") + config.link(c, "InboundTTL.output -> PrivateNextHop.forward") for id, route in pairs(conf.route) do local private_in = "PrivateRouter."..id @@ -111,14 +119,14 @@ function configure_private_router (conf, append) config.app(c, ESP_in, Transmitter) config.link(c, private_in.." -> "..ESP_in..".input") - local private_out = "PrivateNextHop."..id + local private_out = "InboundTTL."..id local DSP_out = "DSP_"..id.."_out" config.app(c, DSP_out, Receiver) config.link(c, DSP_out..".output -> "..private_out) end local private_links = { - input = "PrivateRouter.input", + input = "PrivateDispatch.input", output = "PrivateNextHop.output" } return c, private_links @@ -128,6 +136,7 @@ function configure_public_router (conf, append) conf = lib.parse(conf, confspec) local c = append or config.new() + config.app(c, "PublicDispatch", dispatch.PublicDispatch) config.app(c, "PublicRouter", route.PublicRouter, { routes = conf.route, node_ip4 = conf.public_ip4 @@ -137,11 +146,12 @@ function configure_public_router (conf, append) node_ip4 = conf.public_ip4, nexthop_ip4 = conf.public_nexthop_ip4 }) - config.link(c, "PublicRouter.arp -> PublicNextHop.arp") + config.link(c, "PublicDispatch.forward4 -> PublicRouter.input") + config.link(c, "PublicDispatch.arp -> PublicNextHop.arp") config.app(c, "Protocol_in", Transmitter) config.app(c, "Protocol_out", Receiver) - config.link(c, "PublicRouter.protocol -> Protocol_in.input") + config.link(c, "PublicDispatch.protocol -> Protocol_in.input") config.link(c, "Protocol_out.output -> PublicNextHop.protocol") for id, route in pairs(conf.route) do @@ -161,7 +171,7 @@ function configure_public_router (conf, append) end local public_links = { - input = "PublicRouter.input", + input = "PublicDispatch.input", output = "PublicNextHop.output" } From 0f0f2a016ab061b1144a7db229f1823ef0470145 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Mon, 21 May 2018 00:09:58 +0200 Subject: [PATCH 05/21] vita dispatch: use packet box instead of negative offset w/ pf.match --- src/program/vita/dispatch.lua | 44 ++++++++++++++++------------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/program/vita/dispatch.lua b/src/program/vita/dispatch.lua index db9f58d62e..50b18d816e 100644 --- a/src/program/vita/dispatch.lua +++ b/src/program/vita/dispatch.lua @@ -12,12 +12,6 @@ local exchange = require("program.vita.exchange") local pf_match = require("pf.match") local ffi = require("ffi") --- Ugly hack: given a packet.data pointer we just happen to know the struct --- packet pointer is two bytes behind. -local function payload_packet (ptr) - return ffi.cast("struct packet *", ptr - 2) -end - PrivateDispatch = { name = "PrivateDispatch", @@ -30,6 +24,7 @@ PrivateDispatch = { function PrivateDispatch:new () local o = { + p_box = ffi.new("struct packet *[1]"), ip4 = ipv4:new({}), dispatch = pf_match.compile([[match { ip => forward4 @@ -40,8 +35,8 @@ function PrivateDispatch:new () return setmetatable(o, {__index=PrivateDispatch}) end -function PrivateDispatch:forward4 (data, length) - local p = packet.shiftleft(payload_packet(data), ethernet:sizeof()) +function PrivateDispatch:forward4 () + local p = packet.shiftleft(self.p_box[0], ethernet:sizeof()) assert(self.ip4:new_from_mem(p.data, p.length)) if self.ip4:checksum_ok() then -- Strip datagram of any Ethernet frame padding before encapsulation. @@ -54,13 +49,13 @@ function PrivateDispatch:forward4 (data, length) end end -function PrivateDispatch:arp (data) - local p = packet.shiftleft(payload_packet(data), ethernet:sizeof()) +function PrivateDispatch:arp () + local p = packet.shiftleft(self.p_box[0], ethernet:sizeof()) link.transmit(self.output.arp, p) end -function PrivateDispatch:reject_ethertype (data) - packet.free(payload_packet(data)) +function PrivateDispatch:reject_ethertype () + packet.free(self.p_box[0]) counter.add(self.shm.rxerrors) counter.add(self.shm.ethertype_errors) end @@ -69,6 +64,7 @@ function PrivateDispatch:push () local input = self.input.input while not link.empty(input) do local p = link.receive(input) + self.p_box[0] = p self:dispatch(p.data, p.length) end end @@ -85,6 +81,7 @@ PublicDispatch = { function PublicDispatch:new () local o = { + p_box = ffi.new("struct packet *[1]"), ip4 = ipv4:new({}), dispatch = pf_match.compile([[match { ip proto esp => forward4 @@ -97,33 +94,31 @@ function PublicDispatch:new () return setmetatable(o, {__index=PublicDispatch}) end -function PublicDispatch:forward4 (data) - local p = packet.shiftleft(payload_packet(data), - ethernet:sizeof() + ipv4:sizeof()) +function PublicDispatch:forward4 () + local p = packet.shiftleft(self.p_box[0], ethernet:sizeof() + ipv4:sizeof()) -- NB: Ignore potential differences between IP datagram and Ethernet size -- since the minimum ESP packet exceeds 60 bytes in payload. link.transmit(self.output.forward4, p) end -function PublicDispatch:protocol (data) - local p = packet.shiftleft(payload_packet(data), - ethernet:sizeof() + ipv4:sizeof()) +function PublicDispatch:protocol () + local p = packet.shiftleft(self.p_box[0], ethernet:sizeof() + ipv4:sizeof()) link.transmit(self.output.protocol, p) end -function PublicDispatch:arp (data) - local p = packet.shiftleft(payload_packet(data), ethernet:sizeof()) +function PublicDispatch:arp () + local p = packet.shiftleft(self.p_box[0], ethernet:sizeof()) link.transmit(self.output.arp, p) end -function PublicDispatch:reject_protocol (data) - packet.free(payload_packet(data)) +function PublicDispatch:reject_protocol () + packet.free(self.p_box[0]) counter.add(self.shm.rxerrors) counter.add(self.shm.protocol_errors) end -function PublicDispatch:reject_ethertype (data) - packet.free(payload_packet(data)) +function PublicDispatch:reject_ethertype () + packet.free(self.p_box[0]) counter.add(self.shm.rxerrors) counter.add(self.shm.ethertype_errors) end @@ -132,6 +127,7 @@ function PublicDispatch:push () local input = self.input.input while not link.empty(input) do local p = link.receive(input) + self.p_box[0] = p self:dispatch(p.data, p.length) end end From a80a5ea886cd33bc3b81fa8ad32a9782acb414b5 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Mon, 21 May 2018 00:09:58 +0200 Subject: [PATCH 06/21] vita dispatch: remove some unused module imports --- src/program/vita/dispatch.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/program/vita/dispatch.lua b/src/program/vita/dispatch.lua index 50b18d816e..41e60ce536 100644 --- a/src/program/vita/dispatch.lua +++ b/src/program/vita/dispatch.lua @@ -5,10 +5,6 @@ module(...,package.seeall) local counter = require("core.counter") local ethernet = require("lib.protocol.ethernet") local ipv4 = require("lib.protocol.ipv4") -local arp = require("lib.protocol.arp") -local esp_header = require("lib.protocol.esp") -local esp = require("lib.ipsec.esp") -local exchange = require("program.vita.exchange") local pf_match = require("pf.match") local ffi = require("ffi") From e2f3deeef5310f7dd0611dc308173a42a1e5a339 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Mon, 21 May 2018 15:30:26 +0200 Subject: [PATCH 07/21] vita: reply to ICMP echo requests --- src/program/vita/dispatch.lua | 40 +++++++++++++++++++++++++++++------ src/program/vita/vita.lua | 22 +++++++++++++++++-- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/program/vita/dispatch.lua b/src/program/vita/dispatch.lua index 41e60ce536..a2ec854ac7 100644 --- a/src/program/vita/dispatch.lua +++ b/src/program/vita/dispatch.lua @@ -11,6 +11,9 @@ local ffi = require("ffi") PrivateDispatch = { name = "PrivateDispatch", + config = { + node_ip4 = {required=true} + }, shm = { rxerrors = {counter}, ethertype_errors = {counter}, @@ -18,19 +21,30 @@ PrivateDispatch = { } } -function PrivateDispatch:new () +function PrivateDispatch:new (conf) local o = { p_box = ffi.new("struct packet *[1]"), ip4 = ipv4:new({}), - dispatch = pf_match.compile([[match { + dispatch = pf_match.compile(([[match { + ip icmp[icmptype] = icmp-echo and dst host %s => echo4 + ip icmp and dst host %s => events4 ip => forward4 arp => arp otherwise => reject_ethertype - }]]) + }]]):format(conf.node_ip4, conf.node_ip4)) } return setmetatable(o, {__index=PrivateDispatch}) end +function PrivateDispatch:echo4 () + link.transmit(self.output.echo4, self.p_box[0]) +end + +function PrivateDispatch:events4 () + -- Not implemented. + packet.free(self.p_box[0]) +end + function PrivateDispatch:forward4 () local p = packet.shiftleft(self.p_box[0], ethernet:sizeof()) assert(self.ip4:new_from_mem(p.data, p.length)) @@ -68,6 +82,9 @@ end PublicDispatch = { name = "PublicDispatch", + config = { + node_ip4 = {required=true} + }, shm = { rxerrors = {counter}, ethertype_errors = {counter}, @@ -75,21 +92,32 @@ PublicDispatch = { } } -function PublicDispatch:new () +function PublicDispatch:new (conf) local o = { p_box = ffi.new("struct packet *[1]"), ip4 = ipv4:new({}), - dispatch = pf_match.compile([[match { + dispatch = pf_match.compile(([[match { ip proto esp => forward4 ip proto 99 => protocol + ip icmp[icmptype] = icmp-echo and dst host %s => echo4 + ip icmp and dst host %s => events4 ip => reject_protocol arp => arp otherwise => reject_ethertype - }]]) + }]]):format(conf.node_ip4, conf.node_ip4)) } return setmetatable(o, {__index=PublicDispatch}) end +function PublicDispatch:echo4 () + link.transmit(self.output.echo4, self.p_box[0]) +end + +function PublicDispatch:events4 () + -- Not implemented. + packet.free(self.p_box[0]) +end + function PublicDispatch:forward4 () local p = packet.shiftleft(self.p_box[0], ethernet:sizeof() + ipv4:sizeof()) -- NB: Ignore potential differences between IP datagram and Ethernet size diff --git a/src/program/vita/vita.lua b/src/program/vita/vita.lua index 4d5e25efbf..e53a53fb26 100644 --- a/src/program/vita/vita.lua +++ b/src/program/vita/vita.lua @@ -16,6 +16,8 @@ local interlink = require("lib.interlink") local Receiver = require("apps.interlink.receiver") local Transmitter = require("apps.interlink.transmitter") local intel_mp = require("apps.intel_mp.intel_mp") +local echo4 = require("apps.ipv4.echo") +local ipv4 = require("lib.protocol.ipv4") local numa = require("lib.numa") local yang = require("lib.yang.yang") local C = require("ffi").C @@ -99,9 +101,14 @@ function configure_private_router (conf, append) conf = lib.parse(conf, confspec) local c = append or config.new() - config.app(c, "PrivateDispatch", dispatch.PrivateDispatch) + config.app(c, "PrivateDispatch", dispatch.PrivateDispatch, { + node_ip4 = conf.private_ip4 + }) config.app(c, "OutboundTTL", ttl.DecrementTTL) config.app(c, "PrivateRouter", route.PrivateRouter, {routes=conf.route}) + config.app(c, "PrivateEcho4", echo4.ICMPEcho, { + address = ipv4:pton(conf.private_ip4) + }) config.app(c, "InboundTTL", ttl.DecrementTTL) config.app(c, "PrivateNextHop", nexthop.NextHop4, { node_mac = conf.private_interface.macaddr, @@ -109,9 +116,12 @@ function configure_private_router (conf, append) nexthop_ip4 = conf.private_nexthop_ip4 }) config.link(c, "PrivateDispatch.forward4 -> OutboundTTL.input") + config.link(c, "PrivateDispatch.echo4 -> PrivateEcho4.south") config.link(c, "PrivateDispatch.arp -> PrivateNextHop.arp") config.link(c, "OutboundTTL.output -> PrivateRouter.input") config.link(c, "InboundTTL.output -> PrivateNextHop.forward") + config.link(c, "PrivateEcho4.south -> PrivateNextHop.echo4") + config.link(c, "PrivateEcho4.north -> PrivateEcho4.north") for id, route in pairs(conf.route) do local private_in = "PrivateRouter."..id @@ -136,18 +146,26 @@ function configure_public_router (conf, append) conf = lib.parse(conf, confspec) local c = append or config.new() - config.app(c, "PublicDispatch", dispatch.PublicDispatch) + config.app(c, "PublicDispatch", dispatch.PublicDispatch, { + node_ip4 = conf.public_ip4 + }) config.app(c, "PublicRouter", route.PublicRouter, { routes = conf.route, node_ip4 = conf.public_ip4 }) + config.app(c, "PublicEcho4", echo4.ICMPEcho, { + address = ipv4:pton(conf.public_ip4) + }) config.app(c, "PublicNextHop", nexthop.NextHop4, { node_mac = conf.public_interface.macaddr, node_ip4 = conf.public_ip4, nexthop_ip4 = conf.public_nexthop_ip4 }) config.link(c, "PublicDispatch.forward4 -> PublicRouter.input") + config.link(c, "PublicDispatch.echo4 -> PublicEcho4.south") config.link(c, "PublicDispatch.arp -> PublicNextHop.arp") + config.link(c, "PublicEcho4.south -> PublicNextHop.echo4") + config.link(c, "PublicEcho4.north -> PublicEcho4.north") config.app(c, "Protocol_in", Transmitter) config.app(c, "Protocol_out", Receiver) From f3997bb5451680993c09caa14fa53d21cc9c32d3 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Tue, 22 May 2018 16:54:57 +0200 Subject: [PATCH 08/21] lib.protocol.ipv4: add :swap() method for address swapping. --- src/lib/protocol/README.md | 4 ++++ src/lib/protocol/ipv4.lua | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/src/lib/protocol/README.md b/src/lib/protocol/README.md index e2e18e6dfc..fe3d25d495 100644 --- a/src/lib/protocol/README.md +++ b/src/lib/protocol/README.md @@ -197,6 +197,10 @@ Computes and sets the IPv4 header checksum. Its called automatically by Predicate methods to test if *ip* is equal to the source or destination addresses individually. +— Method **ipv4:swap** + +Swaps the source and destination addresses. + — Function **ipv4:pton** *string* Returns the binary representation of IPv4 address denoted by *string*. diff --git a/src/lib/protocol/ipv4.lua b/src/lib/protocol/ipv4.lua index e008e9f5db..0decd9742d 100644 --- a/src/lib/protocol/ipv4.lua +++ b/src/lib/protocol/ipv4.lua @@ -220,6 +220,14 @@ function ipv4:eq (other) self:src_eq(other:src()) and self:dst_eq(other:dst()) end +function ipv4:swap () + local tmp = ipv4_addr_t() + local h = self:header() + ffi.copy(tmp, h.src_ip, ipv4_addr_t_size) + ffi.copy(h.dst_ip, h.src_ip, ipv4_addr_t_size) + ffi.copy(h.src_ip, tmp, ipv4_addr_t_size) +end + -- Return a pseudo header for checksum calculation in a upper-layer -- protocol (e.g. icmp). Note that the payload length and next-header -- values in the pseudo-header refer to the effective upper-layer From 19fb986137ef7af58c2fb9c1f750b8858a372a67 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Wed, 23 May 2018 13:25:02 +0200 Subject: [PATCH 09/21] lib.protocol.ipv4: fix a bug in _ulp map ICMP in IPv4 has protocol identifier 1, not 58. --- src/lib/protocol/ipv4.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/protocol/ipv4.lua b/src/lib/protocol/ipv4.lua index 0decd9742d..8dc4039280 100644 --- a/src/lib/protocol/ipv4.lua +++ b/src/lib/protocol/ipv4.lua @@ -33,10 +33,10 @@ local ipv4 = subClass(header) ipv4._name = "ipv4" ipv4._ulp = { class_map = { + [1] = "lib.protocol.icmp.header", [6] = "lib.protocol.tcp", [17] = "lib.protocol.udp", [47] = "lib.protocol.gre", - [58] = "lib.protocol.icmp.header", }, method = 'protocol' } ipv4:init( From dd1ad6016927e91a148ffa42919cab9772ef9509 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Wed, 23 May 2018 13:25:54 +0200 Subject: [PATCH 10/21] lib.protocol.ipv4: do not call out to memcmp(3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use a uint32_t comparison for address equality testing instead of calling out to libc’s memcmp(3) via the FFI. --- src/lib/protocol/ipv4.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/protocol/ipv4.lua b/src/lib/protocol/ipv4.lua index 8dc4039280..752bf69578 100644 --- a/src/lib/protocol/ipv4.lua +++ b/src/lib/protocol/ipv4.lua @@ -195,8 +195,12 @@ function ipv4:src (ip) end end +local function ip_eq (x, y) + return ffi.cast("uint32_t *", x)[0] == ffi.cast("uint32_t *", y)[0] +end + function ipv4:src_eq (ip) - return C.memcmp(ip, self:header().src_ip, ipv4_addr_t_size) == 0 + return ip_eq(ip, self:header().src_ip, ipv4_addr_t_size) end function ipv4:dst (ip) @@ -208,7 +212,7 @@ function ipv4:dst (ip) end function ipv4:dst_eq (ip) - return C.memcmp(ip, self:header().dst_ip, ipv4_addr_t_size) == 0 + return ip_eq(ip, self:header().dst_ip, ipv4_addr_t_size) end -- override the default equality method From 4f63c6fe3d69fae32c45f8b8d6798b70cf524bdb Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Wed, 23 May 2018 17:16:12 +0200 Subject: [PATCH 11/21] lib.protocol.ipv4: add :is_fragment() method. --- src/lib/protocol/README.md | 4 ++++ src/lib/protocol/ipv4.lua | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/lib/protocol/README.md b/src/lib/protocol/README.md index fe3d25d495..9e008af461 100644 --- a/src/lib/protocol/README.md +++ b/src/lib/protocol/README.md @@ -201,6 +201,10 @@ addresses individually. Swaps the source and destination addresses. +— Method **ipv4:is_fragment** + +Returns true if the header denotes an IP fragment and false otherwise. + — Function **ipv4:pton** *string* Returns the binary representation of IPv4 address denoted by *string*. diff --git a/src/lib/protocol/ipv4.lua b/src/lib/protocol/ipv4.lua index 752bf69578..0f050ff540 100644 --- a/src/lib/protocol/ipv4.lua +++ b/src/lib/protocol/ipv4.lua @@ -141,6 +141,10 @@ function ipv4:frag_off (frag_off) return lib.bitfield(16, self:header(), 'frag_off', 3, 13, frag_off) end +function ipv4:is_fragment () + return self:frag_off() ~= 0 or band(self:flags(), 0x01) == 1 +end + function ipv4:ttl (ttl) if ttl ~= nil then self:header().ttl = ttl From 5b149aa193fbc332534a1b962ff6370522e64b12 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Tue, 22 May 2018 16:57:37 +0200 Subject: [PATCH 12/21] vita: more complete ICMP handling - respond to ICMP echo requests - generate time exceeded messages on TTL expiration - log various incoming ICMP messages --- src/program/vita/dispatch.lua | 109 ++++++++++--- src/program/vita/icmp.lua | 286 ++++++++++++++++++++++++++++++++++ src/program/vita/route.lua | 36 ++--- src/program/vita/ttl.lua | 7 +- src/program/vita/vita.lua | 43 +++-- 5 files changed, 418 insertions(+), 63 deletions(-) create mode 100644 src/program/vita/icmp.lua diff --git a/src/program/vita/dispatch.lua b/src/program/vita/dispatch.lua index a2ec854ac7..4397ffde27 100644 --- a/src/program/vita/dispatch.lua +++ b/src/program/vita/dispatch.lua @@ -2,6 +2,7 @@ module(...,package.seeall) +local icmp = require("program.vita.icmp") local counter = require("core.counter") local ethernet = require("lib.protocol.ethernet") local ipv4 = require("lib.protocol.ipv4") @@ -26,8 +27,8 @@ function PrivateDispatch:new (conf) p_box = ffi.new("struct packet *[1]"), ip4 = ipv4:new({}), dispatch = pf_match.compile(([[match { - ip icmp[icmptype] = icmp-echo and dst host %s => echo4 - ip icmp and dst host %s => events4 + ip dst host %s and icmp => icmp4 + ip dst host %s => protocol4_unreachable ip => forward4 arp => arp otherwise => reject_ethertype @@ -36,15 +37,6 @@ function PrivateDispatch:new (conf) return setmetatable(o, {__index=PrivateDispatch}) end -function PrivateDispatch:echo4 () - link.transmit(self.output.echo4, self.p_box[0]) -end - -function PrivateDispatch:events4 () - -- Not implemented. - packet.free(self.p_box[0]) -end - function PrivateDispatch:forward4 () local p = packet.shiftleft(self.p_box[0], ethernet:sizeof()) assert(self.ip4:new_from_mem(p.data, p.length)) @@ -59,11 +51,21 @@ function PrivateDispatch:forward4 () end end +function PrivateDispatch:icmp4 () + local p = packet.shiftleft(self.p_box[0], ethernet:sizeof()) + link.transmit(self.output.icmp4, p) +end + function PrivateDispatch:arp () local p = packet.shiftleft(self.p_box[0], ethernet:sizeof()) link.transmit(self.output.arp, p) end +function PrivateDispatch:protocol4_unreachable () + local p = packet.shiftleft(self.p_box[0], ethernet:sizeof()) + link.transmit(self.output.protocol4_unreachable, p) +end + function PrivateDispatch:reject_ethertype () packet.free(self.p_box[0]) counter.add(self.shm.rxerrors) @@ -95,12 +97,11 @@ PublicDispatch = { function PublicDispatch:new (conf) local o = { p_box = ffi.new("struct packet *[1]"), - ip4 = ipv4:new({}), dispatch = pf_match.compile(([[match { ip proto esp => forward4 ip proto 99 => protocol - ip icmp[icmptype] = icmp-echo and dst host %s => echo4 - ip icmp and dst host %s => events4 + ip dst host %s and icmp => icmp4 + ip dst host %s => protocol4_unreachable ip => reject_protocol arp => arp otherwise => reject_ethertype @@ -109,15 +110,6 @@ function PublicDispatch:new (conf) return setmetatable(o, {__index=PublicDispatch}) end -function PublicDispatch:echo4 () - link.transmit(self.output.echo4, self.p_box[0]) -end - -function PublicDispatch:events4 () - -- Not implemented. - packet.free(self.p_box[0]) -end - function PublicDispatch:forward4 () local p = packet.shiftleft(self.p_box[0], ethernet:sizeof() + ipv4:sizeof()) -- NB: Ignore potential differences between IP datagram and Ethernet size @@ -130,11 +122,21 @@ function PublicDispatch:protocol () link.transmit(self.output.protocol, p) end +function PublicDispatch:icmp4 () + local p = packet.shiftleft(self.p_box[0], ethernet:sizeof()) + link.transmit(self.output.icmp4, p) +end + function PublicDispatch:arp () local p = packet.shiftleft(self.p_box[0], ethernet:sizeof()) link.transmit(self.output.arp, p) end +function PublicDispatch:protocol4_unreachable () + local p = packet.shiftleft(self.p_box[0], ethernet:sizeof()) + link.transmit(self.output.protocol4_unreachable, p) +end + function PublicDispatch:reject_protocol () packet.free(self.p_box[0]) counter.add(self.shm.rxerrors) @@ -155,3 +157,64 @@ function PublicDispatch:push () self:dispatch(p.data, p.length) end end + + +InboundDispatch = { + name = "InboundDispatch", + config = { + node_ip4 = {required=true}, + }, + shm = { + protocol_errors = {counter} + } +} + +function InboundDispatch:new (conf) + local o = { + node_ip4n = ipv4:pton(conf.node_ip4), + ip4 = ipv4:new({}) + } + return setmetatable(o, {__index=InboundDispatch}) +end + +function InboundDispatch:forward4 (p) + link.transmit(self.output.forward4, p) +end + +function InboundDispatch:icmp4 (p) + link.transmit(self.output.icmp4, p) +end + +function InboundDispatch:protocol4_unreachable (p) + link.transmit(self.output.protocol4_unreachable, p) +end + +function InboundDispatch:reject_protocol (p) + packet.free(p) + counter.add(self.shm.protocol_errors) +end + +function InboundDispatch:dispatch (p) + local ip4 = self.ip4:new_from_mem(p.data, p.length) + if ip4 then + if ip4:dst_eq(self.node_ip4n) then + if ip4:protocol() == icmp.ICMP4.PROTOCOL then + self:icmp4(p) + else + self:protocol4_unreachable(p) + end + else + self:forward4(p) + end + else + self:reject_protocol(p) + end +end + +function InboundDispatch:push () + for _, input in ipairs(self.input) do + while not link.empty(input) do + self:dispatch(link.receive(input)) + end + end +end diff --git a/src/program/vita/icmp.lua b/src/program/vita/icmp.lua new file mode 100644 index 0000000000..a4282085dc --- /dev/null +++ b/src/program/vita/icmp.lua @@ -0,0 +1,286 @@ +-- Use of this source code is governed by the Apache 2.0 license; see COPYING. + +module(..., package.seeall) + +local ipv4 = require("lib.protocol.ipv4") +local icmp = require("lib.protocol.icmp.header") +local counter = require("core.counter") +local lib = require("core.lib") +local ffi = require("ffi") +local min, max, floor = math.min, math.max, math.floor + +ICMP4 = { + name = "ICMP4", + config = { + node_ip4 = {required=true}, + nexthop_mtu = {}, + max_pps = {default=100} + }, + shm = { + rxerrors = {counter}, + protocol_errors = {counter}, + type_not_implemented_errors = {counter}, + code_not_implemented_errors = {counter}, + destination_unreachable = {counter}, + net_unreachable = {counter}, + host_unreachable = {counter}, + protocol_unreachable = {counter}, + port_unreachable = {counter}, + fragmentation_needed = {counter}, + source_route_failed = {counter}, + time_exceeded = {counter}, + transit_ttl_exceeded = {counter}, + fragment_reassembly_time_exceeded = {counter}, + parameter_problem = {counter}, + redirect = {counter}, + redirect_net = {counter}, + redirect_host = {counter}, + redirect_tos_net = {counter}, + redirect_tos_host = {counter}, + echo_request = {counter} + }, + PROTOCOL = 1, -- ICMP = 1 + payload_offset = ipv4:sizeof() + icmp:sizeof(), + handlers = {} +} + +ICMP4.payload_base_t = ffi.typeof([[struct { + uint8_t unused[4]; + uint8_t excerpt[28]; + } __attribute__((packed))]]) + +ICMP4.fragmentation_needed_t = ffi.typeof([[struct { + uint8_t unused[2]; + uint16_t nexthop_mtu; + } __attribute__((packed))]]) + +ICMP4.payload_t = ffi.typeof([[union { + $ base; + $ fragmentation_needed; + } __attribute__((packed))]], + ICMP4.payload_base_t, + ICMP4.fragmentation_needed_t) + +ICMP4.payload_ptr_t = ffi.typeof("$ *", ICMP4.payload_t) + +function ICMP4:new (conf) + local o = { + ip4 = ipv4:new({ + ttl = 64, + protocol = ICMP4.PROTOCOL, + src = ipv4:pton(conf.node_ip4) + }), + ip4_in = ipv4:new({}), + icmp = icmp:new(), + nexthop_mtu = conf.nexthop_mtu or 1024, + nexthop_mtu_configured = conf.nexthop_mtu ~= nil, + throttle = lib.throttle(1), + max_pps = conf.max_pps, + buckets = {rx = 0, tx = 0}, + num_buckets = 2, + logger = nil + } + for bucket, _ in pairs(o.buckets) do + o.buckets[bucket] = floor(o.max_pps / o.num_buckets) + end + return setmetatable(o, {__index=ICMP4}) +end + +function ICMP4:link () + if not self.logger then + self.logger = lib.logger_new({module = self.appname}) + end + if self.input.fragmentation_needed and not self.nexthop_mtu_configured then + self.logger:log(("WARNING, 'fragmentation_needed' link attached but nexthop_mtu not configured, defaulting to %d.") + :format(self.nexthop_mtu)) + end +end + +function ICMP4:log (p) + local payload = ffi.cast(ICMP4.payload_ptr_t, p.data + ICMP4.payload_offset) + local payload_length = p.length - ICMP4.payload_offset + local excerpt = ffi.string( + payload.base.excerpt, + min(payload_length - ffi.sizeof(payload.base.unused), + ffi.sizeof(payload.base.excerpt)) + ) + self.logger:log(("received message from %s [type %d code %d packet %s ...]") + :format(ipv4:ntop(self.ip4_in:src()), + self.icmp:type(), + self.icmp:code(), + lib.hexdump(excerpt))) +end + +-- Destination Unreachable +ICMP4.handlers[3] = function (self, p) + self:log(p) + packet.free(p) + counter.add(self.shm.destination_unreachable) + counter.add( + ({ [0] = self.shm.net_unreachable, + [1] = self.shm.host_unreachable, + [2] = self.shm.protocol_unreachable, + [3] = self.shm.port_unreachable, + [4] = self.shm.fragmentation_needed, + [5] = self.shm.source_route_failed }) + [self.icmp:code()] + or self.shm.code_not_implemented_errors + ) +end + +-- Time Exceeded +ICMP4.handlers[11] = function (self, p) + self:log(p) + packet.free(p) + counter.add(self.shm.time_exceeded) + counter.add( + ({ [0] = self.shm.transit_ttl_exceeded, + [1] = self.shm.fragment_reassembly_time_exceeded }) + [self.icmp:code()] + or self.shm.code_not_implemented_errors + ) +end + +-- Parameter Problem +ICMP4.handlers[12] = function (self, p) + self:log(p) + packet.free(p) + counter.add(self.shm.parameter_problem) +end + +-- Redirect +ICMP4.handlers[5] = function (self, p) + self:log(p) + packet.free(p) + counter.add(self.shm.redirect) + counter.add( + ({ [0] = self.shm.redirect_net, + [1] = self.shm.redirect_host, + [2] = self.shm.redirect_tos_net, + [3] = self.shm.redirect_tos_host }) + [self.icmp:code()] + or self.shm.code_not_implemented_errors + ) +end + +-- Echo +ICMP4.handlers[8] = function (self, p) + -- Copy payload. + local reply = packet.from_pointer(self:msg_payload(p)) + -- Prepend ICMP header. + self.icmp:type(0) + self.icmp:code(0) + self.icmp:checksum(reply.data, reply.length) + reply = packet.prepend(reply, self.icmp:header(), icmp:sizeof()) + -- Prepend IP header. + self.ip4:dst(self.ip4_in:src()) + self.ip4:total_length(reply.length + ipv4:sizeof()) + self.ip4:checksum() + reply = packet.prepend(reply, self.ip4:header(), ipv4:sizeof()) + -- Send reply. + link.transmit(self.output.output, reply) + packet.free(p) + counter.add(self.shm.echo_request) +end + +function ICMP4:msg_payload (p) + local payload = p.data + ICMP4.payload_offset + local declared_length = self.ip4_in:total_length() - ICMP4.payload_offset + local actual_length = p.length - ICMP4.payload_offset + local length = max(0, min(actual_length, declared_length)) + return payload, length +end + +function ICMP4:handle_msg (p) + -- Ensure packet is a valid ICMP message, and not a fragment. + if not self.ip4_in:new_from_mem(p.data, p.length) + or self.ip4_in:protocol() ~= ICMP4.PROTOCOL + or self.ip4_in:is_fragment() + or not self.icmp:new_from_mem(p.data + ipv4:sizeof(), + p.length - ipv4:sizeof()) + or not self.icmp:checksum_check(self:msg_payload(p)) + then + packet.free(p) + counter.add(self.shm.rxerrors) + counter.add(self.shm.protocol_errors) + return + end + -- Ensure we have a handler for ICMP type of packet. + local handler = self.handlers[self.icmp:type()] + if not handler then + packet.free(p) + counter.add(self.shm.rxerrors) + counter.add(self.shm.type_not_implemented_errors) + return + end + -- Handle incoming message. + handler(self, p) +end + +function ICMP4:send_msg (msgtype, code, p, opt) + opt = opt or {} + local msg = packet.resize(packet.allocate(), ffi.sizeof(ICMP4.payload_t)) + local payload = ffi.cast(ICMP4.payload_ptr_t, msg.data) + -- Set fields, copy packet excerpt. + if opt.nexthop_mtu then + payload.fragmentation_needed.nexthop_mtu = lib.htons(opt.nexthop_mtu) + end + local excerpt_length = min(p.length, ffi.sizeof(payload.base.excerpt)) + ffi.copy(payload.base.excerpt, p.data, excerpt_length) + -- Prepend ICMP header + self.icmp:type(msgtype) + self.icmp:code(code) + self.icmp:checksum(msg.data, msg.length) + msg = packet.prepend(msg, self.icmp:header(), icmp:sizeof()) + -- Prepend IP header. + assert(self.ip4_in:new_from_mem(p.data, p.length)) + self.ip4:dst(self.ip4_in:src()) + self.ip4:total_length(ipv4:sizeof() + msg.length) + self.ip4:checksum() + msg = packet.prepend(msg, self.ip4:header(), ipv4:sizeof()) + -- Send message, free packet. + link.transmit(self.output.output, msg) + packet.free(p) +end + +function ICMP4:rate_limit (bucket) + if self.throttle() then + self.buckets[bucket] = + min(floor(self.buckets[bucket] + self.max_pps / self.num_buckets), + self.max_pps) + end + if self.buckets[bucket] > 0 then + self.buckets[bucket] = self.buckets[bucket] - 1 + return true + end +end + +function ICMP4:push () + -- Process ingoing messages. + while not link.empty(self.input.input) and self:rate_limit('rx') do + self:handle_msg(link.receive(self.input.input)) + end + + -- Process outgoing messages. + if self.input.protocol_unreachable then + while not link.empty(self.input.protocol_unreachable) + and self:rate_limit('tx') do + self:send_msg(3, 2, link.receive(self.input.protocol_unreachable)) + end + end + if self.input.fragmentation_needed then + while not link.empty(self.input.fragmentation_needed) + and self:rate_limit('tx') do + self:send_msg(3, 4, link.receive(self.input.fragmentation_needed), { + nexthop_mtu = self.nexthop_mtu + }) + end + end + if self.input.transit_ttl_exceeded then + while not link.empty(self.input.transit_ttl_exceeded) + and self:rate_limit('tx') do + self:send_msg(11, 0, link.receive(self.input.transit_ttl_exceeded)) + end + end + -- ...remainder is NYI. +end diff --git a/src/program/vita/route.lua b/src/program/vita/route.lua index 61e489889e..7c8c751c2e 100644 --- a/src/program/vita/route.lua +++ b/src/program/vita/route.lua @@ -22,7 +22,6 @@ PrivateRouter = { routes = {required=true} }, shm = { - rxerrors = {counter}, route_errors = {counter} } } @@ -65,20 +64,23 @@ function PrivateRouter:find_route4 (dst) return self.routes[self.routing_table4:search_bytes(dst)] end -function PrivateRouter:push () - local input = self.input.input +function PrivateRouter:route (p) + assert(self.ip4:new_from_mem(p.data, p.length)) + local route = self:find_route4(self.ip4:dst()) + if route then + link.transmit(route.link, p) + else + packet.free(p) + counter.add(self.shm.route_errors) + end +end - while not link.empty(input) do - local p = link.receive(input) - assert(self.ip4:new_from_mem(p.data, p.length)) - local route = self:find_route4(self.ip4:dst()) - if route then - link.transmit(route.link, p) - else - packet.free(p) - counter.add(self.shm.rxerrors) - counter.add(self.shm.route_errors) - end +function PrivateRouter:push () + while not link.empty(self.input.input) do + self:route(link.receive(self.input.input)) + end + while not link.empty(self.input.control) do + self:route(link.receive(self.input.control)) end end @@ -86,11 +88,9 @@ end PublicRouter = { name = "PublicRouter", config = { - routes = {required=true}, - node_ip4 = {required=true} + routes = {required=true} }, shm = { - rxerrors = {counter}, route_errors = {counter} } } @@ -141,9 +141,7 @@ function PublicRouter:push () link.transmit(route.link, p) else packet.free(p) - counter.add(self.shm.rxerrors) counter.add(self.shm.route_errors) end end end - diff --git a/src/program/vita/ttl.lua b/src/program/vita/ttl.lua index f06c7a6d94..89f9ea87ee 100644 --- a/src/program/vita/ttl.lua +++ b/src/program/vita/ttl.lua @@ -8,8 +8,6 @@ local ipv4 = require("lib.protocol.ipv4") DecrementTTL = { name = "DecrementTTL", shm = { - rxerrors = {counter}, - ttl_errors = {counter}, protocol_errors = {counter} } } @@ -27,12 +25,9 @@ function DecrementTTL:push () ip4:ttl_decrement() link.transmit(self.output.output, p) elseif ip4 then - packet.free(p) - counter.add(self.shm.rxerrors) - counter.add(self.shm.ttl_errors) + link.transmit(self.output.time_exceeded, p) else packet.free(p) - counter.add(self.shm.rxerrors) counter.add(self.shm.protocol_errors) end end diff --git a/src/program/vita/vita.lua b/src/program/vita/vita.lua index e53a53fb26..817dc4907c 100644 --- a/src/program/vita/vita.lua +++ b/src/program/vita/vita.lua @@ -11,12 +11,12 @@ local route = require("program.vita.route") local tunnel = require("program.vita.tunnel") local nexthop = require("program.vita.nexthop") local exchange = require("program.vita.exchange") +local icmp = require("program.vita.icmp") schemata = require("program.vita.schemata") local interlink = require("lib.interlink") local Receiver = require("apps.interlink.receiver") local Transmitter = require("apps.interlink.transmitter") local intel_mp = require("apps.intel_mp.intel_mp") -local echo4 = require("apps.ipv4.echo") local ipv4 = require("lib.protocol.ipv4") local numa = require("lib.numa") local yang = require("lib.yang.yang") @@ -105,23 +105,37 @@ function configure_private_router (conf, append) node_ip4 = conf.private_ip4 }) config.app(c, "OutboundTTL", ttl.DecrementTTL) - config.app(c, "PrivateRouter", route.PrivateRouter, {routes=conf.route}) - config.app(c, "PrivateEcho4", echo4.ICMPEcho, { - address = ipv4:pton(conf.private_ip4) + config.app(c, "PrivateRouter", route.PrivateRouter, { + routes = conf.route + }) + config.app(c, "PrivateICMP4", icmp.ICMP4, { + node_ip4 = conf.private_ip4 + }) + config.app(c, "InboundDispatch", dispatch.InboundDispatch, { + node_ip4 = conf.private_ip4 }) config.app(c, "InboundTTL", ttl.DecrementTTL) + config.app(c, "InboundICMP4", icmp.ICMP4, { + node_ip4 = conf.private_ip4 + }) config.app(c, "PrivateNextHop", nexthop.NextHop4, { node_mac = conf.private_interface.macaddr, node_ip4 = conf.private_ip4, nexthop_ip4 = conf.private_nexthop_ip4 }) config.link(c, "PrivateDispatch.forward4 -> OutboundTTL.input") - config.link(c, "PrivateDispatch.echo4 -> PrivateEcho4.south") + config.link(c, "PrivateDispatch.icmp4 -> PrivateICMP4.input") config.link(c, "PrivateDispatch.arp -> PrivateNextHop.arp") + config.link(c, "PrivateDispatch.protocol4_unreachable -> PrivateICMP4.protocol_unreachable") config.link(c, "OutboundTTL.output -> PrivateRouter.input") + config.link(c, "OutboundTTL.time_exceeded -> PrivateICMP4.transit_ttl_exceeded") + config.link(c, "PrivateICMP4.output -> PrivateNextHop.icmp4") + config.link(c, "InboundDispatch.forward4 -> InboundTTL.input") + config.link(c, "InboundDispatch.icmp4 -> InboundICMP4.input") + config.link(c, "InboundDispatch.protocol4_unreachable -> InboundICMP4.protocol_unreachable") config.link(c, "InboundTTL.output -> PrivateNextHop.forward") - config.link(c, "PrivateEcho4.south -> PrivateNextHop.echo4") - config.link(c, "PrivateEcho4.north -> PrivateEcho4.north") + config.link(c, "InboundTTL.time_exceeded -> InboundICMP4.transit_ttl_exceeded") + config.link(c, "InboundICMP4.output -> PrivateRouter.control") for id, route in pairs(conf.route) do local private_in = "PrivateRouter."..id @@ -129,7 +143,7 @@ function configure_private_router (conf, append) config.app(c, ESP_in, Transmitter) config.link(c, private_in.." -> "..ESP_in..".input") - local private_out = "InboundTTL."..id + local private_out = "InboundDispatch."..id local DSP_out = "DSP_"..id.."_out" config.app(c, DSP_out, Receiver) config.link(c, DSP_out..".output -> "..private_out) @@ -150,11 +164,10 @@ function configure_public_router (conf, append) node_ip4 = conf.public_ip4 }) config.app(c, "PublicRouter", route.PublicRouter, { - routes = conf.route, - node_ip4 = conf.public_ip4 + routes = conf.route }) - config.app(c, "PublicEcho4", echo4.ICMPEcho, { - address = ipv4:pton(conf.public_ip4) + config.app(c, "PublicICMP4", icmp.ICMP4, { + node_ip4 = conf.public_ip4 }) config.app(c, "PublicNextHop", nexthop.NextHop4, { node_mac = conf.public_interface.macaddr, @@ -162,10 +175,10 @@ function configure_public_router (conf, append) nexthop_ip4 = conf.public_nexthop_ip4 }) config.link(c, "PublicDispatch.forward4 -> PublicRouter.input") - config.link(c, "PublicDispatch.echo4 -> PublicEcho4.south") + config.link(c, "PublicDispatch.icmp4 -> PublicICMP4.input") config.link(c, "PublicDispatch.arp -> PublicNextHop.arp") - config.link(c, "PublicEcho4.south -> PublicNextHop.echo4") - config.link(c, "PublicEcho4.north -> PublicEcho4.north") + config.link(c, "PublicDispatch.protocol4_unreachable -> PublicICMP4.protocol_unreachable") + config.link(c, "PublicICMP4.output -> PublicNextHop.icmp4") config.app(c, "Protocol_in", Transmitter) config.app(c, "Protocol_out", Receiver) From d304eb0b29265a18e179de6abfe99e651561a252 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Wed, 23 May 2018 20:25:59 +0200 Subject: [PATCH 13/21] vita: more complete MTU handling - reformulate basic MTU configuration: only allow configuring private MTU - generate ICMP "needs fragmentation" message for packets exceeding the private MTU with the DF bit set --- src/program/vita/README.config | 23 ++++++++++++++------- src/program/vita/dispatch.lua | 10 ++++++++- src/program/vita/route.lua | 28 ++++++++++++++++++-------- src/program/vita/tunnel.lua | 1 + src/program/vita/vita-esp-gateway.yang | 3 ++- src/program/vita/vita.lua | 13 ++++++------ 6 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/program/vita/README.config b/src/program/vita/README.config index 7129e6857b..740650a06b 100644 --- a/src/program/vita/README.config +++ b/src/program/vita/README.config @@ -14,7 +14,6 @@ CONFIGURATION SYNTAX interface:= macaddr ; [ vlan ; ] - [ mtu ; ] pciaddr ; route:= @@ -23,6 +22,7 @@ CONFIGURATION SYNTAX gw_ip4 ; preshared_key ; spi ; + [ private_mtu ; ] NOTES @@ -32,10 +32,10 @@ NOTES Vita nodes. Each interface is identified by a Linux PCI bus address, and assigned an - Ethernet (MAC) address. Optionally, the MTU can be specified in bytes, and a - IEEE 802.1Q VLAN Identifier can be set. Given that the underlying hardware - device supports VMDq, it is possible to pass the same PCI bus address for - both interfaces to have them share a single physical port. + Ethernet (MAC) address. Optionally, a IEEE 802.1Q VLAN Identifier can be set. + Given that the underlying hardware device supports VMDq, it is possible to + pass the same PCI bus address for both interfaces to have them share a single + physical port. Each route is given a unique, human readable identifier that must satisfy the pattern [a-zA-Z0-9_]+ (i.e., one or more alphanumeric ASCII and underscore @@ -48,13 +48,20 @@ NOTES associating encrypted traffic for a given route. Like the pre-shared key, the SPI must be the same for both ends of a route. + Optionally, the private MTU can be specified in bytes. The default and + maximum permitted value is 8937. Since Vita performs neither fragmentation + nor reassembly it may be necessary to adjust the next-hop MTU accordingly. + Note that packets leaving the public interface will have an added packet size + overhead due to encapsulation (up to 57 bytes for IPv4 and up to 77 bytes for + IPv6.) + While the default configuration should be generally applicable, the negotiation timeout and lifetime of Security Associations (SA) can be specified in seconds. EXAMPLE - private_interface { macaddr 52:54:00:00:00:00; mtu 1280; pciaddr 0c:00.0; } + private_interface { macaddr 52:54:00:00:00:00; pciaddr 0c:00.0; } public_interface { macaddr 52:54:00:00:00:FF; pciaddr 0c:00.1; } private_ip4 192.168.10.10; @@ -76,7 +83,9 @@ EXAMPLE net_cidr4 192.168.30.0/24; gw_ip4 203.0.113.3; preshared_key CF0BDD7A058BE55C12B7F2AA30D23FF01BDF8BE6571F2019ED7F7EBD3DA97B47; - spi 1002; + spi 1223; } + private_mtu 1280; + sa_ttl 86400; diff --git a/src/program/vita/dispatch.lua b/src/program/vita/dispatch.lua index 4397ffde27..6bb6060918 100644 --- a/src/program/vita/dispatch.lua +++ b/src/program/vita/dispatch.lua @@ -90,7 +90,8 @@ PublicDispatch = { shm = { rxerrors = {counter}, ethertype_errors = {counter}, - protocol_errors = {counter} + protocol_errors = {counter}, + fragment_errors = {counter} } } @@ -98,6 +99,7 @@ function PublicDispatch:new (conf) local o = { p_box = ffi.new("struct packet *[1]"), dispatch = pf_match.compile(([[match { + ip[6:2] & 0x3FFF != 0 => reject_fragment ip proto esp => forward4 ip proto 99 => protocol ip dst host %s and icmp => icmp4 @@ -137,6 +139,12 @@ function PublicDispatch:protocol4_unreachable () link.transmit(self.output.protocol4_unreachable, p) end +function PublicDispatch:reject_fragment () + packet.free(self.p_box[0]) + counter.add(self.shm.rxerrors) + counter.add(self.shm.fragment_errors) +end + function PublicDispatch:reject_protocol () packet.free(self.p_box[0]) counter.add(self.shm.rxerrors) diff --git a/src/program/vita/route.lua b/src/program/vita/route.lua index 7c8c751c2e..ef6b1bfdea 100644 --- a/src/program/vita/route.lua +++ b/src/program/vita/route.lua @@ -5,10 +5,7 @@ module(...,package.seeall) local counter = require("core.counter") local ethernet = require("lib.protocol.ethernet") local ipv4 = require("lib.protocol.ipv4") -local arp = require("lib.protocol.arp") -local esp_header = require("lib.protocol.esp") -local esp = require("lib.ipsec.esp") -local exchange = require("program.vita.exchange") +local esp = require("lib.protocol.esp") local lpm = require("lib.lpm.lpm4_248").LPM4_248 local ctable = require("lib.ctable") local ffi = require("ffi") @@ -19,16 +16,20 @@ local ffi = require("ffi") PrivateRouter = { name = "PrivateRouter", config = { - routes = {required=true} + routes = {required=true}, + mtu = {required=true} }, shm = { - route_errors = {counter} + rxerrors = {counter}, + route_errors = {counter}, + mtu_errors = {counter} } } function PrivateRouter:new (conf) local o = { routes = {}, + mtu = conf.mtu, ip4 = ipv4:new({}) } for id, route in pairs(conf.routes) do @@ -68,9 +69,20 @@ function PrivateRouter:route (p) assert(self.ip4:new_from_mem(p.data, p.length)) local route = self:find_route4(self.ip4:dst()) if route then - link.transmit(route.link, p) + if p.length + ethernet:sizeof() <= self.mtu then + link.transmit(route.link, p) + else + counter.add(self.shm.rxerrors) + counter.add(self.shm.mtu_errors) + if bit.band(self.ip4:flags(), 2) == 2 then -- Don’t fragment bit set? + link.transmit(self.output.fragmentation_needed, p) + else + packet.free(p) + end + end else packet.free(p) + counter.add(self.shm.rxerrors) counter.add(self.shm.route_errors) end end @@ -98,7 +110,7 @@ PublicRouter = { function PublicRouter:new (conf) local o = { routes = {}, - esp = esp_header:new({}) + esp = esp:new({}) } for id, route in pairs(conf.routes) do o.routes[#o.routes+1] = { diff --git a/src/program/vita/tunnel.lua b/src/program/vita/tunnel.lua index 8a06742665..e317d3713b 100644 --- a/src/program/vita/tunnel.lua +++ b/src/program/vita/tunnel.lua @@ -98,6 +98,7 @@ function Tunnel4:new (conf) src = ipv4:pton(conf.src), dst = ipv4:pton(conf.dst), protocol = esp.PROTOCOL, + flags = 2, -- Don’t Fragment ttl = 64 }, ip = ipv4:new{} diff --git a/src/program/vita/vita-esp-gateway.yang b/src/program/vita/vita-esp-gateway.yang index f12349ee43..21cc936e56 100644 --- a/src/program/vita/vita-esp-gateway.yang +++ b/src/program/vita/vita-esp-gateway.yang @@ -18,7 +18,6 @@ module vita-esp-gateway { leaf pciaddr { type pci-address; mandatory true; } leaf macaddr { type yang:mac-address; mandatory true; } leaf vlan { type uint16 { range "0..4095"; } } - leaf mtu { type uint16; } } container private_interface { uses interface; } @@ -30,6 +29,8 @@ module vita-esp-gateway { leaf private_nexthop_ip4 { type inet:ipv4-address-no-zone; mandatory true; } leaf public_nexthop_ip4 { type inet:ipv4-address-no-zone; mandatory true; } + leaf private_mtu { type uint16 { range "0..8937"; } } + list route { key id; unique "net_cidr4 preshared_key spi"; leaf id { type string { pattern '[\w_]+'; } mandatory true; } diff --git a/src/program/vita/vita.lua b/src/program/vita/vita.lua index 817dc4907c..1abd98dd9d 100644 --- a/src/program/vita/vita.lua +++ b/src/program/vita/vita.lua @@ -31,6 +31,7 @@ local confspec = { public_ip4 = {required=true}, private_nexthop_ip4 = {required=true}, public_nexthop_ip4 = {required=true}, + private_mtu = {default=8937}, route = {required=true}, negotiation_ttl = {}, sa_ttl = {} @@ -106,10 +107,12 @@ function configure_private_router (conf, append) }) config.app(c, "OutboundTTL", ttl.DecrementTTL) config.app(c, "PrivateRouter", route.PrivateRouter, { - routes = conf.route + routes = conf.route, + mtu = conf.private_mtu }) config.app(c, "PrivateICMP4", icmp.ICMP4, { - node_ip4 = conf.private_ip4 + node_ip4 = conf.private_ip4, + nexthop_mtu = conf.private_mtu }) config.app(c, "InboundDispatch", dispatch.InboundDispatch, { node_ip4 = conf.private_ip4 @@ -129,6 +132,7 @@ function configure_private_router (conf, append) config.link(c, "PrivateDispatch.protocol4_unreachable -> PrivateICMP4.protocol_unreachable") config.link(c, "OutboundTTL.output -> PrivateRouter.input") config.link(c, "OutboundTTL.time_exceeded -> PrivateICMP4.transit_ttl_exceeded") + config.link(c, "PrivateRouter.fragmentation_needed -> PrivateICMP4.fragmentation_needed") config.link(c, "PrivateICMP4.output -> PrivateNextHop.icmp4") config.link(c, "InboundDispatch.forward4 -> InboundTTL.input") config.link(c, "InboundDispatch.icmp4 -> InboundICMP4.input") @@ -217,11 +221,6 @@ function configure_private_router_with_nic (conf, append) local c, private = configure_private_router(conf, append or config.new()) - -- Gracious limit for user defined MTU on private interface to avoid packet - -- payload overun due to ESP tunnel overhead. - conf.private_interface.mtu = - math.min(conf.private_interface.mtu or 8000, 8000) - conf.private_interface.vmdq = true config.app(c, "PrivateNIC", intel_mp.Intel, conf.private_interface) From a30d4346d9f5047c3d21970615a89573af7cfa82 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Sat, 26 May 2018 12:54:17 +0200 Subject: [PATCH 14/21] vita: factor out save_config for testing purposes --- src/program/vita/conftest.snabb | 4 +--- src/program/vita/test.snabb | 6 +----- src/program/vita/vita.lua | 6 ++++++ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/program/vita/conftest.snabb b/src/program/vita/conftest.snabb index f3f4a4fd45..c94b3000fd 100755 --- a/src/program/vita/conftest.snabb +++ b/src/program/vita/conftest.snabb @@ -11,9 +11,7 @@ local S = require("syscall") local confpath = shm.root.."/"..shm.resolve("group/testconf") local function commit_conf (conf) - local f = assert(io.open(confpath, "w"), "Unable to open file: "..confpath) - yang.print_config_for_schema(vita.schemata['esp-gateway'], conf, f) - f:close() + vita.save_config(vita.schemata['esp-gateway'], confpath, conf) end worker.start( diff --git a/src/program/vita/test.snabb b/src/program/vita/test.snabb index 014718059b..4d93ae4eb1 100755 --- a/src/program/vita/test.snabb +++ b/src/program/vita/test.snabb @@ -93,11 +93,7 @@ engine.configure(c) worker.set_exit_on_worker_death(true) local confpath = shm.root.."/"..shm.resolve("group/testconf") -do - local f = assert(io.open(confpath, "w"), "Unable to open file: "..confpath) - yang.print_config_for_schema(vita.schemata['esp-gateway'], conf, f) - f:close() -end +vita.save_config(vita.schemata['esp-gateway'], confpath, conf) worker.start( "PublicRouterLoopback", diff --git a/src/program/vita/vita.lua b/src/program/vita/vita.lua index 1abd98dd9d..1593ed7612 100644 --- a/src/program/vita/vita.lua +++ b/src/program/vita/vita.lua @@ -375,6 +375,12 @@ function load_config (schema, confpath) ) end +function save_config (schema, confpath, conf) + local f = assert(io.open(confpath, "w"), "Unable to open file: "..confpath) + yang.print_config_for_schema(schema, conf, f) + f:close() +end + function listen_confpath (schema, confpath, loader, interval) interval = interval or 1e9 From 4344a69f497bea708ce8ecd2b888b9ee6505afc8 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Sat, 26 May 2018 12:55:09 +0200 Subject: [PATCH 15/21] vita: clean up bitrot in conftest.snabb --- src/program/vita/conftest.snabb | 35 ++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/program/vita/conftest.snabb b/src/program/vita/conftest.snabb index c94b3000fd..7a079deadc 100755 --- a/src/program/vita/conftest.snabb +++ b/src/program/vita/conftest.snabb @@ -14,16 +14,23 @@ local function commit_conf (conf) vita.save_config(vita.schemata['esp-gateway'], confpath, conf) end +worker.set_exit_on_worker_death(true) + worker.start( "PublicRouterLoopback", ([[require("program.vita.vita").public_router_loopback_worker(%q)]]) :format(confpath) ) +worker.start( + "Exchange", + ([[require("program.vita.vita").exchange_worker(%q)]]) + :format(confpath) +) S.sleep(1) local conf0 = { - private_interface = { macaddr = "52:54:00:00:00:00" }, - public_interface = { macaddr = "52:54:00:00:00:FF" }, + private_interface = { pciaddr = "00:00.0", macaddr = "52:54:00:00:00:00" }, + public_interface = { pciaddr = "00:00.0", macaddr = "52:54:00:00:00:FF" }, private_ip4 = "192.168.10.1", public_ip4 = "203.0.113.1", private_nexthop_ip4 = "192.168.10.1", @@ -35,8 +42,8 @@ commit_conf(conf0) S.sleep(3) local conf1 = { - private_interface = { macaddr = "52:54:00:00:00:00" }, - public_interface = { macaddr = "52:54:00:00:00:FF" }, + private_interface = { pciaddr = "00:00.0", macaddr = "52:54:00:00:00:00" }, + public_interface = { pciaddr = "00:00.0", macaddr = "52:54:00:00:00:FF" }, private_ip4 = "192.168.10.1", public_ip4 = "203.0.113.1", private_nexthop_ip4 = "192.168.10.1", @@ -59,8 +66,8 @@ commit_conf(conf1) S.sleep(3) local conf2 = { - private_interface = { macaddr = "52:54:00:00:00:00" }, - public_interface = { macaddr = "52:54:00:00:00:FF" }, + private_interface = { pciaddr = "00:00.0", macaddr = "52:54:00:00:00:00" }, + public_interface = { pciaddr = "00:00.0", macaddr = "52:54:00:00:00:FF" }, private_ip4 = "192.168.10.1", public_ip4 = "203.0.113.1", private_nexthop_ip4 = "192.168.10.1", @@ -79,8 +86,8 @@ commit_conf(conf2) S.sleep(3) local conf3 = { - private_interface = { macaddr = "52:54:00:00:00:00" }, - public_interface = { macaddr = "52:54:00:00:00:FF" }, + private_interface = { pciaddr = "00:00.0", macaddr = "52:54:00:00:00:00" }, + public_interface = { pciaddr = "00:00.0", macaddr = "52:54:00:00:00:FF" }, private_ip4 = "192.168.10.1", public_ip4 = "203.0.113.1", private_nexthop_ip4 = "192.168.10.1", @@ -99,8 +106,8 @@ commit_conf(conf3) S.sleep(3) local conf4 = { - private_interface = { macaddr = "52:54:00:00:00:00" }, - public_interface = { macaddr = "52:54:00:00:00:FF" }, + private_interface = { pciaddr = "00:00.0", macaddr = "52:54:00:00:00:00" }, + public_interface = { pciaddr = "00:00.0", macaddr = "52:54:00:00:00:FF" }, private_ip4 = "192.168.10.1", public_ip4 = "203.0.113.2", private_nexthop_ip4 = "192.168.10.1", @@ -119,8 +126,8 @@ commit_conf(conf4) S.sleep(3) local conf5 = { - private_interface = { macaddr = "52:54:00:00:00:00" }, - public_interface = { macaddr = "52:54:00:00:00:FF" }, + private_interface = { pciaddr = "00:00.0", macaddr = "52:54:00:00:00:00" }, + public_interface = { pciaddr = "00:00.0", macaddr = "52:54:00:00:00:FF" }, private_ip4 = "192.168.10.1", public_ip4 = "203.0.113.2", private_nexthop_ip4 = "192.168.10.1", @@ -139,8 +146,8 @@ commit_conf(conf5) S.sleep(3) local conf6 = { - private_interface = { macaddr = "52:54:00:00:00:00" }, - public_interface = { macaddr = "52:54:00:00:00:FF" }, + private_interface = { pciaddr = "00:00.0", macaddr = "52:54:00:00:00:00" }, + public_interface = { pciaddr = "00:00.0", macaddr = "52:54:00:00:00:FF" }, private_ip4 = "192.168.10.1", public_ip4 = "203.0.113.2", private_nexthop_ip4 = "192.168.10.1", From da72e097fb5d7da04acd48bb9b81553f476404aa Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Sat, 26 May 2018 12:56:26 +0200 Subject: [PATCH 16/21] apps.test.match: print index of mismatched packet --- src/apps/test/match.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/apps/test/match.lua b/src/apps/test/match.lua index 57d48f7453..fcb09591e1 100644 --- a/src/apps/test/match.lua +++ b/src/apps/test/match.lua @@ -30,7 +30,10 @@ function Match:push () elseif cmp.length ~= p.length or C.memcmp(cmp.data, p.data, cmp.length) ~= 0 then if not self.fuzzy then - table.insert(self.errs, "Mismatch:\n"..dump(cmp).."\n"..dump(p)) + table.insert(self.errs, + "Mismatch at packet #"..(self.seen+1)..":\n" + ..dump(cmp).."\n" + ..dump(p)) end else self.seen = self.seen + 1 From 3fe0d1c7b49d52ff1c2288ad00634966acaf063b Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Sat, 26 May 2018 12:57:10 +0200 Subject: [PATCH 17/21] vita: fix a missing paren in a log message of KeyManager --- src/program/vita/exchange.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/program/vita/exchange.lua b/src/program/vita/exchange.lua index 8fb4144ce4..d3c82017f6 100644 --- a/src/program/vita/exchange.lua +++ b/src/program/vita/exchange.lua @@ -261,7 +261,7 @@ function KeyManager:push () for _, route in ipairs(self.routes) do if route.protocol:reset_if_expired() == Protocol.code.expired then counter.add(self.shm.negotiations_expired) - audit:log("Negotiation expired for '"..route.id.."' (negotiation_ttl") + audit:log("Negotiation expired for '"..route.id.."' (negotiation_ttl)") end if route.status < status.ready then self:negotiate(route) From afdf0c27e93af115ea650d8dc37f11dc32eb910c Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Sat, 26 May 2018 12:58:54 +0200 Subject: [PATCH 18/21] vita: add basic selftest script (selftest.snabb) This test exercises auxillary code paths like ICMP/MTU handling, routing errors, but unfortunately excludes the KeyManager app. --- src/program/vita/selftest-pcaps.snabb | 315 +++++++++++++++++++++ src/program/vita/selftest-private-in.pcap | Bin 0 -> 19250 bytes src/program/vita/selftest-private-out.pcap | Bin 0 -> 725 bytes src/program/vita/selftest-public-in.pcap | Bin 0 -> 1174 bytes src/program/vita/selftest-public-out.pcap | Bin 0 -> 591 bytes src/program/vita/selftest.snabb | 247 ++++++++++++++++ src/program/vita/vita.lua | 8 +- 7 files changed, 566 insertions(+), 4 deletions(-) create mode 100755 src/program/vita/selftest-pcaps.snabb create mode 100644 src/program/vita/selftest-private-in.pcap create mode 100644 src/program/vita/selftest-private-out.pcap create mode 100644 src/program/vita/selftest-public-in.pcap create mode 100644 src/program/vita/selftest-public-out.pcap create mode 100755 src/program/vita/selftest.snabb diff --git a/src/program/vita/selftest-pcaps.snabb b/src/program/vita/selftest-pcaps.snabb new file mode 100755 index 0000000000..17eb635160 --- /dev/null +++ b/src/program/vita/selftest-pcaps.snabb @@ -0,0 +1,315 @@ +#!snabb snsh + +-- Use of this source code is governed by the GNU AGPL license; see COPYING. + +local pcap = require("lib.pcap.pcap") +local ethernet = require("lib.protocol.ethernet") +local ipv4 = require("lib.protocol.ipv4") +local icmp = require("lib.protocol.icmp.header") +local esp = require("lib.ipsec.esp") +local datagram = require("lib.protocol.datagram") +local ffi = require("ffi") + +-- Synopsis: +-- +-- sudo selftest-pcaps.snabb +-- +-- Source selftest-*-in.pcap with packets that exercise various corner cases in +-- Vita. Anything that’s not the happy path. + +PcapLog = {} + +function PcapLog:new (filename) + local o = {} + o.file = io.open(filename, "w") + pcap.write_file_header(o.file) + return setmetatable(o, {__index=PcapLog}) +end + +function PcapLog:write (p) + pcap.write_record(self.file, p.data, p.length) +end + +local private = PcapLog:new("program/vita/selftest-private-in.pcap") +local public = PcapLog:new("program/vita/selftest-public-in.pcap") + +local private_src = ipv4:pton("192.168.0.1") +local private_dst = ipv4:pton("192.168.10.1") +local public_src = ipv4:pton("203.0.0.1") +local public_dst = ipv4:pton("203.0.113.1") +local remote_dst = ipv4:pton("192.168.10.2") + +function icmp4 (conf) + local payload = conf.payload or packet.from_string("0000Hello, World!") + local length = conf.payload_length or payload.length + local msg = datagram:new(payload) + local icm = icmp:new(conf.type, conf.code) + icm:checksum(msg:payload(), conf.payload_length or payload.length) + icm:header().checksum = conf.icmp_checksum or icm:header().checksum + msg:push(icm) + local ip4 = ipv4:new{ + flags = conf.flags, + frag_off = conf.frag_off, + total_length = ipv4:sizeof() + icmp:sizeof() + length, + ttl = conf.ttl or 64, + protocol = conf.protocol or 1, + src = conf.src, + dst = conf.dst + } + ip4:header().checksum = conf.ipv4_checksum or ip4:header().checksum + msg:push(ip4) + msg:push(ethernet:new{type=0x0800}) + return msg:packet() +end + +local sa = esp.encrypt:new{ + aead = "aes-gcm-16-icv", + spi = 1001, + key = "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + salt = "00 00 00 00" +} + +local sa_bad_spi = esp.encrypt:new{ + aead = "aes-gcm-16-icv", + spi = 0, + key = "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + salt = "00 00 00 00" +} + +local sa_replay = esp.encrypt:new{ + aead = "aes-gcm-16-icv", + spi = 1001, + key = "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + salt = "00 00 00 00" +} + +function encap4 (payload, conf) + payload = (conf.sa or sa):encapsulate_tunnel( + packet.shiftleft(payload, ethernet:sizeof()), + conf.nh or 4 + ) + local d = datagram:new(payload) + d:push(ipv4:new{ + flags = conf.flags, + frag_off = conf.frag_off, + total_length = ipv4:sizeof() + (conf.length or payload.length), + ttl = conf.ttl or 64, + protocol = esp.PROTOCOL, + src = conf.src, + dst = conf.dst + }) + d:push(ethernet:new{type=0x0800}) + return d:packet() +end + +-- Echo request +private:write(icmp4{ + type = 8, + src = private_src, + dst = private_dst +}) +public:write(icmp4{ + type = 8, + src = public_src, + dst = public_dst +}) +-- Broken echo request (too short) +private:write(icmp4{ + type = 8, + src = private_src, + dst = private_dst, + payload_length = 10000 +}) +-- Broken echo request (too long) +private:write(icmp4{ + type = 8, + src = private_src, + dst = private_dst, + payload_length = 4 +}) +-- Fragmented echo requests +private:write(icmp4{ + type = 8, + flags = 0x01, + src = private_src, + dst = private_dst +}) +private:write(icmp4{ + type = 8, + frag_off = 17, + src = private_src, + dst = private_dst +}) +-- Echo reply +private:write(icmp4{ + type = 0, + src = private_src, + dst = private_dst +}) +-- Encapsulated echo request +public:write(encap4( + icmp4{ + type = 8, + src = remote_dst, + dst = private_dst + }, + { + src = public_src, + dst = public_dst + } +)) +-- Unreachable protocol (private/public/inbound) +private:write(icmp4{ + protocol = 42, + src = private_src, + dst = private_dst +}) +public:write(icmp4{ + type = 8, + protocol = 42, + src = public_src, + dst = public_dst +}) +public:write(encap4( + icmp4{ + protocol = 42, + src = remote_dst, + dst = private_dst + }, + { + src = public_src, + dst = public_dst + } +)) +-- Fragmentation needed +private:write(icmp4{ + payload = packet.resize(packet.allocate(), 8000), + src = private_src, + dst = remote_dst +}) +private:write(icmp4{ + payload = packet.resize(packet.allocate(), 8000), + flags = 0x02, + src = private_src, + dst = remote_dst +}) +-- TTL expired (private/inbound) +private:write(icmp4{ + type = 8, + src = private_src, + dst = remote_dst, + ttl = 0 +}) +public:write(encap4( + icmp4{ + type = 8, + src = remote_dst, + dst = remote_dst, + ttl = 0 + }, + { + src = public_src, + dst = public_dst + } +)) +-- Reject ESP fragments +public:write(encap4( + icmp4{ + type = 8, + src = remote_dst, + dst = remote_dst + }, + { + src = public_src, + dst = public_dst, + flags = 0x01 + } +)) +public:write(encap4( + icmp4{ + type = 8, + src = remote_dst, + dst = remote_dst + }, + { + src = public_src, + dst = public_dst, + frag_off= 17 + } +)) +-- Bogus SPI +public:write(encap4( + icmp4{ + type = 8, + src = remote_dst, + dst = private_dst + }, + { + sa = sa_bad_spi, + src = public_src, + dst = public_dst + } +)) +-- Bogus SeqNo +public:write(encap4( + icmp4{ + type = 8, + src = remote_dst, + dst = private_dst + }, + { + sa = sa_replay, + src = public_src, + dst = public_dst + } +)) +-- Bogus NextHeader +public:write(encap4( + icmp4{ + type = 8, + src = remote_dst, + dst = private_dst + }, + { + src = public_src, + dst = public_dst, + nh = 42 + } +)) +-- Bogus checksums +private:write(icmp4{ + type = 8, + src = private_src, + dst = remote_dst, + ipv4_checksum = 42 +}) +private:write(icmp4{ + type = 8, + src = private_src, + dst = private_dst, + icmp_checksum = 42 +}) +-- Various ICMP messages +local payload = packet.from_string("....0123456789012345678901234567") +for _, msgtype in ipairs({ + {type=3, codes={0,1,2,3,4,5,100}}, + {type=11, codes={0,1,100}}, + {type=12, codes={0,100}}, + {type=4, codes={0, 100}}, + {type=5, codes={0,1,2,3,100}}, + {type=13, codes={0,100}}, + {type=14, codes={0,100}}, + {type=15, codes={0,100}}, + {type=16, codes={0,100}}, + {type=100, codes={0}} +}) do + for _, code in ipairs(msgtype.codes) do + private:write(icmp4{ + payload = packet.clone(payload), + type = msgtype.type, + code = code, + src = private_src, + dst = private_dst + }) + end +end diff --git a/src/program/vita/selftest-private-in.pcap b/src/program/vita/selftest-private-in.pcap new file mode 100644 index 0000000000000000000000000000000000000000..3c5f33e32fefb22ff24ddbafdcf90bf821a4732a GIT binary patch literal 19250 zcmeI)y-vbV7zW_)lr|yq>uv&0Dlv+`;KrhHby4ErbTkem7`y_#0ORIp+??H1eo7ol zCmp;GE&)$zPiR7_0sOZ2Bn_p3H}84cu#}$fkC!KdOwwa8AQJkGdMyQ8=n%zepJ>H9 zQW51iLeS=J6!Y*naT{+^ zc{Qoe*(No#nO8Pfqc&66j1v=`O->xd6#7-&=GT~u9Fez$hj+qrRiF6lD2Oq_pTIr@ zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV={vG$IyY3bBkA!>N zb;o4_0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Iz*5Qtipz<*?( z#JXMwz?f=r1N{Jhf%GHuGsT3LS}0KD;syt5vfdUR!xbCK-VwDbK|`+t5F@v}rbcmN zb8CBNw^ZI6-_t1*qzbpikZQP1hSZq*&X8)lZw#rH`^u0i-P8Y1O;DHN%1VeX!PX5Y^7v} lp=+Aj3|-T*&CoSNErzZc*<$FLr6xnytZXuLm2~-M*B=@z`;`Cy literal 0 HcmV?d00001 diff --git a/src/program/vita/selftest-private-out.pcap b/src/program/vita/selftest-private-out.pcap new file mode 100644 index 0000000000000000000000000000000000000000..36d7521b1bd4074e804df277f0ed73021ffc3687 GIT binary patch literal 725 zcmca|c+)~A1{MYw`2U}Qff2?5(&kVc6ao^2kQ@xI3=Enez60a?#se$37!RxfX^Ja0 z00NKHoSb|eh4B2MoD@ZnA`qaGF4;0DDQIxRr5 zK%GS}oy<&jAS)1l)OsHQ@*hYZ=0}h`2#{bZ%WrmI$bNBUkdFY`$6(JmF%W7h6T~b8 zNxZ4t3}!kYeX#goVAKc32iUDl91LOL_<)&2fD7mzJTZyiaX@1VVa8wv ZR zc%W;oD3tC?9+jz1xvsvrFjcccGeAoRZ1T_L2d?W=SGc#0caAY<6;TG0O zf5cb<7V%c!e7EmXw|%XTsQXu0;X@2+HlEYm;%}5CuRRqR&XC0XVO=*vD|1@Hm0$0= zzfUH{b_HMr2BSwH3oK1-3b?@C&385`GsHA*fn%e4dT!FvdWRFMzir7|^zE66z1arm z8Dc-?&fKZ=s8FbhGk)TsZ>zFDKHYvcv(S+k+ZhCb$tMC9fzaf`$_mx$m9th`ZoNU(F zkBf=29WC*K^^lc#mxjLUc;l_&p|h^}uSebe3*^{N{j5WB#${uJI=m&Z=UC>aY&MgW zUr(wE->faxG0tXRT-5#0hS|D%<+JZHwcCv^wfY~K`J(w_&Wo>U$8A$@-T8d{SL3#` FngF$xK4$;` literal 0 HcmV?d00001 diff --git a/src/program/vita/selftest-public-out.pcap b/src/program/vita/selftest-public-out.pcap new file mode 100644 index 0000000000000000000000000000000000000000..c32889c00b3476a852e33635b3786ecc9c7a0eb4 GIT binary patch literal 591 zcmca|c+)~A1{MYw`2U}Qff2?5(&kVc6ar-Y1C#$b7+e_`G(n0S7>h5SW+-Gl4b~J_ zYybovsX00MIttp;f#BWIRZogCPuT z1xOwQ@}L;Q)C8cJ3=T$zG9acF0)52%5~w&e6-a;pTbS;#vaMR{qwN3fZk?uK z{VC{3C1Xa&9J3=UrK9$_xadOJL!^-elI?zcxc(r%=`f{fq|h8$N*ss z+Y6w!N1)lBmIl@OrL1(!0EG7o!JFz5?R58L(frd4=G)tA#erGgoe@*CWj zHojrM_4Lx4zzmI;C0%(l(@(WXK3yg4Wqp{#@8r`7Cfhc7Uwi5RHIj7Kq^Co*UbxP( zPUrqf>sh^PXMS&zf190fYxyKrpZJ$z3wXXI&DPu8oqcKgpG9q->XifY> "..private.input) +config.link(c, private.output.." -> private_arp.south") +config.link(c, "public_arp.south -> "..public.input) +config.link(c, public.output.." -> public_arp.south") + +-- Loopback ESP traffic. +config.app(c, "public_out", basic_apps.Tee) +config.app(c, "join", basic_apps.Join) +config.app(c, "filter", filter.PcapFilter, {filter="ip proto esp"}) +config.link(c, "public_arp.north -> public_out.input") +config.link(c, "public_out.loopback -> filter.input") +config.link(c, "filter.output -> join.loopback") +config.link(c, "join.output -> public_arp.north") + +-- Add PCAP sources and sinks. +config.app(c, "private_pcap_in", pcap.PcapReader, + "program/vita/selftest-private-in.pcap") +config.app(c, "public_pcap_in", pcap.PcapReader, + "program/vita/selftest-public-in.pcap") +config.link(c, "private_pcap_in.output -> private_arp.north") +config.link(c, "public_pcap_in.output -> join.input") +if regenerate_pcaps then + -- Regenerate reference outputs. + config.app(c, "private_pcap_out", pcap.PcapWriter, + "program/vita/selftest-private-out.pcap") + config.app(c, "public_pcap_out", pcap.PcapWriter, + "program/vita/selftest-public-out.pcap") + config.link(c, "private_arp.north -> private_pcap_out.input") + config.link(c, "public_out.output -> public_pcap_out.input") +else + -- Match reference outputs. + config.app(c, "private_pcap_out", pcap.PcapReader, + "program/vita/selftest-private-out.pcap") + config.app(c, "public_pcap_out", pcap.PcapReader, + "program/vita/selftest-public-out.pcap") + config.app(c, "match_private", match.Match, {}) + config.link(c, "private_pcap_out.output -> match_private.comparator") + config.link(c, "private_arp.north -> match_private.rx") + config.app(c, "match_public", match.Match, {}) + config.link(c, "public_pcap_out.output -> match_public.comparator") + config.link(c, "public_out.output -> match_public.rx") +end + +engine.configure(c) + +-- Hack to avoid ESP seq# reuse because of packets from public_in.pcap +engine.app_table.ESP_loopback_outbound.sa.seq.no = 100 + +-- Run engine until its idle (all packets have been processed). +local last_frees = counter.read(engine.frees) +local function is_idle () + if counter.read(engine.frees) == last_frees then return true + else last_frees = counter.read(engine.frees) end +end +engine.main({done=is_idle}) + +if regenerate_pcaps then + -- Print final statistics. + engine.report_links() + for appname, app in pairs(engine.app_table) do + if app.shm then + print() + print(appname) + for name, _ in pairs(app.shm.specs) do + local value = counter.read(app.shm[name]) + if value > 0 then + print(("%00d %s"):format(tonumber(value), name)) + end + end + end + end +else + -- Assert application state is as expected. + if #engine.app_table.match_private:errors() > 0 then + engine.app_table.match_private:report() + main.exit(1) + end + if #engine.app_table.match_public:errors() > 0 then + engine.app_table.match_public:report() + main.exit(1) + end + for app, counters in pairs{ + PrivateRouter = { + rxerrors = 2, + mtu_errors = 2 + }, + PublicRouter = { + route_errors = 1, + }, + PrivateDispatch = { + rxerrors = 1, + checksum_errors = 1 + }, + PublicDispatch = { + rxerrors = 2, + fragment_errors = 2 + }, + DSP_loopback_inbound = { + rxerrors = 2, + protocol_errors = 1, + decrypt_errors = 1 + }, + PrivateICMP4 = { + rxerrors = 15, + protocol_errors = 3, + redirect = 5, + redirect_net = 1, + redirect_host = 1, + redirect_tos_net = 1, + redirect_tos_host = 1, + destination_unreachable = 7, + net_unreachable = 1, + protocol_unreachable = 1, + port_unreachable = 1, + host_unreachable = 1, + fragmentation_needed = 1, + source_route_failed = 1, + echo_request = 3, + time_exceeded = 3, + transit_ttl_exceeded = 1, + fragment_reassembly_time_exceeded = 1, + parameter_problem = 2, + type_not_implemented_errors = 12, + code_not_implemented_errors = 3 + }, + PublicICMP4 = { + echo_request = 1 + }, + InboundICMP4 = { + echo_request = 1 + } + } do + for name, should in pairs(counters) do + local actual = tonumber(counter.read(engine.app_table[app].shm[name])) + assert(should == actual, + name.." should be "..should.." but is "..actual) + end + end +end diff --git a/src/program/vita/vita.lua b/src/program/vita/vita.lua index 1593ed7612..b0b0995b31 100644 --- a/src/program/vita/vita.lua +++ b/src/program/vita/vita.lua @@ -311,8 +311,8 @@ end -- ephemeral_keys := { =(SA), ... } (see exchange) -function configure_esp (ephemeral_keys) - local c = config.new() +function configure_esp (ephemeral_keys, append) + local c = append or config.new() for id, sa in pairs(ephemeral_keys.sa) do -- Configure interlink receiver/transmitter for inbound SA @@ -330,8 +330,8 @@ function configure_esp (ephemeral_keys) return c end -function configure_dsp (ephemeral_keys) - local c = config.new() +function configure_dsp (ephemeral_keys, append) + local c = append or config.new() for id, sa in pairs(ephemeral_keys.sa) do -- Configure interlink receiver/transmitter for outbound SA From 0002a08cefc94de9024e1cb4971c2db799c50fa5 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Sat, 26 May 2018 15:01:25 +0200 Subject: [PATCH 19/21] .travis.yml: run vita selftest and conftest --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4d6da8357e..ad0efe0604 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,4 +9,6 @@ before_script: script: - make -j - - (cd src && sudo ./snabb snsh program/vita/test.snabb IMIX 1e6 3) + - (cd src && sudo program/vita/selftest.snabb) + - (cd src && sudo program/vita/conftest.snabb) + - (cd src && sudo program/vita/test.snabb IMIX 1e6 3) From 1d5c47406a47e3e376eedd2971844a64e26df4fe Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Mon, 28 May 2018 12:13:22 +0200 Subject: [PATCH 20/21] vita: do not hardcode key exchange protocol identifier in dispatch --- src/program/vita/dispatch.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/program/vita/dispatch.lua b/src/program/vita/dispatch.lua index 6bb6060918..dd82428dda 100644 --- a/src/program/vita/dispatch.lua +++ b/src/program/vita/dispatch.lua @@ -2,6 +2,7 @@ module(...,package.seeall) +local exchange = require("program.vita.exchange") local icmp = require("program.vita.icmp") local counter = require("core.counter") local ethernet = require("lib.protocol.ethernet") @@ -101,13 +102,13 @@ function PublicDispatch:new (conf) dispatch = pf_match.compile(([[match { ip[6:2] & 0x3FFF != 0 => reject_fragment ip proto esp => forward4 - ip proto 99 => protocol + ip proto %d => protocol ip dst host %s and icmp => icmp4 ip dst host %s => protocol4_unreachable ip => reject_protocol arp => arp otherwise => reject_ethertype - }]]):format(conf.node_ip4, conf.node_ip4)) + }]]):format(exchange.PROTOCOL, conf.node_ip4, conf.node_ip4)) } return setmetatable(o, {__index=PublicDispatch}) end From 48c33a962dfe1dc0f5f5b2568a7621227111fb46 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Fri, 1 Jun 2018 13:02:08 +0200 Subject: [PATCH 21/21] vita: swat performance regression by caching outputs via local The case in DecrementTTL was actually measurable, the case in PrivateRouter was changed preemptively. OTOH: there is a prevailing pattern in dispatch (and to some extent route) where the self.output table is accessed somewhat deep in the call chain, may make sense to evaluate the impact of this. --- src/program/vita/route.lua | 10 ++++++---- src/program/vita/ttl.lua | 6 ++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/program/vita/route.lua b/src/program/vita/route.lua index ef6b1bfdea..a6e3c65074 100644 --- a/src/program/vita/route.lua +++ b/src/program/vita/route.lua @@ -88,11 +88,13 @@ function PrivateRouter:route (p) end function PrivateRouter:push () - while not link.empty(self.input.input) do - self:route(link.receive(self.input.input)) + local input = self.input.input + while not link.empty(input) do + self:route(link.receive(input)) end - while not link.empty(self.input.control) do - self:route(link.receive(self.input.control)) + local control = self.input.control + while not link.empty(control) do + self:route(link.receive(control)) end end diff --git a/src/program/vita/ttl.lua b/src/program/vita/ttl.lua index 89f9ea87ee..96bf7cd705 100644 --- a/src/program/vita/ttl.lua +++ b/src/program/vita/ttl.lua @@ -17,15 +17,17 @@ function DecrementTTL:new () end function DecrementTTL:push () + local output = self.output.output + local time_exceeded = self.output.time_exceeded for _, input in ipairs(self.input) do while not link.empty(input) do local p = link.receive(input) local ip4 = self.ip4:new_from_mem(p.data, p.length) if ip4 and ip4:ttl() > 0 then ip4:ttl_decrement() - link.transmit(self.output.output, p) + link.transmit(output, p) elseif ip4 then - link.transmit(self.output.time_exceeded, p) + link.transmit(time_exceeded, p) else packet.free(p) counter.add(self.shm.protocol_errors)