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..de07861188 100644 --- a/src/Makefile +++ b/src/Makefile @@ -42,7 +42,7 @@ 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 @echo -n "Firmware: " diff --git a/src/apps/ipv6/ns_responder.lua b/src/apps/ipv6/ns_responder.lua index aa1f5a93f4..a708f3688e 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") @@ -13,53 +14,69 @@ 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" -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_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() @@ -88,6 +105,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-selftest-customer.cap.expect b/src/apps/vpn/vpws-selftest-customer.cap.expect new file mode 100644 index 0000000000..62f366c82a Binary files /dev/null and b/src/apps/vpn/vpws-selftest-customer.cap.expect differ diff --git a/src/apps/vpn/vpws-selftest-customer.cap.input b/src/apps/vpn/vpws-selftest-customer.cap.input new file mode 100644 index 0000000000..3cf4908d03 Binary files /dev/null and b/src/apps/vpn/vpws-selftest-customer.cap.input differ diff --git a/src/apps/vpn/vpws-selftest-uplink.cap.expect b/src/apps/vpn/vpws-selftest-uplink.cap.expect new file mode 100644 index 0000000000..d48cba98c2 Binary files /dev/null and b/src/apps/vpn/vpws-selftest-uplink.cap.expect differ 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 0000000000..f2827001e7 Binary files /dev/null and b/src/apps/vpn/vpws-selftest-uplink.cap.input differ diff --git a/src/apps/vpn/vpws.lua b/src/apps/vpn/vpws.lua index d74e8cbbe3..608be5970c 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") @@ -16,31 +17,29 @@ 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 pcap = require("apps.pcap.pcap") 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._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 }) + gre = gre:new({ protocol = 0x6558, checksum = config.checksum, key = config.label }) } - self._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 } } -end - -function vpws:name() - return self.config.name + -- 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)) + o._filter = filter + return o end function vpws:push() @@ -57,7 +56,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 @@ -66,10 +65,35 @@ 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) + 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) @@ -77,12 +101,49 @@ function vpws:push() end end if p then link.transmit(l_out, p) end + datagram:free() end end end -function vpws.selftest() - print("vpws selftest not implemented") +function selftest() + local local_mac = ethernet:pton("90:e2:ba:62:86:e5") + local remote_mac = ethernet:pton("28:94:0f:fd:49:40") + local local_ip = ipv6:pton("2001:620:0:C101:0:0:0:2") + local local_vpn_ip = ipv6:pton("2001:620:0:C000:0:0:0:FC") + local remote_vpn_ip = ipv6:pton("2001:620:0:C000:0:0:0:FE") + local c = config.new() + + config.app(c, "from_uplink", pcap.PcapReader, "apps/vpn/vpws-selftest-uplink.cap.input") + config.app(c, "from_customer", pcap.PcapReader, "apps/vpn/vpws-selftest-customer.cap.input") + config.app(c, "to_customer", pcap.PcapWriter, "apps/vpn/vpws-selftest-customer.cap.output") + config.app(c, "to_uplink", pcap.PcapWriter, "apps/vpn/vpws-selftest-uplink.cap.output") + config.app(c, "vpntp", vpws, { name = "vpntp1", + checksum = true, + label = 0x12345678, + local_vpn_ip = local_vpn_ip, + remote_vpn_ip = remote_vpn_ip, + local_ip = local_ip, + local_mac = local_mac, + remote_mac = remote_mac }) + config.link(c, "from_uplink.output -> 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 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 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 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 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..7ad201f980 --- /dev/null +++ b/src/lib/pcap/filter.lua @@ -0,0 +1,35 @@ +module(..., package.seeall) +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" + +-- 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 diff --git a/src/lib/protocol/datagram.lua b/src/lib/protocol/datagram.lua index 4a3b6c39e6..9f5ea139e8 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,31 @@ 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 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() + 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 if p then packet.coalesce(p) - self._packet = p + o._packet[0] = p else - self._packet = packet.allocate() + o._packet[0] = packet.allocate() local b = buffer.allocate() - packet.add_iovec(self._packet, b, 0) + packet.add_iovec(o._packet[0], b, 0) end - self._parse = { stack = {}, - ulp = class, - iovec = 0, - offset = 0 } + return o end -- Instance methods @@ -80,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 @@ -92,55 +105,89 @@ 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 --- 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 iovec = self._packet.iovecs[parse.iovec] + local class = class or parse.ulp + local iovec = self._packet[0].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 - table.insert(parse.stack, proto) - 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 @@ -151,8 +198,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 +210,48 @@ 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 n = n or 1 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 + 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 + proto = parse.stack[i] + local sizeof = proto:sizeof() + proto:free() + iovec.offset = iovec.offset + sizeof + iovec.length = iovec.length - sizeof + self._packet[0].length = self._packet[0].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 - return proto + 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[0].iovecs[self._parse.iovec] + iovec.offset = iovec.offset + length + iovec.length = iovec.length - length + self._packet[0].length = self._packet[0].length - length + self._parse.ulp = ulp end function datagram:stack () @@ -183,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 @@ -191,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), @@ -199,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 4d9e8fd8dd..72e03c12ec 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 @@ -62,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 @@ -71,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 @@ -84,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 64608ac0d1..d11d1c8e53 100644 --- a/src/lib/protocol/gre.lua +++ b/src/lib/protocol/gre.lua @@ -1,129 +1,157 @@ +module(..., package.seeall) 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_template = [[ +local gre = subClass(header) + +local gre_t = ffi.typeof[[ 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) +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[0] -gre._header_ptr_type = gre_ptr_types[0] +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:_init_new (config) - local opt_size = 0 - if config.checksum then - opt_size = opt_size + 4 - self._checksum = true +function gre:new (config) + local type = nil + 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 end - if config.key ~= nil then - self._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) - end - if self._key_offset ~= nil then - lib.bitfield(16, self._header, 'bits', 2, 1, 1) - self:key(config.key) + + local o + if type then + local subclass = subclasses[type] + o = (package.loaded[subclass] or require(subclass)):new(config) + else + o = gre:superClass().new(self) 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] - -- Reserved bits and version MUST be zero - if lib.bitfield(16, header, 'bits', 4, 12) ~= 0 then - self = nil - return +function gre:new_from_mem (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 bitfield(16, parse_mem[0], 'bits', 3, 13) ~= 0 then + return nil end - self._header = header - local opt_size = 0 - if self:use_checksum() then - opt_size = opt_size + 4 - self._checksum = true + local type = nil + local has_csum, has_key = false, false + if bitfield(16, parse_mem[0], 'bits', 0, 1) == 1 then + type = 'csum' + has_csum = true end - if self:use_key() then - self._key_offset = opt_size - opt_size = opt_size + 4 + if bitfield(16, parse_mem[0], 'bits', 2, 1) == 1 then + if type then + type = 'csum_key' + else + type = 'key' + end + has_key = true 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] + local class = self + if type then + 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 +local function checksum(header, payload, length) + local csum_in = header.csum; + header.csum = 0; + header.reserved1 = 0; + local csum = finish_csum(update_csum(payload, length, + 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) - assert(self._key_offset) - 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 not self._key then + return nil + end 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) + 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/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 908ceccde7..37be21bea2 100644 --- a/src/lib/protocol/header.lua +++ b/src/lib/protocol/header.lua @@ -51,55 +51,117 @@ -- 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. --- --- 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 +-- Header-dependent initializations can be handled by overriding the +-- standard constructor, e.g. +-- +-- 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. +-- 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 +-- 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. +-- +-- 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") -local header = subClass(nil, 'new_from_mem') +local header = subClass(nil) -function header:_init_new_from_mem (mem, size) - assert(ffi.sizeof(self._header_type) <= size) - self._header = ffi.cast(self._header_ptr_type, mem)[0] +-- Class methods + +-- The standard constructor creates a new ctype object for the header. +-- 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 + return o end -function header:header () - return self._header +-- 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 + o._header[0] = ffi.cast(self._header_ptr_type, mem) + return o end -function header:name () - return self._name +-- Instance methods + +function header:header () + return self._header[0][0] 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 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. @@ -108,7 +170,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 3db979ad23..2218809ea7 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,45 +29,55 @@ 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 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 -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 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..d13bf1280d 100644 --- a/src/lib/protocol/icmp/nd/na.lua +++ b/src/lib/protocol/icmp/nd/na.lua @@ -1,33 +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]; - } - ]] -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 @@ -36,46 +21,38 @@ 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 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 - 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 diff --git a/src/lib/protocol/icmp/nd/ns.lua b/src/lib/protocol/icmp/nd/ns.lua index d7b31a5381..4847aedef7 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,22 +20,23 @@ 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 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 58c2a1057b..57ca70837a 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 { @@ -8,13 +9,17 @@ local lladdr_t = ffi.typeof[[ } ]] +local lladdr_ptr_t = ffi.typeof("$ *", lladdr_t) + -- Class variables 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(lladdr_ptr_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..61eba61dc6 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 { @@ -9,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", @@ -23,22 +25,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 + local tlv = ffi.cast(tlv_ptr_t, mem) + 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..dcc5345be6 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) @@ -88,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 @@ -189,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 466ee628d4..a2c54b9d80 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") @@ -8,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)) ]] @@ -43,16 +44,27 @@ 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) + 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) + o:next_header(config.next_header) + o:hop_limit(config.hop_limit) + o:src(config.src) + o:dst(config.dst) + 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) @@ -74,8 +86,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 @@ -83,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 @@ -156,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 8f0e0083b5..debe3d301b 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,32 +27,32 @@ 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 function tcp:src_port (port) - local h = self._header + local h = self:header() if port ~= nil then h.src_port = C.htons(port) end @@ -59,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 @@ -67,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 @@ -75,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 @@ -84,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 @@ -139,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 d454d9c34a..adcfa91b80 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,19 +22,19 @@ 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 function udp:src_port (port) - local h = self._header + local h = self:header() if port ~= nil then h.src_port = C.htons(port) end @@ -41,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 @@ -49,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 @@ -57,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