Created
April 16, 2019 10:11
-
-
Save aoleg94/b68332d7d989b199c4e00a07c0ea90b3 to your computer and use it in GitHub Desktop.
ntpq in lua
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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