From da22a6f02051b4b36752b96c0c4f78523dbca1fb Mon Sep 17 00:00:00 2001 From: Alexander Gall Date: Mon, 26 May 2014 16:10:26 +0200 Subject: [PATCH 01/14] Add capability to recycle objects to class.lua The creation of instance objects should be avoided in packet processing loops to reduce garbage collection overhead. class.lua now supports a simple mechanism where an app can declare an object to no longer be in use. This object is then put on a list in the corresponding class and will be re-used by the constructor method instead of allocating a new object. The class API has been changed slightly to no longer require the declaration of constructor methods. Any method can act as a constructor or extend the standard constructor by calling the new() method of the super class. --- src/lib/lua/class.lua | 135 ++++++++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 59 deletions(-) diff --git a/src/lib/lua/class.lua b/src/lib/lua/class.lua index 69d01321f7..cd10c26e8a 100644 --- a/src/lib/lua/class.lua +++ b/src/lib/lua/class.lua @@ -1,74 +1,91 @@ --- Support for basic OO programming. Copied from --- http://lua-users.org/wiki/InheritanceTutorial with a few --- modifications. +-- Support for basic OO programming. Apart from the usual +-- incantations of setmetatable(), it implements a simple mechanism to +-- avoid table allocations by recycling objects that have been +-- explicitely declared to no longer be in use. +-- +-- All objects are descendants from a simple "elementary class" that +-- implements the basic functionality for the creation and recycling +-- of instance objects through the new() and free() methods. -- -- Usage: --- local require("lib.lua.class") --- local myclass = subClass(baseClass | nil, [constructor_method, ...]) +-- local require("lib.lua.class") +-- local baseClass = require("baseClass") +-- local myclass = subClass(baseClass) +-- local instance = myclass:new() +-- instance:free() +-- +-- If baseClass is nil, myclass will be a direct descendant of +-- elementaryClass +-- +-- The basic constructor new() either allocates a new instance or +-- re-uses one that has been put on the class's freelist by a previous +-- call of the free() instance method. -- --- myclass inherits all public methods and class variables from --- baseClass as well as all constructor methods. The default --- constructor method is called new(). Each constructor executes the --- method called _init_ if it exists, where --- is the name of the constructor method. All arguments supplied to --- the constructor are passed unmodified to the corresponding _init --- method. +-- Calls to methods of the super class must use the 'dot' notation and +-- pass the object as argument itself, e.g. -- -function subClass (baseClass, ... ) - local new_class = {} - local class_mt = {__index = new_class} - local constructors = {...} +-- local myclass = subClass(someClass) +-- function myclass:method(...) +-- myclass:superClass().method(self, ...) +-- -- Customization goes here +-- end +-- +-- Note that the superClass method must be called with reference to +-- the class in which the function is defined. Using +-- self:superClass() would create a loop if the method itself was +-- called from a derived class. + +local elementaryClass = {} +elementaryClass._name = "elementary class" - if baseClass ~= nil then - setmetatable(new_class, {__index = baseClass}) - for c, _ in pairs(baseClass.constructors) do - new_class.constructors[c] = true - end +-- Class methods + +-- Create a new instance of a class or re-use one from the free list. +-- A recycled object has its instance variable _recycled set to true. +-- A class can use this, for example, to perform clean-up on such an +-- object before re-use. +function elementaryClass:new () + assert(self ~= elementaryClass, "Can't instantiate abstract class elementaryClass") + local instance + local freelist = self._freelist + local index = freelist.index + if index > 0 then + instance = freelist.list[index] + instance._recycled = true + freelist.index = index - 1 else - new_class.constructors = {new = true} - end - for _, c in ipairs(constructors) do - assert(not new_class.constructors[c], - "duplicate declaration of constructor method "..c) - new_class.constructors[c] = true + instance = { _recycled = false } + setmetatable(instance, { __index = self }) end + return instance +end - for c, _ in pairs(new_class.constructors) do - new_class[c] = - function (self, ...) - local newinst = {} - setmetatable(newinst, class_mt) - if newinst['_init_'..c] then - newinst['_init_'..c](newinst, ...) - end - return newinst - end - end +-- Instance methods - -- Return the class object of the instance - function new_class:class () - return new_class - end +function elementaryClass:name() + return self._name or nil +end - -- Return the super class object of the instance - function new_class:superClass () - return baseClass - end +-- Put an instance on the free list for recycling +function elementaryClass:free () + local freelist = self:class()._freelist + local index = freelist.index + 1 + freelist.list[index] = self + freelist.index = index +end + +function subClass (baseClass) + local baseClass = baseClass or elementaryClass + local class = { _freelist = { index = 0, list = {} } } + setmetatable(class, { __index = baseClass }) - -- Return true if the caller is an instance of theClass - function new_class:isa (theClass) - local b_isa = false - local cur_class = new_class + function class:class () + return class + end - while (cur_class ~= nil) and (b_isa == false) do - if cur_class == theClass then - b_isa = true - else - cur_class = cur_class:superClass() - end - end - return b_isa + function class:superClass () + return baseClass end - return new_class + return class end From 97241c2a2eaaa74f977c24a07c30076c37b07c9c Mon Sep 17 00:00:00 2001 From: Alexander Gall Date: Mon, 26 May 2014 17:00:52 +0200 Subject: [PATCH 02/14] Adapt lib/protocol and apps/vpn to the new class.lua semantics Also includes some minor bug fixes here and there. --- src/apps/ipv6/ns_responder.lua | 23 ++++--- src/apps/vpn/vpws.lua | 12 ++-- src/lib/protocol/datagram.lua | 68 +++++++++++++------- src/lib/protocol/ethernet.lua | 13 ++-- src/lib/protocol/gre.lua | 69 ++++++++++++--------- src/lib/protocol/header.lua | 68 +++++++++++++------- src/lib/protocol/icmp/header.lua | 11 ++-- src/lib/protocol/icmp/nd/header.lua | 1 + src/lib/protocol/icmp/nd/na.lua | 16 ++--- src/lib/protocol/icmp/nd/ns.lua | 8 ++- src/lib/protocol/icmp/nd/options/lladdr.lua | 9 ++- src/lib/protocol/icmp/nd/options/tlv.lua | 19 +++--- src/lib/protocol/ipv4.lua | 33 +++++----- src/lib/protocol/ipv6.lua | 21 ++++--- src/lib/protocol/tcp.lua | 41 ++++++------ src/lib/protocol/udp.lua | 15 ++--- 16 files changed, 255 insertions(+), 172 deletions(-) diff --git a/src/apps/ipv6/ns_responder.lua b/src/apps/ipv6/ns_responder.lua index aa1f5a93f4..d8f5ebc815 100644 --- a/src/apps/ipv6/ns_responder.lua +++ b/src/apps/ipv6/ns_responder.lua @@ -4,6 +4,7 @@ -- on which NS messages are expected. Non-NS packets are sent on -- north. All packets received on the north port are passed south. +module(..., package.seeall) local ffi = require("ffi") local app = require("core.app") local link = require("core.link") @@ -15,16 +16,19 @@ local icmp = require("lib.protocol.icmp.header") local ns = require("lib.protocol.icmp.nd.ns") local ns_responder = subClass(nil) +ns_responder._name = "ipv6 neighbor solicitation responder" -function ns_responder:_init_new(config) - self._config = config - self._match = { { ethernet }, - { ipv6 }, - { icmp }, - { ns, - function(ns) - return(ns:target_eq(config.local_ip)) - end } } +function ns_responder:new(config) + local o = ns_responder:superClass().new(self) + o._config = config + o._match = { { ethernet }, + { ipv6 }, + { icmp }, + { ns, + function(ns) + return(ns:target_eq(config.local_ip)) + end } } + return o end local function process(self, dgram) @@ -88,6 +92,7 @@ function ns_responder:push() -- Send transit traffic up north link.transmit(l_out, p) end + datagram:free() end end diff --git a/src/apps/vpn/vpws.lua b/src/apps/vpn/vpws.lua index d74e8cbbe3..7da7c26920 100644 --- a/src/apps/vpn/vpws.lua +++ b/src/apps/vpn/vpws.lua @@ -6,6 +6,7 @@ -- frames encapsulated in IP/GRE. The push() method performs the -- appropriate operation depending on the input port. +module(..., package.seeall) local ffi = require("ffi") local C = ffi.C local lib = require("core.lib") @@ -20,15 +21,16 @@ local packet = require("core.packet") local vpws = subClass(nil) local in_to_out = { customer = 'uplink', uplink = 'customer' } -function vpws:_init_new(config) - self._config = config - self._encap = { +function vpws:new(config) + local o = vpws:superClass().new(self) + o._config = config + o._encap = { ether = ethernet:new({ src = config.local_mac, dst = config.remote_mac, type = 0x86dd }), ipv6 = ipv6:new({ next_header = 47, hop_limit = 64, src = config.local_vpn_ip, dst = config.remote_vpn_ip}), gre = gre:new({ protocol = 0x6558, key = config.label }) } - self._match = { { ethernet }, + o._match = { { ethernet }, { ipv6, function(ipv6) return(ipv6:dst_eq(config.local_vpn_ip)) @@ -37,6 +39,7 @@ function vpws:_init_new(config) function(gre) return(not gre:use_key() or gre:key() == config.label) end } } + return o end function vpws:name() @@ -77,6 +80,7 @@ function vpws:push() end end if p then link.transmit(l_out, p) end + datagram:free() end end end diff --git a/src/lib/protocol/datagram.lua b/src/lib/protocol/datagram.lua index 4a3b6c39e6..90fcc79efd 100644 --- a/src/lib/protocol/datagram.lua +++ b/src/lib/protocol/datagram.lua @@ -40,6 +40,7 @@ -- Another new buffer is allocated for the packet's payload. The -- parse() method is not applicable to such a datagram. +module(..., package.seeall) local packet = require("core.packet") local buffer = require("core.buffer") local ffi = require("ffi") @@ -53,19 +54,30 @@ local datagram = subClass(nil) -- initialized for it. If p == nil, a new empty packet is allocated. -- The allocation of buffers is delayed until the first call of the -- push() method. -function datagram:_init_new (p, class) +function datagram:new (p, class) + local o = datagram:superClass().new(self) if p then packet.coalesce(p) - self._packet = p + o._packet = p else - self._packet = packet.allocate() + o._packet = packet.allocate() local b = buffer.allocate() - packet.add_iovec(self._packet, b, 0) + packet.add_iovec(o._packet, b, 0) end - self._parse = { stack = {}, - ulp = class, - iovec = 0, - offset = 0 } + if not o._recycled then + o._parse = { stack = {}, index = 0 } + else + for i, _ in ipairs(o._parse.stack) do + o._parse.stack[i]:free() + o._parse.stack[i] = nil + end + o._parse.index = 0 + o._push = nil + end + o._parse.ulp = class + o._parse.iovec = 0 + o._parse.offset = 0 + return o end -- Instance methods @@ -138,7 +150,9 @@ function datagram:parse (seq) if proto == nil or (check and not check(proto)) then return nil end - table.insert(parse.stack, proto) + local index = parse.index + 1 + parse.stack[index] = proto + parse.index = index parse.ulp = proto:upper_layer() parse.offset = parse.offset + proto:sizeof() end @@ -151,8 +165,11 @@ function datagram:unparse (n) assert(self._parse, "non-parseable datagram") local parse = self._parse local proto - while n > 0 and #parse.stack ~= 0 do - proto = table.remove(parse.stack) + while n > 0 and parse.index ~= 0 do + -- Don't use table.remove to avoid garbage + proto = parse.stack[parse.index] + parse.index = parse.index - 1 + proto:free() parse.offset = parse.offset - proto:sizeof() parse.ulp = proto:class() n = n - 1 @@ -160,22 +177,29 @@ function datagram:unparse (n) end -- Remove the bottom n elements from the parse stack by adjusting the --- offset of the relevant iovec. Returns the last popped protocol --- object. +-- offset of the relevant iovec. function datagram:pop (n) local parse = self._parse assert(parse, "non-parseable datagram") + assert(n <= parse.index) local proto local iovec = self._packet.iovecs[parse.iovec] - while n > 0 and #parse.stack ~= 0 do - proto = table.remove(parse.stack, 1) - local sizeof = proto:sizeof() - iovec.offset = iovec.offset + sizeof - iovec.length = iovec.length - sizeof - self._packet.length = self._packet.length - sizeof - n = n - 1 - end - return proto + -- Don't use table.remove to avoid garbage + for i = 1, parse.index do + if i <= n then + proto = parse.stack[i] + local sizeof = proto:sizeof() + proto:free() + iovec.offset = iovec.offset + sizeof + iovec.length = iovec.length - sizeof + self._packet.length = self._packet.length - sizeof + end + if i+n <= parse.index then + parse.stack[i] = parse.stack[i+n] + else + parse.stack[i] = nil + end + end end function datagram:stack () diff --git a/src/lib/protocol/ethernet.lua b/src/lib/protocol/ethernet.lua index 4d9e8fd8dd..399839d888 100644 --- a/src/lib/protocol/ethernet.lua +++ b/src/lib/protocol/ethernet.lua @@ -1,3 +1,4 @@ +module(..., package.seeall) local ffi = require("ffi") local C = ffi.C local header = require("lib.protocol.header") @@ -26,12 +27,12 @@ ethernet._ulp = { -- Class methods -function ethernet:_init_new (config) - local header = ether_header_t() - ffi.copy(header.ether_dhost, config.dst, 6) - ffi.copy(header.ether_shost, config.src, 6) - header.ether_type = C.htons(config.type) - self._header = header +function ethernet:new (config) + local o = ethernet:superClass().new(self) + o:dst(config.dst) + o:src(config.src) + o:type(config.type) + return o end -- Convert printable address to numeric diff --git a/src/lib/protocol/gre.lua b/src/lib/protocol/gre.lua index 64608ac0d1..aeb0a90ad8 100644 --- a/src/lib/protocol/gre.lua +++ b/src/lib/protocol/gre.lua @@ -1,3 +1,4 @@ +module(..., package.seeall) local ffi = require("ffi") local C = ffi.C local header = require("lib.protocol.header") @@ -37,57 +38,67 @@ gre._ulp = { -- Class methods -function gre:_init_new (config) +function gre:new (config) + local o = gre:superClass().new(self) local opt_size = 0 if config.checksum then opt_size = opt_size + 4 - self._checksum = true + o._checksum = true end if config.key ~= nil then - self._key_offset = opt_size + o._key_offset = opt_size opt_size = opt_size + 4 end - self._header_type = gre_types[opt_size] - self._header_ptr_type = gre_ptr_types[opt_size] - self._header = self._header_type() - if self._checksum then - lib.bitfield(16, self._header, 'bits', 0, 1, 1) + if opt_size > 0 then + o._header_type = gre_types[opt_size] + o._header_ptr_type = gre_ptr_types[opt_size] + o._header = o._header_type() + end + if o._checksum then + lib.bitfield(16, o._header, 'bits', 0, 1, 1) end - if self._key_offset ~= nil then - lib.bitfield(16, self._header, 'bits', 2, 1, 1) - self:key(config.key) + if o._key_offset ~= nil then + lib.bitfield(16, o._header, 'bits', 2, 1, 1) + o:key(config.key) end - self:protocol(config.protocol) + o:protocol(config.protocol) + return o end -function gre:_init_new_from_mem (mem, size) - local sizeof = ffi.sizeof(gre._header_type) - assert(sizeof <= size) - local header = ffi.cast(gre._header_ptr_type, mem)[0] +function gre:new_from_mem (mem, size) + local o = gre:superClass().new_from_mem(self, mem, size) -- Reserved bits and version MUST be zero - if lib.bitfield(16, header, 'bits', 4, 12) ~= 0 then - self = nil - return + if lib.bitfield(16, o._header, 'bits', 4, 12) ~= 0 then + o:free() + return nil end - self._header = header local opt_size = 0 - if self:use_checksum() then + if o:use_checksum() then opt_size = opt_size + 4 - self._checksum = true + o._checksum = true end - if self:use_key() then - self._key_offset = opt_size + if o:use_key() then + o._key_offset = opt_size opt_size = opt_size + 4 end if opt_size > 0 then - self._header_type = gre_types[opt_size] - self._header_ptr_type = gre_ptr_types[opt_size] - self._header = ffi.cast(self._header_ptr_type, self._header)[0] + o._header_type = gre_types[opt_size] + o._header_ptr_type = gre_ptr_types[opt_size] + o._header = ffi.cast(o._header_ptr_type, self._header)[0] end + return o end -- Instance methods +function gre:free () + -- Make sure that this object uses the base header from the gre + -- class when it is being recycled + self._header_type = nil + self._header_ptr_type = nil + gre:superClass().free(self) +end + function gre:checksum (payload, length) assert(self._checksum) local csum_ptr = ffi.cast(ffi.typeof("uint16_t *"), @@ -103,7 +114,9 @@ function gre:use_checksum () end function gre:key (key) - assert(self._key_offset) + if not self._key_offset then + return nil + end local key_ptr = ffi.cast(ffi.typeof("uint32_t *"), ffi.cast(ffi.typeof("uint8_t*"), self._header) + ffi.offsetof(self._header, 'options') diff --git a/src/lib/protocol/header.lua b/src/lib/protocol/header.lua index 908ceccde7..e63d133c16 100644 --- a/src/lib/protocol/header.lua +++ b/src/lib/protocol/header.lua @@ -51,43 +51,67 @@ -- only the type value 0x86dd is defined, which is mapped to the class -- that handles IPv6 headers. -- --- The initializer for the standard constructor new() will typically --- allocate an instance of _header_type and initialize it, e.g. +-- Header-dependent initializations can be handled by overriding the +-- standard constructor, e.g. -- --- function ethernet:_init_new (config) --- local header = ether_header_t() --- ffi.copy(header.ether_dhost, config.dst, 6) --- ffi.copy(header.ether_shost, config.src, 6) --- header.ether_type = C.htons(config.type) --- self._header = header +-- function ethernet:new (config) +-- local o = ethernet:superClass().new(self) +-- o:dst(config.dst) +-- o:src(config.src) +-- o:type(config.type) +-- return o -- end -- --- The header class provides an additional constructor called --- new_from_mem() that interprets a chunk of memory as a protocol --- header using ffi.cast(). A header that requires more sophisticated --- initialization (e.g. variably-sized headers whose actual size --- depends on the contents on the header) must over ride the --- _init_new_from_mem() method. +-- Protocol headers with a variable format can be handled with a +-- little extra work as follows. +-- +-- The class for such a protocl defines just the alternative that can +-- be considered to be the "fundamental" header. It must be +-- sufficient for the new_from_mem() method to determine the actual +-- header. +-- +-- The standard constructors will initialize the header instance with +-- the fundamental type. The protocol class must override both +-- constructor methods to determine the actual header, either from the +-- configuration or the chunk of memory for the new() and +-- new_from_mem() methods, respectively. The important part is that +-- the constructors must override the _header* class variables in the +-- header instance. See lib/protocol/gre.lua for an example of how +-- this can look like. +module(..., package.seeall) local ffi = require("ffi") -local header = subClass(nil, 'new_from_mem') +local header = subClass(nil) + +-- Class methods + +-- The standard constructor creates a new ctype object for the header. +function header:new () + local o = header:superClass().new(self) + o._header = self._header_type() + return o +end -function header:_init_new_from_mem (mem, size) +-- This alternative constructor creates a protocol header from a chunk +-- of memory by "overlaying" a header structure. +function header:new_from_mem (mem, size) + local o = header:superClass().new(self) + -- Using the class variables here does the right thing even if the + -- instance is recycled assert(ffi.sizeof(self._header_type) <= size) - self._header = ffi.cast(self._header_ptr_type, mem)[0] + o._header = ffi.cast(self._header_ptr_type, mem)[0] + return o end +-- Instance methods + function header:header () return self._header end -function header:name () - return self._name -end - function header:sizeof () - return ffi.sizeof(self._header) + return ffi.sizeof(self._header_type) end -- default equality method, can be overriden in the ancestors diff --git a/src/lib/protocol/icmp/header.lua b/src/lib/protocol/icmp/header.lua index 3db979ad23..262b28ca5b 100644 --- a/src/lib/protocol/icmp/header.lua +++ b/src/lib/protocol/icmp/header.lua @@ -1,3 +1,4 @@ +module(..., package.seeall) local ffi = require("ffi") local C = ffi.C local header = require("lib.protocol.header") @@ -28,11 +29,11 @@ icmp._ulp = { -- Class methods -function icmp:_init_new (type, code) - local header = icmp_t() - self._header = header - header.type = type - header.code = code +function icmp:new (type, code) + local o = icmp:superClass().new(self) + o:type(type) + o:code(code) + return o end -- Instance methods diff --git a/src/lib/protocol/icmp/nd/header.lua b/src/lib/protocol/icmp/nd/header.lua index 5799164de6..64bcb0c5f8 100644 --- a/src/lib/protocol/icmp/nd/header.lua +++ b/src/lib/protocol/icmp/nd/header.lua @@ -1,3 +1,4 @@ +module(..., package.seeall) local ffi = require("ffi") local proto_header = require("lib.protocol.header") local tlv = require("lib.protocol.icmp.nd.options.tlv") diff --git a/src/lib/protocol/icmp/nd/na.lua b/src/lib/protocol/icmp/nd/na.lua index 05e4a48e06..5fa389b47b 100644 --- a/src/lib/protocol/icmp/nd/na.lua +++ b/src/lib/protocol/icmp/nd/na.lua @@ -1,3 +1,4 @@ +module(..., package.seeall) local ffi = require("ffi") local C = ffi.C local nd_header = require("lib.protocol.icmp.nd.header") @@ -22,7 +23,7 @@ else override:1, reserved:29; uint8_t target[16]; - } + } __attribute__((packed)) ]] end @@ -36,12 +37,13 @@ na._ulp = { method = nil } -- Class methods -function na:_init_new (target, router, solicited, override) - self._header = na_t() - ffi.copy(self._header.target, target, 16) - self._header.router = router - self._header.solicited = solicited - self._header.override = override +function na:new (target, router, solicited, override) + local o = na:superClass().new(self) + o:target(target) + o:router(router) + o:solicited(solicited) + o:override(override) + return o end -- Instance methods diff --git a/src/lib/protocol/icmp/nd/ns.lua b/src/lib/protocol/icmp/nd/ns.lua index d7b31a5381..849e5c0838 100644 --- a/src/lib/protocol/icmp/nd/ns.lua +++ b/src/lib/protocol/icmp/nd/ns.lua @@ -1,3 +1,4 @@ +module(..., package.seeall) local ffi = require("ffi") local C = ffi.C local nd_header = require("lib.protocol.icmp.nd.header") @@ -19,9 +20,10 @@ ns._ulp = { method = nil } -- Class methods -function ns:_init_new (target) - self._header = ns_t() - ffi.copy(self._header.target, target, 16) +function ns:new (target) + local o = ns:superClass().new(self) + o:target(target) + return o end -- Instance methods diff --git a/src/lib/protocol/icmp/nd/options/lladdr.lua b/src/lib/protocol/icmp/nd/options/lladdr.lua index 58c2a1057b..b0650ee359 100644 --- a/src/lib/protocol/icmp/nd/options/lladdr.lua +++ b/src/lib/protocol/icmp/nd/options/lladdr.lua @@ -1,6 +1,7 @@ +module(..., package.seeall) local ffi = require("ffi") -local lladdr = subClass(nil, 'new_from_mem') +local lladdr = subClass(nil) local lladdr_t = ffi.typeof[[ struct { @@ -12,9 +13,11 @@ local lladdr_t = ffi.typeof[[ lladdr._name = 'll_addr' -- Class methods -function lladdr:_init_new_from_mem (mem, size) +function lladdr:new_from_mem (mem, size) + local o = lladdr:superClass().new(self) assert(size >= ffi.sizeof(lladdr_t)) - self._lladdr = ffi.cast(ffi.typeof("$ *", lladdr_t), mem) + o._lladdr = ffi.cast(ffi.typeof("$ *", lladdr_t), mem) + return o end -- Instance methods diff --git a/src/lib/protocol/icmp/nd/options/tlv.lua b/src/lib/protocol/icmp/nd/options/tlv.lua index c1dca2871f..dfad5a90bf 100644 --- a/src/lib/protocol/icmp/nd/options/tlv.lua +++ b/src/lib/protocol/icmp/nd/options/tlv.lua @@ -1,6 +1,7 @@ +module(..., package.seeall) local ffi = require("ffi") -local tlv = subClass(nil, 'new_from_mem') +local tlv = subClass(nil) local tlv_t = ffi.typeof[[ struct { @@ -23,22 +24,20 @@ tlv._types = { -- Will be overriden for known types tlv._name = "unkown" -function tlv:_init_new (type) -end - -function tlv:_init_new_from_mem (mem, size) +function tlv:new_from_mem (mem, size) + local o = tlv:superClass().new(self) local tlv_t_size = ffi.sizeof(tlv_t) assert(tlv_t_size <= size) local tlv = ffi.cast(ffi.typeof("$ *", tlv_t), mem) - self._tlv = tlv - local class = self._types[tlv.type].class + o._tlv = tlv + local class = o._types[tlv.type].class if class ~= nil then - self._option = + o._option = require(class):new_from_mem(mem + tlv_t_size, size - tlv_t_size) - self._name = self._types[tlv.type].name + o._name = o._types[tlv.type].name end - return tlv + return o end function tlv:name () diff --git a/src/lib/protocol/ipv4.lua b/src/lib/protocol/ipv4.lua index 2d43fa3910..ef76308587 100644 --- a/src/lib/protocol/ipv4.lua +++ b/src/lib/protocol/ipv4.lua @@ -1,3 +1,4 @@ +module(..., package.seeall) local ffi = require("ffi") local C = ffi.C local lib = require("core.lib") @@ -49,22 +50,22 @@ ipv4._ulp = { -- Class methods -function ipv4:_init_new (config) - local header = ipv4hdr_t() - header.ihl_v_tos = C.htonl(0x4000) -- v4 - self._header = header - self:ihl(self:sizeof() / 4) - self:dscp(config.dscp or 0) - self:ecn(config.ecn or 0) - self:total_length(self:sizeof()) -- default to header only - self:id(config.id or 0) - self:flags(config.flags or 0) - self:frag_off(config.frag_off or 0) - self:ttl(config.ttl or 0) - self:protocol(config.protocol or 0xff) - self:src(config.src) - self:dst(config.dst) - self:checksum() +function ipv4:new (config) + local o = ipv4:superClass().new(self) + o._header.ihl_v_tos = C.htonl(0x4000) -- v4 + 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:id(config.id or 0) + o:flags(config.flags or 0) + o:frag_off(config.frag_off or 0) + o:ttl(config.ttl or 0) + o:protocol(config.protocol or 0xff) + o:src(config.src) + o:dst(config.dst) + o:checksum() + return o end function ipv4:pton (p) diff --git a/src/lib/protocol/ipv6.lua b/src/lib/protocol/ipv6.lua index 466ee628d4..32e7d2b62f 100644 --- a/src/lib/protocol/ipv6.lua +++ b/src/lib/protocol/ipv6.lua @@ -1,3 +1,4 @@ +module(..., package.seeall) local ffi = require("ffi") local C = ffi.C local lib = require("core.lib") @@ -43,16 +44,16 @@ ipv6._ulp = { -- Class methods -function ipv6:_init_new (config) - local header = ipv6hdr_t() - header.v_tc_fl = C.htonl(0x60000000) - ffi.copy(header.src_ip, config.src, 16) - ffi.copy(header.dst_ip, config.dst, 16) - self._header = header - self:traffic_class(config.traffic_class) - self:flow_label(config.flow_label) - self:next_header(config.next_header) - self:hop_limit(config.hop_limit) +function ipv6:new (config) + local o = ipv6:superClass().new(self) + o:version(6) + o:traffic_class(config.traffic_class) + o:flow_label(config.flow_label) + o:next_header(config.next_header) + o:hop_limit(config.hop_limit) + o:src(config.src) + o:dst(config.dst) + return o end -- XXX should probably use inet_pton(3) diff --git a/src/lib/protocol/tcp.lua b/src/lib/protocol/tcp.lua index 8f0e0083b5..c29d5c3999 100644 --- a/src/lib/protocol/tcp.lua +++ b/src/lib/protocol/tcp.lua @@ -1,3 +1,4 @@ +module(..., package.seeall) local ffi = require("ffi") local C = ffi.C local lib = require("core.lib") @@ -26,26 +27,26 @@ tcp._ulp = { method = nil } -- Class methods -function tcp:_init_new (config) - local header = tcp_header_t() - header.src_port = C.htons(config.src_port) - header.dst_port = C.htons(config.dst_port) - header.seq = C.htonl(config.seq) - header.ack = C.htonl(config.ack) - header.window_size = C.htons(config.window_size) - header.pad = 0 - self._header = header - self:offset(config.offset or 0) - self:ns(config.ns or 0) - self:cwr(config.cwr or 0) - self:ece(config.ece or 0) - self:urg(config.urg or 0) - self:ack(config.ack or 0) - self:psh(config.psh or 0) - self:rst(config.rst or 0) - self:syn(config.syn or 0) - self:fin(config.fin or 0) - self:checksum() +function tcp:new (config) + local o tcp:superClass().new(self) + o:src_port(config.src_port) + o:dst_port(config.dst_port) + o:seq_num(config.seq) + o:ack_num(config.ack) + o:window_size(config.window_size) + o._header.pad = 0 + o:offset(config.offset or 0) + o:ns(config.ns or 0) + o:cwr(config.cwr or 0) + o:ece(config.ece or 0) + o:urg(config.urg or 0) + o:ack(config.ack or 0) + o:psh(config.psh or 0) + o:rst(config.rst or 0) + o:syn(config.syn or 0) + o:fin(config.fin or 0) + o:checksum() + return o end -- Instance methods diff --git a/src/lib/protocol/udp.lua b/src/lib/protocol/udp.lua index d454d9c34a..5096e4c275 100644 --- a/src/lib/protocol/udp.lua +++ b/src/lib/protocol/udp.lua @@ -1,3 +1,4 @@ +module(..., package.seeall) local ffi = require("ffi") local C = ffi.C local header = require("lib.protocol.header") @@ -21,13 +22,13 @@ udp._ulp = { method = nil } -- Class methods -function udp:_init_new (config) - local header = udp_header_t() - header.src_port = C.htons(config.src_port) - header.dst_port = C.htons(config.dst_port) - header.len = 0 - header.checksum = 0 - self._header = header +function udp:new (config) + local o = udp:superClass().new(self) + o:src_por(tconfig.src_port) + o:dst_port(config.dst_port) + o:length(0) + o._header.checksum = 0 + return o end -- Instance methods From c6da96892736d45d398716e6d318f3a8c5b8ee9c Mon Sep 17 00:00:00 2001 From: Alexander Gall Date: Mon, 26 May 2014 17:15:51 +0200 Subject: [PATCH 03/14] Fix bug in ipv6:ntop() The conversion from numeric to printable for ipv6 addresses was seriously broken. The fix includes a redefinition of the source and destination address in the ipv6 header as uint8_t. --- src/lib/protocol/ipv6.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lib/protocol/ipv6.lua b/src/lib/protocol/ipv6.lua index 32e7d2b62f..54982f9cb3 100644 --- a/src/lib/protocol/ipv6.lua +++ b/src/lib/protocol/ipv6.lua @@ -9,9 +9,9 @@ local ipv6hdr_t = ffi.typeof[[ uint32_t v_tc_fl; // version, tc, flow_label uint16_t payload_length; uint8_t next_header; - uint8_t hop_limit; - char src_ip[16]; - char dst_ip[16]; + uint8_t hop_limit; + uint8_t src_ip[16]; + uint8_t dst_ip[16]; } __attribute__((packed)) ]] @@ -75,8 +75,9 @@ end -- XXX should probably use inet_ntop(3) function ipv6:ntop (n) local p = {} - for i = 0, 7, 1 do - table.insert(p, string.format("%x", C.ntohs(n[i]))) + local n = ffi.cast("uint8_t *", n) + for i = 0, 14, 2 do + table.insert(p, string.format("%02x%02x", n[i], n[i+1])) end return table.concat(p, ":") end From 0a6c926b3e0b2ac17d7e3780e7422bd7345cef62 Mon Sep 17 00:00:00 2001 From: Alexander Gall Date: Mon, 26 May 2014 17:19:32 +0200 Subject: [PATCH 04/14] Add a simple interface to libpcap's packet filter lib/pcap/filter.lua provides a facility to compile an arbitrary libpcap filter expression and apply it to a data packet for efficient classification. --- .travis.yml | 2 +- src/Makefile | 4 ++-- src/lib/pcap/filter.h | 17 +++++++++++++++++ src/lib/pcap/filter.lua | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 src/lib/pcap/filter.h create mode 100644 src/lib/pcap/filter.lua diff --git a/.travis.yml b/.travis.yml index 71d4f7d7e6..6c21d68b6c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: c compiler: - gcc -before_install: "sudo apt-get update && sudo apt-get install -y linux-libc-dev" +before_install: "sudo apt-get update && sudo apt-get install -y linux-libc-dev libpcap-dev" script: - make && cd src && sudo make test_ci; diff --git a/src/Makefile b/src/Makefile index a6e1975c76..1d2b7dc300 100644 --- a/src/Makefile +++ b/src/Makefile @@ -42,9 +42,9 @@ markdown: $(RMOBJS) snabb: $(LUAOBJ) $(HOBJ) $(COBJ) $(ASMOBJ) $(E) "LINK $@" - $(Q) gcc -Wl,-E -Werror -Wall -o $@ $^ \ + $(Q) gcc -Wl,--no-as-needed -Wl,-E -Werror -Wall -o $@ $^ \ ../deps/luajit/src/libluajit.a \ - -lc -ldl -lm -lrt -lpthread + -lc -ldl -lm -lrt -lpthread -lpcap @echo -n "Firmware: " @ln -fs snabb snabbswitch @ls -sh snabb diff --git a/src/lib/pcap/filter.h b/src/lib/pcap/filter.h new file mode 100644 index 0000000000..8f3c337722 --- /dev/null +++ b/src/lib/pcap/filter.h @@ -0,0 +1,17 @@ +struct bpf_program { + uint32_t bf_len; + void *bf_insns; +}; + +struct pcap_pkthdr { + /* record header */ + uint64_t ts_sec; /* timestamp seconds */ + uint64_t ts_usec; /* timestamp microseconds */ + uint32_t incl_len; /* number of octets of packet saved in file */ + uint32_t orig_len; /* actual length of packet */ +}; + +void *pcap_open_dead(int, int); +int pcap_compile(void *, struct bpf_program *, char *, int, uint32_t); +int pcap_offline_filter(struct bpf_program *, struct pcap_pkthdr *, char *); +char * pcap_geterr(void *); diff --git a/src/lib/pcap/filter.lua b/src/lib/pcap/filter.lua new file mode 100644 index 0000000000..b0a56fc8ae --- /dev/null +++ b/src/lib/pcap/filter.lua @@ -0,0 +1,33 @@ +module(..., package.seeall) +local ffi = require("ffi") +local C = ffi.C +require ("lib.pcap.filter_h") + +local filter = subClass(nil) +filter._name = "pcap packet filter" + +-- Dummy pcap handle shared by all instances. Link type 1 corresponds to +-- Ethernet +local pcap = C.pcap_open_dead(1, 0xffff) + +-- Create a filter with an arbitrary libpcap filter expression +function filter:new(program) + local o = filter:superClass().new(self) + o._bpf = ffi.new("struct bpf_program") + if C.pcap_compile(pcap, o._bpf, ffi.cast("char *", program), 1, 0xffffffff) ~= 0 then + o:free() + return nil, C.pcap_geterr(pcap) + end + o._header = ffi.new("struct pcap_pkthdr") + return o +end + +-- Apply the filter to a region of memory +function filter:match(data, length) + local header = self._header + header.incl_len = length + header.orig_len = length + return C.pcap_offline_filter(self._bpf, header, ffi.cast("char *", data)) ~= 0 +end + +return filter From 145a937812973b85dac24b56eb4c36df77a117f7 Mon Sep 17 00:00:00 2001 From: Alexander Gall Date: Mon, 26 May 2014 17:28:31 +0200 Subject: [PATCH 05/14] Add functionality to the datagram module The new method parse_match() allows the parsing and matching of a single protocol header without having to allocate a table, which can help to avoid garbage. The new method parse_n() parses exactly n protocol headers without applying any additional checks. Also new is the pop_raw() method that pops a protocol header without parsing it. This requires the application to already know the type and size of the next header. --- src/lib/protocol/datagram.lua | 127 ++++++++++++++++++++++++---------- 1 file changed, 89 insertions(+), 38 deletions(-) diff --git a/src/lib/protocol/datagram.lua b/src/lib/protocol/datagram.lua index 90fcc79efd..ac406024f3 100644 --- a/src/lib/protocol/datagram.lua +++ b/src/lib/protocol/datagram.lua @@ -112,49 +112,81 @@ function datagram:push (proto) self._packet.length = self._packet.length + sizeof end --- Create protocol header objects from the packet's payload. If --- called with argument nil and the packets ULP is non-nil, a single --- protocol header of type ULP is created. The caller can specify --- matching criteria by passing an array of templates to match for --- each parsed header. Each criteria consists of a reference to a --- header class to match and a function that is evaluated with the --- protocol object as input. A header is only pushed onto the parse --- stack if it matches the class and the function returns a true --- value. The class and function can both be nil to provide "wildcard --- matching". For example, the following code fragment will match a --- packet that contains an ethernet header, followed by an arbitrary --- (but supported) header, followed by an icmp header of type 135. +-- The following methods create protocol header objects from the +-- packet's payload. The basic method parse_match() takes two +-- arguments, which can both be nil. -- --- local eth = require("lib.protocol.ethernet") --- local icmp = require("lib.protocol.icmp") --- dgram:parse({ { ethernet, nil }, { nil, nil }, --- { icmp, function(icmp) return(icmp:type() == 135) end } }) +-- The first argument is a protocol class object which is used to +-- create a protocol instance from the start of the as yet unparsed +-- part of the packet. If class is nil, the current ULP of the packet +-- is used. If the ULP is not set (nil) or the constructor of the +-- protocol instance returns nil, the parsing operation has failed and +-- the method returns nil. The packet remains unchanged. -- --- The method returns the protocol object of the last parsed header or --- nil if either an unsupported ULP is encountered or one of the match --- criteria is not met. -function datagram:parse (seq) +-- If the protocol instance has been created successfully, it is +-- passed as single argument to the anonymous function that has been +-- passed as the second argument to the method. The function can +-- execute any checks that should be performed on the protocol, like +-- matching of a particular value of a header field. It must return +-- either true or false. +-- +-- If the checking function returns false, the parsing has failed and +-- the method returns nil. The packet remains unchanged. +-- +-- If no checking function is supplied or it returns a true value, the +-- parsing has succeeded. The protocol object is pushed onto the +-- datagrams parse stack and returned to the caller. +function datagram:parse_match (class, check) assert(self._parse, "non-parseable datagram") local parse = self._parse - local seq = seq or { { parse.ulp } } - local proto + local class = class or parse.ulp local iovec = self._packet.iovecs[parse.iovec] - for _, elt in ipairs(seq) do - local class, check = elt[1], elt[2] - if not parse.ulp or (class and class ~= parse.ulp) then - return nil - end - proto = parse.ulp:new_from_mem(iovec.buffer.pointer + iovec.offset - + parse.offset, iovec.length - parse.offset) - if proto == nil or (check and not check(proto)) then - return nil - end - local index = parse.index + 1 - parse.stack[index] = proto - parse.index = index - parse.ulp = proto:upper_layer() - parse.offset = parse.offset + proto:sizeof() + if not parse.ulp or (class and class ~= parse.ulp) then + return nil + end + local proto = parse.ulp:new_from_mem(iovec.buffer.pointer + iovec.offset + + parse.offset, iovec.length - parse.offset) + if proto == nil or (check and not check(proto)) then + if proto then proto:free() end + return nil + end + local index = parse.index + 1 + parse.stack[index] = proto + parse.index = index + parse.ulp = proto:upper_layer() + parse.offset = parse.offset + proto:sizeof() + return proto +end + +-- This method is a wrapper for parse_match() that allows parsing of a +-- sequence of headers with a single method call. The method returns +-- the protocol object of the final parsed header or nil if any of the +-- calls to parse_match() return nil. If called with a nil argument, +-- this method is equivalent to parse_match() without arguments. +function datagram:parse (seq) + if not seq then + return self:parse_match() + end + local proto = nil + local i = 1 + while seq[i] do + proto = self:parse_match(seq[i][1], seq[i][2]) + if not proto then break end + i = i+1 + end + return proto +end + +-- This method is a wrapper for parse_match() that parses the next n +-- protocol headers. It returns the last protocol object or nil if +-- less than n headers could be parsed successfully. +function datagram:parse_n (n) + local n = n or 1 + local proto + for i = 1, n do + proto = self:parse_match() + if not proto then break end end return proto end @@ -179,6 +211,7 @@ end -- Remove the bottom n elements from the parse stack by adjusting the -- offset of the relevant iovec. function datagram:pop (n) + local n = n or 1 local parse = self._parse assert(parse, "non-parseable datagram") assert(n <= parse.index) @@ -193,13 +226,31 @@ function datagram:pop (n) iovec.offset = iovec.offset + sizeof iovec.length = iovec.length - sizeof self._packet.length = self._packet.length - sizeof + parse.offset = parse.offset - sizeof end if i+n <= parse.index then parse.stack[i] = parse.stack[i+n] else parse.stack[i] = nil end - end + end + parse.index = parse.index - n +end + +-- Remove bytes from the start of the packet. It is intended +-- as an efficient version of pop() if the caller already knows what +-- type of header is at the start of the packet, for example after a +-- successful match of matcher:compare(). If the caller also knows +-- the type of the subsequent header, it can pass the corresponding +-- protocol class as second argument to pop_raw(). This will set the +-- datagram's upper-layer protocol to this class such that the parse() +-- method can be used to process the datagram further. +function datagram:pop_raw (length, ulp) + local iovec = self._packet.iovecs[self._parse.iovec] + iovec.offset = iovec.offset + length + iovec.length = iovec.length - length + self._packet.length = self._packet.length - length + self._parse.ulp = ulp end function datagram:stack () From 26c99eb15ebd580f135a04ca0953ceefe2be2620 Mon Sep 17 00:00:00 2001 From: Alexander Gall Date: Mon, 26 May 2014 17:51:04 +0200 Subject: [PATCH 06/14] Add support for the new pcap filter module to vpws and ns_responder Both apps use the efficient filter module to avoid garbage during packet classification. --- src/apps/ipv6/ns_responder.lua | 89 +++++++++++++++++++--------------- src/apps/vpn/vpws.lua | 21 +++----- 2 files changed, 58 insertions(+), 52 deletions(-) diff --git a/src/apps/ipv6/ns_responder.lua b/src/apps/ipv6/ns_responder.lua index d8f5ebc815..a708f3688e 100644 --- a/src/apps/ipv6/ns_responder.lua +++ b/src/apps/ipv6/ns_responder.lua @@ -14,6 +14,7 @@ local ethernet = require("lib.protocol.ethernet") local ipv6 = require("lib.protocol.ipv6") local icmp = require("lib.protocol.icmp.header") local ns = require("lib.protocol.icmp.nd.ns") +local filter = require("lib.pcap.filter") local ns_responder = subClass(nil) ns_responder._name = "ipv6 neighbor solicitation responder" @@ -21,49 +22,61 @@ ns_responder._name = "ipv6 neighbor solicitation responder" function ns_responder:new(config) local o = ns_responder:superClass().new(self) o._config = config - o._match = { { ethernet }, - { ipv6 }, - { icmp }, - { ns, - function(ns) - return(ns:target_eq(config.local_ip)) - end } } + o._match_ns = function(ns) + return(ns:target_eq(config.local_ip)) + end + local filter, errmsg = filter:new("icmp6 and ip6[40] = 135") + assert(filter, errmsg and ffi.string(errmsg)) + o._filter = filter return o end local function process(self, dgram) - if dgram:parse(self._match) then - local eth, ipv6, icmp, ns = unpack(dgram:stack()) - local option = ns:options(dgram:payload()) - if not (#option == 1 and option[1]:type() == 1) then - -- Invalid NS, ignore - return nil - end - -- Turn this message into a solicited neighbor - -- advertisement with target ll addr option - - -- Ethernet - eth:swap() - eth:src(self._config.local_mac) - - -- IPv6 - ipv6:dst(ipv6:src()) - ipv6:src(self._config.local_ip) - - -- ICMP - option[1]:type(2) - option[1]:option():addr(self._config.local_mac) - icmp:type(136) - -- Undo/redo icmp and ns headers to get - -- payload and set solicited flag - dgram:unparse(2) - dgram:parse() -- icmp - local payload, length = dgram:payload() - dgram:parse():solicited(1) - icmp:checksum(payload, length, ipv6) - return true + if not self._filter:match(dgram:payload()) then + return false + end + -- Parse the ethernet, ipv6 amd icmp headers + dgram:parse_n(3) + local eth, ipv6, icmp = unpack(dgram:stack()) + local payload, length = dgram:payload() + if not icmp:checksum_check(payload, length, ipv6) then + print(self:name()..": bad icmp checksum") + return nil + end + -- Parse the neighbor solicitation and check if it contains our own + -- address as target + local ns = dgram:parse(nil, self._match_ns) + if not ns then + return nil + end + local option = ns:options(dgram:payload()) + if not (#option == 1 and option[1]:type() == 1) then + -- Invalid NS, ignore + return nil end - return false + -- Turn this message into a solicited neighbor + -- advertisement with target ll addr option + + -- Ethernet + eth:swap() + eth:src(self._config.local_mac) + + -- IPv6 + ipv6:dst(ipv6:src()) + ipv6:src(self._config.local_ip) + + -- ICMP + option[1]:type(2) + option[1]:option():addr(self._config.local_mac) + icmp:type(136) + -- Undo/redo icmp and ns headers to get + -- payload and set solicited flag + dgram:unparse(2) + dgram:parse() -- icmp + local payload, length = dgram:payload() + dgram:parse():solicited(1) + icmp:checksum(payload, length, ipv6) + return true end function ns_responder:push() diff --git a/src/apps/vpn/vpws.lua b/src/apps/vpn/vpws.lua index 7da7c26920..c04cdab077 100644 --- a/src/apps/vpn/vpws.lua +++ b/src/apps/vpn/vpws.lua @@ -17,6 +17,7 @@ local ethernet = require("lib.protocol.ethernet") local ipv6 = require("lib.protocol.ipv6") local gre = require("lib.protocol.gre") local packet = require("core.packet") +local filter = require("lib.pcap.filter") local vpws = subClass(nil) local in_to_out = { customer = 'uplink', uplink = 'customer' } @@ -24,28 +25,20 @@ local in_to_out = { customer = 'uplink', uplink = 'customer' } function vpws:new(config) local o = vpws:superClass().new(self) o._config = config + o._name = config.name o._encap = { ether = ethernet:new({ src = config.local_mac, dst = config.remote_mac, type = 0x86dd }), ipv6 = ipv6:new({ next_header = 47, hop_limit = 64, src = config.local_vpn_ip, dst = config.remote_vpn_ip}), gre = gre:new({ protocol = 0x6558, key = config.label }) } - o._match = { { ethernet }, - { ipv6, - function(ipv6) - return(ipv6:dst_eq(config.local_vpn_ip)) - end }, - { gre, - function(gre) - return(not gre:use_key() or gre:key() == config.label) - end } } + local program = "ip6 and dst host "..ipv6:ntop(config.local_vpn_ip) .." and ip6 proto 47" + local filter, errmsg = filter:new(program) + assert(filter, errmsg and ffi.string(errmsg)) + o._filter = filter return o end -function vpws:name() - return self.config.name -end - function vpws:push() for _, port_in in ipairs({"customer", "uplink"}) do local l_in = self.input[port_in] @@ -69,7 +62,7 @@ function vpws:push() datagram:push(encap.gre) else -- Check for encapsulated frame coming in on uplink - if datagram:parse(self._match) then + if self._filter:match(datagram:payload()) then -- Remove encapsulation to restore the original -- Ethernet frame datagram:pop(3) From ce22d3d873d649440c24b822d6bc49c59a46769c Mon Sep 17 00:00:00 2001 From: Alexander Gall Date: Mon, 26 May 2014 17:52:54 +0200 Subject: [PATCH 07/14] Fix a bug in the checksum algorithm of core/lib.lua --- src/core/lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/lib.lua b/src/core/lib.lua index 6e1a48fdb3..dd6d002f22 100644 --- a/src/core/lib.lua +++ b/src/core/lib.lua @@ -255,7 +255,7 @@ function update_csum (ptr, len, csum0) for i = 0, len-2, 2 do sum = sum + bit.lshift(ptr[i], 8) + ptr[i+1] end - if len % 2 == 1 then sum = sum + bit.lshift(ptr[len-1]) end + if len % 2 == 1 then sum = sum + bit.lshift(ptr[len-1], 1) end return sum end From 3dff2438e54b352b8100b4a68bfc32e8ba03034d Mon Sep 17 00:00:00 2001 From: Alexander Gall Date: Mon, 26 May 2014 20:17:36 +0200 Subject: [PATCH 08/14] Full support for GRE and ICMPv6 checksum The vpws and ns_responder apps now handle checksums correctly in all cases. The GRE key field is now supported correctly as well. --- src/apps/vpn/vpws.lua | 33 ++++++- src/lib/protocol/gre.lua | 162 ++++++++++++++++++++----------- src/lib/protocol/icmp/header.lua | 22 +++-- 3 files changed, 151 insertions(+), 66 deletions(-) diff --git a/src/apps/vpn/vpws.lua b/src/apps/vpn/vpws.lua index c04cdab077..b480ba9d16 100644 --- a/src/apps/vpn/vpws.lua +++ b/src/apps/vpn/vpws.lua @@ -30,8 +30,10 @@ function vpws:new(config) ether = ethernet:new({ src = config.local_mac, dst = config.remote_mac, type = 0x86dd }), ipv6 = ipv6:new({ next_header = 47, hop_limit = 64, src = config.local_vpn_ip, dst = config.remote_vpn_ip}), - gre = gre:new({ protocol = 0x6558, key = config.label }) + gre = gre:new({ protocol = 0x6558, checksum = config.checksum, key = config.label }) } + -- Pre-computed size of combined Ethernet and IPv6 header + o._eth_ipv6_size = ethernet:sizeof() + ipv6:sizeof() local program = "ip6 and dst host "..ipv6:ntop(config.local_vpn_ip) .." and ip6 proto 47" local filter, errmsg = filter:new(program) assert(filter, errmsg and ffi.string(errmsg)) @@ -53,7 +55,7 @@ function vpws:push() -- IPv6 payload length consist of the size of the GRE header plus -- the size of the original packet encap.ipv6:payload_length(encap.gre:sizeof() + p.length) - if encap.gre:use_checksum() then + if encap.gre:checksum() then encap.gre:checksum(datagram:payload()) end -- Copy the finished headers into the packet @@ -65,7 +67,32 @@ function vpws:push() if self._filter:match(datagram:payload()) then -- Remove encapsulation to restore the original -- Ethernet frame - datagram:pop(3) + datagram:pop_raw(self._eth_ipv6_size, gre) + local valid = true + local gre = datagram:parse() + if gre then + if not gre:checksum_check(datagram:payload()) then + print(self:name()..": GRE bad checksum") + valid = false + else + local key = gre:key() + if ((self._config.label and key and key == self._config.label) or + not (self._config.label or key)) then + datagram:pop() + else + print(self:name()..": GRE key mismatch: local " + ..(self._config.label or 'none')..", remote "..(gre:key() or 'none')) + valid = false + end + end + else + -- Unsupported GRE options or flags + valid = false + end + if not valid then + packet.deref(p) + p = nil + end else -- Packet doesn't belong to VPN, discard packet.deref(p) diff --git a/src/lib/protocol/gre.lua b/src/lib/protocol/gre.lua index aeb0a90ad8..fee9991d87 100644 --- a/src/lib/protocol/gre.lua +++ b/src/lib/protocol/gre.lua @@ -11,27 +11,45 @@ local lib = require("core.lib") -- extensions. Note that most of the flags specified in the original -- specification of RFC1701 have been deprecated. -local gre_template = [[ - struct { - uint16_t bits; // Flags, version - uint16_t protocol; - uint8_t options[$]; - } -]] - --- Three different sizes depending on the options used -local gre_types = { [0] = ffi.typeof(gre_template, 0), - [4] = ffi.typeof(gre_template, 4), - [8] = ffi.typeof(gre_template, 8), } -local gre_ptr_types = { [0] = ffi.typeof("$*", gre_types[0]), - [4] = ffi.typeof("$*", gre_types[4]), - [8] = ffi.typeof("$*", gre_types[8]) } local gre = subClass(header) +-- Four different headers depending on the options used +local gre_types = { base = ffi.typeof[[ + struct { + uint16_t bits; // Flags, version + uint16_t protocol; + }]], + csum = ffi.typeof[[ + struct { + uint16_t bits; // Flags, version + uint16_t protocol; + uint16_t csum; + uint16_t reserved1; + }]], + key = ffi.typeof[[ + struct { + uint16_t bits; // Flags, version + uint16_t protocol; + uint32_t key; + }]], + csum_key = ffi.typeof[[ + struct { + uint16_t bits; // Flags, version + uint16_t protocol; + uint16_t csum; + uint16_t reserved1; + uint32_t key; + }]], + } +local gre_ptr_types = {} +for k, v in pairs(gre_types) do + gre_ptr_types[k] = ffi.typeof("$*", v) +end + -- Class variables gre._name = "gre" -gre._header_type = gre_types[0] -gre._header_ptr_type = gre_ptr_types[0] +gre._header_type = gre_types.base +gre._header_ptr_type = gre_ptr_types.base gre._ulp = { class_map = { [0x6558] = "lib.protocol.ethernet" }, method = 'protocol' } @@ -40,24 +58,32 @@ gre._ulp = { function gre:new (config) local o = gre:superClass().new(self) - local opt_size = 0 + local type = nil if config.checksum then - opt_size = opt_size + 4 o._checksum = true + type = 'csum' + else + o._checksum = false end if config.key ~= nil then - o._key_offset = opt_size - opt_size = opt_size + 4 + o._key = true + if type then + type = 'csum_key' + else + type = 'key' + end + else + o._key = false end - if opt_size > 0 then - o._header_type = gre_types[opt_size] - o._header_ptr_type = gre_ptr_types[opt_size] + if type then + o._header_type = gre_types[type] + o._header_ptr_type = gre_ptr_types[type] o._header = o._header_type() end if o._checksum then lib.bitfield(16, o._header, 'bits', 0, 1, 1) end - if o._key_offset ~= nil then + if o._key then lib.bitfield(16, o._header, 'bits', 2, 1, 1) o:key(config.key) end @@ -67,24 +93,34 @@ end function gre:new_from_mem (mem, size) local o = gre:superClass().new_from_mem(self, mem, size) - -- Reserved bits and version MUST be zero - if lib.bitfield(16, o._header, 'bits', 4, 12) ~= 0 then + -- Reserved bits and version MUST be zero. We don't support + -- the sequence number option, i.e. the 'S' flag (bit 3) must + -- be cleared as well + if lib.bitfield(16, o._header, 'bits', 3, 13) ~= 0 then o:free() return nil end - local opt_size = 0 - if o:use_checksum() then - opt_size = opt_size + 4 + local type = nil + if lib.bitfield(16, o._header, 'bits', 0, 1) == 1 then o._checksum = true + type = 'csum' + else + o._checksum = false end - if o:use_key() then - o._key_offset = opt_size - opt_size = opt_size + 4 + if lib.bitfield(16, o._header, 'bits', 2, 1) == 1 then + o._key = true + if type then + type = 'csum_key' + else + type = 'key' + end + else + o._key = false end - if opt_size > 0 then - o._header_type = gre_types[opt_size] - o._header_ptr_type = gre_ptr_types[opt_size] - o._header = ffi.cast(o._header_ptr_type, self._header)[0] + if type then + o._header_type = gre_types[type] + o._header_ptr_type = gre_ptr_types[type] + o._header = ffi.cast(o._header_ptr_type, mem)[0] end return o end @@ -99,39 +135,51 @@ function gre:free () gre:superClass().free(self) end +local function checksum(header, payload, length) + local csum_in = header.csum; + header.csum = 0; + header.reserved1 = 0; + local csum = lib.finish_csum(lib.update_csum(payload, length, + lib.update_csum(header, ffi.sizeof(header), 0))) + header.csum = csum_in + return csum +end + +-- Returns nil if checksumming is disabled. If payload and length is +-- supplied, the checksum is written to the header and returned to the +-- caller. With nil arguments, the current checksum is returned. function gre:checksum (payload, length) - assert(self._checksum) - local csum_ptr = ffi.cast(ffi.typeof("uint16_t *"), - ffi.cast(ffi.typeof("uint8_t*"), self._header) - + ffi.offsetof(self._header, 'options')) - local csum = lib.update_csum(self._header, ffi.sizeof(self._header), 0) - csum = lib.update_csum(payload, length, csum) - csum_ptr[0] = C.htons(lib.finish_csum(csum)) + if not self._checksum then + return nil + end + if payload ~= nil then + -- Calculate and set the checksum + self._header.csum = C.htons(checksum(self._header, payload, length)) + end + return C.ntohs(self._header.csum) end -function gre:use_checksum () - return lib.bitfield(16, self._header, 'bits', 0, 1) == 1 +function gre:checksum_check (payload, length) + if not self._checksum then + return true + end + return checksum(self._header, payload, length) == C.ntohs(self._header.csum) end +-- Returns nil if keying is disabled. Otherwise, the key is set to the +-- given value or the current key is returned if called with a nil +-- argument. function gre:key (key) - if not self._key_offset then + if not self._key then return nil end - local key_ptr = ffi.cast(ffi.typeof("uint32_t *"), - ffi.cast(ffi.typeof("uint8_t*"), self._header) - + ffi.offsetof(self._header, 'options') - + self._key_offset) if key ~= nil then - key_ptr[0] = C.htonl(key) + self._header.key = C.htonl(key) else - return C.ntohl(key_ptr[0]) + return C.ntohl(self._header.key) end end -function gre:use_key () - return lib.bitfield(16, self._header, 'bits', 2, 1) == 1 -end - function gre:protocol (protocol) if protocol ~= nil then self._header.protocol = C.htons(protocol) diff --git a/src/lib/protocol/icmp/header.lua b/src/lib/protocol/icmp/header.lua index 262b28ca5b..3f391646fc 100644 --- a/src/lib/protocol/icmp/header.lua +++ b/src/lib/protocol/icmp/header.lua @@ -54,20 +54,30 @@ function icmp:code (code) end end -function icmp:checksum (payload, length, ipv6) - local h = self._header +local function checksum(header, payload, length, ipv6) local csum = 0 if ipv6 then -- Checksum IPv6 pseudo-header - local ph = ipv6:pseudo_header(length + self:sizeof(), 58) + local ph = ipv6:pseudo_header(length + ffi.sizeof(header), 58) csum = lib.update_csum(ph, ffi.sizeof(ph), csum) end -- Add ICMP header - h.checksum = 0 - csum = lib.update_csum(h, self:sizeof(), csum) + local csum_rcv = header.checksum + header.checksum = 0 + csum = lib.update_csum(header, ffi.sizeof(header), csum) + header.checksum = csum_rcv -- Add ICMP payload csum = lib.update_csum(payload, length, csum) - h.checksum = C.htons(lib.finish_csum(csum)) + return lib.finish_csum(csum) +end + +function icmp:checksum (payload, length, ipv6) + local header = self._header + header.checksum = C.htons(checksum(header, payload, length, ipv6)) +end + +function icmp:checksum_check (payload, length, ipv6) + return checksum(self._header, payload, length, ipv6) == C.ntohs(self._header.checksum) end return icmp From ba9049908688bd73e37e558cd82c40f70ba012cb Mon Sep 17 00:00:00 2001 From: Alexander Gall Date: Tue, 3 Jun 2014 17:05:56 +0200 Subject: [PATCH 09/14] Added selftest for vpws app --- .../vpn/vpws-selftest-customer.cap.expect | Bin 0 -> 15824 bytes src/apps/vpn/vpws-selftest-customer.cap.input | Bin 0 -> 15824 bytes src/apps/vpn/vpws-selftest-uplink.cap.expect | Bin 0 -> 22424 bytes src/apps/vpn/vpws-selftest-uplink.cap.input | Bin 0 -> 22424 bytes src/apps/vpn/vpws.lua | 41 +++++++++++++++++- 5 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 src/apps/vpn/vpws-selftest-customer.cap.expect create mode 100644 src/apps/vpn/vpws-selftest-customer.cap.input create mode 100644 src/apps/vpn/vpws-selftest-uplink.cap.expect create mode 100644 src/apps/vpn/vpws-selftest-uplink.cap.input diff --git a/src/apps/vpn/vpws-selftest-customer.cap.expect b/src/apps/vpn/vpws-selftest-customer.cap.expect new file mode 100644 index 0000000000000000000000000000000000000000..62f366c82af4bd11676261bfd7ee39b429029b60 GIT binary patch literal 15824 zcmd7Mw{DaH5JlnN76o#HXeoITH+}@N0q2}^&N-}ej_bJ2IbgsTTu>v?Zo^wp8S4m3 zRCI_l($(B4M*6;AABJOtd%@k@+yp_)-Eq4^zY`=y6COVp{Be=^EqXsl2!f`sPvV34 zhtcb+NLXXx@YJ(0!8XUZT5{>RzGy zDAgsR`ykc5M)zK-dxP#ms(Xv>om7{E?p&%%Mt3IFrJy^N>Qd32NOfuGj-|SEbVpKM z2D(G3E)(5>RF{QrU#iPSw>2N9^H&o*MM$Xs%u0yCDk>do0RIB(M?EoE$GIjx>j^!Qe7LmQK_yS-H24z zfo@o;>qIvs)pem8lrIt`A+GRM(HLSE>u4>yhdP&~;07gXp@X zx*>F(Qr$4R4ykSgUAt5_impwn8$;JB)s3TTk?JPUHA{7q=$fRuDRhle-88xescr^c zy;L`gu1>0(Lsu)+&7-T4>K4#dOLdFrs-(Iwx=N{T30;L$w~Ve_s#`%*$K5x(K==scr*Zp;Whtu0X2WLYFVqZKKPR>UPlON_D&Fa-_OFblFnfKDsQa t?f_k;RCkCjL#jJMmoC*Eqf3+OPSB-Fb*Jc3q`EV7$x_`px}^W1`vnKq<<a6xEuDp^Zq#gtR~z3 z8cDysj~_W~cpHYr-?enrqXS5T>x}nRXWc_`lAFw_eG^U!FAW5`>fKP zq4P>uhO06x-jV8sdVSK&IH|CmF_&(U5DAbiu0=g}g?jqMkLbs{XUE;bZ=r&Zk%Ul-?-MUJ5h3jIVTT|)0xh@vE z*D9S4*Tq5iN~QDVx_Ibbs&sx_mjK-hmCm2*5}{jF=>oVe3Az=PE|BYPK=<55b;-~z zt8_uUT?%y1TvV3|-BXn=n72!V?ukkl!gcA;Jyz*Lx$Y)(ODbI$*JVKWNToAzT_$u7 zRl0Dly9M0?l`ewovY@-K(nWGzHgxw?x+t#8fo@Tyi{`pq=oVDE7_Q5M?ygD~%XPP* zyQ9*@aa}%i^D13D*A+lFr_v>GT_JR{DqSMi6+t(n(j{?SF?7=|sw;tRN~KHT?Mk7W zROwQ=t_->fl`f6z%Ap%q>C(Bb0=hAkE`#eTp&M1{GP$k_x)GJ`7T1}f8&>JExUL$y zA(bwh>uR7IROxcK&H~+lN|(!Zwa__Lx;(D4Lf5a-<#SyfbbT(WtB0;vr7Ps^8ldY@ z>590{23@y{>KdWzQt66$yCRk{+cYlhCD(v@;u3v?YST^ZN4LT6X$%v{$7UAszG z!*%V@wW)L#uCqhes?ybRT?ce6DxH<<9MCnZbah1_&^4-b4P4g^olT{) zaa|8|4JutD*Y!eIuhKPhT_1FHDqRcL^+RV>=~}tY30DDpa~|t{aE0T&3&bx(VpYRJvZS zn}n`ZrR(FmDdCT4}P8RJZ} zS&emN!dy5M9ktk&sST^6O`6a3>)5|}|Eg~zzK^oLP53^@`ZnWxFYAlLH!JJgg72NI zZ!5mHvc7Hj-pKmm@x7MyZO8XY*0%%SOIhDed@p2u3HYAN`gY-aChJSY_f*!m8{ZRI z-yVFAWqnEbW@LSP@ja6DCF6T2>)VGfEbH5k?}4oE0KWUOzJvJg$@&iAyDRHEjPH)D z?+CtWS)U!>ZCT$@e79tMDfp&jeaGH)~6SBTj_^!$N zPUE{O>pO#QT-J9M-&wPBAnVJ)*DveK#n&h6bKncg`ttAvWPSPgdS!hD_I%Rz&`24ayC%z6@UnxGHtj~q7UDj8IuT9ohj;~eLSAowf>vQ94k@Z#LYnJs@ z;cJrhRpax>`fBhs%KB>YHOTtv@YT!u>haad`Wo=n%K94d)yVoh_^M@nP57#0ea-kP zWqmF9+_F9|z6x1iE534BUmLzMSzkLom#oi+uT<98fzK)H^W!U#^>yMami2YvE0Xnf z<13W)_24Uz_4VS*m-Pkk<;nVj_#CpnK76^dzJ7c;vc3U)*|NStd|9%-A$*y#zF~YB Mvc3?$^#9@e3H5Tw)Bpeg literal 0 HcmV?d00001 diff --git a/src/apps/vpn/vpws-selftest-uplink.cap.input b/src/apps/vpn/vpws-selftest-uplink.cap.input new file mode 100644 index 0000000000000000000000000000000000000000..f2827001e7ad024c652d0b7d25c58394f193bcee GIT binary patch literal 22424 zcmc)NTTBjM7zgmTu+w{|M!2k=iU2%FP`mp_q?y34Yj%&H}kK4|2D?O89vPI zI%3;0+q`hzXS)3LeXISgceVR3Ka1TF;(Rgnn|FJ;%<(d2awg5x^nZD(#e^Ss{b7v9 zHvcpe;_zML?woV-rDLA)H757@F^jP*aXg#sTHxYr*U|~k1txa#``=;QbH-Q0_W8s2 zROPE>`vTy5qVm7YyHhmG8|~^8RufzI!U)JGL(bzHycBJ=+%w-&b|Wc$v* zcUR^6#P)^3H=^==X8Xe78&>(guzeBm4XJ!z*}h2l235XqY+n?711jHlwl5mKewFVh z+jkbeK9z5Z?Tdl$j>wX1xK*}e<#wW)j_ zY+ovTtt#JAwl5977L{)q+m{Ysv&y%e?aP3#N#*lo`!2$FL*-k+_GQA?sPe63`?BC` zQ2AD|ecABUt9+~3z8v`KRKB%rUoL#tRlap>Umkq5D&KmxFCV^ZDxWvoR{&p)%C~{- zv%y!b@@-`MF2Pr&@@-=K?C@2pe4E+6%kWjGd|TMQLioy6J|DKP2);6vZ#&yp3}30r zw}b7w0$+*Bx0CI=3g1T@D-|jd)dAU z_%5q_``Erp`0Og5FWXlI-zAmLkL|05&!+P2XZvd4D^U3kuzlCy%UAghvVFDi<*9s! z*uLxV<*Iy#*}gjXa#X&fY+pTm*(#qu+t&bJmdY2v_BFznsqzJ~eK+8{sPYA|eNFIX zsC>a}Uo(8^D&J|guLZs|l`oX-YlSaW<+HMVZSY-C`NG(~cKA|Mz6iFj1HSVrUnJXi z6TW1XFPiPU1z(cN7sK{-!k4J>#j$-|@Fl2x@oZl=eCJfY1h%gSzIc@{iS4@$U!2O9 z%=Y!d7pwB6uzh#ni&6Pf*}gvb&Z>OrY+pZo(JEgC+cyATl**UM_6@=psq$s9eM9g? zsC+qW-!OdPDqkMkHv(Un%2&Yl-G%Rr%4cKyM&Yxne0H{P48Bm6uaNB vpntp.uplink") + config.link(c, "vpntp.customer -> to_customer.input") + config.link(c, "from_customer.output -> vpntp.customer") + config.link(c, "vpntp.uplink -> to_uplink.input") + app.configure(c) + app.main({duration = 1}) + if (io.open("apps/vpn/vpws-selftest-customer.cap.output"):read('*a') ~= + io.open("apps/vpn/vpws-selftest-customer.cap.expect"):read('*a')) then + print('vpws decapsulation selftest failed.') + os.exit(1) + end + if (io.open("apps/vpn/vpws-selftest-uplink.cap.output"):read('*a') ~= + io.open("apps/vpn/vpws-selftest-uplink.cap.expect"):read('*a')) then + print('vpws encapsulation selftest failed.') + os.exit(1) + end end +vpws.selftest = selftest + return vpws From 18a815527129ea65a709dce6bb41466a2535a79f Mon Sep 17 00:00:00 2001 From: Alexander Gall Date: Fri, 6 Jun 2014 17:13:07 +0200 Subject: [PATCH 10/14] Fix initialization bug in buffer.new_buffer() The origin.type member of a newly allocated buffer needs to be set explicitely after receiving a memory block with undefined contents from malloc(3). --- src/core/buffer.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/buffer.lua b/src/core/buffer.lua index d68674889e..a879f7a274 100644 --- a/src/core/buffer.lua +++ b/src/core/buffer.lua @@ -32,6 +32,7 @@ function new_buffer () local pointer, physical, bytes = memory.dma_alloc(buffersize) local b = lib.malloc("struct buffer") b.pointer, b.physical, b.size = pointer, physical, buffersize + b.origin.type = C.BUFFER_ORIGIN_UNKNOWN return b end From 5c4f12c3190eb78f6262683c6948fce625007b90 Mon Sep 17 00:00:00 2001 From: Alexander Gall Date: Wed, 25 Jun 2014 10:40:19 +0200 Subject: [PATCH 11/14] Tunings to reduce garbage and eliminate NYIs lib/protocol/datagram.lua: pre-allocate the _packet variable upon instantiation as ctype "struct packet *[1]" to avoid boxing. This appears to eliminate the bulk of garbage in the datagram class. lib/protocol/icmp/: pre-define pointer ctypes in various classes to avoid NYIs due to unsupported FastFunc ffi.typeof. lib/protocol/ipv6.lua: pre-allocate a pseudo-header for every ipv6 object to avoid another NYI. lib/protocol/header.lua and all module that touch the _header instance variable: create _header as array of a single pointer to the actual header, just like in datagram.lua. The cast operation in new_from_mem() will update the array rather than create a new boxed pointer. --- src/lib/protocol/datagram.lua | 41 ++++++++--------- src/lib/protocol/ethernet.lua | 12 ++--- src/lib/protocol/gre.lua | 28 ++++++------ src/lib/protocol/header.lua | 44 +++++++++++++++--- src/lib/protocol/icmp/header.lua | 12 ++--- src/lib/protocol/icmp/nd/na.lua | 18 ++++---- src/lib/protocol/icmp/nd/ns.lua | 6 +-- src/lib/protocol/icmp/nd/options/lladdr.lua | 4 +- src/lib/protocol/icmp/nd/options/tlv.lua | 3 +- src/lib/protocol/ipv4.lua | 50 ++++++++++----------- src/lib/protocol/ipv6.lua | 50 +++++++++++++-------- src/lib/protocol/tcp.lua | 38 ++++++++-------- src/lib/protocol/udp.lua | 10 ++--- 13 files changed, 182 insertions(+), 134 deletions(-) diff --git a/src/lib/protocol/datagram.lua b/src/lib/protocol/datagram.lua index ac406024f3..9f5ea139e8 100644 --- a/src/lib/protocol/datagram.lua +++ b/src/lib/protocol/datagram.lua @@ -56,16 +56,9 @@ local datagram = subClass(nil) -- push() method. function datagram:new (p, class) local o = datagram:superClass().new(self) - if p then - packet.coalesce(p) - o._packet = p - else - o._packet = packet.allocate() - local b = buffer.allocate() - packet.add_iovec(o._packet, b, 0) - end if not o._recycled then o._parse = { stack = {}, index = 0 } + o._packet = ffi.new("struct packet *[1]") else for i, _ in ipairs(o._parse.stack) do o._parse.stack[i]:free() @@ -77,6 +70,14 @@ function datagram:new (p, class) o._parse.ulp = class o._parse.iovec = 0 o._parse.offset = 0 + if p then + packet.coalesce(p) + o._packet[0] = p + else + o._packet[0] = packet.allocate() + local b = buffer.allocate() + packet.add_iovec(o._packet[0], b, 0) + end return o end @@ -92,10 +93,10 @@ function datagram:push (proto) local push = self._push if not push then local b = buffer.allocate() - packet.prepend_iovec(self._packet, b, 0) + packet.prepend_iovec(self._packet[0], b, 0) if not self._parse then b = buffer.allocate() - packet.add_iovec(self._packet, b, 0) + packet.add_iovec(self._packet[0], b, 0) self._parse = { ulp = nil, offset = 0 } end -- If the parse stack already exists, its associated iovec was @@ -104,12 +105,12 @@ function datagram:push (proto) self._push = true end local sizeof = proto:sizeof() - local iovec = self._packet.iovecs[0] + local iovec = self._packet[0].iovecs[0] assert(iovec.offset + iovec.length + sizeof <= iovec.buffer.size, "not enough space in buffer to push header") proto:copy(iovec.buffer.pointer + iovec.offset + iovec.length) iovec.length = iovec.length + sizeof - self._packet.length = self._packet.length + sizeof + self._packet[0].length = self._packet[0].length + sizeof end -- The following methods create protocol header objects from the @@ -140,7 +141,7 @@ function datagram:parse_match (class, check) assert(self._parse, "non-parseable datagram") local parse = self._parse local class = class or parse.ulp - local iovec = self._packet.iovecs[parse.iovec] + local iovec = self._packet[0].iovecs[parse.iovec] if not parse.ulp or (class and class ~= parse.ulp) then return nil @@ -216,7 +217,7 @@ function datagram:pop (n) assert(parse, "non-parseable datagram") assert(n <= parse.index) local proto - local iovec = self._packet.iovecs[parse.iovec] + local iovec = self._packet[0].iovecs[parse.iovec] -- Don't use table.remove to avoid garbage for i = 1, parse.index do if i <= n then @@ -225,7 +226,7 @@ function datagram:pop (n) proto:free() iovec.offset = iovec.offset + sizeof iovec.length = iovec.length - sizeof - self._packet.length = self._packet.length - sizeof + self._packet[0].length = self._packet[0].length - sizeof parse.offset = parse.offset - sizeof end if i+n <= parse.index then @@ -246,10 +247,10 @@ end -- datagram's upper-layer protocol to this class such that the parse() -- method can be used to process the datagram further. function datagram:pop_raw (length, ulp) - local iovec = self._packet.iovecs[self._parse.iovec] + local iovec = self._packet[0].iovecs[self._parse.iovec] iovec.offset = iovec.offset + length iovec.length = iovec.length - length - self._packet.length = self._packet.length - length + self._packet[0].length = self._packet[0].length - length self._parse.ulp = ulp end @@ -258,7 +259,7 @@ function datagram:stack () end function datagram:packet () - return(self._packet) + return(self._packet[0]) end -- Return the location and size of the packet's payload. If mem is @@ -266,7 +267,7 @@ end -- appended to the packet's payload first. function datagram:payload (mem, size) local parse = self._parse - local iovec = self._packet.iovecs[parse.iovec] + local iovec = self._packet[0].iovecs[parse.iovec] local payload = iovec.buffer.pointer + iovec.offset + parse.offset if mem ~= nil then assert(size <= iovec.buffer.size - (iovec.offset + iovec.length), @@ -274,7 +275,7 @@ function datagram:payload (mem, size) ffi.copy(iovec.buffer.pointer + iovec.offset + iovec.length, mem, size) iovec.length = iovec.length + size - self._packet.length = self._packet.length + size + self._packet[0].length = self._packet[0].length + size end local p_size = iovec.length - parse.offset return payload, p_size diff --git a/src/lib/protocol/ethernet.lua b/src/lib/protocol/ethernet.lua index 399839d888..72e03c12ec 100644 --- a/src/lib/protocol/ethernet.lua +++ b/src/lib/protocol/ethernet.lua @@ -63,7 +63,7 @@ end -- Instance methods function ethernet:src (a) - local h = self._header + local h = self:header() if a ~= nil then ffi.copy(h.ether_shost, a, 6) else @@ -72,11 +72,11 @@ function ethernet:src (a) end function ethernet:src_eq (a) - return C.memcmp(a, self._header.ether_shost, 6) == 0 + return C.memcmp(a, self:header().ether_shost, 6) == 0 end function ethernet:dst (a) - local h = self._header + local h = self:header() if a ~= nil then ffi.copy(h.ether_dhost, a, 6) else @@ -85,19 +85,19 @@ function ethernet:dst (a) end function ethernet:dst_eq (a) - return C.memcmp(a, self._header.ether_dhost, 6) == 0 + return C.memcmp(a, self:header().ether_dhost, 6) == 0 end function ethernet:swap () local tmp = mac_addr_t() - local h = self._header + local h = self:header() ffi.copy(tmp, h.ether_dhost, 6) ffi.copy(h.ether_dhost, h.ether_shost,6) ffi.copy(h.ether_shost, tmp, 6) end function ethernet:type (t) - local h = self._header + local h = self:header() if t ~= nil then h.ether_type = C.htons(t) else diff --git a/src/lib/protocol/gre.lua b/src/lib/protocol/gre.lua index fee9991d87..b30673789c 100644 --- a/src/lib/protocol/gre.lua +++ b/src/lib/protocol/gre.lua @@ -78,13 +78,13 @@ function gre:new (config) if type then o._header_type = gre_types[type] o._header_ptr_type = gre_ptr_types[type] - o._header = o._header_type() + o._header[0] = o._header_type() end if o._checksum then - lib.bitfield(16, o._header, 'bits', 0, 1, 1) + lib.bitfield(16, o:header(), 'bits', 0, 1, 1) end if o._key then - lib.bitfield(16, o._header, 'bits', 2, 1, 1) + lib.bitfield(16, o:header(), 'bits', 2, 1, 1) o:key(config.key) end o:protocol(config.protocol) @@ -96,18 +96,18 @@ function gre:new_from_mem (mem, size) -- Reserved bits and version MUST be zero. We don't support -- the sequence number option, i.e. the 'S' flag (bit 3) must -- be cleared as well - if lib.bitfield(16, o._header, 'bits', 3, 13) ~= 0 then + if lib.bitfield(16, o:header(), 'bits', 3, 13) ~= 0 then o:free() return nil end local type = nil - if lib.bitfield(16, o._header, 'bits', 0, 1) == 1 then + if lib.bitfield(16, o:header(), 'bits', 0, 1) == 1 then o._checksum = true type = 'csum' else o._checksum = false end - if lib.bitfield(16, o._header, 'bits', 2, 1) == 1 then + if lib.bitfield(16, o:header(), 'bits', 2, 1) == 1 then o._key = true if type then type = 'csum_key' @@ -120,7 +120,7 @@ function gre:new_from_mem (mem, size) if type then o._header_type = gre_types[type] o._header_ptr_type = gre_ptr_types[type] - o._header = ffi.cast(o._header_ptr_type, mem)[0] + o._header[0] = ffi.cast(o._header_ptr_type, mem)[0] end return o end @@ -154,16 +154,16 @@ function gre:checksum (payload, length) end if payload ~= nil then -- Calculate and set the checksum - self._header.csum = C.htons(checksum(self._header, payload, length)) + self:header().csum = C.htons(checksum(self:header(), payload, length)) end - return C.ntohs(self._header.csum) + return C.ntohs(self:header().csum) end function gre:checksum_check (payload, length) if not self._checksum then return true end - return checksum(self._header, payload, length) == C.ntohs(self._header.csum) + return checksum(self:header(), payload, length) == C.ntohs(self:header().csum) end -- Returns nil if keying is disabled. Otherwise, the key is set to the @@ -174,17 +174,17 @@ function gre:key (key) return nil end if key ~= nil then - self._header.key = C.htonl(key) + self:header().key = C.htonl(key) else - return C.ntohl(self._header.key) + return C.ntohl(self:header().key) end end function gre:protocol (protocol) if protocol ~= nil then - self._header.protocol = C.htons(protocol) + self:header().protocol = C.htons(protocol) end - return(C.ntohs(self._header.protocol)) + return(C.ntohs(self:header().protocol)) end return gre diff --git a/src/lib/protocol/header.lua b/src/lib/protocol/header.lua index e63d133c16..2436c49920 100644 --- a/src/lib/protocol/header.lua +++ b/src/lib/protocol/header.lua @@ -78,6 +78,26 @@ -- the constructors must override the _header* class variables in the -- header instance. See lib/protocol/gre.lua for an example of how -- this can look like. +-- +-- The header is stored in the instance variable _header. To avoid the +-- creation of garbage, this is actually an array of pointers with a +-- single element defined as +-- +-- ffi.typeof("$*[1]", self._header_ptr_type) +-- +-- The pointer points to either a ctype object or a region of buffer +-- memory, depending on whether the instance was created via the new() +-- or new_from_mem() methods, respectively. This array is allocated +-- once upon instance creation and avoids the overhead of "boxing" +-- when the instance is recycled by new_from_mem(). As a consequence, +-- the header must be accessed by indexing this array, e.g. +-- +-- self._header[0].some_header_element +-- +-- Caution: to access the actual header, e.g. for ffi.sizeof(), you +-- need to dereference the pointer, i.e. _header[0][0]. This is what +-- the header() method does. +-- module(..., package.seeall) local ffi = require("ffi") @@ -87,9 +107,17 @@ local header = subClass(nil) -- Class methods -- The standard constructor creates a new ctype object for the header. +-- Note: unlike the new_from_mem() method, the new() method creates +-- garbage when an object is recycled. This is not trivial to avoid +-- for header classes with variably-sized headers, because there is +-- currently only a single free list per class. function header:new () local o = header:superClass().new(self) - o._header = self._header_type() + if not o._recycled then + o._header = ffi.typeof("$[1]", o._header_ptr_type)() + end + o._header_aux = self._header_type() + o._header[0] = ffi.cast(o._header_ptr_type, o._header_aux) return o end @@ -97,17 +125,20 @@ end -- of memory by "overlaying" a header structure. function header:new_from_mem (mem, size) local o = header:superClass().new(self) + if not o._recycled then + o._header = ffi.typeof("$[1]", o._header_ptr_type)() + end -- Using the class variables here does the right thing even if the -- instance is recycled assert(ffi.sizeof(self._header_type) <= size) - o._header = ffi.cast(self._header_ptr_type, mem)[0] + o._header[0] = ffi.cast(self._header_ptr_type, mem) return o end -- Instance methods function header:header () - return self._header + return self._header[0][0] end function header:sizeof () @@ -116,14 +147,15 @@ end -- default equality method, can be overriden in the ancestors function header:eq (other) - return ffi.string(self._header, self:sizeof()) == ffi.string(other._header,self:sizeof()) + return (ffi.string(self._header[0], self:sizeof()) == + ffi.string(other._header[0],self:sizeof())) end -- Copy the header to some location in memory (usually a packet -- buffer). The caller must make sure that there is enough space at -- the destination. function header:copy (dst) - ffi.copy(dst, self._header, ffi.sizeof(self._header)) + ffi.copy(dst, self._header[0], ffi.sizeof(self._header[0][0])) end -- Create a new protocol instance that is a copy of this instance. @@ -132,7 +164,7 @@ end function header:clone () local header = self._header_type() local sizeof = ffi.sizeof(header) - ffi.copy(header, self._header, sizeof) + ffi.copy(header, self._header[0], sizeof) return self:class():new_from_mem(header, sizeof) end diff --git a/src/lib/protocol/icmp/header.lua b/src/lib/protocol/icmp/header.lua index 3f391646fc..2218809ea7 100644 --- a/src/lib/protocol/icmp/header.lua +++ b/src/lib/protocol/icmp/header.lua @@ -40,17 +40,17 @@ end function icmp:type (type) if type ~= nil then - self._header.type = type + self:header().type = type else - return self._header.type + return self:header().type end end function icmp:code (code) if code ~= nil then - self._header.code = code + self:header().code = code else - return self._header.code + return self:header().code end end @@ -72,12 +72,12 @@ local function checksum(header, payload, length, ipv6) end function icmp:checksum (payload, length, ipv6) - local header = self._header + local header = self:header() header.checksum = C.htons(checksum(header, payload, length, ipv6)) end function icmp:checksum_check (payload, length, ipv6) - return checksum(self._header, payload, length, ipv6) == C.ntohs(self._header.checksum) + return checksum(self:header(), payload, length, ipv6) == C.ntohs(self:header().checksum) end return icmp diff --git a/src/lib/protocol/icmp/nd/na.lua b/src/lib/protocol/icmp/nd/na.lua index 5fa389b47b..900009d4d0 100644 --- a/src/lib/protocol/icmp/nd/na.lua +++ b/src/lib/protocol/icmp/nd/na.lua @@ -50,34 +50,34 @@ end function na:target (target) if target ~= nil then - ffi.copy(self._header.target, target, 16) + ffi.copy(self:header().target, target, 16) end - return self._header.target + return self:header().target end function na:target_eq (target) - return C.memcmp(target, self._header.target, 16) == 0 + return C.memcmp(target, self:header().target, 16) == 0 end function na:router (r) if r ~= nil then - self._header.router = r + self:header().router = r end - return self._header.router + return self:header().router end function na:solicited (s) if s ~= nil then - self._header.solicited = s + self:header().solicited = s end - return self._header.solicited + return self:header().solicited end function na:override (o) if o ~= nil then - self._header.override = o + self:header().override = o end - return self._header.override + return self:header().override end return na diff --git a/src/lib/protocol/icmp/nd/ns.lua b/src/lib/protocol/icmp/nd/ns.lua index 849e5c0838..4847aedef7 100644 --- a/src/lib/protocol/icmp/nd/ns.lua +++ b/src/lib/protocol/icmp/nd/ns.lua @@ -30,13 +30,13 @@ end function ns:target (target) if target ~= nil then - ffi.copy(self._header.target, target, 16) + ffi.copy(self:header().target, target, 16) end - return self._header.target + return self:header().target end function ns:target_eq (target) - return C.memcmp(target, self._header.target, 16) == 0 + return C.memcmp(target, self:header().target, 16) == 0 end return ns diff --git a/src/lib/protocol/icmp/nd/options/lladdr.lua b/src/lib/protocol/icmp/nd/options/lladdr.lua index b0650ee359..57ca70837a 100644 --- a/src/lib/protocol/icmp/nd/options/lladdr.lua +++ b/src/lib/protocol/icmp/nd/options/lladdr.lua @@ -9,6 +9,8 @@ local lladdr_t = ffi.typeof[[ } ]] +local lladdr_ptr_t = ffi.typeof("$ *", lladdr_t) + -- Class variables lladdr._name = 'll_addr' @@ -16,7 +18,7 @@ lladdr._name = 'll_addr' function lladdr:new_from_mem (mem, size) local o = lladdr:superClass().new(self) assert(size >= ffi.sizeof(lladdr_t)) - o._lladdr = ffi.cast(ffi.typeof("$ *", lladdr_t), mem) + o._lladdr = ffi.cast(lladdr_ptr_t, mem) return o end diff --git a/src/lib/protocol/icmp/nd/options/tlv.lua b/src/lib/protocol/icmp/nd/options/tlv.lua index dfad5a90bf..61eba61dc6 100644 --- a/src/lib/protocol/icmp/nd/options/tlv.lua +++ b/src/lib/protocol/icmp/nd/options/tlv.lua @@ -10,6 +10,7 @@ local tlv_t = ffi.typeof[[ } __attribute__((packed)) ]] +local tlv_ptr_t = ffi.typeof("$ *", tlv_t) tlv._types = { [1] = { name = "src_ll_addr", @@ -28,7 +29,7 @@ function tlv:new_from_mem (mem, size) local o = tlv:superClass().new(self) local tlv_t_size = ffi.sizeof(tlv_t) assert(tlv_t_size <= size) - local tlv = ffi.cast(ffi.typeof("$ *", tlv_t), mem) + local tlv = ffi.cast(tlv_ptr_t, mem) o._tlv = tlv local class = o._types[tlv.type].class if class ~= nil then diff --git a/src/lib/protocol/ipv4.lua b/src/lib/protocol/ipv4.lua index ef76308587..dcc5345be6 100644 --- a/src/lib/protocol/ipv4.lua +++ b/src/lib/protocol/ipv4.lua @@ -52,7 +52,7 @@ ipv4._ulp = { function ipv4:new (config) local o = ipv4:superClass().new(self) - o._header.ihl_v_tos = C.htonl(0x4000) -- v4 + o:header().ihl_v_tos = C.htonl(0x4000) -- v4 o:ihl(o:sizeof() / 4) o:dscp(config.dscp or 0) o:ecn(config.ecn or 0) @@ -89,89 +89,89 @@ end -- Instance methods function ipv4:version (v) - return lib.bitfield(16, self._header, 'ihl_v_tos', 0, 4, v) + return lib.bitfield(16, self:header(), 'ihl_v_tos', 0, 4, v) end function ipv4:ihl (ihl) - return lib.bitfield(16, self._header, 'ihl_v_tos', 4, 4, ihl) + return lib.bitfield(16, self:header(), 'ihl_v_tos', 4, 4, ihl) end function ipv4:dscp (dscp) - return lib.bitfield(16, self._header, 'ihl_v_tos', 8, 6, dscp) + return lib.bitfield(16, self:header(), 'ihl_v_tos', 8, 6, dscp) end function ipv4:ecn (ecn) - return lib.bitfield(16, self._header, 'ihl_v_tos', 14, 2, ecn) + return lib.bitfield(16, self:header(), 'ihl_v_tos', 14, 2, ecn) end function ipv4:total_length (length) if length ~= nil then - self._header.total_length = C.htons(length) + self:header().total_length = C.htons(length) else - return(C.ntohs(self._header.total_length)) + return(C.ntohs(self:header().total_length)) end end function ipv4:id (id) if id ~= nil then - self._header.id = C.htons(id) + self:header().id = C.htons(id) else - return(C.ntohs(self._header.id)) + return(C.ntohs(self:header().id)) end end function ipv4:flags (flags) - return lib.bitfield(16, self._header, 'frag_off', 0, 3, flags) + return lib.bitfield(16, self:header(), 'frag_off', 0, 3, flags) end function ipv4:frag_off (frag_off) - return lib.bitfield(16, self._header, 'frag_off', 3, 13, frag_off) + return lib.bitfield(16, self:header(), 'frag_off', 3, 13, frag_off) end function ipv4:ttl (ttl) if ttl ~= nil then - self._header.ttl = ttl + self:header().ttl = ttl else - return self._header.ttl + return self:header().ttl end end function ipv4:protocol (protocol) if protocol ~= nil then - self._header.protocol = protocol + self:header().protocol = protocol else - return self._header.protocol + return self:header().protocol end end function ipv4:checksum () - local csum = lib.update_csum(self._header, self:sizeof()) - self._header.checksum = C.htons(lib.finish_csum(csum)) - return C.ntohs(self._header.checksum) + local csum = lib.update_csum(self:header(), self:sizeof()) + self:header().checksum = C.htons(lib.finish_csum(csum)) + return C.ntohs(self:header().checksum) end function ipv4:src (ip) if ip ~= nil then - ffi.copy(self._header.src_ip, ip, ipv4_addr_t_size) + ffi.copy(self:header().src_ip, ip, ipv4_addr_t_size) else - return self._header.src_ip + return self:header().src_ip end end function ipv4:src_eq (ip) - return C.memcmp(ip, self._header.src_ip, ipv4_addr_t_size) == 0 + return C.memcmp(ip, self:header().src_ip, ipv4_addr_t_size) == 0 end function ipv4:dst (ip) if ip ~= nil then - ffi.copy(self._header.dst_ip, ip, ipv4_addr_t_size) + ffi.copy(self:header().dst_ip, ip, ipv4_addr_t_size) else - return self._header.dst_ip + return self:header().dst_ip end end function ipv4:dst_eq (ip) - return C.memcmp(ip, self._header.dst_ip, ipv4_addr_t_size) == 0 + return C.memcmp(ip, self:header().dst_ip, ipv4_addr_t_size) == 0 end -- override the default equality method @@ -190,7 +190,7 @@ end -- header if extension headers are present. function ipv4:pseudo_header (ulplen, proto) local ph = ipv4hdr_pseudo_t() - local h = self._header + local h = self:header() ffi.copy(ph, h.src_ip, 2*ipv4_addr_t_size) -- Copy source and destination ph.ulp_length = C.htons(ulplen) ph.ulp_proto = C.htons(proto) diff --git a/src/lib/protocol/ipv6.lua b/src/lib/protocol/ipv6.lua index 54982f9cb3..a2c54b9d80 100644 --- a/src/lib/protocol/ipv6.lua +++ b/src/lib/protocol/ipv6.lua @@ -46,6 +46,9 @@ ipv6._ulp = { function ipv6:new (config) local o = ipv6:superClass().new(self) + if not o._recycled then + o._ph = ipv6hdr_pseudo_t() + end o:version(6) o:traffic_class(config.traffic_class) o:flow_label(config.flow_label) @@ -56,6 +59,14 @@ function ipv6:new (config) return o end +function ipv6:new_from_mem(mem, size) + local o = ipv6:superClass().new_from_mem(self, mem, size) + if not o._recycled then + o._ph = ipv6hdr_pseudo_t() + end + return o +end + -- XXX should probably use inet_pton(3) function ipv6:pton (p) local result = ipv6_addr_t() @@ -85,71 +96,71 @@ end -- Instance methods function ipv6:version (v) - return lib.bitfield(32, self._header, 'v_tc_fl', 0, 4, v) + return lib.bitfield(32, self:header(), 'v_tc_fl', 0, 4, v) end function ipv6:traffic_class (tc) - return lib.bitfield(32, self._header, 'v_tc_fl', 4, 8, tc) + return lib.bitfield(32, self:header(), 'v_tc_fl', 4, 8, tc) end function ipv6:dscp (dscp) - return lib.bitfield(32, self._header, 'v_tc_fl', 4, 6, dscp) + return lib.bitfield(32, self:header(), 'v_tc_fl', 4, 6, dscp) end function ipv6:ecn (ecn) - return lib.bitfield(32, self._header, 'v_tc_fl', 10, 2, ecn) + return lib.bitfield(32, self:header(), 'v_tc_fl', 10, 2, ecn) end function ipv6:flow_label (fl) - return lib.bitfield(32, self._header, 'v_tc_fl', 12, 20, fl) + return lib.bitfield(32, self:header(), 'v_tc_fl', 12, 20, fl) end function ipv6:payload_length (length) if length ~= nil then - self._header.payload_length = C.htons(length) + self:header().payload_length = C.htons(length) else - return(C.ntohs(self._header.payload_length)) + return(C.ntohs(self:header().payload_length)) end end function ipv6:next_header (nh) if nh ~= nil then - self._header.next_header = nh + self:header().next_header = nh else - return(self._header.next_header) + return(self:header().next_header) end end function ipv6:hop_limit (limit) if limit ~= nil then - self._header.hop_limit = limit + self:header().hop_limit = limit else - return(self._header.hop_limit) + return(self:header().hop_limit) end end function ipv6:src (ip) if ip ~= nil then - ffi.copy(self._header.src_ip, ip, 16) + ffi.copy(self:header().src_ip, ip, 16) else - return self._header.src_ip + return self:header().src_ip end end function ipv6:src_eq (ip) - return C.memcmp(ip, self._header.src_ip, 16) == 0 + return C.memcmp(ip, self:header().src_ip, 16) == 0 end function ipv6:dst (ip) if ip ~= nil then - ffi.copy(self._header.dst_ip, ip, 16) + ffi.copy(self:header().dst_ip, ip, 16) else - return self._header.dst_ip + return self:header().dst_ip end end function ipv6:dst_eq (ip) - return C.memcmp(ip, self._header.dst_ip, 16) == 0 + return C.memcmp(ip, self:header().dst_ip, 16) == 0 end -- Return a pseudo header for checksum calculation in a upper-layer @@ -158,8 +169,9 @@ end -- protocol. They differ from the respective values of the ipv6 -- header if extension headers are present. function ipv6:pseudo_header (plen, nh) - local ph = ipv6hdr_pseudo_t() - local h = self._header + local ph = self._ph + ffi.fill(ph, ffi.sizeof(ph)) + local h = self:header() ffi.copy(ph, h.src_ip, 32) -- Copy source and destination ph.ulp_length = C.htons(plen) ph.next_header = nh diff --git a/src/lib/protocol/tcp.lua b/src/lib/protocol/tcp.lua index c29d5c3999..debe3d301b 100644 --- a/src/lib/protocol/tcp.lua +++ b/src/lib/protocol/tcp.lua @@ -34,7 +34,7 @@ function tcp:new (config) o:seq_num(config.seq) o:ack_num(config.ack) o:window_size(config.window_size) - o._header.pad = 0 + o:header().pad = 0 o:offset(config.offset or 0) o:ns(config.ns or 0) o:cwr(config.cwr or 0) @@ -52,7 +52,7 @@ end -- Instance methods function tcp:src_port (port) - local h = self._header + local h = self:header() if port ~= nil then h.src_port = C.htons(port) end @@ -60,7 +60,7 @@ function tcp:src_port (port) end function tcp:dst_port (port) - local h = self._header + local h = self:header() if port ~= nil then h.dst_port = C.htons(port) end @@ -68,7 +68,7 @@ function tcp:dst_port (port) end function tcp:seq_num (seq) - local h = self._header + local h = self:header() if seq ~= nil then h.seq = C.htonl(seq) end @@ -76,7 +76,7 @@ function tcp:seq_num (seq) end function tcp:ack_num (ack) - local h = self._header + local h = self:header() if ack ~= nil then h.ack = C.htonl(ack) end @@ -85,54 +85,54 @@ end function tcp:offset (offset) -- ensure reserved bits are 0 - lib.bitfield(16, self._header, 'off_flags', 4, 3, 0) + lib.bitfield(16, self:header(), 'off_flags', 4, 3, 0) - return lib.bitfield(16, self._header, 'off_flags', 0, 4, offset) + return lib.bitfield(16, self:header(), 'off_flags', 0, 4, offset) end -- set all flags at once function tcp:flags (flags) - return lib.bitfield(16, self._header, 'off_flags', 7, 9, flags) + return lib.bitfield(16, self:header(), 'off_flags', 7, 9, flags) end function tcp:ns (ns) - return lib.bitfield(16, self._header, 'off_flags', 7, 1, ns) + return lib.bitfield(16, self:header(), 'off_flags', 7, 1, ns) end function tcp:cwr (cwr) - return lib.bitfield(16, self._header, 'off_flags', 8, 1, cwr) + return lib.bitfield(16, self:header(), 'off_flags', 8, 1, cwr) end function tcp:ece (ece) - return lib.bitfield(16, self._header, 'off_flags', 9, 1, ece) + return lib.bitfield(16, self:header(), 'off_flags', 9, 1, ece) end function tcp:urg (urg) - return lib.bitfield(16, self._header, 'off_flags', 10, 1, urg) + return lib.bitfield(16, self:header(), 'off_flags', 10, 1, urg) end function tcp:ack (ack) - return lib.bitfield(16, self._header, 'off_flags', 11, 1, ack) + return lib.bitfield(16, self:header(), 'off_flags', 11, 1, ack) end function tcp:psh (psh) - return lib.bitfield(16, self._header, 'off_flags', 12, 1, psh) + return lib.bitfield(16, self:header(), 'off_flags', 12, 1, psh) end function tcp:rst (rst) - return lib.bitfield(16, self._header, 'off_flags', 13, 1, rst) + return lib.bitfield(16, self:header(), 'off_flags', 13, 1, rst) end function tcp:syn (syn) - return lib.bitfield(16, self._header, 'off_flags', 14, 1, syn) + return lib.bitfield(16, self:header(), 'off_flags', 14, 1, syn) end function tcp:fin (fin) - return lib.bitfield(16, self._header, 'off_flags', 15, 1, fin) + return lib.bitfield(16, self:header(), 'off_flags', 15, 1, fin) end function tcp:window_size (window_size) - local h = self._header + local h = self:header() if window_size ~= nil then h.window_size = C.htons(window_size) end @@ -140,7 +140,7 @@ function tcp:window_size (window_size) end function tcp:checksum (payload, length, ip) - local h = self._header + local h = self:header() if payload then local csum = 0 if ip then diff --git a/src/lib/protocol/udp.lua b/src/lib/protocol/udp.lua index 5096e4c275..adcfa91b80 100644 --- a/src/lib/protocol/udp.lua +++ b/src/lib/protocol/udp.lua @@ -27,14 +27,14 @@ function udp:new (config) o:src_por(tconfig.src_port) o:dst_port(config.dst_port) o:length(0) - o._header.checksum = 0 + o:header().checksum = 0 return o end -- Instance methods function udp:src_port (port) - local h = self._header + local h = self:header() if port ~= nil then h.src_port = C.htons(port) end @@ -42,7 +42,7 @@ function udp:src_port (port) end function udp:dst_port (port) - local h = self._header + local h = self:header() if port ~= nil then h.dst_port = C.htons(port) end @@ -50,7 +50,7 @@ function udp:dst_port (port) end function udp:length (len) - local h = self._header + local h = self:header() if len ~= nil then h.len = C.htons(len) end @@ -58,7 +58,7 @@ function udp:length (len) end function udp:checksum (payload, length, ip) - local h = self._header + local h = self:header() if payload then local csum = 0 if ip then From 92a5087ddf1f771e69aa912e1d8985ed5e4dc64b Mon Sep 17 00:00:00 2001 From: Alexander Gall Date: Thu, 26 Jun 2014 11:33:27 +0200 Subject: [PATCH 12/14] Use lib.bitfield in ICMPv6 neighbor advertisement module --- src/lib/protocol/icmp/nd/na.lua | 47 ++++++++------------------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/src/lib/protocol/icmp/nd/na.lua b/src/lib/protocol/icmp/nd/na.lua index 900009d4d0..d13bf1280d 100644 --- a/src/lib/protocol/icmp/nd/na.lua +++ b/src/lib/protocol/icmp/nd/na.lua @@ -1,34 +1,18 @@ module(..., package.seeall) local ffi = require("ffi") local C = ffi.C +local bitfield = require("core.lib").bitfield local nd_header = require("lib.protocol.icmp.nd.header") -local na_t -if ffi.abi("le") then - na_t = ffi.typeof[[ - struct { - uint32_t reserved:5, - override:1, - solicited:1, - router:1, - reserved2:24; - uint8_t target[16]; - } __attribute__((packed)) - ]] -else - na_t = ffi.typeof[[ - struct { - uint32_t router:1, - solicited:1, - override:1, - reserved:29; - uint8_t target[16]; - } __attribute__((packed)) - ]] -end - local na = subClass(nd_header) +local na_t = ffi.typeof[[ + struct { + uint32_t flags; + uint8_t target[16]; + } __attribute__((packed)) +]] + -- Class variables na._name = "neighbor advertisement" na._header_type = na_t @@ -60,24 +44,15 @@ function na:target_eq (target) end function na:router (r) - if r ~= nil then - self:header().router = r - end - return self:header().router + return bitfield(32, self:header(), 'flags', 0, 1, r) end function na:solicited (s) - if s ~= nil then - self:header().solicited = s - end - return self:header().solicited + return bitfield(32, self:header(), 'flags', 1, 1, s) end function na:override (o) - if o ~= nil then - self:header().override = o - end - return self:header().override + return bitfield(32, self:header(), 'flags', 2, 1, o) end return na From 17bf5fc7d9e976ce9dab4cf8ee02b5c612919407 Mon Sep 17 00:00:00 2001 From: Alexander Gall Date: Fri, 27 Jun 2014 11:18:15 +0200 Subject: [PATCH 13/14] Use a better way to deal with variable-size protocol headers A previous commit that reduces garbage in the lib/protocol code broke the handling of variable-sized protocol headers, specifically gre. The gre code and the generic code in lib/protocol/header.lua has bee rewritten to use subclasses in such a case. This makes the code cleaner and allows proper recycling of objects on a per header-type basis. --- src/lib/protocol/gre.lua | 135 +++++++++++------------------- src/lib/protocol/gre_csum.lua | 36 ++++++++ src/lib/protocol/gre_csum_key.lua | 39 +++++++++ src/lib/protocol/gre_key.lua | 35 ++++++++ src/lib/protocol/header.lua | 38 +++++---- 5 files changed, 183 insertions(+), 100 deletions(-) create mode 100644 src/lib/protocol/gre_csum.lua create mode 100644 src/lib/protocol/gre_csum_key.lua create mode 100644 src/lib/protocol/gre_key.lua diff --git a/src/lib/protocol/gre.lua b/src/lib/protocol/gre.lua index b30673789c..d11d1c8e53 100644 --- a/src/lib/protocol/gre.lua +++ b/src/lib/protocol/gre.lua @@ -3,144 +3,111 @@ local ffi = require("ffi") local C = ffi.C local header = require("lib.protocol.header") local lib = require("core.lib") --- +local bitfield, update_csum, finish_csum = lib.bitfield, lib.update_csum, lib.finish_csum -- GRE uses a variable-length header as specified by RFCs 2784 and -- 2890. The actual size is determined by flag bits in the base -- header. This implementation only supports the checksum and key -- extensions. Note that most of the flags specified in the original -- specification of RFC1701 have been deprecated. +-- +-- The gre class implements the base header (without checksum and +-- key). The combinations with checksum and key are handled by the +-- subclasses gre_csum, gre_key and gre_csum_key local gre = subClass(header) --- Four different headers depending on the options used -local gre_types = { base = ffi.typeof[[ - struct { - uint16_t bits; // Flags, version - uint16_t protocol; - }]], - csum = ffi.typeof[[ - struct { - uint16_t bits; // Flags, version - uint16_t protocol; - uint16_t csum; - uint16_t reserved1; - }]], - key = ffi.typeof[[ - struct { - uint16_t bits; // Flags, version - uint16_t protocol; - uint32_t key; - }]], - csum_key = ffi.typeof[[ - struct { - uint16_t bits; // Flags, version - uint16_t protocol; - uint16_t csum; - uint16_t reserved1; - uint32_t key; - }]], - } -local gre_ptr_types = {} -for k, v in pairs(gre_types) do - gre_ptr_types[k] = ffi.typeof("$*", v) -end +local gre_t = ffi.typeof[[ + struct { + uint16_t bits; // Flags, version + uint16_t protocol; + } +]] + +local subclasses = { csum = "lib.protocol.gre_csum", + key = "lib.protocol.gre_key", + csum_key = "lib.protocol.gre_csum_key" } -- Class variables gre._name = "gre" -gre._header_type = gre_types.base -gre._header_ptr_type = gre_ptr_types.base +gre._header_type = gre_t +gre._header_ptr_type = ffi.typeof("$*", gre_t) gre._ulp = { class_map = { [0x6558] = "lib.protocol.ethernet" }, method = 'protocol' } +-- Pre-allocated array for initial parsing in new_from_mem() +local parse_mem = ffi.typeof("$[1]", gre._header_ptr_type)() + -- Class methods function gre:new (config) - local o = gre:superClass().new(self) local type = nil - if config.checksum then - o._checksum = true - type = 'csum' - else - o._checksum = false - end - if config.key ~= nil then - o._key = true - if type then - type = 'csum_key' - else - type = 'key' + if config then + if config.checksum then + type = 'csum' + end + if config.key ~= nil then + if type then + type = 'csum_key' + else + type = 'key' + end end - else - o._key = false end + + local o if type then - o._header_type = gre_types[type] - o._header_ptr_type = gre_ptr_types[type] - o._header[0] = o._header_type() - end - if o._checksum then - lib.bitfield(16, o:header(), 'bits', 0, 1, 1) - end - if o._key then - lib.bitfield(16, o:header(), 'bits', 2, 1, 1) - o:key(config.key) + local subclass = subclasses[type] + o = (package.loaded[subclass] or require(subclass)):new(config) + else + o = gre:superClass().new(self) end o:protocol(config.protocol) return o end function gre:new_from_mem (mem, size) - local o = gre:superClass().new_from_mem(self, mem, size) + parse_mem[0] = ffi.cast(self._header_ptr_type, mem) -- Reserved bits and version MUST be zero. We don't support -- the sequence number option, i.e. the 'S' flag (bit 3) must -- be cleared as well - if lib.bitfield(16, o:header(), 'bits', 3, 13) ~= 0 then - o:free() + if bitfield(16, parse_mem[0], 'bits', 3, 13) ~= 0 then return nil end local type = nil - if lib.bitfield(16, o:header(), 'bits', 0, 1) == 1 then - o._checksum = true + local has_csum, has_key = false, false + if bitfield(16, parse_mem[0], 'bits', 0, 1) == 1 then type = 'csum' - else - o._checksum = false + has_csum = true end - if lib.bitfield(16, o:header(), 'bits', 2, 1) == 1 then - o._key = true + if bitfield(16, parse_mem[0], 'bits', 2, 1) == 1 then if type then type = 'csum_key' else type = 'key' end - else - o._key = false + has_key = true end + local class = self if type then - o._header_type = gre_types[type] - o._header_ptr_type = gre_ptr_types[type] - o._header[0] = ffi.cast(o._header_ptr_type, mem)[0] + local subclass = subclasses[type] + class = package.loaded[subclass] or require(subclass) end + local o = gre:superClass().new_from_mem(class, mem, size) + o._checksum = has_csum + o._key = has_key return o end -- Instance methods -function gre:free () - -- Make sure that this object uses the base header from the gre - -- class when it is being recycled - self._header_type = nil - self._header_ptr_type = nil - gre:superClass().free(self) -end - local function checksum(header, payload, length) local csum_in = header.csum; header.csum = 0; header.reserved1 = 0; - local csum = lib.finish_csum(lib.update_csum(payload, length, - lib.update_csum(header, ffi.sizeof(header), 0))) + local csum = finish_csum(update_csum(payload, length, + update_csum(header, ffi.sizeof(header), 0))) header.csum = csum_in return csum end diff --git a/src/lib/protocol/gre_csum.lua b/src/lib/protocol/gre_csum.lua new file mode 100644 index 0000000000..7b558015a0 --- /dev/null +++ b/src/lib/protocol/gre_csum.lua @@ -0,0 +1,36 @@ +module(..., package.seeall) +local ffi = require("ffi") +local C = ffi.C +local gre = require("lib.protocol.gre") +local lib = require("core.lib") + +-- This is a subclass of gre that includes a checksum over +-- the payload. + +local gre_csum = subClass(gre) + +local gre_csum_t = ffi.typeof[[ + struct { + uint16_t bits; // Flags, version + uint16_t protocol; + uint16_t csum; + uint16_t reserved1; + } +]] + +-- Class variables +gre_csum._name = "gre_csum" +gre_csum._header_type = gre_csum_t +gre_csum._header_ptr_type = ffi.typeof("$*", gre_csum_t) + +-- Class methods + +function gre_csum:new (config) + assert(config and config.checksum) + local o = gre_csum:superClass():superClass().new(self) + lib.bitfield(16, o:header(), 'bits', 0, 1, 1) + self._checksum = true + return o +end + +return gre_csum diff --git a/src/lib/protocol/gre_csum_key.lua b/src/lib/protocol/gre_csum_key.lua new file mode 100644 index 0000000000..8b868f6886 --- /dev/null +++ b/src/lib/protocol/gre_csum_key.lua @@ -0,0 +1,39 @@ +module(..., package.seeall) +local ffi = require("ffi") +local C = ffi.C +local gre = require("lib.protocol.gre") +local lib = require("core.lib") + +-- This subclass of gre includes both, a checksum and key field. + +local gre_csum_key = subClass(gre) + +local gre_csum_key_t = ffi.typeof[[ + struct { + uint16_t bits; // Flags, version + uint16_t protocol; + uint16_t csum; + uint16_t reserved1; + uint32_t key; + } +]] + +-- Class variables +gre_csum_key._name = "gre_csum_key" +gre_csum_key._header_type = gre_csum_key_t +gre_csum_key._header_ptr_type = ffi.typeof("$*", gre_csum_key_t) + +-- Class methods + +function gre_csum_key:new (config) + assert(config and config.checksum and config.key) + local o = gre_csum_key:superClass():superClass().new(self) + lib.bitfield(16, o:header(), 'bits', 0, 1, 1) + lib.bitfield(16, o:header(), 'bits', 2, 1, 1) + self._checksum = true + self._key = true + o:key(config.key) + return o +end + +return gre_csum_key diff --git a/src/lib/protocol/gre_key.lua b/src/lib/protocol/gre_key.lua new file mode 100644 index 0000000000..b1b650468b --- /dev/null +++ b/src/lib/protocol/gre_key.lua @@ -0,0 +1,35 @@ +module(..., package.seeall) +local ffi = require("ffi") +local C = ffi.C +local gre = require("lib.protocol.gre") +local lib = require("core.lib") + +-- This is a subclass of gre that includes a 32-bit key field + +local gre_key = subClass(gre) + +local gre_key_t = ffi.typeof[[ + struct { + uint16_t bits; // Flags, version + uint16_t protocol; + uint32_t key; + } +]] + +-- Class variables +gre_key._name = "gre_key" +gre_key._header_type = gre_key_t +gre_key._header_ptr_type = ffi.typeof("$*", gre_key_t) + +-- Class methods + +function gre_key:new (config) + assert(config and config.key) + local o = gre_key:superClass():superClass().new(self) + lib.bitfield(16, o:header(), 'bits', 2, 1, 1) + self._key = true + o:key(config.key) + return o +end + +return gre_key diff --git a/src/lib/protocol/header.lua b/src/lib/protocol/header.lua index 2436c49920..37be21bea2 100644 --- a/src/lib/protocol/header.lua +++ b/src/lib/protocol/header.lua @@ -62,13 +62,14 @@ -- return o -- end -- --- Protocol headers with a variable format can be handled with a --- little extra work as follows. --- --- The class for such a protocl defines just the alternative that can --- be considered to be the "fundamental" header. It must be --- sufficient for the new_from_mem() method to determine the actual --- header. +-- The generic code here assumes that every protocol has a fixed +-- header of a given type. This is required by the garbage-saving +-- hacks and the object recycling mechanism of the OOP system. +-- Protocols of variable header types must be handled by subclasses, +-- each of which must implement exactly one variant. The base class +-- must implement at least as much as is necessary to determine which +-- subclass needs to be selected. The GRE protocol serves as an +-- example of how this can be implemented. -- -- The standard constructors will initialize the header instance with -- the fundamental type. The protocol class must override both @@ -107,30 +108,35 @@ local header = subClass(nil) -- Class methods -- The standard constructor creates a new ctype object for the header. --- Note: unlike the new_from_mem() method, the new() method creates --- garbage when an object is recycled. This is not trivial to avoid --- for header classes with variably-sized headers, because there is --- currently only a single free list per class. +-- Note that ffi.typeof with a cdecl cannot be compiled (reported as +-- "NYI" by the compiler). Due to the recycling mechanism, it is not +-- expected that this code will ever be called in a hot trace. If +-- this turns out to be a problem, the ctype can be created in +-- advance. +-- +-- Note that the header is initialized to zero in all cases, i.e. the +-- protocol-specific constructors must perform all initialization even +-- if the object is recycled. function header:new () local o = header:superClass().new(self) if not o._recycled then o._header = ffi.typeof("$[1]", o._header_ptr_type)() + o._header_aux = self._header_type() + o._header[0] = ffi.cast(o._header_ptr_type, o._header_aux) + else + ffi.fill(o._header[0], ffi.sizeof(o._header[0][0])) end - o._header_aux = self._header_type() - o._header[0] = ffi.cast(o._header_ptr_type, o._header_aux) return o end -- This alternative constructor creates a protocol header from a chunk -- of memory by "overlaying" a header structure. function header:new_from_mem (mem, size) + assert(ffi.sizeof(self._header_type) <= size) local o = header:superClass().new(self) if not o._recycled then o._header = ffi.typeof("$[1]", o._header_ptr_type)() end - -- Using the class variables here does the right thing even if the - -- instance is recycled - assert(ffi.sizeof(self._header_type) <= size) o._header[0] = ffi.cast(self._header_ptr_type, mem) return o end From f0edb8cec47beb26bd94fa713d05b6154acdedb1 Mon Sep 17 00:00:00 2001 From: Luke Gorrie Date: Wed, 2 Jul 2014 10:27:34 +0200 Subject: [PATCH 14/14] Replace compile-time '-lpcap' with runtime 'ffi.load("pcap",true)' Libpcap will be dynamically found and linked at runtime if/when the lib.pcap.filter module is loaded. --- src/Makefile | 2 +- src/lib/pcap/filter.lua | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 1d2b7dc300..de07861188 100644 --- a/src/Makefile +++ b/src/Makefile @@ -44,7 +44,7 @@ snabb: $(LUAOBJ) $(HOBJ) $(COBJ) $(ASMOBJ) $(E) "LINK $@" $(Q) gcc -Wl,--no-as-needed -Wl,-E -Werror -Wall -o $@ $^ \ ../deps/luajit/src/libluajit.a \ - -lc -ldl -lm -lrt -lpthread -lpcap + -lc -ldl -lm -lrt -lpthread @echo -n "Firmware: " @ln -fs snabb snabbswitch @ls -sh snabb diff --git a/src/lib/pcap/filter.lua b/src/lib/pcap/filter.lua index b0a56fc8ae..7ad201f980 100644 --- a/src/lib/pcap/filter.lua +++ b/src/lib/pcap/filter.lua @@ -3,6 +3,8 @@ local ffi = require("ffi") local C = ffi.C require ("lib.pcap.filter_h") +ffi.load("pcap", true) + local filter = subClass(nil) filter._name = "pcap packet filter"