2025-02-09 08:35:15 +01:00
|
|
|
|
#!/usr/bin/env dnsjit
|
|
|
|
|
-- Author: Petr Špaček (ISC)
|
|
|
|
|
|
|
|
|
|
-- Convert PCAP with IPv[46] & UDP payloads into TCP-stream binary format as
|
|
|
|
|
-- specified by RFC 1035 section "4.2.2. TCP usage". Each packet is preceded by
|
2025-04-21 09:36:50 +02:00
|
|
|
|
-- 2-byte pre‐ambule which specifies length of the following DNS message in
|
|
|
|
|
-- network byte order, immediately followed by raw bytes of the DNS message.
|
2025-02-09 08:35:15 +01:00
|
|
|
|
--
|
|
|
|
|
-- Outputs raw binary to stdout!
|
2025-04-21 09:36:50 +02:00
|
|
|
|
--
|
|
|
|
|
-- This script does not do any filtering or input sanitation.
|
|
|
|
|
-- For filtering capabilities look at dnscap -o dump_format=tcpdns
|
2025-02-09 08:35:15 +01:00
|
|
|
|
|
|
|
|
|
local bit = require("bit")
|
|
|
|
|
local ffi = require("ffi")
|
|
|
|
|
local input = require("dnsjit.input.pcap").new()
|
|
|
|
|
local layer = require("dnsjit.filter.layer").new()
|
|
|
|
|
local object = require("dnsjit.core.objects")
|
2025-04-21 09:36:50 +02:00
|
|
|
|
local log = require("dnsjit.core.log").new("pcap2tcpdns")
|
2025-02-09 08:35:15 +01:00
|
|
|
|
local getopt = require("dnsjit.lib.getopt").new({
|
|
|
|
|
{ "r", "read", "-", "input file to read, use - for stdin", "?" },
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
local tmpbuf = ffi.new("uint8_t[?]", 2)
|
|
|
|
|
local function put_uint16_be(dst, offset, src)
|
|
|
|
|
dst[offset] = bit.rshift(bit.band(src, 0xff00), 8)
|
|
|
|
|
dst[offset + 1] = bit.band(src, 0xff)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
log:enable("all")
|
|
|
|
|
|
|
|
|
|
-- Parse arguments
|
|
|
|
|
local args = {}
|
|
|
|
|
getopt:parse()
|
|
|
|
|
args.read = getopt:val("r")
|
|
|
|
|
|
|
|
|
|
-- Display help
|
|
|
|
|
if getopt:val("help") then
|
|
|
|
|
getopt:usage()
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Set up input
|
|
|
|
|
if args.read ~= "" then
|
|
|
|
|
log:notice("using input PCAP "..args.read)
|
|
|
|
|
if input:open_offline(args.read) ~= 0 then
|
|
|
|
|
log:fatal("failed to open input PCAP "..args.read)
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
getopt:usage()
|
|
|
|
|
log:fatal("input must be specified, use -r")
|
|
|
|
|
end
|
|
|
|
|
layer:producer(input)
|
|
|
|
|
local produce, pctx = layer:produce()
|
|
|
|
|
|
|
|
|
|
-- set up output
|
|
|
|
|
io.stdout:setvbuf("full")
|
|
|
|
|
|
2025-04-21 09:36:50 +02:00
|
|
|
|
local obj, obj_udp, obj_pl
|
|
|
|
|
local npacketsout = 0
|
|
|
|
|
local npacketsskip = 0
|
|
|
|
|
local UDP_ID = object.UDP
|
2025-02-09 08:35:15 +01:00
|
|
|
|
while true do
|
|
|
|
|
obj = produce(pctx)
|
|
|
|
|
if obj == nil then break end
|
|
|
|
|
|
|
|
|
|
obj_pl = obj:cast_to(object.PAYLOAD)
|
2025-04-21 09:36:50 +02:00
|
|
|
|
if obj_pl ~= nil and obj_pl.len <= 65535 and obj_pl:prev().obj_type == UDP_ID then
|
2025-02-09 08:35:15 +01:00
|
|
|
|
-- RFC 1035 framing has just the DNS message size as two bytes (big-endian).
|
2025-04-21 09:36:50 +02:00
|
|
|
|
put_uint16_be(tmpbuf, 0, obj_pl.len)
|
2025-02-09 08:35:15 +01:00
|
|
|
|
io.stdout:write(ffi.string(tmpbuf, 2))
|
|
|
|
|
io.stdout:write(ffi.string(obj_pl.payload, obj_pl.len))
|
2025-04-21 09:36:50 +02:00
|
|
|
|
npacketsout = npacketsout + 1
|
|
|
|
|
else
|
|
|
|
|
npacketsskip = npacketsskip + 1
|
2025-02-09 08:35:15 +01:00
|
|
|
|
end
|
|
|
|
|
end
|
2025-04-21 09:36:50 +02:00
|
|
|
|
log:info(string.format("%d packets copied, %d skipped", npacketsout, npacketsskip))
|