| 121 | |
---|
| 122 | local _sequence = 0 |
---|
| 123 | function next_sequence() |
---|
| 124 | _sequence = _sequence + 1 |
---|
| 125 | return _sequence |
---|
| 126 | end |
---|
| 127 | |
---|
| 128 | function make_ntp_control(req) |
---|
| 129 | req.version = req.version or 4 -- NTP version |
---|
| 130 | req.mode = req.mode or 6 -- control |
---|
| 131 | req.leap = req.leap or 0 |
---|
| 132 | -- contruct |
---|
| 133 | req.li_vn_mode = bor(bor(band(req.mode,0x7), |
---|
| 134 | lshift(band(req.version,0x7),3)), |
---|
| 135 | lshift(band(req.leap,6),0x3)) |
---|
| 136 | req.op = req.op or 0x01 |
---|
| 137 | req.r_m_e_op = band(req.op,0x1f) |
---|
| 138 | req.sequence = req.sequence or next_sequence() |
---|
| 139 | req.status = req.status or 0 |
---|
| 140 | req.associd = req.associd or 0 |
---|
| 141 | req.offset = req.offset or 0 |
---|
| 142 | req.count = req.count or 0 |
---|
| 143 | local qcnt = req.count |
---|
| 144 | req.data = req.data or '' |
---|
| 145 | req.pad = '' |
---|
| 146 | while (qcnt % 8) ~= 0 do |
---|
| 147 | req.pad = req.pad .. '\0' |
---|
| 148 | end |
---|
| 149 | return string.pack('>bbHHHHH', req.li_vn_mode, req.r_m_e_op, req.sequence, |
---|
| 150 | req.status, req.associd, req.offset, req.count) |
---|
| 151 | .. req.data |
---|
| 152 | .. req.pad |
---|
| 153 | , req.sequence |
---|
| 154 | end |
---|
| 155 | |
---|
| 156 | function ntp_control(s, req) |
---|
| 157 | local f = { } |
---|
| 158 | local req_packet = make_ntp_control(req) |
---|
| 159 | s:send(req_packet) |
---|
| 160 | |
---|
| 161 | f.num_frags = 0 |
---|
| 162 | f.offsets = {} |
---|
| 163 | local done = false |
---|
| 164 | repeat |
---|
| 165 | local rv, buf = s:recv(480) -- max packet |
---|
| 166 | local offset, count, cnt |
---|
| 167 | -- need at least a header |
---|
| 168 | if buf:len() < 12 then return "short packet" end |
---|
| 169 | |
---|
| 170 | f.hdr = buf:sub(1,12) |
---|
| 171 | f.buf = buf:sub(13,buf:len()) |
---|
| 172 | cnt, f.li_vn_mode, f.r_m_e_op, f.sequence, |
---|
| 173 | f.status, f.associd, offset, count = string.unpack(f.hdr, '>bbHHHHH') |
---|
| 174 | |
---|
| 175 | f.mode = band(f.li_vn_mode, 0x7) |
---|
| 176 | f.version = band(rshift(f.li_vn_mode, 3), 0x7) |
---|
| 177 | f.leap = band(rshift(f.li_vn_mode, 6), 0x3) |
---|
| 178 | f.op = band(f.r_m_e_op, 0x1f) |
---|
| 179 | f.is_more = band(f.r_m_e_op, 0x20) ~= 0 |
---|
| 180 | f.is_error = band(f.r_m_e_op, 0x40) ~= 0 |
---|
| 181 | f.is_response = band(f.r_m_e_op, 0x80) ~= 0 |
---|
| 182 | |
---|
| 183 | -- validate |
---|
| 184 | if f.version > 4 or f.version < 1 then return "bad version" end |
---|
| 185 | if f.mode ~= 6 then return "not a control packet" end |
---|
| 186 | if not f.is_response then return "not a response packet" end |
---|
| 187 | if req.sequence ~= f.sequence then return "sequence mismatch" end |
---|
| 188 | if req.op ~= f.op then return "opcode mismatch " .. req.op .. " != " .. f.op end |
---|
| 189 | if f.is_error then |
---|
| 190 | return "error: " |
---|
| 191 | .. bit.tohex(band(rshift(f.status, 8), 0xff), 2) |
---|
| 192 | end |
---|
| 193 | local expect = band(band(12 + count + 3, bnot(3)),0xffff) |
---|
| 194 | -- must be aligned on a word boundary |
---|
| 195 | if band(buf:len(), 3) ~= 0 then return "bad padding" end |
---|
| 196 | if expect > buf:len() then |
---|
| 197 | return "bad payload size " .. expect .. " vs. " .. buf:len() |
---|
| 198 | end |
---|
| 199 | if expect < buf:len() then |
---|
| 200 | -- auth |
---|
| 201 | return "auth unsupported " .. expect .. " vs. " .. buf:len() |
---|
| 202 | end |
---|
| 203 | if f.num_frags > 23 then return "too many fragments" end |
---|
| 204 | if count < f.buf:len() then |
---|
| 205 | f.buf = f.buf:sub(1,count) |
---|
| 206 | end |
---|
| 207 | f.offsets[offset] = f.buf |
---|
| 208 | done = not f.is_more |
---|
| 209 | until done |
---|
| 210 | |
---|
| 211 | f.data = '' |
---|
| 212 | for i, buf in pairs(f.offsets) do f.data = f.data .. buf end |
---|
| 213 | return nil, f |
---|
| 214 | end |
---|
| 215 | |
---|
| 267 | function initiate_control(module, check, s) |
---|
| 268 | local err, result = ntp_control(s, {}) |
---|
| 269 | local associations = {} |
---|
| 270 | if err ~= nil then |
---|
| 271 | check.status(err) |
---|
| 272 | return |
---|
| 273 | end |
---|
| 274 | local i = 0 |
---|
| 275 | local len, numassoc = result.data:len(), result.data:len() / 4; |
---|
| 276 | local use_id = 0 |
---|
| 277 | while len > 0 do |
---|
| 278 | local cnt, associd, status = string.unpack(result.data:sub(1+4*i, 4+4*i), '>HH') |
---|
| 279 | i = i + 1 |
---|
| 280 | len = len - 4; |
---|
| 281 | associations[i] = { } |
---|
| 282 | associations[i].associd = associd |
---|
| 283 | associations[i].status = status |
---|
| 284 | if result.version > 1 then |
---|
| 285 | associations[i].flash = band(rshift(status,8),0x7) |
---|
| 286 | associations[i].prefer = band(associations[i].flash,0x2) ~= 0 |
---|
| 287 | associations[i].burst = band(associations[i].flash,0x4) ~= 0 |
---|
| 288 | associations[i].volley = band(associations[i].flash,0x1) ~= 0 |
---|
| 289 | else |
---|
| 290 | associations[i].flash = band(rshift(status,8),0x3) |
---|
| 291 | associations[i].prefer = band(associations[i].flash,0x1) ~= 0 |
---|
| 292 | associations[i].burst = band(associations[i].flash,0x2) ~= 0 |
---|
| 293 | associations[i].volley = false |
---|
| 294 | end |
---|
| 295 | if(associations[i].prefer) then use_id = i end |
---|
| 296 | end |
---|
| 297 | if(use_id < 1) then use_id = 1 end |
---|
| 298 | |
---|
| 299 | err, result = ntp_control(s, { associd = associations[use_id].associd }) |
---|
| 300 | if err ~= nil then |
---|
| 301 | check.status(err) |
---|
| 302 | return |
---|
| 303 | end |
---|
| 304 | local vars = {} |
---|
| 305 | for k, v in string.gmatch(result.data, "%s*([^,]+)=([^,]+)%s*,%s*") do |
---|
| 306 | vars[k] = v; |
---|
| 307 | noit.log("debug", "ntp: %s = %s\n", k, v) |
---|
| 308 | end |
---|
| 309 | check.metric_string('clock_name', vars.srcadr) |
---|
| 310 | check.metric_int32('stratum', tonumber(vars.stratum)) |
---|
| 311 | |
---|
| 312 | -- parse the rec and the reftime |
---|
| 313 | local rec_l, rec_h = vars.rec:match('^0x([%da-fA-F]+)%.([%da-fA-F]+)$') |
---|
| 314 | rec_l, rec_h = tonumber("0x"..rec_l), tonumber("0x"..rec_h) |
---|
| 315 | local rec = parts2timeval(rec_l, rec_h) |
---|
| 316 | |
---|
| 317 | local reftime_l, reftime_h = vars.reftime:match('^0x([%da-fA-F]+)%.([%da-fA-F]+)$') |
---|
| 318 | reftime_l, reftime_h = tonumber("0x"..reftime_l), tonumber("0x"..reftime_h) |
---|
| 319 | local reftime = parts2timeval(reftime_l, reftime_h) |
---|
| 320 | |
---|
| 321 | local when = nil |
---|
| 322 | if rec.sec ~= 0 then when = noit.timeval.seconds(noit.timeval.now() - rec) |
---|
| 323 | elseif reftime.sec ~= 0 then when = noit.timeval.seconds(noit.timeval.now() - reftime) |
---|
| 324 | end |
---|
| 325 | check.metric_double('when', when) |
---|
| 326 | local poll = math.pow(2, math.max(math.min(vars.ppoll or 17, vars.hpoll or 17), 3)) |
---|
| 327 | check.metric_uint32('poll', poll) |
---|
| 328 | check.metric_double('delay', tonumber(vars.delay)) |
---|
| 329 | check.metric_double('offset', tonumber(vars.offset)) |
---|
| 330 | check.metric_double('jitter', tonumber(vars.jitter)) |
---|
| 331 | check.metric_double('dispersion', tonumber(vars.dispersion)) |
---|
| 332 | check.metric_double('xleave', tonumber(vars.xleave)) |
---|
| 333 | check.metric_int32('peers', numassoc) |
---|
| 334 | check.status("ntp successful") |
---|
| 335 | check.available() |
---|
| 336 | check.good() |
---|
| 337 | end |
---|
| 338 | |
---|