Skip to content

Instantly share code, notes, and snippets.

@aoleg94
Created April 16, 2019 10:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aoleg94/b68332d7d989b199c4e00a07c0ea90b3 to your computer and use it in GitHub Desktop.
Save aoleg94/b68332d7d989b199c4e00a07c0ea90b3 to your computer and use it in GitHub Desktop.
ntpq in lua
--local socket = require'socket'
-- http://lua-users.org/wiki/SplitJoin
-- explode(separator, string)
local function explode(d,p)
local t, ll
t={}
ll=0
if(#p == 1) then
return {p}
end
while true do
l = string.find(p, d, ll, true) -- find the next d in the string
if l ~= nil then -- if "not not" found then..
table.insert(t, string.sub(p,ll,l-1)) -- Save it in our array.
ll = l + #d -- save just after where we found it for searching next time.
else
table.insert(t, string.sub(p,ll)) -- Save what's left in our array.
break -- Break at end, as it should be, according to the lua manual.
end
end
return t
end
local function ioassert(msg, res, err)
if res then return res end
assert(false, msg .. ': ' .. (err or '<unknown error>'))
end
local function int2str(x, nb, le)
if nb == 1 then return string.char(math.floor(x % 256)) end
local t = {}
for i=1,nb
do
table.insert(t, math.floor(x % 256))
x = x / 256
end
local tt = {}
if le
then
tt = t
else
for i=1,nb do tt[nb-i+1] = t[i] end
end
return string.char(unpack(tt))
end
local function str2int(s, nb, off, le)
off = off or 0
local t = {string.byte(s, off+1, off+nb)}
local tt = {}
if le
then
for i=1,nb do tt[nb-i+1] = t[i] end
else
tt = t
end
local x = 0
for i=1,nb do x = x * 256 + tt[i] end
return x
end
local function bitset(x, off, nbit)
x = x / (2^off)
x = math.floor(x % (2^(nbit or 1)))
return nbit and x or x==1
end
local has_bit, bit = pcall(require, 'bit')
local has_bit32, bit32 = pcall(require, 'bit32')
if has_bit32 and not has_bit then bit = bit32 end
if has_bit
then
function bitset(x, off, nbit)
x = bit.rshift(x, off)
x = bit.band(x, 2^(nbit or 1)-1)
return nbit and x or x==1
end
end
local function parse_peer_status(ast)
local t = {}
t.config = bitset(ast, 15)
t.authenable = bitset(ast, 14)
t.authentic = bitset(ast, 13)
t.reach = bitset(ast, 12)
t.bcast = bitset(ast, 11)
t.selection = bitset(ast, 8, 3)
t.count = bitset(ast, 4, 4)
t.code = bitset(ast, 0, 4)
return t
end
local function parse_system_status(sst)
local t = {}
t.leap = bitset(sst, 14, 2)
t.source = bitset(sst, 8, 6)
t.count = bitset(sst, 4, 4)
t.event = bitset(sst, 0, 4)
return t
end
local M = {}
function M.ntpcomm(skt, options, skt_select)
M.seq = M.seq or 0
M.seq = M.seq + 1
assert(not options.offset or options.offset == 0, 'multipart request is not supported')
assert(options.opcode > 0 and options.opcode < 0x20, 'invalid opcode')
local pkt = {
'\x16',
int2str(options.opcode, 1),
int2str(M.seq, 2),
int2str(options.status or 0, 2),
int2str(options.assoc or 0, 2),
int2str(options.offset or 0, 2),
int2str(options.count or 0, 2)
}
pkt = table.concat(pkt)
assert(#pkt == 12, 'BUG ntp packet length')
if options.data then pkt = pkt .. options.data end
ioassert('failed to send ntp control packet', skt:send(pkt))
local more = true
local alldata = ''
local ret = {}
while more
do
if skt_select then skt_select() end
local pkt = ioassert('failed to receive ntp control packet', skt:receive(4096))
assert(#pkt >= 12, 'incomplete ntp packet')
local flags1 = str2int(pkt, 1, 0)
local flags2 = str2int(pkt, 1, 1)
local seq = str2int(pkt, 2, 2)
local status = str2int(pkt, 2, 4)
local assoc = str2int(pkt, 2, 6)
local offset = str2int(pkt, 2, 8)
local count = str2int(pkt, 2, 10)
local data = string.sub(pkt, 13, 13 + count - 1)
assert(bitset(flags1, 0, 3) == 6, 'packet is not ntp control')
assert(bitset(flags2, 7), 'ntp packet is not a response')
assert(not bitset(flags2, 6), 'ntp packet has error bit set')
assert(bitset(flags2, 0, 5) == options.opcode, 'ntp control packet opcode mismatch')
ret.rawstatus = status
ret.status = parse_system_status(status)
ret.assoc = assoc
alldata = alldata .. data
more = bitset(flags2, 5)
end
ret.rawdata = alldata
return ret
end
function M.status(skt, skt_select)
local ok, t = pcall(M.ntpcomm, skt, { opcode = 1 }, skt_select)
if not ok then return nil, t end
local n = #t.rawdata
t.assocs = {}
for i=1,n/4
do
local aid = str2int(t.rawdata, 2, (i-1)*4 + 0)
local ast = str2int(t.rawdata, 2, (i-1)*4 + 2)
local av = parse_peer_status(ast)
t.assocs[aid] = av
end
return t
end
function M.collectvars(rawdata)
local s = rawdata:gsub(',%s*', ','):gsub('%s*%z?$', ',')
local vars = {}
for k, v in string.gmatch(s, '([^=]+)=(.-),')
do
if v:sub(1,1) == '"' and v:sub(-1,-1) == '"'
then
v = v:sub(2,-2)
end
vars[k] = tonumber(v) or v
end
return vars
end
function M.vars(assoc, skt, skt_select)
local ok, t = pcall(M.ntpcomm, skt, { opcode = 2, assoc = assoc or 0 }, skt_select)
if not ok then return nil, t end
t.vars = M.collectvars(t.rawdata)
return t
end
function M.mrulist(skt, skt_select)
local ok, t = pcall(M.ntpcomm, skt, { opcode = 12 }, skt_select)
if not ok then return nil, t end
local nonce = t.rawdata:gsub('%s*', '') .. ', frags=32'
ok, t = pcall(M.ntpcomm, skt, { opcode = 10, data = nonce, count = #nonce }, skt_select)
if not t then return nil, t end
local vars = M.collectvars(t.rawdata)
t.mrulist = {}
for k,v in pairs(vars)
do
local var, id = k:match'([^%.]+)%.(%d+)'
id = id and tonumber(id)
if var and id
then
t.mrulist[id+1] = t.mrulist[id+1] or {}
t.mrulist[id+1][var] = v
end
end
return t
end
function M.peers(skt, skt_select)
local t, err = M.status(skt, skt_select)
if not t then return nil, err end
for as,lav in pairs(t.assocs)
do
local t = M.vars(as, skt, skt_select)
if t then
lav.vars = M.collectvars(t.rawdata)
end
end
t.peers = t.assocs
return t
end
function M.new_socket(host, port)
if not host then host = '127.0.0.1' end
if not port then port = 123 end
local socket = require'socket'
local skt = ioassert('failed to create udp socket', socket.udp())
ioassert('failed to connect to ' .. host .. ':' .. port, skt:setpeername(host, port))
return skt
end
function M.new_copas_socket(...)
local coxpcall = require'coxpcall'
pcall = coxpcall.pcall
local copas = require'copas'
return copas.wrap(M.new_socket(...))
end
return M
local NTP = require'ntp'
local skt = NTP.new_socket()
local tp = NTP.peers(skt)
local tv = NTP.vars(nil, skt)
local tm = NTP.mrulist(skt)
--print(ta, tv, tm)
local tally = {[0] = ' ', 'x', '.', '-', '+', '#', '*', 'o'}
local event = {
'mobilize',
'demobilize',
'unreachable',
'reachable',
'restart',
'no_reply',
'rate_exceeded',
'access_denied',
'leap_armed',
'sys_peer',
'clock_event',
'bad_auth',
'popcorn'
}
local leap = {
[0] = 'leap_none',
'leap_add_sec',
'leap_del_sec',
'leap_alarm'
}
local source = {
[0] = 'sync_unspec',
'sync_pps',
'sync_lf_radio',
'sync_hf_radio',
'sync_uhf_radio',
'sync_local',
'sync_ntp',
'sync_other',
'sync_wristwatch',
'sync_telephone'
}
local event = {
[0] = 'unspecified',
'freq_not_set',
'freq_set',
'spike_detect',
'freq_mode',
'clock_sync',
'restart',
'panic_stop',
'no_system_peer',
'leap_armed',
'leap_disarmed',
'leap_event',
'clock_step',
'kern',
'TAI',
'stale leapsecond values'
}
print' remote refid st t when poll reach delay offset jitter'
print'=============================================================================='
for as,av in pairs(tp.peers)
do
print(string.format('%s%-15s %-15s %2d %s %4d %4d %3o % 8.3f % 8.3f % 7.3f',
tally[av.selection] or '?',
av.vars.srcadr,
av.vars.refid:find'%.' and av.vars.refid or '.'..av.vars.refid..'.',
av.vars.stratum,
'?',
-1,
2^av.vars.ppoll,
av.vars.reach,
av.vars.delay,
av.vars.offset,
av.vars.jitter
))
end
print(string.format('associd=%d status=%04x %s, %s, %d event, %s,',
tp.assoc, tp.rawstatus,
leap[tp.status.leap],
source[tp.status.source],
tp.status.count,
event[tp.status.event]
))
for k,v in pairs(tv.vars)
do
print(k..'='..v.. ',')
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment