Show
Ignore:
Timestamp:
02/23/12 18:43:25 (2 years ago)
Author:
Theo Schlossnagle <jesus@omniti.com>
git-committer:
Theo Schlossnagle <jesus@omniti.com> 1330022605 -0500
git-parent:

[4e26cbc3c2cf67c7b90e4e4dc7ebe5ad6b181da2]

git-author:
Theo Schlossnagle <jesus@omniti.com> 1330022605 -0500
Message:

preliminary support for ntp control protocol

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • src/modules-lua/noit/module/ntp.lua

    rf60d872 rc4bfda4  
    3131module(..., package.seeall) 
    3232 
     33local band,     bor,     bxor,     bnot,     rshift,     lshift 
     34    = bit.band, bit.bor, bit.bxor, bit.bnot, bit.rshift, bit.lshift 
     35 
    3336function onload(image) 
    3437  image.xml_description([=[ 
     
    3942  <object>noit.module.ntp</object> 
    4043  <moduleconfig /> 
    41   <checkconfig /> 
     44  <checkconfig> 
     45    <parameter name="port" 
     46               required="optional" 
     47               default="^123$" 
     48               allowed="\d+">The port to which we will attempt to speak NTP.</parameter> 
     49    <parameter name="control" 
     50               required="optional" 
     51               default="^false$" 
     52               allowed="^(?:true|on|false|off)$">Use the NTP control protocol to learn about the other end.  If thise ois not true/on, then this check will determine the NTP telemetry of the target relative to the agent's local time.  If it is true/on, then the agent will request the NTP telemetry of the target regarding it's preferred peer.</parameter> 
     53  </checkconfig> 
    4254  <examples> 
    4355    <example> 
     
    8698end 
    8799 
    88 function ntp642timeval(s) 
    89   local cnt, l32, r32 = string.unpack(s, '>II') 
     100function parts2timeval(l32, r32) 
    90101  local sec = l32 - 2208988800 
    91102  local usec = (r32 - 0.5) / 4294.967296 
    92103  return noit.timeval.new(sec, usec) 
     104end 
     105 
     106function ntp642timeval(s) 
     107  local cnt, l32, r32 = string.unpack(s, '>II') 
     108  return parts2timeval(l32, r32) 
    93109end 
    94110 
     
    103119   return l16 + (r16 / 65536) 
    104120end 
     121 
     122local _sequence = 0 
     123function next_sequence() 
     124  _sequence = _sequence + 1 
     125  return _sequence 
     126end 
     127 
     128function 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 
     154end 
     155 
     156function 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 
     214end 
     215 
    105216 
    106217function make_ntp_request(fin) 
     
    154265end 
    155266 
     267function 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() 
     337end 
     338 
    156339function initiate(module, check) 
    157340    local s = noit.socket(check.target_ip, 'udp') 
     
    162345    check.bad() 
    163346 
    164     s:connect(check.target_ip, 123) 
     347    s:connect(check.target_ip, check.config.port or 123) 
    165348    status.responses = 0 
    166349    status.avg_offset = 0 
    167350    status.offset = { } 
    168351 
     352    if check.config.control == "true" or check.config.control == "on" then 
     353        return initiate_control(module, check, s) 
     354    end 
     355 
    169356    for i = 1,cnt do 
    170357        local req = make_ntp_request() 
    171358        s:send(req) 
    172         local rv, s = s:recv(48) 
     359        local rv, buf = s:recv(48) 
    173360        local now = noit.timeval.now() 
    174         local response = decode_ntp_message(s
     361        local response = decode_ntp_message(buf
    175362        local offset = calculate_offset(response, now) 
    176363        if offset ~= nil then