1/* 2** Zabbix 3** Copyright (C) 2001-2021 Zabbix SIA 4** 5** This program is free software; you can redistribute it and/or modify 6** it under the terms of the GNU General Public License as published by 7** the Free Software Foundation; either version 2 of the License, or 8** (at your option) any later version. 9** 10** This program is distributed in the hope that it will be useful, 11** but WITHOUT ANY WARRANTY; without even the implied warranty of 12** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13** GNU General Public License for more details. 14** 15** You should have received a copy of the GNU General Public License 16** along with this program; if not, write to the Free Software 17** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18**/ 19 20package udp 21 22import ( 23 "bytes" 24 "errors" 25 "math" 26 "net" 27 "strconv" 28 "time" 29 30 "zabbix.com/pkg/conf" 31 "zabbix.com/pkg/log" 32 "zabbix.com/pkg/plugin" 33) 34 35const ( 36 errorInvalidFirstParam = "Invalid first parameter." 37 errorInvalidThirdParam = "Invalid third parameter." 38 errorTooManyParams = "Too many parameters." 39 errorUnsupportedMetric = "Unsupported metric." 40) 41 42const ( 43 ntpVersion = 3 44 ntpModeServer = 4 45 ntpTransmitRspStart = 24 46 ntpTransmitRspEnd = 32 47 ntpTransmitReqStart = 40 48 ntpPacketSize = 48 49 ntpEpochOffset = 2208988800.0 50 ntpScale = 4294967296.0 51) 52 53type Options struct { 54 Timeout time.Duration `conf:"optional,range=1:30"` 55 Capacity int `conf:"optional,range=1:100"` 56} 57 58// Plugin - 59type Plugin struct { 60 plugin.Base 61 options Options 62} 63 64var impl Plugin 65 66func (p *Plugin) createRequest(req []byte) { 67 // NTP configure request settings by specifying the first byte as 68 // 00 011 011 (or 0x1B) 69 // | | +-- client mode (3) 70 // | + ----- version (3) 71 // + -------- leap year indicator, 0 no warning 72 req[0] = 0x1B 73 74 transmitTime := time.Now().Unix() + ntpEpochOffset 75 f := float64(transmitTime) / ntpScale 76 77 for i := 0; i < 8; i++ { 78 f *= 256.0 79 k := int64(f) 80 81 if k >= 256 { 82 k = 255 83 } 84 85 req[ntpTransmitReqStart+i] = byte(k) 86 f -= float64(k) 87 } 88} 89 90func (p *Plugin) validateResponse(rsp []byte, ln int, req []byte) int { 91 if ln != ntpPacketSize { 92 log.Debugf("invalid response size: %d", ln) 93 return 0 94 } 95 96 if !bytes.Equal(req[ntpTransmitReqStart:], rsp[ntpTransmitRspStart:ntpTransmitRspEnd]) { 97 log.Debugf("originate timestamp in the response does not match transmit timestamp in the request: 0x%x 0x%x", 98 rsp[ntpTransmitRspStart:ntpTransmitRspEnd], req[40:]) 99 100 return 0 101 } 102 103 version := (rsp[0] >> 3) & 7 104 if version != ntpVersion { 105 log.Debugf("invalid NTP version in the response: %d", version) 106 return 0 107 } 108 109 mode := rsp[0] & 7 110 if mode != ntpModeServer { 111 log.Debugf("invalid mode in the response: %d", mode) 112 return 0 113 } 114 115 if 15 < rsp[1] { 116 log.Debugf("invalid stratum in the response: %d", rsp[1]) 117 return 0 118 } 119 120 var f float64 121 for i := 0; i < 8; i++ { 122 f = 256*f + float64(rsp[40+i]) 123 } 124 125 transmit := f / ntpScale 126 if transmit == 0 { 127 log.Debugf("invalid transmit timestamp in the response: %v", transmit) 128 return 0 129 } 130 131 return 1 132} 133 134func (p *Plugin) udpExpect(service string, address string) (result int) { 135 var conn net.Conn 136 var err error 137 138 if conn, err = net.DialTimeout("udp", address, time.Second*p.options.Timeout); err != nil { 139 log.Debugf("UDP expect network error: cannot connect to [%s]: %s", address, err.Error()) 140 return 141 } 142 defer conn.Close() 143 144 if err = conn.SetDeadline(time.Now().Add(time.Second * p.options.Timeout)); err != nil { 145 return 146 } 147 148 req := make([]byte, ntpPacketSize) 149 p.createRequest(req) 150 151 if _, err = conn.Write(req); err != nil { 152 log.Debugf("UDP expect network error: cannot write to [%s]: %s", address, err.Error()) 153 return 154 } 155 156 var ln int 157 rsp := make([]byte, ntpPacketSize) 158 159 if ln, err = conn.Read(rsp); err != nil { 160 log.Debugf("UDP expect network error: cannot read from [%s]: %s", address, err.Error()) 161 return 162 } 163 164 return p.validateResponse(rsp, ln, req) 165} 166 167func (p *Plugin) exportNetService(params []string) int { 168 var ip, port string 169 service := params[0] 170 171 if len(params) > 1 && params[1] != "" { 172 ip = params[1] 173 } else { 174 ip = "127.0.0.1" 175 } 176 177 if len(params) == 3 && params[2] != "" { 178 port = params[2] 179 } else { 180 port = service 181 } 182 183 return p.udpExpect(service, net.JoinHostPort(ip, port)) 184} 185 186func toFixed(num float64, precision int) float64 { 187 output := math.Pow(10, float64(precision)) 188 return math.Round(num*output) / output 189} 190 191func (p *Plugin) exportNetServicePerf(params []string) float64 { 192 const floatPrecision = 0.0001 193 194 start := time.Now() 195 ret := p.exportNetService(params) 196 197 if ret == 1 { 198 elapsedTime := toFixed(time.Since(start).Seconds(), 6) 199 200 if elapsedTime < floatPrecision { 201 elapsedTime = floatPrecision 202 } 203 return elapsedTime 204 } 205 return 0.0 206} 207 208// Export - 209func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) (result interface{}, err error) { 210 switch key { 211 case "net.udp.service", "net.udp.service.perf": 212 if len(params) > 3 { 213 err = errors.New(errorTooManyParams) 214 return 215 } 216 if len(params) < 1 || (len(params) == 1 && len(params[0]) == 0) { 217 err = errors.New(errorInvalidFirstParam) 218 return 219 } 220 if params[0] != "ntp" { 221 err = errors.New(errorInvalidFirstParam) 222 return 223 } 224 225 if len(params) == 3 && len(params[2]) != 0 { 226 if _, err = strconv.ParseUint(params[2], 10, 16); err != nil { 227 err = errors.New(errorInvalidThirdParam) 228 return 229 } 230 } 231 232 if key == "net.udp.service" { 233 return p.exportNetService(params), nil 234 } else if key == "net.udp.service.perf" { 235 return p.exportNetServicePerf(params), nil 236 } 237 } 238 239 /* SHOULD_NEVER_HAPPEN */ 240 return nil, errors.New(errorUnsupportedMetric) 241} 242 243func (p *Plugin) Configure(global *plugin.GlobalOptions, options interface{}) { 244 if err := conf.Unmarshal(options, &p.options); err != nil { 245 p.Warningf("cannot unmarshal configuration options: %s", err) 246 } 247 if p.options.Timeout == 0 { 248 p.options.Timeout = time.Duration(global.Timeout) 249 } 250} 251 252func (p *Plugin) Validate(options interface{}) error { 253 var o Options 254 return conf.Unmarshal(options, &o) 255} 256 257func init() { 258 plugin.RegisterMetrics(&impl, "UDP", 259 "net.udp.service", "Checks if service is running and responding to UDP requests.", 260 "net.udp.service.perf", "Checks performance of UDP service.") 261} 262